#!/usr/bin/env python3 # Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca """Baseline feature matrix with as-of comparable summaries. python3 scripts/03_features.py [--synthetic] [--no-geo] → data/{processed|synthetic}/feature_matrix.parquet (+ results/data/feature_matrix_summary*.json). Geo features are joined when data//geo_features.parquet exists (run scripts/04_geo.py first). """ from __future__ import annotations import argparse import json import time import polars as pl from agentic_valuation.config import PROCESSED, RESULTS, SYNTHETIC from agentic_valuation.features.matrix import build_feature_matrix, feature_columns def main() -> None: ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--synthetic", action="store_true") ap.add_argument("--no-geo", action="store_true", help="ignore geo_features.parquet even if present") args = ap.parse_args() base = SYNTHETIC if args.synthetic else PROCESSED t0 = time.perf_counter() analytic = pl.read_parquet(base / "analytic.parquet") splits = pl.read_parquet(base / "splits.parquet") geo = None geo_path = base / "geo_features.parquet" if geo_path.exists() and not args.no_geo: geo = pl.read_parquet(geo_path) fm = build_feature_matrix(analytic, splits, geo=geo, comps_summary=True) fm.write_parquet(base / "feature_matrix.parquet", compression="zstd") num, cat = feature_columns(fm) summary = { "synthetic": args.synthetic, "rows": fm.height, "by_split": {k: int(v) for k, v in fm.group_by("split").len().iter_rows()}, "numeric_features": num, "categorical_features": cat, "geo_joined": geo is not None, "geo_rows": None if geo is None else geo.height, "missingness_pct": {c: round(100.0 * fm[c].null_count() / fm.height, 3) for c in num + cat}, "nb_n_12m_quantiles": {q: float(fm["nb_n_12m"].quantile(q)) for q in (0.05, 0.25, 0.5, 0.75, 0.95)}, "nb_mean_dist_km_median": float(fm["nb_mean_dist_km"].median()), "seconds": round(time.perf_counter() - t0, 2), } out = RESULTS / "data" / ("feature_matrix_summary_synthetic.json" if args.synthetic else "feature_matrix_summary.json") out.parent.mkdir(parents=True, exist_ok=True) out.write_text(json.dumps(summary, indent=1, ensure_ascii=False)) print(json.dumps({k: summary[k] for k in ("rows", "by_split", "geo_joined", "nb_n_12m_quantiles", "nb_mean_dist_km_median", "seconds")}, ensure_ascii=False, indent=1)) if __name__ == "__main__": main()