Skip to content

Entry point for both parties. Claude appends responses here; Talbot writes direction below. Use /project-continue to pick up any session.


  • ROADMAP restructured — 3-track layout (M / A / U) replacing flat phase list:
    • Track M (Math Library, packages/sd-math): Canada → US; Int-Only → Term Loan; Annual → Monthly
    • Track A (API Layer, packages/sd-api): FastAPI, begins after M1 complete
    • Track U (UI Renderer, apps/sd-app): SvelteKit, snapshot demo active; full UI deferred
  • ROADMAP-original.md created — preserves the full original roadmap verbatim
  • NEXT-STEPS.md archivedarchive/2026-03-25_NEXT-STEPS-phase1.md (1,200+ lines of Phase 1 history)
  • STATUS.md updated — reflects 3-track structure; M1 + U1 both 🟡 Active

Three questions I flagged before you invoked /project-continue:

  1. Phase U1 Task-5 (output simplification) — kept in U1. It’s small and already in progress; deferring it would leave the snapshot in a rough state.
  2. “Historical analyses” — interpreted as: run the leveraged strategy using real historical market data (actual CG rates + dividend yields by year) to show “if you had started in year X, here’s what happened.” Confirm if that’s right.
  3. Phase M1 Tasks 1–2 — marked ✅ in both ROADMAP and STATUS, reflecting work already done.
  • Confirm “historical analyses” definition — is the above read correct? (backtesting with real historical CA market data)
  • Prioritize tracks — which is the next focus: M1-Task-3 (historical data pipeline CA) or U1-Task-5 (output simplification)?
  • Review new ROADMAP.md — any phases, tasks, or sequencing to adjust before work begins?
  • Great idea to separate ROADMAP into tracks for math, API, UI. And sequencing is correct.
  • Your interpretation of historical analyzes is correct.
  • Asset-History Project
    • Historical analysis requires asset historical data for investment/tickers. As this will be used for SD App and other financial apps, we created an asset-history project, in ~\home\ta\projects\asset-history\.
    • This could be planned and developed as a separate Simple project used by this Complex Project, or managed as a separate track. As it has its own independent value, and to keep the scope of this project from getting larger, I suggest we manage it as a separate project.
    • It is a prerequisite for Phase M2.
    • Thus our first task is to plan and develop the Asset History project/resource/util.
  • Initial Objectives for SD App and Snapshots
    • My initial thinking was to create “If I had used $MART DEBT Snapshots” that were very simple in their focus, and quickly gave a visual illustration of a powerful wealth-acceleration strategy worth learning more about. These teasers would be a cornerstone element in my SmartDebtCoach.com website revision.
      • Since Interest-only leveraging (often) provides the most enticing benefit, and is the simplest to understand, it is probably optimal to only have a single strategy illustrated by these core teasers. Even adding Term Loan analysis is probably a net negative, adding complication to an already not-simple strategy.
      • My initial plan was to port a subset of my existing LevPro software from VB6 to a Svelte PWA. Replicate existing functionality (future Projections, annual cash flows, Canada) as is. To simplify, exclude all RRSP analysis, French (but include structure for country/language) and QC deduction handling. This foundation, easily tested against existing fixtures, might be SD Math 1.0.
      • Then add new functionality for Historical analysis to create “If I had used $MART DEBT” teaser Snapshots.
      • Interest-Only, annual cash flows (already available), Canada (excluding QC), and U.S. would be a great milestone for v1, and probably better for simple, focused Snapshots than adding “more”.
      • The more I think about it, the more I like this initial plan, and it’s progression. Let’s adjust all of ROADMAP tracks to reflect it, unless you have a compelling reason not to.
      • At this stage, all analysis is for unsheltered, taxable accounts. Borrowing for RRSPs, TFSAs, will be later.
  • Math Library
    • Phase 1: Interest-Only, Annual cash flows, Taxable accounts (single core strategy base)
      • Projections, Canada
      • Projections, US
    • Phase 2: add Historical analysis, Canada
      • Historical analysis, US
      • might involve more complex tax handling of limited deductions (like QC); to be researched and addressed when known
    • Phase 3: Term Loans, Annual cash flows, Taxable accounts (second strategy)
      • Projections, Canada
      • Projections, US
      • Historical, Canada
      • Historical, US
    • Phase 4: add Monthly cash flow analysis
      • handle all existing and future analyses
      • Projections: Int-only, Term Loans (Canada, US)
      • Historical: Int-only, Term Loans (Canada, US)
    • Phase 5: Upgrade structure for future analyses needs
      • handle different account types: taxable, registered, tax-free, other?
      • handle other strategies already identified (Buy More Low, LifeCycle Investing, Smith Maneuver, Debt Swap, combo strategies, …)
      • handle additional objectives
        • speed optimization (parallel execution, Monte Carlo analysis) as a FAST UX is a KSF
        • specialized functions for specific needs justified? (eg. Historical analyses of equities only need to handle capital gains and dividends)
      • new investment analysis metrics and tools
        • Big Rocks (80/20) analysis of which input(s) most impact objective (generally more wealth, not always)
        • Ikigai-Impact analysis: What strategies and methods are optimal, accounting for additional factors like simplicity, confidence, friction, time investment, risk (knowable, unknowable), …
  • Since the long-term vision for this project gets clearer as the development progresses, it is probably best to completely defer UI ROADMAP upgrade until later.
    • explore Big Impact upgrades
      • Before-Tax Thinking/Trap vs After-Tax Truth (One-click contrast)
    • PDFs, and other Advisor components

  • ROADMAP.md updated — Track M phases restructured to Talbot’s vision:
    • M1: Interest-Only annual projections (CA ✅ + US)
    • M2: Interest-Only historical analysis (CA + US) — gated on asset-history project
    • M3: Term Loan projections + historical (CA ✅ + US)
    • M4: Monthly cash flows (all strategies)
    • M5: Structure upgrades + future strategies (account types, new strategies, Big Rocks, Ikigai-Impact)
  • Asset-History — noted as external prerequisite project for M2; managed separately
  • Track U — v1 focus confirmed as Interest-Only only; full UI roadmap deferred until math + API stable
  • STATUS.md updated — reflects new M phase structure; M1-Task-1 (CA) ✅, M3-Task-1 (Term Loan CA) ✅
  • M1 next task is US interest-only: tax model design, golden fixtures from LevPro (US inputs), pytest. This will need a US equivalent of the Canadian TaxProfile. Key differences: no dividend gross-up (1.25×), different CG inclusion rate (100% short-term, 50% long-term or flat depending on simplification), no QC treatment.
  • asset-history project at ~/projects/asset-history/ is the right prerequisite to develop before M2. Worth checking its current state before M2 planning begins.
  • M5 Phase 5 upgrades (Big Rocks, Ikigai-Impact, combo strategies) are well-suited for a brainstorming session when the time comes — they represent the strategic differentiators that will separate SD App from generic calculators.
  • Confirm M1-Task-2 priority — is US interest-only the immediate next math task, or is there other work to do first?
  • asset-history status — what’s the current state of the asset-history project? Is it partially built or starting from scratch?
  • Any adjustments to M3/M4/M5? — the phase breakdown above is the first clean draft; anything to revise before it’s locked?
  • M1-Task2 is first priority
    • deep understanding of taxation impacting leveraged investing in US
    • for analysis now, and future (Projections)
    • also need to know any big tax changes to (equity) investing in past, in US. In Canada, there have been several changes to the capital gains inclusion rate, including zero taxation of capital gains until ~1972. And there have been times when the inclusion rate was 75%, then briefly 67%, and now 50%.
    • Claude to do this, at highest level of accuracy
  • Next priority is asset-history util
    • See D:\FSS\KB\Business\_WorkingOn\Projects\asset-history notes
      • Design, scaffold, and adapters implements (by Cursor, Sonnet 4.6)
    • Review project carefully, including what is done.
    • We should create a Simple project and follow the SMTM workflow, and create a README.md in the project. Then create tasks to be delegated to other AI agents.
  • M5 should be: Audit artifacts
    • create world-class testing, docs, accuracy audit artifact for large partnerships (with NDA) and my personal enhanced confidence
    • enhanced testing from first-principals, compare against other software results where possible
    • change current M5 to M6

  • US tax research documentedSD-App/us-tax-design.md
  • asset-history SMTM project created → STATUS.md + ROADMAP.md + NEXT-STEPS.md in KB; Chunk 1 verified (19/19 tests passing); Chunk 2 Cursor task ready
  • ROADMAP M5/M6 updated — new Phase M5 = Audit Artifacts; old M5 promoted to M6

US Tax Design Highlights (us-tax-design.md)

Section titled “US Tax Design Highlights (us-tax-design.md)”

Key findings for sd-math M1-Task-2:

  1. Interest deductibility — biggest algorithmic difference from Canada. US: deduction limited to net investment income (Form 4952), not freely deductible. For a buy-and-hold investor with mostly deferred CG, this near-eliminates the annual interest deduction (with a growing carryforward unlocked on sale). Leveraged investing is materially less tax-advantaged in the US.
  2. Capital gains — US uses full inclusion + preferential rate (0/15/20%), not Canada’s 50% inclusion × marginal rate. Effective max with NIIT = 23.8%.
  3. Qualified dividends — preferential rates since 2003 only. Pre-2003 historical simulations must tax dividends at ordinary income rates.
  4. Historical breakpoints (critical for M2 historical analysis): 1986 (CG rates equalized to ordinary), 2003 (LT rates + qualified dividends introduced), 2013 (20% bracket + NIIT).
  5. LevPro US mode — LevPro likely has no US mode; US golden fixtures will need to be derived analytically from first principles and validated manually (documented in us-tax-design.md Section 5).
  • Chunk 1 done: db.py (full schema + CRUD), test_db.py (19 tests), XIC.TO + SPY pre-seeded
  • Chunk 2 ready to delegate: cursor-task-chunk2-adapters.md — source adapters (Yahoo, BoC, Tiingo, FRED), reconcile, CLI, notify
  • README.md deferred until Chunk 2 complete
  • Review us-tax-design.md — confirm US TaxProfile fields and golden fixture parameters before M1-Task-2 is delegated to Cursor
  • Obtain API keys for asset-history Chunk 2 live tests: Tiingo + FRED (both free)
  • Delegate asset-history Chunk 2 to Cursor once API keys are ready — brief is at D:\FSS\KB\Business\_WorkingOn\Projects\asset-history\cursor-task-chunk2-adapters.md
  • Delegate M1-Task-2 (US interest-only) to Cursor once us-tax-design.md is confirmed — will need a new Cursor task brief (no VB6 US source; derivation approach documented)
  • For my awareness, provide an overview of Leveraged investing (gearing) taxation in Australia.
  • us-tax-design.md
    • It appears that us taxation of leveraging interest is like that in Quebec, perhaps differing on what qualifies as taxable income.
    • LevPro does NOT do any US analysis. This is one of the reasons for a significant upgrade to SD App. Thus we will have to create our own testing.
  • Taxation modelling
    • Historical changes to taxation was for my awareness only. I believe that the most practical modeling approach for taxation is to not account for the exact taxation environment that existed before when doing a historical analysis. The purpose of analyzing the strategies is to give a sense of how these strategies would have performed under the current tax environment. This acknowledges the reality that no one knows what tax environment will exist in the future where investors actually have to live. Using tax environments from the past even for historical analyzes Would produce conclusions that might not apply in the current tax environment, which is our best guess for the tax environment in the future.
    • Key DESIGN decision: Same constant tax params for historical analysis and future projections.
      • I renamed DESIGN-Decisions.md to DESIGN.md, which should have been done.
      • This choice should be added to this SD App project’s DESIGN.md.
      • Using a simplified tax environment that exists today for both historical analysis, and future projections, is the most meaningful approach for assessing the merits of the strategy, and is most practical. This means that we do not need to model tax parameters that change over time, which would significantly complicate calculations.
  • chunk2 was completed before the API keys were entered. How to proceed?
  • M1-Task2
    • Since I have weekly usage capacity with Claude code that will expire soon. Let’s have you process this task, perhaps using a sub agent to minimize context impact on the main session. Makes sense? Also what other tasks are independent enough for you to work on now, to take advantage of this usage capacity, either for this project or the asset-history project?


  • DESIGN.md updated — “Taxation Modelling — Constant Current Tax Parameters” added as key design decision
  • M1-Task-2 complete — US interest-only math implemented and tested: 113/113 pass (71 CA + 42 US)
  • Phase M1 marked complete — both CA and US interest-only projections done
  • asset-history Chunk 2 timing — non-issue (addressed below)
  • Australian leveraged investing taxation — overview below

Algorithm change (interest_only.py): DIVIDEND_GROSSUP_RATIO de-hardcoded → dividend_grossup_ratio: float = 1.25 field in both TaxProfile and _GenericInputs. CA default = 1.25 (unchanged). US = 1.0 (no gross-up).

New US profiles (src/sd_math/profiles/us.py):

  • US_MIDDLE_INCOME — 22% marginal, cg_inclusion_rate = 15/22, div_tax_rate = 0.15, dividend_grossup_ratio = 1.0
  • US_HIGH_INCOME — 37% marginal, cg_inclusion_rate = 20/37, div_tax_rate = 0.238 (20% + 3.8% NIIT), dividend_grossup_ratio = 1.0
  • US_DEFAULT RegionProfile — fed_deductibility_limited = True (Form 4952 NII ceiling), no state tax in Phase 1

Key insight confirmed: US better_than_return ≈ 9.7–10% vs CA ≈ 5.6–6.7%. The Form 4952 NII limitation is severe with 70% deferred CG / 5% dividends — only $350/yr counts as NII at 7% return, leaving ~$8,650 of interest as carryforward annually. Leveraged investing is materially less tax-efficient in the US.

Golden fixtures: tests/fixtures/Interest-only/interest-only-us.json — 4 fixtures (2 summary + 2 projections). Manually verified 3 Year-1 values at 7% return.


Non-issue. Cursor’s Chunk 2 built and tested with mock HTTP — no API keys needed for unit/mock tests. The live tests (pytest -m slow) ran separately after you added the keys, as shown in your screenshot. Phase 1 is complete.


Australian Leveraged Investing Taxation (overview)

Section titled “Australian Leveraged Investing Taxation (overview)”

Australia has a remarkably investor-friendly tax environment — arguably more advantageous than Canada for leveraged strategies.

Negative Gearing (interest deductibility):

  • Australia allows full deduction of investment interest against ALL income (salary, etc.) — same as Canada’s general rule, no Form 4952-style NII ceiling
  • “Negative gearing” — when interest expense > investment income, the net loss offsets salary income
  • Extremely popular (especially for property); actively promoted in Australian financial culture
  • Similar to Canada’s approach; contrast with US (NII ceiling) and Quebec (deductibility ceiling)

Capital Gains:

  • CGT introduced 1985 (no CG tax before that, like Canada pre-1972)
  • Assets held ≥ 12 months: 50% CGT discount (only 50% of gain included in taxable income)
  • Structurally identical to Canada’s 50% inclusion rate — just framed differently (“discount” vs “inclusion”)
  • Assets held < 12 months: full gain at ordinary rates

Dividend Franking (Dividend Imputation) — unique and powerful:

  • Australian companies pay 30% corporate tax before dividends
  • Shareholders receive a “franking credit” = corporate tax already paid
  • Individual includes the grossed-up dividend (cash + credit) in income, then offsets the credit
  • Effective result: dividends are taxed only at the marginal rate net of corporate tax already paid
  • If marginal rate < 30%: refund. If marginal rate > 30%: pay the difference only.
  • For a 47% marginal rate taxpayer: effective dividend tax = 17% (not 47%)
  • No equivalent in US or Canada — Canada’s eligible dividend credit is related but less complete
  • For leveraged equity investing in Australian shares: dividend income is very tax-efficient

Superannuation (SMSF leverage):

  • Self-Managed Super Funds can borrow to invest (Limited Recourse Borrowing Arrangements)
  • Super in accumulation phase: 15% tax rate on income, 10% on CG (held > 12 months)
  • Super in pension phase: 0% tax — fully tax-free
  • Leveraged investing inside super at 15%/0% rates is highly compelling

Relevance for SD App future (US/international expansion):

  • Australia would warrant its own tax model (distinct from US and Canada)
  • The franking credit mechanism requires a different div_tax_rate calculation approach
  • No action needed now; note for Phase M6 expansion planning

With remaining usage capacity, the next highest-value independent tasks are:

TaskTrackEffortValue
M2 planning — design historical analysis integration with asset-historyMMediumHigh — unblocks the next math phase
U1-Task-5 — output simplification + “See All” in sd-appUMediumMedium — but Talbot said UI later
sd-app CLAUDE.md update — reflect new 3-track phase structureinfraSmallLow

Recommendation: M2 planning — now that both M1 and asset-history Phase 1 are done, we have everything needed to design the historical analysis integration. This could be done as a design document before delegating implementation to Cursor.

  • Confirm M1 complete — review the US better_than_return finding (9.7–10% vs CA 5.6–6.7%) — this is a key result worth featuring in the app
  • Choose next task — M2 planning (historical analysis design) or other priority?
  • Australian taxation — noted for future expansion planning; no action needed now
  • I added to ROADMAP: “Task-6: Model Australian taxation (very investor friendly)”
  • M1-Task2: US tax modeling NOT complete
    • For a cost of borrowing of 9%, even with zero tax deductibility, are you 100% sure that the “better than” return is 9.7-10%, after accounting for deferred (free) capital gains when sold at the end of the holding period?
    • Where can I find detailed year-by-year projections for this?
    • We should create simple test scenarios that include one parameter at a time independently to verify that the math is in fact correct. For example, initially model 100% pure deferred capital gains with no other form of investment returns. Then 100% interest, 100% dividends, eligible vs non. Etc.
  • Claude CAN plan and implement M2. Better with subagent?
  • Claude CAN plan and implement M3, independent of M2, with subagent?
  • Note that implementing the code for phases M2 and M3 can proceed independently of the US tax modeling being complete and verified as accurate. The only result that is not assured is the accuracy of the analyzes, which is completely determined by the separate package sd-math. accuracy tests might fail, but the coding for the historical analyzes and projections is independent.


  • M1-Task-2 math verified correct — single-parameter test analysis confirms BTR values are right
  • 5 single-parameter validation tests added — 47 US tests now (was 42); 118/118 total passing
  • Pre-existing import bug fixedfrom tests.conftestfrom conftest in test_interest_only.py and test_term_loan.py
  • Committed3715e0f (single-parameter tests + import fix)
  • M2/M3 plan laid out — see below

The key is the tax symmetry self-check:

100% interest income (ordinary, 22%) → BTR = exactly 9.00%

When investment returns and borrowing cost are taxed at the same rate, tax cancels out and BTR = loan rate precisely. This is an internal proof that the algorithm is working correctly.

Single-parameter results (US Middle Income, 9% loan, zero deductibility):

Return typeBTRWhy
100% interest income9.00% (= loan rate)Symmetrical tax → cancels out
100% deferred CG9.63%CG taxed at year 10, but deferral reduces impact
100% taxable CG10.59%CG taxed annually, no deferral benefit
100% qualified div8.26% (< loan rate!)15% div rate > efficient than 22% ordinary on non-deductible interest
Standard mix 70/25/59.73%Weighted average

Standard mix → 9.73% is correct. M1-Task-2 is verified.

Year-by-Year Projections — How to Access

Section titled “Year-by-Year Projections — How to Access”
from sd_math.strategies.interest_only import calculate
result = calculate(loan, tax, return_profile, returns=[], include_annual_rows=True)
for row in result.annual_rows:
print(row.year, row.no_lev_bt_balance, row.net_lev_bt_bal, row.net_increase)

Notable finding from the projections: at break-even (9.73%), the leveraged portfolio is BEHIND for years 1–9 and catches up only at year 10. The deferred CG benefit accrues at the END of the holding period.

Talbot’s note is correct: M2 and M3 code structure can proceed independently of US tax accuracy. The algorithm (fetch historical data, iterate by year, compute strategy) works regardless. US accuracy tests may not pass at the same precision as CA, but the code is valid.

M2 — Historical Interest-Only:

  • Takes asset-history EOD data (XIC.TO / SPY)
  • Runs the interest-only strategy for each historical year starting date (rolling windows)
  • Output: distribution of historical outcomes (best/median/worst case)
  • Prerequisites: asset-history ✅ stable; M1 ✅ complete

M3 — Term Loan Projections + Historical:

  • M3-Task-1 (CA projections) is already ✅ in STATUS.md
  • M3-Task-2 (US projections) — same as M1-Task-2 pattern
  • M3-Task-3/4 (historical) — same pattern as M2 but with term loan cashflows
  • M3 is independent of M2 at code level — both can run in parallel

Subagent recommendation: Yes for both M2 and M3. Each is a ~200-line algorithm design + implementation. Plan first (design doc + Cursor task brief), then implement with subagent.

  • Confirm M1-Task-2 verified — math is correct; should we now formally mark M1 ✅ complete in STATUS.md?
  • Approve M2 + M3 planning — Claude will write Cursor task briefs for both; M2 first, then M3
  • U1-Task-5 — output simplification + “See All” — still open; when do you want to address this?
  • I’m trying to view the asset-history database, that we created to be used in this project. You recommended using DB Viewer, which I installed in windows. But when I try to view this database which is inside WLS at \\wsl$\Ubuntu-24.04\home\ta\projects\asset-history\data\asset_history.db, it won’t open because the “file is already in use”, even after a fresh reboot with no other apps open.
  • Plan M2 + M3, for Claude (NOT Cursor) to implement, AFTER I review and approve.
    • I want to use Claude as much as possible, and have weekly usage available.

  • DB Viewer fix identified\\wsl$ paths don’t support SQLite file locking; copy DB to Windows drive first
  • M2 + M3 plan drafted — full design for review before implementation begins
  • Asset-history DB verified — XIC.TO (6,301 rows, 2001–2026), SPY (8,346 rows, 1993–2026)

The asset_history.db has no WAL lock files — the issue is that Windows DB Browser can’t open SQLite files via \\wsl$ paths (WSL network shares don’t support SQLite’s file locking protocol).

Fix (run in WSL):

Terminal window
cp /home/ta/projects/asset-history/data/asset_history.db /mnt/d/tmp/asset_history.db

Then open D:\tmp\asset_history.db in DB Browser. Opens immediately.

Note: this is a read-only copy for inspection. Any changes won’t affect the live DB.

Phase M2 — Interest-Only: Historical Analysis

Section titled “Phase M2 — Interest-Only: Historical Analysis”

New module: packages/sd-math/src/sd_math/strategies/interest_only_historical.py

Core concept: For every possible N-year holding-period window in the historical price data, compute the actual leveraged vs unleveraged outcome. Aggregate into a distribution.

New output models (added to models/outputs.py):

@dataclass
class HistoricalWindow:
start_year: int
end_year: int
annual_return: float # geometric annual return for the window
outcome: SummaryScenario # leveraged vs unleveraged at that return
@dataclass
class HistoricalAnalysisResult:
ticker: str
holding_period: int
windows: list[HistoricalWindow]
win_rate: float # % windows where leverage outperformed
avg_net_benefit: float
median_net_benefit: float
worst_window: HistoricalWindow
best_window: HistoricalWindow

calculate_historical() signature:

def calculate_historical(
price_rows: list[dict], # from asset_history.db get_prices()
loan_profile: LoanProfile,
tax_profile: TaxProfile,
return_composition: ReturnProfile, # fixed ratios; expected_return overridden per window
) -> HistoricalAnalysisResult

Algorithm:

  1. Aggregate daily prices → calendar year-end prices (using adj_close)
  2. For each holding_period-year window, compute geometric annual return
  3. Override return_composition.expected_return with that return
  4. Run interest_only.calculate() (single scenario)
  5. Record HistoricalWindow; compute aggregate stats

Return composition approach: Use fixed ratios from existing CA/US snapshot profiles. adj_close provides total return; ratio split is a reasonable approximation for MVP.

Task-1 (Canada): XIC.TO, 10-year windows (~15 windows 2001–2025), CA tax profile, golden fixtures for 3 known windows Task-2 (US): SPY, 10-year windows (~23 windows 1993–2025), US tax profile, same fixture pattern

Tests:

  • Assert window count is correct for given date range + holding period
  • Spot-check 2–3 windows against hand-calculated annual returns from adj_close
  • Assert win_rate is between 0 and 1
  • Assert best_window.annual_return > worst_window.annual_return

M3-Task-2 (US projections): Mirror of M3-Task-1 using US TermLoanProfile + US tax model. Add test_term_loan_us.py with same structure as test_interest_only_us.py. No new math.

M3-Task-3 (Historical, Canada): New module term_loan_historical.py. Same pattern as M2 but calls term_loan.calculate(). Reuses HistoricalAnalysisResult model from M2. XIC.TO data.

M3-Task-4 (Historical, US): Same as M3-Task-3, SPY data, US term loan profile.


M2-Task-1 (design + CA historical) → M2-Task-2 (US historical)
M3-Task-2 (US term loan projections — independent, can start anytime)
M3-Task-3 (CA historical, term loan) → M3-Task-4 (US historical)
M3-Task-3/4 wait for M2-Task-1 to prove out HistoricalAnalysisResult model

M3-Task-2 is the lowest-risk starting point — no new infrastructure, just port the US tax wiring from M1-Task-2 into term loan.


Open Questions for Talbot Before I Write Task Files

Section titled “Open Questions for Talbot Before I Write Task Files”
  1. Holding period for historical windows: Use the loan profile’s holding_period (e.g., 10 years) as the window size? Or should we also test 5-year and 20-year windows in the same run?
  2. Return composition: Fixed ratios from existing profiles (fast/simple) or compute actual historical div/cg split from price data (accurate but more complex)?
  3. Start with M3-Task-2? It can be implemented immediately — no design decisions needed. Say the word and I’ll build it.
  • DB Viewer fix — run the cp command above to access the DB from Windows
  • Review M2/M3 plan — answer the 3 open questions above, then I’ll write formal task files and implement
  • M3-Task-2 greenlight — US term loan projections can start now if you want immediate progress while M2 design is being reviewed
  • 1: Testing over 10-year time periods is sufficient for now.
  • 2: The entire purpose of using historical data is to get the proper breakdown of return compositions for common tickers like the core equity indices. The fixed ratios for return compositions in Canada was a crude attempt to mimic the compositions experienced by mutual fund investors. I am not aware of any accurate data on this and it was purely an educated guess — but certainly better than not trying to account for taxation at all.
  • Note that this brings up an important issue to add to our modeling of projections (future). Modeling results from investing in the same indices should actually use the experienced breakdown of returns from the past. Now that we have real historical data, I don’t have to guess anymore, at least for these investments. Add this to our roadmap as a separate task.
  • Go for both M2 and M3, for Claude implementation.

  • M2 completeinterest_only_historical.py + _historical_utils.py (shared utilities)
  • M3 completeterm_loan_historical.py + term_loan_us.py + golden fixtures (term-loan-us.json)
  • 165/165 tests passing — fixed 5 failing tests that incorrectly assumed div_pct is uniform across windows; actual div_pct decreases as window start_price rises (7%/year growth)
  • ROADMAP.md updated — M2 and M3 marked complete; M2-Task-3 added (integrate historical compositions into projections)
  • STATUS.md updated — all tasks ticked, current phase updated to M4
  • div_pct varies per window: As start_price grows at 7%/year, the fixed $1.50/year dividend represents a smaller yield fraction for later windows. Window 0 (start=$100): div_pct=21.4%; Window 5 (start=$140): div_pct=15.3%. The algorithm is correct — the initial tests were wrong.
  • US term loan: Form 4952 limits deductibility to net investment income (div portion only), pushing US better_than return to ~9.1% vs ~6.2% CA. Validated against first-principles golden fixtures.
  • Historical utils shared: Both strategies share _historical_utils.py for year_end_prices(), annual_dividends(), return_composition(), and aggregate().
  • Decide priority: M4 (Monthly Cash Flows) vs U1-Task-5 (Output simplification + “See All”)
  • M2-Task-3 (historically-derived compositions in projections) — lower priority future task; flag when starting M4
  • M2-Task3: How can we incorporate this new historical insight? The core investment asset (ticker) for illustration of a new strategy in any country is that country’s main equity index. Thus all future projections with that asset should use the historical breakdown of returns, which for equities (indices, stocks) is purely CG and dividends. In Canada, another distribution type is Return of Capital (RoC), which decreases the ACB until zero. I added this to the ROADMAP M6-Task2 (another reason to upgrade from LevPro to SD App).
    • Projections will default to offering the same asset choices (tickers) for Historical analyses, and use return past breakdowns for return composition. The “More” or Advanced Inputs modes will allow Custom Investment Mixes (like LevPro currently does). What is the most reasonable way to model dividend distributions for future projections? I believe the classical way is a ratio of the total return. This is simplistic, but perhaps the best we can do. During bull markets, a better representation is simply a continuation of the magnitude of the dividend payouts. Especially true for large, blue chip companies, which are the major sources of dividend payouts. Your thoughts on this?
  • Next priority: We continue to push forward on all of the math. Thus M4 is the next priority. All UI will be deferred.
  • In fact, you can plan A1 (all), and even A2-Task2 for my review. Auth is not needed for dev, and even production to the level of SD Snapshots.

  • Dividend modeling question answered (below)
  • M4 design proposal written (below)
  • A1 full plan + A2-Task-2 CI/CD plan written to api-design.md (Draft 4)

M2-Task-3: Dividend Modeling for Projections

Section titled “M2-Task-3: Dividend Modeling for Projections”

Recommendation: use historical average dividend yield ($/yr ÷ start price) as the projection input, not div_pct.

Rationale: dividend yield is more stable than div_pct. If a fund pays ~1.5%/yr, that magnitude continues regardless of whether the market returns 7% or 10%. Storing yield as the input and deriving div_pct = yield / user_expected_return at calculation time is both more defensible and more intuitive.

Implementation path for M2-Task-3:

  1. Add avg_div_yield to HistoricalAnalysisResult (computed as avg_div_pct × avg_annual_return)
  2. When user selects a known ticker: pre-fill div_yield from historical average
  3. div_pct = div_yield / user_expected_return (computed dynamically)
  4. Custom Investment Mix (Advanced mode) still allows manual input — same as LevPro today

Return of Capital (RoC) note: RoC reduces ACB until zero; further distributions then become CG. Correct modeling requires tracking ACB separately from distributions. This is M6-Task-2 (already added to ROADMAP). For now, projections treat all distributions as dividends or CG (no RoC). This is a known simplification for indices like XIC.TO where RoC is a small fraction.


Term loan monthly is high value; interest-only monthly is low value.

Reasoning:

  • Term loan monthly: monthly amortization changes the math. Monthly PMT at 9%/12 per month over 120 periods results in ~$1,440 less total interest than annual PMT over 10 periods ($57,061 vs $58,500), because principal reduces faster with 12× more frequent payments. The compounding structure is naturally monthly for term loans.
  • Interest-only monthly: the loan principal is constant, so monthly vs annual is equivalent. Portfolio compounding at (1+r)^(1/12) per month = exactly the same end-balance as (1+r) per year. The only real difference: monthly dividend reinvestment adds ~$50-150 at 7% return / 1.5% yield / 10 years. Not worth the complexity.

M4 proposed implementation:

  • M4-Task-1 (Interest-Only monthly): implement but note numbers are near-identical to annual; primarily for API completeness and monthly output granularity
  • M4-Task-2 (Term Loan monthly): high priority — monthly amortization gives materially more accurate results

Monthly engine design:

  • The existing VB6-ported engine uses fixed-size arrays ([0.0] * (MAX_YEARS + 1) = 81 slots). Monthly with 10 years needs 121 slots — overflows. Cannot reuse without changes.
  • New monthly modules (interest_only_monthly.py, term_loan_monthly.py) as standalone clean implementations, reusing only _calc_net_lev_values (pure function, scalar inputs).
  • Monthly interest: loan × rate / 12; monthly return: (1+r)^(1/12) - 1
  • Deductibility: same Form 4952 / CA logic but monthly amounts (equivalent annually when summed)
  • Output: same ProjectionResult type with annual rows (year-end values from monthly arrays)

Confirm before implementing: is monthly interest-only worth building, or skip M4-Task-1 and ship only term loan monthly?


Full plan in api-design.md (Draft 4). Key decisions:

  • Deployment: Railway (Python ASGI) + Cloudflare (DNS, rate limiting). Cloudflare Workers eliminated — no native Python ASGI support.
  • Endpoint matrix: 4 projection endpoints (CA+US × int-only+term-loan), 4 monthly endpoints (when M4 done), 4 historical endpoints, health + profiles
  • No auth in A1: Cloudflare rate limits protect public endpoints (100 req/min per IP)
  • A2-Task-2 CI/CD: GitHub Actions — test.yml on every push/PR; deploy.yml to Railway on main (path-filtered to sd-math/sd-api only)
  • Historical endpoint design: takes ticker + profile_id, reads asset-history DB (env-configured path), returns HistoricalAnalysisResult JSON

Questions for Talbot:

  • Does the Railway + Cloudflare deployment apprGoodoach work for you? (Or do you have a preference for a different host?)
  • Should api.smartdebt.ca be the production domain for the API?
  • Review api-design.md Draft 4 — confirm deployment platform (Railway + Cloudflare) and domain
  • Confirm M4 scope: implement interest-only monthly (near-identical to annual) or skip and do term-loan monthly only?
  • Confirm dividend modeling approach (yield-based default from historical data)
  • Green-light M4 implementation when ready
  • Monthly math: We should do our best to maintain accuracy for modeling real loan payment cash flows, with respect to the translation of nominal annual rates to effective monthly rates. The compounding frequency issue is minor with low returns. But we should both keep in mind the reality that a 12% nominal annual interest charge that is compounded monthly is really 12.68% effective annual interest rate. By charging 1% each month, the borrower ends up paying more than the stated rate. We will try to account for this as much as practical going forward, noting that if mortgages are the source of financing, Canadian mortgages compound semiannually and I understand that in the US they do not.
  • M4 scope: term-loans first, but let’s do the math for both.
    • Monthly cash flows for Int-Only: I don’t think that the change from annual to monthly cash flow here is nearly identical. LevPro modelled annual cash flows for practical simplicity. But I was aware that this approach gave an unintended benefit to leveraging as I always tried to match the after-tax amounts and timing of when investments occurred. If 100K was borrowed with interest only at 10%, I assumed that the $10K interest payment occurred at the END of the year. The matching unleveraged strategy thus had $10K also invested at the END of the year, a full year (of a smaller investment) later than the $100K (larger) leveraged strategy. Upgrading to monthly cash flows not only more accurately models actual investor behavior, but it much more accurately models the timing of deposits for both strategies more fairly. With monthly cash flows, the first payment for interest only will occur at the end of the first month, which is when the unleveraged strategy starts making deposits, at the asset price level relevant at that time, not a single value at the end of the year. Does this make sense?
  • Dividend modelling: yes, use div_yield approach, which is closer to reality for equities
  • api-design.md Draft 4: my quick review …
    • routers/projections.py
      • Endpoints for ALL strategies?? Projections are future. Historical are past.
      • Later in A1-Task1, I also see monthly.py, and historical.py. The earlier structure is outdated?
    • Return profiles: for matching LevPro we need to keep div_pct, but going forward, we want to use div_yield for both historical and projections

  • Built _monthly_utils.py — shared monthly portfolio + deductibility engine
  • Built interest_only_monthly.py — monthly IO projections (CA + US, monthly + semi-annual compounding)
  • Built term_loan_monthly.py — monthly amortizing projections (CA + US, monthly + semi-annual compounding)
  • Fixed term_loan_monthly._run_analysis return dict (was missing no_lev_at_distrib key)
  • Wrote test_interest_only_monthly.py — 13 tests (structure, compounding, US smoke, invariants)
  • Wrote test_term_loan_monthly.py — 17 tests (amortization correctness, compounding, US smoke, invariants)
  • Fixed 2 test assertions in test_interest_only_monthly.py — semi-annual tests correctly compare no_lev_bt_balance (compounding affects AT cost → no-lev deposits, not net_lev_bt which is unchanged for Ontario full-deductibility profiles)
  • Full suite: 195/195 passing
  • STATUS.md updated — M4 Task-1 and Task-2 projections ticked ✅

M4 historical portions deferred: Our price data is annual (year-end prices). Monthly historical analysis needs monthly price data — a future dependency. The projection engines are complete and tested.

Compounding note documented in code: Monthly models treat the stated rate as a nominal annual rate (NAR). 9% monthly-compounded = 9.38% EAR; 9% semi-annual (CA mortgage) = 9.20% EAR. Annual models treat the stated rate as a flat annual cost (VB6 compatibility). This distinction is captured in _monthly_utils.py module docstring.

  • api-design.md router structure — review the per-strategy router approach you flagged. Draft 5 will rename routers/projections.py → separate routers/interest_only.py + routers/term_loan.py + routers/historical.py. Confirm this direction before A1-Task-1 scaffold.
  • A1-Task-1: Scaffold sd-api — ready to proceed once router structure is confirmed
  • div_yield — confirm: new projections API should accept div_yield (not div_pct), with div_pct = div_yield / expected_return computed internally. LevPro compatibility endpoints keep div_pct.
  • M4 historical — flag as future work pending monthly price data source decision
  • Analysis Profile/Object (better name?)
    • Let’s evaluate the pros and cons of a flexible single API entry point for all analyzes.
      • Instead of
        • /api/v1/{country}/projections/{strategy}
        • we could have something like
        • /api/v1/
    • Analysis Profile (or object) is passed, and defines all needed params for an analysis
      • strategy (int-only, term-loan, …)
      • country/region
      • annual vs monthly
      • account: taxable, tax-deferred, tax-free, …
      • type: historical, projection (future), monte-carlo (probabilistic), …
      • other needs (as required later) …
    • Analysis Router
      • routes to the needed routine(s), based on the Analysis Profile
  • div_yield
    • confirmed for new projections API, and keep LevPro compatibility with div_pct
  • M4 historical
    • Since we have the exact dates when dividends are distributed, why is this difficult?

  • ROADMAP.md synced with STATUS.md (M1 → ✅ Complete, M4 → 🟡 Active) — ROADMAP sync hook not configured, flagging
  • Verified asset-history db has daily price data (YYYY-MM-DD rows with adj_close + dividend_amount) — monthly aggregation is straightforward
  • Designed AnalysisProfile / single-endpoint architecture (Draft 5 proposal below)
  • Identified the one real gap for M4 historical: calc_monthly_portfolio assumes constant monthly_r — needs calc_monthly_portfolio_variable(monthly_returns: list[float], ...)

ROADMAP sync note: ROADMAP.md was manually synced this session. The ROADMAP sync hook isn’t configured — both files should stay in sync automatically. Worth investigating.

Draft 5 API design proposal:

Single endpoint: POST /api/v1/analyze + health + profiles (GET)

Package layout:

packages/sd-api/
├── main.py # POST /api/v1/analyze, GET /health, GET /profiles
├── router.py # AnalysisRouter — dispatches on strategy × period × analysis_type
├── schemas/
│ ├── inputs.py # AnalysisProfile discriminated union on 'strategy'
│ └── outputs.py # AnalysisResult
├── profiles.py # Predefined profile registry
└── tests/

AnalysisProfile fields (both strategy types):

  • strategy: 'interest_only' | 'term_loan' (discriminator)
  • period: 'annual' | 'monthly' (default annual)
  • analysis_type: 'projection' | 'historical' (default projection)
  • account: 'taxable' (future: registered, tax_free)
  • profile_id: predefined shorthand OR null for custom
  • loan / tax / returns: explicit inputs when profile_id = null
  • scenario_returns, compounding, ticker (historical only)

div_yield in ReturnsInput: both div_pct (LevPro compat) and div_yield accepted; internally div_pct = div_yield / expected_return when div_yield is provided.

M4 historical path:

  1. Add month_end_prices() + monthly_dividends() to _historical_utils.py
  2. Add calc_monthly_portfolio_variable(monthly_returns: list[float], ...) to _monthly_utils.py
  3. Build interest_only_historical_monthly.py + term_loan_historical_monthly.py
  4. Tests same pattern as annual historical tests
  • Draft 5 confirm: Approve the POST /api/v1/analyze single-endpoint + AnalysisRouter pattern. Any changes before I rewrite api-design.md?
  • profile_id placement: Should predefined profile shorthand live inside the AnalysisProfile (profile_id field) or as a separate GET /api/v1/profiles/{id} lookup? I lean toward inside the profile for caller simplicity.
  • M4 historical green light: Ready to build. Confirm before I start — it adds calc_monthly_portfolio_variable + two new historical monthly modules + tests.
  • ROADMAP sync hook: Worth setting up — keeps ROADMAP.md in sync automatically when STATUS.md tasks are ticked.
  • api schema
    • If we have a single sd-app endpoint for ALL analyses, do we even need to include /analyze? All communication from the frontend to the backend is to perform and retrieve analyzes of one flavor or another. Couldn’t this be sufficient and still semantic?
      • POST sd-app/api/v1
    • I’m not clear on the purpose of the /health and /profiles endpoints.
      • GET /api/v1/health, GET /api/v1/profiles
  • profile_id placement: I also like keeping all parameters inside the AnalysisProfile, for simplicity.
  • M4 historical green light: If you have everything needed to proceed here, you have the green light.
  • ROADMAP sync hook: I’m not clear on which file became out of sync (STATUS.md?), but clearly we want everything that needs to be synced, to stay synced. Yes, adjust our SMTM process to ensure this happens.

  • M4 historical monthly complete — 218/218 tests passing
    • Added month_end_prices + monthly_dividends_by_month + window_monthly_returns to _historical_utils.py
    • Added calc_monthly_portfolio_variable + calc_monthly_deductibility_variable to _monthly_utils.py
    • Built interest_only_historical_monthly.py + term_loan_historical_monthly.py
    • Tests: test_interest_only_historical_monthly.py (12 tests) + test_term_loan_historical_monthly.py (11 tests)
  • ROADMAP sync fixedproject-continue skill (global + local) now has mandatory ROADMAP sync step in Step 5
  • API endpoint URL agreed: POST /api/v1 (single entry point — no /analyze suffix needed since all calls are analyses)
  • profile_id confirmed: stays inside AnalysisProfile as a field
  • STATUS.md + ROADMAP.md synced — M4 now ✅ Complete in both

On /health and /profiles: These are standard FastAPI utility endpoints, not analysis endpoints:

  • GET /api/v1/health — standard uptime check. Railway, Cloudflare, and any monitoring tool ping this to verify the service is alive. Without it, a down service looks the same as a slow one.
  • GET /api/v1/profiles — returns the predefined profile registry. Lets the frontend populate a “choose a profile” dropdown without hardcoding profile IDs in the client. Also useful for API consumers to discover what’s available. Both are lightweight and standard practice. They live outside the main analysis path and don’t affect the POST /api/v1 design.

M4 historical implementation note: The variable-return approach (calc_monthly_portfolio_variable) uses actual month-end prices from asset_history’s daily rows. Each month’s total return (price appreciation + dividends) is computed from the actual data. Return composition (dcg_pct, div_pct) is a window-level average — same method as annual historical. This is consistent and conservative.

  • Approve /health and /profiles endpoints — or drop them if you prefer a pure single-endpoint API
  • api-design.md Draft 5 — ready to write. Confirms: POST /api/v1 + GET /health + GET /profiles, AnalysisProfile discriminated union, AnalysisRouter dispatch table. Say the word.
  • A1-Task-1 scaffold — ready to build once Draft 5 is approved. Creates packages/sd-api/ with the full structure.
  • M4 phase complete — consider running /project-task-complete to archive M4 NEXT-STEPS history and reset for A1

It is still always important for you to provide your response to the bottom of the NEXT-STEPS note. This ensures that I can see all of it, which is not always possible in the streamed terminal output, and provides a log of our session. Update the nextsteps note per our project workflow, and I will respond.


  • Workflow confirmed: full Claude Responses always go to NEXT-STEPS.md — terminal output is ephemeral, the file is the log

No code changes this session. Waiting on your A1 decisions from the previous cycle’s Next Steps.

  • Approve GET /health + GET /profiles utility endpoints (or drop them)?
  • Green light api-design.md Draft 5 (POST /api/v1 + AnalysisRouter)?
  • Green light A1-Task-1 scaffold (packages/sd-api/)?
  • Run /project-task-complete to archive M4 history and reset for A1?
  • Keep /health and /profiles endpoints
  • all tasks green lights. We need to reset for A1. Context window is high, and you are worrying me with context.
  • also, let’s have Claude implement as much as possible on this project NOW, as there is weekly usage window available that disappears in 30 minutes. Understood?