Use TryFrom to handle enum errors, get definition loading working with demo definitions

This commit is contained in:
Michael Pivato
2023-12-24 16:48:14 +10:30
parent c1771b66c4
commit 7e866141e7
3 changed files with 199 additions and 97 deletions

View File

@@ -266,7 +266,7 @@ where
let mut limited_ccs: Vec<String> = Vec::new(); let mut limited_ccs: Vec<String> = Vec::new();
for limit_to in limit_tos.iter() { for limit_to in limit_tos.iter() {
// TODO: It is technically possible to have more than one limit to (I think?) for a slot, so consider eventually splitting this and doing a foreach // TODO: It is technically possible to have more than one limit to (I think?) for a slot, so consider eventually splitting this and doing a foreach
let limit_value = area.get(&format!("LimitTo:{}", limit_to)).unwrap(); let limit_value = area.get(&(format!("LimitTo:{}", limit_to))).unwrap();
if limit_value.is_empty() { if limit_value.is_empty() {
continue; continue;
} }
@@ -274,7 +274,7 @@ where
limited_ccs.push(limit_value.clone()); limited_ccs.push(limit_value.clone());
} else { } else {
let mut found_ccs = rollups let mut found_ccs = rollups
.get(&format!("RollupSlot:{}", limit_to)) .get(&(format!("RollupSlot:{}", limit_to)))
.map(|rollups| rollups.get(limit_value)) .map(|rollups| rollups.get(limit_value))
.flatten() .flatten()
.unwrap() .unwrap()

View File

@@ -10,7 +10,10 @@ use polars::lazy::dsl::*;
use polars::prelude::*; use polars::prelude::*;
use serde::Serialize; use serde::Serialize;
use super::csv::{read_definitions, BuildFrom, Component, ConstraintType, Definition, SourceType}; use super::csv::{
read_definitions, BuildFrom, Component, ComponentSourceType, ConstraintType, Definition,
SourceType,
};
// TODO: Polars suggests this, but docs suggest it doesn't have very good platform support // TODO: Polars suggests this, but docs suggest it doesn't have very good platform support
//use jemallocator::Jemalloc; //use jemallocator::Jemalloc;
@@ -251,7 +254,7 @@ pub fn build_encounters_polars(
.iter() .iter()
.any(|f| f.source_type == SourceType::Patient) .any(|f| f.source_type == SourceType::Patient)
|| definition.components.iter().any(|c| match c { || definition.components.iter().any(|c| match c {
Component::Field(source, _) => source == "P", Component::Field(source, _) => *source == ComponentSourceType::Patient,
_ => false, _ => false,
}) })
{ {

View File

@@ -1,6 +1,9 @@
use std::{collections::HashMap, io::Read}; use std::{collections::HashMap, io::Read};
#[derive(Hash, PartialEq, PartialOrd, Ord, Eq)] use anyhow::bail;
use chrono::NaiveDateTime;
#[derive(Hash, PartialEq, PartialOrd)]
pub struct Filter { pub struct Filter {
// Equal/not equal // Equal/not equal
pub equal: bool, pub equal: bool,
@@ -11,30 +14,32 @@ pub struct Filter {
pub source_type: SourceType, pub source_type: SourceType,
} }
#[derive(Hash, PartialEq, PartialOrd, Ord, Eq)] #[derive(Hash, PartialEq, PartialOrd)]
pub enum SourceType { pub enum SourceType {
Patient,
Encounter,
Service,
Transfer,
CodingDiagnosis, CodingDiagnosis,
CodingProcedure, CodingProcedure,
Revenue, Encounter,
Incident, Incident,
Patient,
Revenue,
Service,
Transfer,
} }
impl From<&String> for SourceType { impl TryFrom<&String> for SourceType {
fn from(value: &String) -> Self { type Error = anyhow::Error;
fn try_from(value: &String) -> Result<Self, Self::Error> {
match value.as_str() { match value.as_str() {
"P" => SourceType::Patient, "CD" => Ok(SourceType::CodingDiagnosis),
"CP" => SourceType::CodingProcedure, "CP" => Ok(SourceType::CodingProcedure),
"CD" => SourceType::CodingDiagnosis, "E" => Ok(SourceType::Encounter),
"E" => SourceType::Encounter, "I" => Ok(SourceType::Incident),
"I" => SourceType::Incident, "P" => Ok(SourceType::Patient),
"S" => SourceType::Service, "R" => Ok(SourceType::Revenue),
"R" => SourceType::Revenue, "S" => Ok(SourceType::Service),
"T" => SourceType::Transfer, "T" => Ok(SourceType::Transfer),
_ => panic!{} _ => bail!("Source Type is not valid"),
} }
} }
} }
@@ -48,16 +53,18 @@ pub enum ConstraintType {
NotEqualTo, NotEqualTo,
} }
impl From<&String> for ConstraintType { impl TryFrom<&String> for ConstraintType {
fn from(string: &String) -> Self { type Error = anyhow::Error;
match string.as_str() {
"=" => ConstraintType::Equal, fn try_from(value: &String) -> Result<Self, Self::Error> {
">" => ConstraintType::GreaterThan, match value.as_str() {
">=" => ConstraintType::GreaterThanOrEqualTo, "=" => Ok(ConstraintType::Equal),
"<" => ConstraintType::LessThan, ">" => Ok(ConstraintType::GreaterThan),
"<=" => ConstraintType::LessThanOrEqualTo, ">=" => Ok(ConstraintType::GreaterThanOrEqualTo),
"!=" => ConstraintType::NotEqualTo, "<" => Ok(ConstraintType::LessThan),
_ => panic!(), "<=" => Ok(ConstraintType::LessThanOrEqualTo),
"!=" => Ok(ConstraintType::NotEqualTo),
_ => bail!("Invalid ConstraintType found: {}", value),
} }
} }
} }
@@ -69,11 +76,61 @@ pub struct Constraint {
pub value: String, pub value: String,
} }
#[derive(PartialEq)]
pub enum ExtraType {
CodingDiagnosis,
CodingProcedure,
Encounter,
Patient,
Revenue,
Service,
Transfer,
}
#[derive(PartialEq)]
pub enum ComponentSourceType {
CodingDiagnosis,
CodingProcedure,
Encounter,
Patient,
Revenue,
Service,
Transfer,
Extra(ExtraType),
Classification,
}
impl TryFrom<&str> for ComponentSourceType {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"CD" => Ok(ComponentSourceType::CodingDiagnosis),
"CP" => Ok(ComponentSourceType::CodingProcedure),
"E" => Ok(ComponentSourceType::Encounter),
"P" => Ok(ComponentSourceType::Patient),
"R" => Ok(ComponentSourceType::Revenue),
"S" => Ok(ComponentSourceType::Service),
"T" => Ok(ComponentSourceType::Transfer),
"EC" => Ok(ComponentSourceType::Classification),
"CDX" => Ok(ComponentSourceType::Extra(ExtraType::CodingDiagnosis)),
"CPX" => Ok(ComponentSourceType::Extra(ExtraType::CodingProcedure)),
"EX" => Ok(ComponentSourceType::Extra(ExtraType::Encounter)),
"PX" => Ok(ComponentSourceType::Extra(ExtraType::Patient)),
"RX" => Ok(ComponentSourceType::Extra(ExtraType::Revenue)),
"SX" => Ok(ComponentSourceType::Extra(ExtraType::Service)),
"TX" => Ok(ComponentSourceType::Extra(ExtraType::Transfer)),
_ => bail!("Invalid ComponentSourceType found: {}", value),
}
}
}
pub enum Component { pub enum Component {
Constant(String), Constant(String),
MultiConstant(String),
// Even extras are allowed here, just specify the field type (encounter, service, etc) and the field name (incl Extra: or Classification: as appropriate) // Even extras are allowed here, just specify the field type (encounter, service, etc) and the field name (incl Extra: or Classification: as appropriate)
// TODO: This first string should also be some kind of source type enum, probably shared with source types on filter/constraint // TODO: This first string should also be some kind of source type enum, probably shared with source types on filter/constraint
Field(String, String), Field(ComponentSourceType, String),
} }
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
@@ -88,17 +145,19 @@ pub enum BuildFrom {
Revenue, Revenue,
} }
impl From<&String> for BuildFrom { impl TryFrom<&String> for BuildFrom {
fn from(string: &String) -> Self { type Error = anyhow::Error;
fn try_from(string: &String) -> Result<Self, Self::Error> {
match string.as_str() { match string.as_str() {
"S" => BuildFrom::Service, "S" => Ok(BuildFrom::Service),
"E" => BuildFrom::Encounter, "E" => Ok(BuildFrom::Encounter),
"CP" => BuildFrom::CodingProcedure, "CP" => Ok(BuildFrom::CodingProcedure),
"CD" => BuildFrom::CodingDiagnosis, "CD" => Ok(BuildFrom::CodingDiagnosis),
"T" => BuildFrom::Transfer, "T" => Ok(BuildFrom::Transfer),
"BS" => BuildFrom::LinkedDataset, "BS" => Ok(BuildFrom::LinkedDataset),
"R" => BuildFrom::Revenue, "R" => Ok(BuildFrom::Revenue),
_ => panic!(), _ => bail!("Invalid BuildFrom value: {}", string),
} }
} }
} }
@@ -132,17 +191,20 @@ pub enum Frequency {
OnePerSource, OnePerSource,
} }
impl From<&String> for Frequency { impl TryFrom<&String> for Frequency {
fn from(frequency: &String) -> Self { type Error = anyhow::Error;
fn try_from(frequency: &String) -> Result<Self, Self::Error> {
match frequency.as_str() { match frequency.as_str() {
"O" => Frequency::OnePerSource, "O" => Ok(Frequency::OnePerSource),
"DOCW" => Frequency::DailyOrChangeInWard, "DOCW" => Ok(Frequency::DailyOrChangeInWard),
"D" => Frequency::Daily, "D" => Ok(Frequency::Daily),
"DOCC" => Frequency::DailyOrChangeInClinic, "DOCC" => Ok(Frequency::DailyOrChangeInClinic),
"DEAD" => Frequency::DailyExceptOnAdmissionDay, "DEAD" => Ok(Frequency::DailyExceptOnAdmissionDay),
"OAL" => Frequency::OnlyAdmissionLocation, "OAL" => Ok(Frequency::OnlyAdmissionLocation),
"CIW" => Frequency::ChangeInWard, "CIW" => Ok(Frequency::ChangeInWard),
_ => panic!(), "DDSD" => Ok(Frequency::DailyExceptOnDischargeDay),
_ => bail!("Invalid Frequency found: {}", frequency),
} }
} }
} }
@@ -154,29 +216,30 @@ pub enum RoundingMode {
None, None,
} }
impl From<&String> for RoundingMode { impl TryFrom<&String> for RoundingMode {
fn from(rounding: &String) -> Self { type Error = anyhow::Error;
fn try_from(rounding: &String) -> Result<Self, Self::Error> {
match rounding.as_str() { match rounding.as_str() {
"U" => RoundingMode::UpToClosestWhole, "U" => Ok(RoundingMode::UpToClosestWhole),
"N" => RoundingMode::None, "N" => Ok(RoundingMode::None),
"D" => RoundingMode::DownToClosestWhole, "D" => Ok(RoundingMode::DownToClosestWhole),
"T" => RoundingMode::ToClosestWhole, "T" => Ok(RoundingMode::ToClosestWhole),
// TODO: Just use none when unknown? _ => bail!("Invalid rounding mode found: {}", rounding),
_ => panic!(),
} }
} }
} }
// enum ExtraValue { enum ExtraValue {
// string(String), String(String),
// numeric(f64), Sumeric(f64),
// datetime(NaiveDateTime), Datetime(NaiveDateTime),
// } }
// struct Extra { struct Extra {
// extraType: String, extra_type: String,
// value: ExtraValue, value: ExtraValue,
// } }
// Quantities per type: // Quantities per type:
// Built Service: Constant, SourceQuantity // Built Service: Constant, SourceQuantity
@@ -241,24 +304,28 @@ where
"Definition" => { "Definition" => {
let quantity_type = record.get("BuiltQuantity").unwrap(); let quantity_type = record.get("BuiltQuantity").unwrap();
let rounding_mode = let rounding_mode =
RoundingMode::from(record.get("BuiltQuantityRounding").unwrap()); RoundingMode::try_from(record.get("BuiltQuantityRounding").unwrap())?;
let quantity = match quantity_type.as_str() { let quantity: anyhow::Result<Quantity> = match quantity_type.as_str() {
"S" => Quantity::SourceQuantity, "S" => Ok(Quantity::SourceQuantity),
"C" => Quantity::Constant( "C" => {
record let constant_value =
.get("BuiltQuantityConstant") record.get("BuiltQuantityConstant").unwrap().parse()?;
.unwrap() Ok(Quantity::Constant(constant_value))
.parse() }
.unwrap(), "H" => Ok(Quantity::Hours),
), "D" => Ok(Quantity::Days),
"H" => Quantity::Hours,
// Above 3 are all that's needed for now // Above 3 are all that's needed for now
_ => panic![], invalid_quantity => {
anyhow::bail!("Invalid quantity found: {}", invalid_quantity)
}
}; };
let quantity = quantity?;
let built_quantity = BuiltQuantity { let built_quantity = BuiltQuantity {
quantity, quantity,
rounding_mode, rounding_mode,
}; };
let build_from = BuildFrom::try_from(record.get("BuildFrom").unwrap())?;
let frequency = Frequency::try_from(record.get("Frequency").unwrap())?;
all_definitions.insert( all_definitions.insert(
record.get("Name").unwrap().to_owned(), record.get("Name").unwrap().to_owned(),
Definition { Definition {
@@ -266,8 +333,8 @@ where
components: vec![], components: vec![],
filters: vec![], filters: vec![],
constraints: vec![], constraints: vec![],
build_from: BuildFrom::from(record.get("BuildFrom").unwrap()), build_from,
frequency: Frequency::from(record.get("Frequency").unwrap()), frequency,
quantity: built_quantity, quantity: built_quantity,
// TODO: Figure this out // TODO: Figure this out
// Not even in use, can ignore, or will BuiltService always be the default? // Not even in use, can ignore, or will BuiltService always be the default?
@@ -276,11 +343,16 @@ where
); );
} }
"Filter" => { "Filter" => {
let new_filter = Filter { let new_filter = {
equal: record.get("FilterNotIn").unwrap() != "", let source_type =
field: record.get("FilterField").unwrap().clone(), SourceType::try_from(record.get("FilterSourceType").unwrap())?;
value: record.get("FilterValue").unwrap().clone(), Filter {
source_type: SourceType::from(&record.get("FilterSourceType").unwrap().clone()), // TODO: This all looks wrong
equal: record.get("FilterNotIn").unwrap() != "",
field: record.get("FilterField").unwrap().clone(),
value: record.get("FilterValue").unwrap().clone(),
source_type,
}
}; };
let all_filters = &mut all_definitions let all_filters = &mut all_definitions
.get_mut(record.get("Name").unwrap()) .get_mut(record.get("Name").unwrap())
@@ -293,11 +365,16 @@ where
"C" => { "C" => {
Component::Constant(record.get("ComponentValueOrField").unwrap().to_owned()) Component::Constant(record.get("ComponentValueOrField").unwrap().to_owned())
} }
source => Component::Field( "MC" => Component::MultiConstant(
// TODO: Parse into source type enum
source.to_owned(),
record.get("ComponentValueOrField").unwrap().to_owned(), record.get("ComponentValueOrField").unwrap().to_owned(),
), ),
source => {
let component_source_type = ComponentSourceType::try_from(source)?;
Component::Field(
component_source_type,
record.get("ComponentValueOrField").unwrap().to_owned(),
)
}
}; };
let all_components = &mut all_definitions let all_components = &mut all_definitions
.get_mut(record.get("Name").unwrap()) .get_mut(record.get("Name").unwrap())
@@ -306,11 +383,17 @@ where
all_components.push(component); all_components.push(component);
} }
"Constraint" => { "Constraint" => {
let constraint = Constraint { let constraint = {
source_type: SourceType::from(&record.get("ConstraintSourceType").unwrap().to_owned()), let constraint_type =
field: record.get("ConstraintColumn").unwrap().to_owned(), ConstraintType::try_from(record.get("ConstraintType").unwrap())?;
constraint_type: ConstraintType::from(record.get("ConstraintType").unwrap()), let source_type =
value: record.get("ConstraintValue").unwrap().to_owned(), SourceType::try_from(record.get("ConstraintSourceType").unwrap())?;
Constraint {
source_type,
field: record.get("ConstraintColumn").unwrap().to_owned(),
constraint_type,
value: record.get("ConstraintValue").unwrap().to_owned(),
}
}; };
let all_constraints = &mut all_definitions let all_constraints = &mut all_definitions
.get_mut(record.get("Name").unwrap()) .get_mut(record.get("Name").unwrap())
@@ -323,3 +406,19 @@ where
} }
Ok(all_definitions) Ok(all_definitions)
} }
#[cfg(test)]
mod tests {
use super::read_definitions;
#[test]
fn test_read_definitions() {
let definitions = read_definitions(
&mut csv::Reader::from_path("service_builder_definitions.csv").unwrap(),
);
if let Err(error) = &definitions {
println!("{}", error)
}
assert!(definitions.is_ok())
}
}