feat(monitoring+data): houseka et rentka deviennent les 10e et 11e services KA
- SERVICES += houseka, rentka : houseka avait deja son collecteur/modele/env (commit eb543c9) mais restait hors de la boucle de supervision et des routes ; rentka est integre de bout en bout (RentkaData, rentka_collector offset/2000 comme lou-ka, RENTKA_SOURCE_URL dans .env -> M4M36:8125, cadence attendue 1 h). - connector_health poll desormais les 11 apps (houseka 17 sources, rentka ~680 sources au premier run). - Docstrings 9 -> 11 services. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7 changed files +43 −8
modified
src/collectors/__init__.py
+4 −2
@@ -6,7 +6,7 @@ | ||
| 6 | 6 | # Contact : contact@spboucher.ai |
| 7 | 7 | # Date : 2026-08-16 |
| 8 | 8 | # ============================================ |
| 9 | −"""Collecteurs des 9 services KA et fabriques associées.""" | |
| 9 | +"""Collecteurs des 11 services KA et fabriques associées.""" | |
| 10 | 10 | |
| 11 | 11 | from __future__ import annotations |
| 12 | 12 | |
@@ -19,6 +19,7 @@ from src.collectors.houseka_collector import HousekaCollector | ||
| 19 | 19 | from src.collectors.immoka_collector import ImmokaCollector |
| 20 | 20 | from src.collectors.jobka_collector import JobkaCollector |
| 21 | 21 | from src.collectors.louka_collector import LoukaCollector |
| 22 | +from src.collectors.rentka_collector import RentkaCollector | |
| 22 | 23 | from src.collectors.restoka_collector import RestokaCollector |
| 23 | 24 | from src.collectors.sortika_collector import SortikaCollector |
| 24 | 25 | |
@@ -33,6 +34,7 @@ COLLECTORS: dict[str, type[BaseCollector]] = { | ||
| 33 | 34 | "sortika": SortikaCollector, |
| 34 | 35 | "creaka": CreakaCollector, |
| 35 | 36 | "jobka": JobkaCollector, |
| 37 | + "rentka": RentkaCollector, | |
| 36 | 38 | } |
| 37 | 39 | |
| 38 | 40 | |
@@ -49,5 +51,5 @@ def get_collector(service: str) -> BaseCollector: | ||
| 49 | 51 | |
| 50 | 52 | |
| 51 | 53 | def build_collectors() -> list[BaseCollector]: |
| 52 | − """Instancie les 9 collecteurs, dans l'ordre canonique des services.""" | |
| 54 | + """Instancie les 11 collecteurs, dans l'ordre canonique des services.""" | |
| 53 | 55 | return [cls() for cls in COLLECTORS.values()] |
added
src/collectors/rentka_collector.py
+22 −0
@@ -0,0 +1,22 @@ | ||
| 1 | +# ============================================ | |
| 2 | +# Projet : API-KA | |
| 3 | +# Fichier : src/collectors/rentka_collector.py | |
| 4 | +# Node : m3u96b | |
| 5 | +# Author : Simon-Pierre Boucher | |
| 6 | +# Contact : contact@spboucher.ai | |
| 7 | +# Date : 2026-08-28 | |
| 8 | +# ============================================ | |
| 9 | +"""Collecteur quotidien du service rent-ka (table rentka_data).""" | |
| 10 | + | |
| 11 | +from __future__ import annotations | |
| 12 | + | |
| 13 | +from src.collectors.base_collector import BaseCollector | |
| 14 | + | |
| 15 | + | |
| 16 | +class RentkaCollector(BaseCollector): | |
| 17 | + """Collecte les données quotidiennes de rent-ka depuis RENTKA_SOURCE_URL.""" | |
| 18 | + | |
| 19 | + service = "rentka" | |
| 20 | + items_key = "listings" | |
| 21 | + pagination = "offset" | |
| 22 | + page_size = 2000 # même API que lou-ka (fork) | |
modified
src/config.py
+3 −0
@@ -30,6 +30,8 @@ SERVICES: tuple[str, ...] = ( | ||
| 30 | 30 | "sortika", |
| 31 | 31 | "creaka", |
| 32 | 32 | "jobka", |
| 33 | + "houseka", | |
| 34 | + "rentka", | |
| 33 | 35 | ) |
| 34 | 36 | |
| 35 | 37 | SERVICE_TABLES: dict[str, str] = {service: f"{service}_data" for service in SERVICES} |
@@ -45,6 +47,7 @@ SERVICE_SOURCE_ENV: dict[str, str] = { | ||
| 45 | 47 | "creaka": "CREAKA_SOURCE_URL", |
| 46 | 48 | "jobka": "JOBKA_SOURCE_URL", |
| 47 | 49 | "houseka": "HOUSEKA_SOURCE_URL", |
| 50 | + "rentka": "RENTKA_SOURCE_URL", | |
| 48 | 51 | } |
| 49 | 52 | |
| 50 | 53 | BASE_DIR = Path(__file__).resolve().parents[1] |
modified
src/database/models.py
+7 −0
@@ -125,6 +125,12 @@ class HousekaData(DataRecordMixin, Base): | ||
| 125 | 125 | __tablename__ = "houseka_data" |
| 126 | 126 | |
| 127 | 127 | |
| 128 | +class RentkaData(DataRecordMixin, Base): | |
| 129 | + """Données quotidiennes du service rent-ka.""" | |
| 130 | + | |
| 131 | + __tablename__ = "rentka_data" | |
| 132 | + | |
| 133 | + | |
| 128 | 134 | class JobkaData(DataRecordMixin, Base): |
| 129 | 135 | """Données quotidiennes du service job-ka.""" |
| 130 | 136 | |
@@ -142,6 +148,7 @@ DATA_MODELS: dict[str, type[Base]] = { | ||
| 142 | 148 | "sortika": SortikaData, |
| 143 | 149 | "creaka": CreakaData, |
| 144 | 150 | "jobka": JobkaData, |
| 151 | + "rentka": RentkaData, | |
| 145 | 152 | } |
| 146 | 153 | |
| 147 | 154 | |
modified
src/monitoring/connector_health.py
+3 −2
@@ -6,7 +6,7 @@ | ||
| 6 | 6 | # Contact : contact@spboucher.ai |
| 7 | 7 | # Date : 2026-08-18 |
| 8 | 8 | # ============================================ |
| 9 | −"""Supervision centralisée des connecteurs des 9 apps de l'écosystème KA. | |
| 9 | +"""Supervision centralisée des connecteurs des 11 apps de l'écosystème KA. | |
| 10 | 10 | |
| 11 | 11 | Toutes les 2 h, le job lit le ``GET /api/stats`` de chaque app sœur (LAN, |
| 12 | 12 | lecture seule, timeout 5 s) et en déduit un état par source quand le détail |
@@ -100,6 +100,7 @@ EXPECTED_CADENCE_HOURS: dict[str, float] = { | ||
| 100 | 100 | "creaka": 24, |
| 101 | 101 | "restoka": 168, |
| 102 | 102 | "jobka": 1, |
| 103 | + "rentka": 1, | |
| 103 | 104 | } |
| 104 | 105 | |
| 105 | 106 | |
@@ -571,7 +572,7 @@ def _load_rows(session: Any, service: str) -> dict[str, ConnectorHealth]: | ||
| 571 | 572 | |
| 572 | 573 | |
| 573 | 574 | def run_connector_health_check(now: float | None = None) -> dict[str, Any]: |
| 574 | − """Point d'entrée du job (toutes les 2 h) : vérifie les 9 apps. | |
| 575 | + """Point d'entrée du job (toutes les 2 h) : vérifie les 11 apps. | |
| 575 | 576 | |
| 576 | 577 | L'échec d'une app (réseau, payload) ne fait jamais échouer le job. |
| 577 | 578 | """ |
modified
src/scheduler/daily_job.py
+3 −3
@@ -6,7 +6,7 @@ | ||
| 6 | 6 | # Contact : contact@spboucher.ai |
| 7 | 7 | # Date : 2026-08-16 |
| 8 | 8 | # ============================================ |
| 9 | −"""Job quotidien (02:00, heure du node m3u96b) orchestrant les 9 collecteurs KA. | |
| 9 | +"""Job quotidien (02:00, heure du node m3u96b) orchestrant les 11 collecteurs KA. | |
| 10 | 10 | |
| 11 | 11 | Les collecteurs s'exécutent en parallèle mais de façon indépendante : |
| 12 | 12 | l'échec d'un service ne bloque jamais les autres. Le backfill des 7 derniers |
@@ -34,7 +34,7 @@ from src.utils.logger import alert, get_logger | ||
| 34 | 34 | |
| 35 | 35 | |
| 36 | 36 | def run_all(date_key: datetime.date | None = None) -> list[dict[str, Any]]: |
| 37 | − """Exécute les 9 collecteurs en parallèle puis le backfill des 7 derniers jours. | |
| 37 | + """Exécute les 11 collecteurs en parallèle puis le backfill des 7 derniers jours. | |
| 38 | 38 | |
| 39 | 39 | Returns: |
| 40 | 40 | Résumés des runs du jour (un par service). |
@@ -132,7 +132,7 @@ def main() -> None: | ||
| 132 | 132 | misfire_grace_time=3600, |
| 133 | 133 | ) |
| 134 | 134 | # Supervision des connecteurs de l'écosystème (lecture seule des |
| 135 | − # /api/stats des 9 apps sœurs) : toutes les 2 h, premier passage 90 s | |
| 135 | + # /api/stats des 11 apps sœurs) : toutes les 2 h, premier passage 90 s | |
| 136 | 136 | # après le démarrage du scheduler. |
| 137 | 137 | scheduler.add_job( |
| 138 | 138 | run_connector_health_check, |
modified
src/utils/backup.py
+1 −1
@@ -121,7 +121,7 @@ def backup_service(service: str, date_key: datetime.date | None = None) -> Path: | ||
| 121 | 121 | |
| 122 | 122 | |
| 123 | 123 | def backup_all(date_key: datetime.date | None = None) -> list[Path]: |
| 124 | − """Sauvegarde les 9 services ; un échec n'interrompt pas les autres.""" | |
| 124 | + """Sauvegarde les 11 services ; un échec n'interrompt pas les autres.""" | |
| 125 | 125 | paths: list[Path] = [] |
| 126 | 126 | for service in SERVICES: |
| 127 | 127 | try: |
| 128 | 128 | |