1use serde::{Deserialize, Serialize};
4
5use crate::metric::{MetricClass, MetricDecision};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase", deny_unknown_fields)]
9pub struct CompareReportsRequest {
10 pub report_id: String,
11 pub baseline_report_id: String,
12 #[serde(default)]
13 pub minimum_pass_rate: f64,
14 #[serde(default = "default_max_infra_rate")]
15 pub maximum_infra_failure_rate: f64,
16 #[serde(default = "default_significance")]
17 pub significance_level: f64,
18 #[serde(default)]
19 pub minimum_effect: f64,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub policy: Option<GatePolicy>,
23}
24
25impl CompareReportsRequest {
26 pub fn effective_policy(&self) -> GatePolicy {
29 self.policy.clone().unwrap_or_else(|| GatePolicy {
30 minimum_pass_rate: self.minimum_pass_rate,
31 maximum_infra_failure_rate: self.maximum_infra_failure_rate,
32 significance_level: self.significance_level,
33 minimum_effect: self.minimum_effect,
34 ..GatePolicy::default()
35 })
36 }
37}
38
39fn default_max_infra_rate() -> f64 {
40 1.0
41}
42
43fn default_significance() -> f64 {
44 0.05
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase", deny_unknown_fields)]
51pub struct GatePolicy {
52 #[serde(default)]
54 pub minimum_pass_rate: f64,
55 #[serde(default = "default_max_infra_rate")]
56 pub maximum_infra_failure_rate: f64,
57 #[serde(default = "default_significance")]
58 pub significance_level: f64,
59 #[serde(default)]
61 pub minimum_effect: f64,
62 #[serde(default)]
64 pub goal_minimum_rate: f64,
65 #[serde(default)]
67 pub overrides: Vec<MetricThreshold>,
68}
69
70impl Default for GatePolicy {
71 fn default() -> Self {
72 Self {
73 minimum_pass_rate: 0.0,
74 maximum_infra_failure_rate: 1.0,
75 significance_level: 0.05,
76 minimum_effect: 0.0,
77 goal_minimum_rate: 0.0,
78 overrides: Vec::new(),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase", deny_unknown_fields)]
85pub struct MetricThreshold {
86 pub evaluator_id: String,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub minimum_rate: Option<f64>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub maximum_regression: Option<f64>,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
95#[serde(rename_all = "snake_case")]
96pub enum ViolationReason {
97 GuardrailFailed,
98 BelowMinimumRate,
99 SignificantRegression,
100 InfrastructureFailureRate,
101 AggregatePassRate,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct MetricViolation {
107 pub evaluator_id: String,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub evaluator_version: Option<String>,
110 pub class: MetricClass,
111 pub reason: ViolationReason,
112 pub decision: MetricDecision,
113 pub observed_rate: f64,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub baseline_rate: Option<f64>,
116 pub delta: f64,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub p_value: Option<f64>,
119 pub detail: String,
120}
121
122#[derive(Debug, Clone, Default, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct GateDecision {
126 pub passed: bool,
127 #[serde(default)]
128 pub blocking_metrics: Vec<MetricViolation>,
129 #[serde(default)]
130 pub warnings: Vec<MetricViolation>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct CaseOutcomeDiff {
136 pub case_id: String,
137 pub case_version: String,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub variant_id: Option<String>,
140 pub baseline_passed: bool,
141 pub current_passed: bool,
142 pub flaky_signal: bool,
143 #[serde(default)]
144 pub historical_flake_rate: f64,
145 #[serde(default)]
146 pub historical_samples: u64,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct MetricComparison {
153 pub evaluator_id: String,
154 pub evaluator_version: String,
155 pub class: MetricClass,
156 pub current_rate: f64,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 pub baseline_rate: Option<f64>,
159 pub delta: f64,
160 pub p_value: f64,
161 pub statistically_significant: bool,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct ReportComparison {
167 pub report_id: String,
168 pub baseline_report_id: String,
169 pub pass_rate_delta: f64,
170 pub infra_failure_rate_delta: f64,
171 pub pass_rate_confidence_low: f64,
172 pub pass_rate_confidence_high: f64,
173 pub effect_confidence_low: f64,
174 pub effect_confidence_high: f64,
175 pub p_value: f64,
176 pub statistically_significant: bool,
177 pub changed_cases: Vec<CaseOutcomeDiff>,
178 #[serde(default)]
179 pub metric_comparisons: Vec<MetricComparison>,
180 pub gate_passed: bool,
182 #[serde(default)]
183 pub gate: GateDecision,
184}