From 0f7dff2d16a6c692f01306e08e0c0bd3c3efbe37 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Fri, 23 Feb 2024 14:55:38 +0100 Subject: [PATCH] feat: add sub command to request values from database --- Cargo.toml | 4 ++ README.md | 25 +++++++- src/cli.rs | 43 ++++++++----- src/database.rs | 61 ++++++++++++++++++ src/main.rs | 68 +++++++++++++++++--- src/resources/mod.rs | 21 +++++++ src/resources/query.sql | 133 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 330 insertions(+), 25 deletions(-) create mode 100644 src/database.rs create mode 100644 src/resources/mod.rs create mode 100644 src/resources/query.sql diff --git a/Cargo.toml b/Cargo.toml index 9f5a36a..f805d65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,10 @@ edition = "2021" clap = { version = "4.4", features = ["std", "help", "usage", "derive", "error-context"], default-features = false } console = "0.15" csv = "1.3" +dialoguer = "0.11" itertools = "0.12" +mysql = "24.0" serde = { version = "1.0", features = ["derive"] } +urlencoding = "2.1" + diff --git a/README.md b/README.md index c0b9430..de230d2 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,27 @@ mit dem Befehl `opal-file` aus der CSV-Datei gewonnen werden. bzkf-rwdp-check opal-file --file .csv ``` -Die Anwendung gibt nun eine Liste der ICD-10-Gruppen mit Anzahl der _Conditions_ aus. \ No newline at end of file +Die Anwendung gibt nun eine Liste der ICD-10-Gruppen mit Anzahl der _Conditions_ aus. + +## Prüfung der Daten in der Onkostar-Datenbank + +Die Anzahl der _Conditions_, gruppiert nach ICD-10-Gruppe, kann auch mit dem Befehl `database` aus der Onkostar-Datenbank +abgerufen werden. + +``` +bzkf-rwdp-check database --user me --year 2024 +``` + +Die Anwendung gibt auch hier eine Liste der ICD-10-Gruppen mit Anzahl der _Conditions_ aus. + +Dieser Befehl hat noch weitere Parameter: + +``` +Options: + -D, --database Datenbank-Name [default: onkostar] + -h, --host Datenbank-Host [default: localhost] + -P, --port Datenbank-Host [default: 3306] + -p, --password Passwort. Wenn nicht angegeben, wird danach gefragt + -u, --user Benutzername + -y, --year Jahr der Diagnose +``` \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 8129b84..f98b565 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -22,7 +22,7 @@ use clap::{Parser, Subcommand}; #[derive(Parser)] #[command(author, version, about)] -#[command(arg_required_else_help(true))] +#[command(arg_required_else_help(true), disable_help_flag(true))] pub struct Cli { #[command(subcommand)] pub cmd: SubCommand, @@ -30,20 +30,33 @@ pub struct Cli { #[derive(Subcommand)] pub enum SubCommand { - #[command( - about = "Ermittelt die Prüfwerte aus einem CSV-File für OPAL" - )] + #[command(about = "Ermittelt die Prüfwerte aus einem CSV-File für OPAL")] OpalFile { - #[arg(short, long, help = "CSV-File für Opal")] file: String + #[arg(short, long, help = "CSV-File für Opal")] + file: String, }, - #[command( - about = "Ermittelt die Prüfwerte aus einem CSV-File für OPAL" - )] + #[command(about = "Ermittelt die Prüfwerte aus einem CSV-File für OPAL")] Database { - #[arg(long, help = "Datenbank-Host", default_value="localhost")] host: String, - #[arg(long, help = "Datenbank-Host", default_value="3306")] port: u16, - #[arg(long, help = "Benutzername")] user: String, - } - - -} \ No newline at end of file + #[arg(short = 'D', long, help = "Datenbank-Name", default_value = "onkostar")] + database: String, + #[arg( + short = 'h', + long, + help = "Datenbank-Host", + default_value = "localhost" + )] + host: String, + #[arg(short = 'P', long, help = "Datenbank-Host", default_value = "3306")] + port: u16, + #[arg( + short = 'p', + long, + help = "Passwort. Wenn nicht angegeben, wird danach gefragt" + )] + password: Option, + #[arg(short = 'u', long, help = "Benutzername")] + user: String, + #[arg(short = 'y', long, help = "Jahr der Diagnose")] + year: String, + }, +} diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..51850d2 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,61 @@ +/* + * 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. + */ + +use mysql::prelude::Queryable; +use mysql::{params, Pool}; + +use crate::common::Icd10GroupSize; +use crate::resources::SQL_QUERY; + +pub struct DatabaseSource(String); + +impl DatabaseSource { + pub fn new(database: &str, host: &str, password: &str, port: u16, user: &str) -> Self { + let password = urlencoding::encode(password); + let url = format!("mysql://{user}:{password}@{host}:{port}/{database}"); + DatabaseSource(url) + } + + pub fn check(&self, year: &str) -> Result, ()> { + match Pool::new(self.0.as_str()) { + Ok(pool) => { + if let Ok(mut connection) = pool.get_conn() { + return match connection.exec_map( + SQL_QUERY, + params! {"year" => year}, + |(icd10_group, count)| Icd10GroupSize { + name: icd10_group, + size: count, + }, + ) { + Ok(result) => Ok(result), + Err(_) => Err(()), + }; + } + } + Err(e) => { + println!("{}", e); + return Err(()); + } + } + + Err(()) + } +} diff --git a/src/main.rs b/src/main.rs index f1c6e99..4366090 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,10 +25,26 @@ use clap::Parser; use console::{style, Term}; use crate::cli::{Cli, SubCommand}; +use crate::common::Icd10GroupSize; +use crate::database::DatabaseSource; -mod opal; -mod common; mod cli; +mod common; +mod database; +mod opal; +mod resources; + +fn print_items(items: &[Icd10GroupSize]) { + let term = Term::stdout(); + let _ = term.write_line( + &style("Anzahl der Conditions nach ICD-10-Gruppe") + .yellow() + .to_string(), + ); + items.iter().for_each(|item| { + let _ = term.write_line(&format!("{:<20}={:>6}", item.name, item.size)); + }); +} fn main() -> Result<(), Box> { let term = Term::stdout(); @@ -38,13 +54,47 @@ fn main() -> Result<(), Box> { let items = opal::OpalCsvFile::check(Path::new(&file)) .map_err(|_e| "Kann Datei nicht lesen")?; - let _ = term.write_line(&style("Anzahl der Conditions nach ICD-Gruppe").yellow().to_string()); - items.iter().for_each(|item| { - let _ = term.write_line(&format!("{:<20}={:>6}", item.name, item.size)); - }); - }, - SubCommand::Database { .. } => { - todo!("Not implemented yet") + print_items(&items); + } + SubCommand::Database { + database, + host, + password, + port, + user, + year, + } => { + let password = if let Some(password) = password { + password + } else { + let password = dialoguer::Password::new() + .with_prompt("Password") + .interact() + .unwrap_or_default(); + let _ = term.clear_last_lines(1); + password + }; + + let year = if year.len() == 4 { + year + } else { + format!("2{:0>3}", year) + }; + + let _ = term.write_line( + &style(format!("Warte auf Daten für das Diagnosejahr {}...", year)) + .blue() + .to_string(), + ); + + let db = DatabaseSource::new(&database, &host, &password, port, &user); + let items = db + .check(&year) + .map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?; + + let _ = term.clear_last_lines(1); + + print_items(&items); } } diff --git a/src/resources/mod.rs b/src/resources/mod.rs new file mode 100644 index 0000000..b070577 --- /dev/null +++ b/src/resources/mod.rs @@ -0,0 +1,21 @@ +/* + * 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. + */ + +pub const SQL_QUERY: &str = include_str!("query.sql"); diff --git a/src/resources/query.sql b/src/resources/query.sql new file mode 100644 index 0000000..330be62 --- /dev/null +++ b/src/resources/query.sql @@ -0,0 +1,133 @@ +/* + * 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, + + COUNT(*) as COUNT +FROM ( + + SELECT DISTINCT + EXTRACTVALUE(lme.xml_daten, '//Patienten_Stammdaten/@Patient_ID') AS pid, 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 + JOIN lkr_meldung lm ON (lm.id = lme.lkr_meldung AND lme.typ <> '-1') + WHERE lme.xml_daten LIKE '%ICD_Version%' + AND (lme.xml_daten LIKE '%%') + + ) o1 + LEFT OUTER JOIN ( + + SELECT + 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, MAX(versionsnummer) 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; \ No newline at end of file