# CORE-001D Boundary and Persistence Cleanup Plan ## Goal Make Decimal/unit-safe domain types reliable at external boundaries without forcing a risky full-model rewrite. This slice follows: - `CORE-001A` Decimal/unit foundations - `CORE-001B` overview + hedge migration - `CORE-001C` backtests + event comparison migration ## Why this exists The visible math paths now use unit-safe domain helpers, but several persistence and adapter seams still store or pass raw floats. That is acceptable at the edge for now, but the boundaries should be explicit, tested, and stable. ## Main hotspots found in the current codebase ### 1. Portfolio persistence still serializes raw floats Files: - `app/models/portfolio.py` - workspace-backed `portfolio_config.json` payloads Current state: - `PortfolioConfig` stores float fields - `PortfolioRepository.save/load` round-trips plain JSON numbers Risk: - persistence format is implicit - Decimal-safe internal math can be silently rounded or reinterpreted at reload boundaries ### 2. Legacy portfolio/domain types still expose float-heavy APIs Files: - `app/models/portfolio.py` - `app/services/settings_status.py` - `app/services/alerts.py` Current state: - `LombardPortfolio` remains float-based for compatibility - several services convert Decimal-backed results back to float for formatting/threshold checks Risk: - domain-safe calculations exist, but callers can still drift into ambiguous float semantics ### 3. Backtesting UI/service seams still take float inputs Files: - `app/services/backtesting/ui_service.py` - `app/services/backtesting/comparison.py` - `app/services/event_comparison_ui.py` - `app/domain/backtesting_math.py` Current state: - typed materialization exists, but service entrypoints still accept `float` - conversions back to float happen for model compatibility Risk: - callers can bypass intent and reintroduce unit ambiguity at service boundaries ### 4. Provider/cache adapters use generic JSON and float payloads Files: - `app/services/cache.py` - `app/services/price_feed.py` - `app/services/data_service.py` - `app/services/backtesting/historical_provider.py` Current state: - cache serialization supports datetime only via custom default - provider payloads are mostly raw floats, dicts, and lists Risk: - external payloads are fine to keep float-heavy, but conversion into domain-safe structures should happen at named boundaries and be test-covered ## Recommended implementation order ### Step 1: make persistence format explicit Target: - `PortfolioConfig` JSON shape - workspace portfolio JSON shape Deliverables: - explicit serialization helpers for persisted money/price/weight-like fields - tests proving stable round-trip behavior - docs for JSON number vs string decisions Preferred near-term approach: - keep external JSON ergonomic - document exact persisted field meanings and units - ensure reload path normalizes through a single constructor/adapter ### Step 2: add named boundary adapters Target: - portfolio persistence load/save - price feed quote ingestion - historical close ingestion - options-chain normalization Deliverables: - helper functions with explicit names such as `*_from_provider_payload(...)` or `*_to_persistence_dict(...)` - tests proving conversion behavior and fail-closed validation ### Step 3: reduce raw-float service entrypoints where practical Target: - backtesting UI/comparison service inputs - settings/alerts status helpers Deliverables: - services accept typed or normalized values earlier - float conversion, where still required, happens at the last compatibility seam ## Non-goals - replacing every float in every Pydantic/dataclass immediately - redesigning third-party payload models wholesale - changing public UI formatting behavior just for type purity ## First candidate sub-slices ### CORE-001D1 — Portfolio persistence serialization seam - make `PortfolioConfig` persistence round-trip explicit - add serialization tests for workspace-scoped config files ### CORE-001D2 — Provider and cache adapter boundaries - document/test cache + provider conversion seams - ensure raw external floats are normalized before domain math ### CORE-001D3 — Service entrypoint tightening - narrow float-heavy internal service APIs where easy and low-risk ## Success criteria - persistence schema for bookkeeping-sensitive fields is explicit and tested - Decimal/unit-safe values cross boundaries through named adapters - remaining float-heavy hotspots are either removed or intentionally documented as edge-only - no regression in existing browser-visible flows