SPB Git forge

spb/wp20_uqo

Public
1commits 1branches 0releases
2.8 MBsize
maindefault branch
yesterdaylast push
TeX 59% Python 40.8%
22.6 KB · 337 lines python
Raw Blame History
1#!/usr/bin/env python32"""Experiments 5–8, 10, 11, 13, 14, 15 — cost-approach indications under baselines B0–B4.34Reads data/processed/levels_<tier>.parquet (and the "sold" supplement), applies src/cost_model/cost.py, and writes5results/tables/exp*.csv + results/raw/exp_cost.json. Benchmarks: (i) assessor building value indexed with the StatCan6BCPI (assessor alignment), (ii) asking price (market alignment), (iii) recorded sale price after listing (supplement),7(iv) Vrai Prix production estimates. Nothing is fitted on prices.8"""9import json10import sys11from pathlib import Path1213import numpy as np14import pandas as pd15import yaml1617sys.path.insert(0, str(Path(__file__).resolve().parents[1]))18from src.cost_model import cost  # noqa: E40219from src.evaluation.metrics import cluster_bootstrap, ratio_stats  # noqa: E4022021ROOT = Path(__file__).resolve().parents[1]22P = ROOT / "data" / "processed"23T = ROOT / "results" / "tables"24CFG = cost.CFG25R = {}26TIERS = [t for t in ["haiku", "sonnet", "opus"] if (P / f"levels_{t}.parquet").exists()]27MAIN = "sonnet" if "sonnet" in TIERS else TIERS[0]28LEVELS = ["B0", "B1", "B2", "B3", "B4"]293031def levels(tier):32    L = pd.read_parquet(P / f"levels_{tier}.parquet")33    return {lvl: g.set_index("uid") for lvl, g in L.groupby("level")}343536def run_level(df, p=None):37    out = cost.cost_indication(df, p)38    out["price"] = df["price"]; out["weight"] = df["weight"]; out["muni"] = df["roll_municipalite"]39    out["age_band"] = df["age_band"]; out["price_tier"] = df["price_tier"]; out["cma"] = df["cma"]; out["source"] = df["source"]40    out["ratio_assessor"] = out["improvements_depreciated"] / out["assessor_building_indexed"]41    out["ratio_price"] = out["V_cost"] / out["price"]42    return out434445def summarise(out, label):46    s = {"label": label, "n": int(len(out)),47         "V_median": float(out["V_cost"].median()), "L_share_median": float((out["L"] / out["V_cost"]).median()),48         "RCN_median": float(out["RCN"].median()), "D_median": float(out["D"].median()), "eff_age_median": float(out["effective_age"].median())}49    a = ratio_stats(out["improvements_depreciated"], out["assessor_building_indexed"])50    s.update({f"assessor_{k}": v for k, v in a.items()})51    m = ratio_stats(out["V_cost"], out["price"])52    s.update({f"price_{k}": v for k, v in m.items()})53    return s545556# --------------------------------------------------------------------------- Exp 5/6: decomposition and level comparison57def exp5_6():58    rows, dec = [], []59    for tier in TIERS:60        lv = levels(tier)61        for lvl in LEVELS:62          if lvl not in lv:63              continue64          for anc in ["B", "A"]:65            out = run_level(lv[lvl], {"anchor": anc})66            if anc == "B":67                out.to_parquet(P / f"cost_{tier}_{lvl}.parquet")68            s = summarise(out, f"{tier}/{lvl}/{anc}"); s["tier"] = tier; s["level"] = lvl; s["anchor"] = anc69            # cluster bootstrap on the two headline metrics70            for metric, pred, ref in [("assessor_mdape", "improvements_depreciated", "assessor_building_indexed"), ("price_mdape", "V_cost", "price")]:71                pt, lo, hi = cluster_bootstrap(out, lambda d: float((d[pred] / d[ref] - 1).abs().median() * 100), "muni", B=499)72                s[f"{metric}_lo"], s[f"{metric}_hi"] = lo, hi73            rows.append(s)74            if anc != "B":75                continue76            for grp in ["price_tier", "age_band", "cma"]:77                g = out.groupby(grp, observed=True).agg(n=("V_cost", "size"), L=("L", "median"), RCN=("RCN", "median"), D=("D", "median"),78                                                        S=("S", "median"), improvements=("improvements_depreciated", "median"), V=("V_cost", "median"),79                                                        ratio_assessor_median=("ratio_assessor", "median"), ratio_price_median=("ratio_price", "median")).reset_index()80                g["tier"] = tier; g["level"] = lvl; g["group"] = grp; g = g.rename(columns={grp: "value"})81                dec.append(g)82    df = pd.DataFrame(rows); df.to_csv(T / "exp5_levels_summary.csv", index=False)83    pd.concat(dec).to_csv(T / "exp5_decomposition.csv", index=False)84    R["exp5"] = df.to_dict(orient="records")85    # Exp 6: effect of visual grade/condition — B2 vs B0 deltas by visual grade & condition tercile (main tier)86    lv = levels(MAIN)87    b0 = run_level(lv["B0"]); b2 = run_level(lv["B2"]); d2 = lv["B2"]88    eff = pd.DataFrame({"grade": d2["grade"], "grade_visible": d2.get("grade_visible", True), "condition": d2["condition"],89                        "delta_RCN_pct": (b2["RCN"] / b0["RCN"] - 1) * 100, "delta_D_pts": (b2["D"] - b0["D"]) * 100,90                        "delta_V_pct": (b2["V_cost"] / b0["V_cost"] - 1) * 100, "delta_impr_pct": (b2["improvements_depreciated"] / b0["improvements_depreciated"] - 1) * 100,91                        "ratio_assessor_B0": b0["ratio_assessor"], "ratio_assessor_B2": b2["ratio_assessor"]})92    eff["cond_tercile"] = pd.qcut(eff["condition"], 3, labels=["low", "mid", "high"], duplicates="drop")93    g1 = eff.groupby("grade", observed=True)[["delta_RCN_pct", "delta_impr_pct", "delta_V_pct", "ratio_assessor_B0", "ratio_assessor_B2"]].median().reset_index()94    g2 = eff.groupby("cond_tercile", observed=True)[["delta_D_pts", "delta_impr_pct", "delta_V_pct", "ratio_assessor_B0", "ratio_assessor_B2"]].median().reset_index()95    g1.to_csv(T / "exp6_by_grade.csv", index=False); g2.to_csv(T / "exp6_by_condition.csv", index=False)96    eff.to_parquet(P / f"exp6_effects_{MAIN}.parquet")97    R["exp6"] = {"by_grade": g1.to_dict(orient="records"), "by_condition": g2.to_dict(orient="records"),98                 "share_grade_visible": float(eff["grade_visible"].mean()) if "grade_visible" in eff else None}99    return df100101102# --------------------------------------------------------------------------- Exp 7/8: sensitivities103def exp7_8():104    lv = levels(MAIN)105    base = lv["B3"]106    rows = []107    nm = CFG["temporal_regional"]["non_metro_discount_scenario"]108    scenarios = {109        "baseline (anchor B, Québec 2026 guides)": {},110        "anchor A (Altus Montréal hard costs)": {"anchor": "A"},111        "non-metro regions −15 % (Dessins Drummond 2026)": {"regional_level_factor": base["cma"].map(lambda c: 1.0 if c in nm["metro_cmas"] else nm["factor_other"])},112        "storey multiplier flat (1.0 all)": {"storey_multiplier": {"1": 1.0, "1.5": 1.0, "2": 1.0, "2.5": 1.0, "3": 1.0}},113        "regional level 0.90": {"regional_level_factor": 0.90}, "regional level 1.10": {"regional_level_factor": 1.10},114        "relative growth since 2023 (CMA BCPI / Montréal BCPI)": {"regional_level_factor": "relative"},115        "grade costs: low band": {"grade_per_sqft": {k: v[0] for k, v in cost.anchor()["grade_sensitivity_per_sqft"].items()}},116        "grade costs: high band": {"grade_per_sqft": {k: v[1] for k, v in cost.anchor()["grade_sensitivity_per_sqft"].items()}},117        "soft costs 5 %": {"soft_cost_share": 0.05}, "soft costs 15 %": {"soft_cost_share": 0.15},118        "entrepreneurial incentive 0 %": {"entrepreneurial_incentive": 0.0}, "entrepreneurial incentive 15 %": {"entrepreneurial_incentive": 0.15},119        "add-ons −30 %": {"add_on_shift": -0.30}, "add-ons +30 %": {"add_on_shift": 0.30},120        "economic life 50 y": {"economic_life": 50}, "economic life 75 y": {"economic_life": 75},121        "condition→effective age: half strength": {"condition_strength": 0.5}, "condition→effective age: 1.5× strength": {"condition_strength": 1.5},122        "external obsolescence 10 %": {"external_obsolescence": 0.10},123        "land not indexed": {"land_index_share": 0.0}, "land half indexed": {"land_index_share": 0.5},124        "no depreciation (RCN + S + L)": {"economic_life": 1e9},125        "no depreciation, land not indexed": {"economic_life": 1e9, "land_index_share": 0.0},126    }127    for name, p in scenarios.items():128        p = dict(p)129        if isinstance(p.get("regional_level_factor"), str) and p["regional_level_factor"] == "relative":130            # ratio of the CMA's BCPI level (2023=100 base) to Montréal's at the valuation quarter131            b = cost.load_bcpi(); q = CFG["valuation_quarter"]132            lvl = b[b.refPer == q].set_index("series")["value"]133            series_of = CFG["temporal_regional"]["cma_to_series"]134            p["regional_level_factor"] = base["cma"].map(lambda c: lvl.get(series_of.get(c, "composite"), lvl["composite"]) / lvl["montreal"])135        out = run_level(base, p)136        s = summarise(out, name); s["scenario"] = name; s["block"] = "7" if any(k in name for k in ["regional", "grade costs", "soft", "entrepreneurial", "add-ons", "baseline", "anchor", "metro", "storey"]) else "8"137        rows.append(s)138    df = pd.DataFrame(rows); df.to_csv(T / "exp7_8_sensitivity.csv", index=False)139    R["exp7_8"] = df.to_dict(orient="records")140    return df141142143# --------------------------------------------------------------------------- Exp 10/11: holdout CMA, roll vintage, subgroups144def exp10_calibration():145    """Calibrate the single depreciation parameter (economic life) so that the median ratio of depreciated improvements to146    the assessor's indexed building value equals 1 on the calibration CMAs; evaluate on the geographic holdout (Québec CMA).147    Prices are never used. Reported for B0 and B3 (main tier)."""148    hold = yaml.safe_load(open(ROOT / "configs" / "data.yaml"))["sample"]["holdout_cma_for_geo_oos"]149    lv = levels(MAIN)150    rows = []151    for anc, lvl in [(a, l) for a in ["B", "A"] for l in ["B0", "B3", "B4"]]:152        d = lv[lvl]153        cal, test = d[d["cma"] != hold], d[d["cma"] == hold]154        # (a) life-only calibration (reported even if it hits the grid bound), (b) RCN level-only calibration at life 60,155        # (c) joint grid. The implied hard cost per sq ft for a standard-grade house is reported for (b)/(c).156        def med_ratio(part, p):157            return float(run_level(part, {**p, "anchor": anc})["ratio_assessor"].median())158        life_only = min(range(40, 201, 5), key=lambda L: abs(med_ratio(cal, {"economic_life": L}) - 1))159        level_only = min([round(0.8 + 0.05 * i, 2) for i in range(29)], key=lambda k: abs(med_ratio(cal, {"regional_level_factor": k}) - 1))160        joint = min([(k, L) for k in [round(0.8 + 0.1 * i, 2) for i in range(15)] for L in (50, 60, 75)],161                    key=lambda kl: abs(med_ratio(cal, {"regional_level_factor": kl[0], "economic_life": kl[1]}) - 1))162        std = cost.anchor({"anchor": anc})["grade_per_sqft"]["standard"]163        variants = [("public parameters", {}), (f"life-only calibration ({life_only} y)", {"economic_life": life_only}),164                    (f"RCN level-only calibration (×{level_only} → standard ≈ ${std * level_only:.0f}/sq ft hard cost)", {"regional_level_factor": level_only}),165                    (f"joint calibration (×{joint[0]}, life {joint[1]} y)", {"regional_level_factor": joint[0], "economic_life": joint[1]})]166        for name, part in [("calibration", cal), ("holdout", test)]:167            for tag, p in variants:168                out = run_level(part, {**p, "anchor": anc})169                a = ratio_stats(out["improvements_depreciated"], out["assessor_building_indexed"]); m = ratio_stats(out["V_cost"], out["price"])170                rows.append({"anchor": anc, "level": lvl, "set": name, "parameters": tag, "economic_life": p.get("economic_life", CFG["depreciation"]["economic_life_years"]),171                             "level_factor": p.get("regional_level_factor", 1.0), "implied_standard_per_sqft": std * p.get("regional_level_factor", 1.0),172                             "n": len(out), "D_median": float(out["D"].median()), "assessor_median_ratio": a["median_ratio"], "assessor_mdape": a["mdape"],173                             "assessor_cod": a["cod"], "price_median_ratio": m["median_ratio"], "price_mdape": m["mdape"], "price_within_20pct": m["within_20pct"]})174    df = pd.DataFrame(rows); df.to_csv(T / "exp10_calibration.csv", index=False)175    R["exp10_calibration"] = df.to_dict(orient="records")176    return df177178179def exp10_11():180    rows = []181    for tier in TIERS:182        lv = levels(tier)183        for lvl in ["B0", "B3"]:184            if lvl not in lv:185                continue186            out = run_level(lv[lvl])187            df0 = pd.read_parquet(P / "sample.parquet").set_index("uid").reindex(out.index)188            out["roll_vintage"] = df0["roll_market_date"].astype(str).str[:4]189            out["source"] = df0["source"]190            out["n_images"] = df0["n_images_used"]191            for grp in ["cma", "roll_vintage", "age_band", "price_tier", "source"]:192                for val, g in out.groupby(grp, observed=True):193                    if len(g) < 15:194                        continue195                    a = ratio_stats(g["improvements_depreciated"], g["assessor_building_indexed"]); m = ratio_stats(g["V_cost"], g["price"])196                    rows.append({"tier": tier, "level": lvl, "group": grp, "value": str(val), "n": len(g),197                                 "assessor_median_ratio": a["median_ratio"], "assessor_mdape": a["mdape"], "assessor_cod": a["cod"],198                                 "price_median_ratio": m["median_ratio"], "price_mdape": m["mdape"]})199    df = pd.DataFrame(rows); df.to_csv(T / "exp10_11_subgroups.csv", index=False)200    R["exp10_11"] = df.to_dict(orient="records")201    return df202203204# --------------------------------------------------------------------------- Exp 13: counterfactual scenarios205def exp13():206    lv = levels(MAIN); b3 = lv["B3"]207    base = run_level(b3)208    rows = []209    order = ["basic", "standard", "upgraded", "luxury"]210    for name, mod in [211        ("grade +1 level", lambda d: d.assign(grade=d["grade"].map(lambda g: order[min(3, order.index(g) + 1)] if g in order else g))),212        ("grade −1 level", lambda d: d.assign(grade=d["grade"].map(lambda g: order[max(0, order.index(g) - 1)] if g in order else g))),213        ("condition +1", lambda d: d.assign(condition=(d["condition"] + 1).clip(upper=5))),214        ("condition −1", lambda d: d.assign(condition=(d["condition"] - 1).clip(lower=1))),215        ("extensive renovation evidence", lambda d: d.assign(renovation="extensive")),216        ("basement finished", lambda d: d.assign(basement="finished")),217        ("attached garage added", lambda d: d.assign(garage="attached")),218    ]:219        out = run_level(mod(b3))220        dv = (out["V_cost"] / base["V_cost"]) - 1221        aff = dv.abs() > 1e-9222        rows.append({"scenario": name, "share_affected": float(aff.mean()),223                     "median_delta_V_pct": float(dv.median() * 100), "median_delta_V_pct_affected": float(dv[aff].median() * 100) if aff.any() else 0.0,224                     "median_delta_improvements_pct": float(((out["improvements_depreciated"] / base["improvements_depreciated"]) - 1).median() * 100),225                     "median_delta_improvements_pct_affected": float(((out["improvements_depreciated"] / base["improvements_depreciated"]) - 1)[aff].median() * 100) if aff.any() else 0.0,226                     "median_delta_V_cad": float((out["V_cost"] - base["V_cost"]).median()),227                     "median_delta_V_cad_affected": float((out["V_cost"] - base["V_cost"])[aff].median()) if aff.any() else 0.0})228    df = pd.DataFrame(rows); df.to_csv(T / "exp13_scenarios.csv", index=False)229    R["exp13"] = df.to_dict(orient="records")230    return df231232233# --------------------------------------------------------------------------- Exp 14: Monte Carlo propagation234def exp14(n_draws=400, seed=20261020):235    rng = np.random.default_rng(seed)236    lv = levels(MAIN); b4 = lv["B4"].copy()237    props = pd.read_parquet(P / f"props_{MAIN}_K10.parquet").set_index("uid").reindex(b4.index)238    order = ["basic", "standard", "upgraded", "luxury"]239    gs = cost.anchor()["grade_sensitivity_per_sqft"]240    base = run_level(b4)241    draws = np.zeros((n_draws, len(b4)))242    for i in range(n_draws):243        d = b4.copy()244        # attribute uncertainty: grade jitter scaled by (1 − confidence) and by missing grade; condition noise sd 0.5 (1.0 if missing)245        conf = props["confidence_mean"].fillna(0.75).values246        miss_g = d["uncertain_grade"].fillna(False).astype(float).values if "uncertain_grade" in d else np.zeros(len(d))247        shift = rng.normal(0, 0.35 + 0.6 * (1 - conf) + 0.6 * miss_g)248        gi = d["grade"].map(lambda g: order.index(g) if g in order else 1).values + shift249        d["grade"] = [order[int(min(3, max(0, round(x))))] for x in gi]250        miss_c = d["uncertain_condition"].fillna(False).astype(float).values if "uncertain_condition" in d else np.zeros(len(d))251        d["condition"] = (d["condition"] + rng.normal(0, 0.5 + 0.5 * miss_c)).clip(1, 5)252        # parameter uncertainty: unit cost within band (uniform), life ~ U(50,75), soft ~ U(.05,.15), EI ~ U(0,.15), land share ~ U(0,1), regional ~ U(.9,1.1)253        u = rng.uniform()254        p = {"grade_per_sqft": {k: v[0] + u * (v[1] - v[0]) for k, v in gs.items()},255             "economic_life": rng.uniform(50, 75), "soft_cost_share": rng.uniform(0.05, 0.15),256             "entrepreneurial_incentive": rng.uniform(0.0, 0.15), "land_index_share": rng.uniform(0, 1),257             "regional_level_factor": rng.uniform(0.9, 1.1), "add_on_shift": rng.uniform(-0.3, 0.3)}258        draws[i] = cost.cost_indication(d, p)["V_cost"].values259    lo, med, hi = np.percentile(draws, [10, 50, 90], axis=0)260    mc = pd.DataFrame({"V_point": base["V_cost"].values, "V_mc_median": med, "V_p10": lo, "V_p90": hi, "price": base["price"].values,261                       "assessor_total_indexed": (base["L"] + base["assessor_building_indexed"]).values}, index=b4.index)262    mc["rel_width"] = (mc["V_p90"] - mc["V_p10"]) / mc["V_mc_median"]263    mc["price_in_interval"] = (mc["price"] >= mc["V_p10"]) & (mc["price"] <= mc["V_p90"])264    mc["assessor_in_interval"] = (mc["assessor_total_indexed"] >= mc["V_p10"]) & (mc["assessor_total_indexed"] <= mc["V_p90"])265    mc.to_parquet(P / f"exp14_mc_{MAIN}.parquet")266    # variance decomposition: one-at-a-time, holding others at baseline267    oat = {}268    for name, p in [("unit cost band", {"grade_per_sqft": {k: v[1] for k, v in gs.items()}}), ("economic life 50", {"economic_life": 50}),269                    ("soft+EI high", {"soft_cost_share": 0.15, "entrepreneurial_incentive": 0.15}), ("land not indexed", {"land_index_share": 0.0}),270                    ("regional 1.10", {"regional_level_factor": 1.10})]:271        oat[name] = float(((cost.cost_indication(b4, p)["V_cost"] / base["V_cost"]) - 1).abs().median() * 100)272    d_g = b4.copy(); d_g["grade"] = d_g["grade"].map(lambda g: order[min(3, order.index(g) + 1)] if g in order else g)273    oat["grade +1"] = float(((cost.cost_indication(d_g)["V_cost"] / base["V_cost"]) - 1).abs().median() * 100)274    d_c = b4.copy(); d_c["condition"] = (d_c["condition"] + 1).clip(upper=5)275    oat["condition +1"] = float(((cost.cost_indication(d_c)["V_cost"] / base["V_cost"]) - 1).abs().median() * 100)276    R["exp14"] = {"n_draws": n_draws, "rel_width_median": float(mc["rel_width"].median()), "rel_width_p25_p75": [float(mc["rel_width"].quantile(.25)), float(mc["rel_width"].quantile(.75))],277                  "price_coverage_80": float(mc["price_in_interval"].mean()), "assessor_total_coverage_80": float(mc["assessor_in_interval"].mean()),278                  "one_at_a_time_median_abs_pct": oat}279    pd.DataFrame([R["exp14"]]).to_csv(T / "exp14_mc_summary.csv", index=False)280    pd.Series(oat, name="median_abs_delta_pct").to_csv(T / "exp14_oat.csv")281    return mc282283284# --------------------------------------------------------------------------- Exp 15: market alignment incl. recorded sales285def exp15():286    rows = []287    lv = levels(MAIN)288    smp = pd.read_parquet(P / "sample.parquet").set_index("uid")289    for lvl in LEVELS:290        out = run_level(lv[lvl])291        s = {"level": lvl, "n": len(out)}292        s.update({f"price_{k}": v for k, v in ratio_stats(out["V_cost"], out["price"]).items()})293        # Vrai Prix production estimates as external benchmarks (unknown formula for cost_est)294        for col in ["vp_est", "vp_cost_est", "vp_model_est"]:295            s.update({f"{col}_{k}": v for k, v in ratio_stats(out["V_cost"], smp.loc[out.index, col]).items() if k in ("n", "median_ratio", "mdape")})296        rows.append(s)297    # production benchmarks against price (for context)298    for col in ["vp_est", "vp_cost_est", "roll_model_est"]:299        s = {"level": f"benchmark:{col}", "n": int(smp[col].notna().sum())}300        s.update({f"price_{k}": v for k, v in ratio_stats(smp[col], smp["price"]).items()})301        rows.append(s)302    # recorded sales after listing (supplement + sample units with a later sale)303    sold_rows = []304    sales = pd.read_parquet(P / "frame_sales_after_listing.parquet").sort_values("date").drop_duplicates("uid", keep="last").set_index("uid")305    for tag, lvfile, sfile in [("sold_supplement", P / f"levels_{MAIN}_sold.parquet", P / "sample_sold.parquet"), ("sample", P / f"levels_{MAIN}.parquet", P / "sample.parquet")]:306        if not lvfile.exists():307            continue308        L = pd.read_parquet(lvfile)309        for lvl in ["B0", "B3"]:310            d = L[L.level == lvl].set_index("uid")311            d = d[d.index.isin(sales.index)]312            if len(d) == 0:313                continue314            out = run_level(d)315            sp = sales.loc[out.index, "amount"]316            r = ratio_stats(out["V_cost"], sp); r.update({"set": tag, "level": lvl, "ask_vs_sale_median": float((out["price"] / sp).median())})317            sold_rows.append(r)318    df = pd.DataFrame(rows); df.to_csv(T / "exp15_market_alignment.csv", index=False)319    ds = pd.DataFrame(sold_rows); ds.to_csv(T / "exp15_recorded_sales.csv", index=False)320    R["exp15"] = {"market": df.to_dict(orient="records"), "sales": ds.to_dict(orient="records")}321    return df322323324def main():325    R["tiers"] = TIERS; R["main_tier"] = MAIN; R["schedule_version"] = CFG["version"]326    exp5_6(); exp7_8(); exp10_calibration(); exp10_11(); exp13(); exp14(); exp15()327    json.dump(R, open(ROOT / "results" / "raw" / "exp_cost.json", "w"), indent=1, default=str)328    print(pd.read_csv(T / "exp5_levels_summary.csv")[["tier", "level", "n", "V_median", "L_share_median", "D_median", "assessor_median_ratio", "assessor_mdape", "assessor_cod", "price_median_ratio", "price_mdape", "price_within_20pct"]].round(3).to_string())329    print(pd.read_csv(T / "exp7_8_sensitivity.csv")[["scenario", "V_median", "assessor_median_ratio", "assessor_mdape", "price_median_ratio", "price_mdape"]].round(3).to_string())330    print(json.dumps(R["exp14"], indent=1)); print(pd.read_csv(T / "exp15_recorded_sales.csv").round(3).to_string())331    print(pd.read_csv(T / "exp10_calibration.csv").round(3).to_string())332    print(pd.read_csv(T / "exp15_market_alignment.csv")[[c for c in pd.read_csv(T / "exp15_market_alignment.csv").columns if c in ("level", "n", "price_median_ratio", "price_mdape", "vp_est_median_ratio", "vp_cost_est_median_ratio", "vp_cost_est_mdape", "vp_model_est_median_ratio")]].round(3).to_string())333334335if __name__ == "__main__":336    main()337