1use crate::money::{none, whole_tokens};
2use crate::series::DayTotal;
3use crate::table::at_or_before;
4use patterns_macros::because;
5use std::collections::BTreeMap;
6
7pub type Prices = BTreeMap<u64, f64>;
8
9pub type Rows = BTreeMap<u64, DayTotal>;
10
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct Units {
13 pub wei_per_unit: u128,
14 pub units_per_token: f64,
15}
16because!(Units, "how an amount in smallest units becomes a number of tokens, carried as one value so every chart truncates to the same places");
17
18pub fn whole(amount: u128, units: Units) -> f64 {
19 whole_tokens(amount, units.wei_per_unit, units.units_per_token)
20}
21because!(whole, "an amount as whole tokens under the units a spec states, the one call every chart makes before it prices anything");
22
23pub fn signed_whole(amount: i128, units: Units) -> f64 {
24 match amount < 0 {
25 true => -whole(amount.unsigned_abs(), units),
26 false => whole(amount.unsigned_abs(), units),
27 }
28}
29because!(signed_whole, "a signed total as tokens, negative when the total is, since a lock series can run under zero and a chart draws where it went");
30
31pub fn ending_on(rows: &Rows, day: u64, units: Units) -> f64 {
32 rows.range(..=day)
33 .next_back()
34 .map(|(_, row)| signed_whole(row.ending_amount, units))
35 .unwrap_or(none())
36}
37because!(ending_on, "where a series stood at the end of a day, carried forward from the newest row at or before it, and nothing before the series began");
38
39pub fn row_on(rows: &Rows, day: u64) -> Option<DayTotal> {
40 at_or_before(rows, day)
41}
42because!(row_on, "the newest row of a series at or before a day, for a reader that needs the counts as well as the amount");
43
44pub fn latest_ending(rows: &Rows, units: Units) -> f64 {
45 rows.iter()
46 .next_back()
47 .map(|(_, row)| signed_whole(row.ending_amount, units))
48 .unwrap_or(none())
49}
50because!(latest_ending, "where a series stands today, its last row's ending amount as tokens, or nothing for an empty series");
51
52pub fn day_amount(rows: &Rows, day: u64, units: Units) -> f64 {
53 rows.get(&day)
54 .map(|row| signed_whole(row.amount, units))
55 .unwrap_or(none())
56}
57because!(day_amount, "what a series moved on one exact day as tokens, zero on a day it has no row, for the readers that sum movements rather than read levels");
58
59pub fn latest_tally(rows: &Rows, ending: bool) -> Option<u64> {
60 rows.iter().next_back().map(|(_, row)| {
61 let count = match ending {
62 true => row.ending_count,
63 false => row.count,
64 };
65 count.max(0) as u64
66 })
67}
68because!(latest_tally, "the newest row's count or ending count as a whole number, the figure the metric menu shows beside an entry, or nothing for an empty series");
69
70#[derive(Clone, Copy, Debug, PartialEq)]
71pub struct WeiRounding {
72 pub scale: f64,
73 pub wei_per_scaled: u128,
74}
75because!(WeiRounding, "how a number of tokens is turned back into smallest units for the formula, rounded at a chosen place first, since two charts of the dashboard chose different places and both are kept");
76
77pub fn wei_of(tokens: f64, rounding: WeiRounding) -> u128 {
78 let scaled = (tokens * rounding.scale).round();
79 match scaled <= none() {
80 true => 0,
81 false => (scaled as u128).saturating_mul(rounding.wei_per_scaled),
82 }
83}
84because!(wei_of, "tokens as smallest units after rounding at the chosen place, the reverse of whole for a figure that goes back into the program's arithmetic");