1 """GNUmed vaccination related business objects.
2 """
3
4 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL"
6
7 import sys
8 import logging
9 import io
10
11
12 if __name__ == '__main__':
13 sys.path.insert(0, '../../')
14 from Gnumed.pycommon import gmBusinessDBObject
15 from Gnumed.pycommon import gmPG2
16 from Gnumed.pycommon import gmI18N
17 from Gnumed.pycommon import gmTools
18 from Gnumed.pycommon import gmDateTime
19 if __name__ == '__main__':
20 gmI18N.activate_locale()
21 gmI18N.install_domain()
22 from Gnumed.business import gmMedication
23
24
25 _log = logging.getLogger('gm.vacc')
26
27
28 URL_vaccination_plan = 'http://www.rki.de/DE/Content/Infekt/EpidBull/Archiv/2017/Ausgaben/34_17.pdf?__blob=publicationFile'
29
30
31 _SQL_create_substance4vaccine = """-- in case <%(substance_tag)s> already exists: add ATC
32 UPDATE ref.substance SET atc = '%(atc)s' WHERE lower(description) = lower('%(desc)s') AND atc IS NULL;
33
34 INSERT INTO ref.substance (description, atc)
35 SELECT
36 '%(desc)s',
37 '%(atc)s'
38 WHERE NOT EXISTS (
39 SELECT 1 FROM ref.substance WHERE
40 atc = '%(atc)s'
41 AND
42 description = '%(desc)s'
43 );
44
45 -- generic English
46 SELECT i18n.upd_tx('en', '%(orig)s', '%(trans)s');
47 -- user language, if any, fails if not set
48 SELECT i18n.upd_tx('%(orig)s', '%(trans)s');"""
49
50 _SQL_map_indication2substance = """-- old-style "%(v21_ind)s" => "%(desc)s"
51 INSERT INTO staging.lnk_vacc_ind2subst_dose (fk_indication, fk_dose, is_live)
52 SELECT
53 (SELECT id FROM ref.vacc_indication WHERE description = '%(v21_ind)s'),
54 (SELECT pk_dose FROM ref.v_substance_doses WHERE
55 amount = 1
56 AND
57 unit = 'dose'
58 AND
59 dose_unit = 'shot'
60 AND
61 substance = '%(desc)s'
62 ),
63 %(is_live)s
64 WHERE EXISTS (
65 SELECT 1 FROM ref.vacc_indication WHERE description = '%(v21_ind)s'
66 );"""
67
68 _SQL_create_vacc_product = """-- --------------------------------------------------------------
69 -- in case <%(prod_name)s> exists: add ATC
70 UPDATE ref.drug_product SET atc_code = '%(atc_prod)s' WHERE
71 atc_code IS NULL
72 AND
73 description = '%(prod_name)s'
74 AND
75 preparation = '%(prep)s'
76 AND
77 is_fake IS TRUE;
78
79 INSERT INTO ref.drug_product (description, preparation, is_fake, atc_code)
80 SELECT
81 '%(prod_name)s',
82 '%(prep)s',
83 TRUE,
84 '%(atc_prod)s'
85 WHERE NOT EXISTS (
86 SELECT 1 FROM ref.drug_product WHERE
87 description = '%(prod_name)s'
88 AND
89 preparation = '%(prep)s'
90 AND
91 is_fake = TRUE
92 AND
93 atc_code = '%(atc_prod)s'
94 );"""
95
96 _SQL_create_vaccine = """-- add vaccine if necessary
97 INSERT INTO ref.vaccine (is_live, fk_drug_product)
98 SELECT
99 %(is_live)s,
100 (SELECT pk FROM ref.drug_product WHERE
101 description = '%(prod_name)s'
102 AND
103 preparation = '%(prep)s'
104 AND
105 is_fake = TRUE
106 AND
107 atc_code = '%(atc_prod)s'
108 )
109 WHERE NOT EXISTS (
110 SELECT 1 FROM ref.vaccine WHERE
111 is_live IS %(is_live)s
112 AND
113 fk_drug_product = (
114 SELECT pk FROM ref.drug_product WHERE
115 description = '%(prod_name)s'
116 AND
117 preparation = '%(prep)s'
118 AND
119 is_fake = TRUE
120 AND
121 atc_code = '%(atc_prod)s'
122 )
123 );"""
124
125 _SQL_create_vacc_subst_dose = """-- create dose, assumes substance exists
126 INSERT INTO ref.dose (fk_substance, amount, unit, dose_unit)
127 SELECT
128 (SELECT pk FROM ref.substance WHERE atc = '%(atc_subst)s' AND description = '%(name_subst)s' LIMIT 1),
129 1,
130 'dose',
131 'shot'
132 WHERE NOT EXISTS (
133 SELECT 1 FROM ref.dose WHERE
134 fk_substance = (SELECT pk FROM ref.substance WHERE atc = '%(atc_subst)s' AND description = '%(name_subst)s' LIMIT 1)
135 AND
136 amount = 1
137 AND
138 unit = 'dose'
139 AND
140 dose_unit IS NOT DISTINCT FROM 'shot'
141 );"""
142
143 _SQL_link_dose2vacc_prod = """-- link dose to product
144 INSERT INTO ref.lnk_dose2drug (fk_dose, fk_drug_product)
145 SELECT
146 (SELECT pk from ref.dose WHERE
147 fk_substance = (SELECT pk FROM ref.substance WHERE atc = '%(atc_subst)s' AND description = '%(name_subst)s' LIMIT 1)
148 AND
149 amount = 1
150 AND
151 unit = 'dose'
152 AND
153 dose_unit IS NOT DISTINCT FROM 'shot'
154 ),
155 (SELECT pk FROM ref.drug_product WHERE
156 description = '%(prod_name)s'
157 AND
158 preparation = '%(prep)s'
159 AND
160 is_fake = TRUE
161 AND
162 atc_code = '%(atc_prod)s'
163 )
164 WHERE NOT EXISTS (
165 SELECT 1 FROM ref.lnk_dose2drug WHERE
166 fk_dose = (
167 SELECT PK from ref.dose WHERE
168 fk_substance = (SELECT pk FROM ref.substance WHERE atc = '%(atc_subst)s' AND description = '%(name_subst)s' LIMIT 1)
169 AND
170 amount = 1
171 AND
172 unit = 'dose'
173 AND
174 dose_unit IS NOT DISTINCT FROM 'shot'
175 )
176 AND
177 fk_drug_product = (
178 SELECT pk FROM ref.drug_product WHERE
179 description = '%(prod_name)s'
180 AND
181 preparation = '%(prep)s'
182 AND
183 is_fake = TRUE
184 AND
185 atc_code = '%(atc_prod)s'
186 )
187 );"""
188
189 _SQL_create_indications_mapping_table = """-- set up helper table for conversion of vaccines from using
190 -- linked indications to using linked substances,
191 -- to be dropped after converting vaccines
192 DROP TABLE IF EXISTS staging.lnk_vacc_ind2subst_dose CASCADE;
193
194 CREATE UNLOGGED TABLE staging.lnk_vacc_ind2subst_dose (
195 fk_indication INTEGER
196 NOT NULL
197 REFERENCES ref.vacc_indication(id)
198 ON UPDATE CASCADE
199 ON DELETE RESTRICT,
200 fk_dose INTEGER
201 NOT NULL
202 REFERENCES ref.dose(pk)
203 ON UPDATE CASCADE
204 ON DELETE RESTRICT,
205 is_live
206 BOOLEAN
207 NOT NULL
208 DEFAULT false,
209 UNIQUE(fk_indication, fk_dose),
210 UNIQUE(fk_indication, is_live)
211 );
212
213
214 DROP VIEW IF EXISTS staging.v_lnk_vacc_ind2subst_dose CASCADE;
215
216 CREATE VIEW staging.v_lnk_vacc_ind2subst_dose AS
217 SELECT
218 s_lvi2sd.is_live
219 as mapping_is_for_live_vaccines,
220 r_vi.id
221 as pk_indication,
222 r_vi.description
223 as indication,
224 r_vi.atcs_single_indication,
225 r_vi.atcs_combi_indication,
226 r_d.pk
227 as pk_dose,
228 r_d.amount,
229 r_d.unit,
230 r_d.dose_unit,
231 r_s.pk
232 as pk_substance,
233 r_s.description
234 as substance,
235 r_s.atc
236 as atc_substance
237 FROM
238 staging.lnk_vacc_ind2subst_dose s_lvi2sd
239 inner join ref.vacc_indication r_vi on (r_vi.id = s_lvi2sd.fk_indication)
240 inner join ref.dose r_d on (r_d.pk = s_lvi2sd.fk_dose)
241 inner join ref.substance r_s on (r_s.pk = r_d.fk_substance)
242 ;"""
243
244 _SQL_create_generic_vaccines_script = """-- ==============================================================
245 -- GNUmed database schema change script
246 --
247 -- License: GPL v2 or later
248 -- Author: karsten.hilbert@gmx.net
249 --
250 -- THIS IS A GENERATED FILE. DO NOT EDIT.
251 --
252 -- ==============================================================
253 \set ON_ERROR_STOP 1
254 --set default_transaction_read_only to off;
255
256 -- --------------------------------------------------------------
257 -- indications mapping helper table
258 -- --------------------------------------------------------------
259 %s
260
261 -- --------------------------------------------------------------
262 -- generic vaccine "substances" (= indications)
263 -- --------------------------------------------------------------
264 %s
265
266 -- --------------------------------------------------------------
267 -- generic vaccines
268 -- --------------------------------------------------------------
269 -- new-style vaccines are not linked to indications, so drop
270 -- trigger asserting that condition,
271 DROP FUNCTION IF EXISTS clin.trf_sanity_check_vaccine_has_indications() CASCADE;
272
273
274 -- need to disable trigger before running
275 ALTER TABLE ref.drug_product
276 DISABLE TRIGGER tr_assert_product_has_components
277 ;
278
279 %s
280
281 -- want to re-enable trigger as now all inserted
282 -- vaccines satisfy the conditions
283 ALTER TABLE ref.drug_product
284 ENABLE TRIGGER tr_assert_product_has_components
285 ;
286
287 -- --------------------------------------------------------------
288 -- indications mapping data
289 -- --------------------------------------------------------------
290 -- map old style
291 -- (clin|ref).vacc_indication.description
292 -- to new style
293 -- ref.v_substance_doses.substance
294
295 %s
296
297 -- --------------------------------------------------------------
298 select gm.log_script_insertion('v%s-ref-create_generic_vaccines.sql', '%s');
299 """
300
301
313
314
316
317 _log.debug('including indications mapping table with generic vaccines creation SQL: %s', include_indications_mapping)
318
319 from Gnumed.business import gmVaccDefs
320
321 sql_create_substances = []
322 sql_populate_ind2subst_map = []
323 sql_create_vaccines = []
324
325 for substance_tag in gmVaccDefs._VACCINE_SUBSTANCES:
326 subst = gmVaccDefs._VACCINE_SUBSTANCES[substance_tag]
327 args = {
328 'substance_tag': substance_tag,
329 'atc': subst['atc4target'],
330 'desc': subst['name'],
331 'orig': subst['target'].split('::')[0],
332 'trans': subst['target'].split('::')[-1]
333 }
334 sql_create_substances.append(_SQL_create_substance4vaccine % args)
335 try:
336 for v21_ind in subst['v21_indications']:
337 args['v21_ind'] = v21_ind
338 args['is_live'] = 'false'
339 sql_populate_ind2subst_map.append(_SQL_map_indication2substance % args)
340 except KeyError:
341 pass
342 try:
343 for v21_ind in subst['v21_indications_live']:
344 args['v21_ind'] = v21_ind
345 args['is_live'] = 'true'
346 sql_populate_ind2subst_map.append(_SQL_map_indication2substance % args)
347 except KeyError:
348 pass
349 args = {}
350
351 for key in gmVaccDefs._GENERIC_VACCINES:
352 vaccine_def = gmVaccDefs._GENERIC_VACCINES[key]
353
354 args = {
355 'atc_prod': vaccine_def['atc'],
356 'prod_name': vaccine_def['name'],
357
358 'prep': 'vaccine',
359 'is_live': vaccine_def['live']
360 }
361 sql_create_vaccines.append(_SQL_create_vacc_product % args)
362
363 for ingredient_tag in vaccine_def['ingredients']:
364 vacc_subst_def = gmVaccDefs._VACCINE_SUBSTANCES[ingredient_tag]
365 args['atc_subst'] = vacc_subst_def['atc4target']
366 args['name_subst'] = vacc_subst_def['name']
367
368 sql_create_vaccines.append(_SQL_create_vacc_subst_dose % args)
369
370 sql_create_vaccines.append(_SQL_link_dose2vacc_prod % args)
371
372
373
374
375
376
377
378
379
380
381
382
383 sql_create_vaccines.append(_SQL_create_vaccine % args)
384
385
386 sql = _SQL_create_generic_vaccines_script % (
387 gmTools.bool2subst (
388 include_indications_mapping,
389 _SQL_create_indications_mapping_table,
390 '-- indications mapping table not included'
391 ),
392 '\n\n'.join(sql_create_substances),
393 '\n\n'.join(sql_create_vaccines),
394 gmTools.bool2subst (
395 include_indications_mapping,
396 '\n\n'.join(sql_populate_ind2subst_map),
397 '-- indications mapping table not populated'
398 ),
399 version,
400 version
401 )
402 return sql
403
404
405
406
407 _sql_fetch_vaccine = """SELECT * FROM ref.v_vaccines WHERE %s"""
408
409 -class cVaccine(gmBusinessDBObject.cBusinessDBObject):
410 """Represents one vaccine."""
411
412 _cmd_fetch_payload = _sql_fetch_vaccine % "pk_vaccine = %s"
413
414 _cmds_store_payload = [
415 """UPDATE ref.vaccine SET
416 --id_route = %(pk_route)s,
417 is_live = %(is_live)s,
418 min_age = %(min_age)s,
419 max_age = %(max_age)s,
420 comment = gm.nullify_empty_string(%(comment)s),
421 fk_drug_product = %(pk_drug_product)s
422 WHERE
423 pk = %(pk_vaccine)s
424 AND
425 xmin = %(xmin_vaccine)s
426 RETURNING
427 xmin as xmin_vaccine
428 """
429 ]
430
431 _updatable_fields = [
432
433 'is_live',
434 'min_age',
435 'max_age',
436 'comment',
437 'pk_drug_product'
438 ]
439
440
471
472
473
474
477
478 product = property(_get_product, lambda x:x)
479
480
482 cmd = 'SELECT EXISTS(SELECT 1 FROM clin.vaccination WHERE fk_vaccine = %(pk)s)'
483 args = {'pk': self._payload[self._idx['pk_vaccine']]}
484 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
485 return rows[0][0]
486
487 is_in_use = property(_get_is_in_use, lambda x:x)
488
489
490 -def create_vaccine(pk_drug_product=None, product_name=None, indications=None):
491
492 conn = gmPG2.get_connection(readonly = False)
493
494 if pk_drug_product is None:
495
496 prep = 'vaccine'
497 _log.debug('creating vaccine drug product [%s %s]', product_name, prep)
498 vacc_prod = gmMedication.create_drug_product (
499 product_name = product_name,
500 preparation = prep,
501 return_existing = True,
502
503 doses = indications,
504 link_obj = conn
505 )
506
507 vacc_prod['atc'] = 'J07'
508 vacc_prod.save(conn = conn)
509 pk_drug_product = vacc_prod['pk_drug_product']
510
511 cmd = 'INSERT INTO ref.vaccine (fk_drug_product) values (%(pk_drug_product)s) RETURNING pk'
512 queries = [{'cmd': cmd, 'args': {'pk_drug_product': pk_drug_product}}]
513 rows, idx = gmPG2.run_rw_queries(link_obj = conn, queries = queries, get_col_idx = False, return_data = True, end_tx = True)
514 conn.close()
515 return cVaccine(aPK_obj = rows[0]['pk'])
516
517
519
520 cmd = 'DELETE FROM ref.vaccine WHERE pk = %(pk)s'
521 args = {'pk': vaccine}
522
523 try:
524 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
525 except gmPG2.dbapi.IntegrityError:
526 _log.exception('cannot delete vaccine [%s]', vaccine)
527 return False
528
529 return True
530
531
542
543
544
545
546 sql_fetch_vaccination = """SELECT * FROM clin.v_vaccinations WHERE %s"""
547
549
550 _cmd_fetch_payload = sql_fetch_vaccination % "pk_vaccination = %s"
551
552 _cmds_store_payload = [
553 """UPDATE clin.vaccination SET
554 soap_cat = %(soap_cat)s,
555 clin_when = %(date_given)s,
556 site = gm.nullify_empty_string(%(site)s),
557 batch_no = gm.nullify_empty_string(%(batch_no)s),
558 reaction = gm.nullify_empty_string(%(reaction)s),
559 narrative = gm.nullify_empty_string(%(comment)s),
560 fk_vaccine = %(pk_vaccine)s,
561 fk_provider = %(pk_provider)s,
562 fk_encounter = %(pk_encounter)s,
563 fk_episode = %(pk_episode)s
564 WHERE
565 pk = %(pk_vaccination)s
566 AND
567 xmin = %(xmin_vaccination)s
568 RETURNING
569 xmin as xmin_vaccination
570 """
571 ]
572
573 _updatable_fields = [
574 'soap_cat',
575 'date_given',
576 'site',
577 'batch_no',
578 'reaction',
579 'comment',
580 'pk_vaccine',
581 'pk_provider',
582 'pk_encounter',
583 'pk_episode'
584 ]
585
586
594
595
619
620
622 return cVaccine(aPK_obj = self._payload[self._idx['pk_vaccine']])
623
624 vaccine = property(_get_vaccine, lambda x:x)
625
626
628
629 cmd = """
630 INSERT INTO clin.vaccination (
631 fk_encounter,
632 fk_episode,
633 fk_vaccine,
634 batch_no
635 ) VALUES (
636 %(enc)s,
637 %(epi)s,
638 %(vacc)s,
639 %(batch)s
640 ) RETURNING pk;
641 """
642 args = {
643 'enc': encounter,
644 'epi': episode,
645 'vacc': vaccine,
646 'batch': batch_no
647 }
648
649 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
650
651 return cVaccination(aPK_obj = rows[0][0])
652
653
655 cmd = """DELETE FROM clin.vaccination WHERE pk = %(pk)s"""
656 args = {'pk': vaccination}
657
658 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
659
660
661
674
675
676
729
730
731
732
733 if __name__ == '__main__':
734
735 if len(sys.argv) < 2:
736 sys.exit()
737
738 if sys.argv[1] != 'test':
739 sys.exit()
740
741
742
750
751
753
754 pk_args = {
755 'pat_id': 12,
756 'indication': 'meningococcus C',
757 'seq_no': 1
758 }
759 missing_vacc = cMissingVaccination(aPK_obj=pk_args)
760 fields = missing_vacc.get_fields()
761 print("\nDue vaccination:")
762 print(missing_vacc)
763 for field in fields:
764 print(field, ':', missing_vacc[field])
765
766 pk_args = {
767 'pat_id': 12,
768 'indication': 'haemophilus influenzae b',
769 'seq_no': 2
770 }
771 missing_vacc = cMissingVaccination(aPK_obj=pk_args)
772 fields = missing_vacc.get_fields()
773 print("\nOverdue vaccination (?):")
774 print(missing_vacc)
775 for field in fields:
776 print(field, ':', missing_vacc[field])
777
778
780 pk_args = {
781 'pat_id': 12,
782 'indication': 'tetanus'
783 }
784 missing_booster = cMissingBooster(aPK_obj=pk_args)
785 fields = missing_booster.get_fields()
786 print("\nDue booster:")
787 print(missing_booster)
788 for field in fields:
789 print(field, ':', missing_booster[field])
790
791
793 scheduled_vacc = cScheduledVaccination(aPK_obj=20)
794 print("\nScheduled vaccination:")
795 print(scheduled_vacc)
796 fields = scheduled_vacc.get_fields()
797 for field in fields:
798 print(field, ':', scheduled_vacc[field])
799 print("updatable:", scheduled_vacc.get_updatable_fields())
800
801
803 vaccination_course = cVaccinationCourse(aPK_obj=7)
804 print("\nVaccination course:")
805 print(vaccination_course)
806 fields = vaccination_course.get_fields()
807 for field in fields:
808 print(field, ':', vaccination_course[field])
809 print("updatable:", vaccination_course.get_updatable_fields())
810
811
813 result, msg = put_patient_on_schedule(patient_id=12, course_id=1)
814 print('\nPutting patient id 12 on schedule id 1... %s (%s)' % (result, msg))
815
816
822
823
827
828
831
832
839
840
841
842
843
844
845
846
847
848
849
850
851 test_write_generic_vaccine_sql(sys.argv[2], sys.argv[3])
852