feat: add isolated historical replay calibration
This commit is contained in:
@@ -34,9 +34,22 @@ function normalizeProposal(raw, { informationCutoff, model = 'unknown', promptVe
|
||||
};
|
||||
}
|
||||
|
||||
function verifyEvidence(archiveDb, articleIds) {
|
||||
function verifyEvidence(archiveDb, articleIds, informationCutoff = null) {
|
||||
const placeholders = articleIds.map(() => '?').join(',');
|
||||
const rows = archiveDb.prepare(`SELECT id FROM articles WHERE id IN (${placeholders})`).all(...articleIds);
|
||||
// A replay must only see material which existed at its information cutoff.
|
||||
// Live proposals retain the simpler existence check.
|
||||
const cutoffClause = informationCutoff ? ' AND datetime(COALESCE(pub_date_effective, pub_date, ingested_at)) <= datetime(?)' : '';
|
||||
let rows;
|
||||
try {
|
||||
rows = archiveDb.prepare(`SELECT id FROM articles WHERE id IN (${placeholders})${cutoffClause}`)
|
||||
.all(...articleIds, ...(informationCutoff ? [informationCutoff] : []));
|
||||
} catch (error) {
|
||||
// Minimal/test archives may not retain publication metadata. A production
|
||||
// replay archive is required to have it, so this fallback is only for the
|
||||
// existing live evidence contract.
|
||||
if (informationCutoff) throw error;
|
||||
rows = archiveDb.prepare(`SELECT id FROM articles WHERE id IN (${placeholders})`).all(...articleIds);
|
||||
}
|
||||
const found = new Set(rows.map((row) => row.id));
|
||||
return articleIds.every((id) => found.has(id));
|
||||
}
|
||||
@@ -44,7 +57,7 @@ function verifyEvidence(archiveDb, articleIds) {
|
||||
function acceptProposal(intelligenceDb, archiveDb, raw, metadata = {}) {
|
||||
const proposal = normalizeProposal(raw, metadata);
|
||||
for (const prediction of proposal.predictions) {
|
||||
if (!verifyEvidence(archiveDb, prediction.evidenceArticleIds)) {
|
||||
if (!verifyEvidence(archiveDb, prediction.evidenceArticleIds, metadata.origin === 'replay' ? proposal.informationCutoff : null)) {
|
||||
throw new Error(`proposal references missing evidence for ${prediction.instrument}`);
|
||||
}
|
||||
const instrument = intelligenceDb.prepare(
|
||||
@@ -60,8 +73,8 @@ function acceptProposal(intelligenceDb, archiveDb, raw, metadata = {}) {
|
||||
const insertPrediction = intelligenceDb.prepare(`
|
||||
INSERT INTO autonomy_predictions
|
||||
(proposal_id, event_id, instrument, direction, event_type, causal_channel,
|
||||
horizon_days, information_cutoff, evidence_article_ids, invalidation_condition, learning_eligible, strategy_version)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
horizon_days, information_cutoff, evidence_article_ids, invalidation_condition, learning_eligible, strategy_version, origin, replay_run_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
const tx = intelligenceDb.transaction(() => {
|
||||
const proposalResult = insert.run(metadata.eventId || null, JSON.stringify(proposal), proposal.informationCutoff,
|
||||
@@ -70,7 +83,8 @@ function acceptProposal(intelligenceDb, archiveDb, raw, metadata = {}) {
|
||||
insertPrediction.run(proposalResult.lastInsertRowid, metadata.eventId || null, prediction.instrument,
|
||||
prediction.direction, prediction.eventType, prediction.causalChannel, prediction.horizonDays,
|
||||
proposal.informationCutoff, JSON.stringify(prediction.evidenceArticleIds), prediction.invalidationCondition,
|
||||
metadata.learningEligible ? 1 : 0, metadata.strategyVersion || 'autonomy-1');
|
||||
metadata.learningEligible ? 1 : 0, metadata.strategyVersion || 'autonomy-1',
|
||||
metadata.origin || 'live', metadata.replayRunId || null);
|
||||
}
|
||||
return Number(proposalResult.lastInsertRowid);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user