Add extra test, fix reciprocal allocation matrix creation
This commit is contained in:
@@ -243,7 +243,7 @@ where
|
|||||||
let mut overhead_ccs: Vec<String> = Vec::new();
|
let mut overhead_ccs: Vec<String> = Vec::new();
|
||||||
// overhead department -> total (summed limit to costs)
|
// overhead department -> total (summed limit to costs)
|
||||||
let mut overhead_cc_totals: HashMap<String, f64> = HashMap::new();
|
let mut overhead_cc_totals: HashMap<String, f64> = HashMap::new();
|
||||||
// For each overhead area, get the cost centres in the area, and get all cost centres
|
// For each overhead area, get the cost centres in the area (overhead cost centres), and get all cost centres
|
||||||
// that fit the limit to criteria for the area (skip any cases of overhead cc = other cc).
|
// that fit the limit to criteria for the area (skip any cases of overhead cc = other cc).
|
||||||
// Then get the totals for the other ccs, by looking in the flat_department_costs, where the
|
// Then get the totals for the other ccs, by looking in the flat_department_costs, where the
|
||||||
// allocation statistic matches the allocation statistic for this area
|
// allocation statistic matches the allocation statistic for this area
|
||||||
@@ -253,24 +253,15 @@ where
|
|||||||
let area_name = area.get("Name").unwrap();
|
let area_name = area.get("Name").unwrap();
|
||||||
let allocation_statistic = area.get("AllocationStatistic").unwrap();
|
let allocation_statistic = area.get("AllocationStatistic").unwrap();
|
||||||
let department_type: DepartmentType = DepartmentType::from(area.get("Type").unwrap());
|
let department_type: DepartmentType = DepartmentType::from(area.get("Type").unwrap());
|
||||||
|
|
||||||
|
if department_type == DepartmentType::Overhead {
|
||||||
let current_area_ccs = area_ccs.get(area_name);
|
let current_area_ccs = area_ccs.get(area_name);
|
||||||
|
|
||||||
if current_area_ccs.is_none() {
|
if current_area_ccs.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Also skip if allocation statistic isn't found
|
|
||||||
// let allocation_statistic_found = allocation_statistics
|
|
||||||
// .iter()
|
|
||||||
// .find(|stat| stat.name == *allocation_statistic);
|
|
||||||
// if allocation_statistic_found.is_none() {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
let mut current_area_ccs = current_area_ccs.unwrap().clone();
|
let mut current_area_ccs = current_area_ccs.unwrap().clone();
|
||||||
|
|
||||||
//TODO: This isn't bad, however if it's too slow then consider performance improvements, such as summing totals now
|
|
||||||
// rather than calculating in the next step.
|
|
||||||
if department_type == DepartmentType::Overhead {
|
|
||||||
overhead_ccs.append(&mut current_area_ccs);
|
overhead_ccs.append(&mut current_area_ccs);
|
||||||
let overhead_ccs = area_ccs.get(area_name).unwrap();
|
let overhead_ccs = area_ccs.get(area_name).unwrap();
|
||||||
// TODO: This depends on the area limit criteria. For now just doing any limit criteria
|
// TODO: This depends on the area limit criteria. For now just doing any limit criteria
|
||||||
@@ -377,6 +368,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: (Consider) We could actually cheat here and not use this matrix implementation at all (and thus be more
|
||||||
|
// memory efficient, but maybe slower)
|
||||||
|
// Since we know each operating department in an account will get the proportion of the total overhead amount relative
|
||||||
|
// according to its operating amount from the total amount of the overhead departments, we can just directly calculate
|
||||||
|
// these totals and do some simple multiplications (it does get trickier with multiple accounts, as the cost drivers
|
||||||
|
// are consistent across all accounts, but depend on the allocation statistic to determine which lines to pick from).
|
||||||
let results = reciprocal_allocation_impl(
|
let results = reciprocal_allocation_impl(
|
||||||
allocation_rules,
|
allocation_rules,
|
||||||
initial_account_costs
|
initial_account_costs
|
||||||
@@ -390,6 +387,8 @@ where
|
|||||||
|
|
||||||
for cost in results {
|
for cost in results {
|
||||||
for department in cost.summed_department_costs {
|
for department in cost.summed_department_costs {
|
||||||
|
// Any consumers should assume missing cc/account value was 0 (we already ignore overhead, as they all 0 out)
|
||||||
|
if department.value != 0. {
|
||||||
output.serialize(CsvCost {
|
output.serialize(CsvCost {
|
||||||
account: cost.account.clone(),
|
account: cost.account.clone(),
|
||||||
department: department.department,
|
department: department.department,
|
||||||
@@ -397,6 +396,7 @@ where
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,7 +404,7 @@ fn split_allocation_statistic_range(
|
|||||||
allocation_statistic: &CsvAllocationStatistic,
|
allocation_statistic: &CsvAllocationStatistic,
|
||||||
accounts_sorted: &Vec<String>,
|
accounts_sorted: &Vec<String>,
|
||||||
) -> Vec<AllocationStatisticAccountRange> {
|
) -> Vec<AllocationStatisticAccountRange> {
|
||||||
// TODO: This split needs to be more comprehensive so that we don't split between quotes
|
// TODO: This split needs to be more comprehensive so that we don't split between quotes, so use a regex
|
||||||
let split = allocation_statistic.account_ranges.split(";");
|
let split = allocation_statistic.account_ranges.split(";");
|
||||||
split
|
split
|
||||||
.map(|split| {
|
.map(|split| {
|
||||||
@@ -533,7 +533,7 @@ fn do_solve_reciprocal<T: ReciprocalAllocationSolver>(
|
|||||||
.get(&rule.to_department)
|
.get(&rule.to_department)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
operating_overhead_mappings
|
operating_overhead_mappings
|
||||||
[from_index * overhead_department_mappings.len() + to_index] = rule.percent;
|
[from_index * operating_department_mappings.len() + to_index] = rule.percent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let operating_overhead_mappings_mat: DMatrix<f64> = DMatrix::from_vec(
|
let operating_overhead_mappings_mat: DMatrix<f64> = DMatrix::from_vec(
|
||||||
@@ -598,6 +598,7 @@ fn do_solve_reciprocal<T: ReciprocalAllocationSolver>(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::reciprocal_allocation;
|
use crate::reciprocal_allocation;
|
||||||
use crate::AccountCost;
|
use crate::AccountCost;
|
||||||
|
use crate::CsvCost;
|
||||||
use crate::DepartmentType;
|
use crate::DepartmentType;
|
||||||
use crate::OverheadAllocationRule;
|
use crate::OverheadAllocationRule;
|
||||||
use crate::TotalDepartmentCost;
|
use crate::TotalDepartmentCost;
|
||||||
@@ -683,6 +684,23 @@ mod tests {
|
|||||||
assert_eq!(expected_final_allocations, result);
|
assert_eq!(expected_final_allocations, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_real() {
|
||||||
|
let result = reciprocal_allocation(
|
||||||
|
csv::Reader::from_path("test_line.csv").unwrap(),
|
||||||
|
csv::Reader::from_path("test_account.csv").unwrap(),
|
||||||
|
csv::Reader::from_path("test_alloc_stat.csv").unwrap(),
|
||||||
|
csv::Reader::from_path("test_area.csv").unwrap(),
|
||||||
|
csv::Reader::from_path("test_costcentre.csv").unwrap(),
|
||||||
|
&mut csv::Writer::from_path("test_output_alloc_stat.csv").unwrap(),
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"E".to_owned(),
|
||||||
|
);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_real() {
|
fn test_real() {
|
||||||
let result = reciprocal_allocation(
|
let result = reciprocal_allocation(
|
||||||
|
|||||||
Reference in New Issue
Block a user