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 URL_vaccine_adr_german_default = 'https://nebenwirkungen.pei.de'
32
33
34 _SQL_create_substance4vaccine = """-- in case <%(substance_tag)s> already exists: add ATC
35 UPDATE ref.substance SET atc = '%(atc)s' WHERE lower(description) = lower('%(desc)s') AND atc IS NULL;
36
37 INSERT INTO ref.substance (description, atc)
38 SELECT
39 '%(desc)s',
40 '%(atc)s'
41 WHERE NOT EXISTS (
42 SELECT 1 FROM ref.substance WHERE
43 atc = '%(atc)s'
44 AND
45 description = '%(desc)s'
46 );
47
48 -- generic English
49 SELECT i18n.upd_tx('en', '%(orig)s', '%(trans)s');
50 -- user language, if any, fails if not set
51 SELECT i18n.upd_tx('%(orig)s', '%(trans)s');"""
52
53 _SQL_map_indication2substance = """-- old-style "%(v21_ind)s" => "%(desc)s"
54 INSERT INTO staging.lnk_vacc_ind2subst_dose (fk_indication, fk_dose, is_live)
55 SELECT
56 (SELECT id FROM ref.vacc_indication WHERE description = '%(v21_ind)s'),
57 (SELECT pk_dose FROM ref.v_substance_doses WHERE
58 amount = 1
59 AND
60 unit = 'dose'
61 AND
62 dose_unit = 'shot'
63 AND
64 substance = '%(desc)s'
65 ),
66 %(is_live)s
67 WHERE EXISTS (
68 SELECT 1 FROM ref.vacc_indication WHERE description = '%(v21_ind)s'
69 );"""
70
71 _SQL_create_vacc_product = """-- --------------------------------------------------------------
72 -- in case <%(prod_name)s> exists: add ATC
73 UPDATE ref.drug_product SET atc_code = '%(atc_prod)s' WHERE
74 atc_code IS NULL
75 AND
76 description = '%(prod_name)s'
77 AND
78 preparation = '%(prep)s'
79 AND
80 is_fake IS TRUE;
81
82 INSERT INTO ref.drug_product (description, preparation, is_fake, atc_code)
83 SELECT
84 '%(prod_name)s',
85 '%(prep)s',
86 TRUE,
87 '%(atc_prod)s'
88 WHERE NOT EXISTS (
89 SELECT 1 FROM ref.drug_product WHERE
90 description = '%(prod_name)s'
91 AND
92 preparation = '%(prep)s'
93 AND
94 is_fake = TRUE
95 AND
96 atc_code = '%(atc_prod)s'
97 );"""
98
99 _SQL_create_vaccine = """-- add vaccine if necessary
100 INSERT INTO ref.vaccine (is_live, fk_drug_product)
101 SELECT
102 %(is_live)s,
103 (SELECT pk FROM ref.drug_product WHERE
104 description = '%(prod_name)s'
105 AND
106 preparation = '%(prep)s'
107 AND
108 is_fake = TRUE
109 AND
110 atc_code = '%(atc_prod)s'
111 )
112 WHERE NOT EXISTS (
113 SELECT 1 FROM ref.vaccine WHERE
114 is_live IS %(is_live)s
115 AND
116 fk_drug_product = (
117 SELECT pk FROM ref.drug_product WHERE
118 description = '%(prod_name)s'
119 AND
120 preparation = '%(prep)s'
121 AND
122 is_fake = TRUE
123 AND
124 atc_code = '%(atc_prod)s'
125 )
126 );"""
127
128 _SQL_create_vacc_subst_dose = """-- create dose, assumes substance exists
129 INSERT INTO ref.dose (fk_substance, amount, unit, dose_unit)
130 SELECT
131 (SELECT pk FROM ref.substance WHERE atc = '%(atc_subst)s' AND description = '%(name_subst)s' LIMIT 1),
132 1,
133 'dose',
134 'shot'
135 WHERE NOT EXISTS (
136 SELECT 1 FROM ref.dose WHERE
137 fk_substance = (SELECT pk FROM ref.substance WHERE atc = '%(atc_subst)s' AND description = '%(name_subst)s' LIMIT 1)
138 AND
139 amount = 1
140 AND
141 unit = 'dose'
142 AND
143 dose_unit IS NOT DISTINCT FROM 'shot'
144 );"""
145
146 _SQL_link_dose2vacc_prod = """-- link dose to product
147 INSERT INTO ref.lnk_dose2drug (fk_dose, fk_drug_product)
148 SELECT
149 (SELECT pk from ref.dose WHERE
150 fk_substance = (SELECT pk FROM ref.substance WHERE atc = '%(atc_subst)s' AND description = '%(name_subst)s' LIMIT 1)
151 AND
152 amount = 1
153 AND
154 unit = 'dose'
155 AND
156 dose_unit IS NOT DISTINCT FROM 'shot'
157 ),
158 (SELECT pk FROM ref.drug_product WHERE
159 description = '%(prod_name)s'
160 AND
161 preparation = '%(prep)s'
162 AND
163 is_fake = TRUE
164 AND
165 atc_code = '%(atc_prod)s'
166 )
167 WHERE NOT EXISTS (
168 SELECT 1 FROM ref.lnk_dose2drug WHERE
169 fk_dose = (
170 SELECT PK from ref.dose WHERE
171 fk_substance = (SELECT pk FROM ref.substance WHERE atc = '%(atc_subst)s' AND description = '%(name_subst)s' LIMIT 1)
172 AND
173 amount = 1
174 AND
175 unit = 'dose'
176 AND
177 dose_unit IS NOT DISTINCT FROM 'shot'
178 )
179 AND
180 fk_drug_product = (
181 SELECT pk FROM ref.drug_product WHERE
182 description = '%(prod_name)s'
183 AND
184 preparation = '%(prep)s'
185 AND
186 is_fake = TRUE
187 AND
188 atc_code = '%(atc_prod)s'
189 )
190 );"""
191
192 _SQL_create_indications_mapping_table = """-- set up helper table for conversion of vaccines from using
193 -- linked indications to using linked substances,
194 -- to be dropped after converting vaccines
195 DROP TABLE IF EXISTS staging.lnk_vacc_ind2subst_dose CASCADE;
196
197 CREATE UNLOGGED TABLE staging.lnk_vacc_ind2subst_dose (
198 fk_indication INTEGER
199 NOT NULL
200 REFERENCES ref.vacc_indication(id)
201 ON UPDATE CASCADE
202 ON DELETE RESTRICT,
203 fk_dose INTEGER
204 NOT NULL
205 REFERENCES ref.dose(pk)
206 ON UPDATE CASCADE
207 ON DELETE RESTRICT,
208 is_live
209 BOOLEAN
210 NOT NULL
211 DEFAULT false,
212 UNIQUE(fk_indication, fk_dose),
213 UNIQUE(fk_indication, is_live)
214 );
215
216
217 DROP VIEW IF EXISTS staging.v_lnk_vacc_ind2subst_dose CASCADE;
218
219 CREATE VIEW staging.v_lnk_vacc_ind2subst_dose AS
220 SELECT
221 s_lvi2sd.is_live
222 as mapping_is_for_live_vaccines,
223 r_vi.id
224 as pk_indication,
225 r_vi.description
226 as indication,
227 r_vi.atcs_single_indication,
228 r_vi.atcs_combi_indication,
229 r_d.pk
230 as pk_dose,
231 r_d.amount,
232 r_d.unit,
233 r_d.dose_unit,
234 r_s.pk
235 as pk_substance,
236 r_s.description
237 as substance,
238 r_s.atc
239 as atc_substance
240 FROM
241 staging.lnk_vacc_ind2subst_dose s_lvi2sd
242 inner join ref.vacc_indication r_vi on (r_vi.id = s_lvi2sd.fk_indication)
243 inner join ref.dose r_d on (r_d.pk = s_lvi2sd.fk_dose)
244 inner join ref.substance r_s on (r_s.pk = r_d.fk_substance)
245 ;"""
246
247 _SQL_create_generic_vaccines_script = """-- ==============================================================
248 -- GNUmed database schema change script
249 --
250 -- License: GPL v2 or later
251 -- Author: karsten.hilbert@gmx.net
252 --
253 -- THIS IS A GENERATED FILE. DO NOT EDIT.
254 --
255 -- ==============================================================
256 \set ON_ERROR_STOP 1
257 --set default_transaction_read_only to off;
258
259 -- --------------------------------------------------------------
260 -- indications mapping helper table
261 -- --------------------------------------------------------------
262 %s
263
264 -- --------------------------------------------------------------
265 -- generic vaccine "substances" (= indications)
266 -- --------------------------------------------------------------
267 %s
268
269 -- --------------------------------------------------------------
270 -- generic vaccines
271 -- --------------------------------------------------------------
272 -- new-style vaccines are not linked to indications, so drop
273 -- trigger asserting that condition,
274 DROP FUNCTION IF EXISTS clin.trf_sanity_check_vaccine_has_indications() CASCADE;
275
276
277 -- need to disable trigger before running
278 ALTER TABLE ref.drug_product
279 DISABLE TRIGGER tr_assert_product_has_components
280 ;
281
282 %s
283
284 -- want to re-enable trigger as now all inserted
285 -- vaccines satisfy the conditions
286 ALTER TABLE ref.drug_product
287 ENABLE TRIGGER tr_assert_product_has_components
288 ;
289
290 -- --------------------------------------------------------------
291 -- indications mapping data
292 -- --------------------------------------------------------------
293 -- map old style
294 -- (clin|ref).vacc_indication.description
295 -- to new style
296 -- ref.v_substance_doses.substance
297
298 %s
299
300 -- --------------------------------------------------------------
301 select gm.log_script_insertion('v%s-ref-create_generic_vaccines.sql', '%s');
302 """
303
304
316
317
319
320 _log.debug('including indications mapping table with generic vaccines creation SQL: %s', include_indications_mapping)
321
322 from Gnumed.business import gmVaccDefs
323
324 sql_create_substances = []
325 sql_populate_ind2subst_map = []
326 sql_create_vaccines = []
327
328 for substance_tag in gmVaccDefs._VACCINE_SUBSTANCES:
329 subst = gmVaccDefs._VACCINE_SUBSTANCES[substance_tag]
330 args = {
331 'substance_tag': substance_tag,
332 'atc': subst['atc4target'],
333 'desc': subst['name'],
334 'orig': subst['target'].split('::')[0],
335 'trans': subst['target'].split('::')[-1]
336 }
337 sql_create_substances.append(_SQL_create_substance4vaccine % args)
338 try:
339 for v21_ind in subst['v21_indications']:
340 args['v21_ind'] = v21_ind
341 args['is_live'] = 'false'
342 sql_populate_ind2subst_map.append(_SQL_map_indication2substance % args)
343 except KeyError:
344 pass
345 try:
346 for v21_ind in subst['v21_indications_live']:
347 args['v21_ind'] = v21_ind
348 args['is_live'] = 'true'
349 sql_populate_ind2subst_map.append(_SQL_map_indication2substance % args)
350 except KeyError:
351 pass
352 args = {}
353
354 for key in gmVaccDefs._GENERIC_VACCINES:
355 vaccine_def = gmVaccDefs._GENERIC_VACCINES[key]
356
357 args = {
358 'atc_prod': vaccine_def['atc'],
359 'prod_name': vaccine_def['name'],
360
361 'prep': 'vaccine',
362 'is_live': vaccine_def['live']
363 }
364 sql_create_vaccines.append(_SQL_create_vacc_product % args)
365
366 for ingredient_tag in vaccine_def['ingredients']:
367 vacc_subst_def = gmVaccDefs._VACCINE_SUBSTANCES[ingredient_tag]
368 args['atc_subst'] = vacc_subst_def['atc4target']
369 args['name_subst'] = vacc_subst_def['name']
370
371 sql_create_vaccines.append(_SQL_create_vacc_subst_dose % args)
372
373 sql_create_vaccines.append(_SQL_link_dose2vacc_prod % args)
374
375
376
377
378
379
380
381
382
383
384
385
386 sql_create_vaccines.append(_SQL_create_vaccine % args)
387
388
389 sql = _SQL_create_generic_vaccines_script % (
390 gmTools.bool2subst (
391 include_indications_mapping,
392 _SQL_create_indications_mapping_table,
393 '-- indications mapping table not included'
394 ),
395 '\n\n'.join(sql_create_substances),
396 '\n\n'.join(sql_create_vaccines),
397 gmTools.bool2subst (
398 include_indications_mapping,
399 '\n\n'.join(sql_populate_ind2subst_map),
400 '-- indications mapping table not populated'
401 ),
402 version,
403 version
404 )
405 return sql
406
407
408
409
410 _SQL_get_vaccine_fields = """SELECT * FROM ref.v_vaccines WHERE %s"""
411
412 -class cVaccine(gmBusinessDBObject.cBusinessDBObject):
413 """Represents one vaccine."""
414
415 _cmd_fetch_payload = _SQL_get_vaccine_fields % "pk_vaccine = %s"
416
417 _cmds_store_payload = [
418 """UPDATE ref.vaccine SET
419 --id_route = %(pk_route)s,
420 is_live = %(is_live)s,
421 min_age = %(min_age)s,
422 max_age = %(max_age)s,
423 comment = gm.nullify_empty_string(%(comment)s),
424 fk_drug_product = %(pk_drug_product)s
425 WHERE
426 pk = %(pk_vaccine)s
427 AND
428 xmin = %(xmin_vaccine)s
429 RETURNING
430 xmin as xmin_vaccine
431 """
432 ]
433
434 _updatable_fields = [
435
436 'is_live',
437 'min_age',
438 'max_age',
439 'comment',
440 'pk_drug_product'
441 ]
442
443
474
475
476
477
480
481 product = property(_get_product, lambda x:x)
482
483
485 cmd = 'SELECT EXISTS(SELECT 1 FROM clin.vaccination WHERE fk_vaccine = %(pk)s)'
486 args = {'pk': self._payload[self._idx['pk_vaccine']]}
487 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
488 return rows[0][0]
489
490 is_in_use = property(_get_is_in_use, lambda x:x)
491
492
493 -def create_vaccine(pk_drug_product=None, product_name=None, indications=None, is_live=None):
494
495 assert (is_live is not None), '<is_live> must not be <None>'
496
497 conn = gmPG2.get_connection(readonly = False)
498 if pk_drug_product is None:
499
500 prep = 'vaccine'
501 _log.debug('creating vaccine drug product [%s %s]', product_name, prep)
502 vacc_prod = gmMedication.create_drug_product (
503 product_name = product_name,
504 preparation = prep,
505 return_existing = True,
506
507 doses = indications,
508 link_obj = conn
509 )
510
511 vacc_prod['atc'] = 'J07'
512 vacc_prod.save(conn = conn)
513 pk_drug_product = vacc_prod['pk_drug_product']
514 cmd = 'INSERT INTO ref.vaccine (fk_drug_product, is_live) values (%(pk_drug_product)s, %(live)s) RETURNING pk'
515 queries = [{'cmd': cmd, 'args': {'pk_drug_product': pk_drug_product, 'live': is_live}}]
516 rows, idx = gmPG2.run_rw_queries(link_obj = conn, queries = queries, get_col_idx = False, return_data = True, end_tx = True)
517 conn.close()
518 return cVaccine(aPK_obj = rows[0]['pk'])
519
520
522
523 cmd = 'DELETE FROM ref.vaccine WHERE pk = %(pk)s'
524 args = {'pk': vaccine}
525
526 try:
527 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
528 except gmPG2.dbapi.IntegrityError:
529 _log.exception('cannot delete vaccine [%s]', vaccine)
530 return False
531
532 return True
533
534
546
547
548
549
550 _SQL_get_vaccination_fields = """SELECT * FROM clin.v_vaccinations WHERE %s"""
551
553
554 _cmd_fetch_payload = _SQL_get_vaccination_fields % "pk_vaccination = %s"
555
556 _cmds_store_payload = [
557 """UPDATE clin.vaccination SET
558 soap_cat = %(soap_cat)s,
559 clin_when = %(date_given)s,
560 site = gm.nullify_empty_string(%(site)s),
561 batch_no = gm.nullify_empty_string(%(batch_no)s),
562 reaction = gm.nullify_empty_string(%(reaction)s),
563 narrative = gm.nullify_empty_string(%(comment)s),
564 fk_vaccine = %(pk_vaccine)s,
565 fk_provider = %(pk_provider)s,
566 fk_encounter = %(pk_encounter)s,
567 fk_episode = %(pk_episode)s
568 WHERE
569 pk = %(pk_vaccination)s
570 AND
571 xmin = %(xmin_vaccination)s
572 RETURNING
573 xmin as xmin_vaccination
574 """
575 ]
576
577 _updatable_fields = [
578 'soap_cat',
579 'date_given',
580 'site',
581 'batch_no',
582 'reaction',
583 'comment',
584 'pk_vaccine',
585 'pk_provider',
586 'pk_encounter',
587 'pk_episode'
588 ]
589
590
598
599
623
624
626 return cVaccine(aPK_obj = self._payload[self._idx['pk_vaccine']])
627
628 vaccine = property(_get_vaccine, lambda x:x)
629
630
631 -def get_vaccinations(pk_identity=None, pk_episodes=None, pk_health_issues=None, pk_encounters=None, order_by=None, return_pks=False):
632
633 args = {}
634 where_parts = []
635
636 if pk_identity is not None:
637 args = {'pk_identity': pk_identity}
638 where_parts.append('pk_patient = %(pk_identity)s')
639
640 if (pk_episodes is not None) and (len(pk_episodes) > 0):
641 where_parts.append('pk_episode IN %(pk_epis)s')
642 args['pk_epis'] = tuple(pk_episodes)
643
644 if (pk_health_issues is not None) and (len(pk_health_issues) > 0):
645 where_parts.append('pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(pk_issues)s)')
646 args['pk_issues'] = tuple(pk_health_issues)
647
648 if (pk_encounters is not None) and (len(pk_encounters) > 0):
649 where_parts.append('pk_encounter IN %(pk_encs)s')
650 args['pk_encs'] = tuple(pk_encounters)
651
652 ORDER_BY = gmTools.coalesce (
653 value2test = order_by,
654 return_instead = '',
655 value2return = 'ORDER BY %s' % order_by
656 )
657 if len(where_parts) == 0:
658 WHERE = 'True'
659 else:
660 WHERE = '\nAND '.join(where_parts)
661
662 SQL = '%s %s' % (
663 _SQL_get_vaccination_fields % WHERE,
664 ORDER_BY
665 )
666 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': SQL, 'args': args}], get_col_idx = True)
667 if return_pks:
668 return [ r['pk_vaccination'] for r in rows ]
669 vaccs = [ cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
670 return vaccs
671
672
674
675 cmd = """
676 INSERT INTO clin.vaccination (
677 fk_encounter,
678 fk_episode,
679 fk_vaccine,
680 batch_no
681 ) VALUES (
682 %(enc)s,
683 %(epi)s,
684 %(vacc)s,
685 %(batch)s
686 ) RETURNING pk;
687 """
688 args = {
689 'enc': encounter,
690 'epi': episode,
691 'vacc': vaccine,
692 'batch': batch_no
693 }
694
695 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False, return_data = True)
696
697 return cVaccination(aPK_obj = rows[0][0])
698
699
701 cmd = """DELETE FROM clin.vaccination WHERE pk = %(pk)s"""
702 args = {'pk': vaccination}
703
704 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
705
706
707
720
721
722
774
775
776
777
778 if __name__ == '__main__':
779
780 if len(sys.argv) < 2:
781 sys.exit()
782
783 if sys.argv[1] != 'test':
784 sys.exit()
785
786
787
795
796
798
799 pk_args = {
800 'pat_id': 12,
801 'indication': 'meningococcus C',
802 'seq_no': 1
803 }
804 missing_vacc = cMissingVaccination(aPK_obj=pk_args)
805 fields = missing_vacc.get_fields()
806 print("\nDue vaccination:")
807 print(missing_vacc)
808 for field in fields:
809 print(field, ':', missing_vacc[field])
810
811 pk_args = {
812 'pat_id': 12,
813 'indication': 'haemophilus influenzae b',
814 'seq_no': 2
815 }
816 missing_vacc = cMissingVaccination(aPK_obj=pk_args)
817 fields = missing_vacc.get_fields()
818 print("\nOverdue vaccination (?):")
819 print(missing_vacc)
820 for field in fields:
821 print(field, ':', missing_vacc[field])
822
823
825 pk_args = {
826 'pat_id': 12,
827 'indication': 'tetanus'
828 }
829 missing_booster = cMissingBooster(aPK_obj=pk_args)
830 fields = missing_booster.get_fields()
831 print("\nDue booster:")
832 print(missing_booster)
833 for field in fields:
834 print(field, ':', missing_booster[field])
835
836
838 scheduled_vacc = cScheduledVaccination(aPK_obj=20)
839 print("\nScheduled vaccination:")
840 print(scheduled_vacc)
841 fields = scheduled_vacc.get_fields()
842 for field in fields:
843 print(field, ':', scheduled_vacc[field])
844 print("updatable:", scheduled_vacc.get_updatable_fields())
845
846
848 vaccination_course = cVaccinationCourse(aPK_obj=7)
849 print("\nVaccination course:")
850 print(vaccination_course)
851 fields = vaccination_course.get_fields()
852 for field in fields:
853 print(field, ':', vaccination_course[field])
854 print("updatable:", vaccination_course.get_updatable_fields())
855
856
858 result, msg = put_patient_on_schedule(patient_id=12, course_id=1)
859 print('\nPutting patient id 12 on schedule id 1... %s (%s)' % (result, msg))
860
861
867
868
870 v1 = get_vaccinations(return_pks = True, order_by = 'date_given')
871 print(v1)
872
873
876
877
884
885
886
887
888
889
890
891
892
893
894 test_get_vaccinations()
895
896
897