TeX 55.1%
Python 44.2%
1"""Decode Teranet-NB HPI chart JSON (housepriceindex.ca/_data/indx_data.json) and write long CSVs.2unmunge() from hpi.js v1.9.0-2: d = round(frac*100); real = round((v-d)*100)/100 ; spc additionally *10.3"""4import json, sys, csv, math, datetime as dt5src, downloaded_at, outdir = sys.argv[1], sys.argv[2].strip(), sys.argv[3] # src = raw/teranet_indx_data_2026-10-21.json6d = json.load(open(src))7data = d["data"]; prof = d["profiles"]8start = data["meta"]["start_date"]; end = data["meta"]["end_date"]9y0, m0 = int(start[:4]), int(start[5:7])1011def unmunge(v):12 if v is None or v == 0:13 return v14 dd = round((v - math.floor(v)) * 100)15 if not dd:16 return v17 return round((v - dd) * 100) / 1001819def period(i):20 y = y0 + (m0 - 1 + i) // 12; m = (m0 - 1 + i) % 12 + 121 return f"{y}-{m:02d}"2223URL = "https://housepriceindex.ca/_data/indx_data.json?d=4e4005dc"24targets = {"c11": ("Composite 11", "Canada composite of 11 CMAs"),25 "qc_montreal": ("Montréal", "Montréal CMA"),26 "qc_quebec_city": ("Québec City", "Québec CMA"),27 "qc_sherbrooke": ("Sherbrooke", "Sherbrooke CMA"),28 "qc_trois_rivieres": ("Trois-Rivières", "Trois-Rivières CMA")}29series = {"indx": ("Teranet-NB HPI, not seasonally adjusted", "index"),30 "sa_indx": ("Teranet-NB HPI, seasonally adjusted", "index"),31 "spc": ("Teranet-NB HPI, sales pair count", "count")}32n = len(data["indx"]["c11"])33assert period(n - 1) == end[:7], (period(n - 1), end)34rows_all = []35cov = {}36for key, (name, geo) in targets.items():37 for skey, (sname, kind) in series.items():38 arr = data[skey].get(key)39 if arr is None:40 print("missing", skey, key); continue41 vals = [unmunge(v) for v in arr]42 if skey == "spc":43 vals = [None if v is None else int(round(v * 10)) for v in vals]44 first = last = None; cnt = 045 for i, v in enumerate(vals):46 p = period(i)47 if p < "2021-01" or p > "2026-10":48 continue49 if v is None:50 continue51 rows_all.append({"source": "Teranet-National Bank House Price Index (housepriceindex.ca)",52 "series_name": f"{sname}", "geography": geo,53 "property_scope": "all residential, repeat-sales (land-registry transfers)",54 "frequency": "monthly", "period": p, "value": v,55 "base_period": "2005-06=100" if kind == "index" else "n/a (count)",56 "download_url": URL, "downloaded_at": downloaded_at})57 cnt += 1; first = first or p; last = p58 cov[(name, skey)] = (first, last, cnt)59# sanity: profile indx_value should equal last NSA value60for key in targets:61 last_nsa = [unmunge(v) for v in data["indx"][key] if v is not None][-1]62 print(f"check {key}: profile indx_value={prof[key]['indx_value']} decoded_last={last_nsa}")63cols = ["source","series_name","geography","property_scope","frequency","period","value","base_period","download_url","downloaded_at"]64with open(f"{outdir}/teranet_nb_hpi_monthly.csv", "w", newline="") as f:65 w = csv.DictWriter(f, fieldnames=cols); w.writeheader(); w.writerows(rows_all)66for k, v in cov.items(): print("coverage", k, v)67print("rows", len(rows_all))68