spb/traite-evaluation-qc
Public
1#!/usr/bin/env python32"""Figure ch23 — sensibilité (DONNÉES SYNTHÉTIQUES, cas 1 ; taux documenté Banque du Canada V80691335) :3(a) intérêts intérimaires selon la durée du chantier et le taux (tirage uniforme, L·i·T/2) ;4(b) tornade du coût neuf : durée, taux intérimaire, indirects hors financement, hausse salariale (λ = 40 %), incitatif.5Sortie : assets/figures/ch23/ch23-fig-sensibilite.pdf"""6import sys, pathlib7ROOT = pathlib.Path(__file__).resolve().parents[3]8sys.path.insert(0, str(ROOT / "analysis/python")); sys.path.insert(0, str(ROOT / "analysis/python/cases"))9import numpy as np, matplotlib.pyplot as plt10from matplotlib.ticker import FuncFormatter11from traite.style import setup, palette, savefig, LARGEUR, fmt_milliers12import ch21_cout_neuf as c21, ch23_structure_couts as cas1314def main():15 setup()16 devis, sujet = c21.charger(); cn = c21.cout_neuf_sujet(devis); i_aff = cas.taux_affiche_5ans()17 fc = cas.financement_cas1(devis, cn, i_aff); base = fc["base_hors_financement"]18 mois = np.arange(3, 25); taux = [0.04, 0.05, i_aff, 0.07, 0.09]19 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(LARGEUR, 3.4), gridspec_kw={"width_ratios": [1, 1.25]})20 for k, t in enumerate(taux):21 ax1.plot(mois, [cas.fin.interets_intermediaires(base, t, m / 12)["interets"] / 1000 for m in mois], color=palette.cat[k], lw=2.0 if abs(t - i_aff) < 1e-6 else 1.2, label=f"{t*100:.2f} %".replace(".", ","))22 ax1.axhline(fc["forfait"] / 1000, color=palette.ink2, lw=0.8, ls="--"); ax1.text(3.2, fc["forfait"] / 1000 + 0.3, f"forfait du devis {fc['forfait']/1000:.1f} k$".replace(".", ","), fontsize=6.8, color=palette.ink2, ha="left", va="bottom")23 ax1.set_xlabel("Durée du chantier (mois)"); ax1.set_ylabel("Intérêts intérimaires (k$)"); ax1.set_title("(a) Intérêts selon durée et taux", loc="left")24 ax1.legend(title="Taux annuel", fontsize=7, title_fontsize=7, loc="upper left")25 torn = cas.tornade(devis, cn, i_aff); base_cn = torn[0]["base"]26 torn = sorted(torn, key=lambda o: abs(o["haut"] - o["bas"]))27 y = np.arange(len(torn))28 for yi, o in zip(y, torn):29 lo, hi = min(o["bas"], o["haut"]), max(o["bas"], o["haut"])30 ax2.barh(yi, (hi - lo) / 1000, left=lo / 1000, color=palette.cat[0], height=0.55)31 ax2.text(lo / 1000 - 1, yi, f"{lo/1000:.0f}", ha="right", va="center", fontsize=7); ax2.text(hi / 1000 + 1, yi, f"{hi/1000:.0f}", ha="left", va="center", fontsize=7)32 ax2.axvline(base_cn / 1000, color=palette.ink2, lw=0.8); ax2.text(base_cn / 1000, -0.95, f"base {base_cn/1000:.1f} k$".replace(".", ","), ha="center", va="bottom", fontsize=7, color=palette.ink2); ax2.set_ylim(-1.1, len(torn) - 0.5)33 ax2.set_yticks(y); ax2.set_yticklabels([f"{o['param']}\n{o['etiquette']}" for o in torn], fontsize=7)34 ax2.set_xlabel("Coût neuf (k$)"); ax2.set_title("(b) Tornade du coût neuf", loc="left"); ax2.set_xlim(365, 430)35 ax2.xaxis.set_major_formatter(FuncFormatter(fmt_milliers)); ax2.grid(axis="y", visible=False)36 fig.tight_layout(w_pad=2.0)37 out = savefig(fig, "ch23/ch23-fig-sensibilite.pdf"); print("OK", out)3839if __name__ == "__main__":40 main()41