Merge branch 'product_creater' into 'main'
Product creater See merge request vato007/coster-rs!1
This commit is contained in:
181
Cargo.lock
generated
181
Cargo.lock
generated
@@ -26,17 +26,6 @@ dependencies = [
|
|||||||
"num-traits",
|
"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]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -103,26 +92,24 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.23"
|
version = "4.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
|
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"indexmap",
|
"is-terminal",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "3.2.18"
|
version = "4.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
|
checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
@@ -133,9 +120,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.2.4"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
@@ -286,10 +273,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "errno"
|
||||||
version = "0.12.3"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -297,15 +299,6 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
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]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
@@ -315,6 +308,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.53"
|
version = "0.1.53"
|
||||||
@@ -340,13 +339,25 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "io-lifetimes"
|
||||||
version = "1.9.2"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"libc",
|
||||||
"hashbrown",
|
"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]]
|
[[package]]
|
||||||
@@ -381,9 +392,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.126"
|
version = "0.2.139"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "link-cplusplus"
|
name = "link-cplusplus"
|
||||||
@@ -394,6 +405,12 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.17"
|
version = "0.4.17"
|
||||||
@@ -597,6 +614,20 @@ version = "0.1.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
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]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
@@ -683,12 +714,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "textwrap"
|
|
||||||
version = "0.16.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
@@ -824,3 +849,69 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
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"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ csv = "1.1"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
# num = "0.4"
|
# num = "0.4"
|
||||||
clap = { version = "3.1.18", features = ["derive"] }
|
clap = { version = "4.1.8", features = ["derive"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
|
|||||||
@@ -1,24 +1,65 @@
|
|||||||
|
use core::panic;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
sync::{mpsc, Arc, Mutex},
|
sync::mpsc,
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use rayon::prelude::{IntoParallelRefIterator, ParallelBridge, ParallelIterator};
|
use csv::Position;
|
||||||
|
use itertools::Itertools;
|
||||||
use serde::Serialize;
|
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 {
|
enum Component {
|
||||||
Constant(String),
|
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 {
|
enum BuildFrom {
|
||||||
Service,
|
Service,
|
||||||
Transfer,
|
Transfer,
|
||||||
@@ -27,6 +68,7 @@ enum BuildFrom {
|
|||||||
CodingDiagnosis,
|
CodingDiagnosis,
|
||||||
// TODO: This is hard/expensive, ignore for now as we don't have test data
|
// TODO: This is hard/expensive, ignore for now as we don't have test data
|
||||||
LinkedDataset,
|
LinkedDataset,
|
||||||
|
Revenue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&String> for BuildFrom {
|
impl From<&String> for BuildFrom {
|
||||||
@@ -38,29 +80,116 @@ impl From<&String> for BuildFrom {
|
|||||||
"CD" => BuildFrom::CodingDiagnosis,
|
"CD" => BuildFrom::CodingDiagnosis,
|
||||||
"T" => BuildFrom::Transfer,
|
"T" => BuildFrom::Transfer,
|
||||||
"BS" => BuildFrom::LinkedDataset,
|
"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 {
|
enum Frequency {
|
||||||
OnePerSource,
|
ChangeInBedNumber,
|
||||||
DailyOrChangeInWard,
|
ChangeInClinic,
|
||||||
|
ChangeInConsultant,
|
||||||
|
ChangeInConsultantSpecialty,
|
||||||
|
ChangeInConsultantSpecialtyRollup,
|
||||||
|
ChangeInUnit,
|
||||||
|
ChangeInWard,
|
||||||
Daily,
|
Daily,
|
||||||
|
DailyOrChangeInBedNumber,
|
||||||
|
DailyOrChangeInClinic,
|
||||||
|
DailyOrChangeInConsultant,
|
||||||
|
DailyOrChangeInConsultantSpecialty,
|
||||||
|
DailyOrChangeInConsultantSpecialtyRollup,
|
||||||
|
DailyOrChangeInUnit,
|
||||||
|
DailyOrChangeInWard,
|
||||||
|
DailyExceptOnAdmissionDay,
|
||||||
|
DailyExceptOnAdmissionDayInclSameDay,
|
||||||
|
DailyExceptOnDischargeDay,
|
||||||
|
DailyExceptOnDischargeDayInclSameDay,
|
||||||
|
OnlyAdmissionLocation,
|
||||||
|
OnlyDischargeLocation,
|
||||||
|
OnePerSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&String> for Frequency {
|
impl From<&String> for Frequency {
|
||||||
fn from(frequency: &String) -> Self {
|
fn from(frequency: &String) -> Self {
|
||||||
match frequency.as_str() {
|
match frequency.as_str() {
|
||||||
"S" => Frequency::OnePerSource,
|
"O" => Frequency::OnePerSource,
|
||||||
"DOCW" => Frequency::DailyOrChangeInWard,
|
"DOCW" => Frequency::DailyOrChangeInWard,
|
||||||
"D" => Frequency::Daily,
|
"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 {
|
enum Quantity {
|
||||||
Constant(f64),
|
Constant(f64),
|
||||||
|
// Name of the extra
|
||||||
Extra(String),
|
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 {
|
enum DurationFallback {
|
||||||
@@ -76,7 +205,7 @@ struct Definition {
|
|||||||
constraints: Vec<Constraint>,
|
constraints: Vec<Constraint>,
|
||||||
build_from: BuildFrom,
|
build_from: BuildFrom,
|
||||||
frequency: Frequency,
|
frequency: Frequency,
|
||||||
quantity: Quantity,
|
quantity: BuiltQuantity,
|
||||||
duration_fallback: DurationFallback,
|
duration_fallback: DurationFallback,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,35 +261,101 @@ where
|
|||||||
for record in definitions.deserialize::<HashMap<String, String>>() {
|
for record in definitions.deserialize::<HashMap<String, String>>() {
|
||||||
let record = record?;
|
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)
|
// 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();
|
let record_type = record.get("Type").unwrap();
|
||||||
match recordType.as_str() {
|
match record_type.as_str() {
|
||||||
"Definition" => {
|
"Definition" => {
|
||||||
let build_quantity =
|
let quantity_type = record.get("BuiltQuantity").unwrap();
|
||||||
all_definitions.insert(
|
let rounding_mode =
|
||||||
record.get("Name").unwrap().to_owned(),
|
RoundingMode::from(record.get("BuiltQuantityRounding").unwrap());
|
||||||
Definition {
|
let quantity = match quantity_type.as_str() {
|
||||||
name: record.get("Name").unwrap().to_owned(),
|
"S" => Quantity::SourceQuantity,
|
||||||
components: vec![],
|
"C" => Quantity::Constant(
|
||||||
filters: vec![],
|
record
|
||||||
constraints: vec![],
|
.get("BuiltQuantityConstant")
|
||||||
build_from: BuildFrom::from(record.get("BuildFrom").unwrap()),
|
.unwrap()
|
||||||
frequency: Frequency::from(record.get("Frequency").unwrap()),
|
.parse()
|
||||||
quantity: ,
|
.unwrap(),
|
||||||
duration_fallback: (),
|
),
|
||||||
},
|
"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" => {}
|
"Filter" => {
|
||||||
"Component" => {}
|
let new_filter = Filter {
|
||||||
"Constraint" => {}
|
equal: record.get("FilterNotIn").unwrap() != "",
|
||||||
_ => continue,
|
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
|
let mut mapped_definitions: HashMap<BuildFrom, Vec<Definition>> = HashMap::new();
|
||||||
.into_values()
|
for (_, definition) in all_definitions {
|
||||||
.map(|value| (value.build_from, value))
|
mapped_definitions
|
||||||
.collect();
|
.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)
|
// 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
|
// 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.
|
// 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,
|
// 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
|
// although we're still likely to be bottlenecked by just write-speed
|
||||||
let mut encounters = encounters;
|
let mut encounters = encounters;
|
||||||
encounters
|
let headers = encounters.headers()?.clone();
|
||||||
.deserialize::<HashMap<String, String>>()
|
|
||||||
.map(|encounter| encounter.unwrap())
|
for encounter in encounters.records() {
|
||||||
//TODO: Rayon can't be used with csv, consider just batching reads perhaps?
|
let encounter = encounter?;
|
||||||
// .par_bridge()
|
let position = encounter.position().unwrap();
|
||||||
.for_each(|encounter| {
|
let encounter: HashMap<String, String> = encounter.deserialize(Some(&headers))?;
|
||||||
// TODO: Calculate quantitty for this encounter
|
encounter_positions.insert(
|
||||||
tx.send(Product::default()).unwrap();
|
encounter.get("EncounterNumber").unwrap().to_string(),
|
||||||
});
|
position.clone(),
|
||||||
let encounters: Vec<Product> = vec![];
|
);
|
||||||
encounters.par_iter().for_each(|encounter| {
|
// TODO: For each encounter definition, check this fits the filter criteria/constraints,
|
||||||
println!("{:?}", encounter);
|
// 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();
|
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();
|
write_thread.join().unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
48
src/main.rs
48
src/main.rs
@@ -5,10 +5,10 @@ use coster_rs::CsvCost;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(name = "coster-rs")]
|
#[command(name = "coster-rs")]
|
||||||
#[clap(author = "Pivato M. <mpivato4@gmail.com>")]
|
#[command(author = "Pivato M. <mpivato4@gmail.com>")]
|
||||||
#[clap(version = "0.0.1")]
|
#[command(version = "0.0.1")]
|
||||||
#[clap(about = "Simple, fast, efficient costing tool", long_about = None)]
|
#[command(about = "Simple, fast, efficient costing tool", long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
command: Commands,
|
command: Commands,
|
||||||
@@ -18,44 +18,50 @@ struct Cli {
|
|||||||
enum Commands {
|
enum Commands {
|
||||||
/// Moves money between accounts and departments, using the given rules and lines
|
/// Moves money between accounts and departments, using the given rules and lines
|
||||||
move_money {
|
move_money {
|
||||||
#[clap(short = 'r', long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short = 'r', long, value_name = "FILE")]
|
||||||
rules: PathBuf,
|
rules: PathBuf,
|
||||||
|
|
||||||
#[clap(short = 'l', long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short = 'l', long, value_name = "FILE")]
|
||||||
lines: PathBuf,
|
lines: PathBuf,
|
||||||
|
|
||||||
#[clap(short, long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
|
|
||||||
#[clap(short, long, default_value_t = false)]
|
#[arg(short, long)]
|
||||||
use_numeric_accounts: bool,
|
use_numeric_accounts: bool,
|
||||||
},
|
},
|
||||||
/// Combines rules to the minimum set required
|
/// Combines rules to the minimum set required
|
||||||
smush_rules {
|
smush_rules {
|
||||||
#[clap(short = 'r', long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short = 'r', long, value_name = "FILE")]
|
||||||
rules: PathBuf,
|
rules: PathBuf,
|
||||||
|
|
||||||
#[clap(short, long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
/// Allocates servicing department amounts to operating departments
|
/// Allocates servicing department amounts to operating departments
|
||||||
allocate_overheads {
|
allocate_overheads {
|
||||||
#[clap(short, long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
lines: PathBuf,
|
lines: PathBuf,
|
||||||
|
|
||||||
#[clap(short, long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
accounts: PathBuf,
|
accounts: PathBuf,
|
||||||
|
|
||||||
#[clap(short, long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short = 's', long, value_name = "FILE")]
|
||||||
allocation_statistics: PathBuf,
|
allocation_statistics: PathBuf,
|
||||||
|
|
||||||
#[clap(short, long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
areas: PathBuf,
|
areas: PathBuf,
|
||||||
|
|
||||||
#[clap(short, long, parse(from_os_str), value_name = "FILE")]
|
#[arg(short, long, value_name = "FILE")]
|
||||||
cost_centres: PathBuf,
|
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>,
|
output: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -77,6 +83,8 @@ fn main() -> anyhow::Result<()> {
|
|||||||
allocation_statistics,
|
allocation_statistics,
|
||||||
areas,
|
areas,
|
||||||
cost_centres,
|
cost_centres,
|
||||||
|
use_numeric_accounts,
|
||||||
|
account_type,
|
||||||
output,
|
output,
|
||||||
} => allocate_overheads(
|
} => allocate_overheads(
|
||||||
lines,
|
lines,
|
||||||
@@ -84,6 +92,8 @@ fn main() -> anyhow::Result<()> {
|
|||||||
allocation_statistics,
|
allocation_statistics,
|
||||||
areas,
|
areas,
|
||||||
cost_centres,
|
cost_centres,
|
||||||
|
use_numeric_accounts,
|
||||||
|
account_type,
|
||||||
output,
|
output,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -113,6 +123,8 @@ fn allocate_overheads(
|
|||||||
allocation_statistics: PathBuf,
|
allocation_statistics: PathBuf,
|
||||||
areas: PathBuf,
|
areas: PathBuf,
|
||||||
cost_centres: PathBuf,
|
cost_centres: PathBuf,
|
||||||
|
use_numeric_accounts: bool,
|
||||||
|
account_type: String,
|
||||||
output: Option<PathBuf>,
|
output: Option<PathBuf>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
coster_rs::reciprocal_allocation(
|
coster_rs::reciprocal_allocation(
|
||||||
@@ -122,10 +134,10 @@ fn allocate_overheads(
|
|||||||
csv::Reader::from_path(areas)?,
|
csv::Reader::from_path(areas)?,
|
||||||
csv::Reader::from_path(cost_centres)?,
|
csv::Reader::from_path(cost_centres)?,
|
||||||
&mut csv::Writer::from_path(output.unwrap_or(PathBuf::from("alloc_output.csv")))?,
|
&mut csv::Writer::from_path(output.unwrap_or(PathBuf::from("alloc_output.csv")))?,
|
||||||
true,
|
use_numeric_accounts,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
"E".to_owned(),
|
account_type,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user