animsmith_core/
finding.rs1use serde::Serialize;
6use std::collections::BTreeMap;
7use std::fmt;
8
9use crate::evaluation::EvaluationScope;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Severity {
15 Note,
17 Warning,
20 Error,
23}
24
25impl fmt::Display for Severity {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f.write_str(match self {
28 Severity::Note => "note",
29 Severity::Warning => "warning",
30 Severity::Error => "error",
31 })
32 }
33}
34
35#[derive(Debug, Clone, Serialize)]
37#[serde(untagged)]
38#[non_exhaustive]
39pub enum Value {
40 Number(f64),
42 Text(String),
44}
45
46#[derive(Debug, Clone, Serialize)]
53#[non_exhaustive]
54pub struct MemberMeasurement {
55 pub member: String,
57 pub measurements: BTreeMap<String, Value>,
60}
61
62impl MemberMeasurement {
63 pub fn new(member: impl Into<String>) -> Self {
65 Self {
66 member: member.into(),
67 measurements: BTreeMap::new(),
68 }
69 }
70
71 pub fn measurement(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
73 let value = value.into();
74 if value.is_finite() {
75 self.measurements.insert(name.into(), value);
76 }
77 self
78 }
79}
80
81impl fmt::Display for Value {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Value::Number(n) => write!(f, "{n:.4}"),
85 Value::Text(s) => f.write_str(s),
86 }
87 }
88}
89
90#[derive(Debug, Clone, Serialize)]
97#[non_exhaustive]
98pub struct Finding {
99 pub check_id: &'static str,
101 pub severity: Severity,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub clip: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub bone: Option<String>,
109 #[serde(skip_serializing_if = "Option::is_none")]
113 pub node: Option<String>,
114 #[serde(skip_serializing_if = "Option::is_none")]
117 pub prediction_scope: Option<EvaluationScope>,
118 #[serde(skip_serializing_if = "non_finite_time_or_none")]
121 pub time_s: Option<f32>,
122 #[serde(skip_serializing_if = "non_finite_value_or_none")]
125 pub measured: Option<Value>,
126 #[serde(skip_serializing_if = "non_finite_value_or_none")]
129 pub expected: Option<Value>,
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub members: Option<Vec<MemberMeasurement>>,
133 pub message: String,
135}
136
137impl Finding {
138 pub fn new(check_id: &'static str, severity: Severity, message: impl Into<String>) -> Self {
140 Self {
141 check_id,
142 severity,
143 clip: None,
144 bone: None,
145 node: None,
146 prediction_scope: None,
147 time_s: None,
148 measured: None,
149 expected: None,
150 members: None,
151 message: message.into(),
152 }
153 }
154
155 pub fn clip(mut self, clip: impl Into<String>) -> Self {
157 self.clip = Some(clip.into());
158 self
159 }
160
161 pub fn bone(mut self, bone: impl Into<String>) -> Self {
163 self.bone = Some(bone.into());
164 self
165 }
166
167 pub fn node(mut self, node: impl Into<String>) -> Self {
169 self.node = Some(node.into());
170 self
171 }
172
173 pub fn prediction_scope(mut self, scope: EvaluationScope) -> Self {
179 self.prediction_scope = Some(scope);
180 self
181 }
182
183 pub fn time(mut self, t: f32) -> Self {
185 self.time_s = t.is_finite().then_some(t);
186 self
187 }
188
189 pub fn measured(mut self, v: impl Into<Value>) -> Self {
191 let value = v.into();
192 self.measured = value.is_finite().then_some(value);
193 self
194 }
195
196 pub fn expected(mut self, v: impl Into<Value>) -> Self {
198 let value = v.into();
199 self.expected = value.is_finite().then_some(value);
200 self
201 }
202
203 pub fn members(mut self, mut members: Vec<MemberMeasurement>) -> Self {
208 for member in &mut members {
209 member.measurements.retain(|_, value| value.is_finite());
210 }
211 self.members = Some(members);
212 self
213 }
214}
215
216impl Value {
217 fn is_finite(&self) -> bool {
218 match self {
219 Self::Number(number) => number.is_finite(),
220 Self::Text(_) => true,
221 }
222 }
223}
224
225fn non_finite_time_or_none(value: &Option<f32>) -> bool {
226 value.is_none_or(|time| !time.is_finite())
227}
228
229fn non_finite_value_or_none(value: &Option<Value>) -> bool {
230 value.as_ref().is_none_or(|value| !value.is_finite())
231}
232
233impl From<f64> for Value {
234 fn from(n: f64) -> Self {
235 Value::Number(n)
236 }
237}
238
239impl From<f32> for Value {
240 fn from(n: f32) -> Self {
241 Value::Number(n as f64)
242 }
243}
244
245impl From<&str> for Value {
246 fn from(s: &str) -> Self {
247 Value::Text(s.to_owned())
248 }
249}
250
251impl From<String> for Value {
252 fn from(s: String) -> Self {
253 Value::Text(s)
254 }
255}