LeonardoMdSA commited on
Commit
b1725f1
·
1 Parent(s): 94337ad

barely working daemon

Browse files
app/api/background_drift.py CHANGED
@@ -13,16 +13,10 @@ REFERENCE_PATH = "models/v1/reference_data.csv"
13
  PROD_LOG_PATH = "data/production/predictions_log.csv"
14
  DASHBOARD_JSON = "reports/evidently/drift_report.json"
15
 
16
- # Retention policy (VERY IMPORTANT for HF Spaces)
17
  MAX_ROWS = 5000 # rolling window
18
-
19
  os.makedirs(os.path.dirname(DASHBOARD_JSON), exist_ok=True)
20
 
21
-
22
  async def drift_loop(interval_seconds: int = 10):
23
- """
24
- Continuously compute drift from production inference data.
25
- """
26
  while True:
27
  try:
28
  if not os.path.exists(PROD_LOG_PATH):
@@ -30,13 +24,13 @@ async def drift_loop(interval_seconds: int = 10):
30
  continue
31
 
32
  prod_df = pd.read_csv(PROD_LOG_PATH)
33
-
34
- # ---- Retention window (prevents infinite growth) ----
35
  if len(prod_df) > MAX_ROWS:
36
  prod_df = prod_df.tail(MAX_ROWS)
37
  prod_df.to_csv(PROD_LOG_PATH, index=False)
38
 
39
- # ---- Keep only rows with all required features ----
40
  missing_features = set(predictor.features) - set(prod_df.columns)
41
  if missing_features:
42
  print(f"Skipping drift check, missing features: {missing_features}")
@@ -50,9 +44,11 @@ async def drift_loop(interval_seconds: int = 10):
50
 
51
  reference_df = pd.read_csv(REFERENCE_PATH)
52
 
53
- # ---- FIX: pass reference_df to run_drift_check ----
54
  _, drift_dict = run_drift_check(
55
- prod_df[predictor.features], reference_df[predictor.features], model_version="v1"
 
 
56
  )
57
 
58
  dashboard_payload = {
@@ -64,7 +60,6 @@ async def drift_loop(interval_seconds: int = 10):
64
  ],
65
  }
66
 
67
- # Atomic write (prevents frontend race conditions)
68
  tmp_path = DASHBOARD_JSON + ".tmp"
69
  with open(tmp_path, "w") as f:
70
  json.dump(dashboard_payload, f, indent=2)
 
13
  PROD_LOG_PATH = "data/production/predictions_log.csv"
14
  DASHBOARD_JSON = "reports/evidently/drift_report.json"
15
 
 
16
  MAX_ROWS = 5000 # rolling window
 
17
  os.makedirs(os.path.dirname(DASHBOARD_JSON), exist_ok=True)
18
 
 
19
  async def drift_loop(interval_seconds: int = 10):
 
 
 
20
  while True:
21
  try:
22
  if not os.path.exists(PROD_LOG_PATH):
 
24
  continue
25
 
26
  prod_df = pd.read_csv(PROD_LOG_PATH)
27
+
28
+ # Retention window
29
  if len(prod_df) > MAX_ROWS:
30
  prod_df = prod_df.tail(MAX_ROWS)
31
  prod_df.to_csv(PROD_LOG_PATH, index=False)
32
 
33
+ # Keep only rows with all required features
34
  missing_features = set(predictor.features) - set(prod_df.columns)
35
  if missing_features:
36
  print(f"Skipping drift check, missing features: {missing_features}")
 
44
 
45
  reference_df = pd.read_csv(REFERENCE_PATH)
46
 
47
+ # ---- Run drift on features only ----
48
  _, drift_dict = run_drift_check(
49
+ prod_df[predictor.features],
50
+ reference_df[predictor.features],
51
+ model_version="v1"
52
  )
53
 
54
  dashboard_payload = {
 
60
  ],
61
  }
62
 
 
63
  tmp_path = DASHBOARD_JSON + ".tmp"
64
  with open(tmp_path, "w") as f:
65
  json.dump(dashboard_payload, f, indent=2)
app/api/routes.py CHANGED
@@ -5,7 +5,6 @@ from fastapi.templating import Jinja2Templates
5
 
6
  from app.inference.predictor import Predictor
7
  from app.monitoring.data_loader import load_production_data
8
- from app.monitoring.drift import run_drift_check
9
  from app.monitoring.governance import run_governance_checks
10
 
11
  import pandas as pd
@@ -21,17 +20,17 @@ predictor = Predictor()
21
  PROD_LOG = "data/production/predictions_log.csv"
22
 
23
  # ------------------------------------------------------------------
24
- # ENSURE production log exists at server startup (CRITICAL FIX)
25
  # ------------------------------------------------------------------
26
  os.makedirs(os.path.dirname(PROD_LOG), exist_ok=True)
27
 
28
  if not os.path.exists(PROD_LOG):
29
- # Create empty production log with correct schema
30
  base_cols = list(predictor.features)
31
  extra_cols = [
32
- "prediction",
33
- "probability",
34
- "risk_level",
 
35
  "model_version",
36
  "timestamp",
37
  ]
@@ -63,35 +62,23 @@ async def predict_file(background_tasks: BackgroundTasks, file: UploadFile = Fil
63
  "risk_level": "High" if proba >= 0.75 else "Medium" if proba >= 0.5 else "Low"
64
  })
65
 
66
- # ---- Drift: immediate for frontend ----
67
- reference_df = pd.read_csv("models/v1/reference_data.csv")
68
- _, drift_dict = run_drift_check(
69
- df[predictor.features],
70
- reference_df[predictor.features],
71
- "v1",
72
- )
73
-
74
- drift_for_chart = []
75
- for col, score in drift_dict.items():
76
- try:
77
- score_value = float(score)
78
- if not np.isfinite(score_value):
79
- score_value = 0.0
80
- except Exception:
81
- score_value = 0.0
82
- drift_for_chart.append({"column": col, "score": score_value})
83
-
84
- # ---- Append predictions to production log ----
85
  df_log = df.copy()
86
 
87
- # ---- FIX: Remove existing prediction/risk/probability/etc columns to avoid extra column issue ----
88
- for col in ["prediction", "probability", "risk_level", "model_version", "timestamp"]:
 
 
 
 
 
 
89
  if col in df_log.columns:
90
  df_log = df_log.drop(columns=[col])
91
 
92
- df_log["prediction"] = preds
93
- df_log["probability"] = probas
94
- df_log["risk_level"] = [
95
  "High" if p >= 0.75 else "Medium" if p >= 0.5 else "Low"
96
  for p in probas
97
  ]
@@ -100,25 +87,9 @@ async def predict_file(background_tasks: BackgroundTasks, file: UploadFile = Fil
100
 
101
  df_log.to_csv(PROD_LOG, mode="a", header=False, index=False)
102
 
103
- # ---- Dashboard JSON ----
104
- DASHBOARD_JSON = "reports/evidently/drift_report.json"
105
-
106
- dashboard_payload = {
107
- "n_rows": len(results),
108
- "results": results,
109
- "drift": drift_for_chart,
110
- }
111
-
112
- os.makedirs(os.path.dirname(DASHBOARD_JSON), exist_ok=True)
113
- tmp_path = DASHBOARD_JSON + ".tmp"
114
- with open(tmp_path, "w") as f:
115
- json.dump(dashboard_payload, f, indent=2)
116
- os.replace(tmp_path, DASHBOARD_JSON)
117
-
118
  return JSONResponse({
119
  "n_rows": len(results),
120
  "results": results,
121
- "drift": drift_for_chart,
122
  })
123
 
124
 
@@ -130,8 +101,10 @@ def health():
130
  @router.get("/run-drift")
131
  def run_drift():
132
  current_df = load_production_data()
133
- report_path = run_drift_check(current_df)
134
- return {"status": "drift_check_completed", "report_path": report_path}
 
 
135
 
136
 
137
  @router.get("/dashboard")
 
5
 
6
  from app.inference.predictor import Predictor
7
  from app.monitoring.data_loader import load_production_data
 
8
  from app.monitoring.governance import run_governance_checks
9
 
10
  import pandas as pd
 
20
  PROD_LOG = "data/production/predictions_log.csv"
21
 
22
  # ------------------------------------------------------------------
23
+ # ENSURE production log exists at server startup
24
  # ------------------------------------------------------------------
25
  os.makedirs(os.path.dirname(PROD_LOG), exist_ok=True)
26
 
27
  if not os.path.exists(PROD_LOG):
 
28
  base_cols = list(predictor.features)
29
  extra_cols = [
30
+ "target", # true label
31
+ "model_prediction", # model output
32
+ "model_probability",
33
+ "model_risk_level",
34
  "model_version",
35
  "timestamp",
36
  ]
 
62
  "risk_level": "High" if proba >= 0.75 else "Medium" if proba >= 0.5 else "Low"
63
  })
64
 
65
+ # ---- Append predictions to production log (minimal, fast) ----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  df_log = df.copy()
67
 
68
+ # Keep true target if present
69
+ if "target" in df.columns:
70
+ df_log["target"] = df["target"]
71
+ else:
72
+ df_log["target"] = np.nan
73
+
74
+ # Remove any old model prediction columns to prevent duplicates
75
+ for col in ["model_prediction", "model_probability", "model_risk_level", "model_version", "timestamp"]:
76
  if col in df_log.columns:
77
  df_log = df_log.drop(columns=[col])
78
 
79
+ df_log["model_prediction"] = preds
80
+ df_log["model_probability"] = probas
81
+ df_log["model_risk_level"] = [
82
  "High" if p >= 0.75 else "Medium" if p >= 0.5 else "Low"
83
  for p in probas
84
  ]
 
87
 
88
  df_log.to_csv(PROD_LOG, mode="a", header=False, index=False)
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  return JSONResponse({
91
  "n_rows": len(results),
92
  "results": results,
 
93
  })
94
 
95
 
 
101
  @router.get("/run-drift")
102
  def run_drift():
103
  current_df = load_production_data()
104
+ from app.monitoring.drift import run_drift_check
105
+ reference_df = pd.read_csv("models/v1/reference_data.csv")
106
+ _, drift_dict = run_drift_check(current_df[predictor.features], reference_df[predictor.features])
107
+ return {"status": "drift_check_completed", "drift": drift_dict}
108
 
109
 
110
  @router.get("/dashboard")
app/api/traffic_daemon.py CHANGED
@@ -4,7 +4,6 @@ import pandas as pd
4
  import random
5
  import requests
6
  import os
7
- import time
8
 
9
  API_URL = "http://localhost:8000/predict"
10
  SOURCE_DATA = "data/processed/current_data.csv"
@@ -13,13 +12,9 @@ MIN_SLEEP = 2
13
  MAX_SLEEP = 8
14
  MIN_BATCH = 1
15
  MAX_BATCH = 5
16
- STARTUP_DELAY = 10 # seconds – allow FastAPI to fully start
17
-
18
 
19
  async def traffic_loop():
20
- """
21
- Continuously generate inference traffic against /predict.
22
- """
23
  await asyncio.sleep(STARTUP_DELAY)
24
 
25
  if not os.path.exists(SOURCE_DATA):
@@ -27,20 +22,19 @@ async def traffic_loop():
27
  return
28
 
29
  df = pd.read_csv(SOURCE_DATA)
30
-
31
  print("Traffic daemon started.")
32
 
33
  while True:
34
  try:
35
  batch_size = random.randint(MIN_BATCH, MAX_BATCH)
36
  sample = df.sample(batch_size)
37
-
38
  csv_bytes = sample.to_csv(index=False).encode("utf-8")
39
 
 
40
  response = requests.post(
41
  API_URL,
42
  files={"file": ("sample.csv", csv_bytes, "text/csv")},
43
- timeout=10,
44
  )
45
 
46
  if response.status_code != 200:
 
4
  import random
5
  import requests
6
  import os
 
7
 
8
  API_URL = "http://localhost:8000/predict"
9
  SOURCE_DATA = "data/processed/current_data.csv"
 
12
  MAX_SLEEP = 8
13
  MIN_BATCH = 1
14
  MAX_BATCH = 5
15
+ STARTUP_DELAY = 10 # allow server startup
 
16
 
17
  async def traffic_loop():
 
 
 
18
  await asyncio.sleep(STARTUP_DELAY)
19
 
20
  if not os.path.exists(SOURCE_DATA):
 
22
  return
23
 
24
  df = pd.read_csv(SOURCE_DATA)
 
25
  print("Traffic daemon started.")
26
 
27
  while True:
28
  try:
29
  batch_size = random.randint(MIN_BATCH, MAX_BATCH)
30
  sample = df.sample(batch_size)
 
31
  csv_bytes = sample.to_csv(index=False).encode("utf-8")
32
 
33
+ # ---- Increased timeout to avoid ReadTimeout ----
34
  response = requests.post(
35
  API_URL,
36
  files={"file": ("sample.csv", csv_bytes, "text/csv")},
37
+ timeout=60,
38
  )
39
 
40
  if response.status_code != 200:
app/core/model_registry.py DELETED
@@ -1 +0,0 @@
1
- # model loading/versioning
 
 
app/db/models.py DELETED
@@ -1 +0,0 @@
1
- # ORM-style tables (optional)
 
 
app/db/session.py DELETED
@@ -1 +0,0 @@
1
- # SQLite connection
 
 
app/inference/preprocessing.py DELETED
@@ -1 +0,0 @@
1
- # feature handling
 
 
app/monitoring/metrics.py DELETED
@@ -1 +0,0 @@
1
- # feature stats extraction
 
 
data/production/predictions_log.csv CHANGED
@@ -1,6 +1,9 @@
1
- credit_limit,age,pay_delay_sep,pay_delay_aug,bill_amt_sep,bill_amt_aug,pay_amt_sep,pay_amt_aug,prediction,probability,risk_level,model_version,timestamp
2
- 80000.0,26,0,0,40216.0,10400.0,1400.0,10935.0,0,0,0.18062978660671827,Low,v1,2026-01-14 18:43:36.319064+00:00
3
- 200000.0,36,-1,-1,396.0,396.0,396.0,396.0,0,0,0.1170869637407986,Low,v1,2026-01-14 18:43:36.319064+00:00
4
- 210000.0,39,-1,-1,22861.0,6437.0,6536.0,72037.0,0,0,0.057603159126245064,Low,v1,2026-01-14 18:45:57.603850+00:00
5
- 100000.0,28,1,-2,0.0,0.0,0.0,0.0,1,0,0.28137664031455745,Low,v1,2026-01-14 18:45:57.603850+00:00
6
- 290000.0,36,-2,-2,3602.0,6107.0,6107.0,4538.0,0,0,0.04814376167314694,Low,v1,2026-01-14 18:45:57.603850+00:00
 
 
 
 
1
+ credit_limit,age,pay_delay_sep,pay_delay_aug,bill_amt_sep,bill_amt_aug,pay_amt_sep,pay_amt_aug,target,model_prediction,model_probability,model_risk_level,model_version,timestamp
2
+ 70000.0,48,0,0,20744.0,22093.0,2000.0,2000.0,0,0,0.2563046708563498,Low,v1,2026-01-14 19:33:08.201187+00:00
3
+ 390000.0,42,0,0,310075.0,184647.0,10021.0,4000.0,0,0,0.07238720996682343,Low,v1,2026-01-14 19:33:08.201187+00:00
4
+ 230000.0,50,-2,-2,2789.0,2942.0,2942.0,2520.0,0,0,0.06061841289885345,Low,v1,2026-01-14 19:33:08.201187+00:00
5
+ 40000.0,47,2,2,52358.0,54892.0,4000.0,0.0,1,1,0.608097803732095,Medium,v1,2026-01-14 19:35:59.632246+00:00
6
+ 140000.0,41,0,0,130138.0,132726.0,4756.0,4912.0,1,0,0.19300589506044843,Low,v1,2026-01-14 19:35:59.632246+00:00
7
+ 50000.0,26,-1,-1,5052.0,0.0,0.0,0.0,0,0,0.12125611010883464,Low,v1,2026-01-14 19:37:40.778834+00:00
8
+ 400000.0,42,-1,-1,44198.0,10132.0,10132.0,16932.0,0,0,0.06632404342712281,Low,v1,2026-01-14 19:39:17.748870+00:00
9
+ 280000.0,45,-2,-2,26573.0,17597.0,18388.0,22302.0,0,0,0.036231471756518835,Low,v1,2026-01-14 19:39:17.748870+00:00
reports/evidently/drift_report.html CHANGED
The diff for this file is too large to render. See raw diff
 
reports/evidently/drift_report.json CHANGED
@@ -1,25 +1,6 @@
1
  {
2
- "n_rows": 3,
3
- "results": [
4
- {
5
- "row": 0,
6
- "probability": 0.0576,
7
- "prediction": "No Default",
8
- "risk_level": "Low"
9
- },
10
- {
11
- "row": 1,
12
- "probability": 0.2814,
13
- "prediction": "No Default",
14
- "risk_level": "Low"
15
- },
16
- {
17
- "row": 2,
18
- "probability": 0.0481,
19
- "prediction": "No Default",
20
- "risk_level": "Low"
21
- }
22
- ],
23
  "drift": [
24
  {
25
  "column": "dataset",
@@ -27,35 +8,35 @@
27
  },
28
  {
29
  "column": "age",
30
- "score": 0.44474387293503476
31
  },
32
  {
33
  "column": "bill_amt_aug",
34
- "score": 0.6359128906609168
35
  },
36
  {
37
  "column": "bill_amt_sep",
38
- "score": 0.5786372146880444
39
  },
40
  {
41
  "column": "credit_limit",
42
- "score": 0.5490100613355632
43
  },
44
  {
45
  "column": "pay_amt_aug",
46
- "score": 1.0090700728243183
47
  },
48
  {
49
  "column": "pay_amt_sep",
50
- "score": 0.28309439717741114
51
  },
52
  {
53
  "column": "pay_delay_aug",
54
- "score": 1.298651499017585
55
  },
56
  {
57
  "column": "pay_delay_sep",
58
- "score": 0.7812979392472955
59
  }
60
  ]
61
  }
 
1
  {
2
+ "n_rows": 8,
3
+ "results": [],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  "drift": [
5
  {
6
  "column": "dataset",
 
8
  },
9
  {
10
  "column": "age",
11
+ "score": 0.8679104867707123
12
  },
13
  {
14
  "column": "bill_amt_aug",
15
+ "score": 0.23085843753803348
16
  },
17
  {
18
  "column": "bill_amt_sep",
19
+ "score": 0.37457217443848057
20
  },
21
  {
22
  "column": "credit_limit",
23
+ "score": 0.36873847560130574
24
  },
25
  {
26
  "column": "pay_amt_aug",
27
+ "score": 0.20876449446182804
28
  },
29
  {
30
  "column": "pay_amt_sep",
31
+ "score": 0.27529913358402724
32
  },
33
  {
34
  "column": "pay_delay_aug",
35
+ "score": 0.32956762578626103
36
  },
37
  {
38
  "column": "pay_delay_sep",
39
+ "score": 0.4885655407858251
40
  }
41
  ]
42
  }