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  __version__ = "$Revision: 1.138 $" 
  13  __author__ = "Carlos Moro" 
  14  __license__ = 'GPL' 
  15   
  16  import os.path, sys, types, time, codecs, datetime as pyDT, logging, shutil 
  17   
  18   
  19  import mx.DateTime.Parser as mxParser 
  20  import mx.DateTime as mxDT 
  21   
  22   
  23  if __name__ == '__main__': 
  24          sys.path.insert(0, '../../') 
  25  from Gnumed.pycommon import gmI18N, gmExceptions, gmNull, gmPG2, gmTools 
  26  from Gnumed.business import gmClinicalRecord, gmPerson, gmAllergy, gmDemographicRecord, gmClinNarrative, gmPersonSearch 
  27   
  28   
  29  _log = logging.getLogger('gm.export') 
  30  _log.info(__version__) 
  31  #============================================================ 
32 -class cEmrExport:
33 34 #--------------------------------------------------------
35 - def __init__(self, constraints = None, fileout = None, patient = None):
36 """ 37 Constructs a new instance of exporter 38 39 constraints - Exporter constraints for filtering clinical items 40 fileout - File-like object as target for dumping operations 41 """ 42 if constraints is None: 43 # default constraints to None for complete emr dump 44 self.__constraints = { 45 'since': None, 46 'until': None, 47 'encounters': None, 48 'episodes': None, 49 'issues': None 50 } 51 else: 52 self.__constraints = constraints 53 self.__target = fileout 54 self.__patient = patient 55 self.lab_new_encounter = True 56 self.__filtered_items = []
57 #--------------------------------------------------------
58 - def set_constraints(self, constraints = None):
59 """Sets exporter constraints. 60 61 constraints - Exporter constraints for filtering clinical items 62 """ 63 if constraints is None: 64 # default constraints to None for complete emr dump 65 self.__constraints = { 66 'since': None, 67 'until': None, 68 'encounters': None, 69 'episodes': None, 70 'issues': None 71 } 72 else: 73 self.__constraints = constraints 74 return True
75 #--------------------------------------------------------
76 - def get_constraints(self):
77 """ 78 Retrieve exporter constraints 79 """ 80 return self.__constraints
81 #--------------------------------------------------------
82 - def set_patient(self, patient=None):
83 """ 84 Sets exporter patient 85 86 patient - Patient whose data are to be dumped 87 """ 88 if patient is None: 89 _log.error("can't set None patient for exporter") 90 return 91 self.__patient = patient
92 #--------------------------------------------------------
93 - def set_output_file(self, target=None):
94 """ 95 Sets exporter output file 96 97 @param file_name - The file to dump the EMR to 98 @type file_name - FileType 99 """ 100 self.__target = target
101 #--------------------------------------------------------
102 - def get_patient(self):
103 """ 104 Retrieves patient whose data are to be dumped 105 """ 106 return self.__patient
107 #--------------------------------------------------------
108 - def cleanup(self):
109 """ 110 Exporter class cleanup code 111 """ 112 pass
113 #--------------------------------------------------------
114 - def __dump_vacc_table(self, vacc_regimes):
115 """ 116 Retrieves string containg ASCII vaccination table 117 """ 118 emr = self.__patient.get_emr() 119 # patient dob 120 patient_dob = self.__patient['dob'] 121 date_length = len(patient_dob.strftime('%x')) + 2 122 123 # dictionary of pairs indication : scheduled vaccination 124 vaccinations4regimes = {} 125 for a_vacc_regime in vacc_regimes: 126 indication = a_vacc_regime['indication'] 127 vaccinations4regimes[indication] = emr.get_scheduled_vaccinations(indications=[indication]) 128 # vaccination regimes count 129 chart_columns = len(vacc_regimes) 130 # foot headers 131 foot_headers = ['last booster', 'next booster'] 132 # string for both: ending of vaccinations; non boosters needed 133 ending_str = '=' 134 135 # chart row count, columns width and vaccination dictionary of pairs indication : given shot 136 column_widths = [] 137 chart_rows = -1 138 vaccinations = {} 139 temp = -1 140 for foot_header in foot_headers: # first column width 141 if len(foot_header) > temp: 142 temp = len(foot_header) 143 column_widths.append(temp) 144 for a_vacc_regime in vacc_regimes: 145 if a_vacc_regime['shots'] > chart_rows: # max_seq -> row count 146 chart_rows = a_vacc_regime['shots'] 147 if (len(a_vacc_regime['l10n_indication'])) > date_length: # l10n indication -> column width 148 column_widths.append(len(a_vacc_regime['l10n_indication'])) 149 else: 150 column_widths.append(date_length) # date -> column width at least 151 vaccinations[a_vacc_regime['indication']] = emr.get_vaccinations(indications=[a_vacc_regime['indication']]) # given shots 4 indication 152 153 # patient dob in top of vaccination chart 154 txt = '\nDOB: %s' %(patient_dob.strftime('%x')) + '\n' 155 156 # vacc chart table headers 157 # top ---- header line 158 for column_width in column_widths: 159 txt += column_width * '-' + '-' 160 txt += '\n' 161 # indication names header line 162 txt += column_widths[0] * ' ' + '|' 163 col_index = 1 164 for a_vacc_regime in vacc_regimes: 165 txt += a_vacc_regime['l10n_indication'] + (column_widths[col_index] - len(a_vacc_regime['l10n_indication'])) * ' ' + '|' 166 col_index += 1 167 txt += '\n' 168 # bottom ---- header line 169 for column_width in column_widths: 170 txt += column_width * '-' + '-' 171 txt += '\n' 172 173 # vacc chart data 174 due_date = None 175 # previously displayed date list 176 prev_displayed_date = [patient_dob] 177 for a_regime in vacc_regimes: 178 prev_displayed_date.append(patient_dob) # initialice with patient dob (useful for due first shot date calculation) 179 # iterate data rows 180 for row_index in range(0, chart_rows): 181 row_header = '#%s' %(row_index+1) 182 txt += row_header + (column_widths[0] - len(row_header)) * ' ' + '|' 183 184 for col_index in range(1, chart_columns+1): 185 indication =vacc_regimes[col_index-1]['indication'] 186 seq_no = vacc_regimes[col_index-1]['shots'] 187 if row_index == seq_no: # had just ended scheduled vaccinations 188 txt += ending_str * column_widths[col_index] + '|' 189 elif row_index < seq_no: # vaccination scheduled 190 try: 191 vacc_date = vaccinations[indication][row_index]['date'] # vaccination given 192 vacc_date_str = vacc_date.strftime('%x') 193 txt += vacc_date_str + (column_widths[col_index] - len(vacc_date_str)) * ' ' + '|' 194 prev_displayed_date[col_index] = vacc_date 195 except: 196 if row_index == 0: # due first shot 197 due_date = prev_displayed_date[col_index] + vaccinations4regimes[indication][row_index]['age_due_min'] # FIXME 'age_due_min' not properly retrieved 198 else: # due any other than first shot 199 due_date = prev_displayed_date[col_index] + vaccinations4regimes[indication][row_index]['min_interval'] 200 txt += '('+ due_date.strftime('%Y-%m-%d') + ')' + (column_widths[col_index] - date_length) * ' ' + '|' 201 prev_displayed_date[col_index] = due_date 202 else: # not scheduled vaccination at that position 203 txt += column_widths[col_index] * ' ' + '|' 204 txt += '\n' # end of scheduled vaccination dates display 205 for column_width in column_widths: # ------ separator line 206 txt += column_width * '-' + '-' 207 txt += '\n' 208 209 # scheduled vaccination boosters (date retrieving) 210 all_vreg_boosters = [] 211 for a_vacc_regime in vacc_regimes: 212 vaccs4indication = vaccinations[a_vacc_regime['indication']] # iterate over vaccinations by indication 213 given_boosters = [] # will contain given boosters for current indication 214 for a_vacc in vaccs4indication: 215 try: 216 if a_vacc['is_booster']: 217 given_boosters.append(a_vacc) 218 except: 219 # not a booster 220 pass 221 if len(given_boosters) > 0: 222 all_vreg_boosters.append(given_boosters[len(given_boosters)-1]) # last of given boosters 223 else: 224 all_vreg_boosters.append(None) 225 226 # next booster in schedule 227 all_next_boosters = [] 228 for a_booster in all_vreg_boosters: 229 all_next_boosters.append(None) 230 # scheduled vaccination boosters (displaying string) 231 cont = 0 232 for a_vacc_regime in vacc_regimes: 233 vaccs = vaccinations4regimes[a_vacc_regime['indication']] 234 if vaccs[len(vaccs)-1]['is_booster'] == False: # booster is not scheduled/needed 235 all_vreg_boosters[cont] = ending_str * column_widths[cont+1] 236 all_next_boosters[cont] = ending_str * column_widths[cont+1] 237 else: 238 indication = vacc_regimes[cont]['indication'] 239 if len(vaccinations[indication]) > vacc_regimes[cont]['shots']: # boosters given 240 all_vreg_boosters[cont] = vaccinations[indication][len(vaccinations[indication])-1]['date'].strftime('%Y-%m-%d') # show last given booster date 241 scheduled_booster = vaccinations4regimes[indication][len(vaccinations4regimes[indication])-1] 242 booster_date = vaccinations[indication][len(vaccinations[indication])-1]['date'] + scheduled_booster['min_interval'] 243 if booster_date < mxDT.today(): 244 all_next_boosters[cont] = '<(' + booster_date.strftime('%Y-%m-%d') + ')>' # next booster is due 245 else: 246 all_next_boosters[cont] = booster_date.strftime('%Y-%m-%d') 247 elif len(vaccinations[indication]) == vacc_regimes[cont]['shots']: # just finished vaccinations, begin boosters 248 all_vreg_boosters[cont] = column_widths[cont+1] * ' ' 249 scheduled_booster = vaccinations4regimes[indication][len(vaccinations4regimes[indication])-1] 250 booster_date = vaccinations[indication][len(vaccinations[indication])-1]['date'] + scheduled_booster['min_interval'] 251 if booster_date < mxDT.today(): 252 all_next_boosters[cont] = '<(' + booster_date.strftime('%Y-%m-%d') + ')>' # next booster is due 253 else: 254 all_next_boosters[cont] = booster_date.strftime('%Y-%m-%d') 255 else: 256 all_vreg_boosters[cont] = column_widths[cont+1] * ' ' # unfinished schedule 257 all_next_boosters[cont] = column_widths[cont+1] * ' ' 258 cont += 1 259 260 # given boosters 261 foot_header = foot_headers[0] 262 col_index = 0 263 txt += foot_header + (column_widths[0] - len(foot_header)) * ' ' + '|' 264 col_index += 1 265 for a_vacc_regime in vacc_regimes: 266 txt += str(all_vreg_boosters[col_index-1]) + (column_widths[col_index] - len(str(all_vreg_boosters[col_index-1]))) * ' ' + '|' 267 col_index += 1 268 txt += '\n' 269 for column_width in column_widths: 270 txt += column_width * '-' + '-' 271 txt += '\n' 272 273 # next booster 274 foot_header = foot_headers[1] 275 col_index = 0 276 txt += foot_header + (column_widths[0] - len(foot_header)) * ' ' + '|' 277 col_index += 1 278 for a_vacc_regime in vacc_regimes: 279 txt += str(all_next_boosters[col_index-1]) + (column_widths[col_index] - len(str(all_next_boosters[col_index-1]))) * ' ' + '|' 280 col_index += 1 281 txt += '\n' 282 for column_width in column_widths: 283 txt += column_width * '-' + '-' 284 txt += '\n' 285 286 self.__target.write(txt)
287 #--------------------------------------------------------
288 - def get_vacc_table(self):
289 """ 290 Iterate over patient scheduled regimes preparing vacc tables dump 291 """ 292 293 emr = self.__patient.get_emr() 294 295 # vaccination regimes 296 all_vacc_regimes = emr.get_scheduled_vaccination_regimes() 297 # Configurable: vacc regimes per displayed table 298 # FIXME: option, post 0.1 ? 299 max_regs_per_table = 4 300 301 # Iterate over patient scheduled regimes dumping in tables of 302 # max_regs_per_table regimes per table 303 reg_count = 0 304 vacc_regimes = [] 305 for total_reg_count in range(0,len(all_vacc_regimes)): 306 if reg_count%max_regs_per_table == 0: 307 if len(vacc_regimes) > 0: 308 self.__dump_vacc_table(vacc_regimes) 309 vacc_regimes = [] 310 reg_count = 0 311 vacc_regimes.append(all_vacc_regimes[total_reg_count]) 312 reg_count += 1 313 if len(vacc_regimes) > 0: 314 self.__dump_vacc_table(vacc_regimes)
315 316 #--------------------------------------------------------
317 - def dump_item_fields(self, offset, item, field_list):
318 """ 319 Dump information related to the fields of a clinical item 320 offset - Number of left blank spaces 321 item - Item of the field to dump 322 fields - Fields to dump 323 """ 324 txt = '' 325 for a_field in field_list: 326 if type(a_field) is not types.UnicodeType: 327 a_field = unicode(a_field, encoding='latin1', errors='replace') 328 txt += u'%s%s%s' % ((offset * u' '), a_field, gmTools.coalesce(item[a_field], u'\n', template_initial = u': %s\n')) 329 return txt
330 #--------------------------------------------------------
331 - def get_allergy_output(self, allergy, left_margin = 0):
332 """ 333 Dumps allergy item data 334 allergy - Allergy item to dump 335 left_margin - Number of spaces on the left margin 336 """ 337 txt = '' 338 txt += left_margin*' ' + _('Allergy') + ': \n' 339 txt += self.dump_item_fields((left_margin+3), allergy, ['allergene', 'substance', 'generic_specific','l10n_type', 'definite', 'reaction']) 340 return txt
341 #--------------------------------------------------------
342 - def get_vaccination_output(self, vaccination, left_margin = 0):
343 """ 344 Dumps vaccination item data 345 vaccination - Vaccination item to dump 346 left_margin - Number of spaces on the left margin 347 """ 348 txt = '' 349 txt += left_margin*' ' + _('Vaccination') + ': \n' 350 txt += self.dump_item_fields((left_margin+3), vaccination, ['l10n_indication', 'vaccine', 'batch_no', 'site', 'narrative']) 351 return txt
352 #--------------------------------------------------------
353 - def get_lab_result_output(self, lab_result, left_margin = 0):
354 """ 355 Dumps lab result item data 356 lab_request - Lab request item to dump 357 left_margin - Number of spaces on the left margin 358 """ 359 txt = '' 360 if self.lab_new_encounter: 361 txt += (left_margin)*' ' + _('Lab result') + ': \n' 362 txt += (left_margin+3) * ' ' + lab_result['unified_name'] + ': ' + lab_result['unified_val']+ ' ' + lab_result['val_unit'] + ' (' + lab_result['material'] + ')' + '\n' 363 return txt
364 #--------------------------------------------------------
365 - def get_item_output(self, item, left_margin = 0):
366 """ 367 Obtains formatted clinical item output dump 368 item - The clinical item to dump 369 left_margin - Number of spaces on the left margin 370 """ 371 txt = '' 372 if isinstance(item, gmAllergy.cAllergy): 373 txt += self.get_allergy_output(item, left_margin) 374 # elif isinstance(item, gmVaccination.cVaccination): 375 # txt += self.get_vaccination_output(item, left_margin) 376 # elif isinstance(item, gmPathLab.cLabResult): 377 # txt += self.get_lab_result_output(item, left_margin) 378 # self.lab_new_encounter = False 379 return txt
380 #--------------------------------------------------------
381 - def __fetch_filtered_items(self):
382 """ 383 Retrieve patient clinical items filtered by multiple constraints 384 """ 385 if not self.__patient.connected: 386 return False 387 emr = self.__patient.get_emr() 388 filtered_items = [] 389 filtered_items.extend(emr.get_allergies( 390 since=self.__constraints['since'], 391 until=self.__constraints['until'], 392 encounters=self.__constraints['encounters'], 393 episodes=self.__constraints['episodes'], 394 issues=self.__constraints['issues'])) 395 # try: 396 # filtered_items.extend(emr.get_vaccinations( 397 # since=self.__constraints['since'], 398 # until=self.__constraints['until'], 399 # encounters=self.__constraints['encounters'], 400 # episodes=self.__constraints['episodes'], 401 # issues=self.__constraints['issues'])) 402 # except: 403 # _log.error("vaccination error? outside regime") 404 405 # filtered_items.extend(emr.get_lab_results( 406 # since=self.__constraints['since'], 407 # until=self.__constraints['until'], 408 # encounters=self.__constraints['encounters'], 409 # episodes=self.__constraints['episodes'], 410 # issues=self.__constraints['issues'])) 411 self.__filtered_items = filtered_items 412 return True
413 #--------------------------------------------------------
414 - def get_allergy_summary(self, allergy, left_margin = 0):
415 """ 416 Dumps allergy item data summary 417 allergy - Allergy item to dump 418 left_margin - Number of spaces on the left margin 419 """ 420 txt = _('%sAllergy: %s, %s (noted %s)\n') % ( 421 left_margin * u' ', 422 allergy['descriptor'], 423 gmTools.coalesce(allergy['reaction'], _('unknown reaction')), 424 allergy['date'].strftime('%x') 425 ) 426 # txt = left_margin * ' ' \ 427 # + _('Allergy') + ': ' \ 428 # + allergy['descriptor'] + u', ' \ 429 # + gmTools.coalesce(allergy['reaction'], _('unknown reaction')) ' ' \ 430 # + _('(noted %s)') % allergy['date'].strftime('%x') \ 431 # + '\n' 432 return txt
433 #--------------------------------------------------------
434 - def get_vaccination_summary(self, vaccination, left_margin = 0):
435 """ 436 Dumps vaccination item data summary 437 vaccination - Vaccination item to dump 438 left_margin - Number of spaces on the left margin 439 """ 440 txt = left_margin*' ' + _('Vaccination') + ': ' + vaccination['l10n_indication'] + ', ' + \ 441 vaccination['narrative'] + '\n' 442 return txt
443 #--------------------------------------------------------
444 - def get_lab_result_summary(self, lab_result, left_margin = 0):
445 """ 446 Dumps lab result item data summary 447 lab_request - Lab request item to dump 448 left_margin - Number of spaces on the left margin 449 """ 450 txt = '' 451 if self.lab_new_encounter: 452 txt += (left_margin+3)*' ' + _('Lab') + ': ' + \ 453 lab_result['unified_name'] + '-> ' + lab_result['unified_val'] + \ 454 ' ' + lab_result['val_unit']+ '\n' + '(' + lab_result['req_when'].strftime('%Y-%m-%d') + ')' 455 return txt
456 #--------------------------------------------------------
457 - def get_item_summary(self, item, left_margin = 0):
458 """ 459 Obtains formatted clinical item summary dump 460 item - The clinical item to dump 461 left_margin - Number of spaces on the left margin 462 """ 463 txt = '' 464 if isinstance(item, gmAllergy.cAllergy): 465 txt += self.get_allergy_summary(item, left_margin) 466 # elif isinstance(item, gmVaccination.cVaccination): 467 # txt += self.get_vaccination_summary(item, left_margin) 468 # elif isinstance(item, gmPathLab.cLabResult) and \ 469 # True: 470 # #(item['relevant'] == True or item['abnormal'] == True): 471 # txt += self.get_lab_result_summary(item, left_margin) 472 # self.lab_new_encounter = False 473 return txt
474 #--------------------------------------------------------
475 - def refresh_historical_tree(self, emr_tree):
476 """ 477 checks a emr_tree constructed with this.get_historical_tree() 478 and sees if any new items need to be inserted. 479 """ 480 #TODO , caching eliminates tree update time, so don't really need this 481 self._traverse_health_issues( emr_tree, self._update_health_issue_branch)
482 #--------------------------------------------------------
483 - def get_historical_tree( self, emr_tree):
484 self._traverse_health_issues( emr_tree, self._add_health_issue_branch)
485 #--------------------------------------------------------
486 - def _traverse_health_issues(self, emr_tree, health_issue_action):
487 """ 488 Retrieves patient's historical in form of a wx tree of health issues 489 -> episodes 490 -> encounters 491 Encounter object is associated with item to allow displaying its information 492 """ 493 # variable initialization 494 # this protects the emrBrowser from locking up in a paint event, e.g. in 495 # some configurations which want to use emrBrowser, but don't stop tabbing 496 # to emr browser when no patient selected. the effect is to displace a cNull instance 497 # which is a sane representation when no patient is selected. 498 if not self.__fetch_filtered_items(): 499 return 500 emr = self.__patient.get_emr() 501 unlinked_episodes = emr.get_episodes(issues = [None]) 502 h_issues = [] 503 h_issues.extend(emr.get_health_issues(id_list = self.__constraints['issues'])) 504 # build the tree 505 # unlinked episodes 506 if len(unlinked_episodes) > 0: 507 h_issues.insert(0, { 508 'description': _('Unattributed episodes'), 509 'pk_health_issue': None 510 }) 511 # existing issues 512 for a_health_issue in h_issues: 513 health_issue_action( emr_tree, a_health_issue) 514 515 root_item = emr_tree.GetRootItem() 516 if len(h_issues) == 0: 517 emr_tree.SetItemHasChildren(root_item, False) 518 else: 519 emr_tree.SetItemHasChildren(root_item, True) 520 emr_tree.SortChildren(root_item)
521 #--------------------------------------------------------
522 - def _add_health_issue_branch( self, emr_tree, a_health_issue):
523 """appends to a wx emr_tree , building wx treenodes from the health_issue make this reusable for non-collapsing tree updates""" 524 emr = self.__patient.get_emr() 525 root_node = emr_tree.GetRootItem() 526 issue_node = emr_tree.AppendItem(root_node, a_health_issue['description']) 527 emr_tree.SetItemPyData(issue_node, a_health_issue) 528 episodes = emr.get_episodes(id_list=self.__constraints['episodes'], issues = [a_health_issue['pk_health_issue']]) 529 if len(episodes) == 0: 530 emr_tree.SetItemHasChildren(issue_node, False) 531 else: 532 emr_tree.SetItemHasChildren(issue_node, True) 533 for an_episode in episodes: 534 self._add_episode_to_tree( emr, emr_tree, issue_node,a_health_issue, an_episode) 535 emr_tree.SortChildren(issue_node)
536 #--------------------------------------------------------
537 - def _add_episode_to_tree( self, emr , emr_tree, issue_node, a_health_issue, an_episode):
538 episode_node = emr_tree.AppendItem(issue_node, an_episode['description']) 539 emr_tree.SetItemPyData(episode_node, an_episode) 540 if an_episode['episode_open']: 541 emr_tree.SetItemBold(issue_node, True) 542 543 encounters = self._get_encounters( an_episode, emr ) 544 if len(encounters) == 0: 545 emr_tree.SetItemHasChildren(episode_node, False) 546 else: 547 emr_tree.SetItemHasChildren(episode_node, True) 548 self._add_encounters_to_tree( encounters, emr_tree, episode_node ) 549 emr_tree.SortChildren(episode_node) 550 return episode_node
551 #--------------------------------------------------------
552 - def _add_encounters_to_tree( self, encounters, emr_tree, episode_node):
553 for an_encounter in encounters: 554 # label = u'%s: %s' % (an_encounter['started'].strftime('%Y-%m-%d'), an_encounter['l10n_type']) 555 label = u'%s: %s' % ( 556 an_encounter['started'].strftime('%Y-%m-%d'), 557 gmTools.unwrap ( 558 gmTools.coalesce ( 559 gmTools.coalesce ( 560 gmTools.coalesce ( 561 an_encounter.get_latest_soap ( # soAp 562 soap_cat = 'a', 563 episode = emr_tree.GetPyData(episode_node)['pk_episode'] 564 ), 565 an_encounter['assessment_of_encounter'] # or AOE 566 ), 567 an_encounter['reason_for_encounter'] # or RFE 568 ), 569 an_encounter['l10n_type'] # or type 570 ), 571 max_length = 40 572 ) 573 ) 574 encounter_node_id = emr_tree.AppendItem(episode_node, label) 575 emr_tree.SetItemPyData(encounter_node_id, an_encounter) 576 emr_tree.SetItemHasChildren(encounter_node_id, False)
577 #--------------------------------------------------------
578 - def _get_encounters ( self, an_episode, emr ):
579 encounters = emr.get_encounters ( 580 episodes = [an_episode['pk_episode']] 581 ) 582 return encounters
583 #--------------------------------------------------------
584 - def _update_health_issue_branch(self, emr_tree, a_health_issue):
585 emr = self.__patient.get_emr() 586 root_node = emr_tree.GetRootItem() 587 id, cookie = emr_tree.GetFirstChild(root_node) 588 found = False 589 while id.IsOk(): 590 if emr_tree.GetItemText(id) == a_health_issue['description']: 591 found = True 592 break 593 id,cookie = emr_tree.GetNextChild( root_node, cookie) 594 595 if not found: 596 _log.error("health issue %s should exist in tree already", a_health_issue['description'] ) 597 return 598 issue_node = id 599 episodes = emr.get_episodes(id_list=self.__constraints['episodes'], issues = [a_health_issue['pk_health_issue']]) 600 601 #check for removed episode and update tree 602 tree_episodes = {} 603 id_episode, cookie = emr_tree.GetFirstChild(issue_node) 604 while id_episode.IsOk(): 605 tree_episodes[ emr_tree.GetPyData(id_episode)['pk_episode'] ]= id_episode 606 id_episode,cookie = emr_tree.GetNextChild( issue_node, cookie) 607 608 existing_episode_pk = [ e['pk_episode'] for e in episodes] 609 missing_tree_pk = [ pk for pk in tree_episodes.keys() if pk not in existing_episode_pk] 610 for pk in missing_tree_pk: 611 emr_tree.Remove( tree_episodes[pk] ) 612 613 added_episode_pk = [pk for pk in existing_episode_pk if pk not in tree_episodes.keys()] 614 add_episodes = [ e for e in episodes if e['pk_episode'] in added_episode_pk] 615 616 #check for added episodes and update tree 617 for an_episode in add_episodes: 618 node = self._add_episode_to_tree( emr, emr_tree, issue_node, a_health_issue, an_episode) 619 tree_episodes[an_episode['pk_episode']] = node 620 621 for an_episode in episodes: 622 # found episode, check for encounter change 623 try: 624 #print "getting id_episode of ", an_episode['pk_episode'] 625 id_episode = tree_episodes[an_episode['pk_episode']] 626 except: 627 import pdb 628 pdb.set_trace() 629 # get a map of encounters in the tree by pk_encounter as key 630 tree_enc = {} 631 id_encounter, cookie = emr_tree.GetFirstChild(id_episode) 632 while id_encounter.IsOk(): 633 tree_enc[ emr_tree.GetPyData(id_encounter)['pk_encounter'] ] = id_encounter 634 id_encounter,cookie = emr_tree.GetNextChild(id_episode, cookie) 635 636 # remove encounters in tree not in existing encounters in episode 637 # encounters = self._get_encounters( a_health_issue, an_episode, emr ) 638 encounters = self._get_encounters( an_episode, emr ) 639 existing_enc_pk = [ enc['pk_encounter'] for enc in encounters] 640 missing_enc_pk = [ pk for pk in tree_enc.keys() if pk not in existing_enc_pk] 641 for pk in missing_enc_pk: 642 emr_tree.Remove( tree_enc[pk] ) 643 644 # check for added encounter 645 added_enc_pk = [ pk for pk in existing_enc_pk if pk not in tree_enc.keys() ] 646 add_encounters = [ enc for enc in encounters if enc['pk_encounter'] in added_enc_pk] 647 if add_encounters != []: 648 #print "DEBUG found encounters to add" 649 self._add_encounters_to_tree( add_encounters, emr_tree, id_episode)
650 #--------------------------------------------------------
651 - def get_summary_info(self, left_margin = 0):
652 """ 653 Dumps patient EMR summary 654 """ 655 txt = '' 656 for an_item in self.__filtered_items: 657 txt += self.get_item_summary(an_item, left_margin) 658 return txt
659 #--------------------------------------------------------
660 - def get_episode_summary (self, episode, left_margin = 0):
661 """Dumps episode specific data""" 662 emr = self.__patient.get_emr() 663 encs = emr.get_encounters(episodes = [episode['pk_episode']]) 664 if encs is None: 665 txt = left_margin * ' ' + _('Error retrieving encounters for episode\n%s') % str(episode) 666 return txt 667 no_encs = len(encs) 668 if no_encs == 0: 669 txt = left_margin * ' ' + _('There are no encounters for this episode.') 670 return txt 671 if episode['episode_open']: 672 status = _('active') 673 else: 674 status = _('finished') 675 first_encounter = emr.get_first_encounter(episode_id = episode['pk_episode']) 676 last_encounter = emr.get_last_encounter(episode_id = episode['pk_episode']) 677 txt = _( 678 '%sEpisode "%s" [%s]\n' 679 '%sEncounters: %s (%s - %s)\n' 680 '%sLast worked on: %s\n' 681 ) % ( 682 left_margin * ' ', episode['description'], status, 683 left_margin * ' ', no_encs, first_encounter['started'].strftime('%m/%Y'), last_encounter['last_affirmed'].strftime('%m/%Y'), 684 left_margin * ' ', last_encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M') 685 ) 686 return txt
687 #--------------------------------------------------------
688 - def get_encounter_info(self, episode, encounter, left_margin = 0):
689 """ 690 Dumps encounter specific data (rfe, aoe and soap) 691 """ 692 emr = self.__patient.get_emr() 693 # general 694 txt = (' ' * left_margin) + '#%s: %s - %s %s' % ( 695 encounter['pk_encounter'], 696 encounter['started'].strftime('%Y-%m-%d %H:%M'), 697 encounter['last_affirmed'].strftime('%H:%M (%Z)'), 698 encounter['l10n_type'] 699 ) 700 if (encounter['assessment_of_encounter'] is not None) and (len(encounter['assessment_of_encounter']) > 0): 701 txt += ' "%s"' % encounter['assessment_of_encounter'] 702 txt += '\n\n' 703 704 # rfe/aoe 705 txt += (' ' * left_margin) + '%s: %s\n' % (_('RFE'), encounter['reason_for_encounter']) 706 txt += (' ' * left_margin) + '%s: %s\n' % (_('AOE'), encounter['assessment_of_encounter']) 707 708 # soap 709 soap_cat_labels = { 710 's': _('Subjective'), 711 'o': _('Objective'), 712 'a': _('Assessment'), 713 'p': _('Plan'), 714 None: _('Administrative') 715 } 716 eol_w_margin = '\n' + (' ' * (left_margin+3)) 717 for soap_cat in 'soap': 718 soap_cat_narratives = emr.get_clin_narrative ( 719 episodes = [episode['pk_episode']], 720 encounters = [encounter['pk_encounter']], 721 soap_cats = [soap_cat] 722 ) 723 if soap_cat_narratives is None: 724 continue 725 if len(soap_cat_narratives) == 0: 726 continue 727 txt += (' ' * left_margin) + soap_cat_labels[soap_cat] + ':\n' 728 for soap_entry in soap_cat_narratives: 729 txt += gmTools.wrap ( 730 '%s %.8s: %s\n' % ( 731 soap_entry['date'].strftime('%d.%m. %H:%M'), 732 soap_entry['provider'], 733 soap_entry['narrative'] 734 ), 75 735 ) 736 737 # txt += ( 738 # (' ' * (left_margin+3)) + 739 # soap_entry['date'].strftime('%H:%M %.8s: ') % soap_entry['provider'] + 740 # soap_entry['narrative'].replace('\n', eol_w_margin) + 741 # '\n' 742 # ) 743 #FIXME: add diagnoses 744 745 # items 746 for an_item in self.__filtered_items: 747 if an_item['pk_encounter'] == encounter['pk_encounter']: 748 txt += self.get_item_output(an_item, left_margin) 749 return txt
750 #--------------------------------------------------------
751 - def dump_historical_tree(self):
752 """Dumps patient's historical in form of a tree of health issues 753 -> episodes 754 -> encounters 755 -> clinical items 756 """ 757 758 # fecth all values 759 self.__fetch_filtered_items() 760 emr = self.__patient.get_emr() 761 762 # dump clinically relevant items summary 763 for an_item in self.__filtered_items: 764 self.__target.write(self.get_item_summary(an_item, 3)) 765 766 # begin with the tree 767 h_issues = [] 768 h_issues.extend(emr.get_health_issues(id_list = self.__constraints['issues'])) 769 # unlinked episodes 770 unlinked_episodes = emr.get_episodes(issues = [None]) 771 if len(unlinked_episodes) > 0: 772 h_issues.insert(0, {'description':_('Unattributed episodes'), 'pk_health_issue':None}) 773 for a_health_issue in h_issues: 774 self.__target.write('\n' + 3*' ' + 'Health Issue: ' + a_health_issue['description'] + '\n') 775 episodes = emr.get_episodes(id_list=self.__constraints['episodes'], issues = [a_health_issue['pk_health_issue']]) 776 for an_episode in episodes: 777 self.__target.write('\n' + 6*' ' + 'Episode: ' + an_episode['description'] + '\n') 778 if a_health_issue['pk_health_issue'] is None: 779 issues = None 780 else: 781 issues = [a_health_issue['pk_health_issue']] 782 encounters = emr.get_encounters ( 783 since = self.__constraints['since'], 784 until = self.__constraints['until'], 785 id_list = self.__constraints['encounters'], 786 episodes = [an_episode['pk_episode']], 787 issues = issues 788 ) 789 for an_encounter in encounters: 790 # title 791 self.lab_new_encounter = True 792 self.__target.write( 793 '\n %s %s: %s - %s (%s)\n' % ( 794 _('Encounter'), 795 an_encounter['l10n_type'], 796 an_encounter['started'].strftime('%A, %Y-%m-%d %H:%M'), 797 an_encounter['last_affirmed'].strftime('%m-%d %H:%M'), 798 an_encounter['assessment_of_encounter'] 799 ) 800 ) 801 self.__target.write(self.get_encounter_info(an_episode, an_encounter, 12))
802 #--------------------------------------------------------
803 - def dump_clinical_record(self):
804 """ 805 Dumps in ASCII format patient's clinical record 806 """ 807 emr = self.__patient.get_emr() 808 if emr is None: 809 _log.error('cannot get EMR text dump') 810 print(_( 811 'An error occurred while retrieving a text\n' 812 'dump of the EMR for the active patient.\n\n' 813 'Please check the log file for details.' 814 )) 815 return None 816 self.__target.write('\nOverview\n') 817 self.__target.write('--------\n') 818 self.__target.write("1) Allergy status (for details, see below):\n\n") 819 for allergy in emr.get_allergies(): 820 self.__target.write(" " + allergy['descriptor'] + "\n\n") 821 self.__target.write("2) Vaccination status (* indicates booster):\n") 822 # self.get_vacc_table() 823 self.__target.write("\n3) Historical:\n\n") 824 self.dump_historical_tree() 825 826 try: 827 emr.cleanup() 828 except: 829 print "error cleaning up EMR"
830 #--------------------------------------------------------
831 - def dump_med_docs(self):
832 """ 833 Dumps patient stored medical documents 834 835 """ 836 doc_folder = self.__patient.get_document_folder() 837 838 self.__target.write('\n4) Medical documents: (date) reference - type "comment"\n') 839 self.__target.write(' object - comment') 840 841 docs = doc_folder.get_documents() 842 for doc in docs: 843 self.__target.write('\n\n (%s) %s - %s "%s"' % ( 844 doc['clin_when'].strftime('%Y-%m-%d'), 845 doc['ext_ref'], 846 doc['l10n_type'], 847 doc['comment']) 848 ) 849 for part in doc.parts: 850 self.__target.write('\n %s - %s' % ( 851 part['seq_idx'], 852 part['obj_comment']) 853 ) 854 self.__target.write('\n\n')
855 #--------------------------------------------------------
856 - def dump_demographic_record(self, all = False):
857 """ 858 Dumps in ASCII format some basic patient's demographic data 859 """ 860 if self.__patient is None: 861 _log.error('cannot get Demographic export') 862 print(_( 863 'An error occurred while Demographic record export\n' 864 'Please check the log file for details.' 865 )) 866 return None 867 868 self.__target.write('\n\n\nDemographics') 869 self.__target.write('\n------------\n') 870 self.__target.write(' Id: %s \n' % self.__patient['pk_identity']) 871 cont = 0 872 for name in self.__patient.get_names(): 873 if cont == 0: 874 self.__target.write(' Name (Active): %s, %s\n' % (name['firstnames'], name['lastnames']) ) 875 else: 876 self.__target.write(' Name %s: %s, %s\n' % (cont, name['firstnames'], name['lastnames'])) 877 cont += 1 878 self.__target.write(' Gender: %s\n' % self.__patient['gender']) 879 self.__target.write(' Title: %s\n' % self.__patient['title']) 880 self.__target.write(' Dob: %s\n' % self.__patient.get_formatted_dob(format = '%Y-%m-%d')) 881 self.__target.write(' Medical age: %s\n' % self.__patient.get_medical_age())
882 #--------------------------------------------------------
883 - def dump_constraints(self):
884 """ 885 Dumps exporter filtering constraints 886 """ 887 self.__first_constraint = True 888 if not self.__constraints['since'] is None: 889 self.dump_constraints_header() 890 self.__target.write('\nSince: %s' % self.__constraints['since'].strftime('%Y-%m-%d')) 891 892 if not self.__constraints['until'] is None: 893 self.dump_constraints_header() 894 self.__target.write('\nUntil: %s' % self.__constraints['until'].strftime('%Y-%m-%d')) 895 896 if not self.__constraints['encounters'] is None: 897 self.dump_constraints_header() 898 self.__target.write('\nEncounters: ') 899 for enc in self.__constraints['encounters']: 900 self.__target.write(str(enc) + ' ') 901 902 if not self.__constraints['episodes'] is None: 903 self.dump_constraints_header() 904 self.__target.write('\nEpisodes: ') 905 for epi in self.__constraints['episodes']: 906 self.__target.write(str(epi) + ' ') 907 908 if not self.__constraints['issues'] is None: 909 self.dump_constraints_header() 910 self.__target.write('\nIssues: ') 911 for iss in self.__constraints['issues']: 912 self.__target.write(str(iss) + ' ')
913 #--------------------------------------------------------
914 - def dump_constraints_header(self):
915 """ 916 Dumps constraints header 917 """ 918 if self.__first_constraint == True: 919 self.__target.write('\nClinical items dump constraints\n') 920 self.__target.write('-'*(len(head_txt)-2)) 921 self.__first_constraint = False
922 #============================================================
923 -class cEMRJournalExporter:
924 """Exports patient EMR into a simple chronological journal. 925 926 Note that this export will emit u'' strings only. 927 """
928 - def __init__(self):
929 self.__part_len = 72
930 #-------------------------------------------------------- 931 # external API 932 #--------------------------------------------------------
933 - def export_to_file(self, filename=None, patient=None):
934 """Export medical record into a file. 935 936 @type filename: None (creates filename by itself) or string 937 @type patient: None (use currently active patient) or <gmPerson.cIdentity> instance 938 """ 939 if patient is None: 940 patient = gmPerson.gmCurrentPatient() 941 if not patient.connected: 942 raise ValueError('[%s].export_to_file(): no active patient' % self.__class__.__name__) 943 944 if filename is None: 945 filename = u'%s-%s-%s-%s.txt' % ( 946 _('emr-journal'), 947 patient['lastnames'].replace(u' ', u'_'), 948 patient['firstnames'].replace(u' ', u'_'), 949 patient.get_formatted_dob(format = '%Y-%m-%d') 950 ) 951 path = os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR', patient['dirname'], filename)) 952 953 f = codecs.open(filename = filename, mode = 'w+b', encoding = 'utf8', errors = 'replace') 954 self.export(target = f, patient = patient) 955 f.close() 956 return filename
957 #-------------------------------------------------------- 958 # internal API 959 #--------------------------------------------------------
960 - def export(self, target=None, patient=None):
961 """ 962 Export medical record into a Python object. 963 964 @type target: a python object supporting the write() API 965 @type patient: None (use currently active patient) or <gmPerson.cIdentity> instance 966 """ 967 if patient is None: 968 patient = gmPerson.gmCurrentPatient() 969 if not patient.connected: 970 raise ValueError('[%s].export(): no active patient' % self.__class__.__name__) 971 972 # write header 973 txt = _('Chronological EMR Journal\n') 974 target.write(txt) 975 target.write(u'=' * (len(txt)-1)) 976 target.write('\n') 977 target.write(_('Patient: %s (%s), No: %s\n') % (patient['description'], patient['gender'], patient['pk_identity'])) 978 target.write(_('Born : %s, age: %s\n\n') % ( 979 patient.get_formatted_dob(format = '%x', encoding = gmI18N.get_encoding()), 980 patient.get_medical_age() 981 )) 982 target.write(u'.-%10.10s---%9.9s-------%72.72s\n' % (u'-' * 10, u'-' * 9, u'-' * self.__part_len)) 983 target.write(u'| %10.10s | %9.9s | | %s\n' % (_('Happened'), _('Doc'), _('Narrative'))) 984 target.write(u'|-%10.10s---%9.9s-------%72.72s\n' % (u'-' * 10, u'-' * 9, u'-' * self.__part_len)) 985 986 # get data 987 cmd = u""" 988 select 989 to_char(vemrj.clin_when, 'YYYY-MM-DD') as date, 990 vemrj.*, 991 (select rank from clin.soap_cat_ranks where soap_cat = vemrj.soap_cat) as scr, 992 to_char(vemrj.modified_when, 'YYYY-MM-DD HH24:MI') as date_modified 993 from clin.v_emr_journal vemrj 994 where pk_patient = %s 995 order by date, pk_episode, scr, src_table""" 996 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [patient['pk_identity']]}], get_col_idx = True) 997 998 # write data 999 prev_date = u'' 1000 prev_doc = u'' 1001 prev_soap = u'' 1002 for row in rows: 1003 # narrative 1004 if row['narrative'] is None: 1005 continue 1006 1007 txt = gmTools.wrap ( 1008 text = row['narrative'].replace(u'\r', u'') + (u' (%s)' % row['date_modified']), 1009 width = self.__part_len 1010 ).split('\n') 1011 1012 # same provider ? 1013 curr_doc = row['modified_by'] 1014 if curr_doc != prev_doc: 1015 prev_doc = curr_doc 1016 else: 1017 curr_doc = u'' 1018 1019 # same soap category ? 1020 curr_soap = row['soap_cat'] 1021 if curr_soap != prev_soap: 1022 prev_soap = curr_soap 1023 1024 # same date ? 1025 curr_date = row['date'] 1026 if curr_date != prev_date: 1027 prev_date = curr_date 1028 curr_doc = row['modified_by'] 1029 prev_doc = curr_doc 1030 curr_soap = row['soap_cat'] 1031 prev_soap = curr_soap 1032 else: 1033 curr_date = u'' 1034 1035 # display first part 1036 target.write(u'| %10.10s | %9.9s | %3.3s | %s\n' % ( 1037 curr_date, 1038 curr_doc, 1039 gmClinNarrative.soap_cat2l10n[curr_soap], 1040 txt[0] 1041 )) 1042 1043 # only one part ? 1044 if len(txt) == 1: 1045 continue 1046 1047 template = u'| %10.10s | %9.9s | %3.3s | %s\n' 1048 for part in txt[1:]: 1049 line = template % (u'', u'', u' ', part) 1050 target.write(line) 1051 1052 # write footer 1053 target.write(u'`-%10.10s---%9.9s-------%72.72s\n\n' % (u'-' * 10, u'-' * 9, u'-' * self.__part_len)) 1054 target.write(_('Exported: %s\n') % pyDT.datetime.now().strftime('%c').decode(gmI18N.get_encoding())) 1055 1056 return
1057 #============================================================
1058 -class cMedistarSOAPExporter:
1059 """Export SOAP data per encounter into Medistar import format."""
1060 - def __init__(self, patient=None):
1061 if patient is None: 1062 self.__pat = gmPerson.gmCurrentPatient() 1063 else: 1064 if not isinstance(patient, gmPerson.cIdentity): 1065 raise gmExceptions.ConstructorError, '<patient> argument must be instance of <cIdentity>, but is: %s' % type(patient) 1066 self.__pat = patient
1067 #-------------------------------------------------------- 1068 # external API 1069 #--------------------------------------------------------
1070 - def export_to_file(self, filename=None, encounter=None, soap_cats=u'soap', export_to_import_file=False):
1071 if not self.__pat.connected: 1072 return (False, 'no active patient') 1073 1074 if filename is None: 1075 path = os.path.abspath(os.path.expanduser('~/gnumed/export')) 1076 filename = '%s-%s-%s-%s-%s.txt' % ( 1077 os.path.join(path, 'Medistar-MD'), 1078 time.strftime('%Y-%m-%d',time.localtime()), 1079 self.__pat['lastnames'].replace(' ', '-'), 1080 self.__pat['firstnames'].replace(' ', '_'), 1081 self.__pat.get_formatted_dob(format = '%Y-%m-%d') 1082 ) 1083 1084 f = codecs.open(filename = filename, mode = 'w+b', encoding = 'cp437', errors='replace') 1085 status = self.__export(target = f, encounter = encounter, soap_cats = soap_cats) 1086 f.close() 1087 1088 if export_to_import_file: 1089 # detect "LW:\medistar\inst\soap.txt" 1090 medistar_found = False 1091 for drive in u'cdefghijklmnopqrstuvwxyz': 1092 path = drive + ':\\medistar\\inst' 1093 if not os.path.isdir(path): 1094 continue 1095 try: 1096 import_fname = path + '\\soap.txt' 1097 open(import_fname, mode = 'w+b').close() 1098 _log.debug('exporting narrative to [%s] for Medistar import', import_fname) 1099 shutil.copyfile(filename, import_fname) 1100 medistar_found = True 1101 except IOError: 1102 continue 1103 1104 if not medistar_found: 1105 _log.debug('no Medistar installation found (no <LW>:\\medistar\\inst\\)') 1106 1107 return (status, filename)
1108 #--------------------------------------------------------
1109 - def export(self, target, encounter=None, soap_cats=u'soap'):
1110 return self.__export(target, encounter = encounter, soap_cats = soap_cats)
1111 #-------------------------------------------------------- 1112 # interal API 1113 #--------------------------------------------------------
1114 - def __export(self, target=None, encounter=None, soap_cats=u'soap'):
1115 # get data 1116 cmd = u"select narrative from clin.v_emr_journal where pk_patient=%s and pk_encounter=%s and soap_cat=%s" 1117 for soap_cat in soap_cats: 1118 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': (self.__pat['pk_identity'], encounter['pk_encounter'], soap_cat)}]) 1119 target.write('*MD%s*\r\n' % gmClinNarrative.soap_cat2l10n[soap_cat]) 1120 for row in rows: 1121 text = row[0] 1122 if text is None: 1123 continue 1124 target.write('%s\r\n' % gmTools.wrap ( 1125 text = text, 1126 width = 64, 1127 eol = u'\r\n' 1128 )) 1129 return True
1130 #============================================================ 1131 # main 1132 #------------------------------------------------------------
1133 -def usage():
1134 """ 1135 Prints application usage options to stdout. 1136 """ 1137 print 'usage: python gmPatientExporter [--fileout=<outputfilename>] [--conf-file=<file>] [--text-domain=<textdomain>]' 1138 sys.exit(0)
1139 #------------------------------------------------------------
1140 -def run():
1141 """ 1142 Main module application execution loop. 1143 """ 1144 # More variable initializations 1145 patient = None 1146 patient_id = None 1147 patient_term = None 1148 pat_searcher = gmPersonSearch.cPatientSearcher_SQL() 1149 1150 # App execution loop 1151 while patient_term != 'bye': 1152 patient = gmPersonSearch.ask_for_patient() 1153 if patient is None: 1154 break 1155 # FIXME: needed ? 1156 # gmPerson.set_active_patient(patient=patient) 1157 exporter = cEMRJournalExporter() 1158 exporter.export_to_file(patient=patient) 1159 # export_tool.set_patient(patient) 1160 # Dump patient EMR sections 1161 # export_tool.dump_constraints() 1162 # export_tool.dump_demographic_record(True) 1163 # export_tool.dump_clinical_record() 1164 # export_tool.dump_med_docs() 1165 1166 # Clean ups 1167 # outFile.close() 1168 # export_tool.cleanup() 1169 if patient is not None: 1170 try: 1171 patient.cleanup() 1172 except: 1173 print "error cleaning up patient"
1174 #============================================================ 1175 # main 1176 #------------------------------------------------------------ 1177 if __name__ == "__main__": 1178 gmI18N.activate_locale() 1179 gmI18N.install_domain() 1180 1181 #--------------------------------------------------------
1182 - def export_journal():
1183 1184 print "Exporting EMR journal(s) ..." 1185 pat_searcher = gmPersonSearch.cPatientSearcher_SQL() 1186 while True: 1187 patient = gmPersonSearch.ask_for_patient() 1188 if patient is None: 1189 break 1190 1191 exporter = cEMRJournalExporter() 1192 print "exported into file:", exporter.export_to_file(patient=patient) 1193 1194 if patient is not None: 1195 try: 1196 patient.cleanup() 1197 except: 1198 print "error cleaning up patient" 1199 print "Done."
1200 #-------------------------------------------------------- 1201 print "\n\nGNUmed ASCII EMR Export" 1202 print "=======================" 1203 1204 # run main loop 1205 export_journal() 1206 1207 #============================================================ 1208