pret_a_depenser / scripts /initialisation_tables /create_monitoring_tables.py
SteGONZALEZ's picture
Deploy FastAPI API
0d78ca6
Raw
History Blame Contribute Delete
12 kB
"""
Script de création des tables PostgreSQL de monitoring.
Ce module crée les tables utilisées pour suivre un modèle
de machine learning en production.
Objectif
--------
Mettre en place une couche de monitoring structurée afin de :
- suivre les versions de modèles déployées
- conserver les snapshots de features observées
- stocker les métriques de dérive
- historiser les métriques de performance
- centraliser les alertes
Tables créées
-------------
- model_registry
- feature_store_monitoring
- drift_metrics
- evaluation_metrics
- alerts
"""
from __future__ import annotations
import os
from dotenv import load_dotenv
from sqlalchemy import create_engine, text
# =============================================================================
# Chargement des variables d'environnement
# =============================================================================
load_dotenv()
# =============================================================================
# Configuration
# =============================================================================
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
raise ValueError("DATABASE_URL n'est pas défini dans les variables d'environnement.")
# =============================================================================
# SQL - Tables
# =============================================================================
CREATE_MODEL_REGISTRY_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS model_registry (
id BIGSERIAL PRIMARY KEY,
model_name TEXT NOT NULL,
model_version TEXT NOT NULL,
stage TEXT NOT NULL CHECK (stage IN ('dev', 'staging', 'production', 'archived')),
run_id TEXT,
source_path TEXT,
training_data_version TEXT,
feature_list JSONB,
hyperparameters JSONB,
metrics JSONB,
deployed_at TIMESTAMPTZ,
is_active BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (model_name, model_version)
);
"""
CREATE_FEATURE_STORE_MONITORING_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS feature_store_monitoring (
id BIGSERIAL PRIMARY KEY,
request_id TEXT,
client_id BIGINT,
model_name TEXT NOT NULL,
model_version TEXT NOT NULL,
feature_name TEXT NOT NULL,
feature_value TEXT,
feature_type TEXT,
source_table TEXT,
snapshot_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
"""
CREATE_DRIFT_METRICS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS drift_metrics (
id BIGSERIAL PRIMARY KEY,
model_name TEXT NOT NULL,
model_version TEXT NOT NULL,
feature_name TEXT NOT NULL,
metric_name TEXT NOT NULL,
reference_window_start TIMESTAMPTZ,
reference_window_end TIMESTAMPTZ,
current_window_start TIMESTAMPTZ,
current_window_end TIMESTAMPTZ,
metric_value DOUBLE PRECISION NOT NULL,
threshold_value DOUBLE PRECISION,
drift_detected BOOLEAN NOT NULL DEFAULT FALSE,
details JSONB,
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
"""
CREATE_EVALUATION_METRICS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS evaluation_metrics (
id BIGSERIAL PRIMARY KEY,
model_name TEXT NOT NULL,
model_version TEXT NOT NULL,
dataset_name TEXT NOT NULL,
window_start TIMESTAMPTZ,
window_end TIMESTAMPTZ,
roc_auc DOUBLE PRECISION,
pr_auc DOUBLE PRECISION,
precision_score DOUBLE PRECISION,
recall_score DOUBLE PRECISION,
f1_score DOUBLE PRECISION,
fbeta_score DOUBLE PRECISION,
business_cost DOUBLE PRECISION,
tn INTEGER CHECK (tn IS NULL OR tn >= 0),
fp INTEGER CHECK (fp IS NULL OR fp >= 0),
fn INTEGER CHECK (fn IS NULL OR fn >= 0),
tp INTEGER CHECK (tp IS NULL OR tp >= 0),
sample_size INTEGER CHECK (sample_size IS NULL OR sample_size >= 0),
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
"""
CREATE_ALERTS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS alerts (
id BIGSERIAL PRIMARY KEY,
alert_type TEXT NOT NULL,
severity TEXT NOT NULL CHECK (severity IN ('low', 'medium', 'high', 'critical')),
model_name TEXT,
model_version TEXT,
feature_name TEXT,
title TEXT NOT NULL,
message TEXT NOT NULL,
context JSONB,
status TEXT NOT NULL DEFAULT 'open'
CHECK (status IN ('open', 'acknowledged', 'resolved')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
acknowledged_at TIMESTAMPTZ,
resolved_at TIMESTAMPTZ
);
"""
# =============================================================================
# SQL - Index optimisés
# =============================================================================
CREATE_INDEXES_SQL = [
# -------------------------------------------------------------------------
# model_registry
# -------------------------------------------------------------------------
"""
CREATE INDEX IF NOT EXISTS idx_model_registry_name_version
ON model_registry(model_name, model_version);
""",
"""
CREATE INDEX IF NOT EXISTS idx_model_registry_active
ON model_registry(is_active);
""",
"""
CREATE INDEX IF NOT EXISTS idx_model_registry_stage
ON model_registry(stage);
""",
"""
CREATE INDEX IF NOT EXISTS idx_model_registry_active_model
ON model_registry(model_name, is_active);
""",
"""
CREATE INDEX IF NOT EXISTS idx_model_registry_deployed_at
ON model_registry(deployed_at DESC);
""",
# -------------------------------------------------------------------------
# feature_store_monitoring
# -------------------------------------------------------------------------
"""
CREATE INDEX IF NOT EXISTS idx_feature_store_monitoring_request_id
ON feature_store_monitoring(request_id);
""",
"""
CREATE INDEX IF NOT EXISTS idx_feature_store_monitoring_client_id
ON feature_store_monitoring(client_id);
""",
"""
CREATE INDEX IF NOT EXISTS idx_feature_store_monitoring_feature_name
ON feature_store_monitoring(feature_name);
""",
"""
CREATE INDEX IF NOT EXISTS idx_feature_store_monitoring_snapshot_timestamp
ON feature_store_monitoring(snapshot_timestamp DESC);
""",
"""
CREATE INDEX IF NOT EXISTS idx_feature_store_model_version_time
ON feature_store_monitoring(model_name, model_version, snapshot_timestamp DESC);
""",
"""
CREATE INDEX IF NOT EXISTS idx_feature_store_request_feature
ON feature_store_monitoring(request_id, feature_name);
""",
"""
CREATE INDEX IF NOT EXISTS idx_feature_store_client_time
ON feature_store_monitoring(client_id, snapshot_timestamp DESC);
""",
"""
CREATE INDEX IF NOT EXISTS idx_feature_store_source_table
ON feature_store_monitoring(source_table);
""",
# -------------------------------------------------------------------------
# drift_metrics
# -------------------------------------------------------------------------
"""
CREATE INDEX IF NOT EXISTS idx_drift_metrics_model_version
ON drift_metrics(model_name, model_version);
""",
"""
CREATE INDEX IF NOT EXISTS idx_drift_metrics_feature_name
ON drift_metrics(feature_name);
""",
"""
CREATE INDEX IF NOT EXISTS idx_drift_metrics_detected
ON drift_metrics(drift_detected);
""",
"""
CREATE INDEX IF NOT EXISTS idx_drift_metrics_computed_at
ON drift_metrics(computed_at DESC);
""",
"""
CREATE INDEX IF NOT EXISTS idx_drift_metrics_model_time
ON drift_metrics(model_name, model_version, computed_at DESC);
""",
"""
CREATE INDEX IF NOT EXISTS idx_drift_metrics_model_feature
ON drift_metrics(model_name, model_version, feature_name);
""",
"""
CREATE INDEX IF NOT EXISTS idx_drift_metrics_detected_model
ON drift_metrics(model_name, model_version, drift_detected);
""",
# -------------------------------------------------------------------------
# evaluation_metrics
# -------------------------------------------------------------------------
"""
CREATE INDEX IF NOT EXISTS idx_evaluation_metrics_model_version
ON evaluation_metrics(model_name, model_version);
""",
"""
CREATE INDEX IF NOT EXISTS idx_evaluation_metrics_dataset_name
ON evaluation_metrics(dataset_name);
""",
"""
CREATE INDEX IF NOT EXISTS idx_evaluation_metrics_computed_at
ON evaluation_metrics(computed_at DESC);
""",
"""
CREATE INDEX IF NOT EXISTS idx_evaluation_metrics_model_dataset_time
ON evaluation_metrics(model_name, model_version, dataset_name, computed_at DESC);
""",
# -------------------------------------------------------------------------
# alerts
# -------------------------------------------------------------------------
"""
CREATE INDEX IF NOT EXISTS idx_alerts_status
ON alerts(status);
""",
"""
CREATE INDEX IF NOT EXISTS idx_alerts_severity
ON alerts(severity);
""",
"""
CREATE INDEX IF NOT EXISTS idx_alerts_created_at
ON alerts(created_at DESC);
""",
"""
CREATE INDEX IF NOT EXISTS idx_alerts_model_version_status
ON alerts(model_name, model_version, status);
""",
"""
CREATE INDEX IF NOT EXISTS idx_alerts_open_recent
ON alerts(created_at DESC)
WHERE status = 'open';
""",
]
# =============================================================================
# Fonctions de création
# =============================================================================
def create_model_registry_table(engine) -> None:
"""Crée la table `model_registry`."""
with engine.begin() as connection:
connection.execute(text(CREATE_MODEL_REGISTRY_TABLE_SQL))
print("Table 'model_registry' créée ou déjà existante.")
def create_feature_store_monitoring_table(engine) -> None:
"""Crée la table `feature_store_monitoring`."""
with engine.begin() as connection:
connection.execute(text(CREATE_FEATURE_STORE_MONITORING_TABLE_SQL))
print("Table 'feature_store_monitoring' créée ou déjà existante.")
def create_drift_metrics_table(engine) -> None:
"""Crée la table `drift_metrics`."""
with engine.begin() as connection:
connection.execute(text(CREATE_DRIFT_METRICS_TABLE_SQL))
print("Table 'drift_metrics' créée ou déjà existante.")
def create_evaluation_metrics_table(engine) -> None:
"""Crée la table `evaluation_metrics`."""
with engine.begin() as connection:
connection.execute(text(CREATE_EVALUATION_METRICS_TABLE_SQL))
print("Table 'evaluation_metrics' créée ou déjà existante.")
def create_alerts_table(engine) -> None:
"""Crée la table `alerts`."""
with engine.begin() as connection:
connection.execute(text(CREATE_ALERTS_TABLE_SQL))
print("Table 'alerts' créée ou déjà existante.")
def create_indexes(engine) -> None:
"""Crée les index de monitoring."""
with engine.begin() as connection:
for sql in CREATE_INDEXES_SQL:
connection.execute(text(sql))
print("Index monitoring créés ou déjà existants.")
# =============================================================================
# Orchestration
# =============================================================================
TABLE_CREATORS = [
create_model_registry_table,
create_feature_store_monitoring_table,
create_drift_metrics_table,
create_evaluation_metrics_table,
create_alerts_table,
]
def create_monitoring_tables(engine) -> None:
"""Crée toutes les tables de monitoring puis leurs index."""
for create_table in TABLE_CREATORS:
create_table(engine)
create_indexes(engine)
def main() -> None:
"""Point d'entrée du script."""
print("Connexion à PostgreSQL...")
engine = create_engine(DATABASE_URL, echo=False)
print("Connexion établie.")
create_monitoring_tables(engine)
print("Création des tables de monitoring terminée.")
if __name__ == "__main__":
main()