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