Use TryFrom to handle enum errors, get definition loading working with demo definitions
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
{
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user