spb/traite-evaluation-qc
Public
1#!/usr/bin/env python32"""Figures ch11 — (1) fiche d'état du sujet du cas 1 : fraction de vie utile consommée a_eff/L par composante, colorée par3note d'état, taille ∝ coût neuf ; (2) sensibilité de la dépréciation physique totale à λ et à la note d'état de la structure.4DONNÉES SYNTHÉTIQUES (cas 1, graine 20261031 ; constats d'inspection fictifs de analysis/python/cases/ch11_inspection.py).5Sorties : assets/figures/ch11/ch11-fig-fiche-etat.pdf, assets/figures/ch11/ch11-fig-sensibilite-age-effectif.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 np10import matplotlib.pyplot as plt11from traite.style import setup, palette, savefig, LARGEUR, fmt_dollars12import ch11_inspection as cas1314COUL_ETAT = {1: palette.cat[4], 2: palette.cat[0], 3: palette.cat[2], 4: palette.cat[1], 5: palette.cat[3]}151617def fiche_etat(f):18 fig, ax = plt.subplots(figsize=(LARGEUR, 4.2))19 f = f.sort_values("ratio", ascending=True).reset_index(drop=True)20 y = np.arange(len(f))21 ax.hlines(y, f["age"] / f["vie_utile"], f["ratio"], color=palette.grid, lw=1.2, zorder=1)22 ax.scatter(f["age"] / f["vie_utile"], y, marker="|", s=60, color=palette.ink2, zorder=2, label="âge chronologique / vie utile")23 tailles = 18 + 220 * f["cout_neuf"] / f["cout_neuf"].max()24 for e in sorted(f["etat"].unique()):25 m = f["etat"] == e26 ax.scatter(f.loc[m, "ratio"], y[m], s=tailles[m], color=COUL_ETAT[int(e)], edgecolor=palette.ink2, lw=0.4, zorder=3, label=f"état {int(e)}")27 ax.set_yticks(y); ax.set_yticklabels([c.replace(" (", "\n(") if len(c) > 44 else c for c in f["composante"]], fontsize=7)28 ax.set_xlim(0, 1.45); ax.axvline(1.0, color=palette.ink2, lw=0.7, ls="--")29 ax.set_xlabel("Fraction de la vie utile consommée\n(trait : âge chronologique ; disque : âge effectif, taille selon le coût neuf)", fontsize=7.5)30 ax.grid(axis="y", visible=False)31 ax.legend(loc="upper left", bbox_to_anchor=(0.01, 0.99), fontsize=6.8, ncol=1, title="Note d'état constatée", title_fontsize=7)32 fig.tight_layout()33 return savefig(fig, "ch11/ch11-fig-fiche-etat.pdf")343536def sensibilite(sens, cn):37 fig, ax = plt.subplots(figsize=(LARGEUR, 3.0))38 for i, e in enumerate(sorted(sens["etat_structure"].unique())):39 s = sens[sens["etat_structure"] == e].sort_values("lambda")40 ax.plot(s["lambda"], s["taux"] * 100, marker="o", ms=4, color=palette.cat[i], label=f"structure en état {int(e)}")41 base = sens[(sens["lambda"] == cas.LAMBDA) & (sens["etat_structure"] == 3)]["taux"].iloc[0] * 10042 ax.plot([cas.LAMBDA], [base], marker="*", ms=11, color=palette.brand, ls="none", zorder=5)43 ax.annotate(f"convention retenue : λ = {cas.LAMBDA}, état 3 → {base:.1f} %", (cas.LAMBDA, base), xytext=(0.52, 40), fontsize=7.5,44 arrowprops=dict(arrowstyle="-", color=palette.ink2, lw=0.7))45 ax.set_xlabel("Poids λ de la note d'état dans l'âge effectif (0 = âge chronologique seul ; 1 = note d'état seule)")46 ax.set_ylabel("Dépréciation physique totale (% du coût neuf)")47 ax.set_xticks([0, 0.25, 0.5, 0.75, 1.0]); ax.set_ylim(20, 72)48 ax.legend(fontsize=7, ncol=3, loc="lower left")49 fig.tight_layout()50 return savefig(fig, "ch11/ch11-fig-sensibilite-age-effectif.pdf")515253def main():54 setup()55 devis, sujet, cn = cas.charger()56 f = cas.fiche(devis, cn); trav = cas.travaux(devis, cn)57 print("OK", fiche_etat(f))58 print("OK", sensibilite(cas.sensibilite(f, trav, cn), cn))596061if __name__ == "__main__":62 main()63