Sync assumptions across calculators via nyc_shared_assumptions#22
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
nyc-affordability | f92cddb | Commit Preview URL Branch Preview URL |
May 03 2026, 04:44 AM |
There was a problem hiding this comment.
Pull request overview
This PR extends the existing cross-calculator localStorage syncing pattern so assumption values, not just the shared financial profile, can propagate across /rent/, /coop/, /condo/, and /compare/. It fits into the repo’s browser-only, multi-page calculator architecture by adding a new shared storage contract for assumption fields.
Changes:
- Adds a new
nyc_shared_assumptionslocalStorage key and per-page helpers to load, save, and apply shared assumption values. - Updates the rent, co-op, and condo calculators to write their assumption subsets into the shared key and apply shared values on boot.
- Updates the compare dashboard to read/write shared assumptions and react to cross-tab
storageevents.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
public/rent/index.html |
Adds rent assumption sync via nyc_shared_assumptions and listens for shared assumption storage events. |
public/coop/index.html |
Adds co-op assumption sync via nyc_shared_assumptions and applies shared assumptions during boot/cross-tab updates. |
public/condo/index.html |
Adds condo assumption sync via nyc_shared_assumptions and wires shared assumption updates into storage handling. |
public/compare/index.html |
Adds shared assumption loading/saving for the dashboard and syncs assumption edits across tabs/pages. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| syncEditorFields(); | ||
| $('save-toggle-cb').checked = saveEnabled; | ||
| setText('save-state', saveEnabled ? 'Saved locally' : 'Not saving'); | ||
| applySharedAssumptions(loadSharedAssumptions()); |
| function saveSharedAssumptions(inp) { | ||
| try { | ||
| const cur = loadSharedAssumptions() || {}; | ||
| localStorage.setItem(ASSUMPTIONS_KEY, JSON.stringify({ | ||
| ...cur, | ||
| rent: { incomeMult: inp.incomeMult, rentersInsurance: inp.rentersInsurance, reserveMonths: inp.reserveMonths } | ||
| })); | ||
| } catch(e) {} |
| try { | ||
| const cur = loadSharedAssumptions() || {}; | ||
| localStorage.setItem(ASSUMPTIONS_KEY, JSON.stringify({ | ||
| ...cur, | ||
| coop: { mortgageRate: inp.mortgageRate, dpPct: inp.dpPct, maint: inp.maint, maxDTIPct: inp.maxDTIPct, reserveMo: inp.reserveMo } | ||
| })); | ||
| } catch(e) {} |
| function saveSharedAssumptions(inp) { | ||
| try { | ||
| const cur = loadSharedAssumptions() || {}; | ||
| localStorage.setItem(ASSUMPTIONS_KEY, JSON.stringify({ | ||
| ...cur, | ||
| condo: { mortgageRate: inp.mortgageRate, dpPct: inp.dpPct, commonCharges: inp.commonCharges, propTaxes: inp.propTaxes, hoInsurance: inp.hoInsurance, maxDtiPct: inp.maxDtiPct } | ||
| })); | ||
| } catch(e) {} | ||
| } |
| ].forEach(([id, setter]) => { | ||
| const el = $(id); | ||
| if (el) el.addEventListener('input', () => { setter(num(el.value)); render(); }); | ||
| if (el) el.addEventListener('input', () => { setter(num(el.value)); saveSharedAssumptions(); render(); }); |
- Include ASSUMPTIONS_KEY in co-op legacy domain migration so shared assumptions survive the redirect from nyc-co-op-affordability.com - Fix compare saveSharedAssumptions to read-modify-write instead of overwriting the entire stored object, preventing stale in-memory sections from clobbering updates made by other tabs - Replace compare's a-rent-reserve number input with a <select> matching rent's 0/1/2/3/6 option set, preventing out-of-range values from reaching shared storage - Guard rent applySharedAssumptions against invalid reserve-months values (defense-in-depth for stale storage) - Update compare footer disclaimer to reflect that assumptions are now user-editable, not fixed defaults Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-reviewed
No new issues found. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const cur = loadSharedAssumptions() || {}; | ||
| localStorage.setItem(ASSUMPTIONS_KEY, JSON.stringify({ | ||
| ...cur, | ||
| coop: { mortgageRate: inp.mortgageRate, dpPct: inp.dpPct, maint: inp.maint, maxDTIPct: inp.maxDTIPct, reserveMo: inp.reserveMo } | ||
| })); |
| <div class="input-wrap"><select id="a-rent-reserve"> | ||
| <option value="0">None</option> | ||
| <option value="1">1 month</option> | ||
| <option value="2" selected>2 months</option> | ||
| <option value="3">3 months</option> | ||
| <option value="6">6 months</option> | ||
| </select></div> |
| // Total variable+mansion cash needed for the DP/CC pool at a given price | ||
| const ccAtP = p => p * varFrac + calcMansionTax(p); // variable + mansion (both proportional to price) | ||
| const closeAtP = p => fixedCC + ccAtP(p) + p * dp; // total closing cash | ||
| const resAtP = p => closeAtP(p) + resMo * (maintMo + p * (1 - dp) * K); // total cash with reserves |
| syncEditorFields(); | ||
| $('save-toggle-cb').checked = saveEnabled; | ||
| setText('save-state', saveEnabled ? 'Saved locally' : 'Not saving'); | ||
| applySharedAssumptions(loadSharedAssumptions()); |
| // Price ceilings as a function of dp (0..1) — uses binary search to account for mansion tax | ||
| function priceAtDp(c, dp) { | ||
| // Reserve constraint: all weighted assets cover DP + CC + reserves | ||
| const d = (dp + c.varFrac + c.resMo * (1 - dp) * c.K); | ||
| // Reserve constraint: avail covers DP + fixedCC + varCC + mansion + reserves | ||
| const pRes = c.resMo > 0 | ||
| ? ((d > 0 && c.B > 0) ? c.B / d : 0) | ||
| ? bsearchMaxP(p => p * dp + c.fixedCC + p * c.varFrac + calcMansionTax(p) + c.resMo * (c.maintMo + p * (1 - dp) * c.K) <= c.avail) | ||
| : Infinity; | ||
|
|
||
| // DP/CC constraint: only Closing?-checked accounts cover DP + variable CC. | ||
| // When dDpCC is 0 (no DP and no variable CC), this constraint does not bind (Infinity). | ||
| // When dpCCNum <= 0, the constraint is violated at any positive price (0). | ||
| const dDpCC = dp + c.varFrac; | ||
| // DP/CC constraint: only Closing?-checked accounts cover DP + variable CC + mansion | ||
| let pDpCC; | ||
| if (dDpCC <= 0) { | ||
| pDpCC = Infinity; // no DP or variable CC — constraint doesn't apply | ||
| } else if (c.dpCCNum <= 0) { | ||
| pDpCC = 0; // closing-eligible assets don't even cover fixed CC | ||
| if (c.dpCCNum <= 0) { | ||
| pDpCC = 0; | ||
| } else { | ||
| pDpCC = c.dpCCNum / dDpCC; | ||
| pDpCC = bsearchMaxP(p => p * dp + p * c.varFrac + calcMansionTax(p) <= c.dpCCNum); |
Atomicity / per-calculator keys: - Split nyc_shared_assumptions into three separate localStorage keys (_rent, _coop, _condo) so each page reads and writes only its own key — no merge needed, race condition eliminated - Compare reads all three keys independently and writes each separately - Storage event listeners updated to match the new key names Select styling: - Add .asmp-field .input-wrap select styles so the rent reserve <select> matches the input-wrap design of adjacent controls Mansion tax — compare calcCoop: - calcCoop() now uses bsearchMaxPrice with mansion tax in both the reserve and DP/CC cash constraints, matching the coop calculator Mansion tax — coop computeAffordTarget: - cashNeededAtT and dpCCNeededAtT now include calcMansionTax(target) - dpForCashReserve and dpForCashDpCC DP-lever formulas subtract the mansion tax from the available budget before solving for dp Mansion tax — coop computeOptimizer: - Add kink candidates at each mansion tax tier-boundary price ($1M, $2M, $3M, $5M …); for each boundary compute the dp values where the reserve and DP/CC constraints bind exactly, and add them to the candidate set so the optimizer does not miss kink-adjacent optima Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@copilot please re-review. |
Re-reviewed against all 8 new comments — all addressed in
No remaining issues found. |
Summary
nyc_shared_assumptionslocalStorage key that stores assumption values (mortgage rate, down payment %, DTI, maintenance, income multiplier, etc.) shared across all calculator pagesstorageevents — changing an assumption in one open tab instantly updates other open tabsBehavior
nyc_shared_assumptionswhen the save toggle is ON; cross-tab reads always apply the latest saved valueincomeMult,rentersInsurance,reserveMonthsmortgageRate,dpPct,maint,maxDTIPct,reserveMomortgageRate,dpPct,commonCharges,propTaxes,hoInsurance,maxDtiPctTest plan
/coop/with save ON → open/compare/and verify the rate is reflected in the co-op assumption inputs/compare/with save ON → reload/coop/or/condo/and verify the field is pre-populated/coop/and/compare/), change an assumption in one → verify the other updates in real time🤖 Generated with Claude Code