This commit is contained in:
chaos 2024-11-22 18:50:18 +00:00
parent 0097705c82
commit 6148b1cddd
11 changed files with 311 additions and 114 deletions

View file

@ -1,9 +1,6 @@
use rand::Rng; use rand::Rng;
use crate::types::{ use crate::types::{CustomUnit, Experience, ExportFormat, Ingestion, Unit};
CustomUnit, Experience, ExportFormat, Ingestion,
Unit,
};
pub type JournalType = Box<dyn Journal>; pub type JournalType = Box<dyn Journal>;
@ -52,7 +49,7 @@ impl Journal for InMemJournal {
fn first_experience_by_title(&self, title: &str) -> Option<&Experience> { fn first_experience_by_title(&self, title: &str) -> Option<&Experience> {
self.experiences self.experiences
.values() .values()
.find(|&experience| &experience.title == title) .find(|&experience| experience.title == title)
} }
fn import_psychonaut(&mut self, data: psychonaut_journal_types::ExportData) { fn import_psychonaut(&mut self, data: psychonaut_journal_types::ExportData) {

View file

@ -0,0 +1,227 @@
use std::fmt::Display;
use super::{Dose, Estimation, Unit};
pub fn format_dose(dose: &Dose, unit: &Unit) -> String {
format!("{}", DoseWithUnitRefs { dose, unit })
}
#[derive(Default, PartialEq, Debug, Clone)]
pub struct DoseWithUnit {
pub dose: Dose,
pub unit: Unit,
}
pub(crate) struct DoseWithUnitRefs<'a> {
pub dose: &'a Dose,
pub unit: &'a Unit,
}
impl Display for DoseWithUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
DoseWithUnitRefs {
dose: &self.dose,
unit: &self.unit
}
)
}
}
impl Display for DoseWithUnitRefs<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let dose = self.dose;
let unit = self.unit;
match unit {
Unit::Simple(unit) => {
let is_unknown = dose.contains_unknown && dose.value == 0.0;
let is_estimate = dose.estimation.is_estimate() && !is_unknown;
if is_estimate {
f.write_str("~")?;
}
if is_unknown {
f.write_str("Unknown")?
} else {
let dose_rounded = (dose.value * 100.0).round() / 100.0;
write!(f, "{}", dose_rounded)?;
if dose.contains_unknown {
f.write_str("+Unknown")?;
}
}
if let Estimation::StandardDeviation(deviation) = dose.estimation {
write!(f, "±{}", (deviation.deviation * 100.0).round() / 100.0)?;
}
f.write_str((" ".to_string() + unit).as_str())
}
Unit::Custom { id: _id, unit } => {
let unit = unit.clone().unwrap();
let unit_unit = Unit::Simple(unit.unit.clone());
let ingestion_dose = dose * &unit.dose;
let ingestion_unit = Unit::Simple(unit.original_unit.clone());
// ingestion dose
write!(
f,
"{}",
DoseWithUnitRefs {
dose: &ingestion_dose,
unit: &ingestion_unit
}
)?;
f.write_str(" (")?;
// dose per unit
write!(
f,
"{}",
DoseWithUnitRefs {
dose: &unit.dose,
unit: &ingestion_unit
}
)?;
f.write_str(" * ")?;
// custom unit dose
write!(
f,
"{}",
DoseWithUnitRefs {
dose,
unit: &unit_unit
}
)?;
f.write_str(")")
}
}
}
}
#[cfg(test)]
mod tests {
use crate::types::{AdministrationRoute, CustomUnit, StandardDeviation};
use super::*;
#[test]
fn format_dose() {
let result = super::format_dose(
&Dose {
value: 0.0,
contains_unknown: false,
estimation: Estimation::Precise,
},
&Unit::Simple("mg".to_string()),
);
assert_eq!(result, "0 mg");
}
#[test]
fn format_dose_non_refs() {
let result = format!(
"{}",
DoseWithUnit {
dose: Dose {
value: 0.0,
contains_unknown: false,
estimation: Estimation::Precise,
},
unit: Unit::Simple("mg".to_string())
}
);
assert_eq!(result, "0 mg");
}
#[test]
fn format_dose_estimate() {
let result = super::format_dose(
&Dose {
value: 0.0,
contains_unknown: false,
estimation: Estimation::Estimate,
},
&Unit::Simple("mg".to_string()),
);
assert_eq!(result, "~0 mg");
}
#[test]
fn format_dose_contains_unknown() {
let result = super::format_dose(
&Dose {
value: 10.0,
contains_unknown: true,
estimation: Estimation::Estimate,
},
&Unit::Simple("mg".to_string()),
);
assert_eq!(result, "~10+Unknown mg");
}
#[test]
fn format_dose_standard_derivation() {
let result = super::format_dose(
&Dose {
value: 0.0,
contains_unknown: false,
estimation: Estimation::StandardDeviation(StandardDeviation {
expectation: 0.0,
deviation: 10.0,
}),
},
&Unit::Simple("mg".to_string()),
);
assert_eq!(result, "~0±10 mg");
}
#[test]
fn format_unknown_dose() {
let result = super::format_dose(
&Dose {
value: 0.0,
contains_unknown: true,
estimation: Estimation::Estimate,
},
&Unit::Simple("mg".to_string()),
);
assert_eq!(result, "Unknown mg");
}
#[test]
fn format_unknown_dose_with_custom_unit() {
let result = super::format_dose(
&Dose {
value: 0.0,
contains_unknown: true,
estimation: Estimation::Estimate,
},
&Unit::Custom {
id: 0,
unit: Some(CustomUnit {
name: "Beverage".to_string(),
substance_name: "Caffeine".to_string(),
unit: "sip".to_string(),
original_unit: "mg".to_string(),
dose: Dose {
value: 10.0,
contains_unknown: false,
estimation: Estimation::Estimate,
},
administration_route: AdministrationRoute::Oral,
..CustomUnit::default()
}),
},
);
assert_eq!(result, "Unknown mg (~10 mg * Unknown sip)");
}
}

View file

@ -1,15 +1,13 @@
use std::ops::{Add, Mul}; use std::ops::{Add, Mul};
#[derive(PartialEq, Debug, Copy, Clone)] #[derive(PartialEq, Debug, Copy, Clone, Default)]
#[derive(Default)]
pub enum Estimation { pub enum Estimation {
#[default] #[default]
Precise, Precise,
Estimate, Estimate,
StandardDeviation(StandardDeviation), StandardDeviation(StandardDeviation),
} }
#[derive(PartialEq, Default, Debug, Copy, Clone)] #[derive(PartialEq, Default, Debug, Copy, Clone)]
pub struct StandardDeviation { pub struct StandardDeviation {
pub expectation: f64, pub expectation: f64,

View file

@ -2,7 +2,7 @@ use chrono::{DateTime, Utc};
use super::from_unix_millis; use super::from_unix_millis;
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Default, Debug, Clone)]
pub struct Experience { pub struct Experience {
pub id: i64, pub id: i64,
pub title: String, pub title: String,

View file

@ -5,7 +5,7 @@ use super::{
Unit, Unit,
}; };
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Default, Debug, Clone)]
pub struct Ingestion { pub struct Ingestion {
pub substance_name: String, pub substance_name: String,
pub ingestion_time: DateTime<Utc>, pub ingestion_time: DateTime<Utc>,

View file

@ -5,6 +5,12 @@ pub use estimate::{Estimation, StandardDeviation};
mod dose; mod dose;
pub use dose::Dose; pub use dose::Dose;
mod unit;
pub use unit::Unit;
mod dose_with_unit;
pub use dose_with_unit::{format_dose, DoseWithUnit};
mod consumer; mod consumer;
pub use consumer::Consumer; pub use consumer::Consumer;
@ -23,9 +29,6 @@ pub use custom_unit::CustomUnit;
mod export; mod export;
pub use export::ExportFormat; pub use export::ExportFormat;
mod unit;
pub use unit::Unit;
pub type AdministrationRoute = psychonaut_journal_types::AdministrationRoute; pub type AdministrationRoute = psychonaut_journal_types::AdministrationRoute;
pub(crate) fn from_unix_millis(time: u64) -> DateTime<Utc> { pub(crate) fn from_unix_millis(time: u64) -> DateTime<Utc> {

View file

@ -5,3 +5,9 @@ pub enum Unit {
Simple(String), Simple(String),
Custom { id: i64, unit: Option<CustomUnit> }, Custom { id: i64, unit: Option<CustomUnit> },
} }
impl Default for Unit {
fn default() -> Self {
Unit::Simple("mg".to_string())
}
}

View file

@ -1,9 +1,9 @@
use journal::{ use journal::{
journal::JournalType, journal::JournalType,
types::{Consumer, Experience}, types::{format_dose, Consumer, Experience},
}; };
use crate::formatting::{format_ingestion_dose, format_ingestion_roa, format_ingestion_time}; use crate::formatting::{format_ingestion_roa, format_ingestion_time};
pub fn print_ingestion_log( pub fn print_ingestion_log(
journal: &JournalType, journal: &JournalType,
@ -34,7 +34,7 @@ pub fn print_ingestion_log(
println!( println!(
"{}|{}|{}|{}|{}", "{}|{}|{}|{}|{}",
ingestion.substance_name, ingestion.substance_name,
format_ingestion_dose(&ingestion.dose, &unit), format_dose(&ingestion.dose, &unit),
format_ingestion_roa(ingestion, &unit), format_ingestion_roa(ingestion, &unit),
ingestion.consumer, ingestion.consumer,
format_ingestion_time(ingestion) format_ingestion_time(ingestion)

View file

@ -1,60 +1,9 @@
use journal::types::{Dose, Estimation, Experience, Ingestion, Unit}; use journal::types::{Experience, Ingestion, Unit};
pub fn format_experience_title(experience: &Experience) -> String { pub fn format_experience_title(experience: &Experience) -> String {
format!("{}: {}", experience.title, experience.creation_time) format!("{}: {}", experience.title, experience.creation_time)
} }
pub fn format_ingestion_dose(dose: &Dose, unit: &Unit) -> String {
if dose.value != 0.0 && !dose.contains_unknown {
match unit {
Unit::Simple(unit) => {
let is_estimate = dose.estimation.is_estimate();
let estimate = if is_estimate { "~" } else { "" };
let standard_deviation = {
if let Estimation::StandardDeviation(deviation) = dose.estimation {
format!("±{}", (deviation.deviation * 100.0).round() / 100.0)
} else {
"".to_string()
}
};
let unknown = if dose.contains_unknown {
"+Unknown"
} else {
""
};
let dose = (dose.value * 100.0).round() / 100.0;
format!("{estimate}{dose}{standard_deviation}{unknown} {unit}")
}
Unit::Custom { id: _id, unit } => {
let unit = unit.clone().unwrap();
let unit_unit = Unit::Simple(unit.unit.clone());
let ingestion_dose = dose * &unit.dose;
let ingestion_unit = Unit::Simple(unit.original_unit.clone());
let ingestion_dose = format_ingestion_dose(&ingestion_dose, &ingestion_unit);
let dose_per_unit = format_ingestion_dose(&unit.dose, &ingestion_unit);
let custom_unit_dose = format_ingestion_dose(dose, &unit_unit);
format!("{ingestion_dose} ({dose_per_unit} * {custom_unit_dose})")
}
}
} else {
format!(
"Unknown {}",
match unit {
Unit::Simple(unit) => unit,
Unit::Custom { id: _id, unit } => &unit.as_ref().unwrap().original_unit,
}
)
}
}
pub fn format_ingestion_time(ingestion: &Ingestion) -> String { pub fn format_ingestion_time(ingestion: &Ingestion) -> String {
ingestion.ingestion_time.format("%a %I:%M %p").to_string() ingestion.ingestion_time.format("%a %I:%M %p").to_string()
} }
@ -69,39 +18,57 @@ pub fn format_ingestion_roa(ingestion: &Ingestion, unit: &Unit) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use journal::types::CustomUnit; use chrono::{TimeZone, Utc};
use journal::types::{AdministrationRoute, CustomUnit};
use super::*; use super::*;
#[test] #[test]
fn format_unknown_dose() { fn format_experience_title() {
let result = format_ingestion_dose( let result = super::format_experience_title(&Experience {
&Dose { title: "Title".to_string(),
value: 0.0, creation_time: Utc.timestamp_millis_opt(0).unwrap(),
contains_unknown: true, ..Experience::default()
estimation: Estimation::Estimate, });
}, assert_eq!(result, "Title: 1970-01-01 00:00:00 UTC");
&Unit::Simple("mg".to_string()),
);
assert_eq!(result, "Unknown mg");
} }
#[test] #[test]
fn format_unknown_dose_custom_unit() { fn format_ingestion_time() {
let result = format_ingestion_dose( let result = super::format_ingestion_time(&Ingestion {
&Dose { creation_time: Utc.timestamp_millis_opt(0).unwrap(),
value: 0.0, ..Ingestion::default()
contains_unknown: true, });
estimation: Estimation::Estimate, assert_eq!(result, "Thu 12:00 AM");
}
#[test]
fn format_ingestion_roa() {
let result = super::format_ingestion_roa(
&Ingestion {
roa: AdministrationRoute::Oral,
..Ingestion::default()
},
&Unit::default(),
);
assert_eq!(result, "Oral");
}
#[test]
fn format_ingestion_roa_with_custom_unit() {
let result = super::format_ingestion_roa(
&Ingestion {
roa: AdministrationRoute::Oral,
..Ingestion::default()
}, },
&Unit::Custom { &Unit::Custom {
id: 0, id: 0,
unit: Some(CustomUnit { unit: Some(CustomUnit {
original_unit: "mg".to_string(), name: "32mg/ml".to_string(),
..CustomUnit::default() ..CustomUnit::default()
}), }),
}, },
); );
assert_eq!(result, "Unknown mg"); assert_eq!(result, "Oral (32mg/ml)");
} }
} }

View file

@ -1,8 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
mod tests;
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "UPPERCASE")] #[serde(rename_all = "UPPERCASE")]
pub enum AdministrationRoute { pub enum AdministrationRoute {
@ -119,3 +117,28 @@ pub struct ExportData {
pub custom_substances: Vec<CustomSubstance>, pub custom_substances: Vec<CustomSubstance>,
pub custom_units: Vec<CustomUnit>, pub custom_units: Vec<CustomUnit>,
} }
#[cfg(test)]
mod tests {
use super::{AdministrationRoute::*, *};
#[test]
fn default_administration_dose() {
assert_eq!(AdministrationRoute::default(), AdministrationRoute::Oral);
}
#[test]
fn administration_dose_to_string() {
assert_eq!(Oral.to_string(), "Oral");
assert_eq!(Sublingual.to_string(), "Sublingual");
assert_eq!(Buccal.to_string(), "Buccal");
assert_eq!(Insufflated.to_string(), "Insufflated");
assert_eq!(Rectal.to_string(), "Rectal");
assert_eq!(Transdermal.to_string(), "Transdermal");
assert_eq!(Subcutaneous.to_string(), "Subcutaneous");
assert_eq!(Intramuscular.to_string(), "Intramuscular");
assert_eq!(Intravenous.to_string(), "Intravenous");
assert_eq!(Smoked.to_string(), "Smoked");
assert_eq!(Inhaled.to_string(), "Inhaled");
}
}

View file

@ -1,24 +0,0 @@
#[cfg(test)]
mod tests {
use crate::AdministrationRoute::{self, *};
#[test]
fn default_administration_dose() {
assert_eq!(AdministrationRoute::default(), AdministrationRoute::Oral);
}
#[test]
fn administration_dose_to_string() {
assert_eq!(Oral.to_string(), "Oral");
assert_eq!(Sublingual.to_string(), "Sublingual");
assert_eq!(Buccal.to_string(), "Buccal");
assert_eq!(Insufflated.to_string(), "Insufflated");
assert_eq!(Rectal.to_string(), "Rectal");
assert_eq!(Transdermal.to_string(), "Transdermal");
assert_eq!(Subcutaneous.to_string(), "Subcutaneous");
assert_eq!(Intramuscular.to_string(), "Intramuscular");
assert_eq!(Intravenous.to_string(), "Intravenous");
assert_eq!(Smoked.to_string(), "Smoked");
assert_eq!(Inhaled.to_string(), "Inhaled");
}
}