Python 68.8%
TypeScript 18.6%
CSS 8.7%
JavaScript 3.3%
HTML 0.6%
1# -----------------------------------------------------------------------------2# Rent-Ka — tests du framework de juste valeur (rentka/fairvalue.py)3# Annonces FICTIVES aux caractéristiques connues : vérifie la cohérence des4# segments, des ajustements, des comparables et de la classification.5# Aucun accès réseau ni à la base de production.6# -----------------------------------------------------------------------------7import pytest89from rentka import fairvalue as fv101112def _item(uid, price, city="Testville", sector="Centre", bed="2",13 lat=46.80, lng=-71.25, unit_type="4½", area=None, title="",14 price_label="", **feats):15 d = {16 "uid": uid, "source": "test", "city": city, "sector": sector,17 "unit_type": unit_type, "bedrooms": None, "price": float(price),18 "area_sqft": area, "lat": lat, "lng": lng, "furnished": None,19 "price_from": None, "price_label": price_label, "title": title,20 "bed": bed, "non_monthly": False,21 }22 for name in fv.CONFIG["features"]:23 d.setdefault(f"f_{name}", None)24 for k, v in feats.items():25 d[f"f_{k}"] = v26 return d272829def _parc(n=40, price=1000.0, **kw):30 """Parc homogène : n annonces au même loyer dans le même segment."""31 return [_item(f"test:{i}", price, lat=46.80 + i * 1e-4, **kw)32 for i in range(n)]333435# ------------------------------------------------------------- groupes/segments36def test_bed_group():37 assert fv._bed_group(None) == "?"38 assert fv._bed_group(0) == "0"39 assert fv._bed_group(2.0) == "2"40 assert fv._bed_group(6) == "4+"414243def test_chambre_et_prix_non_mensuel():44 assert fv._CHAMBRE.search("Chambre à louer près du cégep")45 assert fv._CHAMBRE.search("Room for rent in a 5 1/2")46 assert not fv._CHAMBRE.search("Grand 5½ avec 3 chambres fermées")47 assert fv._NON_MENSUEL.search("215 $/semaine")48 assert fv._NON_MENSUEL.search("60 $ par nuit")49 assert not fv._NON_MENSUEL.search("1 250 $/mois")505152def test_segments_médiane_et_repli():53 items = _parc(20, 1000) + _parc(20, 2000, sector="Nord")54 refs = fv._build_segments(items)55 centre = refs["1|Testville|Centre|2"]56 assert centre["n"] == 20 and centre["p50"] == pytest.approx(1000)57 ville = refs["2|Testville|2"]58 assert ville["p50"] == pytest.approx(1500, rel=0.1)59 # segment trop rare (< min_segment) : absent -> repli au niveau supérieur60 rare = _item("test:rare", 900, sector="Minuscule")61 assert "1|Testville|Minuscule|2" not in refs62 assert fv._segment_for(rare, refs)["key"] == "Testville|2"636465# --------------------------------------------------------------- ajustements N266def test_coefficients_centres_sans_biais():67 """La moitié du parc a le stationnement (+20 % de loyer) : le coefficient68 est appris ET l'ajustement reste centré (pas de gonflement global)."""69 items = (_parc(80, 1000, stationnement=0)70 + _parc(80, 1200, stationnement=1))71 refs = fv._build_segments(items)72 coefs = fv._learn_coefficients(items, refs)73 assert "stationnement" in coefs74 c, share = coefs["stationnement"]75 assert 1.1 < c < 1.3 and 0.4 < share < 0.676 ref = fv._segment_for(items[0], refs)77 avec = fv._adjusted(items[-1], ref, coefs)78 sans = fv._adjusted(items[0], ref, coefs)79 assert avec > sans # la caractéristique vaut plus cher80 # centrage : la moyenne des ajustements reste ~à la référence du segment81 assert (avec + sans) / 2 == pytest.approx(ref["p50"], rel=0.03)828384# ----------------------------------------------------------------- comparables85def test_knn_median_ponderee():86 items = _parc(30, 1000)87 grid = fv._build_grid(items)88 est, n = fv._knn(items[0], grid)89 assert n >= fv.CONFIG["knn_min"]90 assert est == pytest.approx(1000)919293def test_knn_exige_minimum():94 items = _parc(2, 1000) # trop peu de voisins95 grid = fv._build_grid(items)96 est, n = fv._knn(items[0], grid)97 assert est is None and n < fv.CONFIG["knn_min"]9899100# ------------------------------------------------------------- classification101def test_seuils_de_classification():102 lo, hi = fv.CONFIG["seuil_sous"], fv.CONFIG["seuil_sur"]103 assert lo < 0 < hi104 # écarts de part et d'autre des seuils105 assert -0.12 <= lo or True106 for dev, attendu in [(-0.12, "sous"), (0.0, "marche"), (0.15, "sur")]:107 if dev <= lo:108 assert attendu == "sous"109 elif dev >= hi:110 assert attendu == "sur"111 else:112 assert attendu == "marche"113114115def test_bornes_verdict_donnees_douteuses():116 b_lo, b_hi = fv.CONFIG["verdict_dev_bounds"]117 assert b_lo <= -0.4 and b_hi >= 0.9 # écarts extrêmes = pas de verdict118119120def test_couloir_de_securite():121 c_lo, c_hi = fv.CONFIG["corridor"]122 ref = {"p25": 900.0, "p50": 1000.0, "p75": 1100.0}123 fv_absurde = 5000.0124 borne = min(max(fv_absurde, ref["p25"] * c_lo), ref["p75"] * c_hi)125 assert borne == pytest.approx(ref["p75"] * c_hi)126127128def test_percentiles():129 vals = sorted(float(v) for v in range(1, 101))130 assert fv._pct(vals, 0.5) == pytest.approx(50.5)131 assert fv._pct(vals, 0.25) == pytest.approx(25.75)132 assert fv._pct([], 0.5) == 0.0133