SPB Git
5.3 KB · 95 lines python
1"""Méthode de comparaison : grille d'ajustements, ajustements séquentiels, ventes appariées, réconciliation."""2from __future__ import annotations3from dataclasses import dataclass4from typing import Sequence, Mapping5import math67def prix_ajuste_additif(prix: float, ajustements: Mapping[str, float]) -> float:8    """P_aj = P + Σ_k A_k (ajustements monétaires, signe du point de vue du SUJET : + si le sujet est supérieur)."""9    return prix + sum(ajustements.values())1011def prix_ajuste_multiplicatif(prix: float, ajustements_pct: Sequence[float]) -> float:12    """P_aj = P × Π_k (1 + a_k) — ajustements en proportion appliqués en cascade (ordre déclaré)."""13    out = prix14    for a in ajustements_pct:15        out *= (1.0 + a)16    return out1718def prix_ajuste_mixte(prix: float, ajustements_pct_sequentiels: Sequence[float], ajustements_monetaires: Mapping[str, float]) -> dict:19    """Pratique courante : d'abord les ajustements de transaction en pourcentage (droits, financement, conditions, dépenses20    post-vente, marché/temps) en cascade, puis les ajustements de propriété en dollars, additifs, sur la base ajustée."""21    base = prix_ajuste_multiplicatif(prix, ajustements_pct_sequentiels)22    final = base + sum(ajustements_monetaires.values())23    return {"base_transaction": base, "prix_ajuste": final}2425def ajustement_temps(prix: float, indice_vente: float, indice_date_evaluation: float) -> float:26    """Ajustement de marché par ratio d'indices : P × I_eval / I_vente − P."""27    return prix * indice_date_evaluation / indice_vente - prix2829def ajustement_temps_taux(prix: float, taux_annuel: float, mois: float) -> float:30    """Ajustement par taux composé mensuel : P × ((1+g)^(mois/12) − 1)."""31    return prix * ((1.0 + taux_annuel) ** (mois / 12.0) - 1.0)3233def ventes_appariees(prix_avec: Sequence[float], prix_sans: Sequence[float]) -> dict:34    """Extraction d'un ajustement par paires : différence moyenne et médiane, écart-type, n."""35    diffs = [a - s for a, s in zip(prix_avec, prix_sans)]36    n = len(diffs)37    m = sum(diffs) / n38    s = math.sqrt(sum((d - m) ** 2 for d in diffs) / (n - 1)) if n > 1 else float("nan")39    med = sorted(diffs)[n // 2] if n % 2 else 0.5 * (sorted(diffs)[n // 2 - 1] + sorted(diffs)[n // 2])40    return {"n": n, "moyenne": m, "mediane": med, "ecart_type": s, "differences": diffs}4142def ajustement_brut_net(ajustements: Mapping[str, float], prix: float) -> dict:43    """Ajustement brut = Σ|A_k| / P ; net = Σ A_k / P (indicateurs de qualité du comparable)."""44    brut = sum(abs(v) for v in ajustements.values())45    net = sum(ajustements.values())46    return {"brut": brut, "net": net, "brut_pct": brut / prix, "net_pct": net / prix}4748@dataclass49class ComparableAjuste:50    nom: str51    prix: float52    ajustements: dict53    @property54    def prix_ajuste(self) -> float: return prix_ajuste_additif(self.prix, self.ajustements)55    @property56    def brut_pct(self) -> float: return ajustement_brut_net(self.ajustements, self.prix)["brut_pct"]57    @property58    def net_pct(self) -> float: return ajustement_brut_net(self.ajustements, self.prix)["net_pct"]5960def grille(comparables: Sequence[ComparableAjuste]) -> list[dict]:61    return [{"nom": c.nom, "prix": c.prix, **c.ajustements, "prix_ajuste": c.prix_ajuste, "brut_pct": c.brut_pct, "net_pct": c.net_pct} for c in comparables]6263def poids_inverse_brut(comparables: Sequence[ComparableAjuste], plancher: float = 0.01) -> list[float]:64    """Pondération ∝ 1 / max(ajustement brut, plancher) — règle usuelle de réconciliation."""65    w = [1.0 / max(c.brut_pct, plancher) for c in comparables]66    s = sum(w)67    return [x / s for x in w]6869def reconciliation(comparables: Sequence[ComparableAjuste], poids: Sequence[float] | None = None) -> dict:70    """Valeur indiquée = Σ w_j P_aj_j ; fournit aussi min, max, médiane, moyenne simple, écart-type pondéré."""71    pa = [c.prix_ajuste for c in comparables]72    n = len(pa)73    w = list(poids) if poids is not None else [1.0 / n] * n74    if abs(sum(w) - 1) > 1e-9:75        raise ValueError("les poids doivent sommer à 1")76    vp = sum(wi * p for wi, p in zip(w, pa))77    var = sum(wi * (p - vp) ** 2 for wi, p in zip(w, pa))78    srt = sorted(pa)79    med = srt[n // 2] if n % 2 else 0.5 * (srt[n // 2 - 1] + srt[n // 2])80    return {"valeur_ponderee": vp, "moyenne": sum(pa) / n, "mediane": med, "min": min(pa), "max": max(pa),81            "ecart_type_pondere": math.sqrt(var), "etendue_pct": (max(pa) - min(pa)) / vp, "poids": w}8283def distance_attributs(sujet: Mapping[str, float], comp: Mapping[str, float], echelles: Mapping[str, float], poids: Mapping[str, float] | None = None) -> float:84    """Distance pondérée normalisée pour la sélection de comparables : sqrt(Σ w_k ((x_k − s_k)/e_k)^2)."""85    poids = poids or {k: 1.0 for k in echelles}86    return math.sqrt(sum(poids[k] * ((comp[k] - sujet[k]) / echelles[k]) ** 2 for k in echelles))8788def ajustement_depuis_coefficient_log(beta: float) -> float:89    """Effet en proportion d'une variable indicatrice dans un modèle semi-log (Halvorsen–Palmquist 1980) : exp(β) − 1."""90    return math.exp(beta) - 1.09192def ajustement_depuis_coefficient_log_sans_biais(beta: float, var_beta: float) -> float:93    """Correction de Kennedy (1981) : exp(β − V(β)/2) − 1."""94    return math.exp(beta - 0.5 * var_beta) - 1.095
Files · 4958fcec3502ce9b6da151998f7eaebf95b47bcc
Menu

Theme