SPB Git forge

spb/resto-ka

Public

Resto·Ka — tous les restaurants du Québec, menus complets et prix réels (famille ·Ka)

52commits 1branches 0releases
11.6 MBsize
maindefault branch
19 days agolast push
Python 69.3% TypeScript 16.7% CSS 7.9% JavaScript 4.7% HTML 1.4%

osm: miroirs Overpass fiabilisés + réessais multi-rondes

Incident du 2026-08-22 : les 4 miroirs sont tombés en même temps (kumi 500,
overpass-api.de injoignable, private.coffee 502, osm.jp certificat TLS
invalide) et le run hebdo a fini à 0 POI (retraits suspendus par le garde-fou).

- osm.jp retiré (hostname mismatch permanent), maps.mail.ru ajouté (instance
  planète complète, seul miroir opérationnel pendant l incident)
- 3 rondes de réessai sur l ensemble des miroirs (attente 0/90/300 s) avant
  d abandonner un type d établissement

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 23, 2026) parent 10ba8a7

1 changed file +43 −25

modified restoka/connectors/osm.py +43 −25
@@ -20,6 +20,7 @@
20 20 from __future__ import annotations
21 21
22 22 import sys
23 +import time
23 24
24 25 from ..hours import parse_opening_hours
25 26 from ..normalize import classify_cuisines
@@ -32,9 +33,20 @@ QC_AREA = 3600061549
32 33 MIRRORS = [
33 34 "https://overpass.kumi.systems/api/interpreter",
34 35 "https://overpass-api.de/api/interpreter",
36 + "https://maps.mail.ru/osm/tools/overpass/api/interpreter",
35 37 "https://overpass.private.coffee/api/interpreter",
36 "https://overpass.osm.jp/api/interpreter",
37 38 ]
39 +# NB : overpass.osm.jp RETIRÉ (2026-08-23) — certificat TLS invalide pour ce
40 +# nom d'hôte (hostname mismatch permanent). maps.mail.ru AJOUTÉ : instance
41 +# planète complète (vérifiée sur l'aire Québec), seul miroir à répondre le
42 +# 2026-08-22 quand kumi/de/coffee sont tombés ensemble et que le run OSM a
43 +# fini à 0 POI.
44 +
45 +# Réessais : les pannes Overpass (500/502, surcharge) sont le plus souvent
46 +# transitoires — on refait des passes complètes sur les miroirs avec une
47 +# attente croissante avant d'abandonner le type d'établissement.
48 +RETRY_ROUNDS = 3
49 +RETRY_BACKOFF_SECONDS = (0, 90, 300)
38 50
39 51 # User-Agent DÉDIÉ aux miroirs Overpass. Vérifié le 2026-08-18 :
40 52 # overpass-api.de répond 406 Not Acceptable à tout UA « déguisé en
@@ -141,30 +153,36 @@ class OsmConnector(BaseConnector):
141 153 f'out tags center;'
142 154 )
143 155 last_exc: Exception | None = None
144 for mirror in MIRRORS:
145 try:
146 resp = self.post(mirror, data={"data": query},
147 headers={"User-Agent": OVERPASS_UA})
148 data = resp.json()
149 # Overpass peut répondre 200 avec des résultats TRONQUÉS et un
150 # « remark » (timeout/mémoire) — traiter comme un échec de
151 # miroir plutôt que d'avaler une liste partielle (incident du
152 # run #2 : 6 213 POI au lieu de 13 531).
153 remark = (data.get("remark") or "").lower()
154 if "timed out" in remark or "error" in remark:
155 raise RuntimeError(f"réponse partielle : {remark[:200]}")
156 elements = data.get("elements") or []
157 # 0 élément pour une requête à l'échelle de la PROVINCE est
158 # toujours implausible (chaque type compte des centaines de
159 # POI au QC) : miroir sans les données (extrait régional…)
160 if not elements:
161 raise RuntimeError("0 élément — miroir sans données "
162 "pour l'aire Québec")
163 return elements
164 except Exception as exc: # miroir surchargé -> suivant
165 last_exc = exc
166 print(f"[resto-ka] osm: {mirror} a échoué ({exc}), miroir suivant",
167 file=sys.stderr)
156 + for round_no in range(RETRY_ROUNDS):
157 + wait = RETRY_BACKOFF_SECONDS[min(round_no, len(RETRY_BACKOFF_SECONDS) - 1)]
158 + if wait:
159 + print(f"[resto-ka] osm: nouvelle passe sur les miroirs dans {wait}s "
160 + f"(ronde {round_no + 1}/{RETRY_ROUNDS})", file=sys.stderr)
161 + time.sleep(wait)
162 + for mirror in MIRRORS:
163 + try:
164 + resp = self.post(mirror, data={"data": query},
165 + headers={"User-Agent": OVERPASS_UA})
166 + data = resp.json()
167 + # Overpass peut répondre 200 avec des résultats TRONQUÉS et un
168 + # « remark » (timeout/mémoire) — traiter comme un échec de
169 + # miroir plutôt que d'avaler une liste partielle (incident du
170 + # run #2 : 6 213 POI au lieu de 13 531).
171 + remark = (data.get("remark") or "").lower()
172 + if "timed out" in remark or "error" in remark:
173 + raise RuntimeError(f"réponse partielle : {remark[:200]}")
174 + elements = data.get("elements") or []
175 + # 0 élément pour une requête à l'échelle de la PROVINCE est
176 + # toujours implausible (chaque type compte des centaines de
177 + # POI au QC) : miroir sans les données (extrait régional…)
178 + if not elements:
179 + raise RuntimeError("0 élément — miroir sans données "
180 + "pour l'aire Québec")
181 + return elements
182 + except Exception as exc: # miroir surchargé -> suivant
183 + last_exc = exc
184 + print(f"[resto-ka] osm: {mirror} a échoué ({exc}), miroir suivant",
185 + file=sys.stderr)
168 186 raise RuntimeError(f"tous les miroirs Overpass ont échoué : {last_exc}")
169 187
170 188 # cuisine par défaut quand ni le tag cuisine ni le nom ne classent le POI
171 189