Session 29: Content generation templates — slate threads, POTD, recaps, matchup previews (1660 tests)

This commit is contained in:
Kev
2026-06-13 21:30:57 -04:00
parent c48aecd510
commit 927c4a5c65
11 changed files with 1063 additions and 1 deletions
+105
View File
@@ -0,0 +1,105 @@
'use strict';
/**
* Content formatter (Session 29).
*
* Transforms structured content objects (from contentTemplateService) into
* platform-ready PLAIN TEXT — suitable for X threads / Telegram. Image
* formatting is a separate design-layer concern. Pure + defensive: a
* missing field renders as a sensible blank, never "undefined".
*/
function fmtOdds(o) {
const n = Number(o);
if (!Number.isFinite(n)) return String(o ?? '');
return n > 0 ? `+${n}` : `${n}`;
}
function fmtTime(iso) {
if (!iso) return '';
try {
return new Date(iso).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', timeZoneName: 'short' });
} catch {
return String(iso);
}
}
function formatPickPost(p) {
const lines = [`${p.player} · ${p.stat} ${p.side || ''} ${p.line ?? ''}`.replace(/\s+/g, ' ').trim()];
lines.push(`Grade: ${p.grade}${p.confidence ? ` · ${p.confidence}% confidence` : ''}`);
if (p.edge) lines.push(`Edge: ${p.edge > 0 ? '+' : ''}${p.edge}%`);
if (p.analysis) lines.push(p.analysis);
return lines.join('\n');
}
function formatGameHighlight(p) {
const parts = [`${p.game}${p.time ? ` · ${fmtTime(p.time)}` : ''}`];
if (p.bestAwayML) parts.push(`Best away ML: ${fmtOdds(p.bestAwayML.odds)} (${p.bestAwayML.book})`);
if (p.bestHomeML) parts.push(`Best home ML: ${fmtOdds(p.bestHomeML.odds)} (${p.bestHomeML.book})`);
if (p.total != null) parts.push(`O/U ${p.total}`);
if (p.bookCount) parts.push(`${p.bookCount} books`);
return parts.join('\n');
}
function formatMovers(p) {
const head = '📈 Biggest line moves:';
const rows = (p.movers || []).map((m) => {
const d = m.delta > 0 ? `+${m.delta}` : `${m.delta}`;
return `${m.player} ${String(m.stat || '').replace(/_/g, ' ')}: ${m.opening ?? '?'}${m.current ?? '?'} (${d})${m.sharpSignal ? ' ⚡' : ''}`;
});
return [head, ...rows].join('\n');
}
function formatSchedule(p) {
return (p.games || [])
.map((g) => `${g.away || '?'} @ ${g.home || '?'}${g.time ? ` · ${fmtTime(g.time)}` : ''}`)
.join('\n');
}
function formatPost(post) {
switch (post.role) {
case 'hook': return post.text || '';
case 'cta': return post.text || '';
case 'pick': return formatPickPost(post);
case 'game_highlight': return formatGameHighlight(post);
case 'movers': return formatMovers(post);
case 'schedule': return formatSchedule(post);
default: return '';
}
}
/** Returns an array of post-ready strings (one per thread post). */
function formatSlateThread(thread) {
if (!thread || !Array.isArray(thread.posts)) return [];
return thread.posts.map(formatPost);
}
/** Single-block POTD text. */
function formatPOTD(potd) {
if (!potd || potd.available === false) return 'No standout pick today — check the full slate at vyndr.app.';
if (potd.dataLevel === 'lines') {
return `🎯 GAME OF THE DAY\n${potd.game}${potd.time ? ` · ${fmtTime(potd.time)}` : ''}\n${potd.total != null ? `O/U ${potd.total}` : ''}${potd.bestHomeML ? `\nBest home ML ${fmtOdds(potd.bestHomeML.odds)} (${potd.bestHomeML.book})` : ''}`.trim();
}
return `🎯 PROP OF THE DAY\n${formatPickPost(potd)}`;
}
/** Recap text block. */
function formatRecap(recap) {
if (!recap || recap.available === false) return 'No graded results in yet today.';
const { record, winRate } = recap;
const lines = [`📊 VYNDR RECAP — ${recap.date}`];
lines.push(`Record: ${record.wins}-${record.losses}${record.pushes ? `-${record.pushes}` : ''}${winRate != null ? ` (${Math.round(winRate * 100)}%)` : ''}`);
if (recap.topHits && recap.topHits.length) {
lines.push('Top hits:');
for (const h of recap.topHits) lines.push(`${h.player} ${h.stat} ${h.side || ''} ${h.line ?? ''}`.replace(/\s+/g, ' ').trim());
}
if (recap.metrics && recap.metrics.brierScore != null) lines.push(`Brier: ${recap.metrics.brierScore}`);
return lines.join('\n');
}
module.exports = {
formatSlateThread,
formatPOTD,
formatRecap,
__internals: { formatPost, formatPickPost, formatGameHighlight, formatMovers, formatSchedule },
};