github-actions
Deploy API from GitHub Actions
2477a46
Raw
History Blame Contribute Delete
5.95 kB
import math
from src.api.monitoring.db import get_connection
def get_input_drift_summary():
query = """
SELECT
input_json
FROM api_requests
WHERE input_json IS NOT NULL;
"""
with get_connection() as conn:
with conn.cursor() as cursor:
cursor.execute(query)
rows = cursor.fetchall()
inputs = [row[0] for row in rows]
numeric_fields = ["temp", "rainfall_mm", "pesticides", "trend"]
summary = {}
for field in numeric_fields:
values = [
float(input_data[field])
for input_data in inputs
if field in input_data and input_data[field] is not None
]
if values:
summary[field] = {
"count": len(values),
"mean": round(sum(values) / len(values), 2),
"min": round(min(values), 2),
"max": round(max(values), 2),
}
else:
summary[field] = {
"count": 0,
"mean": None,
"min": None,
"max": None,
}
return summary
import math
def _calculate_psi(reference_values, production_values, bins=10):
"""
Calcule le PSI entre une distribution de référence
et une distribution de production.
"""
if not reference_values or not production_values:
return None
min_value = min(reference_values)
max_value = max(reference_values)
if min_value == max_value:
return 0
bin_width = (max_value - min_value) / bins
psi = 0
for i in range(bins):
lower = min_value + i * bin_width
upper = min_value + (i + 1) * bin_width
if i == bins - 1:
ref_count = sum(lower <= value <= upper for value in reference_values)
prod_count = sum(lower <= value <= upper for value in production_values)
else:
ref_count = sum(lower <= value < upper for value in reference_values)
prod_count = sum(lower <= value < upper for value in production_values)
ref_pct = ref_count / len(reference_values)
prod_pct = prod_count / len(production_values)
ref_pct = max(ref_pct, 0.0001)
prod_pct = max(prod_pct, 0.0001)
psi += (prod_pct - ref_pct) * math.log(prod_pct / ref_pct)
return round(psi, 4)
def _get_psi_status(psi):
if psi is None:
return "Données insuffisantes"
if psi < 0.1:
return "Stable"
if psi < 0.2:
return "Légère dérive"
return "Drift important"
def get_numeric_psi():
features = ["temp", "rainfall_mm", "pesticides", "trend"]
results = []
with get_connection() as conn:
with conn.cursor() as cursor:
for feature in features:
cursor.execute(
"""
SELECT feature_value
FROM reference_distributions
WHERE feature_name = %s
AND source = 'train';
""",
(feature,)
)
reference_values = [
float(row[0])
for row in cursor.fetchall()
if row[0] is not None
]
cursor.execute(
"""
SELECT input_json
FROM api_requests
WHERE input_json IS NOT NULL;
"""
)
production_inputs = cursor.fetchall()
production_values = []
for row in production_inputs:
input_data = row[0]
if feature in input_data and input_data[feature] is not None:
production_values.append(float(input_data[feature]))
psi = _calculate_psi(reference_values, production_values)
results.append(
{
"feature": feature,
"psi": psi,
"status": _get_psi_status(psi),
"reference_count": len(reference_values),
"production_count": len(production_values),
}
)
return results
def get_distribution_comparison(feature_name):
"""
Compare la distribution d'une variable entre :
- la référence d'entraînement
- les données reçues en production
"""
allowed_features = [
"temp",
"rainfall_mm",
"pesticides",
"trend",
]
if feature_name not in allowed_features:
raise ValueError(
f"Variable non autorisée : {feature_name}"
)
with get_connection() as conn:
with conn.cursor() as cursor:
cursor.execute(
"""
SELECT feature_value
FROM reference_distributions
WHERE feature_name = %s
AND source = 'train';
""",
(feature_name,)
)
reference_values = [
float(row[0])
for row in cursor.fetchall()
if row[0] is not None
]
cursor.execute(
"""
SELECT input_json
FROM api_requests
WHERE input_json IS NOT NULL;
"""
)
production_inputs = cursor.fetchall()
production_values = []
for row in production_inputs:
input_data = row[0]
if (
feature_name in input_data
and input_data[feature_name] is not None
):
production_values.append(
float(input_data[feature_name])
)
return {
"feature": feature_name,
"reference": reference_values,
"production": production_values,
}