1use crate::seq::Facet;
2use patterns_macros::because;
3use std::collections::BTreeMap;
4
5#[derive(patterns_macros::Record)]
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7pub struct Running {
8 pub amount: i128,
9 pub count: i64,
10}
11because!(Running, "a total that events move, signed because an unlock takes back what a lock added and a count of open positions can go under zero when its opening was never seen");
12
13#[derive(patterns_macros::Record)]
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
15pub struct DayTotal {
16 pub amount: i128,
17 pub count: i64,
18 pub ending_amount: i128,
19 pub ending_count: i64,
20}
21because!(DayTotal, "what one day added to a running total and where the total stood at the day's end, the two readings a chart needs: the bar and the line");
22
23pub type Days<S> = BTreeMap<(S, u64), DayTotal>;
24
25pub type Totals<S> = BTreeMap<S, Running>;
26
27pub fn booked<S: Ord + Copy>(days: Days<S>, totals: Totals<S>, series: S, day: u64, amount: i128, count: i64) -> (Days<S>, Totals<S>) {
28 let mut totals = totals;
29 let mut days = days;
30 let running = totals.entry(series).or_default();
31 running.amount = running.amount.saturating_add(amount);
32 running.count = running.count.saturating_add(count);
33 let ending = *running;
34 let row = days.entry((series, day)).or_default();
35 row.amount = row.amount.saturating_add(amount);
36 row.count = row.count.saturating_add(count);
37 row.ending_amount = ending.amount;
38 row.ending_count = ending.count;
39 (days, totals)
40}
41because!(booked, "one movement written to a series: the running total moves, and the day's row takes the movement and the total the day now ends at, so the bar and the line can never disagree");
42
43pub fn booked_all<S: Ord + Copy>(days: Days<S>, totals: Totals<S>, day: u64, bookings: &[(S, i128, i64)]) -> (Days<S>, Totals<S>) {
44 let mut out = (days, totals);
45 for (series, amount, count) in bookings {
46 out = booked(out.0, out.1, *series, day, *amount, *count);
47 }
48 out
49}
50because!(booked_all, "several movements of one event written in order, so an event that moves two series is one call in a reducer");
51
52pub fn moved_total<S: Ord + Copy>(totals: Totals<S>, series: S, amount: i128) -> Totals<S> {
53 let mut out = totals;
54 let running = out.entry(series).or_default();
55 running.amount = running.amount.saturating_add(amount);
56 out
57}
58because!(moved_total, "a running total moved with no day row written, for supply that arrives from outside the program's own events and belongs to the total but to no day of the chart");
59
60pub fn amount_of<S: Ord>(totals: &Totals<S>, series: &S) -> i128 {
61 totals.get(series).map(|r| r.amount).unwrap_or(0)
62}
63because!(amount_of, "where a running total stands, or zero for a series nothing has moved yet, the other half of count_of");
64
65pub fn count_of<S: Ord>(totals: &Totals<S>, series: &S) -> i64 {
66 totals.get(series).map(|r| r.count).unwrap_or(0)
67}
68because!(count_of, "how many a running total counts, the other half of amount_of, zero for a series nothing has moved yet");
69
70pub fn series_days<S: Ord + Copy>(days: &Days<S>, series: S) -> Vec<(u64, DayTotal)> {
71 days.iter()
72 .filter(|((s, _), _)| *s == series)
73 .map(|((_, day), row)| (*day, *row))
74 .collect()
75}
76because!(series_days, "the day rows of one series in day order, out of a table that keys every series together");
77
78pub fn tallied<F, T: Facet<F>>(totals: BTreeMap<T::Value, u64>, rows: &[T]) -> BTreeMap<T::Value, u64>
79where
80 T::Value: Ord + Copy,
81{
82 let mut out = totals;
83 for row in rows {
84 *out.entry(row.facet()).or_default() += 1;
85 }
86 out
87}
88because!(tallied, "a count per kind raised by one for each row of that kind, the totals a feed shows in its filter menu without counting the feed");
89
90pub fn last_facet_or<F, T: Facet<F>>(items: &[T], fallback: T::Value) -> T::Value {
91 items.last().map(|item| item.facet()).unwrap_or(fallback)
92}
93because!(last_facet_or, "the projection of a list's last element, or a fallback for an empty list, which is where a walk's cursor moves to once its rows are priced");