Toutes les sorties et tous les événements du Québec, un seul endroit — 7 connecteurs, fiches SSR, design Groupe KA.
HTML 82.9%
Python 15.2%
TypeScript 0.9%
JavaScript 0.7%
1# -----------------------------------------------------------------------------2# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# ingest.py : pipeline d'ingestion — exécute les connecteurs et synchronise5# la base (ajouts / mises à jour / retraits) = contenu toujours à6# jour (patron Lou-Ka : louka/ingest.py)7# -----------------------------------------------------------------------------8from __future__ import annotations910import sys11import time12import traceback1314from . import db, geocode, venues15from .connectors import CONNECTORS161718def run(sources: list[str] | None = None) -> list[dict]:19 """Exécute l'ingestion pour toutes les sources (ou celles demandées)."""20 con = db.connect()21 results = []22 targets = sources or list(CONNECTORS.keys())23 for sid in targets:24 cls = CONNECTORS.get(sid)25 if cls is None:26 print(f"[sorti-ka] connecteur inconnu : {sid}", file=sys.stderr)27 continue28 t0 = time.time()29 print(f"[sorti-ka] sync {sid} ...")30 try:31 events = cls().fetch()32 finalized, dropped = [], 033 for ev in events:34 try:35 finalized.append(ev.finalize())36 except Exception: # un événement malformé ne bloque pas la source37 dropped += 138 stats = db.sync_source(con, sid, finalized)39 stats["seconds"] = round(time.time() - t0, 1)40 if dropped:41 stats["dropped"] = dropped42 if stats.get("alert"):43 print(f"[sorti-ka] ⚠ ALERTE {sid} : {stats['alert']}")44 print(f"[sorti-ka] {stats}")45 results.append(stats)46 except Exception as exc: # robustesse : une source ne bloque pas les autres47 db.log_failure(con, sid, f"{exc}")48 traceback.print_exc()49 results.append({"source": sid, "error": str(exc)})50 # Cycle de vie temporel (Phase 2) : les événements terminés sont archivés51 # (active=0, jamais supprimés) à chaque cycle — les sources qui publient52 # leur historique (montreal, sitq, laval…) ne gonflent plus l'inventaire.53 try:54 archived = db.archive_past_events(con)55 if archived:56 print(f"[sorti-ka] archivage : {archived} événement(s) terminé(s) → active=0")57 except Exception:58 traceback.print_exc()59 # Santé « futurs » (Phase 2) : une source verte peut mourir en silence60 # (Laval : 41 runs sans erreur, max(date)=2025-12-09). Alerte si une61 # source n'a plus AUCUN événement futur ou chute de plus de 80 %.62 try:63 for alert in db.check_future_health(con):64 print(f"[sorti-ka] ⚠ ALERTE futurs — {alert}")65 except Exception:66 traceback.print_exc()67 # Géocodage des salles (transversal) : remplit lat/lng des événements68 # actifs sans GPS (salle + ville connues ; adresse civique en priorité69 # quand la source la publie) — Nominatim + cache disque,70 # plafond de requêtes réseau par cycle (voir sortika/geocode.py).71 try:72 gstats = geocode.run(con)73 print(f"[sorti-ka] geocode {gstats}")74 except Exception: # le géocodage ne bloque jamais l'ingestion75 traceback.print_exc()76 # Annuaire de salles (Phase 3) : apprentissage des salles géocodées par77 # les sources (0 réseau), extrait OSM (≤ 1 requête/30 j), géocodage des78 # salles récurrentes (plafond VENUES_GEOCODE_MAX), puis application aux79 # événements sans GPS — voir sortika/venues.py. Jamais de GPS inventé.80 try:81 vstats = venues.run(con)82 print(f"[sorti-ka] venues {vstats}")83 except Exception: # l'annuaire ne bloque jamais l'ingestion84 traceback.print_exc()85 try:86 con.execute("PRAGMA optimize")87 except Exception:88 pass89 con.close()90 return results919293def watch(interval_seconds: int = 6 * 3600) -> None:94 """Boucle de rafraîchissement périodique (pseudo-webhook par sondage).9596 Cadence par défaut : 6 h — les calendriers d'événements bougent plus vite97 qu'un parc locatif, mais les sources ouvertes n'exigent pas de temps réel.98 """99 while True:100 run()101 print(f"[sorti-ka] prochaine synchro dans {interval_seconds // 60} min")102 time.sleep(interval_seconds)103