SPB Git
14.7 KB · 216 lines python
1#!/usr/bin/env python32"""Chapitre 10 — contrôle automatique de la présence des douze éléments du contenu minimal d'un rapport écrit3(NPP de l'OEAQ, norme 2, règle 2.3) dans un texte, et calculs auxiliaires du chapitre (probabilité de détection4d'une lacune par révisions indépendantes, incidence d'une hypothèse extraordinaire, échéances réglementaires).56Les quatre extraits de rapports analysés ici sont FICTIFS, rédigés pour le traité (aucun immeuble, aucun client,7aucun évaluateur réel). Le contrôle est lexical : il détecte la présence de formulations caractéristiques, pas la8qualité du contenu — limite discutée au chapitre.910Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/cases/ch10_controle_rapport.py"""11from __future__ import annotations12import re, sys, pathlib, unicodedata, datetime as dt13from dataclasses import dataclass1415sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))16ROOT = pathlib.Path(__file__).resolve().parents[3]1718# ----------------------------------------------------------------------------------------------------------------19# 1. Les douze éléments de la règle 2.3 (paraphrase ; source : NPP OEAQ, norme 2, règle 2.3, éléments 1 à 12)20# ----------------------------------------------------------------------------------------------------------------21@dataclass(frozen=True)22class Element:23    no: int24    cle: str25    libelle: str26    strict: tuple      # motifs caractéristiques (lexique strict)27    souple: tuple      # motifs supplémentaires, plus larges (lexique souple = strict + souple)2829def _n(s: str) -> str:30    """Normalise : minuscules, sans accents, espaces simples, apostrophes droites."""31    s = unicodedata.normalize("NFKD", s).encode("ascii", "ignore").decode("ascii")32    s = s.replace("’", "'").lower()33    return re.sub(r"\s+", " ", s)3435ELEMENTS: list[Element] = [36    Element(1, "identification", "Identification physique et légale de l'immeuble",37            (r"\blot\s+\d", r"cadastre du quebec", r"designation cadastrale", r"identification (physique|legale)"),38            (r"\badresse\b", r"\bsitue au\b", r"matricule")),39    Element(2, "droits", "Droits de propriété évalués",40            (r"pleine propriete", r"droits? (de propriete )?evalues?", r"droit (reel )?evalue", r"fief simple"),41            (r"\busufruit\b", r"emphyteo", r"indivis", r"copropriete divise")),42    Element(3, "but_fin", "But (valeur recherchée) et fin (usage prévu) de l'évaluation",43            (r"\bbut de l'evaluation", r"\bfin de l'evaluation", r"aux fins (de|d')", r"usage prevu du rapport"),44            (r"destinataire", r"financement hypothecaire", r"\bmandat\b")),45    Element(4, "definition_valeur", "Définition de la valeur recherchée",46            (r"valeur marchande", r"definition de la valeur", r"valeur recherchee", r"prix (sincere )?le plus probable"),47            (r"\bvaleur\b.{0,40}\bdefini", r"valeur d'investissement", r"valeur reelle")),48    Element(5, "date", "Date d'évaluation (date effective de l'opinion)",49            (r"date d'evaluation", r"date effective", r"en date du \d", r"\bau \d{1,2}(er)? [a-z]+ \d{4}\b"),50            (r"\b(janvier|fevrier|mars|avril|mai|juin|juillet|aout|septembre|octobre|novembre|decembre) \d{4}\b",)),51    Element(6, "etendue", "Étendue du travail (recherches, vérifications, inspection)",52            (r"etendue (du travail|des recherches|de la mission)", r"portee du mandat", r"travaux (effectues|realises)",53             r"inspect(e|ee|ion)"),54            (r"\bvisite\b", r"recherches? effectuee", r"verifi(e|cation)")),55    Element(7, "reserves_hypotheses", "Réserves limitant la portée de l'acte et hypothèses de base (dont extraordinaires)",56            (r"hypothese", r"\breserves?\b", r"condition(s)? limitative"),57            (r"\bpresum", r"\bsuppos", r"sous reserve")),58    Element(8, "raisonnement", "Traitement de l'information et raisonnement conduisant à la conclusion",59            (r"methode (de comparaison|du cout|du revenu|de la parite|des revenus)", r"\bajustements?\b", r"reconciliation",60             r"raisonnement", r"analyse (des|de) (ventes|donnees|marche)"),61            (r"\bcomparables?\b", r"taux (global|de capitalisation)", r"\bconclu(sion|ons)\b")),62    Element(9, "umpp", "Usage le meilleur et le plus profitable (PHMU)",63            (r"usage le meilleur et le plus profitable", r"plus haute et meilleure utilisation", r"\bumpp\b", r"\bphmu\b"),64            (r"usage optimal", r"meilleur usage")),65    Element(10, "rejet_methode", "Explication du rejet d'une méthode traditionnelle",66            (r"methode[^.]{0,80}(ecartee|rejetee|non retenue|non appliquee|n'a pas ete (retenue|appliquee))",67             r"rejet de la methode", r"(ecartee|rejetee|non retenue)[^.]{0,40}methode"),68            (r"n'a pas ete (utilisee|employee)", r"\bnon pertinente\b")),69    Element(11, "conformite", "Conformité à la substance de la norme 1 et écarts aux directives",70            (r"conform(e|ement|ite) aux normes de pratique", r"normes de pratique professionnelle",71             r"ecart[^.]{0,60}directive", r"directive[^.]{0,60}ecart"),72            (r"\bnormes?\b", r"\boeaq\b", r"ordre des evaluateurs")),73    Element(12, "attestation", "Attestation signée et datée",74            (r"j'atteste", r"\batteste(nt)? par la presente", r"\battestation\b"),75            (r"au mieux de (ma|notre) connaissance", r"\bsign(e|ee|ature)\b")),76]7778def controler(texte: str, lexique: str = "strict") -> dict:79    """Retourne, pour chaque élément, (détecté: bool, motif ayant déclenché la détection ou None)."""80    t = _n(texte)81    res = {}82    for e in ELEMENTS:83        motifs = e.strict + (e.souple if lexique == "souple" else ())84        hit = None85        for m in motifs:86            if re.search(m, t):87                hit = m; break88        res[e.cle] = (hit is not None, hit)89    return res9091def taux_conformite(res: dict) -> float:92    """Équation (10.2) : proportion des douze éléments détectés."""93    return sum(1 for d, _ in res.values() if d) / len(ELEMENTS)9495def manquants(res: dict) -> list[int]:96    return [e.no for e in ELEMENTS if not res[e.cle][0]]9798def p_detection(d: float, n: int) -> float:99    """Probabilité qu'au moins une de n révisions indépendantes, chacune de puissance d, relève une lacune."""100    return 1.0 - (1.0 - d) ** n101102def incidence_hypothese(v_sous_h: float, v_sans_h: float) -> tuple[float, float]:103    """Incidence d'une hypothèse extraordinaire : écart absolu et relatif (base : valeur sans l'hypothèse)."""104    return v_sous_h - v_sans_h, (v_sous_h - v_sans_h) / v_sans_h105106def esperance_sous_incertitude(p: float, v_sous_h: float, v_sans_h: float) -> float:107    return p * v_sous_h + (1 - p) * v_sans_h108109def fin_conservation(dernier_service: dt.date, annees: int = 5) -> dt.date:110    """Règlement sur la tenue des dossiers (C-26, r. 133, art. 6) : au moins 5 ans après le dernier service."""111    return dernier_service.replace(year=dernier_service.year + annees)112113def bilan_formation(heures_total: float, heures_ethique: float, report_anterieur: float = 0.0) -> dict:114    """Règlement sur la formation continue (C-26, r. 127.1) : 30 h par période de 2 ans dont 3 h éthique/déontologie/normes ;115    report maximal de 5 h vers la période suivante."""116    requis_total, requis_ethique, report_max = 30.0, 3.0, 5.0117    dispo = heures_total + min(report_anterieur, report_max)118    deficit_total = max(0.0, requis_total - dispo)119    deficit_ethique = max(0.0, requis_ethique - heures_ethique)120    report_suivant = min(report_max, max(0.0, dispo - requis_total))121    return {"deficit_total": deficit_total, "deficit_ethique": deficit_ethique, "report_suivant": report_suivant,122            "conforme": deficit_total == 0 and deficit_ethique == 0}123124# ----------------------------------------------------------------------------------------------------------------125# 2. Extraits FICTIFS de rapports (rédigés pour le traité ; immeubles, parties et signataires inexistants)126# ----------------------------------------------------------------------------------------------------------------127EXTRAITS = {128"A": """EXTRAIT FICTIF A — « Rapport abrégé »129Objet : résidence unifamiliale à étages située au 123, rue des Érables (adresse fictive), secteur Hull, Gatineau.130Mandat : estimer la valeur de la propriété pour le compte de l'institution prêteuse, aux fins de financement hypothécaire.131Description : cottage de deux étages construit en 1998, superficie habitable de 165 m², terrain de 620 m², garage simple,132sous-sol aménagé. Le bâtiment est en bon état général selon la visite extérieure effectuée.133Analyse : trois ventes comparables du secteur ont été retenues et ajustées pour la superficie, l'âge et le garage.134Les prix ajustés s'établissent entre 538 000 $ et 571 000 $. Par la méthode de comparaison, nous estimons la valeur135à 555 000 $. Ce montant est valable pour une période de six mois.136Signature : Évaluateur fictif, É.A.""",137138"B": """EXTRAIT FICTIF B — « Rapport narratif, section Réserves et conclusion »139Identification : lot 0 000 000 du cadastre du Québec (numéro fictif), circonscription foncière de Hull, bâtiment résidentiel.140Droits évalués : pleine propriété, libre de toute charge autre que celles mentionnées au certificat de localisation.141But et fin : estimer la valeur marchande, telle que définie aux Normes de pratique professionnelle de l'OEAQ, en date142du 1er septembre 2026, aux fins d'une décision d'achat par le client.143Usage le meilleur et le plus profitable : l'usage résidentiel unifamilial actuel est conforme au zonage et demeure144l'usage optimal du site tel qu'aménagé.145Hypothèses : nous présumons que le permis de construction pour l'agrandissement arrière a été obtenu et que les travaux146sont conformes au Code. La méthode du coût a également été appliquée.147Conclusion : la valeur marchande est de 612 000 $. Le présent rapport est conforme aux normes de pratique professionnelle.148J'atteste par la présente, au mieux de ma connaissance, que les faits énoncés sont vrais et exacts.""",149150"C": """EXTRAIT FICTIF C — « Rapport de mise à jour »151Le présent rapport de mise à jour se rapporte au rapport narratif complet portant sur l'immeuble de douze logements152situé au 456, avenue Fictive, Montréal (lot 0 000 001 du cadastre du Québec, numéro fictif), préparé par notre firme153pour le même client en date du 1er mars 2026. Les réserves et hypothèses de base du rapport original demeurent applicables.154L'inspection a été mise à jour le 10 octobre 2026 ; aucun changement physique n'a été constaté.155Les loyers en vigueur ont augmenté de 3,1 % et le taux global de capitalisation retenu passe de 5,25 % à 5,40 %.156Par la méthode du revenu, l'indication de valeur en date du 15 octobre 2026 est de 2 410 000 $, comparativement à1572 450 000 $ au rapport original. La méthode du coût, écartée au rapport original pour un immeuble de 1965, demeure158non retenue. Évaluateur fictif, É.A.""",159160"D": """EXTRAIT FICTIF D — « Sommaire d'un rapport narratif complet »161Identification physique et légale : lot 0 000 002 du cadastre du Québec (numéro fictif), 789, rue Imaginaire, Québec.162Droits évalués : pleine propriété. But et fin de l'évaluation : estimer la valeur marchande aux fins d'un règlement163successoral ; le rapport est destiné au liquidateur. Définition de la valeur : la valeur marchande est définie comme164le prix sincère le plus probable, conformément aux Normes de pratique professionnelle de l'OEAQ. Date d'évaluation :1651er septembre 2026. Étendue du travail : inspection complète de l'immeuble le 28 août 2026, recherche de titres,166analyse de quatorze ventes de triplex. Réserves et hypothèses de base : voir l'annexe ; aucune hypothèse extraordinaire.167Usage le meilleur et le plus profitable : usage résidentiel locatif actuel. Raisonnement : méthode de comparaison168(cinq comparables, ajustements documentés) et méthode du revenu, réconciliation pondérée. La méthode du coût a été169écartée en raison de l'âge du bâtiment et de la difficulté de mesurer la dépréciation. Le rapport est conforme à la170substance de la norme 1 ; aucun écart aux directives. J'atteste par la présente, au mieux de ma connaissance et de ma171conviction, que les faits énoncés sont vrais et exacts. Signé et daté.""",172}173174def main(verbose: bool = True) -> dict:175    out = {"controle": {}, "taux": {}, "manquants": {}}176    for nom, txt in EXTRAITS.items():177        for lex in ("strict", "souple"):178            r = controler(txt, lex)179            out["controle"][(nom, lex)] = r180            out["taux"][(nom, lex)] = taux_conformite(r)181            out["manquants"][(nom, lex)] = manquants(r)182    # probabilités de détection (modèle de révisions indépendantes)183    grille_d = (0.5, 0.7, 0.9); grille_n = (1, 2, 3)184    out["p_detection"] = {(d, n): p_detection(d, n) for d in grille_d for n in grille_n}185    # incidence d'une hypothèse extraordinaire (chiffres FICTIFS de l'extrait B et de l'exercice 10.5)186    out["incidence_B"] = incidence_hypothese(612000, 575000)187    out["esperance_B"] = esperance_sous_incertitude(0.6, 612000, 575000)188    # échéances réglementaires (exercice 10.2)189    out["conservation"] = fin_conservation(dt.date(2026, 11, 15))190    out["formation"] = bilan_formation(heures_total=26, heures_ethique=2, report_anterieur=5)191    out["formation_b"] = bilan_formation(heures_total=33, heures_ethique=3, report_anterieur=0)192    if verbose:193        print("== Contrôle lexical des douze éléments (règle 2.3) — extraits FICTIFS")194        for nom in EXTRAITS:195            for lex in ("strict", "souple"):196                r = out["controle"][(nom, lex)]197                print(f"  Extrait {nom} [{lex:6s}] : {sum(d for d,_ in r.values()):2d}/12 détectés "198                      f"(taux {out['taux'][(nom, lex)]:.3f}) ; manquants : {out['manquants'][(nom, lex)]}")199        print("== Détail (lexique strict) :")200        for nom in EXTRAITS:201            r = out["controle"][(nom, "strict")]202            print("  ", nom, " ".join(f"{e.no}:{'X' if r[e.cle][0] else '.'}" for e in ELEMENTS))203        print("== Probabilité de détection d'une lacune, 1-(1-d)^n :")204        for d in grille_d:205            print("   d =", d, " ".join(f"n={n}: {out['p_detection'][(d,n)]:.3f}" for n in grille_n))206        dv, dr = out["incidence_B"]207        print(f"== Incidence de l'hypothèse extraordinaire (extrait B, fictif) : {dv:,.0f} $ ({dr*100:.2f} %) ; "208              f"espérance à p = 0,6 : {out['esperance_B']:,.0f} $")209        print("== Fin de conservation minimale d'un dossier clos le 2026-11-15 :", out["conservation"])210        print("== Bilan de formation continue (26 h dont 2 h éthique, report 5 h) :", out["formation"])211        print("== Bilan de formation continue (33 h dont 3 h éthique) :", out["formation_b"])212    return out213214if __name__ == "__main__":215    main()216
Files · 11f6c54e39ac2d8ce41b1b464d12e942e4170c06
Menu

Theme