diff --git a/.idea/runConfigurations/Install_CLI__Production_.xml b/.idea/runConfigurations/Install_CLI__Production_.xml new file mode 100644 index 00000000..c6c777c9 --- /dev/null +++ b/.idea/runConfigurations/Install_CLI__Production_.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/apps/cli/Cargo.lock b/apps/cli/Cargo.lock index 5520ba7b..bc870dff 100644 --- a/apps/cli/Cargo.lock +++ b/apps/cli/Cargo.lock @@ -681,6 +681,15 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "coolor" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e93977247fb916abeee1ff8c6594c9b421fd9c26c9b720a3944acb2a7de27b" +dependencies = [ + "crossterm", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -721,6 +730,73 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crokey" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b48209802ec5862bb034cb16719eec24d1c759e62921be7d3c899d0d85f3344b" +dependencies = [ + "crokey-proc_macros", + "crossterm", + "once_cell", + "serde", + "strict", +] + +[[package]] +name = "crokey-proc_macros" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397d3c009d8df93c4b063ddaa44a81ee7098feb056f99b00896c36e2cee9a9f7" +dependencies = [ + "crossterm", + "proc-macro2", + "quote", + "strict", + "syn 1.0.109", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -736,6 +812,31 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1447,6 +1548,29 @@ dependencies = [ "log", ] +[[package]] +name = "lazy-regex" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d12be4595afdf58bd19e4a9f4e24187da2a66700786ff660a418e9059937a4c" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bcd58e6c97a7fcbaffcdc95728b393b8d98933bfadad49ed4097845b57ef0b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.66", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1539,12 +1663,32 @@ dependencies = [ "digest", ] +[[package]] +name = "measurements" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5b734b4e8187ea5777bc29c086f0970a27d8de42061b48f5af32cafc0ca904b" +dependencies = [ + "libm", + "regex", + "serde", +] + [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "minimad" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c5d708226d186590a7b6d4a9780e2bdda5f689e0d58cd17012a298efd745d2" +dependencies = [ + "once_cell", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1560,6 +1704,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "nalgebra" version = "0.29.0" @@ -1626,6 +1782,7 @@ dependencies = [ "lazy_static", "libsqlite3-sys", "log", + "measurements", "neuronek-database-client", "rust-embed", "sea-orm", @@ -1637,6 +1794,7 @@ dependencies = [ "strsim 0.11.1", "structopt", "tabled", + "termimad", "terminal_size", "tokio", "uom", @@ -2658,6 +2816,36 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -2994,6 +3182,12 @@ dependencies = [ "thread_local", ] +[[package]] +name = "strict" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42444fea5b87a39db4218d9422087e66a85d0e7a0963a439b07bcdf91804006" + [[package]] name = "stringprep" version = "0.1.5" @@ -3148,6 +3342,22 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termimad" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab6c8572830b10362f27e242c7c5e749f062ec310b76a0d0b56670eca81f28e" +dependencies = [ + "coolor", + "crokey", + "crossbeam", + "lazy-regex", + "minimad", + "serde", + "thiserror", + "unicode-width", +] + [[package]] name = "terminal_size" version = "0.3.0" diff --git a/apps/cli/Cargo.toml b/apps/cli/Cargo.toml index 04297035..504c0f5c 100644 --- a/apps/cli/Cargo.toml +++ b/apps/cli/Cargo.toml @@ -42,6 +42,8 @@ human-panic = "2.0.0" clap_complete = "4.4.10" dateless = "0.3.1" iso8601-duration = { version = "0.2.0",features = ["serde", "chrono"] } +measurements = { version = "0.11.0",features = ["serde", "std", "from_str"] } +termimad = "0.29.2" [features] default = [] diff --git a/apps/cli/src/cli/main.rs b/apps/cli/src/cli/main.rs index a0bbec46..7ac2f20a 100644 --- a/apps/cli/src/cli/main.rs +++ b/apps/cli/src/cli/main.rs @@ -62,7 +62,7 @@ pub async fn cli() { stderrlog::new() // .module(module_path!()) .show_level(true) - .verbosity(2) + .verbosity(0) .show_module_names(true) .init() .unwrap(); diff --git a/apps/cli/src/core/route_of_administration_dosage.rs b/apps/cli/src/core/dosage.rs similarity index 53% rename from apps/cli/src/core/route_of_administration_dosage.rs rename to apps/cli/src/core/dosage.rs index d87269ba..7105f043 100644 --- a/apps/cli/src/core/route_of_administration_dosage.rs +++ b/apps/cli/src/core/dosage.rs @@ -2,8 +2,7 @@ use std::ops::{Range, RangeFrom, RangeTo}; use std::str::FromStr; use serde::{Deserialize, Serialize}; - -use crate::core::mass::Mass; +pub type Dosage = measurements::Mass; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, Copy)] #[serde(rename_all = "snake_case")] @@ -49,25 +48,56 @@ impl From for String { #[derive(Debug, PartialEq, Clone)] pub enum DosageRange { - From(RangeFrom), - To(RangeTo), - Inclusive(Range), + Heavy(RangeFrom), + Threshold(RangeTo), + Inclusive(Range), } + impl DosageRange { - pub fn contains(&self, mass: &Mass) -> bool { + pub fn contains(&self, mass: Dosage) -> bool { match self { - DosageRange::From(range) => &range.start <= mass, - DosageRange::To(range) => mass <= &range.end, - DosageRange::Inclusive(range) => &range.start <= mass && mass <= &range.end, + DosageRange::Heavy(range) => range.start <= mass, + DosageRange::Threshold(range) => mass <= range.end, + DosageRange::Inclusive(range) => range.start <= mass && mass <= range.end, } } } + +extern crate measurements; +use measurements::*; + +pub fn test_measurements() { + for power in -12..12 { + let val: f64 = 123.456 * (10f64.powf(f64::from(power))); + println!("10^{}...", power); + println!("Temp of {0:.3} outside", Temperature::from_kelvin(val)); + println!("Distance of {0:.3}", Length::from_meters(val)); + println!("Pressure of {0:.3}", Pressure::from_millibars(val)); + println!("Volume of {0:.3}", Volume::from_litres(val)); + println!("Mass of {0:.3}", Mass::from_kilograms(val)); + println!("Speed of {0:.3}", Speed::from_meters_per_second(val)); + println!( + "Acceleration of {0:.3}", + Acceleration::from_meters_per_second_per_second(val) + ); + println!("Energy of {0:.3}", Energy::from_joules(val)); + println!("Power of {0:.3}", Power::from_watts(val)); + println!("Force of {0:.3}", Force::from_newtons(val)); + println!("Force of {0:.3}", Torque::from_newton_metres(val)); + println!( + "Force of {0:.3}", + AngularVelocity::from_radians_per_second(val) + ); + println!("Data size is {0:.3}", Data::from_octets(val)); + } +} + #[derive(Debug, Clone)] pub struct RouteOfAdministrationDosage { pub id: String, pub route_of_administration_id: String, pub dosage_classification: DosageClassification, pub dosage_range: DosageRange, -} +} \ No newline at end of file diff --git a/apps/cli/src/core/mass.rs b/apps/cli/src/core/mass.rs index 35d8342b..5a9967df 100644 --- a/apps/cli/src/core/mass.rs +++ b/apps/cli/src/core/mass.rs @@ -1,18 +1,9 @@ -use uom::si::f32::Mass as MassUom; -use uom::si::mass::{gram, kilogram, milligram}; +use std::num::ParseFloatError; +use std::str::FromStr; +use crate::core::dosage::Dosage; -pub type Mass = MassUom; +pub type Mass = Dosage; -// Implement functionality to parse Mass from string in format "1.0 kg", "1.0 kg", "50mg" "50 mg" and so on... - -pub fn deserialize_mass_unit(mass_str: &str) -> Result { - let mut mass_str = mass_str.split_whitespace(); - let mass = mass_str.next().unwrap().parse::().unwrap(); - let unit = mass_str.next().unwrap(); - match unit { - "kg" => Ok(Mass::new::(mass)), - "g" => Ok(Mass::new::(mass)), - "mg" => Ok(Mass::new::(mass)), - _ => Err("Invalid unit"), - } +pub fn deserialize_dosage(mass_str: &str) -> Result { + return Dosage::from_str(mass_str); } diff --git a/apps/cli/src/core/mod.rs b/apps/cli/src/core/mod.rs index 6d9ef1c2..1b19a7ed 100644 --- a/apps/cli/src/core/mod.rs +++ b/apps/cli/src/core/mod.rs @@ -2,5 +2,5 @@ pub mod ingestion; pub mod mass; pub mod phase; pub mod route_of_administration; -pub mod route_of_administration_dosage; +pub mod dosage; pub mod substance; diff --git a/apps/cli/src/core/route_of_administration.rs b/apps/cli/src/core/route_of_administration.rs index 7293cb64..45e2bb24 100644 --- a/apps/cli/src/core/route_of_administration.rs +++ b/apps/cli/src/core/route_of_administration.rs @@ -3,12 +3,8 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; use strsim::normalized_levenshtein; - -use crate::core::mass::Mass; use crate::core::phase::{Phase, PhaseClassification}; -use crate::core::route_of_administration_dosage::{ - DosageClassification, RouteOfAdministrationDosage, -}; +use crate::core::dosage::{Dosage, DosageClassification, RouteOfAdministrationDosage}; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Eq, Hash)] #[serde(rename_all = "snake_case")] @@ -103,14 +99,14 @@ pub struct RouteOfAdministration { } pub fn get_dosage_classification_by_mass_and_route_of_administration( - mass: &Mass, + mass: &Dosage, route_of_administration: &RouteOfAdministration, ) -> Result { for (classification, dosage) in route_of_administration.dosages.iter() { - if dosage.dosage_range.contains(mass) { + let contains = dosage.dosage_range.contains(mass.clone()); + if contains { return Ok(*classification); } } - Err("No classification found for the given mass and route of administration") -} +} \ No newline at end of file diff --git a/apps/cli/src/ingestion.rs b/apps/cli/src/ingestion.rs index 4acb7e5b..41ad4545 100644 --- a/apps/cli/src/ingestion.rs +++ b/apps/cli/src/ingestion.rs @@ -5,14 +5,16 @@ use chrono_english::{parse_date_string, Dialect}; use chrono_humanize::HumanTime; use db::ingestion::ActiveModel; use db::prelude::Ingestion; +use measurements::Measurement; use sea_orm::{ActiveValue, DatabaseConnection, EntityTrait, QueryTrait}; use serde::{Deserialize, Serialize}; use serde_json::to_string; use tabled::{Table, Tabled}; use uom::num::ToPrimitive; use uom::si::mass::milligram; +use crate::core::dosage::Dosage; -use crate::core::mass::deserialize_mass_unit; +use crate::core::mass::deserialize_dosage; use crate::core::route_of_administration::RouteOfAdministrationClassification; use crate::ingestion_analyzer::analyze_future_ingestion; use crate::service::substance::search_substance; @@ -22,9 +24,7 @@ pub async fn create_ingestion(db: &DatabaseConnection, create_ingestion: CreateI let parsed_time = parse_date_string(&create_ingestion.ingested_at, Utc::now(), Dialect::Us) .unwrap_or_else(|_| Utc::now()); - let parsed_mass = deserialize_mass_unit(&create_ingestion.dosage).unwrap(); - - println!("{:?}", parsed_mass); + let parsed_mass = deserialize_dosage(&create_ingestion.dosage).unwrap(); let substance = match search_substance(db, &create_ingestion.substance_name).await { Some(substance) => substance, @@ -41,9 +41,9 @@ pub async fn create_ingestion(db: &DatabaseConnection, create_ingestion: CreateI administration_route: ActiveValue::Set(Option::from( to_string(&create_ingestion.route_of_administration).unwrap(), )), - dosage_unit: ActiveValue::Set(Option::from("mg".to_owned())), + dosage_unit: ActiveValue::Set(Option::from("kg".to_owned())), dosage_amount: ActiveValue::Set(Option::from( - parsed_mass.get::().to_f32().unwrap() as f64, + parsed_mass.as_kilograms() )), ingestion_date: ActiveValue::Set(Option::from(parsed_time.naive_local())), subject_id: ActiveValue::Set(Option::from(String::from("unknown"))), diff --git a/apps/cli/src/ingestion_analyzer.rs b/apps/cli/src/ingestion_analyzer.rs index c568ee01..08cf86f5 100644 --- a/apps/cli/src/ingestion_analyzer.rs +++ b/apps/cli/src/ingestion_analyzer.rs @@ -2,24 +2,24 @@ // and try to extract and provide as much information as it's // possible. This is a very important part of the application -use std::fmt::Debug; -use std::ops::Deref; +use std::fmt::{Debug, Display}; use std::time::Duration; -use chrono::{Local}; +use chrono::{Local, TimeZone}; use chrono_english::{Dialect, parse_date_string}; use chrono_humanize::HumanTime; use log::{debug, error}; +use measurements::Measurement; use serde::{Deserialize, Serialize}; - +use termimad::MadSkin; use crate::core::ingestion::{IngestionPhase, IngestionPhases}; -use crate::core::mass::{deserialize_mass_unit, Mass}; +use crate::core::mass::{deserialize_dosage, Mass}; use crate::core::phase::PhaseClassification; use crate::core::route_of_administration::{ get_dosage_classification_by_mass_and_route_of_administration, RouteOfAdministrationClassification, }; -use crate::core::route_of_administration_dosage::DosageClassification; +use crate::core::dosage::{Dosage, DosageClassification}; use crate::core::substance::{ get_phases_by_route_of_administration, get_route_of_administration_by_classification_and_substance, @@ -63,7 +63,7 @@ pub async fn analyze_future_ingestion( }); // Parse mass from input - let ingestion_mass = deserialize_mass_unit(&create_ingestion.dosage).unwrap_or_else(|_| { + let ingestion_mass = deserialize_dosage(&create_ingestion.dosage).unwrap_or_else(|_| { error!("Analysis failed: Invalid mass"); panic!("Analysis failed: Invalid mass unit"); }); @@ -74,7 +74,7 @@ pub async fn analyze_future_ingestion( ) .unwrap_or_else(|_| { error!("Analysis failed: Dosage classification not found"); - panic!("Analysis failed: Dosage classification not found"); + return DosageClassification::Unknown; }); // Calculate ingestion plan based on phase information @@ -82,11 +82,11 @@ pub async fn analyze_future_ingestion( let phases = get_phases_by_route_of_administration(&route_of_administration); let total_duration = phases.iter().fold(Duration::default(), |acc, phase| { - if phase.phase_classification == PhaseClassification::Afterglow { - return acc; + return if phase.phase_classification == PhaseClassification::Afterglow { + acc } else { let added = acc + phase.duration_range.end; - return added; + added } }); @@ -134,28 +134,52 @@ pub async fn analyze_future_ingestion( } pub fn pretty_print_ingestion_analysis(ingestion_analysis: &IngestionAnalysis) { - println!("{}", "-".repeat(40)); - println!("Ingestion Analysis for {:?}", ingestion_analysis.substance_name); - println!("Route of Administration: {:?}", ingestion_analysis.route_of_administration_classification); - println!("Dosage: {:?}", ingestion_analysis.dosage); - println!("Dosage Classification: {:?}", ingestion_analysis.dosage_classification); - println!("Total Duration: {:?}", HumanTime::from(chrono::Duration::from_std(ingestion_analysis.total_duration).unwrap()).to_string()); - println!("Phases:"); - // Convert HashMap to Vec + let mut markdown = String::new(); + markdown.push_str(&format!( + "{}", "-".repeat(40) + "\n" + )); + markdown.push_str(&format!( + "Ingestion Analysis for **{}**\n", + ingestion_analysis.substance_name + )); + markdown.push_str(&format!( + "Route of Administration: **{:?}**\n", + ingestion_analysis.route_of_administration_classification + )); + markdown.push_str(&format!( + "Dosage: **{0:.0}**\n", + ingestion_analysis.dosage + )); + markdown.push_str(&format!( + "Dosage Classification: **{:?}**\n", + ingestion_analysis.dosage_classification + )); + markdown.push_str(&format!( + "Total Duration: **{:?}**\n", + HumanTime::from(chrono::Duration::from_std(ingestion_analysis.total_duration).unwrap()).to_string() + )); + markdown.push_str(&format!( + "Phases:\n" + )); + let mut phases: Vec<(&PhaseClassification, &IngestionPhase)> = ingestion_analysis.phases.iter().collect(); - - // Sort Vec based on PhaseClassification phases.sort_by_key(|&(classification, _)| *classification); - - // Iterate over sorted Vec + for (phase_classification, phase) in phases { - println!("* {:?}: {:?}", phase_classification, HumanTime::from(phase.start_time).to_string()); + markdown.push_str(&format!( + " ▶ **{:?}**: {:?}\n", + phase_classification, + HumanTime::from(phase.start_time).to_string() + )); } - println!("{}", "-".repeat(40)); -} - + markdown.push_str(&format!( + "{}", "-".repeat(40) + "\n" + )); + let skin = MadSkin::default(); + println!("{}", skin.term_text(&markdown)); +} #[cfg(test)] mod tests { @@ -163,7 +187,7 @@ mod tests { use crate::core::phase::PhaseClassification; use crate::core::route_of_administration::RouteOfAdministrationClassification; - use crate::core::route_of_administration_dosage::DosageClassification; + use crate::core::dosage::DosageClassification; use crate::ingestion::CreateIngestion; use crate::ingestion_analyzer::analyze_future_ingestion; diff --git a/apps/cli/src/main.rs b/apps/cli/src/main.rs index 62e1d210..818f07af 100644 --- a/apps/cli/src/main.rs +++ b/apps/cli/src/main.rs @@ -3,6 +3,7 @@ use async_std::task; use crate::cli::main::cli; +use crate::core::dosage::test_measurements; mod cli; mod core; diff --git a/apps/cli/src/service/substance.rs b/apps/cli/src/service/substance.rs index e801b556..a1d62c09 100644 --- a/apps/cli/src/service/substance.rs +++ b/apps/cli/src/service/substance.rs @@ -15,7 +15,7 @@ use crate::core::route_of_administration::{ RouteOfAdministration, RouteOfAdministrationClassification, RouteOfAdministrationDosages, RouteOfAdministrationPhases, }; -use crate::core::route_of_administration_dosage::{ +use crate::core::dosage::{ DosageClassification, DosageRange, RouteOfAdministrationDosage, }; use crate::core::substance::{RoutesOfAdministration, Substance}; @@ -58,14 +58,14 @@ pub async fn get_substance_by_name(name: &str) -> Option { format!("{:?} {}", d.upper_bound_amount.unwrap(), mass_unit).as_str(), ) .unwrap(); - DosageRange::To(RangeTo { end: max_mass }) + DosageRange::Threshold(RangeTo { end: max_mass }) } DosageClassification::Heavy => { let min_mass = Mass::from_str( format!("{:?} {}", d.lower_bound_amount.unwrap(), mass_unit).as_str(), ) .unwrap(); - DosageRange::From(RangeFrom { start: min_mass }) + DosageRange::Heavy(RangeFrom { start: min_mass }) } _ => { let min_mass = Mass::from_str(