Merge branch 'product_creater' into 'main'

Product creater

See merge request vato007/coster-rs!1
This commit is contained in:
Michael Pivato
2023-03-08 00:04:36 +00:00
4 changed files with 462 additions and 111 deletions

181
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -1,24 +1,65 @@
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;
struct Filter {}
#[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,
}
struct Constraint {}
enum ConstraintType {
Equal,
GreaterThan,
GreaterThanOrEqualTo,
LessThan,
LessThanOrEqualTo,
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,
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)
// TODO: This first string should also be some kind of source type enum, probably shared with source types on filter/constraint
Field(String, String),
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
enum BuildFrom {
Service,
Transfer,
@@ -27,6 +68,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 +80,116 @@ 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,
// TODO: Just use none when unknown?
_ => 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,
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 {
@@ -76,7 +205,7 @@ struct Definition {
constraints: Vec<Constraint>,
build_from: BuildFrom,
frequency: Frequency,
quantity: Quantity,
quantity: BuiltQuantity,
duration_fallback: DurationFallback,
}
@@ -132,35 +261,101 @@ where
for record in definitions.deserialize::<HashMap<String, String>>() {
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(
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: ,
duration_fallback: (),
},
);
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" => {}
"Component" => {}
"Constraint" => {}
_ => continue,
"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);
}
unknown => println!("Invalid type found: {}", unknown),
}
}
let mut mapped_definitions = all_definitions
.into_values()
.map(|value| (value.build_from, value))
.collect();
let mut mapped_definitions: HashMap<BuildFrom, Vec<Definition>> = 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
@@ -177,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<String, Position> = 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::<HashMap<String, String>>()
.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<Product> = 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<String, String> = 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(())

View File

@@ -5,10 +5,10 @@ use coster_rs::CsvCost;
use serde::Deserialize;
#[derive(Parser)]
#[clap(name = "coster-rs")]
#[clap(author = "Pivato M. <mpivato4@gmail.com>")]
#[clap(version = "0.0.1")]
#[clap(about = "Simple, fast, efficient costing tool", long_about = None)]
#[command(name = "coster-rs")]
#[command(author = "Pivato M. <mpivato4@gmail.com>")]
#[command(version = "0.0.1")]
#[command(about = "Simple, fast, efficient costing tool", long_about = None)]
struct Cli {
#[clap(subcommand)]
command: Commands,
@@ -18,44 +18,50 @@ 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<PathBuf>,
#[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<PathBuf>,
},
/// 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(long, default_value = "E")]
account_type: String,
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
},
}
@@ -77,6 +83,8 @@ fn main() -> anyhow::Result<()> {
allocation_statistics,
areas,
cost_centres,
use_numeric_accounts,
account_type,
output,
} => allocate_overheads(
lines,
@@ -84,6 +92,8 @@ fn main() -> anyhow::Result<()> {
allocation_statistics,
areas,
cost_centres,
use_numeric_accounts,
account_type,
output,
),
}
@@ -113,6 +123,8 @@ fn allocate_overheads(
allocation_statistics: PathBuf,
areas: PathBuf,
cost_centres: PathBuf,
use_numeric_accounts: bool,
account_type: String,
output: Option<PathBuf>,
) -> anyhow::Result<()> {
coster_rs::reciprocal_allocation(
@@ -122,10 +134,10 @@ 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(),
account_type,
)
}