Cursor Task: Normalize Snapshots to $500/Month
Section titled “Cursor Task: Normalize Snapshots to $500/Month”Context
Section titled “Context”The SD App snapshot JSONs currently use test fixture loan amounts (interest-only: $100,000; term loan: $37,543). These were chosen to validate math correctness, not for user-facing presentation.
The user-facing standard has been set: $500/month of gross (before-tax) investable cash flow. All snapshot outputs should reflect this normalization.
- Interest-only:
loan_amountsuch that annual gross interest = $6,000 → $66,667 at 9% - Term loan:
loan_amountsuch that annual payment = $6,000 → $38,506 at 9% for 10 years;loan_payment= $6,000
This is a pure Python task — update two snapshot generator files, regenerate two JSON files. No API changes, no SvelteKit changes.
Files to Read First (in order)
Section titled “Files to Read First (in order)”packages/sd-math/tools/generate_snapshot.py— interest-only generatorpackages/sd-math/tools/generate_snapshot_term_loan.py— term loan generatorapps/sd-app/static/snapshots/int-only-ca.json— current output (for reference)apps/sd-app/static/snapshots/term-loan-ca.json— current output (for reference)packages/sd-math/src/sd_math/models/inputs.py— LoanProfile, TermLoanProfile definitionspackages/sd-math/src/sd_math/strategies/interest_only.py— to understand at_annual_investment derivationpackages/sd-math/src/sd_math/strategies/term_loan.py— to understand loan_payment handling
Hard Rules
Section titled “Hard Rules”- Do NOT change sd-math library code — only the generator scripts and the output JSON files
- Do NOT change the API (
packages/sd-api/) - Do NOT change SvelteKit files (
apps/sd-app/) - Do NOT change test fixtures (
packages/sd-math/tests/fixtures/) — those are for math validation, not UX normalization - Field names in the output JSONs must remain identical to current — only the values change
- Run the generators using the project’s Python environment (same as sd-math tests):
cd packages/sd-math && python tools/generate_snapshot.pyetc.
Changes Required
Section titled “Changes Required”1. packages/sd-math/tools/generate_snapshot.py
Section titled “1. packages/sd-math/tools/generate_snapshot.py”Change the LOAN definition:
# BEFORELOAN = LoanProfile( loan_amount=100_000.0, ...)
# AFTERLOAN = LoanProfile( loan_amount=66_667.0, # $500/month gross: $66,667 × 9% = $6,000/year ...)Everything else in the file stays the same. The generator will produce new output values automatically.
2. packages/sd-math/tools/generate_snapshot_term_loan.py
Section titled “2. packages/sd-math/tools/generate_snapshot_term_loan.py”Change the TERM_LOAN definition:
# BEFORETERM_LOAN = TermLoanProfile( loan_amount=37_543.0, loan_payment=5_850.0, loan_interest_rate=0.09, n_loan_periods=10,)
# AFTERTERM_LOAN = TermLoanProfile( loan_amount=38_506.0, # $500/month gross: annual payment = $6,000 loan_payment=6_000.0, # $500/month × 12 = $6,000/year loan_interest_rate=0.09, n_loan_periods=10,)Everything else in the file stays the same.
Deliverables
Section titled “Deliverables”- Both generator files updated with new loan amounts
- Both JSON snapshots regenerated:
apps/sd-app/static/snapshots/int-only-ca.jsonapps/sd-app/static/snapshots/term-loan-ca.json
Verification
Section titled “Verification”After regenerating, confirm these values in the new JSON files:
int-only-ca.json — middle-income profile:
inputs.loan_amount= 66667analysis_summary.at_annual_investment≈ 3900 (within ±50) (66,667 × 9% = $6,000 gross, minus 35% tax benefit = $3,900 net)analysis_summary.bt_annual_interest= 6000 (exactly)
term-loan-ca.json — middle-income profile:
inputs.loan_amount= 38506inputs.loan_payment= 6000analysis_details.annual_rows[0].interest_part_of_pmt≈ 3465 (Year 1: 38,506 × 9%)analysis_details.annual_rows[0].loan_reduction≈ 2535 (Year 1: 6,000 - 3,465)
Completion Signal
Section titled “Completion Signal”Paste the output of both generator runs here:
# Run 1 — Interest-onlycd packages/sd-math && python tools/generate_snapshot.py# [paste output]
# Run 2 — Term loancd packages/sd-math && python tools/generate_snapshot_term_loan.py# [paste output]Then paste the analysis_summary block from each new JSON file (middle-income profile only) so the values can be spot-checked.
Completion Record — 2026-03-22
Section titled “Completion Record — 2026-03-22”Generator run output
Section titled “Generator run output”# Run 1 — Interest-onlycd packages/sd-math && python tools/generate_snapshot.py
✓ Snapshot written to /home/ta/projects/monorepo/apps/sd-app/static/snapshots/int-only-ca.json Profiles: ['middle-income', 'high-income'] middle-income: 5 summary scenarios, 10 annual rows high-income: 5 summary scenarios, 10 annual rows
# Run 2 — Term loancd packages/sd-math && python tools/generate_snapshot_term_loan.py
✓ Snapshot written to /home/ta/projects/monorepo/apps/sd-app/static/snapshots/term-loan-ca.json Profiles: ['middle-income', 'high-income'] middle-income: 5 summary scenarios, 10 annual rows high-income: 5 summary scenarios, 10 annual rowsVerification — int-only-ca.json (middle-income)
Section titled “Verification — int-only-ca.json (middle-income)”| Check | Expected | Actual | Pass? |
|---|---|---|---|
inputs.loan_amount | 66667 | 66667 | ✓ |
analysis_summary.at_annual_investment | ≈ 3900 (±50) | 3900 | ✓ |
analysis_summary.bt_annual_interest | 6000 | 6000 | ✓ |
analysis_summary block:
{ "at_annual_investment": 3900, "bt_annual_interest": 6000, "scenarios": [ { "expected_return": 0.0, "no_lev_bt_balance": 39000, "net_lev_bt_bal": 0, "net_increase": -39000, "pct_increase": -1.0, "is_better_than": false }, { "expected_return": 0.03, "no_lev_bt_balance": 44391, "net_lev_bt_bal": 19413, "net_increase": -24979, "pct_increase": -0.5627, "is_better_than": false }, { "expected_return": 0.0666, "no_lev_bt_balance": 52184, "net_lev_bt_bal": 52184, "net_increase": 0, "pct_increase": 0.0, "is_better_than": true }, { "expected_return": 0.07, "no_lev_bt_balance": 52972, "net_lev_bt_bal": 55759, "net_increase": 2787, "pct_increase": 0.0526, "is_better_than": false }, { "expected_return": 0.1, "no_lev_bt_balance": 60636, "net_lev_bt_bal": 92733, "net_increase": 32097, "pct_increase": 0.5293, "is_better_than": false } ]}Verification — term-loan-ca.json (middle-income)
Section titled “Verification — term-loan-ca.json (middle-income)”| Check | Expected | Actual | Pass? |
|---|---|---|---|
inputs.loan_amount | 38506 | 38506 | ✓ |
inputs.loan_payment | 6000 | 6000 | ✓ |
annual_rows[0].interest_part_of_pmt | ≈ 3465 (38,506 × 9%) | 3466 | ✓ |
annual_rows[0].loan_reduction | ≈ 2535 (6,000 − 3,465) | 2534 | ✓ |
analysis_summary block:
{ "scenarios": [ { "expected_return": 0.0, "no_lev_bt_balance": 52477, "net_lev_bt_bal": 38506, "net_increase": -13971, "pct_increase": -0.2662, "is_better_than": false }, { "expected_return": 0.03, "no_lev_bt_balance": 59430, "net_lev_bt_bal": 50975, "net_increase": -8456, "pct_increase": -0.1423, "is_better_than": false }, { "expected_return": 0.0617, "no_lev_bt_balance": 67989, "net_lev_bt_bal": 67989, "net_increase": 0, "pct_increase": 0.0, "is_better_than": true }, { "expected_return": 0.07, "no_lev_bt_balance": 70464, "net_lev_bt_bal": 73224, "net_increase": 2760, "pct_increase": 0.0392, "is_better_than": false }, { "expected_return": 0.1, "no_lev_bt_balance": 80291, "net_lev_bt_bal": 95278, "net_increase": 14988, "pct_increase": 0.1867, "is_better_than": false } ]}Files changed
Section titled “Files changed”packages/sd-math/tools/generate_snapshot.py—loan_amount100,000 → 66,667packages/sd-math/tools/generate_snapshot_term_loan.py—loan_amount37,543 → 38,506;loan_payment5,850 → 6,000apps/sd-app/static/snapshots/int-only-ca.json— regeneratedapps/sd-app/static/snapshots/term-loan-ca.json— regenerated
No library code, API code, SvelteKit files, or test fixtures were modified.