1
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
51
52 URL_drug_adr_german_default = 'https://nebenwirkungen.pei.de'
53
54
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
113
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
123
124 _SQL_get_substance = "SELECT * FROM ref.v_substances WHERE %s"
125
126 -class cSubstance(gmBusinessDBObject.cBusinessDBObject):
127
128 _cmd_fetch_payload = _SQL_get_substance % "pk_substance = %s"
129 _cmds_store_payload = [
130 """UPDATE ref.substance SET
131 description = %(substance)s,
132 atc = gm.nullify_empty_string(%(atc)s),
133 intake_instructions = gm.nullify_empty_string(%(intake_instructions)s)
134 WHERE
135 pk = %(pk_substance)s
136 AND
137 xmin = %(xmin_substance)s
138 RETURNING
139 xmin AS xmin_substance
140 """
141 ]
142 _updatable_fields = [
143 'substance',
144 'atc',
145 'intake_instructions'
146 ]
147
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
197
198
199
200
202 args = {'pk_subst': self.pk_obj, 'loincs': tuple(loincs)}
203
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
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
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
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
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
406
407
413
414
415
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
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
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
558
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
576
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
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
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
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
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
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
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
953
954
956
957
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
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
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
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
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
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
1105
1106
1129
1130
1147
1148
1149
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
1218
1219
1221 return substance_intake_exists (
1222 pk_component = self._payload[self._idx['pk_component']],
1223 pk_identity = pk_patient
1224 )
1225
1226
1233
1234
1235
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
1244 return self._payload[self._idx['is_in_use']]
1245
1246 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
1247
1248
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
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
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
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
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
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
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
1645
1647 return self._payload[self._idx['external_code']]
1648
1649 external_code = property(_get_external_code, lambda x:x)
1650
1651
1653
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
1693 return self._payload[self._idx['is_fake_product']]
1694
1695 is_fake_product = property(_get_is_fake_product, lambda x:x)
1696
1697
1699 return self._payload[self._idx['is_vaccine']]
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
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
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
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
1844
1845 _SQL_get_substance_intake = "SELECT * FROM clin.v_substance_intakes WHERE %s"
1846
1847 -class cSubstanceIntakeEntry(gmBusinessDBObject.cBusinessDBObject):
1848 """Represents a substance currently taken by a patient."""
1849
1850 _cmd_fetch_payload = _SQL_get_substance_intake % 'pk_substance_intake = %s'
1851 _cmds_store_payload = [
1852 """UPDATE clin.substance_intake SET
1853 -- if .comment_on_start = '?' then .started will be mapped to NULL
1854 -- in the view, also, .started CANNOT be NULL any other way so far,
1855 -- so do not attempt to set .clin_when if .started is NULL
1856 clin_when = (
1857 CASE
1858 WHEN %(started)s IS NULL THEN clin_when
1859 ELSE %(started)s
1860 END
1861 )::timestamp with time zone,
1862 comment_on_start = gm.nullify_empty_string(%(comment_on_start)s),
1863 discontinued = %(discontinued)s,
1864 discontinue_reason = gm.nullify_empty_string(%(discontinue_reason)s),
1865 schedule = gm.nullify_empty_string(%(schedule)s),
1866 aim = gm.nullify_empty_string(%(aim)s),
1867 narrative = gm.nullify_empty_string(%(notes)s),
1868 intake_is_approved_of = %(intake_is_approved_of)s,
1869 harmful_use_type = %(harmful_use_type)s,
1870 fk_episode = %(pk_episode)s,
1871 -- only used to document "last checked" such that
1872 -- .clin_when -> .started does not have to change meaning
1873 fk_encounter = %(pk_encounter)s,
1874
1875 is_long_term = (
1876 case
1877 when (
1878 (%(is_long_term)s is False)
1879 and
1880 (%(duration)s is NULL)
1881 ) is True then null
1882 else %(is_long_term)s
1883 end
1884 )::boolean,
1885
1886 duration = (
1887 case
1888 when %(is_long_term)s is True then null
1889 else %(duration)s
1890 end
1891 )::interval
1892 WHERE
1893 pk = %(pk_substance_intake)s
1894 AND
1895 xmin = %(xmin_substance_intake)s
1896 RETURNING
1897 xmin as xmin_substance_intake
1898 """
1899 ]
1900 _updatable_fields = [
1901 'started',
1902 'comment_on_start',
1903 'discontinued',
1904 'discontinue_reason',
1905 'intake_is_approved_of',
1906 'schedule',
1907 'duration',
1908 'aim',
1909 'is_long_term',
1910 'notes',
1911 'pk_episode',
1912 'pk_encounter',
1913 'harmful_use_type'
1914 ]
1915
1916
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):
1947
1948
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
1991
1992 txt = ''
1993 if include_metadata:
1994 txt = _('Substance abuse entry [#%s]\n') % self._payload[self._idx['pk_substance_intake']]
1995 txt += ' ' + _('Substance: %s [#%s]%s\n') % (
1996 self._payload[self._idx['substance']],
1997 self._payload[self._idx['pk_substance']],
1998 gmTools.coalesce(self._payload[self._idx['atc_substance']], '', ' ATC %s')
1999 )
2000 txt += ' ' + _('Use type: %s\n') % self.harmful_use_type_string
2001 txt += ' ' + _('Last checked: %s\n') % gmDateTime.pydt_strftime(self._payload[self._idx['last_checked_when']], '%Y %b %d')
2002 if self._payload[self._idx['discontinued']] is not None:
2003 txt += _(' Discontinued %s\n') % (
2004 gmDateTime.pydt_strftime (
2005 self._payload[self._idx['discontinued']],
2006 format = date_format,
2007 accuracy = gmDateTime.acc_days
2008 )
2009 )
2010 txt += gmTools.coalesce(self._payload[self._idx['notes']], '', _(' Notes: %s\n'))
2011 if include_metadata:
2012 txt += '\n'
2013 txt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % {
2014 'row_ver': self._payload[self._idx['row_version']],
2015 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']]),
2016 'mod_by': self._payload[self._idx['modified_by']]
2017 }
2018
2019 return txt
2020
2021
2022 - def format_as_multiple_lines(self, left_margin=0, date_format='%Y %b %d', allergy=None, show_all_product_components=False, include_instructions=False, include_loincs=False):
2023
2024 txt = _('Substance intake entry (%s, %s) [#%s] \n') % (
2025 gmTools.bool2subst (
2026 boolean = self._payload[self._idx['is_currently_active']],
2027 true_return = gmTools.bool2subst (
2028 boolean = self._payload[self._idx['seems_inactive']],
2029 true_return = _('active, needs check'),
2030 false_return = _('active'),
2031 none_return = _('assumed active')
2032 ),
2033 false_return = _('inactive')
2034 ),
2035 gmTools.bool2subst (
2036 boolean = self._payload[self._idx['intake_is_approved_of']],
2037 true_return = _('approved'),
2038 false_return = _('unapproved')
2039 ),
2040 self._payload[self._idx['pk_substance_intake']]
2041 )
2042
2043 if allergy is not None:
2044 certainty = gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'))
2045 txt += '\n'
2046 txt += ' !! ---- Cave ---- !!\n'
2047 txt += ' %s (%s): %s (%s)\n' % (
2048 allergy['l10n_type'],
2049 certainty,
2050 allergy['descriptor'],
2051 gmTools.coalesce(allergy['reaction'], '')[:40]
2052 )
2053 txt += '\n'
2054
2055 txt += ' ' + _('Substance: %s [#%s]\n') % (self._payload[self._idx['substance']], self._payload[self._idx['pk_substance']])
2056 txt += ' ' + _('Preparation: %s\n') % self._payload[self._idx['l10n_preparation']]
2057 txt += ' ' + _('Amount per dose: %s %s') % (
2058 self._payload[self._idx['amount']],
2059 self._get_formatted_units(short = False)
2060 )
2061 txt += '\n'
2062 txt += gmTools.coalesce(self._payload[self._idx['atc_substance']], '', _(' ATC (substance): %s\n'))
2063 if include_loincs and (len(self._payload[self._idx['loincs']]) > 0):
2064 loincs = """
2065 %s %s
2066 %s %s""" % (
2067 (' ' * left_margin),
2068 _('LOINCs to monitor:'),
2069 (' ' * left_margin),
2070 ('\n' + (' ' * (left_margin + 1))).join ([
2071 '%s%s%s' % (
2072 l['loinc'],
2073 gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')),
2074 gmTools.coalesce(l['comment'], '', ' (%s)')
2075 ) for l in self._payload[self._idx['loincs']]
2076 ])
2077 )
2078 txt += '\n'
2079
2080 txt += '\n'
2081
2082 txt += _(' Product name: %s [#%s]\n') % (self._payload[self._idx['product']], self._payload[self._idx['pk_drug_product']])
2083 txt += gmTools.coalesce(self._payload[self._idx['atc_drug']], '', _(' ATC (drug): %s\n'))
2084 if show_all_product_components:
2085 product = self.containing_drug
2086 if len(product['components']) > 1:
2087 for comp in product['components']:
2088 if comp['pk_substance'] == self._payload[self._idx['substance']]:
2089 continue
2090 txt += (' ' + _('Other component: %s %s %s\n') % (
2091 comp['substance'],
2092 comp['amount'],
2093 format_units(comp['unit'], comp['dose_unit'])
2094 ))
2095 txt += gmTools.coalesce(comp['intake_instructions'], '', ' ' + _('Intake: %s') + '\n')
2096 if include_loincs and (len(comp['loincs']) > 0):
2097 txt += (' ' + _('LOINCs to monitor:') + '\n')
2098 txt += '\n'.join([ ' %s%s%s' % (
2099 l['loinc'],
2100 gmTools.coalesce(l['max_age_str'], '', ': %s'),
2101 gmTools.coalesce(l['comment'], '', ' (%s)')
2102 ) for l in comp['loincs'] ])
2103
2104 txt += '\n'
2105
2106 txt += gmTools.coalesce(self._payload[self._idx['schedule']], '', _(' Regimen: %s\n'))
2107
2108 if self._payload[self._idx['is_long_term']]:
2109 duration = ' %s %s' % (gmTools.u_arrow2right, gmTools.u_infinity)
2110 else:
2111 if self._payload[self._idx['duration']] is None:
2112 duration = ''
2113 else:
2114 duration = ' %s %s' % (gmTools.u_arrow2right, gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days))
2115
2116 txt += _(' Started %s%s%s\n') % (
2117 self.medically_formatted_start,
2118 duration,
2119 gmTools.bool2subst(self._payload[self._idx['is_long_term']], _(' (long-term)'), _(' (short-term)'), '')
2120 )
2121
2122 if self._payload[self._idx['discontinued']] is not None:
2123 txt += _(' Discontinued %s\n') % (
2124 gmDateTime.pydt_strftime (
2125 self._payload[self._idx['discontinued']],
2126 format = date_format,
2127 accuracy = gmDateTime.acc_days
2128 )
2129 )
2130 txt += gmTools.coalesce(self._payload[self._idx['discontinue_reason']], '', _(' Reason: %s\n'))
2131
2132 txt += '\n'
2133
2134 txt += gmTools.coalesce(self._payload[self._idx['aim']], '', _(' Aim: %s\n'))
2135 txt += gmTools.coalesce(self._payload[self._idx['episode']], '', _(' Episode: %s\n'))
2136 txt += gmTools.coalesce(self._payload[self._idx['health_issue']], '', _(' Health issue: %s\n'))
2137 txt += gmTools.coalesce(self._payload[self._idx['notes']], '', _(' Advice: %s\n'))
2138 if self._payload[self._idx['intake_instructions']] is not None:
2139 txt += (' '+ (_('Intake: %s') % self._payload[self._idx['intake_instructions']]) + '\n')
2140 if len(self._payload[self._idx['loincs']]) > 0:
2141 txt += (' ' + _('LOINCs to monitor:') + '\n')
2142 txt += '\n'.join([ ' %s%s%s' % (
2143 l['loinc'],
2144 gmTools.coalesce(l['max_age_str'], '', ': %s'),
2145 gmTools.coalesce(l['comment'], '', ' (%s)')
2146 ) for l in self._payload[self._idx['loincs']] ])
2147
2148 txt += '\n'
2149
2150 txt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % {
2151 'row_ver': self._payload[self._idx['row_version']],
2152 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']]),
2153 'mod_by': self._payload[self._idx['modified_by']]
2154 }
2155
2156 return txt
2157
2158
2159 - def turn_into_allergy(self, encounter_id=None, allergy_type='allergy'):
2160 allg = gmAllergy.create_allergy (
2161 allergene = self._payload[self._idx['substance']],
2162 allg_type = allergy_type,
2163 episode_id = self._payload[self._idx['pk_episode']],
2164 encounter_id = encounter_id
2165 )
2166 allg['substance'] = gmTools.coalesce (
2167 self._payload[self._idx['product']],
2168 self._payload[self._idx['substance']]
2169 )
2170 allg['reaction'] = self._payload[self._idx['discontinue_reason']]
2171 allg['atc_code'] = gmTools.coalesce(self._payload[self._idx['atc_substance']], self._payload[self._idx['atc_drug']])
2172 if self._payload[self._idx['external_code_product']] is not None:
2173 allg['substance_code'] = '%s::::%s' % (self._payload[self._idx['external_code_type_product']], self._payload[self._idx['external_code_product']])
2174
2175 if self._payload[self._idx['pk_drug_product']] is None:
2176 allg['generics'] = self._payload[self._idx['substance']]
2177 else:
2178 comps = [ c['substance'] for c in self.containing_drug.components ]
2179 if len(comps) == 0:
2180 allg['generics'] = self._payload[self._idx['substance']]
2181 else:
2182 allg['generics'] = '; '.join(comps)
2183
2184 allg.save()
2185 return allg
2186
2187
2189 return delete_substance_intake(pk_intake = self._payload[self._idx['pk_substance_intake']])
2190
2191
2192
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
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
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
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
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
2332 end = gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%Y %b %d', 'utf8', gmDateTime.acc_days)
2333
2334
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
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
2357 arrow_parts = []
2358
2359
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
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
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
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
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
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
2496 return (' %s ' % gmTools.u_arrow2right_thick).join(arrow_parts)
2497
2498 medically_formatted_start_end = property(_get_medically_formatted_start_end, lambda x:x)
2499
2500
2501 - def _get_as_amts_latex(self, strict=True):
2502 return format_substance_intake_as_amts_latex(intake = self, strict=strict)
2503
2504 as_amts_latex = property(_get_as_amts_latex, lambda x:x)
2505
2506
2507 - def _get_as_amts_data(self, strict=True):
2508 return format_substance_intake_as_amts_data(intake = self, strict = strict)
2509
2510 as_amts_data = property(_get_as_amts_data, lambda x:x)
2511
2512
2514 tests = [
2515
2516 ' 1-1-1-1 ',
2517
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
2538
2539
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
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
2682
2772
2773
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
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
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
2884
2924
2925
2927
2928
2929 first_chars = []
2930 for intake in intakes:
2931 first_chars.append(intake['product'][0])
2932
2933
2934 val_sum = 0
2935 for first_char in first_chars:
2936
2937 if first_char.isdigit():
2938 val_sum += (ord(first_char) + 7)
2939
2940
2941 if first_char.isalpha():
2942 val_sum += ord(first_char.upper())
2943
2944
2945
2946 tmp, remainder = divmod(val_sum, 36)
2947
2948 if remainder < 10:
2949 return '%s' % remainder
2950
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',
2962 'DE',
2963 'DE',
2964 '1',
2965 '$<today::%Y%m%d::8>$',
2966 '$<amts_page_idx::::1>$',
2967 '$<amts_total_pages::::1>$',
2968 '0',
2969
2970 '$<name::%(firstnames)s::45>$',
2971 '$<name::%(lastnames)s::45>$',
2972 '',
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>$',
2977 '$<praxis_comm::workphone::20>$',
2978 '$<praxis_comm::email::80>$',
2979
2980
2981 '264 Seite $<amts_total_pages::::1>$ unten',
2982 '',
2983 '',
2984
2985
2986 '$<amts_intakes_as_data::::9999999>$',
2987
2988 '$<amts_check_symbol::::1>$',
2989 '#@',
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',
3013 'DE',
3014 'DE',
3015 '1',
3016 '$<today::%Y%m%d::8>$',
3017 '1',
3018 '1',
3019 '0',
3020
3021 '$<name::%(firstnames)s::>$',
3022 '$<name::%(lastnames)s::>$',
3023 '',
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
3034 '264 Seite 1 unten',
3035 '',
3036 '',
3037
3038
3039 '$<amts_intakes_as_data_enhanced::::>$',
3040
3041 '$<amts_check_symbol::::1>$',
3042 '#@',
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
3062
3098
3099
3177
3178
3179
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}
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
3263
3264
3265
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
3277
3278 gmI18N.activate_locale()
3279
3280
3281
3282
3283
3291
3292
3300
3301
3302
3303
3304
3305
3306
3307
3308
3316
3317
3325
3326
3338
3339
3345
3346
3351
3352
3355
3366
3367
3370
3371
3375
3376
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392 test_delete_intake()
3393
3394
3395
3396
3397
3398
3399