diff --git a/src/agents/geoparsing.py b/src/agents/geoparsing.py index 999a213..025d559 100644 --- a/src/agents/geoparsing.py +++ b/src/agents/geoparsing.py @@ -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 { diff --git a/src/agents/researcher.py b/src/agents/researcher.py index e25ac25..a0d6cfb 100644 --- a/src/agents/researcher.py +++ b/src/agents/researcher.py @@ -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 + seen_terms.add(tl) + deduped.append(t) + + if not deduped: continue - query = " ".join(kws[:max_keywords]) + query = " ".join(deduped) if not query or query in seen_queries: continue seen_queries.add(query)