SPB Git

Repository Public Research preview

calculs_s09.py 22.4 KB 262 lines python

1#!/usr/bin/env python32"""IMM1003 — Séance 9 — Annexe de calcul des CORRIGES (exercices formatifs, non notés).34Mêmes conventions que validation/verification_calculs.py :5  - arrondi au dollar (ROUND_HALF_UP) à chaque ligne ;6  - transactionnels en séquence (droits → financement → conditions de vente → dépenses → temps),7    temps = prorata simple 0,17 %/mois sur le prix courant ;8  - propriété additive sur le prix ajusté transactionnel ; localisation ±5 % de ce prix ;9  - net / brut = TOUS les ajustements (transactionnels compris), en % du prix de vente ;10  - valeur conclue arrondie au 5 000 $ (ROUND_HALF_UP).11Exécuter : python3 calculs_s09.py12"""13from decimal import Decimal, ROUND_HALF_UP14import datetime as dt1516# ----------------------------------------------------------------- utilitaires17def r0(x): return int(Decimal(str(x)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))18def r1(x): return float(Decimal(str(x)).quantize(Decimal("0.1"), rounding=ROUND_HALF_UP))19def r2(x): return float(Decimal(str(x)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))20def arrondi(x, pas=5000): return int(pas * Decimal(str(x / pas)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))21def f(x): return f"{x:,.0f}".replace(",", " ")           # 446 08122def fs(x): return ("+" if x >= 0 else "−") + f(abs(x))    # +6 148 / −8 17023def mois_ecoules(v, e): return (e.year - v.year) * 12 + (e.month - v.month)2425CAT = {"temps_pct_par_mois": 0.17, "aire_par_pi2": 150, "lot_par_pi2": 6, "lot_seuil_pi2": 1000,26       "garage_simple_vs_aucun": 30000, "garage_double_vs_simple": 15000,27       "soussol_fini_vs_non_fini": 20000, "soussol_fini_vs_partiel": 10000,28       "sdb_complete": 12000, "salle_eau": 6000, "chambre": 10000, "age_par_an": 2000,29       "etat": {"très bon": -8000, "bon": 0, "moyen": 12000}, "localisation_pct": 5.0, "piscine_creusee": 25000}30GARAGE = {"aucun": 0, "simple": 1, "double": 2}31SOUSSOL = {"non fini": 0, "partiel": 1, "fini": 2}32DATE_EVAL = dt.date(2026, 11, 1)3334def v_garage(n):   return {0: 0, 1: CAT["garage_simple_vs_aucun"], 2: CAT["garage_simple_vs_aucun"] + CAT["garage_double_vs_simple"]}[GARAGE[n]]35def v_soussol(n):  return {0: 0, 1: CAT["soussol_fini_vs_non_fini"] - CAT["soussol_fini_vs_partiel"], 2: CAT["soussol_fini_vs_non_fini"]}[SOUSSOL[n]]3637def grille(sujet, comp, taux_temps=None, date_eval=DATE_EVAL, cat=None):38    cat = cat or CAT39    taux = cat["temps_pct_par_mois"] if taux_temps is None else taux_temps40    rows, cour = [], comp["prix"]41    d = comp.get("droits", 0); cour += d; rows.append(("1 Droits de propriété", d, cour))42    fin = -comp.get("financement_avantage", 0); cour += fin; rows.append(("2 Conditions de financement", fin, cour))43    cv = comp.get("conditions_vente_pct", 0.0); m = r0(cour * cv / 100); cour += m; rows.append((f"3 Conditions de vente ({cv:+.1f} %)" if cv else "3 Conditions de vente", m, cour))44    dep = comp.get("depenses_apres_achat", 0); cour += dep; rows.append(("4 Dépenses après achat", dep, cour))45    mois = mois_ecoules(comp["date"], date_eval)46    t = r0(cour * taux / 100 * mois); cour += t; rows.append((f"5 Temps ({mois} mois × {taux:.2f} %)", t, cour))47    pat = cour48    prop = []49    loc = comp.get("localisation", "similaire")50    lp = {"similaire": 0.0, "inférieure": cat["localisation_pct"], "supérieure": -cat["localisation_pct"]}[loc]51    prop.append((f"6 Localisation ({loc}{f', {lp:+.0f} %' if lp else ''})", r0(pat * lp / 100)))52    da = comp["aire"] - sujet["aire"]; prop.append((f"7a Aire ({da:+d} pi² × 150 $)", -da * cat["aire_par_pi2"]))53    dl = comp["lot"] - sujet["lot"]; la = -dl * cat["lot_par_pi2"] if abs(dl) >= cat["lot_seuil_pi2"] else 054    prop.append((f"7b Terrain ({dl:+d} pi²{'' if abs(dl) >= cat['lot_seuil_pi2'] else ', < 1 000'})", la))55    dg = comp["annee"] - sujet["annee"]; prop.append((f"7c Âge ({dg:+d} an × 2 000 $)", -dg * cat["age_par_an"]))56    prop.append((f"7d État ({comp['etat']} vs {sujet['etat']})", cat["etat"][comp["etat"]] - cat["etat"][sujet["etat"]]))57    dc = comp["chambres"] - sujet["chambres"]; prop.append((f"7e Chambres ({dc:+d})", -dc * cat["chambre"]))58    sdb = (sujet["sdb"][0] - comp["sdb"][0]) * cat["sdb_complete"] + (sujet["sdb"][1] - comp["sdb"][1]) * cat["salle_eau"]59    prop.append((f"7f Salles de bains ({comp['sdb'][0]}+{comp['sdb'][1]} s.e. vs {sujet['sdb'][0]}+{sujet['sdb'][1]})", sdb))60    prop.append((f"7g Sous-sol ({comp['soussol']} vs {sujet['soussol']})", v_soussol(sujet["soussol"]) - v_soussol(comp["soussol"])))61    prop.append((f"7h Garage ({comp['garage']} vs {sujet['garage']})", v_garage(sujet["garage"]) - v_garage(comp["garage"])))62    prop.append(("8 Non immobilier", -comp.get("non_immobilier", 0)))63    tp = sum(v for _, v in prop); ind = pat + tp64    adj = [v for _, v, _ in rows] + [v for _, v in prop]65    net, brut = sum(adj), sum(abs(v) for v in adj)66    return {"nom": comp["nom"], "prix": comp["prix"], "mois": mois, "trans": rows, "pat": pat, "prop": prop,67            "total_prop": tp, "indication": ind, "net": net, "net_pct": r1(net / comp["prix"] * 100),68            "brut": brut, "brut_pct": r1(brut / comp["prix"] * 100)}6970def affiche(g, detail=True):71    print(f"--- {g['nom']} : prix {f(g['prix'])} $, {g['mois']} mois")72    if detail:73        for lib, v, c in g["trans"]:74            if v or lib.startswith("5"): print(f"   {lib:<38} {fs(v):>10}   → {f(c)}")75        print(f"   {'Prix ajusté transactionnel':<38} {'':>10}   = {f(g['pat'])}")76        for lib, v in g["prop"]:77            if v: print(f"   {lib:<38} {fs(v):>10}")78    print(f"   Total propriété {fs(g['total_prop'])} → INDICATION {f(g['indication'])} $ | net {fs(g['net'])} ({g['net_pct']} %) | brut {f(g['brut'])} ({g['brut_pct']} %)")7980def reconcilier(grilles, poids):81    assert abs(sum(poids.values()) - 100) < 1e-982    vp = sum(g["indication"] * poids[g["nom"]] / 100 for g in grilles)83    vals = sorted(g["indication"] for g in grilles); n = len(vals)84    med = vals[n // 2] if n % 2 else (vals[n // 2 - 1] + vals[n // 2]) / 285    return {"ponderee": r0(vp), "conclue": arrondi(vp), "moyenne": r0(sum(vals) / n), "moyenne_arr": arrondi(sum(vals) / n),86            "mediane": r0(med), "min": vals[0], "max": vals[-1], "etendue_pct": r1((vals[-1] - vals[0]) / vp * 100)}8788def equivalence_comptant(solde, i_c, i_m, n):89    I = solde * i_c / 100; a = (1 - (1 + i_m / 100) ** -n) / (i_m / 100)90    va_int = I * a; va_cap = solde / (1 + i_m / 100) ** n; va = va_int + va_cap91    return {"interets": r0(I), "facteur": round(a, 4), "va_int": r0(va_int), "va_cap": r0(va_cap), "va": r0(va), "avantage": r0(solde - va),92            "formule_directe": r0(solde * (i_m - i_c) / 100 * a)}9394def ecarts_mediane(ind: dict, seuil=10.0):95    vals = sorted(ind.values()); n = len(vals)96    med = vals[n // 2] if n % 2 else (vals[n // 2 - 1] + vals[n // 2]) / 297    return med, {k: (r1((v - med) / med * 100), "RÉEXAMINER" if abs((v - med) / med * 100) > seuil else "ok") for k, v in ind.items()}9899# ----------------------------------------------------------------- données de l'atelier (bloc 4)100SUJET = {"nom": "Sujet", "annee": 2005, "aire": 1500, "lot": 5500, "chambres": 3, "sdb": (2, 0), "soussol": "fini", "garage": "double", "etat": "bon"}101FIN_C1 = equivalence_comptant(100000, 2.0, 5.0, 3)102COMPS = [103    {"nom": "C1", "prix": 410000, "date": dt.date(2026, 2, 1), "annee": 2003, "aire": 1580, "lot": 5000, "chambres": 3, "sdb": (2, 0), "soussol": "non fini", "garage": "simple", "etat": "bon", "financement_avantage": FIN_C1["avantage"]},104    {"nom": "C2", "prix": 445000, "date": dt.date(2026, 7, 1), "annee": 2006, "aire": 1480, "lot": 5200, "chambres": 3, "sdb": (1, 0), "soussol": "fini", "garage": "double", "etat": "très bon"},105    {"nom": "C3", "prix": 425000, "date": dt.date(2026, 4, 1), "annee": 2005, "aire": 1520, "lot": 6000, "chambres": 3, "sdb": (2, 0), "soussol": "fini", "garage": "double", "etat": "bon", "depenses_apres_achat": 15000},106    {"nom": "C4", "prix": 460000, "date": dt.date(2026, 8, 1), "localisation": "supérieure", "annee": 2008, "aire": 1650, "lot": 7000, "chambres": 4, "sdb": (2, 0), "soussol": "fini", "garage": "double", "etat": "très bon"},107    {"nom": "C5", "prix": 438000, "date": dt.date(2026, 5, 1), "annee": 2004, "aire": 1500, "lot": 5500, "chambres": 3, "sdb": (2, 0), "soussol": "partiel", "garage": "simple", "etat": "moyen", "non_immobilier": 6000},108]109POIDS = {"C2": 30, "C3": 30, "C1": 20, "C5": 15, "C4": 5}110POIDS_SANS_C4 = {"C2": 32, "C3": 32, "C1": 21, "C5": 15, "C4": 0}111112def section(t): print("\n" + "=" * 78 + f"\n{t}\n" + "=" * 78)113114# ================================================================= 0. Atelier du deck (référence)115section("0. ATELIER DU DECK (bloc 4) — référence RESULTATS_CALCULS.md")116print("Équivalence au comptant C1 :", FIN_C1)117G = [grille(SUJET, c) for c in COMPS]118for g in G: affiche(g)119R = reconcilier(G, POIDS)120print(f"\nPondérée {f(R['ponderee'])} → conclue {f(R['conclue'])} $ ; moyenne {f(R['moyenne'])} (→ {f(R['moyenne_arr'])}) ; médiane {f(R['mediane'])} ; étendue {f(R['min'])}–{f(R['max'])} ({R['etendue_pct']} %)")121R2 = reconcilier(G, POIDS_SANS_C4); print(f"Sans C4 (32/32/21/15/0) : {f(R2['ponderee'])} → {f(R2['conclue'])} $")122med, ec = ecarts_mediane({g["nom"]: g["indication"] for g in G}); print("Écarts à la médiane :", ec)123attendu = {"C1": 434978, "C2": 453026, "C3": 442236, "C4": 383729, "C5": 475468}124assert all(g["indication"] == attendu[g["nom"]] for g in G) and R["ponderee"] == 446081 and R["conclue"] == 445000, "écart avec RESULTATS_CALCULS"125print("Concordance avec RESULTATS_CALCULS.md : OK (C1–C5, 446 081 → 445 000)")126127# sensibilité du deck128section("0b. SENSIBILITÉ DU DECK (base 446 081 $)")129for tx in (-0.25, 0.0, 0.17, 0.34):130    print(f"  temps {tx:+.2f} %/mois → {f(reconcilier([grille(SUJET, c, taux_temps=tx) for c in COMPS], POIDS)['ponderee'])} $")131for gd in (10000, 15000, 20000):132    cat = dict(CAT); cat["garage_double_vs_simple"] = gd133    old = CAT["garage_double_vs_simple"]; CAT["garage_double_vs_simple"] = gd134    print(f"  garage double vs simple {f(gd)} $ → {f(reconcilier([grille(SUJET, c) for c in COMPS], POIDS)['ponderee'])} $")135    CAT["garage_double_vs_simple"] = old136for lp in (3.0, 5.0, 8.0):137    old = CAT["localisation_pct"]; CAT["localisation_pct"] = lp138    print(f"  localisation {lp:.0f} % → {f(reconcilier([grille(SUJET, c) for c in COMPS], POIDS)['ponderee'])} $")139    CAT["localisation_pct"] = old140print(f"  question p. 81 (garage 30 000 $) : 446 081 + 15 000 × 0,35 = {f(446081 + 15000 * 0.35)} → {f(arrondi(446081 + 15000 * 0.35))} $")141print(f"  question p. 84 (temps avant financement C1) : 410 000 × 1,0153 − 8 170 = {f(r0(410000 * 1.0153 - 8170))} $ (vs 407 978 ; écart {f(r0(410000 * 1.0153 - 8170) - 407978)} $)")142print(f"  question p. 40 (C4 avec abri + spa 12 000 $) : {f(383729 - 12000)} $ ; écart à la médiane {r1((383729 - 12000 - 442236) / 442236 * 100)} %")143print(f"  p. 37 (C1 sans transactionnels) : net {r1(27000 / 410000 * 100)} %, brut {r1(51000 / 410000 * 100)} %")144print(f"  p. 46 : moyenne des 4 sans C4 = {f(r0((434978 + 453026 + 442236 + 475468) / 4))} ; effet de C4 = {fs(437887 - 451427)} $")145print(f"  p. 35 : temps à −0,25 %/mois sur C1 = {f(r0(401830 * 0.0025 * 9))} $ retirés")146147# ================================================================= A. (conceptuel : pas de calcul) — B. calculs148section("B1. Équivalence au comptant — E1 du deck + variante")149e1 = equivalence_comptant(80000, 3.0, 5.5, 2); print("E1 (80 000 $, 3 % vs 5,5 %, 2 ans) :", e1, "→ prix au comptant", f(395000 - e1["avantage"]), "$ ; en % du prix :", r1(e1["avantage"] / 395000 * 100))150e1b = equivalence_comptant(80000, 3.0, 5.5, 3); print("B1 b (même solde, 3 ans) :", e1b, "→ prix au comptant", f(395000 - e1b["avantage"]))151e1c = equivalence_comptant(80000, 5.5, 5.5, 2); print("B1 c (taux du contrat = taux du marché) : avantage", e1c["avantage"])152print("Vérification C1 par la formule directe S(i_m − i_c)a :", FIN_C1["formule_directe"], "(annuité 2,7232)")153154section("B2. Séquence vs addition — 436 000 $, financement −7 500 $, 6 mois à 0,17 %/mois")155p, fin, m = 436000, 7500, 6156seq_base = p - fin; t_seq = r0(seq_base * 0.0017 * m); seq = seq_base + t_seq157t_add = r0(p * 0.0017 * m); add = p - fin + t_add158inv = r0(p * (1 + 0.0017 * m)) - fin159print(f"  séquence : {f(p)} − {f(fin)} = {f(seq_base)} ; temps {f(seq_base)} × 0,0017 × {m} = {fs(t_seq)} → {f(seq)} $")160print(f"  temps d'abord : {f(p)} × 1,0102 = {f(r0(p * 1.0102))} − {f(fin)} = {f(inv)} $ ; simple addition : {f(add)} $ ; écart = {f(add - seq)} $")161print(f"  base du % : 3 % de 436 000 = {f(436000 * 0.03)} $ vs 3 % de 428 500 = {f(428500 * 0.03)} $")162163section("B3. Revente — 398 000 $ (1er mai 2024) → 418 000 $ (1er nov. 2026), 30 mois")164var = 418000 / 398000 - 1; print(f"  variation {r2(var * 100)} % ; mensuel simple {var * 100 / 30:.3f} %/mois ; annuel composé {( (1 + var) ** (12 / 30) - 1) * 100:.2f} %/an")165print(f"  application : vente de 5 mois à 430 000 $ → 430 000 × {var / 30:.5f} × 5 = {fs(r0(430000 * var / 30 * 5))} $ (vs {fs(r0(430000 * 0.0017 * 5))} $ au taux du cours)")166167section("B4. Net et brut — comparable 462 000 $, 6 mois")168adj = {"temps (6 mois)": r0(462000 * 0.0017 * 6), "garage double vs simple (comp. supérieur)": -15000, "salle de bains (comp. inférieur)": 12000, "aire +20 pi²": -3000, "inclusions": -6000}169net, brut = sum(adj.values()), sum(abs(v) for v in adj.values())170for k, v in adj.items(): print(f"  {k:<45} {fs(v):>9}")171print(f"  net {fs(net)} ({r1(net / 462000 * 100)} %) ; brut {f(brut)} ({r1(brut / 462000 * 100)} %) ; compensation cachée par le net : {f(brut - abs(net))} $")172print(f"  sans le temps : net {fs(net - adj['temps (6 mois)'])} ({r1((net - adj['temps (6 mois)']) / 462000 * 100)} %), brut {f(brut - adj['temps (6 mois)'])} ({r1((brut - adj['temps (6 mois)']) / 462000 * 100)} %)")173174section("B5. Capitalisation d'un loyer de garage")175for loyer, taux in ((175, 7.0), (175, 6.5), (150, 7.0), (200, 7.0)):176    print(f"  {loyer} $/mois à {taux} % → {f(r0(loyer * 12 / (taux / 100)))} $")177178section("B6. Régression hédonique — lecture des coefficients")179b0, b1, sig = 247673, 151.91, 7972180AIRES = [1050, 1100, 1120, 1150, 1180, 1200, 1220, 1250, 1280, 1300, 1350, 1400]181PRIX = [398500, 421500, 414000, 433500, 419500, 432000, 442500, 426500, 446000, 442000, 459500, 454500]182n = len(AIRES); mx, my = sum(AIRES) / n, sum(PRIX) / n183sxx = sum((a - mx) ** 2 for a in AIRES); sxy = sum((a - mx) * (p - my) for a, p in zip(AIRES, PRIX))184bb1 = sxy / sxx; bb0 = my - bb1 * mx185res = [p - (bb0 + bb1 * a) for a, p in zip(AIRES, PRIX)]; sse = sum(r ** 2 for r in res); sst = sum((p - my) ** 2 for p in PRIX)186s2 = sse / (n - 2); se_b1 = (s2 / sxx) ** 0.5187print(f"  ré-estimation : β0 = {f(r0(bb0))}, β1 = {bb1:.2f} $/pi², R² = {1 - sse / sst:.2f}, t = {bb1 / se_b1:.2f}, σ = {f(r0(s2 ** 0.5))} $  (deck : 247 673 ; 151,91 ; 0,81 ; 6,61 ; 7 972)")188print(f"  (a) écart de 80 pi² : 80 × 151,91 = {f(r0(80 * b1))} $ vs catalogue 80 × 150 = {f(80 * 150)} $ (différence {f(r0(80 * b1) - 12000)} $)")189print(f"  (b) résidu de la vente 8 (1 250 pi², 426 500 $) : 426 500 − ({f(b0)} + 151,91 × 1 250 = {f(r0(b0 + b1 * 1250))}) = {fs(r0(426500 - (b0 + b1 * 1250)))} $ = {r1((426500 - (b0 + b1 * 1250)) / sig)} σ")190print(f"  (c) intervalle ±2σ pour une prédiction : ±{f(2 * sig)} $ ; pour 1 200 pi² : {f(r0(b0 + b1 * 1200))} ± {f(2 * sig)} $")191print(f"  (d) IC 95 % approx. de β1 (t crit. 2,23 à 10 ddl) : {b1 - 2.23 * 22.99:.1f} à {b1 + 2.23 * 22.99:.1f} $/pi²")192print(f"  (e) prédiction hors échantillon à 1 500 pi² : {f(r0(b0 + b1 * 1500))} $ — extrapolation (max observé 1 400 pi²)")193194section("B7. Pondération — E3 du deck")195V = {"V1": 441000, "V2": 452000, "V3": 428000}196def pond(w, vals): return sum(w[k] * vals[k] for k in w) / 100197a = pond({"V1": 50, "V2": 30, "V3": 20}, V); print(f"  (a) 50/30/20 : {f(r0(a))} → {f(arrondi(a))} $")198Vb = dict(V); Vb["V3"] = 418000; b = pond({"V1": 50, "V2": 30, "V3": 20}, Vb); print(f"  (b) garage 20 000 $ (V3 = 418 000) : {f(r0(b))} → {f(arrondi(b))} $ (robuste : V3 pèse 20 %)")199c = pond({"V1": 40, "V2": 40, "V3": 20}, V); print(f"  (c) 40/40/20 : {f(r0(c))} → {f(arrondi(c))} $ (l'arrondi saute : frontière 442 500)")200print(f"  (d) moyenne simple : {f(r0(sum(V.values()) / 3))} → {f(arrondi(sum(V.values()) / 3))} $ ; médiane 441 000 $")201print(f"  (e) poids qui ramènent exactement à 442 500 : avec V3 à 20 %, il faut w1 × 441 000 + w2 × 452 000 = 356 900 → w1 = {(0.8 * 452000 - 356900) / (452000 - 441000):.3f}")202203section("B8. Règle de l'aberrant (écart à la médiane > 10 %)")204ind = {"I1": 512000, "I2": 498000, "I3": 505000, "I4": 448000, "I5": 520000, "I6": 501000}205med, ec = ecarts_mediane(ind); print(f"  médiane {f(med)} $ ;", ec)206print(f"  moyenne avec I4 : {f(r0(sum(ind.values()) / 6))} ; sans I4 : {f(r0((sum(ind.values()) - 448000) / 5))} ; la médiane sans I4 : {f(sorted([v for k, v in ind.items() if k != 'I4'])[2])} $")207208# ================================================================= C. démonstrations guidées209section("C1. Grille à deux comparables (E2 du deck)")210S2 = {"nom": "Sujet", "annee": 2010, "aire": 1400, "lot": 5000, "chambres": 3, "sdb": (1, 1), "soussol": "fini", "garage": "simple", "etat": "bon"}211CA = {"nom": "CA", "prix": 452000, "date": dt.date(2026, 6, 1), "annee": 2010, "aire": 1450, "lot": 5000, "chambres": 3, "sdb": (1, 1), "soussol": "fini", "garage": "double", "etat": "bon", "non_immobilier": 3000}212CB = {"nom": "CB", "prix": 418000, "date": dt.date(2026, 3, 1), "annee": 2012, "aire": 1350, "lot": 5000, "chambres": 3, "sdb": (1, 1), "soussol": "fini", "garage": "aucun", "etat": "bon"}213gA, gB = grille(S2, CA), grille(S2, CB); affiche(gA); affiche(gB)214assert gA["indication"] == 430342 and gB["indication"] == 457185215print(f"  écart entre les deux indications : {f(gB['indication'] - gA['indication'])} $ ({r1((gB['indication'] - gA['indication']) / gA['indication'] * 100)} % de CA) ; pondérée 60/40 : {f(r0(0.6 * gA['indication'] + 0.4 * gB['indication']))} → {f(arrondi(0.6 * gA['indication'] + 0.4 * gB['indication']))} $ ; 50/50 : {f(r0((gA['indication'] + gB['indication']) / 2))} → {f(arrondi((gA['indication'] + gB['indication']) / 2))} $")216217section("C2. Formule fermée de l'avantage de financement : S (i_m − i_c) a(i_m, n)")218for lab, (S, ic, im, n) in {"C1": (100000, 2.0, 5.0, 3), "E1": (80000, 3.0, 5.5, 2)}.items():219    e = equivalence_comptant(S, ic, im, n)220    print(f"  {lab} : i_m a + v^n = {im / 100 * e['facteur'] + (1 + im / 100) ** -n:.4f} (≡ 1) ; S(i_m−i_c)a = {f(e['formule_directe'])} $ ; par VA : {f(e['avantage'])} $")221222section("C3. Décomposition de la sensibilité (pourquoi la pondérée bouge de si peu)")223print(f"  garage +5 000 $ → ΔV = 5 000 × (w_C1 + w_C5) = 5 000 × 0,35 = {f(5000 * 0.35)} $ → {f(446081 + 1750)} ✓ (deck 447 831)")224bases = {"C1": (401830, 9, 1.0), "C2": (445000, 4, 1.0), "C3": (440000, 7, 1.0), "C4": (460000, 3, 0.95), "C5": (438000, 6, 1.0)}225dV = sum(POIDS[k] / 100 * b * m * k2 for k, (b, m, k2) in bases.items()) * 0.0017226print(f"  temps 0 → 0,17 %/mois : ΔV = 0,0017 × Σ w_i × base_i × mois_i (× 0,95 pour C4 à cause de la localisation) = {f(r0(dV))} $ ✓ (deck 446 081 − 441 591 = 4 490)")227print(f"  temps 0,17 → −0,25 : ΔV = {f(r0(dV / 0.17 * 0.42))} $ → {f(446081 - r0(dV / 0.17 * 0.42))} $ (deck 434 988 ; l'écart de quelques $ vient des arrondis ligne par ligne)")228print(f"  localisation 5 → 8 % sur C4 : ΔV = −0,03 × 462 346 × 0,05 = {fs(r0(-0.03 * 462346 * 0.05))} $ → {f(446081 + r0(-0.03 * 462346 * 0.05))} ✓ (deck 445 387)")229230# ================================================================= D. étude de cas — variante231section("D. VARIANTE DE L'ATELIER — sujet avec GARAGE SIMPLE et SOUS-SOL PARTIEL (tout le reste inchangé)")232SUJ_D = dict(SUJET); SUJ_D["garage"] = "simple"; SUJ_D["soussol"] = "partiel"233GD = [grille(SUJ_D, c) for c in COMPS]234for g in GD: affiche(g)235RD = reconcilier(GD, POIDS)236print(f"\n  Poids du deck (30/30/20/15/5) : pondérée {f(RD['ponderee'])} → {f(RD['conclue'])} $ ; moyenne {f(RD['moyenne'])} ; médiane {f(RD['mediane'])} ; étendue {RD['etendue_pct']} %")237med, ec = ecarts_mediane({g["nom"]: g["indication"] for g in GD}); print("  écarts à la médiane :", ec)238print(f"  différence avec l'atelier : {f(RD['ponderee'] - 446081)} $ = −(15 000 + 10 000) exactement, car les poids somment à 1 et chaque grille bouge de −25 000 $")239POIDS_D = {"C2": 25, "C3": 30, "C1": 20, "C5": 20, "C4": 5}240RD2 = reconcilier(GD, POIDS_D); print(f"  Poids révisés (C2 25, C3 30, C1 20, C5 20, C4 5) : {f(RD2['ponderee'])} → {f(RD2['conclue'])} $")241POIDS_D3 = {"C2": 25, "C3": 25, "C1": 25, "C5": 25, "C4": 0}242RD3 = reconcilier(GD, POIDS_D3); print(f"  Sans C4, 25/25/25/25 : {f(RD3['ponderee'])} → {f(RD3['conclue'])} $")243print("  sensibilité D (temps) :", {tx: f(reconcilier([grille(SUJ_D, c, taux_temps=tx) for c in COMPS], POIDS)["ponderee"]) for tx in (-0.25, 0.0, 0.17, 0.34)})244print(f"  repère : {f(RD['conclue'])} / 1 500 = {f(r0(RD['conclue'] / 1500))} $/pi² ; rôle 2025 385 000 $")245246# ================================================================= F3. grille piégée247section("F3. Grille piégée — comparable CX (430 000 $, 1er mars 2026, toiture 10 000 $)")248S3 = {"nom": "Sujet", "annee": 2005, "aire": 1500, "lot": 5500, "chambres": 3, "sdb": (2, 0), "soussol": "fini", "garage": "simple", "etat": "bon"}249CX = {"nom": "CX", "prix": 430000, "date": dt.date(2026, 3, 1), "annee": 2005, "aire": 1560, "lot": 5500, "chambres": 4, "sdb": (2, 0), "soussol": "fini", "garage": "double", "etat": "très bon", "depenses_apres_achat": 10000}250gX = grille(S3, CX); affiche(gX)251# version fautive de l'énoncé252t_faux = r0(430000 * 0.0017 * 8)253ind_faux = 430000 + t_faux + 10000 - 9000 - 10000 + 15000 + 8000254print(f"  grille fautive de l'énoncé : temps sur 430 000 = {f(t_faux)} ; garage +15 000 ; état +8 000 ; toiture après le temps → indication {f(ind_faux)} $ ; net/brut sans transactionnels : {fs(-9000 - 10000 + 15000 + 8000)} / {f(42000)}")255print(f"  grille corrigée : indication {f(gX['indication'])} $ ; écart {f(ind_faux - gX['indication'])} $ ; net {fs(gX['net'])} ({gX['net_pct']} %), brut {f(gX['brut'])} ({gX['brut_pct']} %)")256257# ================================================================= F1 — conclusion écrite variante D (chiffres)258section("F1. Chiffres pour la conclusion écrite de la variante D")259print(f"  indications : {', '.join(f'{g['nom']} {f(g['indication'])}' for g in GD)} ; bruts : {', '.join(f'{g['nom']} {g['brut_pct']} %' for g in GD)}")260print(f"  pondérée {f(RD['ponderee'])} → {f(RD['conclue'])} $ ; sans C4 (25×4) {f(RD3['ponderee'])} → {f(RD3['conclue'])} $")261print("\nFIN — toutes les assertions de concordance ont passé.")262
Files main
Menu

Theme