Breed-Enricher: verbesserte Wikipedia-Suche + retry_missed für ki_enriched=2
- _NAME_ALIASES-Dict mit 20 Rassen-Mappings (z.B. Zwergpinscher → Miniature Pinscher) - exsentences=5 statt exintro=1 + Threshold 80 statt 150 Zeichen (fixiert Zwergpinscher, Spinone, Thai Bangkaew) - Neue Fallback-Kette: direkt DE/EN → name_de → Aliasse → normalisierte Namen → opensearch DE/EN - Neue Funktion retry_missed(limit) für ki_enriched=2 ohne DB-Reset - CLI-Flags --retry-missed und --dry-run
This commit is contained in:
parent
5aba366b21
commit
77f6af8817
1 changed files with 304 additions and 122 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
BAN YARO — Rassen-Anreicherung (Wikipedia-grounded)
|
BAN YARO — Rassen-Anreicherung (Wikipedia-grounded)
|
||||||
|
|
||||||
Strategie:
|
Strategie:
|
||||||
1. Wikipedia-Einleitungstext abrufen (de → en Fallback)
|
1. Wikipedia-Einleitungstext abrufen (de → en Fallback + opensearch + Aliasse)
|
||||||
2. Claude Haiku extrahiert Fakten NUR aus dem Quelltext
|
2. Claude Haiku extrahiert Fakten NUR aus dem Quelltext
|
||||||
3. Kein Wikipedia-Artikel → ki_enriched=2, ki_source='none' (nicht veröffentlichen)
|
3. Kein Wikipedia-Artikel → ki_enriched=2, ki_source='none' (nicht veröffentlichen)
|
||||||
|
|
||||||
|
|
@ -31,6 +31,30 @@ _HAIKU_MODEL = "claude-haiku-4-5-20251001"
|
||||||
|
|
||||||
_WP_HEADERS = {"User-Agent": "Banyaro/1.0 (https://banyaro.de; mail@banyaro.de) httpx"}
|
_WP_HEADERS = {"User-Agent": "Banyaro/1.0 (https://banyaro.de; mail@banyaro.de) httpx"}
|
||||||
|
|
||||||
|
# Bekannte Name-Mappings: DB-Name → Liste von Wikipedia-Titeln die zu versuchen sind
|
||||||
|
_NAME_ALIASES: dict[str, list[str]] = {
|
||||||
|
"Staffordshire Bullterrier": ["Staffordshire Bull Terrier"],
|
||||||
|
"Tibet-Spaniel": ["Tibetan Spaniel"],
|
||||||
|
"Tschechischer Terrier": ["Český teriér", "Czech Terrier", "Böhmischer Terrier"],
|
||||||
|
"Weißer Schweizer Schäferhund": ["White Swiss Shepherd Dog", "Berger Blanc Suisse"],
|
||||||
|
"Zwergpinscher": ["Miniature Pinscher"],
|
||||||
|
"wire-haired dachshund": ["Wire-haired Dachshund", "Dachshund"],
|
||||||
|
"Spinone Italiano": ["Spinone"],
|
||||||
|
"Thai Bangkaew Dog": ["Thai Bangkaew"],
|
||||||
|
"Terceira-Dogge": ["Fila da Terceira"],
|
||||||
|
"Ungarische Bracke - Transylvanischer Laufhund": ["Transylvanian Hound"],
|
||||||
|
"anglo-français de moyenne vénerie": ["Anglo-Français de Petite Vénerie", "Anglo-Français de moyenne vénerie"],
|
||||||
|
"artésien-normand": ["Basset Artésien Normand"],
|
||||||
|
"kanadischer Eskinohund": ["Canadian Eskimo Dog"],
|
||||||
|
"Wäller": ["Wäller"],
|
||||||
|
"Serbischer Laufhund": ["Srpski gonič", "Serbian Hound"],
|
||||||
|
"Serbian sheep dog": ["Sharplaninac", "Šarplaninac"],
|
||||||
|
"Slovenský hrubosrstý stavač": ["Slovak Rough-haired Pointer"],
|
||||||
|
"Spino siciliano": ["Cirneco dell'Etna"],
|
||||||
|
"Svensk vit älghund": ["Jamthund", "Jämthund", "Swedish Elkhound"],
|
||||||
|
"Walker Foxhound": ["Treeing Walker Coonhound"],
|
||||||
|
}
|
||||||
|
|
||||||
# Übersetzungstabelle für englische TheDogAPI-Temperamentwörter
|
# Übersetzungstabelle für englische TheDogAPI-Temperamentwörter
|
||||||
_TEMPER_DE: dict[str, str] = {
|
_TEMPER_DE: dict[str, str] = {
|
||||||
"adaptable": "anpassungsfähig",
|
"adaptable": "anpassungsfähig",
|
||||||
|
|
@ -175,60 +199,164 @@ def _parse_json(raw: str) -> dict:
|
||||||
raise ValueError(f"Kein gültiges JSON in Antwort gefunden: {raw[:200]}")
|
raise ValueError(f"Kein gültiges JSON in Antwort gefunden: {raw[:200]}")
|
||||||
|
|
||||||
|
|
||||||
async def _fetch_wikipedia_text(name: str) -> tuple[str | None, str | None]:
|
def _normalize_name(name: str) -> list[str]:
|
||||||
"""Holt den Einleitungstext eines Wikipedia-Artikels (de → en Fallback).
|
"""Erzeugt normalisierte Suchvarianten für einen Rassennamen."""
|
||||||
|
variants = []
|
||||||
|
# Bindestriche durch Leerzeichen ersetzen
|
||||||
|
no_dash = name.replace("-", " ")
|
||||||
|
if no_dash != name:
|
||||||
|
variants.append(no_dash)
|
||||||
|
# Gängige Suffixe abschneiden und Rest behalten
|
||||||
|
suffixes = [
|
||||||
|
" Sheepdog", " Dog", " Hound", " Terrier", " Spaniel",
|
||||||
|
" Shepherd", " Pointer", " Retriever", " Bulldog",
|
||||||
|
]
|
||||||
|
for suffix in suffixes:
|
||||||
|
if name.lower().endswith(suffix.lower()) and len(name) > len(suffix) + 3:
|
||||||
|
variants.append(name[: -len(suffix)].strip())
|
||||||
|
break
|
||||||
|
return variants
|
||||||
|
|
||||||
|
|
||||||
|
async def _wp_direct(client: httpx.AsyncClient, lang: str, title: str) -> tuple[str | None, str | None]:
|
||||||
|
"""Holt Artikeltext via titles-Parameter (direkte Suche).
|
||||||
|
|
||||||
|
Nutzt exsentences=5 statt exintro=1, damit Artikel mit sehr kurzer Einleitung
|
||||||
|
(z.B. Zwergpinscher DE: 143 Zeichen) trotzdem genug Text liefern.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
resp = await client.get(
|
||||||
|
f"https://{lang}.wikipedia.org/w/api.php",
|
||||||
|
params={
|
||||||
|
"action": "query",
|
||||||
|
"titles": title,
|
||||||
|
"prop": "extracts",
|
||||||
|
"exsentences": 5,
|
||||||
|
"explaintext": 1,
|
||||||
|
"format": "json",
|
||||||
|
"redirects": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pages = resp.json().get("query", {}).get("pages", {})
|
||||||
|
for page in pages.values():
|
||||||
|
if page.get("pageid", -1) == -1:
|
||||||
|
continue
|
||||||
|
text = page.get("extract", "").strip()
|
||||||
|
if len(text) > 80:
|
||||||
|
return text[:3000], lang
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("WP direkt (%s/%s) fehlgeschlagen: %s", lang, title, e)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
async def _wp_opensearch(client: httpx.AsyncClient, lang: str, query: str) -> tuple[str | None, str | None]:
|
||||||
|
"""Sucht via opensearch und holt dann den ersten Treffer-Artikel."""
|
||||||
|
try:
|
||||||
|
resp = await client.get(
|
||||||
|
f"https://{lang}.wikipedia.org/w/api.php",
|
||||||
|
params={
|
||||||
|
"action": "opensearch",
|
||||||
|
"search": query,
|
||||||
|
"limit": 1,
|
||||||
|
"format": "json",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
results = resp.json()
|
||||||
|
# opensearch gibt [query, [titles], [descriptions], [urls]] zurück
|
||||||
|
titles = results[1] if len(results) > 1 else []
|
||||||
|
if not titles:
|
||||||
|
return None, None
|
||||||
|
found_title = titles[0]
|
||||||
|
logger.debug("Opensearch (%s) '%s' → '%s'", lang, query, found_title)
|
||||||
|
return await _wp_direct(client, lang, found_title)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("Opensearch (%s/%s) fehlgeschlagen: %s", lang, query, e)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
async def _fetch_wikipedia_text(
|
||||||
|
name: str, name_de: str | None = None
|
||||||
|
) -> tuple[str | None, str | None]:
|
||||||
|
"""Holt den Einleitungstext eines Wikipedia-Artikels.
|
||||||
|
|
||||||
|
Fallback-Kette:
|
||||||
|
1. Direkte Suche DE (Originalname)
|
||||||
|
2. Direkte Suche EN (Originalname)
|
||||||
|
3. Direkte Suche DE (name_de, falls vorhanden und verschieden)
|
||||||
|
4. Aliasse aus _NAME_ALIASES (DE + EN je Alias)
|
||||||
|
5. Normalisierte Namensvarianten direkt DE + EN
|
||||||
|
6. Opensearch DE (Originalname)
|
||||||
|
7. Opensearch EN (Originalname)
|
||||||
|
|
||||||
Returns: (text, lang) oder (None, None) wenn kein Artikel gefunden.
|
Returns: (text, lang) oder (None, None) wenn kein Artikel gefunden.
|
||||||
"""
|
"""
|
||||||
for lang in ("de", "en"):
|
async with httpx.AsyncClient(timeout=10, headers=_WP_HEADERS) as client:
|
||||||
try:
|
|
||||||
async with httpx.AsyncClient(timeout=10, headers=_WP_HEADERS) as client:
|
# 1+2: Direkte Suche mit Originalnamen
|
||||||
resp = await client.get(
|
for lang in ("de", "en"):
|
||||||
f"https://{lang}.wikipedia.org/w/api.php",
|
text, lang_found = await _wp_direct(client, lang, name)
|
||||||
params={
|
if text:
|
||||||
"action": "query",
|
return text, lang_found
|
||||||
"titles": name,
|
|
||||||
"prop": "extracts",
|
# 3: Direkte Suche mit name_de (falls vorhanden und nicht identisch)
|
||||||
"exintro": 1,
|
if name_de and name_de.strip() and name_de.strip() != name:
|
||||||
"explaintext": 1,
|
for lang in ("de", "en"):
|
||||||
"format": "json",
|
text, lang_found = await _wp_direct(client, lang, name_de.strip())
|
||||||
"redirects": 1,
|
if text:
|
||||||
},
|
logger.debug("name_de-Treffer (%s): %s → %s", lang, name, name_de)
|
||||||
)
|
return text, lang_found
|
||||||
pages = resp.json().get("query", {}).get("pages", {})
|
|
||||||
for page in pages.values():
|
# 4: Bekannte Aliasse
|
||||||
if page.get("pageid", -1) == -1:
|
for alias in _NAME_ALIASES.get(name, []):
|
||||||
continue
|
for lang in ("de", "en"):
|
||||||
text = page.get("extract", "").strip()
|
text, lang_found = await _wp_direct(client, lang, alias)
|
||||||
if len(text) > 150:
|
if text:
|
||||||
return text[:3000], lang
|
logger.debug("Alias-Treffer (%s): %s → %s", lang, name, alias)
|
||||||
except Exception as e:
|
return text, lang_found
|
||||||
logger.debug("Wikipedia-Text (%s) fehlgeschlagen für %s: %s", lang, name, e)
|
|
||||||
|
# 5: Normalisierte Namensvarianten
|
||||||
|
for variant in _normalize_name(name):
|
||||||
|
for lang in ("de", "en"):
|
||||||
|
text, lang_found = await _wp_direct(client, lang, variant)
|
||||||
|
if text:
|
||||||
|
logger.debug("Normalisierungs-Treffer (%s): %s → %s", lang, name, variant)
|
||||||
|
return text, lang_found
|
||||||
|
|
||||||
|
# 6+7: Opensearch als letzter Ausweg
|
||||||
|
for lang in ("de", "en"):
|
||||||
|
text, lang_found = await _wp_opensearch(client, lang, name)
|
||||||
|
if text:
|
||||||
|
logger.debug("Opensearch-Treffer (%s): %s", lang, name)
|
||||||
|
return text, lang_found
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
async def _fetch_wikimedia_photo(name: str) -> str | None:
|
async def _fetch_wikimedia_photo(name: str) -> str | None:
|
||||||
"""Sucht ein lizenzfreies Foto via Wikipedia pageimages API (de → en Fallback)."""
|
"""Sucht ein lizenzfreies Foto via Wikipedia pageimages API (de → en Fallback)."""
|
||||||
|
# Auch Aliasse probieren
|
||||||
|
names_to_try = [name] + _NAME_ALIASES.get(name, [])
|
||||||
for lang in ("de", "en"):
|
for lang in ("de", "en"):
|
||||||
try:
|
for title in names_to_try:
|
||||||
async with httpx.AsyncClient(timeout=8, headers=_WP_HEADERS) as client:
|
try:
|
||||||
resp = await client.get(
|
async with httpx.AsyncClient(timeout=8, headers=_WP_HEADERS) as client:
|
||||||
f"https://{lang}.wikipedia.org/w/api.php",
|
resp = await client.get(
|
||||||
params={
|
f"https://{lang}.wikipedia.org/w/api.php",
|
||||||
"action": "query",
|
params={
|
||||||
"titles": name,
|
"action": "query",
|
||||||
"prop": "pageimages",
|
"titles": title,
|
||||||
"format": "json",
|
"prop": "pageimages",
|
||||||
"pithumbsize": 800,
|
"format": "json",
|
||||||
"redirects": 1,
|
"pithumbsize": 800,
|
||||||
},
|
"redirects": 1,
|
||||||
)
|
},
|
||||||
pages = resp.json().get("query", {}).get("pages", {})
|
)
|
||||||
for page in pages.values():
|
pages = resp.json().get("query", {}).get("pages", {})
|
||||||
if "thumbnail" in page:
|
for page in pages.values():
|
||||||
return page["thumbnail"]["source"]
|
if "thumbnail" in page:
|
||||||
except Exception as e:
|
return page["thumbnail"]["source"]
|
||||||
logger.debug("Wikimedia-Foto (%s) fehlgeschlagen für %s: %s", lang, name, e)
|
except Exception as e:
|
||||||
|
logger.debug("Wikimedia-Foto (%s/%s) fehlgeschlagen: %s", lang, title, e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -258,7 +386,89 @@ async def _haiku_complete(prompt: str) -> str:
|
||||||
return resp.content[0].text.strip()
|
return resp.content[0].text.strip()
|
||||||
|
|
||||||
|
|
||||||
async def enrich_breeds(limit: int = 10) -> int:
|
async def _enrich_one(rasse: dict, dry_run: bool = False) -> bool:
|
||||||
|
"""Reichert eine einzelne Rasse an. Gibt True zurück wenn erfolgreich."""
|
||||||
|
name = rasse["name"]
|
||||||
|
name_de = rasse.get("name_de")
|
||||||
|
rasse_id = rasse["id"]
|
||||||
|
|
||||||
|
# 1. Wikipedia-Text holen
|
||||||
|
wiki_text, wiki_lang = await _fetch_wikipedia_text(name, name_de)
|
||||||
|
|
||||||
|
if not wiki_text:
|
||||||
|
if not dry_run:
|
||||||
|
with db() as conn:
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE wiki_rassen SET ki_enriched=2, ki_source='none' WHERE id=?",
|
||||||
|
(rasse_id,),
|
||||||
|
)
|
||||||
|
logger.info("Kein Wikipedia-Artikel: %s → übersprungen", name)
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
logger.info("[DRY-RUN] Gefunden: %s (WP-%s, %d Zeichen)", name, wiki_lang.upper(), len(wiki_text))
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 2. Haiku extrahiert Fakten aus dem Quelltext
|
||||||
|
prompt = _PROMPT.format(name=name, lang=wiki_lang.upper(), wiki_text=wiki_text)
|
||||||
|
try:
|
||||||
|
raw = await _haiku_complete(prompt)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Haiku-Anfrage fehlgeschlagen für %s: %s", name, e)
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = _parse_json(raw)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning("JSON-Parsing fehlgeschlagen für %s: %s", name, e)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 3. DB-Update
|
||||||
|
updates = {
|
||||||
|
k: v for k, v in data.items()
|
||||||
|
if k in _DIRECT_FIELDS and v is not None
|
||||||
|
}
|
||||||
|
if "temperament" in updates:
|
||||||
|
updates["temperament"] = translate_temperament(updates["temperament"])
|
||||||
|
updates["ki_enriched"] = 1
|
||||||
|
updates["ki_model"] = _HAIKU_MODEL
|
||||||
|
updates["ki_source"] = f"wikipedia_{wiki_lang}"
|
||||||
|
|
||||||
|
cols = ", ".join(f"{k}=?" for k in updates)
|
||||||
|
values = list(updates.values()) + [rasse_id]
|
||||||
|
|
||||||
|
try:
|
||||||
|
with db() as conn:
|
||||||
|
conn.execute(f"UPDATE wiki_rassen SET {cols} WHERE id=?", values)
|
||||||
|
logger.info("Rasse angereichert: %s (%d Felder, WP-%s)",
|
||||||
|
name, len(updates) - 2, wiki_lang.upper())
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("DB-Update fehlgeschlagen für %s: %s", name, e)
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 4. Foto holen wenn noch keins vorhanden
|
||||||
|
if not rasse.get("foto_url"):
|
||||||
|
foto_url = await _fetch_wikimedia_photo(name)
|
||||||
|
if foto_url:
|
||||||
|
try:
|
||||||
|
with db() as conn:
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE wiki_rassen SET foto_url=? WHERE id=?",
|
||||||
|
(foto_url, rasse_id),
|
||||||
|
)
|
||||||
|
logger.info("Wikimedia-Foto gesetzt: %s", name)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Foto-Update fehlgeschlagen für %s: %s", name, e)
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def enrich_breeds(limit: int = 10, dry_run: bool = False) -> int:
|
||||||
"""
|
"""
|
||||||
Reichert bis zu `limit` Rassen an (ki_enriched = 0).
|
Reichert bis zu `limit` Rassen an (ki_enriched = 0).
|
||||||
|
|
||||||
|
|
@ -269,7 +479,7 @@ async def enrich_breeds(limit: int = 10) -> int:
|
||||||
"""
|
"""
|
||||||
with db() as conn:
|
with db() as conn:
|
||||||
rassen = conn.execute(
|
rassen = conn.execute(
|
||||||
"""SELECT id, name, slug, herkunft, foto_url FROM wiki_rassen
|
"""SELECT id, name, slug, herkunft, foto_url, name_de FROM wiki_rassen
|
||||||
WHERE ki_enriched = 0
|
WHERE ki_enriched = 0
|
||||||
ORDER BY name ASC
|
ORDER BY name ASC
|
||||||
LIMIT ?""",
|
LIMIT ?""",
|
||||||
|
|
@ -281,81 +491,42 @@ async def enrich_breeds(limit: int = 10) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
enriched_count = 0
|
enriched_count = 0
|
||||||
|
|
||||||
for rasse in rassen:
|
for rasse in rassen:
|
||||||
name = rasse["name"]
|
if await _enrich_one(rasse, dry_run=dry_run):
|
||||||
rasse_id = rasse["id"]
|
|
||||||
|
|
||||||
# 1. Wikipedia-Text holen
|
|
||||||
wiki_text, wiki_lang = await _fetch_wikipedia_text(name)
|
|
||||||
|
|
||||||
if not wiki_text:
|
|
||||||
# Kein Artikel → markieren und überspringen
|
|
||||||
with db() as conn:
|
|
||||||
conn.execute(
|
|
||||||
"UPDATE wiki_rassen SET ki_enriched=2, ki_source='none' WHERE id=?",
|
|
||||||
(rasse_id,),
|
|
||||||
)
|
|
||||||
logger.info("Kein Wikipedia-Artikel: %s → übersprungen", name)
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 2. Haiku extrahiert Fakten aus dem Quelltext
|
|
||||||
prompt = _PROMPT.format(name=name, lang=wiki_lang.upper(), wiki_text=wiki_text)
|
|
||||||
try:
|
|
||||||
raw = await _haiku_complete(prompt)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Haiku-Anfrage fehlgeschlagen für %s: %s", name, e)
|
|
||||||
await asyncio.sleep(3)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = _parse_json(raw)
|
|
||||||
except ValueError as e:
|
|
||||||
logger.warning("JSON-Parsing fehlgeschlagen für %s: %s", name, e)
|
|
||||||
await asyncio.sleep(2)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 3. DB-Update
|
|
||||||
updates = {
|
|
||||||
k: v for k, v in data.items()
|
|
||||||
if k in _DIRECT_FIELDS and v is not None
|
|
||||||
}
|
|
||||||
if "temperament" in updates:
|
|
||||||
updates["temperament"] = translate_temperament(updates["temperament"])
|
|
||||||
updates["ki_enriched"] = 1
|
|
||||||
updates["ki_model"] = _HAIKU_MODEL
|
|
||||||
updates["ki_source"] = f"wikipedia_{wiki_lang}"
|
|
||||||
|
|
||||||
cols = ", ".join(f"{k}=?" for k in updates)
|
|
||||||
values = list(updates.values()) + [rasse_id]
|
|
||||||
|
|
||||||
try:
|
|
||||||
with db() as conn:
|
|
||||||
conn.execute(f"UPDATE wiki_rassen SET {cols} WHERE id=?", values)
|
|
||||||
logger.info("Rasse angereichert: %s (%d Felder, WP-%s)",
|
|
||||||
name, len(updates) - 2, wiki_lang.upper())
|
|
||||||
enriched_count += 1
|
enriched_count += 1
|
||||||
except Exception as e:
|
|
||||||
logger.error("DB-Update fehlgeschlagen für %s: %s", name, e)
|
|
||||||
await asyncio.sleep(2)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 4. Foto holen wenn noch keins vorhanden
|
return enriched_count
|
||||||
if not rasse["foto_url"]:
|
|
||||||
foto_url = await _fetch_wikimedia_photo(name)
|
|
||||||
if foto_url:
|
|
||||||
try:
|
|
||||||
with db() as conn:
|
|
||||||
conn.execute(
|
|
||||||
"UPDATE wiki_rassen SET foto_url=? WHERE id=?",
|
|
||||||
(foto_url, rasse_id),
|
|
||||||
)
|
|
||||||
logger.info("Wikimedia-Foto gesetzt: %s", name)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Foto-Update fehlgeschlagen für %s: %s", name, e)
|
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
async def retry_missed(limit: int = 70, dry_run: bool = False) -> int:
|
||||||
|
"""
|
||||||
|
Versucht erneut, Rassen anzureichern die beim ersten Durchlauf
|
||||||
|
keinen Wikipedia-Artikel hatten (ki_enriched = 2).
|
||||||
|
|
||||||
|
Setzt ki_enriched NICHT zurück — verarbeitet direkt.
|
||||||
|
Bei Erfolg wird ki_enriched=1 gesetzt (überschreibt 2).
|
||||||
|
Bei erneutem Misserfolg bleibt ki_enriched=2.
|
||||||
|
|
||||||
|
Returns: Anzahl erfolgreich angereicherter Rassen.
|
||||||
|
"""
|
||||||
|
with db() as conn:
|
||||||
|
rassen = conn.execute(
|
||||||
|
"""SELECT id, name, slug, herkunft, foto_url, name_de FROM wiki_rassen
|
||||||
|
WHERE ki_enriched = 2
|
||||||
|
ORDER BY name ASC
|
||||||
|
LIMIT ?""",
|
||||||
|
(limit,),
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
if not rassen:
|
||||||
|
logger.info("Keine übersprungenen Rassen zum Retry gefunden.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
logger.info("Retry für %d Rassen mit ki_enriched=2 ...", len(rassen))
|
||||||
|
enriched_count = 0
|
||||||
|
for rasse in rassen:
|
||||||
|
if await _enrich_one(rasse, dry_run=dry_run):
|
||||||
|
enriched_count += 1
|
||||||
|
|
||||||
return enriched_count
|
return enriched_count
|
||||||
|
|
||||||
|
|
@ -405,14 +576,25 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
|
||||||
parser = argparse.ArgumentParser(description="Rassen-Anreicherung (Wikipedia-grounded)")
|
parser = argparse.ArgumentParser(description="Rassen-Anreicherung (Wikipedia-grounded)")
|
||||||
parser.add_argument("--limit", type=int, default=10)
|
parser.add_argument("--limit", type=int, default=10,
|
||||||
|
help="Maximale Anzahl zu verarbeitender Rassen")
|
||||||
parser.add_argument("--reset-gemma", action="store_true",
|
parser.add_argument("--reset-gemma", action="store_true",
|
||||||
help="Gemma-Einträge zurücksetzen bevor angereichert wird")
|
help="Gemma-Einträge zurücksetzen bevor angereichert wird")
|
||||||
|
parser.add_argument("--retry-missed", action="store_true",
|
||||||
|
help="Rassen mit ki_enriched=2 erneut versuchen")
|
||||||
|
parser.add_argument("--dry-run", action="store_true",
|
||||||
|
help="Nur Wikipedia-Suche testen, keine DB-Änderungen")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.reset_gemma:
|
if args.reset_gemma:
|
||||||
n = reset_gemma_entries()
|
n = reset_gemma_entries()
|
||||||
print(f"Reset: {n} Gemma-Einträge zurückgesetzt")
|
print(f"Reset: {n} Gemma-Einträge zurückgesetzt")
|
||||||
|
|
||||||
count = asyncio.run(enrich_breeds(args.limit))
|
if args.retry_missed:
|
||||||
print(f"Angereichert: {count} Rassen")
|
count = asyncio.run(retry_missed(args.limit, dry_run=args.dry_run))
|
||||||
|
prefix = "[DRY-RUN] " if args.dry_run else ""
|
||||||
|
print(f"{prefix}Retry: {count} von {args.limit} Rassen angereichert")
|
||||||
|
else:
|
||||||
|
count = asyncio.run(enrich_breeds(args.limit, dry_run=args.dry_run))
|
||||||
|
prefix = "[DRY-RUN] " if args.dry_run else ""
|
||||||
|
print(f"{prefix}Angereichert: {count} Rassen")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue