Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions scripts/seed-forecasts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4588,6 +4588,9 @@ function summarizeImpactPathScore(path = null) {
if (path.simulationAdjustment !== undefined) {
summary.simulationAdjustment = Number(path.simulationAdjustment);
summary.mergedAcceptanceScore = Number(path.mergedAcceptanceScore || path.acceptanceScore || 0);
if (path.simulationSignal !== undefined) {
summary.simulationSignal = path.simulationSignal;
}
}
return summary;
}
Expand Down Expand Up @@ -11398,7 +11401,7 @@ function negatesDisruption(stabilizer, candidatePacket) {
*/
function computeSimulationAdjustment(expandedPath, simTheaterResult, candidatePacket) {
let adjustment = 0;
const details = { bucketChannelMatch: false, actorOverlapCount: 0, invalidatorHit: false, stabilizerHit: false, resolvedChannel: '', channelSource: 'none', candidateActorCount: 0, actorSource: 'none' };
const details = { bucketChannelMatch: false, actorOverlapCount: 0, invalidatorHit: false, stabilizerHit: false, resolvedChannel: '', channelSource: 'none', candidateActorCount: 0, actorSource: 'none', simPathConfidence: 1.0 };

const { topPaths = [], invalidators = [], stabilizers = [] } = simTheaterResult || {};
const pathBucket = expandedPath?.direct?.targetBucket
Expand Down Expand Up @@ -11445,13 +11448,24 @@ function computeSimulationAdjustment(expandedPath, simTheaterResult, candidatePa
(sp) => matchesBucket(sp, pathBucket) && matchesChannel(sp, pathChannel)
);
if (bucketChannelMatch) {
adjustment += 0.08;
// Scale bonuses by sim path confidence.
// Absent or non-finite → 1.0 (conservative fallback for legacy LLM output without this field).
// Explicit 0 → simConf=0, no positive adjustment (if no negatives fire, adj=0 and early exit).
const rawConf = bucketChannelMatch.confidence;
const simConf = (typeof rawConf !== 'number' || !Number.isFinite(rawConf))
? 1.0
: Math.min(1, Math.max(0, rawConf));
adjustment += +parseFloat((0.08 * simConf).toFixed(3));
details.bucketChannelMatch = true;
details.simPathConfidence = simConf;
const simActors = new Set((Array.isArray(bucketChannelMatch.keyActors) ? bucketChannelMatch.keyActors : []).map(normalizeActorName));
const overlap = candidateActors.filter((a) => simActors.has(a));
details.actorOverlapCount = overlap.length;
// Overlap bonus fires only when both sides have named geo-political actors.
// Macro-financial theaters with role-based stateSummary.actors (e.g. "Commodity traders",
// "Central banks") will have actorOverlapCount=0 — this is expected, not a bug.
if (overlap.length >= 2) {
adjustment += 0.04;
adjustment += +parseFloat((0.04 * simConf).toFixed(3));
Comment on lines +11458 to +11468
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Redundant unary + on parseFloat return value

parseFloat(...) already returns a number (or NaN), so the leading + cast is a no-op in both places. It doesn't affect correctness, but it adds visual noise and suggests the author may have intended Number(...) or +(str) on a string that wasn't already numeric. Consider removing the outer + for clarity:

Suggested change
adjustment += +parseFloat((0.08 * simConf).toFixed(3));
details.bucketChannelMatch = true;
details.simPathConfidence = simConf;
const simActors = new Set((Array.isArray(bucketChannelMatch.keyActors) ? bucketChannelMatch.keyActors : []).map(normalizeActorName));
const overlap = candidateActors.filter((a) => simActors.has(a));
details.actorOverlapCount = overlap.length;
// Overlap bonus fires only when both sides have named geo-political actors.
// Macro-financial theaters with role-based stateSummary.actors (e.g. "Commodity traders",
// "Central banks") will have actorOverlapCount=0 — this is expected, not a bug.
if (overlap.length >= 2) {
adjustment += 0.04;
adjustment += +parseFloat((0.04 * simConf).toFixed(3));
adjustment += parseFloat((0.08 * simConf).toFixed(3));
details.bucketChannelMatch = true;
details.simPathConfidence = simConf;
const simActors = new Set((Array.isArray(bucketChannelMatch.keyActors) ? bucketChannelMatch.keyActors : []).map(normalizeActorName));
const overlap = candidateActors.filter((a) => simActors.has(a));
details.actorOverlapCount = overlap.length;
// Overlap bonus fires only when both sides have named geo-political actors.
// Macro-financial theaters with role-based stateSummary.actors (e.g. "Commodity traders",
// "Central banks") will have actorOverlapCount=0 — this is expected, not a bug.
if (overlap.length >= 2) {
adjustment += parseFloat((0.04 * simConf).toFixed(3));
}

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!

}
}

Expand Down Expand Up @@ -11500,6 +11514,15 @@ function applySimulationMerge(evaluation, simulationOutcome, candidatePackets, s

for (const path of allPaths) {
if (path.type !== 'expanded') continue;
// Clear stale simulation metadata from prior cycles before re-evaluating.
// Must happen before any `continue` so paths with no matching theater or zero
// adjustment don't retain fields written by a different simulation run.
delete path.simulationAdjustment;
delete path.mergedAcceptanceScore;
delete path.simulationSignal;
delete path.demotedBySimulation;
delete path.promotedBySimulation;

const simResult = simByTheater.get(path.candidateStateId);
if (!simResult) continue;

Expand All @@ -11525,6 +11548,13 @@ function applySimulationMerge(evaluation, simulationOutcome, candidatePackets, s

path.simulationAdjustment = adjustment;
path.mergedAcceptanceScore = mergedAcceptanceScore;
path.simulationSignal = {
backed: adjustment > 0,
adjustmentDelta: adjustment,
channelSource: details.channelSource,
demoted: wasAccepted && mergedAcceptanceScore < SIMULATION_MERGE_ACCEPT_THRESHOLD,
simPathConfidence: details.simPathConfidence,
};

if (wasAccepted && mergedAcceptanceScore < SIMULATION_MERGE_ACCEPT_THRESHOLD) {
path.demotedBySimulation = true;
Expand Down Expand Up @@ -16786,6 +16816,7 @@ export {
contradictsPremise,
negatesDisruption,
normalizeActorName,
summarizeImpactPathScore,
SIMULATION_MERGE_ACCEPT_THRESHOLD,
scoreImpactExpansionQuality,
buildImpactExpansionDebugPayload,
Expand Down
21 changes: 21 additions & 0 deletions scripts/seed-forecasts.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ interface ExpandedPathCandidate {
topBucketId?: string;
}

/**
* Compact simulation signal attached to an ExpandedPath when a non-zero adjustment was applied.
* Written by applySimulationMerge; rendered as a chip in ForecastPanel.
*/
interface SimulationSignal {
/** Simulation added a positive bonus to this path (bucket-channel match fired). False for negative-only adjustments (invalidator/stabilizer hit without a bucket-channel match). */
backed: boolean;
/** Raw adjustment delta (+0.08/+0.04 weighted by simPathConfidence; -0.12/-0.15 flat). */
adjustmentDelta: number;
/** Source of the matched channel: 'direct' (from path.direct.channel) | 'market' (from marketContext.topChannel) | 'none'. */
channelSource: 'direct' | 'market' | 'none';
/** Path was demoted below the 0.50 acceptance threshold by simulation. */
demoted: boolean;
/** Confidence of the matched simulation top-path (0–1). 1.0 when absent/non-finite (fallback). Explicit 0 preserved. Only meaningful when backed=true. */
simPathConfidence: number;
}

/** A single expanded path produced by the deep forecast LLM evaluation. */
interface ExpandedPath {
pathId: string;
Expand All @@ -99,6 +116,8 @@ interface ExpandedPath {
simulationAdjustment?: number;
demotedBySimulation?: boolean;
promotedBySimulation?: boolean;
/** Compact simulation signal. Present only when applySimulationMerge produced a non-zero adjustment. */
simulationSignal?: SimulationSignal;
direct?: ExpandedPathDirect;
candidate?: ExpandedPathCandidate;
}
Expand Down Expand Up @@ -170,6 +189,8 @@ interface SimulationAdjustmentDetail {
resolvedChannel: string;
/** Source of resolved channel. */
channelSource: 'direct' | 'market' | 'none';
/** Confidence of the matched simulation top-path (0–1). 1.0 when absent or non-finite (legacy LLM output fallback). Explicit 0 is preserved as 0 — simulation rated the path unsupported. */
simPathConfidence: number;
}

interface SimulationAdjustmentRecord {
Expand Down
Loading
Loading