[ka2] fix connecteur thrace: échec honnête au lieu d'un « 0 trouvé ok » qui menaçait les 158 annonces. Le sync du 2026-09-08 11:00 a rapporté found=0 ok=1 (dérive vs médiane 153, retraits suspendus par le garde-fou) : le fetch() avalait toute exception par projet Livya (`except Exception: continue` sur thrace, radisson et carree-radisson) — une erreur réseau transitoire sur app.livya.com devenait un succès à 0 résultat, invisible du disjoncteur d'ingest.py. Reproduit en live : les 3 pages plans répondent 200 (0,9–6 Mo) et 538 unités brutes se parsent — la source est saine, seul le silence sur erreur était fautif. On laisse désormais l'exception réseau remonter (ingest journalise ok=0) et on lève RuntimeError si une page plans ne parse aucun objet unitId du flux RSC (garde contre un rendu partiel 200 — chaque page en publie des dizaines, tous statuts confondus ; même pattern que les fix le_fjord 472920e et logiluxx 1814e48). Testé : run.py sync thrace → found=151 ok (médiane 153, updated=1), pm2 restart lou-ka-sync, site :8095 → 200.
1 changed file +16 −7
modified
louka/connectors/thrace.py
+16 −7
@@ -116,17 +116,26 @@ class ThraceConnector(BaseConnector): | ||
| 116 | 116 | client, entity = self._module_params() |
| 117 | 117 | projects = self._project_slugs(client) |
| 118 | 118 | |
| 119 | + # échec HONNÊTE : une erreur réseau ou un rendu Next.js partiel (200 | |
| 120 | + # sans objets unitId dans le flux RSC) doit lever (ok=0 dans sync_log) | |
| 121 | + # et non retourner [] — l'ancien `except Exception: continue` sur les | |
| 122 | + # 3 projets a produit le « 0 trouvé ok » du 2026-09-08 11:00 alors que | |
| 123 | + # la source était saine (même pattern que les fix le_fjord/logiluxx) | |
| 119 | 124 | units: dict[str, dict] = {} |
| 120 | 125 | buildings: dict[str, tuple[str, str]] = {} # id -> (nom, slug) |
| 121 | 126 | for slug in projects: |
| 122 | − try: | |
| 123 | − html = self.get( | |
| 124 | − f"{LIVYA}/fr/{client}/projects/{slug}/plans/{entity}", | |
| 125 | − params={"noLayout": "1"}).text | |
| 126 | − except Exception: | |
| 127 | − continue # un projet cassé ne bloque pas | |
| 127 | + html = self.get( | |
| 128 | + f"{LIVYA}/fr/{client}/projects/{slug}/plans/{entity}", | |
| 129 | + params={"noLayout": "1"}).text | |
| 128 | 130 | blob = _flight_blob(html) |
| 129 | − for u in self._units(blob): | |
| 131 | + page_units = self._units(blob) | |
| 132 | + if not page_units: | |
| 133 | + # chaque page plans publie des dizaines d'unités (tous statuts | |
| 134 | + # confondus) : zéro = rendu partiel/illisible, pas un vide réel | |
| 135 | + raise RuntimeError( | |
| 136 | + f"aucun objet unitId dans le flux RSC du projet {slug} " | |
| 137 | + f"({len(html)} o) — rendu partiel ?") | |
| 138 | + for u in page_units: | |
| 130 | 139 | units.setdefault(u["unitId"], u) |
| 131 | 140 | for bid, name, bslug in _BUILDING_RE.findall(blob): |
| 132 | 141 | buildings.setdefault(bid, (name, bslug)) |
| 133 | 142 | |