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();
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()

View File

@@ -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,
})
{

View File

@@ -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<Self, Self::Error> {
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<Self, Self::Error> {
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<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 {
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<Self, Self::Error> {
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<Self, Self::Error> {
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<Self, Self::Error> {
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<Quantity> = 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())
}
}