# -*- coding: utf-8 -*-
"""
build_financial_model.py — C6 FINANCIAL MODEL as a live workbook.

Every number is either an INPUT cell (blue = sourced, yellow = assumption) or a FORMULA.
Nothing is a hard-coded result. Change a driver and the whole model recomputes.
Serves REQ-SYS-05, REQ-SYS-07, REQ-SYS-10, REQ-SYS-11.
"""
import os
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
import project_data as P
import model_params as M
import model_agedcare as AC
import trade_study as TS

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
OUT  = os.path.join(ROOT, "05_Outputs", "NDIS_and_Aged_Care_Financial_Model_v2.0.xlsx")
os.makedirs(os.path.dirname(OUT), exist_ok=True)

A       = "Arial"
INK     = "1B2A38"
BLUE_T  = Font(name=A, size=10, color="0000FF")                 # sourced hard input
YEL     = PatternFill("solid", fgColor="FFFF00")                # assumption to review
BLK     = Font(name=A, size=10)
BOLD    = Font(name=A, size=10, bold=True)
H1      = Font(name=A, size=14, bold=True, color=INK)
H2      = Font(name=A, size=11, bold=True, color="FFFFFF")
NOTE    = Font(name=A, size=10, italic=True, color="55636E")   # REQ-CON-04: nothing below 10pt
WARN    = Font(name=A, size=10, italic=True, bold=True, color="B03A2E")  # REQ-CON-04
HFILL   = PatternFill("solid", fgColor="1B2A38")
SUBFILL = PatternFill("solid", fgColor="EDF2F5")
THIN    = Side(style="thin", color="D5DEE4")
BOX     = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
MONEY   = '$#,##0.00;($#,##0.00);-'
MONEY0  = '$#,##0;($#,##0);-'
PCT     = '0.0%'
NUM1    = '#,##0.0'

wb = Workbook(); wb.remove(wb.active)


def _currency_sentence():
    """DEF-030. This sentence used to say every external figure was verified on the currency date.
    Fifty-three of the sources were verified eighteen days earlier, and CR-007 amended REQ-CON-03
    from an equality test to a staleness window precisely so the project would stop claiming
    otherwise — but the claim survived inside the delivered workbook, where independent
    verification V6 found it. It is now generated from the register's actual accessed dates."""
    from collections import Counter
    c = Counter(x[6] for x in P.SRC)
    parts = ", ".join("%d verified %s" % (n, d) for d, n in sorted(c.items()))
    oldest = min(c)
    import datetime as _dt
    stale = (_dt.date.fromisoformat(oldest) + _dt.timedelta(days=365)).isoformat()
    return ("This model rests on %d sources across %d research waves: %s. The currency date is %s and "
            "every source sits inside the ninety-day staleness window REQ-CON-03 sets. Under GOV-F1.16 a "
            "citation is stale twelve months after it was verified, so the FIRST source to expire does so "
            "on %s (BKL-006). Re-verify before then, not after."
            % (len(P.SRC), len(c), parts, P.CURRENCY_DATE, stale))

def sheet(name, widths):
    ws = wb.create_sheet(name)
    for i, w in enumerate(widths, start=1):
        ws.column_dimensions[get_column_letter(i)].width = w
    # REQ-CON-04, as amended. A spreadsheet is scrolled by nature; what GOV-F4.5a asks of one is
    # that scrolling does not lose the labels. Freezing column A and the rows above the first data
    # row keeps the row label and the column heading on screen on a phone. Sheets narrower than
    # seven columns do not need it, but it costs nothing and is applied uniformly.
    ws.freeze_panes = "B5"
    return ws

def title(ws, text, span, sub=None):
    ws["A1"] = text; ws["A1"].font = H1
    ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=span)
    if sub:
        ws["A2"] = sub; ws["A2"].font = NOTE
        ws.merge_cells(start_row=2, start_column=1, end_row=2, end_column=span)

def header(ws, row, labels, start=1):
    for i, l in enumerate(labels):
        c = ws.cell(row=row, column=start + i, value=l)
        c.font = H2; c.fill = HFILL
        c.alignment = Alignment(wrap_text=True, vertical="center")
    ws.row_dimensions[row].height = 28

def put(ws, addr, value, font=BLK, fmt=None, fill=None, wrap=False):
    c = ws[addr]; c.value = value; c.font = font
    if fmt: c.number_format = fmt
    if fill: c.fill = fill
    c.alignment = Alignment(wrap_text=wrap, vertical="top")
    c.border = BOX
    return c


# =========================================================================
# 1. README
# =========================================================================
ws = sheet("README", [30, 108])
title(ws, "%s — Financial Model  %s" % (P.PROJECT_NAME, P.BASELINE_VERSION),
      2, "OWNER: 01_System/build_financial_model.py. REVIEW: regenerate whenever model_params.py or "
         "model_agedcare.py changes. Generated %s. Do not hand-edit (GOV-D4.11)." % P.CURRENCY_DATE)
rows = [
 ("WHAT THIS MODEL DOES", "Turns the sourced NDIS price limits and the sourced SCHADS wage into a margin per billable "
  "hour, then into a break-even volume, a twelve-month cash curve and a runway figure."),
 ("HOW TO USE IT", "Change a value in the Drivers sheet. Every downstream number recomputes. Nothing in this workbook "
  "is a typed-in result EXCEPT the two comparison sheets named below."),
 ("WHERE THE FORMULAS STOP", "Structure B5:C6 and Combined B5:C9 hold values projected from the two Python models "
  "rather than cell formulas, because they compare figures whose components live in model_agedcare.py and "
  "model_params.py rather than on any sheet here. Their DIFFERENCE and TOTAL columns ARE formulas, so the "
  "comparisons recompute. Checker C37 reconciles the projected values against the models to the cent on every "
  "build, and independent verification V6 raised the earlier blanket claim that nothing here is typed in."),
 ("COLOUR LEGEND", "BLUE text  = a hard input taken from a source. The SRC-### beside it names that source.\n"
  "YELLOW fill = an assumption, not a fact. The ASM-### beside it says what breaks if it is wrong. "
  "These are the cells to challenge first.\nBLACK text = a formula. Do not overwrite it."),
 ("THE ONE THING TO READ FIRST", "UnitEconomics, row 'Contribution if administration is PAID'. If that number is "
  "small, the business only works while you personally do the rostering, notes and invoicing for nothing. "
  "That is the finding, not the margin."),
 ("WHAT THIS MODEL DOES NOT DO", "It does not model weekend or public holiday work: no arithmetically consistent "
  "SCHADS casual penalty rate could be sourced (DEF-003, BKL-002). It does not model SIL or SDA: no property "
  "parameters were supplied (BKL-005). It excludes payroll tax on the assumption the business stays below the "
  "Victorian threshold (ASM-003). It excludes income tax and GST entirely."),
 ("CURRENCY", _currency_sentence()),
 ("NOT ADVICE", "This is a decision-support model, not financial, tax or legal advice."),
]
header(ws, 4, ["", "How this workbook works"])
r = 5
for k, v in rows:
    put(ws, "A%d" % r, k, BOLD, wrap=True)
    put(ws, "B%d" % r, v, BLK, wrap=True)
    ws.row_dimensions[r].height = 15 + 13 * (v.count("\n") + len(v) // 110)
    r += 1


# =========================================================================
# 2. INPUTS — sourced rates
# =========================================================================
ws = sheet("Inputs", [46, 14, 12, 14, 14, 62])
title(ws, "Sourced rate inputs", 6,
      "BLUE = a hard number taken from a named source. Change one only if you have a better source, and update the "
      "Source column when you do.")
header(ws, 4, ["Input", "Value", "Unit", "Source", "Confidence", "Why the confidence is not High"])
INPUT_ROWS = [
 ("NDIS price limit — self-care, weekday daytime (01_011_0107_1_1)", 73.58, "AUD/hr", "SRC-002", "Medium",
  "Read from a 65-page PDF through a summarising fetch, not a raw table. RSK-007. BKL-001 closes this."),
 ("NDIS price limit — community participation, weekday (04_104_0125_6_1)", 73.58, "AUD/hr", "SRC-004", "Medium",
  "Same extraction caveat."),
 ("NDIS price limit — support coordination Level 2 (07_002_0106_8_3)", 100.14, "AUD/hr", "SRC-005", "Medium",
  "Same extraction caveat."),
 ("SCHADS SACS Level 2 casual hourly rate", 45.28, "AUD/hr", "SRC-009", "Medium",
  "fairwork.gov.au blocks automated fetching. Two independent payroll calculators agree exactly, but no first-party "
  "read was achieved. ISS-001."),
 ("SCHADS SACS Level 3 casual hourly rate (used for administration)", 50.61, "AUD/hr", "SRC-009", "Medium",
  "Same. ISS-001."),
 ("Superannuation guarantee, FY2026-27", 0.12, "rate", "SRC-011", "High", "-"),
 ("WorkCover premium, Victorian state average 2026-27", 0.018, "rate", "SRC-012", "Medium",
  "State average across all industries. The disability-sector rate sits in a Special Gazette that could not be read. ASM-004."),
 ("Portable long service leave levy, Victorian community services", 0.0165, "rate", "SRC-013", "High", "-"),
 ("Payroll tax, Victoria — assumed not payable below threshold", 0.0, "rate", "ASM-003", "Medium",
  "The threshold itself could not be confirmed from a primary source. Set this to 0.0485 to test crossing it."),
]
r = 5
for label, val, unit, src, conf, why in INPUT_ROWS:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    c = put(ws, "B%d" % r, val, BLUE_T, PCT if unit == "rate" else MONEY)
    put(ws, "C%d" % r, unit)
    put(ws, "D%d" % r, src, BOLD)
    put(ws, "E%d" % r, conf, BOLD)
    put(ws, "F%d" % r, why, NOTE, wrap=True)
    ws.row_dimensions[r].height = 30
    r += 1
PRICE_CORE = "Inputs!$B$5"; PRICE_SC = "Inputs!$B$7"
WAGE_L2 = "Inputs!$B$8"; WAGE_L3 = "Inputs!$B$9"
SUPER = "Inputs!$B$10"; WC = "Inputs!$B$11"; LSL = "Inputs!$B$12"; PTAX = "Inputs!$B$13"


# =========================================================================
# 3. DRIVERS — assumptions
# =========================================================================
ws = sheet("Drivers", [50, 14, 12, 12, 12, 66])
title(ws, "Drivers — these are ASSUMPTIONS, not facts", 6,
      "YELLOW cells are the ones to argue with. Each names the ASM-### that states what breaks if it is wrong. "
      "Change one and every sheet downstream recomputes.")
header(ws, 4, ["Driver", "Value", "Unit", "ASM", "Confidence", "What happens if this is wrong"])
DRIVER_ROWS = [
 ("Administration hours required per billable hour", 0.25, "hr", "ASM-008", "Low",
  "The single most important number in this model. At 0.40 the core-supports margin turns NEGATIVE. No published "
  "benchmark exists."),
 ("Payment lag from invoice to cash", 14, "days", "ASM-007", "Low",
  "Sets the working capital figure. At 30 days it roughly doubles."),
 ("Bookkeeping and accounting", 300, "AUD/mo", "ASM-005", "Low",
  "No vendor publishes a price. Every $100 of error moves break-even by about five billable hours a month."),
 ("General operating overhead — phone, internet, memberships, travel", 200, "AUD/mo", "ASM-014", "Low",
  "Same sensitivity as bookkeeping."),
 ("Website and brand, one-off", 2500, "AUD", "ASM-006", "Low",
  "The published Australian range is $3,500 to $30,000. A lean single-operator build is assumed."),
 ("Support coordination billable utilisation", 0.55, "rate", "ASM-013", "Low",
  "Drives the entire support coordination result. At 0.35 annual net falls from about $99,000 to about $59,000."),
 ("Hours available per week if you work the business full time", 38, "hr", "ASM-011", "Low",
  "Assumes owner-operator. ACT-001 must confirm this."),
 ("Support workers screened and trained before the first shift", 3, "count", "-", "Low",
  "Scales the per-worker setup cost of about $429 each."),
 ("Owner drawings per month, base case", 0, "AUD/mo", "ASM-012", "Low",
  "Set to zero deliberately. ACT-002 must supply the real number. Every scenario in this model assumes you work for "
  "nothing until you change this."),
 ("Target billable hours per month at steady state", 300, "hr", "-", "Low",
  "Used for the working capital calculation. About 4.6 participants at 15 hours a week each."),
]
r = 5
for label, val, unit, asm, conf, effect in DRIVER_ROWS:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    c = put(ws, "B%d" % r, val, BLUE_T, PCT if unit == "rate" else (MONEY0 if "AUD" in unit else NUM1), YEL)
    put(ws, "C%d" % r, unit)
    put(ws, "D%d" % r, asm, BOLD)
    put(ws, "E%d" % r, conf, BOLD)
    put(ws, "F%d" % r, effect, NOTE, wrap=True)
    ws.row_dimensions[r].height = 30
    r += 1
D_ADMIN = "Drivers!$B$5"; D_LAG = "Drivers!$B$6"; D_BOOK = "Drivers!$B$7"
D_GEN = "Drivers!$B$8"; D_WEB = "Drivers!$B$9"; D_UTIL = "Drivers!$B$10"
D_HRSWK = "Drivers!$B$11"; D_WORKERS = "Drivers!$B$12"; D_DRAW = "Drivers!$B$13"; D_TARGET = "Drivers!$B$14"


# =========================================================================
# 4. COSTS
# =========================================================================
ws = sheet("Costs", [52, 14, 14, 14, 12, 12, 50])
title(ws, "Cost stack", 7,
      "One-off, recurring monthly and recurring annual. Every line names its source. Where no vendor publishes a "
      "price, the line names the ASM instead and the confidence is Low.")
header(ws, 4, ["Cost line", "Low", "Base", "High", "Source", "Confidence", "Note"])
COST_ONEOFF = [
 ("ASIC Pty Ltd company registration", 636, 636, 636, "SRC-025", "Medium", "Secondary summary of ASIC's fee."),
 ("ASIC business name, three years", 108, 108, 108, "SRC-027", "Medium", "Secondary summary."),
 ("ABN, TFN, GST and PAYG registration", 0, 0, 0, "SRC-028", "High", "Free. Instant, or up to 20 business days if referred."),
 ("myID Strong and RAM setup", 0, 0, 0, "SRC-030", "High", "Free. PRODA closes for Commission portals 30 Sep 2026."),
 ("Policy and procedure manual, verification pack", 649, 1080, 1825, "SRC-036", "High", "Two vendors publish prices."),
 ("Verification audit fee", 3500, 4750, 6000, "SRC-034", "Medium",
  "A consultancy's estimate. NO Approved Quality Auditor publishes a rate card. ACT-007 replaces this with two real quotes."),
]
r = 5
put(ws, "A%d" % r, "ONE-OFF SETUP", BOLD, fill=SUBFILL); r += 1
first_oneoff = r
for label, lo, ba, hi, src, conf, note in COST_ONEOFF:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    for col, val in zip("BCD", (lo, ba, hi)):
        put(ws, "%s%d" % (col, r), val, BLUE_T, MONEY0)
    put(ws, "E%d" % r, src, BOLD); put(ws, "F%d" % r, conf, BOLD)
    put(ws, "G%d" % r, note, NOTE, wrap=True); ws.row_dimensions[r].height = 26
    r += 1
put(ws, "A%d" % r, "Website and brand (from Drivers)", BLK)
for col in "BCD":
    put(ws, "%s%d" % (col, r), "=%s" % D_WEB, BLK, MONEY0)
put(ws, "E%d" % r, "ASM-006", BOLD); put(ws, "F%d" % r, "Low", BOLD)
put(ws, "G%d" % r, "Published Australian range is $3,500 to $30,000. This model assumes a lean build.", NOTE, wrap=True)
r += 1
put(ws, "A%d" % r, "Per-worker setup: screening $139.20 + first aid and CPR $290", BLK, wrap=True)
for col in "BCD":
    put(ws, "%s%d" % (col, r), "=(139.2+290)*(%s+1)" % D_WORKERS, BLK, MONEY0)
put(ws, "E%d" % r, "SRC-031, SRC-041", BOLD); put(ws, "F%d" % r, "High", BOLD)
put(ws, "G%d" % r, "Scales with the worker count in Drivers, plus you. Orientation module is free (SRC-042).", NOTE, wrap=True)
r += 1
last_oneoff = r - 1
put(ws, "A%d" % r, "TOTAL ONE-OFF SETUP", BOLD, fill=SUBFILL)
for col in "BCD":
    put(ws, "%s%d" % (col, r), "=SUM(%s%d:%s%d)" % (col, first_oneoff, col, last_oneoff), BOLD, MONEY0, SUBFILL)
put(ws, "E%d" % r, "", fill=SUBFILL); put(ws, "F%d" % r, "", fill=SUBFILL); put(ws, "G%d" % r, "", fill=SUBFILL)
ONEOFF_ROW = r
r += 2

put(ws, "A%d" % r, "RECURRING MONTHLY", BOLD, fill=SUBFILL); r += 1
first_mo = r
MO = [("CRM and rostering (ShiftCare Essentials, 5-staff minimum)", 45, 45, 125, "SRC-037", "High", "Vendor's published pricing."),
      ("Accounting and payroll (Xero)", 78, 78, 143, "SRC-038", "High", "Vendor's published pricing.")]
for label, lo, ba, hi, src, conf, note in MO:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    for col, val in zip("BCD", (lo, ba, hi)):
        put(ws, "%s%d" % (col, r), val, BLUE_T, MONEY0)
    put(ws, "E%d" % r, src, BOLD); put(ws, "F%d" % r, conf, BOLD)
    put(ws, "G%d" % r, note, NOTE, wrap=True); r += 1
for label, ref, lo, hi, asm, note in [
        ("Bookkeeping  (base cell is the Drivers value)", D_BOOK, 200, 500, "ASM-005",
         "No vendor publishes a price. Low and high are the plausible band; the base column is wired to the "
         "Drivers sheet so changing the driver moves the base case."),
        ("General operating overhead  (base cell is the Drivers value)", D_GEN, 150, 350, "ASM-014",
         "No vendor publishes a price. Same structure: low and high bound it, the driver sets the base.")]:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    put(ws, "B%d" % r, lo, BLUE_T, MONEY0)
    put(ws, "C%d" % r, "=%s" % ref, BLK, MONEY0)
    put(ws, "D%d" % r, hi, BLUE_T, MONEY0)
    put(ws, "E%d" % r, asm, BOLD); put(ws, "F%d" % r, "Low", BOLD)
    put(ws, "G%d" % r, note, NOTE, wrap=True); ws.row_dimensions[r].height = 26; r += 1
last_mo = r - 1
put(ws, "A%d" % r, "SUBTOTAL MONTHLY", BOLD, fill=SUBFILL)
for col in "BCD":
    put(ws, "%s%d" % (col, r), "=SUM(%s%d:%s%d)" % (col, first_mo, col, last_mo), BOLD, MONEY0, SUBFILL)
for col in "EFG": put(ws, "%s%d" % (col, r), "", fill=SUBFILL)
MO_ROW = r
r += 2

put(ws, "A%d" % r, "RECURRING ANNUAL", BOLD, fill=SUBFILL); r += 1
first_an = r
AN = [("ASIC company annual review", 342, 342, 342, "SRC-026", "Medium", "Secondary summary of ASIC's fee."),
      ("Insurance: public liability, professional indemnity, personal accident", 1200, 2350, 3500, "SRC-040", "Low",
       "One broker's marketing page, no named underwriter. Management liability has NO published price anywhere. ACT-008.")]
for label, lo, ba, hi, src, conf, note in AN:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    for col, val in zip("BCD", (lo, ba, hi)):
        put(ws, "%s%d" % (col, r), val, BLUE_T, MONEY0)
    put(ws, "E%d" % r, src, BOLD); put(ws, "F%d" % r, conf, BOLD)
    put(ws, "G%d" % r, note, NOTE, wrap=True); ws.row_dimensions[r].height = 26; r += 1
last_an = r - 1
put(ws, "A%d" % r, "SUBTOTAL ANNUAL", BOLD, fill=SUBFILL)
for col in "BCD":
    put(ws, "%s%d" % (col, r), "=SUM(%s%d:%s%d)" % (col, first_an, col, last_an), BOLD, MONEY0, SUBFILL)
for col in "EFG": put(ws, "%s%d" % (col, r), "", fill=SUBFILL)
AN_ROW = r
r += 2
put(ws, "A%d" % r, "TOTAL FIXED COST PER MONTH  (monthly subtotal + annual subtotal / 12)", BOLD, fill=SUBFILL)
for col in "BCD":
    put(ws, "%s%d" % (col, r), "=%s%d+%s%d/12" % (col, MO_ROW, col, AN_ROW), BOLD, MONEY, SUBFILL)
for col in "EFG": put(ws, "%s%d" % (col, r), "", fill=SUBFILL)
FIXED_ROW = r
r += 2
put(ws, "A%d" % r, "NOT INCLUDED — no published price could be found for any of these", WARN, wrap=True)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=7); r += 1
for t in ["NDIS Commission's own application or certificate fee, if one exists — ACT-006 resolves this",
          "Management liability insurance — no published figure exists for a small Australian disability business",
          "Mid-term surveillance audit at about 18 months, estimated at 60-70% of the initial audit fee (SRC-034)",
          "Payroll tax, if the business crosses the Victorian threshold (ASM-003)",
          "Income tax, GST remittance, and any consultant engaged to run the registration"]:
    put(ws, "A%d" % r, "  .  " + t, NOTE, wrap=True)
    ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=7); r += 1
FIXED_BASE = "Costs!$C$%d" % FIXED_ROW
ONEOFF_BASE = "Costs!$C$%d" % ONEOFF_ROW


# =========================================================================
# 5. UNIT ECONOMICS
# =========================================================================
ws = sheet("UnitEconomics", [56, 16, 14, 66])
title(ws, "Unit economics — one billable hour of core support", 4,
      "This is the whole business in eight rows. Read the last two together.")
header(ws, 4, ["Line", "AUD per hour", "% of price", "What it means"])
UE = [
 ("NDIS price limit, weekday daytime", "=%s" % PRICE_CORE, "SRC-002",
  "The most you may charge for one hour. It is a ceiling, not a target."),
 ("Casual support worker wage, SACS Level 2", "=-%s" % WAGE_L2, "SRC-009",
  "Includes the 25% casual loading paid in lieu of leave."),
 ("Superannuation", "=-%s*%s" % (WAGE_L2, SUPER), "SRC-011", "12.00% for FY2026-27."),
 ("WorkCover premium", "=-%s*%s" % (WAGE_L2, WC), "SRC-012", "Victorian state average. The sector rate may be higher."),
 ("Portable long service leave levy", "=-%s*%s" % (WAGE_L2, LSL), "SRC-013",
  "1.65%. Victoria-specific, covers NDIS-funded work, and it is the on-cost most often forgotten."),
 ("Payroll tax", "=-%s*%s" % (WAGE_L2, PTAX), "ASM-003", "Zero while below the Victorian threshold."),
]
r = 5
first_ue = r
for label, formula, src, note in UE:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    put(ws, "B%d" % r, formula, BLK, MONEY)
    put(ws, "C%d" % r, "=B%d/%s" % (r, PRICE_CORE), BLK, PCT)
    put(ws, "D%d" % r, "%s — %s" % (src, note), NOTE, wrap=True)
    ws.row_dimensions[r].height = 26; r += 1
last_ue = r - 1
put(ws, "A%d" % r, "GROSS MARGIN PER BILLABLE HOUR", BOLD, fill=SUBFILL)
put(ws, "B%d" % r, "=SUM(B%d:B%d)" % (first_ue, last_ue), BOLD, MONEY, SUBFILL)
put(ws, "C%d" % r, "=B%d/%s" % (r, PRICE_CORE), BOLD, PCT, SUBFILL)
put(ws, "D%d" % r, "Before ANY overhead. This is not profit.", BOLD, fill=SUBFILL, wrap=True)
GM_ROW = r; r += 2
put(ws, "A%d" % r, "Administration, if you pay someone to do it", BLK, wrap=True)
put(ws, "B%d" % r, "=-%s*(1+%s+%s+%s+%s)*%s" % (WAGE_L3, SUPER, WC, LSL, PTAX, D_ADMIN), BLK, MONEY)
put(ws, "C%d" % r, "=B%d/%s" % (r, PRICE_CORE), BLK, PCT)
put(ws, "D%d" % r, "A coordinator at SACS Level 3, fully loaded, for %s of an hour per billable hour (ASM-008)."
    % "0.25", NOTE, wrap=True)
ADMIN_ROW = r; r += 1
put(ws, "A%d" % r, "CONTRIBUTION IF ADMINISTRATION IS PAID", BOLD, fill=SUBFILL)
put(ws, "B%d" % r, "=B%d+B%d" % (GM_ROW, ADMIN_ROW), BOLD, MONEY, SUBFILL)
put(ws, "C%d" % r, "=B%d/%s" % (r, PRICE_CORE), BOLD, PCT, SUBFILL)
put(ws, "D%d" % r, "READ THIS ROW FIRST. If it is small or negative, the business only works while you do the "
                   "administration yourself for nothing.", BOLD, fill=SUBFILL, wrap=True)
CONTRIB_PAID_ROW = r
ws.row_dimensions[r].height = 30
r += 2
put(ws, "A%d" % r, "CONTRIBUTION IF YOU DO THE ADMINISTRATION", BOLD, fill=SUBFILL)
put(ws, "B%d" % r, "=B%d" % GM_ROW, BOLD, MONEY, SUBFILL)
put(ws, "C%d" % r, "=B%d/%s" % (r, PRICE_CORE), BOLD, PCT, SUBFILL)
put(ws, "D%d" % r, "Costs no cash, but caps how many participants one person can carry.", BOLD, fill=SUBFILL, wrap=True)
CONTRIB_OWNER_ROW = r
r += 2
put(ws, "A%d" % r,
    "Sector reality check: National Disability Services reported about half of NDIS providers were loss-making in "
    "2024-25 and 81% said current pricing is unsustainable (SRC-046). The rows above are the arithmetic behind that "
    "finding. The price is not the problem; the administration load is.", WARN, wrap=True)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=4)
ws.row_dimensions[r].height = 44
CONTRIB_OWNER = "UnitEconomics!$B$%d" % CONTRIB_OWNER_ROW
CONTRIB_PAID  = "UnitEconomics!$B$%d" % CONTRIB_PAID_ROW
LOADED_WAGE   = "(%s*(1+%s+%s+%s+%s))" % (WAGE_L2, SUPER, WC, LSL, PTAX)


# =========================================================================
# 6. BREAK-EVEN
# =========================================================================
ws = sheet("BreakEven", [58, 18, 66])
title(ws, "Break-even and working capital", 3,
      "Break-even is fixed cost divided by contribution per hour. Working capital is the cash that must sit behind "
      "the wage cycle permanently.")
header(ws, 4, ["Measure", "Value", "How it is calculated and what it means"])
BE = [
 ("Fixed cost per month (base case)", "=%s" % FIXED_BASE, MONEY,
  "From the Costs sheet: monthly subtotal plus one twelfth of the annual subtotal."),
 ("Break-even billable hours per month — you do the administration", "=%s/%s" % (FIXED_BASE, CONTRIB_OWNER), NUM1,
  "Fixed cost divided by contribution. About 0.6 of one participant using 15 hours a week."),
 ("Break-even billable hours per month — administration is paid", "=%s/%s" % (FIXED_BASE, CONTRIB_PAID), NUM1,
  "The same fixed cost against a much thinner contribution. About two participants."),
 ("Billable hours to draw $5,000 a month — you do the administration",
  "=(%s+5000)/%s" % (FIXED_BASE, CONTRIB_OWNER), NUM1,
  "About four participants at 15 hours a week, and roughly four to six support workers to deliver it."),
 ("Billable hours to draw $5,000 a month — administration is paid",
  "=(%s+5000)/%s" % (FIXED_BASE, CONTRIB_PAID), NUM1,
  "If this number looks impossible for a new business, that is the finding."),
 ("Target billable hours per month (from Drivers)", "=%s" % D_TARGET, NUM1,
  "About 4.6 participants at 15 hours a week."),
 ("Monthly wage bill at target volume", "=%s*%s" % (D_TARGET, LOADED_WAGE), MONEY,
  "Fully loaded. This leaves your account before any invoice is paid."),
 ("WORKING CAPITAL REQUIRED at the assumed payment lag",
  "=%s*%s*(%s/30)" % (D_TARGET, LOADED_WAGE, D_LAG), MONEY,
  "Wages multiplied by the payment lag as a fraction of a month. Ring-fence this. It is SEPARATE from setup capital "
  "and it grows every time you win a participant."),
 ("Working capital at a 30-day lag instead", "=%s*%s" % (D_TARGET, LOADED_WAGE), MONEY,
  "The prior working files omitted working capital entirely (DEF-001). This is the line that omission hid."),
]
r = 5
for label, formula, fmt, note in BE:
    put(ws, "A%d" % r, label, BOLD if "WORKING CAPITAL" in label else BLK, wrap=True)
    put(ws, "B%d" % r, formula, BOLD if "WORKING CAPITAL" in label else BLK, fmt,
        SUBFILL if "WORKING CAPITAL" in label else None)
    put(ws, "C%d" % r, note, NOTE, wrap=True)
    ws.row_dimensions[r].height = 30; r += 1


# =========================================================================
# 7. SCENARIOS — 12-month cash curves
# =========================================================================
ws = sheet("Scenarios", [30] + [11] * 12 + [14])
title(ws, "Twelve-month cash, three scenarios", 14,
      "All three assume you draw the amount set in Drivers (currently zero). Change that driver and every scenario "
      "recomputes.")
scen = [("BASE: first client month 4, you do the administration", M.RAMP_BASE, CONTRIB_OWNER),
        ("SLOW: first client month 7, you do the administration", M.RAMP_SLOW, CONTRIB_OWNER),
        ("PAID ADMIN: base ramp, but administration is paid", M.RAMP_BASE, CONTRIB_PAID),
        ("DOWNSIDE: no participant at all for twelve months", M.RAMP_ZERO, CONTRIB_OWNER)]
r = 4
for name, ramp, contrib in scen:
    put(ws, "A%d" % r, name, BOLD, fill=SUBFILL)
    ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=14)
    r += 1
    header(ws, r, ["Month"] + [str(i) for i in range(1, 13)] + ["Year 1"])
    hdr = r; r += 1
    put(ws, "A%d" % r, "Billable hours", BLK)
    for i, h in enumerate(ramp):
        put(ws, "%s%d" % (get_column_letter(2 + i), r), h, BLUE_T, NUM1)
    put(ws, "N%d" % r, "=SUM(B%d:M%d)" % (r, r), BOLD, NUM1)
    hrs_row = r; r += 1
    put(ws, "A%d" % r, "Revenue", BLK)
    for i in range(12):
        cl = get_column_letter(2 + i)
        put(ws, "%s%d" % (cl, r), "=%s%d*%s" % (cl, hrs_row, PRICE_CORE), BLK, MONEY0)
    put(ws, "N%d" % r, "=SUM(B%d:M%d)" % (r, r), BOLD, MONEY0)
    r += 1
    put(ws, "A%d" % r, "Contribution", BLK)
    for i in range(12):
        cl = get_column_letter(2 + i)
        put(ws, "%s%d" % (cl, r), "=%s%d*%s" % (cl, hrs_row, contrib), BLK, MONEY0)
    put(ws, "N%d" % r, "=SUM(B%d:M%d)" % (r, r), BOLD, MONEY0)
    contrib_row = r; r += 1
    put(ws, "A%d" % r, "Fixed costs", BLK)
    for i in range(12):
        put(ws, "%s%d" % (get_column_letter(2 + i), r), "=-%s" % FIXED_BASE, BLK, MONEY0)
    put(ws, "N%d" % r, "=SUM(B%d:M%d)" % (r, r), BOLD, MONEY0)
    fixed_row = r; r += 1
    put(ws, "A%d" % r, "Owner drawings", BLK)
    for i in range(12):
        put(ws, "%s%d" % (get_column_letter(2 + i), r), "=-%s" % D_DRAW, BLK, MONEY0)
    put(ws, "N%d" % r, "=SUM(B%d:M%d)" % (r, r), BOLD, MONEY0)
    draw_row = r; r += 1
    put(ws, "A%d" % r, "Net cash in the month", BOLD)
    for i in range(12):
        cl = get_column_letter(2 + i)
        put(ws, "%s%d" % (cl, r), "=%s%d+%s%d+%s%d" % (cl, contrib_row, cl, fixed_row, cl, draw_row), BOLD, MONEY0)
    net_row = r; r += 1
    put(ws, "A%d" % r, "CUMULATIVE CASH", BOLD, fill=SUBFILL)
    put(ws, "B%d" % r, "=B%d-%s" % (net_row, ONEOFF_BASE), BOLD, MONEY0, SUBFILL)
    for i in range(1, 12):
        cl, pv = get_column_letter(2 + i), get_column_letter(1 + i)
        put(ws, "%s%d" % (cl, r), "=%s%d+%s%d" % (pv, r, cl, net_row), BOLD, MONEY0, SUBFILL)
    put(ws, "N%d" % r, "=MIN(B%d:M%d)" % (r, r), BOLD, MONEY0, SUBFILL)
    cum_row = r; r += 1
    put(ws, "A%d" % r, "Deepest cash hole in the year (column N above)", NOTE, wrap=True)
    ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=14)
    r += 2
put(ws, "A%d" % r,
    "Month 1 already carries the full one-off setup cost, which is why every cumulative row starts negative. The "
    "DOWNSIDE row is REQ-SYS-07: it is the cash you must be willing to lose if no participant ever arrives.",
    WARN, wrap=True)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=14)
ws.row_dimensions[r].height = 32


# =========================================================================
# 8. SUPPORT COORDINATION
# =========================================================================
ws = sheet("SupportCoordination", [52, 18, 66])
title(ws, "Support coordination delivered by you personally", 3,
      "No support workers, no wage cycle, no working capital gap. The only driver that matters is billable utilisation.")
header(ws, 4, ["Measure", "Value", "Note"])
SC = [
 ("Price limit, Support Coordination Level 2", "=%s" % PRICE_SC, MONEY, "SRC-005."),
 ("Hours available per week", "=%s" % D_HRSWK, NUM1, "ASM-011. Assumes owner-operator. ACT-001 must confirm."),
 ("Billable utilisation", "=%s" % D_UTIL, PCT, "ASM-013. Low confidence, no published benchmark."),
 ("Billable hours per month", "=%s*%s*52/12" % (D_HRSWK, D_UTIL), NUM1, "Weekly billable hours annualised then monthly."),
 ("Revenue per month", "=B8*%s" % PRICE_SC, MONEY, "Hours multiplied by the price limit."),
 ("Fixed costs per month", "=%s" % FIXED_BASE, MONEY, "Same fixed base as core supports."),
 ("NET CASH PER MONTH", "=B9-B10", MONEY, "Before tax and before any drawing."),
 ("NET CASH PER YEAR", "=B11*12", MONEY, "This is what the owner earns. Compare it to the core-supports scenarios."),
 ("Break-even billable hours per month", "=%s/%s" % (FIXED_BASE, PRICE_SC), NUM1,
  "Very low, because there is no direct labour cost. Utilisation, not volume, is the constraint."),
 ("One-off setup, no registration and no workers",
  "=636+108+%s+139.2+290" % D_WEB, MONEY,
  "No audit and no policy manual, because mandatory registration for support coordination is currently PAUSED (SRC-018)."),
]
r = 5
for label, formula, fmt, note in SC:
    put(ws, "A%d" % r, label, BOLD if label.startswith("NET") else BLK, wrap=True)
    put(ws, "B%d" % r, formula, BOLD if label.startswith("NET") else BLK, fmt,
        SUBFILL if label.startswith("NET") else None)
    put(ws, "C%d" % r, note, NOTE, wrap=True)
    ws.row_dimensions[r].height = 26; r += 1
r += 1
put(ws, "A%d" % r, "UTILISATION SENSITIVITY", BOLD, fill=SUBFILL)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=3); r += 1
header(ws, r, ["Utilisation", "Net cash per year", "Billable hours per month"]); r += 1
for u in (0.25, 0.35, 0.45, 0.55, 0.65, 0.75):
    put(ws, "A%d" % r, u, BLUE_T, PCT)
    put(ws, "B%d" % r, "=(%s*A%d*52/12*%s-%s)*12" % (D_HRSWK, r, PRICE_SC, FIXED_BASE), BLK, MONEY0)
    put(ws, "C%d" % r, "=%s*A%d*52/12" % (D_HRSWK, r), BLK, NUM1)
    r += 1
r += 1
put(ws, "A%d" % r,
    "THE TRADE. This model earns more, on less capital, with no staff. Against that: the NDIS Review recommended "
    "restructuring support coordination into a navigator model, and that design and pilot work runs through 2026-2028 "
    "with the funding mechanism intended to change (SRC-049, RSK-003). You would be building on the one service line "
    "government has said it intends to redesign.", WARN, wrap=True)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=3)
ws.row_dimensions[r].height = 56


# =========================================================================
# 9. SENSITIVITY
# =========================================================================
ws = sheet("Sensitivity", [64, 18, 16, 56])
title(ws, "What breaks the margin", 4,
      "Each row changes ONE assumption and leaves everything else alone. Ranked by how much damage it does.")
header(ws, 4, ["If this assumption is wrong", "Gross margin per hour", "Change", "Which assumption"])
SENS = [
 ("Nothing is wrong — base case as sourced", "=%s" % CONTRIB_OWNER, "-", "Base"),
 ("Administration is paid and runs at 0.40 hr per billable hour",
  "=%s-%s*(1+%s+%s+%s)*0.4" % (CONTRIB_OWNER, WAGE_L3, SUPER, WC, LSL), None,
  "ASM-008 — the model's most dangerous assumption. This turns the margin NEGATIVE."),
 ("Administration is paid at the assumed 0.25 hr per billable hour", "=%s" % CONTRIB_PAID, None, "ASM-008"),
 ("Workers sit at SACS Level 3, not Level 2",
  "=%s-%s*(1+%s+%s+%s+%s)" % (PRICE_CORE, WAGE_L3, SUPER, WC, LSL, PTAX), None,
  "ASM-002 — the award schedule could not be read; fairwork.gov.au is robots-blocked."),
 ("The business crosses the Victorian payroll tax threshold",
  "=%s-%s*(1+%s+%s+%s+0.0485)" % (PRICE_CORE, WAGE_L2, SUPER, WC, LSL), None,
  "ASM-003 — the threshold itself could not be confirmed."),
 ("The disability-sector WorkCover rate is 3.0%, not the 1.8% state average",
  "=%s-%s*(1+%s+0.03+%s+%s)" % (PRICE_CORE, WAGE_L2, SUPER, LSL, PTAX), None,
  "ASM-004 — the industry rate sits in a Special Gazette that could not be read."),
]
r = 5
for label, formula, ch, asm in SENS:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    put(ws, "B%d" % r, formula, BOLD, MONEY)
    put(ws, "C%d" % r, "-" if r == 5 else "=B%d-$B$5" % r, BLK, MONEY)
    put(ws, "D%d" % r, asm, NOTE, wrap=True)
    ws.row_dimensions[r].height = 28; r += 1
r += 1
put(ws, "A%d" % r,
    "Read the second row. There is a plausible, unfalsified version of this business in which every hour of core "
    "support you sell LOSES money. Nothing in the sourced evidence rules it out, because no source publishes an "
    "administration-hours benchmark. Closing ASM-008 with real data from an operating provider is the single "
    "highest-value thing you can do before committing capital.", WARN, wrap=True)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=4)
ws.row_dimensions[r].height = 56


# =========================================================================
# 10. AGED CARE — INPUTS
# =========================================================================
ws = sheet("AC_Inputs", [56, 16, 12, 14, 14, 62])
title(ws, "Aged care inputs — Support at Home", 6,
      "Blue = sourced hard input. Yellow = assumption to review. Nothing here is a result; every result is a "
      "formula somewhere else in this workbook. Change a cell here and the whole aged care model moves.")
header(ws, 4, ["Input", "Value", "Unit", "Source", "Confidence", "Why this value, and what it does"])
AC_NOTES = {
 "price_personal_care":     "IHACPA ADVISORY reference price, not a legal cap. Price caps were legislated for 1 July 2026 and deferred indefinitely on 19 May 2026 with no replacement date. You may charge more or less.",
 "price_domestic_assist":   "Same IHACPA advice. Not used in the base model; held here so a domestic-assistance line can be priced without re-researching it.",
 "price_nursing":           "Same IHACPA advice, registered nurse. Not used in the base model.",
 "wage_homecare_l2_casual": "SCHADS Schedule F Home Care, Level 2 casual, from 1 September 2026. A DIFFERENT schedule from the Social and Community Services stream the NDIS model uses.",
 "wage_homecare_l3_casual": "Schedule F Level 3 casual, used for care management and administration time.",
 "care_mgmt_share":         "Ten per cent is DEDUCTED from each participant's quarterly budget to fund care management. It is revenue to you and it has no NDIS equivalent — it is the single structural advantage aged care holds.",
 "budget_level_1":          "The bottom ongoing classification. At the advisory price this buys about 1.8 service hours a week.",
 "budget_level_8":          "The top ongoing classification. Even this buys only about 13 hours a week. THIS is the constraint, not the price.",
}
r = 5
AC_CELL = {}
for k, (val, unit, src_id, conf) in AC.INPUTS.items():
    put(ws, "A%d" % r, k.replace("_", " "), BLK, wrap=True)
    c = put(ws, "B%d" % r, val, BLUE_T, PCT if unit == "rate" else MONEY)
    AC_CELL[k] = "AC_Inputs!$B$%d" % r
    put(ws, "C%d" % r, unit, BLK)
    put(ws, "D%d" % r, src_id, BLK)
    put(ws, "E%d" % r, conf, BLK)
    put(ws, "F%d" % r, AC_NOTES.get(k, ""), NOTE, wrap=True)
    ws.row_dimensions[r].height = 30
    r += 1

r += 1
put(ws, "A%d" % r, "DRIVERS — assumptions, not sources", BOLD)
r += 1
header(ws, r, ["Driver", "Value", "Unit", "Source", "Confidence", "What breaks if it is wrong"])
r += 1
AC_DRIVER_BREAK = {
 "avg_client_budget":             "The single largest lever in the whole aged care case. At $20,000 the contribution per client falls about 36 per cent; at $12,000 about 65 per cent. Close it by recording the assessed classification of your first five referrals (ACT-010).",
 "care_mgmt_hours_per_client_mo": "At 3 hours the care-management line roughly breaks even instead of contributing. It does not change the sign of the result.",
 "admin_hours_per_billable_hour": "The same assumption that decides the NDIS business. Shared deliberately: one definition, one place to correct it.",
 "wage_coordinator_base":         "ASM-031. Care management and administration are costed at the Social and Community Services Level 3 base rate, NOT the Schedule F Home Care rate, deliberately. IHACPA prices care management at $116 to $127 an hour, implying a role well above a home carer. The Schedule F rate would RAISE the contribution by about $43 a client a month, so this is the conservative choice.",
 "payment_lag_days":              "At a thirty-day lag aged care working capital rises from about $4,200 to about $17,900. That is a material addition to the capital requirement.",
 "participant_contribution_share":"Small exposure. The entire bad-debt allowance is $7.50 a client a month.",
 "contribution_bad_debt":         "At 10 per cent bad debt the allowance rises to $37.50 a client a month, still immaterial against a $1,002 contribution.",
 "clients_target":                "Sets the working-capital figure and the revenue projection. It is a target, not a forecast, and the plan says so.",
}
for k, (val, unit, src_id, conf) in AC.DRIVERS.items():
    put(ws, "A%d" % r, k.replace("_", " "), BLK, wrap=True)
    fmt = PCT if unit == "rate" else (MONEY0 if unit == "AUD/yr" else NUM1)
    c = put(ws, "B%d" % r, val, BLK, fmt, fill=YEL)
    AC_CELL[k] = "AC_Inputs!$B$%d" % r
    put(ws, "C%d" % r, unit, BLK)
    put(ws, "D%d" % r, src_id, BLK)
    put(ws, "E%d" % r, conf, BLK)
    put(ws, "F%d" % r, AC_DRIVER_BREAK.get(k, ""), NOTE, wrap=True)
    ws.row_dimensions[r].height = 34
    r += 1

PRICE_AC   = AC_CELL["price_personal_care"]
WAGE_AC_L2 = AC_CELL["wage_homecare_l2_casual"]
WAGE_AC_L3 = AC_CELL["wage_homecare_l3_casual"]
CM_SHARE   = AC_CELL["care_mgmt_share"]
BUDGET     = AC_CELL["avg_client_budget"]
CM_HOURS   = AC_CELL["care_mgmt_hours_per_client_mo"]
ADMIN_R    = AC_CELL["admin_hours_per_billable_hour"]
LAG        = AC_CELL["payment_lag_days"]
CONTRIB_SH = AC_CELL["participant_contribution_share"]
BADDEBT    = AC_CELL["contribution_bad_debt"]
CLIENTS    = AC_CELL["clients_target"]
COORD_AC   = AC_CELL["wage_coordinator_base"]
ONCOST     = "(1+%s+%s+%s)" % (SUPER, WC, LSL)

# =========================================================================
# 11. AGED CARE — THE UNIT IS THE CLIENT
# =========================================================================
ws = sheet("AC_ClientEconomics", [58, 18, 18, 62])
title(ws, "Contribution per client per month — aged care against NDIS", 4,
      "In the NDIS the price is a hard ceiling and the volume is open. In aged care the price is not capped but the "
      "participant's budget is. The two businesses have different binding constraints, so comparing them per HOUR "
      "misleads. This sheet compares them per CLIENT PER MONTH, which is the unit both businesses actually acquire.")
header(ws, 4, ["", "Aged care", "NDIS", "What this line is"])
AC_ROWS = [
 ("Loaded cost of a support worker hour",
  "=%s*%s" % (WAGE_AC_L2, ONCOST), "=%s*%s" % (WAGE_L2, ONCOST), MONEY,
  "Same on-cost stack applied to two different award schedules. Aged care labour costs within seven per cent of NDIS labour."),
 ("Published hourly price",
  "=%s" % PRICE_AC, "=%s" % PRICE_CORE, MONEY,
  "Aged care advisory, NDIS a hard cap. Aged care pays about forty per cent more for near-identical labour."),
 ("Gross margin an hour",
  "=AC_ClientEconomics!B6-AC_ClientEconomics!B5", "=AC_ClientEconomics!C6-AC_ClientEconomics!C5", MONEY,
  "The headline number, and the misleading one — aged care cannot sell you the hours."),
 ("Revenue a month per client",
  "=%s/12" % BUDGET, "=%d*52/12*%s" % (AC.NDIS_HOURS_PER_CLIENT_WEEK[0], PRICE_CORE), MONEY,
  "Aged care: one twelfth of the assessed budget. NDIS: %d hours a week at the price limit (ASM-025)."
  % AC.NDIS_HOURS_PER_CLIENT_WEEK[0]),
 ("Care management revenue (the 10% pool)",
  "=AC_ClientEconomics!B8*%s" % CM_SHARE, "=0", MONEY,
  "Aged care only. The NDIS has no equivalent revenue line."),
 ("Service revenue",
  "=AC_ClientEconomics!B8-AC_ClientEconomics!B9", "=AC_ClientEconomics!C8", MONEY,
  "What is left to buy actual service hours with."),
 ("Billable hours a month",
  "=AC_ClientEconomics!B10/%s" % PRICE_AC, "=%d*52/12" % AC.NDIS_HOURS_PER_CLIENT_WEEK[0], NUM1,
  "THE CONSTRAINT. In aged care this is an output of the budget; in the NDIS it is an input you choose."),
 ("Billable hours a week",
  "=AC_ClientEconomics!B11*12/52", "=AC_ClientEconomics!C11*12/52", NUM1,
  "Aged care buys about a third of the NDIS hours per client."),
 ("Direct labour",
  "=AC_ClientEconomics!B11*AC_ClientEconomics!B5", "=AC_ClientEconomics!C11*AC_ClientEconomics!C5", MONEY,
  "Hours times the loaded wage."),
 ("Care management cost",
  "=%s*%s*%s" % (CM_HOURS, COORD_AC, ONCOST), "=0", MONEY,
  "Coordinator time against the 10% pool (ASM-021)."),
 ("Service administration cost",
  "=AC_ClientEconomics!B11*%s*%s*%s" % (ADMIN_R, COORD_AC, ONCOST),
  "=AC_ClientEconomics!C11*%s*%s*%s" % (ADMIN_R, WAGE_L3, ONCOST), MONEY,
  "Rostering, notes and claiming, PAID rather than absorbed by you (ASM-008)."),
 ("Bad debt allowance",
  "=AC_ClientEconomics!B8*%s*%s" % (CONTRIB_SH, BADDEBT), "=0", MONEY,
  "A participant co-contribution that can go unpaid (ASM-022, ASM-023)."),
 ("CONTRIBUTION PER CLIENT PER MONTH",
  "=AC_ClientEconomics!B10-AC_ClientEconomics!B13+AC_ClientEconomics!B9-AC_ClientEconomics!B14-AC_ClientEconomics!B15-AC_ClientEconomics!B16",
  "=AC_ClientEconomics!C10-AC_ClientEconomics!C13-AC_ClientEconomics!C15", MONEY,
  "THE COMPARISON THAT DECIDES IT."),
]
r = 5
for label, fa, fn, fmt, note in AC_ROWS:
    bold = label.isupper()
    put(ws, "A%d" % r, label, BOLD if bold else BLK, wrap=True)
    put(ws, "B%d" % r, fa, BOLD if bold else BLK, fmt)
    put(ws, "C%d" % r, fn, BOLD if bold else BLK, fmt)
    put(ws, "D%d" % r, note, NOTE, wrap=True)
    ws.row_dimensions[r].height = 30
    r += 1
put(ws, "A%d" % r, "Ratio, aged care to NDIS", BOLD)
put(ws, "B%d" % r, "=AC_ClientEconomics!B17/AC_ClientEconomics!C17", BOLD, NUM1)
put(ws, "D%d" % r, "Aged care contributes this many times the NDIS figure, on a third of the service hours, "
                   "because it pays about forty per cent more an hour for labour of nearly the same cost AND adds "
                   "a ten per cent care-management revenue line the NDIS does not have.", NOTE, wrap=True)
ws.row_dimensions[r].height = 44
r += 2
put(ws, "A%d" % r, "WHAT THE FIXED BUDGET BUYS ACROSS THE WHOLE CLASSIFICATION RANGE", BOLD)
r += 1
header(ws, r, ["Classification budget a year", "Service hours a week", "Revenue a month", "Contribution a month"])
r += 1
for b, hrs, rev, con in AC.budget_sensitivity():
    put(ws, "A%d" % r, b, BLK, MONEY0)
    put(ws, "B%d" % r, "=A%d*(1-%s)/12/%s*12/52" % (r, CM_SHARE, PRICE_AC), BLK, NUM1)
    put(ws, "C%d" % r, "=A%d/12" % r, BLK, MONEY)
    put(ws, "D%d" % r, "=C%d-C%d*%s-(C%d*(1-%s)/%s)*(AC_ClientEconomics!$B$5+%s*%s*%s)"
                       "+C%d*%s-%s*%s*%s-C%d*%s*%s"
                       % (r, r, CM_SHARE, r, CM_SHARE, PRICE_AC, ADMIN_R, COORD_AC, ONCOST,
                          r, CM_SHARE, CM_HOURS, COORD_AC, ONCOST, r, CONTRIB_SH, BADDEBT), BLK, MONEY)
    r += 1
r += 1
put(ws, "A%d" % r,
    "Read the hours column. Even the TOP classification buys about thirteen service hours a week and most buy under "
    "five. You cannot sell more hours to a willing client — you can only acquire more clients. That makes this an "
    "acquisition business, not a rostering one, and it is the single most important structural difference between "
    "the two ventures.", WARN, wrap=True)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=4)
ws.row_dimensions[r].height = 56

# =========================================================================
# 12. STRUCTURE — TWO ENTITIES OR ONE
# =========================================================================
ws = sheet("Structure", [56, 18, 18, 18, 56])
title(ws, "Two separate companies, or one — what the choice costs", 5,
      "Zaid chose two separate entities (DEC-006). This sheet prices that choice so the number sits in front of the "
      "decision rather than behind it. It is a liability decision, and it is his to make.")
header(ws, 4, ["", "Two entities", "One entity", "Difference", "What is and is not duplicated"])
S = AC.structure_comparison()
rows = [
 ("Fixed cost a month", S["two_entities_fixed_month"], S["one_entity_fixed_month"],
  "Accounting and payroll, bookkeeping, general overhead and the ASIC annual review are paid twice. "
  "Software licensed per client and insurance priced on turnover are not."),
 ("One-off setup", S["two_entities_oneoff"], S["one_entity_oneoff"],
  "One extra company registration and one extra business name."),
]
r = 5
for label, two, one, note in rows:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    put(ws, "B%d" % r, two, BLK, MONEY)
    put(ws, "C%d" % r, one, BLK, MONEY)
    put(ws, "D%d" % r, "=B%d-C%d" % (r, r), BOLD, MONEY)
    put(ws, "E%d" % r, note, NOTE, wrap=True)
    ws.row_dimensions[r].height = 34
    r += 1
put(ws, "A%d" % r, "Cost of separation, a year", BOLD)
put(ws, "D%d" % r, "=D5*12", BOLD, MONEY)
put(ws, "E%d" % r, "The monthly difference, annualised.", NOTE, wrap=True)
r += 1
put(ws, "A%d" % r, "COST OF SEPARATION OVER THREE YEARS", BOLD)
put(ws, "D%d" % r, "=D5*36+D6", BOLD, MONEY)
put(ws, "E%d" % r, "This is the number to hold against the benefit.", NOTE, wrap=True)
r += 2
put(ws, "A%d" % r,
    "What the separation BUYS: a compliance failure in one sector cannot reach the other's assets, and a suspension "
    "by one regulator does not stop all revenue. For two separately regulated care businesses those are real. "
    "What it DOES NOT buy: independence from the owner. Both plans depend on the owner doing administration unpaid "
    "in year one, so illness stops both revenue lines on the same day. Two entities separate legal liability, not "
    "operational dependency (RSK-016). 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.", WARN, wrap=True)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=5)
ws.row_dimensions[r].height = 72

# =========================================================================
# 13. TRADE STUDY 3 — LIVE WEIGHTS
# =========================================================================
ws = sheet("TradeStudy3", [40, 12] + [13] * 4 + [56])
title(ws, "Trade study 3 — which business to start first", 7,
      "Weights fixed and timestamped BEFORE any option was scored: %s. The weight column is LIVE — change a weight "
      "and every score recomputes, so you can find your own answer rather than take mine." % TS.WEIGHTS_RECORD)
header(ws, 4, ["Criterion", "Weight"] + [o[0] for o in TS.OPTIONS] + ["Why this criterion carries this weight"])
WHY = {
 "capital":   "You are the sole capital provider. It is the only criterion where being wrong is unrecoverable.",
 "contrib":   "The unit both businesses acquire. Equal to capital, because a business that cannot fund you is not a business however little it cost to start.",
 "time":      "Every month before revenue is a month of fixed cost against a finite runway. It compounds with capital.",
 "stability": "Both sectors are mid-reform. A model whose funding mechanism is being redesigned is a model whose numbers expire.",
 "owner":     "A line that only works while you personally deliver it is a job, not a business, and it stops when you do.",
}
r = 5
first = r
for key, label, wt in TS.CRITERIA:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    put(ws, "B%d" % r, wt, BLK, '0.00', fill=YEL)
    for i, (ok_, _) in enumerate(TS.OPTIONS):
        put(ws, "%s%d" % (get_column_letter(3 + i), r), TS.SCORES[ok_][key][0], BLK, '0')
    put(ws, "G%d" % r, WHY[key], NOTE, wrap=True)
    ws.row_dimensions[r].height = 32
    r += 1
last = r - 1
put(ws, "A%d" % r, "Weights must total", BOLD)
put(ws, "B%d" % r, "=SUM(B%d:B%d)" % (first, last), BOLD, '0.00')
put(ws, "G%d" % r, "If this is not 1.00 the scores below are meaningless.", WARN, wrap=True)
r += 1
put(ws, "A%d" % r, "WEIGHTED SCORE", BOLD)
for i in range(len(TS.OPTIONS)):
    col = get_column_letter(3 + i)
    put(ws, "%s%d" % (col, r), "=SUMPRODUCT($B$%d:$B$%d,%s%d:%s%d)" % (first, last, col, first, col, last),
        BOLD, '0.00')
put(ws, "G%d" % r, "Live. Change a weight above and this moves.", NOTE, wrap=True)
r += 2
put(ws, "A%d" % r, "THE OPTIONS", BOLD)
r += 1
for key, label in TS.OPTIONS:
    put(ws, "A%d" % r, key, BOLD)
    put(ws, "B%d" % r, label, BLK, wrap=True)
    ws.merge_cells(start_row=r, start_column=2, end_row=r, end_column=7)
    ws.row_dimensions[r].height = 20
    r += 1
r += 1
put(ws, "A%d" % r,
    "The margin between first and second is %.2f, and four of the five criteria change the winner within a weight "
    "movement you could reasonably make. The specific fork: raise capital preservation from 0.25 to 0.31 and support "
    "coordination wins instead. If the most you are willing to lose entirely is a small number, option C is the right "
    "answer and the recommendation is the wrong one. GOV-B6.4 requires that a result this close is escalated, not "
    "taken — recorded as DEC-007, parked with Zaid." % TS.margin(), WARN, wrap=True)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=7)
ws.row_dimensions[r].height = 60

# =========================================================================
# 14. COMBINED — WHAT MUST BE IN THE BANK
# =========================================================================
ws = sheet("Combined", [52, 18, 18, 20, 56])
title(ws, "Both entities together — what must be in the bank before the first client", 5,
      "Base case, owner drawing nothing for six months. Add a $6,000 a month draw and the runway lines roughly triple.")
header(ws, 4, ["", "NDIS entity", "Aged care entity", "Combined", "What this line is"])
Ares = AC.results(); Mres = M.results()
crows = [
 ("One-off setup, base case", Mres["one_off_core"][1], Ares["one_off"][1],
  "Registration, screening, first aid, policy manuals, brand, and the aged care registration fee band."),
 ("Fixed cost a month, base case", Mres["monthly_fixed"][1], Ares["monthly_fixed"][1],
  "Software, accounting, bookkeeping, overhead, insurance and company fees, held separately for each entity."),
 ("Six-month runway, no owner draw", Mres["runway_6mo_core_no_draw"], Ares["runway_6mo_no_draw"],
  "Setup plus six months of fixed cost with no revenue at all."),
 ("Working capital, low case", Mres["working_capital_at_300h"], Ares["working_capital"],
  "Wages fall due before invoices are paid. NDIS at a fourteen-day lag; aged care at seven (ASM-030)."),
 ("Working capital, high case", Mres["working_capital_at_300h_30day"], Ares["working_capital"],
  "NDIS at a thirty-day plan-manager lag. This is the figure that was absent from the earlier plan entirely."),
]
r = 5
for label, n, a, note in crows:
    put(ws, "A%d" % r, label, BLK, wrap=True)
    put(ws, "B%d" % r, n, BLK, MONEY)
    put(ws, "C%d" % r, a, BLK, MONEY)
    put(ws, "D%d" % r, "=B%d+C%d" % (r, r), BOLD, MONEY)
    put(ws, "E%d" % r, note, NOTE, wrap=True)
    ws.row_dimensions[r].height = 32
    r += 1
put(ws, "A%d" % r, "TOTAL CAPITAL BEFORE FIRST REVENUE, low", BOLD)
put(ws, "D%d" % r, "=D7+D8", BOLD, MONEY0)
put(ws, "E%d" % r, "Six-month runway plus low-case working capital, both entities.", NOTE, wrap=True)
r += 1
put(ws, "A%d" % r, "TOTAL CAPITAL BEFORE FIRST REVENUE, high", BOLD)
put(ws, "D%d" % r, "=D7+D9", BOLD, MONEY0)
put(ws, "E%d" % r, "The same with the thirty-day NDIS payment lag.", NOTE, wrap=True)
r += 2
put(ws, "A%d" % r,
    "Your earlier files put the whole startup at $15,000 to $30,000 for ONE business and carried no working-capital "
    "row at all. The gap between that and this is not a difference of opinion — it is the working capital and the "
    "second entity.", WARN, wrap=True)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=5)
ws.row_dimensions[r].height = 44

wb.save(OUT)
print("wrote", OUT)
