ワンクリックで
drift-detection
Run and interpret DATA drift (PSI) AND CONCEPT drift (sliced performance) for an ML service
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Run and interpret DATA drift (PSI) AND CONCEPT drift (sliced performance) for an ML service
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Scaffold and run batch scoring jobs (CronJob + Parquet output) that reuse the service's model + feature-engineering code without opening the live API
Root-cause a performance alert using sliced metrics + ground-truth
Review cloud costs against budget and identify optimization opportunities
Debug ML inference issues — latency spikes, wrong predictions, event loop blocking
Deploy ML service to EKS with Kustomize overlays and IRSA
Deploy ML service to GKE with Kustomize overlays and Workload Identity
| name | drift-detection |
| description | Run and interpret DATA drift (PSI) AND CONCEPT drift (sliced performance) for an ML service |
| allowed-tools | ["Read","Grep","Glob","Bash(python:*)","Bash(kubectl:*)","Bash(curl:*)"] |
| when_to_use | Use when checking data OR concept drift, interpreting PSI/AUC metrics, configuring drift thresholds, diagnosing sliced alerts, or deciding whether retraining is needed. Examples: 'check drift for bankchurn', 'PSI alert fired', 'AUC dropped', 'country=ES showing performance regression', 'interpret the performance report' |
| argument-hint | <service-name> |
| arguments | ["service-name"] |
| authorization_mode | {"run_psi_check":"AUTO","run_sliced_metrics":"AUTO","interpret_thresholds":"AUTO","trigger_retrain":"CONSULT","silence_alert":"STOP","escalation_triggers":[{"psi_over_2x_threshold":"STOP"},{"sliced_auc_drop_over_5pp":"CONSULT"},{"heartbeat_missing":"CONSULT"}]} |
Two complementary layers (ADR-006):
Always investigate data drift FIRST (cheaper, faster). Escalate to concept drift analysis when (a) PSI alert fires and you need to confirm impact, or (b) a performance alert fires directly (AUC below threshold).
| PSI Value | Status | Action | Exit Code |
|---|---|---|---|
| < 0.10 | Stable | No action | 0 |
| 0.10 – 0.20 | Warning | Monitor, increase check frequency | 1 |
| > 0.20 | Alert | Trigger retraining | 2 |
ALWAYS use quantile-based bins (not uniform):
breakpoints = np.percentile(reference, np.linspace(0, 100, bins + 1))
Uniform bins can produce empty bins at extremes → PSI dominated by epsilon noise.
| Feature Type | Problem with PSI | Alternative |
|---|---|---|
| Time series (seasonal) | PSI flags every seasonal change as "drift" | Year-over-Year comparison (same period last year) |
| Text/NLP features | PSI not meaningful for text | OOV (Out-of-Vocabulary) rate: warning > 20%, alert > 35% |
| Low-cardinality categorical | Quantile bins don't work with 3-5 categories | Categorical PSI variant: bins = unique categories |
| Boolean features | Only 2 bins → unstable PSI | Simple proportion test (chi-squared) |
exit 0 → all features stableexit 1 → warning-level drift (monitor)exit 2 → alert-level drift (retraining needed, GitHub Issue created)python src/{service}/monitoring/drift_detection.py \
--reference data/reference/{service}_reference.csv \
--current data/production/{service}_latest.csv \
--output drift_report.json
For each feature, review:
{
"feature_name": "age",
"psi": 0.15,
"status": "warning",
"reference_mean": 45.2,
"current_mean": 48.1,
"bins_detail": [...]
}
| Pattern | Likely Cause | Action |
|---|---|---|
| Single feature PSI high | Upstream data change | Investigate ETL pipeline |
| All features PSI high | Data source change | Full retraining |
| Temporal features PSI high | Seasonal pattern | Use YoY comparison |
| Categorical OOV rate up | New categories in production | Update schema + retrain |
Each feature needs per-feature thresholds with domain reasoning:
THRESHOLDS = {
"stable_feature": {"warning": 0.10, "alert": 0.20},
"volatile_feature": {"warning": 0.15, "alert": 0.30}, # Higher natural variance
"categorical_feature": {"warning": 0.10, "alert": 0.20},
}
For temporal data (e.g., time series, seasonal patterns):
from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
registry = CollectorRegistry()
psi_gauge = Gauge('service_psi_score', 'PSI per feature', ['feature'], registry=registry)
for feature, psi in results.items():
psi_gauge.labels(feature=feature).set(psi)
push_to_gateway('pushgateway:9091', job='drift-detection', registry=registry)
# Check CronJob status
kubectl get cronjob drift-detection -n {namespace}
# Check last job
kubectl get jobs -l app=drift-detection -n {namespace} --sort-by=.metadata.creationTimestamp
# Check heartbeat
curl -s 'http://prometheus:9090/api/v1/query?query=drift_detection_last_run_timestamp'
PSI tells you features CHANGED, not that performance DEGRADED. For degradation you need ground truth (ADR-006). The sliced performance monitor does this:
python -m src.{service}.monitoring.performance_monitor \
--predictions data/predictions_log \
--labels data/labels_log \
--slices configs/slices.yaml \
--window 24h \
--baseline models/baseline_metrics.json \
--output reports/performance.json --push-metrics
Interpret reports/performance.json:
status: ok → no actionstatus: warning → investigate within 4h (P2)status: alert → retraining candidate (P1/P2)status: insufficient_data → ground-truth pipeline issue — investigate
{service}_performance_last_run_timestampjoined_count < predictions_count → label arrival lag — expected if labels
delayed; persistent gap is a ground-truth bugWhen an alert says SlicedAUCBelowAlert country=ES, the RCA pattern is:
cat reports/performance.json | jq '.slices.by_country.ES'jq '.features' drift_report.json | grep -A2 <feature>
sample_size >= min_samples_per_slice must hold; otherwise
the reading is noise and you should wait for more data, NOT retrain.Slices are defined in configs/slices.yaml. NEVER add high-cardinality columns
(user_id, transaction_id) — they blow up Prometheus labels and make grouping
meaningless. Stick to bounded categoricals (country, channel, segment) or
numeric bins.
If data drift AND concept drift agree (PSI up + AUC down for same slice):
gh workflow run retrain-{service}.yml \
-f reason="Concept drift: AUC={auc} for slice {slice}={value}; PSI={psi} on {feature}"
The retrain workflow now runs Champion/Challenger offline BEFORE promotion (ADR-008). Do NOT expect retraining to automatically replace the champion — statistical superiority must be proven.
Chain to /retrain workflow for the full retraining process.
The skill is complete when ALL of the following hold:
configs/slices.yaml (ADR-007)monitor, retrain, or investigate — never silentdrift_detection_last_run_timestamp pushed to Prometheus Pushgateway
(heartbeat — gap absence is itself an alert per ADR-009)ops/last_drift_report.json updated so risk_context.py can read it
on the next agent decisionpsi_over_2x_threshold fired: agent emitted [AGENT MODE: STOP]
and did NOT proceed to retrain or alert silencingops/audit.jsonl with operation=drift_check,
summary of severities, and any chain (retrain / RCA / incident)concept-drift-analysis (deeper, single-slice)model-retrain (consequence path when PSI + AUC agree)