From 9f9fbd31fbcb8512d17611497e9503dddae6b12c Mon Sep 17 00:00:00 2001 From: piv <> Date: Tue, 21 Feb 2023 19:43:14 +1030 Subject: [PATCH 1/4] More progress on product creation, including docs --- src/create_products.rs | 122 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 15 deletions(-) diff --git a/src/create_products.rs b/src/create_products.rs index a382e20..8a361ab 100644 --- a/src/create_products.rs +++ b/src/create_products.rs @@ -9,16 +9,36 @@ use chrono::NaiveDateTime; use rayon::prelude::{IntoParallelRefIterator, ParallelBridge, ParallelIterator}; use serde::Serialize; -struct Filter {} +struct Filter { + // Equal/not equal + equal: bool, + field: String, + value: String, +} -struct Constraint {} +enum ConstraintType { + Equal, + GreaterThan, + GreaterThanOrEqualTo, + LessThan, + LessThanOrEqualTo, + NotEqualTo, +} + +struct Constraint { + source_type: String, + field: String, + constraint_type: ConstraintType, + value: String, +} enum Component { Constant(String), - Field(String), + // Even extras are allowed here, just specify the field type (encounter, service, etc) and the field name (incl Extra: or Classification: as appropriate) + Field(String, String), } -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] enum BuildFrom { Service, Transfer, @@ -27,6 +47,7 @@ enum BuildFrom { CodingDiagnosis, // TODO: This is hard/expensive, ignore for now as we don't have test data LinkedDataset, + Revenue, } impl From<&String> for BuildFrom { @@ -38,29 +59,101 @@ impl From<&String> for BuildFrom { "CD" => BuildFrom::CodingDiagnosis, "T" => BuildFrom::Transfer, "BS" => BuildFrom::LinkedDataset, + "R" => BuildFrom::Revenue, + _ => panic!(), } } } +// Frequency per type: +// Linked Dataset: One Per Area, One per Built Service code, One per source item +// Coding Diagnosis, Coding Procedure, Encounter, Revenue, Service: One per source item +// Transfer: Change in bed number, change in clinic, change in consultant, change in consultant specialty, change in consultant specialty rollup, change in unit, change in ward, daily, daily or Change in bed number, daily or change in clinic, daily or change in consultant, daily or change in consultant specialty, cdaily or hange in consultant specialty rollup, daily or change in unit, daily or change in ward, daily except on admission day, daily except on admission day (incl same day), daily except on discharge day, daily except on discharge day (incl same day), Only admission location, only discharge location, one per source item enum Frequency { - OnePerSource, - DailyOrChangeInWard, + ChangeInBedNumber, + ChangeInClinic, + ChangeInConsultant, + ChangeInConsultantSpecialty, + ChangeInConsultantSpecialtyRollup, + ChangeInUnit, + ChangeInWard, Daily, + DailyOrChangeInBedNumber, + DailyOrChangeInClinic, + DailyOrChangeInConsultant, + DailyOrChangeInConsultantSpecialty, + DailyOrChangeInConsultantSpecialtyRollup, + DailyOrChangeInUnit, + DailyOrChangeInWard, + DailyExceptOnAdmissionDay, + DailyExceptOnAdmissionDayInclSameDay, + DailyExceptOnDischargeDay, + DailyExceptOnDischargeDayInclSameDay, + OnlyAdmissionLocation, + OnlyDischargeLocation, + OnePerSource, } impl From<&String> for Frequency { fn from(frequency: &String) -> Self { match frequency.as_str() { - "S" => Frequency::OnePerSource, + "O" => Frequency::OnePerSource, "DOCW" => Frequency::DailyOrChangeInWard, "D" => Frequency::Daily, + "DOCC" => Frequency::DailyOrChangeInClinic, + "DEAD" => Frequency::DailyExceptOnAdmissionDay, + "OAL" => Frequency::OnlyAdmissionLocation, + "CIW" => Frequency::ChangeInWard, + _ => panic!(), } } } +enum RoundingMode { + ToClosestWhole, + UpToClosestWhole, + DownToClosestWhole, + None, +} + +impl From<&String> for RoundingMode { + fn from(rounding: &String) -> Self { + match rounding.as_str() { + "U" => RoundingMode::UpToClosestWhole, + "N" => RoundingMode::None, + "D" => RoundingMode::DownToClosestWhole, + "T" => RoundingMode::ToClosestWhole, + _ => panic!(), + } + } +} + +// enum ExtraValue { +// string(String), +// numeric(f64), +// datetime(NaiveDateTime), +// } + +// struct Extra { +// extraType: String, +// value: ExtraValue, +// } + +// Quantities per type: +// Built Service: Constant, SourceQuantity +// Coding Diagnosis: Costant, Extra +// Coding Procedure: Costant, Extra +// Encounter: Admission Weight, Age, Constant, Days, Expected Length of Stay, Extra, Hours, ICU Hours, Length of Stay, Mech Vent Hours, Revenue, Weighted Separation +// Revenue: Constant, Extra, SourceQuantity +// Service: Constant, Extra, SourceQuantity +// Transfer: Constant, Days, Extra, Hours enum Quantity { Constant(f64), + // Name of the extra Extra(String), + SourceQuantity, + Hours(RoundingMode), + Days(RoundingMode), } enum DurationFallback { @@ -132,11 +225,10 @@ where for record in definitions.deserialize::>() { let record = record?; // Get the type, then switch based on that, as that's how we determine whether we've got a definition/filter/component/constraint (definition should always come first) - let recordType = record.get("Type").unwrap(); - match recordType.as_str() { + let record_type = record.get("Type").unwrap(); + match record_type.as_str() { "Definition" => { - let build_quantity = - all_definitions.insert( + let build_quantity = all_definitions.insert( record.get("Name").unwrap().to_owned(), Definition { name: record.get("Name").unwrap().to_owned(), @@ -145,19 +237,19 @@ where constraints: vec![], build_from: BuildFrom::from(record.get("BuildFrom").unwrap()), frequency: Frequency::from(record.get("Frequency").unwrap()), - quantity: , - duration_fallback: (), + quantity: Quantity::Constant(1.), + duration_fallback: DurationFallback::BuiltService, }, ); } "Filter" => {} "Component" => {} "Constraint" => {} - _ => continue, + unknown => println!("Invalid type found: {}", unknown), } } - let mut mapped_definitions = all_definitions + let mut mapped_definitions: HashMap = all_definitions .into_values() .map(|value| (value.build_from, value)) .collect(); From c94c18b3e334dd3f9894345af94c3c1185da1852 Mon Sep 17 00:00:00 2001 From: Piv <18462828+Piv200@users.noreply.github.com> Date: Fri, 3 Mar 2023 20:07:22 +1030 Subject: [PATCH 2/4] More product creator implementation --- src/create_products.rs | 232 ++++++++++++++++++++++++++++++++++------- 1 file changed, 194 insertions(+), 38 deletions(-) diff --git a/src/create_products.rs b/src/create_products.rs index 8a361ab..a88493c 100644 --- a/src/create_products.rs +++ b/src/create_products.rs @@ -1,19 +1,25 @@ +use core::panic; use std::{ collections::HashMap, io::{Read, Write}, - sync::{mpsc, Arc, Mutex}, + sync::mpsc, thread, }; use chrono::NaiveDateTime; -use rayon::prelude::{IntoParallelRefIterator, ParallelBridge, ParallelIterator}; +use csv::Position; +use itertools::Itertools; use serde::Serialize; +#[derive(Hash, PartialEq, PartialOrd, Ord, Eq)] struct Filter { // Equal/not equal equal: bool, field: String, value: String, + // TODO: Probably want to enum this. Source type determines things like filtering + // on encounter/patient fields when using something like a transfer + source_type: String, } enum ConstraintType { @@ -25,6 +31,20 @@ enum ConstraintType { NotEqualTo, } +impl From<&String> for ConstraintType { + fn from(string: &String) -> Self { + match string.as_str() { + "=" => ConstraintType::Equal, + ">" => ConstraintType::GreaterThan, + ">=" => ConstraintType::GreaterThanOrEqualTo, + "<" => ConstraintType::LessThan, + "<=" => ConstraintType::LessThanOrEqualTo, + "!=" => ConstraintType::NotEqualTo, + _ => panic!(), + } + } +} + struct Constraint { source_type: String, field: String, @@ -35,6 +55,7 @@ struct Constraint { enum Component { Constant(String), // Even extras are allowed here, just specify the field type (encounter, service, etc) and the field name (incl Extra: or Classification: as appropriate) + // TODO: This first string should also be some kind of source type enum, probably shared with source types on filter/constraint Field(String, String), } @@ -123,6 +144,7 @@ impl From<&String> for RoundingMode { "N" => RoundingMode::None, "D" => RoundingMode::DownToClosestWhole, "T" => RoundingMode::ToClosestWhole, + // TODO: Just use none when unknown? _ => panic!(), } } @@ -152,8 +174,22 @@ enum Quantity { // Name of the extra Extra(String), SourceQuantity, - Hours(RoundingMode), - Days(RoundingMode), + Hours, + Days, + AdmissionWeight, + Age, + ExpectedLengthOfStay, + ICUHours, + LengthOfStay, + MechVentHours, + Revenue, + WeightedSeparation, +} + +// TODO: Pretty sure rounding mode can be used with all quantities, but check this +struct BuiltQuantity { + quantity: Quantity, + rounding_mode: RoundingMode, } enum DurationFallback { @@ -169,7 +205,7 @@ struct Definition { constraints: Vec, build_from: BuildFrom, frequency: Frequency, - quantity: Quantity, + quantity: BuiltQuantity, duration_fallback: DurationFallback, } @@ -228,31 +264,98 @@ where let record_type = record.get("Type").unwrap(); match record_type.as_str() { "Definition" => { - let build_quantity = all_definitions.insert( - record.get("Name").unwrap().to_owned(), - Definition { - name: record.get("Name").unwrap().to_owned(), - components: vec![], - filters: vec![], - constraints: vec![], - build_from: BuildFrom::from(record.get("BuildFrom").unwrap()), - frequency: Frequency::from(record.get("Frequency").unwrap()), - quantity: Quantity::Constant(1.), - duration_fallback: DurationFallback::BuiltService, - }, - ); + let quantity_type = record.get("BuiltQuantity").unwrap(); + let rounding_mode = + RoundingMode::from(record.get("BuiltQuantityRounding").unwrap()); + let quantity = match quantity_type.as_str() { + "S" => Quantity::SourceQuantity, + "C" => Quantity::Constant( + record + .get("BuiltQuantityConstant") + .unwrap() + .parse() + .unwrap(), + ), + "H" => Quantity::Hours, + // Above 3 are all that's needed for now + _ => panic![], + }; + let built_quantity = BuiltQuantity { + quantity, + rounding_mode, + }; + all_definitions + .insert( + record.get("Name").unwrap().clone(), + Definition { + name: record.get("Name").unwrap().to_owned(), + components: vec![], + filters: vec![], + constraints: vec![], + build_from: BuildFrom::from(record.get("BuildFrom").unwrap()), + frequency: Frequency::from(record.get("Frequency").unwrap()), + quantity: built_quantity, + // TODO: Figure this out + // Not even in use, can ignore, or will BuiltService always be the default? + duration_fallback: DurationFallback::BuiltService, + }, + ) + .unwrap(); + } + "Filter" => { + let new_filter = Filter { + equal: record.get("FilterNotIn").unwrap() != "", + field: record.get("FilterField").unwrap().clone(), + value: record.get("FilterValue").unwrap().clone(), + source_type: record.get("FilterSourceType").unwrap().clone(), + }; + let all_filters = &mut all_definitions + .get_mut(record.get("Name").unwrap()) + .unwrap() + .filters; + all_filters.push(new_filter); + } + "Component" => { + let component = match record.get("ComponentSource").unwrap().as_str() { + "C" => { + Component::Constant(record.get("ComponentValueOrField").unwrap().to_owned()) + } + source => Component::Field( + // TODO: Parse into source type enum + source.to_owned(), + record.get("ComponentValueOrField").unwrap().to_owned(), + ), + }; + let all_components = &mut all_definitions + .get_mut(record.get("Name").unwrap()) + .unwrap() + .components; + all_components.push(component); + } + "Constraint" => { + let constraint = Constraint { + source_type: record.get("ConstraintSourceType").unwrap().to_owned(), + field: record.get("ConstraintColumn").unwrap().to_owned(), + constraint_type: ConstraintType::from(record.get("ConstraintType").unwrap()), + value: record.get("ConstraintValue").unwrap().to_owned(), + }; + let all_constraints = &mut all_definitions + .get_mut(record.get("Name").unwrap()) + .unwrap() + .constraints; + all_constraints.push(constraint); } - "Filter" => {} - "Component" => {} - "Constraint" => {} unknown => println!("Invalid type found: {}", unknown), } } - let mut mapped_definitions: HashMap = all_definitions - .into_values() - .map(|value| (value.build_from, value)) - .collect(); + let mut mapped_definitions: HashMap> = HashMap::new(); + for (_, definition) in all_definitions { + mapped_definitions + .entry(definition.build_from) + .or_insert(vec![]) + .push(definition); + } // Then read through each file type line by line if there are definitions for that type, and process all records (read into memory the batch size) // Probably output to a separate thread (or maybe some kind of limited queue?) to write to disk. Try on same thread to start, then if it's too slow @@ -269,23 +372,76 @@ where // Now whenever we want to produce a built service, just write it to tx. + // Note that rust csv can seek to a certain position, so we can read in a batch from a reader, then + // seek to that position in the reader (or position 0) if we couldn't find a particular record. + // Alternatively, we could store an index of all records (e.g. encounter numbers) that map to their position in the reader, + // so we can quickly seek to the appropriate index and read the record. + // https://docs.rs/csv/latest/csv/struct.Reader.html#method.seek + // Store encounter positions in file, so that later when we read through transfers/whatever we can easily + // seak to the correct position quickly in case we have a cache miss + let mut encounter_positions: HashMap = HashMap::new(); + + // TODO: Alternative to storing encounter positions would be to sort portions of the file bits at a time (I think it's called a merge sort?). + // TODO: Try with and without rayon, should be able to help I think as we're going through so much data sequentially, // although we're still likely to be bottlenecked by just write-speed let mut encounters = encounters; - encounters - .deserialize::>() - .map(|encounter| encounter.unwrap()) - //TODO: Rayon can't be used with csv, consider just batching reads perhaps? - // .par_bridge() - .for_each(|encounter| { - // TODO: Calculate quantitty for this encounter - tx.send(Product::default()).unwrap(); - }); - let encounters: Vec = vec![]; - encounters.par_iter().for_each(|encounter| { - println!("{:?}", encounter); + let headers = encounters.headers()?.clone(); + + for encounter in encounters.records() { + let encounter = encounter?; + let position = encounter.position().unwrap(); + let encounter: HashMap = encounter.deserialize(Some(&headers))?; + encounter_positions.insert( + encounter.get("EncounterNumber").unwrap().to_string(), + position.clone(), + ); + // TODO: For each encounter definition, check this fits the filter criteria/constraints, + // and + let definitions = mapped_definitions.get(&BuildFrom::Encounter).unwrap(); + for definition in definitions { + let matching_filter = (definition.filters.is_empty() + || definition.filters.iter().any(|filter| { + let field = encounter.get(filter.field.as_str()); + if field.is_none() { + return false; + } + let field = field.unwrap(); + if filter.equal { + return filter.value == *field; + } else { + return filter.value != *field; + } + })) + && (definition.constraints.is_empty() + || definition.constraints.iter().any(|constraint| { + let field = encounter.get(constraint.field.as_str()); + if field.is_none() { + return false; + } + let field = field.unwrap(); + // TODO: Is this just number/datetime? Should probably be an enum? It's not, seems to be E in the test data + let field_type = &constraint.source_type; + match constraint.constraint_type { + ConstraintType::Equal => *field == constraint.value, + _ => false, + } + })); + if matching_filter { + // Generate the service code + } + } + + // TODO: Generate the built service tx.send(Product::default()).unwrap(); - }); + } + + // Now do the same with transfers, services, etc, referencing the encounter reader by using the + // indexes in encounter_positions + + // Have to drop the tx, which will cause the write thread to finish up so that it can be joined before + // the function ends + drop(tx); write_thread.join().unwrap(); Ok(()) From e68d44afd57ca10d71b60560254064959a6ab869 Mon Sep 17 00:00:00 2001 From: piv <> Date: Sat, 4 Mar 2023 09:14:06 +1030 Subject: [PATCH 3/4] Upgrade to clap v4, add numeric accounts argument to allocate overheads cli --- Cargo.lock | 181 +++++++++++++++++++++++++++++++++++++++------------- Cargo.toml | 2 +- src/main.rs | 40 +++++++----- 3 files changed, 160 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 613b5f7..134ead0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,17 +26,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -103,26 +92,24 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", - "indexmap", + "is-terminal", "once_cell", "strsim", "termcolor", - "textwrap", ] [[package]] name = "clap_derive" -version = "3.2.18" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" dependencies = [ "heck", "proc-macro-error", @@ -133,9 +120,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" dependencies = [ "os_str_bytes", ] @@ -286,10 +273,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] -name = "hashbrown" -version = "0.12.3" +name = "errno" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] [[package]] name = "heck" @@ -297,15 +299,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.2.6" @@ -315,6 +308,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -340,13 +339,25 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "1.9.2" +name = "io-lifetimes" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" dependencies = [ - "autocfg", - "hashbrown", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys", ] [[package]] @@ -381,9 +392,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "link-cplusplus" @@ -394,6 +405,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "log" version = "0.4.17" @@ -597,6 +614,20 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +[[package]] +name = "rustix" +version = "0.36.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.10" @@ -683,12 +714,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "time" version = "0.1.45" @@ -824,3 +849,69 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/Cargo.toml b/Cargo.toml index 5904b58..ed955bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ csv = "1.1" serde = { version = "1", features = ["derive"] } # num = "0.4" -clap = { version = "3.1.18", features = ["derive"] } +clap = { version = "4.1.8", features = ["derive"] } anyhow = "1.0" itertools = "0.10.3" diff --git a/src/main.rs b/src/main.rs index 1f41afb..5bdcd7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,10 +5,10 @@ use coster_rs::CsvCost; use serde::Deserialize; #[derive(Parser)] -#[clap(name = "coster-rs")] -#[clap(author = "Pivato M. ")] -#[clap(version = "0.0.1")] -#[clap(about = "Simple, fast, efficient costing tool", long_about = None)] +#[command(name = "coster-rs")] +#[command(author = "Pivato M. ")] +#[command(version = "0.0.1")] +#[command(about = "Simple, fast, efficient costing tool", long_about = None)] struct Cli { #[clap(subcommand)] command: Commands, @@ -18,44 +18,47 @@ struct Cli { enum Commands { /// Moves money between accounts and departments, using the given rules and lines move_money { - #[clap(short = 'r', long, parse(from_os_str), value_name = "FILE")] + #[arg(short = 'r', long, value_name = "FILE")] rules: PathBuf, - #[clap(short = 'l', long, parse(from_os_str), value_name = "FILE")] + #[arg(short = 'l', long, value_name = "FILE")] lines: PathBuf, - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[arg(short, long, value_name = "FILE")] output: Option, - #[clap(short, long, default_value_t = false)] + #[arg(short, long)] use_numeric_accounts: bool, }, /// Combines rules to the minimum set required smush_rules { - #[clap(short = 'r', long, parse(from_os_str), value_name = "FILE")] + #[arg(short = 'r', long, value_name = "FILE")] rules: PathBuf, - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[arg(short, long, value_name = "FILE")] output: Option, }, /// Allocates servicing department amounts to operating departments allocate_overheads { - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[arg(short, long, value_name = "FILE")] lines: PathBuf, - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[arg(short, long, value_name = "FILE")] accounts: PathBuf, - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[arg(short = 's', long, value_name = "FILE")] allocation_statistics: PathBuf, - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[arg(short, long, value_name = "FILE")] areas: PathBuf, - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[arg(short, long, value_name = "FILE")] cost_centres: PathBuf, - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[arg(short, long)] + use_numeric_accounts: bool, + + #[arg(short, long, value_name = "FILE")] output: Option, }, } @@ -77,6 +80,7 @@ fn main() -> anyhow::Result<()> { allocation_statistics, areas, cost_centres, + use_numeric_accounts, output, } => allocate_overheads( lines, @@ -84,6 +88,7 @@ fn main() -> anyhow::Result<()> { allocation_statistics, areas, cost_centres, + use_numeric_accounts, output, ), } @@ -113,6 +118,7 @@ fn allocate_overheads( allocation_statistics: PathBuf, areas: PathBuf, cost_centres: PathBuf, + use_numeric_accounts: bool, output: Option, ) -> anyhow::Result<()> { coster_rs::reciprocal_allocation( @@ -122,7 +128,7 @@ fn allocate_overheads( csv::Reader::from_path(areas)?, csv::Reader::from_path(cost_centres)?, &mut csv::Writer::from_path(output.unwrap_or(PathBuf::from("alloc_output.csv")))?, - true, + use_numeric_accounts, false, true, "E".to_owned(), From 4c2b9f6915c573d050362c89103affb72bc66164 Mon Sep 17 00:00:00 2001 From: piv <> Date: Sat, 4 Mar 2023 09:32:26 +1030 Subject: [PATCH 4/4] Add account type option to allocat overheads --- src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 5bdcd7d..53fc819 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,6 +58,9 @@ enum Commands { #[arg(short, long)] use_numeric_accounts: bool, + #[arg(long, default_value = "E")] + account_type: String, + #[arg(short, long, value_name = "FILE")] output: Option, }, @@ -81,6 +84,7 @@ fn main() -> anyhow::Result<()> { areas, cost_centres, use_numeric_accounts, + account_type, output, } => allocate_overheads( lines, @@ -89,6 +93,7 @@ fn main() -> anyhow::Result<()> { areas, cost_centres, use_numeric_accounts, + account_type, output, ), } @@ -119,6 +124,7 @@ fn allocate_overheads( areas: PathBuf, cost_centres: PathBuf, use_numeric_accounts: bool, + account_type: String, output: Option, ) -> anyhow::Result<()> { coster_rs::reciprocal_allocation( @@ -131,7 +137,7 @@ fn allocate_overheads( use_numeric_accounts, false, true, - "E".to_owned(), + account_type, ) }