Changelog
Fait par Gillesto — Format Keep a Changelog Versioning : Semantic Versioning 2.0.0
Unreleased
0.1.2 — 2026-10-09
Version corrective issue de l'audit AUDIT.md. Mettre à jour d'urgence : sur 0.1.0 et 0.1.1,
compute_routes() échouait systématiquement avec un paquet installé.
Corrigé (critique)
- Sécurité : PyO3 0.23.5 → 0.29.3.
cargo audit(désormais en CI) signalait RUSTSEC-2025-0020 (dépassement de tampon dansPyString::from_object) et RUSTSEC-2026-0177. Le code n'appelait pas la première, mais la dépendance était vulnérable. MSRV Rust : 1.88. - S1 — Le moteur Rust n'était jamais chargé dans le paquet installé :
from saferoute_core import …alors que maturin place le module danssaferoute.saferoute_core. Import relatif ; message d'erreur clair (NativeModuleNotFoundError) ; en CI (CI=true), l'absence du module natif fait ÉCHOUER la suite au lieu de sauter 3 tests en silence ; test de fumée du wheel dans un environnement vierge (scripts/smoke_wheel.py). - S2 — Un seul wheel publié. Le wheel est désormais
abi3(un par système couvre Python ≥ 3.10) ; le workflow construit les wheels Linux (x86_64, aarch64), Windows et macOS (Intel, ARM) avec maturin-action, les teste, puis publie TOUS les fichiers (TestPyPI d'abord, publication de confiance). - Recherche non terminante et épuisement mémoire (découvert pendant la mesure, S7). Sur Le Cap, une
requête de 1,7 km ne se terminait pas en 90 s puis abattait le processus (
memory allocation failed). L'élagage n'utilisait que les coûts déjà payés. La recherche utilise maintenant des bornes inférieures exactes (deux Dijkstra inverses), un élagage ε surg + h, des labels compacts à pointeurs parents, et un budget de labels (SearchBudgetExceededError,max_labels). Même requête : 84 ms. - Boucle infinie sur un cycle d'arcs de coût nul (longueur 0 et risque 0, présents dans OSM) : la dominance stricte ré-insérait indéfiniment le même label. Dominance faible ; test de régression (l'ancien comportement dépasse le délai).
- Bande passante du KDE fausse (découvert pendant la mesure).
bandwidth_mannonçait 400 m alors que le noyau réel faisait 946 à 2 094 m (mesuré) : la carte de risque était sur-lissée. Le noyau a maintenant exactement l'écart-type annoncé, en mètres sur un plan local. Changement de résultat : les scores de risque sont plus localisés qu'en 0.1.1. - KDE en O(crimes × arcs) (≈ 10¹¹ évaluations pour Londres, plusieurs heures : cause du chargement « sans fin » de l'audit). Binning + filtre gaussien + interpolation : 2,7 s pour Londres, écart maximal de 0,002 (échelle [0, 1]) avec le calcul exact sur les tests.
- S3 — CI jamais verte. Chemins
SafeRoute/…retirés (le dépôt est à la racine du clone) ;Cargo.lockcommité ;extension-moduledevient une feature (le cœur Rust se teste sans Python) ;cargo fmtetclippy -D warnings(qui échouaient) sont propres ; tests Python sur le paquet installé. - S4 — Versions non alignées. 0.1.2 partout (pyproject, Cargo.toml, Cargo.lock) ;
__version__lu depuis les métadonnées du paquet ;bump_version.shvérifie aussiCargo.lock. Le tagv0.1.1n'est pas déplacé (un tag publié ne se réécrit pas) : 0.1.2 le remplace. - Dockerfile inutilisable (copiait
core/etbindings/python/, inexistants) : réécrit. - Limiteur de débit contournable : l'en-tête
X-Forwarded-For, fourni par le client, était cru. Désormais ignoré saufSAFEROUTE_TRUST_PROXY=true(alors, dernier saut du proxy). - Le test
test_invalid_node_raisesattendait[]mais ne s'exécutait jamais ; il attend maintenant l'erreur explicite (RouteNotFoundError). - Familiarité : arcs parallèles — le plus court est retenu (et non le premier de l'itération) ;
la mise à jour est exacte par clé
(u, v, key). validate_crimes: optiondeduplicate(la déduplication par coordonnées supprime de vrais crimes).- Chargeur Police UK : perte silencieuse de données et interblocage (découverts en téléchargeant 15 mois).
Une réponse HTTP 429 était avalée (arrondissement-mois manquant, risque biaisé sans alerte) : nouvelles
tentatives avec
Retry-After, puisDataSourceError(ouallow_partial=True). Les tâches « > 10 000 crimes » gardaient leur créneau de concurrence en attendant leurs quadrants : cinq mois de Westminster saturaient les cinq créneaux et le téléchargement ne progressait plus.
Modifié
- S6 — Sérialisation JSON de tout le graphe remplacée par des colonnes numpy remises à Rust en octets
(
PyGraph.from_arrays) ;record_tripne reconstruit plus le graphe, il envoie les seuls arcs modifiés (update_familiarity).PyGraph.from_jsonreste disponible. Le graphe est validé à l'entrée (nœuds uniques, extrémités connues, poids finis ≥ 0). - S5 — Dépendances.
matplotlibetfoliumpassent dans l'extra[viz](ils n'étaient importés que par le notebook). Classificateurs : Python 3.13 ; Windows / Linux / macOS à la place d'« OS Independent ». compute_safe_routesrelâche le GIL ; erreurs typées (ValueError,RuntimeError,BudgetExceededError) ;compute_routeslèveRouteNotFoundError/SearchBudgetExceededError.- L'API REST répond 404 (aucun chemin), 422 (requête trop complexe), 503 (module natif absent) ; le calcul s'exécute hors de la boucle d'événements.
- Le README et la documentation décrivent ce qui est vrai : l'algorithme est de type NAMOA* inspiré d'A*pex (pas de paires apex) ; la promesse « < 2 s sur Londres » est remplacée par les mesures.
Ajouté
bandwidth_m="auto": bande passante choisie par log-vraisemblance de crimes mis de côté (temporelle si les crimes portent un mois, sinon aléatoire — optimiste). Opt-in : le défaut (Silverman borné à 800 m) est inchangé ; il sur-lisse sur Londres (voirdocs/benchmarks.md).SafeRouteEngine.load_graph(graphe, crimes): n'importe quelle ville / vos propres données (S8).- S7 — Mesures reproductibles :
benchmarks/run.py(chargement, KDE, construction Rust, requêtes par classe de distance, machine documentée) ; résultats dansbenchmarks/results/. - S7 — Garantie ε vérifiée : tests Rust contre l'énumération exhaustive des chemins (250 graphes aléatoires) et l'algorithme de Martins (40 grands graphes), pour ε ∈ {0 ; 0,05 ; 0,1 ; 0,3 ; 0,5}.
- Cache : un manque de RAM supprimait le graphe.
GraphCache.load_grapheffaçait le fichier sur TOUTE exception (une saturation mémoire a détruit un graphe de Londres de 527 Mo pendant les mesures). Les erreurs mémoire / E/S remontent sans toucher au fichier ; une vraie corruption met le fichier de côté (.corrupt).save_crimesest atomique (fichier temporaire puis renommage). bandwidth_mexplicite respecté tel quel (100 m devenait 200 m) ; seule la règle de Silverman est bornée.- S8 —
saferoute.evaluation: évaluation du score de risque (AUC, lift, Spearman) avec protocole temporel et hold-out aléatoire (étiqueté optimiste) ;fetch_london_crimesconserve le mois. docs/donnees.md: sources, licences, fraîcheur ; avertissement d'usage (README, API, doc).- 62 → 64+ tests Rust sans Python ; 106 → 168 tests Python (dont ceux du module natif, jamais exécutés avant).
Connu / non traité
- La familiarité n'influence pas le choix des routes (voir README, « Limites connues »).
safestminimise le risque seul (peut être 3× plus long queshortest).- Trajets longs (4–10 km) : cas défavorables jusqu'au budget de labels (voir
docs/benchmarks.md). - L'évaluation temporelle sur données réelles exige des crimes datés (le cache actuel n'en a pas).
0.1.0 — 2026-04-12
Ajouté
- Algorithme A*pex biobjectif (distance + risque) avec ε-approximation configurable
- Scoring KDE gaussien avec calibration automatique du bandwidth (règle de Silverman)
- Variable de familiarité :
C = w₁·D + w₂·R − w₃·F - 3 itinéraires Pareto-optimaux :
shortest,safest,balanced - Métadonnées enrichies :
estimated_time_min,comfort_score,node_count - Cache local
.graphml+.csv.gzavecplatformdirs(zéro chemin absolu) - API REST FastAPI : 7 endpoints (
/route,/load-city,/risk-map,/nearest-node,/familiarity/*,/health) - Données réelles : Londres (501k nœuds, 100k crimes Police UK) + Le Cap (127k nœuds, SAPS)
- Core Rust compilé via PyO3/maturin —
saferoute_core.so - 102 tests Python + 20 tests Rust d'intégration
- CI/CD GitHub Actions : 3 OS × 3 Python + publication TestPyPI/PyPI
- Hiérarchie d'exceptions :
SafeRouteError,GraphNotLoadedError,RouteNotFoundError,CacheCorruptionError,UnsupportedCityError - Documentation mkdocs avec mkdocstrings (docstrings Google)
- Marqueur PEP 561
py.typed