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

# Source Code for Module Gnumed.business.gmEMRStructItems

       1  # -*- coding: utf8 -*-
       2  """GNUmed health related business object.
       3
       4  license: GPL v2 or later
       5  """
       6  #============================================================
       7  __version__ = "$Revision: 1.157 $"
       8  __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>"
       9
      10  import types, sys, string, datetime, logging, time
      11
      12
      13  if __name__ == '__main__':
      14          sys.path.insert(0, '../../')
      15  from Gnumed.pycommon import gmPG2
      16  from Gnumed.pycommon import gmI18N
      17  from Gnumed.pycommon import gmTools
      18  from Gnumed.pycommon import gmDateTime
      19  from Gnumed.pycommon import gmBusinessDBObject
      20  from Gnumed.pycommon import gmNull
      21  from Gnumed.pycommon import gmExceptions
      22
      23  from Gnumed.business import gmClinNarrative
      24  from Gnumed.business import gmCoding
      25
      26
      27  _log = logging.getLogger('gm.emr')
      28  _log.info(__version__)
      29
      30  try: _
      31  except NameError: _ = lambda x:x
      32  #============================================================
      33  # diagnostic certainty classification
      34  #============================================================
      35  __diagnostic_certainty_classification_map = None
      36
      37 -def diagnostic_certainty_classification2str(classification):
      38
      39          global __diagnostic_certainty_classification_map
      40
      41          if __diagnostic_certainty_classification_map is None:
      42                  __diagnostic_certainty_classification_map = {
      43                          None: u'',
      44                          u'A': _(u'A: Sign'),
      45                          u'B': _(u'B: Cluster of signs'),
      46                          u'C': _(u'C: Syndromic diagnosis'),
      47                          u'D': _(u'D: Scientific diagnosis')
      48                  }
      49
      50          try:
      51                  return __diagnostic_certainty_classification_map[classification]
      52          except KeyError:
      53                  return _(u'<%s>: unknown diagnostic certainty classification') % classification
      54  #============================================================
      55  # Health Issues API
      56  #============================================================
      57  laterality2str = {
      58          None: u'?',
      59          u'na': u'',
      60          u'sd': _('bilateral'),
      61          u'ds': _('bilateral'),
      62          u's': _('left'),
      63          u'd': _('right')
      64  }
      65
      66  #============================================================
      67 -class cHealthIssue(gmBusinessDBObject.cBusinessDBObject):
      68          """Represents one health issue."""
      69
      70          _cmd_fetch_payload = u"select *, xmin_health_issue from clin.v_health_issues where pk_health_issue=%s"
      71          _cmds_store_payload = [
      72                  u"""update clin.health_issue set
      73                                  description = %(description)s,
      74                                  summary = gm.nullify_empty_string(%(summary)s),
      75                                  age_noted = %(age_noted)s,
      76                                  laterality = gm.nullify_empty_string(%(laterality)s),
      77                                  grouping = gm.nullify_empty_string(%(grouping)s),
      78                                  diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s),
      79                                  is_active = %(is_active)s,
      80                                  clinically_relevant = %(clinically_relevant)s,
      81                                  is_confidential = %(is_confidential)s,
      82                                  is_cause_of_death = %(is_cause_of_death)s
      83                          where
      84                                  pk = %(pk_health_issue)s and
      85                                  xmin = %(xmin_health_issue)s""",
      86                  u"select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s"
      87          ]
      88          _updatable_fields = [
      89                  'description',
      90                  'summary',
      91                  'grouping',
      92                  'age_noted',
      93                  'laterality',
      94                  'is_active',
      95                  'clinically_relevant',
      96                  'is_confidential',
      97                  'is_cause_of_death',
      98                  'diagnostic_certainty_classification'
      99          ]
     100          #--------------------------------------------------------
     101 -        def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
     102                  pk = aPK_obj
     103
     104                  if (pk is not None) or (row is not None):
     105                          gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
     106                          return
     107
     108                  if patient is None:
     109                          cmd = u"""select *, xmin_health_issue from clin.v_health_issues
     110                                          where
     111                                                  description = %(desc)s
     112                                                          and
     113                                                  pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)"""
     114                  else:
     115                          cmd = u"""select *, xmin_health_issue from clin.v_health_issues
     116                                          where
     117                                                  description = %(desc)s
     118                                                          and
     119                                                  pk_patient = %(pat)s"""
     120
     121                  queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}]
     122                  rows, idx = gmPG2.run_ro_queries(queries = queries,     get_col_idx = True)
     123
     124                  if len(rows) == 0:
     125                          raise gmExceptions.NoSuchBusinessObjectError, 'no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient)
     126
     127                  pk = rows[0][0]
     128                  r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'}
     129
     130                  gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
     131          #--------------------------------------------------------
     132          # external API
     133          #--------------------------------------------------------
     134 -        def rename(self, description=None):
     135                  """Method for issue renaming.
     136
     137                  @param description
     138                          - the new descriptive name for the issue
     139                  @type description
     140                          - a string instance
     141                  """
     142                  # sanity check
     143                  if not type(description) in [str, unicode] or description.strip() == '':
     144                          _log.error('<description> must be a non-empty string')
     145                          return False
     146                  # update the issue description
     147                  old_description = self._payload[self._idx['description']]
     148                  self._payload[self._idx['description']] = description.strip()
     149                  self._is_modified = True
     150                  successful, data = self.save_payload()
     151                  if not successful:
     152                          _log.error('cannot rename health issue [%s] with [%s]' % (self, description))
     153                          self._payload[self._idx['description']] = old_description
     154                          return False
     155                  return True
     156          #--------------------------------------------------------
     157 -        def get_episodes(self):
     158                  cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s"
     159                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True)
     160                  return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'})  for r in rows ]
     161          #--------------------------------------------------------
     162 -        def close_expired_episode(self, ttl=180):
     163                  """ttl in days"""
     164                  open_episode = self.get_open_episode()
     165                  if open_episode is None:
     166                          return True
     167                  earliest, latest = open_episode.get_access_range()
     168                  ttl = datetime.timedelta(ttl)
     169                  now = datetime.datetime.now(tz=latest.tzinfo)
     170                  if (latest + ttl) > now:
     171                          return False
     172                  open_episode['episode_open'] = False
     173                  success, data = open_episode.save_payload()
     174                  if success:
     175                          return True
     176                  return False            # should be an exception
     177          #--------------------------------------------------------
     178 -        def close_episode(self):
     179                  open_episode = self.get_open_episode()
     180                  open_episode['episode_open'] = False
     181                  success, data = open_episode.save_payload()
     182                  if success:
     183                          return True
     184                  return False
     185          #--------------------------------------------------------
     186 -        def has_open_episode(self):
     187                  cmd = u"select exists (select 1 from clin.episode where fk_health_issue = %s and is_open is True)"
     188                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
     189                  return rows[0][0]
     190          #--------------------------------------------------------
     191 -        def get_open_episode(self):
     192                  cmd = u"select pk from clin.episode where fk_health_issue = %s and is_open is True"
     193                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
     194                  if len(rows) == 0:
     195                          return None
     196                  return cEpisode(aPK_obj=rows[0][0])
     197          #--------------------------------------------------------
     198 -        def age_noted_human_readable(self):
     199                  if self._payload[self._idx['age_noted']] is None:
     200                          return u'<???>'
     201
     202                  # since we've already got an interval we are bound to use it,
     203                  # further transformation will only introduce more errors,
     204                  # later we can improve this deeper inside
     205                  return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
     206          #--------------------------------------------------------
     207 -        def add_code(self, pk_code=None):
     208                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
     209                  cmd = u"INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
     210                  args = {
     211                          'item': self._payload[self._idx['pk_health_issue']],
     212                          'code': pk_code
     213                  }
     214                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     215                  return True
     216          #--------------------------------------------------------
     217 -        def remove_code(self, pk_code=None):
     218                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
     219                  cmd = u"DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
     220                  args = {
     221                          'item': self._payload[self._idx['pk_health_issue']],
     222                          'code': pk_code
     223                  }
     224                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     225                  return True
     226          #--------------------------------------------------------
     227 -        def format_as_journal(self, left_margin=0, date_format='%a, %b %d %Y'):
     228                  rows = gmClinNarrative.get_as_journal (
     229                          issues = (self.pk_obj,),
     230                          order_by = u'pk_episode, pk_encounter, clin_when, scr, src_table'
     231                  )
     232
     233                  if len(rows) == 0:
     234                          return u''
     235
     236                  left_margin = u' ' * left_margin
     237
     238                  lines = []
     239                  lines.append(_('Clinical data generated during encounters under this health issue:'))
     240
     241                  prev_epi = None
     242                  for row in rows:
     243                          if row['pk_episode'] != prev_epi:
     244                                  lines.append(u'')
     245                                  prev_epi = row['pk_episode']
     246
     247                          when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding())
     248                          top_row = u'%s%s %s (%s) %s' % (
     249                                  gmTools.u_box_top_left_arc,
     250                                  gmTools.u_box_horiz_single,
     251                                  gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']],
     252                                  when,
     253                                  gmTools.u_box_horiz_single * 5
     254                          )
     255                          soap = gmTools.wrap (
     256                                  text = row['narrative'],
     257                                  width = 60,
     258                                  initial_indent = u'  ',
     259                                  subsequent_indent = u'  ' + left_margin
     260                          )
     261                          row_ver = u''
     262                          if row['row_version'] > 0:
     263                                  row_ver = u'v%s: ' % row['row_version']
     264                          bottom_row = u'%s%s %s, %s%s %s' % (
     265                                  u' ' * 40,
     266                                  gmTools.u_box_horiz_light_heavy,
     267                                  row['modified_by'],
     268                                  row_ver,
     269                                  row['date_modified'],
     270                                  gmTools.u_box_horiz_heavy_light
     271                          )
     272
     273                          lines.append(top_row)
     274                          lines.append(soap)
     275                          lines.append(bottom_row)
     276
     277                  eol_w_margin = u'\n%s' % left_margin
     278                  return left_margin + eol_w_margin.join(lines) + u'\n'
     279          #--------------------------------------------------------
     280 -        def format (self, left_margin=0, patient=None,
     281                  with_summary=True,
     282                  with_codes=True,
     283                  with_episodes=True,
     284                  with_encounters=True,
     285                  with_medications=True,
     286                  with_hospital_stays=True,
     287                  with_procedures=True,
     288                  with_family_history=True,
     289                  with_documents=True,
     290                  with_tests=True,
     291                  with_vaccinations=True
     292          ):
     293
     294                  if patient.ID != self._payload[self._idx['pk_patient']]:
     295                          msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % (
     296                                  patient.ID,
     297                                  self._payload[self._idx['pk_health_issue']],
     298                                  self._payload[self._idx['pk_patient']]
     299                          )
     300                          raise ValueError(msg)
     301
     302                  lines = []
     303
     304                  lines.append(_('Health Issue %s%s%s%s   [#%s]') % (
     305                          u'\u00BB',
     306                          self._payload[self._idx['description']],
     307                          u'\u00AB',
     308                          gmTools.coalesce (
     309                                  initial = self.laterality_description,
     310                                  instead = u'',
     311                                  template_initial = u' (%s)',
     312                                  none_equivalents = [None, u'', u'?']
     313                          ),
     314                          self._payload[self._idx['pk_health_issue']]
     315                  ))
     316
     317                  if self._payload[self._idx['is_confidential']]:
     318                          lines.append('')
     319                          lines.append(_(' ***** CONFIDENTIAL *****'))
     320                          lines.append('')
     321
     322                  if self._payload[self._idx['is_cause_of_death']]:
     323                          lines.append('')
     324                          lines.append(_(' contributed to death of patient'))
     325                          lines.append('')
     326
     327                  enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']])
     328                  lines.append (_(' Created during encounter: %s (%s - %s)   [#%s]') % (
     329                          enc['l10n_type'],
     330                          enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
     331                          enc['last_affirmed_original_tz'].strftime('%H:%M'),
     332                          self._payload[self._idx['pk_encounter']]
     333                  ))
     334
     335                  if self._payload[self._idx['age_noted']] is not None:
     336                          lines.append(_(' Noted at age: %s') % self.age_noted_human_readable())
     337
     338                  lines.append(u' ' + _('Status') + u': %s, %s%s' % (
     339                          gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')),
     340                          gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')),
     341                          gmTools.coalesce (
     342                                  initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]),
     343                                  instead = u'',
     344                                  template_initial = u', %s',
     345                                  none_equivalents = [None, u'']
     346                          )
     347                  ))
     348
     349                  if with_summary:
     350                          if self._payload[self._idx['summary']] is not None:
     351                                  lines.append(u'')
     352                                  lines.append(gmTools.wrap (
     353                                          text = self._payload[self._idx['summary']],
     354                                          width = 60,
     355                                          initial_indent = u'  ',
     356                                          subsequent_indent = u'  '
     357                                  ))
     358
     359                  # codes ?
     360                  if with_codes:
     361                          codes = self.generic_codes
     362                          if len(codes) > 0:
     363                                  lines.append(u'')
     364                          for c in codes:
     365                                  lines.append(u' %s: %s (%s - %s)' % (
     366                                          c['code'],
     367                                          c['term'],
     368                                          c['name_short'],
     369                                          c['version']
     370                                  ))
     371                          del codes
     372
     373                  lines.append(u'')
     374
     375                  emr = patient.get_emr()
     376
     377                  # episodes
     378                  if with_episodes:
     379                          epis = emr.get_episodes(issues = [self._payload[self._idx['pk_health_issue']]])
     380                          if epis is None:
     381                                  lines.append(_('Error retrieving episodes for this health issue.'))
     382                          elif len(epis) == 0:
     383                                  lines.append(_('There are no episodes for this health issue.'))
     384                          else:
     385                                  lines.append (
     386                                          _('Episodes: %s (most recent: %s%s%s)') % (
     387                                                  len(epis),
     388                                                  gmTools.u_left_double_angle_quote,
     389                                                  emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'],
     390                                                  gmTools.u_right_double_angle_quote
     391                                          )
     392                                  )
     393                                  for epi in epis:
     394                                          lines.append(u' \u00BB%s\u00AB (%s)' % (
     395                                                  epi['description'],
     396                                                  gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed'))
     397                                          ))
     398                          lines.append('')
     399
     400                  # encounters
     401                  if with_encounters:
     402                          first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']])
     403                          last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']])
     404
     405                          if first_encounter is None or last_encounter is None:
     406                                  lines.append(_('No encounters found for this health issue.'))
     407                          else:
     408                                  encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]])
     409                                  lines.append(_('Encounters: %s (%s - %s):') % (
     410                                          len(encs),
     411                                          first_encounter['started_original_tz'].strftime('%m/%Y'),
     412                                          last_encounter['last_affirmed_original_tz'].strftime('%m/%Y')
     413                                  ))
     414                                  lines.append(_(' Most recent: %s - %s') % (
     415                                          last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
     416                                          last_encounter['last_affirmed_original_tz'].strftime('%H:%M')
     417                                  ))
     418
     419                  # medications
     420                  if with_medications:
     421                          meds = emr.get_current_substance_intake (
     422                                  issues = [ self._payload[self._idx['pk_health_issue']] ],
     423                                  order_by = u'is_currently_active, started, substance'
     424                          )
     425                          if len(meds) > 0:
     426                                  lines.append(u'')
     427                                  lines.append(_('Active medications: %s') % len(meds))
     428                          for m in meds:
     429                                  lines.append(m.format(left_margin = (left_margin + 1)))
     430                          del meds
     431
     432                  # hospital stays
     433                  if with_hospital_stays:
     434                          stays = emr.get_hospital_stays (
     435                                  issues = [ self._payload[self._idx['pk_health_issue']] ]
     436                          )
     437                          if len(stays) > 0:
     438                                  lines.append(u'')
     439                                  lines.append(_('Hospital stays: %s') % len(stays))
     440                          for s in stays:
     441                                  lines.append(s.format(left_margin = (left_margin + 1)))
     442                          del stays
     443
     444                  # procedures
     445                  if with_procedures:
     446                          procs = emr.get_performed_procedures (
     447                                  issues = [ self._payload[self._idx['pk_health_issue']] ]
     448                          )
     449                          if len(procs) > 0:
     450                                  lines.append(u'')
     451                                  lines.append(_('Procedures performed: %s') % len(procs))
     452                          for p in procs:
     453                                  lines.append(p.format(left_margin = (left_margin + 1)))
     454                          del procs
     455
     456                  # family history
     457                  if with_family_history:
     458                          fhx = emr.get_family_history(issues = [ self._payload[self._idx['pk_health_issue']] ])
     459                          if len(fhx) > 0:
     460                                  lines.append(u'')
     461                                  lines.append(_('Family History: %s') % len(fhx))
     462                          for f in fhx:
     463                                  lines.append(f.format (
     464                                          left_margin = (left_margin + 1),
     465                                          include_episode = True,
     466                                          include_comment = True,
     467                                          include_codes = False
     468                                  ))
     469                          del fhx
     470
     471                  epis = self.get_episodes()
     472                  if len(epis) > 0:
     473                          epi_pks = [ e['pk_episode'] for e in epis ]
     474
     475                          # documents
     476                          if with_documents:
     477                                  doc_folder = patient.get_document_folder()
     478                                  docs = doc_folder.get_documents(episodes = epi_pks)
     479                                  if len(docs) > 0:
     480                                          lines.append(u'')
     481                                          lines.append(_('Documents: %s') % len(docs))
     482                                  del docs
     483
     484                          # test results
     485                          if with_tests:
     486                                  tests = emr.get_test_results_by_date(episodes = epi_pks)
     487                                  if len(tests) > 0:
     488                                          lines.append(u'')
     489                                          lines.append(_('Measurements and Results: %s') % len(tests))
     490                                  del tests
     491
     492                          # vaccinations
     493                          if with_vaccinations:
     494                                  vaccs = emr.get_vaccinations(episodes = epi_pks, order_by = u'date_given, vaccine')
     495                                  if len(vaccs) > 0:
     496                                          lines.append(u'')
     497                                          lines.append(_('Vaccinations:'))
     498                                  for vacc in vaccs:
     499                                          lines.extend(vacc.format(with_reaction = True))
     500                                  del vaccs
     501
     502                  del epis
     503
     504                  left_margin = u' ' * left_margin
     505                  eol_w_margin = u'\n%s' % left_margin
     506                  lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = u'\n')
     507                  return left_margin + eol_w_margin.join(lines) + u'\n'
     508          #--------------------------------------------------------
     509          # properties
     510          #--------------------------------------------------------
     511          episodes = property(get_episodes, lambda x:x)
     512          #--------------------------------------------------------
     513          open_episode = property(get_open_episode, lambda x:x)
     514          #--------------------------------------------------------
     515 -        def _get_latest_episode(self):
     516                  cmd = u"""SELECT
     517                          coalesce (
     518                                  (SELECT pk FROM clin.episode WHERE fk_health_issue = %(issue)s AND is_open IS TRUE),
     519                                  (SELECT pk FROM clin.v_pat_episodes WHERE fk_health_issue = %(issue)s ORDER BY last_affirmed DESC limit 1)
     520                  )"""
     521                  args = {'issue': self.pk_obj}
     522                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     523                  if len(rows) == 0:
     524                          return None
     525                  return cEpisode(aPK_obj = rows[0][0])
     526
     527          latest_episode = property(_get_latest_episode, lambda x:x)
     528          #--------------------------------------------------------
     529 -        def _get_laterality_description(self):
     530                  try:
     531                          return laterality2str[self._payload[self._idx['laterality']]]
     532                  except KeyError:
     533                          return u'<???>'
     534
     535          laterality_description = property(_get_laterality_description, lambda x:x)
     536          #--------------------------------------------------------
     537 -        def _get_diagnostic_certainty_description(self):
     538                  return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
     539
     540          diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
     541          #--------------------------------------------------------
     542 -        def _get_generic_codes(self):
     543                  if len(self._payload[self._idx['pk_generic_codes']]) == 0:
     544                          return []
     545
     546                  cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
     547                  args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
     548                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     549                  return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
     550
     551 -        def _set_generic_codes(self, pk_codes):
     552                  queries = []
     553                  # remove all codes
     554                  if len(self._payload[self._idx['pk_generic_codes']]) > 0:
     555                          queries.append ({
     556                                  'cmd': u'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s',
     557                                  'args': {
     558                                          'issue': self._payload[self._idx['pk_health_issue']],
     559                                          'codes': tuple(self._payload[self._idx['pk_generic_codes']])
     560                                  }
     561                          })
     562                  # add new codes
     563                  for pk_code in pk_codes:
     564                          queries.append ({
     565                                  'cmd': u'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)',
     566                                  'args': {
     567                                          'issue': self._payload[self._idx['pk_health_issue']],
     568                                          'pk_code': pk_code
     569                                  }
     570                          })
     571                  if len(queries) == 0:
     572                          return
     573                  # run it all in one transaction
     574                  rows, idx = gmPG2.run_rw_queries(queries = queries)
     575                  return
     576
     577          generic_codes = property(_get_generic_codes, _set_generic_codes)
     578  #============================================================
     579 -def create_health_issue(description=None, encounter=None, patient=None):
     580          """Creates a new health issue for a given patient.
     581
     582          description - health issue name
     583          """
     584          try:
     585                  h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient)
     586                  return h_issue
     587          except gmExceptions.NoSuchBusinessObjectError:
     588                  pass
     589
     590          queries = []
     591          cmd = u"insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)"
     592          queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}})
     593
     594          cmd = u"select currval('clin.health_issue_pk_seq')"
     595          queries.append({'cmd': cmd})
     596
     597          rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
     598          h_issue = cHealthIssue(aPK_obj = rows[0][0])
     599
     600          return h_issue
     601  #-----------------------------------------------------------
     602 -def delete_health_issue(health_issue=None):
     603          if isinstance(health_issue, cHealthIssue):
     604                  pk = health_issue['pk_health_issue']
     605          else:
     606                  pk = int(health_issue)
     607
     608          try:
     609                  gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.health_issue where pk=%(pk)s', 'args': {'pk': pk}}])
     610          except gmPG2.dbapi.IntegrityError:
     611                  # should be parsing pgcode/and or error message
     612                  _log.exception('cannot delete health issue')
     613                  raise gmExceptions.DatabaseObjectInUseError('cannot delete health issue, it is in use')
     614  #------------------------------------------------------------
     615  # use as dummy for unassociated episodes
     616 -def get_dummy_health_issue():
     617          issue = {
     618                  'pk_health_issue': None,
     619                  'description': _('Unattributed episodes'),
     620                  'age_noted': None,
     621                  'laterality': u'na',
     622                  'is_active': True,
     623                  'clinically_relevant': True,
     624                  'is_confidential': None,
     625                  'is_cause_of_death': False,
     626                  'is_dummy': True,
     627                  'grouping': None
     628          }
     629          return issue
     630  #-----------------------------------------------------------
     631 -def health_issue2problem(health_issue=None, allow_irrelevant=False):
     632          return cProblem (
     633                  aPK_obj = {
     634                          'pk_patient': health_issue['pk_patient'],
     635                          'pk_health_issue': health_issue['pk_health_issue'],
     636                          'pk_episode': None
     637                  },
     638                  try_potential_problems = allow_irrelevant
     639          )
     640  #============================================================
     641  # episodes API
     642  #============================================================
     643 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
     644          """Represents one clinical episode.
     645          """
     646          _cmd_fetch_payload = u"select * from clin.v_pat_episodes where pk_episode=%s"
     647          _cmds_store_payload = [
     648                  u"""update clin.episode set
     649                                  fk_health_issue = %(pk_health_issue)s,
     650                                  is_open = %(episode_open)s::boolean,
     651                                  description = %(description)s,
     652                                  summary = gm.nullify_empty_string(%(summary)s),
     653                                  diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s)
     654                          where
     655                                  pk = %(pk_episode)s and
     656                                  xmin = %(xmin_episode)s""",
     657                  u"""select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s"""
     658          ]
     659          _updatable_fields = [
     660                  'pk_health_issue',
     661                  'episode_open',
     662                  'description',
     663                  'summary',
     664                  'diagnostic_certainty_classification'
     665          ]
     666          #--------------------------------------------------------
     667 -        def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None):
     668                  pk = aPK_obj
     669                  if pk is None and row is None:
     670
     671                          where_parts = [u'description = %(desc)s']
     672
     673                          if id_patient is not None:
     674                                  where_parts.append(u'pk_patient = %(pat)s')
     675
     676                          if health_issue is not None:
     677                                  where_parts.append(u'pk_health_issue = %(issue)s')
     678
     679                          if encounter is not None:
     680                                  where_parts.append(u'pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)')
     681
     682                          args = {
     683                                  'pat': id_patient,
     684                                  'issue': health_issue,
     685                                  'enc': encounter,
     686                                  'desc': name
     687                          }
     688
     689                          cmd = u"select * from clin.v_pat_episodes where %s" % u' and '.join(where_parts)
     690
     691                          rows, idx = gmPG2.run_ro_queries(
     692                                  queries = [{'cmd': cmd, 'args': args}],
     693                                  get_col_idx=True
     694                          )
     695
     696                          if len(rows) == 0:
     697                                  raise gmExceptions.NoSuchBusinessObjectError, 'no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter)
     698
     699                          r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'}
     700                          gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
     701
     702                  else:
     703                          gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
     704          #--------------------------------------------------------
     705          # external API
     706          #--------------------------------------------------------
     707 -        def get_access_range(self):
     708                  """Get earliest and latest access to this episode.
     709
     710                  Returns a tuple(earliest, latest).
     711                  """
     712                  cmd = u"""
     713  select
     714          min(earliest),
     715          max(latest)
     716  from (
     717          (select
     718                  (case when clin_when < modified_when
     719                          then clin_when
     720                          else modified_when
     721                  end) as earliest,
     722                  (case when clin_when > modified_when
     723                          then clin_when
     724                          else modified_when
     725                  end) as latest
     726          from
     727                  clin.clin_root_item
     728          where
     729                  fk_episode = %(pk)s
     730
     731          ) union all (
     732
     733          select
     734                  modified_when as earliest,
     735                  modified_when as latest
     736          from
     737                  clin.episode
     738          where
     739                  pk = %(pk)s
     740          )
     741  ) as ranges"""
     742                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
     743                  if len(rows) == 0:
     744                          return (gmNull.cNull(warn=False), gmNull.cNull(warn=False))
     745                  return (rows[0][0], rows[0][1])
     746          #--------------------------------------------------------
     747 -        def get_patient(self):
     748                  return self._payload[self._idx['pk_patient']]
     749          #--------------------------------------------------------
     750 -        def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
     751                  return gmClinNarrative.get_narrative (
     752                          soap_cats = soap_cats,
     753                          encounters = encounters,
     754                          episodes = [self.pk_obj],
     755                          order_by = order_by
     756                  )
     757          #--------------------------------------------------------
     758 -        def rename(self, description=None):
     759                  """Method for episode editing, that is, episode renaming.
     760
     761                  @param description
     762                          - the new descriptive name for the encounter
     763                  @type description
     764                          - a string instance
     765                  """
     766                  # sanity check
     767                  if description.strip() == '':
     768                          _log.error('<description> must be a non-empty string instance')
     769                          return False
     770                  # update the episode description
     771                  old_description = self._payload[self._idx['description']]
     772                  self._payload[self._idx['description']] = description.strip()
     773                  self._is_modified = True
     774                  successful, data = self.save_payload()
     775                  if not successful:
     776                          _log.error('cannot rename episode [%s] to [%s]' % (self, description))
     777                          self._payload[self._idx['description']] = old_description
     778                          return False
     779                  return True
     780          #--------------------------------------------------------
     781 -        def add_code(self, pk_code=None):
     782                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
     783
     784                  if pk_code in self._payload[self._idx['pk_generic_codes']]:
     785                          return
     786
     787                  cmd = u"""
     788                          INSERT INTO clin.lnk_code2episode
     789                                  (fk_item, fk_generic_code)
     790                          SELECT
     791                                  %(item)s,
     792                                  %(code)s
     793                          WHERE NOT EXISTS (
     794                                  SELECT 1 FROM clin.lnk_code2episode
     795                                  WHERE
     796                                          fk_item = %(item)s
     797                                                  AND
     798                                          fk_generic_code = %(code)s
     799                          )"""
     800                  args = {
     801                          'item': self._payload[self._idx['pk_episode']],
     802                          'code': pk_code
     803                  }
     804                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     805                  return
     806          #--------------------------------------------------------
     807 -        def remove_code(self, pk_code=None):
     808                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
     809                  cmd = u"DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
     810                  args = {
     811                          'item': self._payload[self._idx['pk_episode']],
     812                          'code': pk_code
     813                  }
     814                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     815                  return True
     816          #--------------------------------------------------------
     817 -        def format_as_journal(self, left_margin=0, date_format='%a, %b %d %Y'):
     818                  rows = gmClinNarrative.get_as_journal (
     819                          episodes = (self.pk_obj,),
     820                          order_by = u'pk_encounter, clin_when, scr, src_table'
     821                          #order_by = u'pk_encounter, scr, clin_when, src_table'
     822                  )
     823
     824                  if len(rows) == 0:
     825                          return u''
     826
     827                  lines = []
     828
     829                  lines.append(_('Clinical data generated during encounters within this episode:'))
     830
     831                  left_margin = u' ' * left_margin
     832
     833                  prev_enc = None
     834                  for row in rows:
     835                          if row['pk_encounter'] != prev_enc:
     836                                  lines.append(u'')
     837                                  prev_enc = row['pk_encounter']
     838
     839                          when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding())
     840                          top_row = u'%s%s %s (%s) %s' % (
     841                                  gmTools.u_box_top_left_arc,
     842                                  gmTools.u_box_horiz_single,
     843                                  gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']],
     844                                  when,
     845                                  gmTools.u_box_horiz_single * 5
     846                          )
     847                          soap = gmTools.wrap (
     848                                  text = row['narrative'],
     849                                  width = 60,
     850                                  initial_indent = u'  ',
     851                                  subsequent_indent = u'  ' + left_margin
     852                          )
     853                          row_ver = u''
     854                          if row['row_version'] > 0:
     855                                  row_ver = u'v%s: ' % row['row_version']
     856                          bottom_row = u'%s%s %s, %s%s %s' % (
     857                                  u' ' * 40,
     858                                  gmTools.u_box_horiz_light_heavy,
     859                                  row['modified_by'],
     860                                  row_ver,
     861                                  row['date_modified'],
     862                                  gmTools.u_box_horiz_heavy_light
     863                          )
     864
     865                          lines.append(top_row)
     866                          lines.append(soap)
     867                          lines.append(bottom_row)
     868
     869                  eol_w_margin = u'\n%s' % left_margin
     870                  return left_margin + eol_w_margin.join(lines) + u'\n'
     871          #--------------------------------------------------------
     872 -        def format(self, left_margin=0, patient=None,
     873                  with_summary=True,
     874                  with_codes=True,
     875                  with_encounters=True,
     876                  with_documents=True,
     877                  with_hospital_stays=True,
     878                  with_procedures=True,
     879                  with_family_history=True,
     880                  with_tests=True,
     881                  with_vaccinations=True,
     882                  with_health_issue=False
     883          ):
     884
     885                  if patient.ID != self._payload[self._idx['pk_patient']]:
     886                          msg = '<patient>.ID = %s but episode %s belongs to patient %s' % (
     887                                  patient.ID,
     888                                  self._payload[self._idx['pk_episode']],
     889                                  self._payload[self._idx['pk_patient']]
     890                          )
     891                          raise ValueError(msg)
     892
     893                  lines = []
     894
     895                  # episode details
     896                  lines.append (_('Episode %s%s%s   [#%s]') % (
     897                          gmTools.u_left_double_angle_quote,
     898                          self._payload[self._idx['description']],
     899                          gmTools.u_right_double_angle_quote,
     900                          self._payload[self._idx['pk_episode']]
     901                  ))
     902
     903                  enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']])
     904                  lines.append (u' ' + _('Created during encounter: %s (%s - %s)   [#%s]') % (
     905                          enc['l10n_type'],
     906                          enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
     907                          enc['last_affirmed_original_tz'].strftime('%H:%M'),
     908                          self._payload[self._idx['pk_encounter']]
     909                  ))
     910
     911                  emr = patient.get_emr()
     912                  encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]])
     913                  first_encounter = None
     914                  last_encounter = None
     915                  if (encs is not None) and (len(encs) > 0):
     916                          first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']])
     917                          last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']])
     918                          if self._payload[self._idx['episode_open']]:
     919                                  end = gmDateTime.pydt_now_here()
     920                                  end_str = gmTools.u_ellipsis
     921                          else:
     922                                  end = last_encounter['last_affirmed']
     923                                  end_str = last_encounter['last_affirmed'].strftime('%m/%Y')
     924                          age = gmDateTime.format_interval_medically(end - first_encounter['started'])
     925                          lines.append(_(' Duration: %s (%s - %s)') % (
     926                                  age,
     927                                  first_encounter['started'].strftime('%m/%Y'),
     928                                  end_str
     929                          ))
     930
     931                  lines.append(u' ' + _('Status') + u': %s%s' % (
     932                          gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')),
     933                          gmTools.coalesce (
     934                                  initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]),
     935                                  instead = u'',
     936                                  template_initial = u', %s',
     937                                  none_equivalents = [None, u'']
     938                          )
     939                  ))
     940
     941                  if with_health_issue:
     942                          lines.append(u' ' + _('Health issue') + u': %s' % gmTools.coalesce (
     943                                  self._payload[self._idx['health_issue']],
     944                                  _('none associated')
     945                          ))
     946
     947                  if with_summary:
     948                          if self._payload[self._idx['summary']] is not None:
     949                                  lines.append(u'')
     950                                  lines.append(gmTools.wrap (
     951                                                  text = self._payload[self._idx['summary']],
     952                                                  width = 60,
     953                                                  initial_indent = u'  ',
     954                                                  subsequent_indent = u'  '
     955                                          )
     956                                  )
     957
     958                  # codes
     959                  if with_codes:
     960                          codes = self.generic_codes
     961                          if len(codes) > 0:
     962                                  lines.append(u'')
     963                          for c in codes:
     964                                  lines.append(u' %s: %s (%s - %s)' % (
     965                                          c['code'],
     966                                          c['term'],
     967                                          c['name_short'],
     968                                          c['version']
     969                                  ))
     970                          del codes
     971
     972                  lines.append(u'')
     973
     974                  # encounters
     975                  if with_encounters:
     976                          if encs is None:
     977                                  lines.append(_('Error retrieving encounters for this episode.'))
     978                          elif len(encs) == 0:
     979                                  #lines.append(_('There are no encounters for this episode.'))
     980                                  pass
     981                          else:
     982                                  lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M'))
     983
     984                                  if len(encs) < 4:
     985                                          line = _('%s encounter(s) (%s - %s):')
     986                                  else:
     987                                          line = _('1st and (up to 3) most recent (of %s) encounters (%s - %s):')
     988                                  lines.append(line % (
     989                                          len(encs),
     990                                          first_encounter['started'].strftime('%m/%Y'),
     991                                          last_encounter['last_affirmed'].strftime('%m/%Y')
     992                                  ))
     993
     994                                  lines.append(u' %s - %s (%s):%s' % (
     995                                          first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
     996                                          first_encounter['last_affirmed_original_tz'].strftime('%H:%M'),
     997                                          first_encounter['l10n_type'],
     998                                          gmTools.coalesce (
     999                                                  first_encounter['assessment_of_encounter'],
    1000                                                  gmTools.coalesce (
    1001                                                          first_encounter['reason_for_encounter'],
    1002                                                          u'',
    1003                                                          u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE'))
    1004                                                  ),
    1005                                                  u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE'))
    1006                                          )
    1007                                  ))
    1008
    1009                                  if len(encs) > 4:
    1010                                          lines.append(_(' ... %s skipped ...') % (len(encs) - 4))
    1011
    1012                                  for enc in encs[1:][-3:]:
    1013                                          lines.append(u' %s - %s (%s):%s' % (
    1014                                                  enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
    1015                                                  enc['last_affirmed_original_tz'].strftime('%H:%M'),
    1016                                                  enc['l10n_type'],
    1017                                                  gmTools.coalesce (
    1018                                                          enc['assessment_of_encounter'],
    1019                                                          gmTools.coalesce (
    1020                                                                  enc['reason_for_encounter'],
    1021                                                                  u'',
    1022                                                                  u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE'))
    1023                                                          ),
    1024                                                          u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE'))
    1025                                                  )
    1026                                          ))
    1027                                  del encs
    1028
    1029                          # spell out last encounter
    1030                          if last_encounter is not None:
    1031                                  lines.append('')
    1032                                  lines.append(_('Progress notes in most recent encounter:'))
    1033                                  lines.extend(last_encounter.format_soap (
    1034                                          episodes = [ self._payload[self._idx['pk_episode']] ],
    1035                                          left_margin = left_margin,
    1036                                          soap_cats = 'soapu',
    1037                                          emr = emr
    1038                                  ))
    1039
    1040                  # documents
    1041                  if with_documents:
    1042                          doc_folder = patient.get_document_folder()
    1043                          docs = doc_folder.get_documents (
    1044                                  episodes = [ self._payload[self._idx['pk_episode']] ]
    1045                          )
    1046                          if len(docs) > 0:
    1047                                  lines.append('')
    1048                                  lines.append(_('Documents: %s') % len(docs))
    1049                          for d in docs:
    1050                                  lines.append(u' %s %s:%s%s' % (
    1051                                          d['clin_when'].strftime('%Y-%m-%d'),
    1052                                          d['l10n_type'],
    1053                                          gmTools.coalesce(d['comment'], u'', u' "%s"'),
    1054                                          gmTools.coalesce(d['ext_ref'], u'', u' (%s)')
    1055                                  ))
    1056                          del docs
    1057
    1058                  # hospital stays
    1059                  if with_hospital_stays:
    1060                          stays = emr.get_hospital_stays(episodes = [ self._payload[self._idx['pk_episode']] ])
    1061                          if len(stays) > 0:
    1062                                  lines.append('')
    1063                                  lines.append(_('Hospital stays: %s') % len(stays))
    1064                          for s in stays:
    1065                                  lines.append(s.format(left_margin = (left_margin + 1)))
    1066                          del stays
    1067
    1068                  # procedures
    1069                  if with_procedures:
    1070                          procs = emr.get_performed_procedures(episodes = [ self._payload[self._idx['pk_episode']] ])
    1071                          if len(procs) > 0:
    1072                                  lines.append(u'')
    1073                                  lines.append(_('Procedures performed: %s') % len(procs))
    1074                          for p in procs:
    1075                                  lines.append(p.format (
    1076                                          left_margin = (left_margin + 1),
    1077                                          include_episode = False,
    1078                                          include_codes = True
    1079                                  ))
    1080                          del procs
    1081
    1082                  # family history
    1083                  if with_family_history:
    1084                          fhx = emr.get_family_history(episodes = [ self._payload[self._idx['pk_episode']] ])
    1085                          if len(fhx) > 0:
    1086                                  lines.append(u'')
    1087                                  lines.append(_('Family History: %s') % len(fhx))
    1088                          for f in fhx:
    1089                                  lines.append(f.format (
    1090                                          left_margin = (left_margin + 1),
    1091                                          include_episode = False,
    1092                                          include_comment = True,
    1093                                          include_codes = True
    1094                                  ))
    1095                          del fhx
    1096
    1097                  # test results
    1098                  if with_tests:
    1099                          tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ])
    1100                          if len(tests) > 0:
    1101                                  lines.append('')
    1102                                  lines.append(_('Measurements and Results:'))
    1103                          for t in tests:
    1104                                  lines.extend(t.format (
    1105                                          with_review = False,
    1106                                          with_comments = False,
    1107                                          date_format = '%Y-%m-%d'
    1108                                  ))
    1109                          del tests
    1110
    1111                  # vaccinations
    1112                  if with_vaccinations:
    1113                          vaccs = emr.get_vaccinations (
    1114                                  episodes = [ self._payload[self._idx['pk_episode']] ],
    1115                                  order_by = u'date_given DESC, vaccine'
    1116                          )
    1117                          if len(vaccs) > 0:
    1118                                  lines.append(u'')
    1119                                  lines.append(_('Vaccinations:'))
    1120                          for vacc in vaccs:
    1121                                  lines.extend(vacc.format (
    1122                                          with_indications = True,
    1123                                          with_comment = True,
    1124                                          with_reaction = True,
    1125                                          date_format = '%Y-%m-%d'
    1126                                  ))
    1127                          del vaccs
    1128
    1129                  left_margin = u' ' * left_margin
    1130                  eol_w_margin = u'\n%s' % left_margin
    1131                  lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = u'\n')
    1132                  return left_margin + eol_w_margin.join(lines) + u'\n'
    1133          #--------------------------------------------------------
    1134          # properties
    1135          #--------------------------------------------------------
    1136 -        def _get_diagnostic_certainty_description(self):
    1137                  return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
    1138
    1139          diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
    1140          #--------------------------------------------------------
    1141 -        def _get_generic_codes(self):
    1142                  if len(self._payload[self._idx['pk_generic_codes']]) == 0:
    1143                          return []
    1144
    1145                  cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
    1146                  args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
    1147                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1148                  return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
    1149
    1150 -        def _set_generic_codes(self, pk_codes):
    1151                  queries = []
    1152                  # remove all codes
    1153                  if len(self._payload[self._idx['pk_generic_codes']]) > 0:
    1154                          queries.append ({
    1155                                  'cmd': u'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s',
    1156                                  'args': {
    1157                                          'epi': self._payload[self._idx['pk_episode']],
    1158                                          'codes': tuple(self._payload[self._idx['pk_generic_codes']])
    1159                                  }
    1160                          })
    1161                  # add new codes
    1162                  for pk_code in pk_codes:
    1163                          queries.append ({
    1164                                  'cmd': u'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)',
    1165                                  'args': {
    1166                                          'epi': self._payload[self._idx['pk_episode']],
    1167                                          'pk_code': pk_code
    1168                                  }
    1169                          })
    1170                  if len(queries) == 0:
    1171                          return
    1172                  # run it all in one transaction
    1173                  rows, idx = gmPG2.run_rw_queries(queries = queries)
    1174                  return
    1175
    1176          generic_codes = property(_get_generic_codes, _set_generic_codes)
    1177          #--------------------------------------------------------
    1178 -        def _get_has_narrative(self):
    1179                  cmd = u"""SELECT EXISTS (
    1180                          SELECT 1 FROM clin.clin_narrative
    1181                          WHERE
    1182                                  fk_episode = %(epi)s
    1183                                          AND
    1184                                  fk_encounter IN (
    1185                                          SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s
    1186                                  )
    1187                  )"""
    1188                  args = {
    1189                          u'pat': self._payload[self._idx['pk_patient']],
    1190                          u'epi': self._payload[self._idx['pk_episode']]
    1191                  }
    1192                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1193                  return rows[0][0]
    1194
    1195          has_narrative = property(_get_has_narrative, lambda x:x)
    1196  #============================================================
    1197 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):
    1198          """Creates a new episode for a given patient's health issue.
    1199
    1200          pk_health_issue - given health issue PK
    1201          episode_name - name of episode
    1202          """
    1203          if not allow_dupes:
    1204                  try:
    1205                          episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter)
    1206                          if episode['episode_open'] != is_open:
    1207                                  episode['episode_open'] = is_open
    1208                                  episode.save_payload()
    1209                          return episode
    1210                  except gmExceptions.ConstructorError:
    1211                          pass
    1212
    1213          queries = []
    1214          cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)"
    1215          queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]})
    1216          queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"})
    1217          rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True)
    1218
    1219          episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'})
    1220          return episode
    1221  #-----------------------------------------------------------
    1222 -def delete_episode(episode=None):
    1223          if isinstance(episode, cEpisode):
    1224                  pk = episode['pk_episode']
    1225          else:
    1226                  pk = int(episode)
    1227
    1228          cmd = u'DELETE FROM clin.episode WHERE pk = %(pk)s'
    1229
    1230          try:
    1231                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
    1232          except gmPG2.dbapi.IntegrityError:
    1233                  # should be parsing pgcode/and or error message
    1234                  _log.exception('cannot delete episode, it is in use')
    1235                  return False
    1236
    1237          return True
    1238  #-----------------------------------------------------------
    1239 -def episode2problem(episode=None, allow_closed=False):
    1240          return cProblem (
    1241                  aPK_obj = {
    1242                          'pk_patient': episode['pk_patient'],
    1243                          'pk_episode': episode['pk_episode'],
    1244                          'pk_health_issue': episode['pk_health_issue']
    1245                  },
    1246                  try_potential_problems = allow_closed
    1247          )
    1248  #============================================================
    1249  # encounter API
    1250  #============================================================
    1251 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
    1252          """Represents one encounter."""
    1253
    1254          _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s"
    1255          _cmds_store_payload = [
    1256                  u"""UPDATE clin.encounter SET
    1257                                  started = %(started)s,
    1258                                  last_affirmed = %(last_affirmed)s,
    1259                                  fk_location = %(pk_location)s,
    1260                                  fk_type = %(pk_type)s,
    1261                                  reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s),
    1262                                  assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s)
    1263                          WHERE
    1264                                  pk = %(pk_encounter)s AND
    1265                                  xmin = %(xmin_encounter)s
    1266                          """,
    1267                  # need to return all fields so we can survive in-place upgrades
    1268                  u"""select * from clin.v_pat_encounters where pk_encounter = %(pk_encounter)s"""
    1269          ]
    1270          _updatable_fields = [
    1271                  'started',
    1272                  'last_affirmed',
    1273                  'pk_location',
    1274                  'pk_type',
    1275                  'reason_for_encounter',
    1276                  'assessment_of_encounter'
    1277          ]
    1278          #--------------------------------------------------------
    1279 -        def set_active(self):
    1280                  """Set the encounter as the active one.
    1281
    1282                  "Setting active" means making sure the encounter
    1283                  row has the youngest "last_affirmed" timestamp of
    1284                  all encounter rows for this patient.
    1285                  """
    1286                  self['last_affirmed'] = gmDateTime.pydt_now_here()
    1287                  self.save()
    1288          #--------------------------------------------------------
    1289 -        def transfer_clinical_data(self, source_episode=None, target_episode=None):
    1290                  """
    1291                  Moves every element currently linked to the current encounter
    1292                  and the source_episode onto target_episode.
    1293
    1294                  @param source_episode The episode the elements are currently linked to.
    1295                  @type target_episode A cEpisode intance.
    1296                  @param target_episode The episode the elements will be relinked to.
    1297                  @type target_episode A cEpisode intance.
    1298                  """
    1299                  if source_episode['pk_episode'] == target_episode['pk_episode']:
    1300                          return True
    1301
    1302                  queries = []
    1303                  cmd = u"""
    1304                          UPDATE clin.clin_root_item
    1305                          SET fk_episode = %(trg)s
    1306                          WHERE
    1307                                  fk_encounter = %(enc)s AND
    1308                                  fk_episode = %(src)s
    1309                          """
    1310                  rows, idx = gmPG2.run_rw_queries(queries = [{
    1311                          'cmd': cmd,
    1312                          'args': {
    1313                                  'trg': target_episode['pk_episode'],
    1314                                  'enc': self.pk_obj,
    1315                                  'src': source_episode['pk_episode']
    1316                          }
    1317                  }])
    1318                  self.refetch_payload()
    1319                  return True
    1320          #--------------------------------------------------------
    1321 -        def same_payload(self, another_object=None):
    1322
    1323                  relevant_fields = [
    1324                          'pk_location',
    1325                          'pk_type',
    1326                          'pk_patient',
    1327                          'reason_for_encounter',
    1328                          'assessment_of_encounter'
    1329                  ]
    1330                  for field in relevant_fields:
    1331                          if self._payload[self._idx[field]] != another_object[field]:
    1332                                  _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
    1333                                  return False
    1334
    1335                  relevant_fields = [
    1336                          'started',
    1337                          'last_affirmed',
    1338                  ]
    1339                  for field in relevant_fields:
    1340                          if self._payload[self._idx[field]] is None:
    1341                                  if another_object[field] is None:
    1342                                          continue
    1343                                  _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
    1344                                  return False
    1345
    1346                          if another_object[field] is None:
    1347                                  return False
    1348
    1349                          # compares at minute granularity
    1350                          if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'):
    1351                                  _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
    1352                                  return False
    1353
    1354                  # compare codes
    1355                  # 1) RFE
    1356                  if another_object['pk_generic_codes_rfe'] is None:
    1357                          if self._payload[self._idx['pk_generic_codes_rfe']] is not None:
    1358                                  return False
    1359                  if another_object['pk_generic_codes_rfe'] is not None:
    1360                          if self._payload[self._idx['pk_generic_codes_rfe']] is None:
    1361                                  return False
    1362                  if (
    1363                          (another_object['pk_generic_codes_rfe'] is None)
    1364                                  and
    1365                          (self._payload[self._idx['pk_generic_codes_rfe']] is None)
    1366                  ) is False:
    1367                          if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]):
    1368                                  return False
    1369                  # 2) AOE
    1370                  if another_object['pk_generic_codes_aoe'] is None:
    1371                          if self._payload[self._idx['pk_generic_codes_aoe']] is not None:
    1372                                  return False
    1373                  if another_object['pk_generic_codes_aoe'] is not None:
    1374                          if self._payload[self._idx['pk_generic_codes_aoe']] is None:
    1375                                  return False
    1376                  if (
    1377                          (another_object['pk_generic_codes_aoe'] is None)
    1378                                  and
    1379                          (self._payload[self._idx['pk_generic_codes_aoe']] is None)
    1380                  ) is False:
    1381                          if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]):
    1382                                  return False
    1383
    1384                  return True
    1385          #--------------------------------------------------------
    1386 -        def has_clinical_data(self):
    1387                  cmd = u"""
    1388  select exists (
    1389          select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s
    1390                  union all
    1391          select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
    1392  )"""
    1393                  args = {
    1394                          'pat': self._payload[self._idx['pk_patient']],
    1395                          'enc': self.pk_obj
    1396                  }
    1397                  rows, idx = gmPG2.run_ro_queries (
    1398                          queries = [{
    1399                                  'cmd': cmd,
    1400                                  'args': args
    1401                          }]
    1402                  )
    1403                  return rows[0][0]
    1404          #--------------------------------------------------------
    1405 -        def has_narrative(self):
    1406                  cmd = u"""
    1407  select exists (
    1408          select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s
    1409  )"""
    1410                  args = {
    1411                          'pat': self._payload[self._idx['pk_patient']],
    1412                          'enc': self.pk_obj
    1413                  }
    1414                  rows, idx = gmPG2.run_ro_queries (
    1415                          queries = [{
    1416                                  'cmd': cmd,
    1417                                  'args': args
    1418                          }]
    1419                  )
    1420                  return rows[0][0]
    1421          #--------------------------------------------------------
    1422 -        def has_soap_narrative(self, soap_cats=None):
    1423                  """soap_cats: <space> = admin category"""
    1424
    1425                  if soap_cats is None:
    1426                          soap_cats = u'soap '
    1427                  else:
    1428                          soap_cats = soap_cats.lower()
    1429
    1430                  cats = []
    1431                  for cat in soap_cats:
    1432                          if cat in u'soapu':
    1433                                  cats.append(cat)
    1434                                  continue
    1435                          if cat == u' ':
    1436                                  cats.append(None)
    1437
    1438                  cmd = u"""
    1439                          SELECT EXISTS (
    1440                                  SELECT 1 FROM clin.clin_narrative
    1441                                  WHERE
    1442                                          fk_encounter = %(enc)s
    1443                                                  AND
    1444                                          soap_cat IN %(cats)s
    1445                                  LIMIT 1
    1446                          )
    1447                  """
    1448                  args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)}
    1449                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}])
    1450                  return rows[0][0]
    1451          #--------------------------------------------------------
    1452 -        def has_documents(self):
    1453                  cmd = u"""
    1454  select exists (
    1455          select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
    1456  )"""
    1457                  args = {
    1458                          'pat': self._payload[self._idx['pk_patient']],
    1459                          'enc': self.pk_obj
    1460                  }
    1461                  rows, idx = gmPG2.run_ro_queries (
    1462                          queries = [{
    1463                                  'cmd': cmd,
    1464                                  'args': args
    1465                          }]
    1466                  )
    1467                  return rows[0][0]
    1468          #--------------------------------------------------------
    1469 -        def get_latest_soap(self, soap_cat=None, episode=None):
    1470
    1471                  if soap_cat is not None:
    1472                          soap_cat = soap_cat.lower()
    1473
    1474                  if episode is None:
    1475                          epi_part = u'fk_episode is null'
    1476                  else:
    1477                          epi_part = u'fk_episode = %(epi)s'
    1478
    1479                  cmd = u"""
    1480  select narrative
    1481  from clin.clin_narrative
    1482  where
    1483          fk_encounter = %%(enc)s
    1484                  and
    1485          soap_cat = %%(cat)s
    1486                  and
    1487          %s
    1488  order by clin_when desc
    1489  limit 1
    1490                  """ % epi_part
    1491
    1492                  args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode}
    1493
    1494                  rows, idx = gmPG2.run_ro_queries (
    1495                          queries = [{
    1496                                  'cmd': cmd,
    1497                                  'args': args
    1498                          }]
    1499                  )
    1500                  if len(rows) == 0:
    1501                          return None
    1502
    1503                  return rows[0][0]
    1504          #--------------------------------------------------------
    1505 -        def get_episodes(self, exclude=None):
    1506                  cmd = u"""
    1507  SELECT * FROM clin.v_pat_episodes
    1508  WHERE
    1509          pk_episode IN (
    1510
    1511                  SELECT DISTINCT fk_episode
    1512                  FROM clin.clin_root_item
    1513                  WHERE fk_encounter = %%(enc)s
    1514
    1515                          UNION
    1516
    1517                  SELECT DISTINCT fk_episode
    1518                  FROM blobs.doc_med
    1519                  WHERE fk_encounter = %%(enc)s
    1520          )
    1521          %s"""
    1522                  args = {'enc': self.pk_obj}
    1523                  if exclude is not None:
    1524                          cmd = cmd % u'AND pk_episode NOT IN %(excluded)s'
    1525                          args['excluded'] = tuple(exclude)
    1526                  else:
    1527                          cmd = cmd % u''
    1528
    1529                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1530
    1531                  return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'})  for r in rows ]
    1532          #--------------------------------------------------------
    1533 -        def add_code(self, pk_code=None, field=None):
    1534                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
    1535                  if field == u'rfe':
    1536                          cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
    1537                  elif field == u'aoe':
    1538                          cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
    1539                  else:
    1540                          raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
    1541                  args = {
    1542                          'item': self._payload[self._idx['pk_encounter']],
    1543                          'code': pk_code
    1544                  }
    1545                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    1546                  return True
    1547          #--------------------------------------------------------
    1548 -        def remove_code(self, pk_code=None, field=None):
    1549                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
    1550                  if field == u'rfe':
    1551                          cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
    1552                  elif field == u'aoe':
    1553                          cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
    1554                  else:
    1555                          raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
    1556                  args = {
    1557                          'item': self._payload[self._idx['pk_encounter']],
    1558                          'code': pk_code
    1559                  }
    1560                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    1561                  return True
    1562          #--------------------------------------------------------
    1563 -        def format_soap(self, episodes=None, left_margin=0, soap_cats='soapu', emr=None, issues=None):
    1564
    1565                  lines = []
    1566                  for soap_cat in soap_cats:
    1567                          soap_cat_narratives = emr.get_clin_narrative (
    1568                                  episodes = episodes,
    1569                                  issues = issues,
    1570                                  encounters = [self._payload[self._idx['pk_encounter']]],
    1571                                  soap_cats = [soap_cat]
    1572                          )
    1573                          if soap_cat_narratives is None:
    1574                                  continue
    1575                          if len(soap_cat_narratives) == 0:
    1576                                  continue
    1577
    1578                          lines.append(u'%s%s %s %s' % (
    1579                                  gmTools.u_box_top_left_arc,
    1580                                  gmTools.u_box_horiz_single,
    1581                                  gmClinNarrative.soap_cat2l10n_str[soap_cat],
    1582                                  gmTools.u_box_horiz_single * 5
    1583                          ))
    1584                          for soap_entry in soap_cat_narratives:
    1585                                  txt = gmTools.wrap (
    1586                                          text = soap_entry['narrative'],
    1587                                          width = 75,
    1588                                          initial_indent = u'',
    1589                                          subsequent_indent = (u' ' * left_margin)
    1590                                  )
    1591                                  lines.append(txt)
    1592                                  when = gmDateTime.pydt_strftime (
    1593                                          soap_entry['date'],
    1594                                          format = '%Y-%m-%d %H:%M',
    1595                                          accuracy = gmDateTime.acc_minutes
    1596                                  )
    1597                                  txt = u'%s%s %.8s, %s %s' % (
    1598                                          u' ' * 40,
    1599                                          gmTools.u_box_horiz_light_heavy,
    1600                                          soap_entry['provider'],
    1601                                          when,
    1602                                          gmTools.u_box_horiz_heavy_light
    1603                                  )
    1604                                  lines.append(txt)
    1605                                  lines.append('')
    1606
    1607                  return lines
    1608          #--------------------------------------------------------
    1609 -        def format_latex(self, date_format=None, soap_cats=None, soap_order=None):
    1610
    1611                  nothing2format = (
    1612                          (self._payload[self._idx['reason_for_encounter']] is None)
    1613                                  and
    1614                          (self._payload[self._idx['assessment_of_encounter']] is None)
    1615                                  and
    1616                          (self.has_soap_narrative(soap_cats = u'soapu') is False)
    1617                  )
    1618                  if nothing2format:
    1619                          return u''
    1620
    1621                  if date_format is None:
    1622                          date_format = '%A, %B %d %Y'
    1623
    1624                  tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % (
    1625                          gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]),
    1626                          self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()),
    1627                          self._payload[self._idx['started']].strftime('%H:%M'),
    1628                          self._payload[self._idx['last_affirmed']].strftime('%H:%M')
    1629                  )
    1630                  tex += u'\\hline \\tabularnewline \n'
    1631
    1632                  for epi in self.get_episodes():
    1633                          soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order)
    1634                          if len(soaps) == 0:
    1635                                  continue
    1636                          tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % (
    1637                                  gmTools.tex_escape_string(_('Problem')),
    1638                                  gmTools.tex_escape_string(epi['description']),
    1639                                  gmTools.coalesce (
    1640                                          initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']),
    1641                                          instead = u'',
    1642                                          template_initial = u' {\\footnotesize [%s]}',
    1643                                          none_equivalents = [None, u'']
    1644                                  )
    1645                          )
    1646                          if epi['pk_health_issue'] is not None:
    1647                                  tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % (
    1648                                          gmTools.tex_escape_string(_('Health issue')),
    1649                                          gmTools.tex_escape_string(epi['health_issue']),
    1650                                          gmTools.coalesce (
    1651                                                  initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']),
    1652                                                  instead = u'',
    1653                                                  template_initial = u' {\\footnotesize [%s]}',
    1654                                                  none_equivalents = [None, u'']
    1655                                          )
    1656                                  )
    1657                          for soap in soaps:
    1658                                  tex += u'{\\small %s} & {\\small %s} \\tabularnewline \n' % (
    1659                                          gmClinNarrative.soap_cat2l10n[soap['soap_cat']],
    1660                                          gmTools.tex_escape_string(soap['narrative'].strip(u'\n'))
    1661                                  )
    1662                          tex += u' & \\tabularnewline \n'
    1663
    1664                  if self._payload[self._idx['reason_for_encounter']] is not None:
    1665                          tex += u'%s & %s \\tabularnewline \n' % (
    1666                                  gmTools.tex_escape_string(_('RFE')),
    1667                                  gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']])
    1668                          )
    1669                  if self._payload[self._idx['assessment_of_encounter']] is not None:
    1670                          tex += u'%s & %s \\tabularnewline \n' % (
    1671                                  gmTools.tex_escape_string(_('AOE')),
    1672                                  gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']])
    1673                          )
    1674
    1675                  tex += u'\\hline \\tabularnewline \n'
    1676                  tex += u' & \\tabularnewline \n'
    1677
    1678                  return tex
    1679          #--------------------------------------------------------
    1680 -        def format(self, episodes=None, with_soap=False, left_margin=0, patient=None, issues=None, with_docs=True, with_tests=True, fancy_header=True, with_vaccinations=True, with_co_encountlet_hints=False, with_rfe_aoe=False, with_family_history=True):
    1681                  """Format an encounter.
    1682
    1683                  with_co_encountlet_hints:
    1684                          - whether to include which *other* episodes were discussed during this encounter
    1685                          - (only makes sense if episodes != None)
    1686                  """
    1687                  lines = []
    1688
    1689                  if fancy_header:
    1690                          lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % (
    1691                                  u' ' * left_margin,
    1692                                  self._payload[self._idx['l10n_type']],
    1693                                  self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'),
    1694                                  self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'),
    1695                                  self._payload[self._idx['source_time_zone']],
    1696                                  gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB'),
    1697                                  self._payload[self._idx['pk_encounter']]
    1698                          ))
    1699
    1700                          lines.append(_('  your time: %s - %s  (@%s = %s%s)\n') % (
    1701                                  self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'),
    1702                                  self._payload[self._idx['last_affirmed']].strftime('%H:%M'),
    1703                                  gmDateTime.current_local_iso_numeric_timezone_string,
    1704                                  gmTools.bool2subst (
    1705                                          gmDateTime.dst_currently_in_effect,
    1706                                          gmDateTime.py_dst_timezone_name,
    1707                                          gmDateTime.py_timezone_name
    1708                                  ),
    1709                                  gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'')
    1710                          ))
    1711
    1712                          if self._payload[self._idx['reason_for_encounter']] is not None:
    1713                                  lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']]))
    1714                                  codes = self.generic_codes_rfe
    1715                                  for c in codes:
    1716                                          lines.append(u' %s: %s (%s - %s)' % (
    1717                                                  c['code'],
    1718                                                  c['term'],
    1719                                                  c['name_short'],
    1720                                                  c['version']
    1721                                          ))
    1722                                  if len(codes) > 0:
    1723                                          lines.append(u'')
    1724
    1725                          if self._payload[self._idx['assessment_of_encounter']] is not None:
    1726                                  lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']]))
    1727                                  codes = self.generic_codes_aoe
    1728                                  for c in codes:
    1729                                          lines.append(u' %s: %s (%s - %s)' % (
    1730                                                  c['code'],
    1731                                                  c['term'],
    1732                                                  c['name_short'],
    1733                                                  c['version']
    1734                                          ))
    1735                                  if len(codes) > 0:
    1736                                          lines.append(u'')
    1737                                  del codes
    1738
    1739                  else:
    1740                          now = gmDateTime.pydt_now_here()
    1741                          if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'):
    1742                                  start = u'%s %s' % (
    1743                                          _('today'),
    1744                                          self._payload[self._idx['started_original_tz']].strftime('%H:%M')
    1745                                  )
    1746                          else:
    1747                                  start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M')
    1748                          lines.append(u'%s%s: %s - %s%s' % (
    1749                                  u' ' * left_margin,
    1750                                  self._payload[self._idx['l10n_type']],
    1751                                  start,
    1752                                  self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'),
    1753                                  gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB')
    1754                          ))
    1755                          if with_rfe_aoe:
    1756                                  if self._payload[self._idx['reason_for_encounter']] is not None:
    1757                                          lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']]))
    1758                                  codes = self.generic_codes_rfe
    1759                                  for c in codes:
    1760                                          lines.append(u' %s: %s (%s - %s)' % (
    1761                                                  c['code'],
    1762                                                  c['term'],
    1763                                                  c['name_short'],
    1764                                                  c['version']
    1765                                          ))
    1766                                  if len(codes) > 0:
    1767                                          lines.append(u'')
    1768                                  if self._payload[self._idx['assessment_of_encounter']] is not None:
    1769                                          lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']]))
    1770                                  codes = self.generic_codes_aoe
    1771                                  if len(codes) > 0:
    1772                                          lines.append(u'')
    1773                                  for c in codes:
    1774                                          lines.append(u' %s: %s (%s - %s)' % (
    1775                                                  c['code'],
    1776                                                  c['term'],
    1777                                                  c['name_short'],
    1778                                                  c['version']
    1779                                          ))
    1780                                  if len(codes) > 0:
    1781                                          lines.append(u'')
    1782                                  del codes
    1783
    1784                  if with_soap:
    1785                          lines.append(u'')
    1786
    1787                          if patient.ID != self._payload[self._idx['pk_patient']]:
    1788                                  msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % (
    1789                                          patient.ID,
    1790                                          self._payload[self._idx['pk_encounter']],
    1791                                          self._payload[self._idx['pk_patient']]
    1792                                  )
    1793                                  raise ValueError(msg)
    1794
    1795                          emr = patient.get_emr()
    1796
    1797                          lines.extend(self.format_soap (
    1798                                  episodes = episodes,
    1799                                  left_margin = left_margin,
    1800                                  soap_cats = 'soapu',
    1801                                  emr = emr,
    1802                                  issues = issues
    1803                          ))
    1804
    1805  #               # family history
    1806  #               if with_family_history:
    1807  #                       if episodes is not None:
    1808  #                               fhx = emr.get_family_history(episodes = episodes)
    1809  #                               if len(fhx) > 0:
    1810  #                                       lines.append(u'')
    1811  #                                       lines.append(_('Family History: %s') % len(fhx))
    1812  #                               for f in fhx:
    1813  #                                       lines.append(f.format (
    1814  #                                               left_margin = (left_margin + 1),
    1815  #                                               include_episode = False,
    1816  #                                               include_comment = True
    1817  #                                       ))
    1818  #                               del fhx
    1819
    1820                  # test results
    1821                  if with_tests:
    1822                          emr = patient.get_emr()
    1823                          tests = emr.get_test_results_by_date (
    1824                                  episodes = episodes,
    1825                                  encounter = self._payload[self._idx['pk_encounter']]
    1826                          )
    1827                          if len(tests) > 0:
    1828                                  lines.append('')
    1829                                  lines.append(_('Measurements and Results:'))
    1830
    1831                          for t in tests:
    1832                                  lines.extend(t.format())
    1833
    1834                          del tests
    1835
    1836                  # vaccinations
    1837                  if with_vaccinations:
    1838                          emr = patient.get_emr()
    1839                          vaccs = emr.get_vaccinations (
    1840                                  episodes = episodes,
    1841                                  encounters = [ self._payload[self._idx['pk_encounter']] ],
    1842                                  order_by = u'date_given DESC, vaccine'
    1843                          )
    1844
    1845                          if len(vaccs) > 0:
    1846                                  lines.append(u'')
    1847                                  lines.append(_('Vaccinations:'))
    1848
    1849                          for vacc in vaccs:
    1850                                  lines.extend(vacc.format (
    1851                                          with_indications = True,
    1852                                          with_comment = True,
    1853                                          with_reaction = True,
    1854                                          date_format = '%Y-%m-%d'
    1855                                  ))
    1856                          del vaccs
    1857
    1858                  # documents
    1859                  if with_docs:
    1860                          doc_folder = patient.get_document_folder()
    1861                          docs = doc_folder.get_documents (
    1862                                  episodes = episodes,
    1863                                  encounter = self._payload[self._idx['pk_encounter']]
    1864                          )
    1865
    1866                          if len(docs) > 0:
    1867                                  lines.append(u'')
    1868                                  lines.append(_('Documents:'))
    1869
    1870                          for d in docs:
    1871                                  lines.append(u' %s %s:%s%s' % (
    1872                                          d['clin_when'].strftime('%Y-%m-%d'),
    1873                                          d['l10n_type'],
    1874                                          gmTools.coalesce(d['comment'], u'', u' "%s"'),
    1875                                          gmTools.coalesce(d['ext_ref'], u'', u' (%s)')
    1876                                  ))
    1877
    1878                          del docs
    1879
    1880                  # co-encountlets
    1881                  if with_co_encountlet_hints:
    1882                          if episodes is not None:
    1883                                  other_epis = self.get_episodes(exclude = episodes)
    1884                                  if len(other_epis) > 0:
    1885                                          lines.append(u'')
    1886                                          lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis))
    1887                                  for epi in other_epis:
    1888                                          lines.append(u' %s%s%s%s' % (
    1889                                                  gmTools.u_left_double_angle_quote,
    1890                                                  epi['description'],
    1891                                                  gmTools.u_right_double_angle_quote,
    1892                                                  gmTools.coalesce(epi['health_issue'], u'', u' (%s)')
    1893                                          ))
    1894
    1895                  eol_w_margin = u'\n%s' % (u' ' * left_margin)
    1896                  return u'%s\n' % eol_w_margin.join(lines)
    1897          #--------------------------------------------------------
    1898          # properties
    1899          #--------------------------------------------------------
    1900 -        def _get_generic_codes_rfe(self):
    1901                  if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0:
    1902                          return []
    1903
    1904                  cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
    1905                  args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])}
    1906                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1907                  return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
    1908
    1909 -        def _set_generic_codes_rfe(self, pk_codes):
    1910                  queries = []
    1911                  # remove all codes
    1912                  if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0:
    1913                          queries.append ({
    1914                                  'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
    1915                                  'args': {
    1916                                          'enc': self._payload[self._idx['pk_encounter']],
    1917                                          'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']])
    1918                                  }
    1919                          })
    1920                  # add new codes
    1921                  for pk_code in pk_codes:
    1922                          queries.append ({
    1923                                  'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
    1924                                  'args': {
    1925                                          'enc': self._payload[self._idx['pk_encounter']],
    1926                                          'pk_code': pk_code
    1927                                  }
    1928                          })
    1929                  if len(queries) == 0:
    1930                          return
    1931                  # run it all in one transaction
    1932                  rows, idx = gmPG2.run_rw_queries(queries = queries)
    1933                  self.refetch_payload()
    1934                  return
    1935
    1936          generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe)
    1937          #--------------------------------------------------------
    1938 -        def _get_generic_codes_aoe(self):
    1939                  if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0:
    1940                          return []
    1941
    1942                  cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
    1943                  args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])}
    1944                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1945                  return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
    1946
    1947 -        def _set_generic_codes_aoe(self, pk_codes):
    1948                  queries = []
    1949                  # remove all codes
    1950                  if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0:
    1951                          queries.append ({
    1952                                  'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
    1953                                  'args': {
    1954                                          'enc': self._payload[self._idx['pk_encounter']],
    1955                                          'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']])
    1956                                  }
    1957                          })
    1958                  # add new codes
    1959                  for pk_code in pk_codes:
    1960                          queries.append ({
    1961                                  'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
    1962                                  'args': {
    1963                                          'enc': self._payload[self._idx['pk_encounter']],
    1964                                          'pk_code': pk_code
    1965                                  }
    1966                          })
    1967                  if len(queries) == 0:
    1968                          return
    1969                  # run it all in one transaction
    1970                  rows, idx = gmPG2.run_rw_queries(queries = queries)
    1971                  self.refetch_payload()
    1972                  return
    1973
    1974          generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
    1975  #-----------------------------------------------------------
    1976 -def create_encounter(fk_patient=None, fk_location=-1, enc_type=None):
    1977          """Creates a new encounter for a patient.
    1978
    1979          fk_patient - patient PK
    1980          fk_location - encounter location
    1981          enc_type - type of encounter
    1982
    1983          FIXME: we don't deal with location yet
    1984          """
    1985          if enc_type is None:
    1986                  enc_type = u'in surgery'
    1987          # insert new encounter
    1988          queries = []
    1989          try:
    1990                  enc_type = int(enc_type)
    1991                  cmd = u"""
    1992                          INSERT INTO clin.encounter (
    1993                                  fk_patient, fk_location, fk_type
    1994                          ) VALUES (
    1995                                  %(pat)s,
    1996                                  -1,
    1997                                  %(typ)s
    1998                          ) RETURNING pk"""
    1999          except ValueError:
    2000                  enc_type = enc_type
    2001                  cmd = u"""
    2002                          insert into clin.encounter (
    2003                                  fk_patient, fk_location, fk_type
    2004                          ) values (
    2005                                  %(pat)s,
    2006                                  -1,
    2007                                  coalesce (
    2008                                          (select pk from clin.encounter_type where description = %(typ)s),
    2009                                          -- pick the first available
    2010                                          (select pk from clin.encounter_type limit 1)
    2011                                  )
    2012                          ) RETURNING pk"""
    2013          args = {'pat': fk_patient, 'typ': enc_type}
    2014          queries.append({'cmd': cmd, 'args': args})
    2015          rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False)
    2016          encounter = cEncounter(aPK_obj = rows[0]['pk'])
    2017
    2018          return encounter
    2019  #-----------------------------------------------------------
    2020 -def update_encounter_type(description=None, l10n_description=None):
    2021
    2022          rows, idx = gmPG2.run_rw_queries(
    2023                  queries = [{
    2024                  'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)",
    2025                  'args': {'desc': description, 'l10n_desc': l10n_description}
    2026                  }],
    2027                  return_data = True
    2028          )
    2029
    2030          success = rows[0][0]
    2031          if not success:
    2032                  _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description)
    2033
    2034          return {'description': description, 'l10n_description': l10n_description}
    2035  #-----------------------------------------------------------
    2036 -def create_encounter_type(description=None, l10n_description=None):
    2037          """This will attempt to create a NEW encounter type."""
    2038
    2039          # need a system name, so derive one if necessary
    2040          if description is None:
    2041                  description = l10n_description
    2042
    2043          args = {
    2044                  'desc': description,
    2045                  'l10n_desc': l10n_description
    2046          }
    2047
    2048          _log.debug('creating encounter type: %s, %s', description, l10n_description)
    2049
    2050          # does it exist already ?
    2051          cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s"
    2052          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    2053
    2054          # yes
    2055          if len(rows) > 0:
    2056                  # both system and l10n name are the same so all is well
    2057                  if (rows[0][0] == description) and (rows[0][1] == l10n_description):
    2058                          _log.info('encounter type [%s] already exists with the proper translation')
    2059                          return {'description': description, 'l10n_description': l10n_description}
    2060
    2061                  # or maybe there just wasn't a translation to
    2062                  # the current language for this type yet ?
    2063                  cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())"
    2064                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    2065
    2066                  # there was, so fail
    2067                  if rows[0][0]:
    2068                          _log.error('encounter type [%s] already exists but with another translation')
    2069                          return None
    2070
    2071                  # else set it
    2072                  cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)"
    2073                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    2074                  return {'description': description, 'l10n_description': l10n_description}
    2075
    2076          # no
    2077          queries = [
    2078                  {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args},
    2079                  {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args}
    2080          ]
    2081          rows, idx = gmPG2.run_rw_queries(queries = queries)
    2082
    2083          return {'description': description, 'l10n_description': l10n_description}
    2084  #-----------------------------------------------------------
    2085 -def get_encounter_types():
    2086          cmd = u"""
    2087                  SELECT
    2088                          _(description) AS l10n_description,
    2089                          description
    2090                  FROM
    2091                          clin.encounter_type
    2092                  ORDER BY
    2093                          l10n_description
    2094          """
    2095          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
    2096          return rows
    2097  #-----------------------------------------------------------
    2098 -def get_encounter_type(description=None):
    2099          cmd = u"SELECT * from clin.encounter_type where description = %s"
    2100          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}])
    2101          return rows
    2102  #-----------------------------------------------------------
    2103 -def delete_encounter_type(description=None):
    2104          cmd = u"delete from clin.encounter_type where description = %(desc)s"
    2105          args = {'desc': description}
    2106          try:
    2107                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    2108          except gmPG2.dbapi.IntegrityError, e:
    2109                  if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION:
    2110                          return False
    2111                  raise
    2112
    2113          return True
    2114  #============================================================
    2115 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
    2116          """Represents one problem.
    2117
    2118          problems are the aggregation of
    2119                  .clinically_relevant=True issues and
    2120                  .is_open=True episodes
    2121          """
    2122          _cmd_fetch_payload = u''                                        # will get programmatically defined in __init__
    2123          _cmds_store_payload = [u"select 1"]
    2124          _updatable_fields = []
    2125
    2126          #--------------------------------------------------------
    2127 -        def __init__(self, aPK_obj=None, try_potential_problems=False):
    2128                  """Initialize.
    2129
    2130                  aPK_obj must contain the keys
    2131                          pk_patient
    2132                          pk_episode
    2133                          pk_health_issue
    2134                  """
    2135                  if aPK_obj is None:
    2136                          raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj)
    2137
    2138                  # As problems are rows from a view of different emr struct items,
    2139                  # the PK can't be a single field and, as some of the values of the
    2140                  # composed PK may be None, they must be queried using 'is null',
    2141                  # so we must programmatically construct the SQL query
    2142                  where_parts = []
    2143                  pk = {}
    2144                  for col_name in aPK_obj.keys():
    2145                          val = aPK_obj[col_name]
    2146                          if val is None:
    2147                                  where_parts.append('%s IS NULL' % col_name)
    2148                          else:
    2149                                  where_parts.append('%s = %%(%s)s' % (col_name, col_name))
    2150                                  pk[col_name] = val
    2151
    2152                  # try to instantiate from true problem view
    2153                  cProblem._cmd_fetch_payload = u"""
    2154                          SELECT *, False as is_potential_problem
    2155                          FROM clin.v_problem_list
    2156                          WHERE %s""" % u' AND '.join(where_parts)
    2157
    2158                  try:
    2159                          gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
    2160                          return
    2161                  except gmExceptions.ConstructorError:
    2162                          _log.exception('actual problem not found, trying "potential" problems')
    2163                          if try_potential_problems is False:
    2164                                  raise
    2165
    2166                  # try to instantiate from potential-problems view
    2167                  cProblem._cmd_fetch_payload = u"""
    2168                          SELECT *, True as is_potential_problem
    2169                          FROM clin.v_potential_problem_list
    2170                          WHERE %s""" % u' AND '.join(where_parts)
    2171                  gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
    2172          #--------------------------------------------------------
    2173 -        def get_as_episode(self):
    2174                  """
    2175                  Retrieve the cEpisode instance equivalent to this problem.
    2176                  The problem's type attribute must be 'episode'
    2177                  """
    2178                  if self._payload[self._idx['type']] != 'episode':
    2179                          _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
    2180                          return None
    2181                  return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
    2182          #--------------------------------------------------------
    2183 -        def get_as_health_issue(self):
    2184                  """
    2185                  Retrieve the cHealthIssue instance equivalent to this problem.
    2186                  The problem's type attribute must be 'issue'
    2187                  """
    2188                  if self._payload[self._idx['type']] != 'issue':
    2189                          _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
    2190                          return None
    2191                  return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
    2192          #--------------------------------------------------------
    2193 -        def get_visual_progress_notes(self, encounter_id=None):
    2194
    2195                  if self._payload[self._idx['type']] == u'issue':
    2196                          episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ]
    2197                          #xxxxxxxxxxxxx
    2198
    2199                  emr = patient.get_emr()
    2200
    2201                  doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID)
    2202                  return doc_folder.get_visual_progress_notes (
    2203                          health_issue = self._payload[self._idx['pk_health_issue']],
    2204                          episode = self._payload[self._idx['pk_episode']]
    2205                  )
    2206          #--------------------------------------------------------
    2207          # properties
    2208          #--------------------------------------------------------
    2209          # doubles as 'diagnostic_certainty_description' getter:
    2210 -        def get_diagnostic_certainty_description(self):
    2211                  return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
    2212
    2213          diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
    2214          #--------------------------------------------------------
    2215 -        def _get_generic_codes(self):
    2216                  if self._payload[self._idx['type']] == u'issue':
    2217                          cmd = u"""
    2218                                  SELECT * FROM clin.v_linked_codes WHERE
    2219                                          item_table = 'clin.lnk_code2h_issue'::regclass
    2220                                                  AND
    2221                                          pk_item = %(item)s
    2222                          """
    2223                          args = {'item': self._payload[self._idx['pk_health_issue']]}
    2224                  else:
    2225                          cmd = u"""
    2226                                  SELECT * FROM clin.v_linked_codes WHERE
    2227                                          item_table = 'clin.lnk_code2episode'::regclass
    2228                                                  AND
    2229                                          pk_item = %(item)s
    2230                          """
    2231                          args = {'item': self._payload[self._idx['pk_episode']]}
    2232
    2233                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2234                  return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
    2235
    2236          generic_codes = property(_get_generic_codes, lambda x:x)
    2237  #-----------------------------------------------------------
    2238 -def problem2episode(problem=None):
    2239          """Retrieve the cEpisode instance equivalent to the given problem.
    2240
    2241          The problem's type attribute must be 'episode'
    2242
    2243          @param problem: The problem to retrieve its related episode for
    2244          @type problem: A gmEMRStructItems.cProblem instance
    2245          """
    2246          if isinstance(problem, cEpisode):
    2247                  return problem
    2248
    2249          exc = TypeError('cannot convert [%s] to episode' % problem)
    2250
    2251          if not isinstance(problem, cProblem):
    2252                  raise exc
    2253
    2254          if problem['type'] != 'episode':
    2255                  raise exc
    2256
    2257          return cEpisode(aPK_obj = problem['pk_episode'])
    2258  #-----------------------------------------------------------
    2259 -def problem2issue(problem=None):
    2260          """Retrieve the cIssue instance equivalent to the given problem.
    2261
    2262          The problem's type attribute must be 'issue'.
    2263
    2264          @param problem: The problem to retrieve the corresponding issue for
    2265          @type problem: A gmEMRStructItems.cProblem instance
    2266          """
    2267          if isinstance(problem, cHealthIssue):
    2268                  return problem
    2269
    2270          exc = TypeError('cannot convert [%s] to health issue' % problem)
    2271
    2272          if not isinstance(problem, cProblem):
    2273                  raise exc
    2274
    2275          if  problem['type'] != 'issue':
    2276                  raise exc
    2277
    2278          return cHealthIssue(aPK_obj = problem['pk_health_issue'])
    2279  #-----------------------------------------------------------
    2280 -def reclass_problem(self, problem=None):
    2281          """Transform given problem into either episode or health issue instance.
    2282          """
    2283          if isinstance(problem, (cEpisode, cHealthIssue)):
    2284                  return problem
    2285
    2286          exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem))
    2287
    2288          if not isinstance(problem, cProblem):
    2289                  _log.debug(u'%s' % problem)
    2290                  raise exc
    2291
    2292          if problem['type'] == 'episode':
    2293                  return cEpisode(aPK_obj = problem['pk_episode'])
    2294
    2295          if problem['type'] == 'issue':
    2296                  return cHealthIssue(aPK_obj = problem['pk_health_issue'])
    2297
    2298          raise exc
    2299  #============================================================
    2300 -class cHospitalStay(gmBusinessDBObject.cBusinessDBObject):
    2301
    2302          _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s"
    2303          _cmds_store_payload = [
    2304                  u"""update clin.hospital_stay set
    2305                                  clin_when = %(admission)s,
    2306                                  discharge = %(discharge)s,
    2307                                  narrative = gm.nullify_empty_string(%(hospital)s),
    2308                                  fk_episode = %(pk_episode)s,
    2309                                  fk_encounter = %(pk_encounter)s
    2310                          where
    2311                                  pk = %(pk_hospital_stay)s and
    2312                                  xmin = %(xmin_hospital_stay)s""",
    2313                  u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s"""
    2314          ]
    2315          _updatable_fields = [
    2316                  'admission',
    2317                  'discharge',
    2318                  'hospital',
    2319                  'pk_episode',
    2320                  'pk_encounter'
    2321          ]
    2322          #-------------------------------------------------------
    2323 -        def format(self, left_margin=0, include_procedures=False, include_docs=False):
    2324
    2325                  if self._payload[self._idx['discharge']] is not None:
    2326                          discharge = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding())
    2327                  else:
    2328                          discharge = u''
    2329
    2330                  line = u'%s%s%s%s: %s%s%s' % (
    2331                          u' ' * left_margin,
    2332                          self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()),
    2333                          discharge,
    2334                          gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'),
    2335                          gmTools.u_left_double_angle_quote,
    2336                          self._payload[self._idx['episode']],
    2337                          gmTools.u_right_double_angle_quote
    2338                  )
    2339
    2340                  return line
    2341  #-----------------------------------------------------------
    2342 -def get_latest_patient_hospital_stay(patient=None):
    2343          queries = [{
    2344                  # this assumes non-overarching stays
    2345                  'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1',
    2346                  'args': {'pat': patient}
    2347          }]
    2348          rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
    2349          if len(rows) == 0:
    2350                  return None
    2351          return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
    2352  #-----------------------------------------------------------
    2353 -def get_patient_hospital_stays(patient=None, ongoing_only=False):
    2354          args = {'pat': patient}
    2355          if ongoing_only:
    2356                  cmd = u"""
    2357                          SELECT *
    2358                          FROM clin.v_pat_hospital_stays
    2359                          WHERE
    2360                                  pk_patient = %(pat)s
    2361                                          AND
    2362                                  discharge is NULL
    2363                          ORDER BY admission"""
    2364          else:
    2365                  cmd = u"""
    2366                          SELECT *
    2367                          FROM clin.v_pat_hospital_stays
    2368                          WHERE pk_patient = %(pat)s
    2369                          ORDER BY admission"""
    2370
    2371          queries = [{'cmd': cmd, 'args': args}]
    2372          rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
    2373
    2374          return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'})  for r in rows ]
    2375  #-----------------------------------------------------------
    2376 -def create_hospital_stay(encounter=None, episode=None):
    2377
    2378          queries = [{
    2379                   'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk',
    2380                   'args': {'enc': encounter, 'epi': episode}
    2381          }]
    2382          rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
    2383
    2384          return cHospitalStay(aPK_obj = rows[0][0])
    2385  #-----------------------------------------------------------
    2386 -def delete_hospital_stay(stay=None):
    2387          cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s'
    2388          args = {'pk': stay}
    2389          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    2390          return True
    2391  #============================================================
    2392 -class cPerformedProcedure(gmBusinessDBObject.cBusinessDBObject):
    2393
    2394          _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s"
    2395          _cmds_store_payload = [
    2396                  u"""UPDATE clin.procedure SET
    2397                                  soap_cat = 'p',
    2398                                  clin_when = %(clin_when)s,
    2399                                  clin_end = %(clin_end)s,
    2400                                  is_ongoing = %(is_ongoing)s,
    2401                                  clin_where = NULLIF (
    2402                                          COALESCE (
    2403                                                  %(pk_hospital_stay)s::TEXT,
    2404                                                  gm.nullify_empty_string(%(clin_where)s)
    2405                                          ),
    2406                                          %(pk_hospital_stay)s::TEXT
    2407                                  ),
    2408                                  narrative = gm.nullify_empty_string(%(performed_procedure)s),
    2409                                  fk_hospital_stay = %(pk_hospital_stay)s,
    2410                                  fk_episode = %(pk_episode)s,
    2411                                  fk_encounter = %(pk_encounter)s
    2412                          WHERE
    2413                                  pk = %(pk_procedure)s AND
    2414                                  xmin = %(xmin_procedure)s
    2415                          RETURNING xmin as xmin_procedure"""
    2416          ]
    2417          _updatable_fields = [
    2418                  'clin_when',
    2419                  'clin_end',
    2420                  'is_ongoing',
    2421                  'clin_where',
    2422                  'performed_procedure',
    2423                  'pk_hospital_stay',
    2424                  'pk_episode',
    2425                  'pk_encounter'
    2426          ]
    2427          #-------------------------------------------------------
    2428 -        def __setitem__(self, attribute, value):
    2429
    2430                  if (attribute == 'pk_hospital_stay') and (value is not None):
    2431                          gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None)
    2432
    2433                  if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''):
    2434                          gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None)
    2435
    2436                  gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
    2437          #-------------------------------------------------------
    2438 -        def format(self, left_margin=0, include_episode=True, include_codes=False):
    2439
    2440                  if self._payload[self._idx['is_ongoing']]:
    2441                          end = _(' (ongoing)')
    2442                  else:
    2443                          end = self._payload[self._idx['clin_end']]
    2444                          if end is None:
    2445                                  end = u''
    2446                          else:
    2447                                  end = u' - %s' % end.strftime('%Y %b %d').decode(gmI18N.get_encoding())
    2448
    2449                  line = u'%s%s%s (%s): %s' % (
    2450                          (u' ' * left_margin),
    2451                          self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()),
    2452                          end,
    2453                          self._payload[self._idx['clin_where']],
    2454                          self._payload[self._idx['performed_procedure']]
    2455                  )
    2456                  if include_episode:
    2457                          line = u'%s (%s)' % (line, self._payload[self._idx['episode']])
    2458
    2459                  if include_codes:
    2460                          codes = self.generic_codes
    2461                          if len(codes) > 0:
    2462                                  line += u'\n'
    2463                          for c in codes:
    2464                                  line += u'%s  %s: %s (%s - %s)\n' % (
    2465                                          (u' ' * left_margin),
    2466                                          c['code'],
    2467                                          c['term'],
    2468                                          c['name_short'],
    2469                                          c['version']
    2470                                  )
    2471                          del codes
    2472
    2473                  return line
    2474          #--------------------------------------------------------
    2475 -        def add_code(self, pk_code=None):
    2476                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
    2477                  cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)"
    2478                  args = {
    2479                          'issue': self._payload[self._idx['pk_procedure']],
    2480                          'code': pk_code
    2481                  }
    2482                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    2483                  return True
    2484          #--------------------------------------------------------
    2485 -        def remove_code(self, pk_code=None):
    2486                  """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
    2487                  cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s"
    2488                  args = {
    2489                          'issue': self._payload[self._idx['pk_procedure']],
    2490                          'code': pk_code
    2491                  }
    2492                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    2493                  return True
    2494          #--------------------------------------------------------
    2495          # properties
    2496          #--------------------------------------------------------
    2497 -        def _get_generic_codes(self):
    2498                  if len(self._payload[self._idx['pk_generic_codes']]) == 0:
    2499                          return []
    2500
    2501                  cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
    2502                  args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
    2503                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2504                  return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
    2505
    2506 -        def _set_generic_codes(self, pk_codes):
    2507                  queries = []
    2508                  # remove all codes
    2509                  if len(self._payload[self._idx['pk_generic_codes']]) > 0:
    2510                          queries.append ({
    2511                                  'cmd': u'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s',
    2512                                  'args': {
    2513                                          'proc': self._payload[self._idx['pk_procedure']],
    2514                                          'codes': tuple(self._payload[self._idx['pk_generic_codes']])
    2515                                  }
    2516                          })
    2517                  # add new codes
    2518                  for pk_code in pk_codes:
    2519                          queries.append ({
    2520                                  'cmd': u'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)',
    2521                                  'args': {
    2522                                          'proc': self._payload[self._idx['pk_procedure']],
    2523                                          'pk_code': pk_code
    2524                                  }
    2525                          })
    2526                  if len(queries) == 0:
    2527                          return
    2528                  # run it all in one transaction
    2529                  rows, idx = gmPG2.run_rw_queries(queries = queries)
    2530                  return
    2531
    2532          generic_codes = property(_get_generic_codes, _set_generic_codes)
    2533  #-----------------------------------------------------------
    2534 -def get_performed_procedures(patient=None):
    2535
    2536          queries = [
    2537                  {
    2538                  'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when',
    2539                  'args': {'pat': patient}
    2540                  }
    2541          ]
    2542
    2543          rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
    2544
    2545          return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'})  for r in rows ]
    2546  #-----------------------------------------------------------
    2547 -def get_latest_performed_procedure(patient=None):
    2548          queries = [
    2549                  {
    2550                  'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when DESC LIMIT 1',
    2551                  'args': {'pat': patient}
    2552                  }
    2553          ]
    2554          rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
    2555          if len(rows) == 0:
    2556                  return None
    2557          return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})
    2558  #-----------------------------------------------------------
    2559 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
    2560
    2561          queries = [{
    2562                  'cmd': u"""
    2563                          INSERT INTO clin.procedure (
    2564                                  fk_encounter,
    2565                                  fk_episode,
    2566                                  soap_cat,
    2567                                  clin_where,
    2568                                  fk_hospital_stay,
    2569                                  narrative
    2570                          ) VALUES (
    2571                                  %(enc)s,
    2572                                  %(epi)s,
    2573                                  'p',
    2574                                  gm.nullify_empty_string(%(loc)s),
    2575                                  %(stay)s,
    2576                                  gm.nullify_empty_string(%(proc)s)
    2577                          )
    2578                          RETURNING pk""",
    2579                  'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure}
    2580          }]
    2581
    2582          rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
    2583
    2584          return cPerformedProcedure(aPK_obj = rows[0][0])
    2585  #-----------------------------------------------------------
    2586 -def delete_performed_procedure(procedure=None):
    2587          cmd = u'delete from clin.procedure where pk = %(pk)s'
    2588          args = {'pk': procedure}
    2589          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    2590          return True
    2591  #============================================================
    2592  # main - unit testing
    2593  #------------------------------------------------------------
    2594  if __name__ == '__main__':
    2595
    2596          if len(sys.argv) < 2:
    2597                  sys.exit()
    2598
    2599          if sys.argv[1] != 'test':
    2600                  sys.exit()
    2601
    2602          #--------------------------------------------------------
    2603          # define tests
    2604          #--------------------------------------------------------
    2605 -        def test_problem():
    2606                  print "\nProblem test"
    2607                  print "------------"
    2608                  prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None})
    2609                  print prob
    2610                  fields = prob.get_fields()
    2611                  for field in fields:
    2612                          print field, ':', prob[field]
    2613                  print '\nupdatable:', prob.get_updatable_fields()
    2614                  epi = prob.get_as_episode()
    2615                  print '\nas episode:'
    2616                  if epi is not None:
    2617                          for field in epi.get_fields():
    2618                                  print '   .%s : %s' % (field, epi[field])
    2619          #--------------------------------------------------------
    2620 -        def test_health_issue():
    2621                  print "\nhealth issue test"
    2622                  print "-----------------"
    2623                  h_issue = cHealthIssue(aPK_obj=2)
    2624                  print h_issue
    2625                  fields = h_issue.get_fields()
    2626                  for field in fields:
    2627                          print field, ':', h_issue[field]
    2628                  print "has open episode:", h_issue.has_open_episode()
    2629                  print "open episode:", h_issue.get_open_episode()
    2630                  print "updateable:", h_issue.get_updatable_fields()
    2631                  h_issue.close_expired_episode(ttl=7300)
    2632                  h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis')
    2633                  print h_issue
    2634                  print h_issue.format_as_journal()
    2635          #--------------------------------------------------------
    2636 -        def test_episode():
    2637                  print "\nepisode test"
    2638                  print "------------"
    2639                  episode = cEpisode(aPK_obj=1)
    2640                  print episode
    2641                  fields = episode.get_fields()
    2642                  for field in fields:
    2643                          print field, ':', episode[field]
    2644                  print "updatable:", episode.get_updatable_fields()
    2645                  raw_input('ENTER to continue')
    2646
    2647                  old_description = episode['description']
    2648                  old_enc = cEncounter(aPK_obj = 1)
    2649
    2650                  desc = '1-%s' % episode['description']
    2651                  print "==> renaming to", desc
    2652                  successful = episode.rename (
    2653                          description = desc
    2654                  )
    2655                  if not successful:
    2656                          print "error"
    2657                  else:
    2658                          print "success"
    2659                          for field in fields:
    2660                                  print field, ':', episode[field]
    2661
    2662                  print "episode range:", episode.get_access_range()
    2663
    2664                  raw_input('ENTER to continue')
    2665
    2666          #--------------------------------------------------------
    2667 -        def test_encounter():
    2668                  print "\nencounter test"
    2669                  print "--------------"
    2670                  encounter = cEncounter(aPK_obj=1)
    2671                  print encounter
    2672                  fields = encounter.get_fields()
    2673                  for field in fields:
    2674                          print field, ':', encounter[field]
    2675                  print "updatable:", encounter.get_updatable_fields()
    2676          #--------------------------------------------------------
    2677 -        def test_encounter2latex():
    2678                  encounter = cEncounter(aPK_obj=1)
    2679                  print encounter
    2680                  print ""
    2681                  print encounter.format_latex()
    2682          #--------------------------------------------------------
    2683 -        def test_performed_procedure():
    2684                  procs = get_performed_procedures(patient = 12)
    2685                  for proc in procs:
    2686                          print proc.format(left_margin=2)
    2687          #--------------------------------------------------------
    2688 -        def test_hospital_stay():
    2689                  stay = create_hospital_stay(encounter = 1, episode = 2)
    2690                  stay['hospital'] = u'Starfleet Galaxy General Hospital'
    2691                  stay.save_payload()
    2692                  print stay
    2693                  for s in get_patient_hospital_stays(12):
    2694                          print s
    2695                  delete_hospital_stay(stay['pk_hospital_stay'])
    2696                  stay = create_hospital_stay(encounter = 1, episode = 4)
    2697          #--------------------------------------------------------
    2698 -        def test_diagnostic_certainty_classification_map():
    2699                  tests = [None, 'A', 'B', 'C', 'D', 'E']
    2700
    2701                  for t in tests:
    2702                          print type(t), t
    2703                          print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)
    2704          #--------------------------------------------------------
    2705 -        def test_episode_codes():
    2706                  epi = cEpisode(aPK_obj = 2)
    2707                  print epi
    2708                  print epi.generic_codes
    2709          #--------------------------------------------------------
    2710          # run them
    2711          #test_episode()
    2712          #test_problem()
    2713          #test_encounter()
    2714          #test_health_issue()
    2715          #test_hospital_stay()
    2716          #test_performed_procedure()
    2717          #test_diagnostic_certainty_classification_map()
    2718          #test_encounter2latex()
    2719          test_episode_codes()
    2720  #============================================================
    2721