File: v21-ref-v_auto_hints.sql

package info (click to toggle)
gnumed-server 22.31-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 46,268 kB
  • sloc: sql: 1,217,633; python: 15,878; sh: 1,590; makefile: 20
file content (405 lines) | stat: -rw-r--r-- 13,959 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
-- ==============================================================
-- GNUmed database schema change script
--
-- License: GPL v2 or later
-- Author: karsten.hilbert@gmx.net
--
-- ==============================================================
\set ON_ERROR_STOP 1

set check_function_bodies to on;
--set default_transaction_read_only to off;

-- --------------------------------------------------------------
alter table ref.auto_hint
	drop constraint if exists ref_auto_hint_sane_rec_query;

alter table ref.auto_hint
	add constraint ref_auto_hint_sane_rec_query check (
		gm.is_null_or_non_empty_string(recommendation_query)
	);

-- --------------------------------------------------------------
drop view if exists ref.v_auto_hints cascade;

create view ref.v_auto_hints as
select
	pk
		as pk_auto_hint,
	query
		as query,
	recommendation_query
		as recommendation_query,
	title
		as title,
	hint
		as hint,
	url
		as url,
	is_active
		as is_active,
	source
		as source,
	lang
		as lang,
	-- this column is set from clin.get_hints_for_patient(),
	-- it only exists in this view in order to enable the syntax
	-- "returns setof ref.v_auto_hints" in that function
	null::text
		as rationale4suppression,
	-- this column is set from clin.get_hints_for_patient(),
	-- it only exists in this view in order to enable the syntax
	-- "returns setof ref.v_auto_hints" in that function
	null::text
		as recommendation,
	md5(
		coalesce(query, '')
		|| coalesce(recommendation_query, '')
		|| coalesce(title, '')
		|| coalesce(hint, '')
		|| coalesce(url, '')
	)	as md5_sum,
	xmin
		as xmin_auto_hint
from
	ref.auto_hint
;


revoke all on ref.v_auto_hints from public;
grant select on ref.v_auto_hints to group "gm-staff";


-- there is no need to update existing suppressions because initially
-- all ref.auto_hint.recommendation_query's will be NULL and thusly
-- add an empty string to the md5 summing input which does not change
-- the md5 sum ...

-- --------------------------------------------------------------
drop function if exists clin.get_hints_for_patient(integer) cascade;

create function clin.get_hints_for_patient(integer)
	returns setof ref.v_auto_hints
	language 'plpgsql'
	as '
DECLARE
	_pk_identity ALIAS FOR $1;
	_hint ref.v_auto_hints%rowtype;
	_query text;
	_md5_suppressed text;
	_rationale4suppression text;
	_suppression_exists boolean;		-- does not mean that the suppression applies
	_hint_currently_applies boolean;	-- regardless of whether suppressed or not
	_hint_recommendation text;
--	_exc_state text;
--	_exc_msg text;
--	_exc_detail text;
--	_exc_hint text;
--	_exc_context text;
BEGIN
	-- loop over all defined hints
	FOR _hint IN SELECT * FROM ref.v_auto_hints WHERE is_active LOOP

		-- is the hint suppressed ?
		SELECT
			md5_sum,
			rationale
				INTO
			_md5_suppressed,
			_rationale4suppression
		FROM clin.suppressed_hint WHERE
			fk_hint = _hint.pk_auto_hint
				AND
			fk_encounter IN (
				SELECT pk FROM clin.encounter WHERE fk_patient = _pk_identity
			);
		IF FOUND THEN
			_suppression_exists := TRUE;
		ELSE
			_suppression_exists := FALSE;
		END IF;

		-- does the hint currently apply ?
		_query := replace(_hint.query, ''ID_ACTIVE_PATIENT'', _pk_identity::text);
		BEGIN
			EXECUTE _query INTO STRICT _hint_currently_applies;
		EXCEPTION
			--WHEN insufficient_privilege THEN RAISE WARNING ''auto hint query failed: %'', _query;
			WHEN others THEN
				RAISE WARNING ''auto hint query failed: %'', _query;
				-- only available starting with PG 9.2:
				--GET STACKED DIAGNOSTICS
				--	_exc_state = RETURNED_SQLSTATE,
				--	_exc_msg = MESSAGE_TEXT,
				--	_exc_detail = PG_EXCEPTION_DETAIL,
				--	_exc_hint = PG_EXCEPTION_HINT,
				--	_exc_context = PG_EXCEPTION_CONTEXT;
				--RAISE WARNING ''SQL STATE: %'', _exc_state;
				--RAISE WARNING ''MESSAGE: %'', _exc_msg;
				--RAISE WARNING ''DETAIL: %'', _exc_detail;
				--RAISE WARNING ''HINT: %'', _exc_hint;
				--RAISE WARNING ''CONTEXT: %'', _exc_context;
				-- workaround for 9.1:
				RAISE WARNING ''SQL STATE: %'', SQLSTATE;
				RAISE WARNING ''MESSAGE: %'', SQLERRM;
				_hint.title := ''ERROR checking for ['' || _hint.title || ''] !'';
				_hint.hint := _query;
				RETURN NEXT _hint;
				-- process next hint
				CONTINUE;
		END;

		IF _suppression_exists THEN
			-- is the hint definition still the same as at the time of suppression ?
			IF _md5_suppressed = _hint.md5_sum THEN
				-- yes, but does this hint currently apply ?
				IF _hint_currently_applies THEN
					-- suppressed, suppression valid, and hint applies: skip this hint
					CONTINUE;
				END IF;
				-- suppressed, suppression valid, hint does NOT apply:
				-- skip but invalidate suppression, because:
				-- * previously the hint applied and the user suppressed it,
				-- * then the patient changed such that the hint does not
				--    apply anymore (but the suppression is still valid),
				-- * when the patient changes again, the hint might apply again
				-- * HOWEVER - since the suppression would still be valid - the
				--   hint would magically get suppressed again (which is
				--   medically unsafe) ...
				-- after invalidation, the hint will no longer be suppressed,
				-- however - since it does not currently apply it - it will
				-- still not be returned until it applies again ...
				--
				-- -----------------------------------------------------------------------
				-- UNFORTUNATELY, the following is currently not _possible_ because
				-- we are running inside a READONLY transaction (due to inherent
				-- security risks when running arbitrary user queries [IOW the hint
				-- SQL] against the database) and we cannot execute a
				-- sub-transaction as READWRITE :-/
				--
				--UPDATE clin.suppressed_hint
				--SET md5_sum = ''invalidated''::text		-- will not ever match any md5 sum
				--WHERE
				--	fk_encounter IN (
				--		SELECT pk FROM clin.encounter WHERE fk_patient = _pk_identity
				--	)
				--		AND
				--	fk_hint = _hint.pk_auto_hint;
				-- -----------------------------------------------------------------------
				--
				-- hence our our workaround is to, indeed, return the hint but
				-- tag it with a magic rationale, by means of which the client
				-- can detect it to be in need of invalidation:
				_hint.title := ''HINT DOES NOT APPLY BUT NEEDS INVALIDATION OF EXISTING SUPPRESSION ['' || _hint.title || ''].'';
				_hint.rationale4suppression := ''magic_tag::please_invalidate_suppression'';
				RETURN NEXT _hint;
				CONTINUE;
			END IF;
			-- suppression exists but hint definition must have changed
			-- does the new hint apply ?
			IF _hint_currently_applies THEN
				-- yes: ignore the suppression but provide previous
				-- rationale for suppression to the user
				_hint.rationale4suppression := _rationale4suppression;
				-- retrieve recommendation
				SELECT clin._get_recommendation_for_patient_hint(_hint.recommendation_query, _pk_identity) INTO STRICT _hint_recommendation;
				_hint.recommendation := _hint_recommendation;
				RETURN NEXT _hint;
				CONTINUE;
			END IF;
			-- no, new hint does not apply, so ask for
			-- invalidation of suppression (see above)
			_hint.title := ''HINT DOES NOT APPLY BUT NEEDS INVALIDATION OF EXISTING SUPPRESSION ['' || _hint.title || ''].'';
			_hint.rationale4suppression := ''please_invalidate_suppression'';
			RETURN NEXT _hint;
			CONTINUE;
		END IF;

		-- hint is not suppressed
		-- does the hint currently apply ?
		IF _hint_currently_applies THEN
			-- yes: retrieve recommendation
			SELECT clin._get_recommendation_for_patient_hint(_hint.recommendation_query, _pk_identity) INTO STRICT _hint_recommendation;
			_hint.recommendation := _hint_recommendation;
			RETURN NEXT _hint;
		END IF;
		-- no: ignore it and process next hint in LOOP

	END LOOP;
	RETURN;
END;';

-- --------------------------------------------------------------
drop function if exists clin._get_recommendation_for_patient_hint(text,integer) cascade;

create function clin._get_recommendation_for_patient_hint(text,integer)
	returns text
	language 'plpgsql'
	as '
DECLARE
	_raw_query ALIAS FOR $1;
	_pk_identity ALIAS FOR $2;
	_query text;
	_recommendation text;
BEGIN
	IF _raw_query IS NULL THEN
		RETURN NULL::text;
	END IF;
	_query := replace(_raw_query, ''ID_ACTIVE_PATIENT'', _pk_identity::text);
	BEGIN
		EXECUTE _query INTO STRICT _recommendation;
	EXCEPTION
		--WHEN insufficient_privilege THEN RAISE WARNING ''auto hint recommendation query failed: %'', _query;
		WHEN others THEN
			RAISE WARNING ''auto hint recommendation query failed: %'', _query;
			-- only available starting with PG 9.2:
			--GET STACKED DIAGNOSTICS
			--	_exc_state = RETURNED_SQLSTATE,
			--	_exc_msg = MESSAGE_TEXT,
			--	_exc_detail = PG_EXCEPTION_DETAIL,
			--	_exc_hint = PG_EXCEPTION_HINT,
			--	_exc_context = PG_EXCEPTION_CONTEXT;
			--RAISE WARNING ''SQL STATE: %'', _exc_state;
			--RAISE WARNING ''MESSAGE: %'', _exc_msg;
			--RAISE WARNING ''DETAIL: %'', _exc_detail;
			--RAISE WARNING ''HINT: %'', _exc_hint;
			--RAISE WARNING ''CONTEXT: %'', _exc_context;
			-- workaround for 9.1:
			RAISE WARNING ''SQL STATE: %'', SQLSTATE;
			RAISE WARNING ''MESSAGE: %'', SQLERRM;
			_recommendation := ''ERROR running hint recommendation query ['' || _query || '']'';
	END;
	RETURN _recommendation;
END;';

-- --------------------------------------------------------------
DELETE FROM ref.auto_hint WHERE title = 'Lack of smoking status documentation';

INSERT INTO ref.auto_hint(query, title, hint, source, lang) VALUES (
	'SELECT NOT EXISTS(SELECT 1 FROM clin.v_nonbrand_intakes WHERE pk_patient = ID_ACTIVE_PATIENT AND atc_substance = ''N07BA01'' AND harmful_use_type IS NOT NULL)',
	'Lack of smoking status documentation',
	'There has never been any smoking status recorded for this patient.',
	'AWMF NVL Schädlicher Tabakgebrauch',
	'en'
);


DELETE FROM ref.auto_hint WHERE title = 'Outdated smoking status documentation';

INSERT INTO ref.auto_hint(title, hint, source, lang, query, recommendation_query) VALUES (
	'Outdated smoking status documentation',
	'Smoking status was last recorded more than one year ago for this smoker.',
	'AWMF NVL Schädlicher Tabakgebrauch',
	'en',
	'SELECT EXISTS (
		SELECT 1 FROM clin.v_nonbrand_intakes WHERE
			(pk_patient = ID_ACTIVE_PATIENT)
				AND
			(atc_substance = ''N07BA01'')
				AND
			(coalesce(harmful_use_type, -1) > 0)
				AND
			((discontinued IS NULL) OR (discontinued > now()))
				AND
			(last_checked_when < now() - ''1 year''::interval)
	);',
	'SELECT
		_(''Smoking status'') || E''\n''
		|| '' '' || _(''Last checked:'') || '' '' || to_char(last_checked_when, ''Mon YYYY'')
		|| (case
				when harmful_use_type = 1 then E''\n'' || _(''harmful use'')
				when harmful_use_type = 2 then E''\n'' || _(''addiction'')
				when harmful_use_type = 3 then E''\n'' || _(''previous addiction'')
			end)
		|| coalesce(E''\n '' || _(''Quit date:'') || '' '' || to_char(discontinued, ''YYYY Mon DD''), '''')
		|| coalesce(E''\n '' || _(''Notes:'') || '' '' || notes, '''')
	FROM
		clin.v_nonbrand_intakes
	WHERE pk_patient = ID_ACTIVE_PATIENT;'
);

-- --------------------------------------------------------------
DELETE FROM ref.auto_hint WHERE title = 'Kontraindikation: ACE/Sartan <-> Schwangerschaft';
DELETE FROM ref.auto_hint WHERE title = 'Contraindication: ACEI/Sartan <-> Pregnancy';

insert into ref.auto_hint(query, title, hint, url, source, lang, recommendation_query) values (
	'SELECT EXISTS (
	-- substance check
	SELECT 1 FROM clin.v_substance_intakes WHERE
		pk_patient = ID_ACTIVE_PATIENT
			AND
		-- on Sartan or ACEI
		(
			substance ~* ''.*sartan.*''
				OR
			substance ~* ''.*angiotensin.*''
				OR
			substance ~ ''.*ACE.*''
				OR
			-- might result in false positives (.Prilocarpin)
			substance ~* ''.+pril.*''
				OR
			atc_brand ~* ''^C09.*''
				OR
			atc_substance ~* ''^C09.*''
	)
) AND EXISTS (
	-- pregnancy check
	SELECT 1 FROM clin.patient WHERE
		fk_identity = ID_ACTIVE_PATIENT
			AND
		coalesce(edc BETWEEN now() - ''1 month''::interval AND now() + ''11 months''::interval, FALSE)
);',
	'Contraindication: ACEI/Sartan <-> Pregnancy',
	'ACEI and Sartans can cause severe fetopathies if used during 2nd and 3rd trimenon.',
	'http://www.akdae.de/Arzneimittelsicherheit/Bekanntgaben/Archiv/2010/201010151.pdf',
	'GNUmed default (AkdÄ 2012)',
	'en',
	'SELECT _(''EDC: '') || to_char(edc, ''YYYY Mon DD'') FROM clin.patient WHERE fk_identity = ID_ACTIVE_PATIENT;'
);

-- --------------------------------------------------------------
DELETE FROM ref.auto_hint WHERE title = 'Outdated or questionable EDC';

insert into ref.auto_hint(title, hint, source, lang, query, recommendation_query) values (
	'Outdated or questionable EDC',
	'The EDC documented for this patient is outdated or too far in the future.',
	'GNUmed default',
	'en',
	'SELECT EXISTS (
	SELECT 1 FROM clin.patient WHERE
		fk_identity = ID_ACTIVE_PATIENT
			AND
		coalesce((
			-- longer than 3 months ago
			edc < now() - ''3 months''::interval
				OR
			-- 2 years in the future
			edc > now() + ''2 years''::interval
		), FALSE)
);',
	'SELECT _(''EDC: '') || to_char(edc, ''YYYY Mon DD'') FROM clin.patient WHERE fk_identity = ID_ACTIVE_PATIENT;'
);

-- --------------------------------------------------------------
DELETE FROM ref.auto_hint WHERE title = 'Questionable EDC';

insert into ref.auto_hint(title, hint, source, lang, query, recommendation_query) values (
	'Questionable EDC',
	'There is an EDC documented for this MALE patient.',
	'GNUmed default',
	'en',
	'SELECT (
	SELECT d_i.gender = ''m'' FROM dem.identity d_i WHERE
		d_i.pk = ID_ACTIVE_PATIENT
) AND EXISTS (
	SELECT 1 FROM clin.patient WHERE
		fk_identity = ID_ACTIVE_PATIENT
			AND
		edc IS NOT NULL
);',
	'SELECT _(''EDC: '') || to_char(edc, ''YYYY Mon DD'') FROM clin.patient WHERE fk_identity = ID_ACTIVE_PATIENT;'
);

-- --------------------------------------------------------------
select gm.log_script_insertion('v21-ref-v_auto_hints.sql', '21.0');