1
2 """Timeline exporter.
3
4 Copyright: authors
5 """
6
7 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
8 __license__ = 'GPL v2 or later (details at http://www.gnu.org)'
9
10 import sys
11 import logging
12 import io
13 import os
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmI18N
19 from Gnumed.pycommon import gmTools
20 from Gnumed.pycommon import gmDateTime
21
22
23 _log = logging.getLogger('gm.tl')
24
25
26 ERA_NAME_CARE_PERIOD = _('Care Period')
27
28
29
30
31
32
33 xml_start = """<?xml version="1.0" encoding="utf-8"?>
34 <timeline>
35 <version>1.16.0</version>
36 <!-- ======================================== Eras ======================================== -->
37 <eras>
38 <era>
39 <name>%s</name>
40 <start>%s</start>
41 <end>%s</end>
42 <color>205,238,241</color>
43 <ends_today>%s</ends_today>
44 </era>
45 <era>
46 <name>%s</name>
47 <start>%s</start>
48 <end>%s</end>
49 <color>161,210,226</color>
50 <ends_today>%s</ends_today>
51 </era>
52 </eras>
53 <!-- ======================================== Categories ======================================== -->
54 <categories>
55 <!-- health issues -->
56 <category>
57 <name>%s</name>
58 <color>255,0,0</color>
59 <font_color>0,0,0</font_color>
60 </category>
61 <!-- episodes -->
62 <category>
63 <name>%s</name>
64 <color>0,255,0</color>
65 <font_color>0,0,0</font_color>
66 </category>
67 <!-- encounters -->
68 <category>
69 <name>%s</name>
70 <color>30,144,255</color>
71 <font_color>0,0,0</font_color>
72 </category>
73 <!-- hospital stays -->
74 <category>
75 <name>%s</name>
76 <color>255,255,0</color>
77 <font_color>0,0,0</font_color>
78 </category>
79 <!-- procedures -->
80 <category>
81 <name>%s</name>
82 <color>160,32,140</color>
83 <font_color>0,0,0</font_color>
84 </category>
85 <!-- documents -->
86 <category>
87 <name>%s</name>
88 <color>255,165,0</color>
89 <font_color>0,0,0</font_color>
90 </category>
91 <!-- vaccinations -->
92 <category>
93 <name>%s</name>
94 <color>144,238,144</color>
95 <font_color>0,0,0</font_color>
96 </category>
97 <!-- substance intake -->
98 <category>
99 <name>%s</name>
100 <color>165,42,42</color>
101 <font_color>0,0,0</font_color>
102 </category>
103 <!-- life events -->
104 <category>
105 <name>%s</name>
106 <color>30,144,255</color>
107 <font_color>0,0,0</font_color>
108 </category>
109 </categories>
110 <!-- ======================================== Events ======================================== -->
111 <events>"""
112
113 xml_end = """
114 </events>
115 <view>
116 <displayed_period>
117 <start>%s</start>
118 <end>%s</end>
119 </displayed_period>
120 <hidden_categories>
121 </hidden_categories>
122 </view>
123 </timeline>"""
124
125
128
129
130
131
132 __xml_issue_template = """
133 <event>
134 <start>%(start)s</start>
135 <end>%(end)s</end>
136 <text>%(container_id)s%(label)s</text>
137 <fuzzy>%(fuzzy)s</fuzzy>
138 <locked>True</locked>
139 <ends_today>%(ends2day)s</ends_today>
140 <category>%(category)s</category>
141 <description>%(desc)s</description>
142 </event>"""
143
188
189
190
191
192 __xml_episode_template = """
193 <event>
194 <start>%(start)s</start>
195 <end>%(end)s</end>
196 <text>%(container_id)s%(label)s</text>
197 <progress>%(progress)s</progress>
198 <fuzzy>False</fuzzy>
199 <locked>True</locked>
200 <ends_today>%(ends2day)s</ends_today>
201 <category>%(category)s</category>
202 <description>%(desc)s</description>
203 </event>"""
204
235
236
237
238
239 __xml_encounter_template = """
240 <event>
241 <start>%s</start>
242 <end>%s</end>
243 <text>%s</text>
244 <progress>0</progress>
245 <fuzzy>False</fuzzy>
246 <locked>True</locked>
247 <ends_today>False</ends_today>
248 <category>%s</category>
249 <description>%s</description>
250 <milestone>%s</milestone>
251 </event>"""
252
273
274
275
276
277 __xml_hospital_stay_template = """
278 <event>
279 <start>%s</start>
280 <end>%s</end>
281 <text>%s</text>
282 <fuzzy>False</fuzzy>
283 <locked>True</locked>
284 <ends_today>False</ends_today>
285 <category>%s</category>
286 <description>%s</description>
287 </event>"""
288
300
301
302
303
304 __xml_procedure_template = """
305 <event>
306 <start>%s</start>
307 <end>%s</end>
308 <text>%s</text>
309 <fuzzy>False</fuzzy>
310 <locked>True</locked>
311 <ends_today>False</ends_today>
312 <category>%s</category>
313 <description>%s</description>
314 </event>"""
315
335
336
337
338
339 __xml_document_template = """
340 <event>
341 <start>%s</start>
342 <end>%s</end>
343 <text>%s</text>
344 <fuzzy>False</fuzzy>
345 <locked>True</locked>
346 <ends_today>False</ends_today>
347 <category>%s</category>
348 <description>%s</description>
349 </event>"""
350
360
361
362
363
364 __xml_vaccination_template = """
365 <event>
366 <start>%s</start>
367 <end>%s</end>
368 <text>%s</text>
369 <fuzzy>False</fuzzy>
370 <locked>True</locked>
371 <ends_today>False</ends_today>
372 <category>%s</category>
373 <description>%s</description>
374 </event>"""
375
389
390
391
392
393 __xml_intake_template = """
394 <event>
395 <start>%s</start>
396 <end>%s</end>
397 <text>%s</text>
398 <fuzzy>False</fuzzy>
399 <locked>True</locked>
400 <ends_today>False</ends_today>
401 <category>%s</category>
402 <description>%s</description>
403 </event>"""
404
427
428
429
430
431 -def create_timeline_file(patient=None, filename=None, include_documents=False, include_vaccinations=False, include_encounters=False):
432
433 emr = patient.emr
434 global now
435 now = gmDateTime.pydt_now_here()
436
437 if filename is None:
438 timeline_fname = gmTools.get_unique_filename(prefix = 'gm-', suffix = '.timeline')
439 else:
440 timeline_fname = filename
441 _log.debug('exporting EMR as timeline into [%s]', timeline_fname)
442 timeline = io.open(timeline_fname, mode = 'wt', encoding = 'utf8', errors = 'xmlcharrefreplace')
443
444 if patient['dob'] is None:
445 lifespan_start = format_pydt(now.replace(year = now.year - 100))
446 else:
447 lifespan_start = format_pydt(patient['dob'])
448
449 if patient['deceased'] is None:
450 life_ends2day = 'True'
451 lifespan_end = format_pydt(now)
452 else:
453 life_ends2day = 'False'
454 lifespan_end = format_pydt(patient['deceased'])
455
456 earliest_care_date = emr.earliest_care_date
457 most_recent_care_date = emr.most_recent_care_date
458 if most_recent_care_date is None:
459 most_recent_care_date = lifespan_end
460 care_ends2day = life_ends2day
461 else:
462 most_recent_care_date = format_pydt(most_recent_care_date)
463 care_ends2day = 'False'
464
465 timeline.write(xml_start % (
466
467 _('Lifespan'),
468 lifespan_start,
469 lifespan_end,
470 life_ends2day,
471 ERA_NAME_CARE_PERIOD,
472 format_pydt(earliest_care_date),
473 most_recent_care_date,
474 care_ends2day,
475
476 _('Health issues'),
477 _('Episodes'),
478 _('Encounters'),
479 _('Hospital stays'),
480 _('Procedures'),
481 _('Documents'),
482 _('Vaccinations'),
483 _('Substances'),
484 _('Life events')
485 ))
486
487 if patient['dob'] is None:
488 start = now.replace(year = now.year - 100)
489 timeline.write(__xml_encounter_template % (
490 format_pydt(start),
491 format_pydt(start),
492 '?',
493 _('Life events'),
494 _('Date of birth unknown'),
495 'True'
496 ))
497 else:
498 start = patient['dob']
499 timeline.write(__xml_encounter_template % (
500 format_pydt(patient['dob']),
501 format_pydt(patient['dob']),
502 '*',
503 _('Life events'),
504 '%s: %s (%s)' % (
505 _('Birth'),
506 patient.get_formatted_dob(format = '%Y %b %d %H:%M', honor_estimation = True),
507 patient.get_medical_age()
508 ),
509 'True'
510 ))
511
512
513 timeline.write(__xml_encounter_template % (
514 format_pydt(earliest_care_date),
515 format_pydt(earliest_care_date),
516 gmTools.u_heavy_greek_cross,
517 _('Life events'),
518 _('Start of Care: %s\n(the earliest recorded event of care in this praxis)') % format_pydt(earliest_care_date, format = '%Y %b %d'),
519 'True'
520 ))
521
522
523
524 timeline.write('\n <!-- ========================================\n Health issues\n======================================== -->')
525 for issue in emr.health_issues:
526 timeline.write(__format_health_issue_as_timeline_xml(issue, patient, emr))
527
528 timeline.write('\n <!-- ========================================\n Episodes\n======================================== -->')
529 for epi in emr.get_episodes(order_by = 'pk_health_issue'):
530 timeline.write(__format_episode_as_timeline_xml(epi, patient))
531
532 if include_encounters:
533 timeline.write(u'\n<!--\n========================================\n Encounters\n======================================== -->')
534 for enc in emr.get_encounters(skip_empty = True):
535 timeline.write(__format_encounter_as_timeline_xml(enc, patient))
536
537 timeline.write('\n<!--\n========================================\n Hospital stays\n======================================== -->')
538 for stay in emr.hospital_stays:
539 timeline.write(__format_hospital_stay_as_timeline_xml(stay))
540
541 timeline.write('\n<!--\n========================================\n Procedures\n======================================== -->')
542 for proc in emr.performed_procedures:
543 timeline.write(__format_procedure_as_timeline_xml(proc))
544
545 if include_vaccinations:
546 timeline.write(u'\n<!--\n========================================\n Vaccinations\n======================================== -->')
547 for vacc in emr.vaccinations:
548 timeline.write(__format_vaccination_as_timeline_xml(vacc))
549
550 timeline.write('\n<!--\n========================================\n Substance intakes\n======================================== -->')
551 for intake in emr.get_current_medications(include_inactive = True, include_unapproved = False):
552 timeline.write(__format_intake_as_timeline_xml(intake))
553
554 if include_documents:
555 timeline.write(u'\n<!--\n========================================\n Documents\n======================================== -->')
556 for doc in patient.document_folder.documents:
557 timeline.write(__format_document_as_timeline_xml(doc))
558
559
560
561
562
563
564
565 if patient['deceased'] is None:
566 end = now
567 else:
568 end = patient['deceased']
569 timeline.write(__xml_encounter_template % (
570 format_pydt(end),
571 format_pydt(end),
572 gmTools.u_dagger,
573 _('Life events'),
574 _('Death: %s') % format_pydt(end, format = '%Y %b %d %H:%M')
575 ))
576
577
578 if end.month == 2:
579 if end.day == 29:
580
581 end = end.replace(day = 28)
582 target_year = end.year + 1
583 end = end.replace(year = target_year)
584 timeline.write(xml_end % (
585 format_pydt(start),
586 format_pydt(end)
587 ))
588
589 timeline.close()
590 return timeline_fname
591
592
593 __fake_timeline_start = """<?xml version="1.0" encoding="utf-8"?>
594 <timeline>
595 <version>0.20.0</version>
596 <categories>
597 <!-- life events -->
598 <category>
599 <name>%s</name>
600 <color>30,144,255</color>
601 <font_color>0,0,0</font_color>
602 </category>
603 </categories>
604 <events>""" % _('Life events')
605
606 __fake_timeline_body_template = """
607 <event>
608 <start>%s</start>
609 <end>%s</end>
610 <text>%s</text>
611 <fuzzy>False</fuzzy>
612 <locked>True</locked>
613 <ends_today>False</ends_today>
614 <!-- category></category -->
615 <description>%s
616 </description>
617 </event>"""
618
620 """Used to create an 'empty' timeline file for display.
621
622 - needed because .clear_timeline() doesn't really work
623 """
624 emr = patient.emr
625 global now
626 now = gmDateTime.pydt_now_here()
627
628 if filename is None:
629 timeline_fname = gmTools.get_unique_filename(prefix = 'gm-', suffix = '.timeline')
630 else:
631 timeline_fname = filename
632
633 _log.debug('creating dummy timeline in [%s]', timeline_fname)
634 timeline = io.open(timeline_fname, mode = 'wt', encoding = 'utf8', errors = 'xmlcharrefreplace')
635
636 timeline.write(__fake_timeline_start)
637
638
639 if patient['dob'] is None:
640 start = now.replace(year = now.year - 100)
641 timeline.write(__xml_encounter_template % (
642 format_pydt(start),
643 format_pydt(start),
644 _('Birth') + ': ?',
645 _('Life events'),
646 _('Date of birth unknown'),
647 'False'
648 ))
649 else:
650 start = patient['dob']
651 timeline.write(__xml_encounter_template % (
652 format_pydt(patient['dob']),
653 format_pydt(patient['dob']),
654 _('Birth') + gmTools.bool2subst(patient['dob_is_estimated'], ' (%s)' % gmTools.u_almost_equal_to, ''),
655 _('Life events'),
656 '',
657 'False'
658 ))
659
660
661 if patient['deceased'] is None:
662 end = now
663 else:
664 end = patient['deceased']
665 timeline.write(__xml_encounter_template % (
666 format_pydt(end),
667 format_pydt(end),
668
669 _('Death'),
670 _('Life events'),
671 '',
672 'False'
673 ))
674
675
676 timeline.write(__fake_timeline_body_template % (
677 format_pydt(start),
678 format_pydt(end),
679 _('Cannot display timeline.'),
680 _('Cannot display timeline.')
681 ))
682
683
684 if end.month == 2:
685 if end.day == 29:
686
687 end = end.replace(day = 28)
688 target_year = end.year + 1
689 end = end.replace(year = target_year)
690 timeline.write(xml_end % (
691 format_pydt(start),
692 format_pydt(end)
693 ))
694
695 timeline.close()
696 return timeline_fname
697
698
699
700
701 if __name__ == '__main__':
702
703 if len(sys.argv) < 2:
704 sys.exit()
705
706 if sys.argv[1] != "test":
707 sys.exit()
708
709 gmI18N.activate_locale()
710 gmI18N.install_domain('gnumed')
711
712 from Gnumed.business import gmPraxis
713 praxis = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.get_praxis_branches()[0])
714
715 from Gnumed.business import gmPerson
716
717 pat = gmPerson.gmCurrentPatient(gmPerson.cPatient(aPK_obj = 14))
718 fname = '~/gnumed/gm2tl-%s.timeline' % pat.subdir_name
719
720 print (create_timeline_file (
721 patient = pat,
722 filename = os.path.expanduser(fname),
723 include_documents = True,
724 include_vaccinations = True,
725 include_encounters = True
726 ))
727