Geo Stage 1-2: subject-geography model + classifier + pipeline wiring
"Closer to Home" foundation (audit greenlit by Codex). Durable geography, kept decoupled from volatile scoring. - Schema: article_geo (breadth/confidence/rationale/geo_version) + article_places (0..N ISO-coded places), separate from article_scores so re-runs/audits never disturb scoring or acceptance. "local" is never stored — it's relative to the reader; the UI computes "Near you" later. - geo.py: LLM proposes place NAMES, code disposes to ISO codes (country alpha-2, US state 2-letter); region words like "Europe" can never become a country. 'global'/placeless is first-class, not failure. Confidence calibrated so 'high' needs an explicit location. Geo is its OWN LLM pass, not merged into the scoring prompt (durable metadata, re-runnable, keeps the sensitive prompt untouched). - store_geo replaces places (geo is re-derivable, unlike scores). tag_articles is idempotent by geo_version, only touches accepted non-duplicate articles. - CLI `geo` command (cycle-locked, --limit/--reclassify) for backfill, plus a bounded geo step in the cycle (--geo-limit 60, --no-geo). scripts/geo_audit.py is the prototype audit tool. 360 tests green; live smoke tagged real articles correctly (Gaza->PS, London->GB, placeless science->global). No UI / SEO pages yet — ranking/personalization only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -217,6 +217,34 @@ CREATE TABLE IF NOT EXISTS article_summaries (
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Where a story is ABOUT (subject geography), kept SEPARATE from article_scores so
|
||||
-- durable geography isn't coupled to volatile scoring/acceptance. "local" is never
|
||||
-- stored here — it's relative to the reader; the UI computes "Near you" by comparing
|
||||
-- these places to the visitor's chosen home. geo_version lets us re-backfill cleanly
|
||||
-- when the prompt/taxonomy changes. 'global' is a real category, not a failure.
|
||||
CREATE TABLE IF NOT EXISTS article_geo (
|
||||
article_id INTEGER PRIMARY KEY REFERENCES articles(id) ON DELETE CASCADE,
|
||||
breadth TEXT NOT NULL DEFAULT 'unknown', -- locality|regional|national|multinational|global|unknown
|
||||
confidence TEXT NOT NULL DEFAULT 'low', -- high|medium|low
|
||||
rationale TEXT,
|
||||
geo_version TEXT,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
-- 0..N normalized places per article (a story can span regions). Codes are ISO
|
||||
-- (country = alpha-2, state = US 2-letter / ISO-3166-2 subdivision), normalized in
|
||||
-- code — never trusting the model's free text.
|
||||
CREATE TABLE IF NOT EXISTS article_places (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
article_id INTEGER NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
||||
country_code TEXT,
|
||||
state_code TEXT,
|
||||
locality TEXT,
|
||||
ord INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_places_article ON article_places(article_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_places_country ON article_places(country_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_article_geo_breadth ON article_geo(breadth);
|
||||
|
||||
-- Privacy-respecting, first-party analytics. NO IP / user-agent / referrer / raw
|
||||
-- URL. visitor_hash is a hash of a random localStorage token (never email/IP).
|
||||
-- The UNIQUE key dedups to one row per (kind, article, visitor, day) — that both
|
||||
|
||||
Reference in New Issue
Block a user