Package Gnumed :: Package exporters :: Module timeline |
[frames] | no frames]

# Source Code for Module Gnumed.exporters.timeline

      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 codecs
     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  xml_start = u"""<?xml version="1.0" encoding="utf-8"?>
     26  <timeline>
     27          <version>0.17.0</version>
     28          <categories>
     29                  <!-- health issues -->
     30                  <category>
     31                          <name>%s</name>
     32                          <color>255,0,0</color>
     33                          <font_color>0,0,0</font_color>
     34                  </category>
     35                  <!-- episodes -->
     36                  <category>
     37                          <name>%s</name>
     38                          <color>0,255,0</color>
     39                          <font_color>0,0,0</font_color>
     40                  </category>
     41                  <!-- encounters -->
     42                  <category>
     43                          <name>%s</name>
     44                          <color>30,144,255</color>
     45                          <font_color>0,0,0</font_color>
     46                  </category>
     47                  <!-- hospital stays -->
     48                  <category>
     49                          <name>%s</name>
     50                          <color>255,255,0</color>
     51                          <font_color>0,0,0</font_color>
     52                  </category>
     53                  <!-- procedures -->
     54                  <category>
     55                          <name>%s</name>
     56                          <color>160,32,140</color>
     57                          <font_color>0,0,0</font_color>
     58                  </category>
     59                  <!-- documents -->
     60                  <category>
     61                          <name>%s</name>
     62                          <color>255,165,0</color>
     63                          <font_color>0,0,0</font_color>
     64                  </category>
     65                  <!-- vaccinations -->
     66                  <category>
     67                          <name>%s</name>
     68                          <color>144,238,144</color>
     69                          <font_color>0,0,0</font_color>
     70                  </category>
     71                  <!-- substance intake -->
     72                  <category>
     73                          <name>%s</name>
     74                          <color>165,42,42</color>
     75                          <font_color>0,0,0</font_color>
     76                  </category>
     77                  <!-- life events -->
     78                  <category>
     79                          <name>%s</name>
     80                          <color>30,144,255</color>
     81                          <font_color>0,0,0</font_color>
     82                  </category>
     83          </categories>
     84          <events>"""
     85
     86  xml_end = u"""
     87          </events>
     88          <view>
     89                  <displayed_period>
     90                          <start>%s</start>
     91                          <end>%s</end>
     92                  </displayed_period>
     93          <hidden_categories>
     94          </hidden_categories>
     95          </view>
     96  </timeline>"""
     97
     98  #============================================================
     99 -def format_pydt(pydt, format = '%Y-%m-%d %H:%M:%S'):
    100          return gmDateTime.pydt_strftime(pydt, format = format, accuracy = gmDateTime.acc_seconds)
    101
    102  #------------------------------------------------------------
    103  # health issues
    104  #------------------------------------------------------------
    105  __xml_issue_template = u"""
    106                  <event>
    107                          <start>%s</start>
    108                          <end>%s</end>
    109                          <text>%s</text>
    110                          <fuzzy>False</fuzzy>
    111                          <locked>True</locked>
    112                          <ends_today>%s</ends_today>
    113                          <category>%s</category>
    114                          <description>%s
    115                          </description>
    116                  </event>"""
    117
    118 -def __format_health_issue_as_timeline_xml(issue, patient, emr):
    119          tooltip = issue.format (
    120                  patient = patient,
    121                  with_summary = True,
    122                  with_codes = True,
    123                  with_episodes = True,
    124                  with_encounters = False,
    125                  with_medications = False,
    126                  with_hospital_stays = False,
    127                  with_procedures = False,
    128                  with_family_history = False,
    129                  with_documents = False,
    130                  with_tests = False,
    131                  with_vaccinations = False
    132          )
    133          safe_start = issue.safe_start_date
    134          possible_start = issue.possible_start_date
    135          txt = u''
    136          if possible_start < safe_start:
    137                  txt += __xml_issue_template % (
    138                          format_pydt(possible_start),                                                                    # start
    139                          format_pydt(safe_start),                                                                                # end
    140                          gmTools.xml_escape_string(u'?[%s]?' % issue['description']),    # text
    141                          u'False',                                                                                                               # ends_today
    142                          _('Health issues'),                                                                                             # category
    143                          gmTools.xml_escape_string(tooltip)                                                              # description
    144                  )
    145          txt += __xml_issue_template % (
    146                  format_pydt(safe_start),                                                                                        # start
    147                  format_pydt(issue.end_date),                                                                            # end
    148                  gmTools.xml_escape_string(issue['description']),                                        # text
    149                  gmTools.bool2subst(issue['is_active'], u'True', u'False'),                      # ends_today
    150                  _('Health issues'),                                                                                                     # category
    151                  gmTools.xml_escape_string(tooltip)                                                                      # description
    152          )
    153          return txt
    154  #------------------------------------------------------------
    155  # episodes
    156  #------------------------------------------------------------
    157  __xml_episode_template = u"""
    158                  <event>
    159                          <start>%s</start>
    160                          <end>%s</end>
    161                          <text>%s</text>
    162                          <fuzzy>False</fuzzy>
    163                          <locked>True</locked>
    164                          <ends_today>%s</ends_today>
    165                          <category>%s</category>
    166                          <description>%s
    167                          </description>
    168                  </event>"""
    169
    170 -def __format_episode_as_timeline_xml(episode, patient):
    171          end = gmTools.bool2subst (
    172                  episode['episode_open'],
    173                  format_pydt(now),
    174                  format_pydt(episode.latest_access_date),
    175          )
    176          return __xml_episode_template % (
    177                  format_pydt(episode.best_guess_start_date),                                                     # start
    178                  end,                                                                                                                            # end
    179                  gmTools.xml_escape_string(episode['description']),                                      # text
    180                  gmTools.bool2subst(episode['episode_open'], u'True', u'False'),         # ends_today
    181                  _('Episodes'),                                                                                                          # category
    182                  gmTools.xml_escape_string(episode.format (                                                      # description
    183                          patient = patient,
    184                          with_summary = True,
    185                          with_codes = True,
    186                          with_encounters = True,
    187                          with_documents = False,
    188                          with_hospital_stays = False,
    189                          with_procedures = False,
    190                          with_family_history = False,
    191                          with_tests = False,
    192                          with_vaccinations = False,
    193                          with_health_issue = True
    194                  ))
    195          )
    196
    197  #------------------------------------------------------------
    198  # encounters
    199  #------------------------------------------------------------
    200  __xml_encounter_template = u"""
    201                  <event>
    202                          <start>%s</start>
    203                          <end>%s</end>
    204                          <text>%s</text>
    205                          <fuzzy>False</fuzzy>
    206                          <locked>True</locked>
    207                          <ends_today>False</ends_today>
    208                          <category>%s</category>
    209                          <description>%s
    210                          </description>
    211                  </event>"""
    212
    213 -def __format_encounter_as_timeline_xml(encounter, patient):
    214          return __xml_encounter_template % (
    215                  format_pydt(encounter['started']),
    216                  format_pydt(encounter['last_affirmed']),
    217                  #u'(%s)' % encounter['pk_episode'],
    218                  gmTools.xml_escape_string(format_pydt(encounter['started'], format = '%b %d')),
    219                  _('Encounters'),                                                                                                # category
    220                  gmTools.xml_escape_string(encounter.format (
    221                          patient = patient,
    222                          with_soap = True,
    223                          with_docs = False,
    224                          with_tests = False,
    225                          fancy_header = False,
    226                          with_vaccinations = False,
    227                          with_co_encountlet_hints = False,
    228                          with_rfe_aoe = True,
    229                          with_family_history = False
    230                  ))
    231          )
    232
    233  #------------------------------------------------------------
    234  # hospital stays
    235  #------------------------------------------------------------
    236  __xml_hospital_stay_template = u"""
    237                  <event>
    238                          <start>%s</start>
    239                          <end>%s</end>
    240                          <text>%s</text>
    241                          <fuzzy>False</fuzzy>
    242                          <locked>True</locked>
    243                          <ends_today>False</ends_today>
    244                          <category>%s</category>
    245                          <description>%s
    246                          </description>
    247                  </event>"""
    248
    249 -def __format_hospital_stay_as_timeline_xml(stay):
    250          end = stay['discharge']
    251          if end is None:
    252                  end = now
    253          return __xml_hospital_stay_template % (
    254                  format_pydt(stay['admission']),
    255                  format_pydt(end),
    256                  gmTools.xml_escape_string(stay['hospital']),
    257                  _('Hospital stays'),                                                                                            # category
    258                  gmTools.xml_escape_string(stay.format())
    259          )
    260
    261  #------------------------------------------------------------
    262  # procedures
    263  #------------------------------------------------------------
    264  __xml_procedure_template = u"""
    265                  <event>
    266                          <start>%s</start>
    267                          <end>%s</end>
    268                          <text>%s</text>
    269                          <fuzzy>False</fuzzy>
    270                          <locked>True</locked>
    271                          <ends_today>False</ends_today>
    272                          <category>%s</category>
    273                          <description>%s
    274                          </description>
    275                  </event>"""
    276
    277 -def __format_procedure_as_timeline_xml(proc):
    278          if proc['is_ongoing']:
    279                  end = now
    280          else:
    281                  if proc['clin_end'] is None:
    282                          end = proc['clin_when']
    283                  else:
    284                          end = proc['clin_end']
    285          return __xml_procedure_template % (
    286                  format_pydt(proc['clin_when']),
    287                  format_pydt(end),
    288                  gmTools.xml_escape_string(proc['performed_procedure']),
    289                  _('Procedures'),
    290                  gmTools.xml_escape_string(proc.format (
    291                          include_episode = True,
    292                          include_codes = True
    293                  ))
    294          )
    295
    296  #------------------------------------------------------------
    297  # documents
    298  #------------------------------------------------------------
    299  __xml_document_template = u"""
    300                  <event>
    301                          <start>%s</start>
    302                          <end>%s</end>
    303                          <text>%s</text>
    304                          <fuzzy>False</fuzzy>
    305                          <locked>True</locked>
    306                          <ends_today>False</ends_today>
    307                          <category>%s</category>
    308                          <description>%s
    309                          </description>
    310                  </event>"""
    311
    312 -def __format_document_as_timeline_xml(doc):
    313          return __xml_document_template % (
    314                  format_pydt(doc['clin_when']),
    315                  format_pydt(doc['clin_when']),
    316                  gmTools.xml_escape_string(doc['l10n_type']),
    317                  _('Documents'),
    318                  gmTools.xml_escape_string(doc.format())
    319          )
    320
    321  #------------------------------------------------------------
    322  # vaccinations
    323  #------------------------------------------------------------
    324  __xml_vaccination_template = u"""
    325                  <event>
    326                          <start>%s</start>
    327                          <end>%s</end>
    328                          <text>%s</text>
    329                          <fuzzy>False</fuzzy>
    330                          <locked>True</locked>
    331                          <ends_today>False</ends_today>
    332                          <category>%s</category>
    333                          <description>%s
    334                          </description>
    335                  </event>"""
    336
    337 -def __format_vaccination_as_timeline_xml(vacc):
    338          return __xml_vaccination_template % (
    339                  format_pydt(vacc['date_given']),
    340                  format_pydt(vacc['date_given']),
    341                  gmTools.xml_escape_string(vacc['vaccine']),
    342                  _('Vaccinations'),
    343                  gmTools.xml_escape_string(u'\n'.join(vacc.format (
    344                          with_indications = True,
    345                          with_comment = True,
    346                          with_reaction = True,
    347                          date_format = '%Y %b %d'
    348                  )))
    349          )
    350
    351  #------------------------------------------------------------
    352  # substance intakt
    353  #------------------------------------------------------------
    354  __xml_intake_template = u"""
    355                  <event>
    356                          <start>%s</start>
    357                          <end>%s</end>
    358                          <text>%s</text>
    359                          <fuzzy>False</fuzzy>
    360                          <locked>True</locked>
    361                          <ends_today>False</ends_today>
    362                          <category>%s</category>
    363                          <description>%s
    364                          </description>
    365                  </event>"""
    366
    367 -def __format_intake_as_timeline_xml(intake):
    368          if intake['discontinued'] is None:
    369                  if intake['duration'] is None:
    370                          if intake['seems_inactive']:
    371                                  end = intake['started']
    372                          else:
    373                                  end = now
    374                  else:
    375                          end = intake['started'] + intake['duration']
    376          else:
    377                  end = intake['discontinued']
    378
    379          return __xml_intake_template % (
    380                  format_pydt(intake['started']),
    381                  format_pydt(end),
    382                  gmTools.xml_escape_string(intake['substance']),
    383                  _('Substances'),
    384                  gmTools.xml_escape_string(intake.format (
    385                          one_line = False,
    386                          show_all_brand_components = False
    387                  ))
    388          )
    389
    390  #------------------------------------------------------------
    391  # library entry point
    392  #------------------------------------------------------------
    393 -def create_timeline_file(patient=None, filename=None):
    394
    395          emr = patient.emr
    396          global now
    397          now = gmDateTime.pydt_now_here()
    398
    399          if filename is None:
    400                  timeline_fname = gmTools.get_unique_filename(prefix = u'gm-', suffix = u'.timeline')
    401          else:
    402                  timeline_fname = filename
    403          _log.debug('exporting EMR as timeline into [%s]', timeline_fname)
    404          timeline = codecs.open(timeline_fname, mode = 'wb', encoding = 'utf8', errors = 'xmlcharrefreplace')
    405          timeline.write(xml_start % (
    406                  _('Health issues'),
    407                  _('Episodes'),
    408                  _('Encounters'),
    409                  _('Hospital stays'),
    410                  _('Procedures'),
    411                  _('Documents'),
    412                  _('Vaccinations'),
    413                  _('Substances'),
    414                  _('Life events')
    415          ))
    416          # birth
    417          if patient['dob'] is None:
    418                  start = now.replace(year = now.year - 100)
    419                  timeline.write(__xml_encounter_template % (
    420                          format_pydt(start),
    421                          format_pydt(start),
    422                          _('Birth') + u': ?',
    423                          _('Life events'),
    424                          _('Date of birth unknown')
    425                  ))
    426          else:
    427                  start = patient['dob']
    428                  timeline.write(__xml_encounter_template % (
    429                          format_pydt(patient['dob']),
    430                          format_pydt(patient['dob']),
    431                          _('Birth') + gmTools.bool2subst(patient['dob_is_estimated'], u' (%s)' % gmTools.u_almost_equal_to, u''),
    432                          _('Life events'),
    433                          u''
    434                  ))
    435
    436          # start of care
    437          timeline.write(__xml_encounter_template % (
    438                  format_pydt(emr.earliest_care_date),
    439                  format_pydt(emr.earliest_care_date),
    440                  _('Start of Care'),
    441                  _('Life events'),
    442                  _('The earliest recorded event of care in this praxis.')
    443          ))
    444
    445          timeline.write(u'\n<!--\n========================================\n Health issues\n======================================== -->')
    446          for issue in emr.health_issues:
    447                  timeline.write(__format_health_issue_as_timeline_xml(issue, patient, emr))
    448
    449          timeline.write(u'\n<!--\n========================================\n Episodes\n======================================== -->')
    450          for epi in emr.get_episodes(order_by = u'pk_health_issue'):
    451                  timeline.write(__format_episode_as_timeline_xml(epi, patient))
    452
    453          timeline.write(u'\n<!--\n========================================\n Encounters\n======================================== -->')
    454          for enc in emr.get_encounters(skip_empty = True):
    455                  timeline.write(__format_encounter_as_timeline_xml(enc, patient))
    456
    457          timeline.write(u'\n<!--\n========================================\n Hospital stays\n======================================== -->')
    458          for stay in emr.hospital_stays:
    459                  timeline.write(__format_hospital_stay_as_timeline_xml(stay))
    460
    461          timeline.write(u'\n<!--\n========================================\n Procedures\n======================================== -->')
    462          for proc in emr.performed_procedures:
    463                  timeline.write(__format_procedure_as_timeline_xml(proc))
    464
    465          timeline.write(u'\n<!--\n========================================\n Vaccinations\n======================================== -->')
    466          for vacc in emr.vaccinations:
    467                  timeline.write(__format_vaccination_as_timeline_xml(vacc))
    468
    469          timeline.write(u'\n<!--\n========================================\n Substance intakes\n======================================== -->')
    470          for intake in emr.get_current_substance_intakes(include_inactive = True, include_unapproved = False):
    471                  timeline.write(__format_intake_as_timeline_xml(intake))
    472
    473          timeline.write(u'\n<!--\n========================================\n Documents\n======================================== -->')
    474          for doc in patient.document_folder.documents:
    475                  timeline.write(__format_document_as_timeline_xml(doc))
    476
    477          # allergies ?
    478          # test results ?
    479
    480          # death
    481          if patient['deceased'] is None:
    482                  end = now
    483          else:
    484                  end = patient['deceased']
    485                  timeline.write(__xml_encounter_template % (
    486                          format_pydt(end),
    487                          format_pydt(end),
    488                          #u'',
    489                          _('Death'),
    490                          _('Life events'),
    491                          u''
    492                  ))
    493
    494          # display range
    495          if end.month == 2:
    496                  if end.day == 29:
    497                          # leap years aren't consecutive
    498                          end = end.replace(day = 28)
    499          target_year = end.year + 1
    500          end = end.replace(year = target_year)
    501          timeline.write(xml_end % (
    502                  format_pydt(start),
    503                  format_pydt(end)
    504          ))
    505          timeline.close()
    506          return timeline_fname
    507
    508  #------------------------------------------------------------
    509  __fake_timeline_start = u"""<?xml version="1.0" encoding="utf-8"?>
    510  <timeline>
    511          <version>0.20.0</version>
    512          <categories>
    513                  <!-- life events -->
    514                  <category>
    515                          <name>%s</name>
    516                          <color>30,144,255</color>
    517                          <font_color>0,0,0</font_color>
    518                  </category>
    519          </categories>
    520          <events>""" % _('Life events')
    521
    522  __fake_timeline_body_template = u"""
    523                  <event>
    524                          <start>%s</start>
    525                          <end>%s</end>
    526                          <text>%s</text>
    527                          <fuzzy>False</fuzzy>
    528                          <locked>True</locked>
    529                          <ends_today>False</ends_today>
    530                          <!-- category></category -->
    531                          <description>%s
    532                          </description>
    533                  </event>"""
    534
    535 -def create_fake_timeline_file(patient=None, filename=None):
    536          """Used to create an 'empty' timeline file for display.
    537
    538          - needed because .clear_timeline() doesn't really work
    539          """
    540          emr = patient.emr
    541          global now
    542          now = gmDateTime.pydt_now_here()
    543
    544          if filename is None:
    545                  timeline_fname = gmTools.get_unique_filename(prefix = u'gm-', suffix = u'.timeline')
    546          else:
    547                  timeline_fname = filename
    548
    549          _log.debug('creating dummy timeline in [%s]', timeline_fname)
    550          timeline = codecs.open(timeline_fname, mode = 'wb', encoding = 'utf8', errors = 'xmlcharrefreplace')
    551
    552          timeline.write(__fake_timeline_start)
    553
    554          # birth
    555          if patient['dob'] is None:
    556                  start = now.replace(year = now.year - 100)
    557                  timeline.write(__xml_encounter_template % (
    558                          format_pydt(start),
    559                          format_pydt(start),
    560                          _('Birth') + u': ?',
    561                          _('Life events'),
    562                          _('Date of birth unknown')
    563                  ))
    564          else:
    565                  start = patient['dob']
    566                  timeline.write(__xml_encounter_template % (
    567                          format_pydt(patient['dob']),
    568                          format_pydt(patient['dob']),
    569                          _('Birth') + gmTools.bool2subst(patient['dob_is_estimated'], u' (%s)' % gmTools.u_almost_equal_to, u''),
    570                          _('Life events'),
    571                          u''
    572                  ))
    573
    574          # death
    575          if patient['deceased'] is None:
    576                  end = now
    577          else:
    578                  end = patient['deceased']
    579                  timeline.write(__xml_encounter_template % (
    580                          format_pydt(end),
    581                          format_pydt(end),
    582                          #u'',
    583                          _('Death'),
    584                          _('Life events'),
    585                          u''
    586                  ))
    587
    588          # fake issue
    589          timeline.write(__fake_timeline_body_template % (
    590                  format_pydt(start),
    591                  format_pydt(end),
    592                  _('Cannot display timeline.'),
    593                  _('Cannot display timeline.')
    594          ))
    595
    596          # display range
    597          if end.month == 2:
    598                  if end.day == 29:
    599                          # leap years aren't consecutive
    600                          end = end.replace(day = 28)
    601          target_year = end.year + 1
    602          end = end.replace(year = target_year)
    603          timeline.write(xml_end % (
    604                  format_pydt(start),
    605                  format_pydt(end)
    606          ))
    607
    608          timeline.close()
    609          return timeline_fname
    610
    611  #============================================================
    612  # main
    613  #------------------------------------------------------------
    614  if __name__ == '__main__':
    615
    616          if len(sys.argv) < 2:
    617                  sys.exit()
    618
    619          if sys.argv[1] != "test":
    620                  sys.exit()
    621
    622          from Gnumed.pycommon import gmI18N
    623          gmI18N.activate_locale()
    624          gmI18N.install_domain('gnumed')
    625          from Gnumed.business import gmPerson
    626          # 14 / 20 / 138 / 58 / 20 / 5
    627          pat = gmPerson.gmCurrentPatient(gmPerson.cPatient(aPK_obj = 12))
    628          fname = u'~/tmp/gm2tl-%s.timeline' % pat.get_dirname()
    629
    630          print create_timeline_file(patient = pat, filename = os.path.expanduser(fname))
    631