kronector / ml /drift_detection.py
Prathamesh Bhamare
feat: wire frontend buttons to API + fix HF Spaces config
77f1d1a
Raw
History Blame Contribute Delete
4.49 kB
"""
KRONECTOR - Drift Detection Pipeline
Monitors incoming F1 data for Concept and Data Drift using Evidently AI.
"""
import logging
import pandas as pd
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset, TargetDriftPreset
from evidently import ColumnMapping
from ml.feature_engineering import prepare_model_data
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(name)s | %(levelname)s | %(message)s")
def detect_drift(
data_path: str = "data_output/fastf1_races.parquet",
current_season: int = 2026,
report_output_path: str = "data_output/drift_report.html"
) -> bool:
"""
Detects drift between the reference dataset (historical) and the current dataset.
Returns True if significant drift is detected.
"""
try:
df = pd.read_parquet(data_path)
except FileNotFoundError:
logger.error(f"Data not found at {data_path}")
return False
# To detect drift accurately, we should evaluate the model's engineered features.
# We use prepare_model_data to impute missing values and encode categoricals.
logger.info("Preparing features for drift detection...")
bundle, _ = prepare_model_data(df)
# Re-attach season for splitting
eval_df = bundle.X.copy()
eval_df["season"] = bundle.metadata["season"]
eval_df["win_probability"] = bundle.y
# Define reference (e.g., up to 2025) and current (2026)
reference = eval_df[eval_df["season"] < current_season].copy()
current = eval_df[eval_df["season"] >= current_season].copy()
# Drop the season column as it's not a model feature we want to check for drift
reference = reference.drop(columns=["season"])
current = current.drop(columns=["season"])
if current.empty:
logger.warning(f"No data available for current season {current_season}. Skipping drift detection.")
return False
logger.info(f"Running drift detection: Reference ({len(reference)} rows) vs Current ({len(current)} rows)")
# Define column mapping
target = "win_probability"
prediction = None
column_mapping = ColumnMapping()
column_mapping.target = target
column_mapping.prediction = prediction
# Since we passed the data through prepare_model_data, all categories are now numerically encoded.
# We treat them as numerical features for Evidently so it can detect distributional shifts in the encoded space.
numerical_features = [col for col in reference.columns if col != target]
column_mapping.numerical_features = numerical_features
column_mapping.categorical_features = []
# Create Evidently Report
report = Report(metrics=[
DataDriftPreset(),
TargetDriftPreset(),
])
logger.info("Generating Evidently AI Drift Report...")
report.run(reference_data=reference, current_data=current, column_mapping=column_mapping)
# Save HTML report
import os
os.makedirs(os.path.dirname(report_output_path), exist_ok=True)
report.save_html(report_output_path)
logger.info(f"Drift report saved to {report_output_path}")
# Parse JSON results to determine if drift was detected
result = report.as_dict()
# Check data drift
data_drift = False
target_drift = False
try:
# Evidently structure navigation
for metric in result["metrics"]:
if metric["metric"] == "DataDriftPreset":
if metric["result"]["dataset_drift"]:
data_drift = True
logger.warning("🚨 DATA DRIFT DETECTED!")
elif metric["metric"] == "TargetDriftPreset":
if metric["result"]["drift_detected"]:
target_drift = True
logger.warning("🚨 TARGET DRIFT DETECTED!")
except KeyError as e:
logger.warning(f"Could not parse drift results exactly: {e}")
return data_drift or target_drift
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Run Drift Detection")
parser.add_argument("--data-path", default="data_output/fastf1_races.parquet")
parser.add_argument("--current-season", type=int, default=2026)
args = parser.parse_args()
drift_detected = detect_drift(
data_path=args.data_path,
current_season=args.current_season
)
print(f"\nFinal Result -> Drift Detected: {drift_detected}")