use crate::spc::SpcAlertEntry;
use crate::util::PyHelperFuncs;
use crate::{custom::ComparisonMetricAlert, psi::PsiFeatureAlert};
use chrono::{DateTime, Utc};
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
#[pyclass(eq)]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
pub enum AlertThreshold {
#[default]
Below,
Above,
Outside,
}
impl Display for AlertThreshold {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
#[pymethods]
impl AlertThreshold {
#[staticmethod]
pub fn from_value(value: &str) -> Option<Self> {
match value.to_lowercase().as_str() {
"below" => Some(AlertThreshold::Below),
"above" => Some(AlertThreshold::Above),
"outside" => Some(AlertThreshold::Outside),
_ => None,
}
}
pub fn __str__(&self) -> String {
match self {
AlertThreshold::Below => "Below".to_string(),
AlertThreshold::Above => "Above".to_string(),
AlertThreshold::Outside => "Outside".to_string(),
}
}
}
#[pyclass]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct AlertCondition {
#[pyo3(get, set)]
pub baseline_value: f64,
#[pyo3(get, set)]
pub alert_threshold: AlertThreshold,
#[pyo3(get, set)]
pub delta: Option<f64>,
}
#[pymethods]
impl AlertCondition {
#[new]
#[pyo3(signature = (baseline_value, alert_threshold, delta=None))]
pub fn new(baseline_value: f64, alert_threshold: AlertThreshold, delta: Option<f64>) -> Self {
Self {
baseline_value,
alert_threshold,
delta,
}
}
pub fn upper_bound(&self) -> f64 {
match self.delta {
Some(d) => self.baseline_value + d,
None => self.baseline_value,
}
}
pub fn lower_bound(&self) -> f64 {
match self.delta {
Some(d) => self.baseline_value - d,
None => self.baseline_value,
}
}
pub fn should_alert(&self, value: f64) -> bool {
match (&self.alert_threshold, self.delta) {
(AlertThreshold::Above, Some(d)) => value > (self.baseline_value + d),
(AlertThreshold::Above, None) => value > self.baseline_value,
(AlertThreshold::Below, Some(d)) => value < (self.baseline_value - d),
(AlertThreshold::Below, None) => value < self.baseline_value,
(AlertThreshold::Outside, Some(d)) => {
value < (self.baseline_value - d) || value > (self.baseline_value + d)
}
(AlertThreshold::Outside, None) => value != self.baseline_value,
}
}
pub fn __str__(&self) -> String {
PyHelperFuncs::__str__(self)
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub enum AlertMap {
Custom(ComparisonMetricAlert),
Psi(PsiFeatureAlert),
Spc(SpcAlertEntry),
GenAI(ComparisonMetricAlert),
#[default]
None,
}
impl AlertMap {
pub fn is_none(&self) -> bool {
matches!(self, AlertMap::None)
}
pub fn entity_name(&self) -> &str {
match self {
AlertMap::Custom(alert) => &alert.metric_name,
AlertMap::Psi(alert) => &alert.feature,
AlertMap::Spc(alert) => &alert.feature,
AlertMap::GenAI(alert) => &alert.metric_name,
AlertMap::None => "none",
}
}
}
#[pyclass]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Alert {
#[pyo3(get)]
pub created_at: DateTime<Utc>,
#[pyo3(get)]
pub entity_name: String,
pub alert: AlertMap,
#[pyo3(get)]
pub id: i32,
#[pyo3(get)]
pub active: bool,
}
#[cfg(feature = "server")]
impl<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> for Alert {
fn from_row(row: &'r sqlx::postgres::PgRow) -> Result<Self, sqlx::Error> {
use sqlx::Row;
let alert_value: serde_json::Value = row.try_get("alert")?;
let alert: AlertMap = serde_json::from_value(alert_value).unwrap_or_default();
Ok(Alert {
created_at: row.try_get("created_at")?,
alert,
entity_name: row.try_get("entity_name")?,
id: row.try_get("id")?,
active: row.try_get("active")?,
})
}
}
#[pymethods]
impl Alert {
pub fn __str__(&self) -> String {
PyHelperFuncs::__str__(self)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Alerts {
pub alerts: Vec<Alert>,
}