Admin: source Articles inspector (verify metrics against real evidence)
New per-row "Articles" button on the Sources table expands a read-only inline
panel of the source's ACTUAL ingested articles — so the automated metrics
(paywall/image/acceptance/duplicate) can be verified against evidence instead of
trusted blind. Distinct from "Check" (which re-samples the LIVE feed for
would-pass quality); this shows what's already in the DB, which is what the table
metrics are computed from.
- Backend: GET /api/admin/sources/{id}/articles?filter=&limit=&offset= (admin,
read-only). queries.source_articles + source_articles_summary — per article:
title, url, date, accepted, reason (the "why"), topic/flavor, paywalled
(domain rule), has_image, duplicate. Summary = counts + source-level paywall
rule.
- Frontend: expandable panel with a summary header ("27 ingested · 18 accepted
· … · paywall rule: ON (domain)"), filter chips (All/Accepted/Rejected/No
image/Duplicates), compact rows with title→link + badges + reason, Load more.
So "100% paywall" or "0% images" becomes clickable evidence: open two articles
to tell a real paywall from a mis-flagged domain, or a true image gap from an
enrichment failure. Test: test_source_articles_inspector. 241 pytest + 11 vitest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -503,3 +503,18 @@ def test_wordsearch_theme_admin(tmp_path, monkeypatch):
|
||||
# remove
|
||||
left = tc.delete(f"/api/admin/wordsearch/themes/{tid}").json()
|
||||
assert not any(t["id"] == tid for t in left)
|
||||
|
||||
|
||||
def test_source_articles_inspector(tmp_path, monkeypatch):
|
||||
app, api = _make(tmp_path, monkeypatch, admin_email="boss@x.com")
|
||||
assert TestClient(app).get("/api/admin/sources/1/articles").status_code == 401 # gated
|
||||
tc = _signin(app, api, "boss@x.com")
|
||||
r = tc.get("/api/admin/sources/1/articles").json()
|
||||
assert r["summary"]["total"] == 1 and r["summary"]["accepted"] == 1 and r["summary"]["no_image"] == 1
|
||||
assert len(r["articles"]) == 1
|
||||
a = r["articles"][0]
|
||||
assert a["title"] == "t1" and a["accepted"] == 1 and a["has_image"] is False and a["paywalled"] is False
|
||||
# filters resolve in SQL; rejected → none (the seeded article is accepted)
|
||||
assert tc.get("/api/admin/sources/1/articles?filter=rejected").json()["articles"] == []
|
||||
assert len(tc.get("/api/admin/sources/1/articles?filter=no_image").json()["articles"]) == 1
|
||||
assert tc.get("/api/admin/sources/999/articles").status_code == 404 # unknown source
|
||||
|
||||
Reference in New Issue
Block a user