Package Gnumed :: Package business :: Module gmGenericEMRItem |
[frames] | no frames]

# Source Code for Module Gnumed.business.gmGenericEMRItem

      1  # -*- coding: utf-8 -*-
      2  """GNUmed clinical business object in generic form.
      3
      4  license: GPL v2 or later
      5  """
      6  #============================================================
      7  __author__ = "<karsten.hilbert@gmx.net>"
      8
      9  import sys
     10  import logging
     11
     12
     13  if __name__ == '__main__':
     14          sys.path.insert(0, '../../')
     15  from Gnumed.pycommon import gmBusinessDBObject
     16  from Gnumed.pycommon import gmPG2
     17  from Gnumed.pycommon import gmTools
     18  from Gnumed.pycommon import gmDateTime
     19
     20  from Gnumed.business import gmSoapDefs
     21  from Gnumed.business.gmEMRStructItems import cHealthIssue
     22  from Gnumed.business.gmEMRStructItems import cEncounter
     23  from Gnumed.business.gmEMRStructItems import cEpisode
     24  from Gnumed.business.gmEMRStructItems import cHospitalStay
     25  from Gnumed.business.gmEMRStructItems import cPerformedProcedure
     26  from Gnumed.business.gmExternalCare import cExternalCareItem
     27  from Gnumed.business.gmVaccination import cVaccination
     28  from Gnumed.business.gmClinNarrative import cNarrative
     29  from Gnumed.business.gmMedication import cSubstanceIntakeEntry
     30  from Gnumed.business.gmAllergy import cAllergy
     31  from Gnumed.business.gmAllergy import cAllergyState
     32  from Gnumed.business.gmFamilyHistory import cFamilyHistory
     33  from Gnumed.business.gmAutoHints import cSuppressedHint
     34  from Gnumed.business.gmAutoHints import cDynamicHint
     35  from Gnumed.business.gmDocuments import cDocument
     36  from Gnumed.business.gmProviderInbox import cInboxMessage
     37  from Gnumed.business.gmPathLab import cTestResult
     38
     39  _log = logging.getLogger('gm.emr')
     40
     41  #============================================================
     42  _MAP_generic_emr_item_table2type_str = {
     43          'clin.encounter': _('Encounter'),
     44          'clin.episode': _('Episode'),
     45          'clin.health_issue': _('Health issue'),
     46          'clin.external_care': _('External care'),
     47          'clin.vaccination': _('Vaccination'),
     48          'clin.clin_narrative': _('Progress note'),
     49          'clin.test_result': _('Test result'),
     50          'clin.substance_intake': _('Substance intake'),
     51          'clin.hospital_stay': _('Hospital stay'),
     52          'clin.procedure': _('Performed procedure'),
     53          'clin.allergy': _('Allergy'),
     54          'clin.allergy_state': _('Allergy state'),
     55          'clin.family_history': _('Family history'),
     56          'blobs.doc_med': _('Document'),
     57          'dem.message_inbox': _('Inbox message'),
     58          'ref.auto_hint': _('Dynamic hint')
     59  }
     60
     61  _MAP_generic_emr_item_table2class = {
     62          'clin.encounter': cEncounter,
     63          'clin.episode': cEpisode,
     64          'clin.health_issue': cHealthIssue,
     65          'clin.external_care': cExternalCareItem,
     66          'clin.vaccination': cVaccination,
     67          'clin.clin_narrative': cNarrative,
     68          'clin.test_result': cTestResult,
     69          'clin.substance_intake': cSubstanceIntakeEntry,
     70          'clin.hospital_stay': cHospitalStay,
     71          'clin.procedure': cPerformedProcedure,
     72          'clin.allergy': cAllergy,
     73          'clin.allergy_state': cAllergyState,
     74          'clin.family_history': cFamilyHistory,
     75          'clin.suppressed_hint': cSuppressedHint,
     76          'blobs.doc_med': cDocument,
     77          'dem.message_inbox': cInboxMessage,
     78          'ref.auto_hint': cDynamicHint
     79  }
     80
     81  #============================================================
     82  # generic items in clin.v_emr_journal
     83  #------------------------------------------------------------
     84  _SQL_get_generic_emr_items = """SELECT
     85          to_char(c_vej.clin_when, 'YYYY-MM-DD') AS date,
     86          c_vej.clin_when,
     87          coalesce(c_vej.soap_cat, '') as soap_cat,
     88          c_vej.narrative,
     89          c_vej.src_table,
     90          c_scr.rank AS scr,
     91          c_vej.modified_when,
     92          to_char(c_vej.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified,
     93          c_vej.modified_by,
     94          c_vej.row_version,
     95          c_vej.pk_episode,
     96          c_vej.pk_encounter,
     97          c_vej.soap_cat as real_soap_cat,
     98          c_vej.src_pk,
     99          c_vej.pk_health_issue,
    100          c_vej.health_issue,
    101          c_vej.episode,
    102          c_vej.issue_active,
    103          c_vej.issue_clinically_relevant,
    104          c_vej.episode_open,
    105          c_vej.encounter_started,
    106          c_vej.encounter_last_affirmed,
    107          c_vej.encounter_l10n_type,
    108          c_vej.pk_patient,
    109          -1 AS xmin_dummy
    110  FROM
    111          clin.v_emr_journal c_vej
    112                  JOIN clin.soap_cat_ranks c_scr on (c_scr.soap_cat IS NOT DISTINCT FROM c_vej.soap_cat)
    113  """
    114
    115  _SQL_get_hints_as_generic_emr_items = """SELECT
    116          to_char(now(), 'YYYY-MM-DD') AS date,
    117          now() as clin_when,
    118          'u'::text as soap_cat,
    119          hints.title || E'\n' || hints.hint
    120                  as narrative,
    121          -- .src_table does not correspond with the
    122          -- .src_pk column source because it is generated
    123          -- from clin.get_hints_for_patient()
    124          'ref.auto_hint'::text as src_table,
    125          c_scr.rank AS scr,
    126          now() as modified_when,
    127          to_char(now(), 'YYYY-MM-DD HH24:MI') AS date_modified,
    128          current_user as modified_by,
    129          0::integer as row_version,
    130          NULL::integer as pk_episode,
    131          %(pk_enc)s as pk_encounter,
    132          'u'::text as real_soap_cat,
    133          hints.pk_auto_hint as src_pk,
    134          NULL::integer as pk_health_issue,
    135          ''::text as health_issue,
    136          ''::text as episode,
    137          False as issue_active,
    138          False as issue_clinically_relevant,
    139          False as episode_open,
    140          %(enc_start)s as encounter_started,
    141          %(enc_last_affirmed)s  as encounter_last_affirmed,
    142          %(enc_type)s as encounter_l10n_type,
    143          %(enc_pat)s as pk_patient,
    144          -1 AS xmin_dummy
    145  FROM
    146          clin.get_hints_for_patient(%(enc_pat)s) AS hints
    147                  JOIN clin.soap_cat_ranks c_scr ON (c_scr.soap_cat = 'u')
    148  """
    149
    150  __SQL_union = """(
    151          %s
    152  ) UNION ALL (
    153          %s
    154  )"""
    155
    156  #------------------------------------------------------------
    157 -class cGenericEMRItem(gmBusinessDBObject.cBusinessDBObject):
    158          """Represents an entry in clin.v_emr_journal."""
    159
    160          _cmd_fetch_payload = _SQL_get_generic_emr_items + "WHERE src_table = %(src_table)s AND src_pk = %(src_pk)s"
    161          _cmds_store_payload = []
    162  #       [       """
    163  #                       -- typically the underlying table name
    164  #                       UPDATE xxx.xxx SET
    165  #                               -- typically "table_col = %(view_col)s"
    166  #                               xxx = %(xxx)s,
    167  #                               xxx = gm.nullify_empty_string(%(xxx)s)
    168  #                       WHERE
    169  #                               pk = %(pk_XXX)s
    170  #                                       AND
    171  #                               xmin = %(xmin_dummy)s
    172  #                       RETURNING
    173  #                               xmin AS xmin_dummy
    174  #                               -- also return columns which are calculated in the view used by
    175  #                               -- the initial SELECT such that they will further on contain their
    176  #                               -- updated value:
    177  #                               --, ...
    178  #                               --, ...
    179  #               """
    180  #       ]
    181          # view columns that can be updated:
    182          _updatable_fields = ['']
    183
    184          #--------------------------------------------------------
    185 -        def format(self, eol=None):
    186                  lines = self.formatted_header
    187                  lines.append(gmTools.u_box_horiz_4dashes * 40)
    188                  lines.extend(self._payload[self._idx['narrative']].strip().split('\n'))
    189                  lines.append('')
    190                  lines.append(_('                        rev %s (%s) by %s in <%s>') % (
    191                          self._payload[self._idx['row_version']],
    192                          self._payload[self._idx['date_modified']],
    193                          self._payload[self._idx['modified_by']],
    194                          self._payload[self._idx['src_table']]
    195                  ))
    196                  if eol is None:
    197                          return lines
    198                  return eol.join(lines)
    199
    200          #--------------------------------------------------------
    201 -        def format_header(self, eol=None):
    202                  lines = []
    203                  lines.append(_('Chart entry (%s): %s       [#%s in %s]') % (
    204                          self.i18n_soap_cat,
    205                          self.item_type_str,
    206                          self._payload[self._idx['src_pk']],
    207                          self._payload[self._idx['src_table']]
    208                  ))
    209                  lines.append(_(' Modified: %s by %s (%s rev %s)') % (
    210                          self._payload[self._idx['date_modified']],
    211                          self._payload[self._idx['modified_by']],
    212                          gmTools.u_arrow2right,
    213                          self._payload[self._idx['row_version']]
    214                  ))
    215                  lines.append('')
    216                  if self._payload[self._idx['health_issue']] is None:
    217                          issue_info = gmTools.u_diameter
    218                  else:
    219                          issue_info = '%s%s' % (
    220                                  self._payload[self._idx['health_issue']],
    221                                  gmTools.bool2subst(self._payload[self._idx['issue_active']], ' (' + _('active') + ')', ' (' + _('inactive') + ')', '')
    222                          )
    223                  lines.append(_('Health issue: %s') % issue_info)
    224                  if self._payload[self._idx['episode']] is None:
    225                          episode_info = gmTools.u_diameter
    226                  else:
    227                          episode_info = '%s%s' % (
    228                                  self._payload[self._idx['episode']],
    229                                  gmTools.bool2subst(self._payload[self._idx['episode_open']], ' (' +  _('open') + ')', ' (' +  _('closed') + ')', '')
    230                          )
    231                  lines.append(_('Episode: %s') % episode_info)
    232                  if self._payload[self._idx['encounter_started']] is None:
    233                          enc_info = gmTools.u_diameter
    234                  else:
    235                          enc_info = '%s - %s (%s)' % (
    236                                  gmDateTime.pydt_strftime(self._payload[self._idx['encounter_started']], '%Y %b %d  %H:%M'),
    237                                  self._payload[self._idx['encounter_last_affirmed']].strftime('%H:%M'),
    238                                  self._payload[self._idx['encounter_l10n_type']]
    239                          )
    240                  lines.append(_('Encounter: %s') % enc_info)
    241                  if eol is None:
    242                          return lines
    243                  return eol.join(lines)
    244
    245          formatted_header = property(format_header)
    246
    247          #--------------------------------------------------------
    248 -        def __get_item_type_str(self):
    249                  try:
    250                          return _MAP_generic_emr_item_table2type_str[self._payload[self._idx['src_table']]]
    251                  except KeyError:
    252                          return '[%s:%s]' % (
    253                                  self._payload[self._idx['src_table']],
    254                                  self._payload[self._idx['src_pk']]
    255                          )
    256
    257          item_type_str = property(__get_item_type_str)
    258
    259          #--------------------------------------------------------
    260 -        def __get_i18n_soap_cat(self):
    261                  return gmSoapDefs.soap_cat2l10n[self._payload[self._idx['soap_cat']]]
    262
    263          i18n_soap_cat = property(__get_i18n_soap_cat)
    264
    265          #--------------------------------------------------------
    266 -        def __get_specialized_item(self):
    267                  item_class = _MAP_generic_emr_item_table2class[self._payload[self._idx['src_table']]]
    268                  return item_class(aPK_obj = self._payload[self._idx['src_pk']])
    269
    270          specialized_item = property(__get_specialized_item)
    271
    272  #------------------------------------------------------------
    273 -def get_generic_emr_items(encounters=None, episodes=None, issues=None, patient=None, soap_cats=None, time_range=None, order_by=None, active_encounter=None, return_pks=False):
    274
    275          faulty_args = (
    276                  (patient is None) and
    277                  (encounters is None) and
    278                  (episodes is None) and
    279                  (issues is None) and
    280                  (active_encounter is None)
    281          )
    282          assert not faulty_args, 'one of <patient>, <episodes>, <issues>, <active_encounter> must not be None'
    283
    284          if (patient is not None) and (active_encounter is not None):
    285                  if patient != active_encounter['pk_patient']:
    286                          raise AssertionError('<patient> (%s) and <active_encounter>["pk_patient"] (%s) must match, if both given', patient, active_encounter['pk_patient'])
    287
    288          if order_by is None:
    289                  order_by = 'ORDER BY clin_when, pk_episode, scr, modified_when, src_table'
    290          else:
    291                  order_by = 'ORDER BY %s' % order_by
    292
    293          if (patient is None) and (active_encounter is not None):
    294                  patient = active_encounter['pk_patient']
    295
    296          where_parts = []
    297          args = {}
    298
    299          if patient is not None:
    300                  where_parts.append('c_vej.pk_patient = %(pat)s')
    301                  args['pat'] = patient
    302
    303          if soap_cats is not None:
    304                  # work around bug in psycopg2 not being able to properly
    305                  # adapt None to NULL inside tuples
    306                  if None in soap_cats:
    307                          where_parts.append('((c_vej.soap_cat IN %(soap_cat)s) OR (c_vej.soap_cat IS NULL))')
    308                          soap_cats.remove(None)
    309                  else:
    310                          where_parts.append('c_vej.soap_cat IN %(soap_cat)s')
    311                  args['soap_cat'] = tuple(soap_cats)
    312
    313          if time_range is not None:
    314                  where_parts.append("c_vej.clin_when > (now() - '%s days'::interval)" % time_range)
    315
    316          if encounters is not None:
    317                  where_parts.append("c_vej.pk_encounter IN %(encs)s")
    318                  args['encs'] = tuple(encounters)
    319
    320          if episodes is not None:
    321                  where_parts.append("c_vej.pk_episode IN %(epis)s")
    322                  args['epis'] = tuple(episodes)
    323
    324          if issues is not None:
    325                  where_parts.append("c_vej.pk_health_issue IN %(issues)s")
    326                  args['issues'] = tuple(issues)
    327
    328          cmd_journal = _SQL_get_generic_emr_items
    329          if len(where_parts) > 0:
    330                  cmd_journal += '\nWHERE\n\t'
    331                  cmd_journal += '\t\tAND\t'.join(where_parts)
    332
    333          if active_encounter is None:
    334                  cmd = cmd_journal + '\n' + order_by
    335          else:
    336                  args['pk_enc'] = active_encounter['pk_encounter']
    337                  args['enc_start'] = active_encounter['started']
    338                  args['enc_last_affirmed'] = active_encounter['last_affirmed']
    339                  args['enc_type'] = active_encounter['l10n_type']
    340                  args['enc_pat'] = active_encounter['pk_patient']
    341                  cmd = __SQL_union % (
    342                          cmd_journal,
    343                          _SQL_get_hints_as_generic_emr_items
    344                  ) + '\n' + order_by
    345
    346          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    347          if return_pks:
    348                  return [ {
    349                          'src_table': r['src_table'],
    350                          'src_pk': r['src_pk']
    351                  } for r in rows ]
    352
    353          return [ cGenericEMRItem(row = {
    354                  'data': r,
    355                  'idx': idx,
    356                  'pk_obj': {'src_table': r['src_table'], 'src_pk': r['src_pk']}
    357          } ) for r in rows ]
    358
    359  #------------------------------------------------------------
    360 -def delete_xxx(pk_XXX=None):
    361          # forward to specialized item
    362          args = {'pk': pk_XXX}
    363          cmd = u"DELETE FROM xxx.xxx WHERE pk = %(pk)s"
    364          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    365          return True
    366
    367  #------------------------------------------------------------
    368 -def generic_item_type_str(table):
    369          try:
    370                  return _MAP_generic_emr_item_table2type_str[table]
    371          except KeyError:
    372                  return _('unmapped entry type from table [%s]') % table
    373
    374  #------------------------------------------------------------
    375  # widget code
    376  # remember to add in clinical item generic workflows
    377  #------------------------------------------------------------
    378 -def edit_xxx(parent=None, xxx=None, single_entry=False, presets=None):
    379          pass
    380  #------------------------------------------------------------
    381 -def delete_xxx():
    382          pass
    383  #------------------------------------------------------------
    384 -def manage_xxx():
    385          pass
    386  #------------------------------------------------------------
    387
    388  #============================================================
    389  # main - unit testing
    390  #------------------------------------------------------------
    391  if __name__ == '__main__':
    392
    393          if len(sys.argv) < 2:
    394                  sys.exit()
    395
    396          if sys.argv[1] != 'test':
    397                  sys.exit()
    398
    399  #       gmI18N.activate_locale()
    400  #       gmI18N.install_domain('gnumed')
    401
    402          #--------------------------------------------------------
    403 -        def test_gen_item():
    404                  for item in get_generic_emr_items (
    405                          order_by = None,
    406                          patient = 12,
    407                          return_pks = False
    408                  ):
    409                          print('------------------------------')
    410                          print(item.format(eol = '\n'))
    411                          print(item.staff_id)
    412                          input('<next>')
    413                          #print(item.specialized_item)
    414                          #input('<next>')
    415
    416          #--------------------------------------------------------
    417          test_gen_item()
    418