spb/traite-evaluation-qc
Public
1#!/usr/bin/env python32"""Figure ch22 — répartition du devis du sujet du cas 1 par corps de métier et par type d'unité de mesure3(mesuré en m³/m²/m lin., dénombré en unités/appareils/semaines, forfait). DONNÉES SYNTHÉTIQUES, graine 20261031.4Mêmes sept blocs et même ordre que la figure 21 (ch21_decomposition.py).5Sortie : assets/figures/ch22/ch22-fig-repartition-corps.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 matplotlib.pyplot as plt10from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars11import ch21_cout_neuf as c2112import ch22_metre as cas131415def main():16 setup()17 devis, _ = c21.charger()18 rep = cas.repartition_par_type(devis)19 types = [c for c in ("mesuré", "dénombré", "forfait") if c in rep.columns]20 etiquettes = {"mesuré": "Mesuré (m³, m², m lin.)", "dénombré": "Dénombré (unités, appareils, semaines)", "forfait": "Forfait"}21 couleurs = {"mesuré": palette.cat[0], "dénombré": palette.cat[2], "forfait": palette.cat[1]}22 fig, ax = plt.subplots(figsize=(LARGEUR, 3.3))23 ordre = list(rep.index)[::-1] # premier bloc en haut24 y = list(range(len(ordre)))25 gauche = [0.0] * len(ordre)26 for t in types:27 vals = [float(rep.loc[c, t]) for c in ordre]28 ax.barh(y, vals, left=gauche, color=couleurs[t], height=0.66, label=etiquettes[t])29 gauche = [g + v for g, v in zip(gauche, vals)]30 tot_di = float(rep.to_numpy().sum())31 for k, c in enumerate(ordre):32 tot = float(rep.loc[c].sum())33 ax.text(tot + 900, k, f"{fmt_dollars(tot)} ({tot/tot_di*100:.1f} %)", va="center", ha="left", fontsize=8, color=palette.ink)34 ax.set_yticks(y); ax.set_yticklabels(ordre)35 ax.set_xlim(0, max(gauche) * 1.45)36 ax.xaxis.set_major_formatter(plt.FuncFormatter(fmt_dollars))37 ax.set_xlabel("Montant du devis (dollars de 2026) ; part du sous-total directs + indirects")38 ax.grid(axis="y", visible=False)39 parts = rep.sum() / tot_di * 10040 ax.set_title("Sous-total D + I = " + fmt_dollars(tot_di) + " : " + ", ".join(f"{etiquettes[t].split(' (')[0].lower()} {parts[t]:.1f} %" for t in types),41 loc="left", fontsize=8.5, color=palette.ink)42 ax.legend(loc="upper center", bbox_to_anchor=(0.45, -0.2), ncol=3, fontsize=7.5)43 out = savefig(fig, "ch22/ch22-fig-repartition-corps.pdf"); print("OK", out)444546if __name__ == "__main__":47 main()48