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