Development Guide¶
This section provides guidance for developers working on the RWA calculator.
Overview¶
The development guide covers:
- Testing Guide - Running tests, writing tests, test fixtures
- Workbooks & UI - Marimo workbooks and interactive UI applications
- Scripts & Automation - Developer scripts for setup, deployment, and test data
- Benchmark Tests - Performance and scale testing (10K-10M)
- Adding Features - Extending the calculator
- Code Style - Coding standards and conventions
- Citation Tracking -
@citesdecorator conventions, watchfire CLI usage, strict-gate behaviour - Citation Coverage Matrix - Auto-generated article -> implementing-function index with click-to-expand source snippets
Development Setup¶
Prerequisites¶
- Python 3.13+
- uv package manager (recommended)
- Git
Setup Steps¶
# Clone the repository
git clone https://github.com/OpenAfterHours/rwa_calculator.git
cd rwa_calculator
# Install with development dependencies
uv sync --all-extras
# Verify installation
uv run pytest
IDE Configuration¶
VS Code:
{
"python.defaultInterpreterPath": ".venv/bin/python",
"python.formatting.provider": "none",
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true
}
}
PyCharm:
1. Open project directory
2. Configure interpreter to use .venv
3. Mark src as Sources Root
4. Enable Ruff integration
Development Workflow¶
TDD Approach¶
The project follows Test-Driven Development:
- Write acceptance test - Define expected behavior
- Write unit tests - Test component behavior
- Implement - Write code to pass tests
- Refactor - Improve code while tests pass
Branch Strategy¶
Commit Convention¶
Types: feat, fix, docs, test, refactor, chore
Project Structure¶
src/rwa_calc/
├── config/ # Configuration (FX rates)
├── contracts/ # Interfaces and data contracts
│ ├── bundles.py # Data transfer objects
│ ├── config.py # Configuration classes
│ ├── errors.py # Error types
│ ├── protocols.py # Component interfaces
│ └── validation.py
├── data/ # Input-domain schemas / validation enums
│ ├── schemas.py # Polars schemas + categorical constraints
│ └── column_spec.py
├── domain/ # Core domain
│ └── enums.py # Enumerations
├── rulebook/ # Regulatory value-home (cited entries)
│ ├── packs/ # common.py / crr.py / b31.py — regulatory values (cited)
│ ├── resolve.py # resolve(regime_id, date) -> frozen ResolvedRulepack
│ ├── compile.py # Decimal -> float boundary
│ └── audit.py # rulepack diff / citation index
└── engine/ # Calculation engine
├── pipeline.py # Run-lifecycle facade over the registry/orchestrator fold
├── registry.py # Literal StageSpec list (ordered stages)
├── orchestrator.py # run_stages fold (threads PipelineContext)
├── stages/ # One run(ctx, rulepack, run_config) adapter per stage
│ ├── hierarchy/ # Hierarchy resolution (impl)
│ ├── classify/ # Classification (impl)
│ ├── crm.py # CRM adapter
│ └── calc.py # SA/IRB/Slotting calculators adapter
├── loader.py # Data loading
├── hierarchy.py # Back-compat shim -> stages/hierarchy/
├── classifier.py # Back-compat shim -> stages/classify/
├── ccf.py # Credit conversion factors
├── aggregator/ # Aggregation (package)
├── crm/ # Credit risk mitigation
├── sa/ # Standardised approach
├── irb/ # IRB approach
└── slotting/ # Slotting approach
Key Development Principles¶
1. LazyFrame Operations¶
Always use Polars LazyFrames:
# Good
result = df.with_columns(
rwa=pl.col("ead") * pl.col("risk_weight")
)
# Bad - row iteration
for row in df.iter_rows():
rwa = row["ead"] * row["risk_weight"]
2. Protocol-Based Design¶
Implement protocols for new components:
from rwa_calc.contracts.protocols import CalculatorProtocol
class MyCalculator:
def calculate(
self,
exposures: pl.LazyFrame,
config: CalculationConfig,
) -> ResultBundle:
# Implementation
...
3. Immutable Data¶
Use frozen dataclasses for data contracts:
from dataclasses import dataclass
@dataclass(frozen=True)
class MyBundle:
data: pl.LazyFrame
metadata: dict
4. Error Accumulation¶
Collect errors instead of raising:
errors = []
for exposure in exposures:
if not valid(exposure):
errors.append(CalculationError(
exposure_id=exposure.id,
message="Invalid exposure"
))
return Result(data=data, errors=errors)
Running Tasks¶
Tests¶
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=src/rwa_calc
# Run specific test file
uv run pytest tests/unit/test_pipeline.py
# Run specific test
uv run pytest tests/unit/test_pipeline.py::test_crr_calculation
Linting¶
# Check code style
uv run ruff check src tests
# Fix automatically
uv run ruff check --fix src tests
# Format code
uv run ruff format src tests
Type Checking¶
Documentation¶
Next Steps¶
- Testing Guide - Comprehensive testing documentation
- Adding Features - How to extend the calculator
- Code Style - Coding conventions