| Home | Trees | Indices | Help |
|
|---|
|
|
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 #============================================================
33
34 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
81 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
107 #--------------------------------------------------------
113 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
485 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
579 encounters = emr.get_encounters (
580 episodes = [an_episode['pk_episode']]
581 )
582 return encounters
583 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
922 #============================================================
924 """Exports patient EMR into a simple chronological journal.
925
926 Note that this export will emit u'' strings only.
927 """
930 #--------------------------------------------------------
931 # external API
932 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #============================================================
1059 """Export SOAP data per encounter into Medistar import format."""
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 #--------------------------------------------------------
1111 #--------------------------------------------------------
1112 # interal API
1113 #--------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #--------------------------------------------------------
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 # $Log: gmPatientExporter.py,v $
1209 # Revision 1.138 2009-09-08 17:14:55 ncq
1210 # - apply unwrap() to encounter node title
1211 #
1212 # Revision 1.137 2009/07/15 12:47:25 ncq
1213 # - properly use patient age
1214 #
1215 # Revision 1.136 2009/06/29 15:01:07 ncq
1216 # - use get-latest-soap in encounter formatting
1217 #
1218 # Revision 1.135 2009/06/04 16:24:35 ncq
1219 # - support dob-less persons
1220 #
1221 # Revision 1.134 2009/01/02 11:36:43 ncq
1222 # - cleanup
1223 #
1224 # Revision 1.133 2008/12/18 21:26:45 ncq
1225 # - missing H in HH24 in date formatting in journal exporter
1226 #
1227 # Revision 1.132 2008/12/09 23:24:29 ncq
1228 # - .date -> .clin_when in doc_med
1229 #
1230 # Revision 1.131 2008/12/01 12:36:57 ncq
1231 # - improved encounter node label
1232 #
1233 # Revision 1.130 2008/10/22 12:06:05 ncq
1234 # - use %x in strftime
1235 #
1236 # Revision 1.129 2008/10/12 15:32:18 ncq
1237 # - support "mod date" in journal
1238 #
1239 # Revision 1.128 2008/09/02 18:59:30 ncq
1240 # - no more fk_patient in clin.health_issue and related changes
1241 #
1242 # Revision 1.127 2008/07/28 15:42:30 ncq
1243 # - cleanup
1244 # - enhance medistar exporter
1245 #
1246 # Revision 1.126 2008/07/12 15:20:55 ncq
1247 # - comment out print
1248 #
1249 # Revision 1.125 2008/07/07 13:39:22 ncq
1250 # - properly sort tree
1251 # - current patient .connected
1252 #
1253 # Revision 1.124 2008/06/24 13:55:14 ncq
1254 # - change encounter node label
1255 #
1256 # Revision 1.123 2008/06/23 09:59:57 ncq
1257 # - much improved journal layout
1258 #
1259 # Revision 1.122 2008/06/15 20:16:02 ncq
1260 # - add a space
1261 #
1262 # Revision 1.121 2008/05/19 15:44:16 ncq
1263 # - just silly cleanup
1264 #
1265 # Revision 1.120 2008/05/07 15:16:01 ncq
1266 # - use centralized soap category translations from gmClinNarrative
1267 #
1268 # Revision 1.119 2008/04/11 12:21:11 ncq
1269 # - some cleanup
1270 #
1271 # Revision 1.118 2008/04/02 10:15:54 ncq
1272 # - show local time zone in encounter summary
1273 #
1274 # Revision 1.117 2008/03/06 18:24:45 ncq
1275 # - indentation fix
1276 #
1277 # Revision 1.116 2008/03/05 22:25:09 ncq
1278 # - no more gmLog
1279 #
1280 # Revision 1.115 2008/01/30 13:46:17 ncq
1281 # - cleanup
1282 #
1283 # Revision 1.114 2008/01/22 11:52:24 ncq
1284 # - Unattributed
1285 # - improved Journal formatting as per list
1286 #
1287 # Revision 1.113 2008/01/13 01:13:58 ncq
1288 # - use issue.age_noted_human_readable()
1289 #
1290 # Revision 1.112 2008/01/11 16:10:00 ncq
1291 # - first/last -> first-/lastnames
1292 #
1293 # Revision 1.111 2007/12/26 22:26:04 shilbert
1294 # - indentation error fixed
1295 #
1296 # Revision 1.110 2007/12/23 11:56:38 ncq
1297 # - improve output, cleanup
1298 #
1299 # Revision 1.109 2007/11/28 11:52:13 ncq
1300 # - get_all_names() -> get_names()
1301 #
1302 # Revision 1.108 2007/11/05 12:10:05 ncq
1303 # - support admin soap type
1304 #
1305 # Revision 1.107 2007/06/18 19:33:19 ncq
1306 # - cleanup
1307 # - show date in narrative, too
1308 #
1309 # Revision 1.106 2007/05/21 14:46:44 ncq
1310 # - use patient['dirname']
1311 #
1312 # Revision 1.105 2007/05/14 13:09:04 ncq
1313 # - use bold on health issues with open episodes
1314 #
1315 # Revision 1.104 2007/04/01 15:25:55 ncq
1316 # - safely get encoding
1317 #
1318 # Revision 1.103 2007/03/02 15:30:00 ncq
1319 # - decode() strftime() output
1320 #
1321 # Revision 1.102 2007/02/22 17:30:48 ncq
1322 # - no more get_identity()
1323 # - patient now cIdentity() child
1324 #
1325 # Revision 1.101 2007/02/19 17:54:06 ncq
1326 # - need to return True when successful
1327 #
1328 # Revision 1.100 2007/02/19 16:56:05 ncq
1329 # - properly check for is_connected()
1330 #
1331 # Revision 1.99 2007/02/19 14:07:31 ncq
1332 # - fix format string parameters
1333 #
1334 # Revision 1.98 2007/02/17 14:10:03 ncq
1335 # - use improved gmTools.coalesce()
1336 #
1337 # Revision 1.97 2007/02/10 23:41:38 ncq
1338 # - fix loading of GNUmed python modules
1339 # - cleaned up journal exporter
1340 # - fixed bug in journal exporter where it expected is_connected()
1341 # in non-gmCurrentPatient-using context, too
1342 # - when run standalone: export journal
1343 #
1344 # Revision 1.96 2007/01/18 22:03:58 ncq
1345 # - a bit of cleanup
1346 #
1347 # Revision 1.95 2007/01/15 20:20:03 ncq
1348 # - move wrap() to gmTools
1349 #
1350 # Revision 1.94 2007/01/13 22:17:40 ncq
1351 # - wrap narrative to 75 characters per line
1352 #
1353 # Revision 1.93 2006/12/13 00:31:24 ncq
1354 # - export into unicode files
1355 # - fix use of get_encounters()
1356 #
1357 # Revision 1.92 2006/11/26 15:44:34 ncq
1358 # - strftime() does not accept u''
1359 #
1360 # Revision 1.91 2006/11/24 14:16:20 ncq
1361 # - unicode-robustify dump_item_fields()
1362 #
1363 # Revision 1.90 2006/11/19 11:05:38 ncq
1364 # - cleanup
1365 #
1366 # Revision 1.89 2006/11/09 17:48:05 ncq
1367 # - ever more careful handling of NULLs
1368 #
1369 # Revision 1.88 2006/11/07 00:25:19 ncq
1370 # - make journal exporter emit strictly u''
1371 #
1372 # Revision 1.87 2006/11/05 17:54:17 ncq
1373 # - don't use issue pk in get_encounters()
1374 # - gmPG -> gmPG2
1375 #
1376 # Revision 1.86 2006/11/05 17:02:54 ncq
1377 # - comment out lab results access, not in use yet
1378 #
1379 # Revision 1.85 2006/10/25 07:46:44 ncq
1380 # - Format() -> strftime() since datetime.datetime does not have .Format()
1381 #
1382 # Revision 1.84 2006/10/25 07:18:12 ncq
1383 # - no more gmPG
1384 #
1385 # Revision 1.83 2006/10/23 13:21:50 ncq
1386 # - vaccs/path lab not yet converted to gmPG2
1387 #
1388 # Revision 1.82 2006/09/03 14:46:26 ncq
1389 # - robustify regarding encoding issues
1390 # - improve test suite
1391 #
1392 # Revision 1.81 2006/07/19 20:25:48 ncq
1393 # - gmPyCompat.py is history
1394 #
1395 # Revision 1.80 2006/06/09 14:39:23 ncq
1396 # - comment out vaccination handling for now
1397 #
1398 # Revision 1.79 2006/05/30 13:36:35 ncq
1399 # - properly use get_encounters()
1400 #
1401 # Revision 1.78 2006/05/04 09:49:20 ncq
1402 # - get_clinical_record() -> get_emr()
1403 # - adjust to changes in set_active_patient()
1404 # - need explicit set_active_patient() after ask_for_patient() if wanted
1405 #
1406 # Revision 1.77 2006/02/27 22:38:36 ncq
1407 # - spell out rfe/aoe as per Richard's request
1408 #
1409 # Revision 1.76 2005/12/25 13:24:30 sjtan
1410 #
1411 # schema changes in names .
1412 #
1413 # Revision 1.75 2005/12/10 23:02:05 ncq
1414 # - tables are in clin.* now
1415 #
1416 # Revision 1.74 2005/10/30 15:48:56 ncq
1417 # - slightly enlarge space for provider signum display
1418 #
1419 # Revision 1.73 2005/10/19 09:06:39 ncq
1420 # - resolve merge conflict: just whitespace diff
1421 #
1422 # Revision 1.72 2005/10/18 13:34:01 sjtan
1423 # after running; small diffs
1424 #
1425 # Revision 1.71 2005/10/15 18:16:24 ncq
1426 # - encounter['description'] is gone, use 'aoe'
1427 #
1428 # Revision 1.70 2005/10/11 21:51:07 ncq
1429 # - rfe/aoe handling changes so adapt to that
1430 #
1431 # Revision 1.69 2005/10/08 12:33:09 sjtan
1432 # 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.
1433 #
1434 # Revision 1.68 2005/10/04 19:22:37 sjtan
1435 # allow a refetch of part of the cache, so that don't have to completely collapse tree view to view after change.
1436 #
1437 # Revision 1.67 2005/10/04 00:04:45 sjtan
1438 # convert to wx.; catch some transitional errors temporarily
1439 #
1440 # Revision 1.66 2005/10/03 13:49:21 sjtan
1441 # using new wx. temporary debugging to stdout(easier to read). where is rfe ?
1442 #
1443 # Revision 1.65 2005/09/11 17:28:20 ncq
1444 # - tree widget now display provider sign, not database account
1445 #
1446 # Revision 1.64 2005/09/09 13:50:07 ncq
1447 # - detail improvements in tree widget progress note output
1448 #
1449 # Revision 1.63 2005/09/08 16:57:06 ncq
1450 # - improve progress note display in tree widget
1451 #
1452 # Revision 1.62 2005/09/05 15:56:27 ncq
1453 # - sort journal by episode within encounters
1454 #
1455 # Revision 1.61 2005/09/04 07:28:51 ncq
1456 # - better naming of dummy health issue for unassociated episodes
1457 # - display time of entry in front of SOAP notes
1458 #
1459 # Revision 1.60 2005/07/04 11:14:36 ncq
1460 # - improved episode summary yet again
1461 #
1462 # Revision 1.59 2005/07/02 18:18:26 ncq
1463 # - improve EMR tree right side info pane according to user
1464 # testing and ideas gleaned from TransHIS
1465 #
1466 # Revision 1.58 2005/06/30 16:11:55 cfmoro
1467 # Bug fix: multiple episode w/o issue when refreshing tree
1468 #
1469 # Revision 1.57 2005/06/30 11:42:05 cfmoro
1470 # Removed debug print
1471 #
1472 # Revision 1.56 2005/06/30 11:30:10 cfmoro
1473 # Minor fix on issue info when no encounters attached
1474 #
1475 # Revision 1.55 2005/06/20 13:03:38 cfmoro
1476 # Relink encounter to another episode
1477 #
1478 # Revision 1.54 2005/06/12 22:09:39 ncq
1479 # - better encounter formatting yet
1480 #
1481 # Revision 1.53 2005/06/07 09:04:45 ncq
1482 # - cleanup, better encounter data display
1483 #
1484 # Revision 1.52 2005/05/17 18:11:41 ncq
1485 # - dob2medical_age is in gmPerson
1486 #
1487 # Revision 1.51 2005/05/12 15:08:31 ncq
1488 # - add Medistar SOAP exporter and wrap(text, width) convenience function
1489 #
1490 # Revision 1.50 2005/04/27 19:59:19 ncq
1491 # - deal with narrative rows that are empty
1492 #
1493 # Revision 1.49 2005/04/12 16:15:36 ncq
1494 # - improve journal style exporter
1495 #
1496 # Revision 1.48 2005/04/12 10:00:19 ncq
1497 # - add cEMRJournalExporter class
1498 #
1499 # Revision 1.47 2005/04/03 20:08:18 ncq
1500 # - GUI stuff does not belong here (eg move to gmEMRBrowser which is to become gmEMRWidgets, eventually)
1501 #
1502 # Revision 1.46 2005/04/03 09:27:25 ncq
1503 # - better wording
1504 #
1505 # Revision 1.45 2005/04/02 21:37:27 cfmoro
1506 # Unlinked episodes displayes in EMR tree and dump
1507 #
1508 # Revision 1.44 2005/04/02 20:45:12 cfmoro
1509 # Implementated exporting emr from gui client
1510 #
1511 # Revision 1.43 2005/03/30 21:14:31 cfmoro
1512 # Using cIdentity recent changes
1513 #
1514 # Revision 1.42 2005/03/29 07:24:07 ncq
1515 # - tabify
1516 #
1517 # Revision 1.41 2005/03/20 17:48:38 ncq
1518 # - add two sanity checks by Syan
1519 #
1520 # Revision 1.40 2005/02/20 08:32:51 sjtan
1521 #
1522 # indentation syntax error.
1523 #
1524 # Revision 1.39 2005/02/03 20:19:16 ncq
1525 # - get_demographic_record() -> get_identity()
1526 #
1527 # Revision 1.38 2005/01/31 13:01:23 ncq
1528 # - use ask_for_patient() in gmPerson
1529 #
1530 # Revision 1.37 2005/01/31 10:19:11 ncq
1531 # - gmPatient -> gmPerson
1532 #
1533 # Revision 1.36 2004/10/26 12:52:56 ncq
1534 # - Carlos: fix conceptual bug by building top-down (eg. issue -> episode
1535 # -> item) instead of bottom-up
1536 #
1537 # Revision 1.35 2004/10/20 21:43:45 ncq
1538 # - cleanup
1539 # - use allergy['descriptor']
1540 # - Format() dates
1541 #
1542 # Revision 1.34 2004/10/20 11:14:55 sjtan
1543 # restored import for unix. get_historical_tree may of changed, but mainly should
1544 # be guards in gmClinicalRecord for changing [] to None when functions expecting None, and client
1545 # functions passing [].
1546 #
1547 # Revision 1.33 2004/10/12 10:52:40 ncq
1548 # - improve vaccinations handling
1549 #
1550 # Revision 1.32 2004/10/11 19:53:41 ncq
1551 # - document classes are now VOs
1552 #
1553 # Revision 1.31 2004/09/29 19:13:37 ncq
1554 # - cosmetical fixes as discussed with our office staff
1555 #
1556 # Revision 1.30 2004/09/29 10:12:50 ncq
1557 # - Carlos added intuitive vaccination table - muchos improvos !
1558 #
1559 # Revision 1.29 2004/09/10 10:39:01 ncq
1560 # - fixed assignment that needs to be comparison == in lambda form
1561 #
1562 # Revision 1.28 2004/09/06 18:55:09 ncq
1563 # - a bunch of cleanups re get_historical_tree()
1564 #
1565 # Revision 1.27 2004/09/01 21:59:01 ncq
1566 # - python classes can only have one single __init__
1567 # - add in Carlos' code for displaying episode/issue summaries
1568 #
1569 # Revision 1.26 2004/08/23 09:08:53 ncq
1570 # - improve output
1571 #
1572 # Revision 1.25 2004/08/11 09:45:28 ncq
1573 # - format SOAP notes, too
1574 #
1575 # Revision 1.24 2004/08/09 18:41:08 ncq
1576 # - improved ASCII dump
1577 #
1578 # Revision 1.23 2004/07/26 00:02:30 ncq
1579 # - Carlos introduces export of RFE/AOE and dynamic layouting (left margin)
1580 #
1581 # Revision 1.22 2004/07/18 10:46:30 ncq
1582 # - lots of cleanup by Carlos
1583 #
1584 # Revision 1.21 2004/07/09 22:39:40 ncq
1585 # - write to file like object passed to __init__
1586 #
1587 # Revision 1.20 2004/07/06 00:26:06 ncq
1588 # - fail on _cfg is_instance of cNull(), not on missing conf-file option
1589 #
1590 # Revision 1.19 2004/07/03 17:15:59 ncq
1591 # - decouple contraint/patient/outfile handling
1592 #
1593 # Revision 1.18 2004/07/02 00:54:04 ncq
1594 # - constraints passing cleanup by Carlos
1595 #
1596 # Revision 1.17 2004/06/30 12:52:36 ncq
1597 # - cleanup
1598 #
1599 # Revision 1.16 2004/06/30 12:43:10 ncq
1600 # - read opts from config file, cleanup
1601 #
1602 # Revision 1.15 2004/06/29 08:16:35 ncq
1603 # - take output file from command line
1604 # - *search* for patients, don't require knowledge of their ID
1605 #
1606 # Revision 1.14 2004/06/28 16:15:56 ncq
1607 # - still more faulty id_ found
1608 #
1609 # Revision 1.13 2004/06/28 15:52:00 ncq
1610 # - some comments
1611 #
1612 # Revision 1.12 2004/06/28 12:18:52 ncq
1613 # - more id_* -> fk_*
1614 #
1615 # Revision 1.11 2004/06/26 23:45:50 ncq
1616 # - cleanup, id_* -> fk/pk_*
1617 #
1618 # Revision 1.10 2004/06/26 06:53:25 ncq
1619 # - id_episode -> pk_episode
1620 # - constrained by date range from Carlos
1621 # - dump documents folder, too, by Carlos
1622 #
1623 # Revision 1.9 2004/06/23 22:06:48 ncq
1624 # - cleaner error handling
1625 # - fit for further work by Carlos on UI interface/dumping to file
1626 # - nice stuff !
1627 #
1628 # Revision 1.8 2004/06/20 18:50:53 ncq
1629 # - some exception catching, needs more cleanup
1630 #
1631 # Revision 1.7 2004/06/20 18:35:07 ncq
1632 # - more work from Carlos
1633 #
1634 # Revision 1.6 2004/05/12 14:34:41 ncq
1635 # - now displays nice vaccination tables
1636 # - work by Carlos Moro
1637 #
1638 # Revision 1.5 2004/04/27 18:54:54 ncq
1639 # - adapt to gmClinicalRecord
1640 #
1641 # Revision 1.4 2004/04/24 13:35:33 ncq
1642 # - vacc table update
1643 #
1644 # Revision 1.3 2004/04/24 12:57:30 ncq
1645 # - stop db listeners on exit
1646 #
1647 # Revision 1.2 2004/04/20 13:00:22 ncq
1648 # - recent changes by Carlos to use VO API
1649 #
1650 # Revision 1.1 2004/03/25 23:10:02 ncq
1651 # - gmEmrExport -> gmPatientExporter by Carlos' suggestion
1652 #
1653 # Revision 1.2 2004/03/25 09:53:30 ncq
1654 # - added log keyword
1655 #
1656
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Oct 18 04:00:19 2011 | http://epydoc.sourceforge.net |