fix(geo+recall): Länder-Centroid statt Hauptstadt + Eigennamen in GNews-Query
Zwei Fixes aus der jp_demo-Verifikation: 1. Geoparsing — Länder mit Centroid statt Hauptstadt Bisher bekam ein Land die Koordinaten seiner Hauptstadt. Damit landeten alle "Japan"-Marker exakt auf Tokyo (35.69, 139.69) und die Karte suggerierte faelschlich ein Ereignis in der Hauptstadt. Neue Tabelle _COUNTRY_CENTROIDS (37 Laender) verortet ein Land in seiner geografischen Mitte (Japan: 36.20, 138.25). Laender ohne Centroid-Eintrag fallen auf die Hauptstadt zurueck. 2. Recall — Eigennamen in den Google-News-Suchfeed erzwingen Beim ersten Refresh fehlt die Headlines-Historie, daher kamen die GNews- Such-Keywords aus der Feed-Selektion. Haiku legt Eigennamen (z.B. "Qilin") in die en-Liste, die ja-Liste hatte nur Allgemeinbegriffe — die ja-Query suchte ohne "Qilin". build_news_search_feeds stellt nicht-englischen Sprach-Queries jetzt die 2 wichtigsten en-Keywords voran (Eigennamen kommen auch in fremdsprachigen Artikeln lateinisch vor). Damit ist schon der erste Refresh spezifisch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -31,6 +31,28 @@ def _get_geonamescache():
|
||||
return _gc
|
||||
|
||||
|
||||
# Geografische Zentren (Centroids) der Laender, keyed nach ISO-2-Code.
|
||||
# Wird genutzt, wenn ein Artikel ein LAND nennt (kein konkreter Ort). Vorher
|
||||
# wurde dem Land die Hauptstadt zugewiesen — das stapelte z.B. alle "Japan"-
|
||||
# Marker exakt auf Tokyo und suggerierte faelschlich ein Ereignis in der
|
||||
# Hauptstadt. Das Centroid liegt in der Landesmitte und ist neutral.
|
||||
# Laender, die hier fehlen, fallen auf die Hauptstadt zurueck (alte Logik).
|
||||
_COUNTRY_CENTROIDS = {
|
||||
"AF": (33.94, 67.71), "AT": (47.52, 14.55), "AZ": (40.14, 47.58),
|
||||
"CH": (46.82, 8.23), "CN": (35.86, 104.20), "CY": (35.13, 33.43),
|
||||
"DE": (51.17, 10.45), "EG": (26.82, 30.80), "ES": (40.46, -3.75),
|
||||
"FR": (46.23, 2.21), "GB": (54.70, -3.28), "GR": (39.07, 21.82),
|
||||
"IL": (31.05, 34.85), "IN": (20.59, 78.96), "IQ": (33.22, 43.68),
|
||||
"IR": (32.43, 53.69), "IT": (41.87, 12.57), "JO": (30.59, 36.24),
|
||||
"JP": (36.20, 138.25), "KP": (40.34, 127.51), "KR": (35.91, 127.77),
|
||||
"KW": (29.31, 47.48), "LB": (33.85, 35.86), "NL": (52.13, 5.29),
|
||||
"OM": (21.47, 55.98), "PK": (30.38, 69.35), "PS": (31.95, 35.23),
|
||||
"QA": (25.32, 51.18), "RU": (61.52, 105.32), "SA": (23.89, 45.08),
|
||||
"SY": (34.80, 38.997), "TR": (38.96, 35.24), "UA": (48.38, 31.17),
|
||||
"US": (39.83, -98.58), "YE": (15.55, 48.52), "TW": (23.80, 121.00),
|
||||
}
|
||||
|
||||
|
||||
# Bekannte Laendernamen (deutsch/englisch/alternativ -> ISO-2 Code + Hauptstadt-Koordinaten)
|
||||
_COUNTRY_ALIASES = {
|
||||
"libanon": {"code": "LB", "name": "Lebanon", "lat": 33.8938, "lon": 35.5018},
|
||||
@@ -106,9 +128,12 @@ def _geocode_offline(name: str, country_code: str = "") -> Optional[dict]:
|
||||
# 1. Bekannte Laender-Aliase (schnellster + sicherster Pfad)
|
||||
alias = _COUNTRY_ALIASES.get(name_lower)
|
||||
if alias:
|
||||
# Land -> geografisches Zentrum (Centroid) statt Hauptstadt, wo bekannt.
|
||||
centroid = _COUNTRY_CENTROIDS.get(alias["code"])
|
||||
lat, lon = centroid if centroid else (alias["lat"], alias["lon"])
|
||||
return {
|
||||
"lat": alias["lat"],
|
||||
"lon": alias["lon"],
|
||||
"lat": lat,
|
||||
"lon": lon,
|
||||
"country_code": alias["code"],
|
||||
"normalized_name": alias["name"],
|
||||
"confidence": 0.95,
|
||||
@@ -118,9 +143,20 @@ def _geocode_offline(name: str, country_code: str = "") -> Optional[dict]:
|
||||
countries = gc.get_countries()
|
||||
for code, country in countries.items():
|
||||
if country.get("name", "").lower() == name_lower:
|
||||
# Land -> Centroid (Landesmitte), wo bekannt. Das verhindert, dass
|
||||
# alle "Japan"-Marker exakt auf Tokyo gestapelt werden.
|
||||
centroid = _COUNTRY_CENTROIDS.get(code)
|
||||
if centroid:
|
||||
return {
|
||||
"lat": centroid[0],
|
||||
"lon": centroid[1],
|
||||
"country_code": code,
|
||||
"normalized_name": country["name"],
|
||||
"confidence": 0.9,
|
||||
}
|
||||
# Kein Centroid hinterlegt -> Fallback auf die Hauptstadt.
|
||||
capital = country.get("capital", "")
|
||||
if capital:
|
||||
# Hauptstadt geocoden, aber als Land benennen
|
||||
cap_alias = _COUNTRY_ALIASES.get(capital.lower())
|
||||
if cap_alias:
|
||||
return {
|
||||
|
||||
@@ -58,15 +58,36 @@ def build_news_search_feeds(
|
||||
locale = _GNEWS_LOCALE.get(lang_key)
|
||||
if not locale:
|
||||
continue
|
||||
kws = keywords_by_lang.get(lang_key) or []
|
||||
# Fallback: wenn fuer die Sprache keine Keywords da sind, "en" nehmen
|
||||
# (lateinische Eigennamen matchen auch in fremdsprachigen News-Indizes).
|
||||
if not kws and lang_key != "en":
|
||||
kws = keywords_by_lang.get("en") or []
|
||||
kws = [str(k).strip() for k in kws if str(k).strip()]
|
||||
if not kws:
|
||||
lang_kws = [str(k).strip() for k in (keywords_by_lang.get(lang_key) or []) if str(k).strip()]
|
||||
en_kws = [str(k).strip() for k in (keywords_by_lang.get("en") or []) if str(k).strip()]
|
||||
|
||||
if lang_key == "en":
|
||||
query_terms = en_kws[:max_keywords]
|
||||
else:
|
||||
# Fuer nicht-englische Sprachen: die ersten 2 englischen Keywords
|
||||
# voranstellen. Haiku ordnet Eigennamen/Akronyme (z.B. "Qilin",
|
||||
# "Asahi") nach vorne — und die kommen auch in fremdsprachigen
|
||||
# Artikeln lateinisch vor. Ohne das fehlt beim ersten Refresh (noch
|
||||
# keine Headlines-Historie) der entscheidende Eigenname in der Query.
|
||||
# Danach 3 sprach-spezifische Keywords.
|
||||
query_terms = en_kws[:2] + lang_kws[:3]
|
||||
# Wenn fuer die Sprache gar keine Keywords da sind: ganz auf en.
|
||||
if not lang_kws:
|
||||
query_terms = en_kws[:max_keywords]
|
||||
|
||||
# Dedup, Reihenfolge erhalten
|
||||
seen_terms: set[str] = set()
|
||||
deduped: list[str] = []
|
||||
for t in query_terms:
|
||||
tl = t.lower()
|
||||
if tl in seen_terms:
|
||||
continue
|
||||
query = " ".join(kws[:max_keywords])
|
||||
seen_terms.add(tl)
|
||||
deduped.append(t)
|
||||
|
||||
if not deduped:
|
||||
continue
|
||||
query = " ".join(deduped)
|
||||
if not query or query in seen_queries:
|
||||
continue
|
||||
seen_queries.add(query)
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren