@timmeck/brain-core 2.10.0 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,301 @@
1
+ // ── Prediction Engine — Proactive Forecasting ────────────────
2
+ //
3
+ // Records domain metrics, generates Holt-Winters / EWMA predictions,
4
+ // resolves them against reality, and auto-calibrates confidence.
5
+ import { randomUUID } from 'node:crypto';
6
+ import { getLogger } from '../utils/logger.js';
7
+ import { holtWintersForecast, ewmaForecast, calibrateConfidence } from './forecaster.js';
8
+ import { PredictionTracker } from './tracker.js';
9
+ // ── Migration ───────────────────────────────────────────
10
+ export function runPredictionMigration(db) {
11
+ db.exec(`
12
+ CREATE TABLE IF NOT EXISTS predictions (
13
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14
+ prediction_id TEXT NOT NULL UNIQUE,
15
+ domain TEXT NOT NULL,
16
+ metric TEXT NOT NULL,
17
+ predicted_value REAL NOT NULL,
18
+ predicted_direction TEXT NOT NULL,
19
+ confidence REAL NOT NULL,
20
+ horizon_ms INTEGER NOT NULL,
21
+ reasoning TEXT NOT NULL DEFAULT '',
22
+ method TEXT NOT NULL,
23
+ status TEXT NOT NULL DEFAULT 'pending',
24
+ actual_value REAL,
25
+ error REAL,
26
+ created_at INTEGER NOT NULL,
27
+ resolved_at INTEGER,
28
+ expires_at INTEGER NOT NULL,
29
+ evidence TEXT NOT NULL DEFAULT '{}'
30
+ );
31
+ CREATE INDEX IF NOT EXISTS idx_predictions_status ON predictions(status);
32
+ CREATE INDEX IF NOT EXISTS idx_predictions_domain ON predictions(domain);
33
+ CREATE INDEX IF NOT EXISTS idx_predictions_metric ON predictions(metric);
34
+ CREATE INDEX IF NOT EXISTS idx_predictions_expires ON predictions(expires_at);
35
+
36
+ CREATE TABLE IF NOT EXISTS prediction_metrics (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ metric TEXT NOT NULL,
39
+ value REAL NOT NULL,
40
+ domain TEXT NOT NULL DEFAULT 'metric',
41
+ timestamp INTEGER NOT NULL
42
+ );
43
+ CREATE INDEX IF NOT EXISTS idx_pred_metrics_metric ON prediction_metrics(metric);
44
+ CREATE INDEX IF NOT EXISTS idx_pred_metrics_ts ON prediction_metrics(timestamp);
45
+
46
+ CREATE TABLE IF NOT EXISTS prediction_state (
47
+ id INTEGER PRIMARY KEY CHECK(id = 1),
48
+ total_predictions INTEGER NOT NULL DEFAULT 0,
49
+ total_resolved INTEGER NOT NULL DEFAULT 0,
50
+ total_correct INTEGER NOT NULL DEFAULT 0,
51
+ calibration_offset REAL NOT NULL DEFAULT 0.0,
52
+ updated_at TEXT DEFAULT (datetime('now'))
53
+ );
54
+ INSERT OR IGNORE INTO prediction_state (id) VALUES (1);
55
+ `);
56
+ }
57
+ // ── Engine ──────────────────────────────────────────────
58
+ export class PredictionEngine {
59
+ db;
60
+ config;
61
+ tracker;
62
+ thoughtStream = null;
63
+ journal = null;
64
+ timer = null;
65
+ calibrationOffset = 0;
66
+ log = getLogger();
67
+ constructor(db, config) {
68
+ this.db = db;
69
+ this.config = {
70
+ brainName: config.brainName,
71
+ defaultHorizonMs: config.defaultHorizonMs ?? 3_600_000,
72
+ expirationMs: config.expirationMs ?? 86_400_000,
73
+ ewmaAlpha: config.ewmaAlpha ?? 0.3,
74
+ trendBeta: config.trendBeta ?? 0.1,
75
+ minDataPoints: config.minDataPoints ?? 5,
76
+ minConfidence: config.minConfidence ?? 0.3,
77
+ maxPredictionsPerCycle: config.maxPredictionsPerCycle ?? 5,
78
+ resolveIntervalMs: config.resolveIntervalMs ?? 60_000,
79
+ };
80
+ runPredictionMigration(db);
81
+ this.tracker = new PredictionTracker(db);
82
+ // Load calibration offset from DB
83
+ this.calibrationOffset = this.tracker.getCalibrationOffset();
84
+ }
85
+ /** Set the ThoughtStream for consciousness integration. */
86
+ setThoughtStream(stream) {
87
+ this.thoughtStream = stream;
88
+ }
89
+ /** Set the Research Journal for logging notable predictions. */
90
+ setJournal(journal) {
91
+ this.journal = journal;
92
+ }
93
+ /** Start periodic resolution timer. */
94
+ start() {
95
+ if (this.timer)
96
+ return;
97
+ this.timer = setInterval(() => {
98
+ try {
99
+ this.cycle();
100
+ }
101
+ catch (err) {
102
+ this.log.error(`[prediction] Cycle error: ${err.message}`);
103
+ }
104
+ }, this.config.resolveIntervalMs);
105
+ this.log.info(`[prediction] Engine started (resolve interval: ${this.config.resolveIntervalMs}ms)`);
106
+ }
107
+ /** Stop the resolution timer. */
108
+ stop() {
109
+ if (this.timer) {
110
+ clearInterval(this.timer);
111
+ this.timer = null;
112
+ }
113
+ }
114
+ /** Record a metric data point. Called by domain event handlers. */
115
+ recordMetric(metric, value, domain = 'metric') {
116
+ this.db.prepare(`
117
+ INSERT INTO prediction_metrics (metric, value, domain, timestamp) VALUES (?, ?, ?, ?)
118
+ `).run(metric, value, domain, Date.now());
119
+ }
120
+ /**
121
+ * Generate a prediction for a specific metric.
122
+ * Returns null if insufficient data or confidence too low.
123
+ */
124
+ predict(input) {
125
+ const horizonMs = input.horizon_ms ?? this.config.defaultHorizonMs;
126
+ const history = this.getMetricHistory(input.metric, 100);
127
+ if (history.length < 2)
128
+ return null;
129
+ const data = history.map(h => h.value);
130
+ const forecast = this.forecast(data, horizonMs);
131
+ // Apply calibration
132
+ const calibratedConfidence = this.applyCalibration(forecast.confidence);
133
+ if (calibratedConfidence < this.config.minConfidence)
134
+ return null;
135
+ const now = Date.now();
136
+ const prediction = {
137
+ prediction_id: randomUUID(),
138
+ domain: input.domain,
139
+ metric: input.metric,
140
+ predicted_value: forecast.value,
141
+ predicted_direction: forecast.direction,
142
+ confidence: calibratedConfidence,
143
+ horizon_ms: horizonMs,
144
+ reasoning: input.reasoning ?? `${forecast.method} forecast (${forecast.dataPoints} points, trend: ${forecast.trend.toFixed(4)})`,
145
+ method: forecast.method,
146
+ status: 'pending',
147
+ created_at: now,
148
+ expires_at: now + this.config.expirationMs,
149
+ evidence: {
150
+ ...input.evidence,
151
+ dataPoints: forecast.dataPoints,
152
+ trend: forecast.trend,
153
+ rawConfidence: forecast.confidence,
154
+ calibrationOffset: this.calibrationOffset,
155
+ },
156
+ };
157
+ this.tracker.store(prediction);
158
+ // Update state
159
+ this.db.prepare(`
160
+ UPDATE prediction_state SET total_predictions = total_predictions + 1, updated_at = datetime('now') WHERE id = 1
161
+ `).run();
162
+ // Emit thought
163
+ this.thoughtStream?.emit('prediction', 'predicting', `Predicted ${input.metric}: ${forecast.direction} to ${forecast.value.toFixed(2)} (${(calibratedConfidence * 100).toFixed(0)}% confidence)`, calibratedConfidence > 0.7 ? 'notable' : 'routine');
164
+ // Journal high-confidence predictions
165
+ if (calibratedConfidence > 0.6 && this.journal) {
166
+ try {
167
+ this.journal.recordDiscovery(`Prediction: ${input.metric} → ${forecast.direction}`, `Forecasted ${input.metric} will go ${forecast.direction} to ${forecast.value.toFixed(2)} within ${(horizonMs / 60_000).toFixed(0)}min. Method: ${forecast.method}, confidence: ${(calibratedConfidence * 100).toFixed(0)}%.`, { prediction_id: prediction.prediction_id, ...prediction.evidence }, calibratedConfidence > 0.8 ? 'notable' : 'routine');
168
+ }
169
+ catch { /* best effort */ }
170
+ }
171
+ return prediction;
172
+ }
173
+ /**
174
+ * Auto-predict for all tracked metrics that have enough data.
175
+ * Skips metrics with pending predictions. Respects maxPredictionsPerCycle.
176
+ */
177
+ autoPredictAll() {
178
+ const trackedMetrics = this.getTrackedMetrics();
179
+ const pendingMetrics = this.tracker.getMetricsWithPending();
180
+ const predictions = [];
181
+ for (const { metric, domain } of trackedMetrics) {
182
+ if (predictions.length >= this.config.maxPredictionsPerCycle)
183
+ break;
184
+ if (pendingMetrics.has(metric))
185
+ continue;
186
+ const prediction = this.predict({ domain, metric });
187
+ if (prediction)
188
+ predictions.push(prediction);
189
+ }
190
+ return predictions;
191
+ }
192
+ /**
193
+ * Resolve expired/due predictions against current metric values.
194
+ * Returns count of resolved predictions.
195
+ */
196
+ resolveExpired() {
197
+ let resolved = 0;
198
+ // 1. Mark truly expired (past expiration, no data)
199
+ const expired = this.tracker.getPendingExpired();
200
+ for (const pred of expired) {
201
+ this.tracker.markExpired(pred.prediction_id);
202
+ resolved++;
203
+ }
204
+ // 2. Resolve predictions past their horizon
205
+ const resolvable = this.tracker.getPendingResolvable();
206
+ for (const pred of resolvable) {
207
+ // Get the latest metric value after the prediction was made
208
+ const row = this.db.prepare(`
209
+ SELECT AVG(value) as avg_value FROM prediction_metrics
210
+ WHERE metric = ? AND timestamp > ?
211
+ ORDER BY timestamp DESC LIMIT 10
212
+ `).get(pred.metric, pred.created_at);
213
+ if (row?.avg_value != null) {
214
+ this.tracker.resolve(pred.prediction_id, row.avg_value);
215
+ resolved++;
216
+ }
217
+ }
218
+ // 3. Recalibrate if we resolved anything
219
+ if (resolved > 0) {
220
+ this.recalibrate();
221
+ }
222
+ return resolved;
223
+ }
224
+ /** List predictions with optional filters. */
225
+ list(domain, status, limit) {
226
+ return this.tracker.list(domain, status, limit);
227
+ }
228
+ /** Get accuracy statistics. */
229
+ getAccuracy(domain) {
230
+ return this.tracker.getAccuracy(domain);
231
+ }
232
+ /** Get full prediction summary. */
233
+ getSummary() {
234
+ const state = this.db.prepare('SELECT * FROM prediction_state WHERE id = 1').get();
235
+ const accuracy = this.tracker.getAccuracy();
236
+ const recent = this.tracker.list(undefined, undefined, 10);
237
+ const totalPredictions = state?.total_predictions ?? 0;
238
+ const pending = this.tracker.list(undefined, 'pending', 1000).length;
239
+ const resolved = totalPredictions - pending;
240
+ // Overall accuracy
241
+ const totalCorrect = accuracy.reduce((sum, a) => sum + a.correct, 0);
242
+ const totalResolved = accuracy.reduce((sum, a) => sum + a.total - a.expired, 0);
243
+ return {
244
+ total_predictions: totalPredictions,
245
+ pending,
246
+ resolved,
247
+ accuracy_rate: totalResolved > 0 ? totalCorrect / totalResolved : 0,
248
+ by_domain: accuracy,
249
+ calibration_offset: this.calibrationOffset,
250
+ recent,
251
+ };
252
+ }
253
+ /** Run a forecast on raw data. Uses Holt-Winters (≥ minDataPoints) or EWMA. */
254
+ forecast(data, _horizonMs) {
255
+ // steps = horizon / avg interval between data points (default 1)
256
+ const steps = 1;
257
+ if (data.length >= this.config.minDataPoints) {
258
+ return holtWintersForecast(data, steps, this.config.ewmaAlpha, this.config.trendBeta);
259
+ }
260
+ return ewmaForecast(data, this.config.ewmaAlpha);
261
+ }
262
+ /** Get metric history as time series. */
263
+ getMetricHistory(metric, limit = 100) {
264
+ return this.db.prepare(`
265
+ SELECT value, timestamp FROM prediction_metrics
266
+ WHERE metric = ?
267
+ ORDER BY timestamp ASC
268
+ LIMIT ?
269
+ `).all(metric, limit);
270
+ }
271
+ // ── Private ─────────────────────────────────────────────
272
+ /** Periodic cycle: resolve + auto-predict. */
273
+ cycle() {
274
+ const resolved = this.resolveExpired();
275
+ if (resolved > 0) {
276
+ this.log.debug(`[prediction] Resolved ${resolved} predictions`);
277
+ }
278
+ }
279
+ /** Recalibrate confidence offset based on historical accuracy. */
280
+ recalibrate() {
281
+ this.calibrationOffset = this.tracker.getCalibrationOffset();
282
+ this.db.prepare(`
283
+ UPDATE prediction_state SET calibration_offset = ?, updated_at = datetime('now') WHERE id = 1
284
+ `).run(this.calibrationOffset);
285
+ }
286
+ /** Apply calibration to raw confidence. */
287
+ applyCalibration(rawConfidence) {
288
+ const buckets = this.tracker.getCalibrationBuckets();
289
+ return calibrateConfidence(rawConfidence, buckets);
290
+ }
291
+ /** Get all unique tracked metrics with their domains. */
292
+ getTrackedMetrics() {
293
+ return this.db.prepare(`
294
+ SELECT DISTINCT metric, domain FROM prediction_metrics
295
+ GROUP BY metric
296
+ HAVING COUNT(*) >= 2
297
+ ORDER BY COUNT(*) DESC
298
+ `).all();
299
+ }
300
+ }
301
+ //# sourceMappingURL=prediction-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prediction-engine.js","sourceRoot":"","sources":["../../src/prediction/prediction-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { Prediction, PredictionDomain, PredictionStatus, PredictionAccuracy, CalibrationBucket } from './types.js';
3
+ export declare class PredictionTracker {
4
+ private db;
5
+ constructor(db: Database.Database);
6
+ /** Store a new prediction in the DB. */
7
+ store(prediction: Prediction): string;
8
+ /** Resolve a prediction against actual value. Returns the determined status. */
9
+ resolve(predictionId: string, actualValue: number): PredictionStatus;
10
+ /** List predictions with optional filters. */
11
+ list(domain?: PredictionDomain, status?: PredictionStatus, limit?: number): Prediction[];
12
+ /** Get accuracy statistics, optionally by domain. */
13
+ getAccuracy(domain?: PredictionDomain): PredictionAccuracy[];
14
+ /** Get calibration offset — average (confidence - actual_accuracy) across buckets. */
15
+ getCalibrationOffset(): number;
16
+ /** Get calibration buckets for confidence adjustment. */
17
+ getCalibrationBuckets(): CalibrationBucket[];
18
+ /** Get pending predictions that have expired (past expires_at). */
19
+ getPendingExpired(): Prediction[];
20
+ /** Get pending predictions that are within their horizon (ready for resolution check). */
21
+ getPendingResolvable(): Prediction[];
22
+ /** Mark a prediction as expired. */
23
+ markExpired(predictionId: string): void;
24
+ /** Get metrics with pending predictions (to skip in autoPredictAll). */
25
+ getMetricsWithPending(): Set<string>;
26
+ private rowToPrediction;
27
+ }
@@ -0,0 +1,221 @@
1
+ // ── Prediction Tracker — DB-backed Storage + Resolution ──────
2
+ export class PredictionTracker {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ /** Store a new prediction in the DB. */
8
+ store(prediction) {
9
+ this.db.prepare(`
10
+ INSERT INTO predictions (
11
+ prediction_id, domain, metric, predicted_value, predicted_direction,
12
+ confidence, horizon_ms, reasoning, method, status,
13
+ created_at, expires_at, evidence
14
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
15
+ `).run(prediction.prediction_id, prediction.domain, prediction.metric, prediction.predicted_value, prediction.predicted_direction, prediction.confidence, prediction.horizon_ms, prediction.reasoning, prediction.method, prediction.status, prediction.created_at, prediction.expires_at, JSON.stringify(prediction.evidence));
16
+ return prediction.prediction_id;
17
+ }
18
+ /** Resolve a prediction against actual value. Returns the determined status. */
19
+ resolve(predictionId, actualValue) {
20
+ const row = this.db.prepare(`
21
+ SELECT * FROM predictions WHERE prediction_id = ?
22
+ `).get(predictionId);
23
+ if (!row)
24
+ return 'expired';
25
+ const predicted = row.predicted_value;
26
+ const predictedDir = row.predicted_direction;
27
+ // Calculate error
28
+ const error = predicted !== 0
29
+ ? Math.abs(predicted - actualValue) / Math.abs(predicted)
30
+ : Math.abs(actualValue);
31
+ // Determine actual direction
32
+ let actualDir;
33
+ if (actualValue > predicted * 1.02)
34
+ actualDir = 'up';
35
+ else if (actualValue < predicted * 0.98)
36
+ actualDir = 'down';
37
+ else
38
+ actualDir = 'stable';
39
+ const directionCorrect = predictedDir === actualDir;
40
+ // Determine status
41
+ let status;
42
+ if (error < 0.10 && directionCorrect) {
43
+ status = 'correct';
44
+ }
45
+ else if (error < 0.25 || directionCorrect) {
46
+ status = 'partial';
47
+ }
48
+ else {
49
+ status = 'wrong';
50
+ }
51
+ const now = Date.now();
52
+ this.db.prepare(`
53
+ UPDATE predictions SET
54
+ status = ?, actual_value = ?, error = ?, resolved_at = ?
55
+ WHERE prediction_id = ?
56
+ `).run(status, actualValue, error, now, predictionId);
57
+ return status;
58
+ }
59
+ /** List predictions with optional filters. */
60
+ list(domain, status, limit = 50) {
61
+ let sql = 'SELECT * FROM predictions WHERE 1=1';
62
+ const params = [];
63
+ if (domain) {
64
+ sql += ' AND domain = ?';
65
+ params.push(domain);
66
+ }
67
+ if (status) {
68
+ sql += ' AND status = ?';
69
+ params.push(status);
70
+ }
71
+ sql += ' ORDER BY created_at DESC LIMIT ?';
72
+ params.push(limit);
73
+ const rows = this.db.prepare(sql).all(...params);
74
+ return rows.map(r => this.rowToPrediction(r));
75
+ }
76
+ /** Get accuracy statistics, optionally by domain. */
77
+ getAccuracy(domain) {
78
+ let sql = `
79
+ SELECT
80
+ domain,
81
+ COUNT(*) as total,
82
+ SUM(CASE WHEN status = 'correct' THEN 1 ELSE 0 END) as correct,
83
+ SUM(CASE WHEN status = 'wrong' THEN 1 ELSE 0 END) as wrong,
84
+ SUM(CASE WHEN status = 'partial' THEN 1 ELSE 0 END) as partial,
85
+ SUM(CASE WHEN status = 'expired' THEN 1 ELSE 0 END) as expired,
86
+ AVG(CASE WHEN error IS NOT NULL THEN error ELSE NULL END) as mean_error,
87
+ AVG(confidence) as avg_confidence
88
+ FROM predictions
89
+ WHERE status != 'pending'
90
+ `;
91
+ const params = [];
92
+ if (domain) {
93
+ sql += ' AND domain = ?';
94
+ params.push(domain);
95
+ }
96
+ sql += ' GROUP BY domain';
97
+ const rows = this.db.prepare(sql).all(...params);
98
+ return rows.map(r => {
99
+ const total = r.total;
100
+ const correct = r.correct;
101
+ const wrong = r.wrong;
102
+ const partial = r.partial;
103
+ const expired = r.expired;
104
+ // Direction accuracy: correct + partial count as direction-correct
105
+ const resolved = total - expired;
106
+ const directionCorrect = correct + partial;
107
+ // Calibration score: how close avg confidence is to actual accuracy
108
+ const actualAccuracy = resolved > 0 ? correct / resolved : 0;
109
+ const avgConfidence = r.avg_confidence;
110
+ const calibrationScore = 1 - Math.abs(actualAccuracy - avgConfidence);
111
+ return {
112
+ domain: r.domain,
113
+ total,
114
+ correct,
115
+ wrong,
116
+ partial,
117
+ expired,
118
+ accuracy_rate: resolved > 0 ? correct / resolved : 0,
119
+ mean_absolute_error: r.mean_error ?? 0,
120
+ calibration_score: Math.max(0, calibrationScore),
121
+ direction_accuracy: resolved > 0 ? directionCorrect / resolved : 0,
122
+ };
123
+ });
124
+ }
125
+ /** Get calibration offset — average (confidence - actual_accuracy) across buckets. */
126
+ getCalibrationOffset() {
127
+ const buckets = this.getCalibrationBuckets();
128
+ if (buckets.length === 0)
129
+ return 0;
130
+ let totalOffset = 0;
131
+ let count = 0;
132
+ for (const b of buckets) {
133
+ if (b.predicted_count >= 3) {
134
+ const midpoint = (b.range_start + b.range_end) / 2;
135
+ totalOffset += midpoint - b.actual_accuracy;
136
+ count++;
137
+ }
138
+ }
139
+ return count > 0 ? totalOffset / count : 0;
140
+ }
141
+ /** Get calibration buckets for confidence adjustment. */
142
+ getCalibrationBuckets() {
143
+ const bucketRanges = [
144
+ [0.0, 0.2], [0.2, 0.4], [0.4, 0.6], [0.6, 0.8], [0.8, 1.01],
145
+ ];
146
+ const buckets = [];
147
+ for (const [start, end] of bucketRanges) {
148
+ const row = this.db.prepare(`
149
+ SELECT
150
+ COUNT(*) as total,
151
+ SUM(CASE WHEN status = 'correct' THEN 1 ELSE 0 END) as correct
152
+ FROM predictions
153
+ WHERE status != 'pending'
154
+ AND confidence >= ? AND confidence < ?
155
+ `).get(start, end);
156
+ const total = row.total ?? 0;
157
+ const correct = row.correct ?? 0;
158
+ buckets.push({
159
+ range_start: start,
160
+ range_end: end === 1.01 ? 1.0 : end,
161
+ predicted_count: total,
162
+ actual_accuracy: total > 0 ? correct / total : 0,
163
+ });
164
+ }
165
+ return buckets;
166
+ }
167
+ /** Get pending predictions that have expired (past expires_at). */
168
+ getPendingExpired() {
169
+ const now = Date.now();
170
+ const rows = this.db.prepare(`
171
+ SELECT * FROM predictions
172
+ WHERE status = 'pending' AND expires_at < ?
173
+ ORDER BY created_at ASC
174
+ `).all(now);
175
+ return rows.map(r => this.rowToPrediction(r));
176
+ }
177
+ /** Get pending predictions that are within their horizon (ready for resolution check). */
178
+ getPendingResolvable() {
179
+ const now = Date.now();
180
+ const rows = this.db.prepare(`
181
+ SELECT * FROM predictions
182
+ WHERE status = 'pending' AND (created_at + horizon_ms) <= ? AND expires_at >= ?
183
+ ORDER BY created_at ASC
184
+ `).all(now, now);
185
+ return rows.map(r => this.rowToPrediction(r));
186
+ }
187
+ /** Mark a prediction as expired. */
188
+ markExpired(predictionId) {
189
+ this.db.prepare(`
190
+ UPDATE predictions SET status = 'expired', resolved_at = ? WHERE prediction_id = ?
191
+ `).run(Date.now(), predictionId);
192
+ }
193
+ /** Get metrics with pending predictions (to skip in autoPredictAll). */
194
+ getMetricsWithPending() {
195
+ const rows = this.db.prepare(`
196
+ SELECT DISTINCT metric FROM predictions WHERE status = 'pending'
197
+ `).all();
198
+ return new Set(rows.map(r => r.metric));
199
+ }
200
+ rowToPrediction(r) {
201
+ return {
202
+ prediction_id: r.prediction_id,
203
+ domain: r.domain,
204
+ metric: r.metric,
205
+ predicted_value: r.predicted_value,
206
+ predicted_direction: r.predicted_direction,
207
+ confidence: r.confidence,
208
+ horizon_ms: r.horizon_ms,
209
+ reasoning: r.reasoning,
210
+ method: r.method,
211
+ status: r.status,
212
+ actual_value: r.actual_value,
213
+ error: r.error,
214
+ created_at: r.created_at,
215
+ resolved_at: r.resolved_at,
216
+ expires_at: r.expires_at,
217
+ evidence: typeof r.evidence === 'string' ? JSON.parse(r.evidence) : r.evidence ?? {},
218
+ };
219
+ }
220
+ }
221
+ //# sourceMappingURL=tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../src/prediction/tracker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ export type PredictionDomain = 'error' | 'trade' | 'engagement' | 'metric' | 'custom';
2
+ export type PredictionStatus = 'pending' | 'correct' | 'wrong' | 'expired' | 'partial';
3
+ export interface PredictionEngineConfig {
4
+ brainName: string;
5
+ /** Default forecast horizon in ms. Default: 3_600_000 (1h) */
6
+ defaultHorizonMs?: number;
7
+ /** Max time before prediction expires in ms. Default: 86_400_000 (24h) */
8
+ expirationMs?: number;
9
+ /** EWMA smoothing factor (0–1). Default: 0.3 */
10
+ ewmaAlpha?: number;
11
+ /** Holt-Winters trend smoothing factor (0–1). Default: 0.1 */
12
+ trendBeta?: number;
13
+ /** Min data points for Holt-Winters (falls back to EWMA below). Default: 5 */
14
+ minDataPoints?: number;
15
+ /** Predictions below this confidence are suppressed. Default: 0.3 */
16
+ minConfidence?: number;
17
+ /** Max predictions generated per autoPredictAll cycle. Default: 5 */
18
+ maxPredictionsPerCycle?: number;
19
+ /** Interval for resolving expired predictions in ms. Default: 60_000 (1 min) */
20
+ resolveIntervalMs?: number;
21
+ }
22
+ export interface Prediction {
23
+ prediction_id: string;
24
+ domain: PredictionDomain;
25
+ metric: string;
26
+ predicted_value: number;
27
+ predicted_direction: 'up' | 'down' | 'stable';
28
+ confidence: number;
29
+ horizon_ms: number;
30
+ reasoning: string;
31
+ method: 'ewma' | 'holt_winters';
32
+ status: PredictionStatus;
33
+ actual_value?: number;
34
+ error?: number;
35
+ created_at: number;
36
+ resolved_at?: number;
37
+ expires_at: number;
38
+ evidence: Record<string, unknown>;
39
+ }
40
+ export interface PredictionAccuracy {
41
+ domain: PredictionDomain;
42
+ total: number;
43
+ correct: number;
44
+ wrong: number;
45
+ partial: number;
46
+ expired: number;
47
+ accuracy_rate: number;
48
+ mean_absolute_error: number;
49
+ calibration_score: number;
50
+ direction_accuracy: number;
51
+ }
52
+ export interface PredictionSummary {
53
+ total_predictions: number;
54
+ pending: number;
55
+ resolved: number;
56
+ accuracy_rate: number;
57
+ by_domain: PredictionAccuracy[];
58
+ calibration_offset: number;
59
+ recent: Prediction[];
60
+ }
61
+ export interface ForecastResult {
62
+ value: number;
63
+ trend: number;
64
+ confidence: number;
65
+ direction: 'up' | 'down' | 'stable';
66
+ method: 'ewma' | 'holt_winters';
67
+ dataPoints: number;
68
+ }
69
+ export interface PredictionInput {
70
+ domain: PredictionDomain;
71
+ metric: string;
72
+ horizon_ms?: number;
73
+ reasoning?: string;
74
+ evidence?: Record<string, unknown>;
75
+ }
76
+ export interface MetricDataPoint {
77
+ value: number;
78
+ timestamp: number;
79
+ }
80
+ export interface CalibrationBucket {
81
+ range_start: number;
82
+ range_end: number;
83
+ predicted_count: number;
84
+ actual_accuracy: number;
85
+ }
@@ -0,0 +1,3 @@
1
+ // ── Prediction Engine Types ─────────────────────────────────
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/prediction/types.ts"],"names":[],"mappings":"AAAA,+DAA+D"}