Scope dial v2: Nearby / Region / Country / World radius on the homepage
Codex-approved evolution: the reader controls the "emotional radius" of the landing. - Census-region "Regional" grain (geo.region_of / region_states). Scope-aware tiering (queries.home_tiers): closest->widest lead, confidence-gated on state + region, never a hard filter — blends outward so the set is always full. 'world' = the global brief. - queries.home_brief takes a scope; /api/brief gains a scope param (nearby|region| country|world). Country-only / non-US homes collapse to country. - Homepage dial replaces the 2-button toggle: adaptive stops (4 with a US state, else Country/World), persisted scope, "Good news closest first" framing. Concrete, soft section labels (Around New Jersey / Across the Northeast / Across the US / Around the world) so the reader sees the dial worked. Backend 366 + frontend tests green. (Latest feed still on v1 local-first; aligning it to the dial is the immediate follow-up.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+54
-19
@@ -259,24 +259,58 @@ def reindex_search(conn: sqlite3.Connection) -> int:
|
||||
return conn.execute("SELECT COUNT(*) FROM article_search").fetchone()[0]
|
||||
|
||||
|
||||
def home_brief(conn: sqlite3.Connection, home_country: str, home_state: str | None = None,
|
||||
limit: int = 7, window_days: int = 3) -> list[dict]:
|
||||
"""Local-first landing highlights. Leads with high/medium-confidence local good news,
|
||||
then blends out to your country and the world so the set is always full (never the
|
||||
sad thin-local look), and prefers already-summarized stories so the calm read stays
|
||||
rich. Brief-shaped rows (incl. summary) tagged with a section, best-first within tier.
|
||||
# Scope dial: the reader's "emotional radius". Each tier is a closest->widest lead
|
||||
# preference, not a hard filter; 'world' is the implicit final tier. State + region
|
||||
# are confidence-gated (high/medium) so a shaky location is never promoted as local.
|
||||
_STATE_SQL = ("(g.confidence IN ('high','medium') AND EXISTS (SELECT 1 FROM article_places p "
|
||||
"WHERE p.article_id = a.id AND p.country_code = ? AND p.state_code = ?))")
|
||||
_COUNTRY_SQL = "(EXISTS (SELECT 1 FROM article_places p WHERE p.article_id = a.id AND p.country_code = ?))"
|
||||
SCOPES = ("nearby", "region", "country", "world")
|
||||
|
||||
|
||||
def _region_sql(n: int) -> str:
|
||||
placeholders = ",".join("?" * n)
|
||||
return ("(g.confidence IN ('high','medium') AND EXISTS (SELECT 1 FROM article_places p "
|
||||
f"WHERE p.article_id = a.id AND p.country_code = ? AND p.state_code IN ({placeholders})))")
|
||||
|
||||
|
||||
def home_tiers(home_country: str, home_state: str | None, scope: str) -> list[tuple]:
|
||||
"""Ordered [(section_key, predicate_sql, params)] closest->widest for a home + scope.
|
||||
Evaluated first-match (CASE WHEN / composed in order), so tiers needn't be SQL-exclusive.
|
||||
'world' is implicit (everything not matched). 'region'/'nearby' need a US state; otherwise
|
||||
they gracefully fall back to country (country-only / non-US homes collapse to Country/World).
|
||||
"""
|
||||
if home_state:
|
||||
near = ("(g.confidence IN ('high','medium') AND EXISTS (SELECT 1 FROM article_places p "
|
||||
"WHERE p.article_id = a.id AND p.country_code = ? AND p.state_code = ?))")
|
||||
country = "EXISTS (SELECT 1 FROM article_places p WHERE p.article_id = a.id AND p.country_code = ?)"
|
||||
section_case = f"CASE WHEN {near} THEN 0 WHEN {country} THEN 1 ELSE 2 END"
|
||||
section_params = [home_country, home_state, home_country]
|
||||
else:
|
||||
near = ("(g.confidence IN ('high','medium') AND EXISTS (SELECT 1 FROM article_places p "
|
||||
"WHERE p.article_id = a.id AND p.country_code = ?))")
|
||||
section_case = f"CASE WHEN {near} THEN 0 ELSE 2 END" # no "country" tier without a state
|
||||
section_params = [home_country]
|
||||
from .geo import region_states
|
||||
rs = region_states(home_state) if home_state else []
|
||||
tiers: list[tuple] = []
|
||||
if scope == "nearby" and home_state:
|
||||
tiers.append(("state", _STATE_SQL, [home_country, home_state]))
|
||||
if rs:
|
||||
tiers.append(("region", _region_sql(len(rs)), [home_country, *rs]))
|
||||
tiers.append(("country", _COUNTRY_SQL, [home_country]))
|
||||
elif scope == "region" and home_state and rs:
|
||||
tiers.append(("region", _region_sql(len(rs)), [home_country, *rs])) # includes the state
|
||||
tiers.append(("country", _COUNTRY_SQL, [home_country]))
|
||||
else: # country scope, country-only / non-US home, or any fallback
|
||||
tiers.append(("country", _COUNTRY_SQL, [home_country]))
|
||||
return tiers
|
||||
|
||||
|
||||
def home_brief(conn: sqlite3.Connection, home_country: str, home_state: str | None = None,
|
||||
scope: str = "nearby", limit: int = 7, window_days: int = 3) -> list[dict]:
|
||||
"""Scope-aware local-first landing highlights. Leads with the reader's chosen radius
|
||||
(state / region / country) then blends outward so the set is always full — "closest
|
||||
first", never three stale local stories. Prefers already-summarized stories so the
|
||||
calm read stays rich. Brief-shaped rows tagged with a concrete section key.
|
||||
"""
|
||||
tiers = home_tiers(home_country, home_state, scope)
|
||||
whens, params = [], []
|
||||
for i, (_key, pred, ps) in enumerate(tiers):
|
||||
whens.append(f"WHEN {pred} THEN {i}")
|
||||
params += ps
|
||||
world_rank = len(tiers)
|
||||
section_case = ("CASE " + " ".join(whens) + f" ELSE {world_rank} END") if whens else "0"
|
||||
section_keys = [k for k, _, _ in tiers] + ["world"]
|
||||
rows = conn.execute(
|
||||
f"""
|
||||
SELECT {_ARTICLE_COLUMNS},
|
||||
@@ -294,12 +328,13 @@ def home_brief(conn: sqlite3.Connection, home_country: str, home_state: str | No
|
||||
COALESCE(a.published_at, a.discovered_at) DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
section_params + [f"-{window_days} days", limit],
|
||||
params + [f"-{window_days} days", limit],
|
||||
).fetchall()
|
||||
out = []
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
d["__section"] = {0: "near", 1: "country", 2: "world"}.get(d.pop("section_rank", 2), "world")
|
||||
rank = d.pop("section_rank", world_rank)
|
||||
d["__section"] = section_keys[rank] if 0 <= rank < len(section_keys) else "world"
|
||||
out.append(d)
|
||||
return out
|
||||
|
||||
|
||||
Reference in New Issue
Block a user