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();
|
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()
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
let source_type =
|
||||||
|
SourceType::try_from(record.get("FilterSourceType").unwrap())?;
|
||||||
|
Filter {
|
||||||
|
// TODO: This all looks wrong
|
||||||
equal: record.get("FilterNotIn").unwrap() != "",
|
equal: record.get("FilterNotIn").unwrap() != "",
|
||||||
field: record.get("FilterField").unwrap().clone(),
|
field: record.get("FilterField").unwrap().clone(),
|
||||||
value: record.get("FilterValue").unwrap().clone(),
|
value: record.get("FilterValue").unwrap().clone(),
|
||||||
source_type: SourceType::from(&record.get("FilterSourceType").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 =
|
||||||
|
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(),
|
field: record.get("ConstraintColumn").unwrap().to_owned(),
|
||||||
constraint_type: ConstraintType::from(record.get("ConstraintType").unwrap()),
|
constraint_type,
|
||||||
value: record.get("ConstraintValue").unwrap().to_owned(),
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user