pickems/reporting/
assess.rs1use std::{iter::Sum, ops::Add};
2
3use anyhow::anyhow;
4
5use crate::{
6 datatypes::{Index, Name, Set, Teams},
7 reporting::Report,
8 simulation::{Simulation, SwissSystem},
9};
10
11#[derive(Debug, Clone, Copy, Default)]
13pub struct AssessReport {
14 pub mean: f32,
16 pub ds: f32,
18 pub success: u64,
20 pub n: u64,
22 pub three_zero_picks: Set,
24 pub advancing_picks: Set,
26 pub zero_three_picks: Set,
28}
29
30impl AssessReport {
31 pub fn new<
33 I1: IntoIterator<Item = Index>,
34 I2: IntoIterator<Item = Index>,
35 I3: IntoIterator<Item = Index>,
36 >(
37 three_zero_picks: I1,
38 advanced_picks: I2,
39 zero_three_picks: I3,
40 ) -> Self {
41 Self {
42 mean: 0.0,
43 ds: 0.0,
44 success: 0,
45 n: 0,
46 three_zero_picks: three_zero_picks.into_iter().collect(),
47 advancing_picks: advanced_picks.into_iter().collect(),
48 zero_three_picks: zero_three_picks.into_iter().collect(),
49 }
50 }
51
52 pub fn try_from_args(
54 teams: &Teams,
55 three_zero_str: &[Name; 2],
56 advancing_str: &[Name; 6],
57 zero_three_str: &[Name; 2],
58 ) -> anyhow::Result<Self> {
59 let mut three_zero_picks = Vec::new();
60 let mut advancing_picks = Vec::new();
61 let mut zero_three_picks = Vec::new();
62
63 for s in three_zero_str {
64 three_zero_picks.push(Index::try_new(
65 teams
66 .names
67 .iter()
68 .position(|name| name == s)
69 .ok_or_else(|| anyhow!("failed to find team \"{s}\" in the input file"))?
70 as u16,
71 )?);
72 }
73
74 for s in advancing_str {
75 advancing_picks.push(Index::try_new(
76 teams
77 .names
78 .iter()
79 .position(|name| name == s)
80 .ok_or_else(|| anyhow!("failed to find team \"{s}\" in the input file"))?
81 as u16,
82 )?);
83 }
84
85 for s in zero_three_str {
86 zero_three_picks.push(Index::try_new(
87 teams
88 .names
89 .iter()
90 .position(|name| name == s)
91 .ok_or_else(|| anyhow!("failed to find team \"{s}\" in the input file"))?
92 as u16,
93 )?);
94 }
95
96 let mut seen = Set::new();
97
98 for pick in three_zero_picks
99 .iter()
100 .chain(advancing_picks.iter())
101 .chain(zero_three_picks.iter())
102 {
103 if !seen.insert(*pick) {
104 anyhow::bail!("duplicate pick: {}", teams.names[pick.to_usize()]);
105 }
106 }
107
108 Ok(Self::new(
109 three_zero_picks,
110 advancing_picks,
111 zero_three_picks,
112 ))
113 }
114}
115
116impl Add for AssessReport {
117 type Output = Self;
118
119 fn add(mut self, rhs: Self) -> Self::Output {
120 if self.n == 0 {
121 rhs
122 } else {
123 self.success += rhs.success;
124
125 let n_self = self.n as f32;
126 let n_rhs = rhs.n as f32;
127 let n = n_self + n_rhs;
128 self.n += rhs.n;
129
130 let delta = self.mean - rhs.mean;
131 self.mean = n_rhs.mul_add(rhs.mean, n_self * self.mean) / n;
132 self.ds += (delta * delta).mul_add(n_self * n_rhs / n, rhs.ds);
133
134 self
135 }
136 }
137}
138
139impl Sum for AssessReport {
140 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
141 iter.fold(Self::default(), |acc, report| acc + report)
142 }
143}
144
145impl Report for AssessReport {
146 fn update(&mut self, ss: &SwissSystem) {
147 self.n += 1;
149
150 let stars = {
153 self.three_zero_picks
154 .iter()
155 .filter(|&pick| ss.wins[pick.to_usize()] == 3 && ss.losses[pick.to_usize()] == 0)
156 .count()
157 + self
158 .advancing_picks
159 .iter()
160 .filter(|&pick| ss.wins[pick.to_usize()] == 3 && ss.losses[pick.to_usize()] > 0)
161 .count()
162 + self
163 .zero_three_picks
164 .iter()
165 .filter(|&pick| {
166 ss.wins[pick.to_usize()] == 0 && ss.losses[pick.to_usize()] == 3
167 })
168 .count()
169 };
170
171 if stars >= 5 {
173 self.success += 1;
174 }
175
176 let delta1 = stars as f32 - self.mean;
177 self.mean += delta1 / self.n as f32;
178 let delta2 = stars as f32 - self.mean;
179 self.ds = delta1.mul_add(delta2, self.ds);
180 }
181
182 fn format(&self, _sim: &Simulation) -> String {
183 let mean = self.mean;
184 let sd = (self.ds / self.n as f32).sqrt();
185 let success = self.success as f32 / self.n as f32 * 100.0;
186
187 format!(
188 "\nSimulated stars earned: {mean:.3} +/- {sd:.3}\nExpected success (>=5 stars): {success:.1}%"
189 )
190 }
191}