Upload runtime.py
Browse files- runtime.py +127 -62
runtime.py
CHANGED
|
@@ -40,10 +40,11 @@ NIFTY_1D_PATH = DATA_DIR / "nifty50_1d.parquet"
|
|
| 40 |
MODEL_PATH = MODEL_DIR / "nifty_opening_direction_model.joblib"
|
| 41 |
LATEST_PATH = MODEL_DIR / "latest_prediction.csv"
|
| 42 |
TEST_PREDICTIONS_PATH = DATA_DIR / "test_predictions.parquet"
|
| 43 |
-
TOMORROW_MODEL_PATH = MODEL_DIR / "nifty_tomorrow_direction_model.joblib"
|
| 44 |
-
TOMORROW_LATEST_PATH = MODEL_DIR / "tomorrow_latest_prediction.csv"
|
| 45 |
-
TOMORROW_SUMMARY_PATH = MODEL_DIR / "tomorrow_summary.json"
|
| 46 |
TOMORROW_TEST_PREDICTIONS_PATH = DATA_DIR / "tomorrow_test_predictions.parquet"
|
|
|
|
| 47 |
FORECASTING_PROJECT_ROOT = Path(
|
| 48 |
os.environ.get(
|
| 49 |
"FORECASTING_PROJECT_ROOT",
|
|
@@ -55,14 +56,16 @@ DAILY_FORECASTER_SUMMARY_PATH = DAILY_FORECASTER_OUTPUT_DIR / "forecaster_summar
|
|
| 55 |
DAILY_FORECASTER_LATEST_PATH = DAILY_FORECASTER_OUTPUT_DIR / "forecaster_latest.csv"
|
| 56 |
DAILY_FORECASTER_PREDICTIONS_PATH = DAILY_FORECASTER_OUTPUT_DIR / "forecaster_test_predictions.csv"
|
| 57 |
TPLUS1_MODEL_PATH = MODEL_DIR / "nifty_1420_tplus1_logistic_model.joblib"
|
| 58 |
-
TPLUS1_LATEST_PATH = MODEL_DIR / "tplus1_latest_prediction.csv"
|
| 59 |
-
TPLUS1_SUMMARY_PATH = MODEL_DIR / "tplus1_summary.json"
|
| 60 |
-
TPLUS1_TEST_PREDICTIONS_PATH = DATA_DIR / "tplus1_test_predictions.parquet"
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
| 66 |
REFRESH_NORMAL = "normal"
|
| 67 |
LIVE_ACCURACY_PATH = MODEL_DIR / "live_accuracy.json"
|
| 68 |
|
|
@@ -381,19 +384,24 @@ def fetch_yahoo_daily(period: str = "1mo") -> pd.DataFrame:
|
|
| 381 |
return yahoo_history_to_ohlcv(raw, daily=True)
|
| 382 |
|
| 383 |
|
| 384 |
-
def append_parquet_rows(path: Path, new_rows: pd.DataFrame, subset: list[str]) -> pd.DataFrame:
|
| 385 |
-
if new_rows.empty:
|
| 386 |
-
if path.exists():
|
| 387 |
-
return pd.read_parquet(path)
|
| 388 |
-
raise RuntimeError(f"No rows returned for {path.name}; leaving parquet unchanged.")
|
| 389 |
if path.exists():
|
| 390 |
existing = pd.read_parquet(path)
|
| 391 |
combined = pd.concat([existing, new_rows], ignore_index=True)
|
| 392 |
else:
|
| 393 |
combined = new_rows.copy()
|
| 394 |
-
combined = combined.drop_duplicates(subset=subset, keep="last").sort_values(subset).reset_index(drop=True)
|
| 395 |
-
combined.to_parquet(path, index=False, compression="zstd")
|
| 396 |
-
return combined
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
|
| 398 |
|
| 399 |
def latest_parquet_date(path: Path) -> date | None:
|
|
@@ -502,7 +510,7 @@ def build_model_row(first5_row: pd.DataFrame) -> pd.DataFrame:
|
|
| 502 |
return output
|
| 503 |
|
| 504 |
|
| 505 |
-
def predict_row(row: pd.DataFrame) -> Prediction:
|
| 506 |
payload = load_model()
|
| 507 |
model = payload["model"]
|
| 508 |
features = payload["features"]
|
|
@@ -515,19 +523,28 @@ def predict_row(row: pd.DataFrame) -> Prediction:
|
|
| 515 |
pred = apply_decision_overlays(raw_pred, row, payload.get("decision_overlays", DECISION_OVERLAYS))
|
| 516 |
is_overridden = bool(raw_pred[0] != pred[0])
|
| 517 |
confidence = directional_confidence(prob_up, pred, threshold)
|
| 518 |
-
prediction = Prediction(
|
| 519 |
-
input_date=pd.to_datetime(row["date"].iloc[0]).date().isoformat(),
|
| 520 |
-
first5_start=str(pd.to_datetime(row["first5_start"].iloc[0])),
|
| 521 |
-
first5_end=str(pd.to_datetime(row["first5_end"].iloc[0])),
|
| 522 |
-
prediction="UP" if int(pred[0]) == 1 else "DOWN",
|
| 523 |
prob_up=float(prob_up[0]),
|
| 524 |
confidence=float(confidence[0]),
|
| 525 |
threshold=threshold,
|
| 526 |
-
model_name=str(payload.get("model_name", "nifty_opening_direction_model")),
|
| 527 |
-
is_overridden=is_overridden,
|
| 528 |
-
)
|
| 529 |
-
pd.DataFrame([prediction.to_dict()]).to_csv(LATEST_PATH, index=False)
|
| 530 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
|
| 532 |
|
| 533 |
def _file_cache_key(path: Path) -> tuple[str, int | None, int | None]:
|
|
@@ -925,23 +942,25 @@ def refresh_tplus1_prediction(session_date: date | None = None) -> dict[str, Any
|
|
| 925 |
input_day = pd.to_datetime(row["date"].iloc[0]).date()
|
| 926 |
target_day = next_trading_day(input_day + timedelta(days=1))
|
| 927 |
summary = load_tplus1_summary()
|
| 928 |
-
out = {
|
| 929 |
-
"input_date": input_day.isoformat(),
|
| 930 |
-
"target_date": target_day.isoformat(),
|
| 931 |
-
"forecast_for": f"next trading session after {input_day.isoformat()}",
|
| 932 |
"prediction": prediction,
|
| 933 |
"prob_up": float(prob_up[0]),
|
| 934 |
"confidence": float(max(prob_up[0], 1.0 - prob_up[0])),
|
| 935 |
"threshold": threshold,
|
| 936 |
"model_name": str(payload.get("model_name", summary.get("model_name", "nifty_1420_tplus1_logistic_model"))),
|
| 937 |
"decision_overlay": summary.get("decision_overlay"),
|
| 938 |
-
"validation_accuracy": summary.get("validation_accuracy"),
|
| 939 |
-
"test_accuracy": summary.get("test_accuracy"),
|
| 940 |
-
"accuracy_goal": summary.get("accuracy_goal"),
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
|
|
|
|
|
|
| 945 |
|
| 946 |
|
| 947 |
def _tomorrow_probability_from_daily(daily: pd.DataFrame, fallback_prob: float) -> float:
|
|
@@ -971,6 +990,8 @@ def refresh_tomorrow_prediction(session_date: date | None = None) -> dict[str, A
|
|
| 971 |
if synced is not None and TOMORROW_LATEST_PATH.exists():
|
| 972 |
latest = pd.read_csv(TOMORROW_LATEST_PATH).iloc[-1].to_dict()
|
| 973 |
cleaned = {k: (None if pd.isna(v) else v) for k, v in latest.items()}
|
|
|
|
|
|
|
| 974 |
if session_date is None:
|
| 975 |
clear_dashboard_payload_cache()
|
| 976 |
return cleaned
|
|
@@ -995,20 +1016,22 @@ def refresh_tomorrow_prediction(session_date: date | None = None) -> dict[str, A
|
|
| 995 |
prob_up = _tomorrow_probability_from_daily(daily[daily["date"].dt.date <= input_day], fallback_prob)
|
| 996 |
prediction = "UP" if prob_up >= threshold else "DOWN"
|
| 997 |
confidence = float(max(prob_up, 1.0 - prob_up))
|
| 998 |
-
row = {
|
| 999 |
-
"input_date": input_day.isoformat(),
|
| 1000 |
-
"target_date": target_day.isoformat(),
|
| 1001 |
-
"prediction": prediction,
|
| 1002 |
-
"prob_up": prob_up,
|
| 1003 |
-
"confidence": confidence,
|
| 1004 |
-
"threshold": threshold,
|
| 1005 |
-
"model_name": str(summary.get("model_name", "nifty_tomorrow_direction_model")),
|
| 1006 |
-
"source_model": str(summary.get("source_model", "tuned_daily_forest_single")),
|
| 1007 |
-
"validation_accuracy": float(summary.get("validation_accuracy", 0.5780141843971631)),
|
| 1008 |
-
"test_accuracy": float(summary.get("test_accuracy", 0.6182795698924731)),
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
|
|
|
|
|
|
|
| 1012 |
summary.update(
|
| 1013 |
{
|
| 1014 |
"latest_forecast_date": row["input_date"],
|
|
@@ -1393,16 +1416,41 @@ def update_opening_outcomes_from_daily() -> dict[str, Any]:
|
|
| 1393 |
|
| 1394 |
def load_live_accuracy() -> dict[str, Any]:
|
| 1395 |
"""Load the live accuracy ledger from disk."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1396 |
if LIVE_ACCURACY_PATH.exists():
|
| 1397 |
try:
|
| 1398 |
-
|
| 1399 |
except Exception:
|
| 1400 |
-
|
| 1401 |
-
|
| 1402 |
-
|
| 1403 |
-
|
| 1404 |
-
|
| 1405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1406 |
|
| 1407 |
|
| 1408 |
def save_live_accuracy(data: dict[str, Any]) -> None:
|
|
@@ -1410,6 +1458,23 @@ def save_live_accuracy(data: dict[str, Any]) -> None:
|
|
| 1410 |
LIVE_ACCURACY_PATH.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
| 1411 |
|
| 1412 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1413 |
def update_live_accuracy(session_date: date) -> dict[str, Any]:
|
| 1414 |
"""Score today's predictions against actual outcomes and update the ledger.
|
| 1415 |
|
|
|
|
| 40 |
MODEL_PATH = MODEL_DIR / "nifty_opening_direction_model.joblib"
|
| 41 |
LATEST_PATH = MODEL_DIR / "latest_prediction.csv"
|
| 42 |
TEST_PREDICTIONS_PATH = DATA_DIR / "test_predictions.parquet"
|
| 43 |
+
TOMORROW_MODEL_PATH = MODEL_DIR / "nifty_tomorrow_direction_model.joblib"
|
| 44 |
+
TOMORROW_LATEST_PATH = MODEL_DIR / "tomorrow_latest_prediction.csv"
|
| 45 |
+
TOMORROW_SUMMARY_PATH = MODEL_DIR / "tomorrow_summary.json"
|
| 46 |
TOMORROW_TEST_PREDICTIONS_PATH = DATA_DIR / "tomorrow_test_predictions.parquet"
|
| 47 |
+
TOMORROW_PREDICTION_HISTORY_PATH = MODEL_DIR / "tomorrow_prediction_history.parquet"
|
| 48 |
FORECASTING_PROJECT_ROOT = Path(
|
| 49 |
os.environ.get(
|
| 50 |
"FORECASTING_PROJECT_ROOT",
|
|
|
|
| 56 |
DAILY_FORECASTER_LATEST_PATH = DAILY_FORECASTER_OUTPUT_DIR / "forecaster_latest.csv"
|
| 57 |
DAILY_FORECASTER_PREDICTIONS_PATH = DAILY_FORECASTER_OUTPUT_DIR / "forecaster_test_predictions.csv"
|
| 58 |
TPLUS1_MODEL_PATH = MODEL_DIR / "nifty_1420_tplus1_logistic_model.joblib"
|
| 59 |
+
TPLUS1_LATEST_PATH = MODEL_DIR / "tplus1_latest_prediction.csv"
|
| 60 |
+
TPLUS1_SUMMARY_PATH = MODEL_DIR / "tplus1_summary.json"
|
| 61 |
+
TPLUS1_TEST_PREDICTIONS_PATH = DATA_DIR / "tplus1_test_predictions.parquet"
|
| 62 |
+
TPLUS1_PREDICTION_HISTORY_PATH = MODEL_DIR / "tplus1_prediction_history.parquet"
|
| 63 |
+
T5_PREDICTION_HISTORY_PATH = MODEL_DIR / "t5_prediction_history.parquet"
|
| 64 |
+
REFRESH_STATE_PATH = MODEL_DIR / "refresh_state.json"
|
| 65 |
+
REFRESH_WAITING = "waiting_second_payload"
|
| 66 |
+
REFRESH_REFRESHING = "refreshing"
|
| 67 |
+
REFRESH_READY = "ready"
|
| 68 |
+
REFRESH_FAILED = "failed"
|
| 69 |
REFRESH_NORMAL = "normal"
|
| 70 |
LIVE_ACCURACY_PATH = MODEL_DIR / "live_accuracy.json"
|
| 71 |
|
|
|
|
| 384 |
return yahoo_history_to_ohlcv(raw, daily=True)
|
| 385 |
|
| 386 |
|
| 387 |
+
def append_parquet_rows(path: Path, new_rows: pd.DataFrame, subset: list[str]) -> pd.DataFrame:
|
| 388 |
+
if new_rows.empty:
|
| 389 |
+
if path.exists():
|
| 390 |
+
return pd.read_parquet(path)
|
| 391 |
+
raise RuntimeError(f"No rows returned for {path.name}; leaving parquet unchanged.")
|
| 392 |
if path.exists():
|
| 393 |
existing = pd.read_parquet(path)
|
| 394 |
combined = pd.concat([existing, new_rows], ignore_index=True)
|
| 395 |
else:
|
| 396 |
combined = new_rows.copy()
|
| 397 |
+
combined = combined.drop_duplicates(subset=subset, keep="last").sort_values(subset).reset_index(drop=True)
|
| 398 |
+
combined.to_parquet(path, index=False, compression="zstd")
|
| 399 |
+
return combined
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def append_prediction_history(path: Path, row: dict[str, Any], subset: list[str]) -> pd.DataFrame:
|
| 403 |
+
frame = pd.DataFrame([row])
|
| 404 |
+
return append_parquet_rows(path, frame, subset)
|
| 405 |
|
| 406 |
|
| 407 |
def latest_parquet_date(path: Path) -> date | None:
|
|
|
|
| 510 |
return output
|
| 511 |
|
| 512 |
|
| 513 |
+
def predict_row(row: pd.DataFrame) -> Prediction:
|
| 514 |
payload = load_model()
|
| 515 |
model = payload["model"]
|
| 516 |
features = payload["features"]
|
|
|
|
| 523 |
pred = apply_decision_overlays(raw_pred, row, payload.get("decision_overlays", DECISION_OVERLAYS))
|
| 524 |
is_overridden = bool(raw_pred[0] != pred[0])
|
| 525 |
confidence = directional_confidence(prob_up, pred, threshold)
|
| 526 |
+
prediction = Prediction(
|
| 527 |
+
input_date=pd.to_datetime(row["date"].iloc[0]).date().isoformat(),
|
| 528 |
+
first5_start=str(pd.to_datetime(row["first5_start"].iloc[0])),
|
| 529 |
+
first5_end=str(pd.to_datetime(row["first5_end"].iloc[0])),
|
| 530 |
+
prediction="UP" if int(pred[0]) == 1 else "DOWN",
|
| 531 |
prob_up=float(prob_up[0]),
|
| 532 |
confidence=float(confidence[0]),
|
| 533 |
threshold=threshold,
|
| 534 |
+
model_name=str(payload.get("model_name", "nifty_opening_direction_model")),
|
| 535 |
+
is_overridden=is_overridden,
|
| 536 |
+
)
|
| 537 |
+
pd.DataFrame([prediction.to_dict()]).to_csv(LATEST_PATH, index=False)
|
| 538 |
+
_record_prediction_history(
|
| 539 |
+
T5_PREDICTION_HISTORY_PATH,
|
| 540 |
+
{
|
| 541 |
+
**prediction.to_dict(),
|
| 542 |
+
"target_date": prediction.input_date,
|
| 543 |
+
"source": "live",
|
| 544 |
+
},
|
| 545 |
+
["target_date"],
|
| 546 |
+
)
|
| 547 |
+
return prediction
|
| 548 |
|
| 549 |
|
| 550 |
def _file_cache_key(path: Path) -> tuple[str, int | None, int | None]:
|
|
|
|
| 942 |
input_day = pd.to_datetime(row["date"].iloc[0]).date()
|
| 943 |
target_day = next_trading_day(input_day + timedelta(days=1))
|
| 944 |
summary = load_tplus1_summary()
|
| 945 |
+
out = {
|
| 946 |
+
"input_date": input_day.isoformat(),
|
| 947 |
+
"target_date": target_day.isoformat(),
|
| 948 |
+
"forecast_for": f"next trading session after {input_day.isoformat()}",
|
| 949 |
"prediction": prediction,
|
| 950 |
"prob_up": float(prob_up[0]),
|
| 951 |
"confidence": float(max(prob_up[0], 1.0 - prob_up[0])),
|
| 952 |
"threshold": threshold,
|
| 953 |
"model_name": str(payload.get("model_name", summary.get("model_name", "nifty_1420_tplus1_logistic_model"))),
|
| 954 |
"decision_overlay": summary.get("decision_overlay"),
|
| 955 |
+
"validation_accuracy": summary.get("validation_accuracy"),
|
| 956 |
+
"test_accuracy": summary.get("test_accuracy"),
|
| 957 |
+
"accuracy_goal": summary.get("accuracy_goal"),
|
| 958 |
+
"source": "live",
|
| 959 |
+
}
|
| 960 |
+
pd.DataFrame([out]).to_csv(TPLUS1_LATEST_PATH, index=False)
|
| 961 |
+
_record_prediction_history(TPLUS1_PREDICTION_HISTORY_PATH, out, ["target_date"])
|
| 962 |
+
clear_dashboard_payload_cache()
|
| 963 |
+
return out
|
| 964 |
|
| 965 |
|
| 966 |
def _tomorrow_probability_from_daily(daily: pd.DataFrame, fallback_prob: float) -> float:
|
|
|
|
| 990 |
if synced is not None and TOMORROW_LATEST_PATH.exists():
|
| 991 |
latest = pd.read_csv(TOMORROW_LATEST_PATH).iloc[-1].to_dict()
|
| 992 |
cleaned = {k: (None if pd.isna(v) else v) for k, v in latest.items()}
|
| 993 |
+
cleaned["source"] = "live"
|
| 994 |
+
_record_prediction_history(TOMORROW_PREDICTION_HISTORY_PATH, cleaned, ["target_date"])
|
| 995 |
if session_date is None:
|
| 996 |
clear_dashboard_payload_cache()
|
| 997 |
return cleaned
|
|
|
|
| 1016 |
prob_up = _tomorrow_probability_from_daily(daily[daily["date"].dt.date <= input_day], fallback_prob)
|
| 1017 |
prediction = "UP" if prob_up >= threshold else "DOWN"
|
| 1018 |
confidence = float(max(prob_up, 1.0 - prob_up))
|
| 1019 |
+
row = {
|
| 1020 |
+
"input_date": input_day.isoformat(),
|
| 1021 |
+
"target_date": target_day.isoformat(),
|
| 1022 |
+
"prediction": prediction,
|
| 1023 |
+
"prob_up": prob_up,
|
| 1024 |
+
"confidence": confidence,
|
| 1025 |
+
"threshold": threshold,
|
| 1026 |
+
"model_name": str(summary.get("model_name", "nifty_tomorrow_direction_model")),
|
| 1027 |
+
"source_model": str(summary.get("source_model", "tuned_daily_forest_single")),
|
| 1028 |
+
"validation_accuracy": float(summary.get("validation_accuracy", 0.5780141843971631)),
|
| 1029 |
+
"test_accuracy": float(summary.get("test_accuracy", 0.6182795698924731)),
|
| 1030 |
+
"source": "live",
|
| 1031 |
+
}
|
| 1032 |
+
pd.DataFrame([row]).to_csv(TOMORROW_LATEST_PATH, index=False)
|
| 1033 |
+
_record_prediction_history(TOMORROW_PREDICTION_HISTORY_PATH, row, ["target_date"])
|
| 1034 |
+
summary = dict(summary)
|
| 1035 |
summary.update(
|
| 1036 |
{
|
| 1037 |
"latest_forecast_date": row["input_date"],
|
|
|
|
| 1416 |
|
| 1417 |
def load_live_accuracy() -> dict[str, Any]:
|
| 1418 |
"""Load the live accuracy ledger from disk."""
|
| 1419 |
+
default = {
|
| 1420 |
+
"tomorrow": {"entries": [], "accuracy": None, "total": 0, "correct_count": 0, "backtest_count": 0, "live_count": 0},
|
| 1421 |
+
"t5": {"entries": [], "accuracy": None, "total": 0, "correct_count": 0, "backtest_count": 0, "live_count": 0},
|
| 1422 |
+
"tplus1": {"entries": [], "accuracy": None, "total": 0, "correct_count": 0, "backtest_count": 0, "live_count": 0},
|
| 1423 |
+
}
|
| 1424 |
+
|
| 1425 |
if LIVE_ACCURACY_PATH.exists():
|
| 1426 |
try:
|
| 1427 |
+
raw = json.loads(LIVE_ACCURACY_PATH.read_text(encoding="utf-8"))
|
| 1428 |
except Exception:
|
| 1429 |
+
raw = None
|
| 1430 |
+
if isinstance(raw, dict):
|
| 1431 |
+
try:
|
| 1432 |
+
for model_id in default:
|
| 1433 |
+
current = raw.get(model_id, {})
|
| 1434 |
+
if not isinstance(current, dict):
|
| 1435 |
+
current = {}
|
| 1436 |
+
entries = current.get("entries", [])
|
| 1437 |
+
if not isinstance(entries, list):
|
| 1438 |
+
entries = []
|
| 1439 |
+
backtest_entries = [entry for entry in entries if str(entry.get("source", "backtest")).lower() == "backtest"]
|
| 1440 |
+
live_entries = [entry for entry in entries if str(entry.get("source", "backtest")).lower() != "backtest"]
|
| 1441 |
+
total = len(entries)
|
| 1442 |
+
correct = sum(1 for e in entries if e.get("correct"))
|
| 1443 |
+
current["entries"] = entries
|
| 1444 |
+
current["backtest_count"] = int(current.get("backtest_count") or len(backtest_entries))
|
| 1445 |
+
current["live_count"] = int(current.get("live_count") or len(live_entries))
|
| 1446 |
+
current["total"] = int(current.get("total") or total)
|
| 1447 |
+
current["correct_count"] = int(current.get("correct_count") or correct)
|
| 1448 |
+
current["accuracy"] = (current["correct_count"] / current["total"]) if current["total"] > 0 else None
|
| 1449 |
+
default[model_id].update(current)
|
| 1450 |
+
return default
|
| 1451 |
+
except Exception:
|
| 1452 |
+
pass
|
| 1453 |
+
return default
|
| 1454 |
|
| 1455 |
|
| 1456 |
def save_live_accuracy(data: dict[str, Any]) -> None:
|
|
|
|
| 1458 |
LIVE_ACCURACY_PATH.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
| 1459 |
|
| 1460 |
|
| 1461 |
+
def _load_prediction_history(path: Path) -> pd.DataFrame:
|
| 1462 |
+
if not path.exists():
|
| 1463 |
+
return pd.DataFrame()
|
| 1464 |
+
frame = pd.read_parquet(path)
|
| 1465 |
+
for col in ("date", "input_date", "target_date", "forecast_date"):
|
| 1466 |
+
if col in frame.columns:
|
| 1467 |
+
frame[col] = pd.to_datetime(frame[col], errors="coerce")
|
| 1468 |
+
sort_cols = [col for col in ("target_date", "input_date", "date", "forecast_date") if col in frame.columns]
|
| 1469 |
+
if sort_cols:
|
| 1470 |
+
return frame.sort_values(sort_cols).reset_index(drop=True)
|
| 1471 |
+
return frame.reset_index(drop=True)
|
| 1472 |
+
|
| 1473 |
+
|
| 1474 |
+
def _record_prediction_history(path: Path, row: dict[str, Any], subset: list[str]) -> None:
|
| 1475 |
+
append_prediction_history(path, row, subset)
|
| 1476 |
+
|
| 1477 |
+
|
| 1478 |
def update_live_accuracy(session_date: date) -> dict[str, Any]:
|
| 1479 |
"""Score today's predictions against actual outcomes and update the ledger.
|
| 1480 |
|