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

View File

@@ -98,3 +98,9 @@ from webui.data.proposals import ( # noqa: F401
get_proposal_detail,
get_proposals_for_gap,
)
# Blog Drafts
from webui.data.blog import ( # noqa: F401
get_all_blog_drafts,
get_blog_draft_detail,
)

View File

@@ -117,7 +117,7 @@ def get_author_detail(db: Database, person_id: int) -> dict | None:
"name": d.name,
"title": d.title,
"date": d.date,
"status": d.status,
"states": d.states,
"categories": r.categories if r else [],
"score": round(r.composite_score, 2) if r else None,
"novelty": r.novelty if r else None,

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

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import json
import re
from collections import Counter, defaultdict
from ietf_analyzer.db import Database