[med-svn] [gnumed-server] 01/03: New upstream version 21.13

Andreas Tille tille at debian.org
Sat May 20 17:32:34 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository gnumed-server.

commit a15b5e90dde220e9503722ef9eeed16961a6cc9f
Author: Andreas Tille <tille at debian.org>
Date:   Sat May 20 18:42:18 2017 +0200

    New upstream version 21.13
---
 server/bootstrap/fixup_db-v21.conf                 |   1 +
 server/bootstrap/update_db-v20_v21.conf            |   5 +-
 server/doc/schema/gnumed-entire_schema.html        | 887 ++++++++++++++++-----
 server/pycommon/gmBackendListener.py               |   6 +-
 server/pycommon/gmDateTime.py                      |  15 +-
 server/pycommon/gmDispatcher.py                    |   8 +-
 server/pycommon/gmLog2.py                          |  58 +-
 server/pycommon/gmPG2.py                           |   4 +
 .../v20-v21/dynamic/v21-release_notes-dynamic.sql  |  23 +-
 server/sql/v20-v21/fixups/v21-Constans-TVT-OE.sql  |  52 ++
 10 files changed, 857 insertions(+), 202 deletions(-)

diff --git a/server/bootstrap/fixup_db-v21.conf b/server/bootstrap/fixup_db-v21.conf
index d8739da..54d29cc 100644
--- a/server/bootstrap/fixup_db-v21.conf
+++ b/server/bootstrap/fixup_db-v21.conf
@@ -41,6 +41,7 @@ v21-clin-get_hints_for_patient-fixup.sql
 v21-notifications-dynamic.sql
 v21-clin-uppercase_soap_cat-fixup.sql
 v21-dem-identity-fixup.sql
+v21-Constans-TVT-OE.sql
 $schema$
 
 #----------------------------------
diff --git a/server/bootstrap/update_db-v20_v21.conf b/server/bootstrap/update_db-v20_v21.conf
index f1abb55..1c5a801 100644
--- a/server/bootstrap/update_db-v20_v21.conf
+++ b/server/bootstrap/update_db-v20_v21.conf
@@ -132,6 +132,7 @@ v21-clin-get_hints_for_patient-fixup.sql
 v21-notifications-dynamic.sql
 v21-clin-uppercase_soap_cat-fixup.sql
 v21-dem-identity-fixup.sql
+v21-Constans-TVT-OE.sql
 $schema$
 
 #----------------------------------
@@ -261,9 +262,9 @@ automatic hints::::select count(1) + 9 from ref.auto_hint
 	select count(1) from ref.auto_hint
 suppressed hints::::select count(1) from clin.suppressed_hint
 	select count(1) from clin.suppressed_hint
-raw keyword expansions::::select count(1) + 1 from ref.keyword_expansion
+raw keyword expansions::::select count(1) + 2 from ref.keyword_expansion
 	select count(1) from ref.keyword_expansion
-mapped keyword expansions::::select count(1) + 1 from ref.v_keyword_expansions
+mapped keyword expansions::::select count(1) + 2 from ref.v_keyword_expansions
 	select count(1) from ref.v_keyword_expansions
 organisations::::select count(1) from dem.org
 	select count(1) from dem.org
diff --git a/server/doc/schema/gnumed-entire_schema.html b/server/doc/schema/gnumed-entire_schema.html
index 006f69d..581eaa2 100644
--- a/server/doc/schema/gnumed-entire_schema.html
+++ b/server/doc/schema/gnumed-entire_schema.html
@@ -112,7 +112,7 @@
   <body>
 
     <!-- Primary Index -->
-	<p><br><br>Dumped on 2017-02-10</p>
+	<p><br><br>Dumped on 2017-05-14</p>
 <h1><a name="index">Index of database - gnumed_v21</a></h1>
 <ul>
     
@@ -143,7 +143,7 @@
     
     <li><a name="clin.schema">clin</a></li><ul>
     	<li><a href="gnumed-entire_schema.html#clin.table.-enum-allergy-type">_enum_allergy_type</a></li><li><a href="gnumed-entire_schema.html#clin.table.allergy">allergy</a></li><li><a href="gnumed-entire_schema.html#clin.table.allergy-state">allergy_state</a></li><li><a href="gnumed-entire_schema.html#clin.table.clin-aux-note">clin_aux_note</a></li><li><a href="gnumed-entire_schema.html#clin.table.clin-diag">clin_diag</a></li><li><a href="gnumed-entire_schema.html#clin.table.clin-item-ty [...]
-  	<li><a href="gnumed-entire_schema.html#clin.function.-get-recommendation-for-patient-hint-text-integer">_get_recommendation_for_patient_hint(text, integer)</a></li><li><a href="gnumed-entire_schema.html#clin.function.add-coded-phrase-text-text-text">add_coded_phrase(text, text, text)</a></li><li><a href="gnumed-entire_schema.html#clin.function.f-del-booster-must-have-base-immunity">f_del_booster_must_have_base_immunity()</a></li><li><a href="gnumed-entire_schema.html#clin.function.f-f [...]
+  	<li><a href="gnumed-entire_schema.html#clin.function.-get-recommendation-for-patient-hint-text-integer">_get_recommendation_for_patient_hint(text, integer)</a></li><li><a href="gnumed-entire_schema.html#clin.function.add-coded-phrase-text-text-text">add_coded_phrase(text, text, text)</a></li><li><a href="gnumed-entire_schema.html#clin.function.f-del-booster-must-have-base-immunity">f_del_booster_must_have_base_immunity()</a></li><li><a href="gnumed-entire_schema.html#clin.function.f-f [...]
     </ul>
     
     <li><a name="de-de.schema">de_de</a></li><ul>
@@ -153,7 +153,7 @@
     
     <li><a name="dem.schema">dem</a></li><ul>
     	<li><a href="gnumed-entire_schema.html#dem.table.address">address</a></li><li><a href="gnumed-entire_schema.html#dem.table.address-type">address_type</a></li><li><a href="gnumed-entire_schema.html#dem.table.country">country</a></li><li><a href="gnumed-entire_schema.html#dem.table.enum-comm-types">enum_comm_types</a></li><li><a href="gnumed-entire_schema.html#dem.table.enum-ext-id-types">enum_ext_id_types</a></li><li><a href="gnumed-entire_schema.html#dem.table.gender-label">gender_l [...]
-  	<li><a href="gnumed-entire_schema.html#dem.function.add-external-id-type-text-text">add_external_id_type(text, text)</a></li><li><a href="gnumed-entire_schema.html#dem.function.add-name-integer-text-text-boolean">add_name(integer, text, text, boolean)</a></li><li><a href="gnumed-entire_schema.html#dem.function.address-exists-text-text-text-text-text-text-text">address_exists(text, text, text, text, text, text, text)</a></li><li><a href="gnumed-entire_schema.html#dem.function.create-ad [...]
+  	<li><a href="gnumed-entire_schema.html#dem.function.add-external-id-type-text-text">add_external_id_type(text, text)</a></li><li><a href="gnumed-entire_schema.html#dem.function.add-name-integer-text-text-boolean">add_name(integer, text, text, boolean)</a></li><li><a href="gnumed-entire_schema.html#dem.function.address-exists-text-text-text-text-text-text-text">address_exists(text, text, text, text, text, text, text)</a></li><li><a href="gnumed-entire_schema.html#dem.function.create-ad [...]
     </ul>
     
     <li><a name="gm.schema">gm</a></li><ul>
@@ -182,7 +182,7 @@
     </ul>
     
     <li><a name="staging.schema">staging</a></li><ul>
-    	<li><a href="gnumed-entire_schema.html#staging.table.lab-request">lab_request</a></li><li><a href="gnumed-entire_schema.html#staging.table.loinc-staging">loinc_staging</a></li><li><a href="gnumed-entire_schema.html#staging.table.test-result">test_result</a></li>
+    	<li><a href="gnumed-entire_schema.html#staging.view.journal-without-suppressed-hints">journal_without_suppressed_hints</a></li><li><a href="gnumed-entire_schema.html#staging.table.lab-request">lab_request</a></li><li><a href="gnumed-entire_schema.html#staging.table.loinc-staging">loinc_staging</a></li><li><a href="gnumed-entire_schema.html#staging.table.test-result">test_result</a></li>
   	
     </ul>
     
@@ -105194,21 +105194,21 @@ END;</pre>
 	
 		<hr>
 		<h2>Function:
-			<a href="gnumed-entire_schema.html#clin.schema">clin</a>.<a name="clin.function.get-hints-for-patient-integer">get_hints_for_patient(integer)</a>
+			<a href="gnumed-entire_schema.html#clin.schema">clin</a>.<a name="clin.function.get-hints-for-patient-pk-identity-integer">get_hints_for_patient(_pk_identity integer)</a>
 		</h2>
 <h3>Returns: SET OF v_auto_hints</h3>
 <h3>Language: PLPGSQL</h3>
         
         <pre>
 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
+	_md5_at_suppression text;
+	_old_rationale4suppression text;
 	_hint_currently_applies boolean;	-- regardless of whether suppressed or not
 	_hint_recommendation text;
+	_title text;
 --	_exc_state text;
 --	_exc_msg text;
 --	_exc_detail text;
@@ -105217,133 +105217,138 @@ DECLARE
 BEGIN
 	-- loop over all defined hints
 	FOR _hint IN SELECT * FROM ref.v_auto_hints WHERE is_active LOOP
+		--raise NOTICE 'checking hint for patient %: %', _pk_identity, _hint.title;
 		-- 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 ?
+		SELECT (clin.hint_suppression_exists(_pk_identity, _hint.pk_auto_hint)).*
+			INTO STRICT _suppression_exists, _md5_at_suppression, _old_rationale4suppression;
+		-- does the hint currently apply ? (regardless of whether it is suppressed)
 		_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;
+		_query := replace(_query, 'clin.v_emr_journal', 'staging.journal_without_suppressed_hints');
+		SELECT (clin.run_hint_query(_hint.title, _query)).*
+			INTO STRICT _hint_currently_applies, _title;
+		-- error ?
+		IF _hint_currently_applies IS NULL THEN
+			--raise NOTICE ' error -> return';
+			_hint.title := _title;
+			_hint.hint := _query;
+			RETURN NEXT _hint;
+			-- process next hint
+			CONTINUE;
+		END IF;
+		-- hint does not apply
+		IF _hint_currently_applies IS FALSE THEN
+			-- does a (previously stored) suppression exist ?
+			IF _suppression_exists IS FALSE THEN
+				-- no, so skip this hint
+				--raise NOTICE ' does not apply -> skip';
 				CONTINUE;
 			END IF;
-			-- no, new hint does not apply, so ask for
-			-- invalidation of suppression (see above)
+			--raise NOTICE ' does not apply but suppression invalid -> return for invalidation';
+			-- hint suppressed but does NOT apply:
+			-- skip hint but invalidate suppression, because:
+			-- * previously the hint must have applied and the user suppressed it,
+			-- * then patient data (or hint definition) changed such that
+			--   the hint does not apply anymore (but the suppression is
+			--   still valid),
+			-- * when patient data 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 will
+			-- still not be returned and shown 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 := 'please_invalidate_suppression';
+			_hint.rationale4suppression := 'magic_tag::does_not_apply::suppression_needs_invalidation';
 			RETURN NEXT _hint;
 			CONTINUE;
 		END IF;
-		-- hint is not suppressed
-		-- does the hint currently apply ?
-		IF _hint_currently_applies THEN
-			-- yes: retrieve recommendation
+		--raise NOTICE ' applies';
+		-- but is there a suppression ?
+		IF _suppression_exists IS FALSE THEN
+			--raise NOTICE ' return';
+			-- no: retrieve recommendation
 			SELECT clin._get_recommendation_for_patient_hint(_hint.recommendation_query, _pk_identity) INTO STRICT _hint_recommendation;
 			_hint.recommendation := _hint_recommendation;
+			-- return hint
 			RETURN NEXT _hint;
+			CONTINUE;
 		END IF;
-		-- no: ignore it and process next hint in LOOP
+		-- yes, is suppressed
+		--raise NOTICE ' is suppressed';
+		-- is the suppression still valid ?
+		-- -> yes, suppression valid
+		IF _md5_at_suppression = _hint.md5_sum THEN
+			--raise NOTICE '-> suppression valid, ignoring hint';
+			-- hint applies, suppressed, suppression valid: skip this hint
+			CONTINUE;
+		END IF;
+		-- -> no, suppression not valid
+		-- hint definition must have changed so ignore the suppression but
+		-- provide previous rationale for suppression to the user
+		_hint.rationale4suppression := _old_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 LOOP;
 	RETURN;
 END;</pre>
 	
 		<hr>
 		<h2>Function:
+			<a href="gnumed-entire_schema.html#clin.schema">clin</a>.<a name="clin.function.hint-suppression-exists-o-rationale-integer-o-md5-integer">hint_suppression_exists(_o_rationale integer, _o_md5 integer)</a>
+		</h2>
+<h3>Returns: record</h3>
+<h3>Language: PLPGSQL</h3>
+        
+        <pre>
+--DECLARE
+--	_md5_suppressed text;
+--	_old_rationale4suppression text;
+BEGIN
+	SELECT
+		md5_sum,
+		rationale
+			INTO
+		_o_md5,
+		_o_rationale
+	FROM clin.suppressed_hint WHERE
+		fk_hint = _pk_hint
+			AND
+		fk_encounter IN (
+			SELECT pk FROM clin.encounter WHERE fk_patient = _pk_identity
+		);
+	IF FOUND THEN
+		_o_exists := TRUE;
+	ELSE
+		_o_exists := FALSE;
+	END IF;
+END;</pre>
+	
+		<hr>
+		<h2>Function:
 			<a href="gnumed-entire_schema.html#clin.schema">clin</a>.<a name="clin.function.move-waiting-list-entry-integer-integer">move_waiting_list_entry(integer, integer)</a>
 		</h2>
 <h3>Returns: boolean</h3>
@@ -105478,6 +105483,43 @@ END;</pre>
 	
 		<hr>
 		<h2>Function:
+			<a href="gnumed-entire_schema.html#clin.schema">clin</a>.<a name="clin.function.run-hint-query-o-title-text-o-applies-text">run_hint_query(_o_title text, _o_applies text)</a>
+		</h2>
+<h3>Returns: record</h3>
+<h3>Language: PLPGSQL</h3>
+        
+        <pre>
+BEGIN
+	BEGIN
+		EXECUTE _query INTO STRICT _o_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;
+			_o_applies := NULL;
+			_o_title := ('ERROR checking for [' || _title || '] !')::TEXT;
+			RETURN;
+	END;
+	_o_title := _title;
+END;</pre>
+	
+		<hr>
+		<h2>Function:
 			<a href="gnumed-entire_schema.html#clin.schema">clin</a>.<a name="clin.function.trf-activate-issue-on-opening-episode">trf_activate_issue_on_opening_episode()</a>
 		</h2>
 <h3>Returns: trigger</h3>
@@ -106123,69 +106165,6 @@ end;</pre>
 	
 		<hr>
 		<h2>Function:
-			<a href="gnumed-entire_schema.html#clin.schema">clin</a>.<a name="clin.function.trf-sane-identity-comment">trf_sane_identity_comment()</a>
-		</h2>
-<h3>Returns: trigger</h3>
-<h3>Language: PLPGSQL</h3>
-        <p>Ensures unique(identity.dob, names.firstnames, names.lastnames, identity.comment)</p>
-        <pre>
-DECLARE
-	_identity_row record;
-	_names_row record;
-BEGIN
-	if TG_TABLE_NAME = 'identity' then
-		if TG_OP = 'UPDATE' then
-			if NEW.comment IS NOT DISTINCT FROM OLD.comment then
-				return NEW;
-			end if;
-		end if;
-		_identity_row := NEW;
-		select * into _names_row from dem.names where id_identity = NEW.pk;
-	else
-		select * into _identity_row from dem.identity where pk = NEW.id_identity;
-		_names_row := NEW;
-	end if;
-	-- any row with
-	PERFORM 1 FROM
-		dem.v_all_persons
-	WHERE
-		-- same firstname
-		firstnames = _names_row.firstnames
-			and
-		-- same lastname
-		lastnames = _names_row.lastnames
-			and
-		-- same gender
-		gender is not distinct from _identity_row.gender
-			and
-		-- same dob (day)
-		dob_only is not distinct from _identity_row.dob
-			and
-		-- same discriminator
-		comment is not distinct from _identity_row.comment
-			and
-		-- but not the currently updated or inserted row
-		pk_identity != _identity_row.pk
-	;
-	if FOUND then
-		RAISE EXCEPTION
-			'% on %.%: More than one person with (firstnames=%), (lastnames=%), (dob=%), (comment=%)',
-				TG_OP,
-				TG_TABLE_SCHEMA,
-				TG_TABLE_NAME,
-				_names_row.firstnames,
-				_names_row.lastnames,
-				_identity_row.dob,
-				_identity_row.comment
-			USING ERRCODE = 'unique_violation'
-		;
-		RETURN NULL;
-	end if;
-	return NEW;
-END;</pre>
-	
-		<hr>
-		<h2>Function:
 			<a href="gnumed-entire_schema.html#clin.schema">clin</a>.<a name="clin.function.trf-sanity-check-enc-epi-ins-upd">trf_sanity_check_enc_epi_ins_upd()</a>
 		</h2>
 <h3>Returns: trigger</h3>
@@ -125689,6 +125668,81 @@ BEGIN
 	return OLD;
 END;</pre>
 	
+		<hr>
+		<h2>Function:
+			<a href="gnumed-entire_schema.html#dem.schema">dem</a>.<a name="dem.function.trf-sane-identity-comment">trf_sane_identity_comment()</a>
+		</h2>
+<h3>Returns: trigger</h3>
+<h3>Language: PLPGSQL</h3>
+        <p>Ensures unique(identity.dob, names.firstnames, names.lastnames, identity.comment)</p>
+        <pre>
+DECLARE
+	_identity_row record;
+	_names_row record;
+	_other_identities integer[];
+BEGIN
+	-- working on dem.identity
+	if TG_TABLE_NAME = 'identity' then
+		-- UPDATEs ...
+		if TG_OP = 'UPDATE' then
+			-- ... which do NOT change .comment ...
+			if NEW.comment IS NOT DISTINCT FROM OLD.comment then
+				-- ... are safe because they were successfully INSERTed before
+				return NEW;
+			end if;
+		end if;
+		-- but INSERTs need checking
+		_identity_row := NEW;
+		select * into _names_row from dem.names where id_identity = NEW.pk;
+	-- working on dem.names
+	else
+		select * into _identity_row from dem.identity where pk = NEW.id_identity;
+		_names_row := NEW;
+	end if;
+	-- there cannot be any combination of identical
+	-- (dob, firstname, lastname, identity.comment)
+	-- so, look for clashing rows
+	SELECT array_agg(pk_identity) INTO _other_identities FROM
+		dem.v_person_names d_vpn
+			join dem.identity d_i on (d_i.pk = d_vpn.pk_identity)
+	WHERE
+		-- same firstname
+		d_vpn.firstnames = _names_row.firstnames
+			AND
+		-- same lastname
+		d_vpn.lastnames = _names_row.lastnames
+			AND
+		-- same gender
+		d_i.gender is not distinct from _identity_row.gender
+			AND
+		-- same dob (day)
+		date_trunc('day', d_i.dob) is not distinct from date_trunc('day', _identity_row.dob)
+			AND
+		-- same discriminator
+		d_i.comment is not distinct from _identity_row.comment
+			AND
+		-- but not the currently updated or inserted row
+		d_i.pk != _identity_row.pk
+	;
+	if coalesce(array_length(_other_identities, 1), 0) > 0 then
+		RAISE EXCEPTION
+			'% on %.%: More than one person with (firstnames=%), (lastnames=%), (dob=%), (comment=%): % & %',
+				TG_OP,
+				TG_TABLE_SCHEMA,
+				TG_TABLE_NAME,
+				_names_row.firstnames,
+				_names_row.lastnames,
+				_identity_row.dob,
+				_identity_row.comment,
+				_identity_row.pk,
+				_other_identities
+			USING ERRCODE = 'unique_violation'
+		;
+		RETURN NULL;
+	end if;
+	return NEW;
+END;</pre>
+	
 
 <!-- gmgm -->
 
@@ -127353,7 +127407,10 @@ BEGIN
 	_cmd := 'create constraint trigger zzz_tr_announce_' || _schema_name || '_' || _table_name || '_ins_upd';
 	_cmd := _cmd || ' after insert or update';
 	_cmd := _cmd || ' on ' || _qualified_table;
-	_cmd := _cmd || ' deferrable';
+	-- needed so a SELECT inside, say, _identity_accessor_SQL running
+	-- concurrently to a "lengthy" TX does not create a serialization
+	-- failure by being a rw-dependancy pivot
+	_cmd := _cmd || ' deferrable initially deferred';
 	_cmd := _cmd || ' for each row';
 	_cmd := _cmd || ' execute procedure gm.trf_announce_table_ins_upd(''' || _payload || ''', ''' || _pk_accessor_SQL || ''', ''' || _identity_accessor_SQL || ''');';
 	execute _cmd;
@@ -127362,7 +127419,10 @@ BEGIN
 	_cmd := 'create constraint trigger zzz_tr_announce_' || _schema_name || '_' || _table_name || '_del';
 	_cmd := _cmd || ' after delete';
 	_cmd := _cmd || ' on ' || _qualified_table;
-	_cmd := _cmd || ' deferrable';
+	-- needed so a SELECT inside, say, _identity_accessor_SQL running
+	-- concurrently to a "lengthy" TX does not create a serialization
+	-- failure by being a rw-dependancy pivot
+	_cmd := _cmd || ' deferrable initially deferred';
 	_cmd := _cmd || ' for each row';
 	_cmd := _cmd || ' execute procedure gm.trf_announce_table_del(''' || _payload || ''', ''' || _pk_accessor_SQL || ''', ''' || _identity_accessor_SQL || ''');';
 	execute _cmd;
@@ -141113,6 +141173,469 @@ END;</pre>
 		
 		
         <hr>
+		<h2>View:
+			
+			<a href="gnumed-entire_schema.html#staging.schema">staging</a>.<a name="staging.view.journal-without-suppressed-hints">journal_without_suppressed_hints</a>
+		</h2>
+        
+
+
+        <table width="100%" cellspacing="0" cellpadding="3">
+                <caption>staging.journal_without_suppressed_hints Structure</caption>
+                <tr>
+                <th>F-Key</th>
+                <th>Name</th>
+                <th>Type</th>
+                <th>Description</th>
+                </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>pk_patient</td>
+            	<td>integer</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>modified_when</td>
+            	<td>timestamp with time zone</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>clin_when</td>
+            	<td>timestamp with time zone</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>modified_by</td>
+            	<td>text</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>soap_cat</td>
+            	<td>text</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>narrative</td>
+            	<td>text</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>pk_encounter</td>
+            	<td>integer</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>pk_episode</td>
+            	<td>integer</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>pk_health_issue</td>
+            	<td>integer</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>src_pk</td>
+            	<td>integer</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>src_table</td>
+            	<td>text</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>row_version</td>
+            	<td>integer</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>health_issue</td>
+            	<td>text</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>issue_laterality</td>
+            	<td>character varying</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>issue_active</td>
+            	<td>boolean</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>issue_clinically_relevant</td>
+            	<td>boolean</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>issue_confidential</td>
+            	<td>boolean</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>episode</td>
+            	<td>text</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>episode_open</td>
+            	<td>boolean</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>encounter_started</td>
+            	<td>timestamp with time zone</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>encounter_last_affirmed</td>
+            	<td>timestamp with time zone</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr1">
+				<td>
+                
+                </td>
+            	<td>encounter_type</td>
+            	<td>text</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+            <tr class="tr0">
+				<td>
+                
+                </td>
+            	<td>encounter_l10n_type</td>
+            	<td>text</td>
+                <td><i>
+				
+
+				
+				
+				</i>
+				
+				</td>
+			 </tr>
+            
+        </table>
+
+        <!-- Inherits -->
+		
+
+		
+
+        <!-- Constraint List -->
+		
+
+        <!-- Foreign Key Discovery -->
+		
+
+    <!-- Indexes -->
+    
+
+	<!-- View Definition -->
+	
+	<pre>
+SELECT v_emr_journal.pk_patient
+,
+    v_emr_journal.modified_when
+,
+    v_emr_journal.clin_when
+,
+    v_emr_journal.modified_by
+,
+    v_emr_journal.soap_cat
+,
+    v_emr_journal.narrative
+,
+    v_emr_journal.pk_encounter
+,
+    v_emr_journal.pk_episode
+,
+    v_emr_journal.pk_health_issue
+,
+    v_emr_journal.src_pk
+,
+    v_emr_journal.src_table
+,
+    v_emr_journal.row_version
+,
+    v_emr_journal.health_issue
+,
+    v_emr_journal.issue_laterality
+,
+    v_emr_journal.issue_active
+,
+    v_emr_journal.issue_clinically_relevant
+,
+    v_emr_journal.issue_confidential
+,
+    v_emr_journal.episode
+,
+    v_emr_journal.episode_open
+,
+    v_emr_journal.encounter_started
+,
+    v_emr_journal.encounter_last_affirmed
+,
+    v_emr_journal.encounter_type
+,
+    v_emr_journal.encounter_l10n_type
+   
+FROM clin.v_emr_journal
+  
+WHERE (v_emr_journal.src_table <> 'clin.suppressed_hint'::text);</pre>
+	
+
+	<!-- List off permissions -->
+	
+
+	<p>
+		<a href="gnumed-entire_schema.html#index">Index</a> -
+		<a href="gnumed-entire_schema.html#staging.schema">Schema staging</a>
+    </p>
+	
+        <hr>
 		<h2>Table:
 			
 			<a href="gnumed-entire_schema.html#staging.schema">staging</a>.<a name="staging.table.lab-request">lab_request</a>
diff --git a/server/pycommon/gmBackendListener.py b/server/pycommon/gmBackendListener.py
index d9345ec..df0eaac 100644
--- a/server/pycommon/gmBackendListener.py
+++ b/server/pycommon/gmBackendListener.py
@@ -238,7 +238,11 @@ class gmBackendListener(gmBorg.cBorg):
 					if item.startswith(u'row PK='):
 						pk_row = int(item.split(u'=')[1])
 					if item.startswith(u'person PK='):
-						pk_identity = int(item.split(u'=')[1])
+						try:
+							pk_identity = int(item.split(u'=')[1])
+						except ValueError:
+							_log.exception(u'error in change notification trigger')
+							pk_identity = -1
 				# try sending intra-client signals:
 				# 1) generic signal
 				self.__messages_sent += 1
diff --git a/server/pycommon/gmDateTime.py b/server/pycommon/gmDateTime.py
index 191c3c4..44e0286 100644
--- a/server/pycommon/gmDateTime.py
+++ b/server/pycommon/gmDateTime.py
@@ -540,7 +540,7 @@ def format_interval(interval=None, accuracy_wanted=None, none_string=None, verbo
 				tag = u' ' + _('second')
 		else:
 			tag = u's'
-		tmp += u' %s%s' % (int(mins), tag)
+		tmp += u' %s%s' % (int(secs), tag)
 
 	if tmp == u'':
 		if verbose:
@@ -2235,10 +2235,19 @@ if __name__ == '__main__':
 	]
 	#-----------------------------------------------------------------------
 	def test_format_interval():
+		intv = pyDT.timedelta(minutes=1, seconds=2)
+		for acc in _accuracy_strings.keys():
+			print ('[%s]: "%s" -> "%s"' % (acc, intv, format_interval(intv, acc)))
+		return
+
 		for tmp in intervals_as_str:
 			intv = str2interval(str_interval = tmp)
+			if intv is None:
+				print(tmp, '->', intv)
+				continue
 			for acc in _accuracy_strings.keys():
 				print ('[%s]: "%s" -> "%s"' % (acc, tmp, format_interval(intv, acc)))
+
 	#-----------------------------------------------------------------------
 	def test_format_interval_medically():
 
@@ -2471,11 +2480,11 @@ if __name__ == '__main__':
 	#test_cFuzzyTimeStamp()
 	#test_get_pydt()
 	#test_str2interval()
-	#test_format_interval()
+	test_format_interval()
 	#test_format_interval_medically()
 	#test_str2pydt()
 	#test_pydt_strftime()
 	#test_calculate_apparent_age()
-	test_is_leap_year()
+	#test_is_leap_year()
 
 #===========================================================================
diff --git a/server/pycommon/gmDispatcher.py b/server/pycommon/gmDispatcher.py
index f0dd6af..8a32d2b 100644
--- a/server/pycommon/gmDispatcher.py
+++ b/server/pycommon/gmDispatcher.py
@@ -102,7 +102,7 @@ def connect(receiver=None, signal=Any, sender=Any, weak=1):
 		raise ValueError('gmDispatcher.connect(): must define <receiver>')
 
 	if signal not in known_signals:
-		_log.error('unknown signal [%(sig)s]', {'sig': signal})
+		_log.warning('unknown signal [%(sig)s]', {'sig': signal})
 
 	if signal is not Any:
 		signal = str(signal)
@@ -141,7 +141,7 @@ def disconnect(receiver, signal=Any, sender=Any, weak=1):
 	Disconnecting is not required. The use of disconnect is the same as for
 	connect, only in reverse. Think of it as undoing a previous connection."""
 	if signal not in known_signals:
-		_log.error('unknown signal [%(sig)s]', {'sig': signal})
+		_log.warning('unknown signal [%(sig)s]', {'sig': signal})
 
 	if signal is not Any:
 		signal = str(signal)
@@ -150,13 +150,13 @@ def disconnect(receiver, signal=Any, sender=Any, weak=1):
 	try:
 		receivers = connections[senderkey][signal]
 	except KeyError:
-		_log.error('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender})
+		_log.warning('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender})
 		print('DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender))
 		return
 	try:
 		receivers.remove(receiver)
 	except ValueError:
-		_log.error('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender})
+		_log.warning('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender})
 		print("DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender))
 	_cleanupConnections(senderkey, signal)
 #---------------------------------------------------------------------
diff --git a/server/pycommon/gmLog2.py b/server/pycommon/gmLog2.py
index 0697db6..f804126 100644
--- a/server/pycommon/gmLog2.py
+++ b/server/pycommon/gmLog2.py
@@ -51,6 +51,9 @@ import io
 import codecs
 import locale
 import datetime as pydt
+import random
+import time
+import calendar
 
 
 _logfile_name = None
@@ -139,6 +142,13 @@ def flush():
 #		logger.debug('  %s: %s', attr, getattr(v, attr))
 
 #===============================================================
+def log_instance_state(instance):
+	logger = logging.getLogger('gm.logging')
+	logger.debug('state of %s', instance)
+	for attr in [ a for a in dir(instance) if not a.startswith('__') ]:
+		logger.debug('  %s: %s', attr, getattr(instance, attr))
+
+#===============================================================
 def log_stack_trace(message=None, t=None, v=None, tb=None):
 
 	logger = logging.getLogger('gm.logging')
@@ -238,9 +248,35 @@ def set_string_encoding(encoding=None):
 	_string_encoding = locale.getpreferredencoding(do_setlocale=False)
 	logger.info(u'setting python.str -> python.unicode encoding to <%s> (locale.getpreferredencoding)', _string_encoding)
 	return True
+
 #===============================================================
 # internal API
 #===============================================================
+__words2hide = []
+
+def add_word2hide(word):
+	if word not in __words2hide:
+		__words2hide.append(word)
+
+#---------------------------------------------------------------
+__original_logger_write_func = None
+
+def __safe_logger_write_func(s):
+	for word in __words2hide:
+		# random is seeded from system time at import,
+		# jump ahead, scrambled by whatever is "now"
+		random.jumpahead(calendar.timegm(time.gmtime()))
+		# from that generate a replacement string valid for
+		# *this* round of replacements of *this* word,
+		# this approach won't mitigate guessing trivial passwords
+		# from replacements of known data (a known-plaintext attack)
+		# but will make automated searching for replaced strings
+		# in the log more difficult
+		bummer = hex(random.randint(0, sys.maxint)).lstrip(u'0x')
+		s = s.replace(word, bummer)
+	__original_logger_write_func(s)
+
+#---------------------------------------------------------------
 def __setup_logging():
 
 	set_string_encoding()
@@ -259,6 +295,10 @@ def __setup_logging():
 
 	_logfile = io.open(_logfile_name, mode = 'wt', encoding = 'utf8', errors = 'replace')
 
+	global __original_logger_write_func
+	__original_logger_write_func = _logfile.write
+	_logfile.write = __safe_logger_write_func
+
 	logging.basicConfig (
 		format = fmt,
 		datefmt = '%Y-%m-%d %H:%M:%S',
@@ -266,12 +306,16 @@ def __setup_logging():
 		stream = _logfile
 	)
 
+	logging.captureWarnings(True)
+
 	logger = logging.getLogger('gm.logging')
 	logger.critical(u'-------- start of logging ------------------------------')
 	logger.info(u'log file is <%s>', _logfile_name)
 	logger.info(u'log level is [%s]', logging.getLevelName(logger.getEffectiveLevel()))
 	logger.info(u'log file encoding is <utf8>')
 	logger.info(u'initial python.str -> python.unicode encoding is <%s>', _string_encoding)
+	logger.debug(u'log file .write() patched from original %s to patched %s', __original_logger_write_func, __safe_logger_write_func)
+
 #---------------------------------------------------------------
 def __get_logfile_name():
 
@@ -309,6 +353,7 @@ def __get_logfile_name():
 	_logfile_name = os.path.join(dir_name, default_logfile_name)
 
 	return True
+
 #===============================================================
 # main
 #---------------------------------------------------------------
@@ -316,10 +361,19 @@ __setup_logging()
 
 if __name__ == '__main__':
 
+	if len(sys.argv) < 2:
+		sys.exit()
+
+	if sys.argv[1] != u'test':
+		sys.exit()
+
 	#-----------------------------------------------------------
 	def test():
 		logger = logging.getLogger('gmLog2.test')
 		logger.error("I expected to see %s::test()" % __file__)
+		add_word2hide(u'super secret passphrase')
+		logger.debug('credentials: super secret passphrase')
+
 		try:
 			int(None)
 		except:
@@ -327,6 +381,4 @@ if __name__ == '__main__':
 			log_stack_trace()
 		flush()
 	#-----------------------------------------------------------
-	if len(sys.argv) > 1 and sys.argv[1] == u'test':
-		test()
-#===============================================================
+	test()
diff --git a/server/pycommon/gmPG2.py b/server/pycommon/gmPG2.py
index 70a4003..4eb2bd6 100644
--- a/server/pycommon/gmPG2.py
+++ b/server/pycommon/gmPG2.py
@@ -434,6 +434,7 @@ def __request_login_params_tui():
 		login.user = prompted_input(prompt = "user name", default = '')
 		tmp = 'password for "%s" (not shown): ' % login.user
 		login.password = getpass.getpass(tmp)
+		gmLog2.add_word2hide(login.password)
 		login.port = prompted_input(prompt = "port", default = 5432)
 	except KeyboardInterrupt:
 		_log.warning("user cancelled text mode login dialog")
@@ -465,7 +466,10 @@ def __request_login_params_gui_wx():
 	if login is None:
 		raise gmExceptions.ConnectionError(_("Can't connect to database without login information!"))
 
+	gmLog2.add_word2hide(login.password)
+
 	return login
+
 #---------------------------------------------------
 def request_login_params():
 	"""Request login parameters for database connection."""
diff --git a/server/sql/v20-v21/dynamic/v21-release_notes-dynamic.sql b/server/sql/v20-v21/dynamic/v21-release_notes-dynamic.sql
index 30fc8bb..cce6ee1 100644
--- a/server/sql/v20-v21/dynamic/v21-release_notes-dynamic.sql
+++ b/server/sql/v20-v21/dynamic/v21-release_notes-dynamic.sql
@@ -17,17 +17,26 @@ INSERT INTO dem.message_inbox (
 ) VALUES (
 	(select pk from dem.staff where db_user = 'any-doc'),
 	(select pk_type from dem.v_inbox_item_type where type = 'memo' and category = 'administrative'),
-	'Release Notes for GNUmed 1.6.12 (database v21.12)',
-	'GNUmed 1.6.12 Release Notes:
+	'Release Notes for GNUmed 1.6.13 (database v21.13)',
+	'GNUmed 1.6.13 Release Notes:
 
-	1.6.12
+	1.6.13
 
-FIX: patient merging [thanks Marc]
+FIX: editing of drug products
+FIX: formatting of intervals with seconds [thanks Rickard]
+FIX: robustify backend listener against change notification trigger errors
+FIX: backport once-only detection of unicode char selector
+FIX: improper handling of notebook page change events
+FIX: error handling on uploading DICOM to Orthanc
 
-	21.12
+IMPROVED: more fully prevent logfile based password leaks
+IMPROVED: add listing of latest vaccination per indication
+IMPROVED: export area change listening and sortability
+IMPROVED: episode edit area behaviour
+IMPROVED: add measurement by clicking empty cell in grid
 
-IMPROVED: logging on dem.identity/dem.names uniqueness violation
+NEW: add Constans algorithm for upper extremity DVT
 ');
 
 -- --------------------------------------------------------------
-select gm.log_script_insertion('v21-release_notes-dynamic.sql', '21.12');
+select gm.log_script_insertion('v21-release_notes-dynamic.sql', '21.13');
diff --git a/server/sql/v20-v21/fixups/v21-Constans-TVT-OE.sql b/server/sql/v20-v21/fixups/v21-Constans-TVT-OE.sql
new file mode 100644
index 0000000..2299587
--- /dev/null
+++ b/server/sql/v20-v21/fixups/v21-Constans-TVT-OE.sql
@@ -0,0 +1,52 @@
+-- ==============================================================
+-- GNUmed database schema change script
+--
+-- License: GPL v2 or later
+-- Author: Karsten Hilbert
+-- 
+-- ==============================================================
+\set ON_ERROR_STOP 1
+--set default_transaction_read_only to off;
+
+-- --------------------------------------------------------------
+delete from ref.keyword_expansion where keyword = 'algo-TVT_OE';
+
+insert into ref.keyword_expansion (
+	fk_staff,
+	keyword,
+	textual_data
+) values (
+	null,
+	'algo-TVT_OE',
+'Constans: Diagnostik TVT Obere Extremität
+-----------------------------------------------------------
+DÄB / 114 / 244-9 / 7.April 2014
+DOI 10.3238/artebl.2017.0244
+
+- Schwellung/Schmerz/Venenzeichnung/Zyanose
+- Armschwäche/-parästhesie
+- ZVK/Schrittmacher/Tumorleiden
+- Einengung Thoraxapertur (Rucksack, Anatomie)
+
+Klinischer Verdacht auf TVT-OE ?: $[Verdacht: Ja/Nein]$
+
+ Constans-Kriterien
+
+$[0/1]$ ZVK oder Schrittmacher
+$[0/1]$ lokalisierter Schmerz
+$[0/1]$ einseitige Schwellung
+$[0/-1]$ alternative Diagnose wahrscheinliche Ursache
+
+Summe $[Summe]$
+
+0/1 - TVT-OE unwahrscheinlich
+	-> D-Dimer
+		< 500µg/l -> TVT-OE weitgehend ausgeschlossen
+		> 500µg/l -> Sonographie
+
+2/3 - TVT-OE wahrscheinlich
+	-> Sonographie
+');
+
+-- --------------------------------------------------------------
+select gm.log_script_insertion('v21-Constans-TVT-OE.sql', '21.13');

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/gnumed-server.git



More information about the debian-med-commit mailing list