mirror of
https://github.com/pcvolkmer/bzkf-rwdp-check.git
synced 2025-04-19 19:16:51 +00:00
416 lines
14 KiB
Rust
416 lines
14 KiB
Rust
/*
|
|
* 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::error::Error;
|
|
use std::path::Path;
|
|
|
|
use clap::Parser;
|
|
use console::{style, Term};
|
|
use csv::WriterBuilder;
|
|
use itertools::Itertools;
|
|
|
|
use crate::cli::{Cli, SubCommand};
|
|
use crate::common::{Check, DiffRecord, Icd10GroupSize};
|
|
use crate::database::DatabaseSource;
|
|
|
|
mod cli;
|
|
mod common;
|
|
mod database;
|
|
mod opal;
|
|
mod resources;
|
|
|
|
fn request_password_if_none(password: Option<String>) -> String {
|
|
if let Some(password) = password {
|
|
password
|
|
} else {
|
|
let password = dialoguer::Password::new()
|
|
.with_prompt("Password")
|
|
.interact()
|
|
.unwrap_or_default();
|
|
let _ = Term::stdout().clear_last_lines(1);
|
|
password
|
|
}
|
|
}
|
|
|
|
fn sanitize_year(year: String) -> String {
|
|
if year.len() == 4 {
|
|
year
|
|
} else {
|
|
format!("2{:0>3}", year)
|
|
}
|
|
}
|
|
|
|
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));
|
|
});
|
|
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(format!("{:<20}={:>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))
|
|
.dim()
|
|
.to_string(),
|
|
);
|
|
let _ = term.write_line(&style("─".repeat(27)).dim().to_string());
|
|
}
|
|
|
|
fn print_extern_notice(include_extern: bool) {
|
|
let _ = Term::stdout().write_line(format!("{} Die Datenbankanfrage schließt Meldungen mit externer Diagnose {}.", style("Hinweis:").bold().underlined(), match include_extern {
|
|
true => style("ein").yellow(),
|
|
false => style("nicht ein (Standard)").green()
|
|
}).as_str());
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
let term = Term::stdout();
|
|
|
|
match Cli::parse().cmd {
|
|
SubCommand::OpalFile { file } => {
|
|
let items = opal::OpalCsvFile::check(Path::new(&file))
|
|
.map_err(|_e| "Kann Datei nicht lesen")?;
|
|
|
|
print_items(&items);
|
|
}
|
|
SubCommand::Database {
|
|
database,
|
|
host,
|
|
password,
|
|
port,
|
|
user,
|
|
year,
|
|
ignore_exports_since,
|
|
include_extern,
|
|
} => {
|
|
let password = request_password_if_none(password);
|
|
let year = sanitize_year(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,
|
|
&ignore_exports_since.unwrap_or("9999-12-31".into()),
|
|
include_extern,
|
|
)
|
|
.map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?;
|
|
|
|
let _ = term.clear_last_lines(1);
|
|
|
|
print_extern_notice(include_extern);
|
|
print_items(&items);
|
|
}
|
|
SubCommand::Export {
|
|
pat_id,
|
|
database,
|
|
host,
|
|
password,
|
|
port,
|
|
user,
|
|
output,
|
|
year,
|
|
ignore_exports_since,
|
|
xls_csv,
|
|
include_extern,
|
|
} => {
|
|
let password = request_password_if_none(password);
|
|
let year = sanitize_year(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
|
|
.export(
|
|
&year,
|
|
&ignore_exports_since.unwrap_or("9999-12-31".into()),
|
|
pat_id,
|
|
include_extern,
|
|
)
|
|
.map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?;
|
|
|
|
let _ = term.clear_last_lines(1);
|
|
|
|
let writer_builder = &mut WriterBuilder::new();
|
|
let mut writer_builder = writer_builder.has_headers(true);
|
|
if xls_csv {
|
|
writer_builder = writer_builder.delimiter(b';');
|
|
}
|
|
let mut writer = writer_builder
|
|
.from_path(Path::new(&output))
|
|
.expect("writeable file");
|
|
|
|
items
|
|
.iter()
|
|
.for_each(|item| writer.serialize(item).unwrap());
|
|
|
|
let _ = term.write_line(
|
|
&style(format!(
|
|
"{} Conditions für das Jahr {} in Datei '{}' exportiert",
|
|
items.len(),
|
|
year,
|
|
output
|
|
))
|
|
.green()
|
|
.to_string(),
|
|
);
|
|
|
|
print_extern_notice(include_extern);
|
|
}
|
|
SubCommand::Compare {
|
|
pat_id,
|
|
database,
|
|
host,
|
|
password,
|
|
port,
|
|
user,
|
|
file,
|
|
year,
|
|
ignore_exports_since,
|
|
include_extern,
|
|
} => {
|
|
let password = request_password_if_none(password);
|
|
let year = sanitize_year(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 db_items = db
|
|
.export(
|
|
&year,
|
|
&ignore_exports_since.unwrap_or("9999-12-31".into()),
|
|
pat_id,
|
|
include_extern,
|
|
)
|
|
.map_err(|_e| "Fehler bei Zugriff auf die Datenbank")?;
|
|
|
|
let _ = term.clear_last_lines(1);
|
|
|
|
let csv_items = opal::OpalCsvFile::export(Path::new(&file))
|
|
.map_err(|_e| "Kann Datei nicht lesen")?;
|
|
|
|
let mut not_in_csv = db_items
|
|
.iter()
|
|
.filter(|db_item| {
|
|
!csv_items
|
|
.iter()
|
|
.map(|csv_item| &csv_item.condition_id)
|
|
.contains(&db_item.condition_id)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
print_extern_notice(include_extern);
|
|
|
|
let _ = term.write_line(
|
|
&style(format!(
|
|
"{} Conditions aus der Datenbank für das Jahr {} - aber nicht in Datei '{}'",
|
|
not_in_csv.len(),
|
|
year,
|
|
file
|
|
))
|
|
.green()
|
|
.to_string(),
|
|
);
|
|
|
|
let _ = term.write_line(&format!(
|
|
"{:<64} {:<10} {:<5} {:<5} {}",
|
|
"Condition-ID", "Datum", "ICD10", "", "PAT-ID"
|
|
));
|
|
|
|
not_in_csv.sort_by_key(|item| item.condition_id.to_string());
|
|
|
|
not_in_csv
|
|
.iter()
|
|
.for_each(|item| match Check::is_relevant(&item.icd_10_code) {
|
|
true => {
|
|
let _ = term.write_line(&format!(
|
|
"{:<64} {:<10} {:<5} {:<5} {}",
|
|
item.condition_id,
|
|
item.diagnosis_date,
|
|
style(&item.icd_10_code).bold().red(),
|
|
"",
|
|
match &item.pat_id {
|
|
Some(ref pat_id) => pat_id.to_string(),
|
|
_ => "".to_string(),
|
|
}
|
|
));
|
|
}
|
|
false => {
|
|
let _ = term.write_line(&format!(
|
|
"{:<64} {:<10} {:<5} {:<5} {}",
|
|
item.condition_id,
|
|
item.diagnosis_date,
|
|
item.icd_10_code,
|
|
"",
|
|
match &item.pat_id {
|
|
Some(ref pat_id) => pat_id.to_string(),
|
|
_ => "".to_string(),
|
|
}
|
|
));
|
|
}
|
|
});
|
|
|
|
let mut not_in_db = csv_items
|
|
.iter()
|
|
.filter(|csv_item| {
|
|
!db_items
|
|
.iter()
|
|
.map(|db_item| &db_item.condition_id)
|
|
.contains(&csv_item.condition_id)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let _ = term.write_line(
|
|
&style(format!(
|
|
"{} Conditions aus Datei '{}' - aber nicht in der Datenbank für das Jahr {}",
|
|
not_in_db.len(),
|
|
file,
|
|
year
|
|
))
|
|
.green()
|
|
.to_string(),
|
|
);
|
|
|
|
let _ = term.write_line(&format!(
|
|
"{:<64} {:<10} {:<5}",
|
|
"Condition-ID", "Datum", "ICD10"
|
|
));
|
|
|
|
not_in_db.sort_by_key(|item| item.condition_id.to_string());
|
|
|
|
not_in_db
|
|
.iter()
|
|
.for_each(|item| match Check::is_relevant(&item.icd_10_code) {
|
|
true => {
|
|
let _ = term.write_line(&format!(
|
|
"{:<64} {:<10} {:<5}",
|
|
item.condition_id,
|
|
item.diagnosis_date,
|
|
style(&item.icd_10_code).bold().red()
|
|
));
|
|
}
|
|
false => {
|
|
let _ = term.write_line(&format!(
|
|
"{:<64} {:<10} {:<5}",
|
|
item.condition_id, item.diagnosis_date, item.icd_10_code
|
|
));
|
|
}
|
|
});
|
|
|
|
let mut icd10diff = db_items
|
|
.iter()
|
|
.filter(|db_item| {
|
|
csv_items
|
|
.iter()
|
|
.map(|db_item| &db_item.condition_id)
|
|
.contains(&db_item.condition_id)
|
|
})
|
|
.filter(|db_item| {
|
|
!csv_items
|
|
.iter()
|
|
.map(|csv_item| {
|
|
format!("{}-{}", csv_item.condition_id, csv_item.icd_10_code)
|
|
})
|
|
.contains(&format!("{}-{}", db_item.condition_id, db_item.icd_10_code))
|
|
})
|
|
.map(|db_item| DiffRecord {
|
|
pat_id: db_item.pat_id.as_ref().map(|pat_id| pat_id.to_string()),
|
|
condition_id: db_item.condition_id.to_string(),
|
|
diagnosis_date: db_item.diagnosis_date.to_string(),
|
|
csv_icd10_code: db_item.icd_10_code.to_string(),
|
|
db_icd10_code: csv_items
|
|
.iter()
|
|
.filter(|csv_item| csv_item.condition_id == db_item.condition_id)
|
|
.collect_vec()
|
|
.first()
|
|
.unwrap()
|
|
.icd_10_code
|
|
.to_string(),
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let _ = term.write_line(
|
|
&style(format!(
|
|
"{} Conditions mit Unterschied im ICD10-Code",
|
|
icd10diff.len()
|
|
))
|
|
.green()
|
|
.to_string(),
|
|
);
|
|
|
|
icd10diff.sort_by_key(|item| item.condition_id.to_string());
|
|
|
|
let _ = term.write_line(&format!(
|
|
"{:<64} {:<10} {:<5} {:<5} {}",
|
|
"Condition-ID", "Datum", "CSV", "DB", "PAT-ID"
|
|
));
|
|
|
|
icd10diff.iter().for_each(|item| {
|
|
let _ = term.write_line(&format!(
|
|
"{:<64} {:<10} {:<5} {:<5} {}",
|
|
item.condition_id,
|
|
item.diagnosis_date,
|
|
match Check::is_relevant(&item.csv_icd10_code) {
|
|
true => style(format!("{:<5}", item.csv_icd10_code)).bold().red(),
|
|
_ => style(format!("{:<5}", item.csv_icd10_code)),
|
|
},
|
|
match Check::is_relevant(&item.db_icd10_code) {
|
|
true => style(format!("{:<5}", item.db_icd10_code)).bold().red(),
|
|
_ => style(format!("{:<5}", item.db_icd10_code)),
|
|
},
|
|
match &item.pat_id {
|
|
Some(ref pat_id) => pat_id.to_string(),
|
|
_ => "".to_string(),
|
|
}
|
|
));
|
|
});
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|