feat: documentation update

- Completed export workflow implementation (query builders, CSV/XLSX serializers, streaming API endpoints, UI modals, automated tests).
- Added export modal UI and client script to trigger downloads directly from dashboard.
- Documented import/export field mapping and usage guidelines in FR-008.
- Updated installation guide with export environment variables, dependencies, and CLI/CI usage instructions.
This commit is contained in:
2025-11-11 18:34:02 +01:00
parent 02906bc960
commit 29f16139a3
11 changed files with 751 additions and 15 deletions

View File

@@ -0,0 +1,184 @@
# Financial Metrics Specification
## 1. Purpose
Define the standard methodology CalMiner uses to compute discounted-cash-flow
profitability metrics, including Net Present Value (NPV), Internal Rate of Return
(IRR), and Payback Period. These calculations underpin scenario evaluation,
reporting, and investment decision support within the platform.
## 2. Scope
- Applies to scenario-level profitability analysis using cash flows stored in
the application database.
- Covers deterministic cash-flow evaluation; stochastic extensions (e.g., Monte
Carlo) may overlay these metrics but should reference this specification.
- Documents the assumptions implemented in `services/financial.py` and the
related pytest coverage in `tests/test_financial.py`.
## 3. Inputs and Definitions
| Symbol | Name | Description | Units / Domain | Notes |
| -------- | ----------------------- | ------------------------------------------- | ----------------- | --------------------------------------------------------- |
| $CF_t$ | Cash flow at period $t$ | Currency amount (positive or negative) | Scenario currency | Negative values typically represent investments/outflows. |
| $t$ | Period index | Fractional period (0 = anchor) | Real number | Derived from explicit index or calendar date. |
| $r$ | Discount rate | Decimal representation of the annual rate | $r > -1$ | Scenario configuration provides default rate. |
| $m$ | Compounds per year | Compounding frequency | Positive integer | Defaults to 1 (annual). |
| $RV$ | Residual value | Terminal value realised after final period | Scenario currency | Optional. |
| $t_{RV}$ | Residual periods | Timing of residual value relative to anchor | Real number | Defaults to last period + 1. |
### Period Anchoring and Timing
Cash flows can be supplied with either:
1. `period_index` — explicit integers/floats overriding all other timing.
2. `date` — calendar dates. The earliest dated flow anchors the timeline and
subsequent dates convert the day difference into fractional periods using a
365-day year divided by `compounds_per_year`.
3. Neither — flows default to sequential periods based on input order.
This aligns with `normalize_cash_flows` in `services/financial.py`, ensuring all
calculations receive `(amount, periods)` tuples.
## 4. Net Present Value (NPV)
### Formula
For a set of cash flows $CF_t$ with discount rate $r$ and compounding frequency
$m$:
$$
\text{NPV} = \sum_{t=0}^{n} \frac{CF_t}{\left(1 + \frac{r}{m}\right)^{t}} +
\begin{cases}
\dfrac{RV}{\left(1 + \frac{r}{m}\right)^{t_{RV}}} & \text{if residual value present} \\
0 & \text{otherwise}
\end{cases}
$$
### Implementation Notes
- `discount_factor` computes $(1 + r/m)^{-t}`; NPV iterates over the normalised
flows and sums `amount \* factor`.
- Residual values default to one period after the final cash flow when
`residual_periods` is omitted.
- Empty cash-flow sequences return 0 unless a residual value is supplied.
## 5. Internal Rate of Return (IRR)
### Definition
IRR is the discount rate $r$ for which NPV equals zero:
$$
0 = \sum_{t=0}^{n} \frac{CF_t}{\left(1 + \frac{r}{m}\right)^{t}}
$$
### Solver Behaviour
- NewtonRaphson iteration starts from `guess` (default 10%).
- Derivative instability or non-finite values trigger a fallback to a bracketed
bisection search between:
- Lower bound: $-0.99 \times m$
- Upper bound: $10.0$ (doubles until the root is bracketed or attempts exceed 12)
- Raises `ConvergenceError` when no sign change is found or the bisection fails
within the iteration budget (`max_iterations` default 100; bisection uses
double this limit).
- Validates that the cash-flow series includes at least one negative and one
positive value; otherwise IRR is undefined and a `ValueError` is raised.
### Caveats
- Multiple sign changes may yield multiple IRRs. The solver returns the root it
finds within the configured bounds; scenarios must interpret the result in
context.
- Rates less than `-1 * m` imply nonphysical periodic rates and are excluded.
## 6. Payback Period
### Definition
The payback period is the earliest period $t$ where cumulative cash flows become
non-negative. With fractional interpolation (default behaviour), the period is
calculated as:
$$
\text{Payback} = t_{prev} + \left(\frac{-\text{Cumulative}_{prev}}{CF_t}\right)
\times (t - t_{prev})
$$
where $t_{prev}$ is the previous period with negative cumulative cash flow.
### Implementation Notes
- Cash flows are sorted by period to ensure chronological accumulation.
- When `allow_fractional` is `False`, the function returns the first period with
non-negative cumulative total without interpolation.
- `PaybackNotReachedError` is raised if the cumulative total never becomes
non-negative.
## 7. Examples
### Example 1: Baseline Project
- Initial investment: $-1,000,000$ at period 0.
- Annual inflows: 300k, 320k, 340k, 360k, 450k (periods 1-5).
- Discount rate: 8% annual, `compounds_per_year = 1`.
| Period | Cash Flow (currency) |
| ------ | -------------------- |
| 0 | -1,000,000 |
| 1 | 300,000 |
| 2 | 320,000 |
| 3 | 340,000 |
| 4 | 360,000 |
| 5 | 450,000 |
- `net_present_value` ≈ 205,759
- `internal_rate_of_return` ≈ 0.158
- `payback_period` ≈ 4.13 periods
### Example 2: Residual Value with Irregular Timing
- Investment: -500,000 on 2024-01-01
- Cash inflows on irregular dates (2024-07-01: 180k, 2025-01-01: 200k,
2025-11-01: 260k)
- Residual value 150k realised two years after final inflow
- Discount rate: 10%, `compounds_per_year = 4`
NPV discounts each cash flow by converting day deltas to quarterly periods. The
residual is discounted at `t_{RV} = last_period + 2` (because the override is
supplied).
## 8. Testing Strategy
`tests/test_financial.py` exercises:
- `normalize_cash_flows` with date-based, index-based, and sequential cash-flow
inputs.
- NPV calculations with and without residual values, including discount-rate
sensitivity checks.
- IRR convergence success cases, invalid inputs, and non-converging scenarios.
- Payback period exact, fractional, and never-payback cases.
Developers extending the financial metrics should add regression tests covering
new assumptions or solver behaviour.
## 9. Integration Notes
- Scenario evaluation services should pass cash flows as `CashFlow` instances to
reuse the shared normalisation logic.
- UI and reporting layers should display rates as percentages but supply them as
decimals to the service layer.
- Future Monte Carlo or sensitivity analyses can reuse the same helpers to
evaluate each simulated cash-flow path.
## 10. References
- Internal implementation: `calminer/services/financial.py`
- Tests: `calminer/tests/test_financial.py`
- Related specification: `calminer-docs/specifications/price_calculation.md`
- Architecture context: `calminer-docs/architecture/08_concepts/02_data_model.md`
```}
```