Commit ·
700e2b6
1
Parent(s): 88260af
updates
Browse files- Dockerfile.hf +0 -1
- app/api/routes.py +4 -3
- app/monitoring/drift.py +18 -6
- app/templates/dashboard.html +32 -33
- open_drift.py +0 -3
- reports/evidently/drift_report.html +0 -0
- scripts/evaluate.py +0 -1
- scripts/run_drift_check.py +0 -1
Dockerfile.hf
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
# HF Spaces–compatible
|
|
|
|
|
|
app/api/routes.py
CHANGED
|
@@ -44,14 +44,15 @@ async def predict_file(background_tasks: BackgroundTasks, file: UploadFile = Fil
|
|
| 44 |
# Correctly get numeric drift scores per column
|
| 45 |
_, drift_dict = run_drift_check(df[predictor.features], reference_df[predictor.features], "v1")
|
| 46 |
|
| 47 |
-
# Ensure
|
| 48 |
drift_for_chart = []
|
| 49 |
for col, score in drift_dict.items():
|
| 50 |
try:
|
| 51 |
score_value = float(score)
|
| 52 |
-
|
|
|
|
| 53 |
except Exception:
|
| 54 |
-
score_value = 0.
|
| 55 |
drift_for_chart.append({"column": col, "score": score_value})
|
| 56 |
|
| 57 |
# Schedule full drift in background as before
|
|
|
|
| 44 |
# Correctly get numeric drift scores per column
|
| 45 |
_, drift_dict = run_drift_check(df[predictor.features], reference_df[predictor.features], "v1")
|
| 46 |
|
| 47 |
+
# Ensure numeric drift values safe for frontend Plotly chart
|
| 48 |
drift_for_chart = []
|
| 49 |
for col, score in drift_dict.items():
|
| 50 |
try:
|
| 51 |
score_value = float(score)
|
| 52 |
+
if not np.isfinite(score_value):
|
| 53 |
+
score_value = 0.0
|
| 54 |
except Exception:
|
| 55 |
+
score_value = 0.0
|
| 56 |
drift_for_chart.append({"column": col, "score": score_value})
|
| 57 |
|
| 58 |
# Schedule full drift in background as before
|
app/monitoring/drift.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
#
|
| 2 |
import os
|
| 3 |
import pandas as pd
|
| 4 |
from evidently.report import Report
|
|
@@ -23,7 +23,7 @@ def run_drift_check(current_data: pd.DataFrame, reference_data: pd.DataFrame, mo
|
|
| 23 |
"""
|
| 24 |
Run Evidently DataDriftPreset on current vs reference data,
|
| 25 |
save HTML report, and run governance checks.
|
| 26 |
-
Returns a tuple: (alerts,
|
| 27 |
"""
|
| 28 |
os.makedirs(REPORT_DIR, exist_ok=True)
|
| 29 |
|
|
@@ -34,10 +34,22 @@ def run_drift_check(current_data: pd.DataFrame, reference_data: pd.DataFrame, mo
|
|
| 34 |
# Extract numeric drift scores per column
|
| 35 |
report_dict = report.as_dict() if hasattr(report, "as_dict") else {}
|
| 36 |
drift_scores = {}
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
# Run governance checks (keeps existing alerts)
|
| 43 |
alerts = governance.check_metrics(report_dict, model_version=model_version)
|
|
|
|
| 1 |
+
# app/monitoring/drift.py
|
| 2 |
import os
|
| 3 |
import pandas as pd
|
| 4 |
from evidently.report import Report
|
|
|
|
| 23 |
"""
|
| 24 |
Run Evidently DataDriftPreset on current vs reference data,
|
| 25 |
save HTML report, and run governance checks.
|
| 26 |
+
Returns a tuple: (alerts, drift_scores)
|
| 27 |
"""
|
| 28 |
os.makedirs(REPORT_DIR, exist_ok=True)
|
| 29 |
|
|
|
|
| 34 |
# Extract numeric drift scores per column
|
| 35 |
report_dict = report.as_dict() if hasattr(report, "as_dict") else {}
|
| 36 |
drift_scores = {}
|
| 37 |
+
|
| 38 |
+
metrics_list = report_dict.get("metrics", [])
|
| 39 |
+
|
| 40 |
+
for metric in metrics_list:
|
| 41 |
+
result = metric.get("result", {})
|
| 42 |
+
# Check column-level drift
|
| 43 |
+
drift_by_columns = result.get("drift_by_columns", {})
|
| 44 |
+
if drift_by_columns:
|
| 45 |
+
for col, info in drift_by_columns.items():
|
| 46 |
+
score = info.get("drift_score", 0.0)
|
| 47 |
+
if score is None or not pd.notna(score):
|
| 48 |
+
score = 0.0
|
| 49 |
+
drift_scores[col] = float(score)
|
| 50 |
+
# fallback: Dataset-level drift metric (PSI share)
|
| 51 |
+
elif metric.get("metric") == "DatasetDriftMetric":
|
| 52 |
+
drift_scores["dataset"] = float(result.get("share_of_drifted_columns", 0.0))
|
| 53 |
|
| 54 |
# Run governance checks (keeps existing alerts)
|
| 55 |
alerts = governance.check_metrics(report_dict, model_version=model_version)
|
app/templates/dashboard.html
CHANGED
|
@@ -21,46 +21,45 @@
|
|
| 21 |
<div id="drift-chart"></div>
|
| 22 |
|
| 23 |
<script>
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
body: formData
|
| 31 |
-
});
|
| 32 |
-
|
| 33 |
-
const data = await response.json();
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
`<pre>${JSON.stringify(data.results, null, 2)}</pre>`;
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
}
|
| 55 |
}
|
|
|
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
</script>
|
| 65 |
</body>
|
| 66 |
</html>
|
|
|
|
| 21 |
<div id="drift-chart"></div>
|
| 22 |
|
| 23 |
<script>
|
| 24 |
+
async function fetchResults(csvFile) {
|
| 25 |
+
const formData = new FormData();
|
| 26 |
+
formData.append("file", csvFile);
|
| 27 |
|
| 28 |
+
const response = await fetch("/predict", { method: "POST", body: formData });
|
| 29 |
+
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
document.getElementById("predictions").innerHTML =
|
| 32 |
+
`<pre>${JSON.stringify(data.results, null, 2)}</pre>`;
|
|
|
|
| 33 |
|
| 34 |
+
const driftContainer = document.getElementById("drift-chart");
|
| 35 |
+
driftContainer.innerHTML = "";
|
| 36 |
|
| 37 |
+
if (Array.isArray(data.drift)) {
|
| 38 |
+
const cols = data.drift.map(d => d.column);
|
| 39 |
+
const scores = data.drift.map(d => {
|
| 40 |
+
let val = Number(d.score);
|
| 41 |
+
if (!Number.isFinite(val)) val = 0;
|
| 42 |
+
return val;
|
| 43 |
+
});
|
| 44 |
|
| 45 |
+
Plotly.newPlot(driftContainer, [{
|
| 46 |
+
x: cols,
|
| 47 |
+
y: scores,
|
| 48 |
+
type: "bar"
|
| 49 |
+
}]);
|
| 50 |
+
} else {
|
| 51 |
+
driftContainer.innerHTML =
|
| 52 |
+
"<p>Drift report scheduled. Open the Evidently HTML report.</p>";
|
|
|
|
| 53 |
}
|
| 54 |
+
}
|
| 55 |
|
| 56 |
+
document.getElementById("upload-form").addEventListener("submit", async (e) => {
|
| 57 |
+
e.preventDefault();
|
| 58 |
+
const fileInput = e.target.file.files[0];
|
| 59 |
+
if (fileInput) {
|
| 60 |
+
await fetchResults(fileInput);
|
| 61 |
+
}
|
| 62 |
+
});
|
| 63 |
</script>
|
| 64 |
</body>
|
| 65 |
</html>
|
open_drift.py
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
import webbrowser
|
| 2 |
-
report_path = r"C:\Users\Rayquaza\Desktop\IT\ML Inference Service with Drift Detection\reports\evidently\drift_report.html"
|
| 3 |
-
webbrowser.open(f"file://{report_path}")
|
|
|
|
|
|
|
|
|
|
|
|
reports/evidently/drift_report.html
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/evaluate.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
# offline evaluation
|
|
|
|
|
|
scripts/run_drift_check.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
# batch drift job
|
|
|
|
|
|