Package Gnumed :: Package exporters :: Module gmPatientExporter
[frames] | no frames]

Source Code for Module Gnumed.exporters.gmPatientExporter

   1  """GNUmed simple ASCII EMR export tool. 
   2   
   3  TODO: 
   4  - GUI mode: 
   5    - post-0.1 ! 
   6    - allow user to select patient 
   7    - allow user to pick episodes/encounters/etc from list 
   8  - output modes: 
   9    - HTML - post-0.1 ! 
  10  """ 
  11  #============================================================ 
  12  # $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/exporters/gmPatientExporter.py,v $ 
  13  # $Id: gmPatientExporter.py,v 1.138 2009-09-08 17:14:55 ncq Exp $ 
  14  __version__ = "$Revision: 1.138 $" 
  15  __author__ = "Carlos Moro" 
  16  __license__ = 'GPL' 
  17   
  18  import os.path, sys, types, time, codecs, datetime as pyDT, logging, shutil 
  19   
  20   
  21  import mx.DateTime.Parser as mxParser 
  22  import mx.DateTime as mxDT 
  23   
  24   
  25  if __name__ == '__main__': 
  26          sys.path.insert(0, '../../') 
  27  from Gnumed.pycommon import gmI18N, gmExceptions, gmNull, gmPG2, gmTools 
  28  from Gnumed.business import gmClinicalRecord, gmPerson, gmAllergy, gmDemographicRecord, gmClinNarrative, gmPersonSearch 
  29   
  30   
  31  _log = logging.getLogger('gm.export') 
  32  _log.info(__version__) 
  33  #============================================================ 
34 -class cEmrExport:
35 36 #--------------------------------------------------------
37 - def __init__(self, constraints = None, fileout = None, patient = None):
38 """ 39 Constructs a new instance of exporter 40 41 constraints - Exporter constraints for filtering clinical items 42 fileout - File-like object as target for dumping operations 43 """ 44 if constraints is None: 45 # default constraints to None for complete emr dump 46 self.__constraints = { 47 'since': None, 48 'until': None, 49 'encounters': None, 50 'episodes': None, 51 'issues': None 52 } 53 else: 54 self.__constraints = constraints 55 self.__target = fileout 56 self.__patient = patient 57 self.lab_new_encounter = True 58 self.__filtered_items = []
59 #--------------------------------------------------------
60 - def set_constraints(self, constraints = None):
61 """Sets exporter constraints. 62 63 constraints - Exporter constraints for filtering clinical items 64 """ 65 if constraints is None: 66 # default constraints to None for complete emr dump 67 self.__constraints = { 68 'since': None, 69 'until': None, 70 'encounters': None, 71 'episodes': None, 72 'issues': None 73 } 74 else: 75 self.__constraints = constraints 76 return True
77 #--------------------------------------------------------
78 - def get_constraints(self):
79 """ 80 Retrieve exporter constraints 81 """ 82 return self.__constraints
83 #--------------------------------------------------------
84 - def set_patient(self, patient=None):
85 """ 86 Sets exporter patient 87 88 patient - Patient whose data are to be dumped 89 """ 90 if patient is None: 91 _log.error("can't set None patient for exporter") 92 return 93 self.__patient = patient
94 #--------------------------------------------------------
95 - def set_output_file(self, target=None):
96 """ 97 Sets exporter output file 98 99 @param file_name - The file to dump the EMR to 100 @type file_name - FileType 101 """ 102 self.__target = target
103 #--------------------------------------------------------
104 - def get_patient(self):
105 """ 106 Retrieves patient whose data are to be dumped 107 """ 108 return self.__patient
109 #--------------------------------------------------------
110 - def cleanup(self):
111 """ 112 Exporter class cleanup code 113 """ 114 pass
115 #--------------------------------------------------------
116 - def __dump_vacc_table(self, vacc_regimes):
117 """ 118 Retrieves string containg ASCII vaccination table 119 """ 120 emr = self.__patient.get_emr() 121 # patient dob 122 patient_dob = self.__patient['dob'] 123 date_length = len(patient_dob.strftime('%x')) + 2 124 125 # dictionary of pairs indication : scheduled vaccination 126 vaccinations4regimes = {} 127 for a_vacc_regime in vacc_regimes: 128 indication = a_vacc_regime['indication'] 129 vaccinations4regimes[indication] = emr.get_scheduled_vaccinations(indications=[indication]) 130 # vaccination regimes count 131 chart_columns = len(vacc_regimes) 132 # foot headers 133 foot_headers = ['last booster', 'next booster'] 134 # string for both: ending of vaccinations; non boosters needed 135 ending_str = '=' 136 137 # chart row count, columns width and vaccination dictionary of pairs indication : given shot 138 column_widths = [] 139 chart_rows = -1 140 vaccinations = {} 141 temp = -1 142 for foot_header in foot_headers: # first column width 143 if len(foot_header) > temp: 144 temp = len(foot_header) 145 column_widths.append(temp) 146 for a_vacc_regime in vacc_regimes: 147 if a_vacc_regime['shots'] > chart_rows: # max_seq -> row count 148 chart_rows = a_vacc_regime['shots'] 149 if (len(a_vacc_regime['l10n_indication'])) > date_length: # l10n indication -> column width 150 column_widths.append(len(a_vacc_regime['l10n_indication'])) 151 else: 152 column_widths.append(date_length) # date -> column width at least 153 vaccinations[a_vacc_regime['indication']] = emr.get_vaccinations(indications=[a_vacc_regime['indication']]) # given shots 4 indication 154 155 # patient dob in top of vaccination chart 156 txt = '\nDOB: %s' %(patient_dob.strftime('%x')) + '\n' 157 158 # vacc chart table headers 159 # top ---- header line 160 for column_width in column_widths: 161 txt += column_width * '-' + '-' 162 txt += '\n' 163 # indication names header line 164 txt += column_widths[0] * ' ' + '|' 165 col_index = 1 166 for a_vacc_regime in vacc_regimes: 167 txt += a_vacc_regime['l10n_indication'] + (column_widths[col_index] - len(a_vacc_regime['l10n_indication'])) * ' ' + '|' 168 col_index += 1 169 txt += '\n' 170 # bottom ---- header line 171 for column_width in column_widths: 172 txt += column_width * '-' + '-' 173 txt += '\n' 174 175 # vacc chart data 176 due_date = None 177 # previously displayed date list 178 prev_displayed_date = [patient_dob] 179 for a_regime in vacc_regimes: 180 prev_displayed_date.append(patient_dob) # initialice with patient dob (useful for due first shot date calculation) 181 # iterate data rows 182 for row_index in range(0, chart_rows): 183 row_header = '#%s' %(row_index+1) 184 txt += row_header + (column_widths[0] - len(row_header)) * ' ' + '|' 185 186 for col_index in range(1, chart_columns+1): 187 indication =vacc_regimes[col_index-1]['indication'] 188 seq_no = vacc_regimes[col_index-1]['shots'] 189 if row_index == seq_no: # had just ended scheduled vaccinations 190 txt += ending_str * column_widths[col_index] + '|' 191 elif row_index < seq_no: # vaccination scheduled 192 try: 193 vacc_date = vaccinations[indication][row_index]['date'] # vaccination given 194 vacc_date_str = vacc_date.strftime('%x') 195 txt += vacc_date_str + (column_widths[col_index] - len(vacc_date_str)) * ' ' + '|' 196 prev_displayed_date[col_index] = vacc_date 197 except: 198 if row_index == 0: # due first shot 199 due_date = prev_displayed_date[col_index] + vaccinations4regimes[indication][row_index]['age_due_min'] # FIXME 'age_due_min' not properly retrieved 200 else: # due any other than first shot 201 due_date = prev_displayed_date[col_index] + vaccinations4regimes[indication][row_index]['min_interval'] 202 txt += '('+ due_date.strftime('%Y-%m-%d') + ')' + (column_widths[col_index] - date_length) * ' ' + '|' 203 prev_displayed_date[col_index] = due_date 204 else: # not scheduled vaccination at that position 205 txt += column_widths[col_index] * ' ' + '|' 206 txt += '\n' # end of scheduled vaccination dates display 207 for column_width in column_widths: # ------ separator line 208 txt += column_width * '-' + '-' 209 txt += '\n' 210 211 # scheduled vaccination boosters (date retrieving) 212 all_vreg_boosters = [] 213 for a_vacc_regime in vacc_regimes: 214 vaccs4indication = vaccinations[a_vacc_regime['indication']] # iterate over vaccinations by indication 215 given_boosters = [] # will contain given boosters for current indication 216 for a_vacc in vaccs4indication: 217 try: 218 if a_vacc['is_booster']: 219 given_boosters.append(a_vacc) 220 except: 221 # not a booster 222 pass 223 if len(given_boosters) > 0: 224 all_vreg_boosters.append(given_boosters[len(given_boosters)-1]) # last of given boosters 225 else: 226 all_vreg_boosters.append(None) 227 228 # next booster in schedule 229 all_next_boosters = [] 230 for a_booster in all_vreg_boosters: 231 all_next_boosters.append(None) 232 # scheduled vaccination boosters (displaying string) 233 cont = 0 234 for a_vacc_regime in vacc_regimes: 235 vaccs = vaccinations4regimes[a_vacc_regime['indication']] 236 if vaccs[len(vaccs)-1]['is_booster'] == False: # booster is not scheduled/needed 237 all_vreg_boosters[cont] = ending_str * column_widths[cont+1] 238 all_next_boosters[cont] = ending_str * column_widths[cont+1] 239 else: 240 indication = vacc_regimes[cont]['indication'] 241 if len(vaccinations[indication]) > vacc_regimes[cont]['shots']: # boosters given 242 all_vreg_boosters[cont] = vaccinations[indication][len(vaccinations[indication])-1]['date'].strftime('%Y-%m-%d') # show last given booster date 243 scheduled_booster = vaccinations4regimes[indication][len(vaccinations4regimes[indication])-1] 244 booster_date = vaccinations[indication][len(vaccinations[indication])-1]['date'] + scheduled_booster['min_interval'] 245 if booster_date < mxDT.today(): 246 all_next_boosters[cont] = '<(' + booster_date.strftime('%Y-%m-%d') + ')>' # next booster is due 247 else: 248 all_next_boosters[cont] = booster_date.strftime('%Y-%m-%d') 249 elif len(vaccinations[indication]) == vacc_regimes[cont]['shots']: # just finished vaccinations, begin boosters 250 all_vreg_boosters[cont] = column_widths[cont+1] * ' ' 251 scheduled_booster = vaccinations4regimes[indication][len(vaccinations4regimes[indication])-1] 252 booster_date = vaccinations[indication][len(vaccinations[indication])-1]['date'] + scheduled_booster['min_interval'] 253 if booster_date < mxDT.today(): 254 all_next_boosters[cont] = '<(' + booster_date.strftime('%Y-%m-%d') + ')>' # next booster is due 255 else: 256 all_next_boosters[cont] = booster_date.strftime('%Y-%m-%d') 257 else: 258 all_vreg_boosters[cont] = column_widths[cont+1] * ' ' # unfinished schedule 259 all_next_boosters[cont] = column_widths[cont+1] * ' ' 260 cont += 1 261 262 # given boosters 263 foot_header = foot_headers[0] 264 col_index = 0 265 txt += foot_header + (column_widths[0] - len(foot_header)) * ' ' + '|' 266 col_index += 1 267 for a_vacc_regime in vacc_regimes: 268 txt += str(all_vreg_boosters[col_index-1]) + (column_widths[col_index] - len(str(all_vreg_boosters[col_index-1]))) * ' ' + '|' 269 col_index += 1 270 txt += '\n' 271 for column_width in column_widths: 272 txt += column_width * '-' + '-' 273 txt += '\n' 274 275 # next booster 276 foot_header = foot_headers[1] 277 col_index = 0 278 txt += foot_header + (column_widths[0] - len(foot_header)) * ' ' + '|' 279 col_index += 1 280 for a_vacc_regime in vacc_regimes: 281 txt += str(all_next_boosters[col_index-1]) + (column_widths[col_index] - len(str(all_next_boosters[col_index-1]))) * ' ' + '|' 282 col_index += 1 283 txt += '\n' 284 for column_width in column_widths: 285 txt += column_width * '-' + '-' 286 txt += '\n' 287 288 self.__target.write(txt)
289 #--------------------------------------------------------
290 - def get_vacc_table(self):
291 """ 292 Iterate over patient scheduled regimes preparing vacc tables dump 293 """ 294 295 emr = self.__patient.get_emr() 296 297 # vaccination regimes 298 all_vacc_regimes = emr.get_scheduled_vaccination_regimes() 299 # Configurable: vacc regimes per displayed table 300 # FIXME: option, post 0.1 ? 301 max_regs_per_table = 4 302 303 # Iterate over patient scheduled regimes dumping in tables of 304 # max_regs_per_table regimes per table 305 reg_count = 0 306 vacc_regimes = [] 307 for total_reg_count in range(0,len(all_vacc_regimes)): 308 if reg_count%max_regs_per_table == 0: 309 if len(vacc_regimes) > 0: 310 self.__dump_vacc_table(vacc_regimes) 311 vacc_regimes = [] 312 reg_count = 0 313 vacc_regimes.append(all_vacc_regimes[total_reg_count]) 314 reg_count += 1 315 if len(vacc_regimes) > 0: 316 self.__dump_vacc_table(vacc_regimes)
317 318 #--------------------------------------------------------
319 - def dump_item_fields(self, offset, item, field_list):
320 """ 321 Dump information related to the fields of a clinical item 322 offset - Number of left blank spaces 323 item - Item of the field to dump 324 fields - Fields to dump 325 """ 326 txt = '' 327 for a_field in field_list: 328 if type(a_field) is not types.UnicodeType: 329 a_field = unicode(a_field, encoding='latin1', errors='replace') 330 txt += u'%s%s%s' % ((offset * u' '), a_field, gmTools.coalesce(item[a_field], u'\n', template_initial = u': %s\n')) 331 return txt
332 #--------------------------------------------------------
333 - def get_allergy_output(self, allergy, left_margin = 0):
334 """ 335 Dumps allergy item data 336 allergy - Allergy item to dump 337 left_margin - Number of spaces on the left margin 338 """ 339 txt = '' 340 txt += left_margin*' ' + _('Allergy') + ': \n' 341 txt += self.dump_item_fields((left_margin+3), allergy, ['allergene', 'substance', 'generic_specific','l10n_type', 'definite', 'reaction']) 342 return txt
343 #--------------------------------------------------------
344 - def get_vaccination_output(self, vaccination, left_margin = 0):
345 """ 346 Dumps vaccination item data 347 vaccination - Vaccination item to dump 348 left_margin - Number of spaces on the left margin 349 """ 350 txt = '' 351 txt += left_margin*' ' + _('Vaccination') + ': \n' 352 txt += self.dump_item_fields((left_margin+3), vaccination, ['l10n_indication', 'vaccine', 'batch_no', 'site', 'narrative']) 353 return txt
354 #--------------------------------------------------------
355 - def get_lab_result_output(self, lab_result, left_margin = 0):
356 """ 357 Dumps lab result item data 358 lab_request - Lab request item to dump 359 left_margin - Number of spaces on the left margin 360 """ 361 txt = '' 362 if self.lab_new_encounter: 363 txt += (left_margin)*' ' + _('Lab result') + ': \n' 364 txt += (left_margin+3) * ' ' + lab_result['unified_name'] + ': ' + lab_result['unified_val']+ ' ' + lab_result['val_unit'] + ' (' + lab_result['material'] + ')' + '\n' 365 return txt
366 #--------------------------------------------------------
367 - def get_item_output(self, item, left_margin = 0):
368 """ 369 Obtains formatted clinical item output dump 370 item - The clinical item to dump 371 left_margin - Number of spaces on the left margin 372 """ 373 txt = '' 374 if isinstance(item, gmAllergy.cAllergy): 375 txt += self.get_allergy_output(item, left_margin) 376 # elif isinstance(item, gmVaccination.cVaccination): 377 # txt += self.get_vaccination_output(item, left_margin) 378 # elif isinstance(item, gmPathLab.cLabResult): 379 # txt += self.get_lab_result_output(item, left_margin) 380 # self.lab_new_encounter = False 381 return txt
382 #--------------------------------------------------------
383 - def __fetch_filtered_items(self):
384 """ 385 Retrieve patient clinical items filtered by multiple constraints 386 """ 387 if not self.__patient.connected: 388 return False 389 emr = self.__patient.get_emr() 390 filtered_items = [] 391 filtered_items.extend(emr.get_allergies( 392 since=self.__constraints['since'], 393 until=self.__constraints['until'], 394 encounters=self.__constraints['encounters'], 395 episodes=self.__constraints['episodes'], 396 issues=self.__constraints['issues'])) 397 # try: 398 # filtered_items.extend(emr.get_vaccinations( 399 # since=self.__constraints['since'], 400 # until=self.__constraints['until'], 401 # encounters=self.__constraints['encounters'], 402 # episodes=self.__constraints['episodes'], 403 # issues=self.__constraints['issues'])) 404 # except: 405 # _log.error("vaccination error? outside regime") 406 407 # filtered_items.extend(emr.get_lab_results( 408 # since=self.__constraints['since'], 409 # until=self.__constraints['until'], 410 # encounters=self.__constraints['encounters'], 411 # episodes=self.__constraints['episodes'], 412 # issues=self.__constraints['issues'])) 413 self.__filtered_items = filtered_items 414 return True
415 #--------------------------------------------------------
416 - def get_allergy_summary(self, allergy, left_margin = 0):
417 """ 418 Dumps allergy item data summary 419 allergy - Allergy item to dump 420 left_margin - Number of spaces on the left margin 421 """ 422 txt = _('%sAllergy: %s, %s (noted %s)\n') % ( 423 left_margin * u' ', 424 allergy['descriptor'], 425 gmTools.coalesce(allergy['reaction'], _('unknown reaction')), 426 allergy['date'].strftime('%x') 427 ) 428 # txt = left_margin * ' ' \ 429 # + _('Allergy') + ': ' \ 430 # + allergy['descriptor'] + u', ' \ 431 # + gmTools.coalesce(allergy['reaction'], _('unknown reaction')) ' ' \ 432 # + _('(noted %s)') % allergy['date'].strftime('%x') \ 433 # + '\n' 434 return txt
435 #--------------------------------------------------------
436 - def get_vaccination_summary(self, vaccination, left_margin = 0):
437 """ 438 Dumps vaccination item data summary 439 vaccination - Vaccination item to dump 440 left_margin - Number of spaces on the left margin 441 """ 442 txt = left_margin*' ' + _('Vaccination') + ': ' + vaccination['l10n_indication'] + ', ' + \ 443 vaccination['narrative'] + '\n' 444 return txt
445 #--------------------------------------------------------
446 - def get_lab_result_summary(self, lab_result, left_margin = 0):
447 """ 448 Dumps lab result item data summary 449 lab_request - Lab request item to dump 450 left_margin - Number of spaces on the left margin 451 """ 452 txt = '' 453 if self.lab_new_encounter: 454 txt += (left_margin+3)*' ' + _('Lab') + ': ' + \ 455 lab_result['unified_name'] + '-> ' + lab_result['unified_val'] + \ 456 ' ' + lab_result['val_unit']+ '\n' + '(' + lab_result['req_when'].strftime('%Y-%m-%d') + ')' 457 return txt
458 #--------------------------------------------------------
459 - def get_item_summary(self, item, left_margin = 0):
460 """ 461 Obtains formatted clinical item summary dump 462 item - The clinical item to dump 463 left_margin - Number of spaces on the left margin 464 """ 465 txt = '' 466 if isinstance(item, gmAllergy.cAllergy): 467 txt += self.get_allergy_summary(item, left_margin) 468 # elif isinstance(item, gmVaccination.cVaccination): 469 # txt += self.get_vaccination_summary(item, left_margin) 470 # elif isinstance(item, gmPathLab.cLabResult) and \ 471 # True: 472 # #(item['relevant'] == True or item['abnormal'] == True): 473 # txt += self.get_lab_result_summary(item, left_margin) 474 # self.lab_new_encounter = False 475 return txt
476 #--------------------------------------------------------
477 - def refresh_historical_tree(self, emr_tree):
478 """ 479 checks a emr_tree constructed with this.get_historical_tree() 480 and sees if any new items need to be inserted. 481 """ 482 #TODO , caching eliminates tree update time, so don't really need this 483 self._traverse_health_issues( emr_tree, self._update_health_issue_branch)
484 #--------------------------------------------------------
485 - def get_historical_tree( self, emr_tree):
486 self._traverse_health_issues( emr_tree, self._add_health_issue_branch)
487 #--------------------------------------------------------
488 - def _traverse_health_issues(self, emr_tree, health_issue_action):
489 """ 490 Retrieves patient's historical in form of a wx tree of health issues 491 -> episodes 492 -> encounters 493 Encounter object is associated with item to allow displaying its information 494 """ 495 # variable initialization 496 # this protects the emrBrowser from locking up in a paint event, e.g. in 497 # some configurations which want to use emrBrowser, but don't stop tabbing 498 # to emr browser when no patient selected. the effect is to displace a cNull instance 499 # which is a sane representation when no patient is selected. 500 if not self.__fetch_filtered_items(): 501 return 502 emr = self.__patient.get_emr() 503 unlinked_episodes = emr.get_episodes(issues = [None]) 504 h_issues = [] 505 h_issues.extend(emr.get_health_issues(id_list = self.__constraints['issues'])) 506 # build the tree 507 # unlinked episodes 508 if len(unlinked_episodes) > 0: 509 h_issues.insert(0, { 510 'description': _('Unattributed episodes'), 511 'pk_health_issue': None 512 }) 513 # existing issues 514 for a_health_issue in h_issues: 515 health_issue_action( emr_tree, a_health_issue) 516 517 emr_tree.SortChildren(emr_tree.GetRootItem())
518 #--------------------------------------------------------
519 - def _add_health_issue_branch( self, emr_tree, a_health_issue):
520 """appends to a wx emr_tree , building wx treenodes from the health_issue make this reusable for non-collapsing tree updates""" 521 emr = self.__patient.get_emr() 522 root_node = emr_tree.GetRootItem() 523 issue_node = emr_tree.AppendItem(root_node, a_health_issue['description']) 524 emr_tree.SetPyData(issue_node, a_health_issue) 525 episodes = emr.get_episodes(id_list=self.__constraints['episodes'], issues = [a_health_issue['pk_health_issue']]) 526 for an_episode in episodes: 527 self._add_episode_to_tree( emr, emr_tree, issue_node,a_health_issue, an_episode) 528 emr_tree.SortChildren(issue_node)
529 #--------------------------------------------------------
530 - def _add_episode_to_tree( self, emr , emr_tree, issue_node, a_health_issue, an_episode):
531 episode_node = emr_tree.AppendItem(issue_node, an_episode['description']) 532 emr_tree.SetPyData(episode_node, an_episode) 533 if an_episode['episode_open']: 534 emr_tree.SetItemBold(issue_node, True) 535 536 encounters = self._get_encounters( an_episode, emr ) 537 self._add_encounters_to_tree( encounters, emr_tree, episode_node ) 538 emr_tree.SortChildren(episode_node) 539 return episode_node
540 #--------------------------------------------------------
541 - def _add_encounters_to_tree( self, encounters, emr_tree, episode_node):
542 for an_encounter in encounters: 543 # label = u'%s: %s' % (an_encounter['started'].strftime('%Y-%m-%d'), an_encounter['l10n_type']) 544 label = u'%s: %s' % ( 545 an_encounter['started'].strftime('%Y-%m-%d'), 546 gmTools.unwrap ( 547 gmTools.coalesce ( 548 gmTools.coalesce ( 549 gmTools.coalesce ( 550 an_encounter.get_latest_soap ( # soAp 551 soap_cat = 'a', 552 episode = emr_tree.GetPyData(episode_node)['pk_episode'] 553 ), 554 an_encounter['assessment_of_encounter'] # or AOE 555 ), 556 an_encounter['reason_for_encounter'] # or RFE 557 ), 558 an_encounter['l10n_type'] # or type 559 ), 560 max_length = 40 561 ) 562 ) 563 encounter_node_id = emr_tree.AppendItem(episode_node, label) 564 emr_tree.SetPyData(encounter_node_id, an_encounter)
565 #--------------------------------------------------------
566 - def _get_encounters ( self, an_episode, emr ):
567 encounters = emr.get_encounters ( 568 episodes = [an_episode['pk_episode']] 569 ) 570 return encounters
571 #--------------------------------------------------------
572 - def _update_health_issue_branch(self, emr_tree, a_health_issue):
573 emr = self.__patient.get_emr() 574 root_node = emr_tree.GetRootItem() 575 id, cookie = emr_tree.GetFirstChild(root_node) 576 found = False 577 while id.IsOk(): 578 if emr_tree.GetItemText(id) == a_health_issue['description']: 579 found = True 580 break 581 id,cookie = emr_tree.GetNextChild( root_node, cookie) 582 583 if not found: 584 _log.error("health issue %s should exist in tree already", a_health_issue['description'] ) 585 return 586 issue_node = id 587 episodes = emr.get_episodes(id_list=self.__constraints['episodes'], issues = [a_health_issue['pk_health_issue']]) 588 589 #check for removed episode and update tree 590 tree_episodes = {} 591 id_episode, cookie = emr_tree.GetFirstChild(issue_node) 592 while id_episode.IsOk(): 593 tree_episodes[ emr_tree.GetPyData(id_episode)['pk_episode'] ]= id_episode 594 id_episode,cookie = emr_tree.GetNextChild( issue_node, cookie) 595 596 existing_episode_pk = [ e['pk_episode'] for e in episodes] 597 missing_tree_pk = [ pk for pk in tree_episodes.keys() if pk not in existing_episode_pk] 598 for pk in missing_tree_pk: 599 emr_tree.Remove( tree_episodes[pk] ) 600 601 added_episode_pk = [pk for pk in existing_episode_pk if pk not in tree_episodes.keys()] 602 add_episodes = [ e for e in episodes if e['pk_episode'] in added_episode_pk] 603 604 #check for added episodes and update tree 605 for an_episode in add_episodes: 606 node = self._add_episode_to_tree( emr, emr_tree, issue_node, a_health_issue, an_episode) 607 tree_episodes[an_episode['pk_episode']] = node 608 609 for an_episode in episodes: 610 # found episode, check for encounter change 611 try: 612 #print "getting id_episode of ", an_episode['pk_episode'] 613 id_episode = tree_episodes[an_episode['pk_episode']] 614 except: 615 import pdb 616 pdb.set_trace() 617 # get a map of encounters in the tree by pk_encounter as key 618 tree_enc = {} 619 id_encounter, cookie = emr_tree.GetFirstChild(id_episode) 620 while id_encounter.IsOk(): 621 tree_enc[ emr_tree.GetPyData(id_encounter)['pk_encounter'] ] = id_encounter 622 id_encounter,cookie = emr_tree.GetNextChild(id_episode, cookie) 623 624 # remove encounters in tree not in existing encounters in episode 625 # encounters = self._get_encounters( a_health_issue, an_episode, emr ) 626 encounters = self._get_encounters( an_episode, emr ) 627 existing_enc_pk = [ enc['pk_encounter'] for enc in encounters] 628 missing_enc_pk = [ pk for pk in tree_enc.keys() if pk not in existing_enc_pk] 629 for pk in missing_enc_pk: 630 emr_tree.Remove( tree_enc[pk] ) 631 632 # check for added encounter 633 added_enc_pk = [ pk for pk in existing_enc_pk if pk not in tree_enc.keys() ] 634 add_encounters = [ enc for enc in encounters if enc['pk_encounter'] in added_enc_pk] 635 if add_encounters != []: 636 #print "DEBUG found encounters to add" 637 self._add_encounters_to_tree( add_encounters, emr_tree, id_episode)
638 #--------------------------------------------------------
639 - def get_summary_info(self, left_margin = 0):
640 """ 641 Dumps patient EMR summary 642 """ 643 txt = '' 644 for an_item in self.__filtered_items: 645 txt += self.get_item_summary(an_item, left_margin) 646 return txt
647 #--------------------------------------------------------
648 - def get_episode_summary (self, episode, left_margin = 0):
649 """Dumps episode specific data""" 650 emr = self.__patient.get_emr() 651 encs = emr.get_encounters(episodes = [episode['pk_episode']]) 652 if encs is None: 653 txt = left_margin * ' ' + _('Error retrieving encounters for episode\n%s') % str(episode) 654 return txt 655 no_encs = len(encs) 656 if no_encs == 0: 657 txt = left_margin * ' ' + _('There are no encounters for this episode.') 658 return txt 659 if episode['episode_open']: 660 status = _('active') 661 else: 662 status = _('finished') 663 first_encounter = emr.get_first_encounter(episode_id = episode['pk_episode']) 664 last_encounter = emr.get_last_encounter(episode_id = episode['pk_episode']) 665 txt = _( 666 '%sEpisode "%s" [%s]\n' 667 '%sEncounters: %s (%s - %s)\n' 668 '%sLast worked on: %s\n' 669 ) % ( 670 left_margin * ' ', episode['description'], status, 671 left_margin * ' ', no_encs, first_encounter['started'].strftime('%m/%Y'), last_encounter['last_affirmed'].strftime('%m/%Y'), 672 left_margin * ' ', last_encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M') 673 ) 674 return txt
675 #--------------------------------------------------------
676 - def get_encounter_info(self, episode, encounter, left_margin = 0):
677 """ 678 Dumps encounter specific data (rfe, aoe and soap) 679 """ 680 emr = self.__patient.get_emr() 681 # general 682 txt = (' ' * left_margin) + '#%s: %s - %s %s' % ( 683 encounter['pk_encounter'], 684 encounter['started'].strftime('%Y-%m-%d %H:%M'), 685 encounter['last_affirmed'].strftime('%H:%M (%Z)'), 686 encounter['l10n_type'] 687 ) 688 if (encounter['assessment_of_encounter'] is not None) and (len(encounter['assessment_of_encounter']) > 0): 689 txt += ' "%s"' % encounter['assessment_of_encounter'] 690 txt += '\n\n' 691 692 # rfe/aoe 693 txt += (' ' * left_margin) + '%s: %s\n' % (_('RFE'), encounter['reason_for_encounter']) 694 txt += (' ' * left_margin) + '%s: %s\n' % (_('AOE'), encounter['assessment_of_encounter']) 695 696 # soap 697 soap_cat_labels = { 698 's': _('Subjective'), 699 'o': _('Objective'), 700 'a': _('Assessment'), 701 'p': _('Plan'), 702 None: _('Administrative') 703 } 704 eol_w_margin = '\n' + (' ' * (left_margin+3)) 705 for soap_cat in 'soap': 706 soap_cat_narratives = emr.get_clin_narrative ( 707 episodes = [episode['pk_episode']], 708 encounters = [encounter['pk_encounter']], 709 soap_cats = [soap_cat] 710 ) 711 if soap_cat_narratives is None: 712 continue 713 if len(soap_cat_narratives) == 0: 714 continue 715 txt += (' ' * left_margin) + soap_cat_labels[soap_cat] + ':\n' 716 for soap_entry in soap_cat_narratives: 717 txt += gmTools.wrap ( 718 '%s %.8s: %s\n' % ( 719 soap_entry['date'].strftime('%d.%m. %H:%M'), 720 soap_entry['provider'], 721 soap_entry['narrative'] 722 ), 75 723 ) 724 725 # txt += ( 726 # (' ' * (left_margin+3)) + 727 # soap_entry['date'].strftime('%H:%M %.8s: ') % soap_entry['provider'] + 728 # soap_entry['narrative'].replace('\n', eol_w_margin) + 729 # '\n' 730 # ) 731 #FIXME: add diagnoses 732 733 # items 734 for an_item in self.__filtered_items: 735 if an_item['pk_encounter'] == encounter['pk_encounter']: 736 txt += self.get_item_output(an_item, left_margin) 737 return txt
738 #--------------------------------------------------------
739 - def dump_historical_tree(self):
740 """Dumps patient's historical in form of a tree of health issues 741 -> episodes 742 -> encounters 743 -> clinical items 744 """ 745 746 # fecth all values 747 self.__fetch_filtered_items() 748 emr = self.__patient.get_emr() 749 750 # dump clinically relevant items summary 751 for an_item in self.__filtered_items: 752 self.__target.write(self.get_item_summary(an_item, 3)) 753 754 # begin with the tree 755 h_issues = [] 756 h_issues.extend(emr.get_health_issues(id_list = self.__constraints['issues'])) 757 # unlinked episodes 758 unlinked_episodes = emr.get_episodes(issues = [None]) 759 if len(unlinked_episodes) > 0: 760 h_issues.insert(0, {'description':_('Unattributed episodes'), 'pk_health_issue':None}) 761 for a_health_issue in h_issues: 762 self.__target.write('\n' + 3*' ' + 'Health Issue: ' + a_health_issue['description'] + '\n') 763 episodes = emr.get_episodes(id_list=self.__constraints['episodes'], issues = [a_health_issue['pk_health_issue']]) 764 for an_episode in episodes: 765 self.__target.write('\n' + 6*' ' + 'Episode: ' + an_episode['description'] + '\n') 766 if a_health_issue['pk_health_issue'] is None: 767 issues = None 768 else: 769 issues = [a_health_issue['pk_health_issue']] 770 encounters = emr.get_encounters ( 771 since = self.__constraints['since'], 772 until = self.__constraints['until'], 773 id_list = self.__constraints['encounters'], 774 episodes = [an_episode['pk_episode']], 775 issues = issues 776 ) 777 for an_encounter in encounters: 778 # title 779 self.lab_new_encounter = True 780 self.__target.write( 781 '\n %s %s: %s - %s (%s)\n' % ( 782 _('Encounter'), 783 an_encounter['l10n_type'], 784 an_encounter['started'].strftime('%A, %Y-%m-%d %H:%M'), 785 an_encounter['last_affirmed'].strftime('%m-%d %H:%M'), 786 an_encounter['assessment_of_encounter'] 787 ) 788 ) 789 self.__target.write(self.get_encounter_info(an_episode, an_encounter, 12))
790 #--------------------------------------------------------
791 - def dump_clinical_record(self):
792 """ 793 Dumps in ASCII format patient's clinical record 794 """ 795 emr = self.__patient.get_emr() 796 if emr is None: 797 _log.error('cannot get EMR text dump') 798 print(_( 799 'An error occurred while retrieving a text\n' 800 'dump of the EMR for the active patient.\n\n' 801 'Please check the log file for details.' 802 )) 803 return None 804 self.__target.write('\nOverview\n') 805 self.__target.write('--------\n') 806 self.__target.write("1) Allergy status (for details, see below):\n\n") 807 for allergy in emr.get_allergies(): 808 self.__target.write(" " + allergy['descriptor'] + "\n\n") 809 self.__target.write("2) Vaccination status (* indicates booster):\n") 810 # self.get_vacc_table() 811 self.__target.write("\n3) Historical:\n\n") 812 self.dump_historical_tree() 813 814 try: 815 emr.cleanup() 816 except: 817 print "error cleaning up EMR"
818 #--------------------------------------------------------
819 - def dump_med_docs(self):
820 """ 821 Dumps patient stored medical documents 822 823 """ 824 doc_folder = self.__patient.get_document_folder() 825 826 self.__target.write('\n4) Medical documents: (date) reference - type "comment"\n') 827 self.__target.write(' object - comment') 828 829 docs = doc_folder.get_documents() 830 for doc in docs: 831 self.__target.write('\n\n (%s) %s - %s "%s"' % ( 832 doc['clin_when'].strftime('%Y-%m-%d'), 833 doc['ext_ref'], 834 doc['l10n_type'], 835 doc['comment']) 836 ) 837 for part in doc.parts: 838 self.__target.write('\n %s - %s' % ( 839 part['seq_idx'], 840 part['obj_comment']) 841 ) 842 self.__target.write('\n\n')
843 #--------------------------------------------------------
844 - def dump_demographic_record(self, all = False):
845 """ 846 Dumps in ASCII format some basic patient's demographic data 847 """ 848 if self.__patient is None: 849 _log.error('cannot get Demographic export') 850 print(_( 851 'An error occurred while Demographic record export\n' 852 'Please check the log file for details.' 853 )) 854 return None 855 856 self.__target.write('\n\n\nDemographics') 857 self.__target.write('\n------------\n') 858 self.__target.write(' Id: %s \n' % self.__patient['pk_identity']) 859 cont = 0 860 for name in self.__patient.get_names(): 861 if cont == 0: 862 self.__target.write(' Name (Active): %s, %s\n' % (name['firstnames'], name['lastnames']) ) 863 else: 864 self.__target.write(' Name %s: %s, %s\n' % (cont, name['firstnames'], name['lastnames'])) 865 cont += 1 866 self.__target.write(' Gender: %s\n' % self.__patient['gender']) 867 self.__target.write(' Title: %s\n' % self.__patient['title']) 868 self.__target.write(' Dob: %s\n' % self.__patient.get_formatted_dob(format = '%Y-%m-%d')) 869 self.__target.write(' Medical age: %s\n' % self.__patient.get_medical_age())
870 #--------------------------------------------------------
871 - def dump_constraints(self):
872 """ 873 Dumps exporter filtering constraints 874 """ 875 self.__first_constraint = True 876 if not self.__constraints['since'] is None: 877 self.dump_constraints_header() 878 self.__target.write('\nSince: %s' % self.__constraints['since'].strftime('%Y-%m-%d')) 879 880 if not self.__constraints['until'] is None: 881 self.dump_constraints_header() 882 self.__target.write('\nUntil: %s' % self.__constraints['until'].strftime('%Y-%m-%d')) 883 884 if not self.__constraints['encounters'] is None: 885 self.dump_constraints_header() 886 self.__target.write('\nEncounters: ') 887 for enc in self.__constraints['encounters']: 888 self.__target.write(str(enc) + ' ') 889 890 if not self.__constraints['episodes'] is None: 891 self.dump_constraints_header() 892 self.__target.write('\nEpisodes: ') 893 for epi in self.__constraints['episodes']: 894 self.__target.write(str(epi) + ' ') 895 896 if not self.__constraints['issues'] is None: 897 self.dump_constraints_header() 898 self.__target.write('\nIssues: ') 899 for iss in self.__constraints['issues']: 900 self.__target.write(str(iss) + ' ')
901 #--------------------------------------------------------
902 - def dump_constraints_header(self):
903 """ 904 Dumps constraints header 905 """ 906 if self.__first_constraint == True: 907 self.__target.write('\nClinical items dump constraints\n') 908 self.__target.write('-'*(len(head_txt)-2)) 909 self.__first_constraint = False
910 #============================================================
911 -class cEMRJournalExporter:
912 """Exports patient EMR into a simple chronological journal. 913 914 Note that this export will emit u'' strings only. 915 """
916 - def __init__(self):
917 self.__part_len = 72
918 #-------------------------------------------------------- 919 # external API 920 #--------------------------------------------------------
921 - def export_to_file(self, filename=None, patient=None):
922 """Export medical record into a file. 923 924 @type filename: None (creates filename by itself) or string 925 @type patient: None (use currently active patient) or <gmPerson.cIdentity> instance 926 """ 927 if patient is None: 928 patient = gmPerson.gmCurrentPatient() 929 if not patient.connected: 930 raise ValueError('[%s].export_to_file(): no active patient' % self.__class__.__name__) 931 932 if filename is None: 933 filename = u'%s-%s-%s-%s.txt' % ( 934 _('emr-journal'), 935 patient['lastnames'].replace(u' ', u'_'), 936 patient['firstnames'].replace(u' ', u'_'), 937 patient.get_formatted_dob(format = '%Y-%m-%d') 938 ) 939 path = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', patient['dirname'], filename)) 940 941 f = codecs.open(filename = filename, mode = 'w+b', encoding = 'utf8', errors = 'replace') 942 self.export(target = f, patient = patient) 943 f.close() 944 return filename
945 #-------------------------------------------------------- 946 # internal API 947 #--------------------------------------------------------
948 - def export(self, target=None, patient=None):
949 """ 950 Export medical record into a Python object. 951 952 @type target: a python object supporting the write() API 953 @type patient: None (use currently active patient) or <gmPerson.cIdentity> instance 954 """ 955 if patient is None: 956 patient = gmPerson.gmCurrentPatient() 957 if not patient.connected: 958 raise ValueError('[%s].export(): no active patient' % self.__class__.__name__) 959 960 # write header 961 txt = _('Chronological EMR Journal\n') 962 target.write(txt) 963 target.write(u'=' * (len(txt)-1)) 964 target.write('\n') 965 target.write(_('Patient: %s (%s), No: %s\n') % (patient['description'], patient['gender'], patient['pk_identity'])) 966 target.write(_('Born : %s, age: %s\n\n') % ( 967 patient.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()), 968 patient.get_medical_age() 969 )) 970 target.write(u'.-%10.10s---%9.9s-------%72.72s\n' % (u'-' * 10, u'-' * 9, u'-' * self.__part_len)) 971 target.write(u'| %10.10s | %9.9s | | %s\n' % (_('Happened'), _('Doc'), _('Narrative'))) 972 target.write(u'|-%10.10s---%9.9s-------%72.72s\n' % (u'-' * 10, u'-' * 9, u'-' * self.__part_len)) 973 974 # get data 975 cmd = u""" 976 select 977 to_char(vemrj.clin_when, 'YYYY-MM-DD') as date, 978 vemrj.*, 979 (select rank from clin.soap_cat_ranks where soap_cat = vemrj.soap_cat) as scr, 980 to_char(vemrj.modified_when, 'YYYY-MM-DD HH24:MI') as date_modified 981 from clin.v_emr_journal vemrj 982 where pk_patient = %s 983 order by date, pk_episode, scr, src_table""" 984 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [patient['pk_identity']]}], get_col_idx = True) 985 986 # write data 987 prev_date = u'' 988 prev_doc = u'' 989 prev_soap = u'' 990 for row in rows: 991 # narrative 992 if row['narrative'] is None: 993 continue 994 995 txt = gmTools.wrap ( 996 text = row['narrative'].replace(u'\r', u'') + (u' (%s)' % row['date_modified']), 997 width = self.__part_len 998 ).split('\n') 999 1000 # same provider ? 1001 curr_doc = row['modified_by'] 1002 if curr_doc != prev_doc: 1003 prev_doc = curr_doc 1004 else: 1005 curr_doc = u'' 1006 1007 # same soap category ? 1008 curr_soap = row['soap_cat'] 1009 if curr_soap != prev_soap: 1010 prev_soap = curr_soap 1011 1012 # same date ? 1013 curr_date = row['date'] 1014 if curr_date != prev_date: 1015 prev_date = curr_date 1016 curr_doc = row['modified_by'] 1017 prev_doc = curr_doc 1018 curr_soap = row['soap_cat'] 1019 prev_soap = curr_soap 1020 else: 1021 curr_date = u'' 1022 1023 # display first part 1024 target.write(u'| %10.10s | %9.9s | %3.3s | %s\n' % ( 1025 curr_date, 1026 curr_doc, 1027 gmClinNarrative.soap_cat2l10n[curr_soap], 1028 txt[0] 1029 )) 1030 1031 # only one part ? 1032 if len(txt) == 1: 1033 continue 1034 1035 template = u'| %10.10s | %9.9s | %3.3s | %s\n' 1036 for part in txt[1:]: 1037 line = template % (u'', u'', u' ', part) 1038 target.write(line) 1039 1040 # write footer 1041 target.write(u'`-%10.10s---%9.9s-------%72.72s\n\n' % (u'-' * 10, u'-' * 9, u'-' * self.__part_len)) 1042 target.write(_('Exported: %s\n') % pyDT.datetime.now().strftime('%c').decode(gmI18N.get_encoding())) 1043 1044 return
1045 #============================================================
1046 -class cMedistarSOAPExporter:
1047 """Export SOAP data per encounter into Medistar import format."""
1048 - def __init__(self, patient=None):
1049 if patient is None: 1050 self.__pat = gmPerson.gmCurrentPatient() 1051 else: 1052 if not isinstance(patient, gmPerson.cIdentity): 1053 raise gmExceptions.ConstructorError, '<patient> argument must be instance of <cIdentity>, but is: %s' % type(patient) 1054 self.__pat = patient
1055 #-------------------------------------------------------- 1056 # external API 1057 #--------------------------------------------------------
1058 - def export_to_file(self, filename=None, encounter=None, soap_cats=u'soap', export_to_import_file=False):
1059 if not self.__pat.connected: 1060 return (False, 'no active patient') 1061 1062 if filename is None: 1063 path = os.path.abspath(os.path.expanduser('~/gnumed/export')) 1064 filename = '%s-%s-%s-%s-%s.txt' % ( 1065 os.path.join(path, 'Medistar-MD'), 1066 time.strftime('%Y-%m-%d',time.localtime()), 1067 self.__pat['lastnames'].replace(' ', '-'), 1068 self.__pat['firstnames'].replace(' ', '_'), 1069 self.__pat.get_formatted_dob(format = '%Y-%m-%d') 1070 ) 1071 1072 f = codecs.open(filename = filename, mode = 'w+b', encoding = 'cp437', errors='replace') 1073 status = self.__export(target = f, encounter = encounter, soap_cats = soap_cats) 1074 f.close() 1075 1076 if export_to_import_file: 1077 # detect "LW:\medistar\inst\soap.txt" 1078 medistar_found = False 1079 for drive in u'cdefghijklmnopqrstuvwxyz': 1080 path = drive + ':\\medistar\\inst' 1081 if not os.path.isdir(path): 1082 continue 1083 try: 1084 import_fname = path + '\\soap.txt' 1085 open(import_fname, mode = 'w+b').close() 1086 _log.debug('exporting narrative to [%s] for Medistar import', import_fname) 1087 shutil.copyfile(filename, import_fname) 1088 medistar_found = True 1089 except IOError: 1090 continue 1091 1092 if not medistar_found: 1093 _log.debug('no Medistar installation found (no <LW>:\\medistar\\inst\\)') 1094 1095 return (status, filename)
1096 #--------------------------------------------------------
1097 - def export(self, target, encounter=None, soap_cats=u'soap'):
1098 return self.__export(target, encounter = encounter, soap_cats = soap_cats)
1099 #-------------------------------------------------------- 1100 # interal API 1101 #--------------------------------------------------------
1102 - def __export(self, target=None, encounter=None, soap_cats=u'soap'):
1103 # get data 1104 cmd = u"select narrative from clin.v_emr_journal where pk_patient=%s and pk_encounter=%s and soap_cat=%s" 1105 for soap_cat in soap_cats: 1106 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': (self.__pat['pk_identity'], encounter['pk_encounter'], soap_cat)}]) 1107 target.write('*MD%s*\r\n' % gmClinNarrative.soap_cat2l10n[soap_cat]) 1108 for row in rows: 1109 text = row[0] 1110 if text is None: 1111 continue 1112 target.write('%s\r\n' % gmTools.wrap ( 1113 text = text, 1114 width = 64, 1115 eol = u'\r\n' 1116 )) 1117 return True
1118 #============================================================ 1119 # main 1120 #------------------------------------------------------------
1121 -def usage():
1122 """ 1123 Prints application usage options to stdout. 1124 """ 1125 print 'usage: python gmPatientExporter [--fileout=<outputfilename>] [--conf-file=<file>] [--text-domain=<textdomain>]' 1126 sys.exit(0)
1127 #------------------------------------------------------------
1128 -def run():
1129 """ 1130 Main module application execution loop. 1131 """ 1132 # More variable initializations 1133 patient = None 1134 patient_id = None 1135 patient_term = None 1136 pat_searcher = gmPersonSearch.cPatientSearcher_SQL() 1137 1138 # App execution loop 1139 while patient_term != 'bye': 1140 patient = gmPersonSearch.ask_for_patient() 1141 if patient is None: 1142 break 1143 # FIXME: needed ? 1144 # gmPerson.set_active_patient(patient=patient) 1145 exporter = cEMRJournalExporter() 1146 exporter.export_to_file(patient=patient) 1147 # export_tool.set_patient(patient) 1148 # Dump patient EMR sections 1149 # export_tool.dump_constraints() 1150 # export_tool.dump_demographic_record(True) 1151 # export_tool.dump_clinical_record() 1152 # export_tool.dump_med_docs() 1153 1154 # Clean ups 1155 # outFile.close() 1156 # export_tool.cleanup() 1157 if patient is not None: 1158 try: 1159 patient.cleanup() 1160 except: 1161 print "error cleaning up patient"
1162 #============================================================ 1163 # main 1164 #------------------------------------------------------------ 1165 if __name__ == "__main__": 1166 gmI18N.activate_locale() 1167 gmI18N.install_domain() 1168 1169 #--------------------------------------------------------
1170 - def export_journal():
1171 1172 print "Exporting EMR journal(s) ..." 1173 pat_searcher = gmPersonSearch.cPatientSearcher_SQL() 1174 while True: 1175 patient = gmPersonSearch.ask_for_patient() 1176 if patient is None: 1177 break 1178 1179 exporter = cEMRJournalExporter() 1180 print "exported into file:", exporter.export_to_file(patient=patient) 1181 1182 if patient is not None: 1183 try: 1184 patient.cleanup() 1185 except: 1186 print "error cleaning up patient" 1187 print "Done."
1188 #-------------------------------------------------------- 1189 print "\n\nGNUmed ASCII EMR Export" 1190 print "=======================" 1191 1192 # run main loop 1193 export_journal() 1194 1195 #============================================================ 1196 # $Log: gmPatientExporter.py,v $ 1197 # Revision 1.138 2009-09-08 17:14:55 ncq 1198 # - apply unwrap() to encounter node title 1199 # 1200 # Revision 1.137 2009/07/15 12:47:25 ncq 1201 # - properly use patient age 1202 # 1203 # Revision 1.136 2009/06/29 15:01:07 ncq 1204 # - use get-latest-soap in encounter formatting 1205 # 1206 # Revision 1.135 2009/06/04 16:24:35 ncq 1207 # - support dob-less persons 1208 # 1209 # Revision 1.134 2009/01/02 11:36:43 ncq 1210 # - cleanup 1211 # 1212 # Revision 1.133 2008/12/18 21:26:45 ncq 1213 # - missing H in HH24 in date formatting in journal exporter 1214 # 1215 # Revision 1.132 2008/12/09 23:24:29 ncq 1216 # - .date -> .clin_when in doc_med 1217 # 1218 # Revision 1.131 2008/12/01 12:36:57 ncq 1219 # - improved encounter node label 1220 # 1221 # Revision 1.130 2008/10/22 12:06:05 ncq 1222 # - use %x in strftime 1223 # 1224 # Revision 1.129 2008/10/12 15:32:18 ncq 1225 # - support "mod date" in journal 1226 # 1227 # Revision 1.128 2008/09/02 18:59:30 ncq 1228 # - no more fk_patient in clin.health_issue and related changes 1229 # 1230 # Revision 1.127 2008/07/28 15:42:30 ncq 1231 # - cleanup 1232 # - enhance medistar exporter 1233 # 1234 # Revision 1.126 2008/07/12 15:20:55 ncq 1235 # - comment out print 1236 # 1237 # Revision 1.125 2008/07/07 13:39:22 ncq 1238 # - properly sort tree 1239 # - current patient .connected 1240 # 1241 # Revision 1.124 2008/06/24 13:55:14 ncq 1242 # - change encounter node label 1243 # 1244 # Revision 1.123 2008/06/23 09:59:57 ncq 1245 # - much improved journal layout 1246 # 1247 # Revision 1.122 2008/06/15 20:16:02 ncq 1248 # - add a space 1249 # 1250 # Revision 1.121 2008/05/19 15:44:16 ncq 1251 # - just silly cleanup 1252 # 1253 # Revision 1.120 2008/05/07 15:16:01 ncq 1254 # - use centralized soap category translations from gmClinNarrative 1255 # 1256 # Revision 1.119 2008/04/11 12:21:11 ncq 1257 # - some cleanup 1258 # 1259 # Revision 1.118 2008/04/02 10:15:54 ncq 1260 # - show local time zone in encounter summary 1261 # 1262 # Revision 1.117 2008/03/06 18:24:45 ncq 1263 # - indentation fix 1264 # 1265 # Revision 1.116 2008/03/05 22:25:09 ncq 1266 # - no more gmLog 1267 # 1268 # Revision 1.115 2008/01/30 13:46:17 ncq 1269 # - cleanup 1270 # 1271 # Revision 1.114 2008/01/22 11:52:24 ncq 1272 # - Unattributed 1273 # - improved Journal formatting as per list 1274 # 1275 # Revision 1.113 2008/01/13 01:13:58 ncq 1276 # - use issue.age_noted_human_readable() 1277 # 1278 # Revision 1.112 2008/01/11 16:10:00 ncq 1279 # - first/last -> first-/lastnames 1280 # 1281 # Revision 1.111 2007/12/26 22:26:04 shilbert 1282 # - indentation error fixed 1283 # 1284 # Revision 1.110 2007/12/23 11:56:38 ncq 1285 # - improve output, cleanup 1286 # 1287 # Revision 1.109 2007/11/28 11:52:13 ncq 1288 # - get_all_names() -> get_names() 1289 # 1290 # Revision 1.108 2007/11/05 12:10:05 ncq 1291 # - support admin soap type 1292 # 1293 # Revision 1.107 2007/06/18 19:33:19 ncq 1294 # - cleanup 1295 # - show date in narrative, too 1296 # 1297 # Revision 1.106 2007/05/21 14:46:44 ncq 1298 # - use patient['dirname'] 1299 # 1300 # Revision 1.105 2007/05/14 13:09:04 ncq 1301 # - use bold on health issues with open episodes 1302 # 1303 # Revision 1.104 2007/04/01 15:25:55 ncq 1304 # - safely get encoding 1305 # 1306 # Revision 1.103 2007/03/02 15:30:00 ncq 1307 # - decode() strftime() output 1308 # 1309 # Revision 1.102 2007/02/22 17:30:48 ncq 1310 # - no more get_identity() 1311 # - patient now cIdentity() child 1312 # 1313 # Revision 1.101 2007/02/19 17:54:06 ncq 1314 # - need to return True when successful 1315 # 1316 # Revision 1.100 2007/02/19 16:56:05 ncq 1317 # - properly check for is_connected() 1318 # 1319 # Revision 1.99 2007/02/19 14:07:31 ncq 1320 # - fix format string parameters 1321 # 1322 # Revision 1.98 2007/02/17 14:10:03 ncq 1323 # - use improved gmTools.coalesce() 1324 # 1325 # Revision 1.97 2007/02/10 23:41:38 ncq 1326 # - fix loading of GNUmed python modules 1327 # - cleaned up journal exporter 1328 # - fixed bug in journal exporter where it expected is_connected() 1329 # in non-gmCurrentPatient-using context, too 1330 # - when run standalone: export journal 1331 # 1332 # Revision 1.96 2007/01/18 22:03:58 ncq 1333 # - a bit of cleanup 1334 # 1335 # Revision 1.95 2007/01/15 20:20:03 ncq 1336 # - move wrap() to gmTools 1337 # 1338 # Revision 1.94 2007/01/13 22:17:40 ncq 1339 # - wrap narrative to 75 characters per line 1340 # 1341 # Revision 1.93 2006/12/13 00:31:24 ncq 1342 # - export into unicode files 1343 # - fix use of get_encounters() 1344 # 1345 # Revision 1.92 2006/11/26 15:44:34 ncq 1346 # - strftime() does not accept u'' 1347 # 1348 # Revision 1.91 2006/11/24 14:16:20 ncq 1349 # - unicode-robustify dump_item_fields() 1350 # 1351 # Revision 1.90 2006/11/19 11:05:38 ncq 1352 # - cleanup 1353 # 1354 # Revision 1.89 2006/11/09 17:48:05 ncq 1355 # - ever more careful handling of NULLs 1356 # 1357 # Revision 1.88 2006/11/07 00:25:19 ncq 1358 # - make journal exporter emit strictly u'' 1359 # 1360 # Revision 1.87 2006/11/05 17:54:17 ncq 1361 # - don't use issue pk in get_encounters() 1362 # - gmPG -> gmPG2 1363 # 1364 # Revision 1.86 2006/11/05 17:02:54 ncq 1365 # - comment out lab results access, not in use yet 1366 # 1367 # Revision 1.85 2006/10/25 07:46:44 ncq 1368 # - Format() -> strftime() since datetime.datetime does not have .Format() 1369 # 1370 # Revision 1.84 2006/10/25 07:18:12 ncq 1371 # - no more gmPG 1372 # 1373 # Revision 1.83 2006/10/23 13:21:50 ncq 1374 # - vaccs/path lab not yet converted to gmPG2 1375 # 1376 # Revision 1.82 2006/09/03 14:46:26 ncq 1377 # - robustify regarding encoding issues 1378 # - improve test suite 1379 # 1380 # Revision 1.81 2006/07/19 20:25:48 ncq 1381 # - gmPyCompat.py is history 1382 # 1383 # Revision 1.80 2006/06/09 14:39:23 ncq 1384 # - comment out vaccination handling for now 1385 # 1386 # Revision 1.79 2006/05/30 13:36:35 ncq 1387 # - properly use get_encounters() 1388 # 1389 # Revision 1.78 2006/05/04 09:49:20 ncq 1390 # - get_clinical_record() -> get_emr() 1391 # - adjust to changes in set_active_patient() 1392 # - need explicit set_active_patient() after ask_for_patient() if wanted 1393 # 1394 # Revision 1.77 2006/02/27 22:38:36 ncq 1395 # - spell out rfe/aoe as per Richard's request 1396 # 1397 # Revision 1.76 2005/12/25 13:24:30 sjtan 1398 # 1399 # schema changes in names . 1400 # 1401 # Revision 1.75 2005/12/10 23:02:05 ncq 1402 # - tables are in clin.* now 1403 # 1404 # Revision 1.74 2005/10/30 15:48:56 ncq 1405 # - slightly enlarge space for provider signum display 1406 # 1407 # Revision 1.73 2005/10/19 09:06:39 ncq 1408 # - resolve merge conflict: just whitespace diff 1409 # 1410 # Revision 1.72 2005/10/18 13:34:01 sjtan 1411 # after running; small diffs 1412 # 1413 # Revision 1.71 2005/10/15 18:16:24 ncq 1414 # - encounter['description'] is gone, use 'aoe' 1415 # 1416 # Revision 1.70 2005/10/11 21:51:07 ncq 1417 # - rfe/aoe handling changes so adapt to that 1418 # 1419 # Revision 1.69 2005/10/08 12:33:09 sjtan 1420 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch. 1421 # 1422 # Revision 1.68 2005/10/04 19:22:37 sjtan 1423 # allow a refetch of part of the cache, so that don't have to completely collapse tree view to view after change. 1424 # 1425 # Revision 1.67 2005/10/04 00:04:45 sjtan 1426 # convert to wx.; catch some transitional errors temporarily 1427 # 1428 # Revision 1.66 2005/10/03 13:49:21 sjtan 1429 # using new wx. temporary debugging to stdout(easier to read). where is rfe ? 1430 # 1431 # Revision 1.65 2005/09/11 17:28:20 ncq 1432 # - tree widget now display provider sign, not database account 1433 # 1434 # Revision 1.64 2005/09/09 13:50:07 ncq 1435 # - detail improvements in tree widget progress note output 1436 # 1437 # Revision 1.63 2005/09/08 16:57:06 ncq 1438 # - improve progress note display in tree widget 1439 # 1440 # Revision 1.62 2005/09/05 15:56:27 ncq 1441 # - sort journal by episode within encounters 1442 # 1443 # Revision 1.61 2005/09/04 07:28:51 ncq 1444 # - better naming of dummy health issue for unassociated episodes 1445 # - display time of entry in front of SOAP notes 1446 # 1447 # Revision 1.60 2005/07/04 11:14:36 ncq 1448 # - improved episode summary yet again 1449 # 1450 # Revision 1.59 2005/07/02 18:18:26 ncq 1451 # - improve EMR tree right side info pane according to user 1452 # testing and ideas gleaned from TransHIS 1453 # 1454 # Revision 1.58 2005/06/30 16:11:55 cfmoro 1455 # Bug fix: multiple episode w/o issue when refreshing tree 1456 # 1457 # Revision 1.57 2005/06/30 11:42:05 cfmoro 1458 # Removed debug print 1459 # 1460 # Revision 1.56 2005/06/30 11:30:10 cfmoro 1461 # Minor fix on issue info when no encounters attached 1462 # 1463 # Revision 1.55 2005/06/20 13:03:38 cfmoro 1464 # Relink encounter to another episode 1465 # 1466 # Revision 1.54 2005/06/12 22:09:39 ncq 1467 # - better encounter formatting yet 1468 # 1469 # Revision 1.53 2005/06/07 09:04:45 ncq 1470 # - cleanup, better encounter data display 1471 # 1472 # Revision 1.52 2005/05/17 18:11:41 ncq 1473 # - dob2medical_age is in gmPerson 1474 # 1475 # Revision 1.51 2005/05/12 15:08:31 ncq 1476 # - add Medistar SOAP exporter and wrap(text, width) convenience function 1477 # 1478 # Revision 1.50 2005/04/27 19:59:19 ncq 1479 # - deal with narrative rows that are empty 1480 # 1481 # Revision 1.49 2005/04/12 16:15:36 ncq 1482 # - improve journal style exporter 1483 # 1484 # Revision 1.48 2005/04/12 10:00:19 ncq 1485 # - add cEMRJournalExporter class 1486 # 1487 # Revision 1.47 2005/04/03 20:08:18 ncq 1488 # - GUI stuff does not belong here (eg move to gmEMRBrowser which is to become gmEMRWidgets, eventually) 1489 # 1490 # Revision 1.46 2005/04/03 09:27:25 ncq 1491 # - better wording 1492 # 1493 # Revision 1.45 2005/04/02 21:37:27 cfmoro 1494 # Unlinked episodes displayes in EMR tree and dump 1495 # 1496 # Revision 1.44 2005/04/02 20:45:12 cfmoro 1497 # Implementated exporting emr from gui client 1498 # 1499 # Revision 1.43 2005/03/30 21:14:31 cfmoro 1500 # Using cIdentity recent changes 1501 # 1502 # Revision 1.42 2005/03/29 07:24:07 ncq 1503 # - tabify 1504 # 1505 # Revision 1.41 2005/03/20 17:48:38 ncq 1506 # - add two sanity checks by Syan 1507 # 1508 # Revision 1.40 2005/02/20 08:32:51 sjtan 1509 # 1510 # indentation syntax error. 1511 # 1512 # Revision 1.39 2005/02/03 20:19:16 ncq 1513 # - get_demographic_record() -> get_identity() 1514 # 1515 # Revision 1.38 2005/01/31 13:01:23 ncq 1516 # - use ask_for_patient() in gmPerson 1517 # 1518 # Revision 1.37 2005/01/31 10:19:11 ncq 1519 # - gmPatient -> gmPerson 1520 # 1521 # Revision 1.36 2004/10/26 12:52:56 ncq 1522 # - Carlos: fix conceptual bug by building top-down (eg. issue -> episode 1523 # -> item) instead of bottom-up 1524 # 1525 # Revision 1.35 2004/10/20 21:43:45 ncq 1526 # - cleanup 1527 # - use allergy['descriptor'] 1528 # - Format() dates 1529 # 1530 # Revision 1.34 2004/10/20 11:14:55 sjtan 1531 # restored import for unix. get_historical_tree may of changed, but mainly should 1532 # be guards in gmClinicalRecord for changing [] to None when functions expecting None, and client 1533 # functions passing []. 1534 # 1535 # Revision 1.33 2004/10/12 10:52:40 ncq 1536 # - improve vaccinations handling 1537 # 1538 # Revision 1.32 2004/10/11 19:53:41 ncq 1539 # - document classes are now VOs 1540 # 1541 # Revision 1.31 2004/09/29 19:13:37 ncq 1542 # - cosmetical fixes as discussed with our office staff 1543 # 1544 # Revision 1.30 2004/09/29 10:12:50 ncq 1545 # - Carlos added intuitive vaccination table - muchos improvos ! 1546 # 1547 # Revision 1.29 2004/09/10 10:39:01 ncq 1548 # - fixed assignment that needs to be comparison == in lambda form 1549 # 1550 # Revision 1.28 2004/09/06 18:55:09 ncq 1551 # - a bunch of cleanups re get_historical_tree() 1552 # 1553 # Revision 1.27 2004/09/01 21:59:01 ncq 1554 # - python classes can only have one single __init__ 1555 # - add in Carlos' code for displaying episode/issue summaries 1556 # 1557 # Revision 1.26 2004/08/23 09:08:53 ncq 1558 # - improve output 1559 # 1560 # Revision 1.25 2004/08/11 09:45:28 ncq 1561 # - format SOAP notes, too 1562 # 1563 # Revision 1.24 2004/08/09 18:41:08 ncq 1564 # - improved ASCII dump 1565 # 1566 # Revision 1.23 2004/07/26 00:02:30 ncq 1567 # - Carlos introduces export of RFE/AOE and dynamic layouting (left margin) 1568 # 1569 # Revision 1.22 2004/07/18 10:46:30 ncq 1570 # - lots of cleanup by Carlos 1571 # 1572 # Revision 1.21 2004/07/09 22:39:40 ncq 1573 # - write to file like object passed to __init__ 1574 # 1575 # Revision 1.20 2004/07/06 00:26:06 ncq 1576 # - fail on _cfg is_instance of cNull(), not on missing conf-file option 1577 # 1578 # Revision 1.19 2004/07/03 17:15:59 ncq 1579 # - decouple contraint/patient/outfile handling 1580 # 1581 # Revision 1.18 2004/07/02 00:54:04 ncq 1582 # - constraints passing cleanup by Carlos 1583 # 1584 # Revision 1.17 2004/06/30 12:52:36 ncq 1585 # - cleanup 1586 # 1587 # Revision 1.16 2004/06/30 12:43:10 ncq 1588 # - read opts from config file, cleanup 1589 # 1590 # Revision 1.15 2004/06/29 08:16:35 ncq 1591 # - take output file from command line 1592 # - *search* for patients, don't require knowledge of their ID 1593 # 1594 # Revision 1.14 2004/06/28 16:15:56 ncq 1595 # - still more faulty id_ found 1596 # 1597 # Revision 1.13 2004/06/28 15:52:00 ncq 1598 # - some comments 1599 # 1600 # Revision 1.12 2004/06/28 12:18:52 ncq 1601 # - more id_* -> fk_* 1602 # 1603 # Revision 1.11 2004/06/26 23:45:50 ncq 1604 # - cleanup, id_* -> fk/pk_* 1605 # 1606 # Revision 1.10 2004/06/26 06:53:25 ncq 1607 # - id_episode -> pk_episode 1608 # - constrained by date range from Carlos 1609 # - dump documents folder, too, by Carlos 1610 # 1611 # Revision 1.9 2004/06/23 22:06:48 ncq 1612 # - cleaner error handling 1613 # - fit for further work by Carlos on UI interface/dumping to file 1614 # - nice stuff ! 1615 # 1616 # Revision 1.8 2004/06/20 18:50:53 ncq 1617 # - some exception catching, needs more cleanup 1618 # 1619 # Revision 1.7 2004/06/20 18:35:07 ncq 1620 # - more work from Carlos 1621 # 1622 # Revision 1.6 2004/05/12 14:34:41 ncq 1623 # - now displays nice vaccination tables 1624 # - work by Carlos Moro 1625 # 1626 # Revision 1.5 2004/04/27 18:54:54 ncq 1627 # - adapt to gmClinicalRecord 1628 # 1629 # Revision 1.4 2004/04/24 13:35:33 ncq 1630 # - vacc table update 1631 # 1632 # Revision 1.3 2004/04/24 12:57:30 ncq 1633 # - stop db listeners on exit 1634 # 1635 # Revision 1.2 2004/04/20 13:00:22 ncq 1636 # - recent changes by Carlos to use VO API 1637 # 1638 # Revision 1.1 2004/03/25 23:10:02 ncq 1639 # - gmEmrExport -> gmPatientExporter by Carlos' suggestion 1640 # 1641 # Revision 1.2 2004/03/25 09:53:30 ncq 1642 # - added log keyword 1643 # 1644