Skip to content

Commit

Permalink
refactor(cli): ♻️ add rebuilding ingestion into internal struct with …
Browse files Browse the repository at this point in the history
…async getter func
  • Loading branch information
keinsell committed Jun 10, 2024
1 parent 5adada6 commit 1c92691
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 29 deletions.
41 changes: 29 additions & 12 deletions apps/cli/src/core/route_of_administration.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::collections::HashMap;
use std::fmt::Display;
use std::str::FromStr;
use sea_orm::DeriveDisplay;

use serde::{Deserialize, Serialize};
use strsim::normalized_levenshtein;
use crate::core::phase::{Phase, PhaseClassification};
Expand All @@ -22,6 +21,24 @@ pub enum RouteOfAdministrationClassification {
Transdermal,
}

impl Display for RouteOfAdministrationClassification {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
RouteOfAdministrationClassification::Buccal => "buccal".to_string(),
RouteOfAdministrationClassification::Inhaled => "inhaled".to_string(),
RouteOfAdministrationClassification::Insufflated => "insufflated".to_string(),
RouteOfAdministrationClassification::Intramuscular => "intramuscular".to_string(),
RouteOfAdministrationClassification::Intravenous => "intravenous".to_string(),
RouteOfAdministrationClassification::Oral => "oral".to_string(),
RouteOfAdministrationClassification::Rectal => "rectal".to_string(),
RouteOfAdministrationClassification::Smoked => "smoked".to_string(),
RouteOfAdministrationClassification::Sublingual => "sublingual".to_string(),
RouteOfAdministrationClassification::Transdermal => "transdermal".to_string(),
};
write!(f, "{}", str)
}
}

impl FromStr for RouteOfAdministrationClassification {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Expand Down Expand Up @@ -67,16 +84,16 @@ impl FromStr for RouteOfAdministrationClassification {
impl From<RouteOfAdministrationClassification> for String {
fn from(roa: RouteOfAdministrationClassification) -> Self {
match roa {
RouteOfAdministrationClassification::Buccal => "buccal".to_string(),
RouteOfAdministrationClassification::Inhaled => "inhaled".to_string(),
RouteOfAdministrationClassification::Insufflated => "insufflated".to_string(),
RouteOfAdministrationClassification::Intramuscular => "intramuscular".to_string(),
RouteOfAdministrationClassification::Intravenous => "intravenous".to_string(),
RouteOfAdministrationClassification::Oral => "oral".to_string(),
RouteOfAdministrationClassification::Rectal => "rectal".to_string(),
RouteOfAdministrationClassification::Smoked => "smoked".to_string(),
RouteOfAdministrationClassification::Sublingual => "sublingual".to_string(),
RouteOfAdministrationClassification::Transdermal => "transdermal".to_string(),
RouteOfAdministrationClassification::Buccal => String::from("buccal"),
RouteOfAdministrationClassification::Inhaled => String::from("inhaled"),
RouteOfAdministrationClassification::Insufflated => String::from("insufflated"),
RouteOfAdministrationClassification::Intramuscular => String::from("intramuscular"),
RouteOfAdministrationClassification::Intravenous => String::from("intravenous"),
RouteOfAdministrationClassification::Oral => String::from("oral"),
RouteOfAdministrationClassification::Rectal => String::from("rectal"),
RouteOfAdministrationClassification::Smoked => String::from("smoked"),
RouteOfAdministrationClassification::Sublingual => String::from("sublingual"),
RouteOfAdministrationClassification::Transdermal => String::from("transdermal")
}
}
}
Expand Down
86 changes: 83 additions & 3 deletions apps/cli/src/ingestion_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
use std::fmt::{Debug, Display};
use std::time::Duration;

use chrono::{Local, TimeZone};
use chrono::{DateTime, Local, TimeZone};
use chrono_english::{Dialect, parse_date_string};
use chrono_humanize::HumanTime;
use log::{debug, error};
use measurements::Measurement;
use serde::{Deserialize, Serialize};
use termimad::MadSkin;
use crate::core::ingestion::{IngestionPhase, IngestionPhases};
use crate::core::ingestion::{Ingestion, IngestionPhase, IngestionPhases};
use crate::core::mass::{deserialize_dosage, Mass};
use crate::core::phase::PhaseClassification;
use crate::core::route_of_administration::{
Expand All @@ -36,11 +36,12 @@ struct DosageAnalysis {

#[derive(Debug)]
pub struct IngestionAnalysis {
ingestion_id: i32,
substance_name: String,
route_of_administration_classification: RouteOfAdministrationClassification,
dosage_classification: DosageClassification,
dosage: Mass,
phases: IngestionPhases,
pub phases: IngestionPhases,
total_duration: Duration,
}

Expand Down Expand Up @@ -120,6 +121,7 @@ pub async fn analyze_future_ingestion(
}).collect();

let ingestion_analysis = IngestionAnalysis {
ingestion_id: 0,
substance_name: substance.name.clone(),
dosage: ingestion_mass.clone(),
route_of_administration_classification: route_of_administration.classification,
Expand All @@ -133,6 +135,84 @@ pub async fn analyze_future_ingestion(
Ok(ingestion_analysis)
}

pub async fn analyze_ingestion(ingestion: &Ingestion) -> Result<IngestionAnalysis, &'static str> {
let substance = get_substance_by_name(&ingestion.substance_name)
.await
.ok_or("Analysis failed: Substance not found")?;

debug!("{:?}", substance);

let route_of_administration = get_route_of_administration_by_classification_and_substance(
&ingestion.administration_route,
&substance,
)
.unwrap_or_else(|_| {
error!("Analysis failed: Route of administration not found");
panic!("Analysis failed: Route of administration not found");
});

let ingestion_mass = ingestion.dosage.clone();

let dosage_classification = get_dosage_classification_by_mass_and_route_of_administration(
&ingestion_mass,
&route_of_administration,
)
.unwrap_or_else(|_| {
error!("Analysis failed: Dosage classification not found");
return DosageClassification::Unknown;
});

let phases = get_phases_by_route_of_administration(&route_of_administration);

let total_duration = phases.iter().fold(Duration::default(), |acc, phase| {
return if phase.phase_classification == PhaseClassification::Afterglow {
acc
} else {
let added = acc + phase.duration_range.end;
added
}
});


let route_of_administration_phases = route_of_administration.phases.clone();

let parsed_time = ingestion.ingested_at;
let mut end_time = DateTime::<Local>::from(parsed_time.clone());

let phase_classifications = [
PhaseClassification::Onset,
PhaseClassification::Comeup,
PhaseClassification::Peak,
PhaseClassification::Offset,
PhaseClassification::Afterglow,
];

let ingestion_phases: IngestionPhases = phase_classifications.iter().filter_map(|classification| {
route_of_administration_phases.get(&classification.clone()).map(|phase| {
let ingestion_phase = IngestionPhase {
phase_classification: classification.clone(),
duration: phase.duration_range.clone(),
start_time: end_time,
end_time: end_time + phase.duration_range.end,
};
end_time = end_time + ingestion_phase.duration.end;
(classification.clone(), ingestion_phase)
})
}).collect();

let ingestion_analysis = IngestionAnalysis {
ingestion_id: ingestion.id.clone(),
substance_name: substance.name.clone(),
dosage: ingestion_mass,
route_of_administration_classification: route_of_administration.classification,
dosage_classification,
phases: ingestion_phases,
total_duration: Duration::from_secs(0),
};

Ok(ingestion_analysis)
}

pub fn pretty_print_ingestion_analysis(ingestion_analysis: &IngestionAnalysis) {
let mut markdown = String::new();
markdown.push_str(&format!(
Expand Down
58 changes: 44 additions & 14 deletions apps/cli/src/service/ingestion.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
use std::fmt::Debug;
use std::fmt::{Debug};
use std::str::FromStr;

use chrono::{DateTime, Local, Utc};
use chrono_english::{parse_date_string, Dialect};
use chrono_humanize::HumanTime;
use db::ingestion::ActiveModel;
use db::prelude::Ingestion;
use sea_orm::{ActiveValue, DatabaseConnection, EntityTrait, QueryTrait};
use serde::{Deserialize, Serialize};
use serde_json::to_string;
use tabled::{Table, Tabled};
use crate::core::dosage::Dosage;
use crate::core::ingestion::{Ingestion};

use crate::core::mass::deserialize_dosage;
use crate::core::route_of_administration::RouteOfAdministrationClassification;
use crate::ingestion_analyzer::analyze_future_ingestion;
use crate::service::substance::search_substance;
use crate::ingestion_analyzer::{analyze_future_ingestion, analyze_ingestion};
use crate::orm::DB_CONNECTION;
use crate::service::substance::{get_substance_by_name, search_substance};

pub async fn create_ingestion(db: &DatabaseConnection, create_ingestion: CreateIngestion) {
// Parse the date from relative to the current time
Expand All @@ -35,7 +37,7 @@ pub async fn create_ingestion(db: &DatabaseConnection, create_ingestion: CreateI
id: ActiveValue::default(),
substance_name: ActiveValue::Set(Option::from(substance.name)),
administration_route: ActiveValue::Set(Option::from(
to_string(&create_ingestion.route_of_administration).unwrap(),
create_ingestion.route_of_administration.to_string().clone(),
)),
dosage_unit: ActiveValue::Set(Option::from("kg".to_owned())),
dosage_amount: ActiveValue::Set(Option::from(
Expand All @@ -46,13 +48,13 @@ pub async fn create_ingestion(db: &DatabaseConnection, create_ingestion: CreateI
stash_id: ActiveValue::NotSet,
};

let ingestion = match Ingestion::insert(ingestion_active_model.clone())
let ingestion = match db::ingestion::Entity::insert(ingestion_active_model.clone())
.exec_with_returning(db)
.await
{
Ok(ingestion) => ingestion,
Err(error) => {
println!("{:?}", Ingestion::insert(ingestion_active_model).query());
println!("{:?}", db::ingestion::Entity::insert(ingestion_active_model).query());
panic!("Error inserting ingestion: {}", error)
}
};
Expand All @@ -73,7 +75,7 @@ pub async fn create_ingestion(db: &DatabaseConnection, create_ingestion: CreateI
}

pub async fn list_ingestion(db: &DatabaseConnection) {
let ingestions = Ingestion::find().all(db).await.unwrap();
let ingestions = db::ingestion::Entity::find().all(db).await.unwrap();

let view_models: Vec<ViewModel> = ingestions
.into_iter()
Expand All @@ -85,16 +87,23 @@ pub async fn list_ingestion(db: &DatabaseConnection) {
id: ingestion.id.to_string(),
ingested_at: HumanTime::from(ingestion_date).to_string(),
dosage: format!(
"{} {}",
ingestion.dosage_amount.unwrap(),
ingestion.dosage_unit.unwrap()
"{0:.0}",
Dosage::from_str(format!(
"{} {}",
ingestion.dosage_amount.unwrap(),
ingestion.dosage_unit.unwrap()
).as_str()).unwrap()
),
substance_name: ingestion.substance_name.unwrap(),
route_of_administration: ingestion.administration_route.unwrap(),
}
})
.collect();

for view_model in &view_models {
get_ingestion_by_id(view_model.id.parse::<i32>().unwrap().clone()).await.unwrap();
}

let string_table = Table::new(view_models);
println!("{}", string_table);
}
Expand All @@ -105,7 +114,29 @@ pub async fn list_ingestion(db: &DatabaseConnection) {
/// and deserializable as this function will be expensive in time and resources it can be memoized to some
/// local cache.
pub async fn get_ingestion_by_id(_id: i32) -> Result<Ingestion, &'static str> {
todo!()
// Find ingestion in database by ID
let ingestion = db::ingestion::Entity::find_by_id(_id).one(&DB_CONNECTION as &DatabaseConnection).await.unwrap().unwrap();
let substance = get_substance_by_name(&ingestion.substance_name.unwrap().clone()).await.unwrap();

let route_of_administration_classification = RouteOfAdministrationClassification::from_str(&ingestion.administration_route.unwrap_or_else(|| panic!("Tried to read route of administration of ingestion but none was found, it's weird as it should be there..."))).unwrap_or_else(|_| panic!("Tried to read route of administration of ingestion but none was found, it's weird as it should be there..."));

let ingestion_mass = Dosage::from_str(format!("{} {}", &ingestion.dosage_amount.unwrap(), &ingestion.dosage_unit.unwrap()).as_str()).unwrap();

// Construct ingestion model
let mut ingestion_model = Ingestion {
id: ingestion.id,
substance_name: substance.name.clone(),
administration_route: route_of_administration_classification.clone(),
ingested_at: DateTime::<Utc>::from_naive_utc_and_offset(ingestion.ingestion_date.unwrap(), Utc),
dosage: ingestion_mass.clone(),
phases: Default::default(),
};

let ingestion_analysis = analyze_ingestion(&ingestion_model).await.unwrap();

ingestion_model.phases = ingestion_analysis.phases;

Ok(ingestion_model)
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand All @@ -128,4 +159,3 @@ pub struct ViewModel {
pub(crate) ingested_at: String,
pub(crate) route_of_administration: String,
}

0 comments on commit 1c92691

Please sign in to comment.