Methodology and formulas

All money is nominal rupees. Rates are decimals (0.08 is 8%). Cash flows are placed at year-end unless stated.

1. Life cover need (coverwise/needs.py)

Human Life Value (HLV)

For t = 1..N, where N = retirement_age - age:

contribution_t = income * (1 + income_growth)^(t-1) - personal_expense * (1 + inflation)^(t-1)
HLV            = sum( max(0, contribution_t) / (1 + discount_rate)^t )
personal_expense = household_annual * personal_share

Worked example (the one in tests/test_needs.py): age 58, retire at 60, income 10,00,000 growing 10%, household expenses 8,00,000 with a 25% personal share (2,00,000), inflation 5%, discount 10%.

t=1: (1,000,000 - 200,000)   / 1.10   = 727,273
t=2: (1,100,000 - 210,000)   / 1.21   = 735,537
HLV                                   = 1,462,810

HLV is an upper bound: it is what the earner's future surplus is worth, not what the family would spend.

Needs-based method

dependents_pv = sum over t=1..H of max(0, dependent_cost*(1+inflation)^(t-1) - spouse_income_t) / (1+r)^t
dependent_cost = household_annual * (1 - personal_share)
H = independence_age - age of the youngest child   (override with family.dependency_years)
goal_pv = cost * (1 + goal_inflation)^year / (1 + r)^year      # education uses education_inflation
gross  = dependents_pv + sum(goal_pv) + home_loan_outstanding + other_liabilities
needs  = max(0, gross - assets - existing_cover)

assets is investments + EPF + PPF + emergency fund + other. existing_cover is group life cover plus the sum assured of every policy with counts_as_cover: true (the default). Spouse income offsets expenses only while the spouse is below retirement_age.

Recommendation

recommended_cover = min(roundup(needs, 5 lakh), roundup(HLV gross, 5 lakh)); the HLV cap is skipped when the earner has no income. The term is the longest of the dependency horizon, the remaining loan tenure and the last goal year, capped at years to retirement (minimum 5).

Sensitivity

The needs method is recomputed over discount rate and inflation +/- 2 points (3x3). HLV is recomputed over discount rate and income growth +/- 2 points. Net figures subtract assets and existing cover.

2. Policy IRR (coverwise/finance.py, policy.py)

Solver

IRR solves sum(cf_i / (1+r)^t_i) = 0 with fractional t_i, so monthly premiums are modelled at their real dates. The solver samples a grid of rates from -99% to 1200%, finds the first sign change of the NPV, then runs Newton steps inside the bracket and falls back to bisection whenever Newton leaves it. It returns None when the flows have no outflow or no inflow (pure term cover) or when NPV never changes sign.

Example: premium 1,000 at t=0 and 1,100 at t=1 gives exactly 10%.

Cash flows

Tax (policy.assess_tax)

Applied per policy using issue_date (if absent, current rules are assumed and a note says so):

Rule Effect
Annual premium above 10% of sum assured (20% for policies issued before 1 Apr 2012) maturity is not exempt under 10(10D); gains taxed at slab_rate
ULIP issued on/after 1 Feb 2021, aggregate annual ULIP premium above 2.5 lakh gains taxed as capital gains (ulip_gain_tax)
Non-ULIP, non-term policy issued on/after 1 Apr 2023, aggregate annual premium above 5 lakh gains taxed at slab_rate; death benefit stays exempt

When a policy is taxable, tax = rate x max(0, maturity + survival benefits - total premiums) is deducted from the maturity payout and a post-tax IRR is reported. These are simplified readings of the rules; check the current Income-tax Act provisions.

Continue versus surrender

Sunk premiums are ignored. With years_elapsed = e and surrender value SV:

flows = [(0, -SV)]                         # what you give up by continuing
      + [future premiums at t = year - e]
      + [future survival benefits and maturity at t = year - e]
IRR of continuing = IRR(flows)
NPV at the alternative = NPV(alt, flows),  alt = portfolio_return x (1 - tax_on_gains)

Continue only if the NPV is positive. The verdict compares all three scenarios.

3. Buy term and invest the rest (coverwise/compare.py)

surplus_y = annual_outgo(bundled) - term_premium          # y < premium_paying_term
          = -term_premium                                  # later years, if the term plan outlasts the payment term
corpus_{y+1} = (corpus_y + surplus_y) * (1 + portfolio_return)       # contribution at the start of the year
final        = corpus - tax_on_gains * max(0, corpus - net_contributions)

Worked example: bundled premium 1,000, term premium 900, two years, 10% return, no tax. Year 1: (0+100) x 1.1 = 110. Year 2: (110+100) x 1.1 = 231.

The bundled side is maturity + survival benefits reinvested at the same return, so both strategies are valued on the same terminal-wealth basis. The breakeven return is the portfolio return at which the two end equal, found by bisection on 0..40%.

Term premium: the --term-premium option, else the policy's term_quote, else an illustrative table by age (interpolated per Rs 1,000 of cover: age 30 is 0.60, 35 is 0.85, 40 is 1.40, 45 is 2.40, 50 is 4.20; smokers x1.8). Treat the table as a placeholder and get real quotes.

Charts: the "value over time" line for a policy is its premiums compounded at the policy's own IRR (so it reaches the maturity amount exactly at the end of the term). "Protection over time" is the sum assured for the bundled policy against term cover plus the investment corpus.

4. Health cover (coverwise/health.py)

Target sum insured

target = roundup(tier_base[tier] x size_multiplier[members] x age_multiplier[oldest member], 5 lakh). Defaults: tier bases 40L / 30L / 20L; multipliers 1.0, 1.5, 1.8, 2.0 for 1-4 members (+0.2 per extra member); age multipliers 1.0 (<30), 1.25 (<45), 1.75 (<60), 2.5. All can be overridden in health.target_table. The ten-year figure applies medical_inflation.

Room-rent proportionate deduction

If the room costs more per day than the cap, every room-linked charge (room, nursing, doctor and OT fees, entered as other_room_linked) is cut by cap / actual:

ratio     = cap / actual_room_rent                 (1 if no cap or under the cap)
deduction = (room_total + other_room_linked) x (1 - ratio)
payable   = bill - deduction
insurer   = min(base_sum_insured, payable x (1 - copay))

Example: room 8,000/day for 4 days, cap 5,000, other linked 60,000, non-linked 2,50,000: linked = 92,000, ratio 0.625, deduction 34,500, payable 3,07,500.

For the claim-size curves the same linked share of the bill and the same ratio are applied to every claim size.

Layering

For a claim X:

base   pays  min(SI_base, X x (1 - linked_share x (1 - ratio)) x (1 - base_copay))
top-up pays  min(SI_t, max(0, X - deductible)) x (1 - topup_copay)       # lowest deductible first, net of lower layers
out of pocket = X - base - sum(top-ups)

The deductible is measured on the full claim, not on what the base paid, which is how a super top-up behaves for a single claim. Two consequences the tool surfaces: a deductible larger than the base sum insured leaves an uncovered band, and room-rent cuts on the base are not recovered by the top-up unless the claim is large enough to cross the deductible.

A structure "reaches" max(base, deductible + top-up SI). The recommendation is the cheapest structure that reaches the target with no uncovered band; otherwise the one that comes closest.