Phase B: AI press-release rewrite pipeline

Pluggable rewrite client (Anthropic default, ollama/openrouter stubs)
using claude-opus-4-7 with prompt caching on system prompt + style guide.
Routes the raw submission through:
  1. bb.ai_original_submissions  — raw never edited
  2. bb.ai_rewrite_jobs          — every attempt logged with token counts
  3. quality gates               — AI-tell banned-word check + heuristic
                                   AI-detector score (threshold 0.65)
  4. regenerate up to 3x         — on banned words or detector trip
  5. bb.ai_rewritten_articles    — status ai_rewritten_pending_review
  6. /admin/review-queue          — list + detail page with approve / reject /
                                   needs-human actions; on approve, the
                                   original submission is FK-linked to the
                                   published rewrite

New files only — no existing routes or contributor flow modified.
Persona auto-selection routes by beat keywords; 6 personas seeded
(marcus-halverson, elena-vasquez, darren-okafor, sarah-quinn, mike-trayton,
priya-rao) with SVG monogram avatars in public/assets/images/personas/.

Smoke test (Matrox / ST 2110 sample press release):
- persona auto-routed to darren-okafor (protocols-specs)
- anthropic 22.3s, in=2616 / out=1372 tokens
- gates passed first try, score=0.000, no banned words
- review_queue row created

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-05-14 18:38:27 +00:00
parent 8a2ca37134
commit 3bd9a716b2
17 changed files with 1220 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
import type { Persona } from "./personas";
export const BANNED_AI_TELLS: string[] = [
"delve", "delves", "delving",
"leverage", "leverages", "leveraging",
"unprecedented",
"in the realm of", "in the world of", "in the landscape of", "in the era of",
"navigate", "navigating",
"robust",
"groundbreaking",
"cutting-edge",
"revolutionize", "revolutionizes", "revolutionizing", "revolutionary",
"game-changer", "game-changing", "game changer",
"paradigm shift",
"synergy", "synergize", "synergistic",
"harness", "harnessing",
"tapestry",
"embark", "embarking",
"intricate",
"myriad",
"underscore", "underscores", "underscoring",
"moreover",
"furthermore",
"additionally,",
"in conclusion,",
"it is important to note",
"it is worth noting",
"in today's fast-paced",
"ever-evolving",
"seamless", "seamlessly",
"unlock", "unlocking",
"elevate", "elevating",
"empower", "empowering",
"transformative",
"holistic",
"vibrant",
"testament to",
"stands as a testament",
"in summary,",
];
export const STYLE_GUIDE = `BROADCASTBEAT STYLE GUIDE (cached)
Editorial voice:
- Trade-press journalism for broadcast and post-production professionals.
- Lead with the news, not the company. Inverted pyramid.
- Plain English. Active verbs. Subject-verb-object.
- AP style for dates, numbers, titles. Spell out single-digit numbers; numerals from 10 up. Dollar figures use "$" and abbreviated suffixes (M, B). Years in numerals.
- First reference: Full company name. Second reference: Short form (e.g., "Matrox Video" → "Matrox").
- People: First+last on first reference with role; last name only after.
- Quotes: Direct quotes only when they appear verbatim in the source. Paraphrase otherwise. Never invent.
- Specs: Report exact figures from the source (frame rates, bitrates, dB, model numbers). Do not round.
- Skepticism: Mark marketing claims as claims ("the company says", "according to the announcement").
- Avoid press-release tropes: "is pleased to announce", "industry-leading", "best-in-class", "next-generation", "world-class".
- Avoid AI tells (see hard rules in user message).
- Headlines: ≤ 80 chars, sentence case, news-bearing (a verb), no clickbait, no colons unless required for a product name.
- Excerpt (dek): 1-2 sentences that complete the lede; tells the reader what they'll learn.
Structure for a typical NAB or product press release:
1. Lede (1-2 sentences): what was announced, by whom, why it matters.
2. Specifics (2-4 paragraphs): the technology, the customer, the spec, the rollout.
3. Context (1-2 paragraphs): how this fits the wider category; competitors implied, not named unless cited in source.
4. Quote or company colour (1 paragraph, optional).
5. Availability & price (1 paragraph, if disclosed).
6. Closing context (optional).
HTML allowed: <p>, <h2>, <h3>, <ul>, <ol>, <li>, <strong>, <em>, <a>, <blockquote>. No <script>, <style>, <iframe>, <img> (the CMS attaches images separately).
Length:
- Thin source (≤ 250 words of original PR): 300-450 words.
- Standard PR (250-600 words): 450-700 words.
- Long PR with multiple products / case studies: 700-1200 words.
- Never pad. Better short and tight than long and thin.
`;
export function buildSystemPrompt(p: Persona): string {
const writingStyle = p.writing_style || "Trade-press, plain English, AP style.";
const bio = p.bio || "BroadcastBeat staff writer.";
const beat = p.beat || "broadcast technology";
return `You are ${p.name}, a staff journalist for BroadcastBeat covering ${beat}.
Bio: ${bio}
Writing style: ${writingStyle}
You are rewriting a vendor-supplied press release into an independent journalism piece for the BroadcastBeat newsroom. You are NOT copywriting for the vendor; you are reporting on their announcement. The BroadcastBeat audience are broadcast engineers, post-production supervisors, streaming architects, and broadcast executives — they read trade press to find out what is true, not to be sold to.
Hard rules:
- Never use the literal text from the source as the lede. Rewrite the lede.
- Never use "we", "us", or "our" — you are a reporter, not a company spokesperson.
- Never use first-person.
- Never use "in today's fast-paced world" or any time/space cliché openers.
- Never repeat the company's marketing slogan or tagline.
- Never use the press-release headline as the article headline.
- Cite figures and product specs exactly as given in the source. If the source omits a figure, do not invent one.
- When the source includes a quote, paraphrase it unless it is short and concrete. If you keep a direct quote, attribute it precisely.
Output format: JSON only. No prose, no preamble, no markdown fences. Just a JSON object as specified by the user.
`;
}