Gnumed.business.gmMedication

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

# Source Code for Module Gnumed.business.gmMedication

       1  # -*- coding: utf-8 -*-
       2  """Medication handling code.
       3
       4  license: GPL v2 or later
       5  """
       6  #============================================================
       7  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
       8
       9  import sys
      10  import logging
      11  import io
      12  import uuid
      13  import re as regex
      14  import datetime as pydt
      15
      16
      17  if __name__ == '__main__':
      18          sys.path.insert(0, '../../')
      19          from Gnumed.pycommon import gmI18N
      20          gmI18N.activate_locale()
      21          gmI18N.install_domain('gnumed')
      22  from Gnumed.pycommon import gmBusinessDBObject
      23  from Gnumed.pycommon import gmTools
      24  from Gnumed.pycommon import gmPG2
      25  from Gnumed.pycommon import gmDispatcher
      26  from Gnumed.pycommon import gmMatchProvider
      27  from Gnumed.pycommon import gmHooks
      28  from Gnumed.pycommon import gmDateTime
      29
      30  from Gnumed.business import gmATC
      31  from Gnumed.business import gmAllergy
      32  from Gnumed.business import gmEMRStructItems
      33
      34
      35  _log = logging.getLogger('gm.meds')
      36
      37  #============================================================
      38  DEFAULT_MEDICATION_HISTORY_EPISODE = _('Medication history')
      39
      40  URL_renal_insufficiency = 'http://www.dosing.de'
      41  URL_renal_insufficiency_search_template = 'http://www.google.com/search?hl=de&source=hp&q=site%%3Adosing.de+%s&btnG=Google-Suche'
      42
      43  URL_long_qt = 'https://www.crediblemeds.org'
      44
      45  #============================================================
      46 -def _on_substance_intake_modified():
      47          """Always relates to the active patient."""
      48          gmHooks.run_hook_script(hook = 'after_substance_intake_modified')
      49
      50  gmDispatcher.connect(_on_substance_intake_modified, 'clin.substance_intake_mod_db')
      51
      52  #============================================================
      53 -def drug2renal_insufficiency_url(search_term=None):
      54
      55          if search_term is None:
      56                  return URL_renal_insufficiency
      57
      58          if isinstance(search_term, str):
      59                  if search_term.strip() == '':
      60                          return URL_renal_insufficiency
      61
      62          terms = []
      63          names = []
      64
      65          if isinstance(search_term, cDrugProduct):
      66                  if search_term['atc'] is not None:
      67                          terms.append(search_term['atc'])
      68
      69          elif isinstance(search_term, cSubstanceIntakeEntry):
      70                  names.append(search_term['substance'])
      71                  if search_term['atc_drug'] is not None:
      72                          terms.append(search_term['atc_drug'])
      73                  if search_term['atc_substance'] is not None:
      74                          terms.append(search_term['atc_substance'])
      75
      76          elif isinstance(search_term, cDrugComponent):
      77                  names.append(search_term['substance'])
      78                  if search_term['atc_drug'] is not None:
      79                          terms.append(search_term['atc_drug'])
      80                  if search_term['atc_substance'] is not None:
      81                          terms.append(search_term['atc_substance'])
      82
      83          elif isinstance(search_term, cSubstance):
      84                  names.append(search_term['substance'])
      85                  if search_term['atc'] is not None:
      86                          terms.append(search_term['atc'])
      87
      88          elif isinstance(search_term, cSubstanceDose):
      89                  names.append(search_term['substance'])
      90                  if search_term['atc'] is not None:
      91                          terms.append(search_term['atc_substance'])
      92
      93          elif search_term is not None:
      94                  names.append('%s' % search_term)
      95                  terms.extend(gmATC.text2atc(text = '%s' % search_term, fuzzy = True))
      96
      97          for name in names:
      98                  if name.endswith('e'):
      99                          terms.append(name[:-1])
     100                  else:
     101                          terms.append(name)
     102
     103          #url_template = 'http://www.google.de/#q=site%%3Adosing.de+%s'
     104          #url = url_template % '+OR+'.join(terms)
     105          url = URL_renal_insufficiency_search_template % '+OR+'.join(terms)
     106
     107          _log.debug('renal insufficiency URL: %s', url)
     108
     109          return url
     110
     111  #============================================================
     112  #============================================================
     113  # plain substances
     114  #------------------------------------------------------------
     115  _SQL_get_substance = "SELECT * FROM ref.v_substances WHERE %s"
     116
     117 -class cSubstance(gmBusinessDBObject.cBusinessDBObject):
     118
     119          _cmd_fetch_payload = _SQL_get_substance % "pk_substance = %s"
     120          _cmds_store_payload = [
     121                  """UPDATE ref.substance SET
     122                                  description = %(substance)s,
     123                                  atc = gm.nullify_empty_string(%(atc)s),
     124                                  intake_instructions = gm.nullify_empty_string(%(intake_instructions)s)
     125                          WHERE
     126                                  pk = %(pk_substance)s
     127                                          AND
     128                                  xmin = %(xmin_substance)s
     129                          RETURNING
     130                                  xmin AS xmin_substance
     131                  """
     132          ]
     133          _updatable_fields = [
     134                  'substance',
     135                  'atc',
     136                  'intake_instructions'
     137          ]
     138          #--------------------------------------------------------
     139 -        def format(self, left_margin=0):
     140                  if len(self._payload[self._idx['loincs']]) == 0:
     141                          loincs = ''
     142                  else:
     143                          loincs = """
     144  %s %s
     145  %s  %s"""       % (
     146                                  (' ' * left_margin),
     147                                  _('LOINCs to monitor:'),
     148                                  (' ' * left_margin),
     149                                  ('\n' + (' ' * (left_margin + 1))).join ([
     150                                          '%s%s%s' % (
     151                                                  l['loinc'],
     152                                                  gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')),
     153                                                  gmTools.coalesce(l['comment'], '', ' (%s)')
     154                                          ) for l in self._payload[self._idx['loincs']]
     155                                  ])
     156                          )
     157                  return (' ' * left_margin) + '%s: %s%s%s%s' % (
     158                          _('Substance'),
     159                          self._payload[self._idx['substance']],
     160                          gmTools.coalesce(self._payload[self._idx['atc']], '', ' [%s]'),
     161                          gmTools.coalesce(self._payload[self._idx['intake_instructions']], '', _('\n Instructions: %s')),
     162                          loincs
     163                  )
     164
     165          #--------------------------------------------------------
     166 -        def save_payload(self, conn=None):
     167                  success, data = super(self.__class__, self).save_payload(conn = conn)
     168
     169                  if not success:
     170                          return (success, data)
     171
     172                  if self._payload[self._idx['atc']] is not None:
     173                          atc = self._payload[self._idx['atc']].strip()
     174                          if atc != '':
     175                                  gmATC.propagate_atc (
     176                                          substance = self._payload[self._idx['substance']].strip(),
     177                                          atc = atc
     178                                  )
     179
     180                  return (success, data)
     181
     182          #--------------------------------------------------------
     183 -        def exists_as_intake(self, pk_patient=None):
     184                  return substance_intake_exists (
     185                          pk_substance = self.pk_obj,
     186                          pk_identity = pk_patient
     187                  )
     188
     189          #--------------------------------------------------------
     190          # properties
     191          #--------------------------------------------------------
     192 -        def _set_loincs(self, loincs):
     193                  args = {'pk_subst': self.pk_obj, 'loincs': tuple(loincs)}
     194                  # insert new entries
     195                  for loinc in loincs:
     196                          cmd = """INSERT INTO ref.lnk_loinc2substance (fk_substance, loinc)
     197                          SELECT
     198                                  %(pk_subst)s, %(loinc)s
     199                          WHERE NOT EXISTS (
     200                                  SELECT 1 from ref.lnk_loinc2substance WHERE fk_substance = %(pk_subst)s AND loinc = %(loinc)s
     201                          )"""
     202                          args['loinc'] = loinc
     203                          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     204
     205                  # delete old entries
     206                  cmd = """DELETE FROM ref.lnk_loinc2substance WHERE fk_substance = %(pk_subst)s AND loinc NOT IN %(loincs)s"""
     207                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     208
     209          loincs = property(lambda x:x, _set_loincs)
     210
     211          #--------------------------------------------------------
     212 -        def _get_is_in_use_by_patients(self):
     213                  cmd = """
     214                          SELECT EXISTS (
     215                                  SELECT 1
     216                                  FROM clin.v_substance_intakes
     217                                  WHERE pk_substance = %(pk)s
     218                                  LIMIT 1
     219                          )"""
     220                  args = {'pk': self.pk_obj}
     221
     222                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     223                  return rows[0][0]
     224
     225          is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
     226
     227          #--------------------------------------------------------
     228 -        def _get_is_drug_component(self):
     229                  cmd = """
     230                          SELECT EXISTS (
     231                                  SELECT 1
     232                                  FROM ref.v_drug_components
     233                                  WHERE pk_substance = %(pk)s
     234                                  LIMIT 1
     235                          )"""
     236                  args = {'pk': self.pk_obj}
     237
     238                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     239                  return rows[0][0]
     240
     241          is_drug_component = property(_get_is_drug_component, lambda x:x)
     242
     243  #------------------------------------------------------------
     244 -def get_substances(order_by=None):
     245          if order_by is None:
     246                  order_by = 'true'
     247          else:
     248                  order_by = 'true ORDER BY %s' % order_by
     249          cmd = _SQL_get_substance % order_by
     250          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
     251          return [ cSubstance(row = {'data': r, 'idx': idx, 'pk_field': 'pk_substance'}) for r in rows ]
     252
     253  #------------------------------------------------------------
     254 -def create_substance(substance=None, atc=None):
     255          if atc is not None:
     256                  atc = atc.strip()
     257
     258          args = {
     259                  'desc': substance.strip(),
     260                  'atc': atc
     261          }
     262          cmd = "SELECT pk FROM ref.substance WHERE lower(description) = lower(%(desc)s)"
     263          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
     264
     265          if len(rows) == 0:
     266                  cmd = """
     267                          INSERT INTO ref.substance (description, atc) VALUES (
     268                                  %(desc)s,
     269                                  coalesce (
     270                                          gm.nullify_empty_string(%(atc)s),
     271                                          (SELECT code FROM ref.atc WHERE term = %(desc)s LIMIT 1)
     272                                  )
     273                          ) RETURNING pk"""
     274                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
     275
     276          if atc is not None:
     277                  gmATC.propagate_atc(substance = substance.strip(), atc = atc)
     278
     279          return cSubstance(aPK_obj = rows[0]['pk'])
     280
     281  #------------------------------------------------------------
     282 -def create_substance_by_atc(substance=None, atc=None, link_obj=None):
     283
     284          if atc is None:
     285                  raise ValueError('<atc> must be supplied')
     286          atc = atc.strip()
     287          if atc == '':
     288                  raise ValueError('<atc> cannot be empty: [%s]', atc)
     289
     290          queries = []
     291          args = {
     292                  'desc': substance.strip(),
     293                  'atc': atc
     294          }
     295          # in case the substance already exists: add ATC
     296          cmd = "UPDATE ref.substance SET atc = %(atc)s WHERE lower(description) = lower(%(desc)s) AND atc IS NULL"
     297          queries.append({'cmd': cmd, 'args': args})
     298          # or else INSERT the substance
     299          cmd = """
     300                  INSERT INTO ref.substance (description, atc)
     301                          SELECT
     302                                  %(desc)s,
     303                                  %(atc)s
     304                          WHERE NOT EXISTS (
     305                                  SELECT 1 FROM ref.substance WHERE atc = %(atc)s
     306                          )
     307                  RETURNING pk"""
     308          queries.append({'cmd': cmd, 'args': args})
     309          rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data = True, get_col_idx = False)
     310          if len(rows) == 0:
     311                  cmd = "SELECT pk FROM ref.substance WHERE atc = %(atc)s LIMIT 1"
     312                  rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
     313
     314          return cSubstance(aPK_obj = rows[0]['pk'], link_obj = link_obj)
     315
     316  #------------------------------------------------------------
     317 -def delete_substance(pk_substance=None):
     318          args = {'pk': pk_substance}
     319          cmd = """
     320                  DELETE FROM ref.substance WHERE
     321                          pk = %(pk)s
     322                                  AND
     323                          -- must not currently be used with a patient
     324                          NOT EXISTS (
     325                                  SELECT 1 FROM clin.v_substance_intakes
     326                                  WHERE pk_substance = %(pk)s
     327                                  LIMIT 1
     328                          )
     329                                  AND
     330                          -- must not currently have doses defined for it
     331                          NOT EXISTS (
     332                                  SELECT 1 FROM ref.dose
     333                                  WHERE fk_substance = %(pk)s
     334                                  LIMIT 1
     335                          )
     336          """
     337          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     338          return True
     339
     340  #============================================================
     341  # substance doses
     342  #------------------------------------------------------------
     343  _SQL_get_substance_dose = "SELECT * FROM ref.v_substance_doses WHERE %s"
     344
     345 -class cSubstanceDose(gmBusinessDBObject.cBusinessDBObject):
     346
     347          _cmd_fetch_payload = _SQL_get_substance_dose % "pk_dose = %s"
     348          _cmds_store_payload = [
     349                  """UPDATE ref.dose SET
     350                                  amount = %(amount)s,
     351                                  unit = %(unit)s,
     352                                  dose_unit = gm.nullify_empty_string(%(dose_unit)s)
     353                          WHERE
     354                                  pk = %(pk_dose)s
     355                                          AND
     356                                  xmin = %(xmin_dose)s
     357                          RETURNING
     358                                  xmin as xmin_dose,
     359                                  pk as pk_dose
     360                  """
     361          ]
     362          _updatable_fields = [
     363                  'amount',
     364                  'unit',
     365                  'dose_unit'
     366          ]
     367
     368          #--------------------------------------------------------
     369 -        def format(self, left_margin=0, include_loincs=False):
     370                  loincs = ''
     371                  if include_loincs and (len(self._payload[self._idx['loincs']]) > 0):
     372                          loincs = """
     373  %s %s
     374  %s  %s"""       % (
     375                                  (' ' * left_margin),
     376                                  _('LOINCs to monitor:'),
     377                                  (' ' * left_margin),
     378                                  ('\n' + (' ' * (left_margin + 1))).join ([
     379                                          '%s%s%s' % (
     380                                                  l['loinc'],
     381                                                  gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')),
     382                                                  gmTools.coalesce(l['comment'], '', ' (%s)')
     383                                          ) for l in self._payload[self._idx['loincs']]
     384                                  ])
     385                          )
     386                  return (' ' * left_margin) + '%s: %s %s%s%s%s%s' % (
     387                          _('Substance dose'),
     388                          self._payload[self._idx['substance']],
     389                          self._payload[self._idx['amount']],
     390                          self.formatted_units,
     391                          gmTools.coalesce(self._payload[self._idx['atc_substance']], '', ' [%s]'),
     392                          gmTools.coalesce(self._payload[self._idx['intake_instructions']], '', '\n' + (' ' * left_margin) + ' ' + _('Instructions: %s')),
     393                          loincs
     394                  )
     395
     396          #--------------------------------------------------------
     397 -        def exists_as_intake(self, pk_patient=None):
     398                  return substance_intake_exists (
     399                          pk_dose = self.pk_obj,
     400                          pk_identity = pk_patient
     401                  )
     402
     403          #--------------------------------------------------------
     404          # properties
     405          #--------------------------------------------------------
     406 -        def _get_is_in_use_by_patients(self):
     407                  cmd = """
     408                          SELECT EXISTS (
     409                                  SELECT 1
     410                                  FROM clin.v_substance_intakes
     411                                  WHERE pk_dose = %(pk)s
     412                                  LIMIT 1
     413                          )"""
     414                  args = {'pk': self.pk_obj}
     415
     416                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     417                  return rows[0][0]
     418
     419          is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
     420
     421          #--------------------------------------------------------
     422 -        def _get_is_drug_component(self):
     423                  cmd = """
     424                          SELECT EXISTS (
     425                                  SELECT 1
     426                                  FROM ref.v_drug_components
     427                                  WHERE pk_dose = %(pk)s
     428                                  LIMIT 1
     429                          )"""
     430                  args = {'pk': self.pk_obj}
     431                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
     432                  return rows[0][0]
     433
     434          is_drug_component = property(_get_is_drug_component, lambda x:x)
     435
     436          #--------------------------------------------------------
     437 -        def _get_formatted_units(self, short=True):
     438                  return format_units (
     439                          self._payload[self._idx['unit']],
     440                          gmTools.coalesce(self._payload[self._idx['dose_unit']], _('delivery unit')),
     441                          short = short
     442                  )
     443
     444          formatted_units = property(_get_formatted_units, lambda x:x)
     445
     446  #------------------------------------------------------------
     447 -def get_substance_doses(order_by=None):
     448          if order_by is None:
     449                  order_by = 'true'
     450          else:
     451                  order_by = 'true ORDER BY %s' % order_by
     452          cmd = _SQL_get_substance_dose % order_by
     453          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
     454          return [ cSubstanceDose(row = {'data': r, 'idx': idx, 'pk_field': 'pk_dose'}) for r in rows ]
     455
     456  #------------------------------------------------------------
     457 -def create_substance_dose(link_obj=None, pk_substance=None, substance=None, atc=None, amount=None, unit=None, dose_unit=None):
     458
     459          if [pk_substance, substance].count(None) != 1:
     460                  raise ValueError('exctly one of <pk_substance> and <substance> must be None')
     461
     462          converted, amount = gmTools.input2decimal(amount)
     463          if not converted:
     464                  raise ValueError('<amount> must be a number: %s (is: %s)', amount, type(amount))
     465
     466          if pk_substance is None:
     467                  pk_substance = create_substance(link_obj = link_obj, substance = substance, atc = atc)['pk_substance']
     468
     469          args = {
     470                  'pk_subst': pk_substance,
     471                  'amount': amount,
     472                  'unit': unit.strip(),
     473                  'dose_unit': dose_unit
     474          }
     475          cmd = """
     476                  SELECT pk FROM ref.dose
     477                  WHERE
     478                          fk_substance = %(pk_subst)s
     479                                  AND
     480                          amount = %(amount)s
     481                                  AND
     482                          unit = %(unit)s
     483                                  AND
     484                          dose_unit IS NOT DISTINCT FROM gm.nullify_empty_string(%(dose_unit)s)
     485                  """
     486          rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
     487
     488          if len(rows) == 0:
     489                  cmd = """
     490                          INSERT INTO ref.dose (fk_substance, amount, unit, dose_unit) VALUES (
     491                                  %(pk_subst)s,
     492                                  %(amount)s,
     493                                  gm.nullify_empty_string(%(unit)s),
     494                                  gm.nullify_empty_string(%(dose_unit)s)
     495                          ) RETURNING pk"""
     496                  rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
     497
     498          return cSubstanceDose(aPK_obj = rows[0]['pk'], link_obj = link_obj)
     499
     500  #------------------------------------------------------------
     501 -def create_substance_dose_by_atc(link_obj=None, substance=None, atc=None, amount=None, unit=None, dose_unit=None):
     502          subst = create_substance_by_atc (
     503                  link_obj = link_obj,
     504                  substance = substance,
     505                  atc = atc
     506          )
     507          return create_substance_dose (
     508                  link_obj = link_obj,
     509                  pk_substance = subst['pk_substance'],
     510                  amount = amount,
     511                  unit = unit,
     512                  dose_unit = dose_unit
     513          )
     514
     515  #------------------------------------------------------------
     516 -def delete_substance_dose(pk_dose=None):
     517          args = {'pk_dose': pk_dose}
     518          cmd = """
     519                  DELETE FROM ref.dose WHERE
     520                          pk = %(pk_dose)s
     521                                  AND
     522                          -- must not currently be used with a patient
     523                          NOT EXISTS (
     524                                  SELECT 1 FROM clin.v_substance_intakes
     525                                  WHERE pk_dose = %(pk_dose)s
     526                                  LIMIT 1
     527                          )
     528                                  AND
     529                          -- must not currently be linked to a drug
     530                          NOT EXISTS (
     531                                  SELECT 1 FROM ref.lnk_dose2drug
     532                                  WHERE fk_dose = %(pk_dose)s
     533                                  LIMIT 1
     534                          )
     535          """
     536          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
     537          return True
     538
     539  #------------------------------------------------------------
     540 -class cSubstanceDoseMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
     541
     542          _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE)
     543
     544          # the "normal query" is run when the search fragment
     545          # does NOT match the regex ._pattern (which is: "chars SPACE digits")
     546          _normal_query = """
     547                  SELECT
     548                          r_vsd.pk_dose
     549                                  AS data,
     550                          (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, ''))
     551                                  AS field_label,
     552                          (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, ''))
     553                                  AS list_label
     554                  FROM
     555                          ref.v_substance_doses r_vsd
     556                  WHERE
     557                          r_vsd.substance %%(fragment_condition)s
     558                  ORDER BY
     559                          list_label
     560                  LIMIT 50"""
     561
     562          # the "regex query" is run when the search fragment
     563          # DOES match the regex ._pattern (which is: "chars SPACE digits")
     564          _regex_query =  """
     565                  SELECT
     566                          r_vsd.pk_dose
     567                                  AS data,
     568                          (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, ''))
     569                                  AS field_label,
     570                          (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, ''))
     571                                  AS list_label
     572                  FROM
     573                          ref.v_substance_doses r_vsd
     574                  WHERE
     575                          %%(fragment_condition)s
     576                  ORDER BY
     577                          list_label
     578                  LIMIT 50"""
     579
     580          #--------------------------------------------------------
     581 -        def getMatchesByPhrase(self, aFragment):
     582                  """Return matches for aFragment at start of phrases."""
     583
     584                  if cSubstanceMatchProvider._pattern.match(aFragment):
     585                          self._queries = [cSubstanceMatchProvider._regex_query]
     586                          fragment_condition = """substance ILIKE %(subst)s
     587                                  AND
     588                          amount::text ILIKE %(amount)s"""
     589                          self._args['subst'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
     590                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
     591                  else:
     592                          self._queries = [cSubstanceMatchProvider._normal_query]
     593                          fragment_condition = "ILIKE %(fragment)s"
     594                          self._args['fragment'] = "%s%%" % aFragment
     595
     596                  return self._find_matches(fragment_condition)
     597
     598          #--------------------------------------------------------
     599 -        def getMatchesByWord(self, aFragment):
     600                  """Return matches for aFragment at start of words inside phrases."""
     601
     602                  if cSubstanceMatchProvider._pattern.match(aFragment):
     603                          self._queries = [cSubstanceMatchProvider._regex_query]
     604
     605                          subst = regex.sub(r'\s*\d+$', '', aFragment)
     606                          subst = gmPG2.sanitize_pg_regex(expression = subst, escape_all = False)
     607
     608                          fragment_condition = """substance ~* %(subst)s
     609                                  AND
     610                          amount::text ILIKE %(amount)s"""
     611
     612                          self._args['subst'] = "( %s)|(^%s)" % (subst, subst)
     613                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
     614                  else:
     615                          self._queries = [cSubstanceMatchProvider._normal_query]
     616                          fragment_condition = "~* %(fragment)s"
     617                          aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False)
     618                          self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment)
     619
     620                  return self._find_matches(fragment_condition)
     621
     622          #--------------------------------------------------------
     623 -        def getMatchesBySubstr(self, aFragment):
     624                  """Return matches for aFragment as a true substring."""
     625
     626                  if cSubstanceMatchProvider._pattern.match(aFragment):
     627                          self._queries = [cSubstanceMatchProvider._regex_query]
     628                          fragment_condition = """substance ILIKE %(subst)s
     629                                  AND
     630                          amount::text ILIKE %(amount)s"""
     631                          self._args['subst'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
     632                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
     633                  else:
     634                          self._queries = [cSubstanceMatchProvider._normal_query]
     635                          fragment_condition = "ILIKE %(fragment)s"
     636                          self._args['fragment'] = "%%%s%%" % aFragment
     637
     638                  return self._find_matches(fragment_condition)
     639
     640  #------------------------------------------------------------
     641  #------------------------------------------------------------
     642 -class cProductOrSubstanceMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
     643
     644          # by product name
     645          _query_drug_product_by_name = """
     646                  SELECT
     647                          ARRAY[1, pk]::INTEGER[]
     648                                  AS data,
     649                          (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', ''))
     650                                  AS list_label,
     651                          (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', ''))
     652                                  AS field_label,
     653                          1 AS rank
     654                  FROM ref.drug_product
     655                  WHERE description %(fragment_condition)s
     656                  LIMIT 50
     657          """
     658          _query_drug_product_by_name_and_strength = """
     659                  SELECT
     660                          ARRAY[1, pk_drug_product]::INTEGER[]
     661                                  AS data,
     662                          (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
     663                                  AS list_label,
     664                          (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
     665                                  AS field_label,
     666                          1 AS rank
     667                  FROM
     668                          (SELECT *, product AS description FROM ref.v_drug_components) AS _components
     669                  WHERE %%(fragment_condition)s
     670                  LIMIT 50
     671          """ % (
     672                  _('w/'),
     673                  _('w/')
     674          )
     675
     676          # by component
     677  #       _query_component_by_name = u"""
     678  #               SELECT
     679  #                       ARRAY[3, r_vdc1.pk_component]::INTEGER[]
     680  #                               AS data,
     681  #                       (r_vdc1.substance || ' ' || r_vdc1.amount || r_vdc1.unit || ' ' || r_vdc1.preparation || ' ('
     682  #                               || r_vdc1.product || ' ['
     683  #                                       || (
     684  #                                               SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
     685  #                                               FROM ref.v_drug_components r_vdc2
     686  #                                               WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
     687  #                                       )
     688  #                               || ']'
     689  #                       || ')'
     690  #                       )       AS field_label,
     691  #                       (r_vdc1.substance || ' ' || r_vdc1.amount || r_vdc1.unit || ' ' || r_vdc1.preparation || ' ('
     692  #                               || r_vdc1.product || ' ['
     693  #                                       || (
     694  #                                               SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
     695  #                                               FROM ref.v_drug_components r_vdc2
     696  #                                               WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
     697  #                                       )
     698  #                               || ']'
     699  #                       || ')'
     700  #                       )       AS list_label,
     701  #                       1 AS rank
     702  #               FROM
     703  #                       (SELECT *, product AS description FROM ref.v_drug_components) AS r_vdc1
     704  #               WHERE
     705  #                       r_vdc1.substance %(fragment_condition)s
     706  #               LIMIT 50"""
     707
     708  #       _query_component_by_name_and_strength = u"""
     709  #               SELECT
     710  #                       ARRAY[3, r_vdc1.pk_component]::INTEGER[]
     711  #                               AS data,
     712  #                       (r_vdc1.substance || ' ' || r_vdc1.amount || r_vdc1.unit || ' ' || r_vdc1.preparation || ' ('
     713  #                               || r_vdc1.product || ' ['
     714  #                                       || (
     715  #                                               SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
     716  #                                               FROM ref.v_drug_components r_vdc2
     717  #                                               WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
     718  #                                       )
     719  #                               || ']'
     720  #                       || ')'
     721  #                       )       AS field_label,
     722  #                       (r_vdc1.substance || ' ' || r_vdc1.amount || r_vdc1.unit || ' ' || r_vdc1.preparation || ' ('
     723  #                               || r_vdc1.product || ' ['
     724  #                                       || (
     725  #                                               SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
     726  #                                               FROM ref.v_drug_components r_vdc2
     727  #                                               WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
     728  #                                       )
     729  #                               || ']'
     730  #                       || ')'
     731  #                       )       AS list_label,
     732  #                       1 AS rank
     733  #               FROM (SELECT *, substance AS description FROM ref.v_drug_components) AS r_vdc1
     734  #               WHERE
     735  #                       %(fragment_condition)s
     736  #               ORDER BY list_label
     737  #               LIMIT 50"""
     738
     739          # by substance name in doses
     740          _query_substance_by_name = """
     741                  SELECT
     742                          data,
     743                          field_label,
     744                          list_label,
     745                          rank
     746                  FROM ((
     747                          -- first: substance intakes which match, because we tend to reuse them often
     748                          SELECT
     749                                  ARRAY[2, pk_substance]::INTEGER[] AS data,
     750                                  (description || ' ' || amount || unit || coalesce('/' || dose_unit, '')) AS field_label,
     751                                  (description || ' ' || amount || unit || coalesce('/' || dose_unit, '') || ' (%s)') AS list_label,
     752                                  1 AS rank
     753                          FROM (
     754                                  SELECT DISTINCT ON (description, amount, unit, dose_unit)
     755                                          pk_substance,
     756                                          substance AS description,
     757                                          amount,
     758                                          unit,
     759                                          dose_unit
     760                                  FROM clin.v_substance_intakes
     761                          ) AS normalized_intakes
     762                          WHERE description %%(fragment_condition)s
     763
     764                  ) UNION ALL (
     765  xxxxxxxxxxxxxxxxxxxxxxxxxxxx
     766                          -- second: consumable substances which match but are not intakes
     767                          SELECT
     768                                  ARRAY[2, pk]::INTEGER[] AS data,
     769                                  (description || ' ' || amount || ' ' || unit) AS field_label,
     770                                  (description || ' ' || amount || ' ' || unit) AS list_label,
     771                                  2 AS rank
     772                          FROM ref.consumable_substance
     773                          WHERE
     774                                  description %%(fragment_condition)s
     775                                          AND
     776                                  pk NOT IN (
     777                                          SELECT fk_substance
     778                                          FROM clin.substance_intake
     779                                          WHERE fk_substance IS NOT NULL
     780                                  )
     781                  )) AS candidates
     782                  --ORDER BY rank, list_label
     783                  LIMIT 50""" % _('in use')
     784
     785          _query_substance_by_name_and_strength =         """
     786                  SELECT
     787                          data,
     788                          field_label,
     789                          list_label,
     790                          rank
     791                  FROM ((
     792                          SELECT
     793                                  ARRAY[2, pk_substance]::INTEGER[] AS data,
     794                                  (description || ' ' || amount || ' ' || unit) AS field_label,
     795                                  (description || ' ' || amount || ' ' || unit || ' (%s)') AS list_label,
     796                                  1 AS rank
     797                          FROM (
     798                                  SELECT DISTINCT ON (description, amount, unit)
     799                                          pk_substance,
     800                                          substance AS description,
     801                                          amount,
     802                                          unit
     803                                  FROM clin.v_nonbraXXXnd_intakes
     804                          ) AS normalized_intakes
     805                          WHERE
     806                                  %%(fragment_condition)s
     807
     808                  ) UNION ALL (
     809
     810                          -- matching substances which are not in intakes
     811                          SELECT
     812                                  ARRAY[2, pk]::INTEGER[] AS data,
     813                                  (description || ' ' || amount || ' ' || unit) AS field_label,
     814                                  (description || ' ' || amount || ' ' || unit) AS list_label,
     815                                  2 AS rank
     816                          FROM ref.consumable_substance
     817                          WHERE
     818                                  %%(fragment_condition)s
     819                                          AND
     820                                  pk NOT IN (
     821                                          SELECT fk_substance
     822                                          FROM clin.substance_intake
     823                                          WHERE fk_substance IS NOT NULL
     824                                  )
     825                  )) AS candidates
     826                  --ORDER BY rank, list_label
     827                  LIMIT 50""" % _('in use')
     828
     829          _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE)
     830
     831          _master_query = """
     832                  SELECT
     833                          data, field_label, list_label, rank
     834                  FROM ((%s) UNION (%s) UNION (%s))
     835                          AS _union
     836                  ORDER BY rank, list_label
     837                  LIMIT 50
     838          """
     839          #--------------------------------------------------------
     840 -        def getMatchesByPhrase(self, aFragment):
     841                  """Return matches for aFragment at start of phrases."""
     842
     843                  if cProductOrSubstanceMatchProvider._pattern.match(aFragment):
     844                          self._queries = [
     845                                  cProductOrSubstanceMatchProvider._master_query % (
     846                                          cProductOrSubstanceMatchProvider._query_drug_product_by_name_and_strength,
     847                                          cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength,
     848                                          cProductOrSubstanceMatchProvider._query_component_by_name_and_strength
     849                                  )
     850                          ]
     851                          #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength]
     852                          fragment_condition = """description ILIKE %(desc)s
     853                                  AND
     854                          amount::text ILIKE %(amount)s"""
     855                          self._args['desc'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
     856                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
     857                  else:
     858                          self._queries = [
     859                                  cProductOrSubstanceMatchProvider._master_query % (
     860                                          cProductOrSubstanceMatchProvider._query_drug_product_by_name,
     861                                          cProductOrSubstanceMatchProvider._query_substance_by_name,
     862                                          cProductOrSubstanceMatchProvider._query_component_by_name
     863                                  )
     864                          ]
     865                          #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name]
     866                          fragment_condition = "ILIKE %(fragment)s"
     867                          self._args['fragment'] = "%s%%" % aFragment
     868
     869                  return self._find_matches(fragment_condition)
     870
     871          #--------------------------------------------------------
     872 -        def getMatchesByWord(self, aFragment):
     873                  """Return matches for aFragment at start of words inside phrases."""
     874
     875                  if cProductOrSubstanceMatchProvider._pattern.match(aFragment):
     876                          self._queries = [
     877                                  cProductOrSubstanceMatchProvider._master_query % (
     878                                          cProductOrSubstanceMatchProvider._query_drug_product_by_name_and_strength,
     879                                          cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength,
     880                                          cProductOrSubstanceMatchProvider._query_component_by_name_and_strength
     881                                  )
     882                          ]
     883                          #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength]
     884
     885                          desc = regex.sub(r'\s*\d+$', '', aFragment)
     886                          desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False)
     887
     888                          fragment_condition = """description ~* %(desc)s
     889                                  AND
     890                          amount::text ILIKE %(amount)s"""
     891
     892                          self._args['desc'] = "( %s)|(^%s)" % (desc, desc)
     893                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
     894                  else:
     895                          self._queries = [
     896                                  cProductOrSubstanceMatchProvider._master_query % (
     897                                          cProductOrSubstanceMatchProvider._query_drug_product_by_name,
     898                                          cProductOrSubstanceMatchProvider._query_substance_by_name,
     899                                          cProductOrSubstanceMatchProvider._query_component_by_name
     900                                  )
     901                          ]
     902                          #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name]
     903                          fragment_condition = "~* %(fragment)s"
     904                          aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False)
     905                          self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment)
     906
     907                  return self._find_matches(fragment_condition)
     908
     909          #--------------------------------------------------------
     910 -        def getMatchesBySubstr(self, aFragment):
     911                  """Return matches for aFragment as a true substring."""
     912
     913                  if cProductOrSubstanceMatchProvider._pattern.match(aFragment):
     914                          self._queries = [
     915                                  cProductOrSubstanceMatchProvider._master_query % (
     916                                          cProductOrSubstanceMatchProvider._query_drug_product_by_name_and_strength,
     917                                          cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength,
     918                                          cProductOrSubstanceMatchProvider._query_component_by_name_and_strength
     919                                  )
     920                          ]
     921                          #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength]
     922                          fragment_condition = """description ILIKE %(desc)s
     923                                  AND
     924                          amount::text ILIKE %(amount)s"""
     925                          self._args['desc'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
     926                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
     927                  else:
     928                          self._queries = [
     929                                  cProductOrSubstanceMatchProvider._master_query % (
     930                                          cProductOrSubstanceMatchProvider._query_drug_product_by_name,
     931                                          cProductOrSubstanceMatchProvider._query_substance_by_name,
     932                                          cProductOrSubstanceMatchProvider._query_component_by_name
     933                                  )
     934                          ]
     935                          #self._queries = [cProductOrSubstanceMatchProvider._query_substance_by_name]
     936                          fragment_condition = "ILIKE %(fragment)s"
     937                          self._args['fragment'] = "%%%s%%" % aFragment
     938
     939                  return self._find_matches(fragment_condition)
     940
     941  #------------------------------------------------------------
     942 -class cSubstanceIntakeObjectMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
     943
     944          # (product name) -> product
     945          _SQL_drug_product_by_name = """
     946                  SELECT
     947                          pk_drug_product
     948                                  AS data,
     949                          (product || ' (' || preparation || ')' || coalesce(' [' || atc || ']', ''))
     950                                  AS list_label,
     951                          (product || ' (' || preparation || ')' || coalesce(' [' || atc || ']', ''))
     952                                  AS field_label
     953                  FROM ref.v_drug_products
     954                  WHERE
     955                          is_vaccine IS FALSE
     956                                  AND
     957                          product %(fragment_condition)s
     958                  LIMIT 50
     959          """
     960          # (component name) -> product
     961          _SQL_drug_product_by_component_name = """
     962                  SELECT
     963                          pk_drug_product
     964                                  AS data,
     965                          (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
     966                                  AS list_label,
     967                          (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
     968                                  AS field_label
     969                  FROM
     970                          ref.v_drug_components
     971                  WHERE substance %%(fragment_condition)s
     972                  LIMIT 50
     973          """ % (
     974                  _('w/'),
     975                  _('w/')
     976          )
     977          # (product name + component strength) -> product
     978          _SQL_drug_product_by_name_and_strength = """
     979                  SELECT
     980                          pk_drug_product
     981                                  AS data,
     982                          (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
     983                                  AS list_label,
     984                          (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
     985                                  AS field_label
     986                  FROM
     987                          (SELECT *, product AS description FROM ref.v_drug_components) AS _components
     988                  WHERE %%(fragment_condition)s
     989                  LIMIT 50
     990          """ % (
     991                  _('w/'),
     992                  _('w/')
     993          )
     994          # (component name + component strength) -> product
     995          _SQL_drug_product_by_component_name_and_strength = """
     996                  SELECT
     997                          pk_drug_product
     998                                  AS data,
     999                          (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
    1000                                  AS list_label,
    1001                          (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
    1002                                  AS field_label
    1003                  FROM
    1004                          (SELECT *, substance AS description FROM ref.v_drug_components) AS _components
    1005                  WHERE %%(fragment_condition)s
    1006                  LIMIT 50
    1007          """ % (
    1008                  _('w/'),
    1009                  _('w/')
    1010          )
    1011          # non-drug substance name
    1012          _SQL_substance_name = """
    1013                  SELECT DISTINCT ON (field_label)
    1014                          data, list_label, field_label
    1015                  FROM (
    1016                          SELECT DISTINCT ON (term)
    1017                                  NULL::integer
    1018                                          AS data,
    1019                                  term || ' (ATC: ' || code || ')'
    1020                                          AS list_label,
    1021                                  term
    1022                                          AS field_label
    1023                          FROM
    1024                                  ref.atc
    1025                          WHERE
    1026                                  lower(term) %(fragment_condition)s
    1027
    1028                          UNION ALL
    1029
    1030                          SELECT DISTINCT ON (description)
    1031                                  NULL::integer
    1032                                          AS data,
    1033                                  description || coalesce(' (ATC: ' || atc || ')', '')
    1034                                          AS list_label,
    1035                                  description
    1036                                          AS field_label
    1037                          FROM
    1038                                  ref.substance
    1039                          WHERE
    1040                                  lower(description) %(fragment_condition)s
    1041                  ) AS nondrug_substances
    1042                  WHERE NOT EXISTS (
    1043                          SELECT 1 FROM ref.v_drug_components WHERE lower(substance) = lower(nondrug_substances.field_label)
    1044                  )
    1045                  LIMIT 30
    1046          """
    1047
    1048          # this query UNIONs together individual queries
    1049          _SQL_regex_master_query = """
    1050                  SELECT
    1051                          data, field_label, list_label
    1052                  FROM ((%s) UNION (%s))
    1053                          AS _union
    1054                  ORDER BY list_label
    1055                  LIMIT 50
    1056          """ % (
    1057                  _SQL_drug_product_by_name_and_strength,
    1058                  _SQL_drug_product_by_component_name_and_strength
    1059          )
    1060          _SQL_nonregex_master_query = """
    1061                  SELECT
    1062                          data, field_label, list_label
    1063                  FROM ((%s) UNION (%s) UNION (%s))
    1064                          AS _union
    1065                  ORDER BY list_label
    1066                  LIMIT 50
    1067          """ % (
    1068                  _SQL_drug_product_by_name,
    1069                  _SQL_drug_product_by_component_name,
    1070                  _SQL_substance_name
    1071          )
    1072
    1073          _REGEX_name_and_strength = regex.compile(r'^\D+\s*\d+$', regex.UNICODE)
    1074
    1075          #--------------------------------------------------------
    1076 -        def getMatchesByPhrase(self, aFragment):
    1077                  """Return matches for aFragment at start of phrases."""
    1078
    1079                  if cSubstanceIntakeObjectMatchProvider._REGEX_name_and_strength.match(aFragment):
    1080                          self._queries = [cSubstanceIntakeObjectMatchProvider._SQL_regex_master_query]
    1081                          fragment_condition = """description ILIKE %(desc)s
    1082                                  AND
    1083                          amount::text ILIKE %(amount)s"""
    1084                          self._args['desc'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
    1085                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
    1086                  else:
    1087                          self._queries = [ cSubstanceIntakeObjectMatchProvider._SQL_nonregex_master_query ]
    1088                          fragment_condition = "ILIKE %(fragment)s"
    1089                          self._args['fragment'] = "%s%%" % aFragment
    1090
    1091                  return self._find_matches(fragment_condition)
    1092
    1093          #--------------------------------------------------------
    1094 -        def getMatchesByWord(self, aFragment):
    1095                  """Return matches for aFragment at start of words inside phrases."""
    1096
    1097                  if cSubstanceIntakeObjectMatchProvider._REGEX_name_and_strength.match(aFragment):
    1098                          self._queries = [cSubstanceIntakeObjectMatchProvider._SQL_regex_master_query]
    1099
    1100                          desc = regex.sub(r'\s*\d+$', '', aFragment)
    1101                          desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False)
    1102
    1103                          fragment_condition = """description ~* %(desc)s
    1104                                  AND
    1105                          amount::text ILIKE %(amount)s"""
    1106
    1107                          self._args['desc'] = "( %s)|(^%s)" % (desc, desc)
    1108                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
    1109                  else:
    1110                          self._queries = [ cSubstanceIntakeObjectMatchProvider._SQL_nonregex_master_query ]
    1111                          fragment_condition = "~* %(fragment)s"
    1112                          aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False)
    1113                          self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment)
    1114
    1115                  return self._find_matches(fragment_condition)
    1116
    1117          #--------------------------------------------------------
    1118 -        def getMatchesBySubstr(self, aFragment):
    1119                  """Return matches for aFragment as a true substring."""
    1120
    1121                  if cSubstanceIntakeObjectMatchProvider._REGEX_name_and_strength.match(aFragment):
    1122                          self._queries = [cSubstanceIntakeObjectMatchProvider._SQL_regex_master_query]
    1123                          fragment_condition = """description ILIKE %(desc)s
    1124                                  AND
    1125                          amount::text ILIKE %(amount)s"""
    1126                          self._args['desc'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
    1127                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
    1128                  else:
    1129                          self._queries = [ cSubstanceIntakeObjectMatchProvider._SQL_nonregex_master_query ]
    1130                          fragment_condition = "ILIKE %(fragment)s"
    1131                          self._args['fragment'] = "%%%s%%" % aFragment
    1132
    1133                  return self._find_matches(fragment_condition)
    1134
    1135  #============================================================
    1136  # drug components
    1137  #------------------------------------------------------------
    1138  _SQL_get_drug_components = 'SELECT * FROM ref.v_drug_components WHERE %s'
    1139
    1140 -class cDrugComponent(gmBusinessDBObject.cBusinessDBObject):
    1141
    1142          _cmd_fetch_payload = _SQL_get_drug_components % 'pk_component = %s'
    1143          _cmds_store_payload = [
    1144                  """UPDATE ref.lnk_dose2drug SET
    1145                                  fk_drug_product = %(pk_drug_product)s,
    1146                                  fk_dose = %(pk_dose)s
    1147                          WHERE
    1148                                  pk = %(pk_component)s
    1149                                          AND
    1150                                  NOT EXISTS (
    1151                                          SELECT 1
    1152                                          FROM clin.substance_intake
    1153                                          WHERE fk_drug_component = %(pk_component)s
    1154                                          LIMIT 1
    1155                                  )
    1156                                          AND
    1157                                  xmin = %(xmin_lnk_dose2drug)s
    1158                          RETURNING
    1159                                  xmin AS xmin_lnk_dose2drug
    1160                  """
    1161          ]
    1162          _updatable_fields = [
    1163                  'pk_drug_product',
    1164                  'pk_dose'
    1165          ]
    1166          #--------------------------------------------------------
    1167 -        def format(self, left_margin=0, include_loincs=False):
    1168                  lines = []
    1169                  lines.append('%s %s%s' % (
    1170                          self._payload[self._idx['substance']],
    1171                          self._payload[self._idx['amount']],
    1172                          self.formatted_units
    1173                  ))
    1174                  lines.append(_('Component of %s (%s)') % (
    1175                          self._payload[self._idx['product']],
    1176                          self._payload[self._idx['l10n_preparation']]
    1177                  ))
    1178                  if self._payload[self._idx['is_fake_product']]:
    1179                          lines.append(' ' + _('(not a real drug product)'))
    1180
    1181                  if self._payload[self._idx['intake_instructions']] is not None:
    1182                          lines.append(_('Instructions: %s') % self._payload[self._idx['intake_instructions']])
    1183                  if self._payload[self._idx['atc_substance']] is not None:
    1184                          lines.append(_('ATC (substance): %s') % self._payload[self._idx['atc_substance']])
    1185                  if self._payload[self._idx['atc_drug']] is not None:
    1186                          lines.append(_('ATC (drug): %s') % self._payload[self._idx['atc_drug']])
    1187                  if self._payload[self._idx['external_code']] is not None:
    1188                          lines.append('%s: %s' % (
    1189                                  self._payload[self._idx['external_code_type']],
    1190                                  self._payload[self._idx['external_code']]
    1191                          ))
    1192
    1193                  if include_loincs:
    1194                          if len(self._payload[self._idx['loincs']]) > 0:
    1195                                  lines.append(_('LOINCs to monitor:'))
    1196                          lines.extend ([
    1197                                  ' %s%s%s' % (
    1198                                          loinc['loinc'],
    1199                                          gmTools.coalesce(loinc['max_age_str'], '', ': ' + _('once within %s')),
    1200                                          gmTools.coalesce(loinc['comment'], '', ' (%s)')
    1201                                  ) for loinc in self._payload[self._idx['loincs']]
    1202                          ])
    1203
    1204                  return (' ' * left_margin) + ('\n' + (' ' * left_margin)).join(lines)
    1205
    1206          #--------------------------------------------------------
    1207 -        def exists_as_intake(self, pk_patient=None):
    1208                  return substance_intake_exists (
    1209                          pk_component = self._payload[self._idx['pk_component']],
    1210                          pk_identity = pk_patient
    1211                  )
    1212
    1213          #--------------------------------------------------------
    1214 -        def turn_into_intake(self, emr=None, encounter=None, episode=None):
    1215                  return create_substance_intake (
    1216                          pk_component = self._payload[self._idx['pk_component']],
    1217                          pk_encounter = encounter,
    1218                          pk_episode = episode
    1219                  )
    1220
    1221          #--------------------------------------------------------
    1222          # properties
    1223          #--------------------------------------------------------
    1224 -        def _get_containing_drug(self):
    1225                  return cDrugProduct(aPK_obj = self._payload[self._idx['pk_drug_product']])
    1226
    1227          containing_drug = property(_get_containing_drug, lambda x:x)
    1228
    1229          #--------------------------------------------------------
    1230 -        def _get_is_in_use_by_patients(self):
    1231                  return self._payload[self._idx['is_in_use']]
    1232
    1233          is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
    1234
    1235          #--------------------------------------------------------
    1236 -        def _get_substance_dose(self):
    1237                  return cSubstanceDose(aPK_obj = self._payload[self._idx['pk_dose']])
    1238
    1239          substance_dose =  property(_get_substance_dose, lambda x:x)
    1240
    1241          #--------------------------------------------------------
    1242 -        def _get_substance(self):
    1243                  return cSubstance(aPK_obj = self._payload[self._idx['pk_substance']])
    1244
    1245          substance =  property(_get_substance, lambda x:x)
    1246
    1247          #--------------------------------------------------------
    1248 -        def _get_formatted_units(self, short=True):
    1249                  return format_units (
    1250                          self._payload[self._idx['unit']],
    1251                          self._payload[self._idx['dose_unit']],
    1252                          self._payload[self._idx['l10n_preparation']]
    1253                  )
    1254
    1255          formatted_units = property(_get_formatted_units, lambda x:x)
    1256
    1257  #------------------------------------------------------------
    1258 -def get_drug_components():
    1259          cmd = _SQL_get_drug_components % 'true ORDER BY product, substance'
    1260          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
    1261          return [ cDrugComponent(row = {'data': r, 'idx': idx, 'pk_field': 'pk_component'}) for r in rows ]
    1262
    1263  #------------------------------------------------------------
    1264 -class cDrugComponentMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
    1265
    1266          _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE)
    1267
    1268          _query_desc_only = """
    1269                  SELECT DISTINCT ON (list_label)
    1270                          r_vdc1.pk_component
    1271                                  AS data,
    1272                          (r_vdc1.substance || ' '
    1273                                  || r_vdc1.amount || r_vdc1.unit || ' '
    1274                                  || r_vdc1.preparation || ' ('
    1275                                  || r_vdc1.product || ' ['
    1276                                          || (
    1277                                                  SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
    1278                                                  FROM ref.v_drug_components r_vdc2
    1279                                                  WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
    1280                                          )
    1281                                  || ']'
    1282                           || ')'
    1283                          )       AS field_label,
    1284                          (r_vdc1.substance || ' '
    1285                                  || r_vdc1.amount || r_vdc1.unit || ' '
    1286                                  || r_vdc1.preparation || ' ('
    1287                                  || r_vdc1.product || ' ['
    1288                                          || (
    1289                                                  SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
    1290                                                  FROM ref.v_drug_components r_vdc2
    1291                                                  WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
    1292                                          )
    1293                                  || ']'
    1294                           || ')'
    1295                          )       AS list_label
    1296                  FROM ref.v_drug_components r_vdc1
    1297                  WHERE
    1298                          r_vdc1.substance %(fragment_condition)s
    1299                                  OR
    1300                          r_vdc1.product %(fragment_condition)s
    1301                  ORDER BY list_label
    1302                  LIMIT 50"""
    1303
    1304          _query_desc_and_amount = """
    1305                  SELECT DISTINCT ON (list_label)
    1306                          pk_component AS data,
    1307                          (r_vdc1.substance || ' '
    1308                                  || r_vdc1.amount || r_vdc1.unit || ' '
    1309                                  || r_vdc1.preparation || ' ('
    1310                                  || r_vdc1.product || ' ['
    1311                                          || (
    1312                                                  SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
    1313                                                  FROM ref.v_drug_components r_vdc2
    1314                                                  WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
    1315                                          )
    1316                                  || ']'
    1317                           || ')'
    1318                          )       AS field_label,
    1319                          (r_vdc1.substance || ' '
    1320                                  || r_vdc1.amount || r_vdc1.unit || ' '
    1321                                  || r_vdc1.preparation || ' ('
    1322                                  || r_vdc1.product || ' ['
    1323                                          || (
    1324                                                  SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
    1325                                                  FROM ref.v_drug_components r_vdc2
    1326                                                  WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
    1327                                          )
    1328                                  || ']'
    1329                           || ')'
    1330                          )       AS list_label
    1331                  FROM ref.v_drug_components
    1332                  WHERE
    1333                          %(fragment_condition)s
    1334                  ORDER BY list_label
    1335                  LIMIT 50"""
    1336          #--------------------------------------------------------
    1337 -        def getMatchesByPhrase(self, aFragment):
    1338                  """Return matches for aFragment at start of phrases."""
    1339
    1340                  if cDrugComponentMatchProvider._pattern.match(aFragment):
    1341                          self._queries = [cDrugComponentMatchProvider._query_desc_and_amount]
    1342                          fragment_condition = """(substance ILIKE %(desc)s OR product ILIKE %(desc)s)
    1343                                  AND
    1344                          amount::text ILIKE %(amount)s"""
    1345                          self._args['desc'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
    1346                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
    1347                  else:
    1348                          self._queries = [cDrugComponentMatchProvider._query_desc_only]
    1349                          fragment_condition = "ILIKE %(fragment)s"
    1350                          self._args['fragment'] = "%s%%" % aFragment
    1351
    1352                  return self._find_matches(fragment_condition)
    1353          #--------------------------------------------------------
    1354 -        def getMatchesByWord(self, aFragment):
    1355                  """Return matches for aFragment at start of words inside phrases."""
    1356
    1357                  if cDrugComponentMatchProvider._pattern.match(aFragment):
    1358                          self._queries = [cDrugComponentMatchProvider._query_desc_and_amount]
    1359
    1360                          desc = regex.sub(r'\s*\d+$', '', aFragment)
    1361                          desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False)
    1362
    1363                          fragment_condition = """(substance ~* %(desc)s OR product ~* %(desc)s)
    1364                                  AND
    1365                          amount::text ILIKE %(amount)s"""
    1366
    1367                          self._args['desc'] = "( %s)|(^%s)" % (desc, desc)
    1368                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
    1369                  else:
    1370                          self._queries = [cDrugComponentMatchProvider._query_desc_only]
    1371                          fragment_condition = "~* %(fragment)s"
    1372                          aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False)
    1373                          self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment)
    1374
    1375                  return self._find_matches(fragment_condition)
    1376          #--------------------------------------------------------
    1377 -        def getMatchesBySubstr(self, aFragment):
    1378                  """Return matches for aFragment as a true substring."""
    1379
    1380                  if cDrugComponentMatchProvider._pattern.match(aFragment):
    1381                          self._queries = [cDrugComponentMatchProvider._query_desc_and_amount]
    1382                          fragment_condition = """(substance ILIKE %(desc)s OR product ILIKE %(desc)s)
    1383                                  AND
    1384                          amount::text ILIKE %(amount)s"""
    1385                          self._args['desc'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
    1386                          self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
    1387                  else:
    1388                          self._queries = [cDrugComponentMatchProvider._query_desc_only]
    1389                          fragment_condition = "ILIKE %(fragment)s"
    1390                          self._args['fragment'] = "%%%s%%" % aFragment
    1391
    1392                  return self._find_matches(fragment_condition)
    1393
    1394  #============================================================
    1395  # drug products
    1396  #------------------------------------------------------------
    1397  _SQL_get_drug_product = "SELECT * FROM ref.v_drug_products WHERE %s"
    1398
    1399 -class cDrugProduct(gmBusinessDBObject.cBusinessDBObject):
    1400          """Represents a drug as marketed by a manufacturer or a generic drug product."""
    1401
    1402          _cmd_fetch_payload = _SQL_get_drug_product % 'pk_drug_product = %s'
    1403          _cmds_store_payload = [
    1404                  """UPDATE ref.drug_product SET
    1405                                  description = %(product)s,
    1406                                  preparation = %(preparation)s,
    1407                                  atc_code = gm.nullify_empty_string(%(atc)s),
    1408                                  external_code = gm.nullify_empty_string(%(external_code)s),
    1409                                  external_code_type = gm.nullify_empty_string(%(external_code_type)s),
    1410                                  is_fake = %(is_fake_product)s,
    1411                                  fk_data_source = %(pk_data_source)s
    1412                          WHERE
    1413                                  pk = %(pk_drug_product)s
    1414                                          AND
    1415                                  xmin = %(xmin_drug_product)s
    1416                          RETURNING
    1417                                  xmin AS xmin_drug_product
    1418                  """
    1419          ]
    1420          _updatable_fields = [
    1421                  'product',
    1422                  'preparation',
    1423                  'atc',
    1424                  'is_fake_product',
    1425                  'external_code',
    1426                  'external_code_type',
    1427                  'pk_data_source'
    1428          ]
    1429          #--------------------------------------------------------
    1430 -        def format(self, left_margin=0, include_component_details=False):
    1431                  lines = []
    1432                  lines.append('%s (%s)' % (
    1433                          self._payload[self._idx['product']],
    1434                          self._payload[self._idx['l10n_preparation']]
    1435                          )
    1436                  )
    1437                  if self._payload[self._idx['atc']] is not None:
    1438                          lines.append('ATC: %s' % self._payload[self._idx['atc']])
    1439                  if self._payload[self._idx['external_code']] is not None:
    1440                          lines.append('%s: %s' % (self._payload[self._idx['external_code_type']], self._payload[self._idx['external_code']]))
    1441                  if len(self._payload[self._idx['components']]) > 0:
    1442                          lines.append(_('Components:'))
    1443                          for comp in self._payload[self._idx['components']]:
    1444                                  lines.append(' %s %s %s' % (
    1445                                          comp['substance'],
    1446                                          comp['amount'],
    1447                                          format_units(comp['unit'], comp['dose_unit'], short = False)
    1448                                  ))
    1449                                  if include_component_details:
    1450                                          if comp['intake_instructions'] is not None:
    1451                                                  lines.append(comp['intake_instructions'])
    1452                                          lines.extend([ '%s%s%s' % (
    1453                                                  l['loinc'],
    1454                                                  gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')),
    1455                                                  gmTools.coalesce(l['comment'], '', ' (%s)')
    1456                                          ) for l in comp['loincs'] ])
    1457
    1458                  if self._payload[self._idx['is_fake_product']]:
    1459                          lines.append('')
    1460                          lines.append(_('this is a fake drug product'))
    1461                  if self.is_vaccine:
    1462                          lines.append(_('this is a vaccine'))
    1463
    1464                  return (' ' * left_margin) + ('\n' + (' ' * left_margin)).join(lines)
    1465
    1466          #--------------------------------------------------------
    1467 -        def save_payload(self, conn=None):
    1468                  success, data = super(self.__class__, self).save_payload(conn = conn)
    1469
    1470                  if not success:
    1471                          return (success, data)
    1472
    1473                  if self._payload[self._idx['atc']] is not None:
    1474                          atc = self._payload[self._idx['atc']].strip()
    1475                          if atc != '':
    1476                                  gmATC.propagate_atc (
    1477                                          link_obj = conn,
    1478                                          substance = self._payload[self._idx['product']].strip(),
    1479                                          atc = atc
    1480                                  )
    1481
    1482                  return (success, data)
    1483
    1484          #--------------------------------------------------------
    1485 -        def set_substance_doses_as_components(self, substance_doses=None, link_obj=None):
    1486                  if self.is_in_use_by_patients:
    1487                          return False
    1488
    1489                  pk_doses2keep = [ s['pk_dose'] for s in substance_doses ]
    1490                  _log.debug('setting components of "%s" from doses: %s', self._payload[self._idx['product']], pk_doses2keep)
    1491
    1492                  args = {'pk_drug_product': self._payload[self._idx['pk_drug_product']]}
    1493                  queries = []
    1494                  # INSERT those which are not there yet
    1495                  cmd = """
    1496                          INSERT INTO ref.lnk_dose2drug (
    1497                                  fk_drug_product,
    1498                                  fk_dose
    1499                          )
    1500                          SELECT
    1501                                  %(pk_drug_product)s,
    1502                                  %(pk_dose)s
    1503                          WHERE NOT EXISTS (
    1504                                  SELECT 1 FROM ref.lnk_dose2drug
    1505                                  WHERE
    1506                                          fk_drug_product = %(pk_drug_product)s
    1507                                                  AND
    1508                                          fk_dose = %(pk_dose)s
    1509                          )"""
    1510                  for pk_dose in pk_doses2keep:
    1511                          args['pk_dose'] = pk_dose
    1512                          queries.append({'cmd': cmd, 'args': args.copy()})
    1513
    1514                  # DELETE those that don't belong anymore
    1515                  args['doses2keep'] = tuple(pk_doses2keep)
    1516                  cmd = """
    1517                          DELETE FROM ref.lnk_dose2drug
    1518                          WHERE
    1519                                  fk_drug_product = %(pk_drug_product)s
    1520                                          AND
    1521                                  fk_dose NOT IN %(doses2keep)s"""
    1522                  queries.append({'cmd': cmd, 'args': args})
    1523                  gmPG2.run_rw_queries(link_obj = link_obj, queries = queries)
    1524                  self.refetch_payload(link_obj = link_obj)
    1525
    1526                  return True
    1527
    1528          #--------------------------------------------------------
    1529 -        def add_component(self, substance=None, atc=None, amount=None, unit=None, dose_unit=None, pk_dose=None, pk_substance=None):
    1530
    1531                  if pk_dose is None:
    1532                          if pk_substance is None:
    1533                                  pk_dose = create_substance_dose(substance = substance, atc = atc, amount = amount, unit = unit, dose_unit = dose_unit)['pk_dose']
    1534                          else:
    1535                                  pk_dose = create_substance_dose(pk_substance = pk_substance, atc = atc, amount = amount, unit = unit, dose_unit = dose_unit)['pk_dose']
    1536
    1537                  args = {
    1538                          'pk_dose': pk_dose,
    1539                          'pk_drug_product': self.pk_obj
    1540                  }
    1541
    1542                  cmd = """
    1543                          INSERT INTO ref.lnk_dose2drug (fk_drug_product, fk_dose)
    1544                          SELECT
    1545                                  %(pk_drug_product)s,
    1546                                  %(pk_dose)s
    1547                          WHERE NOT EXISTS (
    1548                                  SELECT 1 FROM ref.lnk_dose2drug
    1549                                  WHERE
    1550                                          fk_drug_product = %(pk_drug_product)s
    1551                                                  AND
    1552                                          fk_dose = %(pk_dose)s
    1553                          )"""
    1554                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    1555                  self.refetch_payload()
    1556
    1557          #------------------------------------------------------------
    1558 -        def remove_component(self, pk_dose=None, pk_component=None):
    1559                  if len(self._payload[self._idx['components']]) == 1:
    1560                          _log.error('will not remove the only component of a drug')
    1561                          return False
    1562
    1563                  args = {'pk_drug_product': self.pk_obj, 'pk_dose': pk_dose, 'pk_component': pk_component}
    1564
    1565                  if pk_component is None:
    1566                          cmd = """DELETE FROM ref.lnk_dose2drug WHERE
    1567                                                  fk_drug_product = %(pk_drug_product)s
    1568                                                          AND
    1569                                                  fk_dose = %(pk_dose)s
    1570                                                          AND
    1571                                                  NOT EXISTS (
    1572                                                          SELECT 1 FROM clin.v_substance_intakes
    1573                                                          WHERE pk_dose = %(pk_dose)s
    1574                                                          LIMIT 1
    1575                                                  )"""
    1576                  else:
    1577                          cmd = """DELETE FROM ref.lnk_dose2drug WHERE
    1578                                                  pk = %(pk_component)s
    1579                                                          AND
    1580                                                  NOT EXISTS (
    1581                                                          SELECT 1 FROM clin.substance_intake
    1582                                                          WHERE fk_drug_component = %(pk_component)s
    1583                                                          LIMIT 1
    1584                                                  )"""
    1585
    1586                  gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
    1587                  self.refetch_payload()
    1588                  return True
    1589
    1590          #--------------------------------------------------------
    1591 -        def exists_as_intake(self, pk_patient=None):
    1592                  return substance_intake_exists (
    1593                          pk_drug_product = self._payload[self._idx['pk_drug_product']],
    1594                          pk_identity = pk_patient
    1595                  )
    1596
    1597          #--------------------------------------------------------
    1598 -        def turn_into_intake(self, emr=None, encounter=None, episode=None):
    1599                  return create_substance_intake (
    1600                          pk_drug_product = self._payload[self._idx['pk_drug_product']],
    1601                          pk_encounter = encounter,
    1602                          pk_episode = episode
    1603                  )
    1604
    1605          #--------------------------------------------------------
    1606 -        def delete_associated_vaccine(self):
    1607                  if self._payload[self._idx['is_vaccine']] is False:
    1608                          return True
    1609
    1610                  args = {'pk_product': self._payload[self._idx['pk_drug_product']]}
    1611                  cmd = """DELETE FROM ref.vaccine
    1612                  WHERE
    1613                          fk_drug_product = %(pk_product)s
    1614                                  AND
    1615                          -- not in use:
    1616                          NOT EXISTS (
    1617                                  SELECT 1 FROM clin.vaccination WHERE fk_vaccine = (
    1618                                          select pk from ref.vaccine where fk_drug_product = %(pk_product)s
    1619                                  )
    1620                          )
    1621                  RETURNING *"""
    1622                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
    1623                  if len(rows) == 0:
    1624                          _log.debug('cannot delete vaccine on: %s', self)
    1625                          return False
    1626                  return True
    1627
    1628          #--------------------------------------------------------
    1629          # properties
    1630          #--------------------------------------------------------
    1631 -        def _get_external_code(self):
    1632                  return self._payload[self._idx['external_code']]
    1633
    1634          external_code = property(_get_external_code, lambda x:x)
    1635
    1636          #--------------------------------------------------------
    1637 -        def _get_external_code_type(self):
    1638                  # FIXME: maybe evaluate fk_data_source ?
    1639                  return self._payload[self._idx['external_code_type']]
    1640
    1641          external_code_type = property(_get_external_code_type, lambda x:x)
    1642
    1643          #--------------------------------------------------------
    1644 -        def _get_components(self):
    1645                  cmd = _SQL_get_drug_components % 'pk_drug_product = %(product)s'
    1646                  args = {'product': self._payload[self._idx['pk_drug_product']]}
    1647                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1648                  return [ cDrugComponent(row = {'data': r, 'idx': idx, 'pk_field': 'pk_component'}) for r in rows ]
    1649
    1650          components = property(_get_components, lambda x:x)
    1651
    1652          #--------------------------------------------------------
    1653 -        def _get_components_as_doses(self):
    1654                  pk_doses = [ c['pk_dose'] for c in self._payload[self._idx['components']] ]
    1655                  if len(pk_doses) == 0:
    1656                          return []
    1657                  cmd = _SQL_get_substance_dose % 'pk_dose IN %(pks)s'
    1658                  args = {'pks': tuple(pk_doses)}
    1659                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1660                  return [ cSubstanceDose(row = {'data': r, 'idx': idx, 'pk_field': 'pk_dose'}) for r in rows ]
    1661
    1662          components_as_doses = property(_get_components_as_doses, lambda x:x)
    1663
    1664          #--------------------------------------------------------
    1665 -        def _get_components_as_substances(self):
    1666                  pk_substances = [ c['pk_substance'] for c in self._payload[self._idx['components']] ]
    1667                  if len(pk_substances) == 0:
    1668                          return []
    1669                  cmd = _SQL_get_substance % 'pk_substance IN %(pks)s'
    1670                  args = {'pks': tuple(pk_substances)}
    1671                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1672                  return [ cSubstance(row = {'data': r, 'idx': idx, 'pk_field': 'pk_substance'}) for r in rows ]
    1673
    1674          components_as_substances = property(_get_components_as_substances, lambda x:x)
    1675
    1676          #--------------------------------------------------------
    1677 -        def _get_is_fake_product(self):
    1678                  return self._payload[self._idx['is_fake_product']]
    1679
    1680          is_fake_product = property(_get_is_fake_product, lambda x:x)
    1681
    1682          #--------------------------------------------------------
    1683 -        def _get_is_vaccine(self):
    1684                  return self._payload[self._idx['is_vaccine']]
    1685
    1686          is_vaccine = property(_get_is_vaccine, lambda x:x)
    1687
    1688          #--------------------------------------------------------
    1689 -        def _get_is_in_use_by_patients(self):
    1690                  cmd = """
    1691                          SELECT EXISTS (
    1692                                  SELECT 1 FROM clin.substance_intake WHERE
    1693                                          fk_drug_component IN (
    1694                                                  SELECT pk FROM ref.lnk_dose2drug WHERE fk_drug_product = %(pk)s
    1695                                          )
    1696                                  LIMIT 1
    1697                          )"""
    1698                  args = {'pk': self.pk_obj}
    1699                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1700                  return rows[0][0]
    1701
    1702          is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
    1703
    1704          #--------------------------------------------------------
    1705 -        def _get_is_in_use_as_vaccine(self):
    1706                  if self._payload[self._idx['is_vaccine']] is False:
    1707                          return False
    1708                  cmd = 'SELECT EXISTS(SELECT 1 FROM clin.vaccination WHERE fk_vaccine = (select pk from ref.vaccine where fk_drug_product = %(pk)s))'
    1709                  args = {'pk': self.pk_obj}
    1710                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
    1711                  return rows[0][0]
    1712
    1713          is_in_use_as_vaccine = property(_get_is_in_use_as_vaccine, lambda x:x)
    1714
    1715  #------------------------------------------------------------
    1716 -def get_drug_products():
    1717          cmd = _SQL_get_drug_product % 'TRUE ORDER BY product'
    1718          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
    1719          return [ cDrugProduct(row = {'data': r, 'idx': idx, 'pk_field': 'pk_drug_product'}) for r in rows ]
    1720
    1721  #------------------------------------------------------------
    1722 -def get_drug_by_name(product_name=None, preparation=None, link_obj=None):
    1723          args = {'prod_name': product_name, 'prep': preparation}
    1724          cmd = 'SELECT * FROM ref.v_drug_products WHERE lower(product) = lower(%(prod_name)s) AND lower(preparation) = lower(%(prep)s)'
    1725          rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1726          if len(rows) == 0:
    1727                  return None
    1728          return cDrugProduct(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_drug_product'})
    1729
    1730  #------------------------------------------------------------
    1731 -def get_drug_by_atc(atc=None, preparation=None, link_obj=None):
    1732          args = {'atc': atc, 'prep': preparation}
    1733          cmd = 'SELECT * FROM ref.v_drug_products WHERE lower(atc) = lower(%(atc)s) AND lower(preparation) = lower(%(prep)s)'
    1734          rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
    1735          if len(rows) == 0:
    1736                  return None
    1737          return cDrugProduct(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_drug_product'}, link_obj = link_obj)
    1738
    1739  #------------------------------------------------------------
    1740 -def create_drug_product(product_name=None, preparation=None, return_existing=False, link_obj=None, doses=None):
    1741
    1742          if preparation is None:
    1743                  preparation = _('units')
    1744
    1745          if preparation.strip() == '':
    1746                  preparation = _('units')
    1747
    1748          if return_existing:
    1749                  drug = get_drug_by_name(product_name = product_name, preparation = preparation, link_obj = link_obj)
    1750                  if drug is not None:
    1751                          return drug
    1752
    1753          cmd = 'INSERT INTO ref.drug_product (description, preparation) VALUES (%(prod_name)s, %(prep)s) RETURNING pk'
    1754          args = {'prod_name': product_name, 'prep': preparation}
    1755          rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
    1756          product = cDrugProduct(aPK_obj = rows[0]['pk'], link_obj = link_obj)
    1757          if doses is not None:
    1758                  product.set_substance_doses_as_components(substance_doses = doses, link_obj = link_obj)
    1759
    1760          return product
    1761
    1762  #------------------------------------------------------------
    1763 -def create_drug_product_by_atc(atc=None, product_name=None, preparation=None, return_existing=False, link_obj=None):
    1764
    1765          if atc is None:
    1766                  raise ValueError('cannot create drug product by ATC without ATC')
    1767
    1768          if preparation is None:
    1769                  preparation = _('units')
    1770
    1771          if preparation.strip() == '':
    1772                  preparation = _('units')
    1773
    1774          if return_existing:
    1775                  drug = get_drug_by_atc(atc = atc, preparation = preparation, link_obj = link_obj)
    1776                  if drug is not None:
    1777                          return drug
    1778
    1779          drug = create_drug_product (
    1780                  link_obj = link_obj,
    1781                  product_name = product_name,
    1782                  preparation = preparation,
    1783                  return_existing = False
    1784          )
    1785          drug['atc'] = atc
    1786          drug.save(conn = link_obj)
    1787          return drug
    1788
    1789  #------------------------------------------------------------
    1790 -def delete_drug_product(pk_drug_product=None):
    1791          args = {'pk': pk_drug_product}
    1792          queries = []
    1793          # delete components
    1794          cmd = """
    1795                  DELETE FROM ref.lnk_dose2drug
    1796                  WHERE
    1797                          fk_drug_product = %(pk)s
    1798                                  AND
    1799                          NOT EXISTS (
    1800                                  SELECT 1
    1801                                  FROM clin.v_substance_intakes
    1802                                  WHERE pk_drug_product = %(pk)s
    1803                                  LIMIT 1
    1804                          )"""
    1805          queries.append({'cmd': cmd, 'args': args})
    1806          # delete drug
    1807          cmd = """
    1808                  DELETE FROM ref.drug_product
    1809                  WHERE
    1810                          pk = %(pk)s
    1811                                  AND
    1812                          NOT EXISTS (
    1813                                  SELECT 1 FROM clin.v_substance_intakes
    1814                                  WHERE pk_drug_product = %(pk)s
    1815                                  LIMIT 1
    1816                          )"""
    1817          queries.append({'cmd': cmd, 'args': args})
    1818          gmPG2.run_rw_queries(queries = queries)
    1819
    1820  #============================================================
    1821  # substance intakes
    1822  #------------------------------------------------------------
    1823  _SQL_get_substance_intake = "SELECT * FROM clin.v_substance_intakes WHERE %s"
    1824
    1825 -class cSubstanceIntakeEntry(gmBusinessDBObject.cBusinessDBObject):
    1826          """Represents a substance currently taken by a patient."""
    1827
    1828          _cmd_fetch_payload = _SQL_get_substance_intake % 'pk_substance_intake = %s'
    1829          _cmds_store_payload = [
    1830                  """UPDATE clin.substance_intake SET
    1831                                  -- if .comment_on_start = '?' then .started will be mapped to NULL
    1832                                  -- in the view, also, .started CANNOT be NULL any other way so far,
    1833                                  -- so do not attempt to set .clin_when if .started is NULL
    1834                                  clin_when = (
    1835                                          CASE
    1836                                                  WHEN %(started)s IS NULL THEN clin_when
    1837                                                  ELSE %(started)s
    1838                                          END
    1839                                  )::timestamp with time zone,
    1840                                  comment_on_start = gm.nullify_empty_string(%(comment_on_start)s),
    1841                                  discontinued = %(discontinued)s,
    1842                                  discontinue_reason = gm.nullify_empty_string(%(discontinue_reason)s),
    1843                                  schedule = gm.nullify_empty_string(%(schedule)s),
    1844                                  aim = gm.nullify_empty_string(%(aim)s),
    1845                                  narrative = gm.nullify_empty_string(%(notes)s),
    1846                                  intake_is_approved_of = %(intake_is_approved_of)s,
    1847                                  harmful_use_type = %(harmful_use_type)s,
    1848                                  fk_episode = %(pk_episode)s,
    1849                                  -- only used to document "last checked" such that
    1850                                  -- .clin_when -> .started does not have to change meaning
    1851                                  fk_encounter = %(pk_encounter)s,
    1852
    1853                                  is_long_term = (
    1854                                          case
    1855                                                  when (
    1856                                                          (%(is_long_term)s is False)
    1857                                                                  and
    1858                                                          (%(duration)s is NULL)
    1859                                                  ) is True then null
    1860                                                  else %(is_long_term)s
    1861                                          end
    1862                                  )::boolean,
    1863
    1864                                  duration = (
    1865                                          case
    1866                                                  when %(is_long_term)s is True then null
    1867                                                  else %(duration)s
    1868                                          end
    1869                                  )::interval
    1870                          WHERE
    1871                                  pk = %(pk_substance_intake)s
    1872                                          AND
    1873                                  xmin = %(xmin_substance_intake)s
    1874                          RETURNING
    1875                                  xmin as xmin_substance_intake
    1876                  """
    1877          ]
    1878          _updatable_fields = [
    1879                  'started',
    1880                  'comment_on_start',
    1881                  'discontinued',
    1882                  'discontinue_reason',
    1883                  'intake_is_approved_of',
    1884                  'schedule',
    1885                  'duration',
    1886                  'aim',
    1887                  'is_long_term',
    1888                  'notes',
    1889                  'pk_episode',
    1890                  'pk_encounter',
    1891                  'harmful_use_type'
    1892          ]
    1893
    1894          #--------------------------------------------------------
    1895 -        def format_maximum_information(self, patient=None):
    1896                  return self.format (
    1897                          single_line = False,
    1898                          show_all_product_components = True,
    1899                          include_metadata = True,
    1900                          date_format = '%Y %b %d',
    1901                          include_instructions = True,
    1902                          include_loincs = True
    1903                  ).split('\n')
    1904
    1905          #--------------------------------------------------------
    1906 -        def format(self, left_margin=0, date_format='%Y %b %d', single_line=True, allergy=None, show_all_product_components=False, include_metadata=True, include_instructions=False, include_loincs=False):
    1907
    1908                  # medication
    1909                  if self._payload[self._idx['harmful_use_type']] is None:
    1910                          if single_line:
    1911                                  return self.format_as_single_line(left_margin = left_margin, date_format = date_format)
    1912                          return self.format_as_multiple_lines (
    1913                                  left_margin = left_margin,
    1914                                  date_format = date_format,
    1915                                  allergy = allergy,
    1916                                  show_all_product_components = show_all_product_components,
    1917                                  include_instructions = include_instructions
    1918                          )
    1919
    1920                  # abuse
    1921                  if single_line:
    1922                          return self.format_as_single_line_abuse(left_margin = left_margin, date_format = date_format)
    1923
    1924                  return self.format_as_multiple_lines_abuse(left_margin = left_margin, date_format = date_format, include_metadata = include_metadata)
    1925
    1926          #--------------------------------------------------------
    1927 -        def format_as_single_line_abuse(self, left_margin=0, date_format='%Y %b %d'):
    1928                  return '%s%s: %s (%s)' % (
    1929                          ' ' * left_margin,
    1930                          self._payload[self._idx['substance']],
    1931                          self.harmful_use_type_string,
    1932                          gmDateTime.pydt_strftime(self._payload[self._idx['last_checked_when']], '%b %Y')
    1933                  )
    1934
    1935          #--------------------------------------------------------
    1936 -        def format_as_single_line(self, left_margin=0, date_format='%Y %b %d'):
    1937
    1938                  if self._payload[self._idx['is_currently_active']]:
    1939                          if self._payload[self._idx['duration']] is None:
    1940                                  duration = gmTools.bool2subst (
    1941                                          self._payload[self._idx['is_long_term']],
    1942                                          _('long-term'),
    1943                                          _('short-term'),
    1944                                          _('?short-term')
    1945                                  )
    1946                          else:
    1947                                  duration = gmDateTime.format_interval (
    1948                                          self._payload[self._idx['duration']],
    1949                                          accuracy_wanted = gmDateTime.acc_days
    1950                                  )
    1951                  else:
    1952                          duration = gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], date_format)
    1953
    1954                  line = '%s%s (%s %s): %s %s%s (%s)' % (
    1955                          ' ' * left_margin,
    1956                          self.medically_formatted_start,
    1957                          gmTools.u_arrow2right,
    1958                          duration,
    1959                          self._payload[self._idx['substance']],
    1960                          self._payload[self._idx['amount']],
    1961                          self.formatted_units,
    1962                          gmTools.bool2subst(self._payload[self._idx['is_currently_active']], _('ongoing'), _('inactive'), _('?ongoing'))
    1963                  )
    1964
    1965                  return line
    1966
    1967          #--------------------------------------------------------
    1968 -        def format_as_multiple_lines_abuse(self, left_margin=0, date_format='%Y %b %d', include_metadata=True):
    1969
    1970                  txt = ''
    1971                  if include_metadata:
    1972                          txt = _('Substance abuse entry                                              [#%s]\n') % self._payload[self._idx['pk_substance_intake']]
    1973                  txt += ' ' + _('Substance: %s [#%s]%s\n') % (
    1974                          self._payload[self._idx['substance']],
    1975                          self._payload[self._idx['pk_substance']],
    1976                          gmTools.coalesce(self._payload[self._idx['atc_substance']], '', ' ATC %s')
    1977                  )
    1978                  txt += ' ' + _('Use type: %s\n') % self.harmful_use_type_string
    1979                  txt += ' ' + _('Last checked: %s\n') % gmDateTime.pydt_strftime(self._payload[self._idx['last_checked_when']], '%Y %b %d')
    1980                  if self._payload[self._idx['discontinued']] is not None:
    1981                          txt += _(' Discontinued %s\n') % (
    1982                                  gmDateTime.pydt_strftime (
    1983                                          self._payload[self._idx['discontinued']],
    1984                                          format = date_format,
    1985                                          accuracy = gmDateTime.acc_days
    1986                                  )
    1987                          )
    1988                  txt += gmTools.coalesce(self._payload[self._idx['notes']], '', _(' Notes: %s\n'))
    1989                  if include_metadata:
    1990                          txt += '\n'
    1991                          txt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % {
    1992                                  'row_ver': self._payload[self._idx['row_version']],
    1993                                  'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']]),
    1994                                  'mod_by': self._payload[self._idx['modified_by']]
    1995                          }
    1996
    1997                  return txt
    1998
    1999          #--------------------------------------------------------
    2000 -        def format_as_multiple_lines(self, left_margin=0, date_format='%Y %b %d', allergy=None, show_all_product_components=False, include_instructions=False, include_loincs=False):
    2001
    2002                  txt = _('Substance intake entry (%s, %s)   [#%s]                     \n') % (
    2003                          gmTools.bool2subst (
    2004                                  boolean = self._payload[self._idx['is_currently_active']],
    2005                                  true_return = gmTools.bool2subst (
    2006                                          boolean = self._payload[self._idx['seems_inactive']],
    2007                                          true_return = _('active, needs check'),
    2008                                          false_return = _('active'),
    2009                                          none_return = _('assumed active')
    2010                                  ),
    2011                                  false_return = _('inactive')
    2012                          ),
    2013                          gmTools.bool2subst (
    2014                                  boolean = self._payload[self._idx['intake_is_approved_of']],
    2015                                  true_return = _('approved'),
    2016                                  false_return = _('unapproved')
    2017                          ),
    2018                          self._payload[self._idx['pk_substance_intake']]
    2019                  )
    2020
    2021                  if allergy is not None:
    2022                          certainty = gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'))
    2023                          txt += '\n'
    2024                          txt += ' !! ---- Cave ---- !!\n'
    2025                          txt += ' %s (%s): %s (%s)\n' % (
    2026                                  allergy['l10n_type'],
    2027                                  certainty,
    2028                                  allergy['descriptor'],
    2029                                  gmTools.coalesce(allergy['reaction'], '')[:40]
    2030                          )
    2031                          txt += '\n'
    2032
    2033                  txt += ' ' + _('Substance: %s   [#%s]\n') % (self._payload[self._idx['substance']], self._payload[self._idx['pk_substance']])
    2034                  txt += ' ' + _('Preparation: %s\n') % self._payload[self._idx['l10n_preparation']]
    2035                  txt += ' ' + _('Amount per dose: %s %s') % (
    2036                          self._payload[self._idx['amount']],
    2037                          self._get_formatted_units(short = False)
    2038                  )
    2039                  txt += '\n'
    2040                  txt += gmTools.coalesce(self._payload[self._idx['atc_substance']], '', _(' ATC (substance): %s\n'))
    2041                  if include_loincs and (len(self._payload[self._idx['loincs']]) > 0):
    2042                          loincs = """
    2043  %s %s
    2044  %s  %s"""       % (
    2045                                  (' ' * left_margin),
    2046                                  _('LOINCs to monitor:'),
    2047                                  (' ' * left_margin),
    2048                                  ('\n' + (' ' * (left_margin + 1))).join ([
    2049                                          '%s%s%s' % (
    2050                                                  l['loinc'],
    2051                                                  gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')),
    2052                                                  gmTools.coalesce(l['comment'], '', ' (%s)')
    2053                                          ) for l in self._payload[self._idx['loincs']]
    2054                                  ])
    2055                          )
    2056                  txt += '\n'
    2057
    2058                  txt += '\n'
    2059
    2060                  txt += _(' Product name: %s   [#%s]\n') % (self._payload[self._idx['product']], self._payload[self._idx['pk_drug_product']])
    2061                  txt += gmTools.coalesce(self._payload[self._idx['atc_drug']], '', _(' ATC (drug): %s\n'))
    2062                  if show_all_product_components:
    2063                          product = self.containing_drug
    2064                          if len(product['components']) > 1:
    2065                                  for comp in product['components']:
    2066                                          if comp['pk_substance'] == self._payload[self._idx['substance']]:
    2067                                                  continue
    2068                                          txt += ('  ' + _('Other component: %s %s %s\n') % (
    2069                                                  comp['substance'],
    2070                                                  comp['amount'],
    2071                                                  format_units(comp['unit'], comp['dose_unit'])
    2072                                          ))
    2073                                          txt += gmTools.coalesce(comp['intake_instructions'], '', '   ' + _('Intake: %s') + '\n')
    2074                                          if include_loincs and (len(comp['loincs']) > 0):
    2075                                                  txt += ('   ' + _('LOINCs to monitor:') + '\n')
    2076                                                  txt += '\n'.join([ '    %s%s%s' % (
    2077                                                          l['loinc'],
    2078                                                          gmTools.coalesce(l['max_age_str'], '', ': %s'),
    2079                                                          gmTools.coalesce(l['comment'], '', ' (%s)')
    2080                                                  ) for l in comp['loincs'] ])
    2081
    2082                  txt += '\n'
    2083
    2084                  txt += gmTools.coalesce(self._payload[self._idx['schedule']], '', _(' Regimen: %s\n'))
    2085
    2086                  if self._payload[self._idx['is_long_term']]:
    2087                          duration = ' %s %s' % (gmTools.u_arrow2right, gmTools.u_infinity)
    2088                  else:
    2089                          if self._payload[self._idx['duration']] is None:
    2090                                  duration = ''
    2091                          else:
    2092                                  duration = ' %s %s' % (gmTools.u_arrow2right, gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days))
    2093
    2094                  txt += _(' Started %s%s%s\n') % (
    2095                          self.medically_formatted_start,
    2096                          duration,
    2097                          gmTools.bool2subst(self._payload[self._idx['is_long_term']], _(' (long-term)'), _(' (short-term)'), '')
    2098                  )
    2099
    2100                  if self._payload[self._idx['discontinued']] is not None:
    2101                          txt += _(' Discontinued %s\n') % (
    2102                                  gmDateTime.pydt_strftime (
    2103                                          self._payload[self._idx['discontinued']],
    2104                                          format = date_format,
    2105                                          accuracy = gmDateTime.acc_days
    2106                                  )
    2107                          )
    2108                          txt += gmTools.coalesce(self._payload[self._idx['discontinue_reason']], '', _(' Reason: %s\n'))
    2109
    2110                  txt += '\n'
    2111
    2112                  txt += gmTools.coalesce(self._payload[self._idx['aim']], '', _(' Aim: %s\n'))
    2113                  txt += gmTools.coalesce(self._payload[self._idx['episode']], '', _(' Episode: %s\n'))
    2114                  txt += gmTools.coalesce(self._payload[self._idx['health_issue']], '', _(' Health issue: %s\n'))
    2115                  txt += gmTools.coalesce(self._payload[self._idx['notes']], '', _(' Advice: %s\n'))
    2116                  if self._payload[self._idx['intake_instructions']] is not None:
    2117                          txt += (' '+ (_('Intake: %s') % self._payload[self._idx['intake_instructions']]) + '\n')
    2118                  if len(self._payload[self._idx['loincs']]) > 0:
    2119                          txt += (' ' + _('LOINCs to monitor:') + '\n')
    2120                          txt += '\n'.join([ '  %s%s%s' % (
    2121                                  l['loinc'],
    2122                                  gmTools.coalesce(l['max_age_str'], '', ': %s'),
    2123                                  gmTools.coalesce(l['comment'], '', ' (%s)')
    2124                          ) for l in self._payload[self._idx['loincs']] ])
    2125
    2126                  txt += '\n'
    2127
    2128                  txt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % {
    2129                          'row_ver': self._payload[self._idx['row_version']],
    2130                          'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']]),
    2131                          'mod_by': self._payload[self._idx['modified_by']]
    2132                  }
    2133
    2134                  return txt
    2135
    2136          #--------------------------------------------------------
    2137 -        def turn_into_allergy(self, encounter_id=None, allergy_type='allergy'):
    2138                  allg = gmAllergy.create_allergy (
    2139                          allergene = self._payload[self._idx['substance']],
    2140                          allg_type = allergy_type,
    2141                          episode_id = self._payload[self._idx['pk_episode']],
    2142                          encounter_id = encounter_id
    2143                  )
    2144                  allg['substance'] = gmTools.coalesce (
    2145                          self._payload[self._idx['product']],
    2146                          self._payload[self._idx['substance']]
    2147                  )
    2148                  allg['reaction'] = self._payload[self._idx['discontinue_reason']]
    2149                  allg['atc_code'] = gmTools.coalesce(self._payload[self._idx['atc_substance']], self._payload[self._idx['atc_drug']])
    2150                  if self._payload[self._idx['external_code_product']] is not None:
    2151                          allg['substance_code'] = '%s::::%s' % (self._payload[self._idx['external_code_type_product']], self._payload[self._idx['external_code_product']])
    2152
    2153                  if self._payload[self._idx['pk_drug_product']] is None:
    2154                          allg['generics'] = self._payload[self._idx['substance']]
    2155                  else:
    2156                          comps = [ c['substance'] for c in self.containing_drug.components ]
    2157                          if len(comps) == 0:
    2158                                  allg['generics'] = self._payload[self._idx['substance']]
    2159                          else:
    2160                                  allg['generics'] = '; '.join(comps)
    2161
    2162                  allg.save()
    2163                  return allg
    2164
    2165          #--------------------------------------------------------
    2166 -        def delete(self):
    2167                  return delete_substance_intake(pk_intake = self._payload[self._idx['pk_substance_intake']])
    2168
    2169          #--------------------------------------------------------
    2170          # properties
    2171          #--------------------------------------------------------
    2172 -        def _get_harmful_use_type_string(self):
    2173
    2174                  if self._payload[self._idx['harmful_use_type']] is None:
    2175                          return _('medication, not abuse')
    2176                  if self._payload[self._idx['harmful_use_type']] == 0:
    2177                          return _('no or non-harmful use')
    2178                  if self._payload[self._idx['harmful_use_type']] == 1:
    2179                          return _('presently harmful use')
    2180                  if self._payload[self._idx['harmful_use_type']] == 2:
    2181                          return _('presently addicted')
    2182                  if self._payload[self._idx['harmful_use_type']] == 3:
    2183                          return _('previously addicted')
    2184
    2185          harmful_use_type_string = property(_get_harmful_use_type_string)
    2186
    2187          #--------------------------------------------------------
    2188 -        def _get_external_code(self):
    2189                  drug = self.containing_drug
    2190
    2191                  if drug is None:
    2192                          return None
    2193
    2194                  return drug.external_code
    2195
    2196          external_code = property(_get_external_code, lambda x:x)
    2197
    2198          #--------------------------------------------------------
    2199 -        def _get_external_code_type(self):
    2200                  drug = self.containing_drug
    2201
    2202                  if drug is None:
    2203                          return None
    2204
    2205                  return drug.external_code_type
    2206
    2207          external_code_type = property(_get_external_code_type, lambda x:x)
    2208
    2209          #--------------------------------------------------------
    2210 -        def _get_containing_drug(self):
    2211                  if self._payload[self._idx['pk_drug_product']] is None:
    2212                          return None
    2213
    2214                  return cDrugProduct(aPK_obj = self._payload[self._idx['pk_drug_product']])
    2215
    2216          containing_drug = property(_get_containing_drug, lambda x:x)
    2217
    2218          #--------------------------------------------------------
    2219 -        def _get_formatted_units(self, short=True):
    2220                  return format_units (
    2221                          self._payload[self._idx['unit']],
    2222                          self._payload[self._idx['dose_unit']],
    2223                          self._payload[self._idx['l10n_preparation']],
    2224                          short = short
    2225                  )
    2226
    2227          formatted_units = property(_get_formatted_units, lambda x:x)
    2228
    2229          #--------------------------------------------------------
    2230 -        def _get_medically_formatted_start(self):
    2231                  if self._payload[self._idx['comment_on_start']] == '?':
    2232                          return '?'
    2233
    2234                  start_prefix = ''
    2235                  if self._payload[self._idx['comment_on_start']] is not None:
    2236                          start_prefix = gmTools.u_almost_equal_to
    2237
    2238                  duration_taken = gmDateTime.pydt_now_here() - self._payload[self._idx['started']]
    2239
    2240                  three_months = pydt.timedelta(weeks = 13, days = 3)
    2241                  if duration_taken < three_months:
    2242                          return _('%s%s: %s ago%s') % (
    2243                                  start_prefix,
    2244                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b %d', 'utf8', gmDateTime.acc_days),
    2245                                  gmDateTime.format_interval_medically(duration_taken),
    2246                                  gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' (%s)')
    2247                          )
    2248
    2249                  five_years = pydt.timedelta(weeks = 265)
    2250                  if duration_taken < five_years:
    2251                          return _('%s%s: %s ago (%s)') % (
    2252                                  start_prefix,
    2253                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b', 'utf8', gmDateTime.acc_months),
    2254                                  gmDateTime.format_interval_medically(duration_taken),
    2255                                  gmTools.coalesce (
    2256                                          self._payload[self._idx['comment_on_start']],
    2257                                          gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days),
    2258                                  )
    2259                          )
    2260
    2261                  return _('%s%s: %s ago (%s)') % (
    2262                          start_prefix,
    2263                          gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y', 'utf8', gmDateTime.acc_years),
    2264                          gmDateTime.format_interval_medically(duration_taken),
    2265                          gmTools.coalesce (
    2266                                  self._payload[self._idx['comment_on_start']],
    2267                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days),
    2268                          )
    2269                  )
    2270
    2271          medically_formatted_start = property(_get_medically_formatted_start, lambda x:x)
    2272
    2273          #--------------------------------------------------------
    2274 -        def _get_medically_formatted_start_end_of_stopped(self, now):
    2275
    2276                  # format intro
    2277                  if gmDateTime.pydt_is_today(self._payload[self._idx['discontinued']]):
    2278                          intro = _('until today')
    2279                  else:
    2280                          ended_ago = now - self._payload[self._idx['discontinued']]
    2281                          intro = _('until %s%s ago') % (
    2282                                  gmTools.u_almost_equal_to,
    2283                                  gmDateTime.format_interval_medically(ended_ago),
    2284                          )
    2285
    2286                  # format start
    2287                  if self._payload[self._idx['started']] is None:
    2288                          start = gmTools.coalesce(self._payload[self._idx['comment_on_start']], '?')
    2289                  else:
    2290                          start = '%s%s%s' % (
    2291                                  gmTools.bool2subst((self._payload[self._idx['comment_on_start']] is None), '', gmTools.u_almost_equal_to),
    2292                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%Y %b %d', accuracy = gmDateTime.acc_days),
    2293                                  gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]')
    2294                          )
    2295
    2296                  # format duration taken
    2297                  if self._payload[self._idx['started']] is None:
    2298                          duration_taken_str = '?'
    2299                  else:
    2300                          duration_taken = self._payload[self._idx['discontinued']] - self._payload[self._idx['started']] + pydt.timedelta(days = 1)
    2301                          duration_taken_str = gmDateTime.format_interval (duration_taken, gmDateTime.acc_days)
    2302
    2303                  # format duration planned
    2304                  if self._payload[self._idx['duration']] is None:
    2305                          duration_planned_str = ''
    2306                  else:
    2307                          duration_planned_str = _(' [planned: %s]') % gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days)
    2308
    2309                  # format end
    2310                  end = gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%Y %b %d', 'utf8', gmDateTime.acc_days)
    2311
    2312                  # assemble
    2313                  txt = '%s (%s %s %s%s %s %s)' % (
    2314                          intro,
    2315                          start,
    2316                          gmTools.u_arrow2right_thick,
    2317                          duration_taken_str,
    2318                          duration_planned_str,
    2319                          gmTools.u_arrow2right_thick,
    2320                          end
    2321                  )
    2322                  return txt
    2323
    2324          #--------------------------------------------------------
    2325 -        def _get_medically_formatted_start_end(self):
    2326
    2327                  now = gmDateTime.pydt_now_here()
    2328
    2329                  # medications stopped today or before today
    2330                  if self._payload[self._idx['discontinued']] is not None:
    2331                          if (self._payload[self._idx['discontinued']] < now) or (gmDateTime.pydt_is_today(self._payload[self._idx['discontinued']])):
    2332                                  return self._get_medically_formatted_start_end_of_stopped(now)
    2333
    2334                  # ongoing medications
    2335                  arrow_parts = []
    2336
    2337                  # format start
    2338                  if self._payload[self._idx['started']] is None:
    2339                          start_str = gmTools.coalesce(self._payload[self._idx['comment_on_start']], '?')
    2340                  else:
    2341                          start_prefix = gmTools.bool2subst((self._payload[self._idx['comment_on_start']] is None), '', gmTools.u_almost_equal_to)
    2342                          # starts today
    2343                          if gmDateTime.pydt_is_today(self._payload[self._idx['started']]):
    2344                                  start_str = _('today (%s)') % gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%Y %b %d', accuracy = gmDateTime.acc_days)
    2345                          # started in the past
    2346                          elif self._payload[self._idx['started']] < now:
    2347                                  started_ago = now - self._payload[self._idx['started']]
    2348                                  three_months = pydt.timedelta(weeks = 13, days = 3)
    2349                                  five_years = pydt.timedelta(weeks = 265)
    2350                                  if started_ago < three_months:
    2351                                          start_str = _('%s%s%s (%s%s ago, in %s)') % (
    2352                                                  start_prefix,
    2353                                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%b %d', accuracy = gmDateTime.acc_days),
    2354                                                  gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'),
    2355                                                  gmTools.u_almost_equal_to,
    2356                                                  gmDateTime.format_interval_medically(started_ago),
    2357                                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%Y', accuracy = gmDateTime.acc_days)
    2358                                          )
    2359                                  elif started_ago < five_years:
    2360                                          start_str = _('%s%s%s (%s%s ago, %s)') % (
    2361                                                  start_prefix,
    2362                                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b', 'utf8', gmDateTime.acc_months),
    2363                                                  gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'),
    2364                                                  gmTools.u_almost_equal_to,
    2365                                                  gmDateTime.format_interval_medically(started_ago),
    2366                                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days)
    2367                                          )
    2368                                  else:
    2369                                          start_str = _('%s%s%s (%s%s ago, %s)') % (
    2370                                                  start_prefix,
    2371                                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y', 'utf8', gmDateTime.acc_years),
    2372                                                  gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'),
    2373                                                  gmTools.u_almost_equal_to,
    2374                                                  gmDateTime.format_interval_medically(started_ago),
    2375                                                  gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days),
    2376                                          )
    2377                          # starts in the future
    2378                          else:
    2379                                  starts_in = self._payload[self._idx['started']] - now
    2380                                  start_str = _('%s%s%s (in %s%s)') % (
    2381                                          start_prefix,
    2382                                          gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b %d', 'utf8', gmDateTime.acc_days),
    2383                                          gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'),
    2384                                          gmTools.u_almost_equal_to,
    2385                                          gmDateTime.format_interval_medically(starts_in)
    2386                                  )
    2387
    2388                  arrow_parts.append(start_str)
    2389
    2390                  # format durations
    2391                  durations = []
    2392                  if self._payload[self._idx['discontinued']] is not None:
    2393                          if self._payload[self._idx['started']] is not None:
    2394                                  duration_documented = self._payload[self._idx['discontinued']] - self._payload[self._idx['started']]
    2395                                  durations.append(_('%s (documented)') % gmDateTime.format_interval(duration_documented, gmDateTime.acc_days))
    2396                  if self._payload[self._idx['duration']] is not None:
    2397                          durations.append(_('%s (plan)') % gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days))
    2398                  if len(durations) == 0:
    2399                          if self._payload[self._idx['is_long_term']]:
    2400                                  duration_str = gmTools.u_infinity
    2401                          else:
    2402                                  duration_str = '?'
    2403                  else:
    2404                          duration_str = ', '.join(durations)
    2405
    2406                  arrow_parts.append(duration_str)
    2407
    2408                  # format end
    2409                  if self._payload[self._idx['discontinued']] is None:
    2410                          if self._payload[self._idx['duration']] is None:
    2411                                  end_str = '?'
    2412                          else:
    2413                                  if self._payload[self._idx['started']] is None:
    2414                                          end_str = '?'
    2415                                  else:
    2416                                          planned_end = self._payload[self._idx['started']] + self._payload[self._idx['duration']] - pydt.timedelta(days = 1)
    2417                                          if planned_end.year == now.year:
    2418                                                  end_template = '%b %d'
    2419                                                  if planned_end < now:
    2420                                                          planned_end_from_now_str = _('%s ago, in %s') % (gmDateTime.format_interval(now - planned_end, gmDateTime.acc_days), planned_end.year)
    2421                                                  else:
    2422                                                          planned_end_from_now_str = _('in %s, %s') % (gmDateTime.format_interval(planned_end - now, gmDateTime.acc_days), planned_end.year)
    2423                                          else:
    2424                                                  end_template = '%Y'
    2425                                                  if planned_end < now:
    2426                                                          planned_end_from_now_str = _('%s ago = %s') % (
    2427                                                                  gmDateTime.format_interval(now - planned_end, gmDateTime.acc_days),
    2428                                                                  gmDateTime.pydt_strftime(planned_end, '%b %d', 'utf8', gmDateTime.acc_days)
    2429                                                          )
    2430                                                  else:
    2431                                                          planned_end_from_now_str = _('in %s = %s') % (
    2432                                                                  gmDateTime.format_interval(planned_end - now, gmDateTime.acc_days),
    2433                                                                  gmDateTime.pydt_strftime(planned_end, '%b %d', 'utf8', gmDateTime.acc_days)
    2434                                                          )
    2435                                          end_str = '%s (%s)' % (
    2436                                                  gmDateTime.pydt_strftime(planned_end, end_template, 'utf8', gmDateTime.acc_days),
    2437                                                  planned_end_from_now_str
    2438                                          )
    2439                  else:
    2440                          if gmDateTime.is_today(self._payload[self._idx['discontinued']]):
    2441                                  end_str = _('today')
    2442                          elif self._payload[self._idx['discontinued']].year == now.year:
    2443                                  end_date_template = '%b %d'
    2444                                  if self._payload[self._idx['discontinued']] < now:
    2445                                          planned_end_from_now_str = _('%s ago, in %s') % (
    2446                                                  gmDateTime.format_interval(now - self._payload[self._idx['discontinued']], gmDateTime.acc_days),
    2447                                                  self._payload[self._idx['discontinued']].year
    2448                                          )
    2449                                  else:
    2450                                          planned_end_from_now_str = _('in %s, %s') % (
    2451                                                  gmDateTime.format_interval(self._payload[self._idx['discontinued']] - now, gmDateTime.acc_days),
    2452                                                  self._payload[self._idx['discontinued']].year
    2453                                          )
    2454                          else:
    2455                                  end_date_template = '%Y'
    2456                                  if self._payload[self._idx['discontinued']] < now:
    2457                                          planned_end_from_now_str = _('%s ago = %s') % (
    2458                                                  gmDateTime.format_interval(now - self._payload[self._idx['discontinued']], gmDateTime.acc_days),
    2459                                                  gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%b %d', 'utf8', gmDateTime.acc_days)
    2460                                          )
    2461                                  else:
    2462                                          planned_end_from_now_str = _('in %s = %s') % (
    2463                                                  gmDateTime.format_interval(self._payload[self._idx['discontinued']] - now, gmDateTime.acc_days),
    2464                                                  gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%b %d', 'utf8', gmDateTime.acc_days)
    2465                                          )
    2466                          end_str = '%s (%s)' % (
    2467                                  gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], end_date_template, 'utf8', gmDateTime.acc_days),
    2468                                  planned_end_from_now_str
    2469                          )
    2470
    2471                  arrow_parts.append(end_str)
    2472
    2473                  # assemble
    2474                  return (' %s ' % gmTools.u_arrow2right_thick).join(arrow_parts)
    2475
    2476          medically_formatted_start_end = property(_get_medically_formatted_start_end, lambda x:x)
    2477
    2478          #--------------------------------------------------------
    2479 -        def _get_as_amts_latex(self, strict=True):
    2480                  return format_substance_intake_as_amts_latex(intake = self, strict=strict)
    2481
    2482          as_amts_latex = property(_get_as_amts_latex, lambda x:x)
    2483
    2484          #--------------------------------------------------------
    2485 -        def _get_as_amts_data(self, strict=True):
    2486                  return format_substance_intake_as_amts_data(intake = self, strict = strict)
    2487
    2488          as_amts_data = property(_get_as_amts_data, lambda x:x)
    2489
    2490          #--------------------------------------------------------
    2491 -        def _get_parsed_schedule(self):
    2492                  tests = [
    2493                          # lead, trail
    2494                          '       1-1-1-1 ',
    2495                          # leading dose
    2496                          '1-1-1-1',
    2497                          '22-1-1-1',
    2498                          '1/3-1-1-1',
    2499                          '/4-1-1-1'
    2500                  ]
    2501                  pattern = "^(\d\d|/\d|\d/\d|\d)[\s-]{1,5}\d{0,2}[\s-]{1,5}\d{0,2}[\s-]{1,5}\d{0,2}$"
    2502                  for test in tests:
    2503                          print(test.strip(), ":", regex.match(pattern, test.strip()))
    2504
    2505  #------------------------------------------------------------
    2506 -def get_substance_intakes(pk_patient=None):
    2507          args = {'pat': pk_patient}
    2508          if pk_patient is None:
    2509                  cmd = _SQL_get_substance_intake % 'true'
    2510          else:
    2511                  cmd = _SQL_get_substance_intake % 'pk_patient = %(pat)s'
    2512          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
    2513          return [ cSubstanceIntakeEntry(row = {'data': r, 'idx': idx, 'pk_field': 'pk_substance_intake'}) for r in rows ]
    2514
    2515  #------------------------------------------------------------
    2516 -def substance_intake_exists(pk_component=None, pk_identity=None, pk_drug_product=None, pk_dose=None):
    2517
    2518          if [pk_component, pk_drug_product, pk_dose].count(None) != 2:
    2519                  raise ValueError('only one of pk_component, pk_dose, and pk_drug_product can be non-NULL')
    2520
    2521          args = {
    2522                  'pk_comp': pk_component,
    2523                  'pk_pat': pk_identity,
    2524                  'pk_drug_product': pk_drug_product,
    2525                  'pk_dose': pk_dose
    2526          }
    2527          where_parts = ['fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pk_pat)s)']
    2528
    2529          if pk_dose is not None:
    2530                  where_parts.append('fk_drug_component IN (SELECT pk FROM ref.lnk_dose2drug WHERE fk_dose = %(pk_dose)s)')
    2531          if pk_component is not None:
    2532                  where_parts.append('fk_drug_component = %(pk_comp)s')
    2533          if pk_drug_product is not None:
    2534                  where_parts.append('fk_drug_component IN (SELECT pk FROM ref.lnk_dose2drug WHERE fk_drug_product = %(pk_drug_product)s)')
    2535
    2536          cmd = """
    2537                  SELECT EXISTS (
    2538                          SELECT 1 FROM clin.substance_intake
    2539                          WHERE
    2540                                  %s
    2541                          LIMIT 1
    2542                  )
    2543          """ % '\nAND\n'.join(where_parts)
    2544
    2545          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    2546          return rows[0][0]
    2547
    2548  #------------------------------------------------------------
    2549 -def substance_intake_exists_by_atc(pk_identity=None, atc=None):
    2550
    2551          if (atc is None) or (pk_identity is None):
    2552                  raise ValueError('atc and pk_identity cannot be None')
    2553
    2554          args = {
    2555                  'pat': pk_identity,
    2556                  'atc': atc
    2557          }
    2558          where_parts = [
    2559                  'pk_patient = %(pat)s',
    2560                  '((atc_substance = %(atc)s) OR (atc_drug = %(atc)s))'
    2561          ]
    2562          cmd = """
    2563                  SELECT EXISTS (
    2564                          SELECT 1 FROM clin.v_substance_intakes
    2565                          WHERE
    2566                                  %s
    2567                          LIMIT 1
    2568                  )
    2569          """ % '\nAND\n'.join(where_parts)
    2570
    2571          rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    2572          return rows[0][0]
    2573
    2574  #------------------------------------------------------------
    2575 -def create_substance_intake(pk_component=None, pk_encounter=None, pk_episode=None, pk_drug_product=None):
    2576
    2577          if [pk_component, pk_drug_product].count(None) != 1:
    2578                  raise ValueError('only one of pk_component and pk_drug_product can be non-NULL')
    2579
    2580          args = {
    2581                  'pk_enc': pk_encounter,
    2582                  'pk_epi': pk_episode,
    2583                  'pk_comp': pk_component,
    2584                  'pk_drug_product': pk_drug_product
    2585          }
    2586
    2587          if pk_drug_product is not None:
    2588                  cmd = """
    2589                          INSERT INTO clin.substance_intake (
    2590                                  fk_encounter,
    2591                                  fk_episode,
    2592                                  intake_is_approved_of,
    2593                                  fk_drug_component
    2594                          ) VALUES (
    2595                                  %(pk_enc)s,
    2596                                  %(pk_epi)s,
    2597                                  False,
    2598                                  -- select one of the components (the others will be added by a trigger)
    2599                                  (SELECT pk FROM ref.lnk_dose2drug WHERE fk_drug_product = %(pk_drug_product)s LIMIT 1)
    2600                          )
    2601                          RETURNING pk"""
    2602
    2603          if pk_component is not None:
    2604                  cmd = """
    2605                          INSERT INTO clin.substance_intake (
    2606                                  fk_encounter,
    2607                                  fk_episode,
    2608                                  intake_is_approved_of,
    2609                                  fk_drug_component
    2610                          ) VALUES (
    2611                                  %(pk_enc)s,
    2612                                  %(pk_epi)s,
    2613                                  False,
    2614                                  %(pk_comp)s
    2615                          )
    2616                          RETURNING pk"""
    2617
    2618          try:
    2619                  rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
    2620          except gmPG2.dbapi.InternalError as exc:
    2621                  if exc.pgerror is None:
    2622                          raise
    2623                  exc = make_pg_exception_fields_unicode(exc)
    2624                  if 'prevent_duplicate_component' in exc.u_pgerror:
    2625                          _log.exception('will not create duplicate substance intake entry')
    2626                          _log.error(exc.u_pgerror)
    2627                          return None
    2628                  raise
    2629
    2630          return cSubstanceIntakeEntry(aPK_obj = rows[0][0])
    2631
    2632  #------------------------------------------------------------
    2633 -def delete_substance_intake(pk_intake=None, delete_siblings=False):
    2634          if delete_siblings:
    2635                  cmd = """
    2636                          DELETE FROM clin.substance_intake c_si
    2637                          WHERE
    2638                                  c_si.fk_drug_component IN (
    2639                                          SELECT r_ld2d.pk FROM ref.lnk_dose2drug r_ld2d
    2640                                          WHERE r_ld2d.fk_drug_product = (
    2641                                                  SELECT c_vsi1.pk_drug_product FROM clin.v_substance_intakes c_vsi1 WHERE c_vsi1.pk_substance_intake = %(pk)s
    2642                                          )
    2643                                  )
    2644                                          AND
    2645                                  c_si.fk_encounter IN (
    2646                                          SELECT c_e.pk FROM clin.encounter c_e
    2647                                          WHERE c_e.fk_patient = (
    2648                                                  SELECT c_vsi2.pk_patient FROM clin.v_substance_intakes c_vsi2 WHERE c_vsi2.pk_substance_intake = %(pk)s
    2649                                          )
    2650                                  )"""
    2651          else:
    2652                  cmd = 'DELETE FROM clin.substance_intake WHERE pk = %(pk)s'
    2653
    2654          gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk_intake}}])
    2655          return True
    2656
    2657  #------------------------------------------------------------
    2658  # AMTS formatting
    2659  #------------------------------------------------------------
    2660 -def format_substance_intake_as_amts_latex(intake=None, strict=True):
    2661
    2662          _esc = gmTools.tex_escape_string
    2663
    2664          # %(contains)s & %(product)s & %(amount)s%(unit)s & %(preparation)s & \multicolumn{4}{l|}{%(schedule)s} & Einheit & %(notes)s & %(aim)s \tabularnewline{}\hline
    2665          cells = []
    2666          # components
    2667          components = intake.containing_drug['components']
    2668          if len(components) > 3:
    2669                  cells.append(_esc('WS-Kombi.'))
    2670          elif len(components) == 1:
    2671                  c = components[0]
    2672                  if strict:
    2673                          cells.append('\\mbox{%s}' % _esc(c['substance'][:80]))
    2674                  else:
    2675                          cells.append('\\mbox{%s}' % _esc(c['substance']))
    2676          else:
    2677                  if strict:
    2678                          cells.append('\\fontsize{10pt}{12pt}\selectfont %s ' % '\\newline '.join(['\\mbox{%s}' % _esc(c['substance'][:80]) for c in components]))
    2679                  else:
    2680                          cells.append('\\fontsize{10pt}{12pt}\selectfont %s ' % '\\newline '.join(['\\mbox{%s}' % _esc(c['substance']) for c in components]))
    2681          # product
    2682          if strict:
    2683                  cells.append(_esc(intake['product'][:50]))
    2684          else:
    2685                  cells.append(_esc(intake['product']))
    2686          # Wirkstärken
    2687          if len(components) > 3:
    2688                  cells.append('')
    2689          elif len(components) == 1:
    2690                  c = components[0]
    2691                  dose = ('%s%s' % (c['amount'], format_units(c['unit'], c['dose_unit'], short = True))).replace('.', ',')
    2692                  if strict:
    2693                          dose = dose[:11]
    2694                  cells.append(_esc(dose))
    2695          else:           # 2
    2696                  if strict:
    2697                          doses = '\\fontsize{10pt}{12pt}\selectfont %s ' % '\\newline\\ '.join ([
    2698                                  _esc(('%s%s' % (
    2699                                          ('%s' % c['amount']).replace('.', ','),
    2700                                          format_units(c['unit'], c['dose_unit'], short = True)
    2701                                  ))[:11]) for c in components
    2702                          ])
    2703                  else:
    2704                          doses = '\\fontsize{10pt}{12pt}\selectfont %s ' % '\\newline\\ '.join ([
    2705                                  _esc('%s%s' % (
    2706                                          ('%s' % c['amount']).replace('.', ','),
    2707                                          format_units(c['unit'], c['dose_unit'], short = True)
    2708                                  )) for c in components
    2709                          ])
    2710                  cells.append(doses)
    2711          # preparation
    2712          if strict:
    2713                  cells.append(_esc(intake['l10n_preparation'][:7]))
    2714          else:
    2715                  cells.append(_esc(intake['l10n_preparation']))
    2716          # schedule - for now be simple - maybe later parse 1-1-1-1 etc
    2717          if intake['schedule'] is None:
    2718                  cells.append('\\multicolumn{4}{p{3.2cm}|}{\\ }')
    2719          else:
    2720                  # spec says [:20] but implementation guide says: never trim
    2721                  if len(intake['schedule']) > 20:
    2722                          cells.append('\\multicolumn{4}{>{\\RaggedRight}p{3.2cm}|}{\\fontsize{10pt}{12pt}\selectfont %s}' % _esc(intake['schedule']))
    2723                  else:
    2724                          cells.append('\\multicolumn{4}{>{\\RaggedRight}p{3.2cm}|}{%s}' % _esc(intake['schedule']))
    2725          # Einheit to take
    2726          cells.append('')#[:20]
    2727          # notes
    2728          if intake['notes'] is None:
    2729                  cells.append(' ')
    2730          else:
    2731                  if strict:
    2732                          cells.append(_esc(intake['notes'][:80]))
    2733                  else:
    2734                          cells.append('\\fontsize{10pt}{12pt}\selectfont %s ' % _esc(intake['notes']))
    2735          # aim
    2736          if intake['aim'] is None:
    2737                  #cells.append(' ')
    2738                  cells.append(_esc(intake['episode'][:50]))
    2739          else:
    2740                  if strict:
    2741                          cells.append(_esc(intake['aim'][:50]))
    2742                  else:
    2743                          cells.append('\\fontsize{10pt}{12pt}\selectfont %s ' % _esc(intake['aim']))
    2744
    2745          table_row = ' & '.join(cells)
    2746          table_row += '\\tabularnewline{}\n\\hline'
    2747
    2748          return table_row
    2749
    2750  #------------------------------------------------------------
    2751 -def format_substance_intake_as_amts_data(intake=None, strict=True):
    2752          """
    2753          <M a="Handelsname" fd="freie Formangabe" t="freies Dosierschema" dud="freie Dosiereinheit (Stück Tab)" r="reason" i="info">
    2754                  <W w="Metformin" s="500 mg"/>
    2755                  <W ...>
    2756          </M>
    2757          """
    2758          if not strict:
    2759                  pass
    2760                  # relax length checks
    2761
    2762          M_fields = []
    2763          M_fields.append('a="%s"' % intake['product'])
    2764          M_fields.append('fd="%s"' % intake['l10n_preparation'])
    2765          if intake['schedule'] is not None:
    2766                  M_fields.append('t="%s"' % intake['schedule'])
    2767          #M_fields.append(u'dud="%s"' % intake['dose unit, like Stück'])
    2768          if intake['aim'] is None:
    2769                  M_fields.append('r="%s"' % intake['episode'])
    2770          else:
    2771                  M_fields.append('r="%s"' % intake['aim'])
    2772          if intake['notes'] is not None:
    2773                  M_fields.append('i="%s"' % intake['notes'])
    2774          M_line = '<M %s>' % ' '.join(M_fields)
    2775
    2776          W_lines = []
    2777          for comp in intake.containing_drug['components']:
    2778                  W_lines.append('<W w="%s" s="%s %s"/>' % (
    2779                          comp['substance'],
    2780                          comp['amount'],
    2781                          format_units(comp['unit'], comp['dose_unit'], short = True)
    2782                  ))
    2783
    2784          return M_line + ''.join(W_lines) + '</M>'
    2785
    2786  #------------------------------------------------------------
    2787 -def generate_amts_data_template_definition_file(work_dir=None, strict=True):
    2788
    2789          _log.debug('generating AMTS data template definition file(workdir=%s, strict=%s)', work_dir, strict)
    2790
    2791          if not strict:
    2792                  return __generate_enhanced_amts_data_template_definition_file(work_dir = work_dir)
    2793
    2794          amts_lines = [ l for l in ('<MP v="023" U="%s"' % uuid.uuid4().hex + """ l="de-DE"$<<if_not_empty::$<amts_page_idx::::1>$// a="%s"//::>>$$<<if_not_empty::$<amts_page_idx::::>$// z="$<amts_total_pages::::1>$"//::>>$>
    2795  <P g="$<name::%(firstnames)s::45>$" f="$<name::%(lastnames)s::45>$" b="$<date_of_birth::%Y%m%d::8>$"/>
    2796  <A
    2797   n="$<<range_of::$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$::30>>$"
    2798  $<praxis_address:: s="%(street)s"::>$
    2799  $<praxis_address:: z="%(postcode)s"::>$
    2800  $<praxis_address:: c="%(urb)s"::>$
    2801  $<praxis_comm::workphone// p="%(url)s"::20>$
    2802  $<praxis_comm::email// e="%(url)s"::80>$
    2803   t="$<today::%Y%m%d::8>$"
    2804  />
    2805  <O ai="s.S.$<amts_total_pages::::1>$ unten"/>
    2806  $<amts_intakes_as_data::::9999999>$
    2807  </MP>""").split('\n') ]
    2808  #$<<if_not_empty::$<allergy_list::%(descriptor)s//,::>$//<O ai="%s"/>::>>$
    2809
    2810          amts_fname = gmTools.get_unique_filename (
    2811                  prefix = 'gm2amts_data-',
    2812                  suffix = '.txt',
    2813                  tmp_dir = work_dir
    2814          )
    2815          amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8')
    2816          amts_template.write('[form]\n')
    2817          amts_template.write('template = $template$\n')
    2818          amts_template.write(''.join(amts_lines))
    2819          amts_template.write('\n')
    2820          amts_template.write('$template$\n')
    2821          amts_template.close()
    2822
    2823          return amts_fname
    2824
    2825  #------------------------------------------------------------
    2826 -def __generate_enhanced_amts_data_template_definition_file(work_dir=None):
    2827
    2828          amts_lines = [ l for l in ('<MP v="023" U="%s"' % uuid.uuid4().hex + """ l="de-DE" a="1" z="1">
    2829  <P g="$<name::%(firstnames)s::>$" f="$<name::%(lastnames)s::>$" b="$<date_of_birth::%Y%m%d::8>$"/>
    2830  <A
    2831   n="$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$"
    2832  $<praxis_address:: s="%(street)s %(number)s %(subunit)s"::>$
    2833  $<praxis_address:: z="%(postcode)s"::>$
    2834  $<praxis_address:: c="%(urb)s"::>$
    2835  $<praxis_comm::workphone// p="%(url)s"::>$
    2836  $<praxis_comm::email// e="%(url)s"::>$
    2837   t="$<today::%Y%m%d::8>$"
    2838  />
    2839  <O ai="Seite 1 unten"/>
    2840  $<amts_intakes_as_data_enhanced::::9999999>$
    2841  </MP>""").split('\n') ]
    2842  #$<<if_not_empty::$<allergy_list::%(descriptor)s//,::>$//<O ai="%s"/>::>>$
    2843
    2844          amts_fname = gmTools.get_unique_filename (
    2845                  prefix = 'gm2amts_data-utf8-unabridged-',
    2846                  suffix = '.txt',
    2847                  tmp_dir = work_dir
    2848          )
    2849          amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8')
    2850          amts_template.write('[form]\n')
    2851          amts_template.write('template = $template$\n')
    2852          amts_template.write(''.join(amts_lines))
    2853          amts_template.write('\n')
    2854          amts_template.write('$template$\n')
    2855          amts_template.close()
    2856
    2857          return amts_fname
    2858
    2859  #------------------------------------------------------------
    2860  # AMTS v2.0 -- outdated
    2861  #------------------------------------------------------------
    2862 -def format_substance_intake_as_amts_data_v2_0(intake=None, strict=True):
    2863
    2864          if not strict:
    2865                  pass
    2866                  # relax length checks
    2867
    2868          fields = []
    2869
    2870          # components
    2871          components = [ c.split('::') for c in intake.containing_drug['components'] ]
    2872          if len(components) > 3:
    2873                  fields.append('WS-Kombi.')
    2874          elif len(components) == 1:
    2875                  c = components[0]
    2876                  fields.append(c[0][:80])
    2877          else:
    2878                  fields.append('~'.join([c[0][:80] for c in components]))
    2879          # product
    2880          fields.append(intake['product'][:50])
    2881          # Wirkstärken
    2882          if len(components) > 3:
    2883                  fields.append('')
    2884          elif len(components) == 1:
    2885                  c = components[0]
    2886                  fields.append(('%s%s' % (c[1], c[2]))[:11])
    2887          else:
    2888                  fields.append('~'.join([('%s%s' % (c[1], c[2]))[:11] for c in components]))
    2889          # preparation
    2890          fields.append(intake['l10n_preparation'][:7])
    2891          # schedule - for now be simple - maybe later parse 1-1-1-1 etc
    2892          fields.append(gmTools.coalesce(intake['schedule'], '')[:20])
    2893          # Einheit to take
    2894          fields.append('')#[:20]
    2895          # notes
    2896          fields.append(gmTools.coalesce(intake['notes'], '')[:80])
    2897          # aim
    2898          fields.append(gmTools.coalesce(intake['aim'], '')[:50])
    2899
    2900          return '|'.join(fields)
    2901
    2902  #------------------------------------------------------------
    2903 -def calculate_amts_data_check_symbol_v2_0(intakes=None):
    2904
    2905          # first char of generic substance or product name
    2906          first_chars = []
    2907          for intake in intakes:
    2908                  first_chars.append(intake['product'][0])
    2909
    2910          # add up_per page
    2911          val_sum = 0
    2912          for first_char in first_chars:
    2913                  # ziffer: ascii+7
    2914                  if first_char.isdigit():
    2915                          val_sum += (ord(first_char) + 7)
    2916                  # großbuchstabe: ascii
    2917                  # kleinbuchstabe ascii(großbuchstabe)
    2918                  if first_char.isalpha():
    2919                          val_sum += ord(first_char.upper())
    2920                  # other: 0
    2921
    2922          # get remainder of sum mod 36
    2923          tmp, remainder = divmod(val_sum, 36)
    2924          # 0-9 -> '0' - '9'
    2925          if remainder < 10:
    2926                  return '%s' % remainder
    2927          # 10-35 -> 'A' - 'Z'
    2928          return chr(remainder + 55)
    2929
    2930  #------------------------------------------------------------
    2931 -def generate_amts_data_template_definition_file_v2_0(work_dir=None, strict=True):
    2932
    2933          if not strict:
    2934                  return __generate_enhanced_amts_data_template_definition_file(work_dir = work_dir)
    2935
    2936          amts_fields = [
    2937                  'MP',
    2938                  '020',  # Version
    2939                  'DE',   # Land
    2940                  'DE',   # Sprache
    2941                  '1',    # Zeichensatz 1 = Ext ASCII (fest) = ISO8859-1 = Latin1
    2942                  '$<today::%Y%m%d::8>$',
    2943                  '$<amts_page_idx::::1>$',                               # to be set by code using the template
    2944                  '$<amts_total_pages::::1>$',                    # to be set by code using the template
    2945                  '0',    # Zertifizierungsstatus
    2946
    2947                  '$<name::%(firstnames)s::45>$',
    2948                  '$<name::%(lastnames)s::45>$',
    2949                  '',     # Patienten-ID
    2950                  '$<date_of_birth::%Y%m%d::8>$',
    2951
    2952                  '$<<range_of::$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$::30>>$',
    2953                  '$<praxis_address::%(street)s %(number)s %(subunit)s|%(postcode)s|%(urb)s::57>$',               # 55+2 because of 2 embedded "|"s
    2954                  '$<praxis_comm::workphone::20>$',
    2955                  '$<praxis_comm::email::80>$',
    2956
    2957                  #u'264 $<allergy_state::::21>$',                                # param 1, Allergien 25-4 (4 for "264 ", spec says max of 25)
    2958                  '264 Seite $<amts_total_pages::::1>$ unten',    # param 1, Allergien 25-4 (4 for "264 ", spec says max of 25)
    2959                  '', # param 2, not used currently
    2960                  '', # param 3, not used currently
    2961
    2962                  # Medikationseinträge
    2963                  '$<amts_intakes_as_data::::9999999>$',
    2964
    2965                  '$<amts_check_symbol::::1>$',   # Prüfzeichen, value to be set by code using the template, *per page* !
    2966                  '#@',                                                   # Endesymbol
    2967          ]
    2968
    2969          amts_fname = gmTools.get_unique_filename (
    2970                  prefix = 'gm2amts_data-',
    2971                  suffix = '.txt',
    2972                  tmp_dir = work_dir
    2973          )
    2974          amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8')
    2975          amts_template.write('[form]\n')
    2976          amts_template.write('template = $template$\n')
    2977          amts_template.write('|'.join(amts_fields))
    2978          amts_template.write('\n')
    2979          amts_template.write('$template$\n')
    2980          amts_template.close()
    2981
    2982          return amts_fname
    2983
    2984  #------------------------------------------------------------
    2985 -def __generate_enhanced_amts_data_template_definition_file_v2_0(work_dir=None):
    2986
    2987          amts_fields = [
    2988                  'MP',
    2989                  '020',  # Version
    2990                  'DE',   # Land
    2991                  'DE',   # Sprache
    2992                  '1',    # Zeichensatz 1 = Ext ASCII (fest) = ISO8859-1 = Latin1
    2993                  '$<today::%Y%m%d::8>$',
    2994                  '1',    # idx of this page
    2995                  '1',    # total pages
    2996                  '0',    # Zertifizierungsstatus
    2997
    2998                  '$<name::%(firstnames)s::>$',
    2999                  '$<name::%(lastnames)s::>$',
    3000                  '',     # Patienten-ID
    3001                  '$<date_of_birth::%Y%m%d::8>$',
    3002
    3003                  '$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$',
    3004                  '$<praxis_address::%(street)s %(number)s %(subunit)s::>$',
    3005                  '$<praxis_address::%(postcode)s::>$',
    3006                  '$<praxis_address::%(urb)s::>$',
    3007                  '$<praxis_comm::workphone::>$',
    3008                  '$<praxis_comm::email::>$',
    3009
    3010                  #u'264 $<allergy_state::::>$',                                  # param 1, Allergien
    3011                  '264 Seite 1 unten',                                                    # param 1, Allergien
    3012                  '', # param 2, not used currently
    3013                  '', # param 3, not used currently
    3014
    3015                  # Medikationseinträge
    3016                  '$<amts_intakes_as_data_enhanced::::>$',
    3017
    3018                  '$<amts_check_symbol::::1>$',   # Prüfzeichen, value to be set by code using the template, *per page* !
    3019                  '#@',                                                   # Endesymbol
    3020          ]
    3021
    3022          amts_fname = gmTools.get_unique_filename (
    3023                  prefix = 'gm2amts_data-utf8-unabridged-',
    3024                  suffix = '.txt',
    3025                  tmp_dir = work_dir
    3026          )
    3027          amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8')
    3028          amts_template.write('[form]\n')
    3029          amts_template.write('template = $template$\n')
    3030          amts_template.write('|'.join(amts_fields))
    3031          amts_template.write('\n')
    3032          amts_template.write('$template$\n')
    3033          amts_template.close()
    3034
    3035          return amts_fname
    3036
    3037  #------------------------------------------------------------
    3038  # other formatting
    3039  #------------------------------------------------------------
    3040 -def format_substance_intake_notes(emr=None, output_format='latex', table_type=u'by-product'):
    3041
    3042          tex = '%s\n' % _('Additional notes for healthcare professionals')
    3043          tex += '%%%% requires "\\usepackage{longtable}"\n'
    3044          tex += '%%%% requires "\\usepackage{tabu}"\n'
    3045          tex += '\\begin{longtabu} to \\textwidth {|X[,L]|r|X[,L]|}\n'
    3046          tex += '\\hline\n'
    3047          tex += '%s {\\scriptsize (%s)} & %s & %s\\\\\n' % (_('Substance'), _('Drug Product'), _('Strength'), _('Aim'))
    3048          tex += '\\hline\n'
    3049          tex += '%s\n'                   # this is where the lines end up
    3050          tex += '\\end{longtabu}\n'
    3051
    3052          current_meds = emr.get_current_medications (
    3053                  include_inactive = False,
    3054                  include_unapproved = False,
    3055                  order_by = 'product, substance'
    3056          )
    3057          # create lines
    3058          lines = []
    3059          for med in current_meds:
    3060                  if med['aim'] is None:
    3061                          aim = ''
    3062                  else:
    3063                          aim = '{\\scriptsize %s}' % gmTools.tex_escape_string(med['aim'])
    3064                  lines.append('%s {\\small (%s: {\\tiny %s})} & %s%s & %s\\\\' % (
    3065                          gmTools.tex_escape_string(med['substance']),
    3066                          gmTools.tex_escape_string(med['l10n_preparation']),
    3067                          gmTools.tex_escape_string(med['product']),
    3068                          med['amount'],
    3069                          gmTools.tex_escape_string(med.formatted_units),
    3070                          aim
    3071                  ))
    3072                  lines.append(u'\\hline')
    3073
    3074          return tex % '\n'.join(lines)
    3075
    3076  #------------------------------------------------------------
    3077 -def format_substance_intake(emr=None, output_format='latex', table_type='by-product'):
    3078
    3079          # FIXME: add intake_instructions
    3080
    3081          tex = '%s {\\tiny (%s)}\n' % (
    3082                  gmTools.tex_escape_string(_('Medication list')),
    3083                  gmTools.tex_escape_string(_('ordered by brand'))
    3084          )
    3085          tex += '%% requires "\\usepackage{longtable}"\n'
    3086          tex += '%% requires "\\usepackage{tabu}"\n'
    3087          tex += u'\\begin{longtabu} to \\textwidth {|X[-1,L]|X[2.5,L]|}\n'
    3088          tex += u'\\hline\n'
    3089          tex += u'%s & %s\\\\\n' % (
    3090                  gmTools.tex_escape_string(_('Drug')),
    3091                  gmTools.tex_escape_string(_('Regimen / Advice'))
    3092          )
    3093          tex += '\\hline\n'
    3094          tex += '%s\n'
    3095          tex += '\\end{longtabu}\n'
    3096
    3097          # aggregate medication data
    3098          current_meds = emr.get_current_medications (
    3099                  include_inactive = False,
    3100                  include_unapproved = False,
    3101                  order_by = 'product, substance'
    3102          )
    3103          line_data = {}
    3104          for med in current_meds:
    3105                  identifier = med['product']
    3106
    3107                  try:
    3108                          line_data[identifier]
    3109                  except KeyError:
    3110                          line_data[identifier] = {'product': '', 'l10n_preparation': '', 'schedule': '', 'notes': [], 'strengths': []}
    3111
    3112                  line_data[identifier]['product'] = identifier
    3113                  line_data[identifier]['strengths'].append('%s %s%s' % (med['substance'][:20], med['amount'], med.formatted_units))
    3114                  if med['l10n_preparation'] not in identifier:
    3115                          line_data[identifier]['l10n_preparation'] = med['l10n_preparation']
    3116                  sched_parts = []
    3117                  if med['duration'] is not None:
    3118                          sched_parts.append(gmDateTime.format_interval(med['duration'], gmDateTime.acc_days, verbose = True))
    3119                  if med['schedule'] is not None:
    3120                          sched_parts.append(med['schedule'])
    3121                  line_data[identifier]['schedule'] = ': '.join(sched_parts)
    3122                  if med['notes'] is not None:
    3123                          if med['notes'] not in line_data[identifier]['notes']:
    3124                                  line_data[identifier]['notes'].append(med['notes'])
    3125          # format aggregated data
    3126          already_seen = []
    3127          lines = []
    3128          #line1_template = u'\\rule{0pt}{3ex}{\\Large %s} %s & %s \\\\'
    3129          line1_template = u'{\\Large %s} %s & %s\\\\'
    3130          line2_template = u'{\\tiny %s}                     & {\\scriptsize %s}\\\\'
    3131          line3_template = u'                                & {\\scriptsize %s}\\\\'
    3132          for med in current_meds:
    3133                  identifier = med['product']
    3134                  if identifier in already_seen:
    3135                          continue
    3136                  already_seen.append(identifier)
    3137                  lines.append (line1_template % (
    3138                          gmTools.tex_escape_string(line_data[identifier]['product']),
    3139                          gmTools.tex_escape_string(line_data[identifier]['l10n_preparation']),
    3140                          gmTools.tex_escape_string(line_data[identifier]['schedule'])
    3141                  ))
    3142                  strengths = gmTools.tex_escape_string(' / '.join(line_data[identifier]['strengths']))
    3143                  if len(line_data[identifier]['notes']) == 0:
    3144                          first_note = ''
    3145                  else:
    3146                          first_note = gmTools.tex_escape_string(line_data[identifier]['notes'][0])
    3147                  lines.append(line2_template % (strengths, first_note))
    3148                  if len(line_data[identifier]['notes']) > 1:
    3149                          for note in line_data[identifier]['notes'][1:]:
    3150                                  lines.append(line3_template % gmTools.tex_escape_string(note))
    3151                  lines.append('\\hline')
    3152
    3153          return tex % '\n'.join(lines)
    3154
    3155  #============================================================
    3156  # convenience functions
    3157  #------------------------------------------------------------
    3158 -def create_default_medication_history_episode(pk_health_issue=None, encounter=None, link_obj=None):
    3159          return gmEMRStructItems.create_episode (
    3160                  pk_health_issue = pk_health_issue,
    3161                  episode_name = DEFAULT_MEDICATION_HISTORY_EPISODE,
    3162                  is_open = False,
    3163                  allow_dupes = False,
    3164                  encounter = encounter,
    3165                  link_obj = link_obj
    3166          )
    3167
    3168  #------------------------------------------------------------
    3169 -def get_tobacco():
    3170          tobacco = create_drug_product (
    3171                  product_name = _('nicotine'),
    3172                  preparation = _('tobacco'),
    3173                  return_existing = True
    3174          )
    3175          tobacco['is_fake_product'] = True
    3176          tobacco.save()
    3177          nicotine = create_substance_dose_by_atc (
    3178                  substance = _('nicotine'),
    3179                  atc = gmATC.ATC_NICOTINE,
    3180                  amount = 1,
    3181                  unit = 'pack',
    3182                  dose_unit = 'year'
    3183          )
    3184          tobacco.set_substance_doses_as_components(substance_doses = [nicotine])
    3185          return tobacco
    3186
    3187  #------------------------------------------------------------
    3188 -def get_alcohol():
    3189          drink = create_drug_product (
    3190                  product_name = _('alcohol'),
    3191                  preparation = _('liquid'),
    3192                  return_existing = True
    3193          )
    3194          drink['is_fake_product'] = True
    3195          drink.save()
    3196          ethanol = create_substance_dose_by_atc (
    3197                  substance = _('ethanol'),
    3198                  atc = gmATC.ATC_ETHANOL,
    3199                  amount = 1,
    3200                  unit = 'g',
    3201                  dose_unit = 'ml'
    3202          )
    3203          drink.set_substance_doses_as_components(substance_doses = [ethanol])
    3204          return drink
    3205
    3206  #------------------------------------------------------------
    3207 -def get_other_drug(name=None, pk_dose=None):
    3208          drug = create_drug_product (
    3209                  product_name = name,
    3210                  preparation = _('unit'),
    3211                  return_existing = True
    3212          )
    3213          drug['is_fake_product'] = True
    3214          drug.save()
    3215          if pk_dose is None:
    3216                  content = create_substance_dose (
    3217                          substance = name,
    3218                          amount = 1,
    3219                          unit = _('unit'),
    3220                          dose_unit = _('unit')
    3221                  )
    3222          else:
    3223                  content = {'pk_dose': pk_dose}          #cSubstanceDose(aPK_obj = pk_dose)
    3224          drug.set_substance_doses_as_components(substance_doses = [content])
    3225          return drug
    3226
    3227  #------------------------------------------------------------
    3228 -def format_units(unit, dose_unit, preparation=None, short=True):
    3229          if short:
    3230                  return '%s%s' % (unit, gmTools.coalesce(dose_unit, '', '/%s'))
    3231
    3232          return '%s / %s' % (
    3233                  unit,
    3234                  gmTools.coalesce (
    3235                          dose_unit,
    3236                          _('delivery unit%s') % gmTools.coalesce(preparation, '', ' (%s)'),
    3237                          '%s'
    3238                  )
    3239          )
    3240
    3241  #============================================================
    3242  # main
    3243  #------------------------------------------------------------
    3244  if __name__ == "__main__":
    3245
    3246          if len(sys.argv) < 2:
    3247                  sys.exit()
    3248
    3249          if sys.argv[1] != 'test':
    3250                  sys.exit()
    3251
    3252          from Gnumed.pycommon import gmLog2
    3253          #from Gnumed.pycommon import gmI18N
    3254
    3255          gmI18N.activate_locale()
    3256  #       gmDateTime.init()
    3257
    3258          #--------------------------------------------------------
    3259          # generic
    3260          #--------------------------------------------------------
    3261 -        def test_create_substance_intake():
    3262                  drug = create_substance_intake (
    3263                          pk_component = 2,
    3264                          pk_encounter = 1,
    3265                          pk_episode = 1
    3266                  )
    3267                  print(drug)
    3268
    3269          #--------------------------------------------------------
    3270 -        def test_get_substances():
    3271                  for s in get_substances():
    3272                          ##print s
    3273                          print("--------------------------")
    3274                          print(s.format())
    3275                          print('in use:', s.is_in_use_by_patients)
    3276                          print('is component:', s.is_drug_component)
    3277
    3278  #               s = cSubstance(1)
    3279  #               print s
    3280  #               print s['loincs']
    3281  #               print s.format()
    3282  #               print 'in use:', s.is_in_use_by_patients
    3283  #               print 'is component:', s.is_drug_component
    3284
    3285          #--------------------------------------------------------
    3286 -        def test_get_doses():
    3287                  for d in get_substance_doses():
    3288                          #print d
    3289                          print("--------------------------")
    3290                          print(d.format(left_margin = 1, include_loincs = True))
    3291                          print('in use:', d.is_in_use_by_patients)
    3292                          print('is component:', d.is_drug_component)
    3293
    3294          #--------------------------------------------------------
    3295 -        def test_get_components():
    3296                  for c in get_drug_components():
    3297                          #print c
    3298                          print('--------------------------------------')
    3299                          print(c.format())
    3300                          print('dose:', c.substance_dose.format())
    3301                          print('substance:', c.substance.format())
    3302
    3303          #--------------------------------------------------------
    3304 -        def test_get_drugs():
    3305                  for d in get_drug_products():
    3306                          if d['is_fake_product'] or d.is_vaccine:
    3307                                  continue
    3308                          print('--------------------------------------')
    3309                          print(d.format())
    3310                          for c in d.components:
    3311                                  print('-------')
    3312                                  print(c.format())
    3313                                  print(c.substance_dose.format())
    3314                                  print(c.substance.format())
    3315
    3316          #--------------------------------------------------------
    3317 -        def test_get_intakes():
    3318                  for i in get_substance_intakes():
    3319                          #print i
    3320                          print('------------------------------------------------')
    3321                          print('\n'.join(i.format_maximum_information()))
    3322
    3323          #--------------------------------------------------------
    3324 -        def test_get_habit_drugs():
    3325                  print(get_tobacco().format())
    3326                  print(get_alcohol().format())
    3327                  print(get_other_drug(name = 'LSD').format())
    3328
    3329          #--------------------------------------------------------
    3330 -        def test_drug2renal_insufficiency_url():
    3331                  drug2renal_insufficiency_url(search_term = 'Metoprolol')
    3332          #--------------------------------------------------------
    3333 -        def test_medically_formatted_start_end():
    3334                  cmd = "SELECT pk_substance_intake FROM clin.v_substance_intakes"
    3335                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
    3336                  for row in rows:
    3337                          entry = cSubstanceIntakeEntry(row['pk_substance_intake'])
    3338                          print('===============================================================')
    3339                          print(entry.format(left_margin = 1, single_line = False, show_all_product_components = True))
    3340                          print('--------------------------------')
    3341                          print(entry.medically_formatted_start_end)
    3342                          gmTools.prompted_input()
    3343
    3344          #--------------------------------------------------------
    3345 -        def test_generate_amts_data_template_definition_file(work_dir=None, strict=True):
    3346                  print('file:', generate_amts_data_template_definition_file(strict = True))
    3347
    3348          #--------------------------------------------------------
    3349 -        def test_format_substance_intake_as_amts_data():
    3350                  #print format_substance_intake_as_amts_data(cSubstanceIntakeEntry(1))
    3351                  print(cSubstanceIntakeEntry(1).as_amts_data)
    3352
    3353          #--------------------------------------------------------
    3354 -        def test_delete_intake():
    3355                  delete_substance_intake(pk_intake = 1, delete_siblings = True)
    3356
    3357          #--------------------------------------------------------
    3358          # generic
    3359          #test_drug2renal_insufficiency_url()
    3360          #test_interaction_check()
    3361          #test_medically_formatted_start_end()
    3362
    3363          #test_get_substances()
    3364          #test_get_doses()
    3365          #test_get_components()
    3366          #test_get_drugs()
    3367          #test_get_intakes()
    3368          #test_create_substance_intake()
    3369          test_delete_intake()
    3370
    3371          #test_get_habit_drugs()
    3372
    3373          # AMTS
    3374          #test_generate_amts_data_template_definition_file()
    3375          #test_format_substance_intake_as_amts_data()
    3376