Skip to content

fix(forecast): fix impact expansion LLM prompt + pre-filter validation diagnostic#2169

Merged
koala73 merged 4 commits into
mainfrom
worktree-peppy-mixing-pike
Mar 24, 2026
Merged

fix(forecast): fix impact expansion LLM prompt + pre-filter validation diagnostic#2169
koala73 merged 4 commits into
mainfrom
worktree-peppy-mixing-pike

Conversation

@koala73

@koala73 koala73 commented Mar 23, 2026

Copy link
Copy Markdown
Owner

Why this PR?

Two-phase fix for deep forecasts always returning `completed_no_material_change`.

Phase 1 (commits 1-3): Fixed structural bugs — Gemini JSON fence parsing, constraint table in LLM prompt, validation flow-through to debug artifacts, label disambiguation. The pipeline now reaches the LLM and gets back syntactically valid hypotheses.

Phase 2 (this commit): Three cascading scoring gates were mathematically impossible to clear with typical LLM output. Every second_order hypothesis scored as `trace_only` → no expanded paths were ever built.

Root causes:

  • second_order mapped floor (0.66) required `baseScore ≥ 0.776` but typical inputs yield 0.710 → `trace_only`
  • pathScore threshold (0.66) blocked barely-mapped pairs from generating expanded paths
  • `validatedDirectKeys` included `trace_only` items, so a mapped second_order could have no mapped direct parent in `buildImpactPathsForCandidate`
  • "at least 2 evidence keys" was prompt-only text, not enforced at scoring layer

Changes

`scripts/seed-forecasts.mjs`

  • Lower second_order validation floors (mapped: 0.66→0.58, internal: 0.58→0.50) and raise multiplier (0.85→0.88) — typical quality (strength0.75, conf0.75, 2 refs, specificityScore=0.2) now reaches `mapped`
  • Binary `evidenceSupport`: `refs >= 2 → 1, else → 0` — 1-ref hypotheses remain `trace_only` (enforced at scoring layer, not just prompt text)
  • Parent-must-be-mapped invariant: post-validation pass downgrades mapped second_order/third_order whose `dependsOnKey` has no mapped parent
  • Lower `pathScore` threshold 0.66 → 0.50
  • Add 6 missing maritime chokepoints to `CHOKEPOINT_MARKET_REGIONS` (Baltic Sea, Danish Straits, Strait of Gibraltar, Panama Canal, Lombok Strait, Cape of Good Hope)
  • Bump `IMPACT_EXPANSION_REGISTRY_VERSION` v2 → v3 (invalidates stale LLM cache)
  • Prompt v3: explicit `dependsOnKey` pairing rule, 2-evidence citation requirement, confidence calibration guidance (strength 0.82-0.95 for direct, 0.72-0.85 for second_order), direct+second_order pair structure instruction
  • Add `scoringBreakdown` (all hypotheses with full scoring factors) and `gateDetails` (active thresholds) to debug artifact for observability
  • Export `buildImpactExpansionSystemPrompt`, `extractImpactRouteFacilityKey`, `extractImpactCommodityKey` for testability

`tests/forecast-trace-export.test.mjs`

  • 8 new tests (T1-T8) covering all phase 2 changes
  • T1: second_order with typical quality (2 refs) reaches `mapped`
  • T2: second_order with 1 ref stays `trace_only`
  • T3: mapped second_order with `trace_only` parent is downgraded by invariant pass
  • T4: expanded path generated when both direct and second_order are mapped with pathScore ≥ 0.50
  • T5: `scoringBreakdown` includes all hypotheses with scoring factors
  • T6: `gateDetails` present with correct threshold values
  • T7: prompt v3 contains all required guidance strings
  • T8: new chokepoints detected by `extractImpactRouteFacilityKey`

Test plan

  • All 111 tests pass (`node --test tests/forecast-trace-export.test.mjs`)
  • Run `node scripts/seed-forecasts.mjs && node scripts/process-deep-forecast-tasks.mjs --once`
  • Confirm status is NOT `completed_no_material_change`
  • Read R2 debug artifact — confirm `scoringBreakdown` populated, `mappedCount >= 2`, at least one second_order with `validationStatus: 'mapped'`
  • Confirm `gateDetails` shows correct thresholds

@vercel

vercel Bot commented Mar 23, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Mar 24, 2026 4:15am

Request Review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

koala73 added 4 commits March 24, 2026 08:12
…ation diagnostic

Problem: LLM generates 8 hypotheses but 6 get rejected post-hoc because the
prompt gave 3 flat unlinked enum lists without per-variable constraints, causing
the LLM to hallucinate plausible but invalid channel/bucket combinations. Only
2 direct-only hypotheses survived, producing no expanded paths ->
completed_no_material_change on every deep run.

Also fixes two related bugs:
- Duplicate canonical state unit labels (same region + stateKind) blocked the
  deep worker. Added label dedup filter in buildCanonicalStateUnits.
- When all hypotheses failed state-ID filtering in materializeImpactExpansion,
  impactExpansionSummary showed 0 hypotheses with no rejection reasons, losing
  all diagnostic data.

Changes:
- Bump IMPACT_EXPANSION_REGISTRY_VERSION v1 -> v2 to invalidate cached LLM
  responses so the model re-generates with new constraint guidance
- Add buildRegistryConstraintTable() that serializes IMPACT_VARIABLE_REGISTRY
  and MARKET_BUCKET_ALLOWED_CHANNELS into a compact constraint block
- Rewrite buildImpactExpansionSystemPrompt() to replace 3 flat enum lists with
  the structured constraint table, bucket-channel dual gate rule, and explicit
  MiroFish causal chain guidance (direct -> second_order -> third_order example)
- Add validation field to all 3 return paths in evaluateDeepForecastPaths so
  pre-state-filter rejection data is always preserved
- Add hypothesisValidation to buildImpactExpansionDebugPayload capturing
  totalHypotheses, validatedCount, mappedCount, rejectionReasonCounts, and
  per-hypothesis rejection identity (candidateIndex, candidateStateId,
  variableKey, channel, targetBucket, order, rejectionReason)
- Filter duplicate state unit labels post-finalization in buildCanonicalStateUnits
- Export buildRegistryConstraintTable, IMPACT_VARIABLE_REGISTRY,
  MARKET_BUCKET_ALLOWED_CHANNELS for testability
- Add 4 new tests: mapped=0 early return has validation, no-expanded-accepted
  path has validation, hypothesisValidation flows through buildForecastTraceArtifacts,
  buildRegistryConstraintTable matches registry
…ighten test A2

- Extract IMPACT_EXPANSION_REGISTRY_CONSTRAINT_TABLE const so the
  constraint table is built once instead of on every prompt invocation
- Replace IIFE dedup filter in buildCanonicalStateUnits with an explicit
  seenLabels Set for readability
- Add format-assumption comment to buildRegistryConstraintTable
- Rename test A2 and add mapped > 0 assertion to pin it past the
  mapped=0 early-return path
…opping units

The previous dedup filter silently dropped state units whose labels
collided after finalizeStateUnit(), losing their id, forecastIds,
signals, and deep-candidate eligibility.

Two distinct clusters can score below the merge threshold (< 5.5) while
still resolving to the same formatStateUnitLabel() output when they
share the same leadRegion and stateKind. The filter kept the first
and discarded the rest, suppressing valid deep paths.

Fix: replace the .filter() with a .map() that disambiguates collision
labels using dominantDomain suffix, falling back to the last 4 chars
of the unit id if domain-based disambiguation would itself collide.
The seenLabels Set tracks all assigned labels to prevent any secondary
collision. The snapshot validator (and deep worker) no longer sees
duplicate labels, and no units are dropped.

Also export buildCanonicalStateUnits for direct test coverage.
Fixes cascade of three scoring gates that caused every deep run to return
completed_no_material_change despite valid LLM hypothesis generation.

Changes:
- Lower second_order validation floors (mapped: 0.66→0.58, internal: 0.58→0.50)
  and raise multiplier (0.85→0.88) so typical LLM quality (strength~0.75,
  conf~0.75, 2 refs, specificityScore=0.2) now reaches mapped status
- Binary evidenceSupport: refs >= 2 → 1, else → 0 (enforces 2-ref requirement
  at scoring layer, not just prompt text; 1-ref hypotheses stay trace_only)
- Parent-must-be-mapped invariant: post-validation pass downgrades mapped
  second_order/third_order whose dependsOnKey has no mapped parent
- Lower pathScore threshold from 0.66 to 0.50 to allow barely-mapped pairs
  through to expanded path generation
- Add 6 missing maritime chokepoints to CHOKEPOINT_MARKET_REGIONS
- Bump IMPACT_EXPANSION_REGISTRY_VERSION v2 → v3 (invalidates stale LLM cache)
- Prompt v3: explicit dependsOnKey pairing, 2-evidence citation rule,
  confidence calibration guidance, direct+second_order pair structure
- Add scoringBreakdown (all hypotheses with scoring factors) and gateDetails
  (active thresholds) to debug artifact for observability feedback loop
- Export buildImpactExpansionSystemPrompt, extractImpactRouteFacilityKey,
  extractImpactCommodityKey for testability
- 8 new tests (T1-T8) covering all phase 2 changes; 111/111 pass
@koala73
koala73 force-pushed the worktree-peppy-mixing-pike branch from 7c61f6f to 1c0359a Compare March 24, 2026 04:13
@koala73
koala73 merged commit c9d4fc4 into main Mar 24, 2026
7 checks passed
@koala73
koala73 deleted the worktree-peppy-mixing-pike branch March 27, 2026 12:55
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.

1 participant