mangrovedigital/tide-engine-api
0
1# ===== LEGACY / EXPERIMENTAL — NOT SHIPPED =====
2# Part of the superseded engine.core research stack (neighbour-transfer
3# nowcast). NOT imported by the product path (api_server / point_forecast /
4# engine.forecast). Kept for reference only. See LEGACY.md.
5"""Evaluation harness: grade baseline (astronomical only) vs corrected against
6real held-out observations on a common set of valid points.
7"""
8from . import linalg
9
10
11def grade(engine, block, mode="primary"):
12 """Return metrics dict comparing baseline vs corrected on `block` (aligned).
13 Only points with a real observation AND a producible correction are scored.
14
15 (Repaired 2026-07-01: was calling engine.predict_block, an API that no
16 longer exists — core.Engine exposes correct_block. Caught via Manus review.)
17 """
18 astro = engine.harm.predict(block["t"])
19 corrected, _layers = engine.correct_block(block, mode=mode)
20 obs = block["observed"]
21 b_pred, c_pred, truth = [], [], []
22 held = 0
23 for i in range(len(obs)):
24 if obs[i] is None:
25 continue
26 if corrected[i] is None:
27 held += 1
28 continue
29 truth.append(obs[i])
30 b_pred.append(astro[i])
31 c_pred.append(corrected[i])
32 if not truth:
33 return {"n": 0, "held": held, "error": "no scorable points"}
34 base_rmse = linalg.rmse(b_pred, truth)
35 corr_rmse = linalg.rmse(c_pred, truth)
36 base_mae = linalg.mae(b_pred, truth)
37 corr_mae = linalg.mae(c_pred, truth)
38 return {
39 "n": len(truth),
40 "held": held,
41 "baseline_rmse_m": base_rmse,
42 "corrected_rmse_m": corr_rmse,
43 "baseline_mae_m": base_mae,
44 "corrected_mae_m": corr_mae,
45 "rmse_skill": 1.0 - corr_rmse / base_rmse if base_rmse > 0 else 0.0,
46 "rmse_drop_m": base_rmse - corr_rmse,
47 }
48 