Skip to content

Commit

Permalink
add initial terminal user interface
Browse files Browse the repository at this point in the history
  • Loading branch information
keinsell committed Jan 18, 2025
1 parent b22dc30 commit 2f4d1ec
Show file tree
Hide file tree
Showing 52 changed files with 1,271 additions and 1,004 deletions.
22 changes: 0 additions & 22 deletions .cargo/config.toml

This file was deleted.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ logforth = "0.19.0"
measurements = { version = "0.11.0", features = ["std", "serde", "from_str", "regex"] }
miette = { version = "7.4.0", features = ["fancy", "serde", "syntect-highlighter", "derive"] }
pubchem = "0.1.1"
rust-embed = "8.5.0"
rust-embed = "8.2.0"
sea-orm = { version = "1.1.2", features = ["sqlx-sqlite", "runtime-async-std-rustls", "serde_json", "with-chrono"] }
serde = { version = "1.0.216", features = ["derive", "std", "unstable"] }
tabled = { version = "0.17.0", features = ["std", "macros", "ansi", "derive", "tabled_derive"] }
Expand All @@ -55,6 +55,10 @@ serde_json = "1.0.134"
rstest = "0.24.0"
owo-colors = "4.1.0"
chrono-humanize = "0.2.3"
ratatui = { version = "0.26.0", features = ["all-widgets", "macros", "serde"] }
crossterm = "0.27.0"
async-trait = "0.1.85"
indicatif = "0.17.9"

[expand]
color = "always"
Expand Down
4 changes: 2 additions & 2 deletions docs/DEVELOPMENT.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ https://docs.determinate.systems/getting-started
```

## Database chnges
## Database changes

- Database was initalized with `sea-orm-cli migrate init -d ./src/db/migrations`
- Database was initialized with `sea-orm-cli migrate init -d ./src/db/migrations`
Empty file modified docs/JOURNAL.md
100644 → 100755
Empty file.
Empty file modified docs/assets/log_ingestion_preview.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
519 changes: 519 additions & 0 deletions docs/substance.md

Large diffs are not rendered by default.

54 changes: 29 additions & 25 deletions src/lib/analyzer/mod.rs → src/analyzer/mod.rs
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::lib::ingestion::IngestionDate;
use crate::lib::substance::DosageClassification;
use crate::lib::substance::Dosages;
use crate::substance::Substance;
use chrono::TimeDelta;
use chrono_humanize::HumanTime;
use miette::miette;
Expand All @@ -11,8 +9,8 @@ use std::ops::Range;

/// `IngestionAnalysis` is a struct that represents the analysis of an ingestion
/// event. It contains various fields to store information about the ingestion,
/// the substance ingested, the dosage classification, the current phase of the
/// ingestion, the start and end times of the ingestion, the phases of the
/// the substance.rs ingested, the dosage classification, the current phase of
/// the ingestion, the start and end times of the ingestion, the phases of the
/// ingestion, the total duration excluding the afterglow phase, and the
/// progress of the ingestion.
#[derive(Debug, Serialize)]
Expand All @@ -22,13 +20,13 @@ pub struct IngestionAnalysis
/// This field is wrapped in an `Option` and a `Box` to allow for optional
/// ownership.
#[serde(skip_serializing)]
ingestion: Option<Box<crate::lib::ingestion::Ingestion>>,
ingestion: Option<Box<crate::ingestion::Ingestion>>,

/// The substance ingested in this event.
/// The substance.rs ingested in this event.
/// This field is wrapped in an `Option` and a `Box` to allow for optional
/// ownership.
#[serde(skip_serializing)]
substance: Option<Box<crate::lib::substance::Substance>>,
substance: Option<Box<crate::substance::Substance>>,

/// The classification of the dosage for this ingestion.
/// This field is an `Option` to allow for cases where the dosage
Expand All @@ -38,7 +36,7 @@ pub struct IngestionAnalysis
/// The current phase of the ingestion, if it can be determined.
/// This field is an `Option` to allow for cases where the current phase
/// cannot be determined.
pub(crate) current_phase: Option<crate::lib::substance::PhaseClassification>,
pub(crate) current_phase: Option<crate::substance::PhaseClassification>,

/// The start time of the ingestion event (copy of ingestion.ingestion_date)
ingestion_start: IngestionDate,
Expand All @@ -62,7 +60,7 @@ pub struct IngestionAnalysis
#[derive(Debug, Clone, Serialize)]
pub struct IngestionPhase
{
pub(crate) class: crate::lib::substance::PhaseClassification,
pub(crate) class: crate::substance::PhaseClassification,
pub(crate) duration_range: Range<IngestionDate>,
pub(crate) prev: Option<Box<IngestionPhase>>,
pub(crate) next: Option<Box<IngestionPhase>>,
Expand All @@ -80,14 +78,14 @@ pub fn progress_bar(progress: f64, width: usize) -> String
impl IngestionAnalysis
{
pub async fn analyze(
ingestion: crate::lib::ingestion::Ingestion,
substance: crate::lib::substance::Substance,
ingestion: crate::ingestion::Ingestion,
substance: crate::substance::Substance,
) -> miette::Result<Self>
{
let roa = substance
.routes_of_administration
.get(&ingestion.route)
.ok_or_else(|| miette!("Failed to find route of administration for substance"))?
.ok_or_else(|| miette!("Failed to find route of administration for substance.rs"))?
.clone();

let mut phases = Vec::new();
Expand Down Expand Up @@ -118,7 +116,7 @@ impl IngestionAnalysis
total_end_time.map_or(Some(end_time_range), |e| Some(e.max(end_time_range)));

// Update total_end_time_excl_afterglow
if phase_type != crate::lib::substance::PhaseClassification::Afterglow
if phase_type != crate::substance::PhaseClassification::Afterglow
{
total_end_time_excl_afterglow = total_end_time_excl_afterglow
.map_or(Some(end_time_range), |e| Some(e.max(end_time_range)));
Expand Down Expand Up @@ -165,7 +163,7 @@ impl IngestionAnalysis
let roa_dosages = roa.dosages;
let dosage_classification = classify_dosage(ingestion.dosage.clone(), &roa_dosages)?;

// Calculate progress

let now = chrono::Local::now();
let total_duration = total_range_excl_afterglow.end - total_range_excl_afterglow.start;
let elapsed_time = if now < total_range_excl_afterglow.start
Expand Down Expand Up @@ -194,7 +192,11 @@ impl IngestionAnalysis
}
}

use crate::lib::substance::route_of_administration::phase::PHASE_ORDER;

use crate::ingestion::IngestionDate;
use crate::substance::DosageClassification;
use crate::substance::Dosages;
use crate::substance::route_of_administration::phase::PHASE_ORDER;
use chrono::Utc;
use owo_colors::OwoColorize;

Expand Down Expand Up @@ -322,15 +324,15 @@ impl fmt::Display for IngestionAnalysis
}

pub fn classify_dosage(
dosage: crate::lib::dosage::Dosage,
dosage: crate::substance::dosage::Dosage,
roa_dosages: &Dosages,
) -> Result<DosageClassification, miette::Report>
{
for (classification, range) in roa_dosages
for (_classification, range) in roa_dosages
{
if range.contains(&dosage)
{
return Ok(*classification);
return Ok(*_classification);
}
}

Expand Down Expand Up @@ -365,13 +367,15 @@ pub fn classify_dosage(
mod tests
{
use super::*;
use crate::lib::DATABASE_CONNECTION;
use crate::lib::dosage::Dosage;
use crate::lib::migrate_database;
use crate::DATABASE_CONNECTION;
use crate::migrate_database;
use crate::substance::dosage::Dosage;

use crate::substance::DosageClassification;
use rstest::rstest;
use std::str::FromStr;


#[rstest]
#[case("10mg", DosageClassification::Threshold)]
#[case("100mg", DosageClassification::Medium)]
Expand All @@ -383,12 +387,12 @@ mod tests
{
let db = &DATABASE_CONNECTION;
migrate_database(db).await.unwrap();
let caffeine = crate::lib::substance::repository::get_substance_by_name("caffeine", db)
let caffeine = crate::substance::repository::get_substance("caffeine", db)
.await
.unwrap();
let oral_caffeine_roa = caffeine
.routes_of_administration
.get(&crate::lib::route_of_administration::RouteOfAdministrationClassification::Oral)
.unwrap().routes_of_administration
.get(&crate::substance::route_of_administration::RouteOfAdministrationClassification::Oral)
.unwrap()
.clone()
.dosages;
Expand Down
71 changes: 36 additions & 35 deletions src/cli/analyzer/analyze_ingestion.rs → src/cli/analyze.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
use crate::lib;
use crate::lib::CommandHandler;
use crate::lib::dosage::Dosage;
use crate::lib::route_of_administration::RouteOfAdministrationClassification;
use crate::ingestion::Ingestion;
use crate::substance::dosage::Dosage;
use crate::substance::route_of_administration::RouteOfAdministrationClassification;
use crate::utils::AppContext;
use crate::utils::CommandHandler;
use async_trait::async_trait;
use chrono::DateTime;
use chrono::Local;
use clap::Parser;
use miette::IntoDiagnostic;
use sea_orm::EntityTrait;
use sea_orm::prelude::async_trait::async_trait;
use std::str::FromStr;

/// Analyze a previously logged ingestion activity.
///
/// Either the `ingestion_id` **or** the combination of `substance` and
/// Either the `ingestion_id` **or** the combination of `substance.rs` and
/// `dosage` must be provided, but not both at the same time. If
/// `ingestion_id` is provided, all other arguments are ignored.
#[derive(Parser, Debug)]
Expand Down Expand Up @@ -63,7 +64,7 @@ pub struct AnalyzeIngestion
short = 't',
long = "date",
default_value = "now",
value_parser = crate::lib::parse_date_string,
value_parser = crate::utils::parse_date_string,
conflicts_with = "ingestion_id"
)]
pub date: Option<DateTime<Local>>,
Expand All @@ -82,47 +83,47 @@ pub struct AnalyzeIngestion
#[async_trait]
impl CommandHandler for AnalyzeIngestion
{
async fn handle<'a>(&self, context: lib::Context<'a>) -> miette::Result<()>
async fn handle<'a>(&self, ctx: AppContext<'a>) -> miette::Result<()>
{
let ingestion: crate::orm::ingestion::Model = match self.ingestion_id
let ingestion: Ingestion = match self.ingestion_id
{
| Some(..) => crate::orm::ingestion::Entity::find_by_id(self.ingestion_id.unwrap())
.one(context.database_connection)
.one(ctx.database_connection)
.await
.into_diagnostic()?
.unwrap_or_else(|| panic!("Ingestion not found")),
| None => crate::orm::ingestion::Model {
id: 0,
substance_name: self.substance.clone().unwrap(),
dosage: self.dosage.clone().unwrap().as_base_units() as f32,
ingested_at: self.date.unwrap().naive_local(),
updated_at: Default::default(),
route_of_administration: self.roa.unwrap().to_string(),
created_at: Default::default(),
.unwrap_or_else(|| panic!("Ingestion not found"))
.into(),
| None => Ingestion {
id: Default::default(),
dosage: self.dosage.clone().expect("Dosage not provided"),
route: self.roa.unwrap(),
substance: self.substance.clone().expect("Substance not provided"),
ingestion_date: self.date.unwrap_or_else(Local::now),
},
};

let substance = crate::lib::substance::repository::get_substance_by_name(
&ingestion.substance_name,
context.database_connection,
let substance = crate::substance::repository::get_substance(
self.substance.as_ref().unwrap(),
ctx.database_connection,
)
.await?;

let analysis = crate::lib::analyzer::IngestionAnalysis::analyze(
crate::lib::ingestion::Ingestion::from(ingestion),
substance,
)
.await?;

match context.stdout_format
if substance.is_some()
{
| crate::cli::OutputFormat::Pretty => println!("{}", analysis),
| crate::cli::OutputFormat::Json =>
let substance = substance.unwrap();
let analysis =
crate::analyzer::IngestionAnalysis::analyze(ingestion, substance).await?;

match ctx.stdout_format
{
println!(
"{}",
serde_json::to_string_pretty(&analysis).into_diagnostic()?
);
| crate::cli::OutputFormat::Pretty => println!("{}", analysis),
| crate::cli::OutputFormat::Json =>
{
println!(
"{}",
serde_json::to_string_pretty(&analysis).into_diagnostic()?
);
}
}
}

Expand Down
2 changes: 0 additions & 2 deletions src/cli/analyzer/mod.rs

This file was deleted.

Loading

0 comments on commit 2f4d1ec

Please sign in to comment.