# -*- coding: utf-8 -*-
"""build_diagrams.py — REQ-SYS-13. Every process and structure gets a diagram (GOV-F5.1)."""
import os, sys
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
import model_params as M
import model_agedcare as AC

OUT = os.path.join(os.path.dirname(__file__), "..", "02_Work", "diagrams")
os.makedirs(OUT, exist_ok=True)
R = M.results()
A = AC.results()
STR = A["structure"]

INK   = "#1B2A38"
BLUE  = "#1F5C8B"
TEAL  = "#2E8B8B"
AMBER = "#C77B1F"
RED   = "#B03A2E"
GREEN = "#2E7D4F"
GREY  = "#8A98A5"
LIGHT = "#EDF2F5"

plt.rcParams.update({
    "font.family": "DejaVu Sans", "font.size": 9,
    "axes.edgecolor": GREY, "axes.labelcolor": INK, "text.color": INK,
    "xtick.color": INK, "ytick.color": INK, "axes.titlesize": 11, "axes.titleweight": "bold",
})

# Matplotlib treats $...$ as mathtext. Every dollar figure in these diagrams would be
# silently italicised and mangled. Escape globally, once, at the Text layer.
from matplotlib.text import Text as _T
_orig_set_text = _T.set_text
def _escaped_set_text(self, s):
    if isinstance(s, str) and "$" in s:
        s = s.replace("\\$", "$").replace("$", "\\$")
    return _orig_set_text(self, s)
_T.set_text = _escaped_set_text


def save(fig, name):
    p = os.path.join(OUT, name)
    fig.savefig(p, dpi=170, bbox_inches="tight", facecolor="white")
    plt.close(fig)
    print("wrote", os.path.abspath(p))

def box(ax, x, y, w, h, text, fc, tc="white", fs=8.5, weight="bold"):
    ax.add_patch(FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.012,rounding_size=0.02",
                                facecolor=fc, edgecolor="none"))
    ax.text(x + w / 2, y + h / 2, text, ha="center", va="center", color=tc,
            fontsize=fs, fontweight=weight, wrap=True, linespacing=1.35)

def arrow(ax, p1, p2, color=GREY, style="-|>", lw=1.4, ls="-"):
    ax.add_patch(FancyArrowPatch(p1, p2, arrowstyle=style, mutation_scale=11,
                                 color=color, lw=lw, linestyle=ls,
                                 shrinkA=2, shrinkB=2, connectionstyle="arc3,rad=0"))

def blank(figsize):
    fig, ax = plt.subplots(figsize=figsize)
    ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.axis("off")
    return fig, ax


# ---------------------------------------------------------------- D1 critical path
def d1():
    fig, ax = blank((10.4, 5.0))
    ax.set_title("D1  Critical path: incorporation to first NDIS revenue (Victoria, core supports)",
                 loc="left", pad=12)
    lanes = [("SET UP", 0.80, BLUE), ("REGISTER", 0.56, AMBER), ("STAFF UP", 0.32, TEAL), ("TRADE", 0.08, GREEN)]
    for label, y, c in lanes:
        ax.add_patch(FancyBboxPatch((0.005, y - 0.005), 0.075, 0.17,
                                    boxstyle="round,pad=0.004,rounding_size=0.012",
                                    facecolor=LIGHT, edgecolor="none"))
        ax.text(0.042, y + 0.08, label, ha="center", va="center", fontsize=7.5,
                fontweight="bold", color=c, rotation=90)
    n = {}
    def add(k, x, y, w, t, c, fs=7.6):
        box(ax, x, y, w, 0.16, t, c, fs=fs); n[k] = (x, y, w, 0.16)
    add("a", 0.10, 0.80, 0.15, "Register Pty Ltd\nwith ASIC\n$636 . same day", BLUE)
    add("b", 0.28, 0.80, 0.15, "ABN, GST, PAYG\nfree . instant to\n20 business days", BLUE)
    add("c", 0.46, 0.80, 0.15, "myID Strong + RAM\nfree . PRODA closes\n30 Sep 2026", BLUE)
    add("d", 0.64, 0.80, 0.15, "Insurance:\nPL, PI, ML\n$1.2k-$3.5k / yr", BLUE)
    add("e", 0.10, 0.56, 0.15, "Buy policy and\nprocedure manual\n$649-$1,825", AMBER)
    add("f", 0.28, 0.56, 0.15, "Lodge application\nin the Commission\nportal . 60-day limit", AMBER)
    add("g", 0.46, 0.56, 0.15, "Engage Approved\nQuality Auditor\n$3.5k-$6k (quote only)", AMBER)
    add("h", 0.64, 0.56, 0.15, "Verification audit\nand Commission\nreview", AMBER)
    add("i", 0.82, 0.56, 0.155, "CERTIFICATE\n4-6 months\nend to end", RED)
    add("j", 0.10, 0.32, 0.15, "NDIS Worker\nScreening (VIC)\n$139.20 . 3 weeks", TEAL)
    add("k", 0.28, 0.32, 0.15, "First aid + CPR\n$290 per worker", TEAL)
    add("l", 0.46, 0.32, 0.15, "Worker Orientation\nModule . free", TEAL)
    add("m", 0.64, 0.32, 0.15, "CRM, rostering,\npayroll live\n$123-$268 / mo", TEAL)
    add("n", 0.10, 0.08, 0.185, "TRADE UNREGISTERED\nplan-managed and\nself-managed only", GREEN)
    add("o", 0.33, 0.08, 0.175, "Build referral relations:\nsupport coordinators,\nplan managers", GREEN)
    add("p", 0.545, 0.08, 0.16, "First service\nagreement signed", GREEN)
    add("q", 0.745, 0.08, 0.19, "Invoice paid ~14 days\nlater . WAGES FALL DUE\nFIRST", RED)
    def rt(k): x, y, w, h = n[k]; return (x + w, y + h / 2)
    def lt(k): x, y, w, h = n[k]; return (x, y + h / 2)
    def bt(k): x, y, w, h = n[k]; return (x + w / 2, y)
    def tp(k): x, y, w, h = n[k]; return (x + w / 2, y + h)
    for a, b in [("a","b"),("b","c"),("c","d"),("e","f"),("f","g"),("g","h"),("h","i"),
                 ("j","k"),("k","l"),("l","m"),("n","o"),("o","p"),("p","q")]:
        arrow(ax, rt(a), lt(b))
    arrow(ax, bt("a"), tp("e"), color=BLUE)
    arrow(ax, bt("c"), tp("f"), color=BLUE)
    arrow(ax, bt("d"), tp("h"), color=BLUE)
    arrow(ax, bt("j"), tp("n"), color=TEAL)
    arrow(ax, (0.90, 0.56), (0.90, 0.25), color=RED, ls="--")
    ax.text(0.905, 0.40, "Registration unlocks\nagency-managed\nparticipants", fontsize=7,
            color=RED, va="center", ha="left", fontweight="bold")
    ax.text(0.10, 0.005,
            "Solid arrows are sequence. Dashed is an unlock, not a dependency: trading to plan-managed and "
            "self-managed participants can begin before the certificate arrives.\n"
            "Sources: SRC-021 (4-6 month verification pathway), SRC-023 (60-day application limit), "
            "SRC-025 to SRC-041. Figures current at 20 Aug 2026.",
            fontsize=6.6, color=GREY, va="top")
    save(fig, "D1_critical_path.png")


# ---------------------------------------------------------------- D2 who can you serve
def d2():
    fig, ax = blank((10.0, 4.8))
    ax.set_title("D2  Which participants you may serve, and what changes in July 2027", loc="left", pad=12)
    box(ax, 0.335, 0.855, 0.33, 0.115,
        "An NDIS participant's budget is\nmanaged in one of three ways", LIGHT, tc=INK, fs=8.4)
    cols = [(0.030, "SELF-MANAGED\nThe participant pays\nyou directly", GREEN),
            (0.355, "PLAN-MANAGED\nA plan manager pays\nyour invoice", GREEN),
            (0.680, "AGENCY-MANAGED\nYou claim from the\nNDIA portal", RED)]
    for x, t, c in cols:
        box(ax, x, 0.630, 0.29, 0.155, t, c, fs=8.2)
        arrow(ax, (0.50, 0.855), (x + 0.145, 0.785))
    box(ax, 0.030, 0.415, 0.615, 0.165,
        "AN UNREGISTERED PROVIDER MAY SERVE THESE TWO TODAY\n"
        "No audit. No Commission application. No certificate.\nYou can begin trading in month one.", GREEN, fs=8.4)
    box(ax, 0.680, 0.415, 0.29, 0.165,
        "REGISTRATION REQUIRED\nAlso required for SDA and\nfor regulated services such\nas behaviour support", RED, fs=7.9)
    box(ax, 0.030, 0.170, 0.940, 0.185,
        "FROM JULY 2027 THE UNREGISTERED ROUTE INTO CORE SUPPORTS CLOSES\n"
        "The NDIS Amendment (Securing the NDIS for Future Generations) Bill 2026, passed 19 August 2026,\n"
        "extends mandatory registration to personal care, daily living supports and closed settings.\n"
        "Support coordination is excluded for now: the Commission states its mandatory registration\n"
        "'has been paused while we consider further reform'.", AMBER, fs=8.0)
    ax.text(0.030, 0.115,
            "Already in force: mandatory registration for Supported Independent Living and NDIS digital platform providers "
            "commenced 1 July 2026,\nwith a 1 October 2026 deadline for providers already operating. Whether that grace "
            "period reaches a NEW entrant is not confirmed (ASM-009, ACT-005).\n"
            "Sources: SRC-019, SRC-014, SRC-015, SRC-018, SRC-016, SRC-017. Verified against government sources on 20 Aug 2026.",
            fontsize=6.9, color=GREY, va="top")
    save(fig, "D2_who_you_can_serve.png")


# ---------------------------------------------------------------- D3 margin waterfall
def d3():
    fig, ax = plt.subplots(figsize=(9.4, 4.6))
    price = M.INPUTS["price_selfcare_wd"][0]
    wage  = M.INPUTS["wage_sacs_l2_casual"][0]
    sup   = wage * 0.12; wc = wage * 0.018; lsl = wage * 0.0165
    admin = R["loaded_wage_l3"] * 0.25
    labels = ["NDIS price limit\nweekday daytime", "Casual wage\nSACS Level 2",
              "Super\n12.00%", "WorkCover\n1.8%", "Portable LSL\n1.65%",
              "GROSS\nMARGIN", "Paid admin\n0.25 hr", "WHAT IS\nLEFT"]
    vals = [price, -wage, -sup, -wc, -lsl, None, -admin, None]
    run, bottoms, heights, colors = 0.0, [], [], []
    for i, vv in enumerate(vals):
        if vv is None:
            bottoms.append(0); heights.append(run)
            colors.append(GREEN if i == 5 else (RED if run < 8 else AMBER))
        elif vv > 0:
            bottoms.append(0); heights.append(vv); colors.append(BLUE); run = vv
        else:
            bottoms.append(run + vv); heights.append(-vv); colors.append(GREY); run += vv
    ax.bar(range(len(labels)), heights, bottom=bottoms, color=colors, width=0.62)
    for i, (b, h) in enumerate(zip(bottoms, heights)):
        val = h if vals[i] is None or vals[i] > 0 else -h
        ax.text(i, b + h + 1.4, ("$%.2f" % val) if val >= 0 else ("-$%.2f" % abs(val)),
                ha="center", fontsize=8.4, fontweight="bold",
                color=GREEN if (vals[i] is None and i == 5) else (RED if (vals[i] is None and i == 7) else INK))
    ax.set_xticks(range(len(labels)))
    ax.set_xticklabels(labels, fontsize=7.4)
    ax.set_ylabel("AUD per billable hour")
    ax.set_ylim(0, price * 1.16)
    ax.set_title("D3  Where one hour of core support revenue actually goes", loc="left", pad=12)
    ax.spines[["top", "right"]].set_visible(False)
    ax.grid(axis="y", color=LIGHT, lw=1)
    ax.set_axisbelow(True)
    fig.text(0.008, -0.10,
             "Read the last two bars together. $21.30 an hour looks like a business. Pay someone other than yourself to do "
             "the rostering, notes and invoicing at a\nquarter of an hour per billable hour and $14.61 of it is gone. At "
             "0.40 hr the margin turns negative. This is the arithmetic behind the sector finding that about half of NDIS\n"
             "providers were loss-making in 2024-25 (SRC-046). Price is not the problem. Administration is.\n"
             "Sources: SRC-002 (price), SRC-009 (wage), SRC-011, SRC-012, SRC-013 (on-costs), ASM-008 (admin ratio, Low confidence).",
             fontsize=6.9, color=GREY, va="top")
    save(fig, "D3_margin_waterfall.png")


# ---------------------------------------------------------------- D4 cash curves
def d4():
    fig, ax = plt.subplots(figsize=(9.4, 4.6))
    months = list(range(1, 13))
    series = [
        ("Base case: first client month 4, you do the admin unpaid", R["base_rows"], GREEN, "-"),
        ("Slow start: first client month 7, you do the admin unpaid", R["slow_rows"], AMBER, "-"),
        ("Base ramp but administration is paid at 0.25 hr per billable hour", R["paid_rows"], RED, "-"),
    ]
    for label, rows, c, ls in series:
        ax.plot(months, [r["cumulative_cash"] for r in rows], marker="o", ms=3.6,
                color=c, lw=2.0, ls=ls, label=label)
    ax.axhline(0, color=INK, lw=1.1)
    ax.fill_between(months, [-20000] * 12, 0, color=RED, alpha=0.035)
    ax.set_xticks(months)
    ax.set_xlabel("Month of trading")
    ax.set_ylabel("Cumulative cash position (AUD)")
    ax.set_title("D4  Cumulative cash, core supports, before any owner drawings", loc="left", pad=12)
    ax.legend(fontsize=7.4, frameon=False, loc="lower left")
    ax.spines[["top", "right"]].set_visible(False)
    ax.grid(axis="y", color=LIGHT, lw=1); ax.set_axisbelow(True)
    ax.annotate("Deepest hole\n$%s" % f"{abs(R['base_worst']):,.0f}", xy=(3, R["base_worst"]),
                xytext=(1.45, R["base_worst"] - 5200), fontsize=7.4, color=RED, fontweight="bold",
                arrowprops=dict(arrowstyle="->", color=RED, lw=1))
    if R["base_month_positive"]:
        ax.annotate("Cash positive\nmonth %d" % R["base_month_positive"],
                    xy=(R["base_month_positive"], 0), xytext=(R["base_month_positive"] - 0.4, 7200),
                    fontsize=7.4, color=GREEN, fontweight="bold",
                    arrowprops=dict(arrowstyle="->", color=GREEN, lw=1))
    fig.text(0.008, -0.10,
             "None of these three lines pays you a wage. Every one of them assumes you work the business full time for nothing. "
             "Add a $6,000 a month drawing and\nthe green line does not cross zero inside twelve months either.\n"
             "Sources: model_params.py, driven by SRC-002, SRC-009, SRC-011 to SRC-013, SRC-025 to SRC-041 and ASM-005 to ASM-015.",
             fontsize=6.9, color=GREY, va="top")
    save(fig, "D4_cash_curves.png")


# ---------------------------------------------------------------- D5 trade study
def d5():
    fig, ax = plt.subplots(figsize=(9.4, 4.3))
    crit = ["Capital to\nfirst revenue\n20%", "Time to\nfirst revenue\n15%",
            "Regulatory\nstability to 2028\n25%", "Margin resilience\nvs sector evidence\n20%",
            "Ease of\nfirst client\n10%", "Owner-operator\nfit\n10%"]
    b1 = [4, 5, 3, 2, 2, 4]; b2 = [5, 4, 2, 4, 3, 5]; b3 = [1, 1, 3, 4, 2, 1]
    x = range(len(crit)); w = 0.26
    ax.bar([i - w for i in x], b1, w, label="Core supports  (weighted 3.30)", color=BLUE)
    ax.bar(list(x),          b2, w, label="Support coordination  (weighted 3.70)", color=GREEN)
    ax.bar([i + w for i in x], b3, w, label="SIL / SDA  (weighted 2.20)", color=AMBER)
    ax.set_xticks(list(x)); ax.set_xticklabels(crit, fontsize=7.2)
    ax.set_ylabel("Score out of 5"); ax.set_ylim(0, 5.6)
    ax.set_title("D5  Service model trade study. Weights were fixed before any score was given (GOV-B6.3)",
                 loc="left", pad=12)
    ax.legend(fontsize=7.6, frameon=False, ncol=3, loc="upper center")
    ax.spines[["top", "right"]].set_visible(False)
    ax.grid(axis="y", color=LIGHT, lw=1); ax.set_axisbelow(True)
    fig.text(0.008, -0.13,
             "THIS RESULT IS CLOSE AND IT IS YOURS TO DECIDE, NOT MINE (GOV-B6.4). Support coordination leads core supports by "
             "0.40 on a five-point scale.\nRaise the weight on regulatory stability from 25% to 40% and the answer flips to core "
             "supports, 3.45 against 3.40. The entire result turns on how heavily\nyou weight the navigator restructure risk to "
             "support coordination against the July 2027 mandatory registration exposure to core supports.",
             fontsize=6.9, color=GREY, va="top")
    save(fig, "D5_trade_study.png")


# ---------------------------------------------------------------- D6 regulatory timeline
def d6():
    fig, ax = blank((10.4, 4.2))
    ax.set_title("D6  The regulatory clock you are starting against", loc="left", pad=10)
    ax.plot([0.04, 0.97], [0.52, 0.52], color=GREY, lw=2.2, zorder=1)
    ev = [
        (0.075, "1 Jul 2026", "Mandatory registration\ncommences for SIL and\ndigital platform providers", AMBER, 1),
        (0.245, "20 Aug 2026", "TODAY\nEverything in this study\nis current at this date", BLUE, -1),
        (0.415, "1 Oct 2026", "Deadline for existing SIL\nproviders . Thriving Kids\nfoundational supports launch", AMBER, 1),
        (0.585, "30 Sep 2026", "PRODA closes for\nCommission portals .\nmyID + RAM only", TEAL, -1),
        (0.755, "Jul 2027", "MANDATORY REGISTRATION\nreaches personal care and\ndaily living supports", RED, 1),
        (0.925, "2026-2028", "Navigator design and pilot\nreshapes support\ncoordination", RED, -1),
    ]
    for x, date, txt, c, side in ev:
        ax.plot([x], [0.52], marker="o", ms=9, color=c, zorder=3)
        if side > 0:
            ax.plot([x, x], [0.535, 0.655], color=c, lw=1.2, zorder=2)
            ax.text(x, 0.675, date, ha="center", va="bottom", fontsize=8.2, fontweight="bold", color=c)
            ax.text(x, 0.735, txt, ha="center", va="bottom", fontsize=7, color=INK, linespacing=1.45)
        else:
            ax.plot([x, x], [0.505, 0.385], color=c, lw=1.2, zorder=2)
            ax.text(x, 0.365, date, ha="center", va="top", fontsize=8.2, fontweight="bold", color=c)
            ax.text(x, 0.305, txt, ha="center", va="top", fontsize=7, color=INK, linespacing=1.45)
    ax.text(0.04, 0.02,
            "The July 2027 date is the one that matters most to a core-supports business. It gives an unregistered start a "
            "defined shelf life ending 1 July 2027,\nand the verification pathway takes four to six months. Starting the "
            "application late is the failure mode.\n"
            "Sources: SRC-014 to SRC-018, SRC-021, SRC-030, SRC-048.", fontsize=6.9, color=GREY, va="top")
    save(fig, "D6_regulatory_timeline.png")


# ---------------------------------------------------------------- D7 operating process
def d7():
    fig, ax = blank((10.4, 4.4))
    ax.set_title("D7  The operating cycle: referral to cash, and where the cash gap opens", loc="left", pad=12)

    row1 = [
        (0.030, "1. REFERRAL\nA support coordinator\nor plan manager\nsends you a participant", BLUE),
        (0.275, "2. INTAKE\nAssessment, risk\nprofile, consent.\nUnbillable.", BLUE),
        (0.520, "3. AGREEMENT\nService agreement\nsigned and priced to\nthe 2026-27 limits", BLUE),
        (0.765, "4. ROSTER\nMatch a worker on\nskills, clearance and\navailability. Unbillable.", TEAL),
    ]
    row2 = [
        (0.030, "8. PAID\nAbout 14 days after\ninvoice (ASM-007,\nLow confidence)", GREEN),
        (0.275, "7. INVOICE\nSubmit to the plan\nmanager, or claim in\nthe portal once registered", AMBER),
        (0.520, "6. PAY WAGES\nWeekly or fortnightly.\nThis happens BEFORE\nstep 8.", RED),
        (0.765, "5. DELIVER\nThe only billable step.\nShift notes and\ntimesheet follow it.", TEAL),
    ]
    W, H = 0.205, 0.235
    for x, t, c in row1:
        box(ax, x, 0.62, W, H, t, c, fs=7.2, weight="normal")
    for x, t, c in row2:
        box(ax, x, 0.245, W, H, t, c, fs=7.2, weight="normal")
    for i in range(3):
        arrow(ax, (row1[i][0] + W, 0.7375), (row1[i + 1][0], 0.7375))
    arrow(ax, (0.8675, 0.62), (0.8675, 0.48), color=TEAL)
    for i in range(3, 0, -1):
        arrow(ax, (row2[i][0], 0.3625), (row2[i - 1][0] + W, 0.3625))

    ax.text(0.030, 0.575, "UNBILLABLE ADMINISTRATION", fontsize=7, color=GREY, fontweight="bold")
    ax.add_patch(FancyBboxPatch((0.025, 0.555), 0.72, 0.005,
                                boxstyle="square,pad=0", facecolor=GREY, edgecolor="none"))

    ax.add_patch(FancyBboxPatch((0.030, 0.055), 0.94, 0.145,
                                boxstyle="round,pad=0.008,rounding_size=0.016",
                                facecolor="#FBE9E7", edgecolor=RED, lw=1.3))
    ax.text(0.500, 0.128,
            "THE CASH GAP SITS BETWEEN STEP 6 AND STEP 8, AND IT NEVER CLOSES",
            ha="center", va="center", fontsize=8.6, color=RED, fontweight="bold")
    ax.text(0.500, 0.082,
            "At 300 billable hours a month the wage bill is $%s. A 14-day payment lag needs $%s of working capital "
            "standing behind it permanently;\na 30-day lag needs $%s. This is separate from setup capital and it grows "
            "every time you win a participant."
            % (f"{300 * R['loaded_wage_l2']:,.0f}", f"{R['working_capital_at_300h']:,.0f}",
               f"{R['working_capital_at_300h_30day']:,.0f}"),
            ha="center", va="center", fontsize=7.1, color=INK, linespacing=1.5)

    ax.text(0.030, 0.015,
            "Steps 2, 3, 4 and 7 are the administration load costed in ASM-008. D3 shows what happens to margin when you pay "
            "someone else to carry them.\nSources: SRC-052 (travel, non-face-to-face and cancellation claiming), SRC-009 to "
            "SRC-013 (wage cost), ASM-007 (payment lag).",
            fontsize=6.9, color=GREY, va="top")
    save(fig, "D7_operating_cycle.png")


# ---------------------------------------------------------------- D8 break-even
def d8():
    fig, ax = plt.subplots(figsize=(9.4, 4.3))
    hrs = list(range(0, 501, 10))
    fixed = R["monthly_fixed"][1]
    own = [h * R["contribution_owner_admin"] - fixed for h in hrs]
    paid = [h * R["contribution_paid_admin"] - fixed for h in hrs]
    ax.plot(hrs, own, color=GREEN, lw=2.2, label="You do the administration (contribution $%.2f/hr)" % R["contribution_owner_admin"])
    ax.plot(hrs, paid, color=RED, lw=2.2, label="Administration is paid (contribution $%.2f/hr)" % R["contribution_paid_admin"])
    ax.axhline(0, color=INK, lw=1.1)
    ax.axvline(R["breakeven_hrs_owner_admin"], color=GREEN, ls=":", lw=1.4)
    ax.axvline(R["breakeven_hrs_paid_admin"], color=RED, ls=":", lw=1.4)
    ax.annotate("%.0f hrs/mo" % R["breakeven_hrs_owner_admin"],
                xy=(R["breakeven_hrs_owner_admin"], 0), xytext=(R["breakeven_hrs_owner_admin"] + 12, 1100),
                color=GREEN, fontsize=8, fontweight="bold")
    ax.annotate("%.0f hrs/mo" % R["breakeven_hrs_paid_admin"],
                xy=(R["breakeven_hrs_paid_admin"], 0), xytext=(R["breakeven_hrs_paid_admin"] + 12, -1600),
                color=RED, fontsize=8, fontweight="bold")
    ax.axhline(5000, color=GREY, ls="--", lw=1)
    ax.text(6, 5300, "a $5,000 a month drawing needs %.0f hrs (you do admin) or %.0f hrs (admin is paid)"
            % (R["hours_for_60k_draw"], R["hours_for_60k_draw_paid_admin"]), fontsize=7.2, color=GREY)
    ax.set_xlabel("Billable hours delivered per month")
    ax.set_ylabel("Monthly cash contribution after fixed costs (AUD)")
    ax.set_title("D8  Break-even volume, core supports", loc="left", pad=12)
    ax.legend(fontsize=7.6, frameon=False, loc="upper left")
    ax.spines[["top", "right"]].set_visible(False)
    ax.grid(color=LIGHT, lw=1); ax.set_axisbelow(True)
    fig.text(0.008, -0.10,
             "A participant using 15 hours a week consumes about 65 billable hours a month. Read the x-axis in participants: "
             "roughly 0.6 of one to break even if you do the\nadministration, about two if you pay for it, and about four to "
             "draw $5,000 a month yourself.\nFixed cost base: $%s a month (SRC-026, SRC-037 to SRC-040, ASM-005, ASM-014)."
             % f"{fixed:,.0f}", fontsize=6.9, color=GREY, va="top")
    save(fig, "D8_breakeven.png")


# ---------------------------------------------------------------- D9 support coordination
def d9():
    fig, ax = plt.subplots(figsize=(9.0, 4.1))
    u = [s[0] * 100 for s in R["sc_sens"]]
    net = [s[3] for s in R["sc_sens"]]
    bars = ax.bar([str(int(x)) + "%" for x in u], net,
                  color=[GREEN if v > 60000 else (TEAL if v > 40000 else AMBER) for v in net], width=0.6)
    for b, v, s in zip(bars, net, R["sc_sens"]):
        ax.text(b.get_x() + b.get_width() / 2, v + 2600, "$%s" % f"{v:,.0f}",
                ha="center", fontsize=8.2, fontweight="bold")
        ax.text(b.get_x() + b.get_width() / 2, 2600, "%.0f hrs/mo" % s[1],
                ha="center", fontsize=7, color="white", fontweight="bold")
    ax.set_xlabel("Billable utilisation of a 38-hour week")
    ax.set_ylabel("Net cash to the owner per year, before tax (AUD)")
    ax.set_title("D9  Support coordination delivered by you personally: the only driver that matters is utilisation",
                 loc="left", pad=12)
    ax.spines[["top", "right"]].set_visible(False)
    ax.grid(axis="y", color=LIGHT, lw=1); ax.set_axisbelow(True)
    fig.text(0.008, -0.11,
             "No support workers, no rostering, no wage cycle, and therefore no working capital gap. One-off capital is about "
             "$%s against about $%s for core supports.\nThe trade is that this service line is the one the NDIS Review has "
             "targeted for restructure into a navigator model (SRC-049, RSK-003).\n"
             "Price input SRC-005 ($100.14/hr, Support Coordination Level 2). Utilisation is ASM-013, Low confidence and "
             "unbenchmarked."
             % (f"{R['one_off_sc'][1]:,.0f}", f"{R['one_off_core'][1]:,.0f}"),
             fontsize=6.9, color=GREY, va="top")
    save(fig, "D9_support_coordination.png")


# ---------------------------------------------------------------- D10 minimum viable org
def d10():
    fig, ax = blank((10.0, 4.4))
    ax.set_title("D10  Minimum viable organisation, and the roles you cannot avoid", loc="left", pad=12)
    box(ax, 0.305, 0.815, 0.39, 0.145,
        "YOU\nDirector and key personnel\nSuitability-assessed by the Commission", BLUE, fs=8.2)
    roles = [
        (0.020, "QUALITY AND\nCOMPLIANCE\n\nIncident management,\nrestrictive practice,\ncomplaints,\naudit evidence"),
        (0.265, "SERVICE\nDELIVERY\n\nIntake, risk profile,\nservice agreements,\nrostering,\nworker matching"),
        (0.510, "FINANCE\n\n\nTimesheets, SCHADS\npayroll, invoicing,\nclaims, BAS"),
        (0.755, "PEOPLE\n\n\nRecruitment, screening,\nclearances, training,\nsupervision"),
    ]
    for x, t in roles:
        box(ax, x, 0.375, 0.205, 0.325, t, TEAL, fs=7.1, weight="normal")
        arrow(ax, (0.50, 0.815), (x + 0.1025, 0.700))
    box(ax, 0.150, 0.115, 0.700, 0.185,
        "SUPPORT WORKERS  .  casual, SCHADS Social and Community Services stream\n\n"
        "Each one needs an NDIS Worker Screening Check ($139.20), first aid and CPR ($290),\n"
        "and the free Worker Orientation Module.\n"
        "About $429 and three weeks of lead time per worker before a first shift.", TEAL, fs=7.4, weight="normal")
    arrow(ax, (0.378, 0.375), (0.44, 0.300), color=TEAL)
    arrow(ax, (0.622, 0.375), (0.56, 0.300), color=TEAL)
    ax.text(0.020, 0.075,
            "In a one-person business all four boxes above the workers are YOU. That is the administration load costed in D3, "
            "and it is why the owner-operator\nquestion (ACT-001) moves the answer more than any other input in this study.\n"
            "Sources: SRC-031, SRC-041, SRC-042 (per-worker cost and lead time), SRC-010 (classification stream), "
            "NDIS Practice Standards via SRC-020.",
            fontsize=6.9, color=GREY, va="top")
    save(fig, "D10_minimum_org.png")


def d11():
    """REQ-SYS-13. Part II had no diagram at all until independent verification V6 said so. This is
    the structural point of the whole aged care case in one picture: the budget, not the price,
    is the ceiling."""
    fig, ax = blank((9.6, 5.4))
    rows = A["budget_sensitivity"]
    labels = ["$%s\nL1" % f"{rows[0][0]:,.0f}"] + ["$%s" % f"{r[0]:,.0f}" for r in rows[1:-1]] + \
             ["$%s\nL8" % f"{rows[-1][0]:,.0f}"]
    hrs = [r[1] for r in rows]
    con = [r[3] for r in rows]

    ax2 = ax.inset_axes([0.075, 0.295, 0.90, 0.505])
    x = list(range(len(rows)))
    bars = ax2.bar(x, hrs, color=BLUE, width=0.56)
    ax2.set_xticks(x); ax2.set_xticklabels(labels, fontsize=7.6)
    ax2.set_ylabel("Service hours a week the budget buys", fontsize=8.2)
    ax2.set_ylim(0, max(hrs) * 1.40)
    for sp in ("top", "right"):
        ax2.spines[sp].set_visible(False)
    for b, h, c in zip(bars, hrs, con):
        ax2.text(b.get_x() + b.get_width() / 2, h + max(hrs) * 0.035,
                 "%.1f hr\n$%s pcm" % (h, f"{c:,.0f}"), ha="center", va="bottom",
                 fontsize=7.4, color=INK, weight="bold")
    ax2.axhline(A["hours_per_client_week"], color=AMBER, lw=1.3, ls="--")
    ax2.text(len(rows) - 0.42, A["hours_per_client_week"] + 0.28,
             "the modelled average, %.2f hr  [ASM-020]" % A["hours_per_client_week"],
             fontsize=7.4, color=AMBER, ha="right", weight="bold")

    ax.text(0.02, 0.955, "D11 — In aged care the PRICE is not the ceiling. The BUDGET is.",
            fontsize=11.5, weight="bold", color=INK, va="top")
    ax.text(0.02, 0.895,
            "Each bar is one ongoing classification. Ten per cent of the budget is deducted for care management "
            "before any service is delivered; what remains, divided by the\nprice charged, is every hour that "
            "client can ever buy from you in a year. Even the top classification buys about %.0f hours a week and "
            "most buy under five." % A["hours_top_classification_week"],
            fontsize=7.8, color=GREY, va="top")
    ax.text(0.02, 0.185,
            "WHAT THIS MEANS FOR THE BUSINESS.  You cannot sell more hours to a willing client. You can only "
            "acquire more clients. Aged care is an ACQUISITION business,\nnot a rostering one — which is the "
            "opposite of the NDIS, where the price is a hard cap and the volume is open. That single difference "
            "is why the two businesses\nare compared per client per month in Table 15.3 rather than per hour.",
            fontsize=8.2, color=INK, va="top", weight="bold")
    ax.text(0.02, 0.045,
            "Sources: SRC-064 classification budgets · SRC-060 IHACPA advisory price · SRC-063 the ten per cent "
            "care-management deduction · ASM-020 the modelled average budget (Low confidence).\n"
            "Independently verified in V4; the Level 1 endpoint was corrected from a round $12,000 to the "
            "registered $%s after verification pass V6 (DEF-032)." % f"{rows[0][0]:,.0f}",
            fontsize=6.9, color=GREY, va="top")
    save(fig, "D11_budget_constraint.png")


def d12():
    """REQ-SYS-13. The two-entity structure, and the dependency it does NOT separate."""
    fig, ax = blank((9.6, 5.4))
    ax.text(0.02, 0.965, "D12 — Two entities separate LIABILITY. They do not separate YOU.",
            fontsize=11.5, weight="bold", color=INK, va="top")
    ax.text(0.02, 0.905,
            "The choice is recorded as DEC-006 and it is Zaid's to make. This diagram exists so the cost and the "
            "limit of the separation are both visible at once.",
            fontsize=7.8, color=GREY, va="top")

    box(ax, 0.05, 0.66, 0.40, 0.15,
        "AGED CARE PTY LTD\nAged Care Quality and Safety Commission\n"
        "$%s a month fixed  ·  break-even %s client" % (f"{A['monthly_fixed'][1]:,.0f}",
                                                        A["breakeven_clients_paid_admin"]),
        TEAL, fs=8.2)
    box(ax, 0.55, 0.66, 0.40, 0.15,
        "NDIS PTY LTD\nNDIS Quality and Safeguards Commission\n"
        "$%s a month fixed  ·  break-even %s hours" % (f"{R['monthly_fixed'][1]:,.0f}",
                                                       R["breakeven_hrs_paid_admin"]),
        BLUE, fs=8.2)

    ax.plot([0.50, 0.50], [0.575, 0.825], color=GREEN, lw=2.2, ls="--")
    ax.text(0.50, 0.845, "LIABILITY FIREWALL", ha="center", fontsize=8.4, weight="bold", color=GREEN)
    ax.text(0.50, 0.560, "a failure on one side cannot\nreach the other side's assets",
            ha="center", va="top", fontsize=7.4, color=GREEN)

    box(ax, 0.145, 0.320, 0.71, 0.135,
        "ZAID\nRosters, claims and does the administration unpaid in year one,\n"
        "in BOTH entities, because paying for it removes most of the NDIS contribution",
        RED, fs=8.0)
    arrow(ax, (0.27, 0.460), (0.20, 0.655), color=RED, lw=1.8)
    arrow(ax, (0.73, 0.460), (0.80, 0.655), color=RED, lw=1.8)
    ax.text(0.50, 0.292, "THE FIREWALL DOES NOT CROSS THIS BOX. Illness stops both revenue lines on the same day "
                         "(RSK-016, ACT-012).",
            ha="center", va="top", fontsize=8.4, weight="bold", color=RED)

    ax.text(0.02, 0.215,
            "WHAT THE SEPARATION COSTS  —  $%s a month  ·  $%s a year  ·  $%s over three years"
            % (f"{STR['monthly_cost_of_separation']:,.0f}", f"{STR['annual_cost_of_separation']:,.0f}",
               f"{STR['three_year_cost_of_separation']:,.0f}"),
            fontsize=9.4, weight="bold", color=INK, va="top")
    ax.text(0.02, 0.155,
            "Duplicated: accounting and payroll, bookkeeping, general overhead, the ASIC annual review, one extra "
            "company and business-name registration, and two sets of\nemployer registrations for WorkCover and "
            "portable long service leave. NOT duplicated: software licensed per client, worker costs, and insurance, "
            "which is priced\non turnover rather than on the number of policies.",
            fontsize=7.8, color=GREY, va="top")
    ax.text(0.02, 0.045,
            "The test at month twelve: if either entity has not reached break-even, the separation is being paid "
            "for out of a business that cannot afford it (RSK-015).\n"
            "Sources: SRC-025, SRC-026, SRC-027, SRC-038, SRC-067 · ASM-005, ASM-014, ASM-027 · DEC-006.",
            fontsize=6.9, color=GREY, va="top")
    save(fig, "D12_two_entities.png")


if __name__ == "__main__":
    for f in (d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12):
        f()
    print("done")
