Skip to content

Generated file. Source: docs/bond_engine_architecture.md Edit the source document and run npm run docs:sync to 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:
  • ROR
    • ror.ts: src/app/domain/bond-engine/strategies/ror.ts
  • DOR
    • dor.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:
  • TOS
    • tos.ts: src/app/domain/bond-engine/strategies/tos.ts
  • EDO
    • edo.ts: src/app/domain/bond-engine/strategies/edo.ts
  • ROS
    • ros.ts: src/app/domain/bond-engine/strategies/ros.ts
  • ROD
    • rod.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:
  • COI
    • coi.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.ts
  • createTosAnnualAccumulationStrategy: src/app/domain/bond-engine/strategies/tos.ts
  • createCoiAnnualPayoutStrategy: 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 / ROD availability for 800+
  • 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:

  • purchaseEvents
  • reinvestmentDecisions
  • redemptionEvents
  • payoutEvents

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:

  • YearlyResult
  • BondCalculationResult
  • BondSimulationDetails

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.ts
  • src/app/domain/persistence/service.ts
  • src/app/domain/prediction-history/repository.ts
  • src/app/domain/prediction-history/service.ts
  • src/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 analysisMode state 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:

  1. update the shared engine if the rule is genuinely shared
  2. update the thin specialization if the rule is product-specific
  3. reuse the strategy factory in tests
  4. update family docs and product docs together
  5. update init_prompt.md: ai/init_prompt.md if product rules changed

When adding a new bond:

  1. first decide whether it fits an existing family
  2. 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 quarterly
  • ROR, DOR -> monthly-income
  • TOS, EDO, ROS, ROD -> annual-accumulation
  • COI -> annual-payout

This is the intended long-term architecture for maintainability.