fix(simulation): add TypeScript types for simulation pipeline + remove dead legacy fallbacks (#2414)

- Add scripts/seed-forecasts.types.d.ts with ambient interfaces for all
  simulation data structures (CandidatePacket, TheaterResult, SimulationOutcome,
  ExpandedPath, SimulationEvidence, SimulationAdjustmentDetail)
- Add // @ts-check + @param JSDoc to contradictsPremise, negatesDisruption,
  computeSimulationAdjustment, applySimulationMerge
- Add scripts/jsconfig.json to enable tsc --checkJs on seed-forecasts.mjs
- Remove dead legacy fallbacks candidatePacket?.topBucketId and
  candidatePacket?.topChannel (these fields were never at top level in
  production; the correct path is candidatePacket.marketContext.topBucketId).
  TypeScript now enforces this at write time.
- Update all test fixtures to use marketContext: { topBucketId, topChannel }
  shape, matching production CandidatePacket structure
This commit is contained in:
Elie Habib
2026-03-28 13:05:18 +04:00
committed by GitHub
parent 483da385bd
commit 4eb1d292eb
4 changed files with 249 additions and 13 deletions

View File

@@ -6275,8 +6275,7 @@ describe('phase 3 simulation re-ingestion — computeSimulationAdjustment', () =
candidateIndex: 0,
routeFacilityKey,
commodityKey,
topBucketId: 'energy',
topChannel: 'energy_supply_shock',
marketContext: { topBucketId: 'energy', topChannel: 'energy_supply_shock' },
});
it('T1: bucket+channel match gives +0.08', () => {
@@ -6410,7 +6409,7 @@ describe('phase 3 simulation re-ingestion — applySimulationMerge', () => {
third: null,
pathScore: 0.60,
acceptanceScore,
candidate: { routeFacilityKey: 'Red Sea', commodityKey: 'crude_oil', topBucketId: 'energy', topChannel: 'energy_supply_shock' },
candidate: { routeFacilityKey: 'Red Sea', commodityKey: 'crude_oil' },
});
const makeSimOutcome = (theaterId, topPaths, invalidators = [], stabilizers = []) => ({
@@ -6429,7 +6428,7 @@ describe('phase 3 simulation re-ingestion — applySimulationMerge', () => {
const path = makeExpandedPath('state-1', 0.52);
const evaluation = makeEval('completed', [path]);
const simOutcome = makeSimOutcome('state-1', [], ['Red Sea reopened after diplomatic ceasefire']);
const candidatePackets = [{ candidateStateId: 'state-1', routeFacilityKey: 'Red Sea', commodityKey: 'crude_oil', topBucketId: 'energy', topChannel: 'energy_supply_shock' }];
const candidatePackets = [{ candidateStateId: 'state-1', routeFacilityKey: 'Red Sea', commodityKey: 'crude_oil', marketContext: { topBucketId: 'energy', topChannel: 'energy_supply_shock' } }];
const { simulationEvidence } = applySimulationMerge(evaluation, simOutcome, candidatePackets, { generatedAt: Date.now(), impactExpansionCandidates: candidatePackets }, null);
assert.equal(simulationEvidence.pathsDemoted, 1);
assert.equal(evaluation.status, 'completed_no_material_change');
@@ -6441,7 +6440,7 @@ describe('phase 3 simulation re-ingestion — applySimulationMerge', () => {
rejectedPath.direct.affectedAssets = ['Iran', 'Houthi', 'Saudi Aramco'];
const evaluation = makeEval('completed_no_material_change', [acceptedBase], [rejectedPath]);
const simOutcome = makeSimOutcome('state-1', [{ label: 'Oil energy supply shock escalation', summary: 'Crude supply disruption energy', keyActors: ['Iran', 'Houthi'] }]);
const candidatePackets = [{ candidateStateId: 'state-1', routeFacilityKey: 'Red Sea', commodityKey: 'crude_oil', topBucketId: 'energy', topChannel: 'energy_supply_shock' }];
const candidatePackets = [{ candidateStateId: 'state-1', routeFacilityKey: 'Red Sea', commodityKey: 'crude_oil', marketContext: { topBucketId: 'energy', topChannel: 'energy_supply_shock' } }];
const snapshot = { generatedAt: Date.now(), impactExpansionCandidates: candidatePackets, fullRunPredictions: [], predictions: [], inputs: {}, deepForecast: {} };
const { simulationEvidence } = applySimulationMerge(evaluation, simOutcome, candidatePackets, snapshot, null);
assert.equal(simulationEvidence.pathsPromoted, 1);
@@ -6452,7 +6451,7 @@ describe('phase 3 simulation re-ingestion — applySimulationMerge', () => {
const path = makeExpandedPath('state-999', 0.60);
const evaluation = makeEval('completed', [path]);
const simOutcome = makeSimOutcome('state-1', [{ label: 'energy shock', summary: 'energy supply disruption', keyActors: [] }]);
const candidatePackets = [{ candidateStateId: 'state-999', routeFacilityKey: '', commodityKey: '', topBucketId: 'energy', topChannel: 'energy_supply_shock' }];
const candidatePackets = [{ candidateStateId: 'state-999', routeFacilityKey: '', commodityKey: '', marketContext: { topBucketId: 'energy', topChannel: 'energy_supply_shock' } }];
const { simulationEvidence } = applySimulationMerge(evaluation, simOutcome, candidatePackets, null, null);
assert.equal(simulationEvidence.adjustments.length, 0);
assert.equal(evaluation.status, 'completed');
@@ -6462,7 +6461,7 @@ describe('phase 3 simulation re-ingestion — applySimulationMerge', () => {
const path = makeExpandedPath('state-1', 0.60);
const evaluation = makeEval('completed', [path]);
const simOutcome = makeSimOutcome('state-1', [{ label: 'Oil energy supply shock', summary: 'Crude energy disruption', keyActors: [] }]);
const candidatePackets = [{ candidateStateId: 'state-1', routeFacilityKey: '', commodityKey: '', topBucketId: 'energy', topChannel: 'energy_supply_shock' }];
const candidatePackets = [{ candidateStateId: 'state-1', routeFacilityKey: '', commodityKey: '', marketContext: { topBucketId: 'energy', topChannel: 'energy_supply_shock' } }];
const { simulationEvidence } = applySimulationMerge(evaluation, simOutcome, candidatePackets, null, null);
assert.equal(simulationEvidence.outcomeRunId, 'sim-run-001');
assert.equal(simulationEvidence.theaterCount, 1);
@@ -6486,7 +6485,7 @@ describe('phase 3 simulation re-ingestion — applySimulationMerge', () => {
stabilizers: [],
}],
};
const candidatePackets = [{ candidateStateId, routeFacilityKey: 'Red Sea', commodityKey: 'crude_oil', topBucketId: 'energy', topChannel: 'energy_supply_shock' }];
const candidatePackets = [{ candidateStateId, routeFacilityKey: 'Red Sea', commodityKey: 'crude_oil', marketContext: { topBucketId: 'energy', topChannel: 'energy_supply_shock' } }];
const { simulationEvidence } = applySimulationMerge(evaluation, simOutcome, candidatePackets, { generatedAt: Date.now(), impactExpansionCandidates: candidatePackets }, null);
assert.equal(simulationEvidence.pathsDemoted, 1, 'path should be demoted via candidateStateId lookup');
assert.equal(simulationEvidence.adjustments.length, 1);
@@ -6568,12 +6567,12 @@ describe('phase 3 simulation re-ingestion — matching helpers', () => {
});
it('negatesDisruption — non-maritime: rates_inflation bucket + negation term matches', () => {
const candidatePacket = { routeFacilityKey: '', commodityKey: '', stateKind: 'market_repricing', topBucketId: 'rates_inflation' };
const candidatePacket = { routeFacilityKey: '', commodityKey: '', stateKind: 'market_repricing', marketContext: { topBucketId: 'rates_inflation', topChannel: 'policy_rate_pressure' } };
assert.ok(negatesDisruption('inflation pressures stabilized as Fed signals rate normalization', candidatePacket));
});
it('negatesDisruption — non-maritime: unrelated stateKind text does not match', () => {
const candidatePacket = { routeFacilityKey: '', commodityKey: '', stateKind: 'cyber_pressure', topBucketId: 'rates_inflation' };
const candidatePacket = { routeFacilityKey: '', commodityKey: '', stateKind: 'cyber_pressure', marketContext: { topBucketId: 'rates_inflation', topChannel: '' } };
// stabilizer text mentions "shipping restored" but theater is cyber/rates — no keyword match
assert.ok(!negatesDisruption('Red Sea shipping lanes restored to normal', candidatePacket));
});