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 crate::types::{
CustomUnit, Experience, ExportFormat, Ingestion,
Unit,
};
use crate::types::{CustomUnit, Experience, ExportFormat, Ingestion, Unit};
pub type JournalType = Box<dyn Journal>;
@ -52,7 +49,7 @@ impl Journal for InMemJournal {
fn first_experience_by_title(&self, title: &str) -> Option<&Experience> {
self.experiences
.values()
.find(|&experience| &experience.title == title)
.find(|&experience| experience.title == title)
}
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,7 +1,6 @@
use std::ops::{Add, Mul};
#[derive(PartialEq, Debug, Copy, Clone)]
#[derive(Default)]
#[derive(PartialEq, Debug, Copy, Clone, Default)]
pub enum Estimation {
#[default]
Precise,
@ -9,7 +8,6 @@ pub enum Estimation {
StandardDeviation(StandardDeviation),
}
#[derive(PartialEq, Default, Debug, Copy, Clone)]
pub struct StandardDeviation {
pub expectation: f64,

View file

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

View file

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

View file

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

View file

@ -5,3 +5,9 @@ pub enum Unit {
Simple(String),
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::{
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(
journal: &JournalType,
@ -34,7 +34,7 @@ pub fn print_ingestion_log(
println!(
"{}|{}|{}|{}|{}",
ingestion.substance_name,
format_ingestion_dose(&ingestion.dose, &unit),
format_dose(&ingestion.dose, &unit),
format_ingestion_roa(ingestion, &unit),
ingestion.consumer,
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 {
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 {
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)]
mod tests {
use journal::types::CustomUnit;
use chrono::{TimeZone, Utc};
use journal::types::{AdministrationRoute, CustomUnit};
use super::*;
#[test]
fn format_unknown_dose() {
let result = format_ingestion_dose(
&Dose {
value: 0.0,
contains_unknown: true,
estimation: Estimation::Estimate,
},
&Unit::Simple("mg".to_string()),
);
assert_eq!(result, "Unknown mg");
fn format_experience_title() {
let result = super::format_experience_title(&Experience {
title: "Title".to_string(),
creation_time: Utc.timestamp_millis_opt(0).unwrap(),
..Experience::default()
});
assert_eq!(result, "Title: 1970-01-01 00:00:00 UTC");
}
#[test]
fn format_unknown_dose_custom_unit() {
let result = format_ingestion_dose(
&Dose {
value: 0.0,
contains_unknown: true,
estimation: Estimation::Estimate,
fn format_ingestion_time() {
let result = super::format_ingestion_time(&Ingestion {
creation_time: Utc.timestamp_millis_opt(0).unwrap(),
..Ingestion::default()
});
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 {
id: 0,
unit: Some(CustomUnit {
original_unit: "mg".to_string(),
name: "32mg/ml".to_string(),
..CustomUnit::default()
}),
},
);
assert_eq!(result, "Unknown mg");
assert_eq!(result, "Oral (32mg/ml)");
}
}

View file

@ -1,8 +1,6 @@
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display};
mod tests;
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "UPPERCASE")]
pub enum AdministrationRoute {
@ -119,3 +117,28 @@ pub struct ExportData {
pub custom_substances: Vec<CustomSubstance>,
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");
}
}