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