Admin: tabbed operator console (Overview/Content/Sources/Audience/Feedback)

Reshape the long single-page dashboard into a sectioned console (one route,
?section= tabs, sticky subnav) focused on "what needs my attention" first.

* Overview: an "Attention Needed" strip (soft amber/blue, never alarming red)
  derived from the same data — sources resting/flagged, image coverage <70%,
  thin brief, recent feedback — plus at-a-glance pulse cards.
* Content: corpus health + image/summary coverage (with_image, summaries_with_
  image, brief image coverage, 24h image misses) + top opened / topics / tags.
* Sources: filterable table (All/Healthy/Resting/Flagged) — served, last
  success, next poll, failure streak, status — instead of a card pile.
* Audience: visitors, retention, accounts, funnel, sharing, daily trend.
* Feedback: inbox with category filter, newest first, quick mailto reply.

Backend: content_stats gains added_7d + image-coverage fields; source_health
gains review_flag; admin_stats adds attention[] + feedback_7d.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-08 13:03:23 -04:00
parent 489c34d2f2
commit 575f562ad5
3 changed files with 375 additions and 179 deletions
+25
View File
@@ -47,3 +47,28 @@ def test_source_health_orders_failing_first_and_computes_next_due(tmp_path):
assert flaky["next_due_at"] is not None
good = next(s for s in sh if s["name"] == "Good")
assert good["served"] == 1 and good["next_due_at"] is None # never attempted → due now
def test_attention_flags_resting_flagged_and_brief():
from goodnews import queries
content = {"served": 100, "with_image": 90, "latest_brief_size": 5}
sources = [
{"failures": 3, "review_flag": 0},
{"failures": 0, "review_flag": 1},
{"failures": 0, "review_flag": 0},
]
items = queries._attention(content, sources, feedback_7d=2)
texts = " | ".join(i["text"] for i in items)
assert "1 source backing off" in texts # one resting
assert "1 source flagged" in texts # one flagged
assert "brief has only 5" in texts # brief < 7
assert "2 feedback" in texts # recent feedback
# 90/100 = 90% image coverage → no coverage warning
assert "Image coverage" not in texts
def test_attention_clear_when_healthy():
from goodnews import queries
content = {"served": 100, "with_image": 95, "latest_brief_size": 7}
sources = [{"failures": 0, "review_flag": 0}]
assert queries._attention(content, sources, feedback_7d=0) == []