Generated file. Source:
docs/bond_engine_architecture.mdEdit the source document and runnpm run docs:syncto refresh this published copy.
Bond Engine Architecture¶
This document describes the current high-level architecture of the bond calculation engine.
Goal¶
The engine is intentionally split into small product families instead of one giant generic calculator.
Why:
- different bonds have materially different cash-flow behavior
- reusable mechanics should live in shared family engines
- product-specific rules should stay in thin strategy modules
- selection/eligibility rules should stay outside financial simulation
Current Strategy Map¶
Custom quarterly strategy¶
OTS- code:
ots.ts: src/app/domain/bond-engine/strategies/ots.ts
- reason:
- quarter-based redemption and reinvestment model is unique enough to stay separate
Shared monthly-income family¶
- family engine:
monthly-income.ts: src/app/domain/bond-engine/strategies/monthly-income.ts- specializations:
RORror.ts: src/app/domain/bond-engine/strategies/ror.ts
DORdor.ts: src/app/domain/bond-engine/strategies/dor.ts
- shared behavior:
- monthly interest payouts
- monthly tax
- purchase history
- reinvestment decisions
- profitability guard for late reinvestment
Shared annual-accumulation family¶
- family engine:
annual-accumulation.ts: src/app/domain/bond-engine/strategies/annual-accumulation.ts- specializations:
TOStos.ts: src/app/domain/bond-engine/strategies/tos.ts
EDOedo.ts: src/app/domain/bond-engine/strategies/edo.ts
ROSros.ts: src/app/domain/bond-engine/strategies/ros.ts
RODrod.ts: src/app/domain/bond-engine/strategies/rod.ts
- shared behavior:
- yearly compounding inside the batch
- no payout during cycle
- reinvestment only after redemption
- purchase history
- redemption history
Shared annual-payout family¶
- family engine:
annual-payout.ts: src/app/domain/bond-engine/strategies/annual-payout.ts- specializations:
COIcoi.ts: src/app/domain/bond-engine/strategies/coi.ts
- shared behavior:
- yearly interest payout to cash
- yearly tax on paid interest
- no capitalization inside the batch
- purchase history
- payout history
- redemption history
Routing¶
Product dispatch is handled by:
strategy-registry.ts: src/app/domain/bond-engine/strategy-registry.ts
This is intentional. Routing should not grow as a long if/else chain inside calculation logic.
Strategy Factories¶
Each specialization exports its product strategy factory.
Examples:
createRorMonthlyIncomeStrategy: src/app/domain/bond-engine/strategies/ror.tscreateTosAnnualAccumulationStrategy: src/app/domain/bond-engine/strategies/tos.tscreateCoiAnnualPayoutStrategy: src/app/domain/bond-engine/strategies/coi.ts
These factories are the single source of truth for:
- runtime strategy configuration
- test strategy configuration
- public strategy identifiers
Tests should reuse them instead of duplicating inline configs.
Selection vs Calculation Boundary¶
Eligibility and selection rules are outside the financial engine.
Examples:
ROS/RODavailability for800+- current stage only gates availability; it does not yet enforce the purchase amount cap tied to received
800+benefits - sanitizing selected bonds
- selecting only allowed instruments for the current params
These rules belong in:
selection.ts: src/app/domain/bond-engine/selection.ts
Current product limitations for these rules should stay aligned with:
This boundary is important:
- strategies simulate cash flows
- selection decides whether a bond may be chosen at all
- incomplete eligibility rules that are consciously postponed should stay explicitly documented here, not be implied as fully implemented
Do not move product eligibility rules into shared engines or bond strategy modules.
Future Assistant Input Layer¶
If the product introduces a guided assistant that asks the user questions and fills the calculator for them:
- it should be treated as an input orchestration layer above the existing form and store
- it should map answers into the same normalized input model used by the manual calculator flow
- it must not introduce a second calculation path
- it must not implement financial rules on its own
This means:
- the assistant collects intent and parameters
- the existing store/form shape remains the operational source of truth for inputs
- the same bond engine produces the final result regardless of whether inputs came from manual entry or assistant dialogue
Simulation Event Contract¶
The engine exposes optional event ledgers via:
BondSimulationDetails: src/app/types/bonds.ts
Possible event families:
purchaseEventsreinvestmentDecisionsredemptionEventspayoutEvents
Not every strategy uses every event type.
That is expected:
- monthly-income uses reinvestment decisions
- annual-payout uses payout events
- annual-accumulation does not use payout events
Canonical Result Model¶
The public output of every strategy should be treated as a canonical result contract, not only as a UI payload.
Current code contract:
YearlyResultBondCalculationResultBondSimulationDetails
Current source of truth for its semantics:
Why this matters:
- the same result contract feeds chart, summary, yearly table, CSV export, and future persistence features
- it is one of the main architectural boundaries between engine internals and the rest of the product
- future roadmap stages such as saved simulations, inflation prediction history, recurring investing, and assistant flows depend on having enough output resolution here
- current runtime persistence flow now spans:
src/app/domain/persistence/repository.tssrc/app/domain/persistence/service.tssrc/app/domain/prediction-history/repository.tssrc/app/domain/prediction-history/service.tssrc/app/domain/portfolio/types.ts
Design rule:
- engine internals may be richer than the canonical result contract
- analysis models should sit above the canonical result contract when the product needs more than one defended interpretation of the same result
- UI-specific adapters should sit above either the canonical result or the analysis-model layer, but must not redefine semantics locally
- but the canonical result contract must remain the stable shared layer between them
Current implementation direction:
- canonical result types live in
src/app/domain/result-model/types.ts - thin presentation adapters live in
src/app/domain/result-model/view-adapters.ts - future interpretation / analysis models should live in
src/app/domain/analysis-models/ - the implemented interpretation switch is backed by one global
analysisModestate and is exposed through small UI controls in the chart and yearly-table sections rather than through component-local semantics - future saved-simulation contracts live in
src/app/domain/persistence/types.ts - runtime saved-simulation repository lives in
src/app/domain/persistence/repository.ts - shared finalization helpers for strategy families live in
src/app/domain/bond-engine/strategies/shared/finalization.ts - yearly-table comparison rows are also derived in
src/app/domain/result-model/view-adapters.ts, so the UI does not have to recreate comparison-grouping semantics locally
Maintenance Rules¶
When changing a bond family:
- update the shared engine if the rule is genuinely shared
- update the thin specialization if the rule is product-specific
- reuse the strategy factory in tests
- update family docs and product docs together
- update
init_prompt.md: ai/init_prompt.mdif product rules changed
When adding a new bond:
- first decide whether it fits an existing family
- if not, create a new family rather than forcing exceptions into an old one
Current Outcome¶
The codebase now has a complete product map:
OTS-> custom quarterlyROR,DOR-> monthly-incomeTOS,EDO,ROS,ROD-> annual-accumulationCOI-> annual-payout
This is the intended long-term architecture for maintainability.