diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
new file mode 100644
index 00000000..02b915b8
--- /dev/null
+++ b/.idea/git_toolbox_prj.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/cli/src/cli/dashboard/mod.rs b/apps/cli/src/cli/dashboard/mod.rs
index ec7a48fc..9d025c1b 100644
--- a/apps/cli/src/cli/dashboard/mod.rs
+++ b/apps/cli/src/cli/dashboard/mod.rs
@@ -10,8 +10,8 @@ use futures::stream::StreamExt;
use humantime::format_duration;
use tabled::Table;
-use db::sea_orm::*;
use db::sea_orm::DatabaseConnection;
+use db::sea_orm::*;
use crate::core::dosage::Dosage;
use crate::core::ingestion::Ingestion;
@@ -20,28 +20,36 @@ use crate::service::ingestion::get_ingestion_by_id;
pub async fn handle_show_dashboard(database_connection: &DatabaseConnection) {
// Fetch and map all ingestions from database
- let ingestion = db::ingestion::Entity::find().all(database_connection).await.unwrap();
+ let ingestion = db::ingestion::Entity::find()
+ .all(database_connection)
+ .await
+ .unwrap();
let ingestions_stream = stream::iter(ingestion.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::>().await).await;
+ 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::>()
+ .await,
+ )
+ .await;
// Group ingestion by substance name
- let ingestion_by_substance = ingestions.clone()
- .into_iter()
- .fold(HashMap::>::new(), |mut acc, ingestion| {
+ let ingestion_by_substance = ingestions.clone().into_iter().fold(
+ HashMap::>::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 total dosage per substance for last 7 days
- let total_dosage_per_substance_for_last_7_days = ingestion_by_substance.clone()
+ let total_dosage_per_substance_for_last_7_days = ingestion_by_substance
+ .clone()
.into_iter()
.map(|(substance_name, ingestions)| {
let total_dosage: Dosage = ingestions
@@ -51,15 +59,16 @@ pub async fn handle_show_dashboard(database_connection: &DatabaseConnection) {
.collect::>()
.into_iter()
// Use custom summing implementation
- .fold(Dosage::from_str(
- "0.0 mg"
- ).unwrap(), |acc, dosage| acc + dosage);
+ .fold(Dosage::from_str("0.0 mg").unwrap(), |acc, dosage| {
+ acc + dosage
+ });
(substance_name, total_dosage)
})
.collect::>();
-
- let average_dosage_per_day_per_substance_for_last_7_days = ingestion_by_substance.clone()
+
+ let average_dosage_per_day_per_substance_for_last_7_days = ingestion_by_substance
+ .clone()
.into_iter()
.map(|(substance_name, ingestions)| {
let total_dosage: Dosage = ingestions
@@ -69,9 +78,9 @@ pub async fn handle_show_dashboard(database_connection: &DatabaseConnection) {
.collect::>()
.into_iter()
// Use custom summing implementation
- .fold(Dosage::from_str(
- "0.0 mg"
- ).unwrap(), |acc, dosage| acc + dosage);
+ .fold(Dosage::from_str("0.0 mg").unwrap(), |acc, dosage| {
+ acc + dosage
+ });
let average_dosage = total_dosage / 7.0;
(substance_name, average_dosage)
@@ -81,27 +90,46 @@ pub async fn handle_show_dashboard(database_connection: &DatabaseConnection) {
println!("\n");
println!("Substance Name, Total Dosage, Average Dosage per Day");
- let table = Table::from_iter(total_dosage_per_substance_for_last_7_days.into_iter()
- .map(|(substance_name, total_dosage)| {
- vec![
- substance_name.clone(),
- format!("{:.0}", total_dosage).to_string(),
- format!("{:.0}", average_dosage_per_day_per_substance_for_last_7_days.get(&substance_name).unwrap()).to_string(),
- ]
- })
- .collect::>().iter().cloned());
-
+ let table = Table::from_iter(
+ total_dosage_per_substance_for_last_7_days
+ .into_iter()
+ .map(|(substance_name, total_dosage)| {
+ vec![
+ substance_name.clone(),
+ format!("{:.0}", total_dosage).to_string(),
+ format!(
+ "{:.0}",
+ average_dosage_per_day_per_substance_for_last_7_days
+ .get(&substance_name)
+ .unwrap()
+ )
+ .to_string(),
+ ]
+ })
+ .collect::>()
+ .iter()
+ .cloned(),
+ );
+
println!("{}", table.to_string());
println!("\n");
-
+
// Filter all ingestions to find those which are active (onset, comeup, peak, offset)
let active_ingestions = ingestions
.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 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() )
+ daterange.contains(&Local::now())
})
.collect::>();
@@ -111,32 +139,27 @@ pub async fn handle_show_dashboard(database_connection: &DatabaseConnection) {
// 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 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()));
+ 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.
-
- // 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();
- // });
-}
\ No newline at end of file
+}
diff --git a/apps/cli/src/core/ingestion.rs b/apps/cli/src/core/ingestion.rs
index 2a99b116..ae5cb9d2 100644
--- a/apps/cli/src/core/ingestion.rs
+++ b/apps/cli/src/core/ingestion.rs
@@ -1,4 +1,5 @@
use std::collections::HashMap;
+use std::str::FromStr;
use chrono::{DateTime, Local, Utc};
use serde::{Deserialize, Serialize};
@@ -17,7 +18,7 @@ pub struct IngestionPhase {
pub type IngestionPhases = HashMap;
-#[derive( Serialize, Debug, Deserialize, Clone)]
+#[derive(Serialize, Debug, Deserialize, Clone)]
pub struct Ingestion {
pub(crate) id: i32,
pub(crate) substance_name: String,
@@ -45,4 +46,25 @@ impl Ingestion {
phases,
}
}
-}
\ No newline at end of file
+}
+
+impl From for Ingestion {
+ fn from(ingestion: db::ingestion::Model) -> Self {
+ Ingestion {
+ id: ingestion.id,
+ substance_name: ingestion.substance_name.unwrap(),
+ administration_route: RouteOfAdministrationClassification::from_str(
+ ingestion.administration_route.unwrap().as_str(),
+ )
+ .unwrap(),
+ ingested_at: ingestion.ingestion_date.unwrap().and_utc(),
+ dosage: Dosage::from_str(&format!(
+ "{} {}",
+ ingestion.dosage_amount.unwrap(),
+ ingestion.dosage_unit.unwrap()
+ ))
+ .unwrap(),
+ phases: HashMap::new(),
+ }
+ }
+}
diff --git a/apps/cli/src/ingestion_intelligence.rs b/apps/cli/src/ingestion_intelligence.rs
new file mode 100644
index 00000000..92501de1
--- /dev/null
+++ b/apps/cli/src/ingestion_intelligence.rs
@@ -0,0 +1,156 @@
+// Ingestion Intelligence is a tool that helps to calculate all of the metrics
+// related to an ingestions in a cache-able format to be later used for ingestion
+// analysis. This is a ciritial tool for Neuronek project.
+
+use crate::core::dosage::Dosage;
+use crate::orm::DB_CONNECTION;
+use chrono::Utc;
+use db::*;
+use std::str::FromStr;
+
+pub(super) async fn get_total_dosage_of_substance(
+ substance_name: String,
+ date_range: Option<(chrono::DateTime, chrono::DateTime)>,
+) -> Result> {
+ // Fetch all ingestions of substance
+ let mut database_ingestions = db::ingestion::Entity::find()
+ .filter(db::ingestion::Column::SubstanceName.contains(substance_name))
+ .all(&DB_CONNECTION as &DatabaseConnection)
+ .await?;
+
+ // If a date range is provided, filter the ingestions to include only those that fall within the date range.
+ if let Some((start_date, end_date)) = date_range {
+ database_ingestions = database_ingestions
+ .into_iter()
+ .filter(|ingestion| {
+ let ingestion_date: chrono::DateTime =
+ ingestion.ingestion_date.unwrap().and_utc();
+ let dr: (chrono::DateTime, chrono::DateTime) = (start_date, end_date);
+
+ // If the ingestion date is within the date range, include it in the filtered list.
+ let is_within_date_range = dr.0.le(&ingestion_date) && dr.1.ge(&ingestion_date);
+ is_within_date_range
+ })
+ .collect();
+ }
+
+ // Calculate the total dosage by summing up the dosage of each ingestion.
+ let total_dosage = database_ingestions
+ .iter()
+ .map(|ingestion| {
+ let cloned_ingestion = ingestion.clone();
+ return Dosage::from_str(
+ format!(
+ "{} {}",
+ cloned_ingestion.dosage_amount.unwrap(),
+ cloned_ingestion.dosage_unit.unwrap(),
+ )
+ .as_str(),
+ )
+ .unwrap()
+ .to_owned();
+ })
+ .fold(Dosage::from_str("0.0 mg").unwrap(), |acc, dosage| {
+ acc + dosage
+ });
+
+ Ok(total_dosage)
+}
+
+pub(crate) async fn get_average_dosage_of_substance(
+ substance_name: String,
+ date_range: Option<(chrono::DateTime, chrono::DateTime)>,
+) -> Result> {
+ // Fetch all ingestions of substance
+ let mut database_ingestions = db::ingestion::Entity::find()
+ .filter(db::ingestion::Column::SubstanceName.contains(substance_name))
+ .all(&DB_CONNECTION as &DatabaseConnection)
+ .await?;
+
+ // If a date range is provided, filter the ingestions to include only those that fall within the date range.
+ if let Some((start_date, end_date)) = date_range {
+ database_ingestions = database_ingestions
+ .into_iter()
+ .filter(|ingestion| {
+ let ingestion_date: chrono::DateTime =
+ ingestion.ingestion_date.unwrap().and_utc();
+ let dr: (chrono::DateTime, chrono::DateTime) = (start_date, end_date);
+
+ // If the ingestion date is within the date range, include it in the filtered list.
+ let is_within_date_range = dr.0.le(&ingestion_date) && dr.1.ge(&ingestion_date);
+ is_within_date_range
+ })
+ .collect();
+ }
+
+ // Calculate the average dosage by summing up the dosage of each ingestion.
+ let average_dosage = database_ingestions
+ .iter()
+ .map(|ingestion| {
+ let cloned_ingestion = ingestion.clone();
+ return Dosage::from_str(
+ format!(
+ "{} {}",
+ cloned_ingestion.dosage_amount.unwrap(),
+ cloned_ingestion.dosage_unit.unwrap(),
+ )
+ .as_str(),
+ )
+ .unwrap()
+ .to_owned();
+ })
+ .fold(Dosage::from_str("0.0 mg").unwrap(), |acc, dosage| {
+ acc + dosage
+ });
+
+ Ok(average_dosage)
+}
+
+pub(crate) async fn get_average_daily_dosage_of_substance(
+ substance_name: String,
+ date_range: Option<(chrono::DateTime, chrono::DateTime)>,
+) -> Result> {
+ // Fetch all ingestions of substance
+ let mut database_ingestions = db::ingestion::Entity::find()
+ .filter(db::ingestion::Column::SubstanceName.contains(substance_name))
+ .all(&DB_CONNECTION as &DatabaseConnection)
+ .await?;
+
+ // If a date range is provided, filter the ingestions to include only those that fall within the date range.
+ if let Some((start_date, end_date)) = date_range {
+ database_ingestions = database_ingestions
+ .into_iter()
+ .filter(|ingestion| {
+ let ingestion_date: chrono::DateTime =
+ ingestion.ingestion_date.unwrap().and_utc();
+ let dr: (chrono::DateTime, chrono::DateTime) = (start_date, end_date);
+
+ // If the ingestion date is within the date range, include it in the filtered list.
+ let is_within_date_range = dr.0.le(&ingestion_date) && dr.1.ge(&ingestion_date);
+ is_within_date_range
+ })
+ .collect();
+ }
+
+ // Calculate the average dosage by summing up the dosage of each ingestion.
+ let average_daily_dosage = database_ingestions
+ .iter()
+ .map(|ingestion| {
+ let cloned_ingestion = ingestion.clone();
+ return Dosage::from_str(
+ format!(
+ "{} {}",
+ cloned_ingestion.dosage_amount.unwrap(),
+ cloned_ingestion.dosage_unit.unwrap(),
+ )
+ .as_str(),
+ )
+ .unwrap()
+ .to_owned();
+ })
+ .fold(Dosage::from_str("0.0 mg").unwrap(), |acc, dosage| {
+ acc + dosage
+ });
+
+ Ok(average_daily_dosage)
+}
diff --git a/apps/cli/src/main.rs b/apps/cli/src/main.rs
index 1f844e7f..c64c7621 100644
--- a/apps/cli/src/main.rs
+++ b/apps/cli/src/main.rs
@@ -1,15 +1,11 @@
#![feature(duration_constructors)]
-use async_std::task;
-
-use crate::cli::main::cli;
-use crate::core::dosage::test_measurements;
-
mod cli;
mod core;
mod ingestion_analyzer;
mod orm;
mod service;
+mod ingestion_intelligence;
fn main() {
cli::bin::main()
diff --git a/docs/Architecture/storming.d2 b/docs/Architecture/storming.d2
index 313db401..dae91f53 100644
--- a/docs/Architecture/storming.d2
+++ b/docs/Architecture/storming.d2
@@ -37,21 +37,21 @@ classes: {
style.stroke: ${colors.command}
}
actor: {
- shape: person
- style.stroke-dash: 3
+ shape: person
+ style.stroke-dash: 3
+ }
+ event: {
+ style.stroke: ${colors.event}
+ }
+ aggregate: {
+ style.stroke: ${colors.aggregate}
+ }
+ bounded_context: {
+ style.stroke: ${colors.bounded_context}
+ }
+ timeline: {
+ style.stroke: ${colors.timeline}
}
- event: {
- style.stroke: ${colors.event}
- }
- aggregate: {
- style.stroke: ${colors.aggregate}
- }
- bounded_context: {
- style.stroke: ${colors.bounded_context}
- }
- timeline: {
- style.stroke: ${colors.timeline}
- }
med: {
width: 200
height: 200
@@ -73,130 +73,152 @@ classes: {
}
}
-identity_and_access_management {
- label: "Identity and Access Management"
- style.stroke: "#E91E63"
+identity_and_access_management: {
+ label: "Identity and Access Management"
+ style.stroke: "#E91E63"
- # Commands
- create_account {
- label: "Create Account"
- class: command
- }
-
- # Events
- account_created {
- label: "Account Created"
- style.stroke: "#f4a261"
- }
-
- account_created
- account_created.style.stroke: "#f4a261"
-
- create_account
- create_account.style.stroke: "#1976D2"
+ # Commands
+ create_account: {
+ label: "Create Account"
+ class: command
+ }
- account
- account.style.stroke: "#FFB703"
+ # Events
+ account_created: {
+ label: "Account Created"
+ style.stroke: "#f4a261"
+ }
- account_vm
- account_vm.style.stroke: "#4CAF50"
+ account_created
+ account_created.style.stroke: "#f4a261"
- account_registration {
- label: "Account Registration"
- style.stroke: "#E91E63"
+ create_account
+ create_account.style.stroke: "#1976D2"
- register_account {
- label: "Register Account"
- class: command
- }
+ account
+ account.style.stroke: "#FFB703"
- account_registered {
- label: "Account Registered"
- style.stroke: "#f4a261"
- }
+ account_vm
+ account_vm.style.stroke: "#4CAF50"
- account {
- label: "Account"
- class: aggregate
- }
+ account_registration: {
+ label: "Account Registration"
+ style.stroke: "#E91E63"
- unknown_user: {
- label: "Unknown User"
- class: actor
- }
+ register_account: {
+ label: "Register Account"
+ class: command
+ }
- unknown_user -> register_account -> account -> account_registered -> account_vm
+ account_registered: {
+ label: "Account Registered"
+ style.stroke: "#f4a261"
}
- account_confirmation {
- label: "Account Confirmation"
- style.stroke: "#E91E63"
+ account: {
+ label: "Account"
+ class: aggregate
+ }
- confirm_account {
- label: "Confirm Account"
- class: command
- }
+ unknown_user: {
+ label: "Unknown User"
+ class: actor
+ }
- account_confirmed {
- label: "Account Confirmed"
- style.stroke: "#f4a261"
- }
+ unknown_user -> register_account -> account -> account_registered -> account_vm
+ }
- account {
- label: "Account"
- class: aggregate
- }
+ account_confirmation: {
+ label: "Account Confirmation"
+ style.stroke: "#E91E63"
- account_view_model {
- label: "Account View Model"
- class: read_model
- }
+ confirm_account: {
+ label: "Confirm Account"
+ class: command
+ }
- account_registration -> confirm_account -> account -> account_confirmed -> account_view_model
+ account_confirmed: {
+ label: "Account Confirmed"
+ style.stroke: "#f4a261"
}
- account_deletion {
- label: "Account Deletion"
- style.stroke: "#E91E63"
+ account: {
+ label: "Account"
+ class: aggregate
}
- password_reset {
- label: "Password Reset"
- style.stroke: "#E91E63"
+ account_view_model: {
+ label: "Account View Model"
+ class: read_model
}
- account_update {
- label: "Account Update"
- style.stroke: "#E91E63"
+ account_registration -> confirm_account -> account -> account_confirmed -> account_view_model
+ }
+
+ account_deletion: {
+ label: "Account Deletion"
+ style.stroke: "#E91E63"
+ }
+
+ password_reset: {
+ label: "Password Reset"
+ style.stroke: "#E91E63"
+ }
- update_account {
- label: "Update Account"
- class: command
- }
+ account_update: {
+ label: "Account Update"
+ style.stroke: "#E91E63"
- account_updated {
- label: "Account Updated"
- style.stroke: "#f4a261"
- }
+ update_account: {
+ label: "Update Account"
+ class: command
+ }
- account {
- label: "Account"
- class: aggregate
- }
+ account_updated: {
+ label: "Account Updated"
+ style.stroke: "#f4a261"
+ }
- account_view_model {
- label: "Account View Model"
- class: read_model
- }
+ account: {
+ label: "Account"
+ class: aggregate
+ }
- account_registration -> update_account -> account -> account_updated -> account_view_model
+ account_view_model: {
+ label: "Account View Model"
+ class: read_model
}
+ account_registration -> update_account -> account -> account_updated -> account_view_model
+ }
+
+ account_registration -> account_confirmation
+ account_registration -> account_deletion
+ account_confirmation -> password_reset
+ account_registration -> account_update
+}
+ingestion_journal: {
+ label: "Ingestion Journal"
+ style.stroke: "#E91E63"
- account_registration -> account_confirmation
- account_registration -> account_deletion
- account_confirmation -> password_reset
- account_registration -> account_update
+ create_ingestion
+ delete_ingestion
+ update_ingestion
+ plan_ingestion
+
+ plan_ingestion -> ingestion -> ingestions_fetched -> statistics_calculated -> interactions_checked -> duration_calculated -> rules_checked -> ingestion_plan_view_model
}
+# Ingestion Intelligence is a component dedicated to the analysis of the data
+# contained in the ingestions journals to predict and avoid harm potential.
+# This is also intented to calculate and visualize statistics about the
+# interactions between the users and the system.
+ingestion_intelligence: {
+ label: "Ingestion Intelligence"
+
+ # TODO: Average ingestions
+ # TODO: Average dosages
+ # TODO: Most common effects
+ # TODO: Most common side-effects
+}