Skip to content

fix(forecast): resolution-spec emission amendment — horizon-commensurable count + resolvable supply/gps windows (#5010)#5012

Merged
koala73 merged 2 commits into
mainfrom
fix/5010-resolution-spec-emission
Jul 7, 2026
Merged

fix(forecast): resolution-spec emission amendment — horizon-commensurable count + resolvable supply/gps windows (#5010)#5012
koala73 merged 2 commits into
mainfrom
fix/5010-resolution-spec-emission

Conversation

@koala73

@koala73 koala73 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Closes #5010 (Bet-1 follow-up; unblocks #5007). Epic #4930.

Why now: the Bet-2 planning review found the frozen spec shapes left only ~1 currently-published forecast resolvable. Amending emission one day into the resolution-spec clock resets a near-empty record; every day of delay makes the reset more expensive.

Changes (emission only — scripts/_forecast-resolution.mjs)

  1. conflict/ucdp count thresholds are horizon-commensurable. The signal tally is a 365-day trailing count (TRAILING_WINDOW_MS, seed-ucdp-events.mjs), so a raw threshold systematically resolves NO over a ~30d horizon — a biased Brier. Now: threshold = max(1, round(tally × horizonMs/365d × CONFLICT_ESCALATION_RATIO)) (named, exported knob, like MARKET_PRICE_MOVE_RATIO), counted over new events dated within [emission, deadline] — the count() semantics are documented in the grammar header.
  2. supply_chain/gps windows: at-deadline point reads instead of a sustained-48h condition that is unestablishable from current-snapshot feeds (forced permanent VOID).
  3. Left as shipped (confirmed fine): prediction_market: at-endDate, market/infrastructure: within-horizon.

Guardrails carried: omission semantics preserved (no new fields, no nulls), grammar unchanged and still parseable (spaces/?/) values), signal-vocab + horizon drift guards intact.

Verification

  • Tests 54/54 (+3 amendment tests: at-deadline windows for both families; a source-scan asserting no sustained window token is emitted; horizon-scaling of the same tally across 24h vs 30d). Benchmark 6/6; history 15/15 and trace-export 310/310 untouched-green; biome clean.
  • Live DoD spot-check (fresh detector batch, 49 hard specs): 100% shape-resolvable (target ~90%), window distribution within-horizon: 41, at-deadline: 7, at-endDate: 1, zero sustained; conflict thresholds horizon-scoped on real data (7d: 15/1/5 instead of raw 365d tallies).
  • D2 no-selection-change gate re-passed: real selection path with/without the enrichment pass → identical published id set and order.

Per the issue: this intentionally supersedes the ~1 day of old-shape specs in history — the clock resets to today with resolvable shapes.

…able count + resolvable supply/gps windows (#5010)

Bet-2 planning found the frozen Bet-1 spec shapes left only ~1
currently-published forecast resolvable. Amending emission now, one day
into the clock, resets a near-empty record instead of a month-old one.

1. conflict/ucdp count thresholds are horizon-commensurable: the signal
   tally is a 365-day trailing count, so a raw threshold would
   systematically resolve NO over a ~30d window (biased Brier). The
   threshold is now max(1, round(tally × horizonMs/365d ×
   CONFLICT_ESCALATION_RATIO)) — a named, exported knob like
   MARKET_PRICE_MOVE_RATIO — counted over NEW events dated within
   [emission, deadline]. count() semantics documented in the grammar
   header.

2. supply_chain/gps windows change from a sustained-48h condition (which
   is unestablishable from current-snapshot feeds and forced permanent
   VOID) to an at-deadline point read the Bet-2 resolver can establish.

prediction_market at-endDate and market/infrastructure within-horizon
stay as shipped (confirmed fine). Omission semantics preserved — no new
fields, no nulls. Emission only: no probability-spine or publish-selection
change (D2 gate re-passed: identical published set and order).

Live spot-check (fresh detector batch, 49 hard specs): 100% shape-
resolvable, zero sustained windows, conflict thresholds horizon-scoped
(7d: 15/1/5 vs raw tallies). Tests 54/54; benchmark 6/6.

Closes #5010

🤖 Generated with Claude Code
Co-Authored-By: Claude <[email protected]>
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
worldmonitor Ignored Ignored Preview Jul 7, 2026 8:26am

Request Review

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two classes of permanently-unresolvable forecast spec shapes introduced in the Bet-1 emission: it replaces the sustained-48h window on supply_chain/gps with at-deadline point reads (snapshot feeds cannot establish a sustained condition), and it scales the conflict/ucdp_zone event-count threshold from the raw 365-day trailing tally to the forecast horizon, eliminating the systematic NO bias over short windows.

  • CONFLICT_ESCALATION_RATIO and YEAR_MS are added; the threshold now scales as max(1, round(tally × horizonMs/365d × ratio)), matching the semantics of count() which counts new events in [emission, deadline].
  • FAMILY_WINDOW entries for supply_chain and gps change from 'sustained-48h' to 'at-deadline'; all other families are untouched.
  • Three new tests cover the window changes, the absence of any sustained-* token, and the horizon-proportional threshold scaling across 24h vs 30d.

Confidence Score: 4/5

Safe to merge; changes are emission-only, deterministic, and covered by 54 tests including three new amendment-specific assertions.

Both changed families are well-tested with explicit before/after assertions and a source-scan guard. The only open item is that YEAR_MS is private, leaving test literals to duplicate the constant rather than reference the single source of truth.

scripts/_forecast-resolution.mjs — specifically the YEAR_MS constant and the horizon-scaling formula in deriveHardMetrics.

Important Files Changed

Filename Overview
scripts/_forecast-resolution.mjs Core emission module: adds CONFLICT_ESCALATION_RATIO + YEAR_MS, switches supply_chain/gps window to at-deadline, and scales conflict/ucdp_zone count threshold to horizon. Logic is correct; YEAR_MS is module-private while tests duplicate the literal.
tests/forecast-resolution.test.mjs New describe block covers at-deadline window assertions, source-scan guard for sustained-* tokens, and dual-horizon threshold scaling; updated existing threshold assertion to match the new formula.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["deriveHardMetrics - conflict/ucdp_zone"] --> B{"firstFiniteSignalCount ucdp or conflict_events"}
    B -- "null" --> C["return null - judged spec"]
    B -- "tally" --> D{"HORIZON_MS lookup"}
    D -- "undefined" --> E["return null - judged spec"]
    D -- "horizonMs" --> F["threshold = max(1, round(tally x horizonMs/YEAR_MS x ESCALATION_RATIO))"]
    F --> G["Hard spec - window: within-horizon"]
    H["deriveHardMetrics - supply_chain"] --> I["threshold = 60"]
    I --> J["Hard spec - window: at-deadline"]
    K["deriveHardMetrics - gps"] --> L{"firstFiniteSignalCount gps_jamming"}
    L -- "null" --> M["return null - judged spec"]
    L -- "hexes" --> N["threshold = max(1, round(hexes))"]
    N --> O["Hard spec - window: at-deadline"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["deriveHardMetrics - conflict/ucdp_zone"] --> B{"firstFiniteSignalCount ucdp or conflict_events"}
    B -- "null" --> C["return null - judged spec"]
    B -- "tally" --> D{"HORIZON_MS lookup"}
    D -- "undefined" --> E["return null - judged spec"]
    D -- "horizonMs" --> F["threshold = max(1, round(tally x horizonMs/YEAR_MS x ESCALATION_RATIO))"]
    F --> G["Hard spec - window: within-horizon"]
    H["deriveHardMetrics - supply_chain"] --> I["threshold = 60"]
    I --> J["Hard spec - window: at-deadline"]
    K["deriveHardMetrics - gps"] --> L{"firstFiniteSignalCount gps_jamming"}
    L -- "null" --> M["return null - judged spec"]
    L -- "hexes" --> N["threshold = max(1, round(hexes))"]
    N --> O["Hard spec - window: at-deadline"]
Loading

Reviews (1): Last reviewed commit: "fix(forecast): resolution-spec emission ..." | Re-trigger Greptile

Comment on lines +173 to +174
export const CONFLICT_ESCALATION_RATIO = 1.5;
const YEAR_MS = 365 * 24 * 60 * 60 * 1000;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 YEAR_MS is module-private, so the two test assertions that verify the threshold formula must inline 365 * 24 * 60 * 60 * 1000 literally. If the constant is ever changed, tests would diverge silently. Exporting it would let tests reference the same value the implementation uses, following the pattern established by CONFLICT_ESCALATION_RATIO and HORIZON_MS.

Suggested change
export const CONFLICT_ESCALATION_RATIO = 1.5;
const YEAR_MS = 365 * 24 * 60 * 60 * 1000;
export const CONFLICT_ESCALATION_RATIO = 1.5;
export const YEAR_MS = 365 * 24 * 60 * 60 * 1000;

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(forecast): resolution-spec emission amendment — horizon-commensurable count + resolvable supply/gps windows (Bet-1 follow-up, unblocks #5007)

1 participant