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

# Source Code for Module Gnumed.business.gmClinicalRecord

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