SPB Git

Repository Public Research preview

corriges_calculs.py 15.6 KB 196 lines python

1#!/usr/bin/env python32# -*- coding: utf-8 -*-3"""Annexe de CORRIGES.md — IMM1033, séance 8 (coûts indirects et profit entrepreneurial).4Reproduit, avec les conventions de validation/verification_calculs.py (arrondi au dollar à chaque ligne,5chaîne du cours = TP2), tous les calculs des exercices A–F et des quatre exercices projetés P1–P4.6Exécuter : python3 corriges_calculs.py"""7from decimal import Decimal, ROUND_HALF_UP89def r0(x):  # arrondi au dollar (demi vers le haut), comme le moteur10    return int(Decimal(str(x)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))11def r2(x):12    return float(Decimal(str(x)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))13def f(x):  # format québécois : espace fine, sans décimales14    return f"{x:,.0f}".replace(",", " ")15def f2(x):16    return f"{x:,.2f}".replace(",", " ").replace(".", ",")1718P = {"cg": 8.0, "fg": 7.0, "peg": 8.0, "cont": 5.0, "permis_1000": 4.70, "permis_min": 335, "gcr": 2656.68, "gcr_cat2": 3359.68,19     "pref": 4.70, "prime": 2.05, "hon": 4.0, "assur": 0.8, "profit": 11.0, "tps": 5.0, "tvq": 9.975, "redevance_aylmer": 3986.39}2021def cascade(nus):22    cg = r0(nus * P["cg"] / 100); c1 = nus + cg23    fg = r0(c1 * P["fg"] / 100); c2 = c1 + fg24    peg = r0(c2 * P["peg"] / 100); dcours = c2 + peg25    cont = r0(nus * P["cont"] / 100); dc = dcours + cont26    return dict(nus=nus, cg=cg, apres_cg=c1, fg=fg, apres_fg=c2, peg=peg, directs_cours=dcours, cont=cont, dc=dc,27                majoration_pct=r2((dcours / nus - 1) * 100))2829def permis(dc, logements):30    prop = r0(dc / 1000 * P["permis_1000"]); mini = P["permis_min"] * logements31    return dict(prop=prop, mini=mini, permis=max(prop, mini))3233def interets_simpl(C, i_pct, T_mois):34    return r0(C * i_pct / 100 * T_mois / 12 * 0.5)3536def chaine(nus, logements, T, juridique, gcr_applicable, i_pct=None, redevances=0, gcr_unit=None, directs_cours_impose=None, dc_impose=None):37    if directs_cours_impose is None:38        c = cascade(nus)39    else:  # F1 : soumission clés en main (directs du cours et D+C imposés)40        c = dict(nus=None, directs_cours=directs_cours_impose, dc=dc_impose, cont=dc_impose - directs_cours_impose)41    dc = c["dc"]; dcours = c["directs_cours"]42    pm = permis(dc, logements)43    gcr = r0((gcr_unit or P["gcr"]) * logements) if gcr_applicable else 044    i = i_pct if i_pct is not None else P["pref"] + P["prime"]45    inter = interets_simpl(dc, i, T)46    hon = r0(dcours * P["hon"] / 100)47    assur = r0(dc * P["assur"] / 100)48    indirects = pm["permis"] + gcr + inter + hon + assur + juridique + redevances49    assiette = dc + indirects50    profit = r0(assiette * P["profit"] / 100)51    total = assiette + profit52    return dict(cascade=c, permis=pm, gcr=gcr, taux=i, duree=T, interets=inter, honoraires=hon, assurance=assur, juridique=juridique,53                redevances=redevances, indirects=indirects, assiette=assiette, profit=profit, total=total)5455def par_periode(C, i_pct, T, profil="lineaire"):56    i_m = i_pct / 100 / 1257    cum = (lambda t: t / T) if profil == "lineaire" else (lambda t: 3 * (t / T) ** 2 - 2 * (t / T) ** 3)58    solde = 0.0; tot = 0.0; lignes = []59    for t in range(1, T + 1):60        deb = C * (cum(t) - cum(t - 1)); it = solde * i_m; solde += deb + it; tot += it61        lignes.append((t, r0(deb), r0(C * cum(t)), r0(it), r0(solde)))62    return r0(tot), lignes6364def taxes(prix, premier=False):65    tps = prix * P["tps"] / 100; tvq = prix * P["tvq"] / 10066    rt = min(tps * .36, 6300) if prix <= 350000 else (6300 * (450000 - prix) / 100000 if prix < 450000 else 0.0)67    rq = min(tvq * .5, 9975) if prix <= 200000 else (9975 * (300000 - prix) / 100000 if prix < 300000 else 0.0)68    pa = 0.069    if premier:70        pa = tps if prix <= 1000000 else (tps * (1500000 - prix) / 500000 if prix < 1500000 else 0.0)71    reff = max(rt, pa); net = tps + tvq - reff - rq72    return dict(prix=prix, tps=r2(tps), tvq=r2(tvq), brutes=r2(tps + tvq), remb_tps=r2(reff), remb_tvq=r2(rq), nettes=r2(net), pct=r2(net / prix * 100))7374def h(t): print("\n" + "=" * 78 + f"\n{t}\n" + "=" * 78)7576# ---------------------------------------------------------------- Cas de référence (concordance RESULTATS_CALCULS)77h("CAS DE RÉFÉRENCE — six logements, Hull, 14 mois, 6,75 %, GCR non visé (concordance RESULTATS_CALCULS.md)")78BASE = chaine(785000, 6, 14, 12000, False)79c = BASE["cascade"]80print(f"directs nus {f(c['nus'])} | CG {f(c['cg'])} → {f(c['apres_cg'])} | FG {f(c['fg'])} → {f(c['apres_fg'])} | profit EG {f(c['peg'])} → directs du cours {f(c['directs_cours'])}")81print(f"contingences {f(c['cont'])} → D+C {f(c['dc'])} ; majoration en cascade {c['majoration_pct']} %")82print(f"permis max({f(BASE['permis']['prop'])} ; {f(BASE['permis']['mini'])}) = {f(BASE['permis']['permis'])} | GCR {BASE['gcr']} | intérêts {f(BASE['interets'])} | honoraires {f(BASE['honoraires'])} | assurance {f(BASE['assurance'])} | juridique {f(BASE['juridique'])}")83print(f"indirects {f(BASE['indirects'])} | assiette {f(BASE['assiette'])} | profit {f(BASE['profit'])} | COÛT TOTAL {f(BASE['total'])}")84lin, _ = par_periode(1018968, 6.75, 14, "lineaire"); S, lignesS = par_periode(1018968, 6.75, 14, "S")85print(f"intérêts par période : linéaire {f(lin)} ; courbe en S {f(S)} ; simplifiée ½ {f(BASE['interets'])}")86attendu = dict(directs_cours=979718, dc=1018968, indirects=104252, total=1246774, i_demi=40122, lin=38107, S=38002)87ok = (c["directs_cours"], c["dc"], BASE["indirects"], BASE["total"], BASE["interets"], lin, S) == tuple(attendu.values())88print("CONCORDANCE avec RESULTATS_CALCULS.md :", "OK" if ok else "ÉCART !")8990# ---------------------------------------------------------------- Exercices projetés P1–P4 (deck p. 94–101)91h("P1 (E1 du deck) — intercalaires à 7,75 % et 18 mois")92I1 = interets_simpl(1018968, 7.75, 18); d = I1 - BASE["interets"]93V1 = chaine(785000, 6, 18, 12000, False, i_pct=7.75)94print(f"I = 1 018 968 × 0,0775 × 18/12 × ½ = {f(I1)} $ ; écart vs 40 122 = +{f(d)} $ ; effet sur le coût total ≈ {f(d)} × 1,11 = {f(r0(d*1.11))} $")95print(f"chaîne complète : indirects {f(V1['indirects'])} ; assiette {f(V1['assiette'])} ; profit {f(V1['profit'])} ; coût total {f(V1['total'])} $ (écart {f(V1['total']-BASE['total'])} $, {r2((V1['total']/BASE['total']-1)*100)} %)")96print(f"facteur multiplicatif des deux chocs : (7,75/6,75) × (18/14) = {r2(7.75/6.75*18/14)}")9798h("P2 (E2 du deck) — permis et GCR d'un triplex, D+C = 520 000 $")99pm = permis(520000, 3); g = 3 * P["gcr"]100print(f"permis = max({f(pm['prop'])} ; {f(pm['mini'])}) = {f(pm['permis'])} $ ; GCR = 3 × 2 656,68 = {f2(g)} $ ; total réglementaire {f2(pm['permis']+g)} $ = {r2((pm['permis']+g)/520000*100)} % de D+C")101print(f"comparaison six logements : {f(BASE['permis']['permis'])} / {f(BASE['cascade']['dc'])} = {r2(BASE['permis']['permis']/BASE['cascade']['dc']*100)} %")102103h("P3 (E3 du deck) — profit sur coût ↔ marge sur prix")104print(f"(a) p = 12 % → m = 0,12/1,12 = {r2(0.12/1.12*100)} %   (b) m = 15 % → p = 0,15/0,85 = {r2(0.15/0.85*100)} %")105print(f"rappel du cas : 11 % → {r2(0.11/1.11*100)} % ; marge 11 % → p = {r2(0.11/0.89*100)} % ; profit requis {f(r0(1123220*0.11/0.89))} $")106107h("P4 (E4 du deck) — classement de dix postes : voir corrigé (aucun calcul)")108109# ---------------------------------------------------------------- Famille B110h("B1 — cascade sur 250 000 $ de directs nus (duplex)")111b1 = cascade(250000)112print(f"CG {f(b1['cg'])} → {f(b1['apres_cg'])} ; FG {f(b1['fg'])} → {f(b1['apres_fg'])} ; profit EG {f(b1['peg'])} → directs du cours {f(b1['directs_cours'])} ; contingences {f(b1['cont'])} → D+C {f(b1['dc'])}")113somme = r0(250000 * 1.23); print(f"majoration cascade {b1['majoration_pct']} % ; somme +23 % = {f(somme)} ; écart cascade − somme = {f(b1['directs_cours']-somme)} $ ; termes croisés exacts : 0,08×0,07 + 0,08×0,08 + 0,07×0,08 + 0,08×0,07×0,08 = {1.08*1.07*1.08-1.23:.6f}")114115h("B2 — intercalaires, méthode simplifiée : 650 000 $, 7,25 %, 12 mois (puis 9 mois)")116I12 = interets_simpl(650000, 7.25, 12); I9 = interets_simpl(650000, 7.25, 9)117print(f"I(12 mois) = 650 000 × 0,0725 × 12/12 × ½ = {f(I12)} $ ({r2(I12/650000*100)} % de C) ; I(9 mois) = {f(I9)} $")118print(f"erreurs : sans ½ → {f(r0(650000*0.0725))} $ ; durée en mois sans /12 → {f(r0(650000*0.0725*12*0.5))} $ (12 fois trop)")119120h("B3 — permis de construire à Gatineau (règl. 501-2005, art. 104)")121for nom, dc, n in [("unifamiliale", 95000, 1), ("duplex", 130000, 2), ("huit logements", 1400000, 8)]:122    pm = permis(dc, n); print(f"{nom:15s} D+C {f(dc):>10s} $, {n} log. : proportionnel {f(pm['prop']):>6s} ; minimum {f(pm['mini']):>6s} → permis {f(pm['permis'])} $")123print(f"seuil où le minimum cesse de mordre (8 log.) : 8 × 335 / 4,70 × 1 000 = {f(r0(8*335/4.70*1000))} $ ; (1 log.) : {f(r0(335/4.70*1000))} $")124125h("B4 — honoraires : même taux de 4 %, trois bases (directs nus 600 000 $)")126b4 = cascade(600000)127print(f"directs du cours {f(b4['directs_cours'])} ; D+C {f(b4['dc'])}")128print(f"4 % des nus = {f(r0(600000*0.04))} ; 4 % des directs du cours = {f(r0(b4['directs_cours']*0.04))} (convention) ; 4 % de D+C = {f(r0(b4['dc']*0.04))}")129print(f"vérification horaire 210 h × 145 $ = {f(210*145)} $ ; écart vs convention {r2((210*145/(b4['directs_cours']*0.04)-1)*100)} %")130131h("B5 — TPS/TVQ, acheteur-occupant 260 000 $ et 420 000 $ ; premier acheteur 420 000 $")132for prix, prem in [(260000, False), (420000, False), (420000, True)]:133    t = taxes(prix, prem); print(f"{'premier acheteur' if prem else 'occupant':17s} {f(prix)} $ : TPS {f2(t['tps'])} ; TVQ {f2(t['tvq'])} ; brutes {f2(t['brutes'])} ; remb. TPS {f2(t['remb_tps'])} ; remb. TVQ {f2(t['remb_tvq'])} ; NETTES {f2(t['nettes'])} $ ({t['pct']} %)")134135h("B6 — profit et marge")136print(f"(a) p = 9 % → m = {r2(0.09/1.09*100)} %  (b) m = 12 % → p = {r2(0.12/0.88*100)} %  (c) assiette 900 000, profit visé 100 000 $ : p = {r2(100000/900000*100)} % ; m = {r2(100000/1000000*100)} %")137138h("B7 — méthode par période : 300 000 $, 6,75 %, 6 mois, linéaire, intérêt sur solde de début de mois capitalisé")139tot, lg = par_periode(300000, 6.75, 6)140print("mois | décaissement | cumul | intérêt | solde fin")141for l in lg: print(f"{l[0]:>4} | {f(l[1]):>12} | {f(l[2]):>9} | {f(l[3]):>7} | {f(l[4]):>9}")142print(f"I par période = {f(tot)} $ ; simplifiée ½ = {f(interets_simpl(300000, 6.75, 6))} $ ; simple (n−1)/2n = 300 000 × 0,0675/12 × 5/2 = {f(r0(300000*0.0675/12*5/2))} $")143144h("B8 — extraction du profit : vente 1 380 000 $, terrain 190 000 $, assiette reconstituée 1 010 000 $")145bat = 1380000 - 190000; prof = bat - 1010000146print(f"bâtiment implicite {f(bat)} ; profit extrait {f(prof)} $ ; taux sur l'assiette {r2(prof/1010000*100)} % ; marge sur le bâtiment {r2(prof/bat*100)} % ; marge sur le prix total {r2(prof/1380000*100)} %")147148# ---------------------------------------------------------------- Famille C149h("C1 — facteur (n−1)/2n : 14 déboursés mensuels égaux, intérêt simple")150n = 14; C = 1018968; i = 6.75151Isimple = r0(C * i / 100 / 12 * (n - 1) / 2)152print(f"I = C × i/12 × (n−1)/2 = {f(Isimple)} $ ; = 40 122 × 13/14 = {f(r0(40122*13/14))} $ ; capitalisation (+{f(lin-Isimple)}) → {f(lin)} $ (par période linéaire)")153print(f"facteurs : (n−1)/2n = {13/28:.4f} contre 0,5 → écart {r2((13/28/0.5-1)*100)} %")154155h("C2 — conversion profit ↔ marge")156for p in (0.08, 0.11, 0.1236, 0.15, 0.20): print(f"p = {p*100:.2f} % → m = {r2(p/(1+p)*100)} %", end=" ; ")157print(); 158for m in (0.08, 0.0991, 0.11, 0.15, 0.20): print(f"m = {m*100:.2f} % → p = {r2(m/(1-m)*100)} %", end=" ; ")159print()160161h("C3 — écart cascade − somme = termes croisés × nus")162tc = 1.08 * 1.07 * 1.08 - 1.23163print(f"termes croisés = {tc:.6f} ; sur 100 000 $ : {f(r0(100000*tc))} $ ; sur 785 000 $ : {f(r0(785000*tc))} $ (avec arrondis de ligne : {f(BASE['cascade']['directs_cours'] - r0(785000*1.23))} $)")164165# ---------------------------------------------------------------- Famille D — étude de cas (Aylmer, 16 mois)166h("D1 — le même six logements à Aylmer (redevances 6 × 3 986,39 $) et chantier de 16 mois")167red = r0(6 * P["redevance_aylmer"])168D1 = chaine(785000, 6, 16, 12000, False, redevances=red)169print(f"redevances de développement = 6 × 3 986,39 = {f2(6*P['redevance_aylmer'])} → {f(red)} $")170print(f"intérêts 16 mois = 1 018 968 × 0,0675 × 16/12 × ½ = {f(D1['interets'])} $ (vs 40 122 : +{f(D1['interets']-40122)})")171print(f"indirects : permis {f(D1['permis']['permis'])} + GCR 0 + intérêts {f(D1['interets'])} + honoraires {f(D1['honoraires'])} + assurance {f(D1['assurance'])} + juridique {f(D1['juridique'])} + redevances {f(red)} = {f(D1['indirects'])} $")172print(f"assiette {f(D1['assiette'])} ; profit 11 % {f(D1['profit'])} ; COÛT TOTAL {f(D1['total'])} $")173print(f"écart vs cas de base : {f(D1['total']-BASE['total'])} $ ({r2((D1['total']/BASE['total']-1)*100)} %) ; décomposition (5 732 + 23 918) × 1,11 = {f(r0((D1['interets']-40122+red)*1.11))} $")174print(f"unités : {r2(D1['total']/5400)} $/pi² hors sol ; {r2(D1['total']/7200)} $/pi² brut ; {f(r0(D1['total']/6))} $/logement ; indirects/directs du cours {r2(D1['indirects']/979718*100)} %")175print(f"test du marché : (1 279 686 − 1 325 000)/1 325 000 = {r2((D1['total']-1325000)/1325000*100)} % ; profit extrait médian sur la nouvelle assiette = {r2((1325000-D1['assiette'])/D1['assiette']*100)} %")176g2 = r0(6 * P["gcr_cat2"]); print(f"sous-question copropriété divise : GCR catégorie 2 = 6 × 3 359,68 = {f2(6*P['gcr_cat2'])} $ ; redevances inchangées ; coût total si visée : {f(D1['total'] + r0(g2*1.11))} $ (≈ +{f(r0(g2*1.11))})")177D1b = chaine(785000, 6, 16, 12000, True, redevances=red, gcr_unit=P["gcr_cat2"]); print(f"   (chaîne complète, GCR cat. 2 : indirects {f(D1b['indirects'])}, assiette {f(D1b['assiette'])}, profit {f(D1b['profit'])}, total {f(D1b['total'])} $)")178179# ---------------------------------------------------------------- Famille F180h("F1 — soumission clés en main de 1 019 000 $ (dont réserve de 39 000 $) : reconstruire le coût neuf")181F1 = chaine(None, 6, 14, 12000, False, directs_cours_impose=980000, dc_impose=1019000)182print(f"directs du cours (soumission − réserve) = 980 000 ; D+C = 1 019 000")183print(f"permis {f(F1['permis']['permis'])} ; intérêts {f(F1['interets'])} ; honoraires 4 % × 980 000 = {f(F1['honoraires'])} ; assurance {f(F1['assurance'])} ; juridique {f(F1['juridique'])} → indirects {f(F1['indirects'])}")184print(f"assiette {f(F1['assiette'])} ; profit {f(F1['profit'])} ; coût neuf {f(F1['total'])} $ ; sous-estimation si l'on s'arrête à la soumission : {f(F1['total']-1019000)} $ = {r2((1019000/F1['total']-1)*100)} % du coût neuf")185186h("F2 — triplex à Hull : chaîne complète (directs nus 410 000 $, 10 mois, GCR visée, juridique 9 000 $)")187F2 = chaine(410000, 3, 10, 9000, True)188c = F2["cascade"]189print(f"CG {f(c['cg'])} → {f(c['apres_cg'])} ; FG {f(c['fg'])} → {f(c['apres_fg'])} ; profit EG {f(c['peg'])} → directs du cours {f(c['directs_cours'])} ; contingences {f(c['cont'])} → D+C {f(c['dc'])}")190print(f"permis max({f(F2['permis']['prop'])} ; {f(F2['permis']['mini'])}) = {f(F2['permis']['permis'])} ; GCR 3 × 2 656,68 = {f(F2['gcr'])} ; intérêts {f(F2['interets'])} ; honoraires {f(F2['honoraires'])} ; assurance {f(F2['assurance'])} ; juridique {f(F2['juridique'])}")191print(f"indirects {f(F2['indirects'])} ({r2(F2['indirects']/c['directs_cours']*100)} % des directs du cours) ; assiette {f(F2['assiette'])} ; profit {f(F2['profit'])} ; COÛT TOTAL {f(F2['total'])} $ ; {f(r0(F2['total']/3))} $/logement")192print(f"sans GCR (erreur de qualification) : {f(F2['total'] - r0(F2['gcr']*1.11))} $ environ (écart {f(r0(F2['gcr']*1.11))} $)")193194h("F3 — classement de douze postes : voir corrigé (aucun calcul)")195print("\nFin de l'annexe — tous les montants arrondis au dollar à chaque ligne (convention du moteur).")196
Files main
Menu

Theme