SPB Git forge

spb/immo-ka

Public

Immo-Ka — agrégateur des propriétés à vendre au Québec (73 connecteurs, ~40 000 annonces, React+FastAPI)

112commits 1branches 0releases
125.4 MBsize
maindefault branch
13 days agolast push
Python 47.5% HTML 27.9% TypeScript 15.5% CSS 7.2% JavaScript 2%
4.0 KB · 93 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Immo-Ka — Agrégateur de maisons à vendre (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# tests/test_mortgage_validate.py : garde-fous anti-données-aberrantes —5#   4,19 % devenu 419 % DOIT être rejeté, jamais publié.6# -----------------------------------------------------------------------------7import unittest89from immoka.mortgage.validate import validate_batch, validate_product101112def product(**over) -> dict:13    base = {14        "provider": "test", "institution": "Banque Test",15        "rate_type": "fixed", "term_months": 60, "kind": "posted",16        "rate": 4.19, "apr": None, "insured_status": "unknown",17        "purpose": "purchase", "product_name": "Fixe fermé 5 ans",18        "source_url": "https://example.com/taux",19    }20    base.update(over)21    return base222324class TestValidateProduct(unittest.TestCase):25    def test_valid(self):26        ok, issues = validate_product(product())27        self.assertTrue(ok, issues)2829    def test_419_aberration_rejected(self):30        ok, issues = validate_product(product(rate=419.0))31        self.assertFalse(ok)32        self.assertTrue(any("impossible" in i for i in issues))3334    def test_zero_and_negative_rejected(self):35        self.assertFalse(validate_product(product(rate=0.0))[0])36        self.assertFalse(validate_product(product(rate=-1.5))[0])37        self.assertFalse(validate_product(product(rate=0.1))[0])   # < 0,25 %38        self.assertFalse(validate_product(product(rate=25.0))[0])  # > 20 %3940    def test_missing_fields(self):41        for field in ("provider", "institution", "rate_type", "term_months",42                      "kind", "rate", "source_url"):43            ok, issues = validate_product(product(**{field: None}))44            self.assertFalse(ok)45            self.assertTrue(any(field in i for i in issues))4647    def test_term_bounds(self):48        self.assertFalse(validate_product(product(term_months=1))[0])49        self.assertFalse(validate_product(product(term_months=180))[0])50        self.assertFalse(validate_product(product(term_months="60"))[0])51        self.assertTrue(validate_product(product(term_months=3))[0])52        self.assertTrue(validate_product(product(term_months=120))[0])5354    def test_enums(self):55        self.assertFalse(validate_product(product(rate_type="promo"))[0])56        self.assertFalse(validate_product(product(kind="teaser"))[0])57        self.assertFalse(validate_product(product(insured_status="peut-être"))[0])58        self.assertFalse(validate_product(product(purpose="spéculation"))[0])5960    def test_apr_below_rate_rejected(self):61        # Un APR < taux contractuel est mathématiquement impossible.62        self.assertFalse(validate_product(product(rate=4.84, apr=4.20))[0])63        # Tolérance d'arrondi de 0,02 point.64        self.assertTrue(validate_product(product(rate=4.84, apr=4.83))[0])65        self.assertTrue(validate_product(product(rate=4.84, apr=4.861))[0])6667    def test_apr_bounds(self):68        self.assertFalse(validate_product(product(apr=419.0))[0])69        self.assertFalse(validate_product(product(apr="4.9"))[0])707172class TestValidateBatch(unittest.TestCase):73    def test_filters_and_reports(self):74        ok, problems = validate_batch([75            product(),76            product(rate=419.0, product_name="Aberrant"),77            product(term_months=60, rate=4.19),  # duplicata exact → ignoré78            product(term_months=36, product_name="Fixe fermé 3 ans"),79        ])80        self.assertEqual(len(ok), 2)81        self.assertEqual(len(problems), 1)82        self.assertIn("Aberrant", problems[0])8384    def test_same_product_different_rate_kept(self):85        # même clé produit mais taux différent : les deux passent (le store86        # tranchera) — seul le duplicata EXACT est éliminé87        ok, _ = validate_batch([product(rate=4.19), product(rate=4.24)])88        self.assertEqual(len(ok), 2)899091if __name__ == "__main__":92    unittest.main()93