"""Decode Teranet-NB HPI chart JSON (housepriceindex.ca/_data/indx_data.json) and write long CSVs. unmunge() from hpi.js v1.9.0-2: d = round(frac*100); real = round((v-d)*100)/100 ; spc additionally *10. """ import json, sys, csv, math, datetime as dt src, downloaded_at, outdir = sys.argv[1], sys.argv[2].strip(), sys.argv[3] # src = raw/teranet_indx_data_2026-10-21.json d = json.load(open(src)) data = d["data"]; prof = d["profiles"] start = data["meta"]["start_date"]; end = data["meta"]["end_date"] y0, m0 = int(start[:4]), int(start[5:7]) def unmunge(v): if v is None or v == 0: return v dd = round((v - math.floor(v)) * 100) if not dd: return v return round((v - dd) * 100) / 100 def period(i): y = y0 + (m0 - 1 + i) // 12; m = (m0 - 1 + i) % 12 + 1 return f"{y}-{m:02d}" URL = "https://housepriceindex.ca/_data/indx_data.json?d=4e4005dc" targets = {"c11": ("Composite 11", "Canada composite of 11 CMAs"), "qc_montreal": ("Montréal", "Montréal CMA"), "qc_quebec_city": ("Québec City", "Québec CMA"), "qc_sherbrooke": ("Sherbrooke", "Sherbrooke CMA"), "qc_trois_rivieres": ("Trois-Rivières", "Trois-Rivières CMA")} series = {"indx": ("Teranet-NB HPI, not seasonally adjusted", "index"), "sa_indx": ("Teranet-NB HPI, seasonally adjusted", "index"), "spc": ("Teranet-NB HPI, sales pair count", "count")} n = len(data["indx"]["c11"]) assert period(n - 1) == end[:7], (period(n - 1), end) rows_all = [] cov = {} for key, (name, geo) in targets.items(): for skey, (sname, kind) in series.items(): arr = data[skey].get(key) if arr is None: print("missing", skey, key); continue vals = [unmunge(v) for v in arr] if skey == "spc": vals = [None if v is None else int(round(v * 10)) for v in vals] first = last = None; cnt = 0 for i, v in enumerate(vals): p = period(i) if p < "2021-01" or p > "2026-10": continue if v is None: continue rows_all.append({"source": "Teranet-National Bank House Price Index (housepriceindex.ca)", "series_name": f"{sname}", "geography": geo, "property_scope": "all residential, repeat-sales (land-registry transfers)", "frequency": "monthly", "period": p, "value": v, "base_period": "2005-06=100" if kind == "index" else "n/a (count)", "download_url": URL, "downloaded_at": downloaded_at}) cnt += 1; first = first or p; last = p cov[(name, skey)] = (first, last, cnt) # sanity: profile indx_value should equal last NSA value for key in targets: last_nsa = [unmunge(v) for v in data["indx"][key] if v is not None][-1] print(f"check {key}: profile indx_value={prof[key]['indx_value']} decoded_last={last_nsa}") cols = ["source","series_name","geography","property_scope","frequency","period","value","base_period","download_url","downloaded_at"] with open(f"{outdir}/teranet_nb_hpi_monthly.csv", "w", newline="") as f: w = csv.DictWriter(f, fieldnames=cols); w.writeheader(); w.writerows(rows_all) for k, v in cov.items(): print("coverage", k, v) print("rows", len(rows_all))