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
- Premiums:
annual_premiumx modal factor at the start of each instalment, forpremium_paying_termyears. Modal loading: half-yearly +2%, quarterly +4%, monthly +6% (no loading on ULIPs). Quarterly on 62,000 means 4 x 16,120 = 64,480 a year. - Survival benefits:
pct_of_sax sum assured at the end of the listed year. For money-back policies the maturity amount is net of them. - Maturity benefit by scenario:
- guaranteed = sum assured +
guaranteed_addition_ratex SA x term - 4% / 8% = guaranteed +
bonus_rate[s]x SA x term +terminal_bonus[s]x SA - ULIP = fund value from the charge simulation below, no guaranteed scenario
maturity: {guaranteed, "4", "8"}overrides any of these with the figures printed in the insurer's benefit illustration (recommended).- term with
return_of_premium: truereturns total premiums paid. - ULIP fund: for each year, add
premium x (1 - allocation_charge[year])while paying, grow bygross_return - fmc, then subtractadmin_charge_annual + mortality_pct_sa x SA.
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.