LeonardoMdSA commited on
Commit
944b0c5
·
1 Parent(s): a472415

dashboard and backend updates

Browse files
LICENSE CHANGED
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
 
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
app/api/routes.py CHANGED
@@ -1,19 +1,16 @@
1
  # app/api/routes.py
2
  # /predict, /health, /dashboard, /monitoring/run
3
 
4
- from fastapi import APIRouter, BackgroundTasks, UploadFile, File, Request
5
  from fastapi.responses import JSONResponse
6
  from fastapi.templating import Jinja2Templates
7
- from app.api.schemas import PredictionRequest, PredictionResponse
8
  from app.inference.predictor import Predictor
9
- from app.core.logging import log_prediction
10
  from app.monitoring.data_loader import load_production_data
11
  from app.monitoring.drift import run_drift_check
12
  from app.monitoring.governance import run_governance_checks
 
13
  import pandas as pd
14
- import os
15
- from app.core.templates import templates
16
- from fastapi.templating import Jinja2Templates
17
 
18
  templates = Jinja2Templates(directory="app/templates")
19
 
@@ -21,33 +18,58 @@ router = APIRouter()
21
  predictor = Predictor()
22
 
23
 
24
- # Endpoint for CSV upload & prediction with drift
25
  @router.post("/predict")
26
  async def predict_file(
27
  background_tasks: BackgroundTasks,
28
  file: UploadFile = File(...)
29
  ):
30
  df = pd.read_csv(file.file)
31
- predictions, probability = predictor.predict(df)
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  reference_df = pd.read_csv("models/v1/reference_data.csv")
34
  background_tasks.add_task(
35
- run_drift_check, df, reference_df, "v1"
36
  )
37
 
38
  return JSONResponse({
39
- "predictions": predictions.tolist() if hasattr(predictions, "tolist") else predictions,
 
40
  "drift": "scheduled"
41
  })
42
 
43
 
44
-
45
-
46
  @router.get("/health")
47
  def health():
48
  return {"status": "ok"}
49
 
50
 
 
51
  @router.get("/run-drift")
52
  def run_drift():
53
  current_df = load_production_data()
@@ -58,23 +80,31 @@ def run_drift():
58
  }
59
 
60
 
 
61
  @router.get("/monitoring/run")
62
  def monitoring_run(background_tasks: BackgroundTasks, model_version: str = "v1"):
63
- """
64
- Run production monitoring including drift + governance checks in background.
65
- """
66
- # Load current and reference data
67
  current_data = pd.read_csv("data/processed/current_data.csv")
68
- reference_data = pd.read_csv("data/processed/credit_default_clean.csv") # reference
69
 
70
- # Schedule background tasks
71
- background_tasks.add_task(run_drift_check, current_data, reference_data, model_version=model_version)
72
- background_tasks.add_task(run_governance_checks, current_data, model_version=model_version)
 
 
 
 
 
 
 
 
73
 
74
- return {"status": "monitoring triggered", "model_version": model_version}
 
 
 
75
 
76
 
77
- # Dashboard endpoint
78
  @router.get("/dashboard")
79
  def dashboard(request: Request):
80
  return templates.TemplateResponse(
 
1
  # app/api/routes.py
2
  # /predict, /health, /dashboard, /monitoring/run
3
 
4
+ from fastapi import APIRouter, BackgroundTasks, UploadFile, File, Request, HTTPException
5
  from fastapi.responses import JSONResponse
6
  from fastapi.templating import Jinja2Templates
7
+
8
  from app.inference.predictor import Predictor
 
9
  from app.monitoring.data_loader import load_production_data
10
  from app.monitoring.drift import run_drift_check
11
  from app.monitoring.governance import run_governance_checks
12
+
13
  import pandas as pd
 
 
 
14
 
15
  templates = Jinja2Templates(directory="app/templates")
16
 
 
18
  predictor = Predictor()
19
 
20
 
21
+ # CSV upload & prediction
22
  @router.post("/predict")
23
  async def predict_file(
24
  background_tasks: BackgroundTasks,
25
  file: UploadFile = File(...)
26
  ):
27
  df = pd.read_csv(file.file)
 
28
 
29
+ # ---- STRICT MODE: schema enforcement ----
30
+ missing = set(predictor.features) - set(df.columns)
31
+ if missing:
32
+ raise HTTPException(
33
+ status_code=400,
34
+ detail=f"Invalid schema. Missing required columns: {sorted(missing)}"
35
+ )
36
+
37
+ # ---- Model inference ----
38
+ preds, probas = predictor.predict(df)
39
+
40
+ results = []
41
+ for i, (pred, proba) in enumerate(zip(preds, probas)):
42
+ results.append({
43
+ "row": i,
44
+ "probability": round(float(proba), 4),
45
+ "prediction": "Default" if pred == 1 else "No Default",
46
+ "risk_level": (
47
+ "High" if proba >= 0.75 else
48
+ "Medium" if proba >= 0.5 else
49
+ "Low"
50
+ )
51
+ })
52
+
53
+ # ---- Drift scheduled in background ----
54
  reference_df = pd.read_csv("models/v1/reference_data.csv")
55
  background_tasks.add_task(
56
+ run_drift_check, df[predictor.features], reference_df[predictor.features], "v1"
57
  )
58
 
59
  return JSONResponse({
60
+ "n_rows": len(results),
61
+ "results": results,
62
  "drift": "scheduled"
63
  })
64
 
65
 
66
+ # Health
 
67
  @router.get("/health")
68
  def health():
69
  return {"status": "ok"}
70
 
71
 
72
+ # Manual drift run
73
  @router.get("/run-drift")
74
  def run_drift():
75
  current_df = load_production_data()
 
80
  }
81
 
82
 
83
+ # Monitoring pipeline
84
  @router.get("/monitoring/run")
85
  def monitoring_run(background_tasks: BackgroundTasks, model_version: str = "v1"):
 
 
 
 
86
  current_data = pd.read_csv("data/processed/current_data.csv")
87
+ reference_data = pd.read_csv("data/processed/credit_default_clean.csv")
88
 
89
+ background_tasks.add_task(
90
+ run_drift_check,
91
+ current_data[predictor.features],
92
+ reference_data[predictor.features],
93
+ model_version
94
+ )
95
+ background_tasks.add_task(
96
+ run_governance_checks,
97
+ current_data,
98
+ model_version=model_version
99
+ )
100
 
101
+ return {
102
+ "status": "monitoring triggered",
103
+ "model_version": model_version
104
+ }
105
 
106
 
107
+ # Dashboard
108
  @router.get("/dashboard")
109
  def dashboard(request: Request):
110
  return templates.TemplateResponse(
app/inference/predictor.py CHANGED
@@ -12,8 +12,8 @@ class Predictor:
12
  with open(FEATURES_PATH, "r") as f:
13
  self.features = json.load(f)
14
 
15
- def predict(self, payload: dict):
16
- X = np.array([[payload[f] for f in self.features]])
17
- proba = self.model.predict_proba(X)[0, 1]
18
- pred = int(proba >= 0.5)
19
- return pred, float(proba)
 
12
  with open(FEATURES_PATH, "r") as f:
13
  self.features = json.load(f)
14
 
15
+ def predict(self, df):
16
+ X = df[self.features]
17
+ probas = self.model.predict_proba(X)[:, 1]
18
+ preds = (probas >= 0.5).astype(int)
19
+ return preds.tolist(), probas.tolist()
app/templates/dashboard.html CHANGED
@@ -20,33 +20,49 @@
20
  <h2>Drift Metrics</h2>
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
- // Show predictions
32
- document.getElementById("predictions").innerHTML =
33
- `<pre>${JSON.stringify(data.predictions, null, 2)}</pre>`;
34
 
35
- // Show simplified drift chart
36
- const drift = data.drift || [];
37
- const cols = drift.map(d => d.column);
38
- const scores = drift.map(d => d.score);
39
 
40
- const trace = { x: cols, y: scores, type: 'bar' };
41
- Plotly.newPlot('drift-chart', [trace]);
 
 
 
 
 
 
42
  }
 
43
 
44
- document.getElementById("upload-form").addEventListener("submit", async (e) => {
45
- e.preventDefault();
46
- const fileInput = e.target.file.files[0];
47
- if (fileInput) await fetchResults(fileInput);
48
- });
49
- </script>
 
 
50
  </body>
51
  </html>
52
 
 
20
  <h2>Drift Metrics</h2>
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", {
29
+ method: "POST",
30
+ body: formData
31
+ });
32
+
33
+ const data = await response.json();
34
+
35
+ /* ---- Predictions (FIXED) ---- */
36
+ document.getElementById("predictions").innerHTML =
37
+ `<pre>${JSON.stringify(data.predictions, null, 2)}</pre>`;
38
 
39
+ /* ---- Drift (GUARDED) ---- */
40
+ const driftContainer = document.getElementById("drift-chart");
41
+ driftContainer.innerHTML = "";
42
 
43
+ if (Array.isArray(data.drift)) {
44
+ const cols = data.drift.map(d => d.column);
45
+ const scores = data.drift.map(d => d.score);
 
46
 
47
+ Plotly.newPlot(driftContainer, [{
48
+ x: cols,
49
+ y: scores,
50
+ type: "bar"
51
+ }]);
52
+ } else {
53
+ driftContainer.innerHTML =
54
+ "<p>Drift report scheduled. Open the Evidently HTML report.</p>";
55
  }
56
+ }
57
 
58
+ document.getElementById("upload-form").addEventListener("submit", async (e) => {
59
+ e.preventDefault();
60
+ const fileInput = e.target.file.files[0];
61
+ if (fileInput) {
62
+ await fetchResults(fileInput);
63
+ }
64
+ });
65
+ </script>
66
  </body>
67
  </html>
68
 
models/v1/model.pkl CHANGED
Binary files a/models/v1/model.pkl and b/models/v1/model.pkl differ
 
reports/evidently/drift_report.html CHANGED
The diff for this file is too large to render. See raw diff