| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| export function generateBets(templates, feedsByKey, nowMs) { |
| const bets = []; |
| const seen = new Set(); |
| for (const template of Array.isArray(templates) ? templates : []) { |
| const feed = feedsByKey?.[template.feedKey]; |
| let metric = null; |
| try { |
| metric = template.extractMetric(feed, { nowMs }); |
| } catch { |
| metric = null; |
| } |
| if (!metric) continue; |
|
|
| const baseCtx = { template, metric, feed, nowMs }; |
| let deadlineMs; |
| let spec; |
| let question; |
| try { |
| deadlineMs = Number(template.horizonPolicy({ ...baseCtx })); |
| if (!Number.isFinite(deadlineMs)) continue; |
| const ctx = { ...baseCtx, deadlineMs }; |
| spec = template.buildResolutionSpec(ctx); |
| if (!spec) continue; |
| question = template.buildQuestion({ ...ctx, spec }); |
| } catch { |
| continue; |
| } |
| if (!question || typeof question !== 'string') continue; |
|
|
| const ctx = { ...baseCtx, deadlineMs, spec }; |
| const id = String(template.buildId ? template.buildId(ctx) : `${template.id}:${metric.subject}`); |
| const dedupeKey = `${id}@${spec.deadline}`; |
| if (seen.has(dedupeKey)) continue; |
| seen.add(dedupeKey); |
|
|
| |
| |
| |
| let extras = {}; |
| if (template.decorate) { |
| try { extras = template.decorate(ctx) || {}; } catch { extras = {}; } |
| } |
|
|
| bets.push({ |
| ...extras, |
| id, |
| domain: template.domain, |
| title: template.buildTitle ? String(template.buildTitle(ctx)) : question, |
| question, |
| probability: null, |
| resolution: spec, |
| generationOrigin: 'bet_engine', |
| userValueScore: clamp01(template.userValueScore ? Number(template.userValueScore(ctx)) : 0.5), |
| generatedAt: nowMs, |
| feedKey: template.feedKey, |
| templateId: template.id, |
| }); |
| } |
| return bets; |
| } |
|
|
| function clamp01(value) { |
| if (!Number.isFinite(value)) return 0.5; |
| return Math.max(0, Math.min(1, value)); |
| } |
|
|