feat(forecast): plumb simulation decorations from ExpandedPath to Forecast Redis key#2528
feat(forecast): plumb simulation decorations from ExpandedPath to Forecast Redis key#2528
Conversation
…ecast Redis key - Add `simulation_adjustment`, `sim_path_confidence`, `demoted_by_simulation` fields to `Forecast` proto (fields 20-22) and regenerate service client - `writeSimulationDecorations`: after simulation rescore, builds candidateStateId → forecastIds map from snapshot.fullRunStateUnits, picks strongest signal per candidate, writes `forecast:sim-decorations:v1` to Redis (3-day TTL) - `applySimulationDecorationsToForecasts`: reads decorations at start of fast-path seed, mutates predictions in-place (stale-by-one-run, non-fatal on failure) - `buildPublishedForecastPayload`: passes sim fields through to published Redis payload - Add `__setRedisStoreForTests` / test-store bypass in `getRedisCredentials` for unit tests - 12 new WD-* tests covering early-exits, strongest-signal selection, candidate conflict resolution, demotion flag, non-fatal error handling, and full round-trip
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Greptile SummaryThis PR plumbs three simulation decoration fields ( Key changes:
Confidence Score: 4/5Safe to merge after the seed-meta companion key is added; all failures are non-fatal and test coverage is thorough. The only finding is a P2 AGENTS.md convention gap (missing seed-meta write), but the convention uses the word MUST and health-monitoring tooling actively relies on those keys. Bumping to 4/5 to surface this before merge; all other logic, error handling, and test coverage is solid. scripts/seed-forecasts.mjs — add seed-meta:forecast:sim-decorations:v1 companion write inside writeSimulationDecorations Important Files Changed
Sequence DiagramsequenceDiagram
participant SW as Simulation Worker
participant FF as fetchForecasts (Fast-Path Seeder)
participant R2 as Cloudflare R2
participant Redis as Upstash Redis
SW->>R2: writeSimulationOutcome (forecast-eval.json + snapshot)
SW->>SW: applyPostSimulationRescore(runId, freshOutcome, storageConfig)
SW->>R2: getR2JsonObject(evalKey + snapshotKey)
R2-->>SW: evalData, snapshot (with fullRunStateUnits)
SW->>SW: applySimulationMerge → mergeResult
SW-->>Redis: writeSimulationDecorations (fire-and-forget) writes forecast:sim-decorations:v1 (3-day TTL)
Note over Redis: { byForecastId: { [id]: { simulationAdjustment, simPathConfidence, demotedBySimulation } } }
FF->>FF: fetchForecasts()
FF->>Redis: applySimulationDecorationsToForecasts(predictions)
Redis-->>FF: forecast:sim-decorations:v1
FF->>FF: mutate pred.simulationAdjustment / simPathConfidence / demotedBySimulation in-place
FF->>FF: buildPublishedForecastPayload(pred)
FF->>Redis: write forecast:predictions:v2 (with sim fields)
Note over FF: ForecastPanel sub-bar now has data
Reviews (1): Last reviewed commit: "feat(forecast): plumb simulation decorat..." | Re-trigger Greptile |
| await redisSet(url, token, SIMULATION_DECORATIONS_KEY, { | ||
| runId: snapshot?.runId || '', | ||
| generatedAt: Date.now(), | ||
| byForecastId, | ||
| }, SIMULATION_DECORATIONS_TTL_SECONDS); | ||
| console.log(` [SimulationDecorations] Written ${decorationCount} decorations from ${byCandidateId.size} candidates`); | ||
| } catch (err) { |
There was a problem hiding this comment.
AGENTS.md states: "Redis seed scripts MUST write seed-meta:<key> for health monitoring". Other persistent writes in this file follow the convention (e.g. seed-meta:conflict:ema-windows:v1 at line 14865 and seed-meta:intelligence:market-implications at line 15936), but writeSimulationDecorations only writes forecast:sim-decorations:v1 without a companion seed-meta:forecast:sim-decorations:v1 key.
Without this, health-monitoring endpoints that scan seed-meta:* keys will be unaware that the simulation decorations key exists, making it invisible to on-call dashboards after deploy.
Suggested fix — add just after the redisSet call inside the try block:
await redisSet(url, token, `seed-meta:${SIMULATION_DECORATIONS_KEY}`, {
writtenAt: new Date().toISOString(),
decorationCount,
runId: snapshot?.runId || '',
}, SIMULATION_DECORATIONS_TTL_SECONDS);Context Used: AGENTS.md (source)
…ay staleness Without this fix, writeSimulationDecorations() returned early on empty adjustments or zero decorationCount, leaving the prior run's forecast:sim-decorations:v1 intact for the full 3-day TTL. Subsequent fast-path seeds would blindly reapply old flags. Fixes: - Split the `!adjustments?.length` guard: only skip on missing simulationEvidence (bogus data); write an empty byForecastId map when adjustments is [] so later runs always overwrite stale entries - Remove the `decorationCount === 0` early return for the same reason - Add SIMULATION_DECORATIONS_MAX_AGE_MS (48h) guard in applySimulationDecorationsToForecasts as defense-in-depth for the edge case where no simulation has run recently Tests: WD-1, WD-3 updated; WD-2b, WD-13, WD-14 added (287 total, all pass)
…ast path processDeepForecastTask called applySimulationMerge but only extracted simulationEvidence, discarding mergeResult without writing decorations. Forecasts processed through the inline deep path (the common case) never got their forecast:sim-decorations:v1 key populated. Fix: fire-and-forget writeSimulationDecorations(mergeResult, snapshot) immediately after applySimulationMerge in processDeepForecastTask, same pattern as applyPostSimulationRescore. The deep path now writes on the SAME run (no stale-by-one lag). Test WD-15: exercises applySimulationMerge → writeSimulationDecorations with snapshot.fullRunStateUnits, verifying both mapped forecast IDs are decorated via the full chain used by processDeepForecastTask (288 total).
Why this PR?
The simulation confidence sub-bar added in #2526 renders in `ForecastPanel` but had no data: `simulationAdjustment`, `simPathConfidence`, and `demotedBySimulation` only lived on `ExpandedPath` trace artifacts in R2. They never reached the `forecast:predictions:v2` Redis key that the panel reads.
What changed
Proto + generated client
`scripts/seed-forecasts.mjs`
Staleness fix (P1 review finding)
Without the fix, `writeSimulationDecorations` returned early on empty adjustments or zero `decorationCount`, leaving the prior run's key intact for the full 3-day TTL. Old simulation flags would persist across multiple runs.
Fix: split the first guard — only skip when `simulationEvidence` is entirely absent (bogus data); always write even when `adjustments` is `[]` or no forecast IDs matched. Removed the `decorationCount === 0` early return for the same reason.
Also added `SIMULATION_DECORATIONS_MAX_AGE_MS` (48h) guard in `applySimulationDecorationsToForecasts` as defense-in-depth for the edge case where no simulation has run recently (e.g., outage).
`tests/forecast-trace-export.test.mjs`
Test results
287/287 passing. `npm run typecheck` clean.
Post-Deploy Monitoring & Validation