Skip to content

Commit

Permalink
refactor(cli): ♻️ add dashboard command
Browse files Browse the repository at this point in the history
  • Loading branch information
keinsell committed Jun 15, 2024
1 parent 2b499e2 commit 610ce4b
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 35 deletions.
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions apps/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ iso8601-duration = { version = "0.2.0",features = ["serde", "chrono"] }
measurements = { version = "0.11.0",features = ["serde", "std", "from_str"] }
termimad = "0.29.2"
itertools = "0.13.0"
indicatif = "0.17.8"
humantime = "2.1.0"

[features]
default = []
Expand Down
138 changes: 132 additions & 6 deletions apps/cli/src/cli/dashboard/mod.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,147 @@
// TODO: Get active ingestions along their progress
// TODO: Get analisis of side-effects and when they will likely occur

use itertools::Itertools;
use std::collections::HashMap;
use std::str::FromStr;

use chrono::{Duration, Local, Utc};
use futures::stream;
use futures::stream::StreamExt;
use humantime::format_duration;
use tabled::Table;

use db::sea_orm::*;
use db::sea_orm::DatabaseConnection;

use crate::core::dosage::Dosage;
use crate::core::ingestion::Ingestion;
use crate::core::phase::PhaseClassification;
use crate::service::ingestion::get_ingestion_by_id;

pub async fn handle_show_dashboard(database_connection: &DatabaseConnection) {
println!("Dashboard is not implemented yet.");

// Show average dosage per day of each substance that was ingested
let ingestions = db::ingestion::Entity::find().all(database_connection).await.unwrap();
// Fetch and map all ingestions from database
let ingestions = db::ingestion::Entity::find().all(database_connection).await.unwrap();
let ingestions_stream = stream::iter(ingestions.into_iter());

let ingestions = futures::future::join_all(ingestions_stream
.map(|ingestion| {
let ingestion_id = ingestion.id;
async move {
get_ingestion_by_id(ingestion_id).await.unwrap()
}
}).collect::<Vec<_>>().await).await;

// Group ingestions by substance name
let ingestions_by_substance = ingestions
let ingestions_by_substance = ingestions.clone()
.into_iter()
.fold(HashMap::<String, Vec<Ingestion>>::new(), |mut acc, ingestion| {
let substance_name = ingestion.substance_name.clone();
let ingestion_entry = acc.entry(substance_name).or_insert(vec![]);
ingestion_entry.push(ingestion);
acc
});

// Calculate average dosage per day per substance
let average_dosage_per_substance_for_last_7_days = ingestions_by_substance.clone()
.into_iter()
.map(|(substance_name, ingestions)| {
let total_dosage: Dosage = ingestions
.iter()
.map(|ingestion| ingestion.dosage)
.collect::<Vec<Dosage>>()
.into_iter()
// Use custom summing implementation
.fold(Dosage::from_str(
"0.0 mg"
).unwrap(), |acc, dosage| acc + dosage);

let average_dosage = total_dosage / ingestions.len() as f64;
(substance_name, average_dosage)
})
.collect::<HashMap<String, Dosage>>();

println!("Average dosage per substance for last 7 days");
for (substance_name, average_dosage) in average_dosage_per_substance_for_last_7_days {
println!("{}: {1:.0}", substance_name, average_dosage);
}

// Calculate total dosage per substance for last 7 days
let total_dosage_per_substance_for_last_7_days = ingestions_by_substance
.into_iter()
.map(|(substance_name, ingestions)| {
let total_dosage: Dosage = ingestions
.iter()
.filter(|ingestion| ingestion.ingested_at >= Utc::now() - Duration::days(7))
.map(|ingestion| ingestion.dosage)
.collect::<Vec<Dosage>>()
.into_iter()
// Use custom summing implementation
.fold(Dosage::from_str(
"0.0 mg"
).unwrap(), |acc, dosage| acc + dosage);

(substance_name, total_dosage)
})
.collect::<HashMap<String, Dosage>>();

println!("Total dosage per substance for last 7 days");

let table = Table::from_iter(total_dosage_per_substance_for_last_7_days.into_iter()
.map(|(substance_name, total_dosage)| {
vec![
substance_name,
format!("{:.0}", total_dosage).to_string()
]
})
.collect::<Vec<_>>().iter().cloned());

println!("{}", table.to_string());

// Filter all ingestions to find those which are active (onset, comeup, peak, offset)
let active_ingestions = ingestions
.into_iter()
.chunk_by(|ingestion| ingestion.substance_name.as_ref().unwrap().clone()).into_iter();
.filter(|ingestion| {
let ingestion_start = ingestion.phases.get(&PhaseClassification::Onset).unwrap().start_time;
let ingestion_end = ingestion.phases.get(&PhaseClassification::Offset).unwrap().end_time;
let daterange = ingestion_start..ingestion_end;
daterange.contains( &Local::now() )
})
.collect::<Vec<Ingestion>>();

println!("Active ingestions: {:#?}", active_ingestions.len());

// Iterate through active ingestions to print procentage of completion,
// time left and other useful information such as ingestion id and substance name.

active_ingestions.into_iter().for_each(|ingestion| {
let ingestion_start = ingestion.phases.get(&PhaseClassification::Onset).unwrap().start_time;
let ingestion_end = ingestion.phases.get(&PhaseClassification::Offset).unwrap().end_time;
let now = Local::now();
let total_duration = ingestion_end - ingestion_start;
let elapsed_duration = now - ingestion_start;
let remaining_duration = total_duration - elapsed_duration;

println!("#{} {} ({:.0}) | {}", ingestion.id, ingestion.substance_name, ingestion.dosage, format_duration(remaining_duration.to_std().unwrap()));
});

// We need to create progress bar for each active ingestion and
// put them all into easy-readable table allowing end-user to
// quickly see how long each ingestion will last.

println!("Ingestions by substance:");
// active_ingestions.into_iter().for_each(|ingestion| {
// let ingestion_start = ingestion.phases.get(&PhaseClassification::Onset).unwrap().start_time;
// let ingestion_end = ingestion.phases.get(&PhaseClassification::Offset).unwrap().end_time;
// let now = Local::now();
// let total_duration = (ingestion_end - ingestion_start).num_seconds() as u64;
// let elapsed_duration = (now - ingestion_start).num_seconds() as u64;
//
// println!("{}:", ingestion.substance_name);
// let pb = ProgressBar::new(total_duration).with_prefix(Cow::from(format!("{}:", ingestion.substance_name)));
// pb.set_style(ProgressStyle::default_bar());
// pb.set_position(elapsed_duration);
//
// pb.finish_and_clear();
// });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn import_from_psychonautwiki_journal() {
todo!()
}
3 changes: 2 additions & 1 deletion apps/cli/src/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ enum DataManagementCommand {
}

#[derive(StructOpt, Debug)]
enum DashboardCommand {
#[structopt(name = "dashboard")]
struct DashboardCommand {
}

pub async fn cli() {
Expand Down
7 changes: 4 additions & 3 deletions apps/cli/src/core/dosage.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
extern crate measurements;

use std::ops::{Range, RangeFrom, RangeTo};
use std::str::FromStr;

use measurements::*;
use serde::{Deserialize, Serialize};

pub type Dosage = measurements::Mass;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, Copy)]
Expand Down Expand Up @@ -65,9 +69,6 @@ impl DosageRange {
}


extern crate measurements;
use measurements::*;

pub fn test_measurements() {
for power in -12..12 {
let val: f64 = 123.456 * (10f64.powf(f64::from(power)));
Expand Down
7 changes: 3 additions & 4 deletions apps/cli/src/core/ingestion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ use std::collections::HashMap;

use chrono::{DateTime, Local, Utc};
use serde::{Deserialize, Serialize};
use tabled::Tabled;

use crate::core::dosage::Dosage;
use crate::core::phase::{DurationRange, PhaseClassification};
use crate::core::route_of_administration::RouteOfAdministrationClassification;
use crate::ingestion_analyzer::IngestionAnalysis;

#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct IngestionPhase {
pub(crate) phase_classification: PhaseClassification,
pub(crate) duration: DurationRange,
Expand All @@ -18,7 +17,7 @@ pub struct IngestionPhase {

pub type IngestionPhases = HashMap<PhaseClassification, IngestionPhase>;

#[derive( Serialize, Debug, Deserialize)]
#[derive( Serialize, Debug, Deserialize, Clone)]
pub struct Ingestion {
pub(crate) id: i32,
pub(crate) substance_name: String,
Expand Down
54 changes: 33 additions & 21 deletions docs/Architecture/context-diagram.d2
Original file line number Diff line number Diff line change
@@ -1,42 +1,54 @@
direction: left

iam: {
label: "Identity and Access Management (IAM)"

experience: "Experience"
ingestion: "Ingestion"

dosage: "Dosage"
phase: "Phase"

account: "Account"
}

# Account
account: "Account"
subject_database: {
label: "Subject"

# Subject
subject: "Subject"
subject: "Subject"

subject -> account
subject -> _.iam.account: (optionally)
}

substance_database: {
label: "Substance Database"
label: "Public Substance Information"

# Substance
substance: "Substance"
route_of_administration: "Route of Administration"
route_of_administration_dosage: "Route of Administration Dosage"
route_of_administration_phase: "Route of Administration Phase"
dosage: "Dosage"
phase: "Phase"

route_of_administration -> substance
route_of_administration_dosage -> route_of_administration
route_of_administration_phase -> route_of_administration
dosage -> route_of_administration
phase -> route_of_administration
}

journal: {
label: "Journal"

ingestion: "Ingestion"
ingestion_phase: "Ingestion Phase"

# Route of Administration
ingestion -> _.substance_database.substance
ingestion -> _.subject_database.subject

ingestion_phase -> ingestion
}

route_of_administration -> substance
experience_db: {
label: "Experience Dataset"

experience: "Expereince"

# Ingestion
ingestion -> subject
ingestion -> substance
experience -> _.subject_database.subject: {
label: "may contain"
}
experience -> _.journal.ingestion: {
label: "may contain"
}
}

0 comments on commit 610ce4b

Please sign in to comment.