Gnumed.business.gmClinicalRecord

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

# Source Code for Module Gnumed.business.gmClinicalRecord

       1  # -*- coding: utf-8 -*-
       2  """GNUmed clinical patient record."""
       3  #============================================================
       4  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
       5  __license__ = "GPL v2 or later"
       6
       7  # standard libs
       8  import sys
       9  import logging
      10  import threading
      11  import datetime as pydt
      12
      13  if __name__ == '__main__':
      14          sys.path.insert(0, '../../')
      15
      16  from Gnumed.pycommon import gmI18N
      17  from Gnumed.pycommon import gmDateTime
      18
      19  if __name__ == '__main__':
      20          from Gnumed.pycommon import gmLog2
      21          gmI18N.activate_locale()
      22          gmI18N.install_domain()
      23          gmDateTime.init()
      24
      25  from Gnumed.pycommon import gmExceptions
      26  from Gnumed.pycommon import gmPG2
      27  from Gnumed.pycommon import gmDispatcher
      28  from Gnumed.pycommon import gmCfg
      29  from Gnumed.pycommon import gmTools
      30
      31  from Gnumed.business import gmAllergy
      32  from Gnumed.business import gmPathLab
      33  from Gnumed.business import gmLOINC
      34  from Gnumed.business import gmClinNarrative
      35  from Gnumed.business import gmSoapDefs
      36  from Gnumed.business import gmEMRStructItems
      37  from Gnumed.business import gmMedication
      38  from Gnumed.business import gmVaccination
      39  from Gnumed.business import gmFamilyHistory
      40  from Gnumed.business import gmExternalCare
      41  from Gnumed.business import gmOrganization
      42  from Gnumed.business import gmAutoHints
      43  from Gnumed.business.gmDemographicRecord import get_occupations
      44
      45
      46  _log = logging.getLogger('gm.emr')
      47
      48  _here = None
      49  #============================================================
      50  # helper functions
      51  #------------------------------------------------------------
      52  #_func_ask_user = None
      53  #
      54  #def set_func_ask_user(a_func = None):
      55  #       if not callable(a_func):
      56  #               _log.error('[%] not callable, not setting _func_ask_user', a_func)
      57  #               return False
      58  #
      59  #       _log.debug('setting _func_ask_user to [%s]', a_func)
      60  #
      61  #       global _func_ask_user
      62  #       _func_ask_user = a_func
      63
      64  #============================================================
      65  _map_clin_root_item2type_str = {
      66          'clin.encounter': _('Encounter'),
      67          'clin.episode': _('Episode'),
      68          'clin.health_issue': _('Health issue'),
      69          'clin.external_care': _('External care'),
      70          'clin.vaccination': _('Vaccination'),
      71          'clin.clin_narrative': _('Clinical narrative'),
      72          'clin.test_result': _('Test result'),
      73          'clin.substance_intake': _('Substance intake'),
      74          'clin.hospital_stay': _('Hospital stay'),
      75          'clin.procedure': _('Performed procedure'),
      76          'clin.allergy': _('Allergy'),
      77          'clin.allergy_state': _('Allergy state'),
      78          'clin.family_history': _('Family history'),
      79          'blobs.doc_med': _('Document'),
      80          'dem.message_inbox': _('Inbox message'),
      81          'ref.auto_hint': _('Dynamic hint')
      82  }
      83
      84 -def format_clin_root_item_type(table):
      85          try:
      86                  return _map_clin_root_item2type_str[table]
      87          except KeyError:
      88                  return _('unmapped entry type from table [%s]') % table
      89
      90  #------------------------------------------------------------
      91  from Gnumed.business.gmDocuments import cDocument
      92  from Gnumed.business.gmProviderInbox import cInboxMessage
      93
      94  _map_table2class = {
      95          'clin.encounter': gmEMRStructItems.cEncounter,
      96          'clin.episode': gmEMRStructItems.cEpisode,
      97          'clin.health_issue': gmEMRStructItems.cHealthIssue,
      98          'clin.external_care': gmExternalCare.cExternalCareItem,
      99          'clin.vaccination': gmVaccination.cVaccination,
     100          'clin.clin_narrative': gmClinNarrative.cNarrative,
     101          'clin.test_result': gmPathLab.cTestResult,
     102          'clin.substance_intake': gmMedication.cSubstanceIntakeEntry,
     103          'clin.hospital_stay': gmEMRStructItems.cHospitalStay,
     104          'clin.procedure': gmEMRStructItems.cPerformedProcedure,
     105          'clin.allergy': gmAllergy.cAllergy,
     106          'clin.allergy_state': gmAllergy.cAllergyState,
     107          'clin.family_history': gmFamilyHistory.cFamilyHistory,
     108          'clin.suppressed_hint': gmAutoHints.cSuppressedHint,
     109          'blobs.doc_med': cDocument,
     110          'dem.message_inbox': cInboxMessage,
     111          'ref.auto_hint': gmAutoHints.cDynamicHint
     112  }
     113
     114 -def instantiate_clin_root_item(table, pk):
     115          try:
     116                  item_class = _map_table2class[table]
     117          except KeyError:
     118                  _log.error('unmapped clin_root_item entry [%s], cannot instantiate', table)
     119                  return None
     120
     121          return item_class(aPK_obj = pk)
     122
     123  #------------------------------------------------------------
     124 -def format_clin_root_item(table, pk, patient=None):
     125
     126          instance = instantiate_clin_root_item(table, pk)
     127          if instance is None:
     128                  return _('cannot instantiate clinical root item <%s(%s)>' % (table, pk))
     129
     130  #       if patient is not None:
     131  #               if patient.ID != instance['pk_patient']:
     132  #                       raise ValueError(u'patient passed in: [%s], but instance is: [%s:%s:%s]' % (patient.ID, table, pk, instance['pk_patient']))
     133
     134          if hasattr(instance, 'format_maximum_information'):
     135                  return '\n'.join(instance.format_maximum_information(patient = patient))
     136
     137          if hasattr(instance, 'format'):
     138                  try:
     139                          formatted = instance.format(patient = patient)
     140                  except TypeError:
     141                          formatted = instance.format()
     142                  if type(formatted) == type([]):
     143                          return '\n'.join(formatted)
     144                  return formatted
     145
     146          d = instance.fields_as_dict (
     147                  date_format = '%Y %b %d  %H:%M',
     148                  none_string = gmTools.u_diameter,
     149                  escape_style = None,
     150                  bool_strings = [_('True'), _('False')]
     151          )
     152          return gmTools.format_dict_like(d, tabular = True, value_delimiters = None)
     153
     154  #============================================================
     155 -def __noop_delayed_execute(*args, **kwargs):
     156          pass
     157
     158
     159  _delayed_execute = __noop_delayed_execute
     160
     161
     162 -def set_delayed_executor(executor):
     163          if not callable(executor):
     164                  raise TypeError('executor <%s> is not callable' % executor)
     165          global _delayed_execute
     166          _delayed_execute = executor
     167          _log.debug('setting delayed executor to <%s>', executor)
     168
     169  #------------------------------------------------------------
     170 -class cClinicalRecord(object):
     171
     172 -        def __init__(self, aPKey=None):#, allow_user_interaction=True, encounter=None):
     173                  """Fails if
     174
     175                  - no connection to database possible
     176                  - patient referenced by aPKey does not exist
     177                  """
     178                  self.pk_patient = aPKey                 # == identity.pk == primary key
     179                  self.gender = None
     180                  self.dob = None
     181
     182                  from Gnumed.business import gmPraxis
     183                  global _here
     184                  if _here is None:
     185                          _here = gmPraxis.gmCurrentPraxisBranch()
     186
     187                  self.__encounter = None
     188                  self.__setup_active_encounter()
     189
     190                  # register backend notification interests
     191                  # (keep this last so we won't hang on threads when
     192                  #  failing this constructor for other reasons ...)
     193                  if not self._register_interests():
     194                          raise gmExceptions.ConstructorError("cannot register signal interests")
     195
     196                  gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
     197                  #_delayed_execute(gmAllergy.ensure_has_allergy_state, encounter = self.current_encounter['pk_encounter'])
     198
     199                  self.__calculator = None
     200
     201                  _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
     202
     203  #       #--------------------------------------------------------
     204  #       def __old_style_init(self, allow_user_interaction=True):
     205  #
     206  #               _log.error('%s.__old_style_init() used', self.__class__.__name__)
     207  #               print u'*** GNUmed [%s]: __old_style_init() used ***' % self.__class__.__name__
     208  #
     209  #               # FIXME: delegate to worker thread
     210  #               # log access to patient record (HIPAA, for example)
     211  #               cmd = u'SELECT gm.log_access2emr(%(todo)s)'
     212  #               args = {'todo': u'patient [%s]' % self.pk_patient}
     213  #               gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     214  #
     215  #               # load current or create new encounter
     216  #               if _func_ask_user is None:
     217  #                       _log.error('[_func_ask_user] is None')
     218  #                       print "*** GNUmed [%s]: _func_ask_user is not set ***" % self.__class__.__name__
     219  #
     220  ##              # FIXME: delegate to worker thread ?
     221  #               self.remove_empty_encounters()
     222  #
     223  #               self.__encounter = None
     224  #               if not self.__initiate_active_encounter(allow_user_interaction = allow_user_interaction):
     225  #                       raise gmExceptions.ConstructorError("cannot activate an encounter for patient [%s]" % self.pk_patient)
     226  #
     227  ##              # FIXME: delegate to worker thread
     228  #               gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
     229
     230          #--------------------------------------------------------
     231 -        def cleanup(self):
     232                  _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
     233                  if self.__encounter is not None:
     234                          self.__encounter.unlock(exclusive = False)
     235                  return True
     236
     237          #--------------------------------------------------------
     238 -        def log_access(self, action=None):
     239                  if action is None:
     240                          action = 'EMR access for pk_identity [%s]' % self.pk_patient
     241                  args = {'action': action}
     242                  cmd = 'SELECT gm.log_access2emr(%(action)s)'
     243                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     244
     245          #--------------------------------------------------------
     246 -        def _get_calculator(self):
     247                  if self.__calculator is None:
     248                          from Gnumed.business.gmClinicalCalculator import cClinicalCalculator
     249                          self.__calculator = cClinicalCalculator()
     250                          from Gnumed.business.gmPerson import gmCurrentPatient
     251                          curr_pat = gmCurrentPatient()
     252                          if curr_pat.ID == self.pk_patient:
     253                                  self.__calculator.patient = curr_pat
     254                          else:
     255                                  from Gnumed.business.gmPerson import cPatient
     256                                  self.__calculator.patient = cPatient(self.pk_patient)
     257                  return self.__calculator
     258
     259          calculator = property(_get_calculator, lambda x:x)
     260
     261          #--------------------------------------------------------
     262          # messaging
     263          #--------------------------------------------------------
     264 -        def _register_interests(self):
     265                  #gmDispatcher.connect(signal = u'clin.encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
     266                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self.db_modification_callback)
     267
     268                  return True
     269
     270          #--------------------------------------------------------
     271 -        def db_modification_callback(self, **kwds):
     272
     273                  if kwds['table'] != 'clin.encounter':
     274                          return True
     275                  if self.current_encounter is None:
     276                          _log.debug('no local current-encounter, ignoring encounter modification signal')
     277                          return True
     278                  if int(kwds['pk_of_row']) != self.current_encounter['pk_encounter']:
     279                          _log.debug('modified encounter [%s] != local encounter [%s], ignoring signal', kwds['pk_of_row'], self.current_encounter['pk_encounter'])
     280                          return True
     281
     282                  _log.debug('modification of our encounter (%s) signalled (%s)', self.current_encounter['pk_encounter'], kwds['pk_of_row'])
     283
     284                  # get the current encounter as an extra instance
     285                  # from the database to check for changes
     286                  curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
     287
     288                  # the encounter just retrieved and the active encounter
     289                  # have got the same transaction ID so there's no change
     290                  # in the database, there could be a local change in
     291                  # the active encounter but that doesn't matter because
     292                  # no one else can have written to the DB so far
     293                  if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
     294                          _log.debug('same XMIN, no difference between DB and in-client instance of current encounter expected')
     295                          if self.current_encounter.is_modified():
     296                                  _log.error('encounter modification signal from DB with same XMIN as in local in-client instance of encounter BUT local instance ALSO has .is_modified()=True')
     297                                  _log.error('this hints at an error in .is_modified handling')
     298                                  gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
     299                          return True
     300
     301                  # there must have been a change to the active encounter
     302                  # committed to the database from elsewhere,
     303                  # we must fail propagating the change, however, if
     304                  # there are local changes pending
     305                  if self.current_encounter.is_modified():
     306                          gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'signalled enc loaded from DB')
     307                          raise ValueError('unsaved changes in locally active encounter [%s], cannot switch to DB state of encounter [%s]' % (
     308                                  self.current_encounter['pk_encounter'],
     309                                  curr_enc_in_db['pk_encounter']
     310                          ))
     311
     312                  # don't do this: same_payload() does not compare _all_ fields
     313                  # so we can get into a reality disconnect if we don't
     314                  # announce the mod
     315  #               if self.current_encounter.same_payload(another_object = curr_enc_in_db):
     316  #                       _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
     317  #                       return True
     318
     319                  # there was a change in the database from elsewhere,
     320                  # locally, however, we don't have any pending changes,
     321                  # therefore we can propagate the remote change locally
     322                  # without losing anything
     323                  # this really should be the standard case
     324                  gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
     325                  _log.debug('active encounter modified remotely, no locally pending changes, reloading from DB and locally announcing the remote modification')
     326                  self.current_encounter.refetch_payload()
     327                  gmDispatcher.send('current_encounter_modified')
     328
     329                  return True
     330
     331          #--------------------------------------------------------
     332 -        def db_callback_encounter_mod_db(self, **kwds):
     333
     334                  # get the current encounter as an extra instance
     335                  # from the database to check for changes
     336                  curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
     337
     338                  # the encounter just retrieved and the active encounter
     339                  # have got the same transaction ID so there's no change
     340                  # in the database, there could be a local change in
     341                  # the active encounter but that doesn't matter because
     342                  # no one else can have written to the DB so far
     343                  # THIS DOES NOT WORK
     344  #               if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
     345  #                       return True
     346
     347                  # there must have been a change to the active encounter
     348                  # committed to the database from elsewhere,
     349                  # we must fail propagating the change, however, if
     350                  # there are local changes
     351                  if self.current_encounter.is_modified():
     352                          gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
     353                          _log.error('current in client: %s', self.current_encounter)
     354                          raise ValueError('unsaved changes in active encounter [%s], cannot switch [%s]' % (
     355                                  self.current_encounter['pk_encounter'],
     356                                  curr_enc_in_db['pk_encounter']
     357                          ))
     358
     359                  if self.current_encounter.same_payload(another_object = curr_enc_in_db):
     360                          _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
     361                          return True
     362
     363                  # there was a change in the database from elsewhere,
     364                  # locally, however, we don't have any changes, therefore
     365                  # we can propagate the remote change locally without
     366                  # losing anything
     367                  gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
     368                  _log.debug('active encounter modified remotely, reloading from DB and locally announcing the modification')
     369                  self.current_encounter.refetch_payload()
     370                  gmDispatcher.send('current_encounter_modified')
     371
     372                  return True
     373
     374          #--------------------------------------------------------
     375          # API: family history
     376          #--------------------------------------------------------
     377 -        def get_family_history(self, episodes=None, issues=None, encounters=None):
     378                  fhx = gmFamilyHistory.get_family_history (
     379                          order_by = 'l10n_relation, condition',
     380                          patient = self.pk_patient
     381                  )
     382
     383                  if episodes is not None:
     384                          fhx = [ f for f in fhx if f['pk_episode'] in episodes ]
     385
     386                  if issues is not None:
     387                          fhx = [ f for f in fhx if f['pk_health_issue'] in issues ]
     388
     389                  if encounters is not None:
     390                          fhx = [ f for f in fhx if f['pk_encounter'] in encounters ]
     391
     392                  return fhx
     393
     394          #--------------------------------------------------------
     395 -        def add_family_history(self, episode=None, condition=None, relation=None):
     396                  return gmFamilyHistory.create_family_history (
     397                          encounter = self.current_encounter['pk_encounter'],
     398                          episode = episode,
     399                          condition = condition,
     400                          relation = relation
     401                  )
     402
     403          #--------------------------------------------------------
     404          # API: pregnancy
     405          #--------------------------------------------------------
     406 -        def _get_gender(self):
     407                  if self.__gender is not None:
     408                          return self.__gender
     409                  cmd = 'SELECT gender, dob FROM dem.v_all_persons WHERE pk_identity = %(pat)s'
     410                  args = {'pat': self.pk_patient}
     411                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     412                  self.__gender = rows[0]['gender']
     413                  self.__dob = rows[0]['dob']
     414
     415 -        def _set_gender(self, gender):
     416                  self.__gender = gender
     417
     418          gender = property(_get_gender, _set_gender)
     419
     420          #--------------------------------------------------------
     421 -        def _get_dob(self):
     422                  if self.__dob is not None:
     423                          return self.__dob
     424                  cmd = 'SELECT gender, dob FROM dem.v_all_persons WHERE pk_identity = %(pat)s'
     425                  args = {'pat': self.pk_patient}
     426                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     427                  self.__gender = rows[0]['gender']
     428                  self.__dob = rows[0]['dob']
     429
     430 -        def _set_dob(self, dob):
     431                  self.__dob = dob
     432
     433          dob = property(_get_dob, _set_dob)
     434
     435          #--------------------------------------------------------
     436 -        def _get_EDC(self):
     437                  cmd = 'SELECT edc FROM clin.patient WHERE fk_identity = %(pat)s'
     438                  args = {'pat': self.pk_patient}
     439                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     440                  if len(rows) == 0:
     441                          return None
     442                  return rows[0]['edc']
     443
     444 -        def _set_EDC(self, edc):
     445                  cmd = """
     446                          INSERT INTO clin.patient (fk_identity, edc) SELECT
     447                                  %(pat)s,
     448                                  %(edc)s
     449                          WHERE NOT EXISTS (
     450                                  SELECT 1 FROM clin.patient WHERE fk_identity = %(pat)s
     451                          )
     452                          RETURNING pk"""
     453                  args = {'pat': self.pk_patient, 'edc': edc}
     454                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
     455                  if len(rows) == 0:
     456                          cmd = 'UPDATE clin.patient SET edc = %(edc)s WHERE fk_identity = %(pat)s'
     457                          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     458
     459          EDC = property(_get_EDC, _set_EDC)
     460
     461          #--------------------------------------------------------
     462 -        def _EDC_is_fishy(self):
     463                  edc = self.EDC
     464                  if edc is None:
     465                          return False
     466                  if self.gender != 'f':
     467                          return True
     468                  now = gmDateTime.pydt_now_here()
     469                  # mother too young
     470                  if (self.dob + pydt.timedelta(weeks = 5 * 52)) > now:
     471                          return True
     472                  # mother too old
     473                  if (self.dob + pydt.timedelta(weeks = 55 * 52)) < now:
     474                          return True
     475                  # Beulah Hunter, 375 days (http://www.reference.com/motif/health/longest-human-pregnancy-on-record)
     476                  # EDC too far in the future
     477                  if (edc - pydt.timedelta(days = 380)) > now:
     478                          return True
     479                  # even if the pregnancy would have *started* when it
     480                  # was documented to *end* it would be over by now by
     481                  # all accounts
     482                  # EDC too far in the past
     483                  if edc < (now - pydt.timedelta(days = 380)):
     484                          return True
     485
     486          EDC_is_fishy = property(_EDC_is_fishy, lambda x:x)
     487
     488          #--------------------------------------------------------
     489 -        def __normalize_smoking_details(self, details):
     490                  try:
     491                          details['quit_when']
     492                  except KeyError:
     493                          details['quit_when'] = None
     494
     495                  try:
     496                          details['last_confirmed']
     497                          if details['last_confirmed'] is None:
     498                                  details['last_confirmed'] = gmDateTime.pydt_now_here()
     499                  except KeyError:
     500                          details['last_confirmed'] = gmDateTime.pydt_now_here()
     501
     502                  try:
     503                          details['comment']
     504                          if details['comment'].strip() == '':
     505                                  details['comment'] = None
     506                  except KeyError:
     507                          details['comment'] = None
     508
     509                  return details
     510
     511          #--------------------------------------------------------
     512 -        def _get_smoking_status(self):
     513                  use = self.harmful_substance_use
     514                  if use is None:
     515                          return None
     516                  return use['tobacco']
     517
     518 -        def _set_smoking_status(self, status):
     519                  # valid ?
     520                  status_flag, details = status
     521                  self.__harmful_substance_use = None
     522                  args = {
     523                          'pat': self.pk_patient,
     524                          'status': status_flag
     525                  }
     526                  if status_flag is None:
     527                          cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = NULL WHERE fk_identity = %(pat)s'
     528                  elif status_flag == 0:
     529                          details['quit_when'] = None
     530                          args['details'] = gmTools.dict2json(self.__normalize_smoking_details(details))
     531                          cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = %(details)s WHERE fk_identity = %(pat)s'
     532                  else:
     533                          args['details'] = gmTools.dict2json(self.__normalize_smoking_details(details))
     534                          cmd = 'UPDATE clin.patient SET smoking_status = %(status)s, smoking_details = %(details)s WHERE fk_identity = %(pat)s'
     535                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     536
     537          smoking_status = property(_get_smoking_status, _set_smoking_status)
     538
     539          #--------------------------------------------------------
     540 -        def _get_alcohol_status(self):
     541                  use = self.harmful_substance_use
     542                  if use is None:
     543                          return None
     544                  return use['alcohol']
     545
     546 -        def _set_alcohol_status(self, status):
     547                  # valid ?
     548                  harmful, details = status
     549                  self.__harmful_substance_use = None
     550                  args = {'pat': self.pk_patient}
     551                  if harmful is None:
     552                          cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = NULL, c2_details = NULL WHERE fk_identity = %(pat)s'
     553                  elif harmful is False:
     554                          cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = FALSE, c2_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
     555                  else:
     556                          cmd = 'UPDATE clin.patient SET c2_currently_harmful_use = TRUE, c2_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
     557                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     558
     559          alcohol_status = property(_get_alcohol_status, _set_alcohol_status)
     560
     561          #--------------------------------------------------------
     562 -        def _get_drugs_status(self):
     563                  use = self.harmful_substance_use
     564                  if use is None:
     565                          return None
     566                  return use['drugs']
     567
     568 -        def _set_drugs_status(self, status):
     569                  # valid ?
     570                  harmful, details = status
     571                  self.__harmful_substance_use = None
     572                  args = {'pat': self.pk_patient}
     573                  if harmful is None:
     574                          cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = NULL, drugs_details = NULL WHERE fk_identity = %(pat)s'
     575                  elif harmful is False:
     576                          cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = FALSE, drugs_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
     577                  else:
     578                          cmd = 'UPDATE clin.patient SET drugs_currently_harmful_use = TRUE, drugs_details = gm.nullify_empty_string(%(details)s) WHERE fk_identity = %(pat)s'
     579                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     580
     581          drugs_status = property(_get_drugs_status, _set_drugs_status)
     582
     583          #--------------------------------------------------------
     584 -        def _get_harmful_substance_use(self):
     585                  # caching does not take into account status changes from elsewhere
     586                  try:
     587                          self.__harmful_substance_use
     588                  except AttributeError:
     589                          self.__harmful_substance_use = None
     590
     591                  if self.__harmful_substance_use is not None:
     592                          return self.__harmful_substance_use
     593
     594                  args = {'pat': self.pk_patient}
     595                  cmd = """
     596                          SELECT
     597                                  -- tobacco use
     598                                  smoking_status,
     599                                  smoking_details,
     600                                  (smoking_details->>'last_confirmed')::timestamp with time zone
     601                                          AS ts_last,
     602                                  (smoking_details->>'quit_when')::timestamp with time zone
     603                                          AS ts_quit,
     604                                  -- c2 use
     605                                  c2_currently_harmful_use,
     606                                  c2_details,
     607                                  -- other drugs use
     608                                  drugs_currently_harmful_use,
     609                                  drugs_details
     610                          FROM clin.patient
     611                          WHERE fk_identity = %(pat)s
     612                  """
     613                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     614                  if len(rows) == 0:
     615                          return None
     616                  # disentangle smoking
     617                  status = rows[0]['smoking_status']
     618                  details = rows[0]['smoking_details']
     619                  if status is not None:
     620                          details['last_confirmed'] = rows[0]['ts_last']
     621                          details['quit_when'] = rows[0]['ts_quit']
     622                  # set fields
     623                  self.__harmful_substance_use = {
     624                          'tobacco':  (status, details),
     625                          'alcohol': (rows[0]['c2_currently_harmful_use'], rows[0]['c2_details']),
     626                          'drugs': (rows[0]['drugs_currently_harmful_use'], rows[0]['drugs_details'])
     627                  }
     628
     629                  return self.__harmful_substance_use
     630
     631
     632 -        def _get_harmful_substance_use2(self):
     633                  cmd = 'SELECT * FROM clin.v_substance_intakes WHERE harmful_use_type  = %s'
     634
     635          harmful_substance_use = property(_get_harmful_substance_use, lambda x:x)
     636
     637          #--------------------------------------------------------
     638 -        def format_harmful_substance_use(self, include_tobacco=True, include_alcohol=True, include_drugs=True, include_nonuse=True, include_unknown=True):
     639                  use = self.harmful_substance_use
     640                  if use is None:
     641                          return []
     642
     643                  lines = []
     644
     645                  if include_tobacco:
     646                          status, details = use['tobacco']
     647                          add_details = False
     648                          if status is None:
     649                                  if include_unknown:
     650                                          lines.append(_('unknown smoking status'))
     651                          elif status == 0:
     652                                  if include_nonuse:
     653                                          lines.append('%s (%s)' % (_('non-smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
     654                                          add_details = True
     655                          elif status == 1:               # now or previous
     656                                  if details['quit_when'] is None:
     657                                          lines.append('%s (%s)' % (_('current smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
     658                                          add_details = True
     659                                  elif details['quit_when'] < gmDateTime.pydt_now_here():
     660                                          if include_nonuse:
     661                                                  lines.append('%s (%s)' % (_('ex-smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
     662                                                  add_details = True
     663                                  else:
     664                                          lines.append('%s (%s)' % (_('current smoker'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
     665                                          add_details = True
     666                          elif status == 2:               # addicted
     667                                  lines.append('%s (%s)' % (_('tobacco addiction'), gmDateTime.pydt_strftime(details['last_confirmed'], '%Y %b %d')))
     668                                  add_details = True
     669                          if add_details:
     670                                  if details['quit_when'] is not None:
     671                                          lines.append(' %s: %s' % (_('Quit date'), gmDateTime.pydt_strftime(details['quit_when'], '%Y %b %d')))
     672                                  if details['comment'] is not None:
     673                                          lines.append(' %s' % details['comment'])
     674
     675                  if include_alcohol:
     676                          status, details = use['alcohol']
     677                          if status is False:
     678                                  if include_nonuse:
     679                                          if len(lines) > 0:
     680                                                  lines.append('')
     681                                          lines.append(_('no or non-harmful alcohol use'))
     682                                          lines.append(' %s' % details)
     683                          elif status is True:
     684                                  if len(lines) > 0:
     685                                          lines.append('')
     686                                  lines.append(_('harmful alcohol use'))
     687                                  lines.append(' %s' % details)
     688                          else:
     689                                  if include_unknown:
     690                                          if len(lines) > 0:
     691                                                  lines.append('')
     692                                          lines.append(_('unknown alcohol use'))
     693                                          lines.append(' %s' % details)
     694
     695                  if include_drugs:
     696                          status, details = use['drugs']
     697                          if status is False:
     698                                  if include_nonuse:
     699                                          if len(lines) > 0:
     700                                                  lines.append('')
     701                                          lines.append(_('no or non-harmful drug use'))
     702                                          lines.append(' %s' % details)
     703                          elif status is True:
     704                                  if len(lines) > 0:
     705                                          lines.append('')
     706                                  lines.append(_('harmful drug use'))
     707                                  lines.append(' %s' % details)
     708                          else:
     709                                  if include_unknown:
     710                                          if len(lines) > 0:
     711                                                  lines.append('')
     712                                          lines.append(_('unknown drug use'))
     713                                          lines.append(' %s' % details)
     714
     715                  return lines
     716
     717          #--------------------------------------------------------
     718 -        def _get_currently_abuses_substances(self):
     719                  # returns True / False / None (= unknown)
     720
     721                  use = self.harmful_substance_use
     722                  # we know that at least one group is used:
     723                  if use['alcohol'][0] is True:
     724                          return True
     725                  if use['drugs'][0] is True:
     726                          return True
     727                  if use['tobacco'][0] > 0:
     728                  # is True:
     729                          if use['tobacco'][1]['quit_when'] is None:
     730                                  return True
     731                  # at this point no group is currently used for sure
     732                  # we don't know about some of the groups so we can NOT say: no abuse at all:
     733                  if use['alcohol'][0] is None:
     734                          return None
     735                  if use['drugs'][0] is None:
     736                          return None
     737                  if use['tobacco'][0] is None:
     738                          return None
     739                  # at this point all groups must be FALSE, except for
     740                  # tobacco which can also be TRUE _but_, if so, a quit
     741                  # date has been set, which is considered non-abuse
     742                  return False
     743
     744          currently_abuses_substances = property(_get_currently_abuses_substances, lambda x:x)
     745
     746          #--------------------------------------------------------
     747          # API: performed procedures
     748          #--------------------------------------------------------
     749 -        def get_performed_procedures(self, episodes=None, issues=None):
     750
     751                  procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient)
     752
     753                  if episodes is not None:
     754                          procs = [ p for p in procs if p['pk_episode'] in episodes ]
     755
     756                  if issues is not None:
     757                          procs = [ p for p in procs if p['pk_health_issue'] in issues ]
     758
     759                  return procs
     760
     761          performed_procedures = property(get_performed_procedures, lambda x:x)
     762          #--------------------------------------------------------
     763 -        def get_latest_performed_procedure(self):
     764                  return gmEMRStructItems.get_latest_performed_procedure(patient = self.pk_patient)
     765          #--------------------------------------------------------
     766 -        def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
     767                  return gmEMRStructItems.create_performed_procedure (
     768                          encounter = self.current_encounter['pk_encounter'],
     769                          episode = episode,
     770                          location = location,
     771                          hospital_stay = hospital_stay,
     772                          procedure = procedure
     773                  )
     774          #--------------------------------------------------------
     775 -        def get_procedure_locations_as_org_units(self):
     776                  where = 'pk_org_unit IN (SELECT DISTINCT pk_org_unit FROM clin.v_procedures_not_at_hospital WHERE pk_patient = %(pat)s)'
     777                  args = {'pat': self.pk_patient}
     778                  cmd = gmOrganization._SQL_get_org_unit % where
     779                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     780                  return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
     781
     782          #--------------------------------------------------------
     783          # API: hospitalizations
     784          #--------------------------------------------------------
     785 -        def get_hospital_stays(self, episodes=None, issues=None, ongoing_only=False):
     786                  stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient, ongoing_only = ongoing_only)
     787                  if episodes is not None:
     788                          stays = [ s for s in stays if s['pk_episode'] in episodes ]
     789                  if issues is not None:
     790                          stays = [ s for s in stays if s['pk_health_issue'] in issues ]
     791                  return stays
     792
     793          hospital_stays = property(get_hospital_stays, lambda x:x)
     794          #--------------------------------------------------------
     795 -        def get_latest_hospital_stay(self):
     796                  return gmEMRStructItems.get_latest_patient_hospital_stay(patient = self.pk_patient)
     797          #--------------------------------------------------------
     798 -        def add_hospital_stay(self, episode=None, fk_org_unit=None):
     799                  return gmEMRStructItems.create_hospital_stay (
     800                          encounter = self.current_encounter['pk_encounter'],
     801                          episode = episode,
     802                          fk_org_unit = fk_org_unit
     803                  )
     804          #--------------------------------------------------------
     805 -        def get_hospital_stay_stats_by_hospital(self, cover_period=None):
     806                  args = {'pat': self.pk_patient, 'range': cover_period}
     807                  where_parts = ['pk_patient = %(pat)s']
     808                  if cover_period is not None:
     809                          where_parts.append('discharge > (now() - %(range)s)')
     810
     811                  cmd = """
     812                          SELECT hospital, count(1) AS frequency
     813                          FROM clin.v_hospital_stays
     814                          WHERE
     815                                  %s
     816                          GROUP BY hospital
     817                          ORDER BY frequency DESC
     818                  """ % ' AND '.join(where_parts)
     819
     820                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     821                  return rows
     822          #--------------------------------------------------------
     823 -        def get_attended_hospitals_as_org_units(self):
     824                  where = 'pk_org_unit IN (SELECT DISTINCT pk_org_unit FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s)'
     825                  args = {'pat': self.pk_patient}
     826                  cmd = gmOrganization._SQL_get_org_unit % where
     827                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     828                  return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
     829
     830          #--------------------------------------------------------
     831          # API: narrative
     832          #--------------------------------------------------------
     833 -        def add_notes(self, notes=None, episode=None, encounter=None):
     834                  enc = gmTools.coalesce (
     835                          encounter,
     836                          self.current_encounter['pk_encounter']
     837                  )
     838                  for note in notes:
     839                          gmClinNarrative.create_narrative_item (
     840                                  narrative = note[1],
     841                                  soap_cat = note[0],
     842                                  episode_id = episode,
     843                                  encounter_id = enc
     844                          )
     845                  return True
     846
     847          #--------------------------------------------------------
     848 -        def add_clin_narrative(self, note='', soap_cat='s', episode=None, link_obj=None):
     849                  if note.strip() == '':
     850                          _log.info('will not create empty clinical note')
     851                          return None
     852                  if isinstance(episode, gmEMRStructItems.cEpisode):
     853                          episode = episode['pk_episode']
     854                  instance = gmClinNarrative.create_narrative_item (
     855                          link_obj = link_obj,
     856                          narrative = note,
     857                          soap_cat = soap_cat,
     858                          episode_id = episode,
     859                          encounter_id = self.current_encounter['pk_encounter']
     860                  )
     861                  return instance
     862
     863          #--------------------------------------------------------
     864 -        def get_clin_narrative(self, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
     865                  """Get SOAP notes pertinent to this encounter.
     866
     867                          encounters
     868                                  - list of encounters the narrative of which are to be retrieved
     869                          episodes
     870                                  - list of episodes the narrative of which are to be retrieved
     871                          issues
     872                                  - list of health issues the narrative of which are to be retrieved
     873                          soap_cats
     874                                  - list of SOAP categories of the narrative to be retrieved
     875                  """
     876                  where_parts = ['pk_patient = %(pat)s']
     877                  args = {'pat': self.pk_patient}
     878
     879                  if issues is not None:
     880                          where_parts.append('pk_health_issue IN %(issues)s')
     881                          if len(issues) == 0:
     882                                  args['issues'] = tuple()
     883                          else:
     884                                  if isinstance(issues[0], gmEMRStructItems.cHealthIssue):
     885                                          args['issues'] = tuple([ i['pk_health_issue'] for i in issues ])
     886                                  elif isinstance(issues[0], int):
     887                                          args['issues'] = tuple(issues)
     888                                  else:
     889                                          raise ValueError('<issues> must be list of type int (=pk) or cHealthIssue, but 1st issue is: %s' % issues[0])
     890
     891                  if episodes is not None:
     892                          where_parts.append('pk_episode IN %(epis)s')
     893                          if len(episodes) == 0:
     894                                  args['epis'] = tuple()
     895                          else:
     896                                  if isinstance(episodes[0], gmEMRStructItems.cEpisode):
     897                                          args['epis'] = tuple([ e['pk_episode'] for e in episodes ])
     898                                  elif isinstance(episodes[0], int):
     899                                          args['epis'] = tuple(episodes)
     900                                  else:
     901                                          raise ValueError('<episodes> must be list of type int (=pk) or cEpisode, but 1st episode is: %s' % episodes[0])
     902
     903                  if encounters is not None:
     904                          where_parts.append('pk_encounter IN %(encs)s')
     905                          if len(encounters) == 0:
     906                                  args['encs'] = tuple()
     907                          else:
     908                                  if isinstance(encounters[0], gmEMRStructItems.cEncounter):
     909                                          args['encs'] = tuple([ e['pk_encounter'] for e in encounters ])
     910                                  elif isinstance(encounters[0], int):
     911                                          args['encs'] = tuple(encounters)
     912                                  else:
     913                                          raise ValueError('<encounters> must be list of type int (=pk) or cEncounter, but 1st encounter is: %s' % encounters[0])
     914
     915                  if soap_cats is not None:
     916                          where_parts.append('c_vn.soap_cat IN %(cats)s')
     917                          args['cats'] = tuple(gmSoapDefs.soap_cats2list(soap_cats))
     918
     919                  if providers is not None:
     920                          where_parts.append('c_vn.modified_by IN %(docs)s')
     921                          args['docs'] = tuple(providers)
     922
     923                  cmd = """
     924                          SELECT
     925                                  c_vn.*,
     926                                  c_scr.rank AS soap_rank
     927                          FROM
     928                                  clin.v_narrative c_vn
     929                                          LEFT JOIN clin.soap_cat_ranks c_scr on c_vn.soap_cat = c_scr.soap_cat
     930                          WHERE %s
     931                          ORDER BY date, soap_rank
     932                  """ % ' AND '.join(where_parts)
     933
     934                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     935                  return [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
     936
     937          #--------------------------------------------------------
     938 -        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):
     939                  return gmClinNarrative.get_as_journal (
     940                          patient = self.pk_patient,
     941                          since = since,
     942                          until = until,
     943                          encounters = encounters,
     944                          episodes = episodes,
     945                          issues = issues,
     946                          soap_cats = soap_cats,
     947                          providers = providers,
     948                          order_by = order_by,
     949                          time_range = time_range,
     950                          active_encounter = self.active_encounter
     951                  )
     952
     953          #--------------------------------------------------------
     954 -        def search_narrative_simple(self, search_term=''):
     955
     956                  search_term = search_term.strip()
     957                  if search_term == '':
     958                          return []
     959
     960                  cmd = """
     961                          SELECT
     962                                  *,
     963                                  coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
     964                                          as episode,
     965                                  coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
     966                                          as health_issue,
     967                                  (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
     968                                          as encounter_started,
     969                                  (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
     970                                          as encounter_ended,
     971                                  (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
     972                                          as encounter_type
     973                          from clin.v_narrative4search vn4s
     974                          WHERE
     975                                  pk_patient = %(pat)s and
     976                                  vn4s.narrative ~ %(term)s
     977                          order by
     978                                  encounter_started
     979                  """ # case sensitive
     980                  rows, idx = gmPG2.run_ro_queries(queries = [
     981                          {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
     982                  ])
     983                  return rows
     984          #--------------------------------------------------------
     985 -        def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
     986                  fields = [
     987                          'age',
     988                          "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
     989                          'modified_by',
     990                          'clin_when',
     991                          "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
     992                          'pk_item',
     993                          'pk_encounter',
     994                          'pk_episode',
     995                          'pk_health_issue',
     996                          'src_table'
     997                  ]
     998                  select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
     999                  # handle constraint conditions
    1000                  where_snippets = []
    1001                  params = {}
    1002                  where_snippets.append('pk_patient=%(pat_id)s')
    1003                  params['pat_id'] = self.pk_patient
    1004                  if not since is None:
    1005                          where_snippets.append('clin_when >= %(since)s')
    1006                          params['since'] = since
    1007                  if not until is None:
    1008                          where_snippets.append('clin_when <= %(until)s')
    1009                          params['until'] = until
    1010                  # FIXME: these are interrelated, eg if we constrain encounter
    1011                  # we automatically constrain issue/episode, so handle that,
    1012                  # encounters
    1013                  if not encounters is None and len(encounters) > 0:
    1014                          params['enc'] = encounters
    1015                          if len(encounters) > 1:
    1016                                  where_snippets.append('fk_encounter in %(enc)s')
    1017                          else:
    1018                                  where_snippets.append('fk_encounter=%(enc)s')
    1019                  # episodes
    1020                  if not episodes is None and len(episodes) > 0:
    1021                          params['epi'] = episodes
    1022                          if len(episodes) > 1:
    1023                                  where_snippets.append('fk_episode in %(epi)s')
    1024                          else:
    1025                                  where_snippets.append('fk_episode=%(epi)s')
    1026                  # health issues
    1027                  if not issues is None and len(issues) > 0:
    1028                          params['issue'] = issues
    1029                          if len(issues) > 1:
    1030                                  where_snippets.append('fk_health_issue in %(issue)s')
    1031                          else:
    1032                                  where_snippets.append('fk_health_issue=%(issue)s')
    1033
    1034                  where_clause = ' and '.join(where_snippets)
    1035                  order_by = 'order by src_table, age'
    1036                  cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
    1037
    1038                  rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
    1039                  if rows is None:
    1040                          _log.error('cannot load item links for patient [%s]' % self.pk_patient)
    1041                          return None
    1042
    1043                  # -- sort the data --
    1044                  # FIXME: by issue/encounter/episode, eg formatting
    1045                  # aggregate by src_table for item retrieval
    1046                  items_by_table = {}
    1047                  for item in rows:
    1048                          src_table = item[view_col_idx['src_table']]
    1049                          pk_item = item[view_col_idx['pk_item']]
    1050                          if src_table not in items_by_table:
    1051                                  items_by_table[src_table] = {}
    1052                          items_by_table[src_table][pk_item] = item
    1053
    1054                  # get mapping for issue/episode IDs
    1055                  issues = self.get_health_issues()
    1056                  issue_map = {}
    1057                  for issue in issues:
    1058                          issue_map[issue['pk_health_issue']] = issue['description']
    1059                  episodes = self.get_episodes()
    1060                  episode_map = {}
    1061                  for episode in episodes:
    1062                          episode_map[episode['pk_episode']] = episode['description']
    1063                  emr_data = {}
    1064                  # get item data from all source tables
    1065                  ro_conn = self._conn_pool.GetConnection('historica')
    1066                  curs = ro_conn.cursor()
    1067                  for src_table in items_by_table.keys():
    1068                          item_ids = items_by_table[src_table].keys()
    1069                          # we don't know anything about the columns of
    1070                          # the source tables but, hey, this is a dump
    1071                          if len(item_ids) == 0:
    1072                                  _log.info('no items in table [%s] ?!?' % src_table)
    1073                                  continue
    1074                          elif len(item_ids) == 1:
    1075                                  cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
    1076                                  if not gmPG.run_query(curs, None, cmd, item_ids[0]):
    1077                                          _log.error('cannot load items from table [%s]' % src_table)
    1078                                          # skip this table
    1079                                          continue
    1080                          elif len(item_ids) > 1:
    1081                                  cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
    1082                                  if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
    1083                                          _log.error('cannot load items from table [%s]' % src_table)
    1084                                          # skip this table
    1085                                          continue
    1086                          rows = curs.fetchall()
    1087                          table_col_idx = gmPG.get_col_indices(curs)
    1088                          # format per-table items
    1089                          for row in rows:
    1090                                  # FIXME: make this get_pkey_name()
    1091                                  pk_item = row[table_col_idx['pk_item']]
    1092                                  view_row = items_by_table[src_table][pk_item]
    1093                                  age = view_row[view_col_idx['age']]
    1094                                  # format metadata
    1095                                  try:
    1096                                          episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
    1097                                  except:
    1098                                          episode_name = view_row[view_col_idx['pk_episode']]
    1099                                  try:
    1100                                          issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
    1101                                  except:
    1102                                          issue_name = view_row[view_col_idx['pk_health_issue']]
    1103
    1104                                  if age not in emr_data:
    1105                                          emr_data[age] = []
    1106
    1107                                  emr_data[age].append(
    1108                                          _('%s: encounter (%s)') % (
    1109                                                  view_row[view_col_idx['clin_when']],
    1110                                                  view_row[view_col_idx['pk_encounter']]
    1111                                          )
    1112                                  )
    1113                                  emr_data[age].append(_('health issue: %s') % issue_name)
    1114                                  emr_data[age].append(_('episode     : %s') % episode_name)
    1115                                  # format table specific data columns
    1116                                  # - ignore those, they are metadata, some
    1117                                  #   are in clin.v_pat_items data already
    1118                                  cols2ignore = [
    1119                                          'pk_audit', 'row_version', 'modified_when', 'modified_by',
    1120                                          'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
    1121                                  ]
    1122                                  col_data = []
    1123                                  for col_name in table_col_idx.keys():
    1124                                          if col_name in cols2ignore:
    1125                                                  continue
    1126                                          emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
    1127                                  emr_data[age].append("----------------------------------------------------")
    1128                                  emr_data[age].append("-- %s from table %s" % (
    1129                                          view_row[view_col_idx['modified_string']],
    1130                                          src_table
    1131                                  ))
    1132                                  emr_data[age].append("-- written %s by %s" % (
    1133                                          view_row[view_col_idx['modified_when']],
    1134                                          view_row[view_col_idx['modified_by']]
    1135                                  ))
    1136                                  emr_data[age].append("----------------------------------------------------")
    1137                  curs.close()
    1138                  return emr_data
    1139          #--------------------------------------------------------
    1140 -        def get_patient_ID(self):
    1141                  return self.pk_patient
    1142          #--------------------------------------------------------
    1143 -        def get_statistics(self):
    1144                  union_query = '\n       union all\n'.join ([
    1145                          """
    1146                                  SELECT ((
    1147                                          -- all relevant health issues + active episodes WITH health issue
    1148                                          SELECT COUNT(1)
    1149                                          FROM clin.v_problem_list
    1150                                          WHERE
    1151                                                  pk_patient = %(pat)s
    1152                                                          AND
    1153                                                  pk_health_issue is not null
    1154                                  ) + (
    1155                                          -- active episodes WITHOUT health issue
    1156                                          SELECT COUNT(1)
    1157                                          FROM clin.v_problem_list
    1158                                          WHERE
    1159                                                  pk_patient = %(pat)s
    1160                                                          AND
    1161                                                  pk_health_issue is null
    1162                                  ))""",
    1163                          'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
    1164                          'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
    1165                          'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
    1166                          'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
    1167                          'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s',
    1168                          'SELECT count(1) FROM clin.v_procedures WHERE pk_patient = %(pat)s',
    1169                          # active and approved substances == medication
    1170                          """
    1171                                  SELECT count(1)
    1172                                  FROM clin.v_substance_intakes
    1173                                  WHERE
    1174                                          pk_patient = %(pat)s
    1175                                                  AND
    1176                                          is_currently_active IN (null, true)
    1177                                                  AND
    1178                                          intake_is_approved_of IN (null, true)""",
    1179                          'SELECT count(1) FROM clin.v_vaccinations WHERE pk_patient = %(pat)s'
    1180                  ])
    1181
    1182                  rows, idx = gmPG2.run_ro_queries (
    1183                          queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
    1184                          get_col_idx = False
    1185                  )
    1186
    1187                  stats = dict (
    1188                          problems = rows[0][0],
    1189                          encounters = rows[1][0],
    1190                          items = rows[2][0],
    1191                          documents = rows[3][0],
    1192                          results = rows[4][0],
    1193                          stays = rows[5][0],
    1194                          procedures = rows[6][0],
    1195                          active_drugs = rows[7][0],
    1196                          vaccinations = rows[8][0]
    1197                  )
    1198
    1199                  return stats
    1200          #--------------------------------------------------------
    1201 -        def format_statistics(self):
    1202                  return _(
    1203                          'Medical problems: %(problems)s\n'
    1204                          'Total encounters: %(encounters)s\n'
    1205                          'Total EMR entries: %(items)s\n'
    1206                          'Active medications: %(active_drugs)s\n'
    1207                          'Documents: %(documents)s\n'
    1208                          'Test results: %(results)s\n'
    1209                          'Hospitalizations: %(stays)s\n'
    1210                          'Procedures: %(procedures)s\n'
    1211                          'Vaccinations: %(vaccinations)s'
    1212                  ) % self.get_statistics()
    1213          #--------------------------------------------------------
    1214 -        def format_summary(self):
    1215
    1216                  cmd = "SELECT dob FROM dem.v_all_persons WHERE pk_identity = %(pk)s"
    1217                  args = {'pk': self.pk_patient}
    1218                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1219                  dob = rows[0]['dob']
    1220
    1221                  stats = self.get_statistics()
    1222                  first = self.get_first_encounter()
    1223                  last = self.get_last_encounter()
    1224                  probs = self.get_problems()
    1225
    1226                  txt = ''
    1227                  if len(probs) > 0:
    1228                          txt += _(' %s known problems, clinically relevant thereof:\n') % stats['problems']
    1229                  else:
    1230                          txt += _(' %s known problems\n') % stats['problems']
    1231                  for prob in probs:
    1232                          if not prob['clinically_relevant']:
    1233                                  continue
    1234                          txt += '   \u00BB%s\u00AB (%s)\n' % (
    1235                                  prob['problem'],
    1236                                  gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive'))
    1237                          )
    1238                  txt += '\n'
    1239                  txt += _(' %s encounters from %s to %s\n') % (
    1240                          stats['encounters'],
    1241                          gmDateTime.pydt_strftime(first['started'], '%Y %b %d'),
    1242                          gmDateTime.pydt_strftime(last['started'], '%Y %b %d')
    1243                  )
    1244                  txt += _(' %s active medications\n') % stats['active_drugs']
    1245                  txt += _(' %s documents\n') % stats['documents']
    1246                  txt += _(' %s test results\n') % stats['results']
    1247                  txt += _(' %s hospitalizations') % stats['stays']
    1248                  if stats['stays'] == 0:
    1249                          txt += '\n'
    1250                  else:
    1251                          txt += _(', most recently:\n%s\n') % self.get_latest_hospital_stay().format(left_margin = 3)
    1252                  # FIXME: perhaps only count "ongoing ones"
    1253                  txt += _(' %s performed procedures') % stats['procedures']
    1254                  if stats['procedures'] == 0:
    1255                          txt += '\n'
    1256                  else:
    1257                          txt += _(', most recently:\n%s\n') % self.get_latest_performed_procedure().format(left_margin = 3)
    1258
    1259                  txt += '\n'
    1260                  txt += _('Allergies and Intolerances\n')
    1261
    1262                  allg_state = self.allergy_state
    1263                  txt += (' ' + allg_state.state_string)
    1264                  if allg_state['last_confirmed'] is not None:
    1265                          txt += _(' (last confirmed %s)') % gmDateTime.pydt_strftime(allg_state['last_confirmed'], '%Y %b %d')
    1266                  txt += '\n'
    1267                  txt += gmTools.coalesce(allg_state['comment'], '', ' %s\n')
    1268                  for allg in self.get_allergies():
    1269                          txt += ' %s: %s\n' % (
    1270                                  allg['descriptor'],
    1271                                  gmTools.coalesce(allg['reaction'], _('unknown reaction'))
    1272                          )
    1273
    1274                  meds = self.get_current_medications(order_by = 'intake_is_approved_of DESC, substance')
    1275                  if len(meds) > 0:
    1276                          txt += '\n'
    1277                          txt += _('Medications and Substances')
    1278                          txt += '\n'
    1279                  for m in meds:
    1280                          txt += '%s\n' % m.format_as_single_line(left_margin = 1)
    1281
    1282                  fhx = self.get_family_history()
    1283                  if len(fhx) > 0:
    1284                          txt += '\n'
    1285                          txt += _('Family History')
    1286                          txt += '\n'
    1287                  for f in fhx:
    1288                          txt += '%s\n' % f.format(left_margin = 1)
    1289
    1290                  jobs = get_occupations(pk_identity = self.pk_patient)
    1291                  if len(jobs) > 0:
    1292                          txt += '\n'
    1293                          txt += _('Occupations')
    1294                          txt += '\n'
    1295                  for job in jobs:
    1296                          txt += ' %s%s\n' % (
    1297                                  job['l10n_occupation'],
    1298                                  gmTools.coalesce(job['activities'], '', ': %s')
    1299                          )
    1300
    1301                  vaccs = self.get_latest_vaccinations()
    1302                  if len(vaccs) > 0:
    1303                          txt += '\n'
    1304                          txt += _('Vaccinations')
    1305                          txt += '\n'
    1306                  inds = sorted(vaccs.keys())
    1307                  for ind in inds:
    1308                          ind_count, vacc = vaccs[ind]
    1309                          if dob is None:
    1310                                  age_given = ''
    1311                          else:
    1312                                  age_given = ' @ %s' % gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age (
    1313                                          start = dob,
    1314                                          end = vacc['date_given']
    1315                                  ))
    1316                          since = _('%s ago') % gmDateTime.format_interval_medically(vacc['interval_since_given'])
    1317                          txt += ' %s (%s%s): %s%s (%s %s%s%s)\n' % (
    1318                                  ind,
    1319                                  gmTools.u_sum,
    1320                                  ind_count,
    1321                                  #gmDateTime.pydt_strftime(vacc['date_given'], '%b %Y'),
    1322                                  since,
    1323                                  age_given,
    1324                                  vacc['vaccine'],
    1325                                  gmTools.u_left_double_angle_quote,
    1326                                  vacc['batch_no'],
    1327                                  gmTools.u_right_double_angle_quote
    1328                          )
    1329
    1330                  care = self.get_external_care_items(order_by = 'issue, organization, unit, provider', exclude_inactive = True)
    1331                  if len(care) > 0:
    1332                          txt += '\n'
    1333                          txt += _('External care')
    1334                          txt += '\n'
    1335                  for item in care:
    1336                          txt += ' %s: %s\n' % (
    1337                                  item['issue'],
    1338                                  gmTools.coalesce (
    1339                                          item['provider'],
    1340                                          '%s@%s' % (item['unit'], item['organization']),
    1341                                          '%%s (%s@%s)' % (item['unit'], item['organization'])
    1342                                  )
    1343                          )
    1344
    1345                  return txt
    1346
    1347          #--------------------------------------------------------
    1348 -        def format_as_journal(self, left_margin=0, patient=None):
    1349                  txt = ''
    1350                  for enc in self.get_encounters(skip_empty = True):
    1351                          txt += gmTools.u_box_horiz_4dashes * 70 + '\n'
    1352                          txt += enc.format (
    1353                                  episodes = None,                        # means: each touched upon
    1354                                  left_margin = left_margin,
    1355                                  patient = patient,
    1356                                  fancy_header = False,
    1357                                  with_soap = True,
    1358                                  with_docs = True,
    1359                                  with_tests = True,
    1360                                  with_vaccinations = True,
    1361                                  with_co_encountlet_hints = False,               # irrelevant
    1362                                  with_rfe_aoe = True,
    1363                                  with_family_history = True,
    1364                                  by_episode = True
    1365                          )
    1366
    1367                  return txt
    1368          #--------------------------------------------------------
    1369          # API: allergy
    1370          #--------------------------------------------------------
    1371 -        def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
    1372                  """Retrieves patient allergy items.
    1373
    1374                          remove_sensitivities
    1375                                  - retrieve real allergies only, without sensitivities
    1376                          since
    1377                                  - initial date for allergy items
    1378                          until
    1379                                  - final date for allergy items
    1380                          encounters
    1381                                  - list of encounters whose allergies are to be retrieved
    1382                          episodes
    1383                                  - list of episodes whose allergies are to be retrieved
    1384                          issues
    1385                                  - list of health issues whose allergies are to be retrieved
    1386          """
    1387                  cmd = "SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
    1388                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
    1389                  filtered_allergies = []
    1390                  for r in rows:
    1391                          filtered_allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
    1392
    1393                  # ok, let's constrain our list
    1394                  if ID_list is not None:
    1395                          filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_allergy'] in ID_list ]
    1396                          if len(filtered_allergies) == 0:
    1397                                  _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
    1398                                  # better fail here contrary to what we do elsewhere
    1399                                  return None
    1400                          else:
    1401                                  return filtered_allergies
    1402
    1403                  if remove_sensitivities:
    1404                          filtered_allergies = [ allg for allg in filtered_allergies if allg['type'] == 'allergy' ]
    1405                  if since is not None:
    1406                          filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] >= since ]
    1407                  if until is not None:
    1408                          filtered_allergies = [ allg for allg in filtered_allergies if allg['date'] < until ]
    1409                  if issues is not None:
    1410                          filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_health_issue'] in issues ]
    1411                  if episodes is not None:
    1412                          filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_episode'] in episodes ]
    1413                  if encounters is not None:
    1414                          filtered_allergies = [ allg for allg in filtered_allergies if allg['pk_encounter'] in encounters ]
    1415
    1416                  return filtered_allergies
    1417          #--------------------------------------------------------
    1418 -        def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
    1419                  if encounter_id is None:
    1420                          encounter_id = self.current_encounter['pk_encounter']
    1421
    1422                  if episode_id is None:
    1423                          issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
    1424                          epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
    1425                          episode_id = epi['pk_episode']
    1426
    1427                  new_allergy = gmAllergy.create_allergy (
    1428                          allergene = allergene,
    1429                          allg_type = allg_type,
    1430                          encounter_id = encounter_id,
    1431                          episode_id = episode_id
    1432                  )
    1433
    1434                  return new_allergy
    1435          #--------------------------------------------------------
    1436 -        def delete_allergy(self, pk_allergy=None):
    1437                  cmd = 'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
    1438                  args = {'pk_allg': pk_allergy}
    1439                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    1440
    1441          #--------------------------------------------------------
    1442 -        def is_allergic_to(self, atcs=None, inns=None, product_name=None):
    1443                  """Cave: only use with one potential allergic agent
    1444                  otherwise you won't know which of the agents the allergy is to."""
    1445
    1446                  # we don't know the state
    1447                  if self.allergy_state is None:
    1448                          return None
    1449
    1450                  # we know there's no allergies
    1451                  if self.allergy_state == 0:
    1452                          return False
    1453
    1454                  args = {
    1455                          'atcs': atcs,
    1456                          'inns': inns,
    1457                          'prod_name': product_name,
    1458                          'pat': self.pk_patient
    1459                  }
    1460                  allergenes = []
    1461                  where_parts = []
    1462
    1463                  if len(atcs) == 0:
    1464                          atcs = None
    1465                  if atcs is not None:
    1466                          where_parts.append('atc_code in %(atcs)s')
    1467                  if len(inns) == 0:
    1468                          inns = None
    1469                  if inns is not None:
    1470                          where_parts.append('generics in %(inns)s')
    1471                          allergenes.extend(inns)
    1472                  if product_name is not None:
    1473                          where_parts.append('substance = %(prod_name)s')
    1474                          allergenes.append(product_name)
    1475
    1476                  if len(allergenes) != 0:
    1477                          where_parts.append('allergene in %(allgs)s')
    1478                          args['allgs'] = tuple(allergenes)
    1479
    1480                  cmd = """
    1481  SELECT * FROM clin.v_pat_allergies
    1482  WHERE
    1483          pk_patient = %%(pat)s
    1484          AND ( %s )""" % ' OR '.join(where_parts)
    1485
    1486                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1487
    1488                  if len(rows) == 0:
    1489                          return False
    1490
    1491                  return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
    1492          #--------------------------------------------------------
    1493 -        def _set_allergy_state(self, state):
    1494
    1495                  if state not in gmAllergy.allergy_states:
    1496                          raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
    1497
    1498                  allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
    1499                  allg_state['has_allergy'] = state
    1500                  allg_state.save_payload()
    1501                  return True
    1502
    1503 -        def _get_allergy_state(self):
    1504                  return gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
    1505
    1506          allergy_state = property(_get_allergy_state, _set_allergy_state)
    1507          #--------------------------------------------------------
    1508          # API: external care
    1509          #--------------------------------------------------------
    1510 -        def get_external_care_items(self, order_by=None, exclude_inactive=False):
    1511                  return gmExternalCare.get_external_care_items (
    1512                          pk_identity = self.pk_patient,
    1513                          order_by = order_by,
    1514                          exclude_inactive = exclude_inactive
    1515                  )
    1516
    1517          external_care_items = property(get_external_care_items, lambda x:x)
    1518
    1519          #--------------------------------------------------------
    1520          # API: episodes
    1521          #--------------------------------------------------------
    1522 -        def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
    1523                  """Fetches from backend patient episodes.
    1524
    1525                  id_list - Episodes' PKs list
    1526                  issues - Health issues' PKs list to filter episodes by
    1527                  open_status - return all (None) episodes, only open (True) or closed (False) one(s)
    1528                  """
    1529                  if (unlinked_only is True) and (issues is not None):
    1530                          raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
    1531
    1532                  if order_by is None:
    1533                          order_by = ''
    1534                  else:
    1535                          order_by = 'ORDER BY %s' % order_by
    1536
    1537                  args = {
    1538                          'pat': self.pk_patient,
    1539                          'open': open_status
    1540                  }
    1541                  where_parts = ['pk_patient = %(pat)s']
    1542
    1543                  if open_status is not None:
    1544                          where_parts.append('episode_open IS %(open)s')
    1545
    1546                  if unlinked_only:
    1547                          where_parts.append('pk_health_issue is NULL')
    1548
    1549                  if issues is not None:
    1550                          where_parts.append('pk_health_issue IN %(issues)s')
    1551                          args['issues'] = tuple(issues)
    1552
    1553                  if id_list is not None:
    1554                          where_parts.append('pk_episode IN %(epis)s')
    1555                          args['epis'] = tuple(id_list)
    1556
    1557                  cmd = "SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
    1558                          ' AND '.join(where_parts),
    1559                          order_by
    1560                  )
    1561                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1562
    1563                  return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
    1564
    1565          episodes = property(get_episodes, lambda x:x)
    1566          #------------------------------------------------------------------
    1567 -        def get_unlinked_episodes(self, open_status=None, order_by=None):
    1568                  return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
    1569
    1570          unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
    1571          #------------------------------------------------------------------
    1572 -        def get_episodes_by_encounter(self, pk_encounter=None):
    1573                  cmd = """SELECT distinct pk_episode
    1574                                          from clin.v_pat_items
    1575                                          WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
    1576                  args = {
    1577                          'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
    1578                          'pat': self.pk_patient
    1579                  }
    1580                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    1581                  if len(rows) == 0:
    1582                          return []
    1583                  epis = []
    1584                  for row in rows:
    1585                          epis.append(row[0])
    1586                  return self.get_episodes(id_list=epis)
    1587          #------------------------------------------------------------------
    1588 -        def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False, allow_dupes=False, link_obj=None):
    1589                  """Add episode 'episode_name' for a patient's health issue.
    1590
    1591                  - silently returns if episode already exists
    1592                  """
    1593                  episode = gmEMRStructItems.create_episode (
    1594                          link_obj = link_obj,
    1595                          pk_health_issue = pk_health_issue,
    1596                          episode_name = episode_name,
    1597                          is_open = is_open,
    1598                          encounter = self.current_encounter['pk_encounter'],
    1599                          allow_dupes = allow_dupes
    1600                  )
    1601                  return episode
    1602          #--------------------------------------------------------
    1603 -        def get_most_recent_episode(self, issue=None):
    1604                  # try to find the episode with the most recently modified clinical item
    1605
    1606                  issue_where = gmTools.coalesce(issue, '', 'and pk_health_issue = %(issue)s')
    1607
    1608                  cmd = """
    1609  SELECT pk
    1610  from clin.episode
    1611  WHERE pk = (
    1612          SELECT distinct on(pk_episode) pk_episode
    1613          from clin.v_pat_items
    1614          WHERE
    1615                  pk_patient = %%(pat)s
    1616                          and
    1617                  modified_when = (
    1618                          SELECT max(vpi.modified_when)
    1619                          from clin.v_pat_items vpi
    1620                          WHERE vpi.pk_patient = %%(pat)s
    1621                  )
    1622                  %s
    1623          -- guard against several episodes created at the same moment of time
    1624          limit 1
    1625          )""" % issue_where
    1626                  rows, idx = gmPG2.run_ro_queries(queries = [
    1627                          {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
    1628                  ])
    1629                  if len(rows) != 0:
    1630                          return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
    1631
    1632                  # no clinical items recorded, so try to find
    1633                  # the youngest episode for this patient
    1634                  cmd = """
    1635  SELECT vpe0.pk_episode
    1636  from
    1637          clin.v_pat_episodes vpe0
    1638  WHERE
    1639          vpe0.pk_patient = %%(pat)s
    1640                  and
    1641          vpe0.episode_modified_when = (
    1642                  SELECT max(vpe1.episode_modified_when)
    1643                  from clin.v_pat_episodes vpe1
    1644                  WHERE vpe1.pk_episode = vpe0.pk_episode
    1645          )
    1646          %s""" % issue_where
    1647                  rows, idx = gmPG2.run_ro_queries(queries = [
    1648                          {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
    1649                  ])
    1650                  if len(rows) != 0:
    1651                          return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
    1652
    1653                  return None
    1654          #--------------------------------------------------------
    1655 -        def episode2problem(self, episode=None):
    1656                  return gmEMRStructItems.episode2problem(episode=episode)
    1657          #--------------------------------------------------------
    1658          # API: problems
    1659          #--------------------------------------------------------
    1660 -        def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
    1661                  """Retrieve a patient's problems.
    1662
    1663                  "Problems" are the UNION of:
    1664
    1665                          - issues which are .clinically_relevant
    1666                          - episodes which are .is_open
    1667
    1668                  Therefore, both an issue and the open episode
    1669                  thereof can each be listed as a problem.
    1670
    1671                  include_closed_episodes/include_irrelevant_issues will
    1672                  include those -- which departs from the definition of
    1673                  the problem list being "active" items only ...
    1674
    1675                  episodes - episodes' PKs to filter problems by
    1676                  issues - health issues' PKs to filter problems by
    1677                  """
    1678                  # FIXME: this could use a good measure of streamlining, probably
    1679
    1680                  args = {'pat': self.pk_patient}
    1681
    1682                  cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
    1683                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1684
    1685                  # Instantiate problem items
    1686                  problems = []
    1687                  for row in rows:
    1688                          pk_args = {
    1689                                  'pk_patient': self.pk_patient,
    1690                                  'pk_health_issue': row['pk_health_issue'],
    1691                                  'pk_episode': row['pk_episode']
    1692                          }
    1693                          problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
    1694
    1695                  # include non-problems ?
    1696                  other_rows = []
    1697                  if include_closed_episodes:
    1698                          cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
    1699                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1700                          other_rows.extend(rows)
    1701
    1702                  if include_irrelevant_issues:
    1703                          cmd = """SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
    1704                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1705                          other_rows.extend(rows)
    1706
    1707                  if len(other_rows) > 0:
    1708                          for row in other_rows:
    1709                                  pk_args = {
    1710                                          'pk_patient': self.pk_patient,
    1711                                          'pk_health_issue': row['pk_health_issue'],
    1712                                          'pk_episode': row['pk_episode']
    1713                                  }
    1714                                  problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
    1715
    1716                  # filter
    1717                  if issues is not None:
    1718                          problems = [ p for p in problems if p['pk_health_issue'] in issues ]
    1719                  if episodes is not None:
    1720                          problems = [ p for p in problems if p['pk_episode'] in episodes ]
    1721
    1722                  return problems
    1723
    1724          #--------------------------------------------------------
    1725 -        def problem2episode(self, problem=None):
    1726                  return gmEMRStructItems.problem2episode(problem = problem)
    1727
    1728          #--------------------------------------------------------
    1729 -        def problem2issue(self, problem=None):
    1730                  return gmEMRStructItems.problem2issue(problem = problem)
    1731
    1732          #--------------------------------------------------------
    1733 -        def reclass_problem(self, problem):
    1734                  return gmEMRStructItems.reclass_problem(problem = problem)
    1735
    1736          #--------------------------------------------------------
    1737 -        def get_candidate_diagnoses(self):
    1738                  cmd = "SELECT * FROM clin.v_candidate_diagnoses WHERE pk_patient = %(pat)s"
    1739                  rows, idx = gmPG2.run_ro_queries (
    1740                          queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}],
    1741                          get_col_idx = False
    1742                  )
    1743                  return rows
    1744
    1745          candidate_diagnoses = property(get_candidate_diagnoses)
    1746
    1747          #--------------------------------------------------------
    1748          # API: health issues
    1749          #--------------------------------------------------------
    1750 -        def get_health_issues(self, id_list = None):
    1751
    1752                  cmd = "SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
    1753                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
    1754                  issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
    1755
    1756                  if id_list is None:
    1757                          return issues
    1758
    1759                  if len(id_list) == 0:
    1760                          raise ValueError('id_list to filter by is empty, most likely a programming error')
    1761
    1762                  filtered_issues = []
    1763                  for issue in issues:
    1764                          if issue['pk_health_issue'] in id_list:
    1765                                  filtered_issues.append(issue)
    1766
    1767                  return filtered_issues
    1768
    1769          health_issues = property(get_health_issues, lambda x:x)
    1770
    1771          #------------------------------------------------------------------
    1772 -        def add_health_issue(self, issue_name=None):
    1773                  """Adds patient health issue."""
    1774                  return gmEMRStructItems.create_health_issue (
    1775                          description = issue_name,
    1776                          encounter = self.current_encounter['pk_encounter'],
    1777                          patient = self.pk_patient
    1778                  )
    1779          #--------------------------------------------------------
    1780 -        def health_issue2problem(self, issue=None):
    1781                  return gmEMRStructItems.health_issue2problem(issue = issue)
    1782          #--------------------------------------------------------
    1783          # API: substance intake
    1784          #--------------------------------------------------------
    1785 -        def get_current_medications(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
    1786                  return self._get_current_substance_intakes (
    1787                          include_inactive = include_inactive,
    1788                          include_unapproved = include_unapproved,
    1789                          order_by = order_by,
    1790                          episodes = episodes,
    1791                          issues = issues,
    1792                          exclude_medications = False,
    1793                          exclude_potential_abuses = True
    1794                  )
    1795
    1796          #--------------------------------------------------------
    1797 -        def _get_abused_substances(self, order_by=None):
    1798                  return self._get_current_substance_intakes (
    1799                          include_inactive = True,
    1800                          include_unapproved = True,
    1801                          order_by = order_by,
    1802                          episodes = None,
    1803                          issues = None,
    1804                          exclude_medications = True,
    1805                          exclude_potential_abuses = False
    1806                  )
    1807
    1808          abused_substances = property(_get_abused_substances, lambda x:x)
    1809
    1810          #--------------------------------------------------------
    1811 -        def _get_current_substance_intakes(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None, exclude_potential_abuses=False, exclude_medications=False):
    1812
    1813                  where_parts = ['pk_patient = %(pat)s']
    1814                  args = {'pat': self.pk_patient}
    1815
    1816                  if not include_inactive:
    1817                          where_parts.append('is_currently_active IN (TRUE, NULL)')
    1818
    1819                  if not include_unapproved:
    1820                          where_parts.append('intake_is_approved_of IN (TRUE, NULL)')
    1821
    1822                  if exclude_potential_abuses:
    1823                          where_parts.append('harmful_use_type IS NULL')
    1824
    1825                  if exclude_medications:
    1826                          where_parts.append('harmful_use_type IS NOT NULL')
    1827
    1828                  if order_by is None:
    1829                          order_by = ''
    1830                  else:
    1831                          order_by = 'ORDER BY %s' % order_by
    1832
    1833                  cmd = "SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
    1834                          '\nAND '.join(where_parts),
    1835                          order_by
    1836                  )
    1837                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1838                  intakes = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'})  for r in rows ]
    1839
    1840                  if episodes is not None:
    1841                          intakes = [ i for i in intakes if i['pk_episode'] in episodes  ]
    1842
    1843                  if issues is not None:
    1844                          intakes = [ i for i in intakes if i ['pk_health_issue'] in issues ]
    1845
    1846                  return intakes
    1847
    1848          #--------------------------------------------------------
    1849 -        def add_substance_intake(self, pk_component=None, pk_episode=None, pk_drug_product=None, pk_health_issue=None):
    1850                  pk_enc = self.current_encounter['pk_encounter']
    1851                  if pk_episode is None:
    1852                          pk_episode = gmMedication.create_default_medication_history_episode (
    1853                                  pk_health_issue = pk_health_issue,
    1854                                  encounter = pk_enc
    1855                          )
    1856                  return gmMedication.create_substance_intake (
    1857                          pk_component = pk_component,
    1858                          pk_encounter = pk_enc,
    1859                          pk_episode = pk_episode,
    1860                          pk_drug_product = pk_drug_product
    1861                  )
    1862
    1863          #--------------------------------------------------------
    1864 -        def substance_intake_exists(self, pk_component=None, pk_substance=None, pk_drug_product=None):
    1865                  return gmMedication.substance_intake_exists (
    1866                          pk_component = pk_component,
    1867                          pk_substance = pk_substance,
    1868                          pk_identity = self.pk_patient,
    1869                          pk_drug_product = pk_drug_product
    1870                  )
    1871
    1872          #--------------------------------------------------------
    1873          # API: vaccinations
    1874          #--------------------------------------------------------
    1875 -        def add_vaccination(self, episode=None, vaccine=None, batch_no=None):
    1876                  return gmVaccination.create_vaccination (
    1877                          encounter = self.current_encounter['pk_encounter'],
    1878                          episode = episode,
    1879                          vaccine = vaccine,
    1880                          batch_no = batch_no
    1881                  )
    1882
    1883          #--------------------------------------------------------
    1884 -        def get_latest_vaccinations(self, episodes=None, issues=None, atc_indications=None):
    1885                  """Returns latest given vaccination for each vaccinated indication.
    1886
    1887                  as a dict {'l10n_indication': cVaccination instance}
    1888
    1889                  Note that this will produce duplicate vaccination instances on combi-indication vaccines !
    1890                  """
    1891                  args = {'pat': self.pk_patient}
    1892                  where_parts = ['c_v_shots.pk_patient = %(pat)s']
    1893
    1894                  if (episodes is not None) and (len(episodes) > 0):
    1895                          where_parts.append('c_v_shots.pk_episode IN %(epis)s')
    1896                          args['epis'] = tuple(episodes)
    1897
    1898                  if (issues is not None) and (len(issues) > 0):
    1899                          where_parts.append('c_v_shots.pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
    1900                          args['issues'] = tuple(issues)
    1901
    1902                  if (atc_indications is not None) and (len(atc_indications) > 0):
    1903                          where_parts.append('c_v_plv4i.atc_indication IN %(atc_inds)s')
    1904                          args['atc_inds'] = tuple(atc_indications)
    1905
    1906                  # find the shots
    1907                  cmd = """
    1908                          SELECT
    1909                                  c_v_shots.*,
    1910                                  c_v_plv4i.l10n_indication,
    1911                                  c_v_plv4i.no_of_shots
    1912                          FROM
    1913                                  clin.v_vaccinations c_v_shots
    1914                                          JOIN clin.v_pat_last_vacc4indication c_v_plv4i ON (c_v_shots.pk_vaccination = c_v_plv4i.pk_vaccination)
    1915                          WHERE %s
    1916                  """ % '\nAND '.join(where_parts)
    1917                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1918
    1919                  # none found
    1920                  if len(rows) == 0:
    1921                          return {}
    1922
    1923                  # turn them into vaccinations
    1924                  # (idx is constant)
    1925                  vaccs = {}
    1926                  for shot_row in rows:
    1927                          vaccs[shot_row['l10n_indication']] = (
    1928                                  shot_row['no_of_shots'],
    1929                                  gmVaccination.cVaccination(row = {'idx': idx, 'data': shot_row, 'pk_field': 'pk_vaccination'})
    1930                          )
    1931
    1932                  return vaccs
    1933
    1934          #--------------------------------------------------------
    1935 -        def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
    1936
    1937                  args = {'pat': self.pk_patient}
    1938                  where_parts = ['pk_patient = %(pat)s']
    1939
    1940                  if order_by is None:
    1941                          order_by = ''
    1942                  else:
    1943                          order_by = 'ORDER BY %s' % order_by
    1944
    1945                  if (episodes is not None) and (len(episodes) > 0):
    1946                          where_parts.append('pk_episode IN %(epis)s')
    1947                          args['epis'] = tuple(episodes)
    1948
    1949                  if (issues is not None) and (len(issues) > 0):
    1950                          where_parts.append('pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
    1951                          args['issues'] = tuple(issues)
    1952
    1953                  if (encounters is not None) and (len(encounters) > 0):
    1954                          where_parts.append('pk_encounter IN %(encs)s')
    1955                          args['encs'] = tuple(encounters)
    1956
    1957                  cmd = '%s %s' % (
    1958                          gmVaccination._SQL_get_vaccination_fields % '\nAND '.join(where_parts),
    1959                          order_by
    1960                  )
    1961                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1962                  vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'})  for r in rows ]
    1963
    1964                  return vaccs
    1965
    1966          vaccinations = property(get_vaccinations, lambda x:x)
    1967
    1968          #--------------------------------------------------------
    1969          # old/obsolete:
    1970          #--------------------------------------------------------
    1971 -        def get_scheduled_vaccination_regimes(self, ID=None, indications=None):
    1972                  """Retrieves vaccination regimes the patient is on.
    1973
    1974                          optional:
    1975                          * ID - PK of the vaccination regime
    1976                          * indications - indications we want to retrieve vaccination
    1977                                  regimes for, must be primary language, not l10n_indication
    1978                  """
    1979                  # FIXME: use course, not regime
    1980                  # retrieve vaccination regimes definitions
    1981                  cmd = """SELECT distinct on(pk_course) pk_course
    1982                                   FROM clin.v_vaccs_scheduled4pat
    1983                                   WHERE pk_patient=%s"""
    1984                  rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
    1985                  if rows is None:
    1986                          _log.error('cannot retrieve scheduled vaccination courses')
    1987                          return None
    1988                  # Instantiate vaccination items and keep cache
    1989                  for row in rows:
    1990                          self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
    1991
    1992                  # ok, let's constrain our list
    1993                  filtered_regimes = []
    1994                  filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
    1995                  if ID is not None:
    1996                          filtered_regimes = [ r for r in filtered_regimes if r['pk_course'] == ID ]
    1997                          if len(filtered_regimes) == 0:
    1998                                  _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
    1999                                  return []
    2000                          else:
    2001                                  return filtered_regimes[0]
    2002                  if indications is not None:
    2003                          filtered_regimes = [ r for r in filtered_regimes if r['indication'] in indications ]
    2004
    2005                  return filtered_regimes
    2006          #--------------------------------------------------------
    2007  #       def get_vaccinated_indications(self):
    2008  #               """Retrieves patient vaccinated indications list.
    2009  #
    2010  #               Note that this does NOT rely on the patient being on
    2011  #               some schedule or other but rather works with what the
    2012  #               patient has ACTUALLY been vaccinated against. This is
    2013  #               deliberate !
    2014  #               """
    2015  #               # most likely, vaccinations will be fetched close
    2016  #               # by so it makes sense to count on the cache being
    2017  #               # filled (or fill it for nearby use)
    2018  #               vaccinations = self.get_vaccinations()
    2019  #               if vaccinations is None:
    2020  #                       _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
    2021  #                       return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
    2022  #               if len(vaccinations) == 0:
    2023  #                       return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
    2024  #               v_indications = []
    2025  #               for vacc in vaccinations:
    2026  #                       tmp = [vacc['indication'], vacc['l10n_indication']]
    2027  #                       # remove duplicates
    2028  #                       if tmp in v_indications:
    2029  #                               continue
    2030  #                       v_indications.append(tmp)
    2031  #               return (True, v_indications)
    2032          #--------------------------------------------------------
    2033 -        def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
    2034                  """Retrieves list of vaccinations the patient has received.
    2035
    2036                  optional:
    2037                  * ID - PK of a vaccination
    2038                  * indications - indications we want to retrieve vaccination
    2039                          items for, must be primary language, not l10n_indication
    2040          * since - initial date for allergy items
    2041          * until - final date for allergy items
    2042          * encounters - list of encounters whose allergies are to be retrieved
    2043          * episodes - list of episodes whose allergies are to be retrieved
    2044          * issues - list of health issues whose allergies are to be retrieved
    2045                  """
    2046                  try:
    2047                          self.__db_cache['vaccinations']['vaccinated']
    2048                  except KeyError:
    2049                          self.__db_cache['vaccinations']['vaccinated'] = []
    2050                          # Important fetch ordering by indication, date to know if a vaccination is booster
    2051                          cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
    2052                                          WHERE pk_patient=%s
    2053                                          order by indication, date"""
    2054                          rows, idx  = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
    2055                          if rows is None:
    2056                                  _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
    2057                                  del self.__db_cache['vaccinations']['vaccinated']
    2058                                  return None
    2059                          # Instantiate vaccination items
    2060                          vaccs_by_ind = {}
    2061                          for row in rows:
    2062                                  vacc_row = {
    2063                                          'pk_field': 'pk_vaccination',
    2064                                          'idx': idx,
    2065                                          'data': row
    2066                                  }
    2067                                  vacc = gmVaccination.cVaccination(row=vacc_row)
    2068                                  self.__db_cache['vaccinations']['vaccinated'].append(vacc)
    2069                                  # keep them, ordered by indication
    2070                                  try:
    2071                                          vaccs_by_ind[vacc['indication']].append(vacc)
    2072                                  except KeyError:
    2073                                          vaccs_by_ind[vacc['indication']] = [vacc]
    2074
    2075                          # calculate sequence number and is_booster
    2076                          for ind in vaccs_by_ind.keys():
    2077                                  vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
    2078                                  for vacc in vaccs_by_ind[ind]:
    2079                                          # due to the "order by indication, date" the vaccinations are in the
    2080                                          # right temporal order inside the indication-keyed dicts
    2081                                          seq_no = vaccs_by_ind[ind].index(vacc) + 1
    2082                                          vacc['seq_no'] = seq_no
    2083                                          # if no active schedule for indication we cannot
    2084                                          # check for booster status (eg. seq_no > max_shot)
    2085                                          if (vacc_regimes is None) or (len(vacc_regimes) == 0):
    2086                                                  continue
    2087                                          if seq_no > vacc_regimes[0]['shots']:
    2088                                                  vacc['is_booster'] = True
    2089                          del vaccs_by_ind
    2090
    2091                  # ok, let's constrain our list
    2092                  filtered_shots = []
    2093                  filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
    2094                  if ID is not None:
    2095                          filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
    2096                          if len(filtered_shots) == 0:
    2097                                  _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
    2098                                  return None
    2099                          else:
    2100                                  return filtered_shots[0]
    2101                  if since is not None:
    2102                          filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
    2103                  if until is not None:
    2104                          filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
    2105                  if issues is not None:
    2106                          filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
    2107                  if episodes is not None:
    2108                          filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
    2109                  if encounters is not None:
    2110                          filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
    2111                  if indications is not None:
    2112                          filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
    2113                  return filtered_shots
    2114          #--------------------------------------------------------
    2115 -        def get_scheduled_vaccinations(self, indications=None):
    2116                  """Retrieves vaccinations scheduled for a regime a patient is on.
    2117
    2118                  The regime is referenced by its indication (not l10n)
    2119
    2120                  * indications - List of indications (not l10n) of regimes we want scheduled
    2121                                  vaccinations to be fetched for
    2122                  """
    2123                  try:
    2124                          self.__db_cache['vaccinations']['scheduled']
    2125                  except KeyError:
    2126                          self.__db_cache['vaccinations']['scheduled'] = []
    2127                          cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
    2128                          rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
    2129                          if rows is None:
    2130                                  _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
    2131                                  del self.__db_cache['vaccinations']['scheduled']
    2132                                  return None
    2133                          # Instantiate vaccination items
    2134                          for row in rows:
    2135                                  vacc_row = {
    2136                                          'pk_field': 'pk_vacc_def',
    2137                                          'idx': idx,
    2138                                          'data': row
    2139                                  }
    2140                                  self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
    2141
    2142                  # ok, let's constrain our list
    2143                  if indications is None:
    2144                          return self.__db_cache['vaccinations']['scheduled']
    2145                  filtered_shots = []
    2146                  filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
    2147                  filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
    2148                  return filtered_shots
    2149          #--------------------------------------------------------
    2150 -        def get_missing_vaccinations(self, indications=None):
    2151                  try:
    2152                          self.__db_cache['vaccinations']['missing']
    2153                  except KeyError:
    2154                          self.__db_cache['vaccinations']['missing'] = {}
    2155                          # 1) non-booster
    2156                          self.__db_cache['vaccinations']['missing']['due'] = []
    2157                          # get list of (indication, seq_no) tuples
    2158                          cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
    2159                          rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
    2160                          if rows is None:
    2161                                  _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
    2162                                  return None
    2163                          pk_args = {'pat_id': self.pk_patient}
    2164                          if rows is not None:
    2165                                  for row in rows:
    2166                                          pk_args['indication'] = row[0]
    2167                                          pk_args['seq_no'] = row[1]
    2168                                          self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
    2169
    2170                          # 2) boosters
    2171                          self.__db_cache['vaccinations']['missing']['boosters'] = []
    2172                          # get list of indications
    2173                          cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
    2174                          rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
    2175                          if rows is None:
    2176                                  _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
    2177                                  return None
    2178                          pk_args = {'pat_id': self.pk_patient}
    2179                          if rows is not None:
    2180                                  for row in rows:
    2181                                          pk_args['indication'] = row[0]
    2182                                          self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
    2183
    2184                  # if any filters ...
    2185                  if indications is None:
    2186                          return self.__db_cache['vaccinations']['missing']
    2187                  if len(indications) == 0:
    2188                          return self.__db_cache['vaccinations']['missing']
    2189                  # ... apply them
    2190                  filtered_shots = {
    2191                          'due': [],
    2192                          'boosters': []
    2193                  }
    2194                  for due_shot in self.__db_cache['vaccinations']['missing']['due']:
    2195                          if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
    2196                                  filtered_shots['due'].append(due_shot)
    2197                  for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
    2198                          if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
    2199                                  filtered_shots['boosters'].append(due_shot)
    2200                  return filtered_shots
    2201
    2202          #------------------------------------------------------------------
    2203          # API: encounters
    2204          #------------------------------------------------------------------
    2205 -        def _get_current_encounter(self):
    2206                  return self.__encounter
    2207
    2208 -        def _set_current_encounter(self, encounter):
    2209                  # first ever setting ? -> fast path
    2210                  if self.__encounter is None:
    2211                          _log.debug('first setting of active encounter in this clinical record instance')
    2212                          encounter.lock(exclusive = False)               # lock new
    2213                          self.__encounter = encounter
    2214                          gmDispatcher.send('current_encounter_switched')
    2215                          return True
    2216
    2217                  # real switch -> slow path
    2218                  _log.debug('switching of active encounter')
    2219                  # fail if the currently active encounter has unsaved changes
    2220                  if self.__encounter.is_modified():
    2221                          gmTools.compare_dict_likes(self.__encounter, encounter, 'modified enc in client', 'enc to switch to')
    2222                          _log.error('current in client: %s', self.__encounter)
    2223                          raise ValueError('unsaved changes in active encounter [%s], cannot switch to another one [%s]' % (
    2224                                  self.__encounter['pk_encounter'],
    2225                                  encounter['pk_encounter']
    2226                          ))
    2227
    2228                  prev_enc = self.__encounter
    2229                  encounter.lock(exclusive = False)               # lock new
    2230                  self.__encounter = encounter
    2231                  prev_enc.unlock(exclusive = False)              # unlock old
    2232                  gmDispatcher.send('current_encounter_switched')
    2233
    2234                  return True
    2235
    2236          current_encounter = property(_get_current_encounter, _set_current_encounter)
    2237          active_encounter = property(_get_current_encounter, _set_current_encounter)
    2238
    2239          #--------------------------------------------------------
    2240 -        def __setup_active_encounter(self):
    2241                  _log.debug('setting up active encounter for identity [%s]', self.pk_patient)
    2242
    2243                  # log access to patient record (HIPAA, for example)
    2244                  _delayed_execute(self.log_access, action = 'pulling chart for identity [%s]' % self.pk_patient)
    2245
    2246                  # cleanup (not async, because we don't want recent encounters
    2247                  # to become the active one just because they are recent)
    2248                  self.remove_empty_encounters()
    2249
    2250                  # activate very recent encounter if available
    2251                  if self.__activate_very_recent_encounter():
    2252                          return
    2253
    2254                  fairly_recent_enc = self.__get_fairly_recent_encounter()
    2255
    2256                  # create new encounter for the time being
    2257                  self.start_new_encounter()
    2258
    2259                  if fairly_recent_enc is None:
    2260                          return
    2261
    2262                  # but check whether user wants to continue a "fairly recent" one
    2263                  gmDispatcher.send (
    2264                          signal = 'ask_for_encounter_continuation',
    2265                          new_encounter = self.__encounter,
    2266                          fairly_recent_encounter = fairly_recent_enc
    2267                  )
    2268
    2269          #------------------------------------------------------------------
    2270 -        def __activate_very_recent_encounter(self):
    2271                  """Try to attach to a "very recent" encounter if there is one.
    2272
    2273                  returns:
    2274                          False: no "very recent" encounter
    2275                  True: success
    2276                  """
    2277                  cfg_db = gmCfg.cCfgSQL()
    2278                  min_ttl = cfg_db.get2 (
    2279                          option = 'encounter.minimum_ttl',
    2280                          workplace = _here.active_workplace,
    2281                          bias = 'user',
    2282                          default = '1 hour 30 minutes'
    2283                  )
    2284                  cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
    2285                          SELECT pk_encounter
    2286                          FROM clin.v_most_recent_encounters
    2287                          WHERE
    2288                                  pk_patient = %s
    2289                                          and
    2290                                  last_affirmed > (now() - %s::interval)
    2291                          ORDER BY
    2292                                  last_affirmed DESC
    2293                          LIMIT 1
    2294                  )"""
    2295                  enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}], get_col_idx = True)
    2296
    2297                  # none found
    2298                  if len(enc_rows) == 0:
    2299                          _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
    2300                          return False
    2301
    2302                  _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0]['pk_encounter'])
    2303
    2304                  # attach to existing
    2305                  self.current_encounter = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
    2306                  return True
    2307
    2308          #------------------------------------------------------------------
    2309 -        def __get_fairly_recent_encounter(self):
    2310                  cfg_db = gmCfg.cCfgSQL()
    2311                  min_ttl = cfg_db.get2 (
    2312                          option = 'encounter.minimum_ttl',
    2313                          workplace = _here.active_workplace,
    2314                          bias = 'user',
    2315                          default = '1 hour 30 minutes'
    2316                  )
    2317                  max_ttl = cfg_db.get2 (
    2318                          option = 'encounter.maximum_ttl',
    2319                          workplace = _here.active_workplace,
    2320                          bias = 'user',
    2321                          default = '6 hours'
    2322                  )
    2323
    2324                  # do we happen to have a "fairly recent" candidate ?
    2325                  cmd = gmEMRStructItems.SQL_get_encounters % """pk_encounter = (
    2326                          SELECT pk_encounter
    2327                          FROM clin.v_most_recent_encounters
    2328                          WHERE
    2329                                  pk_patient=%s
    2330                                          AND
    2331                                  last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
    2332                          ORDER BY
    2333                                  last_affirmed DESC
    2334                          LIMIT 1
    2335                  )"""
    2336                  enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
    2337
    2338                  # none found
    2339                  if len(enc_rows) == 0:
    2340                          _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
    2341                          return None
    2342
    2343                  _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
    2344                  return gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
    2345
    2346  #       #------------------------------------------------------------------
    2347  #       def __check_for_fairly_recent_encounter(self):
    2348  #
    2349  #               cfg_db = gmCfg.cCfgSQL()
    2350  #               min_ttl = cfg_db.get2 (
    2351  #                       option = u'encounter.minimum_ttl',
    2352  #                       workplace = _here.active_workplace,
    2353  #                       bias = u'user',
    2354  #                       default = u'1 hour 30 minutes'
    2355  #               )
    2356  #               max_ttl = cfg_db.get2 (
    2357  #                       option = u'encounter.maximum_ttl',
    2358  #                       workplace = _here.active_workplace,
    2359  #                       bias = u'user',
    2360  #                       default = u'6 hours'
    2361  #               )
    2362  #
    2363  #               # do we happen to have a "fairly recent" candidate ?
    2364  #               cmd = gmEMRStructItems.SQL_get_encounters % u"""pk_encounter = (
    2365  #                       SELECT pk_encounter
    2366  #                       FROM clin.v_most_recent_encounters
    2367  #                       WHERE
    2368  #                               pk_patient=%s
    2369  #                                       AND
    2370  #                               last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
    2371  #                       ORDER BY
    2372  #                               last_affirmed DESC
    2373  #                       LIMIT 1
    2374  #               )"""
    2375  #               enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}], get_col_idx = True)
    2376  #
    2377  #               # none found
    2378  #               if len(enc_rows) == 0:
    2379  #                       _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
    2380  #                       return
    2381  #
    2382  #               _log.debug('"fairly recent" encounter [%s] found', enc_rows[0]['pk_encounter'])
    2383  #               fairly_recent_enc = gmEMRStructItems.cEncounter(row = {'data': enc_rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
    2384  #               gmDispatcher.send(u'ask_for_encounter_continuation', current = self.__encounter, fairly_recent_encounter = fairly_recent_enc)
    2385
    2386  #       #------------------------------------------------------------------
    2387  #       def __activate_fairly_recent_encounter(self, allow_user_interaction=True):
    2388  #               """Try to attach to a "fairly recent" encounter if there is one.
    2389  #
    2390  #               returns:
    2391  #                       False: no "fairly recent" encounter, create new one
    2392  #               True: success
    2393  #               """
    2394  #               if _func_ask_user is None:
    2395  #                       _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
    2396  #                       return False
    2397  #
    2398  #               if not allow_user_interaction:
    2399  #                       _log.exception('user interaction not desired, not looking for fairly recent encounter')
    2400  #                       return False
    2401  #
    2402  #               cfg_db = gmCfg.cCfgSQL()
    2403  #               min_ttl = cfg_db.get2 (
    2404  #                       option = u'encounter.minimum_ttl',
    2405  #                       workplace = _here.active_workplace,
    2406  #                       bias = u'user',
    2407  #                       default = u'1 hour 30 minutes'
    2408  #               )
    2409  #               max_ttl = cfg_db.get2 (
    2410  #                       option = u'encounter.maximum_ttl',
    2411  #                       workplace = _here.active_workplace,
    2412  #                       bias = u'user',
    2413  #                       default = u'6 hours'
    2414  #               )
    2415  #               cmd = u"""
    2416  #                       SELECT pk_encounter
    2417  #                       FROM clin.v_most_recent_encounters
    2418  #                       WHERE
    2419  #                               pk_patient=%s
    2420  #                                       AND
    2421  #                               last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
    2422  #                       ORDER BY
    2423  #                               last_affirmed DESC"""
    2424  #               enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
    2425  #               # none found
    2426  #               if len(enc_rows) == 0:
    2427  #                       _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
    2428  #                       return False
    2429  #
    2430  #               _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0])
    2431  #
    2432  #               encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
    2433  #               # ask user whether to attach or not
    2434  #               cmd = u"""
    2435  #                       SELECT title, firstnames, lastnames, gender, dob
    2436  #                       FROM dem.v_all_persons WHERE pk_identity=%s"""
    2437  #               pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
    2438  #               pat = pats[0]
    2439  #               pat_str = u'%s %s %s (%s), %s  [#%s]' % (
    2440  #                       gmTools.coalesce(pat[0], u'')[:5],
    2441  #                       pat[1][:15],
    2442  #                       pat[2][:15],
    2443  #                       pat[3],
    2444  #                       gmDateTime.pydt_strftime(pat[4], '%Y %b %d'),
    2445  #                       self.pk_patient
    2446  #               )
    2447  #               msg = _(
    2448  #                       '%s\n'
    2449  #                       '\n'
    2450  #                       "This patient's chart was worked on only recently:\n"
    2451  #                       '\n'
    2452  #                       ' %s  %s - %s  (%s)\n'
    2453  #                       '\n'
    2454  #                       ' Reason for Encounter:\n'
    2455  #                       '  %s\n'
    2456  #                       ' Assessment of Encounter:\n'
    2457  #                       '  %s\n'
    2458  #                       '\n'
    2459  #                       'Do you want to continue that consultation\n'
    2460  #                       'or do you want to start a new one ?\n'
    2461  #               ) % (
    2462  #                       pat_str,
    2463  #                       gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'),
    2464  #                       gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'),
    2465  #                       encounter['l10n_type'],
    2466  #                       gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
    2467  #                       gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
    2468  #               )
    2469  #               attach = False
    2470  #               try:
    2471  #                       attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
    2472  #               except:
    2473  #                       _log.exception('cannot ask user for guidance, not attaching to existing encounter')
    2474  #                       return False
    2475  #               if not attach:
    2476  #                       return False
    2477  #
    2478  #               # attach to existing
    2479  #               self.current_encounter = encounter
    2480  #               _log.debug('"fairly recent" encounter re-activated')
    2481  #               return True
    2482
    2483          #------------------------------------------------------------------
    2484 -        def start_new_encounter(self):
    2485                  cfg_db = gmCfg.cCfgSQL()
    2486                  enc_type = cfg_db.get2 (
    2487                          option = 'encounter.default_type',
    2488                          workplace = _here.active_workplace,
    2489                          bias = 'user'
    2490                  )
    2491                  if enc_type is None:
    2492                          enc_type = gmEMRStructItems.get_most_commonly_used_encounter_type()
    2493                  if enc_type is None:
    2494                          enc_type = 'in surgery'
    2495                  enc = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
    2496                  enc['pk_org_unit'] = _here['pk_org_unit']
    2497                  enc.save()
    2498                  self.current_encounter = enc
    2499                  _log.debug('new encounter [%s] activated', enc['pk_encounter'])
    2500
    2501          #------------------------------------------------------------------
    2502 -        def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False, order_by=None, max_encounters=None):
    2503                  """Retrieves patient's encounters.
    2504
    2505                  id_list - PKs of encounters to fetch
    2506                  since - initial date for encounter items, DateTime instance
    2507                  until - final date for encounter items, DateTime instance
    2508                  episodes - PKs of the episodes the encounters belong to (many-to-many relation)
    2509                  issues - PKs of the health issues the encounters belong to (many-to-many relation)
    2510                  skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
    2511
    2512                  NOTE: if you specify *both* issues and episodes
    2513                  you will get the *aggregate* of all encounters even
    2514                  if the episodes all belong to the health issues listed.
    2515                  IOW, the issues broaden the episode list rather than
    2516                  the episode list narrowing the episodes-from-issues
    2517                  list.
    2518                  Rationale: If it was the other way round it would be
    2519                  redundant to specify the list of issues at all.
    2520                  """
    2521                  # if issues are given, translate them to their episodes
    2522                  if (issues is not None) and (len(issues) > 0):
    2523                          # - find episodes corresponding to the health issues in question
    2524                          cmd = "SELECT distinct pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue in %(issue_pks)s AND pk_patient = %(pat)s"
    2525                          args = {'issue_pks': tuple(issues), 'pat': self.pk_patient}
    2526                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    2527                          epis4issues_pks = [ r['pk_episode']  for r in rows ]
    2528                          if episodes is None:
    2529                                  episodes = []
    2530                          episodes.extend(epis4issues_pks)
    2531
    2532                  if (episodes is not None) and (len(episodes) > 0):
    2533                          # since the episodes to filter by belong to the patient in question so will
    2534                          # the encounters found with them - hence we don't need a WHERE on the patient ...
    2535                          # but, better safe than sorry ...
    2536                          args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
    2537                          cmd = "SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode IN %(epi_pks)s AND fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s)"
    2538                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    2539                          encs4epis_pks = [ r['fk_encounter'] for r in rows ]
    2540                          if id_list is None:
    2541                                  id_list = []
    2542                          id_list.extend(encs4epis_pks)
    2543
    2544                  where_parts = ['c_vpe.pk_patient = %(pat)s']
    2545                  args = {'pat': self.pk_patient}
    2546
    2547                  if skip_empty:
    2548                          where_parts.append("""NOT (
    2549                                  gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
    2550                                          AND
    2551                                  gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
    2552                                          AND
    2553                                  NOT EXISTS (
    2554                                          SELECT 1 FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_patient = %(pat)s AND c_vpi.pk_encounter = c_vpe.pk_encounter
    2555                                                  UNION ALL
    2556                                          SELECT 1 FROM blobs.v_doc_med b_vdm WHERE b_vdm.pk_patient = %(pat)s AND b_vdm.pk_encounter = c_vpe.pk_encounter
    2557                                  ))""")
    2558
    2559                  if since is not None:
    2560                          where_parts.append('c_vpe.started >= %(start)s')
    2561                          args['start'] = since
    2562
    2563                  if until is not None:
    2564                          where_parts.append('c_vpe.last_affirmed <= %(end)s')
    2565                          args['end'] = since
    2566
    2567                  if (id_list is not None) and (len(id_list) > 0):
    2568                          where_parts.append('c_vpe.pk_encounter IN %(enc_pks)s')
    2569                          args['enc_pks'] = tuple(id_list)
    2570
    2571                  if order_by is None:
    2572                          order_by = 'c_vpe.started'
    2573
    2574                  if max_encounters is None:
    2575                          limit = ''
    2576                  else:
    2577                          limit = 'LIMIT %s' % max_encounters
    2578
    2579                  cmd = """
    2580                          SELECT * FROM clin.v_pat_encounters c_vpe
    2581                          WHERE
    2582                                  %s
    2583                          ORDER BY %s %s
    2584                  """ % (
    2585                          ' AND '.join(where_parts),
    2586                          order_by,
    2587                          limit
    2588                  )
    2589                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2590                  encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
    2591
    2592                  # we've got the encounters, start filtering
    2593                  filtered_encounters = []
    2594                  filtered_encounters.extend(encounters)
    2595
    2596                  if (episodes is not None) and (len(episodes) > 0):
    2597                          # since the episodes to filter by belong to the patient in question so will
    2598                          # the encounters found with them - hence we don't need a WHERE on the patient ...
    2599                          # but, better safe than sorry ...
    2600                          args = {'epi_pks': tuple(episodes), 'pat': self.pk_patient}
    2601                          cmd = "SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode IN %(epi_pks)s AND fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s)"
    2602                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    2603                          encs4epis_pks = [ r['fk_encounter'] for r in rows ]
    2604                          filtered_encounters = [ enc for enc in filtered_encounters if enc['pk_encounter'] in encs4epis_pks ]
    2605
    2606                  return filtered_encounters
    2607
    2608          #--------------------------------------------------------
    2609 -        def get_first_encounter(self, issue_id=None, episode_id=None):
    2610                  """Retrieves first encounter for a particular issue and/or episode.
    2611
    2612                  issue_id - First encounter associated health issue
    2613                  episode - First encounter associated episode
    2614                  """
    2615                  if issue_id is None:
    2616                          issues = None
    2617                  else:
    2618                          issues = [issue_id]
    2619
    2620                  if episode_id is None:
    2621                          episodes = None
    2622                  else:
    2623                          episodes = [episode_id]
    2624
    2625                  encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started', max_encounters = 1)
    2626                  if len(encounters) == 0:
    2627                          return None
    2628
    2629                  return encounters[0]
    2630
    2631          first_encounter = property(get_first_encounter, lambda x:x)
    2632
    2633          #--------------------------------------------------------
    2634 -        def get_earliest_care_date(self):
    2635                  args = {'pat': self.pk_patient}
    2636                  cmd = """
    2637  SELECT MIN(earliest) FROM (
    2638          (
    2639                  SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
    2640
    2641          ) UNION ALL (
    2642
    2643                  SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
    2644
    2645          ) UNION ALL (
    2646
    2647                  SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
    2648
    2649          ) UNION ALL (
    2650
    2651                  SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
    2652
    2653          ) UNION ALL (
    2654
    2655                  SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
    2656
    2657          ) UNION ALL (
    2658
    2659                  SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
    2660
    2661          ) UNION ALL (
    2662
    2663                  SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
    2664
    2665          )
    2666  ) AS candidates"""
    2667                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2668                  return rows[0][0]
    2669
    2670          earliest_care_date = property(get_earliest_care_date, lambda x:x)
    2671
    2672          #--------------------------------------------------------
    2673 -        def get_most_recent_care_date(self):
    2674                  encounters = self.get_encounters(order_by = 'started DESC', max_encounters = 1)
    2675                  if len(encounters) == 0:
    2676                          return None
    2677                  return encounters[0]['last_affirmed']
    2678
    2679          most_recent_care_date = property(get_most_recent_care_date)
    2680
    2681          #--------------------------------------------------------
    2682 -        def get_last_encounter(self, issue_id=None, episode_id=None):
    2683                  """Retrieves last encounter for a concrete issue and/or episode
    2684
    2685                  issue_id - Last encounter associated health issue
    2686                  episode_id - Last encounter associated episode
    2687                  """
    2688                  if issue_id is None:
    2689                          issues = None
    2690                  else:
    2691                          issues = [issue_id]
    2692
    2693                  if episode_id is None:
    2694                          episodes = None
    2695                  else:
    2696                          episodes = [episode_id]
    2697
    2698                  encounters = self.get_encounters(issues = issues, episodes = episodes, order_by = 'started DESC', max_encounters = 1)
    2699                  if len(encounters) == 0:
    2700                          return None
    2701
    2702                  return encounters[0]
    2703
    2704          last_encounter = property(get_last_encounter, lambda x:x)
    2705
    2706          #------------------------------------------------------------------
    2707 -        def get_encounter_stats_by_type(self, cover_period=None):
    2708                  args = {'pat': self.pk_patient, 'range': cover_period}
    2709                  where_parts = ['pk_patient = %(pat)s']
    2710                  if cover_period is not None:
    2711                          where_parts.append('last_affirmed > now() - %(range)s')
    2712
    2713                  cmd = """
    2714                          SELECT l10n_type, count(1) AS frequency
    2715                          FROM clin.v_pat_encounters
    2716                          WHERE
    2717                                  %s
    2718                          GROUP BY l10n_type
    2719                          ORDER BY frequency DESC
    2720                  """ % ' AND '.join(where_parts)
    2721                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2722                  return rows
    2723
    2724          #------------------------------------------------------------------
    2725 -        def get_last_but_one_encounter(self, issue_id=None, episode_id=None):
    2726
    2727                  args = {'pat': self.pk_patient}
    2728
    2729                  if (issue_id is None) and (episode_id is None):
    2730                          cmd = """
    2731                                  SELECT * FROM clin.v_pat_encounters
    2732                                  WHERE pk_patient = %(pat)s
    2733                                  ORDER BY started DESC
    2734                                  LIMIT 2
    2735                          """
    2736                  else:
    2737                          where_parts = []
    2738
    2739                          if issue_id is not None:
    2740                                  where_parts.append('pk_health_issue = %(issue)s')
    2741                                  args['issue'] = issue_id
    2742
    2743                          if episode_id is not None:
    2744                                  where_parts.append('pk_episode = %(epi)s')
    2745                                  args['epi'] = episode_id
    2746
    2747                          cmd = """
    2748                                  SELECT *
    2749                                  FROM clin.v_pat_encounters
    2750                                  WHERE
    2751                                          pk_patient = %%(pat)s
    2752                                                  AND
    2753                                          pk_encounter IN (
    2754                                                  SELECT distinct pk_encounter
    2755                                                  FROM clin.v_narrative
    2756                                                  WHERE
    2757                                                          %s
    2758                                          )
    2759                                  ORDER BY started DESC
    2760                                  LIMIT 2
    2761                          """ % ' AND '.join(where_parts)
    2762
    2763                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2764
    2765                  if len(rows) == 0:
    2766                          return None
    2767
    2768                  # just one encounter within the above limits
    2769                  if len(rows) == 1:
    2770                          # is it the current encounter ?
    2771                          if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
    2772                                  # yes
    2773                                  return None
    2774                          # no
    2775                          return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
    2776
    2777                  # more than one encounter
    2778                  if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
    2779                          return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
    2780
    2781                  return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
    2782
    2783          last_but_one_encounter = property(get_last_but_one_encounter, lambda x:x)
    2784
    2785          #------------------------------------------------------------------
    2786 -        def remove_empty_encounters(self):
    2787                  _log.debug('removing empty encounters for pk_identity [%s]', self.pk_patient)
    2788                  cfg_db = gmCfg.cCfgSQL()
    2789                  ttl = cfg_db.get2 (
    2790                          option = 'encounter.ttl_if_empty',
    2791                          workplace = _here.active_workplace,
    2792                          bias = 'user',
    2793                          default = '1 week'
    2794                  )
    2795  #               # FIXME: this should be done async
    2796                  cmd = "SELECT clin.remove_old_empty_encounters(%(pat)s::INTEGER, %(ttl)s::INTERVAL)"
    2797                  args = {'pat': self.pk_patient, 'ttl': ttl}
    2798                  try:
    2799                          rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
    2800                  except:
    2801                          _log.exception('error deleting empty encounters')
    2802                          return False
    2803
    2804                  if not rows[0][0]:
    2805                          _log.debug('no encounters deleted (less than 2 exist)')
    2806
    2807                  return True
    2808
    2809          #------------------------------------------------------------------
    2810          # API: measurements / test results
    2811          #------------------------------------------------------------------
    2812 -        def get_most_recent_results_for_patient(self, no_of_results=1):
    2813                  return gmPathLab.get_most_recent_results_for_patient (
    2814                          no_of_results = no_of_results,
    2815                          patient = self.pk_patient
    2816                  )
    2817
    2818          #------------------------------------------------------------------
    2819 -        def get_most_recent_results_in_loinc_group(self, loincs=None, no_of_results=1, consider_meta_type=False):
    2820                  return gmPathLab.get_most_recent_results_in_loinc_group (
    2821                          loincs = loincs,
    2822                          no_of_results = no_of_results,
    2823                          consider_meta_type = consider_meta_type,
    2824                          patient = self.pk_patient
    2825                  )
    2826
    2827          #------------------------------------------------------------------
    2828 -        def get_most_recent_results_for_test_type(self, test_type=None, no_of_results=1):
    2829                  return gmPathLab.get_most_recent_results_for_test_type (
    2830                          test_type = test_type,
    2831                          no_of_results = no_of_results,
    2832                          patient = self.pk_patient
    2833                  )
    2834
    2835          #------------------------------------------------------------------
    2836 -        def get_most_recent_result_for_test_types(self, pk_test_types=None):
    2837                  return gmPathLab.get_most_recent_result_for_test_types (
    2838                          pk_test_types = pk_test_types,
    2839                          pk_patient = self.pk_patient
    2840                  )
    2841
    2842          #------------------------------------------------------------------
    2843 -        def get_result_at_timestamp(self, timestamp=None, test_type=None, loinc=None, tolerance_interval='12 hours'):
    2844                  return gmPathLab.get_result_at_timestamp (
    2845                          timestamp = timestamp,
    2846                          test_type = test_type,
    2847                          loinc = loinc,
    2848                          tolerance_interval = tolerance_interval,
    2849                          patient = self.pk_patient
    2850                  )
    2851
    2852          #------------------------------------------------------------------
    2853 -        def get_results_for_day(self, timestamp=None, order_by=None):
    2854                  return gmPathLab.get_results_for_day (
    2855                          timestamp = timestamp,
    2856                          patient = self.pk_patient,
    2857                          order_by = order_by
    2858                  )
    2859
    2860          #------------------------------------------------------------------
    2861 -        def get_results_for_issue(self, pk_health_issue=None, order_by=None):
    2862                  return gmPathLab.get_results_for_issue (
    2863                          pk_health_issue = pk_health_issue,
    2864                          order_by = order_by
    2865                  )
    2866
    2867          #------------------------------------------------------------------
    2868 -        def get_results_for_episode(self, pk_episode=None):
    2869                  return gmPathLab.get_results_for_episode(pk_episode = pk_episode)
    2870
    2871          #------------------------------------------------------------------
    2872 -        def get_unsigned_results(self, order_by=None):
    2873                  if order_by is None:
    2874                          order_by = ''
    2875                  else:
    2876                          order_by = 'ORDER BY %s' % order_by
    2877                  cmd = """
    2878                          SELECT * FROM clin.v_test_results
    2879                          WHERE
    2880                                  pk_patient = %%(pat)s
    2881                                          AND
    2882                                  reviewed IS FALSE
    2883                          %s""" % order_by
    2884                  args = {'pat': self.pk_patient}
    2885                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2886                  return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
    2887
    2888          #------------------------------------------------------------------
    2889          # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
    2890 -        def get_test_types_for_results(self, order_by=None, unique_meta_types=False):
    2891                  """Retrieve data about test types for which this patient has results."""
    2892                  if order_by is None:
    2893                          order_by = ''
    2894                  else:
    2895                          order_by = 'ORDER BY %s' % order_by
    2896
    2897                  if unique_meta_types:
    2898                          cmd = """
    2899                                  SELECT * FROM clin.v_test_types c_vtt
    2900                                  WHERE c_vtt.pk_test_type IN (
    2901                                                  SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type
    2902                                                  FROM clin.v_test_results c_vtr1
    2903                                                  WHERE
    2904                                                          c_vtr1.pk_patient = %%(pat)s
    2905                                                                  AND
    2906                                                          c_vtr1.pk_meta_test_type IS NOT NULL
    2907                                          UNION ALL
    2908                                                  SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type
    2909                                                  FROM clin.v_test_results c_vtr2
    2910                                                  WHERE
    2911                                                          c_vtr2.pk_patient = %%(pat)s
    2912                                                                  AND
    2913                                                          c_vtr2.pk_meta_test_type IS NULL
    2914                                  )
    2915                                  %s""" % order_by
    2916                  else:
    2917                          cmd = """
    2918                                  SELECT * FROM clin.v_test_types c_vtt
    2919                                  WHERE c_vtt.pk_test_type IN (
    2920                                          SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type
    2921                                          FROM clin.v_test_results c_vtr
    2922                                          WHERE c_vtr.pk_patient = %%(pat)s
    2923                                  )
    2924                                  %s""" % order_by
    2925
    2926                  args = {'pat': self.pk_patient}
    2927                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    2928                  return [ gmPathLab.cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
    2929
    2930          #------------------------------------------------------------------
    2931 -        def get_dates_for_results(self, tests=None, reverse_chronological=True):
    2932                  """Get the dates for which we have results."""
    2933                  where_parts = ['pk_patient = %(pat)s']
    2934                  args = {'pat': self.pk_patient}
    2935
    2936                  if tests is not None:
    2937                          where_parts.append('pk_test_type IN %(tests)s')
    2938                          args['tests'] = tuple(tests)
    2939
    2940                  cmd = """
    2941                          SELECT DISTINCT ON (clin_when_day)
    2942                                  clin_when_day,
    2943                                  is_reviewed
    2944                          FROM (
    2945                                  SELECT
    2946                                          date_trunc('day', clin_when)
    2947                                                  AS clin_when_day,
    2948                                          bool_and(reviewed)
    2949                                                  AS is_reviewed
    2950                                  FROM (
    2951                                          SELECT
    2952                                                  clin_when,
    2953                                                  reviewed,
    2954                                                  pk_patient,
    2955                                                  pk_test_result
    2956                                          FROM clin.v_test_results
    2957                                          WHERE %s
    2958                                  )
    2959                                          AS patient_tests
    2960                                  GROUP BY clin_when_day
    2961                          )
    2962                                  AS grouped_days
    2963                          ORDER BY clin_when_day %s
    2964                  """ % (
    2965                          ' AND '.join(where_parts),
    2966                          gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
    2967                  )
    2968                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    2969                  return rows
    2970
    2971          #------------------------------------------------------------------
    2972 -        def get_issues_or_episodes_for_results(self, tests=None):
    2973                  """Get the issues/episodes for which we have results."""
    2974                  where_parts = ['pk_patient = %(pat)s']
    2975                  args = {'pat': self.pk_patient}
    2976
    2977                  if tests is not None:
    2978                          where_parts.append('pk_test_type IN %(tests)s')
    2979                          args['tests'] = tuple(tests)
    2980                  where = ' AND '.join(where_parts)
    2981                  cmd = """
    2982                  SELECT * FROM ((
    2983                          -- issues, each including all it"s episodes
    2984                          SELECT
    2985                                  health_issue AS problem,
    2986                                  pk_health_issue,
    2987                                  NULL::integer AS pk_episode,
    2988                                  1 AS rank
    2989                          FROM clin.v_test_results
    2990                          WHERE pk_health_issue IS NOT NULL AND %s
    2991                          GROUP BY pk_health_issue, problem
    2992                  ) UNION ALL (
    2993                          -- episodes w/o issue
    2994                          SELECT
    2995                                  episode AS problem,
    2996                                  NULL::integer AS pk_health_issue,
    2997                                  pk_episode,
    2998                                  2 AS rank
    2999                          FROM clin.v_test_results
    3000                          WHERE pk_health_issue IS NULL AND %s
    3001                          GROUP BY pk_episode, problem
    3002                  )) AS grouped_union
    3003                  ORDER BY rank, problem
    3004                  """ % (where, where)
    3005                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    3006                  return rows
    3007
    3008          #------------------------------------------------------------------
    3009 -        def get_test_results(self, encounters=None, episodes=None, tests=None, order_by=None):
    3010                  return gmPathLab.get_test_results (
    3011                          pk_patient = self.pk_patient,
    3012                          encounters = encounters,
    3013                          episodes = episodes,
    3014                          order_by = order_by
    3015                  )
    3016          #------------------------------------------------------------------
    3017 -        def get_test_results_by_date(self, encounter=None, episodes=None, tests=None, reverse_chronological=True):
    3018
    3019                  where_parts = ['pk_patient = %(pat)s']
    3020                  args = {'pat': self.pk_patient}
    3021
    3022                  if tests is not None:
    3023                          where_parts.append('pk_test_type IN %(tests)s')
    3024                          args['tests'] = tuple(tests)
    3025
    3026                  if encounter is not None:
    3027                          where_parts.append('pk_encounter = %(enc)s')
    3028                          args['enc'] = encounter
    3029
    3030                  if episodes is not None:
    3031                          where_parts.append('pk_episode IN %(epis)s')
    3032                          args['epis'] = tuple(episodes)
    3033
    3034                  cmd = """
    3035                          SELECT * FROM clin.v_test_results
    3036                          WHERE %s
    3037                          ORDER BY clin_when %s, pk_episode, unified_name
    3038                  """ % (
    3039                          ' AND '.join(where_parts),
    3040                          gmTools.bool2subst(reverse_chronological, 'DESC', 'ASC', 'DESC')
    3041                  )
    3042                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    3043
    3044                  tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
    3045
    3046                  return tests
    3047          #------------------------------------------------------------------
    3048 -        def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
    3049
    3050                  try:
    3051                          epi = int(episode)
    3052                  except:
    3053                          epi = episode['pk_episode']
    3054
    3055                  try:
    3056                          type = int(type)
    3057                  except:
    3058                          type = type['pk_test_type']
    3059
    3060                  tr = gmPathLab.create_test_result (
    3061                          link_obj = link_obj,
    3062                          encounter = self.current_encounter['pk_encounter'],
    3063                          episode = epi,
    3064                          type = type,
    3065                          intended_reviewer = intended_reviewer,
    3066                          val_num = val_num,
    3067                          val_alpha = val_alpha,
    3068                          unit = unit
    3069                  )
    3070
    3071                  return tr
    3072
    3073          #------------------------------------------------------------------
    3074 -        def get_labs_as_org_units(self):
    3075                  where = 'pk_org_unit IN (%s)' % """
    3076                          SELECT DISTINCT fk_org_unit FROM clin.test_org WHERE pk IN (
    3077                                  SELECT DISTINCT pk_test_org FROM clin.v_test_results where pk_patient = %(pat)s
    3078                          )"""
    3079                  args = {'pat': self.pk_patient}
    3080                  cmd = gmOrganization._SQL_get_org_unit % where
    3081                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    3082                  return [ gmOrganization.cOrgUnit(row = {'pk_field': 'pk_org_unit', 'data': r, 'idx': idx}) for r in rows ]
    3083
    3084          #------------------------------------------------------------------
    3085 -        def _get_best_gfr_or_crea(self):
    3086
    3087                  measured_gfr = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_gfr_quantity, no_of_results = 1)
    3088                  crea = self.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_creatinine_quantity, no_of_results = 1)
    3089
    3090                  if (measured_gfr is None) and (crea is None):
    3091                          return None
    3092
    3093                  if (measured_gfr is not None) and (crea is None):
    3094                          return measured_gfr
    3095
    3096                  # from here, Crea cannot be None anymore
    3097                  if measured_gfr is None:
    3098                          eGFR = self.calculator.eGFR
    3099                          if eGFR.numeric_value is None:
    3100                                  return crea
    3101                          return eGFR
    3102
    3103                  # from here, measured_gfr cannot be None anymore, either
    3104                  two_weeks = pydt.timedelta(weeks = 2)
    3105                  gfr_too_old = (crea['clin_when'] - measured_gfr['clin_when']) > two_weeks
    3106                  if not gfr_too_old:
    3107                          return measured_gfr
    3108
    3109                  # from here, measured_gfr is considered too
    3110                  # old, so attempt a more timely estimate
    3111                  eGFR = self.calculator.eGFR
    3112                  if eGFR.numeric_value is None:
    3113                          # return crea since we cannot get a
    3114                          # better estimate for some reason
    3115                          return crea
    3116
    3117                  return eGFR
    3118
    3119          best_gfr_or_crea = property(_get_best_gfr_or_crea, lambda x:x)
    3120
    3121          #------------------------------------------------------------------
    3122 -        def _get_bmi(self):
    3123                  return self.calculator.bmi
    3124
    3125          bmi = property(_get_bmi, lambda x:x)
    3126
    3127          #------------------------------------------------------------------
    3128 -        def _get_dynamic_hints(self):
    3129                  return gmAutoHints.get_hints_for_patient(pk_identity = self.pk_patient, pk_encounter = self.current_encounter['pk_encounter'])
    3130
    3131          dynamic_hints = property(_get_dynamic_hints, lambda x:x)
    3132
    3133          #------------------------------------------------------------------
    3134          #------------------------------------------------------------------
    3135          #------------------------------------------------------------------
    3136 -        def get_lab_request(self, pk=None, req_id=None, lab=None):
    3137                  # FIXME: verify that it is our patient ? ...
    3138                  req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
    3139                  return req
    3140          #------------------------------------------------------------------
    3141 -        def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
    3142                  if encounter_id is None:
    3143                          encounter_id = self.current_encounter['pk_encounter']
    3144                  status, data = gmPathLab.create_lab_request(
    3145                          lab=lab,
    3146                          req_id=req_id,
    3147                          pat_id=self.pk_patient,
    3148                          encounter_id=encounter_id,
    3149                          episode_id=episode_id
    3150                  )
    3151                  if not status:
    3152                          _log.error(str(data))
    3153                          return None
    3154                  return data
    3155
    3156  #============================================================
    3157  # main
    3158  #------------------------------------------------------------
    3159  if __name__ == "__main__":
    3160
    3161          if len(sys.argv) == 1:
    3162                  sys.exit()
    3163
    3164          if sys.argv[1] != 'test':
    3165                  sys.exit()
    3166
    3167          from Gnumed.pycommon import gmLog2
    3168
    3169          from Gnumed.business import gmPraxis
    3170          branches = gmPraxis.get_praxis_branches()
    3171          praxis = gmPraxis.gmCurrentPraxisBranch(branches[0])
    3172
    3173 -        def _do_delayed(*args, **kwargs):
    3174                  print(args)
    3175                  print(kwargs)
    3176                  args[0](*args[1:], **kwargs)
    3177
    3178          set_delayed_executor(_do_delayed)
    3179
    3180          #-----------------------------------------
    3181 -        def test_allergy_state():
    3182                  emr = cClinicalRecord(aPKey=1)
    3183                  state = emr.allergy_state
    3184                  print("allergy state is:", state)
    3185
    3186                  print("setting state to 0")
    3187                  emr.allergy_state = 0
    3188
    3189                  print("setting state to None")
    3190                  emr.allergy_state = None
    3191
    3192                  print("setting state to 'abc'")
    3193                  emr.allergy_state = 'abc'
    3194
    3195          #-----------------------------------------
    3196 -        def test_get_test_names():
    3197                  emr = cClinicalRecord(aPKey = 6)
    3198                  rows = emr.get_test_types_for_results(unique_meta_types = True)
    3199                  print("test result names:", len(rows))
    3200  #               for row in rows:
    3201  #                       print row
    3202
    3203          #-----------------------------------------
    3204 -        def test_get_dates_for_results():
    3205                  emr = cClinicalRecord(aPKey=12)
    3206                  rows = emr.get_dates_for_results()
    3207                  print("test result dates:")
    3208                  for row in rows:
    3209                          print(row)
    3210
    3211          #-----------------------------------------
    3212 -        def test_get_measurements():
    3213                  emr = cClinicalRecord(aPKey=12)
    3214                  rows, idx = emr.get_measurements_by_date()
    3215                  print("test results:")
    3216                  for row in rows:
    3217                          print(row)
    3218
    3219          #-----------------------------------------
    3220 -        def test_get_test_results_by_date():
    3221                  emr = cClinicalRecord(aPKey=12)
    3222                  tests = emr.get_test_results_by_date()
    3223                  print("test results:")
    3224                  for test in tests:
    3225                          print(test)
    3226
    3227          #-----------------------------------------
    3228 -        def test_get_statistics():
    3229                  emr = cClinicalRecord(aPKey=12)
    3230                  for key, item in emr.get_statistics().items():
    3231                          print(key, ":", item)
    3232
    3233          #-----------------------------------------
    3234 -        def test_get_problems():
    3235                  emr = cClinicalRecord(aPKey=12)
    3236
    3237                  probs = emr.get_problems()
    3238                  print("normal probs (%s):" % len(probs))
    3239                  for p in probs:
    3240                          print('%s (%s)' % (p['problem'], p['type']))
    3241
    3242                  probs = emr.get_problems(include_closed_episodes=True)
    3243                  print("probs + closed episodes (%s):" % len(probs))
    3244                  for p in probs:
    3245                          print('%s (%s)' % (p['problem'], p['type']))
    3246
    3247                  probs = emr.get_problems(include_irrelevant_issues=True)
    3248                  print("probs + issues (%s):" % len(probs))
    3249                  for p in probs:
    3250                          print('%s (%s)' % (p['problem'], p['type']))
    3251
    3252                  probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
    3253                  print("probs + issues + epis (%s):" % len(probs))
    3254                  for p in probs:
    3255                          print('%s (%s)' % (p['problem'], p['type']))
    3256
    3257          #-----------------------------------------
    3258 -        def test_add_test_result():
    3259                  emr = cClinicalRecord(aPKey=12)
    3260                  tr = emr.add_test_result (
    3261                          episode = 1,
    3262                          intended_reviewer = 1,
    3263                          type = 1,
    3264                          val_num = 75,
    3265                          val_alpha = 'somewhat obese',
    3266                          unit = 'kg'
    3267                  )
    3268                  print(tr)
    3269
    3270          #-----------------------------------------
    3271 -        def test_get_most_recent_episode():
    3272                  emr = cClinicalRecord(aPKey=12)
    3273                  print(emr.get_most_recent_episode(issue = 2))
    3274
    3275          #-----------------------------------------
    3276 -        def test_get_almost_recent_encounter():
    3277                  emr = cClinicalRecord(aPKey=12)
    3278                  print(emr.get_last_encounter(issue_id=2))
    3279                  print(emr.get_last_but_one_encounter(issue_id=2))
    3280
    3281          #-----------------------------------------
    3282 -        def test_get_encounters():
    3283                  emr = cClinicalRecord(aPKey = 5)
    3284                  print(emr.get_first_encounter(episode_id = 1638))
    3285                  print(emr.get_last_encounter(episode_id = 1638))
    3286
    3287          #-----------------------------------------
    3288 -        def test_get_issues():
    3289                  emr = cClinicalRecord(aPKey = 12)
    3290                  for issue in emr.health_issues:
    3291                          print(issue['description'])
    3292
    3293          #-----------------------------------------
    3294 -        def test_get_dx():
    3295                  emr = cClinicalRecord(aPKey = 12)
    3296                  for dx in emr.candidate_diagnoses:
    3297                          print(dx)
    3298
    3299          #-----------------------------------------
    3300 -        def test_get_meds():
    3301                  emr = cClinicalRecord(aPKey=12)
    3302                  for med in emr.get_current_medications():
    3303                          print(med)
    3304
    3305          #-----------------------------------------
    3306 -        def test_get_abuses():
    3307                  emr = cClinicalRecord(aPKey=12)
    3308                  for med in emr.abused_substances:
    3309                          print(med.format(single_line = True))
    3310
    3311          #-----------------------------------------
    3312 -        def test_is_allergic_to():
    3313                  emr = cClinicalRecord(aPKey = 12)
    3314                  print(emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), product_name = sys.argv[2]))
    3315
    3316          #-----------------------------------------
    3317 -        def test_get_as_journal():
    3318                  emr = cClinicalRecord(aPKey = 12)
    3319                  for journal_line in emr.get_as_journal():
    3320                          #print journal_line.keys()
    3321                          print('%(date)s  %(modified_by)s  %(soap_cat)s  %(narrative)s' % journal_line)
    3322                          print("")
    3323
    3324          #-----------------------------------------
    3325 -        def test_get_most_recent():
    3326                  emr = cClinicalRecord(aPKey=12)
    3327                  print(emr.get_most_recent_results_for_test_type())
    3328
    3329          #-----------------------------------------
    3330 -        def test_episodes():
    3331                  emr = cClinicalRecord(aPKey=12)
    3332                  print("episodes:", emr.episodes)
    3333                  print("unlinked:", emr.unlinked_episodes)
    3334
    3335          #-----------------------------------------
    3336 -        def test_format_as_journal():
    3337                  emr = cClinicalRecord(aPKey=12)
    3338                  from Gnumed.business.gmPerson import cPatient
    3339                  pat = cPatient(aPK_obj = 12)
    3340                  print(emr.format_as_journal(left_margin = 1, patient = pat))
    3341
    3342          #-----------------------------------------
    3343 -        def test_smoking():
    3344                  emr = cClinicalRecord(aPKey=12)
    3345                  #print emr.is_or_was_smoker
    3346                  smoking, details = emr.smoking_status
    3347                  print('status:', smoking)
    3348                  print('details:')
    3349                  print(details)
    3350                  emr.smoking_status = (True, {'comment': '2', 'last_confirmed': gmDateTime.pydt_now_here()})
    3351                  print(emr.smoking_status)
    3352                  print(emr.alcohol_status)
    3353                  print(emr.drugs_status)
    3354
    3355          #-----------------------------------------
    3356
    3357          #test_allergy_state()
    3358          #test_is_allergic_to()
    3359
    3360          #test_get_test_names()
    3361          #test_get_dates_for_results()
    3362          #test_get_measurements()
    3363          #test_get_test_results_by_date()
    3364          #test_get_statistics()
    3365          #test_get_problems()
    3366          #test_add_test_result()
    3367          #test_get_most_recent_episode()
    3368          #test_get_almost_recent_encounter()
    3369          #test_get_meds()
    3370          #test_get_as_journal()
    3371          #test_get_most_recent()
    3372          #test_episodes()
    3373          #test_format_as_journal()
    3374          #test_smoking()
    3375          #test_get_abuses()
    3376          #test_get_encounters()
    3377          #test_get_issues()
    3378          test_get_dx()
    3379
    3380  #       emr = cClinicalRecord(aPKey = 12)
    3381
    3382  #       # Vacc regimes
    3383  #       vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
    3384  #       print '\nVaccination regimes: '
    3385  #       for a_regime in vacc_regimes:
    3386  #               pass
    3387  #               #print a_regime
    3388  #       vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
    3389  #       #print vacc_regime
    3390
    3391  #       # vaccination regimes and vaccinations for regimes
    3392  #       scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
    3393  #       print 'Vaccinations for the regime:'
    3394  #       for a_scheduled_vacc in scheduled_vaccs:
    3395  #               pass
    3396  #               #print '   %s' %(a_scheduled_vacc)
    3397
    3398  #       # vaccination next shot and booster
    3399  #       vaccinations = emr.get_vaccinations()
    3400  #       for a_vacc in vaccinations:
    3401  #               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'])
    3402
    3403  #       # first and last encounters
    3404  #       first_encounter = emr.get_first_encounter(issue_id = 1)
    3405  #       print '\nFirst encounter: ' + str(first_encounter)
    3406  #       last_encounter = emr.get_last_encounter(episode_id = 1)
    3407  #       print '\nLast encounter: ' + str(last_encounter)
    3408  #       print ''
    3409
    3410          #dump = record.get_missing_vaccinations()
    3411          #f = io.open('vaccs.lst', 'wb')
    3412          #if dump is not None:
    3413          #       print "=== due ==="
    3414          #       f.write(u"=== due ===\n")
    3415          #       for row in dump['due']:
    3416          #               print row
    3417          #               f.write(repr(row))
    3418          #               f.write(u'\n')
    3419          #       print "=== overdue ==="
    3420          #       f.write(u"=== overdue ===\n")
    3421          #       for row in dump['overdue']:
    3422          #               print row
    3423          #               f.write(repr(row))
    3424          #               f.write(u'\n')
    3425          #f.close()
    3426