From 6617ce76a0ef54a8a7aa85994d613bcd1e71cc8c Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Tue, 11 Jun 2024 19:30:25 +0200 Subject: [PATCH 01/10] feat: parse LKR protocol file --- src/lkrexport.rs | 167 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 + testdaten/testdaten_1.xml | 89 ++++++++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 src/lkrexport.rs create mode 100644 testdaten/testdaten_1.xml diff --git a/src/lkrexport.rs b/src/lkrexport.rs new file mode 100644 index 0000000..3c38d10 --- /dev/null +++ b/src/lkrexport.rs @@ -0,0 +1,167 @@ +/* + * 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 std::fs; +use std::path::Path; +use std::str::FromStr; + +use itertools::Itertools; +use regex::Regex; + +pub struct LkrExportProtocolFile { + pub patients: Vec, +} + +impl LkrExportProtocolFile { + pub fn parse_file(path: &Path) -> Result { + let xml_file_content = fs::read_to_string(path).map_err(|_| ())?; + Self::parse(&xml_file_content) + } + + pub fn parse(content: &str) -> Result { + let re = Regex::new(r"(?s)(?(.*?))").unwrap(); + + if re.is_match(content) { + let patients = re + .find_iter(content) + .map(|m| Patient { + raw_value: m.as_str().to_string(), + }) + .collect_vec(); + return Ok(LkrExportProtocolFile { patients }); + } + + Err(()) + } +} + +pub struct Patient { + pub raw_value: String, +} + +impl Patient { + pub fn meldungen(&self) -> Vec { + let re = Regex::new(r"(?s)(?)").unwrap(); + + if re.is_match(&self.raw_value) { + return re + .find_iter(&self.raw_value) + .map(|m| Meldung { + raw_value: m.as_str().to_string(), + }) + .collect_vec(); + } + vec![] + } +} + +pub struct Meldung { + pub raw_value: String, +} + +impl FromStr for Meldung { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(Meldung { + raw_value: s.to_string(), + }) + } +} + +impl Meldung { + pub fn id(&self) -> Option { + let re = Regex::new(r#"Meldung_ID="(?(.*?))""#).unwrap(); + + if re.is_match(&self.raw_value) { + let caps = re.captures(&self.raw_value).unwrap(); + return Some(caps["meldung_id"].to_string()); + } + + None + } + + pub fn database_id(&self) -> Option { + return match self.id() { + Some(id) => { + let re1 = Regex::new(r"^(?[0-9A-F]+)").unwrap(); + let re2 = Regex::new(r"(?[0-9]+)$").unwrap(); + + if re1.is_match(&id) { + match re1.find(&id).map(|m| m.as_str().to_string()) { + Some(val) => match u64::from_str_radix(&val, 16) { + Ok(val) => Some(val.to_string()), + _ => None, + }, + _ => None, + } + } else if re2.is_match(&id) { + re2.find(&id).map(|m| m.as_str().to_string()) + } else { + None + } + } + _ => None, + }; + } +} + +#[cfg(test)] +mod tests { + use crate::lkrexport::LkrExportProtocolFile; + + #[test] + fn should_read_xml_file_content() { + let actual = LkrExportProtocolFile::parse(include_str!("../testdaten/testdaten_1.xml")); + + assert!(actual.is_ok()); + assert_eq!(actual.unwrap().patients.len(), 2); + } + + #[test] + fn should_get_meldungen() { + let actual = LkrExportProtocolFile::parse(include_str!("../testdaten/testdaten_1.xml")); + + assert!(actual.is_ok()); + + let patients = actual.unwrap().patients; + + assert_eq!(patients[0].meldungen().len(), 1); + assert_eq!(patients[1].meldungen().len(), 1); + } + + #[test] + fn should_get_meldung_database_id() { + let actual = LkrExportProtocolFile::parse(include_str!("../testdaten/testdaten_1.xml")); + + assert!(actual.is_ok()); + + let patients = actual.unwrap().patients; + + assert_eq!( + patients[0].meldungen()[0].database_id(), + Some("1727528".to_string()) + ); + assert_eq!( + patients[1].meldungen()[0].database_id(), + Some("1727824".to_string()) + ); + } +} diff --git a/src/main.rs b/src/main.rs index 50d368f..43c6e8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,10 +28,12 @@ use itertools::Itertools; use crate::cli::{Cli, SubCommand}; use crate::common::{Check, DiffRecord, Icd10GroupSize}; use crate::database::DatabaseSource; +use crate::lkrexport::LkrExportProtocolFile; mod cli; mod common; mod database; +mod lkrexport; mod opal; mod resources; diff --git a/testdaten/testdaten_1.xml b/testdaten/testdaten_1.xml new file mode 100644 index 0000000..43976bf --- /dev/null +++ b/testdaten/testdaten_1.xml @@ -0,0 +1,89 @@ + + + + TEST + Musterstraße 1, 012345 Musterhausen + + + + + E123456789 + 123456789 + Tester + + Patrick + Tester + M + 01.01.1980 + + + Testweg + 1 + DE + 01234 + Musterhausen + + + + + + 11.06.2024 + I + statusaenderung + + C17.2 + 10 2015 GM + 10.06.2024 + T + + + + 11.06.2024 + praeth + + + + + + + + E123456789 + 123456789 + Tester + + Patricia + Tester + W + 01.01.1980 + + + Testweg + 1 + DE + 01234 + Musterhausen + + + + + + 11.06.2024 + I + statusaenderung + + C17.2 + 10 2015 GM + 01.01.2024 + T + + + + 10.01.2024 + praeth + + + + + + + From 0dfc6a0083d4d793ae014f50999deea77740602d Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Tue, 11 Jun 2024 19:43:13 +0200 Subject: [PATCH 02/10] feat: request data from db and compare with file --- src/cli.rs | 26 ++++++++++++++++++ src/database.rs | 28 +++++++++++++++++++- src/lkrexport.rs | 7 +++++ src/main.rs | 44 +++++++++++++++++++++++++++++++ src/resources/exported-to-lkr.sql | 25 ++++++++++++++++++ src/resources/mod.rs | 2 ++ 6 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/resources/exported-to-lkr.sql diff --git a/src/cli.rs b/src/cli.rs index 34b9783..98c0d1e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -148,6 +148,32 @@ pub enum SubCommand { )] include_histo_zyto: bool, }, + #[command(about = "Abgleich zwischen LKR-Export-Protokoll und Onkostar-Datenbank")] + CheckExport { + #[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, long, help = "LKR-Export-Protokoll-Datei")] + file: PathBuf, + #[arg(long, help = "Exportpaketnummer", default_value = "0")] + export_package: u16, + }, } fn value_is_date(value: &str) -> Result { diff --git a/src/database.rs b/src/database.rs index 6ab88e0..595d55d 100644 --- a/src/database.rs +++ b/src/database.rs @@ -24,7 +24,7 @@ use mysql::prelude::Queryable; use mysql::{params, Pool}; use crate::common::{ExportData, Icd10GroupSize}; -use crate::resources::{EXPORT_QUERY, SQL_QUERY}; +use crate::resources::{EXPORTED_TO_LKR, EXPORT_QUERY, SQL_QUERY}; pub struct DatabaseSource(String); @@ -111,4 +111,30 @@ impl DatabaseSource { Err(()) } + + pub fn exported(&self, export_id: u16) -> Result, ()> { + 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( + EXPORTED_TO_LKR, + params! { + "export_id" => export_id, + }, + |(id, xml_data)| (id, xml_data), + ) { + Ok(result) => Ok(result), + Err(_) => { + return Err(()); + } + }; + } + } + Err(_) => { + return Err(()); + } + } + + Err(()) + } } diff --git a/src/lkrexport.rs b/src/lkrexport.rs index 3c38d10..e01ea6a 100644 --- a/src/lkrexport.rs +++ b/src/lkrexport.rs @@ -50,6 +50,13 @@ impl LkrExportProtocolFile { Err(()) } + + pub fn meldungen(&self) -> Vec { + self.patients + .iter() + .flat_map(|patient| patient.meldungen()) + .collect_vec() + } } pub struct Patient { diff --git a/src/main.rs b/src/main.rs index 43c6e8e..c6d9801 100644 --- a/src/main.rs +++ b/src/main.rs @@ -423,6 +423,50 @@ fn main() -> Result<(), Box> { )); }); } + SubCommand::CheckExport { + database, + host, + password, + port, + user, + file, + export_package, + } => { + let password = request_password_if_none(password); + + let _ = term.write_line( + &style(format!( + "Warte auf Daten für den LKR-Export '{}'...", + export_package + )) + .blue() + .to_string(), + ); + + let db = DatabaseSource::new(&database, &host, &password, port, &user); + + let exported_db_msg = db + .exported(export_package) + .map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?; + + let xml_file_content = LkrExportProtocolFile::parse_file(file.as_path()) + .map_err(|_e| "Fehler bei Zugriff auf die Protokolldatei")?; + + let _ = term.clear_last_lines(1); + + if exported_db_msg.len() != xml_file_content.meldungen().len() { + let _ = term.write_line( + &style(format!("Nicht übereinstimmende Anzahl an Meldungen:",)) + .yellow() + .to_string(), + ); + let _ = term.write_line(&format!( + "Datenbank: {:>10}\nProtokolldatei: {:>10}", + exported_db_msg.len(), + xml_file_content.meldungen().len() + )); + } + } } Ok(()) diff --git a/src/resources/exported-to-lkr.sql b/src/resources/exported-to-lkr.sql new file mode 100644 index 0000000..5e8655b --- /dev/null +++ b/src/resources/exported-to-lkr.sql @@ -0,0 +1,25 @@ +/* + * 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 + id, + xml_daten +FROM lkr_meldung_export +WHERE lkr_export = :export_id OR (0 = :export_id AND lkr_export IN (SELECT MAX(lkr_export) FROM lkr_meldung_export)); \ No newline at end of file diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 78c2878..5df086a 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -21,3 +21,5 @@ pub const SQL_QUERY: &str = include_str!("query.sql"); pub const EXPORT_QUERY: &str = include_str!("export.sql"); + +pub const EXPORTED_TO_LKR: &str = include_str!("exported-to-lkr.sql"); From 53cde34166a25893aedb4126a7e741dfd367391c Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Tue, 11 Jun 2024 20:19:41 +0200 Subject: [PATCH 03/10] feat: use id as string --- src/resources/exported-to-lkr.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/exported-to-lkr.sql b/src/resources/exported-to-lkr.sql index 5e8655b..9820553 100644 --- a/src/resources/exported-to-lkr.sql +++ b/src/resources/exported-to-lkr.sql @@ -19,7 +19,7 @@ */ SELECT - id, + CONVERT(id,char) AS id, xml_daten FROM lkr_meldung_export WHERE lkr_export = :export_id OR (0 = :export_id AND lkr_export IN (SELECT MAX(lkr_export) FROM lkr_meldung_export)); \ No newline at end of file From ac2f73d0bbca76cd1abbccfcd08310da62a4ea9c Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Tue, 11 Jun 2024 20:26:40 +0200 Subject: [PATCH 04/10] fix: ignore exports with typ -1 --- src/resources/exported-to-lkr.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resources/exported-to-lkr.sql b/src/resources/exported-to-lkr.sql index 9820553..ace8d03 100644 --- a/src/resources/exported-to-lkr.sql +++ b/src/resources/exported-to-lkr.sql @@ -22,4 +22,5 @@ SELECT CONVERT(id,char) AS id, xml_daten FROM lkr_meldung_export -WHERE lkr_export = :export_id OR (0 = :export_id AND lkr_export IN (SELECT MAX(lkr_export) FROM lkr_meldung_export)); \ No newline at end of file +WHERE typ <> -1 + AND (lkr_export = :export_id OR (0 = :export_id AND lkr_export IN (SELECT MAX(lkr_export) FROM lkr_meldung_export))); \ No newline at end of file From c5c0fcf6d46f03f741d5f377244fb638a6ae651f Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jun 2024 13:40:15 +0200 Subject: [PATCH 05/10] feat: add function to resolve database id --- src/lkrexport.rs | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/lkrexport.rs b/src/lkrexport.rs index e01ea6a..ad187d4 100644 --- a/src/lkrexport.rs +++ b/src/lkrexport.rs @@ -105,28 +105,31 @@ impl Meldung { None } + #[allow(unused)] pub fn database_id(&self) -> Option { - return match self.id() { - Some(id) => { - let re1 = Regex::new(r"^(?[0-9A-F]+)").unwrap(); - let re2 = Regex::new(r"(?[0-9]+)$").unwrap(); - - if re1.is_match(&id) { - match re1.find(&id).map(|m| m.as_str().to_string()) { - Some(val) => match u64::from_str_radix(&val, 16) { - Ok(val) => Some(val.to_string()), - _ => None, - }, - _ => None, - } - } else if re2.is_match(&id) { - re2.find(&id).map(|m| m.as_str().to_string()) - } else { - None - } - } + match self.id() { + Some(id) => to_database_id(&id), _ => None, - }; + } + } +} + +pub fn to_database_id(id: &str) -> Option { + let re1 = Regex::new(r"^(?[0-9A-F]+)").unwrap(); + let re2 = Regex::new(r"(?[0-9]+)$").unwrap(); + + if re1.is_match(id) { + match re1.find(id).map(|m| m.as_str().to_string()) { + Some(val) => match u64::from_str_radix(&val, 16) { + Ok(val) => Some(val.to_string()), + _ => None, + }, + _ => None, + } + } else if re2.is_match(id) { + re2.find(id).map(|m| m.as_str().to_string()) + } else { + None } } From af4dec0279a55f1978ab0dfb1bf25d14dfbd33da Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jun 2024 13:42:02 +0200 Subject: [PATCH 06/10] feat: show entries missing in compared source --- src/main.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index c6d9801..7dcb215 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +use std::collections::HashMap; use std::error::Error; use clap::Parser; @@ -28,7 +29,7 @@ use itertools::Itertools; use crate::cli::{Cli, SubCommand}; use crate::common::{Check, DiffRecord, Icd10GroupSize}; use crate::database::DatabaseSource; -use crate::lkrexport::LkrExportProtocolFile; +use crate::lkrexport::{to_database_id, LkrExportProtocolFile}; mod cli; mod common; @@ -445,16 +446,42 @@ fn main() -> Result<(), Box> { let db = DatabaseSource::new(&database, &host, &password, port, &user); - let exported_db_msg = db + let db_meldungen = db .exported(export_package) .map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?; - let xml_file_content = LkrExportProtocolFile::parse_file(file.as_path()) - .map_err(|_e| "Fehler bei Zugriff auf die Protokolldatei")?; + let db_row_count = db_meldungen.len(); + + let db_meldungen = db_meldungen + .iter() + .map(|entry| LkrExportProtocolFile::parse(&entry.1)) + .filter(|entry| entry.is_ok()) + .flat_map(|entry| entry.unwrap().meldungen()) + .filter(|meldung| meldung.id().is_some()) + .map(|meldung| (meldung.id().unwrap(), meldung)) + .collect::>(); + + let xml_meldungen = LkrExportProtocolFile::parse_file(file.as_path()) + .map_err(|_e| "Fehler bei Zugriff auf die Protokolldatei")? + .meldungen() + .into_iter() + .filter(|meldung| meldung.id().is_some()) + .map(|meldung| (meldung.id().unwrap(), meldung)) + .collect::>(); let _ = term.clear_last_lines(1); - if exported_db_msg.len() != xml_file_content.meldungen().len() { + let _ = term.write_line( + &style(format!( + "{} Datenbankeinträge mit {} Meldungen abgerufen", + db_row_count, + db_meldungen.len() + )) + .green() + .to_string(), + ); + + if db_meldungen.len() != xml_meldungen.len() { let _ = term.write_line( &style(format!("Nicht übereinstimmende Anzahl an Meldungen:",)) .yellow() @@ -462,9 +489,51 @@ fn main() -> Result<(), Box> { ); let _ = term.write_line(&format!( "Datenbank: {:>10}\nProtokolldatei: {:>10}", - exported_db_msg.len(), - xml_file_content.meldungen().len() + db_meldungen.len(), + xml_meldungen.len() )); + + let missing_db_ids = xml_meldungen + .keys() + .filter(|&key| !db_meldungen.contains_key(key)) + .collect_vec(); + + if !missing_db_ids.is_empty() { + let _ = term.write_line( + &style(format!("In der Datenbank fehlende Meldungen::",)) + .yellow() + .to_string(), + ); + + missing_db_ids.iter().sorted().for_each(|&item| { + let _ = term.write_line(&format!( + "{} ({})", + item, + to_database_id(item).unwrap_or("?".into()) + )); + }); + } + + let missing_xml_ids = db_meldungen + .keys() + .filter(|&key| !xml_meldungen.contains_key(key)) + .collect_vec(); + + if !missing_xml_ids.is_empty() { + let _ = term.write_line( + &style(format!("In der Protokolldatei fehlende Meldungen::",)) + .yellow() + .to_string(), + ); + + missing_xml_ids.iter().sorted().for_each(|&item| { + let _ = term.write_line(&format!( + "{} ({})", + item, + to_database_id(item).unwrap_or("?".into()) + )); + }); + } } } } From 3e07408a7db42ad91663a856e72a94f2a94c1d83 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jun 2024 15:51:23 +0200 Subject: [PATCH 07/10] feat: show entries with multiple meldung item in xml_daten --- src/main.rs | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7dcb215..b2b7d1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -446,13 +446,11 @@ fn main() -> Result<(), Box> { let db = DatabaseSource::new(&database, &host, &password, port, &user); - let db_meldungen = db + let db_entries = db .exported(export_package) .map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?; - let db_row_count = db_meldungen.len(); - - let db_meldungen = db_meldungen + let db_meldungen = db_entries .iter() .map(|entry| LkrExportProtocolFile::parse(&entry.1)) .filter(|entry| entry.is_ok()) @@ -474,7 +472,7 @@ fn main() -> Result<(), Box> { let _ = term.write_line( &style(format!( "{} Datenbankeinträge mit {} Meldungen abgerufen", - db_row_count, + db_entries.len(), db_meldungen.len() )) .green() @@ -483,7 +481,7 @@ fn main() -> Result<(), Box> { if db_meldungen.len() != xml_meldungen.len() { let _ = term.write_line( - &style(format!("Nicht übereinstimmende Anzahl an Meldungen:",)) + &style("\nNicht übereinstimmende Anzahl an Meldungen:") .yellow() .to_string(), ); @@ -500,7 +498,7 @@ fn main() -> Result<(), Box> { if !missing_db_ids.is_empty() { let _ = term.write_line( - &style(format!("In der Datenbank fehlende Meldungen::",)) + &style("\nIn der Datenbank fehlende Meldungen:") .yellow() .to_string(), ); @@ -521,7 +519,7 @@ fn main() -> Result<(), Box> { if !missing_xml_ids.is_empty() { let _ = term.write_line( - &style(format!("In der Protokolldatei fehlende Meldungen::",)) + &style("\nIn der Protokolldatei fehlende Meldungen:") .yellow() .to_string(), ); @@ -535,6 +533,31 @@ fn main() -> Result<(), Box> { }); } } + + let multiple_meldung_entries = db_entries + .iter() + .map(|(lkr_meldung, meldung)| (lkr_meldung, LkrExportProtocolFile::parse(&meldung))) + .filter_map(|(lkr_meldung, meldung)| { + if meldung.unwrap().meldungen().len() > 1 { + Some(lkr_meldung) + } else { + None + } + }) + .sorted() + .collect_vec(); + + if !multiple_meldung_entries.is_empty() { + let _ = term.write_line( + &style("\nFolgende Einträge in `lkr_meldung_export` haben mehrere Meldungsinhalte in `xml_daten`:") + .yellow() + .to_string(), + ); + + multiple_meldung_entries.iter().for_each(|item| { + let _ = term.write_line(&item.to_string()); + }); + } } } From 58026f11d0dd4ef0cb8bacaa70fa53d1766023c3 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jun 2024 16:48:58 +0200 Subject: [PATCH 08/10] feat: extract ICD10 code --- src/lkrexport.rs | 32 +++++++++++++++++++++++++++++++- testdaten/testdaten_1.xml | 2 +- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/lkrexport.rs b/src/lkrexport.rs index ad187d4..27e3e2b 100644 --- a/src/lkrexport.rs +++ b/src/lkrexport.rs @@ -93,6 +93,7 @@ impl FromStr for Meldung { } } +#[allow(unused)] impl Meldung { pub fn id(&self) -> Option { let re = Regex::new(r#"Meldung_ID="(?(.*?))""#).unwrap(); @@ -105,7 +106,18 @@ impl Meldung { None } - #[allow(unused)] + pub fn icd10(&self) -> Option { + let re = Regex::new(r"(?s)(?(.*?))") + .unwrap(); + + if re.is_match(&self.raw_value) { + let caps = re.captures(&self.raw_value).unwrap(); + return Some(caps["icd10"].to_string()); + } + + None + } + pub fn database_id(&self) -> Option { match self.id() { Some(id) => to_database_id(&id), @@ -174,4 +186,22 @@ mod tests { Some("1727824".to_string()) ); } + + #[test] + fn should_get_meldung_icd10() { + let actual = LkrExportProtocolFile::parse(include_str!("../testdaten/testdaten_1.xml")); + + assert!(actual.is_ok()); + + let patients = actual.unwrap().patients; + + assert_eq!( + patients[0].meldungen()[0].icd10(), + Some("C17.1".to_string()) + ); + assert_eq!( + patients[1].meldungen()[0].icd10(), + Some("C17.2".to_string()) + ); + } } diff --git a/testdaten/testdaten_1.xml b/testdaten/testdaten_1.xml index 43976bf..3b01820 100644 --- a/testdaten/testdaten_1.xml +++ b/testdaten/testdaten_1.xml @@ -31,7 +31,7 @@ I statusaenderung - C17.2 + C17.1 10 2015 GM 10.06.2024 T From fd454675133a8fb0b5dfd969bf6e4f08452e2bb8 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jun 2024 16:50:29 +0200 Subject: [PATCH 09/10] feat: show items not equal within db and xml --- src/lkrexport.rs | 19 ++++++++++++++++++- src/main.rs | 46 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/lkrexport.rs b/src/lkrexport.rs index 27e3e2b..ee3e25c 100644 --- a/src/lkrexport.rs +++ b/src/lkrexport.rs @@ -124,6 +124,11 @@ impl Meldung { _ => None, } } + + pub fn no_linebreak(&self) -> String { + let re = Regex::new(r"\n\s*").unwrap(); + re.replace_all(&self.raw_value, "").trim().to_string() + } } pub fn to_database_id(id: &str) -> Option { @@ -147,7 +152,7 @@ pub fn to_database_id(id: &str) -> Option { #[cfg(test)] mod tests { - use crate::lkrexport::LkrExportProtocolFile; + use crate::lkrexport::{LkrExportProtocolFile, Meldung}; #[test] fn should_read_xml_file_content() { @@ -204,4 +209,16 @@ mod tests { Some("C17.2".to_string()) ); } + + #[test] + fn should_get_meldung_with_trimmed_margin() { + let meldung = Meldung { + raw_value: " \n TestInhalt 3\n\n".into(), + }; + + assert_eq!( + meldung.no_linebreak(), + "TestInhalt 3".to_string() + ); + } } diff --git a/src/main.rs b/src/main.rs index b2b7d1e..4c0a91d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ use itertools::Itertools; use crate::cli::{Cli, SubCommand}; use crate::common::{Check, DiffRecord, Icd10GroupSize}; use crate::database::DatabaseSource; -use crate::lkrexport::{to_database_id, LkrExportProtocolFile}; +use crate::lkrexport::{to_database_id, LkrExportProtocolFile, Meldung}; mod cli; mod common; @@ -467,6 +467,11 @@ fn main() -> Result<(), Box> { .map(|meldung| (meldung.id().unwrap(), meldung)) .collect::>(); + let missing_xml_ids = db_meldungen + .keys() + .filter(|&key| !xml_meldungen.contains_key(key)) + .collect_vec(); + let _ = term.clear_last_lines(1); let _ = term.write_line( @@ -512,11 +517,6 @@ fn main() -> Result<(), Box> { }); } - let missing_xml_ids = db_meldungen - .keys() - .filter(|&key| !xml_meldungen.contains_key(key)) - .collect_vec(); - if !missing_xml_ids.is_empty() { let _ = term.write_line( &style("\nIn der Protokolldatei fehlende Meldungen:") @@ -558,6 +558,40 @@ fn main() -> Result<(), Box> { let _ = term.write_line(&item.to_string()); }); } + + let different_content = db_meldungen + .iter() + .filter(|(id, _)| !missing_xml_ids.contains(id)) + .filter(|(id, meldung)| { + xml_meldungen + .get(&id.to_string()) + .unwrap_or(&Meldung { + raw_value: String::new(), + }) + .no_linebreak() + != meldung.no_linebreak() + }) + .map(|(_, meldung)| meldung.id().unwrap_or("?".into())) + .collect_vec(); + + if !different_content.is_empty() { + let _ = term.write_line( + &style(&format!( + "\nFolgende {} Meldungen unterscheiden sich in der Datenbank und der Protokolldatei:", + different_content.len() + )) + .yellow() + .to_string(), + ); + + different_content.iter().sorted().for_each(|item| { + let _ = term.write_line(&format!( + "{} ({})", + item, + to_database_id(item).unwrap_or("?".into()) + )); + }); + } } } From d3181510555575a51ef914b4521877ae7cc00ed8 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jun 2024 17:41:06 +0200 Subject: [PATCH 10/10] docs: add section to README describing `check-export` command --- README.md | 10 +++++++++- src/main.rs | 12 ++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0a2bb8a..15a450a 100644 --- a/README.md +++ b/README.md @@ -98,4 +98,12 @@ Options: Die Anwendung kann auch die Conditions in der CSV-Datei mit der Onkostar-Datenbank direkt vergleichen. Hierzu kann der Befehl `compare` genutzt werden. Dieser verwendet alle Optionen für die Datenbank und die -Option `--file` für die CSV-Datei und gibt eine Übersicht auf der Konsole aus. \ No newline at end of file +Option `--file` für die CSV-Datei und gibt eine Übersicht auf der Konsole aus. + +## Vergleich der XML-basierten LKR-Export-Protokolldatei mit der Datenbank + +Mithilfe dieser Anwendung kann auch der aktuelle Inhalt der Datenbank gegen die LKR-Export-Protokolldatei für einen +Export verglichen werden. + +Der Befehl `check-export` kann zusammen mit der Angabe der Protokolldatei (`--file`) und der Angabe des +Exports (`--export-package=...`) und den Optionen für den Datenbankzugriff ausgeführt werden. \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4c0a91d..bb8ab9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -536,7 +536,7 @@ fn main() -> Result<(), Box> { let multiple_meldung_entries = db_entries .iter() - .map(|(lkr_meldung, meldung)| (lkr_meldung, LkrExportProtocolFile::parse(&meldung))) + .map(|(lkr_meldung, meldung)| (lkr_meldung, LkrExportProtocolFile::parse(meldung))) .filter_map(|(lkr_meldung, meldung)| { if meldung.unwrap().meldungen().len() > 1 { Some(lkr_meldung) @@ -584,11 +584,15 @@ fn main() -> Result<(), Box> { .to_string(), ); - different_content.iter().sorted().for_each(|item| { + let _ = term.write_line( + "Dies kann auch aufgrund der verwendeten XML-Encodierung auftreten und bedeutet nicht immer eine inhaltliche Abweichung." + ); + + different_content.iter().sorted().for_each(|id| { let _ = term.write_line(&format!( "{} ({})", - item, - to_database_id(item).unwrap_or("?".into()) + id, + to_database_id(id).unwrap_or("?".into()) )); }); }