1use crate::spc::SpcAlertEntry;
2use crate::util::PyHelperFuncs;
3use crate::{custom::ComparisonMetricAlert, psi::PsiFeatureAlert};
4use chrono::{DateTime, Utc};
5use pyo3::prelude::*;
6use serde::{Deserialize, Serialize};
7use std::fmt::{Display, Formatter};
8
9#[pyclass(eq)]
10#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
11pub enum AlertThreshold {
12 #[default]
13 Below,
14 Above,
15 Outside,
16}
17
18impl Display for AlertThreshold {
19 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
20 write!(f, "{self:?}")
21 }
22}
23
24#[pymethods]
25impl AlertThreshold {
26 #[staticmethod]
27 pub fn from_value(value: &str) -> Option<Self> {
28 match value.to_lowercase().as_str() {
29 "below" => Some(AlertThreshold::Below),
30 "above" => Some(AlertThreshold::Above),
31 "outside" => Some(AlertThreshold::Outside),
32 _ => None,
33 }
34 }
35
36 pub fn __str__(&self) -> String {
37 match self {
38 AlertThreshold::Below => "Below".to_string(),
39 AlertThreshold::Above => "Above".to_string(),
40 AlertThreshold::Outside => "Outside".to_string(),
41 }
42 }
43}
44
45#[pyclass]
46#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
47pub struct AlertCondition {
48 #[pyo3(get, set)]
50 pub baseline_value: f64,
51
52 #[pyo3(get, set)]
53 pub alert_threshold: AlertThreshold,
54
55 #[pyo3(get, set)]
61 pub delta: Option<f64>,
62}
63
64#[pymethods]
65impl AlertCondition {
66 #[new]
67 #[pyo3(signature = (baseline_value, alert_threshold, delta=None))]
68 pub fn new(baseline_value: f64, alert_threshold: AlertThreshold, delta: Option<f64>) -> Self {
69 Self {
70 baseline_value,
71 alert_threshold,
72 delta,
73 }
74 }
75
76 pub fn upper_bound(&self) -> f64 {
78 match self.delta {
79 Some(d) => self.baseline_value + d,
80 None => self.baseline_value,
81 }
82 }
83
84 pub fn lower_bound(&self) -> f64 {
86 match self.delta {
87 Some(d) => self.baseline_value - d,
88 None => self.baseline_value,
89 }
90 }
91
92 pub fn should_alert(&self, value: f64) -> bool {
94 match (&self.alert_threshold, self.delta) {
95 (AlertThreshold::Above, Some(d)) => value > (self.baseline_value + d),
96 (AlertThreshold::Above, None) => value > self.baseline_value,
97 (AlertThreshold::Below, Some(d)) => value < (self.baseline_value - d),
98 (AlertThreshold::Below, None) => value < self.baseline_value,
99 (AlertThreshold::Outside, Some(d)) => {
100 value < (self.baseline_value - d) || value > (self.baseline_value + d)
101 }
102 (AlertThreshold::Outside, None) => value != self.baseline_value,
103 }
104 }
105
106 pub fn __str__(&self) -> String {
107 PyHelperFuncs::__str__(self)
108 }
109}
110
111#[derive(Serialize, Deserialize, Debug, Default, Clone)]
112pub enum AlertMap {
113 Custom(ComparisonMetricAlert),
114 Psi(PsiFeatureAlert),
115 Spc(SpcAlertEntry),
116 GenAI(ComparisonMetricAlert),
117
118 #[default]
119 None,
120}
121
122impl AlertMap {
123 pub fn is_none(&self) -> bool {
124 matches!(self, AlertMap::None)
125 }
126
127 pub fn entity_name(&self) -> &str {
128 match self {
129 AlertMap::Custom(alert) => &alert.metric_name,
130 AlertMap::Psi(alert) => &alert.feature,
131 AlertMap::Spc(alert) => &alert.feature,
132 AlertMap::GenAI(alert) => &alert.metric_name,
133 AlertMap::None => "none",
134 }
135 }
136}
137
138#[pyclass]
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct Alert {
141 #[pyo3(get)]
142 pub created_at: DateTime<Utc>,
143
144 #[pyo3(get)]
145 pub entity_name: String,
146
147 pub alert: AlertMap,
148
149 #[pyo3(get)]
150 pub id: i32,
151
152 #[pyo3(get)]
153 pub active: bool,
154}
155
156#[cfg(feature = "server")]
157impl<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> for Alert {
158 fn from_row(row: &'r sqlx::postgres::PgRow) -> Result<Self, sqlx::Error> {
159 use sqlx::Row;
160
161 let alert_value: serde_json::Value = row.try_get("alert")?;
162 let alert: AlertMap = serde_json::from_value(alert_value).unwrap_or_default();
163
164 Ok(Alert {
165 created_at: row.try_get("created_at")?,
166 alert,
167 entity_name: row.try_get("entity_name")?,
168 id: row.try_get("id")?,
169 active: row.try_get("active")?,
170 })
171 }
172}
173
174#[pymethods]
175impl Alert {
176 pub fn __str__(&self) -> String {
177 PyHelperFuncs::__str__(self)
179 }
180}
181
182#[derive(Serialize, Deserialize, Debug, Clone)]
183pub struct Alerts {
184 pub alerts: Vec<Alert>,
185}