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.20.0</version>
36 <timetype>gregoriantime</timetype>
37 <!-- ======================================== Eras ======================================== -->
38 <eras>
39 <era>
40 <name>%s</name>
41 <start>%s</start>
42 <end>%s</end>
43 <color>205,238,241</color>
44 <ends_today>%s</ends_today>
45 </era>
46 <era>
47 <name>%s</name>
48 <start>%s</start>
49 <end>%s</end>
50 <color>161,210,226</color>
51 <ends_today>%s</ends_today>
52 </era>
53 </eras>
54 <!-- ======================================== Categories ======================================== -->
55 <categories>
56 <!-- health issues -->
57 <category>
58 <name>%s</name>
59 <color>255,0,0</color>
60 <font_color>0,0,0</font_color>
61 </category>
62 <!-- episodes -->
63 <category>
64 <name>%s</name>
65 <color>0,255,0</color>
66 <font_color>0,0,0</font_color>
67 </category>
68 <!-- encounters -->
69 <category>
70 <name>%s</name>
71 <color>30,144,255</color>
72 <font_color>0,0,0</font_color>
73 </category>
74 <!-- hospital stays -->
75 <category>
76 <name>%s</name>
77 <color>255,255,0</color>
78 <font_color>0,0,0</font_color>
79 </category>
80 <!-- procedures -->
81 <category>
82 <name>%s</name>
83 <color>160,32,140</color>
84 <font_color>0,0,0</font_color>
85 </category>
86 <!-- documents -->
87 <category>
88 <name>%s</name>
89 <color>255,165,0</color>
90 <font_color>0,0,0</font_color>
91 </category>
92 <!-- vaccinations -->
93 <category>
94 <name>%s</name>
95 <color>144,238,144</color>
96 <font_color>0,0,0</font_color>
97 </category>
98 <!-- substance intake -->
99 <category>
100 <name>%s</name>
101 <color>165,42,42</color>
102 <font_color>0,0,0</font_color>
103 </category>
104 <!-- life events -->
105 <category>
106 <name>%s</name>
107 <color>30,144,255</color>
108 <font_color>0,0,0</font_color>
109 </category>
110 </categories>
111 <!-- ======================================== Events ======================================== -->
112 <events>"""
113
114 xml_end = """
115 </events>
116 <view>
117 <displayed_period>
118 <start>%s</start>
119 <end>%s</end>
120 </displayed_period>
121 <hidden_categories>
122 </hidden_categories>
123 </view>
124 </timeline>"""
125
126
129
130
131
132
133 __xml_issue_template = """
134 <event>
135 <start>%(start)s</start>
136 <end>%(end)s</end>
137 <text>%(container_id)s%(label)s</text>
138 <fuzzy>%(fuzzy)s</fuzzy>
139 <locked>True</locked>
140 <ends_today>%(ends2day)s</ends_today>
141 <category>%(category)s</category>
142 <description>%(desc)s</description>
143 </event>"""
144
196
197
198
199
200 __xml_episode_template = """
201 <event>
202 <start>%(start)s</start>
203 <end>%(end)s</end>
204 <text>%(container_id)s%(label)s</text>
205 <progress>%(progress)s</progress>
206 <fuzzy>False</fuzzy>
207 <locked>True</locked>
208 <ends_today>%(ends2day)s</ends_today>
209 <category>%(category)s</category>
210 <description>%(desc)s</description>
211 </event>"""
212
247
248
249
250
251 __xml_encounter_template = """
252 <event>
253 <start>%s</start>
254 <end>%s</end>
255 <text>%s</text>
256 <progress>0</progress>
257 <fuzzy>False</fuzzy>
258 <locked>True</locked>
259 <ends_today>False</ends_today>
260 <category>%s</category>
261 <description>%s</description>
262 <milestone>%s</milestone>
263 </event>"""
264
285
286
287
288
289 __xml_hospital_stay_template = """
290 <event>
291 <start>%s</start>
292 <end>%s</end>
293 <text>%s</text>
294 <fuzzy>False</fuzzy>
295 <locked>True</locked>
296 <ends_today>False</ends_today>
297 <category>%s</category>
298 <description>%s</description>
299 </event>"""
300
312
313
314
315
316 __xml_procedure_template = """
317 <event>
318 <start>%s</start>
319 <end>%s</end>
320 <text>%s</text>
321 <fuzzy>False</fuzzy>
322 <locked>True</locked>
323 <ends_today>False</ends_today>
324 <category>%s</category>
325 <description>%s</description>
326 </event>"""
327
347
348
349
350
351 __xml_document_template = """
352 <event>
353 <start>%s</start>
354 <end>%s</end>
355 <text>%s</text>
356 <fuzzy>False</fuzzy>
357 <locked>True</locked>
358 <ends_today>False</ends_today>
359 <category>%s</category>
360 <description>%s</description>
361 </event>"""
362
372
373
374
375
376 __xml_vaccination_template = """
377 <event>
378 <start>%s</start>
379 <end>%s</end>
380 <text>%s</text>
381 <fuzzy>False</fuzzy>
382 <locked>True</locked>
383 <ends_today>False</ends_today>
384 <category>%s</category>
385 <description>%s</description>
386 </event>"""
387
401
402
403
404
405 __xml_intake_template = """
406 <event>
407 <start>%s</start>
408 <end>%s</end>
409 <text>%s</text>
410 <fuzzy>False</fuzzy>
411 <locked>True</locked>
412 <ends_today>False</ends_today>
413 <category>%s</category>
414 <description>%s</description>
415 </event>"""
416
439
440
441
442
443 -def create_timeline_file(patient=None, filename=None, include_documents=False, include_vaccinations=False, include_encounters=False):
444
445 emr = patient.emr
446 global now
447 now = gmDateTime.pydt_now_here()
448
449 if filename is None:
450 timeline_fname = gmTools.get_unique_filename(prefix = 'gm-', suffix = '.timeline')
451 else:
452 timeline_fname = filename
453 _log.debug('exporting EMR as timeline into [%s]', timeline_fname)
454 timeline = io.open(timeline_fname, mode = 'wt', encoding = 'utf8', errors = 'xmlcharrefreplace')
455
456 if patient['dob'] is None:
457 lifespan_start = format_pydt(now.replace(year = now.year - 100))
458 else:
459 lifespan_start = format_pydt(patient['dob'])
460
461 if patient['deceased'] is None:
462 life_ends2day = 'True'
463 lifespan_end = format_pydt(now)
464 else:
465 life_ends2day = 'False'
466 lifespan_end = format_pydt(patient['deceased'])
467
468 earliest_care_date = emr.earliest_care_date
469 most_recent_care_date = emr.most_recent_care_date
470 if most_recent_care_date is None:
471 most_recent_care_date = lifespan_end
472 care_ends2day = life_ends2day
473 else:
474 most_recent_care_date = format_pydt(most_recent_care_date)
475 care_ends2day = 'False'
476
477 timeline.write(xml_start % (
478
479 _('Lifespan'),
480 lifespan_start,
481 lifespan_end,
482 life_ends2day,
483 ERA_NAME_CARE_PERIOD,
484 format_pydt(earliest_care_date),
485 most_recent_care_date,
486 care_ends2day,
487
488 _('Health issues'),
489 _('Episodes'),
490 _('Encounters'),
491 _('Hospital stays'),
492 _('Procedures'),
493 _('Documents'),
494 _('Vaccinations'),
495 _('Substances'),
496 _('Life events')
497 ))
498
499 if patient['dob'] is None:
500 start = now.replace(year = now.year - 100)
501 timeline.write(__xml_encounter_template % (
502 format_pydt(start),
503 format_pydt(start),
504 '?',
505 _('Life events'),
506 _('Date of birth unknown'),
507 'True'
508 ))
509 else:
510 start = patient['dob']
511 timeline.write(__xml_encounter_template % (
512 format_pydt(patient['dob']),
513 format_pydt(patient['dob']),
514 '*',
515 _('Life events'),
516 _('Birth: %s') % patient.get_formatted_dob(format = '%Y %b %d %H:%M', honor_estimation = True),
517 'True'
518 ))
519
520
521 timeline.write(__xml_encounter_template % (
522 format_pydt(earliest_care_date),
523 format_pydt(earliest_care_date),
524 gmTools.u_heavy_greek_cross,
525 _('Life events'),
526 _('Start of Care: %s\n(the earliest recorded event of care in this praxis)') % format_pydt(earliest_care_date, format = '%Y %b %d'),
527 'True'
528 ))
529
530
531
532 timeline.write('\n <!-- ========================================\n Health issues\n======================================== -->')
533 for issue in emr.health_issues:
534 timeline.write(__format_health_issue_as_timeline_xml(issue, patient, emr))
535
536 timeline.write('\n <!-- ========================================\n Episodes\n======================================== -->')
537 for epi in emr.get_episodes(order_by = 'pk_health_issue'):
538 timeline.write(__format_episode_as_timeline_xml(epi, patient))
539
540 if include_encounters:
541 timeline.write(u'\n<!--\n========================================\n Encounters\n======================================== -->')
542 for enc in emr.get_encounters(skip_empty = True):
543 timeline.write(__format_encounter_as_timeline_xml(enc, patient))
544
545 timeline.write('\n<!--\n========================================\n Hospital stays\n======================================== -->')
546 for stay in emr.hospital_stays:
547 timeline.write(__format_hospital_stay_as_timeline_xml(stay))
548
549 timeline.write('\n<!--\n========================================\n Procedures\n======================================== -->')
550 for proc in emr.performed_procedures:
551 timeline.write(__format_procedure_as_timeline_xml(proc))
552
553 if include_vaccinations:
554 timeline.write(u'\n<!--\n========================================\n Vaccinations\n======================================== -->')
555 for vacc in emr.vaccinations:
556 timeline.write(__format_vaccination_as_timeline_xml(vacc))
557
558 timeline.write('\n<!--\n========================================\n Substance intakes\n======================================== -->')
559 for intake in emr.get_current_medications(include_inactive = True, include_unapproved = False):
560 timeline.write(__format_intake_as_timeline_xml(intake))
561
562 if include_documents:
563 timeline.write(u'\n<!--\n========================================\n Documents\n======================================== -->')
564 for doc in patient.document_folder.documents:
565 timeline.write(__format_document_as_timeline_xml(doc))
566
567
568
569
570
571
572
573 if patient['deceased'] is None:
574 end = now
575 else:
576 end = patient['deceased']
577 death_ago = gmDateTime.format_apparent_age_medically (
578 age = gmDateTime.calculate_apparent_age(start = end, end = now)
579 )
580 timeline.write(__xml_encounter_template % (
581 format_pydt(end),
582 format_pydt(end),
583 gmTools.u_dagger,
584 _('Life events'),
585 _('Death: %s\n(%s ago at age %s)') % (
586 format_pydt(end, format = '%Y %b %d %H:%M'),
587 death_ago,
588 patient.get_medical_age()
589 ),
590 'True'
591 ))
592
593
594 if end.month == 2:
595 if end.day == 29:
596
597 end = end.replace(day = 28)
598 target_year = end.year + 1
599 end = end.replace(year = target_year)
600 timeline.write(xml_end % (
601 format_pydt(start),
602 format_pydt(end)
603 ))
604
605 timeline.close()
606 return timeline_fname
607
608
609 __fake_timeline_start = """<?xml version="1.0" encoding="utf-8"?>
610 <timeline>
611 <version>0.20.0</version>
612 <categories>
613 <!-- life events -->
614 <category>
615 <name>%s</name>
616 <color>30,144,255</color>
617 <font_color>0,0,0</font_color>
618 </category>
619 </categories>
620 <events>""" % _('Life events')
621
622 __fake_timeline_body_template = """
623 <event>
624 <start>%s</start>
625 <end>%s</end>
626 <text>%s</text>
627 <fuzzy>False</fuzzy>
628 <locked>True</locked>
629 <ends_today>False</ends_today>
630 <!-- category></category -->
631 <description>%s
632 </description>
633 </event>"""
634
636 """Used to create an 'empty' timeline file for display.
637
638 - needed because .clear_timeline() doesn't really work
639 """
640 emr = patient.emr
641 global now
642 now = gmDateTime.pydt_now_here()
643
644 if filename is None:
645 timeline_fname = gmTools.get_unique_filename(prefix = 'gm-', suffix = '.timeline')
646 else:
647 timeline_fname = filename
648
649 _log.debug('creating dummy timeline in [%s]', timeline_fname)
650 timeline = io.open(timeline_fname, mode = 'wt', encoding = 'utf8', errors = 'xmlcharrefreplace')
651
652 timeline.write(__fake_timeline_start)
653
654
655 if patient['dob'] is None:
656 start = now.replace(year = now.year - 100)
657 timeline.write(__xml_encounter_template % (
658 format_pydt(start),
659 format_pydt(start),
660 _('Birth') + ': ?',
661 _('Life events'),
662 _('Date of birth unknown'),
663 'False'
664 ))
665 else:
666 start = patient['dob']
667 timeline.write(__xml_encounter_template % (
668 format_pydt(patient['dob']),
669 format_pydt(patient['dob']),
670 _('Birth') + gmTools.bool2subst(patient['dob_is_estimated'], ' (%s)' % gmTools.u_almost_equal_to, ''),
671 _('Life events'),
672 '',
673 'False'
674 ))
675
676
677 if patient['deceased'] is None:
678 end = now
679 else:
680 end = patient['deceased']
681 timeline.write(__xml_encounter_template % (
682 format_pydt(end),
683 format_pydt(end),
684
685 _('Death'),
686 _('Life events'),
687 '',
688 'False'
689 ))
690
691
692 timeline.write(__fake_timeline_body_template % (
693 format_pydt(start),
694 format_pydt(end),
695 _('Cannot display timeline.'),
696 _('Cannot display timeline.')
697 ))
698
699
700 if end.month == 2:
701 if end.day == 29:
702
703 end = end.replace(day = 28)
704 target_year = end.year + 1
705 end = end.replace(year = target_year)
706 timeline.write(xml_end % (
707 format_pydt(start),
708 format_pydt(end)
709 ))
710
711 timeline.close()
712 return timeline_fname
713
714
715
716
717 if __name__ == '__main__':
718
719 if len(sys.argv) < 2:
720 sys.exit()
721
722 if sys.argv[1] != "test":
723 sys.exit()
724
725 gmI18N.activate_locale()
726 gmI18N.install_domain('gnumed')
727
728 from Gnumed.business import gmPraxis
729 praxis = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.get_praxis_branches()[0])
730
731 from Gnumed.business import gmPerson
732
733 pat = gmPerson.gmCurrentPatient(gmPerson.cPatient(aPK_obj = 14))
734 fname = '~/gnumed/gm2tl-%s.timeline' % pat.subdir_name
735
736 print (create_timeline_file (
737 patient = pat,
738 filename = os.path.expanduser(fname),
739 include_documents = True,
740 include_vaccinations = True,
741 include_encounters = True
742 ))
743