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,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-label="Sarah Quinn">
<defs>
<linearGradient id="sq-bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#451a03"/>
<stop offset="1" stop-color="#b45309"/>
</linearGradient>
<linearGradient id="sq-ring" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fcd34d"/>
<stop offset="1" stop-color="#d97706"/>
</linearGradient>
</defs>
<rect width="256" height="256" rx="128" fill="url(#sq-bg)"/>
<circle cx="128" cy="128" r="118" fill="none" stroke="url(#sq-ring)" stroke-width="3" opacity="0.45"/>
<text x="128" y="148" text-anchor="middle" font-family="Georgia, 'Times New Roman', serif" font-size="92" font-weight="700" fill="#fef3c7" letter-spacing="-2">SQ</text>
<text x="128" y="200" text-anchor="middle" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="600" fill="#fcd34d" letter-spacing="3">BUSINESS / M&amp;A</text>
</svg>

After

Width:  |  Height:  |  Size: 995 B