"""Download Statistics Canada benchmarks via the WDS API (public, no key). - 18-10-0004-01 CPI monthly NSA: Quebec (geo 11), Montréal (13), Canada (2) × food aggregates (members 3,4,5,21,22,28,29,30,34,39,49,58) - 18-10-0245-01 Monthly average retail prices for selected products: Quebec, all 110 products Raw JSON kept in data/raw/statcan/ with download timestamp; tidy CSVs in data/processed/. """ from __future__ import annotations import json, time, requests, pandas as pd from src.common import ROOT, path, get_logger, ledger log = get_logger("statcan") WDS = "https://www150.statcan.gc.ca/t1/wds/rest/" RAW = ROOT / "data" / "raw" / "statcan"; RAW.mkdir(parents=True, exist_ok=True) def coords(product_id: int, pairs: list[tuple[int, int]], periods: int) -> pd.DataFrame: body = [{"productId": product_id, "coordinate": f"{g}.{p}.0.0.0.0.0.0.0.0", "latestN": periods} for g, p in pairs] r = requests.post(WDS + "getDataFromCubePidCoordAndLatestNPeriods", json=body, timeout=120); r.raise_for_status() (RAW / f"{product_id}_{int(time.time())}.json").write_text(r.text) rows = [] for item in r.json(): if item["status"] != "SUCCESS": log.warning("WDS item failed: %s", item); continue o = item["object"] for dp in o["vectorDataPoint"]: rows.append(dict(productId=product_id, coordinate=o["coordinate"], vectorId=o["vectorId"], ref_date=dp["refPer"], value=dp["value"], scalar=dp["scalarFactorCode"], status=dp.get("statusCode"))) return pd.DataFrame(rows) def main() -> None: meta = {} for pid in (18100004, 18100245): r = requests.post(WDS + "getCubeMetadata", json=[{"productId": pid}], timeout=120); r.raise_for_status() meta[pid] = r.json()[0]["object"]; (RAW / f"meta_{pid}.json").write_text(r.text) # CPI m = meta[18100004]; geo = {x["memberId"]: x["memberNameEn"] for x in m["dimension"][0]["member"]} prodn = {x["memberId"]: x["memberNameEn"] for x in m["dimension"][1]["member"]} food_members = [2, 3, 4, 5, 16, 21, 22, 28, 29, 30, 34, 39, 49, 58] cpi = coords(18100004, [(g, p) for g in (2, 11, 13) for p in food_members], periods=60) cpi["geo"] = cpi.coordinate.str.split(".").str[0].astype(int).map(geo) cpi["product"] = cpi.coordinate.str.split(".").str[1].astype(int).map(prodn) cpi.to_csv(path("processed") / "statcan_cpi_food.csv", index=False) log.info("CPI: %d points, %s → %s, latest release %s", len(cpi), cpi.ref_date.min(), cpi.ref_date.max(), m.get("releaseTime")) # Average retail prices m2 = meta[18100245]; geo2 = {x["memberId"]: x["memberNameEn"] for x in m2["dimension"][0]["member"]} prod2 = {x["memberId"]: x["memberNameEn"] for x in m2["dimension"][1]["member"]} qc = [k for k, v in geo2.items() if v == "Quebec"][0] arp = coords(18100245, [(qc, p) for p in prod2], periods=36) arp["geo"] = "Quebec"; arp["product"] = arp.coordinate.str.split(".").str[1].astype(int).map(prod2) arp.to_csv(path("processed") / "statcan_avg_retail_prices_qc.csv", index=False) log.info("Average retail prices QC: %d points, %d products, %s → %s", len(arp), arp["product"].nunique(), arp.ref_date.min(), arp.ref_date.max()) ledger("1", "statcan", f"CPI {len(cpi)} pts to {cpi.ref_date.max()}; ARP {len(arp)} pts to {arp.ref_date.max()}") if __name__ == "__main__": main()