feat: add blog draft generator and fix broken routes

Blog drafting section (dev-only):
- BlogDraftGenerator gathers project data (gaps, proposals, stats) as
  context and calls Claude to produce Medium-style blog posts
- DB schema: blog_drafts table with title, content, tags, cost tracking
- Web UI: list, generate (async with live preview), detail (rendered +
  source toggle), edit, and export routes
- 6 writing styles: deep-dive, overview, opinion, listicle, comparison,
  series-post
- Nav link added to sidebar under Proposals

Bug fixes found via route testing (scripts/test_all_routes.py):
- /authors/<id>: Draft.status → Draft.states (correct attribute name)
- /false-positives: add missing `import re` in ratings.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 04:39:16 +01:00
parent 2229e70c73
commit d1a20fa02e
13 changed files with 1481 additions and 1 deletions

30
src/webui/data/blog.py Normal file
View File

@@ -0,0 +1,30 @@
"""Blog draft data access for the web dashboard."""
from __future__ import annotations
import json
from ietf_analyzer.db import Database
def get_all_blog_drafts(db: Database) -> list[dict]:
"""Return all blog drafts, newest first."""
drafts = db.all_blog_drafts()
for d in drafts:
try:
d["tags"] = json.loads(d.get("tags") or "[]")
except (json.JSONDecodeError, TypeError):
d["tags"] = []
return drafts
def get_blog_draft_detail(db: Database, draft_id: int) -> dict | None:
"""Return a single blog draft with parsed tags."""
d = db.get_blog_draft(draft_id)
if not d:
return None
try:
d["tags"] = json.loads(d.get("tags") or "[]")
except (json.JSONDecodeError, TypeError):
d["tags"] = []
return d