Initial import of Politrack, a self-hosted service that tracks Austrian politicians from federal to municipal level - roles, votes, money flows, statements, meetings, social media and legal proceedings - with every claim linked to its sources. - backend: FastAPI + Postgres/pgvector; adapter framework (fetch -> parse -> load -> enrich) with robots.txt/TDM-aware fetching, a raw-response archive for reparsing, and a claim/evidence model with corroboration - sources: Parliament, Wikidata, EU Parliament/Commission, Council of Ministers, Rechnungshof party reports and donations, procurement, RTR media transparency, political ad libraries, WKStA, news feeds, Wikipedia edits, Bluesky, Mastodon, public Telegram channels, YouTube and X (official API) - web: Next.js frontend in German and English with typed API client - mcp: MCP server for AI agents and RAG - docs: architecture, data model, sources, legal notes, Unraid deployment and contributor guides; Docker Compose setup for Unraid Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.9 KiB
Python
70 lines
2.9 KiB
Python
"""Sources, topics, statistics, moderation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from psycopg import Connection
|
|
|
|
|
|
def list_sources(conn: Connection) -> list[dict]:
|
|
return conn.execute(
|
|
"""
|
|
SELECT s.slug, s.name, s.kind, s.homepage, s.license, s.access_method, s.default_trust, s.tos_notes,
|
|
(SELECT count(*) FROM document d WHERE d.source_id = s.id) AS documents
|
|
FROM source s ORDER BY s.kind, s.default_trust DESC, s.name
|
|
"""
|
|
).fetchall()
|
|
|
|
|
|
def list_topics(conn: Connection) -> list[dict]:
|
|
return conn.execute("SELECT slug, name_de, name_en FROM topic ORDER BY name_de").fetchall()
|
|
|
|
|
|
def pending_claims(conn: Connection, limit: int = 50) -> list[dict]:
|
|
return conn.execute(
|
|
"""
|
|
SELECT c.id, c.claim_type, c.summary_de, c.occurred_at, p.full_name,
|
|
(SELECT jsonb_agg(jsonb_build_object('url', d.url, 'title', d.title))
|
|
FROM claim_evidence e JOIN document d ON d.id = e.document_id WHERE e.claim_id = c.id) AS evidence
|
|
FROM claim c LEFT JOIN person p ON p.id = c.person_id
|
|
WHERE c.review_status = 'pending' AND c.merged_into IS NULL
|
|
ORDER BY c.occurred_at DESC NULLS LAST LIMIT %s
|
|
""",
|
|
(limit,),
|
|
).fetchall()
|
|
|
|
|
|
def set_review(conn: Connection, claim_id: int, decision: str, reviewer: str) -> None:
|
|
if decision not in ("published", "rejected"):
|
|
raise ValueError("decision must be 'published' or 'rejected'")
|
|
conn.execute(
|
|
"UPDATE claim SET review_status = %s, reviewed_by = %s, reviewed_at = now() WHERE id = %s",
|
|
(decision, reviewer, claim_id),
|
|
)
|
|
|
|
|
|
def stats(conn: Connection) -> dict:
|
|
return conn.execute(
|
|
"""
|
|
SELECT (SELECT count(*) FROM person) AS persons,
|
|
(SELECT count(*) FROM person_status WHERE status = 'active') AS active_persons,
|
|
(SELECT count(*) FROM organization WHERE kind = 'party') AS parties,
|
|
(SELECT count(*) FROM claim WHERE merged_into IS NULL AND review_status = 'published') AS claims,
|
|
(SELECT count(*) FROM claim WHERE merged_into IS NULL AND status = 'corroborated') AS corroborated,
|
|
(SELECT count(*) FROM claim WHERE review_status = 'pending') AS pending_review,
|
|
(SELECT count(*) FROM document) AS documents,
|
|
(SELECT count(*) FROM vote_event) AS vote_events,
|
|
(SELECT count(*) FROM vote_event WHERE roll_call) AS roll_call_votes,
|
|
(SELECT count(*) FROM event) AS events,
|
|
(SELECT count(*) FROM money_flow) AS money_flows,
|
|
(SELECT max(finished_at) FROM run WHERE status IN ('ok', 'partial')) AS last_update
|
|
"""
|
|
).fetchone()
|
|
|
|
|
|
def recent_runs(conn: Connection, limit: int = 50) -> list[dict]:
|
|
return conn.execute(
|
|
"SELECT adapter, mode, status, started_at, finished_at, stats, left(error, 2000) AS error FROM run "
|
|
"ORDER BY id DESC LIMIT %s",
|
|
(limit,),
|
|
).fetchall()
|