diff --git a/src/overhead_allocation.rs b/src/overhead_allocation.rs index 54e11e0..ac0dcd2 100644 --- a/src/overhead_allocation.rs +++ b/src/overhead_allocation.rs @@ -266,7 +266,7 @@ where let mut limited_ccs: Vec = Vec::new(); 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 - 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() { continue; } @@ -274,7 +274,7 @@ where limited_ccs.push(limit_value.clone()); } else { let mut found_ccs = rollups - .get(&format!("RollupSlot:{}", limit_to)) + .get(&(format!("RollupSlot:{}", limit_to))) .map(|rollups| rollups.get(limit_value)) .flatten() .unwrap() diff --git a/src/products/create_products.rs b/src/products/create_products.rs index c519a60..023d68b 100644 --- a/src/products/create_products.rs +++ b/src/products/create_products.rs @@ -10,7 +10,10 @@ use polars::lazy::dsl::*; use polars::prelude::*; 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 //use jemallocator::Jemalloc; @@ -251,7 +254,7 @@ pub fn build_encounters_polars( .iter() .any(|f| f.source_type == SourceType::Patient) || definition.components.iter().any(|c| match c { - Component::Field(source, _) => source == "P", + Component::Field(source, _) => *source == ComponentSourceType::Patient, _ => false, }) { diff --git a/src/products/csv.rs b/src/products/csv.rs index 74c453e..a1e2dec 100644 --- a/src/products/csv.rs +++ b/src/products/csv.rs @@ -1,6 +1,9 @@ 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 { // Equal/not equal pub equal: bool, @@ -11,30 +14,32 @@ pub struct Filter { pub source_type: SourceType, } -#[derive(Hash, PartialEq, PartialOrd, Ord, Eq)] +#[derive(Hash, PartialEq, PartialOrd)] pub enum SourceType { - Patient, - Encounter, - Service, - Transfer, CodingDiagnosis, CodingProcedure, - Revenue, + Encounter, Incident, + Patient, + Revenue, + Service, + Transfer, } -impl From<&String> for SourceType { - fn from(value: &String) -> Self { +impl TryFrom<&String> for SourceType { + type Error = anyhow::Error; + + fn try_from(value: &String) -> Result { match value.as_str() { - "P" => SourceType::Patient, - "CP" => SourceType::CodingProcedure, - "CD" => SourceType::CodingDiagnosis, - "E" => SourceType::Encounter, - "I" => SourceType::Incident, - "S" => SourceType::Service, - "R" => SourceType::Revenue, - "T" => SourceType::Transfer, - _ => panic!{} + "CD" => Ok(SourceType::CodingDiagnosis), + "CP" => Ok(SourceType::CodingProcedure), + "E" => Ok(SourceType::Encounter), + "I" => Ok(SourceType::Incident), + "P" => Ok(SourceType::Patient), + "R" => Ok(SourceType::Revenue), + "S" => Ok(SourceType::Service), + "T" => Ok(SourceType::Transfer), + _ => bail!("Source Type is not valid"), } } } @@ -48,16 +53,18 @@ pub enum ConstraintType { NotEqualTo, } -impl From<&String> for ConstraintType { - fn from(string: &String) -> Self { - match string.as_str() { - "=" => ConstraintType::Equal, - ">" => ConstraintType::GreaterThan, - ">=" => ConstraintType::GreaterThanOrEqualTo, - "<" => ConstraintType::LessThan, - "<=" => ConstraintType::LessThanOrEqualTo, - "!=" => ConstraintType::NotEqualTo, - _ => panic!(), +impl TryFrom<&String> for ConstraintType { + type Error = anyhow::Error; + + fn try_from(value: &String) -> Result { + match value.as_str() { + "=" => Ok(ConstraintType::Equal), + ">" => Ok(ConstraintType::GreaterThan), + ">=" => Ok(ConstraintType::GreaterThanOrEqualTo), + "<" => Ok(ConstraintType::LessThan), + "<=" => Ok(ConstraintType::LessThanOrEqualTo), + "!=" => Ok(ConstraintType::NotEqualTo), + _ => bail!("Invalid ConstraintType found: {}", value), } } } @@ -69,11 +76,61 @@ pub struct Constraint { 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 { + 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 { 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) // 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)] @@ -88,17 +145,19 @@ pub enum BuildFrom { Revenue, } -impl From<&String> for BuildFrom { - fn from(string: &String) -> Self { +impl TryFrom<&String> for BuildFrom { + type Error = anyhow::Error; + + fn try_from(string: &String) -> Result { match string.as_str() { - "S" => BuildFrom::Service, - "E" => BuildFrom::Encounter, - "CP" => BuildFrom::CodingProcedure, - "CD" => BuildFrom::CodingDiagnosis, - "T" => BuildFrom::Transfer, - "BS" => BuildFrom::LinkedDataset, - "R" => BuildFrom::Revenue, - _ => panic!(), + "S" => Ok(BuildFrom::Service), + "E" => Ok(BuildFrom::Encounter), + "CP" => Ok(BuildFrom::CodingProcedure), + "CD" => Ok(BuildFrom::CodingDiagnosis), + "T" => Ok(BuildFrom::Transfer), + "BS" => Ok(BuildFrom::LinkedDataset), + "R" => Ok(BuildFrom::Revenue), + _ => bail!("Invalid BuildFrom value: {}", string), } } } @@ -132,17 +191,20 @@ pub enum Frequency { OnePerSource, } -impl From<&String> for Frequency { - fn from(frequency: &String) -> Self { +impl TryFrom<&String> for Frequency { + type Error = anyhow::Error; + + fn try_from(frequency: &String) -> Result { match frequency.as_str() { - "O" => Frequency::OnePerSource, - "DOCW" => Frequency::DailyOrChangeInWard, - "D" => Frequency::Daily, - "DOCC" => Frequency::DailyOrChangeInClinic, - "DEAD" => Frequency::DailyExceptOnAdmissionDay, - "OAL" => Frequency::OnlyAdmissionLocation, - "CIW" => Frequency::ChangeInWard, - _ => panic!(), + "O" => Ok(Frequency::OnePerSource), + "DOCW" => Ok(Frequency::DailyOrChangeInWard), + "D" => Ok(Frequency::Daily), + "DOCC" => Ok(Frequency::DailyOrChangeInClinic), + "DEAD" => Ok(Frequency::DailyExceptOnAdmissionDay), + "OAL" => Ok(Frequency::OnlyAdmissionLocation), + "CIW" => Ok(Frequency::ChangeInWard), + "DDSD" => Ok(Frequency::DailyExceptOnDischargeDay), + _ => bail!("Invalid Frequency found: {}", frequency), } } } @@ -154,29 +216,30 @@ pub enum RoundingMode { None, } -impl From<&String> for RoundingMode { - fn from(rounding: &String) -> Self { +impl TryFrom<&String> for RoundingMode { + type Error = anyhow::Error; + + fn try_from(rounding: &String) -> Result { match rounding.as_str() { - "U" => RoundingMode::UpToClosestWhole, - "N" => RoundingMode::None, - "D" => RoundingMode::DownToClosestWhole, - "T" => RoundingMode::ToClosestWhole, - // TODO: Just use none when unknown? - _ => panic!(), + "U" => Ok(RoundingMode::UpToClosestWhole), + "N" => Ok(RoundingMode::None), + "D" => Ok(RoundingMode::DownToClosestWhole), + "T" => Ok(RoundingMode::ToClosestWhole), + _ => bail!("Invalid rounding mode found: {}", rounding), } } } -// enum ExtraValue { -// string(String), -// numeric(f64), -// datetime(NaiveDateTime), -// } +enum ExtraValue { + String(String), + Sumeric(f64), + Datetime(NaiveDateTime), +} -// struct Extra { -// extraType: String, -// value: ExtraValue, -// } +struct Extra { + extra_type: String, + value: ExtraValue, +} // Quantities per type: // Built Service: Constant, SourceQuantity @@ -241,24 +304,28 @@ where "Definition" => { let quantity_type = record.get("BuiltQuantity").unwrap(); let rounding_mode = - RoundingMode::from(record.get("BuiltQuantityRounding").unwrap()); - let quantity = match quantity_type.as_str() { - "S" => Quantity::SourceQuantity, - "C" => Quantity::Constant( - record - .get("BuiltQuantityConstant") - .unwrap() - .parse() - .unwrap(), - ), - "H" => Quantity::Hours, + RoundingMode::try_from(record.get("BuiltQuantityRounding").unwrap())?; + let quantity: anyhow::Result = match quantity_type.as_str() { + "S" => Ok(Quantity::SourceQuantity), + "C" => { + let constant_value = + record.get("BuiltQuantityConstant").unwrap().parse()?; + Ok(Quantity::Constant(constant_value)) + } + "H" => Ok(Quantity::Hours), + "D" => Ok(Quantity::Days), // 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 { quantity, rounding_mode, }; + let build_from = BuildFrom::try_from(record.get("BuildFrom").unwrap())?; + let frequency = Frequency::try_from(record.get("Frequency").unwrap())?; all_definitions.insert( record.get("Name").unwrap().to_owned(), Definition { @@ -266,8 +333,8 @@ where components: vec![], filters: vec![], constraints: vec![], - build_from: BuildFrom::from(record.get("BuildFrom").unwrap()), - frequency: Frequency::from(record.get("Frequency").unwrap()), + build_from, + frequency, quantity: built_quantity, // TODO: Figure this out // Not even in use, can ignore, or will BuiltService always be the default? @@ -276,11 +343,16 @@ where ); } "Filter" => { - let new_filter = Filter { - equal: record.get("FilterNotIn").unwrap() != "", - field: record.get("FilterField").unwrap().clone(), - value: record.get("FilterValue").unwrap().clone(), - source_type: SourceType::from(&record.get("FilterSourceType").unwrap().clone()), + let new_filter = { + let source_type = + SourceType::try_from(record.get("FilterSourceType").unwrap())?; + Filter { + // 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 .get_mut(record.get("Name").unwrap()) @@ -293,11 +365,16 @@ where "C" => { Component::Constant(record.get("ComponentValueOrField").unwrap().to_owned()) } - source => Component::Field( - // TODO: Parse into source type enum - source.to_owned(), + "MC" => Component::MultiConstant( 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 .get_mut(record.get("Name").unwrap()) @@ -306,11 +383,17 @@ where all_components.push(component); } "Constraint" => { - let constraint = Constraint { - source_type: SourceType::from(&record.get("ConstraintSourceType").unwrap().to_owned()), - field: record.get("ConstraintColumn").unwrap().to_owned(), - constraint_type: ConstraintType::from(record.get("ConstraintType").unwrap()), - value: record.get("ConstraintValue").unwrap().to_owned(), + let constraint = { + let constraint_type = + ConstraintType::try_from(record.get("ConstraintType").unwrap())?; + let source_type = + 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 .get_mut(record.get("Name").unwrap()) @@ -323,3 +406,19 @@ where } 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()) + } +}