"""New homes per resident in New York, Tokyo, London, the Paris region and Toronto: the analysis behind a
SparkyData article.

    py src/build_article.py homes-per-resident

How fast New York adds homes for its population against four other big cities, now and over about thirty
years. Each city's own measure is kept and named (cards ``census_bps``, ``housing_production``,
``world_city_housing``; populations from ``world_city_population``):
- New York: homes permitted (the Census Bureau's Building Permits Survey, the five boroughs, 1990 on) and
  homes completed (the Department of City Planning's Housing Database, net of demolitions, 2010 on);
- Tokyo (Tokyo-to): housing starts from building-works notices (MLIT, 1951 on), the measure closest to a
  permit;
- London: net additional dwellings (MHCLG Table 118, financial years from 2000-01), the official measure of
  supply, completions less demolitions; the building-control starts undercount and are shown apart;
- the Paris region (Île-de-France): dwellings started and authorised (SDES, 2002 on);
- Toronto (the census metropolitan area): starts and completions (CMHC, 1972 on; population from 2001).

A rate is homes per 1,000 residents in the same year. The ten-year average (2016-2025, London's ten
financial years 2015-16 to 2024-25) is the comparison least moved by one year's rush; the current year is
compared over the same months with last year (New York and Tokyo January to September; Toronto and the Paris
region January to September). A change between two counts of homes is not a count of independent events,
so no chance range is drawn for it; it is reported as the change it is.

Writes results.json, viz.json, charts.json, data.csv and build.json into output/articles/homes_per_resident/
and site/public/articles/homes-per-resident/, and copies this script there as analysis.py. Deterministic:
no sampling, and SEED is kept for any resampling.
"""

from __future__ import annotations

import re

import pandas as pd

from article_kit import Article, lines, r, strip
from nycdata import housing_db, permits, world_housing as H, world_population as P

SLUG = "homes-per-resident"
SEED = 20261104
FIRST, LAST = 1990, 2025
DECADE = (2016, 2025)
BOROUGHS = ["Bronx", "Brooklyn", "Manhattan", "Queens", "Staten Island"]
COY = ("dcp_city_of_yes_housing_final_plan", "dcp_city_of_yes_housing_opportunity_final_plan.pdf")
SDES_NOTE = ("sdes_construction_logements_release_2026_09", "sdes_construction_logements_2026_09.html")
MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September",
               "October", "November", "December"]
#: the measure each city is compared on, and its label
MAIN = [("nyc_permits", "New York, homes permitted"), ("tokyo", "Tokyo, homes started"),
        ("toronto", "Toronto area, homes started"), ("paris", "Paris region, homes started"),
        ("london", "London, net homes added")]


def homes_by_measure() -> dict[str, pd.Series]:
    """Every yearly count, indexed by calendar year (London's financial year by the year it starts in)."""
    y = permits.yearly()
    comp = housing_db.completions().set_index("year")
    pm = H.paris_monthly()
    idf = pm[pm["area"].eq("ile_de_france")]
    tm = H.toronto_monthly()
    lq = H.london_quarterly()
    return {"nyc_permits": y[y["area"].isin(BOROUGHS)].groupby("year")["units"].sum(),
            "nyc_completions": comp["net"],
            "tokyo": H.tokyo_annual().set_index("year")["starts"],
            "toronto": H.annual(tm, "starts"), "toronto_completions": H.annual(tm, "completions"),
            "paris": H.annual(idf, "started"), "paris_authorised": H.annual(idf, "authorised"),
            "london": H.london_net().set_index("fy")["net"],
            "london_bc_starts": H.annual(lq, "starts"), "london_bc_completions": H.annual(lq, "completions")}


AREA = {"nyc_permits": "new_york", "nyc_completions": "new_york", "tokyo": "tokyo", "toronto": "toronto_cma",
        "toronto_completions": "toronto_cma", "paris": "ile_de_france", "paris_authorised": "ile_de_france",
        "london": "london", "london_bc_starts": "london", "london_bc_completions": "london"}


def main(argv: list[str] | None = None) -> int:
    """Run the analysis and write every file (no options)."""
    A = Article(SLUG, __file__, seed=SEED)
    s = homes_by_measure()
    per = {k: P.per_1000(v.astype(float), AREA[k]) for k, v in s.items()}

    # ---- the decade, every measure
    lo, hi = DECADE
    dec = {}
    for k, v in per.items():
        w = v.loc[lo - 1:hi - 1] if k == "london" else v.loc[lo:hi]
        need = 10
        dec[k] = {"per_1000": r(w.mean(), 2), "years": int(w.notna().sum()),
                  "first": int(w.index.min()), "last": int(w.index.max())}
        assert dec[k]["years"] == need, (k, dec[k])
    A.results["decade"] = dec
    order = sorted((k for k, _ in MAIN), key=lambda k: dec[k]["per_1000"])
    assert order[0] == "nyc_permits", order            # the page: New York's is the lowest
    # New York's completions, its other measure, are below every other city's main measure too
    assert all(dec["nyc_completions"]["per_1000"] < dec[k]["per_1000"] for k, _ in MAIN if k != "nyc_permits")
    A.results["decade_times"] = {"tokyo_vs_nyc": r(dec["tokyo"]["per_1000"] / dec["nyc_permits"]["per_1000"], 1)}
    ln = H.london_net().set_index("fy")
    ex = P.per_1000((ln["net"] - ln["census_adjustment"]).astype(float), "london").loc[lo - 1:hi - 1]
    A.results["london_ex_adjustment"] = r(ex.mean(), 2)
    pm_ = H.paris_monthly()
    core = {"tokyo_wards": P.per_1000(H.tokyo_wards_annual().set_index("year")["starts"].astype(float), "tokyo_wards"),
            "toronto_city": P.per_1000(H.toronto_city_annual().set_index("year")["starts"].astype(float), "toronto"),
            "paris_city": P.per_1000(H.annual(pm_[pm_["area"].eq("paris")], "started").astype(float), "paris"),
            "paris_inner": P.per_1000(H.annual(pm_[pm_["area"].eq("paris_inner")], "started").astype(float), "paris_inner")}
    A.results["core_decade"] = {k: r(v.loc[lo:hi].mean(), 1) for k, v in core.items()}
    assert all(v.loc[lo:hi].notna().sum() == 10 for v in core.values())
    early = {k: (per[k].loc[2005:2014] if k == "london" else per[k].loc[2006:2015]) for k, _ in MAIN}
    A.results["early_decade"] = {k: r(v.mean(), 2) for k, v in early.items()}
    assert min(A.results["early_decade"], key=A.results["early_decade"].get) == "nyc_permits"

    # ---- the long view: Tokyo above New York in every year since 1990
    years = list(range(FIRST, LAST + 1))
    ny, tk = per["nyc_permits"], per["tokyo"]
    assert all(tk[t] > ny[t] for t in years), "the page says Tokyo was above New York every year"
    tokyo_pop = P.population("tokyo").set_index("year")["pop"]
    revised = tokyo_pop.where(tokyo_pop.index < 2021, tokyo_pop * 0.995)        # half a percent lower from 2021 (assumed)
    tk_rev = 1000 * s["tokyo"].astype(float).reindex(revised.index).dropna() / revised
    tokyo_sensitivity = {"decade": r(tk_rev.loc[2016:2025].mean(), 1), "y2025": r(tk_rev[LAST], 1)}
    assert tokyo_sensitivity == {"decade": r(per["tokyo"].loc[2016:2025].mean(), 1), "y2025": r(tk[LAST], 1)}, tokyo_sensitivity
    A.results["tokyo_revision_sensitivity"] = {
        "population_lower_pct": 0.5, "from_year": 2021,
        "decade": r(tk_rev.loc[2016:2025].mean(), 2), "y2025": r(tk_rev[LAST], 2),
        "decade_as_read": r(per["tokyo"].loc[2016:2025].mean(), 2), "y2025_as_read": r(tk[LAST], 2),
        "assumption": True,
        "reason": ("TMG revised November 2020 to September 2026 to the 2025 census on 29 October 2026, lowering "
                   "1 January 2026 from 14,270,748 to 14,234,309 (0.26%; source tmg_population_monthly's note); the "
                   "revised table was not at a fetchable address, so 2021-2025 are lowered by 0.5%, about twice that "
                   "share, and the printed Tokyo figures are checked unchanged at one decimal")}
    ny_peak = int(ny.loc[FIRST:LAST].idxmax())
    A.results["long"] = {
        "tokyo_above_every_year": True,
        "nyc_1990s_max": r(ny.loc[1990:1999].max(), 1), "nyc_peak_year": ny_peak, "nyc_peak": r(ny[ny_peak], 1),
        "nyc_peak_units": int(s["nyc_permits"][ny_peak]), "tokyo_peak_year_rate": r(tk[ny_peak], 1),
        "nyc_2005_2008_min": r(ny.loc[2005:2008].min(), 1), "nyc_2005_2008_max": r(ny.loc[2005:2008].max(), 1),
        "paris_2005_2008_min": r(per["paris"].loc[2005:2008].min(), 1), "paris_2005_2008_max": r(per["paris"].loc[2005:2008].max(), 1),
        "nyc_2009": r(ny[2009], 1), "tokyo_1990": r(tk[1990], 1), "tokyo_2003": r(tk[2003], 1), "tokyo_2025": r(tk[LAST], 1),
        "tokyo_2025_units": int(s["tokyo"][LAST]),
        "nyc_2025": r(ny[LAST], 1), "nyc_2025_units": int(s["nyc_permits"][LAST]),
        "tokyo_vs_nyc_2025": r(tk[LAST] / ny[LAST], 1),
    }
    A.results["long"]["nyc_back_at_2005_pace"] = [int(t) for t in range(2009, LAST + 1) if ny[t] >= ny.loc[2005:2008].min()]
    assert A.results["long"]["nyc_back_at_2005_pace"] == [2015, 2022], A.results["long"]["nyc_back_at_2005_pace"]

    # ---- 2025, each city's measures
    A.results["y2025"] = {k: {"count": int(s[k][LAST]) if LAST in s[k].index else None,
                              "per_1000": r(per[k][LAST], 2) if LAST in per[k].index else None} for k in s}
    A.results["y2025"]["london"] = {"count": int(s["london"][2024]), "per_1000": r(per["london"][2024], 2),
                                    "label": "2024-25"}
    for k in ("london_bc_starts", "london_bc_completions"):     # calendar years, unlike the net count's April-to-March
        A.results["y2025"][k]["period"] = "calendar 2025 (four quarters)"
    comp = housing_db.completions().set_index("year")
    A.results["nyc_completions_2025"] = {"net": int(comp.loc[LAST, "net"]), "new_build": int(comp.loc[LAST, "new_build"]),
                                         "first_year": int(comp.index.min()),
                                         "is_highest": bool(comp["net"].idxmax() == LAST)}
    assert A.results["nyc_completions_2025"]["is_highest"]
    one = {"tokyo": per["tokyo"][LAST], "toronto": per["toronto"][LAST], "paris": per["paris"][LAST], "london": per["london"][2024]}
    A.results["y2025_main"] = {k: r(v, 1) for k, v in one.items()}
    # the page: New York's 2025 permits sat below all four other main measures; its net completions above three
    A.results["y2025_nyc_permits_lowest"] = bool(all(A.results["y2025"]["nyc_permits"]["per_1000"] < v for v in one.values()))
    assert A.results["y2025_nyc_permits_lowest"]
    assert all(per["nyc_completions"][LAST] > v for k, v in one.items() if k != "tokyo") and per["nyc_completions"][LAST] < one["tokyo"]
    py = housing_db.permit_years(LAST)
    A.results["nyc_completions_2025"]["permitted_2022_pct"] = r(100 * py.get(2022, 0.0), 0)
    A.results["nyc_completions_2025"]["known_permit_year_pct"] = r(100 * housing_db.permit_year_coverage(LAST), 1)
    mt_ = H.tokyo_monthly()
    m25 = mt_[mt_["year"] == LAST].set_index("month")["starts"]
    A.results["tokyo_march_2025"] = {"starts": int(m25[3]), "median_other_months": int(m25.drop(3).median())}

    # ---- this year, the same months as last year
    tm, mt = H.toronto_monthly(), H.tokyo_monthly()
    pm = H.paris_monthly()
    idf = pm[pm["area"].eq("ile_de_france")]
    p26, p25 = permits.so_far(2026, 9), permits.so_far(2025, 9)
    now = {
        "nyc": {"months": 9, "now": int(p26[p26["area"].isin(BOROUGHS)]["units"].sum()),
                "before": int(p25[p25["area"].isin(BOROUGHS)]["units"].sum()), "area": "new_york"},
        "tokyo": {"months": 9, "now": int(H.so_far(mt, "starts", 9)[2026]), "before": int(H.so_far(mt, "starts", 9)[2025]),
                  "area": "tokyo"},
        "toronto": {"months": 9, "now": int(H.so_far(tm, "starts", 9)[2026]), "before": int(H.so_far(tm, "starts", 9)[2025]),
                    "area": "toronto_cma"},
        "paris": {"months": 9, "now": int(H.so_far(idf, "started", 9)[2026]), "before": int(H.so_far(idf, "started", 9)[2025]),
                  "area": "ile_de_france"},
    }
    for k, v in now.items():
        pop = P.population(v["area"]).set_index("year")["pop"]
        v["pop_year"] = int(2026 if 2026 in pop.index else pop.index.max())      # 2025 except the Paris region's 1 January 2026
        p = pop[v["pop_year"]]
        v["change_pct"] = r(100 * (v["now"] / v["before"] - 1), 0)
        v["per_1000"] = r(1000 * v["now"] / p, 2)
    lq = H.london_quarterly().set_index(["year", "quarter"])["starts"]
    now["london_bc"] = {"months": 6, "now": int(lq.loc[(2026, 1)] + lq.loc[(2026, 2)]),
                        "before": int(lq.loc[(2025, 1)] + lq.loc[(2025, 2)])}
    A.results["now"] = now
    lq4 = H.london_quarterly().set_index(["year", "quarter"])["completions"]
    ln = H.london_net().set_index("fy")
    A.results["london_undercount"] = {"label": "2024-25", "period": "April 2024 to March 2025, both counts", "new_build": int(round(ln.loc[2024, "new_build"], -1)),
                                      "bc_completions": int(lq4.loc[(2024, 2)] + lq4.loc[(2024, 3)] + lq4.loc[(2024, 4)] + lq4.loc[(2025, 1)])}
    A.results["now_times"] = {"tokyo_vs_nyc": r(now["tokyo"]["per_1000"] / now["nyc"]["per_1000"], 1),
                              "nyc_lead_over_toronto_pct": r(100 * (now["nyc"]["per_1000"] / now["toronto"]["per_1000"] - 1), 0)}
    assert now["nyc"]["per_1000"] > now["toronto"]["per_1000"] and now["nyc"]["per_1000"] < now["paris"]["per_1000"]
    A.latest("2026-09", "Census Bureau permits, MLIT, CMHC and SDES starts, January to September 2026 (the same months as 2025)")
    A.long_view(FIRST, LAST, "New York's homes permitted and Tokyo's homes started per 1,000 residents, 1990-2025 "
                "(Toronto from 2001, the Paris region from 2002, London from 2000-01)")

    # ---- the documents
    Q = A.quotes()
    quotes = {}
    for key, needle in {"coy_adopted": "On December 5, the City Council approved City of Yes for Housing Opportunity with modifications",
                        "coy_82000": "Enables the creation of 82,000 new homes over the next 15 years"}.items():
        q = Q.quote(COY[0], COY[1], needle)
        quotes[key] = {"quote": q["quote"], "page": q.get("page"), "source": q.get("source")}
    for key, needle in {"sitadel3": "À compter de mars 2026, les données sur la construction neuve de logements sont produites "
                                    "à partir du dispositif Sitadel3, qui remplace Sitadel2"}.items():
        q = Q.quote(SDES_NOTE[0], SDES_NOTE[1], needle)
        quotes[key] = {"quote": q["quote"], "page": q.get("page"), "source": q.get("source")}
    A.results["quotes"] = quotes
    A.results["history"] = {"coy_homes": int(re.search(r"([\d,]+) new homes", quotes["coy_82000"]["quote"]).group(1).replace(",", "")),
                            "coy_years": int(re.search(r"next (\d+) years", quotes["coy_82000"]["quote"]).group(1))}

    # ---- the reproduction: the figures the agencies published
    A.reproduce("Tokyo homes started, 2025 (MLIT annual table)", published=122130, published_moe90=None,
                reproduced=int(s["tokyo"][LAST]), kind="count", tolerance=0)
    A.reproduce("Tokyo homes started, 2025 (the twelve monthly tables added up)", published=int(s["tokyo"][LAST]),
                published_moe90=None, reproduced=int(H.annual(mt, "starts")[LAST]), kind="count", tolerance=0)
    A.reproduce("London net additional dwellings, 2024-25 (Table 118, rounded)", published=32680, published_moe90=None,
                reproduced=int(round(s["london"][2024], -1)), kind="count", tolerance=0)
    A.reproduce("Toronto area homes started, 2025 (CMHC)", published=26087, published_moe90=None,
                reproduced=int(s["toronto"][LAST]), kind="count", tolerance=0)
    A.results["reproduction_source"] = ("MLIT's annual table; MHCLG Table 118's rounded release; CMHC's 2025 total as "
                                        "Statistics Canada's table gives it")
    A.results["reproduction_note"] = "Administrative counts: the files must give the published totals exactly."

    # ---- printed figures
    D = A.results["decade"]
    for k in ("nyc_permits", "nyc_completions", "tokyo", "toronto", "paris", "london", "london_bc_starts"):
        A.printed(f"decade.{k}.per_1000", f"{D[k]['per_1000']:.1f}")
    A.printed("decade_times.tokyo_vs_nyc", f"{A.results['decade_times']['tokyo_vs_nyc']:.1f}")
    A.printed("decade.paris_authorised.per_1000", f"{D['paris_authorised']['per_1000']:.1f}")
    A.printed("london_ex_adjustment", f"{A.results['london_ex_adjustment']:.1f}")
    for k in ("tokyo_wards", "toronto_city", "paris_city", "paris_inner"):
        A.printed(f"core_decade.{k}", f"{A.results['core_decade'][k]:.1f}")
    A.printed("early_decade.nyc_permits", f"{A.results['early_decade']['nyc_permits']:.1f}")
    A.printed("nyc_completions_2025.permitted_2022_pct", f"{A.results['nyc_completions_2025']['permitted_2022_pct']:.0f}%")
    Lg = A.results["long"]
    for k in ("nyc_peak", "tokyo_peak_year_rate", "nyc_2005_2008_min", "nyc_2005_2008_max", "paris_2005_2008_min",
              "paris_2005_2008_max", "nyc_2009", "tokyo_1990", "tokyo_2025", "nyc_2025", "nyc_1990s_max",
              "tokyo_vs_nyc_2025"):
        A.printed(f"long.{k}", f"{Lg[k]:.1f}")
    A.printed("long.nyc_peak_units", f"{Lg['nyc_peak_units']:,}")
    A.printed("long.nyc_2025_units", f"{Lg['nyc_2025_units']:,}")
    A.printed("long.tokyo_2025_units", f"{Lg['tokyo_2025_units']:,}")
    Y = A.results["y2025"]
    A.printed("y2025.nyc_completions.per_1000", f"{Y['nyc_completions']['per_1000']:.1f}")
    A.printed("y2025.toronto.per_1000", f"{Y['toronto']['per_1000']:.1f}")
    A.printed("y2025.paris.per_1000", f"{Y['paris']['per_1000']:.1f}")
    A.printed("nyc_completions_2025.net", f"{A.results['nyc_completions_2025']['net']:,}")
    N = A.results["now"]
    for k in ("nyc", "tokyo", "toronto", "paris"):
        A.printed(f"now.{k}.change_pct", f"{abs(N[k]['change_pct']):.0f}%")
    for k in ("nyc", "tokyo", "toronto", "paris"):
        A.printed(f"now.{k}.now", f"{N[k]['now']:,}")
        A.printed(f"now.{k}.before", f"{N[k]['before']:,}")
    A.printed("now_times.nyc_lead_over_toronto_pct", f"{A.results['now_times']['nyc_lead_over_toronto_pct']:.0f}%")
    A.printed("tokyo_march_2025.starts", f"{A.results['tokyo_march_2025']['starts']:,}")
    A.printed("tokyo_march_2025.median_other_months", f"{A.results['tokyo_march_2025']['median_other_months']:,}")
    A.printed("nyc_completions_2025.known_permit_year_pct", f"{A.results['nyc_completions_2025']['known_permit_year_pct']:.1f}%")
    # the page: 2025's permits were fewer than in any year from 2022 to 2024
    assert all(s["nyc_permits"][LAST] < s["nyc_permits"][t] for t in (2022, 2023, 2024))
    A.printed("now.nyc.per_1000", f"{N['nyc']['per_1000']:.1f}")
    A.printed("now.toronto.per_1000", f"{N['toronto']['per_1000']:.1f}")
    A.printed("now.paris.per_1000", f"{N['paris']['per_1000']:.1f}")
    A.printed("now.tokyo.per_1000", f"{N['tokyo']['per_1000']:.1f}")
    A.printed("now_times.tokyo_vs_nyc", f"{A.results['now_times']['tokyo_vs_nyc']:.1f}")
    A.printed("london_undercount.new_build", f"{A.results['london_undercount']['new_build']:,}")
    A.printed("london_undercount.bc_completions", f"{A.results['london_undercount']['bc_completions']:,}")
    A.printed("history.coy_homes", f"{A.results['history']['coy_homes']:,}")
    A.printed("history.coy_years", f"{A.results['history']['coy_years']}")
    A.results["per_base"] = 1000
    A.printed("per_base", "1,000")

    # ---- charts
    def cell(k):
        v = per[k]
        if k == "london":
            return {str(t + 1): r(v[t], 2) for t in v.index if FIRST <= t + 1 <= LAST}   # a financial year at the year it ends
        return {str(t): r(v[t], 2) for t in v.index if FIRST <= t <= LAST}
    A.chart("long_view", lines(
        title="Tokyo started more homes per resident than New York permitted in every year since 1990",
        subtitle="New homes a year per 1,000 residents, each city's own measure",
        x=[[str(t), str(t)] for t in years],
        series=[[k, label, label.split(",")[0]] for k, label in MAIN],
        cells={"": {k: cell(k) for k, _ in MAIN}}, format="dec1", axis="New homes per 1,000 residents",
        x_ticks=[str(t) for t in years if t % 5 == 0 or t == LAST],
        note="New York: homes in buildings permitted (Census Bureau). Tokyo: homes started, from the notices owners file "
             "(MLIT). Toronto: homes started in the metropolitan area, from 2001 (CMHC). Paris region: homes started in "
             "Île-de-France, from 2002 (SDES). London: net additional homes (completions less demolitions), each financial "
             "year (April to March) drawn at the year it ends, from 2000-01 (MHCLG).",
        provenance="Census Bureau; MLIT; CMHC via Statistics Canada; SDES; MHCLG; each country's population estimates"))

    dec_rows = [("nyc_permits", "New York, permitted", "NYC permitted"), ("nyc_completions", "New York, completed (net)", "NYC completed"),
                ("london", "London, net added", "London net"), ("paris", "Paris region, started", "Paris region"),
                ("toronto", "Toronto area, started", "Toronto area"), ("tokyo", "Tokyo, started", "Tokyo")]
    early = {k: per[k].loc[2005:2014] if k == "london" else per[k].loc[2006:2015] for k, _, _ in dec_rows}
    A.chart("decade", strip(
        title="New York added the fewest homes per resident in both decades",
        subtitle="New homes a year per 1,000 residents, averaged over each decade",
        x=[["2006-2015", "2006 to 2015"], ["2016-2025", "2016 to 2025"]],
        series=[[k, label, short] for k, label, short in dec_rows],
        cells={"": {k: {**({"2006-2015": r(early[k].mean(), 2)} if early[k].notna().sum() == 10 else {}),
                        "2016-2025": D[k]["per_1000"]} for k, _, _ in dec_rows}},
        format="dec1", axis="New homes per 1,000 residents",
        note="Each city's own measure (the measures table sets them side by side). New York's completions start in 2010, so their earlier "
             "decade is left out; London's decades are financial years ending in each span.",
        provenance="Census Bureau; NYC Department of City Planning; MHCLG; SDES; CMHC; MLIT"))

    A.chart("this_year", strip(
        title="New York's permits nearly doubled in the first nine months of 2026",
        subtitle="New homes in January to September of 2025 and 2026",
        x=[["2025", "2025"], ["2026", "2026"]],
        series=[["nyc", "New York, permitted", "New York"], ["tokyo", "Tokyo, started", "Tokyo"],
                ["toronto", "Toronto area, started", "Toronto"], ["paris", "Paris region, started", "Paris region"]],
        cells={"": {k: {"2025": N[k]["before"], "2026": N[k]["now"]} for k in ("nyc", "tokyo", "toronto", "paris")}},
        format="num", axis="New homes",
        note="Counts, not rates: the cities differ in size. New York's 2026 permits are preliminary. Tokyo's 2025 includes an "
             "unusually large March. The Paris region's 2026 comes from the ministry's new system (Sitadel3) from March, "
             "which slightly revised the published figures.",
        provenance="Census Bureau; MLIT; CMHC via Statistics Canada; SDES"))

    rows_out = []
    for k, v in s.items():
        for t, n in v.items():
            pr = per[k].get(t)
            rows_out.append({"measure": k, "year": int(t), "homes": float(n), "per_1000": None if pr is None else r(pr, 3)})
    A.write(data=pd.DataFrame(rows_out))
    print(f"{SLUG}: wrote {A.out}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
