fix: calibrate from replay outcomes

This commit is contained in:
ImBenji
2026-08-13 19:10:59 +01:00
parent c9c2d0c8ef
commit 4aa2367971
2 changed files with 97 additions and 10 deletions
+49 -10
View File
@@ -6,13 +6,26 @@ const { decide } = require('../src/autonomy/policy');
function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
function refreshCalibration(db, version = `cal-${Date.now()}`) {
function snapshotToDecisionInput(snapshot, direction) {
return {
direction,
probability: snapshot.directional_probability,
expectedExcessReturn: snapshot.expected_excess_return,
lowerReturn: snapshot.lower_return,
upperReturn: snapshot.upper_return,
sampleSize: snapshot.sample_size,
};
}
function refreshCalibration(db, version = `cal-${Date.now()}`, { origin = 'live', source = origin, replayRunId = null } = {}) {
const learningClause = origin === 'live' ? 'AND p.learning_eligible = 1' : '';
const replayClause = replayRunId ? 'AND p.replay_run_id = @replayRunId' : '';
const groups = db.prepare(`
SELECT p.direction, p.event_type, p.horizon_days, o.*
FROM autonomy_predictions p
JOIN autonomy_outcomes o ON o.prediction_id = p.id
WHERE p.status = 'resolved' AND p.learning_eligible = 1 AND p.origin = 'live'
`).all().reduce((map, row) => {
WHERE p.status = 'resolved' AND p.origin = @origin ${learningClause} ${replayClause}
`).all({ origin, replayRunId }).reduce((map, row) => {
const key = cohortKey({ direction: row.direction, eventType: row.event_type, horizonDays: row.horizon_days });
if (!map.has(key)) map.set(key, []);
map.get(key).push(row);
@@ -22,20 +35,44 @@ function refreshCalibration(db, version = `cal-${Date.now()}`) {
INSERT INTO autonomy_calibration_snapshots
(cohort_key, sample_size, effective_sample_size, directional_probability,
expected_excess_return, lower_return, upper_return, parent_cohort_key, version, source, replay_run_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'live', NULL)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const tx = db.transaction(() => {
for (const [key, rows] of groups) {
if (db.prepare("SELECT 1 FROM autonomy_calibration_snapshots WHERE cohort_key = ? AND version = ?").get(key, version)) continue;
if (db.prepare(`
SELECT 1 FROM autonomy_calibration_snapshots
WHERE cohort_key = ? AND version = ? AND source = ? AND COALESCE(replay_run_id, 0) = COALESCE(?, 0)
`).get(key, version, source, replayRunId)) continue;
const result = calibrateOutcomes(rows);
insert.run(key, result.sampleSize, result.effectiveSampleSize, result.directionalProbability,
result.expectedExcessReturn, result.lowerReturn, result.upperReturn, null, version);
result.expectedExcessReturn, result.lowerReturn, result.upperReturn, null, version, source, replayRunId);
}
});
tx();
return groups.size;
}
function refreshHistoricalCalibration(db, version = `replay-cal-${Date.now()}`) {
const runs = db.prepare(`
SELECT DISTINCT replay_run_id AS replayRunId
FROM autonomy_predictions
WHERE origin = 'replay' AND replay_run_id IS NOT NULL
ORDER BY replay_run_id
`).all();
let groups = 0;
for (const run of runs) {
groups += refreshCalibration(db, `${version}-run-${run.replayRunId}`, {
origin: 'replay',
source: 'replay',
replayRunId: run.replayRunId,
});
}
if (!runs.length) {
groups += refreshCalibration(db, version, { origin: 'replay', source: 'replay' });
}
return groups;
}
function createDecisions(db, strategyVersion = 'autonomy-1') {
const predictions = db.prepare(`
SELECT p.* FROM autonomy_predictions p
@@ -57,7 +94,7 @@ function createDecisions(db, strategyVersion = 'autonomy-1') {
const key = cohortKey({ direction: prediction.direction, eventType: prediction.event_type, horizonDays: prediction.horizon_days });
const calibration = latest.get(key);
const decision = calibration
? decide({ ...calibration, direction: prediction.direction }, { minSampleSize: 30 })
? decide(snapshotToDecisionInput(calibration, prediction.direction), { minSampleSize: 30 })
: { action: 'ABSTAIN', rationale: 'calibration unavailable' };
insert.run(prediction.id, decision.action, calibration?.directional_probability || null,
calibration?.expected_excess_return || null, decision.rationale, strategyVersion);
@@ -114,10 +151,12 @@ async function runCalibrationWorker({ intelligencePath, pollMs = 60000, workerId
while (true) {
try {
const state = db.prepare('SELECT COUNT(*) AS count, COALESCE(MAX(prediction_id), 0) AS max_id FROM autonomy_outcomes').get();
const groups = refreshCalibration(db, `cal-${state.count}-${state.max_id}`);
const version = `cal-${state.count}-${state.max_id}`;
const groups = refreshCalibration(db, version);
const historicalGroups = refreshHistoricalCalibration(db, version);
const decisions = createDecisions(db);
const replayEvaluations = refreshReplayEvaluations(db);
if (groups || decisions || replayEvaluations) console.log(`[${workerId}] calibration groups=${groups} decisions=${decisions} replay_evaluations=${replayEvaluations}`);
if (groups || historicalGroups || decisions || replayEvaluations) console.log(`[${workerId}] calibration groups=${groups} historical_groups=${historicalGroups} decisions=${decisions} replay_evaluations=${replayEvaluations}`);
} catch (error) {
console.error(`[${workerId}] calibration error:`, error.message);
}
@@ -125,4 +164,4 @@ async function runCalibrationWorker({ intelligencePath, pollMs = 60000, workerId
}
}
module.exports = { refreshCalibration, createDecisions, refreshReplayEvaluations, runCalibrationWorker };
module.exports = { refreshCalibration, refreshHistoricalCalibration, createDecisions, refreshReplayEvaluations, runCalibrationWorker };