1
2 """Medication handling code.
3
4 license: GPL v2 or later
5 """
6
7 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
8
9 import sys
10 import logging
11 import io
12 import uuid
13 import re as regex
14 import datetime as pydt
15
16
17 if __name__ == '__main__':
18 sys.path.insert(0, '../../')
19 from Gnumed.pycommon import gmI18N
20 gmI18N.activate_locale()
21 gmI18N.install_domain('gnumed')
22 from Gnumed.pycommon import gmBusinessDBObject
23 from Gnumed.pycommon import gmTools
24 from Gnumed.pycommon import gmPG2
25 from Gnumed.pycommon import gmDispatcher
26 from Gnumed.pycommon import gmMatchProvider
27 from Gnumed.pycommon import gmHooks
28 from Gnumed.pycommon import gmDateTime
29
30 from Gnumed.business import gmATC
31 from Gnumed.business import gmAllergy
32 from Gnumed.business import gmEMRStructItems
33
34
35 _log = logging.getLogger('gm.meds')
36
37
38
39 DEFAULT_MEDICATION_HISTORY_EPISODE = _('Medication history')
40
41 URL_renal_insufficiency = 'http://www.dosing.de'
42 URL_renal_insufficiency_search_template = 'http://www.google.com/search?hl=de&source=hp&q=site%%3Adosing.de+%s&btnG=Google-Suche'
43
44 URL_long_qt = 'https://www.crediblemeds.org'
45
46
50
51 gmDispatcher.connect(_on_substance_intake_modified, 'clin.substance_intake_mod_db')
52
53
55
56 if search_term is None:
57 return URL_renal_insufficiency
58
59 if isinstance(search_term, str):
60 if search_term.strip() == '':
61 return URL_renal_insufficiency
62
63 terms = []
64 names = []
65
66 if isinstance(search_term, cDrugProduct):
67 if search_term['atc'] is not None:
68 terms.append(search_term['atc'])
69
70 elif isinstance(search_term, cSubstanceIntakeEntry):
71 names.append(search_term['substance'])
72 if search_term['atc_drug'] is not None:
73 terms.append(search_term['atc_drug'])
74 if search_term['atc_substance'] is not None:
75 terms.append(search_term['atc_substance'])
76
77 elif isinstance(search_term, cDrugComponent):
78 names.append(search_term['substance'])
79 if search_term['atc_drug'] is not None:
80 terms.append(search_term['atc_drug'])
81 if search_term['atc_substance'] is not None:
82 terms.append(search_term['atc_substance'])
83
84 elif isinstance(search_term, cSubstance):
85 names.append(search_term['substance'])
86 if search_term['atc'] is not None:
87 terms.append(search_term['atc'])
88
89 elif isinstance(search_term, cSubstanceDose):
90 names.append(search_term['substance'])
91 if search_term['atc'] is not None:
92 terms.append(search_term['atc_substance'])
93
94 elif search_term is not None:
95 names.append('%s' % search_term)
96 terms.extend(gmATC.text2atc(text = '%s' % search_term, fuzzy = True))
97
98 for name in names:
99 if name.endswith('e'):
100 terms.append(name[:-1])
101 else:
102 terms.append(name)
103
104
105
106 url = URL_renal_insufficiency_search_template % '+OR+'.join(terms)
107
108 _log.debug('renal insufficiency URL: %s', url)
109
110 return url
111
112
113
114
115
116 _SQL_get_substance = "SELECT * FROM ref.v_substances WHERE %s"
117
118 -class cSubstance(gmBusinessDBObject.cBusinessDBObject):
119
120 _cmd_fetch_payload = _SQL_get_substance % "pk_substance = %s"
121 _cmds_store_payload = [
122 """UPDATE ref.substance SET
123 description = %(substance)s,
124 atc = gm.nullify_empty_string(%(atc)s),
125 intake_instructions = gm.nullify_empty_string(%(intake_instructions)s)
126 WHERE
127 pk = %(pk_substance)s
128 AND
129 xmin = %(xmin_substance)s
130 RETURNING
131 xmin AS xmin_substance
132 """
133 ]
134 _updatable_fields = [
135 'substance',
136 'atc',
137 'intake_instructions'
138 ]
139
165
166
168 success, data = super(self.__class__, self).save_payload(conn = conn)
169
170 if not success:
171 return (success, data)
172
173 if self._payload[self._idx['atc']] is not None:
174 atc = self._payload[self._idx['atc']].strip()
175 if atc != '':
176 gmATC.propagate_atc (
177 substance = self._payload[self._idx['substance']].strip(),
178 atc = atc
179 )
180
181 return (success, data)
182
183
189
190
191
192
194 args = {'pk_subst': self.pk_obj, 'loincs': tuple(loincs)}
195
196 for loinc in loincs:
197 cmd = """INSERT INTO ref.lnk_loinc2substance (fk_substance, loinc)
198 SELECT
199 %(pk_subst)s, %(loinc)s
200 WHERE NOT EXISTS (
201 SELECT 1 from ref.lnk_loinc2substance WHERE fk_substance = %(pk_subst)s AND loinc = %(loinc)s
202 )"""
203 args['loinc'] = loinc
204 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
205
206
207 cmd = """DELETE FROM ref.lnk_loinc2substance WHERE fk_substance = %(pk_subst)s AND loinc NOT IN %(loincs)s"""
208 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
209
210 loincs = property(lambda x:x, _set_loincs)
211
212
214 cmd = """
215 SELECT EXISTS (
216 SELECT 1
217 FROM clin.v_substance_intakes
218 WHERE pk_substance = %(pk)s
219 LIMIT 1
220 )"""
221 args = {'pk': self.pk_obj}
222
223 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
224 return rows[0][0]
225
226 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
227
228
230 cmd = """
231 SELECT EXISTS (
232 SELECT 1
233 FROM ref.v_drug_components
234 WHERE pk_substance = %(pk)s
235 LIMIT 1
236 )"""
237 args = {'pk': self.pk_obj}
238
239 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
240 return rows[0][0]
241
242 is_drug_component = property(_get_is_drug_component, lambda x:x)
243
244
246 if order_by is None:
247 order_by = 'true'
248 else:
249 order_by = 'true ORDER BY %s' % order_by
250 cmd = _SQL_get_substance % order_by
251 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
252 return [ cSubstance(row = {'data': r, 'idx': idx, 'pk_field': 'pk_substance'}) for r in rows ]
253
254
256 if atc is not None:
257 atc = atc.strip()
258
259 args = {
260 'desc': substance.strip(),
261 'atc': atc
262 }
263 cmd = "SELECT pk FROM ref.substance WHERE lower(description) = lower(%(desc)s)"
264 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
265
266 if len(rows) == 0:
267 cmd = """
268 INSERT INTO ref.substance (description, atc) VALUES (
269 %(desc)s,
270 coalesce (
271 gm.nullify_empty_string(%(atc)s),
272 (SELECT code FROM ref.atc WHERE term = %(desc)s LIMIT 1)
273 )
274 ) RETURNING pk"""
275 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
276
277 if atc is not None:
278 gmATC.propagate_atc(substance = substance.strip(), atc = atc)
279
280 return cSubstance(aPK_obj = rows[0]['pk'])
281
282
284
285 if atc is None:
286 raise ValueError('<atc> must be supplied')
287 atc = atc.strip()
288 if atc == '':
289 raise ValueError('<atc> cannot be empty: [%s]', atc)
290
291 queries = []
292 args = {
293 'desc': substance.strip(),
294 'atc': atc
295 }
296
297 cmd = "UPDATE ref.substance SET atc = %(atc)s WHERE lower(description) = lower(%(desc)s) AND atc IS NULL"
298 queries.append({'cmd': cmd, 'args': args})
299
300 cmd = """
301 INSERT INTO ref.substance (description, atc)
302 SELECT
303 %(desc)s,
304 %(atc)s
305 WHERE NOT EXISTS (
306 SELECT 1 FROM ref.substance WHERE atc = %(atc)s
307 )
308 RETURNING pk"""
309 queries.append({'cmd': cmd, 'args': args})
310 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data = True, get_col_idx = False)
311 if len(rows) == 0:
312 cmd = "SELECT pk FROM ref.substance WHERE atc = %(atc)s LIMIT 1"
313 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
314
315 return cSubstance(aPK_obj = rows[0]['pk'], link_obj = link_obj)
316
317
319 args = {'pk': pk_substance}
320 cmd = """
321 DELETE FROM ref.substance WHERE
322 pk = %(pk)s
323 AND
324 -- must not currently be used with a patient
325 NOT EXISTS (
326 SELECT 1 FROM clin.v_substance_intakes
327 WHERE pk_substance = %(pk)s
328 LIMIT 1
329 )
330 AND
331 -- must not currently have doses defined for it
332 NOT EXISTS (
333 SELECT 1 FROM ref.dose
334 WHERE fk_substance = %(pk)s
335 LIMIT 1
336 )
337 """
338 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
339 return True
340
341
342
343
344 _SQL_get_substance_dose = "SELECT * FROM ref.v_substance_doses WHERE %s"
345
347
348 _cmd_fetch_payload = _SQL_get_substance_dose % "pk_dose = %s"
349 _cmds_store_payload = [
350 """UPDATE ref.dose SET
351 amount = %(amount)s,
352 unit = %(unit)s,
353 dose_unit = gm.nullify_empty_string(%(dose_unit)s)
354 WHERE
355 pk = %(pk_dose)s
356 AND
357 xmin = %(xmin_dose)s
358 RETURNING
359 xmin as xmin_dose,
360 pk as pk_dose
361 """
362 ]
363 _updatable_fields = [
364 'amount',
365 'unit',
366 'dose_unit'
367 ]
368
369
396
397
403
404
405
406
408 cmd = """
409 SELECT EXISTS (
410 SELECT 1
411 FROM clin.v_substance_intakes
412 WHERE pk_dose = %(pk)s
413 LIMIT 1
414 )"""
415 args = {'pk': self.pk_obj}
416
417 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
418 return rows[0][0]
419
420 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
421
422
424 cmd = """
425 SELECT EXISTS (
426 SELECT 1
427 FROM ref.v_drug_components
428 WHERE pk_dose = %(pk)s
429 LIMIT 1
430 )"""
431 args = {'pk': self.pk_obj}
432 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
433 return rows[0][0]
434
435 is_drug_component = property(_get_is_drug_component, lambda x:x)
436
437
444
445 formatted_units = property(_get_formatted_units, lambda x:x)
446
447
449 if order_by is None:
450 order_by = 'true'
451 else:
452 order_by = 'true ORDER BY %s' % order_by
453 cmd = _SQL_get_substance_dose % order_by
454 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
455 return [ cSubstanceDose(row = {'data': r, 'idx': idx, 'pk_field': 'pk_dose'}) for r in rows ]
456
457
458 -def create_substance_dose(link_obj=None, pk_substance=None, substance=None, atc=None, amount=None, unit=None, dose_unit=None):
459
460 if [pk_substance, substance].count(None) != 1:
461 raise ValueError('exctly one of <pk_substance> and <substance> must be None')
462
463 converted, amount = gmTools.input2decimal(amount)
464 if not converted:
465 raise ValueError('<amount> must be a number: %s (is: %s)', amount, type(amount))
466
467 if pk_substance is None:
468 pk_substance = create_substance(link_obj = link_obj, substance = substance, atc = atc)['pk_substance']
469
470 args = {
471 'pk_subst': pk_substance,
472 'amount': amount,
473 'unit': unit.strip(),
474 'dose_unit': dose_unit
475 }
476 cmd = """
477 SELECT pk FROM ref.dose
478 WHERE
479 fk_substance = %(pk_subst)s
480 AND
481 amount = %(amount)s
482 AND
483 unit = %(unit)s
484 AND
485 dose_unit IS NOT DISTINCT FROM gm.nullify_empty_string(%(dose_unit)s)
486 """
487 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
488
489 if len(rows) == 0:
490 cmd = """
491 INSERT INTO ref.dose (fk_substance, amount, unit, dose_unit) VALUES (
492 %(pk_subst)s,
493 %(amount)s,
494 gm.nullify_empty_string(%(unit)s),
495 gm.nullify_empty_string(%(dose_unit)s)
496 ) RETURNING pk"""
497 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
498
499 return cSubstanceDose(aPK_obj = rows[0]['pk'], link_obj = link_obj)
500
501
515
516
518 args = {'pk_dose': pk_dose}
519 cmd = """
520 DELETE FROM ref.dose WHERE
521 pk = %(pk_dose)s
522 AND
523 -- must not currently be used with a patient
524 NOT EXISTS (
525 SELECT 1 FROM clin.v_substance_intakes
526 WHERE pk_dose = %(pk_dose)s
527 LIMIT 1
528 )
529 AND
530 -- must not currently be linked to a drug
531 NOT EXISTS (
532 SELECT 1 FROM ref.lnk_dose2drug
533 WHERE fk_dose = %(pk_dose)s
534 LIMIT 1
535 )
536 """
537 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
538 return True
539
540
542
543 _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE)
544
545
546
547 _normal_query = """
548 SELECT
549 r_vsd.pk_dose
550 AS data,
551 (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, ''))
552 AS field_label,
553 (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, ''))
554 AS list_label
555 FROM
556 ref.v_substance_doses r_vsd
557 WHERE
558 r_vsd.substance %%(fragment_condition)s
559 ORDER BY
560 list_label
561 LIMIT 50"""
562
563
564
565 _regex_query = """
566 SELECT
567 r_vsd.pk_dose
568 AS data,
569 (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, ''))
570 AS field_label,
571 (r_vsd.substance || ' ' || r_vsd.amount || ' ' || r_vsd.unit || coalesce(' / ' r_vsd.dose_unit ||, ''))
572 AS list_label
573 FROM
574 ref.v_substance_doses r_vsd
575 WHERE
576 %%(fragment_condition)s
577 ORDER BY
578 list_label
579 LIMIT 50"""
580
581
583 """Return matches for aFragment at start of phrases."""
584
585 if cSubstanceMatchProvider._pattern.match(aFragment):
586 self._queries = [cSubstanceMatchProvider._regex_query]
587 fragment_condition = """substance ILIKE %(subst)s
588 AND
589 amount::text ILIKE %(amount)s"""
590 self._args['subst'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
591 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
592 else:
593 self._queries = [cSubstanceMatchProvider._normal_query]
594 fragment_condition = "ILIKE %(fragment)s"
595 self._args['fragment'] = "%s%%" % aFragment
596
597 return self._find_matches(fragment_condition)
598
599
601 """Return matches for aFragment at start of words inside phrases."""
602
603 if cSubstanceMatchProvider._pattern.match(aFragment):
604 self._queries = [cSubstanceMatchProvider._regex_query]
605
606 subst = regex.sub(r'\s*\d+$', '', aFragment)
607 subst = gmPG2.sanitize_pg_regex(expression = subst, escape_all = False)
608
609 fragment_condition = """substance ~* %(subst)s
610 AND
611 amount::text ILIKE %(amount)s"""
612
613 self._args['subst'] = "( %s)|(^%s)" % (subst, subst)
614 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
615 else:
616 self._queries = [cSubstanceMatchProvider._normal_query]
617 fragment_condition = "~* %(fragment)s"
618 aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False)
619 self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment)
620
621 return self._find_matches(fragment_condition)
622
623
625 """Return matches for aFragment as a true substring."""
626
627 if cSubstanceMatchProvider._pattern.match(aFragment):
628 self._queries = [cSubstanceMatchProvider._regex_query]
629 fragment_condition = """substance ILIKE %(subst)s
630 AND
631 amount::text ILIKE %(amount)s"""
632 self._args['subst'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
633 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
634 else:
635 self._queries = [cSubstanceMatchProvider._normal_query]
636 fragment_condition = "ILIKE %(fragment)s"
637 self._args['fragment'] = "%%%s%%" % aFragment
638
639 return self._find_matches(fragment_condition)
640
641
642
644
645
646 _query_drug_product_by_name = """
647 SELECT
648 ARRAY[1, pk]::INTEGER[]
649 AS data,
650 (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', ''))
651 AS list_label,
652 (description || ' (' || preparation || ')' || coalesce(' [' || atc_code || ']', ''))
653 AS field_label,
654 1 AS rank
655 FROM ref.drug_product
656 WHERE description %(fragment_condition)s
657 LIMIT 50
658 """
659 _query_drug_product_by_name_and_strength = """
660 SELECT
661 ARRAY[1, pk_drug_product]::INTEGER[]
662 AS data,
663 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
664 AS list_label,
665 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
666 AS field_label,
667 1 AS rank
668 FROM
669 (SELECT *, product AS description FROM ref.v_drug_components) AS _components
670 WHERE %%(fragment_condition)s
671 LIMIT 50
672 """ % (
673 _('w/'),
674 _('w/')
675 )
676
677
678
679
680
681
682
683
684
685
686
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 _query_substance_by_name = """
742 SELECT
743 data,
744 field_label,
745 list_label,
746 rank
747 FROM ((
748 -- first: substance intakes which match, because we tend to reuse them often
749 SELECT
750 ARRAY[2, pk_substance]::INTEGER[] AS data,
751 (description || ' ' || amount || unit || coalesce('/' || dose_unit, '')) AS field_label,
752 (description || ' ' || amount || unit || coalesce('/' || dose_unit, '') || ' (%s)') AS list_label,
753 1 AS rank
754 FROM (
755 SELECT DISTINCT ON (description, amount, unit, dose_unit)
756 pk_substance,
757 substance AS description,
758 amount,
759 unit,
760 dose_unit
761 FROM clin.v_substance_intakes
762 ) AS normalized_intakes
763 WHERE description %%(fragment_condition)s
764
765 ) UNION ALL (
766 xxxxxxxxxxxxxxxxxxxxxxxxxxxx
767 -- second: consumable substances which match but are not intakes
768 SELECT
769 ARRAY[2, pk]::INTEGER[] AS data,
770 (description || ' ' || amount || ' ' || unit) AS field_label,
771 (description || ' ' || amount || ' ' || unit) AS list_label,
772 2 AS rank
773 FROM ref.consumable_substance
774 WHERE
775 description %%(fragment_condition)s
776 AND
777 pk NOT IN (
778 SELECT fk_substance
779 FROM clin.substance_intake
780 WHERE fk_substance IS NOT NULL
781 )
782 )) AS candidates
783 --ORDER BY rank, list_label
784 LIMIT 50""" % _('in use')
785
786 _query_substance_by_name_and_strength = """
787 SELECT
788 data,
789 field_label,
790 list_label,
791 rank
792 FROM ((
793 SELECT
794 ARRAY[2, pk_substance]::INTEGER[] AS data,
795 (description || ' ' || amount || ' ' || unit) AS field_label,
796 (description || ' ' || amount || ' ' || unit || ' (%s)') AS list_label,
797 1 AS rank
798 FROM (
799 SELECT DISTINCT ON (description, amount, unit)
800 pk_substance,
801 substance AS description,
802 amount,
803 unit
804 FROM clin.v_nonbraXXXnd_intakes
805 ) AS normalized_intakes
806 WHERE
807 %%(fragment_condition)s
808
809 ) UNION ALL (
810
811 -- matching substances which are not in intakes
812 SELECT
813 ARRAY[2, pk]::INTEGER[] AS data,
814 (description || ' ' || amount || ' ' || unit) AS field_label,
815 (description || ' ' || amount || ' ' || unit) AS list_label,
816 2 AS rank
817 FROM ref.consumable_substance
818 WHERE
819 %%(fragment_condition)s
820 AND
821 pk NOT IN (
822 SELECT fk_substance
823 FROM clin.substance_intake
824 WHERE fk_substance IS NOT NULL
825 )
826 )) AS candidates
827 --ORDER BY rank, list_label
828 LIMIT 50""" % _('in use')
829
830 _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE)
831
832 _master_query = """
833 SELECT
834 data, field_label, list_label, rank
835 FROM ((%s) UNION (%s) UNION (%s))
836 AS _union
837 ORDER BY rank, list_label
838 LIMIT 50
839 """
840
871
872
874 """Return matches for aFragment at start of words inside phrases."""
875
876 if cProductOrSubstanceMatchProvider._pattern.match(aFragment):
877 self._queries = [
878 cProductOrSubstanceMatchProvider._master_query % (
879 cProductOrSubstanceMatchProvider._query_drug_product_by_name_and_strength,
880 cProductOrSubstanceMatchProvider._query_substance_by_name_and_strength,
881 cProductOrSubstanceMatchProvider._query_component_by_name_and_strength
882 )
883 ]
884
885
886 desc = regex.sub(r'\s*\d+$', '', aFragment)
887 desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False)
888
889 fragment_condition = """description ~* %(desc)s
890 AND
891 amount::text ILIKE %(amount)s"""
892
893 self._args['desc'] = "( %s)|(^%s)" % (desc, desc)
894 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
895 else:
896 self._queries = [
897 cProductOrSubstanceMatchProvider._master_query % (
898 cProductOrSubstanceMatchProvider._query_drug_product_by_name,
899 cProductOrSubstanceMatchProvider._query_substance_by_name,
900 cProductOrSubstanceMatchProvider._query_component_by_name
901 )
902 ]
903
904 fragment_condition = "~* %(fragment)s"
905 aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False)
906 self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment)
907
908 return self._find_matches(fragment_condition)
909
910
941
942
944
945
946 _SQL_drug_product_by_name = """
947 SELECT
948 pk_drug_product
949 AS data,
950 (product || ' (' || preparation || ')' || coalesce(' [' || atc || ']', ''))
951 AS list_label,
952 (product || ' (' || preparation || ')' || coalesce(' [' || atc || ']', ''))
953 AS field_label
954 FROM ref.v_drug_products
955 WHERE
956 is_vaccine IS FALSE
957 AND
958 product %(fragment_condition)s
959 LIMIT 50
960 """
961
962 _SQL_drug_product_by_component_name = """
963 SELECT
964 pk_drug_product
965 AS data,
966 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
967 AS list_label,
968 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
969 AS field_label
970 FROM
971 ref.v_drug_components
972 WHERE substance %%(fragment_condition)s
973 LIMIT 50
974 """ % (
975 _('w/'),
976 _('w/')
977 )
978
979 _SQL_drug_product_by_name_and_strength = """
980 SELECT
981 pk_drug_product
982 AS data,
983 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
984 AS list_label,
985 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
986 AS field_label
987 FROM
988 (SELECT *, product AS description FROM ref.v_drug_components) AS _components
989 WHERE %%(fragment_condition)s
990 LIMIT 50
991 """ % (
992 _('w/'),
993 _('w/')
994 )
995
996 _SQL_drug_product_by_component_name_and_strength = """
997 SELECT
998 pk_drug_product
999 AS data,
1000 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
1001 AS list_label,
1002 (product || ' (' || preparation || ' %s ' || amount || unit || coalesce('/' || dose_unit, '') || ' ' || substance || ')' || coalesce(' [' || atc_drug || ']', ''))
1003 AS field_label
1004 FROM
1005 (SELECT *, substance AS description FROM ref.v_drug_components) AS _components
1006 WHERE %%(fragment_condition)s
1007 LIMIT 50
1008 """ % (
1009 _('w/'),
1010 _('w/')
1011 )
1012
1013 _SQL_substance_name = """
1014 SELECT DISTINCT ON (field_label)
1015 data, list_label, field_label
1016 FROM (
1017 SELECT DISTINCT ON (term)
1018 NULL::integer
1019 AS data,
1020 term || ' (ATC: ' || code || ')'
1021 AS list_label,
1022 term
1023 AS field_label
1024 FROM
1025 ref.atc
1026 WHERE
1027 lower(term) %(fragment_condition)s
1028
1029 UNION ALL
1030
1031 SELECT DISTINCT ON (description)
1032 NULL::integer
1033 AS data,
1034 description || coalesce(' (ATC: ' || atc || ')', '')
1035 AS list_label,
1036 description
1037 AS field_label
1038 FROM
1039 ref.substance
1040 WHERE
1041 lower(description) %(fragment_condition)s
1042 ) AS nondrug_substances
1043 WHERE NOT EXISTS (
1044 SELECT 1 FROM ref.v_drug_components WHERE lower(substance) = lower(nondrug_substances.field_label)
1045 )
1046 LIMIT 30
1047 """
1048
1049
1050 _SQL_regex_master_query = """
1051 SELECT
1052 data, field_label, list_label
1053 FROM ((%s) UNION (%s))
1054 AS _union
1055 ORDER BY list_label
1056 LIMIT 50
1057 """ % (
1058 _SQL_drug_product_by_name_and_strength,
1059 _SQL_drug_product_by_component_name_and_strength
1060 )
1061 _SQL_nonregex_master_query = """
1062 SELECT
1063 data, field_label, list_label
1064 FROM ((%s) UNION (%s) UNION (%s))
1065 AS _union
1066 ORDER BY list_label
1067 LIMIT 50
1068 """ % (
1069 _SQL_drug_product_by_name,
1070 _SQL_drug_product_by_component_name,
1071 _SQL_substance_name
1072 )
1073
1074 _REGEX_name_and_strength = regex.compile(r'^\D+\s*\d+$', regex.UNICODE)
1075
1076
1093
1094
1117
1118
1135
1136
1137
1138
1139 _SQL_get_drug_components = 'SELECT * FROM ref.v_drug_components WHERE %s'
1140
1142
1143 _cmd_fetch_payload = _SQL_get_drug_components % 'pk_component = %s'
1144 _cmds_store_payload = [
1145 """UPDATE ref.lnk_dose2drug SET
1146 fk_drug_product = %(pk_drug_product)s,
1147 fk_dose = %(pk_dose)s
1148 WHERE
1149 pk = %(pk_component)s
1150 AND
1151 NOT EXISTS (
1152 SELECT 1
1153 FROM clin.substance_intake
1154 WHERE fk_drug_component = %(pk_component)s
1155 LIMIT 1
1156 )
1157 AND
1158 xmin = %(xmin_lnk_dose2drug)s
1159 RETURNING
1160 xmin AS xmin_lnk_dose2drug
1161 """
1162 ]
1163 _updatable_fields = [
1164 'pk_drug_product',
1165 'pk_dose'
1166 ]
1167
1206
1207
1209 return substance_intake_exists (
1210 pk_component = self._payload[self._idx['pk_component']],
1211 pk_identity = pk_patient
1212 )
1213
1214
1221
1222
1223
1224
1226 return cDrugProduct(aPK_obj = self._payload[self._idx['pk_drug_product']])
1227
1228 containing_drug = property(_get_containing_drug, lambda x:x)
1229
1230
1232 return self._payload[self._idx['is_in_use']]
1233
1234 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
1235
1236
1238 return cSubstanceDose(aPK_obj = self._payload[self._idx['pk_dose']])
1239
1240 substance_dose = property(_get_substance_dose, lambda x:x)
1241
1242
1244 return cSubstance(aPK_obj = self._payload[self._idx['pk_substance']])
1245
1246 substance = property(_get_substance, lambda x:x)
1247
1248
1255
1256 formatted_units = property(_get_formatted_units, lambda x:x)
1257
1258
1263
1264
1266
1267 _pattern = regex.compile(r'^\D+\s*\d+$', regex.UNICODE)
1268
1269 _query_desc_only = """
1270 SELECT DISTINCT ON (list_label)
1271 r_vdc1.pk_component
1272 AS data,
1273 (r_vdc1.substance || ' '
1274 || r_vdc1.amount || r_vdc1.unit || ' '
1275 || r_vdc1.preparation || ' ('
1276 || r_vdc1.product || ' ['
1277 || (
1278 SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
1279 FROM ref.v_drug_components r_vdc2
1280 WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
1281 )
1282 || ']'
1283 || ')'
1284 ) AS field_label,
1285 (r_vdc1.substance || ' '
1286 || r_vdc1.amount || r_vdc1.unit || ' '
1287 || r_vdc1.preparation || ' ('
1288 || r_vdc1.product || ' ['
1289 || (
1290 SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
1291 FROM ref.v_drug_components r_vdc2
1292 WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
1293 )
1294 || ']'
1295 || ')'
1296 ) AS list_label
1297 FROM ref.v_drug_components r_vdc1
1298 WHERE
1299 r_vdc1.substance %(fragment_condition)s
1300 OR
1301 r_vdc1.product %(fragment_condition)s
1302 ORDER BY list_label
1303 LIMIT 50"""
1304
1305 _query_desc_and_amount = """
1306 SELECT DISTINCT ON (list_label)
1307 pk_component AS data,
1308 (r_vdc1.substance || ' '
1309 || r_vdc1.amount || r_vdc1.unit || ' '
1310 || r_vdc1.preparation || ' ('
1311 || r_vdc1.product || ' ['
1312 || (
1313 SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
1314 FROM ref.v_drug_components r_vdc2
1315 WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
1316 )
1317 || ']'
1318 || ')'
1319 ) AS field_label,
1320 (r_vdc1.substance || ' '
1321 || r_vdc1.amount || r_vdc1.unit || ' '
1322 || r_vdc1.preparation || ' ('
1323 || r_vdc1.product || ' ['
1324 || (
1325 SELECT array_to_string(array_agg(r_vdc2.amount), ' / ')
1326 FROM ref.v_drug_components r_vdc2
1327 WHERE r_vdc2.pk_drug_product = r_vdc1.pk_drug_product
1328 )
1329 || ']'
1330 || ')'
1331 ) AS list_label
1332 FROM ref.v_drug_components
1333 WHERE
1334 %(fragment_condition)s
1335 ORDER BY list_label
1336 LIMIT 50"""
1337
1339 """Return matches for aFragment at start of phrases."""
1340
1341 if cDrugComponentMatchProvider._pattern.match(aFragment):
1342 self._queries = [cDrugComponentMatchProvider._query_desc_and_amount]
1343 fragment_condition = """(substance ILIKE %(desc)s OR product ILIKE %(desc)s)
1344 AND
1345 amount::text ILIKE %(amount)s"""
1346 self._args['desc'] = '%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
1347 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
1348 else:
1349 self._queries = [cDrugComponentMatchProvider._query_desc_only]
1350 fragment_condition = "ILIKE %(fragment)s"
1351 self._args['fragment'] = "%s%%" % aFragment
1352
1353 return self._find_matches(fragment_condition)
1354
1356 """Return matches for aFragment at start of words inside phrases."""
1357
1358 if cDrugComponentMatchProvider._pattern.match(aFragment):
1359 self._queries = [cDrugComponentMatchProvider._query_desc_and_amount]
1360
1361 desc = regex.sub(r'\s*\d+$', '', aFragment)
1362 desc = gmPG2.sanitize_pg_regex(expression = desc, escape_all = False)
1363
1364 fragment_condition = """(substance ~* %(desc)s OR product ~* %(desc)s)
1365 AND
1366 amount::text ILIKE %(amount)s"""
1367
1368 self._args['desc'] = "( %s)|(^%s)" % (desc, desc)
1369 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
1370 else:
1371 self._queries = [cDrugComponentMatchProvider._query_desc_only]
1372 fragment_condition = "~* %(fragment)s"
1373 aFragment = gmPG2.sanitize_pg_regex(expression = aFragment, escape_all = False)
1374 self._args['fragment'] = "( %s)|(^%s)" % (aFragment, aFragment)
1375
1376 return self._find_matches(fragment_condition)
1377
1379 """Return matches for aFragment as a true substring."""
1380
1381 if cDrugComponentMatchProvider._pattern.match(aFragment):
1382 self._queries = [cDrugComponentMatchProvider._query_desc_and_amount]
1383 fragment_condition = """(substance ILIKE %(desc)s OR product ILIKE %(desc)s)
1384 AND
1385 amount::text ILIKE %(amount)s"""
1386 self._args['desc'] = '%%%s%%' % regex.sub(r'\s*\d+$', '', aFragment)
1387 self._args['amount'] = '%s%%' % regex.sub(r'^\D+\s*', '', aFragment)
1388 else:
1389 self._queries = [cDrugComponentMatchProvider._query_desc_only]
1390 fragment_condition = "ILIKE %(fragment)s"
1391 self._args['fragment'] = "%%%s%%" % aFragment
1392
1393 return self._find_matches(fragment_condition)
1394
1395
1396
1397
1398 _SQL_get_drug_product = "SELECT * FROM ref.v_drug_products WHERE %s"
1399
1401 """Represents a drug as marketed by a manufacturer or a generic drug product."""
1402
1403 _cmd_fetch_payload = _SQL_get_drug_product % 'pk_drug_product = %s'
1404 _cmds_store_payload = [
1405 """UPDATE ref.drug_product SET
1406 description = %(product)s,
1407 preparation = %(preparation)s,
1408 atc_code = gm.nullify_empty_string(%(atc)s),
1409 external_code = gm.nullify_empty_string(%(external_code)s),
1410 external_code_type = gm.nullify_empty_string(%(external_code_type)s),
1411 is_fake = %(is_fake_product)s,
1412 fk_data_source = %(pk_data_source)s
1413 WHERE
1414 pk = %(pk_drug_product)s
1415 AND
1416 xmin = %(xmin_drug_product)s
1417 RETURNING
1418 xmin AS xmin_drug_product
1419 """
1420 ]
1421 _updatable_fields = [
1422 'product',
1423 'preparation',
1424 'atc',
1425 'is_fake_product',
1426 'external_code',
1427 'external_code_type',
1428 'pk_data_source'
1429 ]
1430
1466
1467
1469 success, data = super(self.__class__, self).save_payload(conn = conn)
1470
1471 if not success:
1472 return (success, data)
1473
1474 if self._payload[self._idx['atc']] is not None:
1475 atc = self._payload[self._idx['atc']].strip()
1476 if atc != '':
1477 gmATC.propagate_atc (
1478 link_obj = conn,
1479 substance = self._payload[self._idx['product']].strip(),
1480 atc = atc
1481 )
1482
1483 return (success, data)
1484
1485
1487 if self.is_in_use_by_patients:
1488 return False
1489
1490 pk_doses2keep = [ s['pk_dose'] for s in substance_doses ]
1491 _log.debug('setting components of "%s" from doses: %s', self._payload[self._idx['product']], pk_doses2keep)
1492
1493 args = {'pk_drug_product': self._payload[self._idx['pk_drug_product']]}
1494 queries = []
1495
1496 cmd = """
1497 INSERT INTO ref.lnk_dose2drug (
1498 fk_drug_product,
1499 fk_dose
1500 )
1501 SELECT
1502 %(pk_drug_product)s,
1503 %(pk_dose)s
1504 WHERE NOT EXISTS (
1505 SELECT 1 FROM ref.lnk_dose2drug
1506 WHERE
1507 fk_drug_product = %(pk_drug_product)s
1508 AND
1509 fk_dose = %(pk_dose)s
1510 )"""
1511 for pk_dose in pk_doses2keep:
1512 args['pk_dose'] = pk_dose
1513 queries.append({'cmd': cmd, 'args': args.copy()})
1514
1515
1516 args['doses2keep'] = tuple(pk_doses2keep)
1517 cmd = """
1518 DELETE FROM ref.lnk_dose2drug
1519 WHERE
1520 fk_drug_product = %(pk_drug_product)s
1521 AND
1522 fk_dose NOT IN %(doses2keep)s"""
1523 queries.append({'cmd': cmd, 'args': args})
1524 gmPG2.run_rw_queries(link_obj = link_obj, queries = queries)
1525 self.refetch_payload(link_obj = link_obj)
1526
1527 return True
1528
1529
1530 - def add_component(self, substance=None, atc=None, amount=None, unit=None, dose_unit=None, pk_dose=None, pk_substance=None):
1531
1532 if pk_dose is None:
1533 if pk_substance is None:
1534 pk_dose = create_substance_dose(substance = substance, atc = atc, amount = amount, unit = unit, dose_unit = dose_unit)['pk_dose']
1535 else:
1536 pk_dose = create_substance_dose(pk_substance = pk_substance, atc = atc, amount = amount, unit = unit, dose_unit = dose_unit)['pk_dose']
1537
1538 args = {
1539 'pk_dose': pk_dose,
1540 'pk_drug_product': self.pk_obj
1541 }
1542
1543 cmd = """
1544 INSERT INTO ref.lnk_dose2drug (fk_drug_product, fk_dose)
1545 SELECT
1546 %(pk_drug_product)s,
1547 %(pk_dose)s
1548 WHERE NOT EXISTS (
1549 SELECT 1 FROM ref.lnk_dose2drug
1550 WHERE
1551 fk_drug_product = %(pk_drug_product)s
1552 AND
1553 fk_dose = %(pk_dose)s
1554 )"""
1555 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1556 self.refetch_payload()
1557
1558
1560 if len(self._payload[self._idx['components']]) == 1:
1561 _log.error('will not remove the only component of a drug')
1562 return False
1563
1564 args = {'pk_drug_product': self.pk_obj, 'pk_dose': pk_dose, 'pk_component': pk_component}
1565
1566 if pk_component is None:
1567 cmd = """DELETE FROM ref.lnk_dose2drug WHERE
1568 fk_drug_product = %(pk_drug_product)s
1569 AND
1570 fk_dose = %(pk_dose)s
1571 AND
1572 NOT EXISTS (
1573 SELECT 1 FROM clin.v_substance_intakes
1574 WHERE pk_dose = %(pk_dose)s
1575 LIMIT 1
1576 )"""
1577 else:
1578 cmd = """DELETE FROM ref.lnk_dose2drug WHERE
1579 pk = %(pk_component)s
1580 AND
1581 NOT EXISTS (
1582 SELECT 1 FROM clin.substance_intake
1583 WHERE fk_drug_component = %(pk_component)s
1584 LIMIT 1
1585 )"""
1586
1587 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1588 self.refetch_payload()
1589 return True
1590
1591
1593 return substance_intake_exists (
1594 pk_drug_product = self._payload[self._idx['pk_drug_product']],
1595 pk_identity = pk_patient
1596 )
1597
1598
1605
1606
1608 if self._payload[self._idx['is_vaccine']] is False:
1609 return True
1610
1611 args = {'pk_product': self._payload[self._idx['pk_drug_product']]}
1612 cmd = """DELETE FROM ref.vaccine
1613 WHERE
1614 fk_drug_product = %(pk_product)s
1615 AND
1616 -- not in use:
1617 NOT EXISTS (
1618 SELECT 1 FROM clin.vaccination WHERE fk_vaccine = (
1619 select pk from ref.vaccine where fk_drug_product = %(pk_product)s
1620 )
1621 )
1622 RETURNING *"""
1623 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
1624 if len(rows) == 0:
1625 _log.debug('cannot delete vaccine on: %s', self)
1626 return False
1627 return True
1628
1629
1630
1631
1633 return self._payload[self._idx['external_code']]
1634
1635 external_code = property(_get_external_code, lambda x:x)
1636
1637
1639
1640 return self._payload[self._idx['external_code_type']]
1641
1642 external_code_type = property(_get_external_code_type, lambda x:x)
1643
1644
1646 cmd = _SQL_get_drug_components % 'pk_drug_product = %(product)s'
1647 args = {'product': self._payload[self._idx['pk_drug_product']]}
1648 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1649 return [ cDrugComponent(row = {'data': r, 'idx': idx, 'pk_field': 'pk_component'}) for r in rows ]
1650
1651 components = property(_get_components, lambda x:x)
1652
1653
1655 pk_doses = [ c['pk_dose'] for c in self._payload[self._idx['components']] ]
1656 if len(pk_doses) == 0:
1657 return []
1658 cmd = _SQL_get_substance_dose % 'pk_dose IN %(pks)s'
1659 args = {'pks': tuple(pk_doses)}
1660 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1661 return [ cSubstanceDose(row = {'data': r, 'idx': idx, 'pk_field': 'pk_dose'}) for r in rows ]
1662
1663 components_as_doses = property(_get_components_as_doses, lambda x:x)
1664
1665
1667 pk_substances = [ c['pk_substance'] for c in self._payload[self._idx['components']] ]
1668 if len(pk_substances) == 0:
1669 return []
1670 cmd = _SQL_get_substance % 'pk_substance IN %(pks)s'
1671 args = {'pks': tuple(pk_substances)}
1672 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1673 return [ cSubstance(row = {'data': r, 'idx': idx, 'pk_field': 'pk_substance'}) for r in rows ]
1674
1675 components_as_substances = property(_get_components_as_substances, lambda x:x)
1676
1677
1679 return self._payload[self._idx['is_fake_product']]
1680
1681 is_fake_product = property(_get_is_fake_product, lambda x:x)
1682
1683
1685 return self._payload[self._idx['is_vaccine']]
1686
1687 is_vaccine = property(_get_is_vaccine, lambda x:x)
1688
1689
1691 cmd = """
1692 SELECT EXISTS (
1693 SELECT 1 FROM clin.substance_intake WHERE
1694 fk_drug_component IN (
1695 SELECT pk FROM ref.lnk_dose2drug WHERE fk_drug_product = %(pk)s
1696 )
1697 LIMIT 1
1698 )"""
1699 args = {'pk': self.pk_obj}
1700 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1701 return rows[0][0]
1702
1703 is_in_use_by_patients = property(_get_is_in_use_by_patients, lambda x:x)
1704
1705
1707 if self._payload[self._idx['is_vaccine']] is False:
1708 return False
1709 cmd = 'SELECT EXISTS(SELECT 1 FROM clin.vaccination WHERE fk_vaccine = (select pk from ref.vaccine where fk_drug_product = %(pk)s))'
1710 args = {'pk': self.pk_obj}
1711 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1712 return rows[0][0]
1713
1714 is_in_use_as_vaccine = property(_get_is_in_use_as_vaccine, lambda x:x)
1715
1716
1721
1722
1724 args = {'prod_name': product_name, 'prep': preparation}
1725 cmd = 'SELECT * FROM ref.v_drug_products WHERE lower(product) = lower(%(prod_name)s) AND lower(preparation) = lower(%(prep)s)'
1726 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1727 if len(rows) == 0:
1728 return None
1729 return cDrugProduct(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_drug_product'})
1730
1731
1733 args = {'atc': atc, 'prep': preparation}
1734 cmd = 'SELECT * FROM ref.v_drug_products WHERE lower(atc) = lower(%(atc)s) AND lower(preparation) = lower(%(prep)s)'
1735 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1736 if len(rows) == 0:
1737 return None
1738 return cDrugProduct(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_drug_product'}, link_obj = link_obj)
1739
1740
1741 -def create_drug_product(product_name=None, preparation=None, return_existing=False, link_obj=None, doses=None):
1742
1743 if preparation is None:
1744 preparation = _('units')
1745
1746 if preparation.strip() == '':
1747 preparation = _('units')
1748
1749 if return_existing:
1750 drug = get_drug_by_name(product_name = product_name, preparation = preparation, link_obj = link_obj)
1751 if drug is not None:
1752 return drug
1753
1754 cmd = 'INSERT INTO ref.drug_product (description, preparation) VALUES (%(prod_name)s, %(prep)s) RETURNING pk'
1755 args = {'prod_name': product_name, 'prep': preparation}
1756 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
1757 product = cDrugProduct(aPK_obj = rows[0]['pk'], link_obj = link_obj)
1758 if doses is not None:
1759 product.set_substance_doses_as_components(substance_doses = doses, link_obj = link_obj)
1760
1761 return product
1762
1763
1765
1766 if atc is None:
1767 raise ValueError('cannot create drug product by ATC without ATC')
1768
1769 if preparation is None:
1770 preparation = _('units')
1771
1772 if preparation.strip() == '':
1773 preparation = _('units')
1774
1775 if return_existing:
1776 drug = get_drug_by_atc(atc = atc, preparation = preparation, link_obj = link_obj)
1777 if drug is not None:
1778 return drug
1779
1780 drug = create_drug_product (
1781 link_obj = link_obj,
1782 product_name = product_name,
1783 preparation = preparation,
1784 return_existing = False
1785 )
1786 drug['atc'] = atc
1787 drug.save(conn = link_obj)
1788 return drug
1789
1790
1792 args = {'pk': pk_drug_product}
1793 queries = []
1794
1795 cmd = """
1796 DELETE FROM ref.lnk_dose2drug
1797 WHERE
1798 fk_drug_product = %(pk)s
1799 AND
1800 NOT EXISTS (
1801 SELECT 1
1802 FROM clin.v_substance_intakes
1803 WHERE pk_drug_product = %(pk)s
1804 LIMIT 1
1805 )"""
1806 queries.append({'cmd': cmd, 'args': args})
1807
1808 cmd = """
1809 DELETE FROM ref.drug_product
1810 WHERE
1811 pk = %(pk)s
1812 AND
1813 NOT EXISTS (
1814 SELECT 1 FROM clin.v_substance_intakes
1815 WHERE pk_drug_product = %(pk)s
1816 LIMIT 1
1817 )"""
1818 queries.append({'cmd': cmd, 'args': args})
1819 gmPG2.run_rw_queries(queries = queries)
1820
1821
1822
1823
1824 _SQL_get_substance_intake = "SELECT * FROM clin.v_substance_intakes WHERE %s"
1825
1826 -class cSubstanceIntakeEntry(gmBusinessDBObject.cBusinessDBObject):
1827 """Represents a substance currently taken by a patient."""
1828
1829 _cmd_fetch_payload = _SQL_get_substance_intake % 'pk_substance_intake = %s'
1830 _cmds_store_payload = [
1831 """UPDATE clin.substance_intake SET
1832 -- if .comment_on_start = '?' then .started will be mapped to NULL
1833 -- in the view, also, .started CANNOT be NULL any other way so far,
1834 -- so do not attempt to set .clin_when if .started is NULL
1835 clin_when = (
1836 CASE
1837 WHEN %(started)s IS NULL THEN clin_when
1838 ELSE %(started)s
1839 END
1840 )::timestamp with time zone,
1841 comment_on_start = gm.nullify_empty_string(%(comment_on_start)s),
1842 discontinued = %(discontinued)s,
1843 discontinue_reason = gm.nullify_empty_string(%(discontinue_reason)s),
1844 schedule = gm.nullify_empty_string(%(schedule)s),
1845 aim = gm.nullify_empty_string(%(aim)s),
1846 narrative = gm.nullify_empty_string(%(notes)s),
1847 intake_is_approved_of = %(intake_is_approved_of)s,
1848 harmful_use_type = %(harmful_use_type)s,
1849 fk_episode = %(pk_episode)s,
1850 -- only used to document "last checked" such that
1851 -- .clin_when -> .started does not have to change meaning
1852 fk_encounter = %(pk_encounter)s,
1853
1854 is_long_term = (
1855 case
1856 when (
1857 (%(is_long_term)s is False)
1858 and
1859 (%(duration)s is NULL)
1860 ) is True then null
1861 else %(is_long_term)s
1862 end
1863 )::boolean,
1864
1865 duration = (
1866 case
1867 when %(is_long_term)s is True then null
1868 else %(duration)s
1869 end
1870 )::interval
1871 WHERE
1872 pk = %(pk_substance_intake)s
1873 AND
1874 xmin = %(xmin_substance_intake)s
1875 RETURNING
1876 xmin as xmin_substance_intake
1877 """
1878 ]
1879 _updatable_fields = [
1880 'started',
1881 'comment_on_start',
1882 'discontinued',
1883 'discontinue_reason',
1884 'intake_is_approved_of',
1885 'schedule',
1886 'duration',
1887 'aim',
1888 'is_long_term',
1889 'notes',
1890 'pk_episode',
1891 'pk_encounter',
1892 'harmful_use_type'
1893 ]
1894
1895
1897 return self.format (
1898 single_line = False,
1899 show_all_product_components = True,
1900 include_metadata = True,
1901 date_format = '%Y %b %d',
1902 include_instructions = True,
1903 include_loincs = True
1904 ).split('\n')
1905
1906
1907 - 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):
1926
1927
1935
1936
1938
1939 if self._payload[self._idx['is_currently_active']]:
1940 if self._payload[self._idx['duration']] is None:
1941 duration = gmTools.bool2subst (
1942 self._payload[self._idx['is_long_term']],
1943 _('long-term'),
1944 _('short-term'),
1945 _('?short-term')
1946 )
1947 else:
1948 duration = gmDateTime.format_interval (
1949 self._payload[self._idx['duration']],
1950 accuracy_wanted = gmDateTime.acc_days
1951 )
1952 else:
1953 duration = gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], date_format)
1954
1955 line = '%s%s (%s %s): %s %s%s (%s)' % (
1956 ' ' * left_margin,
1957 self.medically_formatted_start,
1958 gmTools.u_arrow2right,
1959 duration,
1960 self._payload[self._idx['substance']],
1961 self._payload[self._idx['amount']],
1962 self.formatted_units,
1963 gmTools.bool2subst(self._payload[self._idx['is_currently_active']], _('ongoing'), _('inactive'), _('?ongoing'))
1964 )
1965
1966 return line
1967
1968
1970
1971 txt = ''
1972 if include_metadata:
1973 txt = _('Substance abuse entry [#%s]\n') % self._payload[self._idx['pk_substance_intake']]
1974 txt += ' ' + _('Substance: %s [#%s]%s\n') % (
1975 self._payload[self._idx['substance']],
1976 self._payload[self._idx['pk_substance']],
1977 gmTools.coalesce(self._payload[self._idx['atc_substance']], '', ' ATC %s')
1978 )
1979 txt += ' ' + _('Use type: %s\n') % self.harmful_use_type_string
1980 txt += ' ' + _('Last checked: %s\n') % gmDateTime.pydt_strftime(self._payload[self._idx['last_checked_when']], '%Y %b %d')
1981 if self._payload[self._idx['discontinued']] is not None:
1982 txt += _(' Discontinued %s\n') % (
1983 gmDateTime.pydt_strftime (
1984 self._payload[self._idx['discontinued']],
1985 format = date_format,
1986 accuracy = gmDateTime.acc_days
1987 )
1988 )
1989 txt += gmTools.coalesce(self._payload[self._idx['notes']], '', _(' Notes: %s\n'))
1990 if include_metadata:
1991 txt += '\n'
1992 txt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % {
1993 'row_ver': self._payload[self._idx['row_version']],
1994 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']]),
1995 'mod_by': self._payload[self._idx['modified_by']]
1996 }
1997
1998 return txt
1999
2000
2001 - 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):
2002
2003 txt = _('Substance intake entry (%s, %s) [#%s] \n') % (
2004 gmTools.bool2subst (
2005 boolean = self._payload[self._idx['is_currently_active']],
2006 true_return = gmTools.bool2subst (
2007 boolean = self._payload[self._idx['seems_inactive']],
2008 true_return = _('active, needs check'),
2009 false_return = _('active'),
2010 none_return = _('assumed active')
2011 ),
2012 false_return = _('inactive')
2013 ),
2014 gmTools.bool2subst (
2015 boolean = self._payload[self._idx['intake_is_approved_of']],
2016 true_return = _('approved'),
2017 false_return = _('unapproved')
2018 ),
2019 self._payload[self._idx['pk_substance_intake']]
2020 )
2021
2022 if allergy is not None:
2023 certainty = gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'))
2024 txt += '\n'
2025 txt += ' !! ---- Cave ---- !!\n'
2026 txt += ' %s (%s): %s (%s)\n' % (
2027 allergy['l10n_type'],
2028 certainty,
2029 allergy['descriptor'],
2030 gmTools.coalesce(allergy['reaction'], '')[:40]
2031 )
2032 txt += '\n'
2033
2034 txt += ' ' + _('Substance: %s [#%s]\n') % (self._payload[self._idx['substance']], self._payload[self._idx['pk_substance']])
2035 txt += ' ' + _('Preparation: %s\n') % self._payload[self._idx['l10n_preparation']]
2036 txt += ' ' + _('Amount per dose: %s %s') % (
2037 self._payload[self._idx['amount']],
2038 self._get_formatted_units(short = False)
2039 )
2040 txt += '\n'
2041 txt += gmTools.coalesce(self._payload[self._idx['atc_substance']], '', _(' ATC (substance): %s\n'))
2042 if include_loincs and (len(self._payload[self._idx['loincs']]) > 0):
2043 loincs = """
2044 %s %s
2045 %s %s""" % (
2046 (' ' * left_margin),
2047 _('LOINCs to monitor:'),
2048 (' ' * left_margin),
2049 ('\n' + (' ' * (left_margin + 1))).join ([
2050 '%s%s%s' % (
2051 l['loinc'],
2052 gmTools.coalesce(l['max_age_str'], '', ': ' + _('once within %s')),
2053 gmTools.coalesce(l['comment'], '', ' (%s)')
2054 ) for l in self._payload[self._idx['loincs']]
2055 ])
2056 )
2057 txt += '\n'
2058
2059 txt += '\n'
2060
2061 txt += _(' Product name: %s [#%s]\n') % (self._payload[self._idx['product']], self._payload[self._idx['pk_drug_product']])
2062 txt += gmTools.coalesce(self._payload[self._idx['atc_drug']], '', _(' ATC (drug): %s\n'))
2063 if show_all_product_components:
2064 product = self.containing_drug
2065 if len(product['components']) > 1:
2066 for comp in product['components']:
2067 if comp['pk_substance'] == self._payload[self._idx['substance']]:
2068 continue
2069 txt += (' ' + _('Other component: %s %s %s\n') % (
2070 comp['substance'],
2071 comp['amount'],
2072 format_units(comp['unit'], comp['dose_unit'])
2073 ))
2074 txt += gmTools.coalesce(comp['intake_instructions'], '', ' ' + _('Intake: %s') + '\n')
2075 if include_loincs and (len(comp['loincs']) > 0):
2076 txt += (' ' + _('LOINCs to monitor:') + '\n')
2077 txt += '\n'.join([ ' %s%s%s' % (
2078 l['loinc'],
2079 gmTools.coalesce(l['max_age_str'], '', ': %s'),
2080 gmTools.coalesce(l['comment'], '', ' (%s)')
2081 ) for l in comp['loincs'] ])
2082
2083 txt += '\n'
2084
2085 txt += gmTools.coalesce(self._payload[self._idx['schedule']], '', _(' Regimen: %s\n'))
2086
2087 if self._payload[self._idx['is_long_term']]:
2088 duration = ' %s %s' % (gmTools.u_arrow2right, gmTools.u_infinity)
2089 else:
2090 if self._payload[self._idx['duration']] is None:
2091 duration = ''
2092 else:
2093 duration = ' %s %s' % (gmTools.u_arrow2right, gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days))
2094
2095 txt += _(' Started %s%s%s\n') % (
2096 self.medically_formatted_start,
2097 duration,
2098 gmTools.bool2subst(self._payload[self._idx['is_long_term']], _(' (long-term)'), _(' (short-term)'), '')
2099 )
2100
2101 if self._payload[self._idx['discontinued']] is not None:
2102 txt += _(' Discontinued %s\n') % (
2103 gmDateTime.pydt_strftime (
2104 self._payload[self._idx['discontinued']],
2105 format = date_format,
2106 accuracy = gmDateTime.acc_days
2107 )
2108 )
2109 txt += gmTools.coalesce(self._payload[self._idx['discontinue_reason']], '', _(' Reason: %s\n'))
2110
2111 txt += '\n'
2112
2113 txt += gmTools.coalesce(self._payload[self._idx['aim']], '', _(' Aim: %s\n'))
2114 txt += gmTools.coalesce(self._payload[self._idx['episode']], '', _(' Episode: %s\n'))
2115 txt += gmTools.coalesce(self._payload[self._idx['health_issue']], '', _(' Health issue: %s\n'))
2116 txt += gmTools.coalesce(self._payload[self._idx['notes']], '', _(' Advice: %s\n'))
2117 if self._payload[self._idx['intake_instructions']] is not None:
2118 txt += (' '+ (_('Intake: %s') % self._payload[self._idx['intake_instructions']]) + '\n')
2119 if len(self._payload[self._idx['loincs']]) > 0:
2120 txt += (' ' + _('LOINCs to monitor:') + '\n')
2121 txt += '\n'.join([ ' %s%s%s' % (
2122 l['loinc'],
2123 gmTools.coalesce(l['max_age_str'], '', ': %s'),
2124 gmTools.coalesce(l['comment'], '', ' (%s)')
2125 ) for l in self._payload[self._idx['loincs']] ])
2126
2127 txt += '\n'
2128
2129 txt += _('Revision: #%(row_ver)s, %(mod_when)s by %(mod_by)s.') % {
2130 'row_ver': self._payload[self._idx['row_version']],
2131 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']]),
2132 'mod_by': self._payload[self._idx['modified_by']]
2133 }
2134
2135 return txt
2136
2137
2138 - def turn_into_allergy(self, encounter_id=None, allergy_type='allergy'):
2139 allg = gmAllergy.create_allergy (
2140 allergene = self._payload[self._idx['substance']],
2141 allg_type = allergy_type,
2142 episode_id = self._payload[self._idx['pk_episode']],
2143 encounter_id = encounter_id
2144 )
2145 allg['substance'] = gmTools.coalesce (
2146 self._payload[self._idx['product']],
2147 self._payload[self._idx['substance']]
2148 )
2149 allg['reaction'] = self._payload[self._idx['discontinue_reason']]
2150 allg['atc_code'] = gmTools.coalesce(self._payload[self._idx['atc_substance']], self._payload[self._idx['atc_drug']])
2151 if self._payload[self._idx['external_code_product']] is not None:
2152 allg['substance_code'] = '%s::::%s' % (self._payload[self._idx['external_code_type_product']], self._payload[self._idx['external_code_product']])
2153
2154 if self._payload[self._idx['pk_drug_product']] is None:
2155 allg['generics'] = self._payload[self._idx['substance']]
2156 else:
2157 comps = [ c['substance'] for c in self.containing_drug.components ]
2158 if len(comps) == 0:
2159 allg['generics'] = self._payload[self._idx['substance']]
2160 else:
2161 allg['generics'] = '; '.join(comps)
2162
2163 allg.save()
2164 return allg
2165
2166
2168 return delete_substance_intake(pk_intake = self._payload[self._idx['pk_substance_intake']])
2169
2170
2171
2172
2174
2175 if self._payload[self._idx['harmful_use_type']] is None:
2176 return _('medication, not abuse')
2177 if self._payload[self._idx['harmful_use_type']] == 0:
2178 return _('no or non-harmful use')
2179 if self._payload[self._idx['harmful_use_type']] == 1:
2180 return _('presently harmful use')
2181 if self._payload[self._idx['harmful_use_type']] == 2:
2182 return _('presently addicted')
2183 if self._payload[self._idx['harmful_use_type']] == 3:
2184 return _('previously addicted')
2185
2186 harmful_use_type_string = property(_get_harmful_use_type_string)
2187
2188
2190 drug = self.containing_drug
2191
2192 if drug is None:
2193 return None
2194
2195 return drug.external_code
2196
2197 external_code = property(_get_external_code, lambda x:x)
2198
2199
2201 drug = self.containing_drug
2202
2203 if drug is None:
2204 return None
2205
2206 return drug.external_code_type
2207
2208 external_code_type = property(_get_external_code_type, lambda x:x)
2209
2210
2212 if self._payload[self._idx['pk_drug_product']] is None:
2213 return None
2214
2215 return cDrugProduct(aPK_obj = self._payload[self._idx['pk_drug_product']])
2216
2217 containing_drug = property(_get_containing_drug, lambda x:x)
2218
2219
2221 return format_units (
2222 self._payload[self._idx['unit']],
2223 self._payload[self._idx['dose_unit']],
2224 self._payload[self._idx['l10n_preparation']],
2225 short = short
2226 )
2227
2228 formatted_units = property(_get_formatted_units, lambda x:x)
2229
2230
2232 if self._payload[self._idx['comment_on_start']] == '?':
2233 return '?'
2234
2235 start_prefix = ''
2236 if self._payload[self._idx['comment_on_start']] is not None:
2237 start_prefix = gmTools.u_almost_equal_to
2238
2239 duration_taken = gmDateTime.pydt_now_here() - self._payload[self._idx['started']]
2240
2241 three_months = pydt.timedelta(weeks = 13, days = 3)
2242 if duration_taken < three_months:
2243 return _('%s%s: %s ago%s') % (
2244 start_prefix,
2245 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b %d', 'utf8', gmDateTime.acc_days),
2246 gmDateTime.format_interval_medically(duration_taken),
2247 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' (%s)')
2248 )
2249
2250 five_years = pydt.timedelta(weeks = 265)
2251 if duration_taken < five_years:
2252 return _('%s%s: %s ago (%s)') % (
2253 start_prefix,
2254 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b', 'utf8', gmDateTime.acc_months),
2255 gmDateTime.format_interval_medically(duration_taken),
2256 gmTools.coalesce (
2257 self._payload[self._idx['comment_on_start']],
2258 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days),
2259 )
2260 )
2261
2262 return _('%s%s: %s ago (%s)') % (
2263 start_prefix,
2264 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y', 'utf8', gmDateTime.acc_years),
2265 gmDateTime.format_interval_medically(duration_taken),
2266 gmTools.coalesce (
2267 self._payload[self._idx['comment_on_start']],
2268 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days),
2269 )
2270 )
2271
2272 medically_formatted_start = property(_get_medically_formatted_start, lambda x:x)
2273
2274
2276
2277
2278 if gmDateTime.pydt_is_today(self._payload[self._idx['discontinued']]):
2279 intro = _('until today')
2280 else:
2281 ended_ago = now - self._payload[self._idx['discontinued']]
2282 intro = _('until %s%s ago') % (
2283 gmTools.u_almost_equal_to,
2284 gmDateTime.format_interval_medically(ended_ago),
2285 )
2286
2287
2288 if self._payload[self._idx['started']] is None:
2289 start = gmTools.coalesce(self._payload[self._idx['comment_on_start']], '?')
2290 else:
2291 start = '%s%s%s' % (
2292 gmTools.bool2subst((self._payload[self._idx['comment_on_start']] is None), '', gmTools.u_almost_equal_to),
2293 gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%Y %b %d', accuracy = gmDateTime.acc_days),
2294 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]')
2295 )
2296
2297
2298 if self._payload[self._idx['started']] is None:
2299 duration_taken_str = '?'
2300 else:
2301 duration_taken = self._payload[self._idx['discontinued']] - self._payload[self._idx['started']] + pydt.timedelta(days = 1)
2302 duration_taken_str = gmDateTime.format_interval (duration_taken, gmDateTime.acc_days)
2303
2304
2305 if self._payload[self._idx['duration']] is None:
2306 duration_planned_str = ''
2307 else:
2308 duration_planned_str = _(' [planned: %s]') % gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days)
2309
2310
2311 end = gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%Y %b %d', 'utf8', gmDateTime.acc_days)
2312
2313
2314 txt = '%s (%s %s %s%s %s %s)' % (
2315 intro,
2316 start,
2317 gmTools.u_arrow2right_thick,
2318 duration_taken_str,
2319 duration_planned_str,
2320 gmTools.u_arrow2right_thick,
2321 end
2322 )
2323 return txt
2324
2325
2327
2328 now = gmDateTime.pydt_now_here()
2329
2330
2331 if self._payload[self._idx['discontinued']] is not None:
2332 if (self._payload[self._idx['discontinued']] < now) or (gmDateTime.pydt_is_today(self._payload[self._idx['discontinued']])):
2333 return self._get_medically_formatted_start_end_of_stopped(now)
2334
2335
2336 arrow_parts = []
2337
2338
2339 if self._payload[self._idx['started']] is None:
2340 start_str = gmTools.coalesce(self._payload[self._idx['comment_on_start']], '?')
2341 else:
2342 start_prefix = gmTools.bool2subst((self._payload[self._idx['comment_on_start']] is None), '', gmTools.u_almost_equal_to)
2343
2344 if gmDateTime.pydt_is_today(self._payload[self._idx['started']]):
2345 start_str = _('today (%s)') % gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%Y %b %d', accuracy = gmDateTime.acc_days)
2346
2347 elif self._payload[self._idx['started']] < now:
2348 started_ago = now - self._payload[self._idx['started']]
2349 three_months = pydt.timedelta(weeks = 13, days = 3)
2350 five_years = pydt.timedelta(weeks = 265)
2351 if started_ago < three_months:
2352 start_str = _('%s%s%s (%s%s ago, in %s)') % (
2353 start_prefix,
2354 gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%b %d', accuracy = gmDateTime.acc_days),
2355 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'),
2356 gmTools.u_almost_equal_to,
2357 gmDateTime.format_interval_medically(started_ago),
2358 gmDateTime.pydt_strftime(self._payload[self._idx['started']], format = '%Y', accuracy = gmDateTime.acc_days)
2359 )
2360 elif started_ago < five_years:
2361 start_str = _('%s%s%s (%s%s ago, %s)') % (
2362 start_prefix,
2363 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b', 'utf8', gmDateTime.acc_months),
2364 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'),
2365 gmTools.u_almost_equal_to,
2366 gmDateTime.format_interval_medically(started_ago),
2367 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days)
2368 )
2369 else:
2370 start_str = _('%s%s%s (%s%s ago, %s)') % (
2371 start_prefix,
2372 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y', 'utf8', gmDateTime.acc_years),
2373 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'),
2374 gmTools.u_almost_equal_to,
2375 gmDateTime.format_interval_medically(started_ago),
2376 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%b %d', 'utf8', gmDateTime.acc_days),
2377 )
2378
2379 else:
2380 starts_in = self._payload[self._idx['started']] - now
2381 start_str = _('%s%s%s (in %s%s)') % (
2382 start_prefix,
2383 gmDateTime.pydt_strftime(self._payload[self._idx['started']], '%Y %b %d', 'utf8', gmDateTime.acc_days),
2384 gmTools.coalesce(self._payload[self._idx['comment_on_start']], '', ' [%s]'),
2385 gmTools.u_almost_equal_to,
2386 gmDateTime.format_interval_medically(starts_in)
2387 )
2388
2389 arrow_parts.append(start_str)
2390
2391
2392 durations = []
2393 if self._payload[self._idx['discontinued']] is not None:
2394 if self._payload[self._idx['started']] is not None:
2395 duration_documented = self._payload[self._idx['discontinued']] - self._payload[self._idx['started']]
2396 durations.append(_('%s (documented)') % gmDateTime.format_interval(duration_documented, gmDateTime.acc_days))
2397 if self._payload[self._idx['duration']] is not None:
2398 durations.append(_('%s (plan)') % gmDateTime.format_interval(self._payload[self._idx['duration']], gmDateTime.acc_days))
2399 if len(durations) == 0:
2400 if self._payload[self._idx['is_long_term']]:
2401 duration_str = gmTools.u_infinity
2402 else:
2403 duration_str = '?'
2404 else:
2405 duration_str = ', '.join(durations)
2406
2407 arrow_parts.append(duration_str)
2408
2409
2410 if self._payload[self._idx['discontinued']] is None:
2411 if self._payload[self._idx['duration']] is None:
2412 end_str = '?'
2413 else:
2414 if self._payload[self._idx['started']] is None:
2415 end_str = '?'
2416 else:
2417 planned_end = self._payload[self._idx['started']] + self._payload[self._idx['duration']] - pydt.timedelta(days = 1)
2418 if planned_end.year == now.year:
2419 end_template = '%b %d'
2420 if planned_end < now:
2421 planned_end_from_now_str = _('%s ago, in %s') % (gmDateTime.format_interval(now - planned_end, gmDateTime.acc_days), planned_end.year)
2422 else:
2423 planned_end_from_now_str = _('in %s, %s') % (gmDateTime.format_interval(planned_end - now, gmDateTime.acc_days), planned_end.year)
2424 else:
2425 end_template = '%Y'
2426 if planned_end < now:
2427 planned_end_from_now_str = _('%s ago = %s') % (
2428 gmDateTime.format_interval(now - planned_end, gmDateTime.acc_days),
2429 gmDateTime.pydt_strftime(planned_end, '%b %d', 'utf8', gmDateTime.acc_days)
2430 )
2431 else:
2432 planned_end_from_now_str = _('in %s = %s') % (
2433 gmDateTime.format_interval(planned_end - now, gmDateTime.acc_days),
2434 gmDateTime.pydt_strftime(planned_end, '%b %d', 'utf8', gmDateTime.acc_days)
2435 )
2436 end_str = '%s (%s)' % (
2437 gmDateTime.pydt_strftime(planned_end, end_template, 'utf8', gmDateTime.acc_days),
2438 planned_end_from_now_str
2439 )
2440 else:
2441 if gmDateTime.is_today(self._payload[self._idx['discontinued']]):
2442 end_str = _('today')
2443 elif self._payload[self._idx['discontinued']].year == now.year:
2444 end_date_template = '%b %d'
2445 if self._payload[self._idx['discontinued']] < now:
2446 planned_end_from_now_str = _('%s ago, in %s') % (
2447 gmDateTime.format_interval(now - self._payload[self._idx['discontinued']], gmDateTime.acc_days),
2448 self._payload[self._idx['discontinued']].year
2449 )
2450 else:
2451 planned_end_from_now_str = _('in %s, %s') % (
2452 gmDateTime.format_interval(self._payload[self._idx['discontinued']] - now, gmDateTime.acc_days),
2453 self._payload[self._idx['discontinued']].year
2454 )
2455 else:
2456 end_date_template = '%Y'
2457 if self._payload[self._idx['discontinued']] < now:
2458 planned_end_from_now_str = _('%s ago = %s') % (
2459 gmDateTime.format_interval(now - self._payload[self._idx['discontinued']], gmDateTime.acc_days),
2460 gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%b %d', 'utf8', gmDateTime.acc_days)
2461 )
2462 else:
2463 planned_end_from_now_str = _('in %s = %s') % (
2464 gmDateTime.format_interval(self._payload[self._idx['discontinued']] - now, gmDateTime.acc_days),
2465 gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], '%b %d', 'utf8', gmDateTime.acc_days)
2466 )
2467 end_str = '%s (%s)' % (
2468 gmDateTime.pydt_strftime(self._payload[self._idx['discontinued']], end_date_template, 'utf8', gmDateTime.acc_days),
2469 planned_end_from_now_str
2470 )
2471
2472 arrow_parts.append(end_str)
2473
2474
2475 return (' %s ' % gmTools.u_arrow2right_thick).join(arrow_parts)
2476
2477 medically_formatted_start_end = property(_get_medically_formatted_start_end, lambda x:x)
2478
2479
2480 - def _get_as_amts_latex(self, strict=True):
2481 return format_substance_intake_as_amts_latex(intake = self, strict=strict)
2482
2483 as_amts_latex = property(_get_as_amts_latex, lambda x:x)
2484
2485
2486 - def _get_as_amts_data(self, strict=True):
2487 return format_substance_intake_as_amts_data(intake = self, strict = strict)
2488
2489 as_amts_data = property(_get_as_amts_data, lambda x:x)
2490
2491
2493 tests = [
2494
2495 ' 1-1-1-1 ',
2496
2497 '1-1-1-1',
2498 '22-1-1-1',
2499 '1/3-1-1-1',
2500 '/4-1-1-1'
2501 ]
2502 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}$"
2503 for test in tests:
2504 print(test.strip(), ":", regex.match(pattern, test.strip()))
2505
2506
2515
2516
2518
2519 if [pk_component, pk_drug_product, pk_dose].count(None) != 2:
2520 raise ValueError('only one of pk_component, pk_dose, and pk_drug_product can be non-NULL')
2521
2522 args = {
2523 'pk_comp': pk_component,
2524 'pk_pat': pk_identity,
2525 'pk_drug_product': pk_drug_product,
2526 'pk_dose': pk_dose
2527 }
2528 where_parts = ['fk_encounter IN (SELECT pk FROM clin.encounter WHERE fk_patient = %(pk_pat)s)']
2529
2530 if pk_dose is not None:
2531 where_parts.append('fk_drug_component IN (SELECT pk FROM ref.lnk_dose2drug WHERE fk_dose = %(pk_dose)s)')
2532 if pk_component is not None:
2533 where_parts.append('fk_drug_component = %(pk_comp)s')
2534 if pk_drug_product is not None:
2535 where_parts.append('fk_drug_component IN (SELECT pk FROM ref.lnk_dose2drug WHERE fk_drug_product = %(pk_drug_product)s)')
2536
2537 cmd = """
2538 SELECT EXISTS (
2539 SELECT 1 FROM clin.substance_intake
2540 WHERE
2541 %s
2542 LIMIT 1
2543 )
2544 """ % '\nAND\n'.join(where_parts)
2545
2546 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2547 return rows[0][0]
2548
2549
2551
2552 if (atc is None) or (pk_identity is None):
2553 raise ValueError('atc and pk_identity cannot be None')
2554
2555 args = {
2556 'pat': pk_identity,
2557 'atc': atc
2558 }
2559 where_parts = [
2560 'pk_patient = %(pat)s',
2561 '((atc_substance = %(atc)s) OR (atc_drug = %(atc)s))'
2562 ]
2563 cmd = """
2564 SELECT EXISTS (
2565 SELECT 1 FROM clin.v_substance_intakes
2566 WHERE
2567 %s
2568 LIMIT 1
2569 )
2570 """ % '\nAND\n'.join(where_parts)
2571
2572 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2573 return rows[0][0]
2574
2575
2577
2578 if [pk_component, pk_drug_product].count(None) != 1:
2579 raise ValueError('only one of pk_component and pk_drug_product can be non-NULL')
2580
2581 args = {
2582 'pk_enc': pk_encounter,
2583 'pk_epi': pk_episode,
2584 'pk_comp': pk_component,
2585 'pk_drug_product': pk_drug_product
2586 }
2587
2588 if pk_drug_product is not None:
2589 cmd = """
2590 INSERT INTO clin.substance_intake (
2591 fk_encounter,
2592 fk_episode,
2593 intake_is_approved_of,
2594 fk_drug_component
2595 ) VALUES (
2596 %(pk_enc)s,
2597 %(pk_epi)s,
2598 False,
2599 -- select one of the components (the others will be added by a trigger)
2600 (SELECT pk FROM ref.lnk_dose2drug WHERE fk_drug_product = %(pk_drug_product)s LIMIT 1)
2601 )
2602 RETURNING pk"""
2603
2604 if pk_component is not None:
2605 cmd = """
2606 INSERT INTO clin.substance_intake (
2607 fk_encounter,
2608 fk_episode,
2609 intake_is_approved_of,
2610 fk_drug_component
2611 ) VALUES (
2612 %(pk_enc)s,
2613 %(pk_epi)s,
2614 False,
2615 %(pk_comp)s
2616 )
2617 RETURNING pk"""
2618
2619 try:
2620 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
2621 except gmPG2.dbapi.InternalError as exc:
2622 if exc.pgerror is None:
2623 raise
2624 exc = make_pg_exception_fields_unicode(exc)
2625 if 'prevent_duplicate_component' in exc.u_pgerror:
2626 _log.exception('will not create duplicate substance intake entry')
2627 _log.error(exc.u_pgerror)
2628 return None
2629 raise
2630
2631 return cSubstanceIntakeEntry(aPK_obj = rows[0][0])
2632
2633
2635 if delete_siblings:
2636 cmd = """
2637 DELETE FROM clin.substance_intake c_si
2638 WHERE
2639 c_si.fk_drug_component IN (
2640 SELECT r_ld2d.pk FROM ref.lnk_dose2drug r_ld2d
2641 WHERE r_ld2d.fk_drug_product = (
2642 SELECT c_vsi1.pk_drug_product FROM clin.v_substance_intakes c_vsi1 WHERE c_vsi1.pk_substance_intake = %(pk)s
2643 )
2644 )
2645 AND
2646 c_si.fk_encounter IN (
2647 SELECT c_e.pk FROM clin.encounter c_e
2648 WHERE c_e.fk_patient = (
2649 SELECT c_vsi2.pk_patient FROM clin.v_substance_intakes c_vsi2 WHERE c_vsi2.pk_substance_intake = %(pk)s
2650 )
2651 )"""
2652 else:
2653 cmd = 'DELETE FROM clin.substance_intake WHERE pk = %(pk)s'
2654
2655 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk_intake}}])
2656 return True
2657
2658
2659
2660
2750
2751
2786
2787
2789
2790 _log.debug('generating AMTS data template definition file(workdir=%s, strict=%s)', work_dir, strict)
2791
2792 if not strict:
2793 return __generate_enhanced_amts_data_template_definition_file(work_dir = work_dir)
2794
2795 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>$"//::>>$>
2796 <P g="$<name::%(firstnames)s::45>$" f="$<name::%(lastnames)s::45>$" b="$<date_of_birth::%Y%m%d::8>$"/>
2797 <A
2798 n="$<<range_of::$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$::30>>$"
2799 $<praxis_address:: s="%(street)s"::>$
2800 $<praxis_address:: z="%(postcode)s"::>$
2801 $<praxis_address:: c="%(urb)s"::>$
2802 $<praxis_comm::workphone// p="%(url)s"::20>$
2803 $<praxis_comm::email// e="%(url)s"::80>$
2804 t="$<today::%Y%m%d::8>$"
2805 />
2806 <O ai="s.S.$<amts_total_pages::::1>$ unten"/>
2807 $<amts_intakes_as_data::::9999999>$
2808 </MP>""").split('\n') ]
2809
2810
2811 amts_fname = gmTools.get_unique_filename (
2812 prefix = 'gm2amts_data-',
2813 suffix = '.txt',
2814 tmp_dir = work_dir
2815 )
2816 amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8')
2817 amts_template.write('[form]\n')
2818 amts_template.write('template = $template$\n')
2819 amts_template.write(''.join(amts_lines))
2820 amts_template.write('\n')
2821 amts_template.write('$template$\n')
2822 amts_template.close()
2823
2824 return amts_fname
2825
2826
2828
2829 amts_lines = [ l for l in ('<MP v="023" U="%s"' % uuid.uuid4().hex + """ l="de-DE" a="1" z="1">
2830 <P g="$<name::%(firstnames)s::>$" f="$<name::%(lastnames)s::>$" b="$<date_of_birth::%Y%m%d::8>$"/>
2831 <A
2832 n="$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$"
2833 $<praxis_address:: s="%(street)s %(number)s %(subunit)s"::>$
2834 $<praxis_address:: z="%(postcode)s"::>$
2835 $<praxis_address:: c="%(urb)s"::>$
2836 $<praxis_comm::workphone// p="%(url)s"::>$
2837 $<praxis_comm::email// e="%(url)s"::>$
2838 t="$<today::%Y%m%d::8>$"
2839 />
2840 <O ai="Seite 1 unten"/>
2841 $<amts_intakes_as_data_enhanced::::9999999>$
2842 </MP>""").split('\n') ]
2843
2844
2845 amts_fname = gmTools.get_unique_filename (
2846 prefix = 'gm2amts_data-utf8-unabridged-',
2847 suffix = '.txt',
2848 tmp_dir = work_dir
2849 )
2850 amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8')
2851 amts_template.write('[form]\n')
2852 amts_template.write('template = $template$\n')
2853 amts_template.write(''.join(amts_lines))
2854 amts_template.write('\n')
2855 amts_template.write('$template$\n')
2856 amts_template.close()
2857
2858 return amts_fname
2859
2860
2861
2862
2902
2903
2905
2906
2907 first_chars = []
2908 for intake in intakes:
2909 first_chars.append(intake['product'][0])
2910
2911
2912 val_sum = 0
2913 for first_char in first_chars:
2914
2915 if first_char.isdigit():
2916 val_sum += (ord(first_char) + 7)
2917
2918
2919 if first_char.isalpha():
2920 val_sum += ord(first_char.upper())
2921
2922
2923
2924 tmp, remainder = divmod(val_sum, 36)
2925
2926 if remainder < 10:
2927 return '%s' % remainder
2928
2929 return chr(remainder + 55)
2930
2931
2933
2934 if not strict:
2935 return __generate_enhanced_amts_data_template_definition_file(work_dir = work_dir)
2936
2937 amts_fields = [
2938 'MP',
2939 '020',
2940 'DE',
2941 'DE',
2942 '1',
2943 '$<today::%Y%m%d::8>$',
2944 '$<amts_page_idx::::1>$',
2945 '$<amts_total_pages::::1>$',
2946 '0',
2947
2948 '$<name::%(firstnames)s::45>$',
2949 '$<name::%(lastnames)s::45>$',
2950 '',
2951 '$<date_of_birth::%Y%m%d::8>$',
2952
2953 '$<<range_of::$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$::30>>$',
2954 '$<praxis_address::%(street)s %(number)s %(subunit)s|%(postcode)s|%(urb)s::57>$',
2955 '$<praxis_comm::workphone::20>$',
2956 '$<praxis_comm::email::80>$',
2957
2958
2959 '264 Seite $<amts_total_pages::::1>$ unten',
2960 '',
2961 '',
2962
2963
2964 '$<amts_intakes_as_data::::9999999>$',
2965
2966 '$<amts_check_symbol::::1>$',
2967 '#@',
2968 ]
2969
2970 amts_fname = gmTools.get_unique_filename (
2971 prefix = 'gm2amts_data-',
2972 suffix = '.txt',
2973 tmp_dir = work_dir
2974 )
2975 amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8')
2976 amts_template.write('[form]\n')
2977 amts_template.write('template = $template$\n')
2978 amts_template.write('|'.join(amts_fields))
2979 amts_template.write('\n')
2980 amts_template.write('$template$\n')
2981 amts_template.close()
2982
2983 return amts_fname
2984
2985
2987
2988 amts_fields = [
2989 'MP',
2990 '020',
2991 'DE',
2992 'DE',
2993 '1',
2994 '$<today::%Y%m%d::8>$',
2995 '1',
2996 '1',
2997 '0',
2998
2999 '$<name::%(firstnames)s::>$',
3000 '$<name::%(lastnames)s::>$',
3001 '',
3002 '$<date_of_birth::%Y%m%d::8>$',
3003
3004 '$<praxis::%(praxis)s,%(branch)s::>$,$<current_provider::::>$',
3005 '$<praxis_address::%(street)s %(number)s %(subunit)s::>$',
3006 '$<praxis_address::%(postcode)s::>$',
3007 '$<praxis_address::%(urb)s::>$',
3008 '$<praxis_comm::workphone::>$',
3009 '$<praxis_comm::email::>$',
3010
3011
3012 '264 Seite 1 unten',
3013 '',
3014 '',
3015
3016
3017 '$<amts_intakes_as_data_enhanced::::>$',
3018
3019 '$<amts_check_symbol::::1>$',
3020 '#@',
3021 ]
3022
3023 amts_fname = gmTools.get_unique_filename (
3024 prefix = 'gm2amts_data-utf8-unabridged-',
3025 suffix = '.txt',
3026 tmp_dir = work_dir
3027 )
3028 amts_template = io.open(amts_fname, mode = 'wt', encoding = 'utf8')
3029 amts_template.write('[form]\n')
3030 amts_template.write('template = $template$\n')
3031 amts_template.write('|'.join(amts_fields))
3032 amts_template.write('\n')
3033 amts_template.write('$template$\n')
3034 amts_template.close()
3035
3036 return amts_fname
3037
3038
3039
3040
3078
3079
3164
3165
3166
3167
3168 -def create_default_medication_history_episode(pk_health_issue=None, encounter=None, link_obj=None):
3169 return gmEMRStructItems.create_episode (
3170 pk_health_issue = pk_health_issue,
3171 episode_name = DEFAULT_MEDICATION_HISTORY_EPISODE,
3172 is_open = False,
3173 allow_dupes = False,
3174 encounter = encounter,
3175 link_obj = link_obj
3176 )
3177
3178
3196
3197
3215
3216
3236
3237
3250
3251
3252
3253
3254 if __name__ == "__main__":
3255
3256 if len(sys.argv) < 2:
3257 sys.exit()
3258
3259 if sys.argv[1] != 'test':
3260 sys.exit()
3261
3262 from Gnumed.pycommon import gmLog2
3263
3264
3265 gmI18N.activate_locale()
3266
3267
3268
3269
3270
3278
3279
3287
3288
3289
3290
3291
3292
3293
3294
3295
3303
3304
3312
3313
3325
3326
3332
3333
3338
3339
3342
3353
3354
3357
3358
3362
3363
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379 test_delete_intake()
3380
3381
3382
3383
3384
3385
3386