feat(forecast): deterministic quantile path so dscr_p10 stops reading seeded rows
dscr_p10 is the feature TW-01 fires on, and so the origin of nearly every alert an officer sees — but nothing had written forecast_point since the seeder. A headline number computed from demo data is worse than no headline number, because it looks identical to a real one. ForecastStrategy is a seam shaped like ScoringStrategy: the LightGBM quantile heads implement it later and the swap is one property. The forecast and the score answer different questions and fail differently, so they get separate seams. The method is fully explainable. Baseline is the median whole-month net position from the ledger, median so one festival month does not become the expectation, and the partial current month is discarded. Drift comes from margin_squeeze, else feed_cost_change, capped so one sharp month does not extrapolate into a cliff. Spread widens with the square root of the horizon and again for every input we did not have. Missing inputs widen the band, never lower the centre. A pessimistic P50 built on ignorance would manufacture breaches. An enterprise with too little ledger gets no path rather than a fabricated one, so dscr_p10 resolves NO_FORECAST visibly. forecast_point gains engine/model_version. Seeded rows carry neither, and udyam.forecast.trust-seeded decides whether dscr_p10 may fall back to them. Left true so the demo portfolio stays populated; with it false the picture is honest — 6 enterprises forecast, 136 NO_FORECAST, and trip-wires firing drops from 34 to 5. The forecast reads margin features and dscr_p10 reads the forecast, so features are computed twice per enterprise rather than letting either half read a previous run's answer. Seeder now writes 150 days of ledger, not 31: the baseline needs whole months and one month leaves nothing to take a median of. Ledger joins loans in the backfill pass, keyed on clientId. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,3 +116,24 @@ udyam.features.climate-window-weeks=8
|
||||
udyam.features.min-zscore-samples=6
|
||||
|
||||
logging.level.com.lincfusion.udyamlinc=INFO
|
||||
|
||||
# ── Forecast (ARCHITECTURE R1 seam) ─────────────────────────
|
||||
# DETERMINISTIC until the LightGBM quantile heads exist. Both implement
|
||||
# ForecastStrategy, so the swap is this one property.
|
||||
udyam.forecast.engine=${UDYAM_FORECAST_ENGINE:DETERMINISTIC}
|
||||
udyam.forecast.lookback-months=6
|
||||
udyam.forecast.min-months=2
|
||||
# P10/P90 half-width at h=1 when every input is present; each missing input adds
|
||||
# to it, and it grows with the square root of the horizon. Missing inputs widen
|
||||
# the band, never lower the centre — a pessimistic P50 built on ignorance would
|
||||
# manufacture breaches.
|
||||
udyam.forecast.base-spread=0.18
|
||||
udyam.forecast.missing-input-penalty=0.10
|
||||
udyam.forecast.max-spread=0.75
|
||||
udyam.forecast.max-drift=0.30
|
||||
# Whether dscr_p10 may fall back to the seeder's forecast rows, which carry no
|
||||
# engine. True keeps the demo portfolio populated; false makes every enterprise
|
||||
# the forecast cannot reach report NO_FORECAST, and TW-01 skip rather than fire
|
||||
# on a number nothing computed. Set false before any run whose alerts are meant
|
||||
# to be believed.
|
||||
udyam.forecast.trust-seeded=${UDYAM_TRUST_SEEDED_FORECASTS:true}
|
||||
|
||||
Reference in New Issue
Block a user