mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* feat(simulation): add keyActorRoles field to fix actor overlap bonus vocabulary mismatch
The +0.04 actor overlap bonus never reliably fired in production because
stateSummary.actors uses role-category strings ('Commodity traders',
'Policy officials') while simulation keyActors uses named geo-political
entities ('Iran', 'Houthi'). 53 production runs audited showed the bonus
fired once out of 53.
Fix: add keyActorRoles?: string[] to SimulationTopPath. The Round 2 prompt
now includes a CANDIDATE ACTOR ROLES section with theater-local role vocab
seeded from candidatePacket.stateSummary.actors. The LLM copies matching
roles into keyActorRoles. applySimulationMerge scores overlap against
keyActorRoles when actorSource=stateSummary, preserving the existing
keyActors entity-overlap path for the affectedAssets fallback.
- buildSimulationPackageFromDeepSnapshot: add actorRoles[] to each theater
from candidate.stateSummary.actors (theater-scoped, no cross-theater noise)
- buildSimulationRound2SystemPrompt: inject CANDIDATE ACTOR ROLES section
with exact-copy instruction and keyActorRoles in JSON template
- tryParseSimulationRoundPayload: extract keyActorRoles from round 2 output
- mergedPaths.map(): filter keyActorRoles against theater.actorRoles guardrail
- computeSimulationAdjustment: dual-path overlap — roleOverlapCount for
stateSummary, keyActorsOverlapCount for affectedAssets (backwards compat)
- summarizeImpactPathScore: project roleOverlapCount + keyActorsOverlapCount
into path-scorecards.json simDetail
New fields: roleOverlapCount, keyActorsOverlapCount in SimulationAdjustmentDetail
and ScorecardSimDetail. actorOverlapCount preserved as backwards-compat alias.
Tests: 308 pass (was 301 before). New tests T-P1/T-P2/T-P3 (prompt/parser),
T-RO1/T-RO2/T-RO3 (role overlap logic), T-PKG1 (pkg builder actorRoles),
plus fixture updates for T2/T-F/T-G/T-J/T-K/T-N2/T-SC-4.
🤖 Generated with Claude Sonnet 4.6 via Claude Code (https://claude.ai/claude-code) + Compound Engineering v2.49.0
Co-Authored-By: Claude Sonnet 4.6 (200K context) <noreply@anthropic.com>
* fix(simulation): address CE review findings from PR #2582
- Add SimulationPackageTheater interface to seed-forecasts.types.d.ts
(actorRoles was untyped under @ts-check)
- Add keyActorRoles to uiTheaters Redis projection in writeSimulationOutcome
(field was stripped from Redis snapshot; only visible in R2 artifact)
- Extract keyActorRoles IIFE to named sanitizeKeyActorRoles() function;
hoist allowedRoles Set computation out of per-path loop
- Harden bonusOverlap ternary: explicit branch for actorSource='none'
prevents silent fallthrough if new actorSource values are added
- Eliminate roleOverlap intermediate array in computeSimulationAdjustment
- Add U+2028/U+2029 Unicode line-separator stripping to sanitizeForPrompt
- Apply sanitizeForPrompt at tryParseSimulationRoundPayload parse boundary;
add JSDoc to newly-exported function
All 308 tests pass, typecheck + typecheck:api clean.
* fix(sim): restore const sanitized in sanitizeKeyActorRoles after early-return guard
Prior edit added `if (!allowedRoles.length) return []` but accidentally removed
the `const sanitized = ...` line, leaving the filter on line below referencing an
undefined variable. Restores the full function body:
if (!allowedRoles.length) return [];
const sanitized = (Array.isArray(rawRoles) ? rawRoles : [])
.map((s) => sanitizeForPrompt(String(s)).slice(0, 80));
const allowedNorm = new Set(allowedRoles.map(normalizeActorName));
return sanitized.filter((s) => allowedNorm.has(normalizeActorName(s))).slice(0, 8);
308/308 tests pass.
---------
Co-authored-by: Claude Sonnet 4.6 (200K context) <noreply@anthropic.com>
57 lines
1.9 KiB
Markdown
57 lines
1.9 KiB
Markdown
---
|
|
status: pending
|
|
priority: p2
|
|
issue_id: "090"
|
|
tags: [code-review, simulation, correctness, architecture]
|
|
dependencies: []
|
|
---
|
|
|
|
# `bonusOverlap` ternary silently falls through to affectedAssets path for future `actorSource` values
|
|
|
|
## Problem Statement
|
|
|
|
In `computeSimulationAdjustment`:
|
|
|
|
```js
|
|
const bonusOverlap = actorSrc === 'stateSummary' ? roleOverlap.length : details.keyActorsOverlapCount;
|
|
```
|
|
|
|
The `else` branch catches both `actorSource='affectedAssets'` (correct) and `actorSource='none'` (incorrect — `keyActorsOverlapCount` would be 0 anyway, but the branch is semantically wrong) and any future third value. If a new `actorSource` variant is added later, it will silently use the `affectedAssets` entity-overlap count rather than failing visibly.
|
|
|
|
## Findings
|
|
|
|
- Flagged by architecture-strategist during PR #2582 review
|
|
- Current `actorSource` values: `'stateSummary'` | `'affectedAssets'` | `'none'`
|
|
- `actorSource='none'` currently has `candidateActors=[]` so `keyActorsOverlapCount=0`, masking the incorrect branch
|
|
- The fix is one line and makes the intent explicit
|
|
|
|
## Proposed Solution
|
|
|
|
```js
|
|
// BEFORE:
|
|
const bonusOverlap = actorSrc === 'stateSummary' ? roleOverlap.length : details.keyActorsOverlapCount;
|
|
|
|
// AFTER:
|
|
const bonusOverlap = actorSrc === 'stateSummary'
|
|
? details.roleOverlapCount
|
|
: actorSrc === 'affectedAssets'
|
|
? details.keyActorsOverlapCount
|
|
: 0;
|
|
```
|
|
|
|
This also removes the need for the intermediate `roleOverlap` array (use `details.roleOverlapCount` directly as flagged by code-simplicity-reviewer).
|
|
|
|
## Technical Details
|
|
|
|
- Files: `scripts/seed-forecasts.mjs` (computeSimulationAdjustment, ~line 11501)
|
|
- Effort: Trivial | Risk: Very Low
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] All three `actorSource` values have explicit branches (no implicit fallthrough)
|
|
- [ ] `node --test tests/forecast-trace-export.test.mjs` passes
|
|
|
|
## Work Log
|
|
|
|
- 2026-03-31: Identified by architecture-strategist during PR #2582 review
|