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

# Source Code for Module Gnumed.business.gmPathLab

       1  """GNUmed measurements related business objects."""
       2  #============================================================
       3  __version__ = "$Revision: 1.81 $"
       4  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
       5  __license__ = "GPL"
       6
       7
       8  import types, sys, logging, codecs, decimal
       9
      10  if __name__ == '__main__':
      11          sys.path.insert(0, '../../')
      12
      13  from Gnumed.pycommon import gmDateTime
      14  if __name__ == '__main__':
      15          from Gnumed.pycommon import gmLog2
      16          from Gnumed.pycommon import gmI18N
      17          gmDateTime.init()
      18  from Gnumed.pycommon import gmExceptions
      19  from Gnumed.pycommon import gmBusinessDBObject
      20  from Gnumed.pycommon import gmPG2
      21  from Gnumed.pycommon import gmTools
      22  from Gnumed.pycommon import gmDispatcher
      23  from Gnumed.pycommon import gmHooks
      24  from Gnumed.business import gmOrganization
      25
      26
      27  _log = logging.getLogger('gm.lab')
      28  _log.info(__version__)
      29
      30  # FIXME: use UCUM from Regenstrief Institute
      31
      32  #============================================================
      33 -def _on_test_result_modified():
      34          """Always relates to the active patient."""
      35          gmHooks.run_hook_script(hook = u'after_test_result_modified')
      36
      37  gmDispatcher.connect(_on_test_result_modified, u'test_result_mod_db')
      38
      39  #============================================================
      40 -class cTestOrg(gmBusinessDBObject.cBusinessDBObject):
      41          """Represents one test org/lab."""
      42          _cmd_fetch_payload = u"""SELECT * FROM clin.v_test_orgs WHERE pk_test_org = %s"""
      43          _cmds_store_payload = [
      44                  u"""UPDATE clin.test_org SET
      45                                  fk_org_unit = %(pk_org_unit)s,
      46                                  contact = gm.nullify_empty_string(%(test_org_contact)s),
      47                                  comment = gm.nullify_empty_string(%(comment)s)
      48                          WHERE
      49                                  pk = %(pk_test_org)s
      50                                          AND
      51                                  xmin = %(xmin_test_org)s
      52                          RETURNING
      53                                  xmin AS xmin_test_org
      54                  """
      55          ]
      56          _updatable_fields = [
      57                  u'pk_org_unit',
      58                  u'test_org_contact',
      59                  u'comment'
      60          ]
      61  #------------------------------------------------------------
      62 -def create_test_org(name=None, comment=None, pk_org_unit=None):
      63
      64          if name is None:
      65                  name = _('inhouse lab')
      66                  comment = _('auto-generated')
      67
      68          # get org unit
      69          if pk_org_unit is None:
      70                  org = gmOrganization.org_exists(organization = name)
      71                  if org is None:
      72                          org = gmOrganization.create_org (
      73                                  organization = name,
      74                                  category = u'Laboratory'
      75                          )
      76                  org_unit = gmOrganization.create_org_unit (
      77                          pk_organization = org['pk_org'],
      78                          unit = name
      79                  )
      80                  pk_org_unit = org_unit['pk_org_unit']
      81
      82          # test org exists ?
      83          args = {'pk_unit': pk_org_unit}
      84          cmd = u'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s'
      85          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
      86
      87          if len(rows) == 0:
      88                  cmd = u'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk'
      89                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
      90
      91          test_org = cTestOrg(aPK_obj = rows[0][0])
      92          if comment is not None:
      93                  comment = comment.strip()
      94          test_org['comment'] = comment
      95          test_org.save()
      96
      97          return test_org
      98  #------------------------------------------------------------
      99 -def delete_test_org(test_org=None):
     100          args = {'pk': test_org}
     101          cmd = u"""
     102                  DELETE FROM clin.test_org
     103                  WHERE
     104                          pk = %(pk)s
     105                                  AND
     106                          NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1)
     107                                  AND
     108                          NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1)
     109          """
     110          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     111  #------------------------------------------------------------
     112 -def get_test_orgs(order_by=u'unit'):
     113          cmd = u'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by
     114          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
     115          return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
     116  #============================================================
     117 -class cMetaTestType(gmBusinessDBObject.cBusinessDBObject):
     118          """Represents one meta test type under which actual test types can be aggregated."""
     119
     120          _cmd_fetch_payload = u"""select * from clin.meta_test_type where pk = %s"""
     121
     122          _cmds_store_payload = []
     123
     124          _updatable_fields = []
     125  #------------------------------------------------------------
     126 -def delete_meta_type(meta_type=None):
     127          cmd = u'delete from clin.meta_test_type where pk = %(pk)s'
     128          args = {'pk': meta_type}
     129          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     130  #------------------------------------------------------------
     131 -def get_meta_test_types():
     132          cmd = u'select * from clin.meta_test_type'
     133          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
     134          return [ cMetaTestType(row = {'pk_field': 'pk', 'data': r, 'idx': idx}) for r in rows ]
     135  #============================================================
     136 -class cUnifiedTestType(gmBusinessDBObject.cBusinessDBObject):
     137          """Represents one unified test type."""
     138
     139          # FIXME: if we ever want to write we need to include XMIN in the view
     140          _cmd_fetch_payload = u"""select * from clin.v_unified_test_types where pk_test_type = %s"""
     141
     142          _cmds_store_payload = []
     143
     144          _updatable_fields = []
     145          #--------------------------------------------------------
     146 -        def get_most_recent_result(self, pk_patient=None):
     147                  cmd = u"""
     148                          SELECT pk_test_result, clin_when
     149                          FROM clin.v_test_results
     150                          WHERE
     151                                  pk_patient = %(pat)s
     152                                          AND
     153                                  pk_meta_test_type = %(pkmtt)s
     154                          ORDER BY clin_when DESC
     155                          LIMIT 1
     156                  """
     157                  args = {'pat': pk_patient, 'pkmtt': self._payload[self._idx['pk_meta_test_type']]}
     158                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     159                  if len(rows) == 0:
     160                          return None
     161                  return cTestResult(aPK_obj = rows[0]['pk_test_result'])
     162  #============================================================
     163 -class cMeasurementType(gmBusinessDBObject.cBusinessDBObject):
     164          """Represents one test result type."""
     165
     166          _cmd_fetch_payload = u"""select * from clin.v_test_types where pk_test_type = %s"""
     167
     168          _cmds_store_payload = [
     169                  u"""update clin.test_type set
     170                                  abbrev = %(abbrev)s,
     171                                  name = %(name)s,
     172                                  loinc = gm.nullify_empty_string(%(loinc)s),
     173                                  code = gm.nullify_empty_string(%(code)s),
     174                                  coding_system = gm.nullify_empty_string(%(coding_system)s),
     175                                  comment = gm.nullify_empty_string(%(comment_type)s),
     176                                  conversion_unit = gm.nullify_empty_string(%(conversion_unit)s),
     177                                  fk_test_org = %(pk_test_org)s
     178                          where pk = %(pk_test_type)s""",
     179                  u"""select xmin_test_type from clin.v_test_types where pk_test_type = %(pk_test_type)s"""
     180          ]
     181
     182          _updatable_fields = [
     183                  'abbrev',
     184                  'name',
     185                  'loinc',
     186                  'code',
     187                  'coding_system',
     188                  'comment_type',
     189                  'conversion_unit',
     190                  'pk_test_org'
     191          ]
     192          #--------------------------------------------------------
     193  #       def __setitem__(self, attribute, value):
     194  #
     195  #               # find fk_test_org from name
     196  #               if (attribute == 'fk_test_org') and (value is not None):
     197  #                       try:
     198  #                               int(value)
     199  #                       except:
     200  #                               cmd = u"select pk from clin.test_org where internal _name = %(val)s"
     201  #                               rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'val': value}}])
     202  #                               if len(rows) == 0:
     203  #                                       raise ValueError('[%s]: no test org for [%s], cannot set <%s>' % (self.__class__.__name__, value, attribute))
     204  #                               value = rows[0][0]
     205  #
     206  #               gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
     207          #--------------------------------------------------------
     208 -        def _get_in_use(self):
     209                  cmd = u'select exists(select 1 from clin.test_result where fk_type = %(pk_type)s)'
     210                  args = {'pk_type': self._payload[self._idx['pk_test_type']]}
     211                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     212                  return rows[0][0]
     213
     214          in_use = property(_get_in_use, lambda x:x)
     215  #------------------------------------------------------------
     216 -def get_measurement_types(order_by=None):
     217          cmd = u'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, u'', u'order by %s')
     218          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
     219          return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
     220  #------------------------------------------------------------
     221 -def find_measurement_type(lab=None, abbrev=None, name=None):
     222
     223                  if (abbrev is None) and (name is None):
     224                          raise ValueError('must have <abbrev> and/or <name> set')
     225
     226                  where_snippets = []
     227
     228                  if lab is None:
     229                          where_snippets.append('pk_test_org IS NULL')
     230                  else:
     231                          try:
     232                                  int(lab)
     233                                  where_snippets.append('pk_test_org = %(lab)s')
     234                          except (TypeError, ValueError):
     235                                  where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
     236
     237                  if abbrev is not None:
     238                          where_snippets.append('abbrev = %(abbrev)s')
     239
     240                  if name is not None:
     241                          where_snippets.append('name = %(name)s')
     242
     243                  where_clause = u' and '.join(where_snippets)
     244                  cmd = u"select * from clin.v_test_types where %s" % where_clause
     245                  args = {'lab': lab, 'abbrev': abbrev, 'name': name}
     246
     247                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
     248
     249                  if len(rows) == 0:
     250                          return None
     251
     252                  tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
     253                  return tt
     254  #------------------------------------------------------------
     255 -def delete_measurement_type(measurement_type=None):
     256          cmd = u'delete from clin.test_type where pk = %(pk)s'
     257          args = {'pk': measurement_type}
     258          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     259  #------------------------------------------------------------
     260 -def create_measurement_type(lab=None, abbrev=None, unit=None, name=None):
     261          """Create or get test type."""
     262
     263          ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name)
     264          # found ?
     265          if ttype is not None:
     266                  return ttype
     267
     268          _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit)
     269
     270          # not found, so create it
     271          if unit is None:
     272                  _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit))
     273                  raise ValueError('need <unit> to create test type')
     274
     275          # make query
     276          cols = []
     277          val_snippets = []
     278          vals = {}
     279
     280          # lab
     281          if lab is None:
     282                  lab = create_test_org()['pk_test_org']
     283
     284          cols.append('fk_test_org')
     285          try:
     286                  vals['lab'] = int(lab)
     287                  val_snippets.append('%(lab)s')
     288          except:
     289                  vals['lab'] = lab
     290                  val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
     291
     292          # code
     293          cols.append('abbrev')
     294          val_snippets.append('%(abbrev)s')
     295          vals['abbrev'] = abbrev
     296
     297          # unit
     298          cols.append('conversion_unit')
     299          val_snippets.append('%(unit)s')
     300          vals['unit'] = unit
     301
     302          # name
     303          if name is not None:
     304                  cols.append('name')
     305                  val_snippets.append('%(name)s')
     306                  vals['name'] = name
     307
     308          col_clause = u', '.join(cols)
     309          val_clause = u', '.join(val_snippets)
     310          queries = [
     311                  {'cmd': u'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals},
     312                  {'cmd': u"select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"}
     313          ]
     314          rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
     315          ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
     316
     317          return ttype
     318  #============================================================
     319 -class cTestResult(gmBusinessDBObject.cBusinessDBObject):
     320          """Represents one test result."""
     321
     322          _cmd_fetch_payload = u"select * from clin.v_test_results where pk_test_result = %s"
     323
     324          _cmds_store_payload = [
     325                  u"""update clin.test_result set
     326                                  clin_when = %(clin_when)s,
     327                                  narrative = nullif(trim(%(comment)s), ''),
     328                                  val_num = %(val_num)s,
     329                                  val_alpha = nullif(trim(%(val_alpha)s), ''),
     330                                  val_unit = nullif(trim(%(val_unit)s), ''),
     331                                  val_normal_min = %(val_normal_min)s,
     332                                  val_normal_max = %(val_normal_max)s,
     333                                  val_normal_range = nullif(trim(%(val_normal_range)s), ''),
     334                                  val_target_min = %(val_target_min)s,
     335                                  val_target_max = %(val_target_max)s,
     336                                  val_target_range = nullif(trim(%(val_target_range)s), ''),
     337                                  abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''),
     338                                  norm_ref_group = nullif(trim(%(norm_ref_group)s), ''),
     339                                  note_test_org = nullif(trim(%(note_test_org)s), ''),
     340                                  material = nullif(trim(%(material)s), ''),
     341                                  material_detail = nullif(trim(%(material_detail)s), ''),
     342                                  fk_intended_reviewer = %(pk_intended_reviewer)s,
     343                                  fk_encounter = %(pk_encounter)s,
     344                                  fk_episode = %(pk_episode)s,
     345                                  fk_type = %(pk_test_type)s,
     346                                  fk_request = %(pk_request)s
     347                          where
     348                                  pk = %(pk_test_result)s and
     349                                  xmin = %(xmin_test_result)s""",
     350                  u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s"""
     351          ]
     352
     353          _updatable_fields = [
     354                  'clin_when',
     355                  'comment',
     356                  'val_num',
     357                  'val_alpha',
     358                  'val_unit',
     359                  'val_normal_min',
     360                  'val_normal_max',
     361                  'val_normal_range',
     362                  'val_target_min',
     363                  'val_target_max',
     364                  'val_target_range',
     365                  'abnormality_indicator',
     366                  'norm_ref_group',
     367                  'note_test_org',
     368                  'material',
     369                  'material_detail',
     370                  'pk_intended_reviewer',
     371                  'pk_encounter',
     372                  'pk_episode',
     373                  'pk_test_type',
     374                  'pk_request'
     375          ]
     376          #--------------------------------------------------------
     377 -        def format(self, with_review=True, with_comments=True, date_format='%Y-%m-%d %H:%M'):
     378
     379                  lines = []
     380
     381                  lines.append(u' %s %s (%s): %s %s%s' % (
     382                          self._payload[self._idx['clin_when']].strftime(date_format),
     383                          self._payload[self._idx['unified_abbrev']],
     384                          self._payload[self._idx['unified_name']],
     385                          self._payload[self._idx['unified_val']],
     386                          self._payload[self._idx['val_unit']],
     387                          gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], u'', u' (%s)')
     388                  ))
     389
     390                  if with_comments:
     391                          if gmTools.coalesce(self._payload[self._idx['comment']], u'').strip() != u'':
     392                                  lines.append(_('   Doc: %s') % self._payload[self._idx['comment']].strip())
     393                          if gmTools.coalesce(self._payload[self._idx['note_test_org']], u'').strip() != u'':
     394                                  lines.append(_('   MTA: %s') % self._payload[self._idx['note_test_org']].strip())
     395
     396                  if with_review:
     397                          if self._payload[self._idx['reviewed']]:
     398                                  if self._payload[self._idx['is_clinically_relevant']]:
     399                                          lines.append(u'   %s  %s: %s' % (
     400                                                  self._payload[self._idx['last_reviewer']],
     401                                                  self._payload[self._idx['last_reviewed']].strftime('%Y-%m-%d %H:%M'),
     402                                                  gmTools.bool2subst (
     403                                                          self._payload[self._idx['is_technically_abnormal']],
     404                                                          _('abnormal and relevant'),
     405                                                          _('normal but relevant')
     406                                                  )
     407                                          ))
     408                          else:
     409                                  lines.append(_('   unreviewed'))
     410
     411                  return lines
     412          #--------------------------------------------------------
     413 -        def _get_reference_ranges(self):
     414
     415                  cmd = u"""
     416  select
     417  distinct on (norm_ref_group_str, val_unit, val_normal_min, val_normal_max, val_normal_range, val_target_min, val_target_max, val_target_range)
     418          pk_patient,
     419          val_unit,
     420          val_normal_min, val_normal_max, val_normal_range,
     421          val_target_min, val_target_max, val_target_range,
     422          norm_ref_group,
     423          coalesce(norm_ref_group, '') as norm_ref_group_str
     424  from
     425          clin.v_test_results
     426  where
     427          pk_test_type = %(pk_type)s
     428  """
     429                  args = {'pk_type': self._payload[self._idx['pk_test_type']]}
     430                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     431                  return rows
     432
     433 -        def _set_reference_ranges(self, val):
     434                  raise AttributeError('[%s]: reference ranges not settable') % self.__class__.__name__
     435
     436          reference_ranges = property(_get_reference_ranges, _set_reference_ranges)
     437          #--------------------------------------------------------
     438 -        def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
     439
     440                  # FIXME: this is not concurrency safe
     441                  if self._payload[self._idx['reviewed']]:
     442                          self.__change_existing_review (
     443                                  technically_abnormal = technically_abnormal,
     444                                  clinically_relevant = clinically_relevant,
     445                                  comment = comment
     446                          )
     447                  else:
     448                          # do not sign off unreviewed results if
     449                          # NOTHING AT ALL is known about them
     450                          if technically_abnormal is None:
     451                                  if clinically_relevant is None:
     452                                          comment = gmTools.none_if(comment, u'', strip_string = True)
     453                                          if comment is None:
     454                                                  if make_me_responsible is False:
     455                                                          return True
     456                          self.__set_new_review (
     457                                  technically_abnormal = technically_abnormal,
     458                                  clinically_relevant = clinically_relevant,
     459                                  comment = comment
     460                          )
     461
     462                  if make_me_responsible is True:
     463                          cmd = u"SELECT pk FROM dem.staff WHERE db_user = current_user"
     464                          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
     465                          self['pk_intended_reviewer'] = rows[0][0]
     466                          self.save_payload()
     467                          return
     468
     469                  self.refetch_payload()
     470          #--------------------------------------------------------
     471          # internal API
     472          #--------------------------------------------------------
     473 -        def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
     474                  """Add a review to a row.
     475
     476                          - if technically abnormal is not provided/None it will be set
     477                            to True if the lab's indicator has a meaningful value
     478                          - if clinically relevant is not provided/None it is set to
     479                            whatever technically abnormal is
     480                  """
     481                  if technically_abnormal is None:
     482                          technically_abnormal = False
     483                          if self._payload[self._idx['abnormality_indicator']] is not None:
     484                                  if self._payload[self._idx['abnormality_indicator']].strip() != u'':
     485                                          technically_abnormal = True
     486
     487                  if clinically_relevant is None:
     488                          clinically_relevant = technically_abnormal
     489
     490                  cmd = u"""
     491  INSERT INTO clin.reviewed_test_results (
     492          fk_reviewed_row,
     493          is_technically_abnormal,
     494          clinically_relevant,
     495          comment
     496  ) VALUES (
     497          %(pk)s,
     498          %(abnormal)s,
     499          %(relevant)s,
     500          gm.nullify_empty_string(%(cmt)s)
     501  )"""
     502                  args = {
     503                          'pk': self._payload[self._idx['pk_test_result']],
     504                          'abnormal': technically_abnormal,
     505                          'relevant': clinically_relevant,
     506                          'cmt': comment
     507                  }
     508
     509                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     510          #--------------------------------------------------------
     511 -        def __change_existing_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
     512                  """Change a review on a row.
     513
     514                          - if technically abnormal/clinically relevant are
     515                            None they are not set
     516                  """
     517                  args = {
     518                          'pk_row': self._payload[self._idx['pk_test_result']],
     519                          'abnormal': technically_abnormal,
     520                          'relevant': clinically_relevant,
     521                          'cmt': comment
     522                  }
     523
     524                  set_parts = [
     525                          u'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)',
     526                          u'comment = gm.nullify_empty_string(%(cmt)s)'
     527                  ]
     528
     529                  if technically_abnormal is not None:
     530                          set_parts.append(u'is_technically_abnormal = %(abnormal)s')
     531
     532                  if clinically_relevant is not None:
     533                          set_parts.append(u'clinically_relevant = %(relevant)s')
     534
     535                  cmd = u"""
     536  UPDATE clin.reviewed_test_results SET
     537          %s
     538  WHERE
     539          fk_reviewed_row = %%(pk_row)s
     540  """ % u',\n     '.join(set_parts)
     541
     542                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     543  #------------------------------------------------------------
     544 -def delete_test_result(result=None):
     545
     546          try:
     547                  pk = int(result)
     548          except (TypeError, AttributeError):
     549                  pk = result['pk_test_result']
     550
     551          cmd = u'delete from clin.test_result where pk = %(pk)s'
     552          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
     553  #------------------------------------------------------------
     554 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
     555
     556          cmd1 = u"""
     557  insert into clin.test_result (
     558          fk_encounter,
     559          fk_episode,
     560          fk_type,
     561          fk_intended_reviewer,
     562          val_num,
     563          val_alpha,
     564          val_unit
     565  ) values (
     566          %(enc)s,
     567          %(epi)s,
     568          %(type)s,
     569          %(rev)s,
     570          %(v_num)s,
     571          %(v_alpha)s,
     572          %(unit)s
     573  )"""
     574
     575          cmd2 = u"""
     576  select *
     577  from
     578          clin.v_test_results
     579  where
     580          pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))"""
     581
     582          args = {
     583                  u'enc': encounter,
     584                  u'epi': episode,
     585                  u'type': type,
     586                  u'rev': intended_reviewer,
     587                  u'v_num': val_num,
     588                  u'v_alpha': val_alpha,
     589                  u'unit': unit
     590          }
     591
     592          rows, idx = gmPG2.run_rw_queries (
     593                  queries = [
     594                          {'cmd': cmd1, 'args': args},
     595                          {'cmd': cmd2}
     596                  ],
     597                  return_data = True,
     598                  get_col_idx = True
     599          )
     600
     601          tr = cTestResult(row = {
     602                  'pk_field': 'pk_test_result',
     603                  'idx': idx,
     604                  'data': rows[0]
     605          })
     606
     607          return tr
     608  #------------------------------------------------------------
     609 -def format_test_results(results=None, output_format=u'latex'):
     610
     611          _log.debug(u'formatting test results into [%s]', output_format)
     612
     613          if output_format == u'latex':
     614                  return __format_test_results_latex(results = results)
     615
     616          msg = _('unknown test results output format [%s]') % output_format
     617          _log.error(msg)
     618          return msg
     619  #------------------------------------------------------------
     620 -def __tests2latex_minipage(results=None, width=u'1.5cm', show_time=False, show_range=True):
     621
     622          if len(results) == 0:
     623                  return u'\\begin{minipage}{%s} \\end{minipage}' % width
     624
     625          lines = []
     626          for t in results:
     627
     628                  tmp = u''
     629
     630                  if show_time:
     631                          tmp += u'{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M')
     632
     633                  tmp += u'%.8s' % t['unified_val']
     634
     635                  lines.append(tmp)
     636                  tmp = u''
     637
     638                  if show_range:
     639                          has_range = (
     640                                  t['unified_target_range'] is not None
     641                                          or
     642                                  t['unified_target_min'] is not None
     643                                          or
     644                                  t['unified_target_max'] is not None
     645                          )
     646                          if has_range:
     647                                  if t['unified_target_range'] is not None:
     648                                          tmp += u'{\\tiny %s}' % t['unified_target_range']
     649                                  else:
     650                                          tmp += u'{\\tiny %s}' % (
     651                                                  gmTools.coalesce(t['unified_target_min'], u'- ', u'%s - '),
     652                                                  gmTools.coalesce(t['unified_target_max'], u'', u'%s')
     653                                          )
     654                                  lines.append(tmp)
     655
     656          return u'\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, u' \\\\ '.join(lines))
     657  #------------------------------------------------------------
     658 -def __tests2latex_cell(results=None, show_time=False, show_range=True):
     659
     660          if len(results) == 0:
     661                  return u''
     662
     663          lines = []
     664          for t in results:
     665
     666                  tmp = u''
     667
     668                  if show_time:
     669                          tmp += u'\\tiny %s ' % t['clin_when'].strftime('%H:%M')
     670
     671                  tmp += u'\\normalsize %.8s' % t['unified_val']
     672
     673                  lines.append(tmp)
     674                  tmp = u'\\tiny %s' % gmTools.coalesce(t['val_unit'], u'', u'%s ')
     675
     676                  if not show_range:
     677                          lines.append(tmp)
     678                          continue
     679
     680                  has_range = (
     681                          t['unified_target_range'] is not None
     682                                  or
     683                          t['unified_target_min'] is not None
     684                                  or
     685                          t['unified_target_max'] is not None
     686                  )
     687
     688                  if not has_range:
     689                          lines.append(tmp)
     690                          continue
     691
     692                  if t['unified_target_range'] is not None:
     693                          tmp += u'[%s]' % t['unified_target_range']
     694                  else:
     695                          tmp += u'[%s%s]' % (
     696                                  gmTools.coalesce(t['unified_target_min'], u'--', u'%s--'),
     697                                  gmTools.coalesce(t['unified_target_max'], u'', u'%s')
     698                          )
     699                  lines.append(tmp)
     700
     701          return u' \\\\ '.join(lines)
     702  #------------------------------------------------------------
     703 -def __format_test_results_latex(results=None):
     704
     705          if len(results) == 0:
     706                  return u'\\noindent %s' % _('No test results to format.')
     707
     708          # discover the columns and rows
     709          dates = {}
     710          tests = {}
     711          grid = {}
     712          for result in results:
     713  #               row_label = u'%s \\ \\tiny (%s)}' % (result['unified_abbrev'], result['unified_name'])
     714                  row_label = result['unified_abbrev']
     715                  tests[row_label] = None
     716                  col_label = u'{\\scriptsize %s}' % result['clin_when'].strftime('%Y-%m-%d')
     717                  dates[col_label] = None
     718                  try:
     719                          grid[row_label]
     720                  except KeyError:
     721                          grid[row_label] = {}
     722                  try:
     723                          grid[row_label][col_label].append(result)
     724                  except KeyError:
     725                          grid[row_label][col_label] = [result]
     726
     727          col_labels = sorted(dates.keys(), reverse = True)
     728          del dates
     729          row_labels = sorted(tests.keys())
     730          del tests
     731
     732          col_def = len(col_labels) * u'>{\\raggedleft}p{1.7cm}|'
     733
     734          # format them
     735          tex = u"""\\noindent %s
     736
     737  \\noindent \\begin{tabular}{|l|%s}
     738  \\hline
     739   & %s \\tabularnewline
     740  \\hline
     741
     742  %%s \\tabularnewline
     743
     744  \\hline
     745
     746  \\end{tabular}""" % (
     747                  _('Test results'),
     748                  col_def,
     749                  u' & '.join(col_labels)
     750          )
     751
     752          rows = []
     753
     754          # loop over rows
     755          for rl in row_labels:
     756                  cells = [rl]
     757                  # loop over cols per row
     758                  for cl in col_labels:
     759                          try:
     760                                  # get tests for this (row/col) position
     761                                  tests = grid[rl][cl]
     762                          except KeyError:
     763                                  # none there, so insert empty cell
     764                                  cells.append(u' ')
     765                                  continue
     766
     767                          cells.append (
     768                                  __tests2latex_cell (
     769                                          results = tests,
     770                                          show_time = (len(tests) > 1),
     771                                          show_range = True
     772                                  )
     773                          )
     774
     775                  rows.append(u' & '.join(cells))
     776
     777          return tex % u' \\tabularnewline\n \\hline\n'.join(rows)
     778
     779  #============================================================
     780 -def export_results_for_gnuplot(results=None, filename=None):
     781
     782          if filename is None:
     783                  filename = gmTools.get_unique_filename(prefix = u'gm2gpl-', suffix = '.dat')
     784
     785          # sort results into series by test type
     786          series = {}
     787          for r in results:
     788                  try:
     789                          series[r['unified_name']].append(r)
     790                  except KeyError:
     791                          series[r['unified_name']] = [r]
     792
     793          gp_data = codecs.open(filename, 'wb', 'utf8')
     794
     795          gp_data.write(u'# %s\n' % _('GNUmed test results export for Gnuplot plotting'))
     796          gp_data.write(u'# -------------------------------------------------------------\n')
     797          gp_data.write(u'# first line of index: test type abbreviation & name\n')
     798          gp_data.write(u'#\n')
     799          gp_data.write(u'# clin_when at full precision\n')
     800          gp_data.write(u'# value\n')
     801          gp_data.write(u'# unit\n')
     802          gp_data.write(u'# unified (target or normal) range: lower bound\n')
     803          gp_data.write(u'# unified (target or normal) range: upper bound\n')
     804          gp_data.write(u'# normal range: lower bound\n')
     805          gp_data.write(u'# normal range: upper bound\n')
     806          gp_data.write(u'# target range: lower bound\n')
     807          gp_data.write(u'# target range: upper bound\n')
     808          gp_data.write(u'# clin_when formatted into string as x-axis tic label\n')
     809          gp_data.write(u'# -------------------------------------------------------------\n')
     810
     811          for test_type in series.keys():
     812                  if len(series[test_type]) == 0:
     813                          continue
     814
     815                  r = series[test_type][0]
     816                  title = u'%s (%s)' % (
     817                          r['unified_abbrev'],
     818                          r['unified_name']
     819                  )
     820                  gp_data.write(u'\n\n"%s" "%s"\n' % (title, title))
     821
     822                  prev_date = None
     823                  prev_year = None
     824                  for r in series[test_type]:
     825                          curr_date = r['clin_when'].strftime('%Y-%m-%d')
     826                          if curr_date == prev_date:
     827                                  gp_data.write(u'\n# %s\n' % _('blank line inserted to allow for discontinued line drawing for same-day values'))
     828                          if r['clin_when'].year == prev_year:
     829                                  when_template = '%b %d %H:%M'
     830                          else:
     831                                  when_template = '%b %d %H:%M (%Y)'
     832                          prev_year = r['clin_when'].year
     833                          gp_data.write (u'%s %s "%s" %s %s %s %s %s %s "%s"\n' % (
     834                                  r['clin_when'].strftime('%Y-%m-%d_%H:%M'),
     835                                  r['unified_val'],
     836                                  gmTools.coalesce(r['val_unit'], u'"<?>"'),
     837                                  gmTools.coalesce(r['unified_target_min'], u'"<?>"'),
     838                                  gmTools.coalesce(r['unified_target_max'], u'"<?>"'),
     839                                  gmTools.coalesce(r['val_normal_min'], u'"<?>"'),
     840                                  gmTools.coalesce(r['val_normal_max'], u'"<?>"'),
     841                                  gmTools.coalesce(r['val_target_min'], u'"<?>"'),
     842                                  gmTools.coalesce(r['val_target_max'], u'"<?>"'),
     843                                  gmDateTime.pydt_strftime (
     844                                          r['clin_when'],
     845                                          format = when_template,
     846                                          accuracy = gmDateTime.acc_minutes
     847                                  )
     848                          ))
     849                          prev_date = curr_date
     850
     851          gp_data.close()
     852
     853          return filename
     854  #============================================================
     855 -class cLabResult(gmBusinessDBObject.cBusinessDBObject):
     856          """Represents one lab result."""
     857
     858          _cmd_fetch_payload = """
     859                  select *, xmin_test_result from v_results4lab_req
     860                  where pk_result=%s"""
     861          _cmds_lock_rows_for_update = [
     862                  """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update"""
     863          ]
     864          _cmds_store_payload = [
     865                  """update test_result set
     866                                  clin_when = %(val_when)s,
     867                                  narrative = %(progress_note_result)s,
     868                                  fk_type = %(pk_test_type)s,
     869                                  val_num = %(val_num)s::numeric,
     870                                  val_alpha = %(val_alpha)s,
     871                                  val_unit = %(val_unit)s,
     872                                  val_normal_min = %(val_normal_min)s,
     873                                  val_normal_max = %(val_normal_max)s,
     874                                  val_normal_range = %(val_normal_range)s,
     875                                  val_target_min = %(val_target_min)s,
     876                                  val_target_max = %(val_target_max)s,
     877                                  val_target_range = %(val_target_range)s,
     878                                  abnormality_indicator = %(abnormal)s,
     879                                  norm_ref_group = %(ref_group)s,
     880                                  note_provider = %(note_provider)s,
     881                                  material = %(material)s,
     882                                  material_detail = %(material_detail)s
     883                          where pk = %(pk_result)s""",
     884                  """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s"""
     885                  ]
     886
     887          _updatable_fields = [
     888                  'val_when',
     889                  'progress_note_result',
     890                  'val_num',
     891                  'val_alpha',
     892                  'val_unit',
     893                  'val_normal_min',
     894                  'val_normal_max',
     895                  'val_normal_range',
     896                  'val_target_min',
     897                  'val_target_max',
     898                  'val_target_range',
     899                  'abnormal',
     900                  'ref_group',
     901                  'note_provider',
     902                  'material',
     903                  'material_detail'
     904          ]
     905          #--------------------------------------------------------
     906 -        def __init__(self, aPK_obj=None, row=None):
     907                  """Instantiate.
     908
     909                  aPK_obj as dict:
     910                          - patient_id
     911                          - when_field (see view definition)
     912                          - when
     913                          - test_type
     914                          - val_num
     915                          - val_alpha
     916                          - unit
     917                  """
     918                  # instantiate from row data ?
     919                  if aPK_obj is None:
     920                          gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
     921                          return
     922                  pk = aPK_obj
     923                  # find PK from row data ?
     924                  if type(aPK_obj) == types.DictType:
     925                          # sanity checks
     926                          if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]:
     927                                  raise gmExceptions.ConstructorError, 'parameter error: %s' % aPK_obj
     928                          if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None):
     929                                  raise gmExceptions.ConstructorError, 'parameter error: val_num and val_alpha cannot both be None'
     930                          # get PK
     931                          where_snippets = [
     932                                  'pk_patient=%(patient_id)s',
     933                                  'pk_test_type=%(test_type)s',
     934                                  '%s=%%(when)s' % aPK_obj['when_field'],
     935                                  'val_unit=%(unit)s'
     936                          ]
     937                          if aPK_obj['val_num'] is not None:
     938                                  where_snippets.append('val_num=%(val_num)s::numeric')
     939                          if aPK_obj['val_alpha'] is not None:
     940                                  where_snippets.append('val_alpha=%(val_alpha)s')
     941
     942                          where_clause = ' and '.join(where_snippets)
     943                          cmd = "select pk_result from v_results4lab_req where %s" % where_clause
     944                          data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
     945                          if data is None:
     946                                  raise gmExceptions.ConstructorError, 'error getting lab result for: %s' % aPK_obj
     947                          if len(data) == 0:
     948                                  raise gmExceptions.NoSuchClinItemError, 'no lab result for: %s' % aPK_obj
     949                          pk = data[0][0]
     950                  # instantiate class
     951                  gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
     952          #--------------------------------------------------------
     953 -        def get_patient(self):
     954                  cmd = """
     955                          select
     956                                  %s,
     957                                  vbp.title,
     958                                  vbp.firstnames,
     959                                  vbp.lastnames,
     960                                  vbp.dob
     961                          from v_basic_person vbp
     962                          where vbp.pk_identity=%%s""" % self._payload[self._idx['pk_patient']]
     963                  pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']])
     964                  return pat[0]
     965  #============================================================
     966 -class cLabRequest(gmBusinessDBObject.cBusinessDBObject):
     967          """Represents one lab request."""
     968
     969          _cmd_fetch_payload = """
     970                  select *, xmin_lab_request from v_lab_requests
     971                  where pk_request=%s"""
     972          _cmds_lock_rows_for_update = [
     973                  """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update"""
     974          ]
     975          _cmds_store_payload = [
     976                  """update lab_request set
     977                                  request_id=%(request_id)s,
     978                                  lab_request_id=%(lab_request_id)s,
     979                                  clin_when=%(sampled_when)s,
     980                                  lab_rxd_when=%(lab_rxd_when)s,
     981                                  results_reported_when=%(results_reported_when)s,
     982                                  request_status=%(request_status)s,
     983                                  is_pending=%(is_pending)s::bool,
     984                                  narrative=%(progress_note)s
     985                          where pk=%(pk_request)s""",
     986                  """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s"""
     987          ]
     988          _updatable_fields = [
     989                  'request_id',
     990                  'lab_request_id',
     991                  'sampled_when',
     992                  'lab_rxd_when',
     993                  'results_reported_when',
     994                  'request_status',
     995                  'is_pending',
     996                  'progress_note'
     997          ]
     998          #--------------------------------------------------------
     999 -        def __init__(self, aPK_obj=None, row=None):
    1000                  """Instantiate lab request.
    1001
    1002                  The aPK_obj can be either a dict with the keys "req_id"
    1003                  and "lab" or a simple primary key.
    1004                  """
    1005                  # instantiate from row data ?
    1006                  if aPK_obj is None:
    1007                          gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
    1008                          return
    1009                  pk = aPK_obj
    1010                  # instantiate from "req_id" and "lab" ?
    1011                  if type(aPK_obj) == types.DictType:
    1012                          # sanity check
    1013                          try:
    1014                                  aPK_obj['req_id']
    1015                                  aPK_obj['lab']
    1016                          except:
    1017                                  _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info())
    1018                                  raise gmExceptions.ConstructorError, '[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj)
    1019                          # generate query
    1020                          where_snippets = []
    1021                          vals = {}
    1022                          where_snippets.append('request_id=%(req_id)s')
    1023                          if type(aPK_obj['lab']) == types.IntType:
    1024                                  where_snippets.append('pk_test_org=%(lab)s')
    1025                          else:
    1026                                  where_snippets.append('lab_name=%(lab)s')
    1027                          where_clause = ' and '.join(where_snippets)
    1028                          cmd = "select pk_request from v_lab_requests where %s" % where_clause
    1029                          # get pk
    1030                          data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
    1031                          if data is None:
    1032                                  raise gmExceptions.ConstructorError, '[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj)
    1033                          if len(data) == 0:
    1034                                  raise gmExceptions.NoSuchClinItemError, '[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj)
    1035                          pk = data[0][0]
    1036                  # instantiate class
    1037                  gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
    1038          #--------------------------------------------------------
    1039 -        def get_patient(self):
    1040                  cmd = """
    1041                          select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob
    1042                          from v_pat_items vpi, v_basic_person vbp
    1043                          where
    1044                                  vpi.pk_item=%s
    1045                                          and
    1046                                  vbp.pk_identity=vpi.pk_patient"""
    1047                  pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']])
    1048                  if pat is None:
    1049                          _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']])
    1050                          return None
    1051                  if len(pat) == 0:
    1052                          _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']])
    1053                          return None
    1054                  return pat[0]
    1055  #============================================================
    1056  # convenience functions
    1057  #------------------------------------------------------------
    1058 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
    1059          """Create or get lab request.
    1060
    1061                  returns tuple (status, value):
    1062                          (True, lab request instance)
    1063                          (False, error message)
    1064                          (None, housekeeping_todo primary key)
    1065          """
    1066          req = None
    1067          aPK_obj = {
    1068                  'lab': lab,
    1069                  'req_id': req_id
    1070          }
    1071          try:
    1072                  req = cLabRequest (aPK_obj)
    1073          except gmExceptions.NoSuchClinItemError, msg:
    1074                  _log.info('%s: will try to create lab request' % str(msg))
    1075          except gmExceptions.ConstructorError, msg:
    1076                  _log.exception(str(msg), sys.exc_info(), verbose=0)
    1077                  return (False, msg)
    1078          # found
    1079          if req is not None:
    1080                  db_pat = req.get_patient()
    1081                  if db_pat is None:
    1082                          _log.error('cannot cross-check patient on lab request')
    1083                          return (None, '')
    1084                  # yes but ambigous
    1085                  if pat_id != db_pat[0]:
    1086                          _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat))
    1087                          me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $'
    1088                          to = 'user'
    1089                          prob = _('The lab request already exists but belongs to a different patient.')
    1090                          sol = _('Verify which patient this lab request really belongs to.')
    1091                          ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat)
    1092                          cat = 'lab'
    1093                          status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat)
    1094                          return (None, data)
    1095                  return (True, req)
    1096          # not found
    1097          queries = []
    1098          if type(lab) is types.IntType:
    1099                  cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)"
    1100          else:
    1101                  cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, (select pk from test_org where internal_OBSOLETE_name=%s), %s)"
    1102          queries.append((cmd, [encounter_id, episode_id, str(lab), req_id]))
    1103          cmd = "select currval('lab_request_pk_seq')"
    1104          queries.append((cmd, []))
    1105          # insert new
    1106          result, err = gmPG.run_commit('historica', queries, True)
    1107          if result is None:
    1108                  return (False, err)
    1109          try:
    1110                  req = cLabRequest(aPK_obj=result[0][0])
    1111          except gmExceptions.ConstructorError, msg:
    1112                  _log.exception(str(msg), sys.exc_info(), verbose=0)
    1113                  return (False, msg)
    1114          return (True, req)
    1115  #------------------------------------------------------------
    1116 -def create_lab_result(patient_id=None, when_field=None, when=None, test_type=None, val_num=None, val_alpha=None, unit=None, encounter_id=None, request=None):
    1117          tres = None
    1118          data = {
    1119                  'patient_id': patient_id,
    1120                  'when_field': when_field,
    1121                  'when': when,
    1122                  'test_type': test_type,
    1123                  'val_num': val_num,
    1124                  'val_alpha': val_alpha,
    1125                  'unit': unit
    1126          }
    1127          try:
    1128                  tres = cLabResult(aPK_obj=data)
    1129                  # exists already, so fail
    1130                  _log.error('will not overwrite existing test result')
    1131                  _log.debug(str(tres))
    1132                  return (None, tres)
    1133          except gmExceptions.NoSuchClinItemError:
    1134                  _log.debug('test result not found - as expected, will create it')
    1135          except gmExceptions.ConstructorError, msg:
    1136                  _log.exception(str(msg), sys.exc_info(), verbose=0)
    1137                  return (False, msg)
    1138          if request is None:
    1139                  return (False, _('need lab request when inserting lab result'))
    1140          # not found
    1141          if encounter_id is None:
    1142                  encounter_id = request['pk_encounter']
    1143          queries = []
    1144          cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)"
    1145          queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit]))
    1146          cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)"
    1147          queries.append((cmd, [request['pk_request']]))
    1148          cmd = "select currval('test_result_pk_seq')"
    1149          queries.append((cmd, []))
    1150          # insert new
    1151          result, err = gmPG.run_commit('historica', queries, True)
    1152          if result is None:
    1153                  return (False, err)
    1154          try:
    1155                  tres = cLabResult(aPK_obj=result[0][0])
    1156          except gmExceptions.ConstructorError, msg:
    1157                  _log.exception(str(msg), sys.exc_info(), verbose=0)
    1158                  return (False, msg)
    1159          return (True, tres)
    1160  #------------------------------------------------------------
    1161 -def get_unreviewed_results(limit=50):
    1162          # sanity check
    1163          if limit < 1:
    1164                  limit = 1
    1165          # retrieve one more row than needed so we know there's more available ;-)
    1166          lim = limit + 1
    1167          cmd = """
    1168                  select pk_result
    1169                  from v_results4lab_req
    1170                  where reviewed is false
    1171                  order by pk_patient
    1172                  limit %s""" % lim
    1173          rows = gmPG.run_ro_query('historica', cmd)
    1174          if rows is None:
    1175                  _log.error('error retrieving unreviewed lab results')
    1176                  return (None, _('error retrieving unreviewed lab results'))
    1177          if len(rows) == 0:
    1178                  return (False, [])
    1179          # more than LIMIT rows ?
    1180          if len(rows) == lim:
    1181                  more_avail = True
    1182                  # but deliver only LIMIT rows so that our assumption holds true...
    1183                  del rows[limit]
    1184          else:
    1185                  more_avail = False
    1186          results = []
    1187          for row in rows:
    1188                  try:
    1189                          results.append(cLabResult(aPK_obj=row[0]))
    1190                  except gmExceptions.ConstructorError:
    1191                          _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0)
    1192          return (more_avail, results)
    1193  #------------------------------------------------------------
    1194 -def get_pending_requests(limit=250):
    1195          lim = limit + 1
    1196          cmd = "select pk from lab_request where is_pending is true limit %s" % lim
    1197          rows = gmPG.run_ro_query('historica', cmd)
    1198          if rows is None:
    1199                  _log.error('error retrieving pending lab requests')
    1200                  return (None, None)
    1201          if len(rows) == 0:
    1202                  return (False, [])
    1203          results = []
    1204          # more than LIMIT rows ?
    1205          if len(rows) == lim:
    1206                  too_many = True
    1207                  # but deliver only LIMIT rows so that our assumption holds true...
    1208                  del rows[limit]
    1209          else:
    1210                  too_many = False
    1211          requests = []
    1212          for row in rows:
    1213                  try:
    1214                          requests.append(cLabRequest(aPK_obj=row[0]))
    1215                  except gmExceptions.ConstructorError:
    1216                          _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0)
    1217          return (too_many, requests)
    1218  #------------------------------------------------------------
    1219 -def get_next_request_ID(lab=None, incrementor_func=None):
    1220          """Get logically next request ID for given lab.
    1221
    1222          - incrementor_func:
    1223            - if not supplied the next ID is guessed
    1224            - if supplied it is applied to the most recently used ID
    1225          """
    1226          if type(lab) == types.IntType:
    1227                  lab_snippet = 'vlr.fk_test_org=%s'
    1228          else:
    1229                  lab_snippet = 'vlr.lab_name=%s'
    1230                  lab = str(lab)
    1231          cmd =  """
    1232                  select request_id
    1233                  from lab_request lr0
    1234                  where lr0.clin_when = (
    1235                          select max(vlr.sampled_when)
    1236                          from v_lab_requests vlr
    1237                          where %s
    1238                  )""" % lab_snippet
    1239          rows = gmPG.run_ro_query('historica', cmd, None, lab)
    1240          if rows is None:
    1241                  _log.warning('error getting most recently used request ID for lab [%s]' % lab)
    1242                  return ''
    1243          if len(rows) == 0:
    1244                  return ''
    1245          most_recent = rows[0][0]
    1246          # apply supplied incrementor
    1247          if incrementor_func is not None:
    1248                  try:
    1249                          next = incrementor_func(most_recent)
    1250                  except TypeError:
    1251                          _log.error('cannot call incrementor function [%s]' % str(incrementor_func))
    1252                          return most_recent
    1253                  return next
    1254          # try to be smart ourselves
    1255          for pos in range(len(most_recent)):
    1256                  header = most_recent[:pos]
    1257                  trailer = most_recent[pos:]
    1258                  try:
    1259                          return '%s%s' % (header, str(int(trailer) + 1))
    1260                  except ValueError:
    1261                          header = most_recent[:-1]
    1262                          trailer = most_recent[-1:]
    1263                          return '%s%s' % (header, chr(ord(trailer) + 1))
    1264  #============================================================
    1265 -def calculate_bmi(mass=None, height=None, age=None):
    1266          """Calculate BMI.
    1267
    1268          mass: kg
    1269          height: cm
    1270          age: not yet used
    1271
    1272          returns:
    1273                  (True/False, data)
    1274                  True: data = (bmi, lower_normal, upper_normal)
    1275                  False: data = error message
    1276          """
    1277          converted, mass = gmTools.input2decimal(mass)
    1278          if not converted:
    1279                  return False, u'mass: cannot convert <%s> to Decimal' % mass
    1280
    1281          converted, height = gmTools.input2decimal(height)
    1282          if not converted:
    1283                  return False, u'height: cannot convert <%s> to Decimal' % height
    1284
    1285          approx_surface = (height / decimal.Decimal(100))**2
    1286          bmi = mass / approx_surface
    1287
    1288          print mass, height, '->', approx_surface, '->', bmi
    1289
    1290          lower_normal_mass = 20.0 * approx_surface
    1291          upper_normal_mass = 25.0 * approx_surface
    1292
    1293          return True, (bmi, lower_normal_mass, upper_normal_mass)
    1294  #============================================================
    1295  # main - unit testing
    1296  #------------------------------------------------------------
    1297  if __name__ == '__main__':
    1298
    1299          if len(sys.argv) < 2:
    1300                  sys.exit()
    1301
    1302          if sys.argv[1] != 'test':
    1303                  sys.exit()
    1304
    1305          import time
    1306
    1307          gmI18N.activate_locale()
    1308          gmI18N.install_domain()
    1309
    1310          #------------------------------------------
    1311 -        def test_create_test_result():
    1312                  tr = create_test_result (
    1313                          encounter = 1,
    1314                          episode = 1,
    1315                          type = 1,
    1316                          intended_reviewer = 1,
    1317                          val_num = '12',
    1318                          val_alpha=None,
    1319                          unit = 'mg/dl'
    1320                  )
    1321                  print tr
    1322                  return tr
    1323          #------------------------------------------
    1324 -        def test_delete_test_result():
    1325                  tr = test_create_test_result()
    1326                  delete_test_result(tr)
    1327          #------------------------------------------
    1328 -        def test_result():
    1329                  r = cTestResult(aPK_obj=1)
    1330                  print r
    1331                  print r.reference_ranges
    1332          #------------------------------------------
    1333 -        def test_lab_result():
    1334                  print "test_result()"
    1335  #               lab_result = cLabResult(aPK_obj=4)
    1336                  data = {
    1337                          'patient_id': 12,
    1338                          'when_field': 'val_when',
    1339                          'when': '2000-09-17 18:23:00+02',
    1340                          'test_type': 9,
    1341                          'val_num': 17.3,
    1342                          'val_alpha': None,
    1343                          'unit': 'mg/l'
    1344                  }
    1345                  lab_result = cLabResult(aPK_obj=data)
    1346                  print lab_result
    1347                  fields = lab_result.get_fields()
    1348                  for field in fields:
    1349                          print field, ':', lab_result[field]
    1350                  print "updatable:", lab_result.get_updatable_fields()
    1351                  print time.time()
    1352                  print lab_result.get_patient()
    1353                  print time.time()
    1354          #------------------------------------------
    1355 -        def test_request():
    1356                  print "test_request()"
    1357                  try:
    1358  #                       lab_req = cLabRequest(aPK_obj=1)
    1359  #                       lab_req = cLabRequest(req_id='EML#SC937-0176-CEC#11', lab=2)
    1360                          data = {
    1361                                  'req_id': 'EML#SC937-0176-CEC#11',
    1362                                  'lab': 'Enterprise Main Lab'
    1363                          }
    1364                          lab_req = cLabRequest(aPK_obj=data)
    1365                  except gmExceptions.ConstructorError, msg:
    1366                          print "no such lab request:", msg
    1367                          return
    1368                  print lab_req
    1369                  fields = lab_req.get_fields()
    1370                  for field in fields:
    1371                          print field, ':', lab_req[field]
    1372                  print "updatable:", lab_req.get_updatable_fields()
    1373                  print time.time()
    1374                  print lab_req.get_patient()
    1375                  print time.time()
    1376          #--------------------------------------------------------
    1377 -        def test_unreviewed():
    1378                  data = get_unreviewed_results()
    1379                  for result in data:
    1380                          print result
    1381          #--------------------------------------------------------
    1382 -        def test_pending():
    1383                  data = get_pending_requests()
    1384                  for result in data:
    1385                          print result
    1386          #--------------------------------------------------------
    1387 -        def test_create_measurement_type():
    1388                  print create_measurement_type (
    1389                          lab = None,
    1390                          abbrev = u'tBZ2',
    1391                          unit = u'mg%',
    1392                          name = 'BZ (test 2)'
    1393                  )
    1394          #--------------------------------------------------------
    1395 -        def test_meta_test_type():
    1396                  mtt = cMetaTestType(aPK_obj = 1)
    1397                  print mtt
    1398                  print get_meta_test_types()
    1399          #--------------------------------------------------------
    1400 -        def test_test_type():
    1401                  tt = cMeasurementType(aPK_obj = 1)
    1402                  print tt
    1403                  print get_measurement_types()
    1404          #--------------------------------------------------------
    1405 -        def test_format_test_results():
    1406                  results = [
    1407                          cTestResult(aPK_obj=1),
    1408                          cTestResult(aPK_obj=2),
    1409                          cTestResult(aPK_obj=3)
    1410  #                       cTestResult(aPK_obj=4)
    1411                  ]
    1412                  print format_test_results(results = results)
    1413          #--------------------------------------------------------
    1414 -        def test_calculate_bmi():
    1415                  done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3])
    1416                  bmi, low, high = data
    1417
    1418                  print "BMI:", bmi
    1419                  print "low:", low, "kg"
    1420                  print "hi :", high, "kg"
    1421
    1422          #--------------------------------------------------------
    1423
    1424          #test_result()
    1425          #test_create_test_result()
    1426          #test_delete_test_result()
    1427          #test_create_measurement_type()
    1428          #test_lab_result()
    1429          #test_request()
    1430          #test_create_result()
    1431          #test_unreviewed()
    1432          #test_pending()
    1433          #test_meta_test_type()
    1434          #test_test_type()
    1435          #test_format_test_results()
    1436          test_calculate_bmi()
    1437
    1438  #============================================================
    1439