| 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 gmTools
19 from Gnumed.pycommon import gmDateTime
20
21
22 _log = logging.getLogger('gm.tl')
23
24 #============================================================
25 if __name__ == '__main__':
26 _ = lambda x:x
27
28 ERA_NAME_CARE_PERIOD = _('Care Period')
29
30 #============================================================
31
32 # <icon>base-64 encoded PNG image data</icon>
33
34 #============================================================
35 xml_start = """<?xml version="1.0" encoding="utf-8"?>
36 <timeline>
37 <version>1.16.0</version>
38 <!-- ======================================== Eras ======================================== -->
39 <eras>
40 <era>
41 <name>%s</name>
42 <start>%s</start>
43 <end>%s</end>
44 <color>205,238,241</color>
45 <ends_today>%s</ends_today>
46 </era>
47 <era>
48 <name>%s</name>
49 <start>%s</start>
50 <end>%s</end>
51 <color>161,210,226</color>
52 <ends_today>%s</ends_today>
53 </era>
54 </eras>
55 <!-- ======================================== Categories ======================================== -->
56 <categories>
57 <!-- health issues -->
58 <category>
59 <name>%s</name>
60 <color>255,0,0</color>
61 <font_color>0,0,0</font_color>
62 </category>
63 <!-- episodes -->
64 <category>
65 <name>%s</name>
66 <color>0,255,0</color>
67 <font_color>0,0,0</font_color>
68 </category>
69 <!-- encounters -->
70 <category>
71 <name>%s</name>
72 <color>30,144,255</color>
73 <font_color>0,0,0</font_color>
74 </category>
75 <!-- hospital stays -->
76 <category>
77 <name>%s</name>
78 <color>255,255,0</color>
79 <font_color>0,0,0</font_color>
80 </category>
81 <!-- procedures -->
82 <category>
83 <name>%s</name>
84 <color>160,32,140</color>
85 <font_color>0,0,0</font_color>
86 </category>
87 <!-- documents -->
88 <category>
89 <name>%s</name>
90 <color>255,165,0</color>
91 <font_color>0,0,0</font_color>
92 </category>
93 <!-- vaccinations -->
94 <category>
95 <name>%s</name>
96 <color>144,238,144</color>
97 <font_color>0,0,0</font_color>
98 </category>
99 <!-- substance intake -->
100 <category>
101 <name>%s</name>
102 <color>165,42,42</color>
103 <font_color>0,0,0</font_color>
104 </category>
105 <!-- life events -->
106 <category>
107 <name>%s</name>
108 <color>30,144,255</color>
109 <font_color>0,0,0</font_color>
110 </category>
111 </categories>
112 <!-- ======================================== Events ======================================== -->
113 <events>"""
114
115 xml_end = """
116 </events>
117 <view>
118 <displayed_period>
119 <start>%s</start>
120 <end>%s</end>
121 </displayed_period>
122 <hidden_categories>
123 </hidden_categories>
124 </view>
125 </timeline>"""
126
127 #============================================================
130
131 #------------------------------------------------------------
132 # health issues
133 #------------------------------------------------------------
134 __xml_issue_template = """
135 <event>
136 <start>%(start)s</start>
137 <end>%(end)s</end>
138 <text>%(container_id)s%(label)s</text>
139 <fuzzy>%(fuzzy)s</fuzzy>
140 <locked>True</locked>
141 <ends_today>%(ends2day)s</ends_today>
142 <category>%(category)s</category>
143 <description>%(desc)s</description>
144 </event>"""
145
147 # container IDs are supposed to be numeric
148 # 85bd7a14a1e74aab8db072ff8f417afb@H30.rldata.local
149 data = {'category': _('Health issues')}
150 possible_start = issue.possible_start_date
151 safe_start = issue.safe_start_date
152 end = issue.clinical_end_date
153 ends_today = 'False'
154 if end is None:
155 # open episode or active
156 ends_today = 'True'
157 end = now
158 data['desc'] = gmTools.xml_escape_string(issue.format (
159 patient = patient,
160 with_summary = True,
161 with_codes = True,
162 with_episodes = True,
163 with_encounters = False,
164 with_medications = False,
165 with_hospital_stays = False,
166 with_procedures = False,
167 with_family_history = False,
168 with_documents = False,
169 with_tests = False,
170 with_vaccinations = False
171 ).strip().strip('\n').strip())
172 txt = gmTools.shorten_words_in_line(text = issue['description'], max_length = 25, min_word_length = 5)
173 xml = ''
174 if possible_start < safe_start:
175 data['start'] = format_pydt(possible_start)
176 data['end'] = format_pydt(safe_start)
177 data['ends2day'] = 'False'
178 data['fuzzy'] = 'True'
179 data['container_id'] = ''
180 data['label'] = '?%s?' % gmTools.xml_escape_string(txt)
181 xml += __xml_issue_template % data
182 data['start'] = format_pydt(safe_start)
183 data['end'] = format_pydt(end)
184 data['ends2day'] = ends_today
185 data['fuzzy'] = 'False'
186 data['container_id'] = '[%s]' % issue['pk_health_issue']
187 data['label'] = gmTools.xml_escape_string(txt)
188 xml += __xml_issue_template % data
189 return xml
190
191 #------------------------------------------------------------
192 # episodes
193 #------------------------------------------------------------
194 __xml_episode_template = """
195 <event>
196 <start>%(start)s</start>
197 <end>%(end)s</end>
198 <text>%(container_id)s%(label)s</text>
199 <progress>%(progress)s</progress>
200 <fuzzy>False</fuzzy>
201 <locked>True</locked>
202 <ends_today>%(ends2day)s</ends_today>
203 <category>%(category)s</category>
204 <description>%(desc)s</description>
205 </event>"""
206
208 data = {
209 'category': _('Episodes'),
210 'start': format_pydt(episode.best_guess_clinical_start_date),
211 'container_id': gmTools.coalesce(episode['pk_health_issue'], '', '(%s)'),
212 'label': gmTools.xml_escape_string (
213 gmTools.shorten_words_in_line(text = episode['description'], max_length = 20, min_word_length = 5)
214 ),
215 'ends2day': gmTools.bool2subst(episode['episode_open'], 'True', 'False'),
216 'progress': gmTools.bool2subst(episode['episode_open'], '0', '100'),
217 'desc': gmTools.xml_escape_string(episode.format (
218 patient = patient,
219 with_summary = True,
220 with_codes = True,
221 with_encounters = True,
222 with_documents = False,
223 with_hospital_stays = False,
224 with_procedures = False,
225 with_family_history = False,
226 with_tests = False,
227 with_vaccinations = False,
228 with_health_issue = True
229 ).strip().strip('\n').strip())
230 }
231 end = episode.best_guess_clinical_end_date
232 if end is None:
233 data['end'] = format_pydt(now)
234 else:
235 data['end'] = format_pydt(end)
236 return __xml_episode_template % data
237
238 #------------------------------------------------------------
239 # encounters
240 #------------------------------------------------------------
241 __xml_encounter_template = """
242 <event>
243 <start>%s</start>
244 <end>%s</end>
245 <text>%s</text>
246 <progress>0</progress>
247 <fuzzy>False</fuzzy>
248 <locked>True</locked>
249 <ends_today>False</ends_today>
250 <category>%s</category>
251 <description>%s</description>
252 <milestone>%s</milestone>
253 </event>"""
254
256 return __xml_encounter_template % (
257 format_pydt(encounter['started']),
258 format_pydt(encounter['last_affirmed']),
259 #u'(%s)' % encounter['pk_episode'],
260 gmTools.xml_escape_string(format_pydt(encounter['started'], format = '%b %d')),
261 _('Encounters'), # category
262 gmTools.xml_escape_string(encounter.format (
263 patient = patient,
264 with_soap = True,
265 with_docs = False,
266 with_tests = False,
267 fancy_header = False,
268 with_vaccinations = False,
269 with_co_encountlet_hints = False,
270 with_rfe_aoe = True,
271 with_family_history = False
272 ).strip().strip('\n').strip()),
273 'False'
274 )
275
276 #------------------------------------------------------------
277 # hospital stays
278 #------------------------------------------------------------
279 __xml_hospital_stay_template = """
280 <event>
281 <start>%s</start>
282 <end>%s</end>
283 <text>%s</text>
284 <fuzzy>False</fuzzy>
285 <locked>True</locked>
286 <ends_today>False</ends_today>
287 <category>%s</category>
288 <description>%s</description>
289 </event>"""
290
292 end = stay['discharge']
293 if end is None:
294 end = now
295 return __xml_hospital_stay_template % (
296 format_pydt(stay['admission']),
297 format_pydt(end),
298 gmTools.xml_escape_string(stay['hospital']),
299 _('Hospital stays'), # category
300 gmTools.xml_escape_string(stay.format().strip().strip('\n').strip())
301 )
302
303 #------------------------------------------------------------
304 # procedures
305 #------------------------------------------------------------
306 __xml_procedure_template = """
307 <event>
308 <start>%s</start>
309 <end>%s</end>
310 <text>%s</text>
311 <fuzzy>False</fuzzy>
312 <locked>True</locked>
313 <ends_today>False</ends_today>
314 <category>%s</category>
315 <description>%s</description>
316 </event>"""
317
319 if proc['is_ongoing']:
320 end = now
321 else:
322 if proc['clin_end'] is None:
323 end = proc['clin_when']
324 else:
325 end = proc['clin_end']
326 desc = gmTools.shorten_words_in_line(text = proc['performed_procedure'], max_length = 20, min_word_length = 5)
327 return __xml_procedure_template % (
328 format_pydt(proc['clin_when']),
329 format_pydt(end),
330 gmTools.xml_escape_string(desc),
331 _('Procedures'),
332 gmTools.xml_escape_string(proc.format (
333 include_episode = True,
334 include_codes = True
335 ).strip().strip('\n').strip())
336 )
337
338 #------------------------------------------------------------
339 # documents
340 #------------------------------------------------------------
341 __xml_document_template = """
342 <event>
343 <start>%s</start>
344 <end>%s</end>
345 <text>%s</text>
346 <fuzzy>False</fuzzy>
347 <locked>True</locked>
348 <ends_today>False</ends_today>
349 <category>%s</category>
350 <description>%s</description>
351 </event>"""
352
354 desc = gmTools.shorten_words_in_line(text = doc['l10n_type'], max_length = 20, min_word_length = 5)
355 return __xml_document_template % (
356 format_pydt(doc['clin_when']),
357 format_pydt(doc['clin_when']),
358 gmTools.xml_escape_string(desc),
359 _('Documents'),
360 gmTools.xml_escape_string(doc.format().strip().strip('\n').strip())
361 )
362
363 #------------------------------------------------------------
364 # vaccinations
365 #------------------------------------------------------------
366 __xml_vaccination_template = """
367 <event>
368 <start>%s</start>
369 <end>%s</end>
370 <text>%s</text>
371 <fuzzy>False</fuzzy>
372 <locked>True</locked>
373 <ends_today>False</ends_today>
374 <category>%s</category>
375 <description>%s</description>
376 </event>"""
377
379 return __xml_vaccination_template % (
380 format_pydt(vacc['date_given']),
381 format_pydt(vacc['date_given']),
382 gmTools.xml_escape_string(vacc['vaccine']),
383 _('Vaccinations'),
384 gmTools.xml_escape_string('\n'.join(vacc.format (
385 with_indications = True,
386 with_comment = True,
387 with_reaction = True,
388 date_format = '%Y %b %d'
389 )).strip().strip('\n').strip())
390 )
391
392 #------------------------------------------------------------
393 # substance intake
394 #------------------------------------------------------------
395 __xml_intake_template = """
396 <event>
397 <start>%s</start>
398 <end>%s</end>
399 <text>%s</text>
400 <fuzzy>False</fuzzy>
401 <locked>True</locked>
402 <ends_today>False</ends_today>
403 <category>%s</category>
404 <description>%s</description>
405 </event>"""
406
408 if intake['discontinued'] is None:
409 if intake['duration'] is None:
410 if intake['seems_inactive']:
411 end = intake['started']
412 else:
413 end = now
414 else:
415 end = intake['started'] + intake['duration']
416 else:
417 end = intake['discontinued']
418
419 return __xml_intake_template % (
420 format_pydt(intake['started']),
421 format_pydt(end),
422 gmTools.xml_escape_string(intake['substance']),
423 _('Substances'),
424 gmTools.xml_escape_string(intake.format (
425 single_line = False,
426 show_all_product_components = False
427 ).strip().strip('\n').strip())
428 )
429
430 #------------------------------------------------------------
431 # main library entry point
432 #------------------------------------------------------------
434
435 emr = patient.emr
436 global now
437 now = gmDateTime.pydt_now_here()
438
439 if filename is None:
440 timeline_fname = gmTools.get_unique_filename(prefix = 'gm-', suffix = '.timeline') # .timeline required ...
441 else:
442 timeline_fname = filename
443 _log.debug('exporting EMR as timeline into [%s]', timeline_fname)
444 timeline = io.open(timeline_fname, mode = 'wt', encoding = 'utf8', errors = 'xmlcharrefreplace')
445
446 if patient['dob'] is None:
447 lifespan_start = format_pydt(now.replace(year = now.year - 100))
448 else:
449 lifespan_start = format_pydt(patient['dob'])
450
451 if patient['deceased'] is None:
452 life_ends2day = 'True'
453 lifespan_end = format_pydt(now)
454 else:
455 life_ends2day = 'False'
456 lifespan_end = format_pydt(patient['deceased'])
457
458 earliest_care_date = emr.earliest_care_date
459 most_recent_care_date = emr.most_recent_care_date
460 if most_recent_care_date is None:
461 most_recent_care_date = lifespan_end
462 care_ends2day = life_ends2day
463 else:
464 most_recent_care_date = format_pydt(most_recent_care_date)
465 care_ends2day = 'False'
466
467 timeline.write(xml_start % (
468 # era: life span of patient
469 _('Lifespan'),
470 lifespan_start,
471 lifespan_end,
472 life_ends2day,
473 ERA_NAME_CARE_PERIOD,
474 format_pydt(earliest_care_date),
475 most_recent_care_date,
476 care_ends2day,
477 # categories
478 _('Health issues'),
479 _('Episodes'),
480 _('Encounters'),
481 _('Hospital stays'),
482 _('Procedures'),
483 _('Documents'),
484 _('Vaccinations'),
485 _('Substances'),
486 _('Life events')
487 ))
488 # birth
489 if patient['dob'] is None:
490 start = now.replace(year = now.year - 100)
491 timeline.write(__xml_encounter_template % (
492 format_pydt(start),
493 format_pydt(start),
494 '?',
495 _('Life events'),
496 _('Date of birth unknown'),
497 'True'
498 ))
499 else:
500 start = patient['dob']
501 timeline.write(__xml_encounter_template % (
502 format_pydt(patient['dob']),
503 format_pydt(patient['dob']),
504 '*',
505 _('Life events'),
506 '%s: %s (%s)' % (
507 _('Birth'),
508 patient.get_formatted_dob(format = '%Y %b %d %H:%M', honor_estimation = True),
509 patient.get_medical_age()
510 ),
511 'True'
512 ))
513
514 # start of care
515 timeline.write(__xml_encounter_template % (
516 format_pydt(earliest_care_date),
517 format_pydt(earliest_care_date),
518 gmTools.u_heavy_greek_cross,
519 _('Life events'),
520 _('Start of Care: %s\n(the earliest recorded event of care in this praxis)') % format_pydt(earliest_care_date, format = '%Y %b %d'),
521 'True'
522 ))
523
524 # containers must be defined before their
525 # subevents, so put health issues first
526 timeline.write('\n <!-- ========================================\n Health issues\n======================================== -->')
527 for issue in emr.health_issues:
528 timeline.write(__format_health_issue_as_timeline_xml(issue, patient, emr))
529
530 timeline.write('\n <!-- ========================================\n Episodes\n======================================== -->')
531 for epi in emr.get_episodes(order_by = 'pk_health_issue'):
532 timeline.write(__format_episode_as_timeline_xml(epi, patient))
533
534 # simply too many
535 #timeline.write(u'\n<!--\n========================================\n Encounters\n======================================== -->')
536 #for enc in emr.get_encounters(skip_empty = True):
537 # timeline.write(__format_encounter_as_timeline_xml(enc, patient))
538
539 timeline.write('\n<!--\n========================================\n Hospital stays\n======================================== -->')
540 for stay in emr.hospital_stays:
541 timeline.write(__format_hospital_stay_as_timeline_xml(stay))
542
543 timeline.write('\n<!--\n========================================\n Procedures\n======================================== -->')
544 for proc in emr.performed_procedures:
545 timeline.write(__format_procedure_as_timeline_xml(proc))
546
547 # timeline.write(u'\n<!--\n========================================\n Vaccinations\n======================================== -->')
548 # for vacc in emr.vaccinations:
549 # timeline.write(__format_vaccination_as_timeline_xml(vacc))
550
551 timeline.write('\n<!--\n========================================\n Substance intakes\n======================================== -->')
552 for intake in emr.get_current_medications(include_inactive = True, include_unapproved = False):
553 timeline.write(__format_intake_as_timeline_xml(intake))
554
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 from Gnumed.pycommon import gmI18N
710 gmI18N.activate_locale()
711 gmI18N.install_domain('gnumed')
712
713 from Gnumed.business import gmPraxis
714 praxis = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.get_praxis_branches()[0])
715
716 from Gnumed.business import gmPerson
717 # 14 / 20 / 138 / 58 / 20 / 5
718 pat = gmPerson.gmCurrentPatient(gmPerson.cPatient(aPK_obj = 14))
719 fname = '~/gnumed/gm2tl-%s.timeline' % pat.subdir_name
720
721 print(create_timeline_file(patient = pat, filename = os.path.expanduser(fname)))
722
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu May 10 01:55:20 2018 | http://epydoc.sourceforge.net |