SPB Git forge

spb/wp16_uqo

Public
11commits 1branches 1releases
3.3 MBsize
maindefault branch
2 h agolast push
TeX 52.9% Python 45.7% Shell 1.4%
3.6 KB · 86 lines python
Raw Blame History
1# -*- coding: utf-8 -*-2"""Validation empirique de la méthode des comparables — fonctions communes.3Données : instantané Vrai Prix (transactions ⋈ units) du 2026-10-10.4Toutes les sorties sont versionnées sous out/<version>/.5"""6from __future__ import annotations7import json, math, os, sqlite3, time8from pathlib import Path9import numpy as np10import pandas as pd1112ROOT = Path(os.environ.get("CV_ROOT", Path(__file__).resolve().parents[1]))13DB = Path(os.environ.get("CV_DB", ROOT / "data" / "vraiprix-20261010.db"))14REF = ROOT / "ref"15VERSION = os.environ.get("CV_VERSION", time.strftime("%Y-%m-%d"))16OUT = ROOT / "out" / VERSION17OUT.mkdir(parents=True, exist_ok=True)18(OUT / "fig").mkdir(exist_ok=True)1920# Périodes (séparation chronologique)21TRAIN_END = "2023-12-31"      # développement22VAL_START, VAL_END = "2024-01-01", "2024-12-31"   # choix des configurations23TEST_START, TEST_END = "2025-01-01", "2026-09-29"  # évaluation finale hors échantillon24MONTH0 = "2021-01"            # premier mois des données2526def log(msg: str) -> None:27    print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)2829def connect(path: Path = DB) -> sqlite3.Connection:30    return sqlite3.connect(f"file:{path}?mode=ro", uri=True, timeout=60)3132def _json_default(o):33    if isinstance(o, np.integer): return int(o)34    if isinstance(o, np.floating): return None if np.isnan(o) else float(o)35    if isinstance(o, np.bool_): return bool(o)36    if isinstance(o, np.ndarray): return o.tolist()37    if isinstance(o, pd.Timestamp): return o.strftime("%Y-%m-%d")38    if isinstance(o, float) and math.isnan(o): return None39    return str(o)4041def save_json(obj, name: str) -> Path:42    p = OUT / name; p.parent.mkdir(parents=True, exist_ok=True)43    with open(p, "w", encoding="utf-8") as f:44        json.dump(obj, f, ensure_ascii=False, indent=2, default=_json_default)45    return p4647def load_json(name: str):48    with open(OUT / name, encoding="utf-8") as f:49        return json.load(f)5051def save_parquet(df: pd.DataFrame, name: str) -> Path:52    p = OUT / name; df.to_parquet(p, index=False); return p5354def load_parquet(name: str) -> pd.DataFrame:55    return pd.read_parquet(OUT / name)5657def month_index(dates) -> np.ndarray:58    """Mois entier depuis MONTH0 (2021-01 = 0)."""59    d = pd.to_datetime(dates)60    y0, m0 = map(int, MONTH0.split("-"))61    return ((d.dt.year - y0) * 12 + (d.dt.month - m0)).to_numpy()6263def to_xy_km(lat, lng, lat0=46.8, lng0=-71.2):64    """Projection équirectangulaire locale (km) : erreur < 1 % pour des distances < 50 km."""65    x = (np.asarray(lng, float) - lng0) * 111.32 * np.cos(np.radians(np.asarray(lat, float)))66    y = (np.asarray(lat, float) - lat0) * 110.5767    return x, y6869def load_repertoire() -> pd.DataFrame:70    mun = pd.read_csv(REF / "MUN.csv", dtype=str, encoding="utf-8-sig")71    mun = mun.rename(columns=str.lower)72    out = pd.DataFrame({73        "code_mun": mun["mcode"].str.zfill(5),74        "mun_nom": mun["munnom"],75        "region": mun["regadm"].str.extract(r"\((\d+)\)")[0],76        "region_nom": mun["regadm"].str.replace(r"\s*\(\d+\)", "", regex=True),77        "mrc": mun["mrc"].str.extract(r"\((\d+)\)")[0],78        "mrc_nom": mun["mrc"].str.replace(r"\s*\(\d+\)", "", regex=True),79    })80    return out.drop_duplicates("code_mun")8182# Codages catégoriels (ordre stable)83LIEN = {"Détaché": 0, "Jumelé": 1, "En rangée 1 côté": 2, "En rangée plus de 1 côté": 2, "Intégré": 3}84GENRE = {"De plain-pied": 0, "Unimodulaire": 0, "À étages entiers": 1, "À niveaux décalés": 2, "À étage mansardé": 3}85BTYPE = {"single-story": 0, "full-story": 1, "mansard": 2, "split-level": 3, "single-wide": 4}86