SPB Git forge

spb/sorti-ka

Public

Toutes les sorties et tous les événements du Québec, un seul endroit — 7 connecteurs, fiches SSR, design Groupe KA.

58commits 1branches 0releases
13.7 MBsize
maindefault branch
17 days agolast push
HTML 82.9% Python 15.2% TypeScript 0.9% JavaScript 0.7%
4.3 KB · 103 lines python
Raw Blame History
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