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

# Source Code for Module Gnumed.business.gmClinicalRecord

       1  # -*- coding: utf8 -*-
       2  """GNUmed clinical patient record.
       3
       4  Make sure to call set_func_ask_user() and set_encounter_ttl() early on in
       5  your code (before cClinicalRecord.__init__() is called for the first time).
       6  """
       7  #============================================================
       8  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
       9  __license__ = "GPL v2 or later"
      10
      11  # standard libs
      12  import sys
      13  import logging
      14
      15
      16  if __name__ == '__main__':
      17          sys.path.insert(0, '../../')
      18          from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
      19          gmI18N.activate_locale()
      20          gmI18N.install_domain()
      21          gmDateTime.init()
      22
      23  from Gnumed.pycommon import gmExceptions
      24  from Gnumed.pycommon import gmPG2
      25  from Gnumed.pycommon import gmDispatcher
      26  from Gnumed.pycommon import gmI18N
      27  from Gnumed.pycommon import gmCfg
      28  from Gnumed.pycommon import gmTools
      29  from Gnumed.pycommon import gmDateTime
      30
      31  from Gnumed.business import gmAllergy
      32  from Gnumed.business import gmPathLab
      33  from Gnumed.business import gmLOINC
      34  from Gnumed.business import gmClinNarrative
      35  from Gnumed.business import gmEMRStructItems
      36  from Gnumed.business import gmMedication
      37  from Gnumed.business import gmVaccination
      38  from Gnumed.business import gmFamilyHistory
      39  from Gnumed.business.gmDemographicRecord import get_occupations
      40
      41
      42  _log = logging.getLogger('gm.emr')
      43
      44  _here = None
      45  #============================================================
      46  # helper functions
      47  #------------------------------------------------------------
      48  _func_ask_user = None
      49
      50 -def set_func_ask_user(a_func = None):
      51          if not callable(a_func):
      52                  _log.error('[%] not callable, not setting _func_ask_user', a_func)
      53                  return False
      54
      55          _log.debug('setting _func_ask_user to [%s]', a_func)
      56
      57          global _func_ask_user
      58          _func_ask_user = a_func
      59
      60  #============================================================
      61 -class cClinicalRecord(object):
      62
      63 -        def __init__(self, aPKey=None, allow_user_interaction=True):
      64                  """Fails if
      65
      66                  - no connection to database possible
      67                  - patient referenced by aPKey does not exist
      68                  """
      69                  self.pk_patient = aPKey                 # == identity.pk == primary key
      70
      71                  # FIXME: delegate to worker thread
      72                  # log access to patient record (HIPAA, for example)
      73                  cmd = u'SELECT gm.log_access2emr(%(todo)s)'
      74                  args = {'todo': u'patient [%s]' % aPKey}
      75                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
      76
      77                  from Gnumed.business import gmPraxis
      78                  global _here
      79                  if _here is None:
      80                          _here = gmPraxis.gmCurrentPraxisBranch()
      81
      82                  # load current or create new encounter
      83                  if _func_ask_user is None:
      84                          _log.error('[_func_ask_user] is None')
      85                          print "*** GNUmed [%s]: _func_ask_user is not set ***" % self.__class__.__name__
      86
      87                  # FIXME: delegate to worker thread ?
      88                  self.remove_empty_encounters()
      89
      90                  self.__encounter = None
      91                  if not self.__initiate_active_encounter(allow_user_interaction = allow_user_interaction):
      92                          raise gmExceptions.ConstructorError, "cannot activate an encounter for patient [%s]" % aPKey
      93
      94                  # FIXME: delegate to worker thread
      95                  gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
      96
      97                  # register backend notification interests
      98                  # (keep this last so we won't hang on threads when
      99                  #  failing this constructor for other reasons ...)
     100                  if not self._register_interests():
     101                          raise gmExceptions.ConstructorError, "cannot register signal interests"
     102
     103                  _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
     104          #--------------------------------------------------------
     105 -        def __del__(self):
     106                  pass
     107          #--------------------------------------------------------
     108 -        def cleanup(self):
     109                  _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
     110                  return True
     111          #--------------------------------------------------------
     112          # messaging
     113          #--------------------------------------------------------
     114 -        def _register_interests(self):
     115                  gmDispatcher.connect(signal = u'clin.encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
     116
     117                  return True
     118          #--------------------------------------------------------
     119 -        def db_callback_encounter_mod_db(self, **kwds):
     120
     121                  # get the current encounter as an extra instance
     122                  # from the database to check for changes
     123                  curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
     124
     125                  # the encounter just retrieved and the active encounter
     126                  # have got the same transaction ID so there's no change
     127                  # in the database, there could be a local change in
     128                  # the active encounter but that doesn't matter
     129                  # THIS DOES NOT WORK
     130  #               if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
     131  #                       return True
     132
     133                  # there must have been a change to the active encounter
     134                  # committed to the database from elsewhere,
     135                  # we must fail propagating the change, however, if
     136                  # there are local changes
     137                  if self.current_encounter.is_modified():
     138                          _log.debug('unsaved changes in active encounter, cannot switch to another one')
     139                          raise ValueError('unsaved changes in active encounter, cannot switch to another one')
     140
     141                  if self.current_encounter.same_payload(another_object = curr_enc_in_db):
     142                          _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
     143                          return True
     144
     145                  # there was a change in the database from elsewhere,
     146                  # locally, however, we don't have any changes, therefore
     147                  # we can propagate the remote change locally without
     148                  # losing anything
     149                  _log.debug('active encounter modified remotely, reloading and announcing the modification')
     150                  self.current_encounter.refetch_payload()
     151                  gmDispatcher.send(u'current_encounter_modified')
     152
     153                  return True
     154          #--------------------------------------------------------
     155          # API: family history
     156          #--------------------------------------------------------
     157 -        def get_family_history(self, episodes=None, issues=None, encounters=None):
     158                  fhx = gmFamilyHistory.get_family_history (
     159                          order_by = u'l10n_relation, condition',
     160                          patient = self.pk_patient
     161                  )
     162
     163                  if episodes is not None:
     164                          fhx = filter(lambda f: f['pk_episode'] in episodes, fhx)
     165
     166                  if issues is not None:
     167                          fhx = filter(lambda f: f['pk_health_issue'] in issues, fhx)
     168
     169                  if encounters is not None:
     170                          fhx = filter(lambda f: f['pk_encounter'] in encounters, fhx)
     171
     172                  return fhx
     173          #--------------------------------------------------------
     174 -        def add_family_history(self, episode=None, condition=None, relation=None):
     175                  return gmFamilyHistory.create_family_history (
     176                          encounter = self.current_encounter['pk_encounter'],
     177                          episode = episode,
     178                          condition = condition,
     179                          relation = relation
     180                  )
     181          #--------------------------------------------------------
     182          # API: performed procedures
     183          #--------------------------------------------------------
     184 -        def get_performed_procedures(self, episodes=None, issues=None):
     185
     186                  procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient)
     187
     188                  if episodes is not None:
     189                          procs = filter(lambda p: p['pk_episode'] in episodes, procs)
     190
     191                  if issues is not None:
     192                          procs = filter(lambda p: p['pk_health_issue'] in issues, procs)
     193
     194                  return procs
     195
     196          performed_procedures = property(get_performed_procedures, lambda x:x)
     197          #--------------------------------------------------------
     198 -        def get_latest_performed_procedure(self):
     199                  return gmEMRStructItems.get_latest_performed_procedure(patient = self.pk_patient)
     200          #--------------------------------------------------------
     201 -        def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
     202                  return gmEMRStructItems.create_performed_procedure (
     203                          encounter = self.current_encounter['pk_encounter'],
     204                          episode = episode,
     205                          location = location,
     206                          hospital_stay = hospital_stay,
     207                          procedure = procedure
     208                  )
     209          #--------------------------------------------------------
     210          # API: hospitalizations
     211          #--------------------------------------------------------
     212 -        def get_hospital_stays(self, episodes=None, issues=None, ongoing_only=False):
     213                  stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient, ongoing_only = ongoing_only)
     214                  if episodes is not None:
     215                          stays = filter(lambda s: s['pk_episode'] in episodes, stays)
     216                  if issues is not None:
     217                          stays = filter(lambda s: s['pk_health_issue'] in issues, stays)
     218                  return stays
     219
     220          hospital_stays = property(get_hospital_stays, lambda x:x)
     221          #--------------------------------------------------------
     222 -        def get_latest_hospital_stay(self):
     223                  return gmEMRStructItems.get_latest_patient_hospital_stay(patient = self.pk_patient)
     224          #--------------------------------------------------------
     225 -        def add_hospital_stay(self, episode=None, fk_org_unit=None):
     226                  return gmEMRStructItems.create_hospital_stay (
     227                          encounter = self.current_encounter['pk_encounter'],
     228                          episode = episode,
     229                          fk_org_unit = fk_org_unit
     230                  )
     231          #--------------------------------------------------------
     232 -        def get_hospital_stay_stats_by_hospital(self, cover_period=None):
     233                  args = {'pat': self.pk_patient, 'range': cover_period}
     234                  where_parts = [u'pk_patient = %(pat)s']
     235                  if cover_period is not None:
     236                          where_parts.append(u'discharge > (now() - %(range)s)')
     237
     238                  cmd = u"""
     239                          SELECT hospital, count(1) AS frequency
     240                          FROM clin.v_hospital_stays
     241                          WHERE
     242                                  %s
     243                          GROUP BY hospital
     244                          ORDER BY frequency DESC
     245                  """ % u' AND '.join(where_parts)
     246
     247                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     248                  return rows
     249          #--------------------------------------------------------
     250          # API: narrative
     251          #--------------------------------------------------------
     252 -        def add_notes(self, notes=None, episode=None, encounter=None):
     253
     254                  enc = gmTools.coalesce (
     255                          encounter,
     256                          self.current_encounter['pk_encounter']
     257                  )
     258
     259                  for note in notes:
     260                          success, data = gmClinNarrative.create_clin_narrative (
     261                                  narrative = note[1],
     262                                  soap_cat = note[0],
     263                                  episode_id = episode,
     264                                  encounter_id = enc
     265                          )
     266
     267                  return True
     268          #--------------------------------------------------------
     269 -        def add_clin_narrative(self, note='', soap_cat='s', episode=None):
     270                  if note.strip() == '':
     271                          _log.info('will not create empty clinical note')
     272                          return None
     273                  if isinstance(episode, gmEMRStructItems.cEpisode):
     274                          episode = episode['pk_episode']
     275                  status, data = gmClinNarrative.create_clin_narrative (
     276                          narrative = note,
     277                          soap_cat = soap_cat,
     278                          episode_id = episode,
     279                          encounter_id = self.current_encounter['pk_encounter']
     280                  )
     281                  if not status:
     282                          _log.error(str(data))
     283                          return None
     284                  return data
     285          #--------------------------------------------------------
     286 -        def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
     287                  """Get SOAP notes pertinent to this encounter.
     288
     289                          since
     290                                  - initial date for narrative items
     291                          until
     292                                  - final date for narrative items
     293                          encounters
     294                                  - list of encounters whose narrative are to be retrieved
     295                          episodes
     296                                  - list of episodes whose narrative are to be retrieved
     297                          issues
     298                                  - list of health issues whose narrative are to be retrieved
     299                          soap_cats
     300                                  - list of SOAP categories of the narrative to be retrieved
     301                  """
     302                  where_parts = [u'pk_patient = %(pat)s']
     303                  args = {u'pat': self.pk_patient}
     304
     305                  if issues is not None:
     306                          where_parts.append(u'pk_health_issue IN %(issues)s')
     307                          args['issues'] = tuple(issues)
     308
     309                  if episodes is not None:
     310                          where_parts.append(u'pk_episode IN %(epis)s')
     311                          args['epis'] = tuple(episodes)
     312
     313                  if encounters is not None:
     314                          where_parts.append(u'pk_encounter IN %(encs)s')
     315                          args['encs'] = tuple(encounters)
     316
     317                  if soap_cats is not None:
     318                          where_parts.append(u'c_vn.soap_cat IN %(cats)s')
     319                          soap_cats = list(soap_cats)
     320                          args['cats'] = [ cat.lower() for cat in soap_cats if cat is not None ]
     321                          if None in soap_cats:
     322                                  args['cats'].append(None)
     323                          args['cats'] = tuple(args['cats'])
     324
     325                  cmd = u"""
     326                          SELECT
     327                                  c_vn.*,
     328                                  c_scr.rank AS soap_rank
     329                          FROM
     330                                  clin.v_narrative c_vn
     331                                          LEFT JOIN clin.soap_cat_ranks c_scr on c_vn.soap_cat = c_scr.soap_cat
     332                          WHERE %s
     333                          ORDER BY date, soap_rank
     334                  """ % u' AND '.join(where_parts)
     335
     336                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     337
     338                  filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
     339
     340                  if since is not None:
     341                          filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
     342
     343                  if until is not None:
     344                          filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
     345
     346                  if providers is not None:
     347                          filtered_narrative = filter(lambda narr: narr['modified_by'] in providers, filtered_narrative)
     348
     349                  return filtered_narrative
     350          #--------------------------------------------------------
     351 -        def get_as_journal(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None):
     352                  return gmClinNarrative.get_as_journal (
     353                          patient = self.pk_patient,
     354                          since = since,
     355                          until = until,
     356                          encounters = encounters,
     357                          episodes = episodes,
     358                          issues = issues,
     359                          soap_cats = soap_cats,
     360                          providers = providers,
     361                          order_by = order_by,
     362                          time_range = time_range
     363                  )
     364          #--------------------------------------------------------
     365 -        def search_narrative_simple(self, search_term=''):
     366
     367                  search_term = search_term.strip()
     368                  if search_term == '':
     369                          return []
     370
     371                  cmd = u"""
     372  SELECT
     373          *,
     374          coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
     375                  as episode,
     376          coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
     377                  as health_issue,
     378          (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
     379                  as encounter_started,
     380          (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
     381                  as encounter_ended,
     382          (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
     383                  as encounter_type
     384  from clin.v_narrative4search vn4s
     385  WHERE
     386          pk_patient = %(pat)s and
     387          vn4s.narrative ~ %(term)s
     388  order by
     389          encounter_started
     390  """             # case sensitive
     391                  rows, idx = gmPG2.run_ro_queries(queries = [
     392                          {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
     393                  ])
     394                  return rows
     395          #--------------------------------------------------------
     396 -        def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
     397                  fields = [
     398                          'age',
     399                          "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
     400                          'modified_by',
     401                          'clin_when',
     402                          "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
     403                          'pk_item',
     404                          'pk_encounter',
     405                          'pk_episode',
     406                          'pk_health_issue',
     407                          'src_table'
     408                  ]
     409                  select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
     410                  # handle constraint conditions
     411                  where_snippets = []
     412                  params = {}
     413                  where_snippets.append('pk_patient=%(pat_id)s')
     414                  params['pat_id'] = self.pk_patient
     415                  if not since is None:
     416                          where_snippets.append('clin_when >= %(since)s')
     417                          params['since'] = since
     418                  if not until is None:
     419                          where_snippets.append('clin_when <= %(until)s')
     420                          params['until'] = until
     421                  # FIXME: these are interrelated, eg if we constrain encounter
     422                  # we automatically constrain issue/episode, so handle that,
     423                  # encounters
     424                  if not encounters is None and len(encounters) > 0:
     425                          params['enc'] = encounters
     426                          if len(encounters) > 1:
     427                                  where_snippets.append('fk_encounter in %(enc)s')
     428                          else:
     429                                  where_snippets.append('fk_encounter=%(enc)s')
     430                  # episodes
     431                  if not episodes is None and len(episodes) > 0:
     432                          params['epi'] = episodes
     433                          if len(episodes) > 1:
     434                                  where_snippets.append('fk_episode in %(epi)s')
     435                          else:
     436                                  where_snippets.append('fk_episode=%(epi)s')
     437                  # health issues
     438                  if not issues is None and len(issues) > 0:
     439                          params['issue'] = issues
     440                          if len(issues) > 1:
     441                                  where_snippets.append('fk_health_issue in %(issue)s')
     442                          else:
     443                                  where_snippets.append('fk_health_issue=%(issue)s')
     444
     445                  where_clause = ' and '.join(where_snippets)
     446                  order_by = 'order by src_table, age'
     447                  cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
     448
     449                  rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
     450                  if rows is None:
     451                          _log.error('cannot load item links for patient [%s]' % self.pk_patient)
     452                          return None
     453
     454                  # -- sort the data --
     455                  # FIXME: by issue/encounter/episode, eg formatting
     456                  # aggregate by src_table for item retrieval
     457                  items_by_table = {}
     458                  for item in rows:
     459                          src_table = item[view_col_idx['src_table']]
     460                          pk_item = item[view_col_idx['pk_item']]
     461                          if not items_by_table.has_key(src_table):
     462                                  items_by_table[src_table] = {}
     463                          items_by_table[src_table][pk_item] = item
     464
     465                  # get mapping for issue/episode IDs
     466                  issues = self.get_health_issues()
     467                  issue_map = {}
     468                  for issue in issues:
     469                          issue_map[issue['pk_health_issue']] = issue['description']
     470                  episodes = self.get_episodes()
     471                  episode_map = {}
     472                  for episode in episodes:
     473                          episode_map[episode['pk_episode']] = episode['description']
     474                  emr_data = {}
     475                  # get item data from all source tables
     476                  ro_conn = self._conn_pool.GetConnection('historica')
     477                  curs = ro_conn.cursor()
     478                  for src_table in items_by_table.keys():
     479                          item_ids = items_by_table[src_table].keys()
     480                          # we don't know anything about the columns of
     481                          # the source tables but, hey, this is a dump
     482                          if len(item_ids) == 0:
     483                                  _log.info('no items in table [%s] ?!?' % src_table)
     484                                  continue
     485                          elif len(item_ids) == 1:
     486                                  cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
     487                                  if not gmPG.run_query(curs, None, cmd, item_ids[0]):
     488                                          _log.error('cannot load items from table [%s]' % src_table)
     489                                          # skip this table
     490                                          continue
     491                          elif len(item_ids) > 1:
     492                                  cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
     493                                  if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
     494                                          _log.error('cannot load items from table [%s]' % src_table)
     495                                          # skip this table
     496                                          continue
     497                          rows = curs.fetchall()
     498                          table_col_idx = gmPG.get_col_indices(curs)
     499                          # format per-table items
     500                          for row in rows:
     501                                  # FIXME: make this get_pkey_name()
     502                                  pk_item = row[table_col_idx['pk_item']]
     503                                  view_row = items_by_table[src_table][pk_item]
     504                                  age = view_row[view_col_idx['age']]
     505                                  # format metadata
     506                                  try:
     507                                          episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
     508                                  except:
     509                                          episode_name = view_row[view_col_idx['pk_episode']]
     510                                  try:
     511                                          issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
     512                                  except:
     513                                          issue_name = view_row[view_col_idx['pk_health_issue']]
     514
     515                                  if not emr_data.has_key(age):
     516                                          emr_data[age] = []
     517
     518                                  emr_data[age].append(
     519                                          _('%s: encounter (%s)') % (
     520                                                  view_row[view_col_idx['clin_when']],
     521                                                  view_row[view_col_idx['pk_encounter']]
     522                                          )
     523                                  )
     524                                  emr_data[age].append(_('health issue: %s') % issue_name)
     525                                  emr_data[age].append(_('episode     : %s') % episode_name)
     526                                  # format table specific data columns
     527                                  # - ignore those, they are metadata, some
     528                                  #   are in clin.v_pat_items data already
     529                                  cols2ignore = [
     530                                          'pk_audit', 'row_version', 'modified_when', 'modified_by',
     531                                          'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
     532                                  ]
     533                                  col_data = []
     534                                  for col_name in table_col_idx.keys():
     535                                          if col_name in cols2ignore:
     536                                                  continue
     537                                          emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
     538                                  emr_data[age].append("----------------------------------------------------")
     539                                  emr_data[age].append("-- %s from table %s" % (
     540                                          view_row[view_col_idx['modified_string']],
     541                                          src_table
     542                                  ))
     543                                  emr_data[age].append("-- written %s by %s" % (
     544                                          view_row[view_col_idx['modified_when']],
     545                                          view_row[view_col_idx['modified_by']]
     546                                  ))
     547                                  emr_data[age].append("----------------------------------------------------")
     548                  curs.close()
     549                  return emr_data
     550          #--------------------------------------------------------
     551 -        def get_patient_ID(self):
     552                  return self.pk_patient
     553          #--------------------------------------------------------
     554 -        def get_statistics(self):
     555                  union_query = u'\n      union all\n'.join ([
     556                          u"""
     557                                  SELECT ((
     558                                          -- all relevant health issues + active episodes WITH health issue
     559                                          SELECT COUNT(1)
     560                                          FROM clin.v_problem_list
     561                                          WHERE
     562                                                  pk_patient = %(pat)s
     563                                                          AND
     564                                                  pk_health_issue is not null
     565                                  ) + (
     566                                          -- active episodes WITHOUT health issue
     567                                          SELECT COUNT(1)
     568                                          FROM clin.v_problem_list
     569                                          WHERE
     570                                                  pk_patient = %(pat)s
     571                                                          AND
     572                                                  pk_health_issue is null
     573                                  ))""",
     574                          u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
     575                          u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
     576                          u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
     577                          u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
     578                          u'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s',
     579                          u'SELECT count(1) FROM clin.v_procedures WHERE pk_patient = %(pat)s',
     580                          # active and approved substances == medication
     581                          u"""
     582                                  SELECT count(1)
     583                                  FROM clin.v_substance_intakes
     584                                  WHERE
     585                                          pk_patient = %(pat)s
     586                                                  AND
     587                                          is_currently_active IN (null, true)
     588                                                  AND
     589                                          intake_is_approved_of IN (null, true)""",
     590                          u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s'
     591                  ])
     592
     593                  rows, idx = gmPG2.run_ro_queries (
     594                          queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
     595                          get_col_idx = False
     596                  )
     597
     598                  stats = dict (
     599                          problems = rows[0][0],
     600                          encounters = rows[1][0],
     601                          items = rows[2][0],
     602                          documents = rows[3][0],
     603                          results = rows[4][0],
     604                          stays = rows[5][0],
     605                          procedures = rows[6][0],
     606                          active_drugs = rows[7][0],
     607                          vaccinations = rows[8][0]
     608                  )
     609
     610                  return stats
     611          #--------------------------------------------------------
     612 -        def format_statistics(self):
     613                  return _(
     614                          'Medical problems: %(problems)s\n'
     615                          'Total encounters: %(encounters)s\n'
     616                          'Total EMR entries: %(items)s\n'
     617                          'Active medications: %(active_drugs)s\n'
     618                          'Documents: %(documents)s\n'
     619                          'Test results: %(results)s\n'
     620                          'Hospitalizations: %(stays)s\n'
     621                          'Procedures: %(procedures)s\n'
     622                          'Vaccinations: %(vaccinations)s'
     623                  ) % self.get_statistics()
     624          #--------------------------------------------------------
     625 -        def format_summary(self):
     626
     627                  cmd = u"SELECT dob from dem.v_basic_person where pk_identity = %(pk)s"
     628                  args = {'pk': self.pk_patient}
     629                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     630                  dob = rows[0]['dob']
     631
     632                  stats = self.get_statistics()
     633                  first = self.get_first_encounter()
     634                  last = self.get_last_encounter()
     635                  probs = self.get_problems()
     636
     637                  txt = u''
     638                  if len(probs) > 0:
     639                          txt += _(' %s known problems, clinically relevant thereof:\n') % stats['problems']
     640                  else:
     641                          txt += _(' %s known problems\n') % stats['problems']
     642                  for prob in probs:
     643                          if not prob['clinically_relevant']:
     644                                  continue
     645                          txt += u'   \u00BB%s\u00AB (%s)\n' % (
     646                                  prob['problem'],
     647                                  gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive'))
     648                          )
     649                  txt += u'\n'
     650                  txt += _(' %s encounters from %s to %s\n') % (
     651                          stats['encounters'],
     652                          gmDateTime.pydt_strftime(first['started'], '%Y %b %d'),
     653                          gmDateTime.pydt_strftime(last['started'], '%Y %b %d')
     654                  )
     655                  txt += _(' %s active medications\n') % stats['active_drugs']
     656                  txt += _(' %s documents\n') % stats['documents']
     657                  txt += _(' %s test results\n') % stats['results']
     658                  txt += _(' %s hospitalizations') % stats['stays']
     659                  if stats['stays'] == 0:
     660                          txt += u'\n'
     661                  else:
     662                          txt += _(', most recently:\n%s\n') % self.get_latest_hospital_stay().format(left_margin = 3)
     663                  # FIXME: perhaps only count "ongoing ones"
     664                  txt += _(' %s performed procedures') % stats['procedures']
     665                  if stats['procedures'] == 0:
     666                          txt += u'\n'
     667                  else:
     668                          txt += _(', most recently:\n%s\n') % self.get_latest_performed_procedure().format(left_margin = 3)
     669
     670                  txt += u'\n'
     671                  txt += _('Allergies and Intolerances\n')
     672
     673                  allg_state = self.allergy_state
     674                  txt += (u' ' + allg_state.state_string)
     675                  if allg_state['last_confirmed'] is not None:
     676                          txt += _(' (last confirmed %s)') % gmDateTime.pydt_strftime(allg_state['last_confirmed'], '%Y %b %d')
     677                  txt += u'\n'
     678                  txt += gmTools.coalesce(allg_state['comment'], u'', u' %s\n')
     679                  for allg in self.get_allergies():
     680                          txt += u' %s: %s\n' % (
     681                                  allg['descriptor'],
     682                                  gmTools.coalesce(allg['reaction'], _('unknown reaction'))
     683                          )
     684
     685                  meds = self.get_current_substance_intakes(order_by = u'intake_is_approved_of DESC, substance')
     686                  if len(meds) > 0:
     687                          txt += u'\n'
     688                          txt += _('Medications and Substances')
     689                          txt += u'\n'
     690                  for m in meds:
     691                          txt += u'%s\n' % m.format_as_one_line(left_margin = 1)
     692
     693                  fhx = self.get_family_history()
     694                  if len(fhx) > 0:
     695                          txt += u'\n'
     696                          txt += _('Family History')
     697                          txt += u'\n'
     698                  for f in fhx:
     699                          txt += u'%s\n' % f.format(left_margin = 1)
     700
     701                  jobs = get_occupations(pk_identity = self.pk_patient)
     702                  if len(jobs) > 0:
     703                          txt += u'\n'
     704                          txt += _('Occupations')
     705                          txt += u'\n'
     706                  for job in jobs:
     707                          txt += u' %s%s\n' % (
     708                                  job['l10n_occupation'],
     709                                  gmTools.coalesce(job['activities'], u'', u': %s')
     710                          )
     711
     712                  vaccs = self.get_latest_vaccinations()
     713                  if len(vaccs) > 0:
     714                          txt += u'\n'
     715                          txt += _('Vaccinations')
     716                          txt += u'\n'
     717                  inds = sorted(vaccs.keys())
     718                  for ind in inds:
     719                          ind_count, vacc = vaccs[ind]
     720                          if dob is None:
     721                                  age_given = u''
     722                          else:
     723                                  age_given = u' @ %s' % gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age (
     724                                          start = dob,
     725                                          end = vacc['date_given']
     726                                  ))
     727                          since = _('%s ago') % gmDateTime.format_interval_medically(vacc['interval_since_given'])
     728                          txt += u' %s (%s%s): %s%s (%s %s%s%s)\n' % (
     729                                  ind,
     730                                  gmTools.u_sum,
     731                                  ind_count,
     732                                  #gmDateTime.pydt_strftime(vacc['date_given'], '%b %Y'),
     733                                  since,
     734                                  age_given,
     735                                  vacc['vaccine'],
     736                                  gmTools.u_left_double_angle_quote,
     737                                  vacc['batch_no'],
     738                                  gmTools.u_right_double_angle_quote
     739                          )
     740
     741                  return txt
     742          #--------------------------------------------------------
     743 -        def format_as_journal(self, left_margin=0, patient=None):
     744                  txt = u''
     745                  for enc in self.get_encounters(skip_empty = True):
     746                          txt += gmTools.u_box_horiz_4dashes * 70 + u'\n'
     747                          txt += enc.format (
     748                                  episodes = None,                        # means: each touched upon
     749                                  left_margin = left_margin,
     750                                  patient = patient,
     751                                  fancy_header = False,
     752                                  with_soap = True,
     753                                  with_docs = True,
     754                                  with_tests = True,
     755                                  with_vaccinations = True,
     756                                  with_co_encountlet_hints = False,               # irrelevant
     757                                  with_rfe_aoe = True,
     758                                  with_family_history = True,
     759                                  by_episode = True
     760                          )
     761
     762                  return txt
     763          #--------------------------------------------------------
     764          # API: allergy
     765          #--------------------------------------------------------
     766 -        def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
     767                  """Retrieves patient allergy items.
     768
     769                          remove_sensitivities
     770                                  - retrieve real allergies only, without sensitivities
     771                          since
     772                                  - initial date for allergy items
     773                          until
     774                                  - final date for allergy items
     775                          encounters
     776                                  - list of encounters whose allergies are to be retrieved
     777                          episodes
     778                                  - list of episodes whose allergies are to be retrieved
     779                          issues
     780                                  - list of health issues whose allergies are to be retrieved
     781          """
     782                  cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
     783                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
     784                  allergies = []
     785                  for r in rows:
     786                          allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
     787
     788                  # ok, let's constrain our list
     789                  filtered_allergies = []
     790                  filtered_allergies.extend(allergies)
     791
     792                  if ID_list is not None:
     793                          filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
     794                          if len(filtered_allergies) == 0:
     795                                  _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
     796                                  # better fail here contrary to what we do elsewhere
     797                                  return None
     798                          else:
     799                                  return filtered_allergies
     800
     801                  if remove_sensitivities:
     802                          filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
     803                  if since is not None:
     804                          filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
     805                  if until is not None:
     806                          filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
     807                  if issues is not None:
     808                          filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
     809                  if episodes is not None:
     810                          filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
     811                  if encounters is not None:
     812                          filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
     813
     814                  return filtered_allergies
     815          #--------------------------------------------------------
     816 -        def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
     817                  if encounter_id is None:
     818                          encounter_id = self.current_encounter['pk_encounter']
     819
     820                  if episode_id is None:
     821                          issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
     822                          epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
     823                          episode_id = epi['pk_episode']
     824
     825                  new_allergy = gmAllergy.create_allergy (
     826                          allergene = allergene,
     827                          allg_type = allg_type,
     828                          encounter_id = encounter_id,
     829                          episode_id = episode_id
     830                  )
     831
     832                  return new_allergy
     833          #--------------------------------------------------------
     834 -        def delete_allergy(self, pk_allergy=None):
     835                  cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
     836                  args = {'pk_allg': pk_allergy}
     837                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     838          #--------------------------------------------------------
     839 -        def is_allergic_to(self, atcs=None, inns=None, brand=None):
     840                  """Cave: only use with one potential allergic agent
     841                  otherwise you won't know which of the agents the allergy is to."""
     842
     843                  # we don't know the state
     844                  if self.allergy_state is None:
     845                          return None
     846
     847                  # we know there's no allergies
     848                  if self.allergy_state == 0:
     849                          return False
     850
     851                  args = {
     852                          'atcs': atcs,
     853                          'inns': inns,
     854                          'brand': brand,
     855                          'pat': self.pk_patient
     856                  }
     857                  allergenes = []
     858                  where_parts = []
     859
     860                  if len(atcs) == 0:
     861                          atcs = None
     862                  if atcs is not None:
     863                          where_parts.append(u'atc_code in %(atcs)s')
     864                  if len(inns) == 0:
     865                          inns = None
     866                  if inns is not None:
     867                          where_parts.append(u'generics in %(inns)s')
     868                          allergenes.extend(inns)
     869                  if brand is not None:
     870                          where_parts.append(u'substance = %(brand)s')
     871                          allergenes.append(brand)
     872
     873                  if len(allergenes) != 0:
     874                          where_parts.append(u'allergene in %(allgs)s')
     875                          args['allgs'] = tuple(allergenes)
     876
     877                  cmd = u"""
     878  SELECT * FROM clin.v_pat_allergies
     879  WHERE
     880          pk_patient = %%(pat)s
     881          AND ( %s )""" % u' OR '.join(where_parts)
     882
     883                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     884
     885                  if len(rows) == 0:
     886                          return False
     887
     888                  return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
     889          #--------------------------------------------------------
     890 -        def _set_allergy_state(self, state):
     891
     892                  if state not in gmAllergy.allergy_states:
     893                          raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
     894
     895                  allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
     896                  allg_state['has_allergy'] = state
     897                  allg_state.save_payload()
     898                  return True
     899
     900 -        def _get_allergy_state(self):
     901                  return gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
     902
     903          allergy_state = property(_get_allergy_state, _set_allergy_state)
     904          #--------------------------------------------------------
     905          # API: episodes
     906          #--------------------------------------------------------
     907 -        def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
     908                  """Fetches from backend patient episodes.
     909
     910                  id_list - Episodes' PKs list
     911                  issues - Health issues' PKs list to filter episodes by
     912                  open_status - return all (None) episodes, only open (True) or closed (False) one(s)
     913                  """
     914                  if (unlinked_only is True) and (issues is not None):
     915                          raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
     916
     917                  if order_by is None:
     918                          order_by = u''
     919                  else:
     920                          order_by = u'ORDER BY %s' % order_by
     921
     922                  args = {
     923                          'pat': self.pk_patient,
     924                          'open': open_status
     925                  }
     926                  where_parts = [u'pk_patient = %(pat)s']
     927
     928                  if open_status is not None:
     929                          where_parts.append(u'episode_open IS %(open)s')
     930
     931                  if unlinked_only:
     932                          where_parts.append(u'pk_health_issue is NULL')
     933
     934                  if issues is not None:
     935                          where_parts.append(u'pk_health_issue IN %(issues)s')
     936                          args['issues'] = tuple(issues)
     937
     938                  if id_list is not None:
     939                          where_parts.append(u'pk_episode IN %(epis)s')
     940                          args['epis'] = tuple(id_list)
     941
     942                  cmd = u"SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
     943                          u' AND '.join(where_parts),
     944                          order_by
     945                  )
     946                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     947
     948                  return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
     949
     950          episodes = property(get_episodes, lambda x:x)
     951          #------------------------------------------------------------------
     952 -        def get_unlinked_episodes(self, open_status=None, order_by=None):
     953                  return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
     954
     955          unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
     956          #------------------------------------------------------------------
     957 -        def get_episodes_by_encounter(self, pk_encounter=None):
     958                  cmd = u"""SELECT distinct pk_episode
     959                                          from clin.v_pat_items
     960                                          WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
     961                  args = {
     962                          'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
     963                          'pat': self.pk_patient
     964                  }
     965                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     966                  if len(rows) == 0:
     967                          return []
     968                  epis = []
     969                  for row in rows:
     970                          epis.append(row[0])
     971                  return self.get_episodes(id_list=epis)
     972          #------------------------------------------------------------------
     973 -        def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
     974                  """Add episode 'episode_name' for a patient's health issue.
     975
     976                  - silently returns if episode already exists
     977                  """
     978                  episode = gmEMRStructItems.create_episode (
     979                          pk_health_issue = pk_health_issue,
     980                          episode_name = episode_name,
     981                          is_open = is_open,
     982                          encounter = self.current_encounter['pk_encounter']
     983                  )
     984                  return episode
     985          #--------------------------------------------------------
     986 -        def get_most_recent_episode(self, issue=None):
     987                  # try to find the episode with the most recently modified clinical item
     988
     989                  issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
     990
     991                  cmd = u"""
     992  SELECT pk
     993  from clin.episode
     994  WHERE pk = (
     995          SELECT distinct on(pk_episode) pk_episode
     996          from clin.v_pat_items
     997          WHERE
     998                  pk_patient = %%(pat)s
     999                          and
    1000                  modified_when = (
    1001                          SELECT max(vpi.modified_when)
    1002                          from clin.v_pat_items vpi
    1003                          WHERE vpi.pk_patient = %%(pat)s
    1004                  )
    1005                  %s
    1006          -- guard against several episodes created at the same moment of time
    1007          limit 1
    1008          )""" % issue_where
    1009                  rows, idx = gmPG2.run_ro_queries(queries = [
    1010                          {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
    1011                  ])
    1012                  if len(rows) != 0:
    1013                          return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
    1014
    1015                  # no clinical items recorded, so try to find
    1016                  # the youngest episode for this patient
    1017                  cmd = u"""
    1018  SELECT vpe0.pk_episode
    1019  from
    1020          clin.v_pat_episodes vpe0
    1021  WHERE
    1022          vpe0.pk_patient = %%(pat)s
    1023                  and
    1024          vpe0.episode_modified_when = (
    1025                  SELECT max(vpe1.episode_modified_when)
    1026                  from clin.v_pat_episodes vpe1
    1027                  WHERE vpe1.pk_episode = vpe0.pk_episode
    1028          )
    1029          %s""" % issue_where
    1030                  rows, idx = gmPG2.run_ro_queries(queries = [
    1031                          {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
    1032                  ])
    1033                  if len(rows) != 0:
    1034                          return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
    1035
    1036                  return None
    1037          #--------------------------------------------------------
    1038 -        def episode2problem(self, episode=None):
    1039                  return gmEMRStructItems.episode2problem(episode=episode)
    1040          #--------------------------------------------------------
    1041          # API: problems
    1042          #--------------------------------------------------------
    1043 -        def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
    1044                  """Retrieve a patient's problems.
    1045
    1046                  "Problems" are the UNION of:
    1047
    1048                          - issues which are .clinically_relevant
    1049                          - episodes which are .is_open
    1050
    1051                  Therefore, both an issue and the open episode
    1052                  thereof can each be listed as a problem.
    1053
    1054                  include_closed_episodes/include_irrelevant_issues will
    1055                  include those -- which departs from the definition of
    1056                  the problem list being "active" items only ...
    1057
    1058                  episodes - episodes' PKs to filter problems by
    1059                  issues - health issues' PKs to filter problems by
    1060                  """
    1061                  # FIXME: this could use a good measure of streamlining, probably
    1062
    1063                  args = {'pat': self.pk_patient}
    1064
    1065                  cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
    1066                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1067
    1068                  # Instantiate problem items
    1069                  problems = []
    1070                  for row in rows:
    1071                          pk_args = {
    1072                                  u'pk_patient': self.pk_patient,
    1073                                  u'pk_health_issue': row['pk_health_issue'],
    1074                                  u'pk_episode': row['pk_episode']
    1075                          }
    1076                          problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
    1077
    1078                  # include non-problems ?
    1079                  other_rows = []
    1080                  if include_closed_episodes:
    1081                          cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
    1082                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1083                          other_rows.extend(rows)
    1084
    1085                  if include_irrelevant_issues:
    1086                          cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
    1087                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1088                          other_rows.extend(rows)
    1089
    1090                  if len(other_rows) > 0:
    1091                          for row in other_rows:
    1092                                  pk_args = {
    1093                                          u'pk_patient': self.pk_patient,
    1094                                          u'pk_health_issue': row['pk_health_issue'],
    1095                                          u'pk_episode': row['pk_episode']
    1096                                  }
    1097                                  problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
    1098
    1099                  # filter ?
    1100                  if (episodes is None) and (issues is None):
    1101                          return problems
    1102
    1103                  # filter
    1104                  if issues is not None:
    1105                          problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
    1106                  if episodes is not None:
    1107                          problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
    1108
    1109                  return problems
    1110          #--------------------------------------------------------
    1111 -        def problem2episode(self, problem=None):
    1112                  return gmEMRStructItems.problem2episode(problem = problem)
    1113          #--------------------------------------------------------
    1114 -        def problem2issue(self, problem=None):
    1115                  return gmEMRStructItems.problem2issue(problem = problem)
    1116          #--------------------------------------------------------
    1117 -        def reclass_problem(self, problem):
    1118                  return gmEMRStructItems.reclass_problem(problem = problem)
    1119          #--------------------------------------------------------
    1120          # API: health issues
    1121          #--------------------------------------------------------
    1122 -        def get_health_issues(self, id_list = None):
    1123
    1124                  cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
    1125                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
    1126                  issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
    1127
    1128                  if id_list is None:
    1129                          return issues
    1130
    1131                  if len(id_list) == 0:
    1132                          raise ValueError('id_list to filter by is empty, most likely a programming error')
    1133
    1134                  filtered_issues = []
    1135                  for issue in issues:
    1136                          if issue['pk_health_issue'] in id_list:
    1137                                  filtered_issues.append(issue)
    1138
    1139                  return filtered_issues
    1140
    1141          health_issues = property(get_health_issues, lambda x:x)
    1142          #------------------------------------------------------------------
    1143 -        def add_health_issue(self, issue_name=None):
    1144                  """Adds patient health issue."""
    1145                  return gmEMRStructItems.create_health_issue (
    1146                          description = issue_name,
    1147                          encounter = self.current_encounter['pk_encounter'],
    1148                          patient = self.pk_patient
    1149                  )
    1150          #--------------------------------------------------------
    1151 -        def health_issue2problem(self, issue=None):
    1152                  return gmEMRStructItems.health_issue2problem(issue = issue)
    1153          #--------------------------------------------------------
    1154          # API: substance intake
    1155          #--------------------------------------------------------
    1156 -        def get_current_substance_intakes(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
    1157
    1158                  where_parts = [u'pk_patient = %(pat)s']
    1159                  args = {'pat': self.pk_patient}
    1160
    1161                  if not include_inactive:
    1162                          where_parts.append(u'is_currently_active IN (true, null)')
    1163
    1164                  if not include_unapproved:
    1165                          where_parts.append(u'intake_is_approved_of IN (true, null)')
    1166
    1167                  if order_by is None:
    1168                          order_by = u''
    1169                  else:
    1170                          order_by = u'ORDER BY %s' % order_by
    1171
    1172                  cmd = u"SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
    1173                          u'\nAND '.join(where_parts),
    1174                          order_by
    1175                  )
    1176                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1177                  meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'})  for r in rows ]
    1178
    1179                  if episodes is not None:
    1180                          meds = filter(lambda s: s['pk_episode'] in episodes, meds)
    1181
    1182                  if issues is not None:
    1183                          meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
    1184
    1185                  return meds
    1186          #--------------------------------------------------------
    1187 -        def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
    1188                  return gmMedication.create_substance_intake (
    1189                          pk_substance = pk_substance,
    1190                          pk_component = pk_component,
    1191                          encounter = self.current_encounter['pk_encounter'],
    1192                          episode = episode,
    1193                          preparation = preparation
    1194                  )
    1195          #--------------------------------------------------------
    1196 -        def substance_intake_exists(self, pk_component=None, pk_substance=None):
    1197                  return gmMedication.substance_intake_exists (
    1198                          pk_component = pk_component,
    1199                          pk_substance = pk_substance,
    1200                          pk_identity = self.pk_patient
    1201                  )
    1202          #--------------------------------------------------------
    1203          # API: vaccinations
    1204          #--------------------------------------------------------
    1205 -        def add_vaccination(self, episode=None, vaccine=None, batch_no=None):
    1206                  return gmVaccination.create_vaccination (
    1207                          encounter = self.current_encounter['pk_encounter'],
    1208                          episode = episode,
    1209                          vaccine = vaccine,
    1210                          batch_no = batch_no
    1211                  )
    1212          #--------------------------------------------------------
    1213 -        def get_latest_vaccinations(self, episodes=None, issues=None):
    1214                  """Returns latest given vaccination for each vaccinated indication.
    1215
    1216                  as a dict {'l10n_indication': cVaccination instance}
    1217
    1218                  Note that this will produce duplicate vaccination instances on combi-indication vaccines !
    1219                  """
    1220                  # find the PKs
    1221                  args = {'pat': self.pk_patient}
    1222                  where_parts = [u'pk_patient = %(pat)s']
    1223
    1224                  if (episodes is not None) and (len(episodes) > 0):
    1225                          where_parts.append(u'pk_episode IN %(epis)s')
    1226                          args['epis'] = tuple(episodes)
    1227
    1228                  if (issues is not None) and (len(issues) > 0):
    1229                          where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
    1230                          args['issues'] = tuple(issues)
    1231
    1232                  cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
    1233                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1234
    1235                  # none found
    1236                  if len(rows) == 0:
    1237                          return {}
    1238
    1239                  vpks = [ ind['pk_vaccination'] for ind in rows ]
    1240                  vinds = [ ind['l10n_indication'] for ind in rows ]
    1241                  ind_counts = [ ind['indication_count'] for ind in rows ]
    1242
    1243                  # turn them into vaccinations
    1244                  cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
    1245                  args = {'pks': tuple(vpks)}
    1246                  rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1247
    1248                  vaccs = {}
    1249                  for idx in range(len(vpks)):
    1250                          pk = vpks[idx]
    1251                          ind_count = ind_counts[idx]
    1252                          for r in rows:
    1253                                  if r['pk_vaccination'] == pk:
    1254                                          vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
    1255
    1256                  return vaccs
    1257          #--------------------------------------------------------
    1258 -        def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
    1259
    1260                  args = {'pat': self.pk_patient}
    1261                  where_parts = [u'pk_patient = %(pat)s']
    1262
    1263                  if order_by is None:
    1264                          order_by = u''
    1265                  else:
    1266                          order_by = u'ORDER BY %s' % order_by
    1267
    1268                  if (episodes is not None) and (len(episodes) > 0):
    1269                          where_parts.append(u'pk_episode IN %(epis)s')
    1270                          args['epis'] = tuple(episodes)
    1271
    1272                  if (issues is not None) and (len(issues) > 0):
    1273                          where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
    1274                          args['issues'] = tuple(issues)
    1275
    1276                  if (encounters is not None) and (len(encounters) > 0):
    1277                          where_parts.append(u'pk_encounter IN %(encs)s')
    1278                          args['encs'] = tuple(encounters)
    1279
    1280                  cmd = u'%s %s' % (
    1281                          gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
    1282                          order_by
    1283                  )
    1284                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1285                  vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'})  for r in rows ]
    1286
    1287                  return vaccs
    1288
    1289          vaccinations = property(get_vaccinations, lambda x:x)
    1290          #--------------------------------------------------------
    1291          # old/obsolete:
    1292          #--------------------------------------------------------
    1293 -        def get_scheduled_vaccination_regimes(self, ID=None, indications=None):
    1294                  """Retrieves vaccination regimes the patient is on.
    1295
    1296                          optional:
    1297                          * ID - PK of the vaccination regime
    1298                          * indications - indications we want to retrieve vaccination
    1299                                  regimes for, must be primary language, not l10n_indication
    1300                  """
    1301                  # FIXME: use course, not regime
    1302                  # retrieve vaccination regimes definitions
    1303                  cmd = """SELECT distinct on(pk_course) pk_course
    1304                                   FROM clin.v_vaccs_scheduled4pat
    1305                                   WHERE pk_patient=%s"""
    1306                  rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
    1307                  if rows is None:
    1308                          _log.error('cannot retrieve scheduled vaccination courses')
    1309                          return None
    1310                  # Instantiate vaccination items and keep cache
    1311                  for row in rows:
    1312                          self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
    1313
    1314                  # ok, let's constrain our list
    1315                  filtered_regimes = []
    1316                  filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
    1317                  if ID is not None:
    1318                          filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
    1319                          if len(filtered_regimes) == 0:
    1320                                  _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
    1321                                  return []
    1322                          else:
    1323                                  return filtered_regimes[0]
    1324                  if indications is not None:
    1325                          filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
    1326
    1327                  return filtered_regimes
    1328          #--------------------------------------------------------
    1329  #       def get_vaccinated_indications(self):
    1330  #               """Retrieves patient vaccinated indications list.
    1331  #
    1332  #               Note that this does NOT rely on the patient being on
    1333  #               some schedule or other but rather works with what the
    1334  #               patient has ACTUALLY been vaccinated against. This is
    1335  #               deliberate !
    1336  #               """
    1337  #               # most likely, vaccinations will be fetched close
    1338  #               # by so it makes sense to count on the cache being
    1339  #               # filled (or fill it for nearby use)
    1340  #               vaccinations = self.get_vaccinations()
    1341  #               if vaccinations is None:
    1342  #                       _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
    1343  #                       return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
    1344  #               if len(vaccinations) == 0:
    1345  #                       return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
    1346  #               v_indications = []
    1347  #               for vacc in vaccinations:
    1348  #                       tmp = [vacc['indication'], vacc['l10n_indication']]
    1349  #                       # remove duplicates
    1350  #                       if tmp in v_indications:
    1351  #                               continue
    1352  #                       v_indications.append(tmp)
    1353  #               return (True, v_indications)
    1354          #--------------------------------------------------------
    1355 -        def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
    1356                  """Retrieves list of vaccinations the patient has received.
    1357
    1358                  optional:
    1359                  * ID - PK of a vaccination
    1360                  * indications - indications we want to retrieve vaccination
    1361                          items for, must be primary language, not l10n_indication
    1362          * since - initial date for allergy items
    1363          * until - final date for allergy items
    1364          * encounters - list of encounters whose allergies are to be retrieved
    1365          * episodes - list of episodes whose allergies are to be retrieved
    1366          * issues - list of health issues whose allergies are to be retrieved
    1367                  """
    1368                  try:
    1369                          self.__db_cache['vaccinations']['vaccinated']
    1370                  except KeyError:
    1371                          self.__db_cache['vaccinations']['vaccinated'] = []
    1372                          # Important fetch ordering by indication, date to know if a vaccination is booster
    1373                          cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
    1374                                          WHERE pk_patient=%s
    1375                                          order by indication, date"""
    1376                          rows, idx  = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
    1377                          if rows is None:
    1378                                  _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
    1379                                  del self.__db_cache['vaccinations']['vaccinated']
    1380                                  return None
    1381                          # Instantiate vaccination items
    1382                          vaccs_by_ind = {}
    1383                          for row in rows:
    1384                                  vacc_row = {
    1385                                          'pk_field': 'pk_vaccination',
    1386                                          'idx': idx,
    1387                                          'data': row
    1388                                  }
    1389                                  vacc = gmVaccination.cVaccination(row=vacc_row)
    1390                                  self.__db_cache['vaccinations']['vaccinated'].append(vacc)
    1391                                  # keep them, ordered by indication
    1392                                  try:
    1393                                          vaccs_by_ind[vacc['indication']].append(vacc)
    1394                                  except KeyError:
    1395                                          vaccs_by_ind[vacc['indication']] = [vacc]
    1396
    1397                          # calculate sequence number and is_booster
    1398                          for ind in vaccs_by_ind.keys():
    1399                                  vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
    1400                                  for vacc in vaccs_by_ind[ind]:
    1401                                          # due to the "order by indication, date" the vaccinations are in the
    1402                                          # right temporal order inside the indication-keyed dicts
    1403                                          seq_no = vaccs_by_ind[ind].index(vacc) + 1
    1404                                          vacc['seq_no'] = seq_no
    1405                                          # if no active schedule for indication we cannot
    1406                                          # check for booster status (eg. seq_no > max_shot)
    1407                                          if (vacc_regimes is None) or (len(vacc_regimes) == 0):
    1408                                                  continue
    1409                                          if seq_no > vacc_regimes[0]['shots']:
    1410                                                  vacc['is_booster'] = True
    1411                          del vaccs_by_ind
    1412
    1413                  # ok, let's constrain our list
    1414                  filtered_shots = []
    1415                  filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
    1416                  if ID is not None:
    1417                          filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
    1418                          if len(filtered_shots) == 0:
    1419                                  _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
    1420                                  return None
    1421                          else:
    1422                                  return filtered_shots[0]
    1423                  if since is not None:
    1424                          filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
    1425                  if until is not None:
    1426                          filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
    1427                  if issues is not None:
    1428                          filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
    1429                  if episodes is not None:
    1430                          filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
    1431                  if encounters is not None:
    1432                          filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
    1433                  if indications is not None:
    1434                          filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
    1435                  return filtered_shots
    1436          #--------------------------------------------------------
    1437 -        def get_scheduled_vaccinations(self, indications=None):
    1438                  """Retrieves vaccinations scheduled for a regime a patient is on.
    1439
    1440                  The regime is referenced by its indication (not l10n)
    1441
    1442                  * indications - List of indications (not l10n) of regimes we want scheduled
    1443                                  vaccinations to be fetched for
    1444                  """
    1445                  try:
    1446                          self.__db_cache['vaccinations']['scheduled']
    1447                  except KeyError:
    1448                          self.__db_cache['vaccinations']['scheduled'] = []
    1449                          cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
    1450                          rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
    1451                          if rows is None:
    1452                                  _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
    1453                                  del self.__db_cache['vaccinations']['scheduled']
    1454                                  return None
    1455                          # Instantiate vaccination items
    1456                          for row in rows:
    1457                                  vacc_row = {
    1458                                          'pk_field': 'pk_vacc_def',
    1459                                          'idx': idx,
    1460                                          'data': row
    1461                                  }
    1462                                  self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
    1463
    1464                  # ok, let's constrain our list
    1465                  if indications is None:
    1466                          return self.__db_cache['vaccinations']['scheduled']
    1467                  filtered_shots = []
    1468                  filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
    1469                  filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
    1470                  return filtered_shots
    1471          #--------------------------------------------------------
    1472 -        def get_missing_vaccinations(self, indications=None):
    1473                  try:
    1474                          self.__db_cache['vaccinations']['missing']
    1475                  except KeyError:
    1476                          self.__db_cache['vaccinations']['missing'] = {}
    1477                          # 1) non-booster
    1478                          self.__db_cache['vaccinations']['missing']['due'] = []
    1479                          # get list of (indication, seq_no) tuples
    1480                          cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
    1481                          rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
    1482                          if rows is None:
    1483                                  _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
    1484                                  return None
    1485                          pk_args = {'pat_id': self.pk_patient}
    1486                          if rows is not None:
    1487                                  for row in rows:
    1488                                          pk_args['indication'] = row[0]
    1489                                          pk_args['seq_no'] = row[1]
    1490                                          self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
    1491
    1492                          # 2) boosters
    1493                          self.__db_cache['vaccinations']['missing']['boosters'] = []
    1494                          # get list of indications
    1495                          cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
    1496                          rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
    1497                          if rows is None:
    1498                                  _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
    1499                                  return None
    1500                          pk_args = {'pat_id': self.pk_patient}
    1501                          if rows is not None:
    1502                                  for row in rows:
    1503                                          pk_args['indication'] = row[0]
    1504                                          self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
    1505
    1506                  # if any filters ...
    1507                  if indications is None:
    1508                          return self.__db_cache['vaccinations']['missing']
    1509                  if len(indications) == 0:
    1510                          return self.__db_cache['vaccinations']['missing']
    1511                  # ... apply them
    1512                  filtered_shots = {
    1513                          'due': [],
    1514                          'boosters': []
    1515                  }
    1516                  for due_shot in self.__db_cache['vaccinations']['missing']['due']:
    1517                          if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
    1518                                  filtered_shots['due'].append(due_shot)
    1519                  for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
    1520                          if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
    1521                                  filtered_shots['boosters'].append(due_shot)
    1522                  return filtered_shots
    1523          #------------------------------------------------------------------
    1524          # API: encounters
    1525          #------------------------------------------------------------------
    1526 -        def _get_current_encounter(self):
    1527                  return self.__encounter
    1528
    1529 -        def _set_current_encounter(self, encounter):
    1530
    1531                  # first ever setting ?
    1532                  if self.__encounter is None:
    1533                          _log.debug('first setting of active encounter in this clinical record instance')
    1534                  else:
    1535                          _log.debug('switching of active encounter')
    1536                          # fail if the currently active encounter has unsaved changes
    1537                          if self.__encounter.is_modified():
    1538                                  _log.debug('unsaved changes in active encounter, cannot switch to another one')
    1539                                  raise ValueError('unsaved changes in active encounter, cannot switch to another one')
    1540
    1541                  # be more conservative, it seems to have brought about
    1542                  # races involving encounter mod signals which made GNUmed crash
    1543  #               # set the currently active encounter and announce that change
    1544  #               if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
    1545  #                       now = gmDateTime.pydt_now_here()
    1546  #                       if now > encounter['started']:
    1547  #                               encounter['last_affirmed'] = now                # this will trigger an "clin.encounter_mod_db"
    1548  #                               encounter.save()
    1549                  self.__encounter = encounter
    1550                  gmDispatcher.send(u'current_encounter_switched')
    1551
    1552                  return True
    1553
    1554          current_encounter = property(_get_current_encounter, _set_current_encounter)
    1555          active_encounter = property(_get_current_encounter, _set_current_encounter)
    1556          #------------------------------------------------------------------
    1557 -        def __initiate_active_encounter(self, allow_user_interaction=True):
    1558
    1559                  # 1) "very recent" encounter recorded ?
    1560                  if self.__activate_very_recent_encounter():
    1561                          return True
    1562
    1563                  # 2) "fairly recent" encounter recorded ?
    1564                  if self.__activate_fairly_recent_encounter(allow_user_interaction = allow_user_interaction):
    1565                          return True
    1566
    1567                  # 3) start a completely new encounter
    1568                  self.start_new_encounter()
    1569                  return True
    1570          #------------------------------------------------------------------
    1571 -        def __activate_very_recent_encounter(self):
    1572                  """Try to attach to a "very recent" encounter if there is one.
    1573
    1574                  returns:
    1575                          False: no "very recent" encounter, create new one
    1576                  True: success
    1577                  """
    1578                  cfg_db = gmCfg.cCfgSQL()
    1579                  min_ttl = cfg_db.get2 (
    1580                          option = u'encounter.minimum_ttl',
    1581                          workplace = _here.active_workplace,
    1582                          bias = u'user',
    1583                          default = u'1 hour 30 minutes'
    1584                  )
    1585                  cmd = u"""
    1586                          SELECT pk_encounter
    1587                          FROM clin.v_most_recent_encounters
    1588                          WHERE
    1589                                  pk_patient = %s
    1590                                          and
    1591                                  last_affirmed > (now() - %s::interval)
    1592                          ORDER BY
    1593                                  last_affirmed DESC"""
    1594                  enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
    1595                  # none found
    1596                  if len(enc_rows) == 0:
    1597                          _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
    1598                          return False
    1599                  # attach to existing
    1600                  self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
    1601                  _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
    1602                  return True
    1603          #------------------------------------------------------------------
    1604 -        def __activate_fairly_recent_encounter(self, allow_user_interaction=True):
    1605                  """Try to attach to a "fairly recent" encounter if there is one.
    1606
    1607                  returns:
    1608                          False: no "fairly recent" encounter, create new one
    1609                  True: success
    1610                  """
    1611                  if _func_ask_user is None:
    1612                          _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
    1613                          return False
    1614
    1615                  if not allow_user_interaction:
    1616                          _log.exception('user interaction not desired, not looking for fairly recent encounter')
    1617                          return False
    1618
    1619                  cfg_db = gmCfg.cCfgSQL()
    1620                  min_ttl = cfg_db.get2 (
    1621                          option = u'encounter.minimum_ttl',
    1622                          workplace = _here.active_workplace,
    1623                          bias = u'user',
    1624                          default = u'1 hour 30 minutes'
    1625                  )
    1626                  max_ttl = cfg_db.get2 (
    1627                          option = u'encounter.maximum_ttl',
    1628                          workplace = _here.active_workplace,
    1629                          bias = u'user',
    1630                          default = u'6 hours'
    1631                  )
    1632                  cmd = u"""
    1633                          SELECT pk_encounter
    1634                          FROM clin.v_most_recent_encounters
    1635                          WHERE
    1636                                  pk_patient=%s
    1637                                          AND
    1638                                  last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
    1639                          ORDER BY
    1640                                  last_affirmed DESC"""
    1641                  enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
    1642                  # none found
    1643                  if len(enc_rows) == 0:
    1644                          _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
    1645                          return False
    1646
    1647                  _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0])
    1648
    1649                  encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
    1650                  # ask user whether to attach or not
    1651                  cmd = u"""
    1652                          SELECT title, firstnames, lastnames, gender, dob
    1653                          FROM dem.v_basic_person WHERE pk_identity=%s"""
    1654                  pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
    1655                  pat = pats[0]
    1656                  pat_str = u'%s %s %s (%s), %s  [#%s]' % (
    1657                          gmTools.coalesce(pat[0], u'')[:5],
    1658                          pat[1][:15],
    1659                          pat[2][:15],
    1660                          pat[3],
    1661                          gmDateTime.pydt_strftime(pat[4], '%Y %b %d'),
    1662                          self.pk_patient
    1663                  )
    1664                  msg = _(
    1665                          '%s\n'
    1666                          '\n'
    1667                          "This patient's chart was worked on only recently:\n"
    1668                          '\n'
    1669                          ' %s  %s - %s  (%s)\n'
    1670                          '\n'
    1671                          ' Reason for Encounter:\n'
    1672                          '  %s\n'
    1673                          ' Assessment of Encounter:\n'
    1674                          '  %s\n'
    1675                          '\n'
    1676                          'Do you want to continue that consultation\n'
    1677                          'or do you want to start a new one ?\n'
    1678                  ) % (
    1679                          pat_str,
    1680                          gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'),
    1681                          gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'),
    1682                          encounter['l10n_type'],
    1683                          gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
    1684                          gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
    1685                  )
    1686                  attach = False
    1687                  try:
    1688                          attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
    1689                  except:
    1690                          _log.exception('cannot ask user for guidance, not attaching to existing encounter')
    1691                          return False
    1692                  if not attach:
    1693                          return False
    1694
    1695                  # attach to existing
    1696                  self.current_encounter = encounter
    1697                  _log.debug('"fairly recent" encounter re-activated')
    1698                  return True
    1699          #------------------------------------------------------------------
    1700 -        def start_new_encounter(self):
    1701                  cfg_db = gmCfg.cCfgSQL()
    1702                  enc_type = cfg_db.get2 (
    1703                          option = u'encounter.default_type',
    1704                          workplace = _here.active_workplace,
    1705                          bias = u'user'
    1706                  )
    1707                  if enc_type is None:
    1708                          enc_type = gmEMRStructItems.get_most_commonly_used_encounter_type()
    1709                  if enc_type is None:
    1710                          enc_type = u'in surgery'
    1711                  enc = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
    1712                  enc['pk_org_unit'] = _here['pk_org_unit']
    1713                  enc.save()
    1714                  self.current_encounter = enc
    1715                  _log.debug('new encounter [%s] initiated' % self.current_encounter['pk_encounter'])
    1716          #------------------------------------------------------------------
    1717 -        def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False):
    1718                  """Retrieves patient's encounters.
    1719
    1720                  id_list - PKs of encounters to fetch
    1721                  since - initial date for encounter items, DateTime instance
    1722                  until - final date for encounter items, DateTime instance
    1723                  episodes - PKs of the episodes the encounters belong to (many-to-many relation)
    1724                  issues - PKs of the health issues the encounters belong to (many-to-many relation)
    1725                  skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
    1726
    1727                  NOTE: if you specify *both* issues and episodes
    1728                  you will get the *aggregate* of all encounters even
    1729                  if the episodes all belong to the health issues listed.
    1730                  IOW, the issues broaden the episode list rather than
    1731                  the episode list narrowing the episodes-from-issues
    1732                  list.
    1733                  Rationale: If it was the other way round it would be
    1734                  redundant to specify the list of issues at all.
    1735                  """
    1736                  where_parts = [u'c_vpe.pk_patient = %(pat)s']
    1737                  args = {'pat': self.pk_patient}
    1738
    1739                  if skip_empty:
    1740                          where_parts.append(u"""NOT (
    1741                                  gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
    1742                                          AND
    1743                                  gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
    1744                                          AND
    1745                                  NOT EXISTS (
    1746                                          SELECT 1 FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_patient = %(pat)s AND c_vpi.pk_encounter = c_vpe.pk_encounter
    1747                                                  UNION ALL
    1748                                          SELECT 1 FROM blobs.v_doc_med b_vdm WHERE b_vdm.pk_patient = %(pat)s AND b_vdm.pk_encounter = c_vpe.pk_encounter
    1749                                  ))""")
    1750
    1751                  if since is not None:
    1752                          where_parts.append(u'c_vpe.started >= %(start)s')
    1753                          args['start'] = since
    1754
    1755                  if until is not None:
    1756                          where_parts.append(u'c_vpe.last_affirmed <= %(end)s')
    1757                          args['end'] = since
    1758
    1759                  cmd = u"""
    1760                          SELECT *
    1761                          FROM clin.v_pat_encounters c_vpe
    1762                          WHERE
    1763                                  %s
    1764                          ORDER BY started
    1765                  """ % u' AND '.join(where_parts)
    1766                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1767                  encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
    1768
    1769                  # we've got the encounters, start filtering
    1770                  filtered_encounters = []
    1771                  filtered_encounters.extend(encounters)
    1772
    1773                  if id_list is not None:
    1774                          filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
    1775
    1776                  if (issues is not None) and (len(issues) > 0):
    1777                          issues = tuple(issues)
    1778                          # however, this seems like the proper approach:
    1779                          # - find episodes corresponding to the health issues in question
    1780                          cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
    1781                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
    1782                          epi_ids = map(lambda x:x[0], rows)
    1783                          if episodes is None:
    1784                                  episodes = []
    1785                          episodes.extend(epi_ids)
    1786
    1787                  if (episodes is not None) and (len(episodes) > 0):
    1788                          episodes = tuple(episodes)
    1789                          # if the episodes to filter by belong to the patient in question so will
    1790                          # the encounters found with them - hence we don't need a WHERE on the patient ...
    1791                          cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
    1792                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
    1793                          enc_ids = map(lambda x:x[0], rows)
    1794                          filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
    1795
    1796                  return filtered_encounters
    1797          #--------------------------------------------------------
    1798 -        def get_first_encounter(self, issue_id=None, episode_id=None):
    1799                  """Retrieves first encounter for a particular issue and/or episode.
    1800
    1801                  issue_id - First encounter associated health issue
    1802                  episode - First encounter associated episode
    1803                  """
    1804                  # FIXME: use direct query
    1805                  if issue_id is None:
    1806                          issues = None
    1807                  else:
    1808                          issues = [issue_id]
    1809
    1810                  if episode_id is None:
    1811                          episodes = None
    1812                  else:
    1813                          episodes = [episode_id]
    1814
    1815                  encounters = self.get_encounters(issues=issues, episodes=episodes)
    1816                  if len(encounters) == 0:
    1817                          return None
    1818
    1819                  # FIXME: this does not scale particularly well, I assume
    1820                  encounters.sort(lambda x,y: cmp(x['started'], y['started']))
    1821                  return encounters[0]
    1822          #--------------------------------------------------------
    1823 -        def get_earliest_care_date(self):
    1824                  args = {'pat': self.pk_patient}
    1825                  cmd = u"""
    1826  SELECT MIN(earliest) FROM (
    1827          (
    1828                  SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
    1829
    1830          ) UNION ALL (
    1831
    1832                  SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
    1833
    1834          ) UNION ALL (
    1835
    1836                  SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
    1837
    1838          ) UNION ALL (
    1839
    1840                  SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
    1841
    1842          ) UNION ALL (
    1843
    1844                  SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
    1845
    1846          ) UNION ALL (
    1847
    1848                  SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
    1849
    1850          ) UNION ALL (
    1851
    1852                  SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
    1853
    1854          )
    1855  ) AS candidates"""
    1856                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1857                  return rows[0][0]
    1858
    1859          earliest_care_date = property(get_earliest_care_date, lambda x:x)
    1860          #--------------------------------------------------------
    1861 -        def get_last_encounter(self, issue_id=None, episode_id=None):
    1862                  """Retrieves last encounter for a concrete issue and/or episode
    1863
    1864                  issue_id - Last encounter associated health issue
    1865                  episode_id - Last encounter associated episode
    1866                  """
    1867                  # FIXME: use direct query
    1868
    1869                  if issue_id is None:
    1870                          issues = None
    1871                  else:
    1872                          issues = [issue_id]
    1873
    1874                  if episode_id is None:
    1875                          episodes = None
    1876                  else:
    1877                          episodes = [episode_id]
    1878
    1879                  encounters = self.get_encounters(issues=issues, episodes=episodes)
    1880                  if len(encounters) == 0:
    1881                          return None
    1882
    1883                  # FIXME: this does not scale particularly well, I assume
    1884                  encounters.sort(lambda x,y: cmp(x['started'], y['started']))
    1885                  return encounters[-1]
    1886
    1887          last_encounter = property(get_last_encounter, lambda x:x)
    1888          #------------------------------------------------------------------
    1889 -        def get_encounter_stats_by_type(self, cover_period=None):
    1890                  args = {'pat': self.pk_patient, 'range': cover_period}
    1891                  where_parts = [u'pk_patient = %(pat)s']
    1892                  if cover_period is not None:
    1893                          where_parts.append(u'last_affirmed > now() - %(range)s')
    1894
    1895                  cmd = u"""
    1896                          SELECT l10n_type, count(1) AS frequency
    1897                          FROM clin.v_pat_encounters
    1898                          WHERE
    1899                                  %s
    1900                          GROUP BY l10n_type
    1901                          ORDER BY frequency DESC
    1902                  """ % u' AND '.join(where_parts)
    1903                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1904                  return rows
    1905          #------------------------------------------------------------------
    1906 -        def get_last_but_one_encounter(self, issue_id=None, episode_id=None):
    1907
    1908                  args = {'pat': self.pk_patient}
    1909
    1910                  if (issue_id is None) and (episode_id is None):
    1911                          cmd = u"""
    1912                                  SELECT * FROM clin.v_pat_encounters
    1913                                  WHERE pk_patient = %(pat)s
    1914                                  ORDER BY started DESC
    1915                                  LIMIT 2
    1916                          """
    1917                  else:
    1918                          where_parts = []
    1919
    1920                          if issue_id is not None:
    1921                                  where_parts.append(u'pk_health_issue = %(issue)s')
    1922                                  args['issue'] = issue_id
    1923
    1924                          if episode_id is not None:
    1925                                  where_parts.append(u'pk_episode = %(epi)s')
    1926                                  args['epi'] = episode_id
    1927
    1928                          cmd = u"""
    1929                                  SELECT *
    1930                                  FROM clin.v_pat_encounters
    1931                                  WHERE
    1932                                          pk_patient = %%(pat)s
    1933                                                  AND
    1934                                          pk_encounter IN (
    1935                                                  SELECT distinct pk_encounter
    1936                                                  FROM clin.v_narrative
    1937                                                  WHERE
    1938                                                          %s
    1939                                          )
    1940                                  ORDER BY started DESC
    1941                                  LIMIT 2
    1942                          """ % u' AND '.join(where_parts)
    1943
    1944                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1945
    1946                  if len(rows) == 0:
    1947                          return None
    1948
    1949                  # just one encounter within the above limits
    1950                  if len(rows) == 1:
    1951                          # is it the current encounter ?
    1952                          if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
    1953                                  # yes
    1954                                  return None
    1955                          # no
    1956                          return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
    1957
    1958                  # more than one encounter
    1959                  if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
    1960                          return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
    1961
    1962                  return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
    1963          #------------------------------------------------------------------
    1964 -        def remove_empty_encounters(self):
    1965                  cfg_db = gmCfg.cCfgSQL()
    1966                  ttl = cfg_db.get2 (
    1967                          option = u'encounter.ttl_if_empty',
    1968                          workplace = _here.active_workplace,
    1969                          bias = u'user',
    1970                          default = u'1 week'
    1971                  )
    1972
    1973  #               # FIXME: this should be done async
    1974                  cmd = u"SELECT clin.remove_old_empty_encounters(%(pat)s::INTEGER, %(ttl)s::INTERVAL)"
    1975                  args = {'pat': self.pk_patient, 'ttl': ttl}
    1976                  try:
    1977                          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    1978                  except:
    1979                          _log.exception('error deleting empty encounters')
    1980
    1981                  return True
    1982          #------------------------------------------------------------------
    1983          # API: measurements / test results
    1984          #------------------------------------------------------------------
    1985 -        def get_most_recent_results(self, test_type=None, loinc=None, no_of_results=1):
    1986                  return gmPathLab.get_most_recent_results (
    1987                          test_type = test_type,
    1988                          loinc = loinc,
    1989                          no_of_results = no_of_results,
    1990                          patient = self.pk_patient
    1991                  )
    1992          #------------------------------------------------------------------
    1993 -        def get_result_at_timestamp(self, timestamp=None, test_type=None, loinc=None, tolerance_interval='12 hours'):
    1994                  return gmPathLab.get_result_at_timestamp (
    1995                          timestamp = timestamp,
    1996                          test_type = test_type,
    1997                          loinc = loinc,
    1998                          tolerance_interval = tolerance_interval,
    1999                          patient = self.pk_patient
    2000                  )
    2001          #------------------------------------------------------------------
    2002 -        def get_unsigned_results(self, order_by=None):
    2003                  if order_by is None:
    2004                          order_by = u''
    2005                  else:
    2006                          order_by = u'ORDER BY %s' % order_by
    2007                  cmd = u"""
    2008                          SELECT * FROM clin.v_test_results
    2009                          WHERE
    2010                                  pk_patient = %%(pat)s
    2011                                          AND
    2012                                  reviewed IS FALSE
    2013                          %s""" % order_by
    2014                  args = {'pat': self.pk_patient}
    2015                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2016                  return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
    2017          #------------------------------------------------------------------
    2018          # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
    2019 -        def get_test_types_for_results(self):
    2020                  """Retrieve data about test types for which this patient has results."""
    2021
    2022                  cmd = u"""
    2023                  SELECT * FROM (
    2024                          SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
    2025                          FROM clin.v_test_results
    2026                          WHERE pk_patient = %(pat)s
    2027                  ) AS foo
    2028                  ORDER BY clin_when desc, unified_name
    2029                  """
    2030                  args = {'pat': self.pk_patient}
    2031                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    2032                  return [ gmPathLab.cMeasurementType(aPK_obj = row['pk_test_type']) for row in rows ]
    2033          #------------------------------------------------------------------
    2034 -        def get_dates_for_results(self, tests=None, reverse_chronological=True):
    2035                  """Get the dates for which we have results."""
    2036                  where_parts = [u'pk_patient = %(pat)s']
    2037                  args = {'pat': self.pk_patient}
    2038
    2039                  if tests is not None:
    2040                          where_parts.append(u'pk_test_type IN %(tests)s')
    2041                          args['tests'] = tuple(tests)
    2042
    2043                  cmd = u"""
    2044                          SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
    2045                          FROM clin.v_test_results
    2046                          WHERE %s
    2047                          ORDER BY cwhen %s
    2048                  """ % (
    2049                          u' AND '.join(where_parts),
    2050                          gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
    2051                  )
    2052                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    2053                  return rows
    2054          #------------------------------------------------------------------
    2055 -        def get_test_results(self, encounters=None, episodes=None, tests=None, order_by=None):
    2056                  return gmPathLab.get_test_results (
    2057                          pk_patient = self.pk_patient,
    2058                          encounters = encounters,
    2059                          episodes = episodes,
    2060                          order_by = order_by
    2061                  )
    2062          #------------------------------------------------------------------
    2063 -        def get_test_results_by_date(self, encounter=None, episodes=None, tests=None, reverse_chronological=True):
    2064
    2065                  where_parts = [u'pk_patient = %(pat)s']
    2066                  args = {'pat': self.pk_patient}
    2067
    2068                  if tests is not None:
    2069                          where_parts.append(u'pk_test_type IN %(tests)s')
    2070                          args['tests'] = tuple(tests)
    2071
    2072                  if encounter is not None:
    2073                          where_parts.append(u'pk_encounter = %(enc)s')
    2074                          args['enc'] = encounter
    2075
    2076                  if episodes is not None:
    2077                          where_parts.append(u'pk_episode IN %(epis)s')
    2078                          args['epis'] = tuple(episodes)
    2079
    2080                  cmd = u"""
    2081                          SELECT * FROM clin.v_test_results
    2082                          WHERE %s
    2083                          ORDER BY clin_when %s, pk_episode, unified_name
    2084                  """ % (
    2085                          u' AND '.join(where_parts),
    2086                          gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
    2087                  )
    2088                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2089
    2090                  tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
    2091
    2092                  return tests
    2093          #------------------------------------------------------------------
    2094 -        def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
    2095
    2096                  try:
    2097                          epi = int(episode)
    2098                  except:
    2099                          epi = episode['pk_episode']
    2100
    2101                  try:
    2102                          type = int(type)
    2103                  except:
    2104                          type = type['pk_test_type']
    2105
    2106                  if intended_reviewer is None:
    2107                          intended_reviewer = gmStaff.gmCurrentProvider()['pk_staff']
    2108
    2109                  tr = gmPathLab.create_test_result (
    2110                          encounter = self.current_encounter['pk_encounter'],
    2111                          episode = epi,
    2112                          type = type,
    2113                          intended_reviewer = intended_reviewer,
    2114                          val_num = val_num,
    2115                          val_alpha = val_alpha,
    2116                          unit = unit
    2117                  )
    2118
    2119                  return tr
    2120          #------------------------------------------------------------------
    2121          #------------------------------------------------------------------
    2122          #------------------------------------------------------------------
    2123          #------------------------------------------------------------------
    2124 -        def get_lab_request(self, pk=None, req_id=None, lab=None):
    2125                  # FIXME: verify that it is our patient ? ...
    2126                  req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
    2127                  return req
    2128          #------------------------------------------------------------------
    2129 -        def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
    2130                  if encounter_id is None:
    2131                          encounter_id = self.current_encounter['pk_encounter']
    2132                  status, data = gmPathLab.create_lab_request(
    2133                          lab=lab,
    2134                          req_id=req_id,
    2135                          pat_id=self.pk_patient,
    2136                          encounter_id=encounter_id,
    2137                          episode_id=episode_id
    2138                  )
    2139                  if not status:
    2140                          _log.error(str(data))
    2141                          return None
    2142                  return data
    2143
    2144  #============================================================
    2145  # main
    2146  #------------------------------------------------------------
    2147  if __name__ == "__main__":
    2148
    2149          if len(sys.argv) == 1:
    2150                  sys.exit()
    2151
    2152          if sys.argv[1] != 'test':
    2153                  sys.exit()
    2154
    2155          from Gnumed.pycommon import gmLog2
    2156          #-----------------------------------------
    2157 -        def test_allergy_state():
    2158                  emr = cClinicalRecord(aPKey=1)
    2159                  state = emr.allergy_state
    2160                  print "allergy state is:", state
    2161
    2162                  print "setting state to 0"
    2163                  emr.allergy_state = 0
    2164
    2165                  print "setting state to None"
    2166                  emr.allergy_state = None
    2167
    2168                  print "setting state to 'abc'"
    2169                  emr.allergy_state = 'abc'
    2170          #-----------------------------------------
    2171 -        def test_get_test_names():
    2172                  emr = cClinicalRecord(aPKey=12)
    2173                  rows = emr.get_test_types_for_results()
    2174                  print "test result names:"
    2175                  for row in rows:
    2176                          print row
    2177          #-----------------------------------------
    2178 -        def test_get_dates_for_results():
    2179                  emr = cClinicalRecord(aPKey=12)
    2180                  rows = emr.get_dates_for_results()
    2181                  print "test result dates:"
    2182                  for row in rows:
    2183                          print row
    2184          #-----------------------------------------
    2185 -        def test_get_measurements():
    2186                  emr = cClinicalRecord(aPKey=12)
    2187                  rows, idx = emr.get_measurements_by_date()
    2188                  print "test results:"
    2189                  for row in rows:
    2190                          print row
    2191          #-----------------------------------------
    2192 -        def test_get_test_results_by_date():
    2193                  emr = cClinicalRecord(aPKey=12)
    2194                  tests = emr.get_test_results_by_date()
    2195                  print "test results:"
    2196                  for test in tests:
    2197                          print test
    2198          #-----------------------------------------
    2199 -        def test_get_statistics():
    2200                  emr = cClinicalRecord(aPKey=12)
    2201                  for key, item in emr.get_statistics().iteritems():
    2202                          print key, ":", item
    2203          #-----------------------------------------
    2204 -        def test_get_problems():
    2205                  emr = cClinicalRecord(aPKey=12)
    2206
    2207                  probs = emr.get_problems()
    2208                  print "normal probs (%s):" % len(probs)
    2209                  for p in probs:
    2210                          print u'%s (%s)' % (p['problem'], p['type'])
    2211
    2212                  probs = emr.get_problems(include_closed_episodes=True)
    2213                  print "probs + closed episodes (%s):" % len(probs)
    2214                  for p in probs:
    2215                          print u'%s (%s)' % (p['problem'], p['type'])
    2216
    2217                  probs = emr.get_problems(include_irrelevant_issues=True)
    2218                  print "probs + issues (%s):" % len(probs)
    2219                  for p in probs:
    2220                          print u'%s (%s)' % (p['problem'], p['type'])
    2221
    2222                  probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
    2223                  print "probs + issues + epis (%s):" % len(probs)
    2224                  for p in probs:
    2225                          print u'%s (%s)' % (p['problem'], p['type'])
    2226          #-----------------------------------------
    2227 -        def test_add_test_result():
    2228                  emr = cClinicalRecord(aPKey=12)
    2229                  tr = emr.add_test_result (
    2230                          episode = 1,
    2231                          intended_reviewer = 1,
    2232                          type = 1,
    2233                          val_num = 75,
    2234                          val_alpha = u'somewhat obese',
    2235                          unit = u'kg'
    2236                  )
    2237                  print tr
    2238          #-----------------------------------------
    2239 -        def test_get_most_recent_episode():
    2240                  emr = cClinicalRecord(aPKey=12)
    2241                  print emr.get_most_recent_episode(issue = 2)
    2242          #-----------------------------------------
    2243 -        def test_get_almost_recent_encounter():
    2244                  emr = cClinicalRecord(aPKey=12)
    2245                  print emr.get_last_encounter(issue_id=2)
    2246                  print emr.get_last_but_one_encounter(issue_id=2)
    2247          #-----------------------------------------
    2248 -        def test_get_meds():
    2249                  emr = cClinicalRecord(aPKey=12)
    2250                  for med in emr.get_current_substance_intakes():
    2251                          print med
    2252          #-----------------------------------------
    2253 -        def test_is_allergic_to():
    2254                  emr = cClinicalRecord(aPKey = 12)
    2255                  print emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), brand = sys.argv[2])
    2256          #-----------------------------------------
    2257 -        def test_get_as_journal():
    2258                  emr = cClinicalRecord(aPKey = 12)
    2259                  for journal_line in emr.get_as_journal():
    2260                          #print journal_line.keys()
    2261                          print u'%(date)s  %(modified_by)s  %(soap_cat)s  %(narrative)s' % journal_line
    2262                          print ""
    2263          #-----------------------------------------
    2264 -        def test_get_most_recent():
    2265                  emr = cClinicalRecord(aPKey=12)
    2266                  print emr.get_most_recent_results()
    2267          #-----------------------------------------
    2268 -        def test_episodes():
    2269                  emr = cClinicalRecord(aPKey=12)
    2270                  print "episodes:", emr.episodes
    2271                  print "unlinked:", emr.unlinked_episodes
    2272
    2273          #-----------------------------------------
    2274 -        def test_format_as_journal():
    2275                  emr = cClinicalRecord(aPKey=12)
    2276                  from Gnumed.business.gmPerson import cPatient
    2277                  pat = cPatient(aPK_obj = 12)
    2278                  print emr.format_as_journal(left_margin = 1, patient = pat)
    2279          #-----------------------------------------
    2280
    2281          #test_allergy_state()
    2282          #test_is_allergic_to()
    2283
    2284          #test_get_test_names()
    2285          #test_get_dates_for_results()
    2286          #test_get_measurements()
    2287          #test_get_test_results_by_date()
    2288          #test_get_statistics()
    2289          #test_get_problems()
    2290          #test_add_test_result()
    2291          #test_get_most_recent_episode()
    2292          #test_get_almost_recent_encounter()
    2293          #test_get_meds()
    2294          #test_get_as_journal()
    2295          #test_get_most_recent()
    2296          #test_episodes()
    2297          test_format_as_journal()
    2298
    2299  #       emr = cClinicalRecord(aPKey = 12)
    2300
    2301  #       # Vacc regimes
    2302  #       vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
    2303  #       print '\nVaccination regimes: '
    2304  #       for a_regime in vacc_regimes:
    2305  #               pass
    2306  #               #print a_regime
    2307  #       vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
    2308  #       #print vacc_regime
    2309
    2310  #       # vaccination regimes and vaccinations for regimes
    2311  #       scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
    2312  #       print 'Vaccinations for the regime:'
    2313  #       for a_scheduled_vacc in scheduled_vaccs:
    2314  #               pass
    2315  #               #print '   %s' %(a_scheduled_vacc)
    2316
    2317  #       # vaccination next shot and booster
    2318  #       vaccinations = emr.get_vaccinations()
    2319  #       for a_vacc in vaccinations:
    2320  #               print '\nVaccination %s , date: %s, booster: %s, seq no: %s' %(a_vacc['batch_no'], a_vacc['date'].strftime('%Y-%m-%d'), a_vacc['is_booster'], a_vacc['seq_no'])
    2321
    2322  #       # first and last encounters
    2323  #       first_encounter = emr.get_first_encounter(issue_id = 1)
    2324  #       print '\nFirst encounter: ' + str(first_encounter)
    2325  #       last_encounter = emr.get_last_encounter(episode_id = 1)
    2326  #       print '\nLast encounter: ' + str(last_encounter)
    2327  #       print ''
    2328
    2329          #dump = record.get_missing_vaccinations()
    2330          #f = open('vaccs.lst', 'wb')
    2331          #if dump is not None:
    2332          #       print "=== due ==="
    2333          #       f.write("=== due ===\n")
    2334          #       for row in dump['due']:
    2335          #               print row
    2336          #               f.write(repr(row))
    2337          #               f.write('\n')
    2338          #       print "=== overdue ==="
    2339          #       f.write("=== overdue ===\n")
    2340          #       for row in dump['overdue']:
    2341          #               print row
    2342          #               f.write(repr(row))
    2343          #               f.write('\n')
    2344          #f.close()
    2345