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:
184
specifications/financial_metrics.md
Normal file
184
specifications/financial_metrics.md
Normal 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
|
||||
|
||||
- Newton–Raphson 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`
|
||||
|
||||
```}
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user