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
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
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
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
77 """
78 Retrieve exporter constraints
79 """
80 return self.__constraints
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
103 """
104 Retrieves patient whose data are to be dumped
105 """
106 return self.__patient
107
109 """
110 Exporter class cleanup code
111 """
112 pass
113
115 """
116 Retrieves string containg ASCII vaccination table
117 """
118 emr = self.__patient.get_emr()
119
120 patient_dob = self.__patient['dob']
121 date_length = len(patient_dob.strftime('%x')) + 2
122
123
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
129 chart_columns = len(vacc_regimes)
130
131 foot_headers = ['last booster', 'next booster']
132
133 ending_str = '='
134
135
136 column_widths = []
137 chart_rows = -1
138 vaccinations = {}
139 temp = -1
140 for foot_header in foot_headers:
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:
146 chart_rows = a_vacc_regime['shots']
147 if (len(a_vacc_regime['l10n_indication'])) > date_length:
148 column_widths.append(len(a_vacc_regime['l10n_indication']))
149 else:
150 column_widths.append(date_length)
151 vaccinations[a_vacc_regime['indication']] = emr.get_vaccinations(indications=[a_vacc_regime['indication']])
152
153
154 txt = '\nDOB: %s' %(patient_dob.strftime('%x')) + '\n'
155
156
157
158 for column_width in column_widths:
159 txt += column_width * '-' + '-'
160 txt += '\n'
161
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
169 for column_width in column_widths:
170 txt += column_width * '-' + '-'
171 txt += '\n'
172
173
174 due_date = None
175
176 prev_displayed_date = [patient_dob]
177 for a_regime in vacc_regimes:
178 prev_displayed_date.append(patient_dob)
179
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:
188 txt += ending_str * column_widths[col_index] + '|'
189 elif row_index < seq_no:
190 try:
191 vacc_date = vaccinations[indication][row_index]['date']
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:
197 due_date = prev_displayed_date[col_index] + vaccinations4regimes[indication][row_index]['age_due_min']
198 else:
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:
203 txt += column_widths[col_index] * ' ' + '|'
204 txt += '\n'
205 for column_width in column_widths:
206 txt += column_width * '-' + '-'
207 txt += '\n'
208
209
210 all_vreg_boosters = []
211 for a_vacc_regime in vacc_regimes:
212 vaccs4indication = vaccinations[a_vacc_regime['indication']]
213 given_boosters = []
214 for a_vacc in vaccs4indication:
215 try:
216 if a_vacc['is_booster']:
217 given_boosters.append(a_vacc)
218 except:
219
220 pass
221 if len(given_boosters) > 0:
222 all_vreg_boosters.append(given_boosters[len(given_boosters)-1])
223 else:
224 all_vreg_boosters.append(None)
225
226
227 all_next_boosters = []
228 for a_booster in all_vreg_boosters:
229 all_next_boosters.append(None)
230
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:
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']:
240 all_vreg_boosters[cont] = vaccinations[indication][len(vaccinations[indication])-1]['date'].strftime('%Y-%m-%d')
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') + ')>'
245 else:
246 all_next_boosters[cont] = booster_date.strftime('%Y-%m-%d')
247 elif len(vaccinations[indication]) == vacc_regimes[cont]['shots']:
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') + ')>'
253 else:
254 all_next_boosters[cont] = booster_date.strftime('%Y-%m-%d')
255 else:
256 all_vreg_boosters[cont] = column_widths[cont+1] * ' '
257 all_next_boosters[cont] = column_widths[cont+1] * ' '
258 cont += 1
259
260
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
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
296 all_vacc_regimes = emr.get_scheduled_vaccination_regimes()
297
298
299 max_regs_per_table = 4
300
301
302
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
375
376
377
378
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
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
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
427
428
429
430
431
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
467
468
469
470
471
472
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
481 self._traverse_health_issues( emr_tree, self._update_health_issue_branch)
482
484 self._traverse_health_issues( emr_tree, self._add_health_issue_branch)
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
494
495
496
497
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
505
506 if len(unlinked_episodes) > 0:
507 h_issues.insert(0, {
508 'description': _('Unattributed episodes'),
509 'pk_health_issue': None
510 })
511
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
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 (
562 soap_cat = 'a',
563 episode = emr_tree.GetPyData(episode_node)['pk_episode']
564 ),
565 an_encounter['assessment_of_encounter']
566 ),
567 an_encounter['reason_for_encounter']
568 ),
569 an_encounter['l10n_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
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
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
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
623 try:
624
625 id_episode = tree_episodes[an_episode['pk_episode']]
626 except:
627 import pdb
628 pdb.set_trace()
629
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
637
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
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
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
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
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
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 'soapu':
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
738
739
740
741
742
743
744
745
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
759 self.__fetch_filtered_items()
760 emr = self.__patient.get_emr()
761
762
763 for an_item in self.__filtered_items:
764 self.__target.write(self.get_item_summary(an_item, 3))
765
766
767 h_issues = []
768 h_issues.extend(emr.get_health_issues(id_list = self.__constraints['issues']))
769
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
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
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
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
924 """Exports patient EMR into a simple chronological journal.
925
926 Note that this export will emit u'' strings only.
927 """
930
931
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
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
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
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
999 prev_date = u''
1000 prev_doc = u''
1001 prev_soap = u''
1002 for row in rows:
1003
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
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
1020 curr_soap = row['soap_cat']
1021 if curr_soap != prev_soap:
1022 prev_soap = curr_soap
1023
1024
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
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
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
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."""
1067
1068
1069
1070 - def export_to_file(self, filename=None, encounter=None, soap_cats=u'soapu', 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
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'soapu'):
1110 return self.__export(target, encounter = encounter, soap_cats = soap_cats)
1111
1112
1113
1114 - def __export(self, target=None, encounter=None, soap_cats=u'soapu'):
1115
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
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
1174
1175
1176
1177 if __name__ == "__main__":
1178 gmI18N.activate_locale()
1179 gmI18N.install_domain()
1180
1181
1200
1201 print "\n\nGNUmed ASCII EMR Export"
1202 print "======================="
1203
1204
1205 export_journal()
1206
1207
1208