Content engine: posts that structurally cannot lie

PHASE 0 — contentEngine makes Truth Law structural, not careful. Copy is
token-substituted and an unbacked {token} REFUSES to render -- there is no
code path that produces a plausible default. The fact contract is asserted
before any string is built. Card and copy render from ONE fact object, so
a caption and a card cannot disagree. No live model writes factual claims:
the voice is in the template, the facts are pulled, and the voice-polish
port is deliberately unwired, because an LLM that can rewrite a sentence
can rewrite a number.

18 tests carry the proof. The one that matters most: ZERO IS PRESENT.
"0 cleared B+" is our most honest possible post, and treating 0 as missing
would be the Number(null)===0 breach wearing its opposite coat -- it would
silently delete exactly the post the brand is built on.

PHASE 1 — three templates, generating real posts from tonight's data:
hot hitters off the repaired full-season log, the honesty flex off the
real servedGrade distribution (2,140 graded / 70 cleared B+ / 42% not
separable / A unissuable), and streaks verified from settled outcomes only.

THE ENGINE CAUGHT A BUG IN ITSELF, and it is the sharpest lesson here. The
first run published "No hitter is meaningfully hot tonight -- we could
dress up a middling week as a streak. We don't." That was FALSE: the
box-score cache spans only the settled window, every player had under 20
games, and the pool was empty. A broken pull was publishing as considered
editorial judgement -- the fourth appearance of this class tonight and the
first where our OWN HONESTY COPY was the disguise.

Fixed structurally rather than by patching the number: an absent() variant
may now DECLINE to speak, and the template separates "no candidates at
all" (SKIP with a reason) from "candidates judged, none hot" (honest
absence). Both locked by test. Source corrected to mlbStatsAdapter.fullLog,
the same log the repaired champion reads.

PHASE 2 — cardRenderer emits SVG rather than canvas: it is text, so it
diffs in review and its numbers are greppable, which matters when the
whole claim is that the numbers are real. VYND white + R green, slashed-Y,
scanlines, mono. The card never formats its own facts -- every string
arrives pre-rendered and gate-checked.

PHASE 3 — scripts/generate-content.js writes copy + card per template to
.content-out/<date>/. Template N+1 is a registry entry: requires, pull,
copy, card, absent. Queued as stubs, not built: hot takes, daily reads,
"grades we DIDN'T give", cross-sport streak variants (the streak template
is already sport-agnostic -- settled outcomes and a noun).

FULLY ISOLATED: read-only on every source, zero writes to serving, model
or ledger tables. Serving fingerprint verified unchanged. The accrual clock
is untouched at 0 eligible dates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1sivYNqY2TS5ftykmHBU9
This commit is contained in:
Kev
2026-08-07 16:34:57 -04:00
parent 08791520fc
commit 74aa75945e
9 changed files with 901 additions and 0 deletions
@@ -0,0 +1,94 @@
'use strict';
/**
* TEMPLATE 1 — HOT HITTERS.
*
* Reads the REPAIRED full-season log, not a ten-game slice. "Hot" here means a
* recent rate measured against that hitter's own season rate — which is only a
* meaningful comparison now that the season rate is a season.
*/
const { knownNumber } = require('../../../utils/known');
/** A hitter must have this much history before we call him anything. */
const MIN_SEASON_GAMES = 20;
const RECENT = 10;
module.exports = {
id: 'hot_hitters',
sport: 'mlb',
label: 'Hot Hitters',
requires: ['count', 'hitters', 'window', 'date'],
async pull(deps) {
const rows = await deps.hitterForm(); // injected, read-only
// ── EMPTY POOL IS NOT AN HONEST ABSENCE ────────────────────────────────
// The first run of this template emitted "no hitter is meaningfully hot"
// while the real cause was a source holding fewer than 20 games for EVERY
// player. That reads as a considered editorial judgement and is actually a
// broken pull -- the exact failure class that has bitten this codebase four
// times tonight, here wearing the costume of our own honesty copy.
//
// So the two are separated: no candidates at all is a SKIP with a reason;
// candidates present but none hot is the honest absence.
const candidates = (rows || []).length;
const usable = (rows || []).filter((r) =>
knownNumber(r.season_games) !== null && r.season_games >= MIN_SEASON_GAMES
&& knownNumber(r.recent_rate) !== null && knownNumber(r.season_rate) !== null);
const hot = usable
.map((r) => ({ ...r, lift: r.recent_rate - r.season_rate }))
.filter((r) => r.lift > 0)
.sort((a, b) => b.lift - a.lift)
.slice(0, 5);
return {
date: deps.date,
window: RECENT,
candidates,
qualified: usable.length,
count: hot.length || null, // zero hot hitters is an ABSENT list, not "0 hot hitters"
hitters: hot.length ? hot : null,
list: hot.map((h, i) =>
`${i + 1}. ${h.name}${Math.round(h.recent_rate * 100)}% last ${RECENT}, ${Math.round(h.season_rate * 100)}% season`).join('\n'),
top_name: hot[0] ? hot[0].name : null,
top_recent: hot[0] ? Math.round(hot[0].recent_rate * 100) : null,
top_season: hot[0] ? Math.round(hot[0].season_rate * 100) : null,
};
},
copy: () => `WHO'S ACTUALLY HOT — {date}
{top_name} is hitting {top_recent}% over his last {window}. His season number is {top_season}%.
That gap is the whole point. Everybody else is guessing at it.
{list}
Measured off full season logs, not a ten-game window that flatters whoever ran hot last week.`,
card: () => ({
kind: 'list',
title: "WHO'S ACTUALLY HOT",
subtitle: 'LAST {window} vs SEASON · {date}',
lines: [
{ text: '{top_name}', style: 'lead' },
{ text: '{top_recent}% last {window} · {top_season}% season', style: 'stat' },
{ text: '', style: 'rule' },
{ text: '{list}', style: 'body' },
],
footer: 'Rates measured from full season game logs.',
}),
absent: (gaps, facts) => {
// Only speak if there was actually a pool to judge.
if (!facts || !facts.qualified) {
return {
copy: null,
card: null,
skip: `no qualified hitters in the pool (candidates=${(facts && facts.candidates) || 0}, qualified=${(facts && facts.qualified) || 0}) — source problem, not a quiet night`,
};
}
return {
copy: "No hitter is meaningfully hot tonight.\n\nWe could dress up a middling week as a streak. We don't.",
card: { kind: 'absence', title: 'NOTHING HOT TONIGHT', subtitle: 'AND WE WILL SAY SO', lines: [{ text: 'No hitter cleared his own season rate by enough to name.', style: 'body' }], footer: 'An empty list is a real answer.' },
};
},
};