nx_finance_web.nx source
↩ module page · 392 lines · 30395 B
1// nx_finance_web.nx -- the SOVEREIGN, ZERO-JAVASCRIPT nishifamily.com/finance dashboard renderer.
2// ONE renderer (fw_render), TWO consumers (DRY #15): the static emitter (nx_fin_dashboard -> a file the
3// Publisher ships) AND the live OPAQUE route (olg_route GET /finance, session-gated like /audio). The page
4// is SERVER-COMPUTED: the debt-payoff plan (avalanche vs snowball) is run LIVE through nx_debt -> nx_money
5// at render time, so the numbers are the real engine's output, not hardcoded. dbt_strategy is pure (no
6// store), so this is safe to run inside the daemon request path. 0 <script>: themes via radio/:checked,
7// no client code -- all logic is in this Nishi organ (operator: "no js, nishi everywhere").
8// license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_money.nx"
11import "nx_debt_payoff.nx"
12import "nx_fin_finance.nx"
13import "nx_bills.nx"
14import "nx_accommodate.nx"
15import "nx_adhd.nx"
16import "nx_purchase.nx"
17import "nx_climbout.nx"
18import "nx_intake_profile.nx"
19import "nx_climbout_real.nx"
20import "nx_climbout_progress.nx" // R7: read THIS user's climb progress for the live page
21import "nx_bills_store.nx" // the person's REAL recurring bills (entered via /finance/bills)
22
23func fw_cat(out: *u8, off: i64, s: *u8) -> i64 {
24 var o: i64 = off; var i: i64 = 0
25 while s[i] != (0 as u8) { out[o] = s[i]; o = o + 1; i = i + 1 }
26 return o
27}
28// unsigned decimal of v (>=0) into out at off; returns new off.
29func fw_n(out: *u8, off: i64, v: i64) -> i64 {
30 if v == 0 { out[off] = 0x30 as u8; return off + 1 }
31 let tmp: *u8 = sys_mmap(24); var m: i64 = v; var k: i64 = 0
32 while m > 0 { tmp[k] = (0x30 + (m % 10)) as u8; m = m / 10; k = k + 1 }
33 var o: i64 = off; var i: i64 = k - 1
34 while i >= 0 { out[o] = tmp[i]; o = o + 1; i = i - 1 }
35 return o
36}
37// money cents -> "$D.CC" (exact, no-float) at off; returns new off.
38func fw_money(out: *u8, off: i64, cents: i64) -> i64 {
39 var o: i64 = off
40 var c: i64 = cents
41 if c < 0 { out[o] = 0x2d as u8; o = o + 1; c = 0 - c }
42 out[o] = 0x24 as u8; o = o + 1
43 let dollars: i64 = c / 100
44 let cc: i64 = c - dollars * 100
45 o = fw_n(out, o, dollars)
46 out[o] = 0x2e as u8; o = o + 1
47 out[o] = (0x30 + (cc / 10)) as u8; o = o + 1
48 out[o] = (0x30 + (cc % 10)) as u8; o = o + 1
49 return o
50}
51// day-of-month [1..31] for a UTC epoch-seconds value (Howard Hinnant's civil_from_days, day component).
52// Used to compute "today" for overdue-bill detection. UTC (off by at most a day near midnight; advisory).
53func fw_day_of_month(epoch_sec: i64) -> i64 {
54 var z: i64 = epoch_sec / 86400
55 z = z + 719468
56 var era: i64 = z / 146097
57 if z < 0 { era = (z - 146096) / 146097 }
58 let doe: i64 = z - era * 146097
59 let yoe: i64 = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365
60 let doy: i64 = doe - (365 * yoe + yoe / 4 - yoe / 100)
61 let mp: i64 = (5 * doy + 2) / 153
62 return doy - (153 * mp + 2) / 5 + 1
63}
64
65// the human label for a climb-out tier (the action the person actually takes next). DATA, not magic (#11).
66func fw_tier_label(tier: i64) -> *u8 {
67 if tier == CO_STABILIZE { return "pay the overdue bill (stop the bleeding first)" as *u8 }
68 if tier == CO_RECOVER { return "dispute the medical overcharges to recover what you are owed" as *u8 }
69 if tier == CO_CUTCOST { return "switch to the cheapest SAFE rate" as *u8 }
70 if tier == CO_ATTACKDEBT { return "attack the highest-APR debt" as *u8 }
71 if tier == CO_BUILD { return "build your one-month emergency buffer" as *u8 }
72 return "your next step" as *u8
73}
74
75// ADVANCE the climb: mark this user's CURRENT next step done (the "I did this step" button posts here).
76// Recomputes the SAME plan fw_render shows (deterministic from the person's profile), finds the next
77// not-done step, and marks it -> on reload the bar rises + that step disappears. Idempotent-safe: if all
78// steps are already done, next<0 and nothing is marked. Returns 0 ok.
79func fw_progress_advance(respondent: *u8) -> i64 {
80 let bb_bal: i64 = ip_get_pos("knowledge/store/intake-" as *u8, respondent, "debt_balance\x00" as *u8, 2000000)
81 let bb_apr: i64 = mny_rate_from_pct(ip_get_pos("knowledge/store/intake-" as *u8, respondent, "debt_apr_pct\x00" as *u8, 24), 1)
82 let bb_pay: i64 = ip_get_pos("knowledge/store/intake-" as *u8, respondent, "monthly_payment\x00" as *u8, 60000)
83 let co_tiers: *i64 = sys_mmap(8 * 8) as *i64
84 let co_imp: *i64 = sys_mmap(8 * 8) as *i64
85 let co_order: *i64 = sys_mmap(8 * 8) as *i64
86 let co_nn: *i64 = sys_mmap(16) as *i64
87 cor_dashboard("knowledge/store/bills-" as *u8, respondent, fw_day_of_month(sys_now_realtime_sec()), bb_bal, bb_apr, bb_pay, co_tiers, co_imp, co_order, co_nn)
88 let n: i64 = co_nn[0]
89 let done: *u8 = sys_mmap(64)
90 cp_get("knowledge/store/progress-" as *u8, respondent, done, n)
91 let next: i64 = cp_next(done, co_order, n)
92 if next >= 0 { cp_mark("knowledge/store/progress-" as *u8, respondent, next, n) }
93 return next
94}
95
96// human label for a bill category (BL_HOUSING..BL_OTHER). DATA, not magic (#11).
97func fw_bill_cat_label(cat: i64) -> *u8 {
98 if cat == BL_HOUSING { return "Housing" as *u8 }
99 if cat == BL_INSURANCE { return "Insurance" as *u8 }
100 if cat == BL_UTILITY { return "Utility" as *u8 }
101 if cat == BL_DEBT { return "Debt" as *u8 }
102 return "Other" as *u8
103}
104// human label for a bill cadence (months).
105func fw_cadence_label(cad: i64) -> *u8 {
106 if cad == 1 { return "monthly" as *u8 }
107 if cad == 3 { return "quarterly" as *u8 }
108 if cad == 6 { return "every 6 months" as *u8 }
109 if cad == 12 { return "yearly" as *u8 }
110 return "per period" as *u8
111}
112
113// Render the full dashboard HTML into out (caller buffer >= ~16 KiB). Returns byte length.
114func fw_render(out: *u8, respondent: *u8) -> i64 {
115 // --- live engine computation: avalanche vs snowball on a representative debt set ---
116 let rate24: i64 = mny_rate_from_pct(24, 1)
117 let rate6: i64 = mny_rate_from_pct(6, 1)
118 let bal: *i64 = sys_mmap(8 * 2) as *i64
119 let apr: *i64 = sys_mmap(8 * 2) as *i64
120 let minp: *i64 = sys_mmap(8 * 2) as *i64
121 bal[0] = 1000000; apr[0] = rate24; minp[0] = 20000
122 bal[1] = 200000; apr[1] = rate6; minp[1] = 5000
123 let avi: *i64 = sys_mmap(16) as *i64
124 let sni: *i64 = sys_mmap(16) as *i64
125 let avm: i64 = dbt_strategy(bal, apr, minp, 2, 50000, 0, avi)
126 let snm: i64 = dbt_strategy(bal, apr, minp, 2, 50000, 1, sni)
127 let saved: i64 = sni[0] - avi[0]
128 let sooner: i64 = snm - avm
129
130 var o: i64 = 0
131 o = fw_cat(out, o, "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">" as *u8)
132 o = fw_cat(out, o, "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" as *u8)
133 o = fw_cat(out, o, "<title>Nishi Finance</title><style>" as *u8)
134 o = fw_cat(out, o, "*{box-sizing:border-box}html,body{margin:0}" as *u8)
135 o = fw_cat(out, o, ".thr{position:absolute;width:0;height:0;opacity:0;pointer-events:none}" as *u8)
136 o = fw_cat(out, o, ".app{--bg:#fbfbf9;--fg:#1d1d1b;--muted:#6b6b66;--rule:#e3e3dd;--sheet:#fff;--accent:#0b6b3a;background:var(--bg);color:var(--fg);min-height:100vh;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif}" as *u8)
137 o = fw_cat(out, o, "#th-dark:checked~.app{--bg:#11161a;--fg:#d8e0d8;--muted:#8d958d;--rule:#26303a;--sheet:#161d22;--accent:#46d08a}" as *u8)
138 o = fw_cat(out, o, "#th-sepia:checked~.app{--bg:#f4ecd8;--fg:#43391f;--muted:#8a7a55;--rule:#e0d4b6;--sheet:#fbf4e3;--accent:#0b6b3a}" as *u8)
139 o = fw_cat(out, o, ".bar{position:sticky;top:0;display:flex;gap:.6rem;align-items:center;padding:.7rem 1rem;background:var(--sheet);border-bottom:1px solid var(--rule)}" as *u8)
140 o = fw_cat(out, o, ".bar h1{font-size:1.15rem;margin:0;flex:1}" as *u8)
141 o = fw_cat(out, o, ".tbar label{cursor:pointer;border:1px solid var(--rule);border-radius:8px;padding:.25rem .55rem;font-size:13px;color:var(--fg)}" as *u8)
142 o = fw_cat(out, o, "main{max-width:880px;margin:0 auto;padding:1.4rem}" as *u8)
143 o = fw_cat(out, o, "section{background:var(--sheet);border:1px solid var(--rule);border-radius:12px;padding:1.1rem 1.2rem;margin:1.1rem 0}" as *u8)
144 o = fw_cat(out, o, "h2{font-size:1.02rem;margin:.1rem 0 .8rem}" as *u8)
145 o = fw_cat(out, o, "table{width:100%;border-collapse:collapse;font-size:.93rem}" as *u8)
146 o = fw_cat(out, o, "th,td{text-align:left;padding:.45rem .5rem;border-bottom:1px solid var(--rule)}" as *u8)
147 o = fw_cat(out, o, "td.n,th.n{text-align:right;font-variant-numeric:tabular-nums}" as *u8)
148 o = fw_cat(out, o, ".win{background:var(--accent);color:#fff;border-radius:10px;padding:.8rem 1rem;font-weight:600;margin-top:.9rem}" as *u8)
149 o = fw_cat(out, o, ".muted{color:var(--muted);font-size:.86rem;line-height:1.5}" as *u8)
150 o = fw_cat(out, o, ".pill{display:inline-block;font-size:.74rem;border:1px solid var(--rule);border-radius:999px;padding:.1rem .5rem;color:var(--muted);margin-right:.35rem}" as *u8)
151 o = fw_cat(out, o, ":focus-visible{outline:3px solid var(--accent);outline-offset:2px}" as *u8)
152 o = fw_cat(out, o, "@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}" as *u8)
153 o = fw_cat(out, o, "</style></head><body>" as *u8)
154 o = fw_cat(out, o, "<input class=\"thr\" type=\"radio\" name=\"nxtheme\" id=\"th-light\" checked>" as *u8)
155 o = fw_cat(out, o, "<input class=\"thr\" type=\"radio\" name=\"nxtheme\" id=\"th-sepia\">" as *u8)
156 o = fw_cat(out, o, "<input class=\"thr\" type=\"radio\" name=\"nxtheme\" id=\"th-dark\">" as *u8)
157 o = fw_cat(out, o, "<div class=\"app\"><header class=\"bar\"><h1>\xf0\x9f\x92\xb0 Nishi Finance</h1>" as *u8)
158 o = fw_cat(out, o, "<span class=\"tbar\" role=\"group\" aria-label=\"Theme\"><label for=\"th-light\">Light</label><label for=\"th-sepia\">Sepia</label><label for=\"th-dark\">Dark</label></span></header><main>" as *u8)
159
160 // --- FOCUS BANNER (page-level "one thing at a time"): the single most important action, FIRST, before
161 // all the detail -- so an overwhelmed person never has to scan 11 sections to find what to do. ---
162 let fb_amt: *i64 = sys_mmap(8 * 64) as *i64; let fb_cad: *i64 = sys_mmap(8 * 64) as *i64
163 let fb_due: *i64 = sys_mmap(8 * 64) as *i64; let fb_cat: *i64 = sys_mmap(8 * 64) as *i64; let fb_pd: *i64 = sys_mmap(8 * 64) as *i64
164 let fb_n: i64 = bs_load("knowledge/store/bills-" as *u8, respondent, fb_amt, fb_cad, fb_due, fb_cat, fb_pd, 64)
165 var fb_oblig: i64 = 0
166 if fb_n > 0 { fb_oblig = bl_monthly_total(fb_amt, fb_cad, fb_n) }
167 let fb_income: i64 = ip_get_pos("knowledge/store/intake-" as *u8, respondent, "income_monthly\x00" as *u8, 400000)
168 let fb_surplus: i64 = fb_income - fb_oblig
169 let fb_oidx: *i64 = sys_mmap(8 * 64) as *i64
170 var fb_overdue: i64 = 0
171 if fb_n > 0 { fb_overdue = bl_overdue(fb_due, fb_pd, fw_day_of_month(sys_now_realtime_sec()), fb_n, fb_oidx) }
172 o = fw_cat(out, o, "<section><div class=\"win\"><strong>\xf0\x9f\x91\x89 Your one thing right now:</strong> " as *u8)
173 if fb_surplus < 0 {
174 o = fw_cat(out, o, "you’re " as *u8); o = fw_money(out, o, 0 - fb_surplus)
175 o = fw_cat(out, o, "/month underwater. Before anything else, close that gap — cut a bill or add income. Everything below helps you do exactly that." as *u8)
176 } else {
177 if fb_overdue > 0 {
178 o = fw_cat(out, o, "pay the " as *u8); o = fw_n(out, o, fb_overdue)
179 o = fw_cat(out, o, " overdue bill" as *u8)
180 if fb_overdue != 1 { o = fw_cat(out, o, "s" as *u8) }
181 o = fw_cat(out, o, " — stop late fees first. The rest can wait." as *u8)
182 } else {
183 o = fw_cat(out, o, "you’re cashflow-positive with nothing overdue. Do just the ONE next step in your climb-out plan below — the rest is saved for when you’re ready." as *u8)
184 }
185 }
186 o = fw_cat(out, o, "</div></section>" as *u8)
187
188 o = fw_cat(out, o, "<section><span class=\"pill\">sovereign</span><span class=\"pill\">no-float exact cents</span><span class=\"pill\">append-only double-entry</span><span class=\"pill\">never-brick #26</span>" as *u8)
189 o = fw_cat(out, o, "<p class=\"muted\">Your money engine, built from the hardware rung up. Amounts are exact integer cents (floats are wrong for money); the ledger is content-addressed and append-only (history is the truth); no irreversible money action happens without a dry-run-proven, confirmation-gated path. This dashboard is rendered by a Nishi organ — zero JavaScript, served behind the OPAQUE login wall.</p></section>" as *u8)
190
191 o = fw_cat(out, o, "<section><h2>Debt payoff plan — Avalanche vs Snowball</h2>" as *u8)
192 o = fw_cat(out, o, "<p class=\"muted\">Worked example: a $10,000 balance at 24% APR (min $200) and a $2,000 balance at 6% APR (min $50), with a $500/month budget. Computed live by the sovereign engine just now.</p>" as *u8)
193 o = fw_cat(out, o, "<table><thead><tr><th>Strategy</th><th class=\"n\">Months to debt-free</th><th class=\"n\">Total interest paid</th></tr></thead><tbody>" as *u8)
194 o = fw_cat(out, o, "<tr><td>Avalanche (highest APR first)</td><td class=\"n\">" as *u8)
195 o = fw_n(out, o, avm); o = fw_cat(out, o, "</td><td class=\"n\">" as *u8); o = fw_money(out, o, avi[0]); o = fw_cat(out, o, "</td></tr>" as *u8)
196 o = fw_cat(out, o, "<tr><td>Snowball (lowest balance first)</td><td class=\"n\">" as *u8)
197 o = fw_n(out, o, snm); o = fw_cat(out, o, "</td><td class=\"n\">" as *u8); o = fw_money(out, o, sni[0]); o = fw_cat(out, o, "</td></tr>" as *u8)
198 o = fw_cat(out, o, "</tbody></table>" as *u8)
199 o = fw_cat(out, o, "<div class=\"win\">Avalanche saves " as *u8); o = fw_money(out, o, saved)
200 o = fw_cat(out, o, " in interest and finishes " as *u8); o = fw_n(out, o, sooner)
201 o = fw_cat(out, o, " month(s) sooner.</div></section>" as *u8)
202
203 let f_usury: i64 = mny_rate_from_pct(36, 1)
204 let f_bal: i64 = 2000000
205 let f_pay: i64 = 60000
206 let f_aprs: *i64 = sys_mmap(8 * 4) as *i64
207 let f_fees: *i64 = sys_mmap(8 * 4) as *i64
208 let f_upf: *i64 = sys_mmap(8 * 4) as *i64
209 let f_lic: *i64 = sys_mmap(8 * 4) as *i64
210 f_aprs[0] = mny_rate_from_pct(24, 1); f_fees[0] = 0; f_upf[0] = 0; f_lic[0] = 1
211 f_aprs[1] = mny_rate_from_pct(11, 1); f_fees[1] = 10000; f_upf[1] = 0; f_lic[1] = 1
212 f_aprs[2] = mny_rate_from_pct(9, 1); f_fees[2] = 5000; f_upf[2] = 0; f_lic[2] = 1
213 f_aprs[3] = mny_rate_from_pct(4, 1); f_fees[3] = 0; f_upf[3] = 1; f_lic[3] = 0
214 let f_bi: *i64 = sys_mmap(16) as *i64
215 let f_best: i64 = fe_rank_best(f_bal, f_pay, f_aprs, f_fees, f_upf, f_lic, 4, f_usury, f_bi)
216 let f_mc: *i64 = sys_mmap(16) as *i64
217 let f_cur: i64 = fe_total_cost(f_bal, f_pay, f_aprs[0], f_fees[0], f_mc)
218 let f_save: i64 = f_cur - f_best
219 o = fw_cat(out, o, "<section><h2>Affordable funding — true cost, scam-shielded</h2>" as *u8)
220 o = fw_cat(out, o, "<p class=\"muted\">Worked example: $20,000 of high-interest debt at $600/month, ranked by TRUE total cost (fees + interest), not the headline rate. Any “lender” that demands an upfront payment or is unlicensed is EXCLUDED — the #1 loan-scam tell.</p>" as *u8)
221 o = fw_cat(out, o, "<table><thead><tr><th>Option</th><th class=\"n\">True total cost</th></tr></thead><tbody>" as *u8)
222 o = fw_cat(out, o, "<tr><td>Current cards (24% APR)</td><td class=\"n\">" as *u8); o = fw_money(out, o, f_cur); o = fw_cat(out, o, "</td></tr>" as *u8)
223 o = fw_cat(out, o, "<tr><td>Cheapest SAFE option found</td><td class=\"n\">" as *u8); o = fw_money(out, o, f_best); o = fw_cat(out, o, "</td></tr>" as *u8)
224 o = fw_cat(out, o, "</tbody></table>" as *u8)
225 o = fw_cat(out, o, "<div class=\"win\">Switching to the cheapest SAFE option saves " as *u8); o = fw_money(out, o, f_save); o = fw_cat(out, o, " — and the fake “international 4%” bait was rejected as advance-fee fraud.</div></section>" as *u8)
226 // monthly obligations from the person's REAL bills (bs_load) if entered; representative otherwise
227 let sb_amt: *i64 = sys_mmap(8 * 64) as *i64; let sb_cad: *i64 = sys_mmap(8 * 64) as *i64
228 let sb_due: *i64 = sys_mmap(8 * 64) as *i64; let sb_cat: *i64 = sys_mmap(8 * 64) as *i64; let sb_pd: *i64 = sys_mmap(8 * 64) as *i64
229 let sbn: i64 = bs_load("knowledge/store/bills-" as *u8, respondent, sb_amt, sb_cad, sb_due, sb_cat, sb_pd, 64)
230 var s_total: i64 = 0
231 if sbn > 0 {
232 s_total = bl_monthly_total(sb_amt, sb_cad, sbn)
233 } else {
234 let s_amt: *i64 = sys_mmap(8 * 8) as *i64
235 let s_cad: *i64 = sys_mmap(8 * 8) as *i64
236 s_amt[0] = 200000; s_cad[0] = 1
237 s_amt[1] = 180000; s_cad[1] = 6
238 s_amt[2] = 60000; s_cad[2] = 1
239 s_amt[3] = 15000; s_cad[3] = 1
240 s_total = bl_monthly_total(s_amt, s_cad, 4)
241 }
242 o = fw_cat(out, o, "<section><h2>Your situation — snapshot</h2>" as *u8)
243 if sbn > 0 {
244 o = fw_cat(out, o, "<p class=\"muted\">From your intake survey + the bills you entered (insurance, mortgage, HOA, utilities).</p>" as *u8)
245 } else {
246 o = fw_cat(out, o, "<p class=\"muted\">From your intake survey + bills register. Demo values shown — complete the survey + add your bills (below) to use your own numbers.</p>" as *u8)
247 }
248 o = fw_cat(out, o, "<table><tbody><tr><td>Monthly obligations (all bills, cadence-normalized)</td><td class=\"n\">" as *u8)
249 o = fw_money(out, o, s_total)
250 o = fw_cat(out, o, "</td></tr></tbody></table>" as *u8)
251 o = fw_cat(out, o, "<div class=\"win\">Climb-out plan: stabilize the bills → kill the highest-APR debt → audit medical bills for overcharges → switch to the cheapest SAFE rate → build a one-month buffer. One step at a time.</div></section>" as *u8)
252 // --- MONTHLY CASHFLOW: the core crisis number -- money IN minus committed bills OUT ---
253 let cf_income: i64 = ip_get_pos("knowledge/store/intake-" as *u8, respondent, "income_monthly\x00" as *u8, 400000)
254 let cf_surplus: i64 = cf_income - s_total
255 o = fw_cat(out, o, "<section><h2>Your monthly cashflow</h2>" as *u8)
256 o = fw_cat(out, o, "<p class=\"muted\">The most important number: what comes IN each month minus what is already committed to bills. What is left is what you can put toward the climb — or, if negative, the gap to close first.</p>" as *u8)
257 o = fw_cat(out, o, "<table><tbody>" as *u8)
258 o = fw_cat(out, o, "<tr><td>Income (monthly)</td><td class=\"n\">" as *u8); o = fw_money(out, o, cf_income); o = fw_cat(out, o, "</td></tr>" as *u8)
259 o = fw_cat(out, o, "<tr><td>Bills (monthly, cadence-normalized)</td><td class=\"n\">−" as *u8); o = fw_money(out, o, s_total); o = fw_cat(out, o, "</td></tr>" as *u8)
260 o = fw_cat(out, o, "</tbody></table>" as *u8)
261 if cf_surplus >= 0 {
262 o = fw_cat(out, o, "<div class=\"win\">Left over: " as *u8); o = fw_money(out, o, cf_surplus); o = fw_cat(out, o, " / month — put it toward your one next step, then the emergency buffer.</div>" as *u8)
263 } else {
264 o = fw_cat(out, o, "<div class=\"win\"><strong>You are " as *u8); o = fw_money(out, o, 0 - cf_surplus); o = fw_cat(out, o, " / month underwater.</strong> Closing this gap — cut a bill, lower a rate, or raise income — is the #1 priority; the plan below targets exactly this.</div>" as *u8)
265 }
266 // SURVIVAL RUNWAY: if income stopped, how long does available cash cover the bills? (job-loss resilience)
267 let rw_save: i64 = ip_get_pos("knowledge/store/intake-" as *u8, respondent, "available\x00" as *u8, 50000)
268 let rw_jobrisk: i64 = ip_get_pos("knowledge/store/intake-" as *u8, respondent, "job_loss_risk\x00" as *u8, 0)
269 if s_total > 0 {
270 let rw_m: i64 = fe_runway_months(rw_save, s_total)
271 o = fw_cat(out, o, "<div class=\"win\">If your income stopped today, the " as *u8)
272 o = fw_money(out, o, rw_save)
273 o = fw_cat(out, o, " you marked as available would cover about " as *u8)
274 if rw_m >= 1 {
275 o = fw_n(out, o, rw_m); o = fw_cat(out, o, " month" as *u8)
276 if rw_m != 1 { o = fw_cat(out, o, "s" as *u8) }
277 } else {
278 o = fw_n(out, o, fe_runway_days(rw_save, s_total)); o = fw_cat(out, o, " days" as *u8)
279 }
280 o = fw_cat(out, o, " of bills — your survival runway." as *u8)
281 if rw_jobrisk == 1 {
282 o = fw_cat(out, o, " <strong>You flagged job-loss risk</strong>, so this is the number that matters most: the plan below builds your buffer and cuts ongoing costs to lengthen it." as *u8)
283 }
284 o = fw_cat(out, o, "</div>" as *u8)
285 }
286 o = fw_cat(out, o, "</section>" as *u8)
287 let a_budget: i64 = ac_load_budget(AC_ADHD, 2, 5)
288 o = fw_cat(out, o, "<section><h2>Built for how your brain works</h2>" as *u8)
289 o = fw_cat(out, o, "<p class=\"muted\">ADHD, fatigue, sleep apnea, or anything that makes a long list overwhelming? Nishi shows you ONE thing at a time, reminders on, no time pressure. Disabilities should disappear here.</p>" as *u8)
290 o = fw_cat(out, o, "<div class=\"win\">Showing " as *u8); o = fw_n(out, o, a_budget); o = fw_cat(out, o, " of 5 steps — your one next step: <strong>stabilize the bills</strong>. The rest is saved for when you are ready.</div></section>" as *u8)
291 let im_hrs: i64 = adhd_hours_cost(20000, 2500)
292 let im_pct: i64 = adhd_pct_of_goal(20000, 100000)
293 o = fw_cat(out, o, "<section><h2>ADHD-aware money guard</h2>" as *u8)
294 o = fw_cat(out, o, "<p class=\"muted\">ADHD makes impulse buys, missed plans, and task-freeze expensive. Nishi steps in — gently, never blocking. You always decide.</p>" as *u8)
295 o = fw_cat(out, o, "<div class=\"win\">Impulse pause: that $200 gadget? Sleep on it 24h — it is " as *u8)
296 o = fw_n(out, o, im_hrs)
297 o = fw_cat(out, o, " hours of work and " as *u8)
298 o = fw_n(out, o, im_pct)
299 o = fw_cat(out, o, "% of your emergency buffer. Still want it tomorrow?</div>" as *u8)
300 o = fw_cat(out, o, "<p class=\"muted\">Planning: your bills surface days before they are due, so nothing slips. Frozen on a task? Nishi shrinks it to a 2-minute first step — just open the bill.</p></section>" as *u8)
301 let bb_bal: i64 = ip_get_pos("knowledge/store/intake-" as *u8, respondent, "debt_balance\x00" as *u8, 2000000)
302 let bb_apr: i64 = mny_rate_from_pct(ip_get_pos("knowledge/store/intake-" as *u8, respondent, "debt_apr_pct\x00" as *u8, 24), 1)
303 let bb_pay: i64 = ip_get_pos("knowledge/store/intake-" as *u8, respondent, "monthly_payment\x00" as *u8, 60000)
304 let bb_saved: i64 = pur_interest_saved(bb_bal, bb_apr, bb_pay, 20000)
305 let bb_true: i64 = pur_true_cost(20000, bb_saved)
306 o = fw_cat(out, o, "<section><h2>Before you buy (in the Nishi browser)</h2>" as *u8)
307 o = fw_cat(out, o, "<p class=\"muted\">When you reach for “buy”, the Nishi browser shows what you HAVE vs what it truly COSTS — including the opportunity cost. Grounded in real economics, not guesswork.</p>" as *u8)
308 o = fw_cat(out, o, "<div class=\"win\">That $200 gadget? Sticker is $200, but it truly costs " as *u8)
309 o = fw_money(out, o, bb_true)
310 o = fw_cat(out, o, " — because that $200 would have saved " as *u8)
311 o = fw_money(out, o, bb_saved)
312 o = fw_cat(out, o, " in interest on your 24% debt. What you have vs what it really costs, every time.</div></section>" as *u8)
313 let co_tiers: *i64 = sys_mmap(8 * 8) as *i64
314 let co_imp: *i64 = sys_mmap(8 * 8) as *i64
315 let co_order: *i64 = sys_mmap(8 * 8) as *i64
316 let co_nn: *i64 = sys_mmap(16) as *i64
317 let co_tot: i64 = cor_dashboard("knowledge/store/bills-" as *u8, respondent, fw_day_of_month(sys_now_realtime_sec()), bb_bal, bb_apr, bb_pay, co_tiers, co_imp, co_order, co_nn)
318 let co_budget: i64 = ac_load_budget(AC_ADHD, 2, co_nn[0])
319 o = fw_cat(out, o, "<section><h2>Your climb-out plan</h2>" as *u8)
320 o = fw_cat(out, o, "<p class=\"muted\">Everything the engines found — overcharges, a cheaper rate, the debt, the bills — fused into one path in the right order, then filtered to one step at a time.</p>" as *u8)
321 o = fw_cat(out, o, "<div class=\"win\">Total climb-out: " as *u8)
322 o = fw_money(out, o, co_tot)
323 o = fw_cat(out, o, " recovered + saved across the plan. Showing " as *u8)
324 o = fw_n(out, o, co_budget)
325 o = fw_cat(out, o, " step — your one next step: <strong>pay the overdue HOA bill</strong> (stop the bleeding first). The rest is sequenced and saved for when you are ready.</div></section>" as *u8)
326 // --- R7: TRACK THE CLIMB -- this user's progress over the same plan, made visible + interactive ---
327 let pr_done: *u8 = sys_mmap(64)
328 let pr_n: i64 = co_nn[0]
329 cp_get("knowledge/store/progress-" as *u8, respondent, pr_done, pr_n)
330 let pr_dn: i64 = cp_count(pr_done, pr_n)
331 let pr_pct: i64 = cp_pct(pr_dn, pr_n)
332 let pr_next: i64 = cp_next(pr_done, co_order, pr_n)
333 o = fw_cat(out, o, "<section><h2>Your climb to financial peace</h2>" as *u8)
334 o = fw_cat(out, o, "<p class=\"muted\">Every step you finish disappears — you only ever see the next one. No long overwhelming list, just the climb adding up.</p>" as *u8)
335 o = fw_cat(out, o, "<div class=\"win\">" as *u8)
336 o = fw_n(out, o, pr_dn); o = fw_cat(out, o, " of " as *u8); o = fw_n(out, o, pr_n)
337 o = fw_cat(out, o, " steps done — you are " as *u8); o = fw_n(out, o, pr_pct)
338 o = fw_cat(out, o, "% of the way to financial peace.</div>" as *u8)
339 o = fw_cat(out, o, "<div style=\"background:#1f2937;border-radius:6px;height:14px;margin:.6rem 0;overflow:hidden\"><div style=\"background:#34d399;height:14px;width:" as *u8)
340 o = fw_n(out, o, pr_pct)
341 o = fw_cat(out, o, "%\"></div></div>" as *u8)
342 if pr_next >= 0 {
343 o = fw_cat(out, o, "<p>Your one next step: <strong>" as *u8)
344 o = fw_cat(out, o, fw_tier_label(co_tiers[pr_next]))
345 o = fw_cat(out, o, "</strong></p>" as *u8)
346 o = fw_cat(out, o, "<form method=\"post\" action=\"/finance/progress\"><button type=\"submit\" style=\"background:#34d399;color:#06281d;border:0;border-radius:8px;padding:.7rem 1.1rem;font-size:1rem;font-weight:700;cursor:pointer;min-height:44px\">✓ I did this step</button></form>" as *u8)
347 } else {
348 o = fw_cat(out, o, "<div class=\"win\">You have completed every step — you climbed out. This is what financial peace looks like.</div>" as *u8)
349 }
350 o = fw_cat(out, o, "</section>" as *u8)
351 // --- YOUR BILLS: the person's OWN recurring obligations, entered via the form (real, not representative) ---
352 let yb_amt: *i64 = sys_mmap(8 * 64) as *i64
353 let yb_cad: *i64 = sys_mmap(8 * 64) as *i64
354 let yb_due: *i64 = sys_mmap(8 * 64) as *i64
355 let yb_cat: *i64 = sys_mmap(8 * 64) as *i64
356 let yb_pd: *i64 = sys_mmap(8 * 64) as *i64
357 let ybn: i64 = bs_load("knowledge/store/bills-" as *u8, respondent, yb_amt, yb_cad, yb_due, yb_cat, yb_pd, 64)
358 o = fw_cat(out, o, "<section><h2>Your bills</h2>" as *u8)
359 if ybn > 0 {
360 o = fw_cat(out, o, "<div class=\"win\">Your real monthly obligations: " as *u8)
361 o = fw_money(out, o, bl_monthly_total(yb_amt, yb_cad, ybn))
362 o = fw_cat(out, o, " across " as *u8); o = fw_n(out, o, ybn); o = fw_cat(out, o, " bills.</div><ul>" as *u8)
363 var ybi: i64 = 0
364 while ybi < ybn {
365 o = fw_cat(out, o, "<li>" as *u8)
366 o = fw_cat(out, o, fw_bill_cat_label(yb_cat[ybi]))
367 o = fw_cat(out, o, " — " as *u8); o = fw_money(out, o, yb_amt[ybi])
368 o = fw_cat(out, o, " " as *u8); o = fw_cat(out, o, fw_cadence_label(yb_cad[ybi]))
369 o = fw_cat(out, o, ", due day " as *u8); o = fw_n(out, o, yb_due[ybi])
370 if yb_pd[ybi] == 0 { o = fw_cat(out, o, " — <strong>unpaid</strong>" as *u8) } else { o = fw_cat(out, o, " — paid" as *u8) }
371 o = fw_cat(out, o, "</li>" as *u8)
372 ybi = ybi + 1
373 }
374 o = fw_cat(out, o, "</ul>" as *u8)
375 } else {
376 o = fw_cat(out, o, "<p class=\"muted\">No bills yet. Add your recurring bills (rent, insurance, utilities) so your plan runs on your real numbers, not examples.</p>" as *u8)
377 }
378 o = fw_cat(out, o, "<form method=\"post\" action=\"/finance/bills\" style=\"display:grid;gap:.5rem;max-width:440px\">" as *u8)
379 o = fw_cat(out, o, "<label>Amount ($) <input name=\"bill_amount\" type=\"number\" min=\"0\" required></label>" as *u8)
380 o = fw_cat(out, o, "<label>How often <select name=\"bill_cadence\"><option value=\"1\">Monthly</option><option value=\"3\">Quarterly</option><option value=\"6\">Every 6 months</option><option value=\"12\">Yearly</option></select></label>" as *u8)
381 o = fw_cat(out, o, "<label>Due day of month <input name=\"bill_due_day\" type=\"number\" min=\"1\" max=\"31\" value=\"1\"></label>" as *u8)
382 o = fw_cat(out, o, "<label>Category <select name=\"bill_category\"><option value=\"0\">Housing</option><option value=\"1\">Insurance</option><option value=\"2\">Utility</option><option value=\"3\">Debt</option><option value=\"4\">Other</option></select></label>" as *u8)
383 o = fw_cat(out, o, "<label><input name=\"bill_paid\" type=\"checkbox\" value=\"1\"> Already paid this period</label>" as *u8)
384 o = fw_cat(out, o, "<button type=\"submit\" style=\"background:#34d399;color:#06281d;border:0;border-radius:8px;padding:.7rem 1.1rem;font-weight:700;min-height:44px;cursor:pointer\">Add this bill</button>" as *u8)
385 o = fw_cat(out, o, "</form></section>" as *u8)
386 o = fw_cat(out, o, "<section><h2>Ladder</h2><p class=\"muted\">R1 no-float money · R2 double-entry ledger + net-worth-as-view · R4 debt & amortization (here) · next: bill/EOB ingest, medical-overbilling audit, jurisdiction rule packs, the fail-safe action queue. Built the Nishi way: build → gate → measure → bank, each rung sovereign (nx_cc→nxasm, no third-party).</p></section>" as *u8)
387
388 o = fw_cat(out, o, "<footer class=\"muted\" style=\"text-align:center;padding:1.4rem\">Sovereign · zero-JavaScript · exact no-float cents · behind the OPAQUE wall · Nishi Finance</footer>" as *u8)
389 o = fw_cat(out, o, "</main></div></body></html>" as *u8)
390 out[o] = 0 as u8
391 return o
392}