Compare commits

..

36 Commits

Author SHA1 Message Date
d40b1c5887 chore: update dependencies 2025-01-19 22:37:47 +01:00
79402510fd Update dependencies 2024-10-29 00:00:23 +01:00
c6a589b2fd fix: ignore oBDS containing Menge_Tumorkonferenz
This will replace previous used filter and only includes oBDS messages without
information about tumor board information.
2024-09-13 16:15:14 +02:00
851bdad9d0 chore: update copyright notice 2024-09-13 16:15:14 +02:00
0b6024e80b
Merge pull request #1 from pcvolkmer/issue_5
feat: ignore non oBDS 2.x messages
2024-08-30 17:42:59 +02:00
fb60cb5042 feat: ignore non oBDS 2.x messages 2024-07-24 13:55:20 +02:00
f48a96e64d refactor: Extract func to print list of missing messages 2024-07-03 19:43:38 +02:00
2ef4ec4c90 fix: use correct table alias 2024-07-02 09:17:08 +02:00
f7dd9c3b37 chore: use common query filter for export 2024-07-02 08:54:48 +02:00
cfd46c34af refactor: use references as argument 2024-06-25 15:49:20 +02:00
1c41b71ca2 docs: add information about reformatting XML files 2024-06-23 14:49:20 +02:00
925aa4e786 refactor: change parameter type to &str 2024-06-23 14:48:47 +02:00
54dd9163a9 refactor: use 'package' as primary argument name but keep 'export-package' 2024-06-18 09:22:32 +02:00
97004be3ba chore: bump version 2024-06-13 16:33:52 +02:00
7fc724db6a
Merge pull request #4 from pcvolkmer/issue_1
feat: show schema versions and change extern detection
2024-06-13 16:22:41 +02:00
46eb43aab9 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`.
2024-06-13 16:16:50 +02:00
e1ca2d927a feat: also remove windows like line breaks 2024-06-13 09:25:46 +02:00
07478cf6a3 feat: convert self-closing tags when sanitizing xml 2024-06-13 09:24:16 +02:00
3520806e79 feat: sort items with different content by database id 2024-06-13 09:20:54 +02:00
aa1ec6c114 test: add test for Meldung#id() 2024-06-12 18:10:07 +02:00
a905a816f5 style: use brighter blue to make message more visible 2024-06-12 18:03:22 +02:00
8869090b08
Merge pull request #3 from pcvolkmer/issue_2
Vergleich der LKR-Meldungen in Datenbank mit LKR-Export-Protokolldatei
2024-06-12 17:49:59 +02:00
d318151055 docs: add section to README describing check-export command 2024-06-12 17:41:06 +02:00
fd45467513 feat: show items not equal within db and xml 2024-06-12 16:50:29 +02:00
58026f11d0 feat: extract ICD10 code 2024-06-12 16:48:58 +02:00
3e07408a7d feat: show entries with multiple meldung item in xml_daten 2024-06-12 15:51:23 +02:00
af4dec0279 feat: show entries missing in compared source 2024-06-12 13:42:02 +02:00
c5c0fcf6d4 feat: add function to resolve database id 2024-06-12 13:40:48 +02:00
ac2f73d0bb fix: ignore exports with typ -1 2024-06-11 20:26:40 +02:00
53cde34166 feat: use id as string 2024-06-11 20:19:41 +02:00
0dfc6a0083 feat: request data from db and compare with file 2024-06-11 19:43:13 +02:00
6617ce76a0 feat: parse LKR protocol file 2024-06-11 19:30:25 +02:00
66c02edccc build: create release drafts and upload assets 2024-06-07 11:33:21 +02:00
665e728040 build: use powershell to create zip file on windows 2024-06-07 11:32:30 +02:00
2b94618f9f test: add simple test to check icd10 code mapping 2024-06-07 09:54:46 +02:00
ceba8d28b9 build: add GitHub action to run tests 2024-06-07 09:34:55 +02:00
17 changed files with 992 additions and 65 deletions

46
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,46 @@
name: Create release and upload assets
on:
push:
tags:
- 'v*'
jobs:
linuxbuild:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Run tests
run: cargo test --verbose
- run: make linux-package
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
draft: 'true'
make_latest: 'true'
generate_release_notes: 'true'
files: |
*linux.tar.gz
windowsbuild:
runs-on: windows-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Run tests
run: cargo test --verbose
- run: make win-package
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
draft: 'true'
make_latest: 'true'
generate_release_notes: 'true'
files: |
*win64.zip

22
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: "Run Tests"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

View File

@ -1,6 +1,6 @@
[package]
name = "bzkf-rwdp-check"
version = "0.3.3"
version = "0.4.0"
edition = "2021"
authors = ["Paul-Christian Volkmer <volkmer_p@ukw.de>"]
description = "Anwendung zur Durchführung einer Plausibilitätsprüfung anhand der Daten für die BZKF Real World Data Platform."
@ -11,11 +11,11 @@ clap = { version = "4.5", features = ["std", "help", "usage", "derive", "error-c
console = "0.15"
csv = "1.3"
dialoguer = "0.11"
itertools = "0.13"
itertools = "0.14"
mysql = "25.0"
serde = { version = "1.0", features = ["derive"] }
urlencoding = "2.1"
regex = "1.10"
regex = "1.11"
[profile.release]
opt-level = "s"

View File

@ -16,7 +16,8 @@ win-package: win-binary-x86_64
cp target/x86_64-pc-windows-gnu/release/$(PROG_NAME).exe $(PROG_NAME)/
cp README.md $(PROG_NAME)/
cp LICENSE $(PROG_NAME)/
zip $(PROG_NAME)-$(TAG)_win64.zip $(PROG_NAME)/*
# first try (linux) zip command, then powershell sub command to create ZIP file
zip $(PROG_NAME)-$(TAG)_win64.zip $(PROG_NAME)/* || powershell Compress-ARCHIVE $(PROG_NAME) $(PROG_NAME)-$(TAG)_win64.zip
rm -rf $(PROG_NAME) || true
.PHONY: linux-package

View File

@ -2,6 +2,9 @@
Anwendung zur Durchführung einer Plausibilitätsprüfung anhand der Daten für die BZKF Real World Data Platform.
**Hinweis:** Dies ist eine Weiterführung des ursprünglichen
Projekts [bzkf-rwdp-check](https://github.com/CCC-MF/bzkf-rwdp-check)
## Aufbau der ETL-Strecke an den Standorten
Die Daten werden aus der Onkostar-Datenbank ausgelesen und in Apache-Kafka eingespeist.
@ -67,10 +70,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
@ -99,3 +106,15 @@ Die Anwendung kann auch die Conditions in der CSV-Datei mit der Onkostar-Datenba
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.
## 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 (`--package=...` bzw. `--export-package=...`) und den Optionen für den Datenbankzugriff ausgeführt werden.
Zur Überprüfung werden irrelevante Leerzeichen und Zeilenumbrüche entfernt und _Self-Closed-Tags_ ersetzt
(`<Meldeanlass />` => `<Meldeanlass></Meldeanlass>`),
da in Onkostar in der Datenbank und der LKR-Export-Protokolldatei verschiedene Formatierungen verwendet werden (können).

View File

@ -1,7 +1,7 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 Comprehensive Cancer Center Mainfranken and contributors.
* Copyright (C) 2024 the original author or authors.
*
* 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
@ -62,6 +62,8 @@ pub enum SubCommand {
year: String,
#[arg(long, value_parser = value_is_date, help = "Ignoriere LKR-Exporte seit Datum")]
ignore_exports_since: Option<String>,
#[arg(long, help = "Ignoriere Meldungen, die nicht im oBDS 2.x Format sind")]
ignore_non_obds_2: bool,
#[arg(long, help = "Meldungen mit externer Diagnose einschließen")]
include_extern: bool,
#[arg(
@ -69,6 +71,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"
@ -101,6 +105,8 @@ pub enum SubCommand {
year: String,
#[arg(long, value_parser = value_is_date, help = "Ignoriere LKR-Exporte seit Datum")]
ignore_exports_since: Option<String>,
#[arg(long, help = "Ignoriere Meldungen, die nicht im oBDS 2.x Format sind")]
ignore_non_obds_2: bool,
#[arg(long, help = "Export mit Trennzeichen ';' für Excel")]
xls_csv: bool,
#[arg(long, help = "Meldungen mit externer Diagnose einschließen")]
@ -140,6 +146,8 @@ pub enum SubCommand {
year: String,
#[arg(long, value_parser = value_is_date, help = "Ignoriere LKR-Exporte seit Datum")]
ignore_exports_since: Option<String>,
#[arg(long, help = "Ignoriere Meldungen, die nicht im oBDS 2.x Format sind")]
ignore_non_obds_2: bool,
#[arg(long, help = "Meldungen mit externer Diagnose einschließen")]
include_extern: bool,
#[arg(
@ -148,6 +156,37 @@ 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<String>,
#[arg(short = 'u', long, help = "Benutzername")]
user: String,
#[arg(short, long, help = "LKR-Export-Protokoll-Datei")]
file: PathBuf,
#[arg(
long,
alias = "export-package",
help = "Exportpaketnummer",
default_value = "0"
)]
package: u16,
},
}
fn value_is_date(value: &str) -> Result<String, String> {

View File

@ -1,7 +1,7 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 Comprehensive Cancer Center Mainfranken and contributors.
* Copyright (C) 2024 the original author or authors.
*
* 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
@ -23,6 +23,7 @@ use serde::{Deserialize, Serialize};
pub struct Icd10GroupSize {
pub name: String,
pub schema_version: Option<String>,
pub size: usize,
}
@ -68,6 +69,7 @@ impl Check {
.map(|(icd10, group)| (icd10, group.collect::<Vec<_>>()))
.map(|record| Icd10GroupSize {
name: record.0,
schema_version: None,
size: record.1.len(),
})
.collect::<Vec<_>>();
@ -116,3 +118,15 @@ impl Check {
.to_string()
}
}
#[cfg(test)]
mod tests {
use crate::common::Check;
#[test]
fn should_map_icd10_code_as_expected() {
assert_eq!(Check::map_icd_code("D39.1"), "C56, D39.1");
assert_eq!(Check::map_icd_code("C00"), "C00-C14");
assert_eq!(Check::map_icd_code("F79.9"), "Other");
}
}

View File

@ -1,7 +1,7 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 Comprehensive Cancer Center Mainfranken and contributors.
* Copyright (C) 2024 the original author or authors.
*
* 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
@ -24,7 +24,23 @@ 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, 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);
@ -39,27 +55,35 @@ impl DatabaseSource {
&self,
year: &str,
ignore_exports_since: &str,
ignore_non_obds_2: bool,
include_extern: bool,
include_histo_zyto: bool,
schema_versions: bool,
) -> Result<Vec<Icd10GroupSize>, ()> {
let params = params! {
"year" => year,
"ignore_exports_since" => ignore_exports_since,
"ignore_non_obds_2" => if ignore_non_obds_2 { 1 } else { 0 },
"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(()),
};
}
}
@ -75,6 +99,7 @@ impl DatabaseSource {
&self,
year: &str,
ignore_exports_since: &str,
ignore_non_obds_2: bool,
use_pat_id: bool,
include_extern: bool,
include_histo_zyto: bool,
@ -87,6 +112,7 @@ impl DatabaseSource {
params! {
"year" => year,
"ignore_exports_since" => ignore_exports_since,
"ignore_non_obds_2" => if ignore_non_obds_2 { 1 } else { 0 },
"include_extern" => if include_extern { 1 } else { 0 },
"include_histo_zyto" => if include_histo_zyto { 1 } else { 0 }
},
@ -111,4 +137,30 @@ impl DatabaseSource {
Err(())
}
pub fn exported(&self, package: u16) -> Result<Vec<(String, String)>, ()> {
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" => package,
},
|(id, xml_data)| (id, xml_data),
) {
Ok(result) => Ok(result),
Err(_) => {
return Err(());
}
};
}
}
Err(_) => {
return Err(());
}
}
Err(())
}
}

270
src/lkrexport.rs Normal file
View File

@ -0,0 +1,270 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 the original author or authors.
*
* 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<Patient>,
}
impl LkrExportProtocolFile {
pub fn parse_file(path: &Path) -> Result<LkrExportProtocolFile, ()> {
let xml_file_content = fs::read_to_string(path).map_err(|_| ())?;
Self::parse(&xml_file_content)
}
pub fn parse(content: &str) -> Result<LkrExportProtocolFile, ()> {
let re = Regex::new(r"(?s)(?<patient><Patient>(.*?)</Patient>)").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 fn meldungen(&self) -> Vec<Meldung> {
self.patients
.iter()
.flat_map(|patient| patient.meldungen())
.collect_vec()
}
}
pub struct Patient {
pub raw_value: String,
}
impl Patient {
pub fn meldungen(&self) -> Vec<Meldung> {
let re = Regex::new(r"(?s)(?<meldung><Meldung(.*?)</Meldung>)").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<Self, Self::Err> {
Ok(Meldung {
raw_value: s.to_string(),
})
}
}
#[allow(unused)]
impl Meldung {
pub fn id(&self) -> Option<String> {
let re = Regex::new(r#"Meldung_ID="(?<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 icd10(&self) -> Option<String> {
let re = Regex::new(r"(?s)<Primaertumor_ICD_Code>(?<icd10>(.*?))</Primaertumor_ICD_Code>")
.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<String> {
match self.id() {
Some(id) => to_database_id(&id),
_ => None,
}
}
pub fn sanitized_xml_string(&self) -> String {
let re = Regex::new(r"[\r|\n]+\s*").unwrap();
let content = re.replace_all(&self.raw_value, "").trim().to_string();
let re = Regex::new(r"<[^>]+/>").unwrap();
if re.is_match(&content) {
let mut c = content.to_string();
re.find_iter(&content)
.map(|m| m.as_str().to_string().replace('<', "").replace("/>", ""))
.for_each(|tag| {
c = c.replace(&format!("<{}/>", tag), &format!("<{}></{}>", tag, tag));
});
return c;
}
content
}
}
pub fn to_database_id(id: &str) -> Option<String> {
let re1 = Regex::new(r"^(?<id>[0-9A-F]+)").unwrap();
let re2 = Regex::new(r"(?<id>[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
}
}
#[cfg(test)]
mod tests {
use crate::lkrexport::{LkrExportProtocolFile, Meldung};
#[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_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].id(),
Some("TEST1727528".to_string())
);
assert_eq!(
patients[1].meldungen()[0].id(),
Some("001A5D50-TEST".to_string())
);
}
#[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())
);
}
#[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())
);
}
#[test]
fn should_get_meldung_with_trimmed_margin() {
let meldung = Meldung {
raw_value: " <Test>\n <Test2>TestInhalt 3</Test2>\n</Test>\n".into(),
};
assert_eq!(
meldung.sanitized_xml_string(),
"<Test><Test2>TestInhalt 3</Test2></Test>".to_string()
);
}
#[test]
fn should_get_meldung_without_self_closing_tags() {
let meldung = Meldung {
raw_value:
" <Test>\n <Test2/>\n <Content>Test</Content>\n <Test3/>\n <Test2/>\n</Test>\n"
.into(),
};
assert_eq!(
meldung.sanitized_xml_string(),
"<Test><Test2></Test2><Content>Test</Content><Test3></Test3><Test2></Test2></Test>"
.to_string()
);
}
}

View File

@ -1,7 +1,7 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 Comprehensive Cancer Center Mainfranken and contributors.
* Copyright (C) 2024 the original author or authors.
*
* 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
@ -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,10 +29,12 @@ 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, Meldung};
mod cli;
mod common;
mod database;
mod lkrexport;
mod opal;
mod resources;
@ -48,9 +51,9 @@ fn request_password_if_none(password: Option<String>) -> String {
}
}
fn sanitize_year(year: String) -> String {
fn sanitize_year(year: &str) -> String {
if year.len() == 4 {
year
year.to_string()
} else {
format!("2{:0>3}", year)
}
@ -64,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) {
@ -118,15 +129,18 @@ fn main() -> Result<(), Box<dyn Error>> {
user,
year,
ignore_exports_since,
ignore_non_obds_2,
include_extern,
include_histo_zyto,
schema_versions,
} => {
let password = request_password_if_none(password);
let year = sanitize_year(year);
let year = sanitize_year(&year);
let _ = term.write_line(
&style(format!("Warte auf Daten für das Diagnosejahr {}...", year))
.blue()
.bright()
.to_string(),
);
@ -135,8 +149,10 @@ fn main() -> Result<(), Box<dyn Error>> {
.check(
&year,
&ignore_exports_since.unwrap_or("9999-12-31".into()),
ignore_non_obds_2,
include_extern,
include_histo_zyto,
schema_versions,
)
.map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?;
@ -155,16 +171,18 @@ fn main() -> Result<(), Box<dyn Error>> {
output,
year,
ignore_exports_since,
ignore_non_obds_2,
xls_csv,
include_extern,
include_histo_zyto,
} => {
let password = request_password_if_none(password);
let year = sanitize_year(year);
let year = sanitize_year(&year);
let _ = term.write_line(
&style(format!("Warte auf Daten für das Diagnosejahr {}...", year))
.blue()
.bright()
.to_string(),
);
@ -173,6 +191,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.export(
&year,
&ignore_exports_since.unwrap_or("9999-12-31".into()),
ignore_non_obds_2,
pat_id,
include_extern,
include_histo_zyto,
@ -217,15 +236,17 @@ fn main() -> Result<(), Box<dyn Error>> {
file,
year,
ignore_exports_since,
ignore_non_obds_2,
include_extern,
include_histo_zyto,
} => {
let password = request_password_if_none(password);
let year = sanitize_year(year);
let year = sanitize_year(&year);
let _ = term.write_line(
&style(format!("Warte auf Daten für das Diagnosejahr {}...", year))
.blue()
.bright()
.to_string(),
);
@ -234,6 +255,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.export(
&year,
&ignore_exports_since.unwrap_or("9999-12-31".into()),
ignore_non_obds_2,
pat_id,
include_extern,
include_histo_zyto,
@ -247,7 +269,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut not_in_csv = db_items
.iter()
.filter(|db_item| {
.filter(|&db_item| {
!csv_items
.iter()
.map(|csv_item| &csv_item.condition_id)
@ -277,7 +299,7 @@ fn main() -> Result<(), Box<dyn Error>> {
not_in_csv
.iter()
.for_each(|item| match Check::is_relevant(&item.icd_10_code) {
.for_each(|&item| match Check::is_relevant(&item.icd_10_code) {
true => {
let _ = term.write_line(&format!(
"{:<64} {:<10} {:<5} {:<5} {}",
@ -308,7 +330,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut not_in_db = csv_items
.iter()
.filter(|csv_item| {
.filter(|&csv_item| {
!db_items
.iter()
.map(|db_item| &db_item.condition_id)
@ -421,6 +443,185 @@ fn main() -> Result<(), Box<dyn Error>> {
));
});
}
SubCommand::CheckExport {
database,
host,
password,
port,
user,
file,
package,
} => {
let password = request_password_if_none(password);
let _ = term.write_line(
&style(format!(
"Warte auf Daten für den LKR-Export '{}'...",
package
))
.blue()
.bright()
.to_string(),
);
let db = DatabaseSource::new(&database, &host, &password, port, &user);
let db_entries = db
.exported(package)
.map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?;
let db_meldungen = db_entries
.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::<HashMap<_, _>>();
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::<HashMap<_, _>>();
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(
&style(format!(
"{} Datenbankeinträge mit {} Meldungen abgerufen",
db_entries.len(),
db_meldungen.len()
))
.green()
.to_string(),
);
fn print_missing_ids(missing_ids: &[&String], term: &Term) {
missing_ids.iter().sorted().for_each(|&item| {
let _ = term.write_line(&format!(
"{} ({})",
item,
to_database_id(item).unwrap_or("?".into())
));
});
}
if db_meldungen.len() != xml_meldungen.len() {
let _ = term.write_line(
&style("\nNicht übereinstimmende Anzahl an Meldungen:")
.yellow()
.to_string(),
);
let _ = term.write_line(&format!(
"Datenbank: {:>10}\nProtokolldatei: {:>10}",
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("\nIn der Datenbank fehlende Meldungen:")
.yellow()
.to_string(),
);
print_missing_ids(&missing_db_ids, &term);
}
if !missing_xml_ids.is_empty() {
let _ = term.write_line(
&style("\nIn der Protokolldatei fehlende Meldungen:")
.yellow()
.to_string(),
);
print_missing_ids(&missing_xml_ids, &term);
}
}
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());
});
}
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(),
})
.sanitized_xml_string()
!= meldung.sanitized_xml_string()
})
.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(),
);
let _ = term.write_line(
"Dies kann auch aufgrund der verwendeten XML-Encodierung auftreten und bedeutet nicht immer eine inhaltliche Abweichung."
);
different_content
.iter()
.sorted_by(|&id1, &id2| {
to_database_id(id1)
.unwrap_or_default()
.cmp(&to_database_id(id2).unwrap_or_default())
})
.for_each(|id| {
let _ = term.write_line(&format!(
"{} ({})",
id,
to_database_id(id).unwrap_or("?".into())
));
});
}
}
}
Ok(())

View File

@ -1,7 +1,7 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 Comprehensive Cancer Center Mainfranken and contributors.
* Copyright (C) 2024 the original author or authors.
*
* 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

View File

@ -1,7 +1,7 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 Comprehensive Cancer Center Mainfranken and contributors.
* Copyright (C) 2024 the original author or authors.
*
* 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
@ -29,15 +29,17 @@ FROM (
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(lm.xml_daten, '//Primaertumor_ICD_Code'), ' ', 1) AS condcodingcode,
SUBSTRING_INDEX(EXTRACTVALUE(lm.xml_daten, '//Diagnosedatum'), ' ', 1) AS diagnosedatum,
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(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1) AS diagnosedatum,
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 lm.xml_daten LIKE '%ICD_Version%'
AND SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lm.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) = :year
AND (lm.xml_daten LIKE '%<cTNM%' OR lm.xml_daten LIKE '%<pTNM%' OR lm.xml_daten LIKE '%<Menge_Histologie>%' OR lm.xml_daten LIKE '%<Menge_Weitere_Klassifikation>%')
AND (lm.xml_daten NOT LIKE '%histologie_zytologie%' OR 1 = :include_histo_zyto)
WHERE lme.xml_daten LIKE '%ICD_Version%'
AND lme.typ <> -1
AND lme.xml_daten NOT LIKE '%<Menge_Tumorkonferenz%'
AND SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) = :year
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)
AND (EXTRACTVALUE(lme.xml_daten, '//ADT_GEKID/@Schema_Version') LIKE '2.%' OR 1 = :ignore_non_obds_2)
) o1
LEFT OUTER JOIN (

View File

@ -0,0 +1,26 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 the original author or authors.
*
* 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
CONVERT(id,char) AS id,
xml_daten
FROM lkr_meldung_export
WHERE typ <> -1
AND (lkr_export = :export_id OR (0 = :export_id AND lkr_export IN (SELECT MAX(lkr_export) FROM lkr_meldung_export)));

View File

@ -1,7 +1,7 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 Comprehensive Cancer Center Mainfranken and contributors.
* Copyright (C) 2024 the original author or authors.
*
* 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
@ -20,4 +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");

View File

@ -1,7 +1,7 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 Comprehensive Cancer Center Mainfranken and contributors.
* Copyright (C) 2024 the original author or authors.
*
* 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
@ -107,34 +107,35 @@ 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 '%<cTNM%' OR lm.xml_daten LIKE '%<pTNM%' OR lm.xml_daten LIKE '%<Menge_Histologie>%' OR lm.xml_daten LIKE '%<Menge_Weitere_Klassifikation>%')
AND (lm.xml_daten NOT LIKE '%histologie_zytologie%' OR 1 = :include_histo_zyto)
) o1
LEFT OUTER JOIN (
AND lme.typ <> -1
AND lme.xml_daten NOT LIKE '%<Menge_Tumorkonferenz%'
AND SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) = :year
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)
AND (EXTRACTVALUE(lme.xml_daten, '//ADT_GEKID/@Schema_Version') LIKE '2.%' OR 1 = :ignore_non_obds_2)
) 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;

View File

@ -0,0 +1,141 @@
/*
* This file is part of bzkf-rwdp-check
*
* Copyright (C) 2024 the original author or authors.
*
* 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 lme.xml_daten NOT LIKE '%<Menge_Tumorkonferenz%'
AND SUBSTRING_INDEX(SUBSTRING_INDEX(EXTRACTVALUE(lme.xml_daten, '//Diagnosedatum'), ' ', 1), '.', -1) = :year
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)
AND (EXTRACTVALUE(lme.xml_daten, '//ADT_GEKID/@Schema_Version') LIKE '2.%' OR 1 = :ignore_non_obds_2)
) 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;

89
testdaten/testdaten_1.xml Normal file
View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ADT_GEKID xmlns="http://www.gekid.de/namespace" Schema_Version="2.2.3">
<Absender Absender_ID="TEST" Software_ID="ONKOSTAR" Installations_ID="2011">
<Absender_Bezeichnung>TEST</Absender_Bezeichnung>
<Absender_Anschrift>Musterstraße 1, 012345 Musterhausen</Absender_Anschrift>
</Absender>
<Menge_Patient>
<Patient>
<Patienten_Stammdaten Patient_ID="20001234">
<KrankenversichertenNr>E123456789</KrankenversichertenNr>
<KrankenkassenNr>123456789</KrankenkassenNr>
<Patienten_Nachname>Tester</Patienten_Nachname>
<Patienten_Titel></Patienten_Titel>
<Patienten_Vornamen>Patrick</Patienten_Vornamen>
<Patienten_Geburtsname>Tester</Patienten_Geburtsname>
<Patienten_Geschlecht>M</Patienten_Geschlecht>
<Patienten_Geburtsdatum>01.01.1980</Patienten_Geburtsdatum>
<Menge_Adresse>
<Adresse>
<Patienten_Strasse>Testweg</Patienten_Strasse>
<Patienten_Hausnummer>1</Patienten_Hausnummer>
<Patienten_Land>DE</Patienten_Land>
<Patienten_PLZ>01234</Patienten_PLZ>
<Patienten_Ort>Musterhausen</Patienten_Ort>
</Adresse>
</Menge_Adresse>
</Patienten_Stammdaten>
<Menge_Meldung>
<Meldung Meldung_ID="TEST1727528" Melder_ID="TEST">
<Meldedatum>11.06.2024</Meldedatum>
<Meldebegruendung>I</Meldebegruendung>
<Meldeanlass>statusaenderung</Meldeanlass>
<Tumorzuordnung Tumor_ID="1">
<Primaertumor_ICD_Code>C17.1</Primaertumor_ICD_Code>
<Primaertumor_ICD_Version>10 2015 GM</Primaertumor_ICD_Version>
<Diagnosedatum>10.06.2024</Diagnosedatum>
<Seitenlokalisation>T</Seitenlokalisation>
</Tumorzuordnung>
<Menge_Tumorkonferenz>
<Tumorkonferenz Tumorkonferenz_ID="1234567">
<Tumorkonferenz_Datum>11.06.2024</Tumorkonferenz_Datum>
<Tumorkonferenz_Typ>praeth</Tumorkonferenz_Typ>
</Tumorkonferenz>
</Menge_Tumorkonferenz>
</Meldung>
</Menge_Meldung>
</Patient>
<Patient>
<Patienten_Stammdaten Patient_ID="20004321">
<KrankenversichertenNr>E123456789</KrankenversichertenNr>
<KrankenkassenNr>123456789</KrankenkassenNr>
<Patienten_Nachname>Tester</Patienten_Nachname>
<Patienten_Titel></Patienten_Titel>
<Patienten_Vornamen>Patricia</Patienten_Vornamen>
<Patienten_Geburtsname>Tester</Patienten_Geburtsname>
<Patienten_Geschlecht>W</Patienten_Geschlecht>
<Patienten_Geburtsdatum>01.01.1980</Patienten_Geburtsdatum>
<Menge_Adresse>
<Adresse>
<Patienten_Strasse>Testweg</Patienten_Strasse>
<Patienten_Hausnummer>1</Patienten_Hausnummer>
<Patienten_Land>DE</Patienten_Land>
<Patienten_PLZ>01234</Patienten_PLZ>
<Patienten_Ort>Musterhausen</Patienten_Ort>
</Adresse>
</Menge_Adresse>
</Patienten_Stammdaten>
<Menge_Meldung>
<Meldung Meldung_ID="001A5D50-TEST" Melder_ID="TEST">
<Meldedatum>11.06.2024</Meldedatum>
<Meldebegruendung>I</Meldebegruendung>
<Meldeanlass>statusaenderung</Meldeanlass>
<Tumorzuordnung Tumor_ID="1">
<Primaertumor_ICD_Code>C17.2</Primaertumor_ICD_Code>
<Primaertumor_ICD_Version>10 2015 GM</Primaertumor_ICD_Version>
<Diagnosedatum>01.01.2024</Diagnosedatum>
<Seitenlokalisation>T</Seitenlokalisation>
</Tumorzuordnung>
<Menge_Tumorkonferenz>
<Tumorkonferenz Tumorkonferenz_ID="1234568">
<Tumorkonferenz_Datum>10.01.2024</Tumorkonferenz_Datum>
<Tumorkonferenz_Typ>praeth</Tumorkonferenz_Typ>
</Tumorkonferenz>
</Menge_Tumorkonferenz>
</Meldung>
</Menge_Meldung>
</Patient>
</Menge_Patient>
</ADT_GEKID>