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