fix: use async postgres for autonomy http routes

This commit is contained in:
ImBenji
2026-08-17 14:10:29 +01:00
parent aea0ed437e
commit 7ff69ad99d
3 changed files with 101 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
const { Pool } = require('pg');
const pools = new Map();
function postgresUrl() { return process.env.DURIIN_POSTGRES_URL || process.env.DATABASE_URL; }
function poolFor(schema = 'intelligence') {
const connectionString = postgresUrl();
if (!connectionString) throw new Error('DURIIN_POSTGRES_URL is required');
const key = `${connectionString}|${schema}`;
if (!pools.has(key)) {
pools.set(key, new Pool({
connectionString,
max: Math.max(1, Number(process.env.POSTGRES_HTTP_POOL_SIZE) || 4),
options: `-c search_path=${schema},public`,
}));
}
return pools.get(key);
}
async function all(schema, sql, params = []) { return (await poolFor(schema).query(sql, params)).rows; }
async function get(schema, sql, params = []) { return (await poolFor(schema).query(sql, params)).rows[0]; }
module.exports = { poolFor, all, get };
+63
View File
@@ -6,6 +6,7 @@ const db = require('../db');
const config = require('../config'); const config = require('../config');
const Database = require('better-sqlite3'); const Database = require('better-sqlite3');
const { openRuntimeDb, isPostgresEnabled } = require('../db/runtime'); const { openRuntimeDb, isPostgresEnabled } = require('../db/runtime');
const pg = require('../db/pgAsync');
let idb = null; let idb = null;
let statsSummaryCache = null; let statsSummaryCache = null;
@@ -152,6 +153,68 @@ async function adminRoutes(fastify) {
// public status endpoint. // public status endpoint.
fastify.get('/admin/api/autonomy/overview', async (request, reply) => { fastify.get('/admin/api/autonomy/overview', async (request, reply) => {
if (!checkAuth(request, reply)) return; if (!checkAuth(request, reply)) return;
if (isPostgresEnabled()) {
const hasSchema = await pg.get('intelligence', "SELECT 1 FROM information_schema.tables WHERE table_schema = $1 AND table_name = $2", ['intelligence', 'autonomy_jobs']);
if (!hasSchema) return { enabled: false, reason: 'autonomy schema is not initialized' };
const [jobs, predictionCounts, decisionCounts, proposalCounts, outcomeSummary, instruments, latestRows, latestOrders, account, calibration, replay] = await Promise.all([
pg.all('intelligence', 'SELECT lane, status, COUNT(*) AS count FROM autonomy_jobs GROUP BY lane, status ORDER BY lane, status'),
pg.all('intelligence', 'SELECT status, COUNT(*) AS count FROM autonomy_predictions GROUP BY status'),
pg.all('intelligence', 'SELECT action, COUNT(*) AS count FROM autonomy_decisions GROUP BY action'),
pg.all('intelligence', 'SELECT status, COUNT(*) AS count FROM autonomy_proposals GROUP BY status'),
pg.get('intelligence', "SELECT COUNT(*) AS total, SUM(direction_correct) AS correct, AVG(excess_return) AS average_excess_return FROM autonomy_outcomes o JOIN autonomy_predictions p ON p.id = o.prediction_id WHERE p.origin = 'live'"),
pg.get('intelligence', 'SELECT COUNT(*) AS count FROM autonomy_instruments WHERE active=1 AND tradable=1'),
pg.all('intelligence', `
SELECT p.id, p.instrument, p.direction, p.event_type, p.causal_channel,
p.horizon_days, p.information_cutoff, p.evidence_article_ids,
p.invalidation_condition, p.learning_eligible, p.status, p.created_at,
d.action, d.calibrated_probability, d.expected_excess_return, d.rationale,
o.excess_return, o.direction_correct
FROM autonomy_predictions p
LEFT JOIN autonomy_decisions d ON d.id = (
SELECT MAX(d2.id) FROM autonomy_decisions d2 WHERE d2.prediction_id = p.id
)
LEFT JOIN autonomy_outcomes o ON o.prediction_id = p.id
ORDER BY p.id DESC LIMIT 12
`),
pg.all('intelligence', `
SELECT oi.id, oi.client_order_id, oi.instrument, oi.side, oi.notional,
oi.status, oi.broker_order_id, oi.attempts, oi.last_error,
oi.created_at, oi.updated_at, d.action
FROM autonomy_order_intents oi
JOIN autonomy_decisions d ON d.id = oi.decision_id
ORDER BY oi.id DESC LIMIT 12
`),
pg.get('intelligence', 'SELECT broker, equity, cash, buying_power, captured_at FROM autonomy_account_snapshots ORDER BY id DESC LIMIT 1'),
pg.all('intelligence', 'SELECT cohort_key, sample_size, effective_sample_size, directional_probability, expected_excess_return, lower_return, upper_return, created_at FROM autonomy_calibration_snapshots ORDER BY id DESC LIMIT 8'),
pg.get('intelligence', `
SELECT r.id, r.status, r.watermark_at, r.cursor_article_id, r.cursor_effective_at,
r.processed_articles, r.updated_at,
SUM(CASE WHEN p.status = 'resolved' THEN 1 ELSE 0 END) AS resolved_predictions,
COUNT(p.id) AS predictions,
SUM(o.direction_correct) AS correct_predictions,
(SELECT COUNT(*) FROM autonomy_replay_evaluations e WHERE e.replay_run_id = r.id) AS evaluations
FROM autonomy_replay_runs r
LEFT JOIN autonomy_predictions p ON p.replay_run_id = r.id
LEFT JOIN autonomy_outcomes o ON o.prediction_id = p.id
GROUP BY r.id ORDER BY r.id DESC LIMIT 1
`),
]);
const latestPredictions = latestRows.map((row) => {
let evidenceCount = 0;
try { evidenceCount = JSON.parse(row.evidence_article_ids || '[]').length; } catch (_) {}
const { evidence_article_ids: ignored, ...safeRow } = row;
return { ...safeRow, evidence_count: evidenceCount };
});
return {
enabled: true,
mode: process.env.AUTONOMY_EXECUTION_MODE || 'shadow',
broker: { name: 'Alpaca Paper', configured: Boolean(process.env.ALPACA_PAPER_KEY_ID && process.env.ALPACA_PAPER_SECRET_KEY) },
jobs, predictionCounts, decisionCounts, proposalCounts, outcomes: outcomeSummary,
allowlistedInstruments: instruments.count, latestPredictions, latestOrders,
account: account || null, calibration, replay: replay || null,
generatedAt: new Date().toISOString(),
};
}
const intelligenceDb = getIntelligenceDb(); const intelligenceDb = getIntelligenceDb();
if (!intelligenceDb) return { enabled: false, reason: 'intelligence database unavailable' }; if (!intelligenceDb) return { enabled: false, reason: 'intelligence database unavailable' };
const hasSchema = isPostgresEnabled() const hasSchema = isPostgresEnabled()
+14
View File
@@ -1,5 +1,6 @@
const path = require('path'); const path = require('path');
const { openRuntimeDb, isPostgresEnabled } = require('../db/runtime'); const { openRuntimeDb, isPostgresEnabled } = require('../db/runtime');
const pg = require('../db/pgAsync');
const intelligencePath = process.env.INTELLIGENCE_DB || path.resolve(process.cwd(), 'intelligence.sqlite'); const intelligencePath = process.env.INTELLIGENCE_DB || path.resolve(process.cwd(), 'intelligence.sqlite');
const db = openRuntimeDb(intelligencePath, { schema: 'intelligence', readonly: true }); const db = openRuntimeDb(intelligencePath, { schema: 'intelligence', readonly: true });
@@ -8,6 +9,19 @@ async function autonomyRoutes(fastify) {
fastify.get('/health', async () => ({ ok: true, service: 'duriin-api' })); fastify.get('/health', async () => ({ ok: true, service: 'duriin-api' }));
fastify.get('/autonomy/status', async () => { fastify.get('/autonomy/status', async () => {
if (isPostgresEnabled()) {
const exists = await pg.get('intelligence', "SELECT 1 FROM information_schema.tables WHERE table_schema = $1 AND table_name = $2", ['intelligence', 'autonomy_jobs']);
if (!exists) return { enabled: false, reason: 'autonomy schema is not initialized' };
const [jobs, predictions, decisions, outcomes, legacy, instruments] = await Promise.all([
pg.all('intelligence', 'SELECT lane, status, COUNT(*) AS count FROM autonomy_jobs GROUP BY lane, status ORDER BY lane, status'),
pg.all('intelligence', 'SELECT status, COUNT(*) AS count FROM autonomy_predictions GROUP BY status ORDER BY status'),
pg.all('intelligence', 'SELECT action, COUNT(*) AS count FROM autonomy_decisions GROUP BY action ORDER BY action'),
pg.get('intelligence', 'SELECT COUNT(*) AS total, SUM(direction_correct) AS correct, AVG(excess_return) AS average_excess_return FROM autonomy_outcomes'),
pg.get('intelligence', 'SELECT COUNT(*) AS count FROM autonomy_legacy_records'),
pg.get('intelligence', 'SELECT COUNT(*) AS count FROM autonomy_instruments WHERE active=1 AND tradable=1'),
]);
return { enabled: true, jobs, predictions, decisions, outcomes, legacyRecords: legacy.count, allowlistedInstruments: instruments.count };
}
const exists = isPostgresEnabled() const exists = isPostgresEnabled()
? db.prepare("SELECT 1 FROM information_schema.tables WHERE table_schema = ? AND table_name = ?").get('intelligence', 'autonomy_jobs') ? db.prepare("SELECT 1 FROM information_schema.tables WHERE table_schema = ? AND table_name = ?").get('intelligence', 'autonomy_jobs')
: db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='autonomy_jobs'").get(); : db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='autonomy_jobs'").get();