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

# Source Code for Module Gnumed.business.gmClinNarrative

      1  """GNUmed clinical narrative business object."""
      2  #============================================================
      3  __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, Karsten Hilbert <Karsten.Hilbert@gmx.net>"
      4  __license__ = 'GPL v2 or later (for details see http://gnu.org)'
      5
      6  import sys
      7  import logging
      8
      9
     10  if __name__ == '__main__':
     11          sys.path.insert(0, '../../')
     12  from Gnumed.pycommon import gmPG2
     13  from Gnumed.pycommon import gmBusinessDBObject
     14  from Gnumed.pycommon import gmTools
     15  from Gnumed.pycommon import gmDispatcher
     16  from Gnumed.pycommon import gmHooks
     17  from Gnumed.pycommon import gmDateTime
     18
     19  from Gnumed.business import gmCoding
     20  from Gnumed.business import gmSoapDefs
     21  from Gnumed.business import gmAutoHints
     22
     23
     24  _log = logging.getLogger('gm.emr')
     25
     26  #============================================================
     27 -def _on_soap_modified():
     28          """Always relates to the active patient."""
     29          gmHooks.run_hook_script(hook = 'after_soap_modified')
     30
     31  gmDispatcher.connect(_on_soap_modified, 'clin.clin_narrative_mod_db')
     32
     33  #============================================================
     34 -class cNarrative(gmBusinessDBObject.cBusinessDBObject):
     35          """Represents one clinical free text entry."""
     36
     37          _cmd_fetch_payload = "SELECT * FROM clin.v_narrative WHERE pk_narrative = %s"
     38          _cmds_store_payload = [
     39                  """update clin.clin_narrative set
     40                                  narrative = %(narrative)s,
     41                                  clin_when = %(date)s,
     42                                  soap_cat = lower(%(soap_cat)s),
     43                                  fk_encounter = %(pk_encounter)s,
     44                                  fk_episode = %(pk_episode)s
     45                          WHERE
     46                                  pk = %(pk_narrative)s
     47                                          AND
     48                                  xmin = %(xmin_clin_narrative)s
     49                          RETURNING
     50                                  xmin AS xmin_clin_narrative"""
     51                  ]
     52
     53          _updatable_fields = [
     54                  'narrative',
     55                  'date',
     56                  'soap_cat',
     57                  'pk_episode',
     58                  'pk_encounter'
     59          ]
     60
     61          #--------------------------------------------------------
     62 -        def format_maximum_information(self, patient=None):
     63                  return self.format(fancy = True, width = 70).split('\n')
     64
     65          #--------------------------------------------------------
     66 -        def format(self, left_margin='', fancy=False, width=75):
     67
     68                  if fancy:
     69                          txt = gmTools.wrap (
     70                                  text = _('%s: %s by %.8s (v%s)\n%s') % (
     71                                          self._payload[self._idx['date']].strftime('%x %H:%M'),
     72                                          gmSoapDefs.soap_cat2l10n_str[self._payload[self._idx['soap_cat']]],
     73                                          self._payload[self._idx['modified_by']],
     74                                          self._payload[self._idx['row_version']],
     75                                          self._payload[self._idx['narrative']]
     76                                  ),
     77                                  width = width,
     78                                  initial_indent = '',
     79                                  subsequent_indent = left_margin + '   '
     80                          )
     81                  else:
     82                          txt = '%s [%s]: %s (%.8s)' % (
     83                                  self._payload[self._idx['date']].strftime('%x %H:%M'),
     84                                  gmSoapDefs.soap_cat2l10n[self._payload[self._idx['soap_cat']]],
     85                                  self._payload[self._idx['narrative']],
     86                                  self._payload[self._idx['modified_by']]
     87                          )
     88                          if len(txt) > width:
     89                                  txt = txt[:width] + gmTools.u_ellipsis
     90
     91                  return txt
     92
     93          #--------------------------------------------------------
     94 -        def add_code(self, pk_code=None):
     95                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
     96
     97                  if pk_code in self._payload[self._idx['pk_generic_codes']]:
     98                          return
     99
    100                  cmd = """
    101                          INSERT INTO clin.lnk_code2narrative
    102                                  (fk_item, fk_generic_code)
    103                          SELECT
    104                                  %(item)s,
    105                                  %(code)s
    106                          WHERE NOT EXISTS (
    107                                  SELECT 1 FROM clin.lnk_code2narrative
    108                                  WHERE
    109                                          fk_item = %(item)s
    110                                                  AND
    111                                          fk_generic_code = %(code)s
    112                          )"""
    113                  args = {
    114                          'item': self._payload[self._idx['pk_narrative']],
    115                          'code': pk_code
    116                  }
    117                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    118                  return
    119
    120          #--------------------------------------------------------
    121 -        def remove_code(self, pk_code=None):
    122                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
    123                  cmd = "DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
    124                  args = {
    125                          'item': self._payload[self._idx['pk_narrative']],
    126                          'code': pk_code
    127                  }
    128                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    129                  return True
    130
    131          #--------------------------------------------------------
    132          # properties
    133          #--------------------------------------------------------
    134 -        def _get_generic_codes(self):
    135                  if len(self._payload[self._idx['pk_generic_codes']]) == 0:
    136                          return []
    137
    138                  cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
    139                  args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
    140                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    141                  return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
    142
    143 -        def _set_generic_codes(self, pk_codes):
    144                  queries = []
    145                  # remove all codes
    146                  if len(self._payload[self._idx['pk_generic_codes']]) > 0:
    147                          queries.append ({
    148                                  'cmd': 'DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(narr)s AND fk_generic_code IN %(codes)s',
    149                                  'args': {
    150                                          'narr': self._payload[self._idx['pk_narrative']],
    151                                          'codes': tuple(self._payload[self._idx['pk_generic_codes']])
    152                                  }
    153                          })
    154                  # add new codes
    155                  for pk_code in pk_codes:
    156                          queries.append ({
    157                                  'cmd': 'INSERT INTO clin.lnk_code2narrative (fk_item, fk_generic_code) VALUES (%(narr)s, %(pk_code)s)',
    158                                  'args': {
    159                                          'narr': self._payload[self._idx['pk_narrative']],
    160                                          'pk_code': pk_code
    161                                  }
    162                          })
    163                  if len(queries) == 0:
    164                          return
    165                  # run it all in one transaction
    166                  rows, idx = gmPG2.run_rw_queries(queries = queries)
    167                  return
    168
    169          generic_codes = property(_get_generic_codes, _set_generic_codes)
    170
    171  #============================================================
    172 -def create_progress_note(soap=None, episode_id=None, encounter_id=None, link_obj=None):
    173          """Create clinical narrative entries.
    174
    175          <soap>
    176                  must be a dict, the keys being SOAP categories (including U and
    177                  None=admin) and the values being text (possibly multi-line)
    178
    179          Existing but empty ('' or None) categories are skipped.
    180          """
    181          if soap is None:
    182                  return True
    183
    184          if not gmSoapDefs.are_valid_soap_cats(soap.keys(), allow_upper = True):
    185                  raise ValueError('invalid SOAP category in <soap> dictionary: %s', soap)
    186
    187          if link_obj is None:
    188                  link_obj = gmPG2.get_connection(readonly = False)
    189                  conn_rollback = link_obj.rollback
    190                  conn_commit = link_obj.commit
    191                  conn_close = link_obj.close
    192          else:
    193                  conn_rollback = lambda x:x
    194                  conn_commit = lambda x:x
    195                  conn_close = lambda x:x
    196
    197          instances = {}
    198          for cat in soap:
    199                  val = soap[cat]
    200                  if val is None:
    201                          continue
    202                  if ''.join([ v.strip() for v in val ]) == '':
    203                          continue
    204                  instance = create_narrative_item (
    205                          narrative = '\n'.join([ v.strip() for v in val ]),
    206                          soap_cat = cat,
    207                          episode_id = episode_id,
    208                          encounter_id = encounter_id,
    209                          link_obj = link_obj
    210                  )
    211                  if instance is None:
    212                          continue
    213                  instances[cat] = instance
    214
    215          conn_commit()
    216          conn_close()
    217          return instances
    218
    219  #============================================================
    220 -def create_narrative_item(narrative=None, soap_cat=None, episode_id=None, encounter_id=None, link_obj=None):
    221          """Creates a new clinical narrative entry
    222
    223                  narrative - free text clinical narrative
    224                  soap_cat - soap category
    225                  episode_id - episodes's primary key
    226                  encounter_id - encounter's primary key
    227
    228                  any of the args being None (except soap_cat) will fail the SQL code
    229          """
    230          # silently do not insert empty narrative
    231          narrative = narrative.strip()
    232          if narrative == '':
    233                  return None
    234
    235          args = {'enc': encounter_id, 'epi': episode_id, 'soap': soap_cat, 'narr': narrative}
    236
    237          # insert new narrative
    238          # but, also silently, do not insert true duplicates
    239          # this should check for .provider = current_user but
    240          # the view has provider mapped to their staff alias
    241          cmd = """
    242                  INSERT INTO clin.clin_narrative
    243                          (fk_encounter, fk_episode, narrative, soap_cat)
    244                  SELECT
    245                          %(enc)s, %(epi)s, %(narr)s, lower(%(soap)s)
    246                  WHERE NOT EXISTS (
    247                          SELECT 1 FROM clin.v_narrative
    248                          WHERE
    249                                  pk_encounter = %(enc)s
    250                                          AND
    251                                  pk_episode = %(epi)s
    252                                          AND
    253                                  soap_cat = lower(%(soap)s)
    254                                          AND
    255                                  narrative = %(narr)s
    256                  )
    257                  RETURNING pk"""
    258          rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
    259          if len(rows) == 1:
    260                  # re-use same link_obj if given because when called from create_progress_note we won't yet see rows inside a new tx
    261                  return cNarrative(aPK_obj = rows[0]['pk'], link_obj = link_obj)
    262
    263          if len(rows) > 1:
    264                  raise Exception('more than one row returned from single-row INSERT')
    265
    266          # retrieve existing narrative
    267          cmd = """
    268                  SELECT * FROM clin.v_narrative
    269                  WHERE
    270                          pk_encounter = %(enc)s
    271                                  AND
    272                          pk_episode = %(epi)s
    273                                  AND
    274                          soap_cat = lower(%(soap)s)
    275                                  AND
    276                          narrative = %(narr)s
    277          """
    278          rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    279          if len(rows) == 1:
    280                  return cNarrative(row = {'pk_field': 'pk_narrative', 'data': rows[0], 'idx': idx})
    281
    282          raise Exception('retrieving known-to-exist narrative row returned 0 or >1 result: %s' % len(rows))
    283
    284  #------------------------------------------------------------
    285 -def delete_clin_narrative(narrative=None):
    286          """Deletes a clin.clin_narrative row by it's PK."""
    287          cmd = "DELETE FROM clin.clin_narrative WHERE pk=%s"
    288          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [narrative]}])
    289          return True
    290
    291  #------------------------------------------------------------
    292 -def get_narrative(since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, patient=None, order_by=None):
    293          """Get SOAP notes pertinent to this encounter.
    294
    295                  since
    296                          - initial date for narrative items
    297                  until
    298                          - final date for narrative items
    299                  encounters
    300                          - list of encounters whose narrative are to be retrieved
    301                  episodes
    302                          - list of episodes whose narrative are to be retrieved
    303                  issues
    304                          - list of health issues whose narrative are to be retrieved
    305                  soap_cats
    306                          - list of SOAP categories of the narrative to be retrieved
    307          """
    308          where_parts = ['TRUE']
    309          args = {}
    310
    311          if encounters is not None:
    312                  where_parts.append('pk_encounter IN %(encs)s')
    313                  args['encs'] = tuple(encounters)
    314
    315          if episodes is not None:
    316                  where_parts.append('pk_episode IN %(epis)s')
    317                  args['epis'] = tuple(episodes)
    318
    319          if issues is not None:
    320                  where_parts.append('pk_health_issue IN %(issues)s')
    321                  args['issues'] = tuple(issues)
    322
    323          if patient is not None:
    324                  where_parts.append('pk_patient = %(pat)s')
    325                  args['pat'] = patient
    326
    327          if soap_cats is not None:
    328                  where_parts.append('c_vn.soap_cat IN %(soap_cats)s')
    329                  args['soap_cats'] = tuple(soap_cats)
    330
    331          if order_by is None:
    332                  order_by = 'ORDER BY date, soap_rank'
    333          else:
    334                  order_by = 'ORDER BY %s' % order_by
    335
    336          cmd = """
    337                  SELECT
    338                          c_vn.*,
    339                          c_scr.rank AS soap_rank
    340                  FROM
    341                          clin.v_narrative c_vn
    342                                  LEFT JOIN clin.soap_cat_ranks c_scr ON c_vn.soap_cat = c_scr.soap_cat
    343                  WHERE
    344                          %s
    345                  %s
    346          """ % (
    347                  ' AND '.join(where_parts),
    348                  order_by
    349          )
    350
    351          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    352
    353          filtered_narrative = [ cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
    354
    355          if since is not None:
    356                  filtered_narrative = [ narr for narr in filtered_narrative if narr['date'] >= since ]
    357
    358          if until is not None:
    359                  filtered_narrative = [ narr for narr in filtered_narrative if narr['date'] < until ]
    360
    361          if providers is not None:
    362                  filtered_narrative = [ narr for narr in filtered_narrative if narr['modified_by'] in providers ]
    363
    364          return filtered_narrative
    365
    366  #       if issues is not None:
    367  #               filtered_narrative = (lambda narr: narr['pk_health_issue'] in issues, filtered_narrative)
    368  #
    369  #       if episodes is not None:
    370  #               filtered_narrative = (lambda narr: narr['pk_episode'] in episodes, filtered_narrative)
    371  #
    372  #       if encounters is not None:
    373  #               filtered_narrative = (lambda narr: narr['pk_encounter'] in encounters, filtered_narrative)
    374
    375  #       if soap_cats is not None:
    376  #               soap_cats = map(lambda c: c.lower(), soap_cats)
    377  #               filtered_narrative = (lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative)
    378
    379  #------------------------------------------------------------
    380 -def get_as_journal(since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None, patient=None, active_encounter=None):
    381
    382          if (patient is None) and (episodes is None) and (issues is None) and (encounters is None):
    383                  raise ValueError('at least one of <patient>, <episodes>, <issues>, <encounters> must not be None')
    384
    385          if order_by is None:
    386                  order_by = 'ORDER BY clin_when, pk_episode, scr, modified_when, src_table'
    387          else:
    388                  order_by = 'ORDER BY %s' % order_by
    389
    390          where_parts = []
    391          args = {}
    392
    393          if patient is not None:
    394                  where_parts.append('c_vej.pk_patient = %(pat)s')
    395                  args['pat'] = patient
    396
    397          if soap_cats is not None:
    398                  # work around bug in psycopg2 not being able to properly
    399                  # adapt None to NULL inside tuples
    400                  if None in soap_cats:
    401                          where_parts.append('((c_vej.soap_cat IN %(soap_cat)s) OR (c_vej.soap_cat IS NULL))')
    402                          soap_cats.remove(None)
    403                  else:
    404                          where_parts.append('c_vej.soap_cat IN %(soap_cat)s')
    405                  args['soap_cat'] = tuple(soap_cats)
    406
    407          if time_range is not None:
    408                  where_parts.append("c_vej.clin_when > (now() - '%s days'::interval)" % time_range)
    409
    410          if episodes is not None:
    411                  where_parts.append("c_vej.pk_episode IN %(epis)s")
    412                  args['epis'] = tuple(episodes)
    413
    414          if issues is not None:
    415                  where_parts.append("c_vej.pk_health_issue IN %(issues)s")
    416                  args['issues'] = tuple(issues)
    417
    418          # FIXME: implement more constraints
    419
    420          cmd_journal = """
    421                  SELECT
    422                          to_char(c_vej.clin_when, 'YYYY-MM-DD') AS date,
    423                          c_vej.clin_when,
    424                          coalesce(c_vej.soap_cat, '') as soap_cat,
    425                          c_vej.narrative,
    426                          c_vej.src_table,
    427                          c_scr.rank AS scr,
    428                          c_vej.modified_when,
    429                          to_char(c_vej.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified,
    430                          c_vej.modified_by,
    431                          c_vej.row_version,
    432                          c_vej.pk_episode,
    433                          c_vej.pk_encounter,
    434                          c_vej.soap_cat as real_soap_cat,
    435                          c_vej.src_pk,
    436                          c_vej.pk_health_issue,
    437                          c_vej.health_issue,
    438                          c_vej.episode,
    439                          c_vej.issue_active,
    440                          c_vej.issue_clinically_relevant,
    441                          c_vej.episode_open,
    442                          c_vej.encounter_started,
    443                          c_vej.encounter_last_affirmed,
    444                          c_vej.encounter_l10n_type,
    445                          c_vej.pk_patient
    446                  FROM
    447                          clin.v_emr_journal c_vej
    448                                  join clin.soap_cat_ranks c_scr on (c_scr.soap_cat IS NOT DISTINCT FROM c_vej.soap_cat)
    449                  WHERE
    450                          %s
    451          """ % '\n\t\t\t\t\tAND\n\t\t\t\t'.join(where_parts)
    452
    453          if active_encounter is None:
    454                  cmd = cmd_journal + '\n ' + order_by
    455          else:
    456                  args['pk_enc'] = active_encounter['pk_encounter']
    457                  args['enc_start'] = active_encounter['started']
    458                  args['enc_last_affirmed'] = active_encounter['last_affirmed']
    459                  args['enc_type'] = active_encounter['l10n_type']
    460                  args['enc_pat'] = active_encounter['pk_patient']
    461                  cmd_hints = """
    462                          SELECT
    463                                  to_char(now(), 'YYYY-MM-DD') AS date,
    464                                  now() as clin_when,
    465                                  'u'::text as soap_cat,
    466                                  hints.title || E'\n' || hints.hint
    467                                          as narrative,
    468                                  -- .src_table does not correspond with the
    469                                  -- .src_pk column source because it is generated
    470                                  -- from clin.get_hints_for_patient()
    471                                  'ref.auto_hint'::text as src_table,
    472                                  c_scr.rank AS scr,
    473                                  now() as modified_when,
    474                                  to_char(now(), 'YYYY-MM-DD HH24:MI') AS date_modified,
    475                                  current_user as modified_by,
    476                                  0::integer as row_version,
    477                                  NULL::integer as pk_episode,
    478                                  %(pk_enc)s as pk_encounter,
    479                                  'u'::text as real_soap_cat,
    480                                  hints.pk_auto_hint as src_pk,
    481                                  NULL::integer as pk_health_issue,
    482                                  ''::text as health_issue,
    483                                  ''::text as episode,
    484                                  False as issue_active,
    485                                  False as issue_clinically_relevant,
    486                                  False as episode_open,
    487                                  %(enc_start)s as encounter_started,
    488                                  %(enc_last_affirmed)s  as encounter_last_affirmed,
    489                                  %(enc_type)s as encounter_l10n_type,
    490                                  %(enc_pat)s as pk_patient
    491                          FROM
    492                                  clin.get_hints_for_patient(%(enc_pat)s) AS hints
    493                                          JOIN clin.soap_cat_ranks c_scr ON (c_scr.soap_cat = 'u')
    494                  """
    495                  cmd = cmd_journal + '\nUNION ALL\n' + cmd_hints + '\n' + order_by
    496
    497          journal_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    498
    499          return journal_rows
    500
    501  #============================================================
    502  # convenience functions
    503  #============================================================
    504  __VIEW_DEF = """
    505  create temporary view v_narrative4search as
    506
    507  select * from (
    508
    509          -- clin.clin_root_items
    510          select
    511                  vpi.pk_patient as pk_patient,
    512                  vpi.soap_cat as soap_cat,
    513                  vpi.narrative as narrative,
    514                  vpi.pk_encounter as pk_encounter,
    515                  vpi.pk_episode as pk_episode,
    516                  vpi.pk_health_issue as pk_health_issue,
    517                  vpi.pk_item as src_pk,
    518                  vpi.src_table as src_table
    519          from
    520                  clin.v_pat_items vpi
    521          where
    522                  src_table not in (
    523                          'clin.allergy',
    524                          'clin.test_result',
    525                          'clin.procedure',
    526                          'clin.substance_intake',
    527                          'clin.family_history'
    528                  )
    529
    530
    531          union all
    532
    533                  select * from clin.v_subst_intake4narr_search
    534                  where gm.is_null_or_blank_string(narrative) is FALSE
    535
    536
    537          union all
    538          select          -- clin.procedure
    539                  (select fk_patient from clin.encounter where pk = cpr.fk_encounter)
    540                          as pk_patient,
    541                  cpr.soap_cat
    542                          as soap_cat,
    543                  cpr.narrative
    544                          as narrative,
    545                  cpr.fk_encounter
    546                          as pk_encounter,
    547                  cpr.fk_episode
    548                          as pk_episode,
    549                  (select fk_health_issue from clin.episode where pk = cpr.fk_episode)
    550                          as pk_health_issue,
    551                  cpr.pk
    552                          as src_pk,
    553                  'clin.procedure'
    554                          as src_table
    555          from
    556                  clin.procedure cpr
    557          where
    558                  cpr.narrative is not NULL
    559
    560
    561          union all
    562          select          -- test results
    563                  (select fk_patient from clin.encounter where pk = ctr.fk_encounter)
    564                          as pk_patient,
    565                  ctr.soap_cat
    566                          as soap_cat,
    567                  coalesce(ctr.narrative, '')
    568                          || coalesce(' ' || ctr.val_alpha, '')
    569                          || coalesce(' ' || ctr.val_unit, '')
    570                          || coalesce(' ' || ctr.val_normal_range, '')
    571                          || coalesce(' ' || ctr.val_target_range, '')
    572                          || coalesce(' ' || ctr.norm_ref_group, '')
    573                          || coalesce(' ' || ctr.note_test_org, '')
    574                          || coalesce(' ' || ctr.material, '')
    575                          || coalesce(' ' || ctr.material_detail, '')
    576                          as narrative,
    577                  ctr.fk_encounter
    578                          as pk_encounter,
    579                  ctr.fk_episode
    580                          as pk_episode,
    581                  (select fk_health_issue from clin.episode where pk = ctr.fk_episode)
    582                          as pk_health_issue,
    583                  ctr.pk
    584                          as src_pk,
    585                  'clin.test_result'
    586                          as src_table
    587          from
    588                  clin.test_result ctr
    589
    590
    591          union all       -- test result reviews
    592          select
    593                  (select fk_patient from clin.encounter where pk =
    594                          (select fk_encounter from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row)
    595                  )
    596                          as pk_patient,
    597                  'o'::text
    598                          as soap_cat,
    599                  crtr.comment
    600                          as narrative,
    601                  (select fk_encounter from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row)
    602                          as pk_encounter,
    603                  (select fk_episode from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row)
    604                          as pk_episode,
    605                  (select fk_health_issue from clin.episode where pk =
    606                          (select fk_episode from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row)
    607                  )
    608                          as pk_health_issue,
    609                  crtr.pk
    610                          as src_pk,
    611                  'clin.reviewed_test_results'
    612                          as src_table
    613          from
    614                  clin.reviewed_test_results crtr
    615
    616
    617          union all
    618          select -- allergy state
    619                  (select fk_patient from clin.encounter where pk = cas.fk_encounter)
    620                          as pk_patient,
    621                  'o'::text
    622                          as soap_cat,
    623                  cas.comment
    624                          as narrative,
    625                  cas.fk_encounter
    626                          as pk_encounter,
    627                  null
    628                          as pk_episode,
    629                  null
    630                          as pk_health_issue,
    631                  cas.pk
    632                          as src_pk,
    633                  'clin.allergy_state'
    634                          as src_table
    635          from
    636                  clin.allergy_state cas
    637          where
    638                  cas.comment is not NULL
    639
    640
    641          union all
    642          select -- allergies
    643                  (select fk_patient from clin.encounter where pk = ca.fk_encounter)
    644                          as pk_patient,
    645                  ca.soap_cat
    646                          as soap_cat,
    647                  coalesce(narrative, '')
    648                          || coalesce(' ' || substance, '')
    649                          || coalesce(' ' || substance_code, '')
    650                          || coalesce(' ' || generics, '')
    651                          || coalesce(' ' || allergene, '')
    652                          || coalesce(' ' || atc_code, '')
    653                          as narrative,
    654                  ca.fk_encounter
    655                          as pk_encounter,
    656                  ca.fk_episode
    657                          as pk_episode,
    658                  (select fk_health_issue from clin.episode where pk = ca.fk_episode)
    659                          as pk_health_issue,
    660                  ca.pk
    661                          as src_pk,
    662                  'clin.allergy'
    663                          as src_table
    664          from
    665                  clin.allergy ca
    666
    667
    668          union all       -- health issues
    669          select
    670                  (select fk_patient from clin.encounter where pk = chi.fk_encounter)
    671                          as pk_patient,
    672                  'a' as soap_cat,
    673                  chi.description
    674                          || coalesce(' ' || chi.summary, '')
    675                          as narrative,
    676                  chi.fk_encounter
    677                          as pk_encounter,
    678                  null
    679                          as pk_episode,
    680                  chi.pk
    681                          as pk_health_issue,
    682                  chi.pk
    683                          as src_pk,
    684                  'clin.health_issue'
    685                          as src_table
    686          from
    687                  clin.health_issue chi
    688
    689
    690          union all       -- encounters
    691          select
    692                  cenc.fk_patient as pk_patient,
    693                  's' as soap_cat,
    694                  coalesce(cenc.reason_for_encounter, '')
    695                          || coalesce(' ' || cenc.assessment_of_encounter, '')
    696                          as narrative,
    697                  cenc.pk as pk_encounter,
    698                  null as pk_episode,
    699                  null as pk_health_issue,
    700                  cenc.pk as src_pk,
    701                  'clin.encounter' as src_table
    702          from
    703                  clin.encounter cenc
    704
    705
    706          union all       -- episodes
    707          select
    708                  (select fk_patient from clin.encounter where pk = cep.fk_encounter)
    709                          as pk_patient,
    710                  's' as soap_cat,
    711                  cep.description
    712                          || coalesce(' ' || cep.summary, '')
    713                          as narrative,
    714                  cep.fk_encounter
    715                          as pk_encounter,
    716                  cep.pk
    717                          as pk_episode,
    718                  cep.fk_health_issue
    719                          as pk_health_issue,
    720                  cep.pk
    721                          as src_pk,
    722                  'clin.episode'
    723                          as src_table
    724          from
    725                  clin.episode cep
    726
    727
    728          union all       -- family history
    729          select
    730                  c_vfhx.pk_patient,
    731                  c_vfhx.soap_cat,
    732                  (c_vfhx.relation || ' / ' || c_vfhx.l10n_relation || ' '
    733                   || c_vfhx.name_relative || ': '
    734                   || c_vfhx.condition
    735                  ) as narrative,
    736                  c_vfhx.pk_encounter,
    737                  c_vfhx.pk_episode,
    738                  c_vfhx.pk_health_issue,
    739                  c_vfhx.pk_family_history as src_pk,
    740                  'clin.family_history' as src_table
    741          from
    742                  clin.v_family_history c_vfhx
    743
    744
    745          union all       -- documents
    746          select
    747                  vdm.pk_patient as pk_patient,
    748                  'o' as soap_cat,
    749                  (vdm.l10n_type || ' ' ||
    750                   coalesce(vdm.ext_ref, '') || ' ' ||
    751                   coalesce(vdm.comment, '')
    752                  ) as narrative,
    753                  vdm.pk_encounter as pk_encounter,
    754                  vdm.pk_episode as pk_episode,
    755                  vdm.pk_health_issue as pk_health_issue,
    756                  vdm.pk_doc as src_pk,
    757                  'blobs.doc_med' as src_table
    758          from
    759                  blobs.v_doc_med vdm
    760
    761
    762          union all       -- document objects
    763          select
    764                  vo4d.pk_patient as pk_patient,
    765                  'o' as soap_cat,
    766                  vo4d.obj_comment as narrative,
    767                  vo4d.pk_encounter as pk_encounter,
    768                  vo4d.pk_episode as pk_episode,
    769                  vo4d.pk_health_issue as pk_health_issue,
    770                  vo4d.pk_obj as src_pk,
    771                  'blobs.doc_obj' as src_table
    772          from
    773                  blobs.v_obj4doc_no_data vo4d
    774
    775
    776          union all       -- document descriptions
    777          select
    778                  vdd.pk_patient as pk_patient,
    779                  'o' as soap_cat,
    780                  vdd.description as narrative,
    781                  vdd.pk_encounter as pk_encounter,
    782                  vdd.pk_episode as pk_episode,
    783                  vdd.pk_health_issue as pk_health_issue,
    784                  vdd.pk_doc_desc as src_pk,
    785                  'blobs.doc_desc' as src_table
    786          from
    787                  blobs.v_doc_desc vdd
    788
    789
    790          union all       -- reviewed documents
    791          select
    792                  vrdo.pk_patient as pk_patient,
    793                  's' as soap_cat,
    794                  vrdo.comment as narrative,
    795                  null as pk_encounter,
    796                  vrdo.pk_episode as pk_episode,
    797                  vrdo.pk_health_issue as pk_health_issue,
    798                  vrdo.pk_review_root as src_pk,
    799                  'blobs.v_reviewed_doc_objects' as src_table
    800          from
    801                  blobs.v_reviewed_doc_objects vrdo
    802
    803
    804          union all       -- patient tags
    805          select
    806                  d_vit.pk_identity
    807                          as pk_patient,
    808                  's' as soap_cat,
    809                  d_vit.l10n_description
    810                          || coalesce(' ' || d_vit.comment, '')
    811                          as narrative,
    812                  null
    813                          as pk_encounter,
    814                  null
    815                          as pk_episode,
    816                  null
    817                          as pk_health_issue,
    818                  d_vit.pk_identity_tag
    819                          as src_pk,
    820                  'dem.v_identity_tags'
    821                          as src_table
    822          from
    823                  dem.v_identity_tags d_vit
    824
    825
    826          union all       -- external care
    827          select
    828                  c_vec.pk_identity
    829                          as pk_patient,
    830                  's' as soap_cat,
    831                  case
    832                          when c_vec.pk_health_issue is null then
    833                                  coalesce(c_vec.issue, '')
    834                                  || coalesce(' / ' || c_vec.provider, '')
    835                                  || coalesce(' / ' || c_vec.comment, '')
    836                          else
    837                                  coalesce(c_vec.provider, '')
    838                                  || coalesce(' / ' || c_vec.comment, '')
    839                  end as narrative,
    840                  c_vec.pk_encounter
    841                          as pk_encounter,
    842                  null
    843                          as pk_episode,
    844                  c_vec.pk_health_issue
    845                          as pk_health_issue,
    846                  c_vec.pk_external_care
    847                          as src_pk,
    848                  'clin.v_external_care'
    849                          as src_table
    850          from
    851                  clin.v_external_care c_vec
    852
    853
    854          union all       -- export items
    855          select
    856                  c_vei.pk_identity
    857                          as pk_patient,
    858                  's' as soap_cat,
    859                  case
    860                          when c_vei.pk_doc_obj is null then
    861                                  coalesce(c_vei.description, '')
    862                                  || coalesce(' / ' || c_vei.filename, '')
    863                          else
    864                                  coalesce(c_vei.description, '')
    865                  end as narrative,
    866                  null
    867                          as pk_encounter,
    868                  null
    869                          as pk_episode,
    870                  null
    871                          as pk_health_issue,
    872                  c_vei.pk_export_item
    873                          as src_pk,
    874                  'clin.v_export_items'
    875                          as src_table
    876          from
    877                  clin.v_export_items c_vei
    878
    879
    880          union all       -- hint suppression rationale
    881          select
    882                  (select fk_patient from clin.encounter where pk = c_sh.fk_encounter)
    883                          as pk_patient,
    884                  'p' as soap_cat,
    885                  c_sh.rationale
    886                          as narrative,
    887                  c_sh.fk_encounter
    888                          as pk_encounter,
    889                  null
    890                          as pk_episode,
    891                  null
    892                          as pk_health_issue,
    893                  c_sh.pk
    894                          as src_pk,
    895                  'clin.suppressed_hint'
    896                          as src_table
    897          from
    898                  clin.suppressed_hint c_sh
    899
    900  ) as union_table
    901
    902  where
    903          trim(coalesce(union_table.narrative, '')) != ''
    904  ;
    905  """
    906
    907 -def search_text_across_emrs(search_term=None):
    908
    909          if search_term is None:
    910                  return []
    911
    912          if search_term.strip() == '':
    913                  return []
    914
    915          #cmd = u'select * from clin.v_narrative4search where narrative ~* %(term)s order by pk_patient limit 1000'
    916          #rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'term': search_term}}], get_col_idx = False)
    917          cmd = u'select * from v_narrative4search where narrative ~* %(term)s order by pk_patient limit 1000'
    918          queries = [
    919                  {'cmd': __VIEW_DEF},
    920                  {'cmd': cmd, 'args': {'term': search_term}}
    921          ]
    922          rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
    923          return rows
    924
    925  #============================================================
    926  # main
    927  #------------------------------------------------------------
    928  if __name__ == '__main__':
    929
    930          if len(sys.argv) < 2:
    931                  sys.exit()
    932
    933          if sys.argv[1] != 'test':
    934                  sys.exit()
    935
    936          from Gnumed.pycommon import gmI18N
    937          gmI18N.activate_locale()
    938          gmI18N.install_domain(domain = 'gnumed')
    939
    940          #-----------------------------------------
    941 -        def test_narrative():
    942                  print("\nnarrative test")
    943                  print("--------------")
    944                  narrative = cNarrative(aPK_obj=7)
    945                  fields = narrative.get_fields()
    946                  for field in fields:
    947                          print(field, ':', narrative[field])
    948                  print("updatable:", narrative.get_updatable_fields())
    949                  print("codes:", narrative.generic_codes)
    950                  #print "adding code..."
    951                  #narrative.add_code('Test code', 'Test coding system')
    952                  #print "codes:", diagnose.get_codes()
    953
    954                  #print "creating narrative..."
    955                  #new_narrative = create_narrative_item(narrative = 'Test narrative', soap_cat = 'a', episode_id=1, encounter_id=2)
    956                  #print new_narrative
    957
    958          #-----------------------------------------
    959 -        def test_search_text_across_emrs():
    960                  results = search_text_across_emrs('cut')
    961                  for r in results:
    962                          print(r)
    963          #-----------------------------------------
    964
    965          #test_search_text_across_emrs()
    966          test_narrative()
    967