Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(cli): ♻️ add rebuilding ingestion into internal struct #500

Merged
merged 14 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs :
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: "neuronek-cli-${{ steps.compile.outputs.BUILT_ARCHIVE }}"
name: "${{ steps.compile.outputs.BUILT_ARCHIVE }}"
path: |
${{ steps.compile.outputs.BUILT_ARCHIVE }}
${{ steps.compile.outputs.BUILT_CHECKSUM }}
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,
}

Loading