# -*- coding: utf-8 -*-
"""
export_model_json.py — projects both financial models and the trade study into one JSON file
for the Node deck builder.
OWNER: 01_System/export_model_json.py
REVIEW: whenever a model gains a field the deck reads.
WHY: build_deck.js is JavaScript and cannot import the Python models. Before this file existed
it read /tmp/model.json, which someone had written by hand in a shell — so the deck's figures
came from a file outside the project, outside configuration control, and gone the moment the
machine restarted (GOV-F3.11: no links to live files outside the project). The deck now reads a
generated projection inside the project, and build_all.py regenerates it before every deck build.
"""
import os, sys, json
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import model_params as M
import model_agedcare as AC
import trade_study as TS
# 02_Work/_scratch is for throwaway probes only — the placement map forbids anything another
# file depends on living there, and the deck depends on this. It is a generated projection,
# so it belongs in 01_System beside the other generated projections.
OUT = os.path.join(HERE, "model_export.json")
def build():
R = M.results()
A = AC.results()
S = A["structure"]
base, winner, runner, sens = TS.sensitivity()
d = dict(R)
d["base_cum"] = [r["cumulative_cash"] for r in R["base_rows"]]
d["slow_cum"] = [r["cumulative_cash"] for r in R["slow_rows"]]
d["paid_cum"] = [r["cumulative_cash"] for r in R["paid_rows"]]
d["base_final"] = d["base_cum"][-1]
d["slow_final"] = d["slow_cum"][-1]
d["paid_final"] = d["paid_cum"][-1]
d["agedcare"] = A
d["structure"] = S
d["trade"] = {
"weights_record": TS.WEIGHTS_RECORD,
"criteria": [{"key": k, "label": l, "weight": w} for k, l, w in TS.CRITERIA],
"ranking": [{"option": k, "label": l, "score": sc} for k, l, sc in base],
"margin": TS.margin(),
"flips": [{"criterion": lab, "weight_now": wt,
"flips_at": (fl[0] if fl else None),
"hands_it_to": (fl[1] if fl else None),
"new_score": (fl[2] if fl else None)} for lab, wt, fl, _ in sens],
}
d["combined"] = {
"one_off": round(R["one_off_core"][1] + A["one_off"][1], 2),
"fixed_month": S["two_entities_fixed_month"],
"runway_6mo": round(R["runway_6mo_core_no_draw"] + A["runway_6mo_no_draw"], 2),
"working_capital_low": round(R["working_capital_at_300h"] + A["working_capital"], 2),
"working_capital_high": round(R["working_capital_at_300h_30day"] + A["working_capital"], 2),
"total_capital_low": round(R["runway_6mo_core_no_draw"] + A["runway_6mo_no_draw"]
+ R["working_capital_at_300h"] + A["working_capital"], 2),
"total_capital_high": round(R["runway_6mo_core_no_draw"] + A["runway_6mo_no_draw"]
+ R["working_capital_at_300h_30day"] + A["working_capital"], 2),
}
return d
if __name__ == "__main__":
os.makedirs(os.path.dirname(OUT), exist_ok=True)
with open(OUT, "w", encoding="utf-8") as f:
json.dump(build(), f, indent=1, default=str)
print("wrote %s" % os.path.relpath(os.path.abspath(OUT), os.path.dirname(HERE)))