SPB Git forge

spb/rent-ka

Public
8commits 1branches 0releases
7.4 MBsize
maindefault branch
19 days agolast push
Python 68.8% TypeScript 18.6% CSS 8.7% JavaScript 3.3% HTML 0.6%
4.6 KB · 126 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Rent-Ka — Rental listings aggregator (Canada, outside Québec)3# Author: Simon-Pierre Boucher — contact@spboucher.ai4# test_kascores.py : barème des KA Scores — décroissance, bornes, labels,5#   non-aberration spatiale (deux immeubles voisins → scores proches).6# -----------------------------------------------------------------------------7import json8import math910import pytest1112from rentka import kascores13from rentka.kascores import (14    Grid, TileIndex, _decroissance, label, score_calme, score_global,15    score_services, score_transit, score_walk,16)171819def test_decroissance():20    assert _decroissance(100, 400, 1600) == 1.021    assert _decroissance(400, 400, 1600) == 1.022    assert _decroissance(1600, 400, 1600) == 0.023    assert _decroissance(2500, 400, 1600) == 0.024    assert 0.49 < _decroissance(1000, 400, 1600) < 0.51252627def test_labels():28    assert label(92) == "Exceptionnel"29    assert label(71) == "Excellent"30    assert label(60) == "Très bon"31    assert label(45) == "Moyen"32    assert label(10) == "Faible"33    assert label(None) is None343536def _env_urbain(lat=45.52, lng=-73.58):37    """Micro-quartier synthétique : tout à ~200 m."""38    d = 0.002  # ~200 m39    pois = {cat: [[lat + d, lng]] for cat, *_ in kascores.WALK_BAREME}40    pois["bus"] = [[lat, lng + d]]41    pois["metro"] = [[lat - d, lng]]42    pois["hopital"] = [[lat + 2 * d, lng]]43    return {"lines": {}, "points": {}, "pois": pois}444546def test_walk_urbain_vs_desert():47    idx = TileIndex(_env_urbain())48    s, det = score_walk(idx, 45.52, -73.58)49    assert s is not None and s > 8550    vide = TileIndex({"lines": {}, "points": {}, "pois": {}})51    s2, det2 = score_walk(vide, 45.52, -73.58)52    assert s2 is None and "raison" in det2   # honnêteté : pas de 0 trompeur535455def test_transit_non_desservi_et_blend():56    vide = TileIndex({"lines": {}, "points": {}, "pois": {}})57    s, det = score_transit(vide, 45.52, -73.58, None)58    assert s is None and "raison" in det     # « Non desservi », pas 059    idx = TileIndex(_env_urbain())60    proche, _ = score_transit(idx, 45.52, -73.58, 90.0)61    loin, _ = score_transit(idx, 45.52, -73.58, 10.0)62    assert proche is not None and loin is not None and proche > loin636465def test_calme_autoroute_penalise():66    lat, lng = 45.52, -73.5867    calme_env = {"lines": {}, "points": {}, "pois": {}}68    bruyant_env = {69        "lines": {"autoroute": [[[lat + 0.0005, lng - 0.01],70                                 [lat + 0.0005, lng + 0.01]]]},71        "points": {}, "pois": {},72    }73    s_calme, _ = score_calme(TileIndex(calme_env), lat, lng)74    s_bruyant, d = score_calme(TileIndex(bruyant_env), lat, lng)75    assert s_calme - s_bruyant > 2576    assert any("autoroute" in x["source"] for x in d["sources_bruit"])777879def test_services_rendement_decroissant():80    lat, lng = 45.52, -73.5881    def env(n):82        return {"lines": {}, "points": {}, "pois": {83            "epicerie": [[lat + 0.001 * i, lng] for i in range(1, n + 1)]}}84    s3, _ = score_services(TileIndex(env(3)), lat, lng)85    s6, _ = score_services(TileIndex(env(6)), lat, lng)86    s12, _ = score_services(TileIndex(env(12)), lat, lng)87    assert s3 < s6 < s1288    assert (s6 - s3) > (s12 - s6)   # passer de 3→6 vaut plus que 6→12... x2899091def test_global_renormalise_sans_transit():92    s = score_global({"walk": 80, "transit": None, "bike": 60,93                      "calme": 70, "services": 50})94    assert s is not None and 60 < s < 7595    assert score_global({"walk": 80, "transit": None, "bike": None,96                         "calme": None, "services": None}) is None979899def test_coherence_spatiale_sur_le_parc():100    """Deux immeubles à < 200 m → scores globaux proches (médiane < 8 pts)."""101    from rentka import db102    con = db.connect()103    rows = con.execute(104        "SELECT lat, lng, global AS g FROM kascores"105        " WHERE global IS NOT NULL ORDER BY coord_key LIMIT 3000").fetchall()106    con.close()107    if len(rows) < 200:108        pytest.skip("pas assez de scores calculés")109    g = Grid()110    vals = {}111    for r in rows:112        g.add(r["lat"], r["lng"])113        vals[(round(r["lat"], 5), round(r["lng"], 5))] = r["g"]114    diffs = []115    for r in rows[:800]:116        for d, la, ln in g.near(r["lat"], r["lng"], 200):117            if d < 1:118                continue119            v = vals.get((round(la, 5), round(ln, 5)))120            if v is not None:121                diffs.append(abs(v - r["g"]))122    assert diffs, "aucune paire voisine trouvée"123    diffs.sort()124    mediane = diffs[len(diffs) // 2]125    assert mediane < 8, f"médiane des écarts voisins : {mediane}"126