This commit is contained in:
chaos 2024-11-27 19:10:02 +00:00
parent 935672d716
commit 5e4a4cc54f
20 changed files with 416 additions and 330 deletions

124
journal/src/calculate.rs Normal file
View file

@ -0,0 +1,124 @@
use std::{
collections::HashMap,
ops::{Add, Mul},
};
use crate::types::{Dose, HydrationIngestion, IngestionKind, Nutrients, SubstanceIngestion};
#[derive(Debug, Default, PartialEq)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct CalculatedValues {
pub doses: HashMap<String, Dose>,
pub nutrients: Nutrients,
pub hydration: HydrationIngestion,
}
impl Add<CalculatedValues> for CalculatedValues {
type Output = CalculatedValues;
fn add(self, rhs: CalculatedValues) -> Self::Output {
let mut doses = self.doses.clone();
let mut nutrients = self.nutrients.clone();
let mut hydration = self.hydration.clone();
for (substance_id, substance_dose) in rhs.doses.iter() {
let dose = self
.doses
.get(substance_id)
.cloned()
.or(Some(Dose::new_precise(0.0)))
.unwrap();
doses.insert(substance_id.clone(), dose + substance_dose.clone());
}
nutrients.kcal += rhs.nutrients.kcal;
hydration.amount_ml += rhs.hydration.amount_ml;
CalculatedValues {
doses,
nutrients,
hydration,
}
}
}
impl Mul<f64> for CalculatedValues {
type Output = CalculatedValues;
fn mul(self, rhs: f64) -> Self::Output {
let mut doses = self.doses.clone();
let mut nutrients = self.nutrients.clone();
let mut hydration = self.hydration.clone();
for (_substance_id, substance_dose) in doses.iter_mut() {
*substance_dose = substance_dose.clone() * Dose::new_precise(rhs);
}
nutrients.kcal = (rhs * nutrients.kcal as f64).round() as u64;
hydration.amount_ml *= rhs;
CalculatedValues {
doses,
nutrients,
hydration,
}
}
}
impl CalculatedValues {
fn new() -> Self {
Self {
..Default::default()
}
}
}
pub fn calculate_ingestion_kind(kind: IngestionKind) -> CalculatedValues {
let mut values = CalculatedValues::new();
let mut ingestions: Vec<IngestionKind> = Vec::new();
ingestions.push(kind);
while !ingestions.is_empty() {
let current_ingestions = ingestions.clone();
for kind in current_ingestions {
match kind {
IngestionKind::Unknown => {}
IngestionKind::Substance(substance) => {
let dose = values
.doses
.get(&substance.id)
.cloned()
.or(Some(Dose::new_precise(0.0)))
.unwrap();
values
.doses
.insert(substance.id.clone(), dose + substance.dose.clone());
}
IngestionKind::Sustenance(sustenance) => {
let amount = sustenance.amount;
let mut sustenance = sustenance.sustenance.clone().unwrap();
for include in sustenance.includes.iter_mut() {
match include {
IngestionKind::Unknown => {}
IngestionKind::Substance(ingestion) => {
ingestion.dose = ingestion.dose.clone() * Dose::new_precise(amount)
}
IngestionKind::Sustenance(ingestion) => ingestion.amount *= amount,
IngestionKind::Hydration(ingestion) => ingestion.amount_ml *= amount,
}
ingestions.push(include.clone());
}
}
IngestionKind::Hydration(hydration) => {
values.hydration.amount_ml += hydration.amount_ml;
}
}
}
}
values
}

View file

@ -4,11 +4,11 @@ use chrono::Utc;
use rand::Rng; use rand::Rng;
use crate::types::{ use crate::types::{
CustomUnit, Dose, Ingestion, IngestionKind, IngestionKinds, Session, Substance, CustomUnit, Dose, Ingestion, IngestionKind, Session, Substance, SubstanceIngestion, Sustenance,
SubstanceIngestion, Sustenance, SustenanceIngestion, Unit, SustenanceIngestion, Unit,
}; };
use super::{Journal, JournalType}; use super::{Journal, JournalIntegrityChecks, JournalTrait, JournalType};
mod data; mod data;
use data::JSONJournalData; use data::JSONJournalData;
@ -67,7 +67,7 @@ impl JSONJournal {
} }
} }
impl Journal for JSONJournal { impl JournalTrait for JSONJournal {
fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> { fn save(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.save() self.save()
} }
@ -80,83 +80,40 @@ impl Journal for JSONJournal {
self.data.ingestions.get(&id).cloned() self.data.ingestions.get(&id).cloned()
} }
fn get_substance(&self, name: &str) -> Option<Substance> { fn get_substance(&self, id: &str) -> Option<Substance> {
self.data.substances.get(name).cloned() self.data.substances.get(id).cloned()
} }
fn get_sustenance(&self, sustenance_id: &str) -> Option<Sustenance> { fn get_sustenance(&self, id: &str) -> Option<Sustenance> {
self.data.sustenances.get(sustenance_id).cloned() self.data.sustenances.get(id).cloned()
} }
fn get_custom_unit(&self, id: i32) -> Option<CustomUnit> { fn get_custom_unit(&self, id: i32) -> Option<CustomUnit> {
self.data.custom_units.get(&id).cloned() self.data.custom_units.get(&id).cloned()
} }
fn resolve_ingestion_kind( fn resolve_ingestion_kind(&self, kind: IngestionKind) -> Option<IngestionKind> {
&self, let mut kind = kind;
kind: &IngestionKind,
calculate: bool,
) -> Option<IngestionKinds> {
println!("{kind:#?}");
let mut kinds: IngestionKinds = IngestionKinds::from(kind);
if let IngestionKind::Sustenance(sustenance) = kind { if let IngestionKind::Sustenance(ingestion_type) = &mut kind {
let includes = self.get_sustenance(&sustenance.sustenance_id)?.includes; if let Some(mut sustenance) = self.get_sustenance(&ingestion_type.id) {
sustenance.includes = sustenance
for include in includes.into_iter() { .includes
if calculate { .into_iter()
match include { .map(|include| self.resolve_ingestion_kind(include).unwrap())
IngestionKind::Sustenance(ingestion) => { .collect();
kinds.includes.push(IngestionKinds { ingestion_type.sustenance = Some(sustenance);
ingestion: IngestionKind::Sustenance(SustenanceIngestion {
amount: sustenance.amount * ingestion.amount,
..ingestion.clone()
}),
includes: self
.resolve_ingestion_kind(
&IngestionKind::Sustenance(ingestion),
calculate,
)
.unwrap()
.includes,
});
}
IngestionKind::Unknown => {
kinds
.includes
.push(IngestionKinds::from(&IngestionKind::Unknown));
}
IngestionKind::Substance(ingestion) => {
kinds
.includes
.push(IngestionKinds::from(&IngestionKind::Substance(
SubstanceIngestion {
dose: Dose::new_precise(sustenance.amount)
* ingestion.dose.clone(),
..ingestion
},
)));
}
IngestionKind::Hydration { amount } => {
kinds
.includes
.push(IngestionKinds::from(&IngestionKind::Hydration {
amount: (sustenance.amount * amount as f64).round() as u64,
}));
}
}
} else {
kinds.includes.push(IngestionKinds::from(&include))
}
} }
} else if let IngestionKind::Substance(ingestion_type) = &mut kind {
ingestion_type.substance = self.get_substance(&ingestion_type.id);
} }
Some(kinds) Some(kind)
} }
fn resolve_substance_unit(&self, ingestion: &SubstanceIngestion) -> Option<Unit> { fn resolve_substance_unit(&self, ingestion: &SubstanceIngestion) -> Option<Unit> {
match ingestion.custom_unit_id { match ingestion.custom_unit_id {
None => match self.get_substance(&ingestion.substance_name) { None => match self.get_substance(&ingestion.id) {
Some(substance) => Some(Unit::Simple(substance.unit)), Some(substance) => Some(Unit::Simple(substance.unit)),
None => None, None => None,
}, },
@ -180,14 +137,18 @@ impl Journal for JSONJournal {
} }
fn create_substance(&mut self, substance: Substance) -> Substance { fn create_substance(&mut self, substance: Substance) -> Substance {
assert!(substance.id == substance.id.replace(" ", "_").to_ascii_uppercase());
self.data self.data
.substances .substances
.insert(substance.name.clone(), substance.clone()); .insert(substance.id.clone(), substance.clone());
substance substance
} }
fn create_sustenance(&mut self, sustenance: Sustenance) -> Sustenance { fn create_sustenance(&mut self, sustenance: Sustenance) -> Sustenance {
assert!(sustenance.id == sustenance.id.replace(" ", "_").to_ascii_uppercase());
self.data self.data
.sustenances .sustenances
.insert(sustenance.id.clone(), sustenance.clone()); .insert(sustenance.id.clone(), sustenance.clone());
@ -211,13 +172,14 @@ impl Journal for JSONJournal {
self.data.sessions.insert(id, Session { id, ..session }); self.data.sessions.insert(id, Session { id, ..session });
} }
fn update_substance(&mut self, name: &str, substance: Substance, create: bool) { fn update_substance(&mut self, id: &str, substance: Substance, create: bool) {
assert!(self.data.substances.contains_key(name) || create); assert!(self.data.substances.contains_key(id) || create);
assert!(id == id.replace(" ", "_").to_ascii_uppercase());
self.data.substances.insert( self.data.substances.insert(
name.to_string(), id.to_string(),
Substance { Substance {
name: name.to_string(), id: id.to_string(),
..substance ..substance
}, },
); );
@ -225,6 +187,7 @@ impl Journal for JSONJournal {
fn update_sustenance(&mut self, id: &str, sustenance: Sustenance, create: bool) { fn update_sustenance(&mut self, id: &str, sustenance: Sustenance, create: bool) {
assert!(self.data.sustenances.contains_key(id) || create); assert!(self.data.sustenances.contains_key(id) || create);
assert!(id == id.replace(" ", "_").to_ascii_uppercase());
self.data.sustenances.insert( self.data.sustenances.insert(
id.to_string(), id.to_string(),
@ -253,3 +216,6 @@ impl Journal for JSONJournal {
.cloned() .cloned()
} }
} }
impl JournalIntegrityChecks for JSONJournal {}
impl Journal for JSONJournal {}

View file

@ -1,8 +1,8 @@
use crate::types::{ use crate::types::{
CustomUnit, Ingestion, IngestionKind, IngestionKinds, Session, Substance, SubstanceIngestion, CustomUnit, Ingestion, IngestionKind, Session, Substance, SubstanceIngestion, Sustenance, Unit,
Sustenance, Unit,
}; };
pub trait Journal: JournalTrait + JournalIntegrityChecks {}
pub type JournalType = Box<dyn Journal>; pub type JournalType = Box<dyn Journal>;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -11,20 +11,16 @@ pub enum JournalError {
Unknown, Unknown,
} }
pub trait Journal { pub trait JournalTrait {
fn save(&mut self) -> Result<(), Box<dyn std::error::Error>>; fn save(&mut self) -> Result<(), Box<dyn std::error::Error>>;
fn get_session(&self, id: i32) -> Option<Session>; fn get_session(&self, id: i32) -> Option<Session>;
fn get_session_ingestions(&self, id: i32) -> Option<Vec<Ingestion>>; fn get_session_ingestions(&self, id: i32) -> Option<Vec<Ingestion>>;
fn get_substance(&self, name: &str) -> Option<Substance>; fn get_substance(&self, id: &str) -> Option<Substance>;
fn get_sustenance(&self, id: &str) -> Option<Sustenance>; fn get_sustenance(&self, id: &str) -> Option<Sustenance>;
fn get_custom_unit(&self, id: i32) -> Option<CustomUnit>; fn get_custom_unit(&self, id: i32) -> Option<CustomUnit>;
fn resolve_ingestion_kind( fn resolve_ingestion_kind(&self, ingestion: IngestionKind) -> Option<IngestionKind>;
&self,
ingestion: &IngestionKind,
calculate: bool,
) -> Option<IngestionKinds>;
fn resolve_substance_unit(&self, ingestion: &SubstanceIngestion) -> Option<Unit>; fn resolve_substance_unit(&self, ingestion: &SubstanceIngestion) -> Option<Unit>;
fn create_session(&mut self, title: &str) -> Session; fn create_session(&mut self, title: &str) -> Session;
@ -42,6 +38,12 @@ pub trait Journal {
fn first_session_by_title(&self, title: &str) -> Option<Session>; fn first_session_by_title(&self, title: &str) -> Option<Session>;
} }
pub trait JournalIntegrityChecks: JournalTrait {
fn check_sustenance_for_recursion(&self, _sustenance: Sustenance) -> Option<Vec<String>> {
todo!()
}
}
#[cfg(feature = "json_journal")] #[cfg(feature = "json_journal")]
mod json_journal; mod json_journal;
#[cfg(feature = "json_journal")] #[cfg(feature = "json_journal")]

View file

@ -1,2 +1,3 @@
pub mod calculate;
pub mod journal; pub mod journal;
pub mod types; pub mod types;

View file

@ -0,0 +1,43 @@
use std::fmt::Display;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "UPPERCASE")]
pub enum AdministrationRoute {
Oral,
Sublingual,
Buccal,
Insufflated,
Rectal,
Transdermal,
Subcutaneous,
Intramuscular,
Intravenous,
Smoked,
Inhaled,
}
impl Default for AdministrationRoute {
fn default() -> Self {
Self::Oral
}
}
impl Display for AdministrationRoute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Self::Oral => f.write_str("Oral"),
Self::Sublingual => f.write_str("Sublingual"),
Self::Buccal => f.write_str("Buccal"),
Self::Insufflated => f.write_str("Insufflated"),
Self::Rectal => f.write_str("Rectal"),
Self::Transdermal => f.write_str("Transdermal"),
Self::Subcutaneous => f.write_str("Subcutaneous"),
Self::Intramuscular => f.write_str("Intramuscular"),
Self::Intravenous => f.write_str("Intravenous"),
Self::Smoked => f.write_str("Smoked"),
Self::Inhaled => f.write_str("Inhaled"),
}
}
}

View file

@ -17,73 +17,3 @@ pub struct CustomUnit {
pub creation_time: DateTime<Utc>, pub creation_time: DateTime<Utc>,
pub is_archived: bool, pub is_archived: bool,
} }
impl From<psychonaut_journal_types::CustomUnit> for CustomUnit {
fn from(custom_unit: psychonaut_journal_types::CustomUnit) -> Self {
CustomUnit {
id: custom_unit.id,
name: custom_unit.name,
substance_name: custom_unit.substance_name,
unit: custom_unit.unit,
original_unit: custom_unit.original_unit,
dose: {
if custom_unit.estimate_standard_deviation.is_some() {
Dose::new_deviation(
custom_unit.dose.unwrap(),
custom_unit.estimate_standard_deviation.unwrap(),
)
} else if custom_unit.is_estimate && custom_unit.dose.is_some() {
Dose::new_estimate(custom_unit.dose.unwrap())
} else if custom_unit.dose.is_some() {
Dose::new_precise(custom_unit.dose.unwrap())
} else {
Dose::new_unknown()
}
},
administration_route: custom_unit.administration_route,
creation_time: from_unix_millis(custom_unit.creation_time),
is_archived: custom_unit.is_archived,
}
}
}
#[cfg(test)]
mod tests {
use crate::types::{from_unix_millis, AdministrationRoute, Dose};
use super::CustomUnit;
use psychonaut_journal_types::CustomUnit as PsychonautCustomUnit;
#[test]
fn psychonaut_journal_conversion() {
assert_eq!(
CustomUnit::from(PsychonautCustomUnit {
id: 0,
substance_name: "Caffeine".to_string(),
name: "Beverage".to_string(),
creation_time: 0,
administration_route: AdministrationRoute::Oral,
dose: Some(10.0),
unit: "sip".to_string(),
original_unit: "mg".to_string(),
is_estimate: true,
estimate_standard_deviation: Some(10.0),
is_archived: false
}),
CustomUnit {
id: 0,
name: "Beverage".to_string(),
substance_name: "Caffeine".to_string(),
unit: "sip".to_string(),
original_unit: "mg".to_string(),
dose: Dose::new_deviation(10.0, 10.0),
administration_route: AdministrationRoute::Oral,
creation_time: from_unix_millis(0),
is_archived: false
}
);
}
}

View file

@ -1,9 +1,9 @@
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use super::{dose::Dose, from_unix_millis, Consumer}; use super::Consumer;
mod kind; mod kind;
pub use kind::{IngestionKind, IngestionKinds}; pub use kind::IngestionKind;
mod substance; mod substance;
pub use substance::SubstanceIngestion; pub use substance::SubstanceIngestion;
@ -11,6 +11,9 @@ pub use substance::SubstanceIngestion;
mod sustenance; mod sustenance;
pub use sustenance::SustenanceIngestion; pub use sustenance::SustenanceIngestion;
mod hydration;
pub use hydration::HydrationIngestion;
#[derive(PartialEq, Default, Debug, Clone)] #[derive(PartialEq, Default, Debug, Clone)]
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
pub struct Ingestion { pub struct Ingestion {
@ -20,87 +23,3 @@ pub struct Ingestion {
pub notes: String, pub notes: String,
pub kind: IngestionKind, pub kind: IngestionKind,
} }
impl From<psychonaut_journal_types::Ingestion> for Ingestion {
fn from(ingestion: psychonaut_journal_types::Ingestion) -> Self {
Ingestion {
ingestion_time: from_unix_millis(ingestion.ingestion_time),
creation_time: from_unix_millis(ingestion.creation_time),
kind: IngestionKind::Substance(SubstanceIngestion {
substance_name: ingestion.substance_name,
dose: {
if ingestion.estimate_standard_deviation.is_some() {
Dose::new_deviation(
ingestion.dose.unwrap(),
ingestion.estimate_standard_deviation.unwrap(),
)
} else if ingestion.is_estimate && ingestion.dose.is_some() {
Dose::new_estimate(ingestion.dose.unwrap())
} else if ingestion.dose.is_some() {
Dose::new_precise(ingestion.dose.unwrap())
} else {
Dose::new_unknown()
}
},
custom_unit_id: ingestion.custom_unit_id,
roa: ingestion.roa,
stomach_fullness: ingestion.stomach_fullness,
}),
consumer: match ingestion.consumer_name {
Some(name) => Consumer::Named(name),
None => Consumer::Default,
},
notes: ingestion.notes,
}
}
}
#[cfg(test)]
mod tests {
use crate::types::{
from_unix_millis,
ingestion::{IngestionKind, SubstanceIngestion},
AdministrationRoute, Consumer, Dose,
};
use super::Ingestion;
use psychonaut_journal_types::Ingestion as PsychonautIngestion;
#[test]
fn psychonaut_journal_conversion() {
assert_eq!(
Ingestion::from(PsychonautIngestion {
substance_name: "Caffeine".to_string(),
ingestion_time: 0,
creation_time: 0,
dose: Some(10.0),
unit: "mg".to_string(),
is_estimate: false,
estimate_standard_deviation: None,
custom_unit_id: None,
roa: AdministrationRoute::Oral,
consumer_name: None,
notes: "".to_string(),
stomach_fullness: None,
}),
Ingestion {
ingestion_time: from_unix_millis(0),
creation_time: from_unix_millis(0),
kind: IngestionKind::Substance(SubstanceIngestion {
substance_name: "Caffeine".to_string(),
dose: Dose::new_precise(10.0),
custom_unit_id: None,
roa: AdministrationRoute::Oral,
stomach_fullness: None
}),
consumer: Consumer::Default,
notes: "".to_string(),
}
);
}
}

View file

@ -0,0 +1,13 @@
use std::fmt::Display;
#[derive(PartialEq, Default, Debug, Clone)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct HydrationIngestion {
pub amount_ml: f64,
}
impl Display for HydrationIngestion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ml", self.amount_ml)
}
}

View file

@ -1,4 +1,4 @@
use super::{SubstanceIngestion, SustenanceIngestion}; use super::{HydrationIngestion, SubstanceIngestion, SustenanceIngestion};
#[derive(PartialEq, Default, Debug, Clone)] #[derive(PartialEq, Default, Debug, Clone)]
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
@ -8,23 +8,5 @@ pub enum IngestionKind {
Unknown, Unknown,
Substance(SubstanceIngestion), Substance(SubstanceIngestion),
Sustenance(SustenanceIngestion), Sustenance(SustenanceIngestion),
Hydration { Hydration(HydrationIngestion),
amount: u64,
},
}
#[derive(PartialEq, Default, Debug, Clone)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct IngestionKinds {
pub ingestion: IngestionKind,
pub includes: Vec<IngestionKinds>,
}
impl From<&IngestionKind> for IngestionKinds {
fn from(value: &IngestionKind) -> Self {
IngestionKinds {
ingestion: value.clone(),
..Default::default()
}
}
} }

View file

@ -1,9 +1,11 @@
use crate::types::{AdministrationRoute, Dose}; use crate::types::{AdministrationRoute, Dose, Substance};
#[derive(PartialEq, Default, Debug, Clone)] #[derive(PartialEq, Default, Debug, Clone)]
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
pub struct SubstanceIngestion { pub struct SubstanceIngestion {
pub substance_name: String, pub id: String,
pub substance: Option<Substance>,
pub dose: Dose, pub dose: Dose,
pub custom_unit_id: Option<i32>, pub custom_unit_id: Option<i32>,
pub roa: AdministrationRoute, pub roa: AdministrationRoute,

View file

@ -1,6 +1,10 @@
use crate::types::Sustenance;
#[derive(PartialEq, Default, Debug, Clone)] #[derive(PartialEq, Default, Debug, Clone)]
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
pub struct SustenanceIngestion { pub struct SustenanceIngestion {
pub sustenance_id: String, pub id: String,
pub sustenance: Option<Sustenance>,
pub amount: f64, pub amount: f64,
} }

View file

@ -14,7 +14,7 @@ pub use consumer::Consumer;
mod ingestion; mod ingestion;
pub use ingestion::{ pub use ingestion::{
Ingestion, IngestionKind, IngestionKinds, SubstanceIngestion, SustenanceIngestion, HydrationIngestion, Ingestion, IngestionKind, SubstanceIngestion, SustenanceIngestion,
}; };
mod substance; mod substance;
@ -32,7 +32,8 @@ pub use session::Session;
mod custom_unit; mod custom_unit;
pub use custom_unit::CustomUnit; pub use custom_unit::CustomUnit;
pub type AdministrationRoute = psychonaut_journal_types::AdministrationRoute; pub mod administration_route;
pub use administration_route::AdministrationRoute;
pub(crate) fn from_unix_millis(time: u64) -> DateTime<Utc> { pub(crate) fn from_unix_millis(time: u64) -> DateTime<Utc> {
Utc.timestamp_millis_opt(time as i64).unwrap() Utc.timestamp_millis_opt(time as i64).unwrap()

View file

@ -1,4 +1,4 @@
#[derive(Debug, Clone)] #[derive(PartialEq, Default, Debug, Clone)]
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
pub struct Nutrients { pub struct Nutrients {
pub kcal: u64, pub kcal: u64,

View file

@ -1,7 +1,9 @@
#[derive(Debug, Clone)] #[derive(Debug, PartialEq, Clone)]
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
pub struct Substance { pub struct Substance {
pub id: String,
pub name: String, pub name: String,
pub description: String, pub description: String,
pub unit: String, pub unit: String,
} }

View file

@ -1,6 +1,6 @@
use super::{IngestionKind, Nutrients}; use super::{IngestionKind, Nutrients};
#[derive(Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
pub struct Sustenance { pub struct Sustenance {
pub id: String, pub id: String,

View file

@ -2,13 +2,19 @@ use std::{collections::HashMap, fs::File};
use journal::{ use journal::{
journal::JournalType, journal::JournalType,
types::{CustomUnit, Ingestion, Session, Substance}, types::{
Consumer, CustomUnit, Dose, Ingestion, IngestionKind, Session, Substance,
SubstanceIngestion,
},
}; };
use psychonaut_journal_types::ExportData; use psychonaut_journal_types::ExportData;
use rand::Rng; use rand::Rng;
use types::{from_administration_route, from_unix_millis};
use crate::{args::JournalLocation, helpers::load_journal}; use crate::{args::JournalLocation, helpers::load_journal};
mod types;
mod custom_substance; mod custom_substance;
mod custom_unit; mod custom_unit;
@ -68,16 +74,14 @@ pub fn psychonaut_import(args: &PsychonautImportArgs) -> Result<(), Box<dyn std:
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
for custom_unit in export_data.custom_units.into_iter() { for custom_unit in export_data.custom_units.into_iter() {
custom_unit::create_or_update_custom_unit( custom_unit::create_or_update_custom_unit(&mut journal, custom_unit);
&mut journal,
CustomUnit::from(custom_unit),
);
} }
for custom_substance in export_data.custom_substances.into_iter() { for custom_substance in export_data.custom_substances.into_iter() {
custom_substance::create_or_check_custom_substance( custom_substance::create_or_check_custom_substance(
&mut journal, &mut journal,
Substance { Substance {
id: custom_substance.name.replace(" ", "_").to_ascii_uppercase(),
name: custom_substance.name.clone(), name: custom_substance.name.clone(),
description: custom_substance.description.clone(), description: custom_substance.description.clone(),
unit: custom_substance.units, unit: custom_substance.units,
@ -88,13 +92,14 @@ pub fn psychonaut_import(args: &PsychonautImportArgs) -> Result<(), Box<dyn std:
for experience in export_data.experiences.iter() { for experience in export_data.experiences.iter() {
for ingestion in experience.ingestions.iter() { for ingestion in experience.ingestions.iter() {
let substance_name = &ingestion.substance_name; let substance_name = &ingestion.substance_name;
let substance_id = substance_name.replace(" ", "_").to_ascii_uppercase();
let ingestion_unit = match ingestion.custom_unit_id { let ingestion_unit = match ingestion.custom_unit_id {
Some(id) => journal.get_custom_unit(id).unwrap().original_unit, Some(id) => journal.get_custom_unit(id).unwrap().original_unit,
None => ingestion.unit.clone(), None => ingestion.unit.clone(),
}; };
match journal.get_substance(substance_name) { match journal.get_substance(&substance_id) {
Some(substance) => { Some(substance) => {
if substance.unit != ingestion_unit { if substance.unit != ingestion_unit {
panic!( panic!(
@ -105,6 +110,7 @@ pub fn psychonaut_import(args: &PsychonautImportArgs) -> Result<(), Box<dyn std:
} }
None => { None => {
journal.create_substance(Substance { journal.create_substance(Substance {
id: substance_id,
name: substance_name.clone(), name: substance_name.clone(),
description: "".to_string(), description: "".to_string(),
unit: ingestion_unit, unit: ingestion_unit,
@ -134,8 +140,42 @@ pub fn psychonaut_import(args: &PsychonautImportArgs) -> Result<(), Box<dyn std:
); );
let mut ingestions: Vec<Ingestion> = Vec::new(); let mut ingestions: Vec<Ingestion> = Vec::new();
for ingestion in experience.ingestions.iter() { for ingestion in experience.ingestions.into_iter() {
ingestions.push(Ingestion::from(ingestion.clone())) ingestions.push(Ingestion {
ingestion_time: from_unix_millis(ingestion.ingestion_time),
creation_time: from_unix_millis(ingestion.creation_time),
kind: IngestionKind::Substance(SubstanceIngestion {
id: ingestion.substance_name.replace(" ", "_").to_uppercase(),
substance: None,
dose: {
if ingestion.estimate_standard_deviation.is_some() {
Dose::new_deviation(
ingestion.dose.unwrap(),
ingestion.estimate_standard_deviation.unwrap(),
)
} else if ingestion.is_estimate && ingestion.dose.is_some() {
Dose::new_estimate(ingestion.dose.unwrap())
} else if ingestion.dose.is_some() {
Dose::new_precise(ingestion.dose.unwrap())
} else {
Dose::new_unknown()
}
},
custom_unit_id: ingestion.custom_unit_id,
roa: from_administration_route(ingestion.roa),
stomach_fullness: ingestion.stomach_fullness,
}),
consumer: match ingestion.consumer_name {
Some(name) => Consumer::Named(name),
None => Consumer::Default,
},
notes: ingestion.notes,
})
} }
journal.set_session_ingestions(session_id, ingestions); journal.set_session_ingestions(session_id, ingestions);

View file

@ -22,7 +22,6 @@ pub fn create_or_check_custom_substance(journal: &mut JournalType, custom_substa
} }
#[derive(Debug, Clone, clap::Args)] #[derive(Debug, Clone, clap::Args)]
#[group(args = vec!["id", "name"], required = true, multiple = false)]
pub struct PsychonautImportCustomSubstanceArgs { pub struct PsychonautImportCustomSubstanceArgs {
#[command(flatten)] #[command(flatten)]
pub import_global_args: PsychonautImportGlobalArgs, pub import_global_args: PsychonautImportGlobalArgs,
@ -48,6 +47,7 @@ pub fn psychonaut_import_custom_substance(
create_or_check_custom_substance( create_or_check_custom_substance(
&mut journal, &mut journal,
Substance { Substance {
id: custom_substance.name.replace(" ", "_").to_ascii_uppercase(),
name: custom_substance.name.clone(), name: custom_substance.name.clone(),
description: custom_substance.description.clone(), description: custom_substance.description.clone(),
unit: custom_substance.units, unit: custom_substance.units,

View file

@ -1,8 +1,46 @@
use journal::{journal::JournalType, types::CustomUnit}; use journal::{
journal::JournalType,
types::{CustomUnit, Dose},
};
use super::{load_journal_and_export_data, PsychonautImportGlobalArgs}; use super::{
load_journal_and_export_data,
types::{from_administration_route, from_unix_millis},
PsychonautImportGlobalArgs,
};
pub fn create_or_update_custom_unit(
journal: &mut JournalType,
custom_unit: psychonaut_journal_types::CustomUnit,
) {
let unit = CustomUnit {
id: custom_unit.id,
name: custom_unit.name,
substance_name: custom_unit.substance_name,
unit: custom_unit.unit,
original_unit: custom_unit.original_unit,
dose: {
if custom_unit.estimate_standard_deviation.is_some() {
Dose::new_deviation(
custom_unit.dose.unwrap(),
custom_unit.estimate_standard_deviation.unwrap(),
)
} else if custom_unit.is_estimate && custom_unit.dose.is_some() {
Dose::new_estimate(custom_unit.dose.unwrap())
} else if custom_unit.dose.is_some() {
Dose::new_precise(custom_unit.dose.unwrap())
} else {
Dose::new_unknown()
}
},
administration_route: from_administration_route(custom_unit.administration_route),
creation_time: from_unix_millis(custom_unit.creation_time),
is_archived: custom_unit.is_archived,
};
pub fn create_or_update_custom_unit(journal: &mut JournalType, unit: CustomUnit) {
match journal.get_custom_unit(unit.id) { match journal.get_custom_unit(unit.id) {
Some(custom_unit) => { Some(custom_unit) => {
println!( println!(
@ -44,7 +82,7 @@ pub fn psychonaut_import_custom_unit(
match unit { match unit {
Some(unit) => { Some(unit) => {
create_or_update_custom_unit(&mut journal, CustomUnit::from(unit)); create_or_update_custom_unit(&mut journal, unit);
} }
None => { None => {
panic!("Could not find custom unit with ID: {id}") panic!("Could not find custom unit with ID: {id}")
@ -59,7 +97,7 @@ pub fn psychonaut_import_custom_unit(
match unit { match unit {
Some(unit) => { Some(unit) => {
create_or_update_custom_unit(&mut journal, CustomUnit::from(unit)); create_or_update_custom_unit(&mut journal, unit);
} }
None => { None => {
panic!("Could not find custom unit with name: {name}") panic!("Could not find custom unit with name: {name}")

View file

@ -0,0 +1,36 @@
use chrono::{DateTime, TimeZone, Utc};
use journal::types::AdministrationRoute;
pub fn from_unix_millis(time: u64) -> DateTime<Utc> {
Utc.timestamp_millis_opt(time as i64).unwrap()
}
pub fn from_administration_route(
roa: psychonaut_journal_types::AdministrationRoute,
) -> AdministrationRoute {
match roa {
psychonaut_journal_types::AdministrationRoute::Oral => AdministrationRoute::Oral,
psychonaut_journal_types::AdministrationRoute::Sublingual => {
AdministrationRoute::Sublingual
}
psychonaut_journal_types::AdministrationRoute::Buccal => AdministrationRoute::Buccal,
psychonaut_journal_types::AdministrationRoute::Insufflated => {
AdministrationRoute::Insufflated
}
psychonaut_journal_types::AdministrationRoute::Rectal => AdministrationRoute::Rectal,
psychonaut_journal_types::AdministrationRoute::Transdermal => {
AdministrationRoute::Transdermal
}
psychonaut_journal_types::AdministrationRoute::Subcutaneous => {
AdministrationRoute::Subcutaneous
}
psychonaut_journal_types::AdministrationRoute::Intramuscular => {
AdministrationRoute::Intramuscular
}
psychonaut_journal_types::AdministrationRoute::Intravenous => {
AdministrationRoute::Intravenous
}
psychonaut_journal_types::AdministrationRoute::Smoked => AdministrationRoute::Smoked,
psychonaut_journal_types::AdministrationRoute::Inhaled => AdministrationRoute::Inhaled,
}
}

View file

@ -1,8 +1,8 @@
use journal::{ use journal::{
journal::JournalType, journal::JournalType,
types::{ types::{
format_dose, Consumer, Dose, Ingestion, IngestionKind, IngestionKinds, Session, format_dose, Consumer, Ingestion, IngestionKind, Session, SubstanceIngestion,
SubstanceIngestion, SustenanceIngestion, SustenanceIngestion,
}, },
}; };
@ -17,7 +17,11 @@ fn print_substance_ingestion(
println!( println!(
"Substance|{}|{}|{}|{}|{}", "Substance|{}|{}|{}|{}|{}",
substance_ingestion.substance_name, substance_ingestion
.substance
.as_ref()
.expect("substance_ingestion not provided with a substance")
.name,
format_dose(&substance_ingestion.dose, &unit), format_dose(&substance_ingestion.dose, &unit),
format_substance_ingestion_roa(substance_ingestion, &unit), format_substance_ingestion_roa(substance_ingestion, &unit),
ingestion.consumer, ingestion.consumer,
@ -30,9 +34,7 @@ fn print_sustenance_ingestion(
ingestion: &Ingestion, ingestion: &Ingestion,
sustenance_ingestion: &SustenanceIngestion, sustenance_ingestion: &SustenanceIngestion,
) { ) {
let sustenance = journal let sustenance = journal.get_sustenance(&sustenance_ingestion.id).unwrap();
.get_sustenance(&sustenance_ingestion.sustenance_id)
.unwrap();
println!( println!(
"Sustenance|{}|{} {}|{}|{}", "Sustenance|{}|{} {}|{}|{}",
@ -44,35 +46,7 @@ fn print_sustenance_ingestion(
) )
} }
pub fn calculate_ingestion_kinds(kind: &mut IngestionKinds) { pub fn print_ingestion(journal: &JournalType, ingestion: &Ingestion, depth: u64) {
//println!("{kind:#?}");
if let IngestionKind::Sustenance(sustenance) = &kind.ingestion {
for kinds in kind.includes.iter_mut() {
match &mut kinds.ingestion {
IngestionKind::Substance(ingestion) => {
ingestion.dose = Dose::new_precise(sustenance.amount) * ingestion.dose.clone();
}
IngestionKind::Sustenance(ingestion) => {
ingestion.amount *= sustenance.amount;
calculate_ingestion_kinds(kinds)
}
IngestionKind::Hydration { amount } => {
*amount = (sustenance.amount * *amount as f64).round() as u64
}
_ => {}
}
}
}
}
pub fn print_ingestion_kinds(
journal: &JournalType,
ingestion: &Ingestion,
kinds: &IngestionKinds,
depth: u64,
) {
for _ in 0..(depth) { for _ in 0..(depth) {
print!(" "); print!(" ");
} }
@ -80,27 +54,35 @@ pub fn print_ingestion_kinds(
print!("- "); print!("- ");
} }
match &kinds.ingestion { match &ingestion.kind {
IngestionKind::Unknown => {} IngestionKind::Unknown => {}
IngestionKind::Substance(substance_ingestion) => { IngestionKind::Substance(substance) => {
print_substance_ingestion(journal, ingestion, substance_ingestion); print_substance_ingestion(journal, ingestion, &substance);
} }
IngestionKind::Sustenance(sustenance_ingestion) => { IngestionKind::Sustenance(sustenance) => {
print_sustenance_ingestion(journal, ingestion, sustenance_ingestion); print_sustenance_ingestion(journal, ingestion, sustenance);
let sustenance = sustenance.sustenance.as_ref().unwrap();
for include in &sustenance.includes {
print_ingestion(
journal,
&Ingestion {
kind: include.clone(),
..ingestion.clone()
},
depth + 1,
)
}
} }
IngestionKind::Hydration { amount } => { IngestionKind::Hydration(hydration) => {
println!( println!(
"Hydration|{} ml|{}|{}", "Hydration|{}|{}|{}",
amount, hydration,
ingestion.consumer, ingestion.consumer,
format_ingestion_time(ingestion) format_ingestion_time(ingestion)
) )
} }
} }
for kinds in &kinds.includes {
print_ingestion_kinds(journal, ingestion, kinds, depth + 1);
}
} }
pub fn print_ingestion_log( pub fn print_ingestion_log(
@ -108,22 +90,23 @@ pub fn print_ingestion_log(
session: &Session, session: &Session,
consumer_filter: Option<&Vec<Consumer>>, consumer_filter: Option<&Vec<Consumer>>,
) { ) {
let ingestions = journal let mut ingestions = journal
.get_session_ingestions(session.id) .get_session_ingestions(session.id)
.expect("could not find ingestions for session"); .expect("could not find ingestions for session");
for ingestion in ingestions.iter() { for ingestion in ingestions.iter_mut() {
if let Some(consumer_filter) = consumer_filter { if let Some(consumer_filter) = consumer_filter {
if !consumer_filter.contains(&ingestion.consumer) { if !consumer_filter.contains(&ingestion.consumer) {
continue; continue;
} }
} }
let ingestion_kinds = journal ingestion.kind = journal
.resolve_ingestion_kind(&ingestion.kind, true) .resolve_ingestion_kind(ingestion.kind.clone())
.unwrap(); .unwrap();
println!("{ingestion_kinds:#?}");
print_ingestion_kinds(journal, ingestion, &ingestion_kinds, 0) //let calculated_values = calculate_ingestion_kinds(ingestion_kinds);
print_ingestion(journal, ingestion, 0)
} }
} }