@@ -1305,16 +1305,18 @@ def compute_retirement_corpus(age, monthly_income, desired_monthly_pension=None,
13051305
13061306def compute_term_insurance_need (age , monthly_income , retirement_age = 60 ):
13071307 """
1308- Calculate term insurance requirement using Human Life Value method.
1309- Formula: (retirement_age - age) * monthly_income * 12
1308+ Calculate term insurance requirement.
1309+ Formula: 15x annual income × 1.3 (inflation buffer) = 19.5x annual income
1310+
1311+ For example: ₹30,000/month = ₹3.6 lakh/year → Required cover = ₹70.2 lakh
13101312
13111313 Returns the required term cover amount.
13121314 """
1313- age_val = _safe_float (age , 30 )
13141315 mi = _safe_float (monthly_income , 0.0 )
1315- years_to_retirement = max ( 0 , retirement_age - age_val )
1316+ annual_income = mi * 12
13161317
1317- required_cover = years_to_retirement * mi * 12
1318+ # 15x annual income with 1.3x inflation buffer = 19.5x
1319+ required_cover = annual_income * 15 * 1.3
13181320 return round (required_cover , 0 )
13191321
13201322def compute_ihs (savings_percent , current_products , allocation ):
@@ -3724,7 +3726,7 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
37243726 ["Current Status" , life_status ],
37253727 ["Current Coverage" , f"Rs. { _format_indian_amount (life_cover )} " if life_cover > 0 else "Rs. 0" ],
37263728 ["Recommended Coverage" , f"Rs. { _format_indian_amount (required_term_cover )} minimum" ],
3727- ["Basis" , "10x annual income" ],
3729+ ["Basis" , "15x annual income (with 1.3x inflation buffer) " ],
37283730 ["Gap" , f"Rs. { _format_indian_amount (max (0 , required_term_cover - life_cover ))} " ],
37293731 ]
37303732 life_table = Table (
@@ -4003,21 +4005,48 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
40034005 funding_pct = 100
40044006 available_for_goals = monthly_surplus
40054007
4006- # Reality Check Summary Box
4008+ # Reality Check Summary Box - Enhanced with Existing SIP Context
40074009 story .append (Spacer (1 , 12 ))
4008- story .append (Paragraph ("<b>Reality Check: Affordability Summary</b>" , styles ["h2" ]))
4010+ story .append (Paragraph ("<b>Reality Check: Your Actual Financial Situation</b>" , styles ["h2" ]))
4011+
4012+ # Calculate insurance provision for proper context
4013+ term_gap = max (0 , required_term_cover - life_cover )
4014+ health_gap = max (0 , required_health_cover - health_cover )
4015+ client_age = _safe_float (age , 35 )
4016+ term_rate_per_lakh = 500 if client_age < 35 else (700 if client_age < 45 else 1200 )
4017+ health_rate_per_lakh = 2500 if client_age < 35 else (3500 if client_age < 45 else 5000 )
4018+ term_premium_yearly = (term_gap / 100000 ) * term_rate_per_lakh if term_gap > 0 else 0
4019+ health_premium_yearly = (health_gap / 100000 ) * health_rate_per_lakh if health_gap > 0 else 0
4020+ monthly_insurance_provision = (term_premium_yearly + health_premium_yearly ) / 12
4021+
4022+ # Calculate remaining after insurance
4023+ remaining_after_insurance = max (0 , monthly_surplus - monthly_insurance_provision )
4024+
4025+ # Total investing = existing SIP + new allocations
4026+ total_investing = total_monthly_sip + remaining_after_insurance
4027+ total_investing_pct = (total_investing / total_required_sip * 100 ) if total_required_sip > 0 else 100
40094028
40104029 summary_rows = [
4011- ["Total Goals" , f"{ len (goals )} " ],
4012- ["Total Required SIP" , f"Rs. { total_required_sip :,.0f} /month" ],
4013- ["Current SIP Commitment" , f"Rs. { total_monthly_sip :,.0f} /month" if total_monthly_sip > 0 else "None" ],
4014- ["Available Monthly Surplus" , f"Rs. { _format_indian_amount (monthly_surplus )} " ],
4015- ["Available for New Goals" , f"Rs. { _format_indian_amount (available_for_goals )} " ],
4016- ["Funding Capacity" , f"{ min (funding_pct , 100 ):.0f} % of requirement" ],
4030+ ["Monthly Income" , f"Rs. { _format_indian_amount (monthly_income )} " ],
4031+ ["Monthly Expenses" , f"Rs. { _format_indian_amount (monthly_expenses )} " ],
4032+ ["Monthly Surplus" , f"Rs. { _format_indian_amount (monthly_surplus )} " ],
4033+ ["" , "" ], # Separator row
4034+ ["Current SIP (Already Investing)" , f"Rs. { total_monthly_sip :,.0f} /month" if total_monthly_sip > 0 else "None" ],
40174035 ]
40184036
4019- if shortfall > 0 :
4020- summary_rows .append (["Monthly Shortfall" , f"Rs. { shortfall :,.0f} /month" ])
4037+ if monthly_insurance_provision > 0 :
4038+ summary_rows .append (["Insurance Provision Needed" , f"Rs. { monthly_insurance_provision :,.0f} /month" ])
4039+ summary_rows .append (["Available for NEW Additions" , f"Rs. { remaining_after_insurance :,.0f} /month" ])
4040+
4041+ summary_rows .extend ([
4042+ ["" , "" ], # Separator row
4043+ ["TOTAL Investing" , f"Rs. { total_investing :,.0f} /month" ],
4044+ ["Goal Requirement" , f"Rs. { total_required_sip :,.0f} /month" ],
4045+ ["Coverage" , f"{ min (total_investing_pct , 100 ):.1f} % of requirement" ],
4046+ ])
4047+
4048+ if total_required_sip > total_investing :
4049+ summary_rows .append (["Shortfall" , f"Rs. { total_required_sip - total_investing :,.0f} /month" ])
40214050
40224051 summary_table = Table (
40234052 summary_rows ,
@@ -4026,9 +4055,9 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
40264055 )
40274056
40284057 # Color based on funding status
4029- if funding_pct >= 95 :
4058+ if total_investing_pct >= 95 :
40304059 header_color = colors .HexColor ('#2D6A4F' ) # Green
4031- elif funding_pct >= 50 :
4060+ elif total_investing_pct >= 50 :
40324061 header_color = colors .HexColor ('#E9C46A' ) # Yellow
40334062 else :
40344063 header_color = colors .HexColor ('#9D4B45' ) # Red
@@ -4041,6 +4070,75 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
40414070 ('BOTTOMPADDING' , (0 ,0 ), (- 1 ,- 1 ), 6 ),
40424071 ]))
40434072 story .append (summary_table )
4073+ story .append (Spacer (1 , 12 ))
4074+
4075+ # =========================================================================
4076+ # GOAL-WISE SIP ALLOCATION TABLE
4077+ # =========================================================================
4078+ if len (goal_analysis ) > 0 :
4079+ story .append (Paragraph ("<b>Goal-wise SIP Allocation</b>" , styles ["h2" ]))
4080+
4081+ # Calculate per-goal allocations
4082+ goal_sip_rows = [["Goal" , "Required SIP" , "Allocated SIP" , "Gap" , "Status" ]]
4083+
4084+ for ga in goal_analysis :
4085+ g_name = ga ["name" ][:25 ] # Truncate long names
4086+ required_sip = ga ["required_sip" ]
4087+
4088+ # Proportional allocation from available surplus
4089+ if total_required_sip > 0 and required_sip > 0 :
4090+ allocated_sip = (required_sip / total_required_sip ) * remaining_after_insurance
4091+ else :
4092+ allocated_sip = remaining_after_insurance / max (1 , len (goal_analysis ))
4093+
4094+ # Add share of existing SIP (proportionally)
4095+ if total_monthly_sip > 0 and total_required_sip > 0 :
4096+ existing_sip_share = (required_sip / total_required_sip ) * total_monthly_sip
4097+ allocated_sip += existing_sip_share
4098+
4099+ gap = max (0 , required_sip - allocated_sip )
4100+
4101+ if gap <= 0 or (allocated_sip >= required_sip * 0.95 ):
4102+ status = "✓ Funded"
4103+ elif allocated_sip >= required_sip * 0.5 :
4104+ status = "Partial"
4105+ else :
4106+ status = "Gap"
4107+
4108+ goal_sip_rows .append ([
4109+ sanitize_pdf_text (g_name ),
4110+ f"Rs. { required_sip :,.0f} " ,
4111+ f"Rs. { allocated_sip :,.0f} " ,
4112+ f"Rs. { gap :,.0f} " if gap > 0 else "-" ,
4113+ status
4114+ ])
4115+
4116+ goal_sip_table = Table (goal_sip_rows , hAlign = "LEFT" , colWidths = [120 , 80 , 80 , 80 , 60 ])
4117+ goal_sip_table .setStyle (TableStyle ([
4118+ ('BACKGROUND' , (0 ,0 ), (- 1 ,0 ), colors .HexColor ('#457B9D' )),
4119+ ('TEXTCOLOR' , (0 ,0 ), (- 1 ,0 ), colors .white ),
4120+ ('GRID' , (0 ,0 ), (- 1 ,- 1 ), 0.5 , colors .grey ),
4121+ ('FONTNAME' , (0 ,0 ), (- 1 ,0 ), 'Helvetica-Bold' ),
4122+ ('FONTSIZE' , (0 ,0 ), (- 1 ,- 1 ), 8 ),
4123+ ('BOTTOMPADDING' , (0 ,0 ), (- 1 ,0 ), 6 ),
4124+ ]))
4125+ story .append (goal_sip_table )
4126+ story .append (Spacer (1 , 8 ))
4127+
4128+ # Context message
4129+ if total_monthly_sip > 0 :
4130+ story .append (Paragraph (
4131+ f"<i>Note: You are already investing Rs. { total_monthly_sip :,.0f} /month through existing SIPs. "
4132+ f"After insurance provision, Rs. { remaining_after_insurance :,.0f} /month can be added. "
4133+ f"Total: Rs. { total_investing :,.0f} /month ({ total_investing_pct :.1f} % of goal needs).</i>" ,
4134+ styles ["BodyText" ]
4135+ ))
4136+ else :
4137+ story .append (Paragraph (
4138+ f"<i>Note: After insurance provision of Rs. { monthly_insurance_provision :,.0f} /month, "
4139+ f"Rs. { remaining_after_insurance :,.0f} /month is available for goal SIPs.</i>" ,
4140+ styles ["BodyText" ]
4141+ ))
40444142
40454143 # =========================================================================
40464144 # REALISTIC ACTION PLAN - What you CAN do with what you HAVE
0 commit comments