nx_money_situation.nx source
↩ module page · 83 lines · 5201 B
1// nx_money_situation.nx -- the FOUNDATION of the comprehensive money system: a person's whole financial SITUATION
2// (income, expenses, debts, assets, goals) + the derived metrics every module reads so advice is "optimal for THEIR
3// situation", not generic. Integer-exact (cents / basis-points). Composes nx_debt_strategy (debts layout + underwater
4// check). The keystone is sit_priority(): given the WHOLE picture it routes to the right focus -- relief, emergency
5// fund, debt, or invest -- the decision the operator asked for. license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "nx_debt_strategy.nx" // DS_FIELDS + ds_can_meet_minimums (debts[i*3] = [balance, apr_bps, min])
8const SIT_MAGIC_10000: i64 = 10000
9
10// Situation = a flat i64 array (SIT_FIELDS long) + a separate debts array.
11const SIT_INCOME_GROSS: i64 = 0 // monthly gross income cents
12const SIT_INCOME_NET: i64 = 1 // monthly after-tax income cents
13const SIT_EXPENSES_ESSENTIAL: i64 = 2 // monthly essentials cents (housing/food/utilities/transport/insurance)
14const SIT_SPEND_DISCRETIONARY: i64 = 3 // monthly discretionary spend cents
15const SIT_CASH: i64 = 4 // liquid cash/savings cents
16const SIT_RETIREMENT: i64 = 5 // retirement assets cents
17const SIT_OTHER_ASSETS: i64 = 6 // other assets cents
18const SIT_DEPENDENTS: i64 = 7 // number of dependents
19const SIT_EMPLOYED: i64 = 8 // 1 = employed, 0 = not
20const SIT_EFUND_TARGET_MONTHS: i64 = 9 // desired emergency fund, in months of essentials
21const SIT_FIELDS: i64 = 16
22
23// documented lending/planning STANDARDS (not tuned magic -- cited conventions):
24const DTI_HEALTHY_BPS: i64 = 3600 // back-end DTI < 36% = healthy (conventional-mortgage guideline)
25const DTI_STRESSED_BPS: i64 = 4300 // 36-43% stretched; > 43% exceeds the CFPB ability-to-repay QM limit
26const EFUND_MIN_MONTHS: i64 = 3 // 3-6 months of essentials = standard emergency fund floor
27
28// --- aggregates ---
29func sit_total_debt(debts: *i64, ndebts: i64) -> i64 { var s: i64 = 0; var i: i64 = 0; while i < ndebts { s = s + debts[i*DS_FIELDS+0]; i = i + 1 } return s }
30func sit_total_min(debts: *i64, ndebts: i64) -> i64 { var s: i64 = 0; var i: i64 = 0; while i < ndebts { s = s + debts[i*DS_FIELDS+2]; i = i + 1 } return s }
31func sit_total_assets(sit: *i64) -> i64 { return sit[SIT_CASH] + sit[SIT_RETIREMENT] + sit[SIT_OTHER_ASSETS] }
32func sit_net_worth(sit: *i64, debts: *i64, ndebts: i64) -> i64 { return sit_total_assets(sit) - sit_total_debt(debts, ndebts) }
33
34// --- monthly cash flow ---
35// surplus = net income - essentials - discretionary - debt minimums (what is actually left; may be negative)
36func sit_monthly_surplus(sit: *i64, debts: *i64, ndebts: i64) -> i64 {
37 return sit[SIT_INCOME_NET] - sit[SIT_EXPENSES_ESSENTIAL] - sit[SIT_SPEND_DISCRETIONARY] - sit_total_min(debts, ndebts)
38}
39// max debt paydown = what COULD attack debt if discretionary spend were fully cut (feeds ds_simulate extra). Floored at 0.
40func sit_max_debt_paydown(sit: *i64, debts: *i64, ndebts: i64) -> i64 {
41 let v: i64 = sit[SIT_INCOME_NET] - sit[SIT_EXPENSES_ESSENTIAL] - sit_total_min(debts, ndebts)
42 if v < 0 { return 0 }
43 return v
44}
45
46// --- ratios (basis points) ---
47func sit_dti_bps(sit: *i64, debts: *i64, ndebts: i64) -> i64 {
48 if sit[SIT_INCOME_GROSS] <= 0 { return 0 }
49 return sit_total_min(debts, ndebts) * SIT_MAGIC_10000 / sit[SIT_INCOME_GROSS]
50}
51func sit_savings_rate_bps(sit: *i64, debts: *i64, ndebts: i64) -> i64 {
52 if sit[SIT_INCOME_NET] <= 0 { return 0 }
53 return sit_monthly_surplus(sit, debts, ndebts) * SIT_MAGIC_10000 / sit[SIT_INCOME_NET]
54}
55
56// --- emergency fund ---
57func sit_efund_months_x100(sit: *i64) -> i64 { // months of essentials covered by cash, x100
58 if sit[SIT_EXPENSES_ESSENTIAL] <= 0 { return 0 }
59 return sit[SIT_CASH] * 100 / sit[SIT_EXPENSES_ESSENTIAL]
60}
61func sit_efund_target_cents(sit: *i64) -> i64 { return sit[SIT_EFUND_TARGET_MONTHS] * sit[SIT_EXPENSES_ESSENTIAL] }
62func sit_efund_gap_cents(sit: *i64) -> i64 { let g: i64 = sit_efund_target_cents(sit) - sit[SIT_CASH]; if g < 0 { return 0 } return g }
63
64// --- health (documented thresholds) --- 0 = healthy(<36%), 1 = stretched(36-43%), 2 = distress(>43%)
65func sit_dti_status(sit: *i64, debts: *i64, ndebts: i64) -> i64 {
66 let d: i64 = sit_dti_bps(sit, debts, ndebts)
67 if d < DTI_HEALTHY_BPS { return 0 }
68 if d <= DTI_STRESSED_BPS { return 1 }
69 return 2
70}
71
72// --- the KEYSTONE: situation-aware priority router. What should THIS person focus on FIRST? ---
73// 0 = CRISIS / relief (negative monthly surplus, or debt underwater even at max paydown) -> hardship modules
74// 1 = build EMERGENCY FUND (below the standard floor)
75// 2 = attack DEBT (has debt, emergency fund ok)
76// 3 = INVEST / grow (no debt, emergency fund ok, surplus)
77func sit_priority(sit: *i64, debts: *i64, ndebts: i64) -> i64 {
78 if sit_monthly_surplus(sit, debts, ndebts) < 0 { return 0 }
79 if ds_can_meet_minimums(debts, ndebts, sit_max_debt_paydown(sit, debts, ndebts)) == 0 { return 0 }
80 if sit_efund_months_x100(sit) < EFUND_MIN_MONTHS * 100 { return 1 }
81 if sit_total_debt(debts, ndebts) > 0 { return 2 }
82 return 3
83}