| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf8 -*-
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 # <icon>base-64 encoded PNG image data</icon>
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 # health issues
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
145 # container IDs are supposed to be numeric
146 # 85bd7a14a1e74aab8db072ff8f417afb@H30.rldata.local
147 data = {'category': _('Health issues')}
148 possible_start = issue.possible_start_date
149 safe_start = issue.safe_start_date
150 end = issue.clinical_end_date
151 ends_today = 'False'
152 if end is None:
153 # open episode or active
154 ends_today = 'True'
155 end = now
156 data['desc'] = gmTools.xml_escape_string(issue.format (
157 patient = patient,
158 with_summary = True,
159 with_codes = True,
160 with_episodes = True,
161 with_encounters = False,
162 with_medications = False,
163 with_hospital_stays = False,
164 with_procedures = False,
165 with_family_history = False,
166 with_documents = False,
167 with_tests = False,
168 with_vaccinations = False
169 ).strip().strip('\n').strip())
170 label = gmTools.shorten_words_in_line(text = issue['description'], max_length = 25, min_word_length = 5)
171 xml = ''
172 # if possible_start < safe_start:
173 # data['start'] = format_pydt(possible_start)
174 # data['end'] = format_pydt(safe_start)
175 # data['ends2day'] = 'False'
176 # data['fuzzy'] = 'True'
177 # data['container_id'] = ''
178 # data['label'] = '?%s?' % gmTools.xml_escape_string(label)
179 # xml += __xml_issue_template % data
180 data['start'] = format_pydt(safe_start)
181 data['end'] = format_pydt(end)
182 data['ends2day'] = ends_today
183 data['fuzzy'] = 'False'
184 data['container_id'] = '[%s]' % issue['pk_health_issue']
185 data['label'] = gmTools.xml_escape_string(label)
186 xml += __xml_issue_template % data
187 return xml
188
189 #------------------------------------------------------------
190 # episodes
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
206 data = {
207 'category': _('Episodes'),
208 'start': format_pydt(episode.best_guess_clinical_start_date),
209 'container_id': gmTools.coalesce(episode['pk_health_issue'], '', '(%s)'),
210 'label': gmTools.xml_escape_string (
211 gmTools.shorten_words_in_line(text = episode['description'], max_length = 20, min_word_length = 5)
212 ),
213 'ends2day': gmTools.bool2subst(episode['episode_open'], 'True', 'False'),
214 'progress': gmTools.bool2subst(episode['episode_open'], '0', '100'),
215 'desc': gmTools.xml_escape_string(episode.format (
216 patient = patient,
217 with_summary = True,
218 with_codes = True,
219 with_encounters = True,
220 with_documents = False,
221 with_hospital_stays = False,
222 with_procedures = False,
223 with_family_history = False,
224 with_tests = False,
225 with_vaccinations = False,
226 with_health_issue = True
227 ).strip().strip('\n').strip())
228 }
229 end = episode.best_guess_clinical_end_date
230 if end is None:
231 data['end'] = format_pydt(now)
232 else:
233 data['end'] = format_pydt(end)
234 return __xml_episode_template % data
235
236 #------------------------------------------------------------
237 # encounters
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
254 return __xml_encounter_template % (
255 format_pydt(encounter['started']),
256 format_pydt(encounter['last_affirmed']),
257 #u'(%s)' % encounter['pk_episode'],
258 gmTools.xml_escape_string(format_pydt(encounter['started'], format = '%b %d')),
259 _('Encounters'), # category
260 gmTools.xml_escape_string(encounter.format (
261 patient = patient,
262 with_soap = True,
263 with_docs = False,
264 with_tests = False,
265 fancy_header = False,
266 with_vaccinations = False,
267 with_co_encountlet_hints = False,
268 with_rfe_aoe = True,
269 with_family_history = False
270 ).strip().strip('\n').strip()),
271 'False'
272 )
273
274 #------------------------------------------------------------
275 # hospital stays
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
290 end = stay['discharge']
291 if end is None:
292 end = now
293 return __xml_hospital_stay_template % (
294 format_pydt(stay['admission']),
295 format_pydt(end),
296 gmTools.xml_escape_string(stay['hospital']),
297 _('Hospital stays'), # category
298 gmTools.xml_escape_string(stay.format().strip().strip('\n').strip())
299 )
300
301 #------------------------------------------------------------
302 # procedures
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
317 if proc['is_ongoing']:
318 end = now
319 else:
320 if proc['clin_end'] is None:
321 end = proc['clin_when']
322 else:
323 end = proc['clin_end']
324 desc = gmTools.shorten_words_in_line(text = proc['performed_procedure'], max_length = 20, min_word_length = 5)
325 return __xml_procedure_template % (
326 format_pydt(proc['clin_when']),
327 format_pydt(end),
328 gmTools.xml_escape_string(desc),
329 _('Procedures'),
330 gmTools.xml_escape_string(proc.format (
331 include_episode = True,
332 include_codes = True
333 ).strip().strip('\n').strip())
334 )
335
336 #------------------------------------------------------------
337 # documents
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
352 desc = gmTools.shorten_words_in_line(text = doc['l10n_type'], max_length = 20, min_word_length = 5)
353 return __xml_document_template % (
354 format_pydt(doc['clin_when']),
355 format_pydt(doc['clin_when']),
356 gmTools.xml_escape_string(desc),
357 _('Documents'),
358 gmTools.xml_escape_string(doc.format().strip().strip('\n').strip())
359 )
360
361 #------------------------------------------------------------
362 # vaccinations
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
377 return __xml_vaccination_template % (
378 format_pydt(vacc['date_given']),
379 format_pydt(vacc['date_given']),
380 gmTools.xml_escape_string(vacc['vaccine']),
381 _('Vaccinations'),
382 gmTools.xml_escape_string('\n'.join(vacc.format (
383 with_indications = True,
384 with_comment = True,
385 with_reaction = True,
386 date_format = '%Y %b %d'
387 )).strip().strip('\n').strip())
388 )
389
390 #------------------------------------------------------------
391 # substance intake
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
406 if intake['discontinued'] is None:
407 if intake['duration'] is None:
408 if intake['seems_inactive']:
409 end = intake['started']
410 else:
411 end = now
412 else:
413 end = intake['started'] + intake['duration']
414 else:
415 end = intake['discontinued']
416
417 return __xml_intake_template % (
418 format_pydt(intake['started']),
419 format_pydt(end),
420 gmTools.xml_escape_string(intake['substance']),
421 _('Substances'),
422 gmTools.xml_escape_string(intake.format (
423 single_line = False,
424 show_all_product_components = False
425 ).strip().strip('\n').strip())
426 )
427
428 #------------------------------------------------------------
429 # main library entry point
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') # .timeline required ...
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 # era: life span of patient
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 # categories
476 _('Health issues'),
477 _('Episodes'),
478 _('Encounters'),
479 _('Hospital stays'),
480 _('Procedures'),
481 _('Documents'),
482 _('Vaccinations'),
483 _('Substances'),
484 _('Life events')
485 ))
486 # birth
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 # start of care
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 # containers must be defined before their
523 # subevents, so put health issues first
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 # allergies ?
560 # - unclear where and how to place
561 # test results ?
562 # - too many events, at most "day sample drawn"
563
564 # death
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 # display range
578 if end.month == 2:
579 if end.day == 29:
580 # leap years aren't consecutive
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 # birth
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 # death
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 #u'',
669 _('Death'),
670 _('Life events'),
671 '',
672 'False'
673 ))
674
675 # fake issue
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 # display range
684 if end.month == 2:
685 if end.day == 29:
686 # leap years aren't consecutive
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 # main
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 # 14 / 20 / 138 / 58 / 20 / 5
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
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Jan 25 02:55:27 2019 | http://epydoc.sourceforge.net |