From 46eb43aab957c4a6bdf75d2e1e79c21c706b6294 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Thu, 13 Jun 2024 16:16:50 +0200 Subject: [PATCH] feat: show schema versions and change extern detection This adds a new optional argument to split conditions by used schema versions in addition to ICD10 group. Since a JOIN on table `lkr_meldung` might use newer, not yet exported information the detection of external items will be done by using `Melder_ID`. --- README.md | 4 + src/cli.rs | 2 + src/common.rs | 2 + src/database.rs | 50 +++++-- src/main.rs | 24 +++- src/resources/mod.rs | 2 + src/resources/query.sql | 30 ++--- src/resources/query_with_schema_version.sql | 140 ++++++++++++++++++++ 8 files changed, 218 insertions(+), 36 deletions(-) create mode 100644 src/resources/query_with_schema_version.sql diff --git a/README.md b/README.md index 15a450a..b5de9ee 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,14 @@ Dies eignet sich um nachträglich Zahlen zu einem bestimmten Datum zu ermitteln. Der optionale Parameter `--include-extern` schließt Meldungen mit externer Diagnosestellung ein. Diese sind normalerweise nicht enthalten. +Die Entscheidung, ob eine Meldung intern oder extern gemeldet wird, wird anhand der `Melder_ID` getroffen. +Enthält diese die Zeichenkette `9999` wird von einer externen Meldung ausgegangen. Der optionale Parameter `--include-histo-zyto` schließt Meldungen mit Meldeanlass `histologhie_zytologie` ein. Diese sind normalerweise ebenfalls nicht enthalten. +Mit dem optionalen Parameter `--schema-versions` werden die Angaben zudem noch oBDS-Schema-Version getrennt ausgegeben. + ## Export aus der Onkostar-Datenbank Die Anwendung ist in der Lage, mit dem Befehl `export` die Spalten diff --git a/src/cli.rs b/src/cli.rs index 98c0d1e..2685305 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -69,6 +69,8 @@ pub enum SubCommand { help = "Meldungen mit Meldeanlass 'histologie_zytologie' einschließen" )] include_histo_zyto: bool, + #[arg(long, help = "Meldungen mit oBDS-Schema-version anzeigen")] + schema_versions: bool, }, #[command( about = "Erstellt eine (reduzierte) CSV-Datei zum direkten Vergleich mit der OPAL-CSV-Datei" diff --git a/src/common.rs b/src/common.rs index 1a283ce..329cc46 100644 --- a/src/common.rs +++ b/src/common.rs @@ -23,6 +23,7 @@ use serde::{Deserialize, Serialize}; pub struct Icd10GroupSize { pub name: String, + pub schema_version: Option, pub size: usize, } @@ -68,6 +69,7 @@ impl Check { .map(|(icd10, group)| (icd10, group.collect::>())) .map(|record| Icd10GroupSize { name: record.0, + schema_version: None, size: record.1.len(), }) .collect::>(); diff --git a/src/database.rs b/src/database.rs index 595d55d..7db1d94 100644 --- a/src/database.rs +++ b/src/database.rs @@ -24,7 +24,23 @@ use mysql::prelude::Queryable; use mysql::{params, Pool}; use crate::common::{ExportData, Icd10GroupSize}; -use crate::resources::{EXPORTED_TO_LKR, EXPORT_QUERY, SQL_QUERY}; +use crate::resources::{EXPORTED_TO_LKR, EXPORT_QUERY, SQL_QUERY, SQL_QUERY_WITH_SCHEMA_VERSION}; + +fn result_mapper() -> fn((String, String, usize)) -> Icd10GroupSize { + |(icd10_group, _, count)| Icd10GroupSize { + name: icd10_group, + schema_version: None, + size: count, + } +} + +fn result_mapper_with_schema_version() -> fn((String, String, usize)) -> Icd10GroupSize { + |(icd10_group, schema_version, count)| Icd10GroupSize { + name: icd10_group, + schema_version: Some(schema_version), + size: count, + } +} pub struct DatabaseSource(String); @@ -41,25 +57,31 @@ impl DatabaseSource { ignore_exports_since: &str, include_extern: bool, include_histo_zyto: bool, + schema_versions: bool, ) -> Result, ()> { + let params = params! { + "year" => year, + "ignore_exports_since" => ignore_exports_since, + "include_extern" => if include_extern { 1 } else { 0 }, + "include_histo_zyto" => if include_histo_zyto { 1 } else { 0 } + }; + match Pool::new(self.0.as_str()) { Ok(pool) => { if let Ok(mut connection) = pool.try_get_conn(Duration::from_secs(3)) { - return match connection.exec_map( - SQL_QUERY, - params! { - "year" => year, - "ignore_exports_since" => ignore_exports_since, - "include_extern" => if include_extern { 1 } else { 0 }, - "include_histo_zyto" => if include_histo_zyto { 1 } else { 0 } + return match schema_versions { + true => match connection.exec_map( + SQL_QUERY_WITH_SCHEMA_VERSION, + params, + result_mapper_with_schema_version(), + ) { + Ok(result) => Ok(result), + Err(_) => Err(()), }, - |(icd10_group, count)| Icd10GroupSize { - name: icd10_group, - size: count, + false => match connection.exec_map(SQL_QUERY, params, result_mapper()) { + Ok(result) => Ok(result), + Err(_) => Err(()), }, - ) { - Ok(result) => Ok(result), - Err(_) => Err(()), }; } } diff --git a/src/main.rs b/src/main.rs index cabfb6f..39df57b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,26 +67,34 @@ fn print_items(items: &[Icd10GroupSize]) { .to_string(), ); items.iter().for_each(|item| { - let _ = term.write_line(&format!("{:<20}={:>6}", item.name, item.size)); + let _ = term.write_line(&format!( + "{:<20} {:<6} ={:>6}", + item.name, + item.schema_version.as_ref().unwrap_or(&String::new()), + item.size + )); }); let sum: usize = items .iter() .filter(|item| item.name != "Other") .map(|item| item.size) .sum(); - let _ = term.write_line(&style("─".repeat(27)).dim().to_string()); + let _ = term.write_line(&style("─".repeat(35)).dim().to_string()); let _ = term.write_line( - &style(format!("{:<20}={:>6}", "Summe (C**.*/D**.*)", sum)) - .dim() - .to_string(), + &style(format!( + "{:<20} {:<6} ={:>6}", + "Summe (C**.*/D**.*)", "", sum + )) + .dim() + .to_string(), ); let sum: usize = items.iter().map(|item| item.size).sum(); let _ = term.write_line( - &style(format!("{:<20}={:>6}", "Gesamtsumme", sum)) + &style(format!("{:<20} {:<6} ={:>6}", "Gesamtsumme", "", sum)) .dim() .to_string(), ); - let _ = term.write_line(&style("─".repeat(27)).dim().to_string()); + let _ = term.write_line(&style("─".repeat(35)).dim().to_string()); } fn print_extern_notice(include_extern: bool) { @@ -123,6 +131,7 @@ fn main() -> Result<(), Box> { ignore_exports_since, include_extern, include_histo_zyto, + schema_versions, } => { let password = request_password_if_none(password); let year = sanitize_year(year); @@ -141,6 +150,7 @@ fn main() -> Result<(), Box> { &ignore_exports_since.unwrap_or("9999-12-31".into()), include_extern, include_histo_zyto, + schema_versions, ) .map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?; diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 5df086a..5cd215c 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -20,6 +20,8 @@ pub const SQL_QUERY: &str = include_str!("query.sql"); +pub const SQL_QUERY_WITH_SCHEMA_VERSION: &str = include_str!("query_with_schema_version.sql"); + pub const EXPORT_QUERY: &str = include_str!("export.sql"); pub const EXPORTED_TO_LKR: &str = include_str!("exported-to-lkr.sql"); diff --git a/src/resources/query.sql b/src/resources/query.sql index 1f614e3..f8852b1 100644 --- a/src/resources/query.sql +++ b/src/resources/query.sql @@ -107,34 +107,34 @@ SELECT CASE ELSE 'Other' END AS ICD10_GROUP, - + '' AS schema_version, COUNT(*) as COUNT FROM ( - SELECT DISTINCT + lme.lkr_meldung, EXTRACTVALUE(lme.xml_daten, '//Patienten_Stammdaten/@Patient_ID') AS pid, + EXTRACTVALUE(lme.xml_daten, '//ADT_GEKID/@Schema_Version') AS schema_version, lme.versionsnummer, SHA2(CONCAT('https://fhir.diz.uk-erlangen.de/identifiers/onkostar-xml-condition-id|', EXTRACTVALUE(lme.xml_daten, '//Patienten_Stammdaten/@Patient_ID'), 'condition', EXTRACTVALUE(lme.xml_daten, '//Diagnose/@Tumor_ID')), 256) AS cond_id, - SUBSTRING_INDEX(EXTRACTVALUE(lm.xml_daten, '//Primaertumor_ICD_Code'), ' ', 1) AS condcodingcode, - SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lm.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) AS diagnosejahr + SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Primaertumor_ICD_Code'), ' ', 1) AS condcodingcode, + SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) AS diagnosejahr FROM lkr_meldung_export lme - JOIN lkr_meldung lm ON (lm.id = lme.lkr_meldung AND lme.typ <> '-1' AND lm.extern <= :include_extern) WHERE lme.xml_daten LIKE '%ICD_Version%' - AND SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lm.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) = :year - AND (lm.xml_daten LIKE '%%' OR lm.xml_daten LIKE '%%') - AND (lm.xml_daten NOT LIKE '%histologie_zytologie%' OR 1 = :include_histo_zyto) - ) o1 - LEFT OUTER JOIN ( - + AND lme.typ <> -1 + AND SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) = :year + AND (lme.xml_daten LIKE '%%' OR lme.xml_daten LIKE '%%') + AND (lme.xml_daten NOT LIKE '%histologie_zytologie%' OR 1 = :include_histo_zyto) + AND (EXTRACTVALUE(lme.xml_daten, '//Meldende_Stelle') NOT LIKE '%9999%' OR 1 <= :include_extern) + ) o1 +LEFT OUTER JOIN ( SELECT DISTINCT + lme.lkr_meldung, SHA2(CONCAT('https://fhir.diz.uk-erlangen.de/identifiers/onkostar-xml-condition-id|', EXTRACTVALUE(lme.xml_daten, '//Patienten_Stammdaten/@Patient_ID'), 'condition', EXTRACTVALUE(lme.xml_daten, '//Diagnose/@Tumor_ID')), 256) AS cond_id, - CASE WHEN le.exportiert_am < :ignore_exports_since THEN MAX(versionsnummer) ELSE ~0 END AS max_version + CASE WHEN STR_TO_DATE(EXTRACTVALUE(lme.xml_daten, '//Meldedatum'), '%d.%c.%Y') < :ignore_exports_since THEN MAX(versionsnummer) ELSE ~0 END AS max_version FROM lkr_meldung_export lme - JOIN lkr_export le ON (lme.lkr_export = le.id) WHERE SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) = :year GROUP BY cond_id ORDER BY cond_id - - ) o2 +) o2 ON (o1.cond_id = o2.cond_id AND o1.versionsnummer < max_version) WHERE diagnosejahr = :year AND o2.cond_id IS NULL GROUP BY ICD10_GROUP; \ No newline at end of file diff --git a/src/resources/query_with_schema_version.sql b/src/resources/query_with_schema_version.sql new file mode 100644 index 0000000..aa4aa53 --- /dev/null +++ b/src/resources/query_with_schema_version.sql @@ -0,0 +1,140 @@ +/* + * This file is part of bzkf-rwdp-check + * + * Copyright (C) 2024 Comprehensive Cancer Center Mainfranken and contributors. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +SELECT CASE + WHEN condcodingcode LIKE 'C00%' + OR condcodingcode LIKE 'C01%' + OR condcodingcode LIKE 'C02%' + OR condcodingcode LIKE 'C03%' + OR condcodingcode LIKE 'C04%' + OR condcodingcode LIKE 'C05%' + OR condcodingcode LIKE 'C06%' + OR condcodingcode LIKE 'C07%' + OR condcodingcode LIKE 'C08%' + OR condcodingcode LIKE 'C09%' + OR condcodingcode LIKE 'C10%' + OR condcodingcode LIKE 'C11%' + OR condcodingcode LIKE 'C12%' + OR condcodingcode LIKE 'C13%' + OR condcodingcode LIKE 'C14%' THEN 'C00-C14' + + WHEN condcodingcode LIKE 'C15%' THEN 'C15' + + WHEN condcodingcode LIKE 'C16%' THEN 'C16' + + WHEN condcodingcode LIKE 'C18%' + OR condcodingcode LIKE 'C19%' + OR condcodingcode LIKE 'C20%' + OR condcodingcode LIKE 'C21%' THEN 'C18-C21' + + WHEN condcodingcode LIKE 'C22%' THEN 'C22' + + WHEN condcodingcode LIKE 'C23%' + OR condcodingcode LIKE 'C24%' THEN 'C23-C24' + + WHEN condcodingcode LIKE 'C25%' THEN 'C25' + + WHEN condcodingcode LIKE 'C32%' THEN 'C32' + + WHEN condcodingcode LIKE 'C33%' + OR condcodingcode LIKE 'C34%' THEN 'C33-C34' + + WHEN condcodingcode LIKE 'C43%' THEN 'C43' + + WHEN condcodingcode LIKE 'C50%' + OR condcodingcode LIKE 'D05%' THEN 'C50, D05' + + WHEN condcodingcode LIKE 'C53%' + OR condcodingcode LIKE 'D06%' THEN 'C53, D06' + + WHEN condcodingcode LIKE 'C54%' + OR condcodingcode LIKE 'C55%' THEN 'C54-C55' + + WHEN condcodingcode LIKE 'C56%' + OR condcodingcode = 'D39.1' THEN 'C56, D39.1' + + WHEN condcodingcode LIKE 'C61%' THEN 'C61' + + WHEN condcodingcode LIKE 'C62%' THEN 'C62' + + WHEN condcodingcode LIKE 'C64%' THEN 'C64' + + WHEN condcodingcode LIKE 'C67%' + OR condcodingcode = 'D09.0' + OR condcodingcode = 'D41.4' THEN 'C67, D09.0, D41.4' + + WHEN condcodingcode LIKE 'C70%' + OR condcodingcode LIKE 'C71%' + OR condcodingcode LIKE 'C72%' THEN 'C70-C72' + + WHEN condcodingcode LIKE 'C73%' THEN 'C73' + + WHEN condcodingcode LIKE 'C81%' THEN 'C81' + + WHEN condcodingcode LIKE 'C82%' + OR condcodingcode LIKE 'C83%' + OR condcodingcode LIKE 'C84%' + OR condcodingcode LIKE 'C85%' + OR condcodingcode LIKE 'C86%' + OR condcodingcode LIKE 'C87%' + OR condcodingcode LIKE 'C88%' + OR condcodingcode LIKE 'C96%' THEN 'C82-C88, C96' + + WHEN condcodingcode LIKE 'C90%' THEN 'C90' + + WHEN condcodingcode LIKE 'C91%' + OR condcodingcode LIKE 'C92%' + OR condcodingcode LIKE 'C93%' + OR condcodingcode LIKE 'C94%' + OR condcodingcode LIKE 'C95%' THEN 'C91-C95' + + ELSE 'Other' + END AS ICD10_GROUP, + schema_version, + COUNT(*) as COUNT +FROM ( + SELECT DISTINCT + lme.lkr_meldung, + EXTRACTVALUE(lme.xml_daten, '//Patienten_Stammdaten/@Patient_ID') AS pid, + EXTRACTVALUE(lme.xml_daten, '//ADT_GEKID/@Schema_Version') AS schema_version, + lme.versionsnummer, + SHA2(CONCAT('https://fhir.diz.uk-erlangen.de/identifiers/onkostar-xml-condition-id|', EXTRACTVALUE(lme.xml_daten, '//Patienten_Stammdaten/@Patient_ID'), 'condition', EXTRACTVALUE(lme.xml_daten, '//Diagnose/@Tumor_ID')), 256) AS cond_id, + SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Primaertumor_ICD_Code'), ' ', 1) AS condcodingcode, + SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) AS diagnosejahr + FROM lkr_meldung_export lme + WHERE lme.xml_daten LIKE '%ICD_Version%' + AND lme.typ <> -1 + AND SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) = :year + AND (lme.xml_daten LIKE '%%' OR lme.xml_daten LIKE '%%') + AND (lme.xml_daten NOT LIKE '%histologie_zytologie%' OR 1 = :include_histo_zyto) + AND (EXTRACTVALUE(lme.xml_daten, '//Meldende_Stelle') NOT LIKE '%9999%' OR 1 <= :include_extern) + ) o1 +LEFT OUTER JOIN ( + SELECT DISTINCT + lme.lkr_meldung, + SHA2(CONCAT('https://fhir.diz.uk-erlangen.de/identifiers/onkostar-xml-condition-id|', EXTRACTVALUE(lme.xml_daten, '//Patienten_Stammdaten/@Patient_ID'), 'condition', EXTRACTVALUE(lme.xml_daten, '//Diagnose/@Tumor_ID')), 256) AS cond_id, + CASE WHEN STR_TO_DATE(EXTRACTVALUE(lme.xml_daten, '//Meldedatum'), '%d.%c.%Y') < :ignore_exports_since THEN MAX(versionsnummer) ELSE ~0 END AS max_version + FROM lkr_meldung_export lme + WHERE SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) = :year + GROUP BY cond_id ORDER BY cond_id +) o2 +ON (o1.cond_id = o2.cond_id AND o1.versionsnummer < max_version) +WHERE diagnosejahr = :year AND o2.cond_id IS NULL +GROUP BY ICD10_GROUP, schema_version; \ No newline at end of file