# actuarial

`actuarial` values the two employee benefit obligations that almost every Indian company has to report each year. The first is **gratuity** under the Payment of Gratuity Act, which is a post-employment defined benefit. The second is **leave encashment**, which is an other long-term benefit. Both are valued under **Ind AS 19 / AS 15 (revised)** with the **Projected Unit Credit (PUC)** method. Give it an employee census CSV, a YAML file of assumptions and the year's plan-asset cash flows. It returns the DBO, current service cost and interest cost, and a full roll-forward from the prior year in which actuarial gains and losses are split into financial, demographic and experience components. It also produces the Ind AS 19 sensitivity table, the maturity profile and the weighted average duration. Results come out as an Excel workbook and as a disclosure note in Markdown and HTML.

## Features

- **PUC engine:** each employee is projected to every future exit year (death, withdrawal, retirement). The engine uses a multiple-decrement table built from attrition bands and mortality, applies projected salary, attributes the benefit to service rendered and discounts it to the valuation date.
- **Gratuity rules:** 15/26 × monthly salary (basic + DA) × completed years. A part year counts as a full year when it is over six months. Benefits are capped at ₹20 lakh, and the 5-year vesting rule applies except on death. All of these can be configured.
- **Leave encashment:** the accumulated balance (capped at the maximum accumulation) is encashed at projected salary on any exit, with an optional annual availment rate. The service cost is the value of the coming year's accrual.
- **Mortality:** a built-in, compact **approximation** of IALM 2012-14 Ultimate, which is clearly labelled as such. Rates can be scaled, female rates can use an age setback, and a full custom table can be loaded.
- **Attrition:** age bands, with optional overrides for each category (e.g. Worker / Staff).
- **Reconciliation:** opening DBO + service cost + interest cost − benefits paid + past service cost ± actuarial (gain)/loss = closing DBO. Each assumption set is re-run in steps so that gains and losses can be attributed:
  - plan amendment (past service cost)
  - financial assumptions
  - demographic assumptions
  - experience
- **Plan assets:** a roll-forward of the plan assets (interest income, return excluding interest, contributions, benefits paid), together with the net liability, P&L and OCI. Remeasurements of leave encashment go to P&L, as Ind AS 19 para 156 requires.
- **Sensitivity:** ±0.5% and ±1% changes to the discount rate, salary escalation and attrition, showing the DBO impact in ₹ and %.
- **Outputs:** an Excel workbook (summary, per-employee detail, reconciliation, sensitivity and maturity profile for each benefit) and a disclosure note in `.md` and `.html` with Indian digit grouping (1,23,45,678).
- **`explain` command:** prints the exit-by-exit PUC projection for one employee, for audit queries.

## Install

```bash
cd actuarial
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
```

Requires Python 3.12+. The runtime dependencies are click, PyYAML, openpyxl and rich.

## Quickstart

```bash
# single-date valuation
.venv/bin/actuarial value -c examples/census_2025.csv -a examples/assumptions_2025.yaml -o out/fy25

# two-year roll-forward with full disclosure (the main use case)
.venv/bin/actuarial reconcile \
  --prior-census examples/census_2025.csv --prior-assumptions examples/assumptions_2025.yaml \
  -c examples/census_2026.csv -a examples/assumptions_2026.yaml \
  --cashflows examples/cashflows_2025_26.yaml -o out/fy26 --company "Example Industries Ltd"
```

## Usage

### `actuarial reconcile`

This is real output from the bundled 300-employee example (FY 2025-26, gratuity part):

```
──────────────────────────── Gratuity (post-employment defined benefit) ────────────────────────────
Change in present value of obligation
 Item                                            ┃ Amount (INR)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━
 Opening defined benefit obligation              │  6,39,12,385
 Current service cost                            │    59,78,264
 Past service cost (plan amendment)              │            0
 Interest cost                                   │    45,37,779
 Benefits paid                                   │  (64,30,620)
 Actuarial (gain)/loss - financial assumptions   │    31,14,364
 Actuarial (gain)/loss - demographic assumptions │       10,578
 Actuarial (gain)/loss - experience adjustments  │    11,41,285
 Closing defined benefit obligation              │  7,22,64,035
Change in fair value of plan assets
 Item                                            ┃ Amount (INR)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━
 Opening fair value                              │  5,24,08,000
 Interest income                                 │    37,20,968
 Return on plan assets excluding interest income │     (26,168)
 Employer contributions                          │    76,69,000
 Benefits paid from the fund                     │  (57,87,558)
 Closing fair value                              │  5,79,84,242
Net liability recognised in the balance sheet
 Item                        ┃       Opening ┃       Closing
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━
 Present value of obligation │   6,39,12,385 │   7,22,64,035
 Fair value of plan assets   │ (5,24,08,000) │ (5,79,84,242)
 Net liability / (asset)     │   1,15,04,385 │   1,42,79,793
Expense recognised in the statement of profit and loss
 Item                                 ┃ Amount (INR)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━
 Current service cost                 │    59,78,264
 Past service cost                    │            0
 Net interest cost                    │     8,16,811
 Expense recognised in profit or loss │    67,95,075
Remeasurements recognised in other comprehensive income
 Item                                            ┃ Amount (INR)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━
 Actuarial (gain)/loss on obligation             │    42,66,227
 Return on plan assets excluding interest income │       26,168
 Net (income)/expense recognised in OCI          │    42,92,395
Sensitivity analysis
 Assumption        ┃ Change ┃   DBO (INR) ┃ Impact (INR) ┃ Impact %
━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━
 Discount rate     │  -1.0% │ 7,89,00,856 │    66,36,821 │   +9.18%
 Discount rate     │  -0.5% │ 7,54,63,498 │    31,99,463 │   +4.43%
 Discount rate     │  +0.5% │ 6,92,81,883 │  (29,82,153) │   -4.13%
 Discount rate     │  +1.0% │ 6,64,98,511 │  (57,65,525) │   -7.98%
 Salary escalation │  -1.0% │ 7,01,20,851 │  (21,43,184) │   -2.97%
 Salary escalation │  -0.5% │ 7,12,30,844 │  (10,33,192) │   -1.43%
 Salary escalation │  +0.5% │ 7,32,53,701 │     9,89,666 │   +1.37%
 Salary escalation │  +1.0% │ 7,42,43,826 │    19,79,791 │   +2.74%
 Attrition rate    │  -1.0% │ 6,86,94,408 │  (35,69,627) │   -4.94%
 Attrition rate    │  -0.5% │ 7,05,48,479 │  (17,15,556) │   -2.37%
 Attrition rate    │  +0.5% │ 7,38,51,735 │    15,87,699 │   +2.20%
 Attrition rate    │  +1.0% │ 7,53,21,382 │    30,57,347 │   +4.23%
Weighted average duration: 9.12 years
```

The leave encashment section follows. Its actuarial gain or loss goes to P&L, not OCI:

```
Expense recognised in the statement of profit and loss
 Current service cost                 │    82,91,398
 Past service cost                    │            0
 Net interest cost                    │    27,23,872
 Net actuarial (gain)/loss            │     2,56,334
 Expense recognised in profit or loss │  1,12,71,604
```

With `-o DIR` the command writes these files:

| File | Contents |
|---|---|
| `valuation.xlsx` | Summary, plus Detail, Reconciliation, Sensitivity and Maturity sheets for each benefit (one row per employee in Detail) |
| `disclosure.md` | Disclosure note: assumptions (current vs prior), membership data, every table above, maturity profile, duration |
| `disclosure.html` | The same note as a styled standalone HTML page |

### `actuarial value`

`value` runs a valuation at a single date with no roll-forward. It prints the DBO, the service cost and interest cost for the next year, and the sensitivity table. Use `-b gratuity|leave|all` to choose benefits.

### `actuarial explain`

```
$ actuarial explain -c examples/census_2026.csv -a examples/assumptions_2026.yaml --id E0004 --rows 3
E0004 age 31.41, past service 9.29 yrs, salary 21,200
     t ┃ Age ┃      Cause ┃     Prob ┃ Service ┃  Salary ┃   Benefit ┃ Accrued ┃ PV accrued
━━━━━━━╇━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━
  0.50 │  31 │      death │ 0.000978 │    9.79 │  22,083 │   127,400 │ 120,891 │        114
  0.50 │  31 │ withdrawal │ 0.079959 │    9.79 │  22,083 │   127,400 │ 120,891 │      9,356
  1.50 │  32 │      death │ 0.000937 │   10.79 │  23,960 │   152,052 │ 130,907 │        111
  ...
 28.59 │  60 │ retirement │ 0.180007 │   37.88 │ 218,434 │ 2,000,000 │ 490,351 │     13,637
DBO 118,238.26   current service cost 12,222.00
```

## Configuration

**Census CSV:** `employee_id, dob, doj, monthly_salary, leave_balance_days, gender, category`. Dates use ISO format, and `monthly_salary` is basic + DA.

**Assumptions YAML** (see `examples/assumptions_2026.yaml`):

```yaml
valuation_date: 2026-03-31
discount_rate: 0.0675
salary_escalation: 0.085
retirement_age: 60
mortality: {scale: 1.0, female_setback_years: 0}   # optional custom_table: {age: qx}
attrition:
  default:   [{up_to_age: 30, rate: 0.12}, {up_to_age: 44, rate: 0.05}, {up_to_age: 100, rate: 0.02}]
  categories: {Worker: [...]}                       # optional per-category bands
gratuity: {days: 15, divisor: 26, cap: 2000000, vesting_years: 5}
leave: {encashment_divisor: 30, max_accumulation_days: 300, annual_accrual_days: 30, availment_rate: 0.05}
```

**Cash flows YAML** (passed with `--cashflows`):

```yaml
gratuity:
  plan_assets: {opening_fair_value: 52408000, contributions: 7669000, benefits_paid: 5787558, actual_return: 3694800}
  benefits_paid_directly: 643062
leave:
  benefits_paid_directly: 4375554
```

`examples/generate.py` regenerates the synthetic two-year example deterministically. It produces 300 employees at 31-Mar-2025 and simulates exits, raises and new joiners up to 31-Mar-2026.

## Architecture

| Module | Responsibility |
|---|---|
| `census.py` | Census CSV loading and validation, `Employee` |
| `assumptions.py` | Assumptions YAML, attrition bands, mortality wrapper, gratuity and leave rules |
| `mortality.py` | Compact IALM 2012-14 approximation and log-linear interpolation |
| `engine.py` | PUC core: exit projection (multiple decrement), gratuity and leave valuation, duration, maturity profile |
| `analysis.py` | Plan assets, year-on-year reconciliation with gain/loss attribution, sensitivity |
| `report.py` | Disclosure sections rendered to Markdown, HTML and Excel |
| `cli.py` | `value`, `reconcile` and `explain` commands |

The methodology, formulas and a worked example are in [docs/methodology.md](docs/methodology.md).

## Tests

```bash
.venv/bin/pytest -q      # 34 tests
```

The tests cover the following:

- a single-employee PUC result checked against a hand calculation (in `tests/test_puc_hand.py`, with the arithmetic written out)
- the gratuity cap, vesting and the six-month rounding rule
- dependent decrement probabilities
- the reconciliation identity
- gain/loss attribution and past service cost
- the direction of each sensitivity
- the duration and maturity profile
- the CLI, end to end

## Limitations

- **The mortality table is an approximation.** It is anchored on five-year ages and interpolated log-linearly, so it is not the published IALM 2012-14 table. Supply `mortality.custom_table` with the full table for any report that will be signed off.
- Interest cost is opening DBO × discount rate. Interest income is opening fair value × discount rate. Mid-year contributions and benefit payments do not earn interest.
- Benefits are attributed in a straight line over total service. The extra attribution rule that Ind AS 19 para 73 requires once the ₹20 lakh cap binds is not applied, so for employees whose projected benefit hits the cap the DBO is somewhat understated.
- Salary is projected continuously at `(1 + e)^t`. In-service exits fall at mid-year. Disability, ill-health retirement and minimum funding requirements are not modelled.
- In the leave model, the balance is encashed only on exit. The value of leave availed in service (compensated absences) is not valued separately.
- This tool is for illustration and internal estimates. It is not a substitute for a valuation certified by an actuary.

## License

MIT. See [LICENSE](LICENSE).
