subtr_actor/stats/accumulators/
ball_third.rs1use super::*;
2
3#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
4pub struct BallThirdStats {
5 pub tracked_time: f32,
6 pub team_zero_third_time: f32,
7 pub neutral_third_time: f32,
8 pub team_one_third_time: f32,
9 #[serde(default, skip_serializing_if = "LabeledFloatSums::is_empty")]
10 pub labeled_time: LabeledFloatSums,
11}
12
13impl BallThirdStats {
14 pub fn team_zero_third_pct(&self) -> f32 {
15 if self.tracked_time == 0.0 {
16 0.0
17 } else {
18 self.team_zero_third_time * 100.0 / self.tracked_time
19 }
20 }
21
22 pub fn team_one_third_pct(&self) -> f32 {
23 if self.tracked_time == 0.0 {
24 0.0
25 } else {
26 self.team_one_third_time * 100.0 / self.tracked_time
27 }
28 }
29
30 pub fn neutral_third_pct(&self) -> f32 {
31 if self.tracked_time == 0.0 {
32 0.0
33 } else {
34 self.neutral_third_time * 100.0 / self.tracked_time
35 }
36 }
37
38 pub fn time_with_labels(&self, labels: &[StatLabel]) -> f32 {
39 self.labeled_time.sum_matching(labels)
40 }
41
42 pub fn for_team(&self, is_team_zero: bool) -> BallThirdTeamStats {
43 let (defensive_third_time, offensive_third_time) = if is_team_zero {
44 (self.team_zero_third_time, self.team_one_third_time)
45 } else {
46 (self.team_one_third_time, self.team_zero_third_time)
47 };
48
49 let mut labeled_time = LabeledFloatSums::default();
50 for entry in &self.labeled_time.entries {
51 labeled_time.add(
52 entry
53 .labels
54 .iter()
55 .map(|label| team_relative_ball_third_label(label, is_team_zero)),
56 entry.value,
57 );
58 }
59
60 BallThirdTeamStats {
61 tracked_time: self.tracked_time,
62 defensive_third_time,
63 neutral_third_time: self.neutral_third_time,
64 offensive_third_time,
65 labeled_time,
66 }
67 }
68}
69
70#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, ts_rs::TS)]
71#[ts(export)]
72pub struct BallThirdTeamStats {
73 pub tracked_time: f32,
74 pub defensive_third_time: f32,
75 pub neutral_third_time: f32,
76 pub offensive_third_time: f32,
77 #[serde(default, skip_serializing_if = "LabeledFloatSums::is_empty")]
78 pub labeled_time: LabeledFloatSums,
79}
80
81#[derive(Debug, Clone, Default, PartialEq)]
82pub struct BallThirdStatsAccumulator {
83 stats: BallThirdStats,
84}
85
86impl BallThirdStatsAccumulator {
87 pub fn new() -> Self {
88 Self::default()
89 }
90
91 pub fn stats(&self) -> &BallThirdStats {
92 &self.stats
93 }
94
95 pub fn apply_event(&mut self, event: &BallThirdEvent) {
96 if !event.active {
97 return;
98 }
99
100 self.stats.tracked_time += event.duration;
101 let field_third = match event.field_third.as_str() {
102 "team_zero_third" => {
103 self.stats.team_zero_third_time += event.duration;
104 "team_zero_third"
105 }
106 "team_one_third" => {
107 self.stats.team_one_third_time += event.duration;
108 "team_one_third"
109 }
110 "neutral_third" => {
111 self.stats.neutral_third_time += event.duration;
112 "neutral_third"
113 }
114 _ => return,
115 };
116 self.stats
117 .labeled_time
118 .add([StatLabel::new("field_third", field_third)], event.duration);
119 }
120}
121
122fn team_relative_ball_third_label(label: &StatLabel, is_team_zero: bool) -> StatLabel {
123 match (label.key, label.value) {
124 ("field_third", "team_zero_third") => StatLabel::new(
125 "field_third",
126 if is_team_zero {
127 "defensive_third"
128 } else {
129 "offensive_third"
130 },
131 ),
132 ("field_third", "team_one_third") => StatLabel::new(
133 "field_third",
134 if is_team_zero {
135 "offensive_third"
136 } else {
137 "defensive_third"
138 },
139 ),
140 _ => label.clone(),
141 }
142}