SPB Git
17.5 KB · 276 lines python
1"""Tests du chapitre 22 — métré et quantités du sujet du cas 1.2Tolérances : 0,01 $ sur les montants recalculés depuis le devis ; 0,05 m² / m³ / m sur les quantités géométriques citées3(arrondies à deux décimales dans le texte) ; 5e-4 sur les proportions ; identités à 1e-9.4Les montants du chapitre 21 (D, I, C_N) doivent être strictement inchangés."""5import sys, pathlib, math6import numpy as np, pytest7sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases"))8import ch21_cout_neuf as c219import ch22_metre as cas10from traite import cout as ct1112Q = 0.05131415@pytest.fixture(scope="module")16def data():17    devis, sujet = c21.charger()18    return {"devis": devis, "sujet": sujet, "e": cas.empreinte(), "S": cas.superficies(), "q": cas.quantites_geometriques(),19            "V": cas.verification(devis), "eff": None, "cn": c21.cout_neuf_sujet(devis)}202122class TestCoherenceCh21:23    def test_montants_inchanges(self, data):24        cn = data["cn"]25        assert cn["direct"] == pytest.approx(298826.17, abs=0.01)26        assert cn["indirects"] == pytest.approx(62598.68, abs=0.01)27        assert cn["total"] == pytest.approx(397567.33, abs=0.01)28        assert len(data["devis"]) == 382930    def test_corps_identiques(self, data):31        g = c21.regrouper(data["devis"])32        assert list(g.index) == c21.ORDRE_CORPS and int(g["postes"].sum()) == 383334    def test_bordereau_quantites_fois_prix(self, data):35        d = data["devis"]36        assert np.allclose(d["quantite"] * d["prix_unitaire"], d["montant"], atol=0.011)37        detail = ct.cout_par_composantes(dict(zip(d["poste"], d["quantite"])), dict(zip(d["poste"], d["prix_unitaire"])))38        assert detail["total"] == pytest.approx(361424.85, abs=0.2)394041class TestGeometrie:42    def test_empreinte_et_perimetre(self, data):43        e = data["e"]44        assert e["aire_au_sol"] == pytest.approx(95.04, abs=1e-9)45        assert e["perimetre"] == pytest.approx(39.84, abs=1e-9)46        assert e["garage"] == pytest.approx(24.95, abs=0.005) and e["aire_sous_sol"] == pytest.approx(70.09, abs=0.005)47        # invariance du périmètre d'une forme en L découpée dans un rectangle48        L, W, gx, gy = cas.PLAN["L"], cas.PLAN["W"], cas.PLAN["gx"], cas.PLAN["gy"]49        perim_L = (L - gx) + gy + gx + (W - gy) + L + W   # parcours de la forme en L50        assert perim_L == pytest.approx(2 * (L + W), abs=1e-9)5152    def test_superficies_conventions(self, data):53        S = data["S"]54        assert S["aire_etages_mefq"] == pytest.approx(190.08, abs=1e-9)55        assert S["perimetre_etages_mefq"] == pytest.approx(79.68, abs=1e-9)56        assert S["habitable"] == pytest.approx(165.13, abs=0.005)57        assert round(S["habitable"]) == data["sujet"]["superficie_habitable_m2"]58        assert S["nette_hors_sol"] == pytest.approx(135.37, abs=Q)59        assert S["nette_rez"] == pytest.approx(58.56, abs=Q) and S["nette_etage"] == pytest.approx(76.81, abs=Q)60        assert S["sous_sol_net"] == pytest.approx(58.65, abs=Q) and S["sous_sol_fini_net"] == pytest.approx(49.31, abs=Q)61        assert S["aire_etages_mefq"] * c21.PI2_PAR_M2 == pytest.approx(2046, abs=0.6)62        assert S["habitable"] * c21.PI2_PAR_M2 == pytest.approx(1777, abs=0.6)6364    def test_couts_unitaires_conventions(self, data):65        S, cn = data["S"], data["cn"]66        assert cn["total"] / S["aire_etages_mefq"] == pytest.approx(2092, abs=0.6)67        assert cn["total"] / S["habitable"] == pytest.approx(2408, abs=0.6)68        assert cn["total"] / S["nette_hors_sol"] == pytest.approx(2937, abs=0.6)69        assert S["habitable"] / S["aire_etages_mefq"] == pytest.approx(0.869, abs=5e-4)70        assert S["nette_hors_sol"] / S["habitable"] == pytest.approx(0.820, abs=5e-4)71        assert S["nette_hors_sol"] / S["aire_etages_mefq"] == pytest.approx(0.712, abs=5e-4)72        # écart de 40 % entre les coûts unitaires extrêmes73        assert (cn["total"] / S["nette_hors_sol"]) / (cn["total"] / S["aire_etages_mefq"]) - 1 == pytest.approx(0.404, abs=5e-3)7475    def test_pieces_couvrent_les_niveaux(self, data):76        pcs = data["S"]["pieces"]77        assert pcs.loc[pcs["niveau"] == "rez", "brute"].sum() == pytest.approx(95.04, abs=1e-6)78        assert pcs.loc[pcs["niveau"] == "etage", "brute"].sum() == pytest.approx(95.04, abs=1e-6)79        assert pcs.loc[pcs["niveau"] == "soussol", "brute"].sum() == pytest.approx(70.09, abs=0.01)80        assert (pcs["nette"] > 0).all()8182    def test_cloisons(self):83        assert cas.cloisons("rez") == pytest.approx(21.52, abs=Q)84        assert cas.cloisons("etage") == pytest.approx(47.94, abs=Q)85        assert cas.cloisons("soussol") == pytest.approx(20.80, abs=Q)86        assert cas.separation_garage() == pytest.approx(10.26, abs=1e-9)8788    def test_formules_elementaires(self):89        # aire décalée d'un rectangle = formule exacte90        assert cas.aire_decalee(12 * 7.92, 2 * (12 + 7.92), 0.9) == pytest.approx((12 + 1.8) * (7.92 + 1.8), abs=1e-9)91        # prismoïde d'un parallélépipède = A·h ; d'une pyramide tronquée : cohérent avec la formule exacte92        assert cas.volume_prismoidal(10, 10, 10, 3) == pytest.approx(30)93        a, b, h = 4.0, 6.0, 3.0   # tronc de pyramide à bases carrées94        exact = h / 3 * (a * a + b * b + a * b)95        assert cas.volume_prismoidal(a * a, ((a + b) / 2) ** 2, b * b, h) == pytest.approx(exact, rel=1e-12)96        assert math.sqrt(1 + 0.5 ** 2) == pytest.approx(1.1180, abs=5e-5)979899class TestQuantites:100    def test_mesures_citees(self, data):101        q = data["q"]102        attendu = {"Excavation et remblai": 408.94, "Fondations béton (semelles, murs)": 36.79, "Dalle de sous-sol": 95.04,103                   "Drain français et imperméabilisation": 42.24, "Charpente murs et planchers": 324.65, "Fermes de toit": 111.62,104                   "Couverture bardeaux d'asphalte": 124.79, "Revêtement extérieur (brique 40 % / fibrociment 60 %)": 238.79,105                   "Isolation et pare-vapeur": 419.82, "Gypse et tirage de joints": 733.00, "Peinture intérieure": 733.00,106                   "Planchers bois franc": 103.56, "Planchers céramique": 31.81, "Planchers flottants (sous-sol)": 70.09,107                   "Gouttières et solins": 48.80, "Balcon et perron": 14.10, "Entrée asphaltée": 55.00}108        for k, v in attendu.items():109            assert q[k]["q"] == pytest.approx(v, abs=Q), k110111    def test_details_cites(self, data):112        q = data["q"]113        d = q["Charpente murs et planchers"]["detail"]114        assert d["planchers"] == pytest.approx(165.13, abs=Q) and d["murs_brut"] == pytest.approx(194.42, abs=Q)115        assert d["ouvertures"] == pytest.approx(34.90, abs=Q) and d["murs_net"] == pytest.approx(159.52, abs=Q)116        f = q["Fondations béton (semelles, murs)"]["detail"]117        assert f["L_axe"] == pytest.approx(38.84, abs=1e-9) and f["V_murs"] == pytest.approx(23.69, abs=Q) and f["V_semelles"] == pytest.approx(6.99, abs=Q)118        x = q["Excavation et remblai"]["detail"]119        assert x["A_bas"] == pytest.approx(109.19, abs=Q) and x["A_mi"] == pytest.approx(141.45, abs=Q) and x["A_haut"] == pytest.approx(177.10, abs=Q)120        assert x["V_sous_sol"] == pytest.approx(369.25, abs=Q) and x["V_garage"] == pytest.approx(15.39, abs=Q) and x["V_racc"] == pytest.approx(24.30, abs=Q)121        g = q["Gypse et tirage de joints"]["detail"]122        assert g["plafonds"] == pytest.approx(209.63, abs=Q) and g["murs"] == pytest.approx(582.53, abs=Q)123        assert g["sep_garage"] == pytest.approx(25.03, abs=Q) and g["ouvertures"] == pytest.approx(84.19, abs=Q)124        r = q["Revêtement extérieur (brique 40 % / fibrociment 60 %)"]["detail"]125        assert r["murs"] == pytest.approx(223.10, abs=Q) and r["pignons"] == pytest.approx(15.68, abs=Q)126        i = q["Isolation et pare-vapeur"]["detail"]127        assert i["fondation"] == pytest.approx(91.37, abs=Q) and i["rive"] == pytest.approx(23.90, abs=Q) and i["sep_murs"] == pytest.approx(25.03, abs=Q)128129    def test_denombrements(self, data):130        q = data["q"]131        assert q["Fenêtres (PVC double vitrage)"]["q"] == 16 and q["Fenêtres (PVC double vitrage)"]["detail"] == {"rez": 6, "etage": 8, "soussol": 2}132        assert q["Portes intérieures et moulures"]["q"] == 14 and q["Plomberie brute et finition"]["q"] == 14 == len(cas.APPAREILS)133        assert q["Portes extérieures"]["q"] == 3 and q["Porte de garage"]["q"] == 1 and q["Escaliers"]["q"] == 2134        assert q["Supervision de chantier (salaires)"]["q"] == 26135136137class TestVerification:138    def test_verdicts(self, data):139        V = data["V"]140        nb = V["verdict"].value_counts().to_dict()141        assert nb == {"conforme": 20, "ratio": 14, "à justifier": 2, "à préciser": 2}142        assert set(V.loc[V["verdict"] == "à justifier", "poste"]) == {"Gypse et tirage de joints", "Peinture intérieure"}143        assert set(V.loc[V["verdict"] == "à préciser", "poste"]) == {"Planchers bois franc", "Planchers céramique"}144        assert len(V) == 38 and int((V["classe"] == "mesuré").sum()) == 17 and int((V["classe"] == "dénombré").sum()) == 7145146    def test_ecarts_cites(self, data):147        V = data["V"].set_index("poste")148        assert V.loc["Gypse et tirage de joints", "ecart"] == pytest.approx(0.182, abs=5e-4)149        assert V.loc["Planchers bois franc", "ecart"] == pytest.approx(0.090, abs=5e-4)150        assert V.loc["Planchers céramique", "ecart"] == pytest.approx(-0.091, abs=5e-4)151        assert V.loc["Excavation et remblai", "ecart"] == pytest.approx(-0.026, abs=5e-4)152        assert V.loc["Fondations béton (semelles, murs)", "ecart"] == pytest.approx(-0.032, abs=5e-4)153        assert V.loc["Charpente murs et planchers", "ecart"] == pytest.approx(-0.016, abs=5e-4)154        assert V.loc["Gypse et tirage de joints", "delta_montant"] == pytest.approx(3104.20, abs=0.01)155        assert V.loc["Peinture intérieure", "delta_montant"] == pytest.approx(1143.59, abs=0.01)156        # planchers finis hors sol : total conforme même si la répartition diffère157        tot_calc = V.loc["Planchers bois franc", "q_calc"] + V.loc["Planchers céramique", "q_calc"]158        tot_devis = V.loc["Planchers bois franc", "q_devis"] + V.loc["Planchers céramique", "q_devis"]159        assert tot_calc == pytest.approx(135.37, abs=Q) and tot_devis == 130 and abs(tot_calc / tot_devis - 1) < 0.05160161    def test_effet_metre_controle(self, data):162        eff = cas.effet_metre_controle(data["devis"], data["V"])163        assert eff["delta_D"] == pytest.approx(3410.22, abs=0.01)164        assert eff["D2"] == pytest.approx(302236.39, abs=0.01)165        assert eff["CN2"] == pytest.approx(401318.58, abs=0.01)166        assert eff["delta_CN"] == pytest.approx(3751.24, abs=0.01)167        assert eff["part_D"] == pytest.approx(0.0114, abs=5e-5) and eff["part_CN"] == pytest.approx(0.0094, abs=5e-5)168        assert eff["mult"] == pytest.approx(1.3304, abs=5e-5)169        # identité : ΔC_N = ΔD × (1 + τE) lorsque les indirects sont fixés en dollars170        assert eff["delta_CN"] == pytest.approx(eff["delta_D"] * 1.10, rel=1e-9)171172173class TestSensibilitePropagation:174    def test_sensibilite(self, data):175        s = cas.sensibilite_quantites(data["devis"]).set_index("corps")176        assert s.loc["Enveloppe", "dCN_+0.10"] == pytest.approx(7739, abs=1) and s.loc["Enveloppe", "pct_+0.10"] == pytest.approx(0.0195, abs=5e-5)177        assert s.loc["Tous les postes directs", "dCN_+0.10"] == pytest.approx(32871, abs=1) and s.loc["Tous les postes directs", "pct_+0.10"] == pytest.approx(0.0827, abs=5e-5)178        assert s.loc["Aménagements extérieurs", "dCN_-0.10"] == pytest.approx(-1827, abs=1)179        # linéarité : ΔC_N = e × montant × (1 + τE)180        assert s.loc["Enveloppe", "dCN_+0.10"] == pytest.approx(0.10 * s.loc["Enveloppe", "montant"] * 1.10, rel=1e-9)181        assert s["part_D"].iloc[:-1].sum() == pytest.approx(1.0, abs=1e-9)182183    def test_propagation(self, data):184        p = cas.propagation(data["devis"])185        assert p["herfindahl"] == pytest.approx(0.0539, abs=5e-5) and p["equiv_uniforme"] == pytest.approx(18.6, abs=0.05)186        assert p["sigma_D_rel"] == pytest.approx(0.0116, abs=5e-5) and p["sigma_D"] == pytest.approx(3467.78, abs=0.01)187        assert p["poste_max"] == "Charpente murs et planchers" and p["max_part"] == pytest.approx(0.132, abs=5e-4)188        # bornes : 1/n ≤ H ≤ 1 et σ indépendants ≤ σ corrélés189        assert 1 / p["n"] <= p["herfindahl"] <= 1 and p["sigma_D_rel"] <= p["sigma_si_parfaitement_correle"]190191    def test_pertes(self, data):192        per = cas.pertes(data["devis"]).set_index("poste")193        assert per.loc["Gypse et tirage de joints", "q_commander"] == pytest.approx(694.4, abs=Q)194        assert per.loc["Couverture bardeaux d'asphalte", "q_commander"] == pytest.approx(137.5, abs=Q)195        assert per["cout_pertes_si_prix_pose"].sum() == pytest.approx(11283.78, abs=0.01)196197    def test_repartition_types(self, data):198        rep = cas.repartition_par_type(data["devis"])199        tot = rep.sum()200        assert tot["mesuré"] == pytest.approx(174351.56, abs=0.01) and tot["dénombré"] == pytest.approx(89850.00, abs=0.01) and tot["forfait"] == pytest.approx(97223.29, abs=0.01)201        assert tot.sum() == pytest.approx(361424.85, abs=0.01)202        assert rep.loc["Enveloppe", "mesuré"] == pytest.approx(47580.84, abs=0.01) and rep.loc["Coûts indirects", "dénombré"] == pytest.approx(35708.66, abs=0.01)203204205class TestExercices:206    """Chiffres des solutions (solutions/ch22.tex)."""207208    def test_ex1_conventions(self):209        L, W, gx, gy = 10.40, 9.15, 3.66, 6.10210        aire_sol = L * W; etages = 2 * aire_sol; hab = etages - gx * gy211        assert aire_sol == pytest.approx(95.16, abs=0.005) and etages == pytest.approx(190.32, abs=0.005)212        assert gx * gy == pytest.approx(22.33, abs=0.005) and hab == pytest.approx(167.99, abs=0.005)213        assert 2 * (L + W) == pytest.approx(39.10, abs=1e-9)214        net = 2 * (L - 0.6) * (W - 0.6) - (gx - 0.3 - 0.075) * (gy - 0.3 - 0.075)215        assert net == pytest.approx(148.77, abs=0.01)216        assert etages * 10.7639 == pytest.approx(2048.6, abs=0.1) and hab * 10.7639 == pytest.approx(1808.2, abs=0.1)217218    def test_ex2_murs_nets(self):219        P = 2 * (10.40 + 9.15); brut = P * 2.44 * 2220        ouv = 12 * 1.68 + 2 * 1.85 + 2.74 * 2.13221        assert brut == pytest.approx(190.81, abs=0.01) and ouv == pytest.approx(29.70, abs=0.01)222        assert brut - ouv == pytest.approx(161.11, abs=0.01) and ouv / brut == pytest.approx(0.156, abs=5e-4)223224    def test_ex3_toiture(self):225        A = (10.40 + 0.8) * (9.15 + 0.8); f = math.sqrt(1 + (8 / 12) ** 2)226        assert A == pytest.approx(111.44, abs=0.01) and f == pytest.approx(1.2019, abs=5e-5)227        assert A * f == pytest.approx(133.94, abs=0.01) and A * f * 1.10 == pytest.approx(147.33, abs=0.01)228        f6 = math.sqrt(1.25)229        assert A * f / (A * f6) - 1 == pytest.approx(0.0750, abs=5e-5)230231    def test_ex4_excavation(self):232        A, P, h, d = 95.16, 39.10, 2.50, 0.90233        rect = cas.aire_decalee(A, P, d) * h234        Ab = cas.aire_decalee(A, P, d); Am = cas.aire_decalee(A, P, d + 0.5 * h / 2); At = cas.aire_decalee(A, P, d + 0.5 * h)235        V = cas.volume_prismoidal(Ab, Am, At, h)236        Am1 = cas.aire_decalee(A, P, d + 1.0 * h / 2); At1 = cas.aire_decalee(A, P, d + 1.0 * h)237        V1 = cas.volume_prismoidal(Ab, Am1, At1, h)238        assert Ab == pytest.approx(133.59, abs=0.01) and rect == pytest.approx(333.98, abs=0.01)239        assert Am == pytest.approx(164.09, abs=0.01) and At == pytest.approx(197.72, abs=0.01)240        assert V == pytest.approx(411.53, abs=0.01) and V1 == pytest.approx(499.50, abs=0.01)241        assert V / rect - 1 == pytest.approx(0.2322, abs=5e-4) and V1 / rect - 1 == pytest.approx(0.4956, abs=5e-4)242243    def test_ex5_prix_unitaire(self):244        m, w, hpu, s, e = 11.50, 0.12, 0.35, 62.0, 0.80245        p = m * (1 + w) + hpu * s + e246        assert m * (1 + w) == pytest.approx(12.88, abs=0.005) and hpu * s == pytest.approx(21.70, abs=0.005) and p == pytest.approx(35.38, abs=0.005)247        assert hpu * s / p == pytest.approx(0.613, abs=5e-4)248        assert p * 1.15 == pytest.approx(40.69, abs=0.005)249        assert (m * (1 + w) + hpu * s * 1.05 + e) / p - 1 == pytest.approx(0.0307, abs=5e-5)250        assert (m * (1 + w) + hpu * 0.9 * s + e) / p - 1 == pytest.approx(-0.0613, abs=5e-5)251252    def test_ex6_propagation(self, data):253        p = cas.propagation(data["devis"], 0.05); p10 = cas.propagation(data["devis"], 0.10)254        assert p10["sigma_D_rel"] == pytest.approx(2 * p["sigma_D_rel"], rel=1e-12)255        # 33 postes de parts égales256        assert 0.05 * math.sqrt(1 / 33) == pytest.approx(0.0087, abs=5e-5)257        # 2 postes concentrés : parts 0,5 / 0,5 → H = 0,5258        assert 0.05 * math.sqrt(0.5) == pytest.approx(0.0354, abs=5e-5)259        assert p["sigma_D"] * 1.3304 == pytest.approx(4613, abs=2)260261    def test_ex7_plain_pied(self):262        L, W = 14.00, 9.00; P = 2 * (L + W); A = L * W263        assert A == 126 and P == 46264        murs = P * 2.44 - (11 * 1.68 + 2 * 1.85); assert murs == pytest.approx(90.06, abs=0.01)265        plafond = (L - 0.6) * (W - 0.6); assert plafond == pytest.approx(112.56, abs=0.01)266        cloisons = 38.0 * 2.44 * 2; assert cloisons == pytest.approx(185.44, abs=0.01)267        gypse = plafond + murs + cloisons - 2 * 9 * 1.85268        assert gypse == pytest.approx(354.76, abs=0.01)269        ecart = gypse / 300 - 1; assert ecart == pytest.approx(0.1825, abs=5e-4)270        delta = (gypse - 300) * 27.47 + (gypse - 300) * 10.12271        assert delta == pytest.approx(2058.3, abs=0.5)272        toit = (L + 0.8) * (W + 0.8) * math.sqrt(1.25); assert toit == pytest.approx(162.16, abs=0.01)273        assert toit / 150 - 1 == pytest.approx(0.0811, abs=5e-4)274        total = delta + (toit - 150) * 61.44275        assert total == pytest.approx(2805.52, abs=0.01) and total * 1.3304 == pytest.approx(3732.46, abs=0.5)276
Files · 3c869615bf2283d3927a1ebb038f3436dbc36f91
Menu

Theme