| Home | Trees | Indices | Help |
|
|---|
|
|
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 #============================================================
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 #============================================================
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
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
193 return substance_intake_exists (
194 pk_substance = self.pk_obj,
195 pk_identity = pk_patient
196 )
197
198 #--------------------------------------------------------
199 # properties
200 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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
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 #--------------------------------------------------------
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 #--------------------------------------------------------
413
414 #--------------------------------------------------------
415 # properties
416 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #------------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #------------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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
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 #--------------------------------------------------------
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 #--------------------------------------------------------
1221 return substance_intake_exists (
1222 pk_component = self._payload[self._idx['pk_component']],
1223 pk_identity = pk_patient
1224 )
1225
1226 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
1245
1246 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
1247
1248 #--------------------------------------------------------
1250 return cSubstanceDose(aPK_obj = self._payload[self._idx['pk_dose']])
1251
1252 substance_dose = property(_get_substance_dose, lambda x:x)
1253
1254 #--------------------------------------------------------
1256 return cSubstance(aPK_obj = self._payload[self._idx['pk_substance']])
1257
1258 substance = property(_get_substance, lambda x:x)
1259
1260 #--------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #------------------------------------------------------------
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 #--------------------------------------------------------
1607 return substance_intake_exists (
1608 pk_drug_product = self._payload[self._idx['pk_drug_product']],
1609 pk_identity = pk_patient
1610 )
1611
1612 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
1648
1649 external_code = property(_get_external_code, lambda x:x)
1650
1651 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
1694
1695 is_fake_product = property(_get_is_fake_product, lambda x:x)
1696
1697 #--------------------------------------------------------
1700
1701 is_vaccine = property(_get_is_vaccine, lambda x:x)
1702
1703 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
2189 return delete_substance_intake(pk_intake = self._payload[self._idx['pk_substance_intake']])
2190
2191 #--------------------------------------------------------
2192 # properties
2193 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #------------------------------------------------------------
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 #--------------------------------------------------------
3285 drug = create_substance_intake (
3286 pk_component = 2,
3287 pk_encounter = 1,
3288 pk_episode = 1
3289 )
3290 print(drug)
3291
3292 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
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 #--------------------------------------------------------
3341 for i in get_substance_intakes():
3342 #print i
3343 print('------------------------------------------------')
3344 print('\n'.join(i.format_maximum_information()))
3345
3346 #--------------------------------------------------------
3348 print(get_tobacco().format())
3349 print(get_alcohol().format())
3350 print(get_other_drug(name = 'LSD').format())
3351
3352 #--------------------------------------------------------
3354 drug2renal_insufficiency_url(search_term = 'Metoprolol')
3355 #--------------------------------------------------------
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 #--------------------------------------------------------
3369 print('file:', generate_amts_data_template_definition_file(strict = True))
3370
3371 #--------------------------------------------------------
3373 #print format_substance_intake_as_amts_data(cSubstanceIntakeEntry(1))
3374 print(cSubstanceIntakeEntry(1).as_amts_data)
3375
3376 #--------------------------------------------------------
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
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Feb 29 02:55:27 2020 | http://epydoc.sourceforge.net |