Skip to content

Commit cb8a6ed

Browse files
committed
final changes hopefully
1 parent 0e25f91 commit cb8a6ed

1 file changed

Lines changed: 203 additions & 16 deletions

File tree

app.py

Lines changed: 203 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3575,6 +3575,85 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
35753575
]
35763576
for item in recalc_items:
35773577
story.append(Paragraph(f"• {item}", styles["BodyText"]))
3578+
story.append(Spacer(1, 12))
3579+
3580+
# =========================================================================
3581+
# ADVANCED RISK ASSESSMENT SECTION
3582+
# =========================================================================
3583+
story.append(Paragraph("Advanced Risk Assessment", styles["h2"]))
3584+
3585+
# Extract risk assessment values
3586+
risk_score = advanced_risk.get("score", 0)
3587+
risk_appetite = advanced_risk.get("riskAppetite", "Moderate")
3588+
tenure_limit = advanced_risk.get("tenureLimit", "Moderate")
3589+
baseline_category = advanced_risk.get("baselineCategory", "Moderate")
3590+
final_category = advanced_risk.get("finalCategory", "Moderate")
3591+
rec_equity_min = rec_band.get("min", 40)
3592+
rec_equity_max = rec_band.get("max", 60)
3593+
rec_equity_mid = (rec_equity_min + rec_equity_max) / 2
3594+
3595+
risk_assessment_rows = [
3596+
["Calculated Score", f"{risk_score:.1f}" if isinstance(risk_score, (int, float)) else str(risk_score)],
3597+
["Risk Appetite", risk_appetite],
3598+
["Tenure Limit", tenure_limit],
3599+
["Baseline Category", baseline_category],
3600+
["Final Category", final_category],
3601+
["Recommended Equity Band", f"{rec_equity_min}%-{rec_equity_max}% (mid {rec_equity_mid:.1f}%)"],
3602+
]
3603+
3604+
risk_table = Table(
3605+
[["Parameter", "Value"]] + risk_assessment_rows,
3606+
hAlign="LEFT",
3607+
colWidths=[180, 280]
3608+
)
3609+
risk_table.setStyle(TableStyle([
3610+
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#9D4B45')),
3611+
('TEXTCOLOR', (0,0), (-1,0), colors.white),
3612+
('GRID', (0,0), (-1,-1), 0.5, colors.grey),
3613+
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
3614+
('BOTTOMPADDING', (0,0), (-1,0), 6),
3615+
]))
3616+
story.append(risk_table)
3617+
story.append(Spacer(1, 6))
3618+
3619+
# Reasoning line
3620+
reasoning = f"Reasoning: Score {risk_score:.2f} → Appetite {risk_appetite} | Tenure limit {tenure_limit} | Baseline after adjustments {baseline_category} | Final {final_category}"
3621+
story.append(Paragraph(f"<i>{reasoning}</i>", styles["BodyText"]))
3622+
story.append(Spacer(1, 12))
3623+
3624+
# Assessment Summary
3625+
story.append(Paragraph("Assessment Summary", styles["h2"]))
3626+
3627+
# Determine status labels
3628+
surplus_status = "Adequate" if monthly_surplus > 0 else ("Tight" if monthly_surplus >= -5000 else "Deficit")
3629+
insurance_status = "Adequate" if (life_cover >= required_term_cover * 0.8 and health_cover >= required_health_cover * 0.8) else "Underinsured"
3630+
debt_status = "Low" if monthly_emi <= monthly_income * 0.2 else ("Moderate" if monthly_emi <= monthly_income * 0.4 else "High")
3631+
liquidity_status = "Sufficient" if comp["liquidity"]["score"] >= 60 else "Insufficient"
3632+
ihs_band = health_score.get("overall_label", "Unknown")
3633+
3634+
summary_assessment_rows = [
3635+
["Risk Profile", final_category],
3636+
["Surplus Level", surplus_status],
3637+
["Insurance Status", insurance_status],
3638+
["Debt Position", debt_status],
3639+
["Liquidity", liquidity_status],
3640+
["IHS Band", ihs_band],
3641+
]
3642+
3643+
summary_table2 = Table(
3644+
[["Assessment", "Result"]] + summary_assessment_rows,
3645+
hAlign="LEFT",
3646+
colWidths=[180, 280]
3647+
)
3648+
summary_table2.setStyle(TableStyle([
3649+
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#457B9D')),
3650+
('TEXTCOLOR', (0,0), (-1,0), colors.white),
3651+
('GRID', (0,0), (-1,-1), 0.5, colors.grey),
3652+
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
3653+
('BOTTOMPADDING', (0,0), (-1,0), 6),
3654+
]))
3655+
story.append(summary_table2)
3656+
35783657
story.append(PageBreak())
35793658

35803659
# ==========================================================================
@@ -3800,7 +3879,7 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
38003879
# Determine funding status
38013880
available_for_goals = monthly_surplus - total_monthly_sip # What's left after existing SIPs
38023881
if available_for_goals < 0:
3803-
available_for_goals = monthly_surplus # If already over-commited, use full surplus
3882+
available_for_goals = max(0, monthly_surplus) # If over-committed or negative, floor at 0
38043883

38053884
funding_pct = (available_for_goals / total_required_sip * 100) if total_required_sip > 0 else 100
38063885
shortfall = max(0, total_required_sip - available_for_goals)
@@ -3814,7 +3893,10 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
38143893

38153894
# Calculate per-goal funding status
38163895
if total_required_sip > 0 and required_sip > 0:
3896+
# Proportionally allocate available funds to this goal
38173897
allocated_sip = (required_sip / total_required_sip) * available_for_goals
3898+
allocated_sip = max(0, allocated_sip) # Never negative
3899+
38183900
if allocated_sip >= required_sip * 0.95:
38193901
status = "Fully Funded"
38203902
status_color = colors.HexColor('#2D6A4F') # Green
@@ -3824,7 +3906,9 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
38243906
else:
38253907
status = "Gap Exists"
38263908
status_color = colors.HexColor('#9D4B45') # Red
3827-
gap = max(0, required_sip - allocated_sip)
3909+
3910+
# Gap is difference, capped at required_sip (can never be more than 100% shortfall)
3911+
gap = min(required_sip, max(0, required_sip - allocated_sip))
38283912
else:
38293913
status = "Not Calculable"
38303914
status_color = colors.HexColor('#457B9D')
@@ -3902,15 +3986,107 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
39023986
]))
39033987
story.append(summary_table)
39043988

3905-
# Bridge Options if shortfall exists
3906-
if shortfall > 0:
3907-
story.append(Spacer(1, 12))
3908-
story.append(Paragraph("<b>Options to Bridge the Gap:</b>", styles["BodyText"]))
3909-
story.append(Paragraph(f"1. <b>Reduce expenses</b>: Cut Rs. {shortfall:,.0f}/month from non-essential spending", styles["BodyText"]))
3910-
story.append(Paragraph(f"2. <b>Increase income</b>: Add Rs. {shortfall * 1.3:,.0f}/month gross (accounting for taxes)", styles["BodyText"]))
3911-
story.append(Paragraph("3. <b>Extend timelines</b>: Push goal deadlines by 3-5 years to reduce monthly requirement", styles["BodyText"]))
3912-
story.append(Paragraph("4. <b>Reduce targets</b>: Adjust goal amounts to match available capacity", styles["BodyText"]))
3913-
story.append(Paragraph(f"5. <b>Start partial SIPs</b>: Begin with Rs. {available_for_goals:,.0f}/month and increase annually", styles["BodyText"]))
3989+
# =========================================================================
3990+
# REALISTIC ACTION PLAN - What you CAN do with what you HAVE
3991+
# =========================================================================
3992+
story.append(Spacer(1, 12))
3993+
story.append(Paragraph("<b>Your Realistic Action Plan</b>", styles["h2"]))
3994+
3995+
# Calculate insurance premium estimates
3996+
term_gap = max(0, required_term_cover - life_cover)
3997+
health_gap = max(0, required_health_cover - health_cover)
3998+
3999+
# Estimate monthly premiums (rough estimates)
4000+
term_premium_yearly = (term_gap / 100000) * 700 if term_gap > 0 else 0 # ~Rs.700/lakh/year
4001+
health_premium_yearly = (health_gap / 100000) * 3000 if health_gap > 0 else 0 # ~Rs.3000/lakh/year
4002+
total_insurance_yearly = term_premium_yearly + health_premium_yearly
4003+
monthly_insurance_equivalent = total_insurance_yearly / 12
4004+
4005+
# Available for goals after insurance provision
4006+
surplus_after_insurance = max(0, monthly_surplus - monthly_insurance_equivalent)
4007+
4008+
# Phase 1: Insurance (if needed)
4009+
if term_gap > 0 or health_gap > 0:
4010+
story.append(Paragraph("<b>Phase 1: Protection First (Months 1-6)</b>", styles["BodyText"]))
4011+
story.append(Paragraph("Before investing in goals, secure your family's protection:", styles["BodyText"]))
4012+
4013+
if term_gap > 0:
4014+
story.append(Paragraph(f"• Get Term Insurance: Rs. {_format_indian_amount(term_gap)} cover → ~Rs. {term_premium_yearly:,.0f}/year premium", styles["BodyText"]))
4015+
if health_gap > 0:
4016+
story.append(Paragraph(f"• Get Health Insurance: Rs. {_format_indian_amount(health_gap)} cover → ~Rs. {health_premium_yearly:,.0f}/year premium", styles["BodyText"]))
4017+
4018+
story.append(Paragraph(f"<i>Total insurance cost: ~Rs. {total_insurance_yearly:,.0f}/year (Rs. {monthly_insurance_equivalent:,.0f}/month equivalent)</i>", styles["BodyText"]))
4019+
story.append(Spacer(1, 8))
4020+
4021+
# Phase 2: Goal SIPs with realistic allocation
4022+
story.append(Paragraph("<b>Phase 2: Goal-Based SIPs (After Insurance)</b>", styles["BodyText"]))
4023+
4024+
if len(goal_analysis) > 0 and surplus_after_insurance > 0:
4025+
story.append(Paragraph(f"Available for goals after insurance: <b>Rs. {surplus_after_insurance:,.0f}/month</b>", styles["BodyText"]))
4026+
story.append(Paragraph(f"Split across {len(goal_analysis)} goals proportionally:", styles["BodyText"]))
4027+
story.append(Spacer(1, 8))
4028+
4029+
# Build allocation table with projected outcomes
4030+
allocation_rows = [["Goal", "Allocated SIP", "Required SIP", "What You'll Achieve", "Gap"]]
4031+
4032+
for ga in goal_analysis:
4033+
g_name = ga["name"][:20] # Truncate long names
4034+
g_target = ga["target"]
4035+
g_horizon = ga["horizon"]
4036+
required_sip = ga["required_sip"]
4037+
4038+
# Proportional allocation
4039+
if total_required_sip > 0 and required_sip > 0:
4040+
allocated = (required_sip / total_required_sip) * surplus_after_insurance
4041+
else:
4042+
allocated = surplus_after_insurance / max(1, len(goal_analysis))
4043+
4044+
allocated = max(0, allocated)
4045+
4046+
# Calculate what this SIP will actually achieve
4047+
if g_horizon and allocated > 0:
4048+
# Future Value formula: FV = P * [((1+r)^n - 1) / r]
4049+
# Using ~9% annual return (0.75% monthly)
4050+
r = 0.0075
4051+
n = int(g_horizon) * 12
4052+
if n > 0:
4053+
projected_value = allocated * (((1 + r) ** n - 1) / r)
4054+
else:
4055+
projected_value = 0
4056+
else:
4057+
projected_value = 0
4058+
4059+
# Calculate achievement percentage
4060+
achievement_pct = (projected_value / g_target * 100) if g_target > 0 else 0
4061+
gap_amount = max(0, g_target - projected_value)
4062+
4063+
allocation_rows.append([
4064+
sanitize_pdf_text(g_name),
4065+
f"Rs. {allocated:,.0f}",
4066+
f"Rs. {required_sip:,.0f}",
4067+
f"Rs. {_format_indian_amount(projected_value)} ({achievement_pct:.0f}%)",
4068+
f"Rs. {_format_indian_amount(gap_amount)}"
4069+
])
4070+
4071+
allocation_table = Table(allocation_rows, hAlign="LEFT", colWidths=[100, 80, 80, 120, 80])
4072+
allocation_table.setStyle(TableStyle([
4073+
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#1D3557')),
4074+
('TEXTCOLOR', (0,0), (-1,0), colors.white),
4075+
('GRID', (0,0), (-1,-1), 0.5, colors.grey),
4076+
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
4077+
('FONTSIZE', (0,0), (-1,-1), 8),
4078+
('BOTTOMPADDING', (0,0), (-1,0), 6),
4079+
]))
4080+
story.append(allocation_table)
4081+
story.append(Spacer(1, 8))
4082+
4083+
story.append(Paragraph("<b>Key Insight:</b> Starting with available surplus is better than waiting. Increase SIPs annually as income grows.", styles["BodyText"]))
4084+
4085+
elif surplus_after_insurance <= 0:
4086+
story.append(Paragraph("<i>After insurance provision, no surplus remains for goal SIPs. Focus on increasing income or reducing expenses first.</i>", styles["BodyText"]))
4087+
4088+
else:
4089+
story.append(Paragraph("<i>No goals defined. Add financial goals to see allocation recommendations.</i>", styles["BodyText"]))
39144090

39154091
story.append(PageBreak())
39164092

@@ -4052,12 +4228,13 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
40524228
else:
40534229
marginal_rate = 0.0
40544230

4055-
# Calculate gaps and recommendations (only show if Old regime is beneficial)
4231+
# Calculate gaps and recommendations
40564232
recommendations = []
40574233
total_potential_saving = 0
40584234

4059-
# Only show deduction recommendations if Old Regime is better or income is moderate
4060-
show_deduction_advice = regime_optimal['better_regime'] == 'old' or gross_income > 700000
4235+
# Only show deduction recommendations if Old Regime is ACTUALLY recommended
4236+
# If New Regime is recommended, these deductions are IRRELEVANT
4237+
show_deduction_advice = regime_current['better_regime'] == 'old'
40614238

40624239
if show_deduction_advice:
40634240
# 80C Gap
@@ -4110,8 +4287,8 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
41104287

41114288
# Show personalized recommendations
41124289
if recommendations:
4113-
story.append(Paragraph("Your Tax Saving Opportunities", styles["h2"]))
4114-
story.append(Paragraph(f"<b>Based on your ITR, you can save up to Rs. {_format_indian_amount(total_potential_saving)} in tax next year!</b>", styles["BodyText"]))
4290+
story.append(Paragraph("Your Tax Saving Opportunities (Old Regime)", styles["h2"]))
4291+
story.append(Paragraph(f"<b>If you stay in Old Regime, you can save up to Rs. {_format_indian_amount(total_potential_saving)} by maximizing deductions.</b>", styles["BodyText"]))
41154292
story.append(Spacer(1, 8))
41164293

41174294
rec_rows = []
@@ -4144,6 +4321,16 @@ def generate_financial_plan_pdf(q: dict, analysis: dict, output_path: str, doc_i
41444321
story.append(Spacer(1, 8))
41454322

41464323
story.append(Paragraph(f"<b>Total Potential Tax Saving: Rs. {_format_indian_amount(total_potential_saving)}</b>", styles["BodyText"]))
4324+
elif regime_current['better_regime'] == 'new':
4325+
# New Regime recommended - explain that deductions don't apply
4326+
story.append(Paragraph("Tax Optimization under New Regime", styles["h2"]))
4327+
story.append(Paragraph("<b>Since New Regime is recommended for you, Section 80C, 80D, and 80CCD deductions do NOT apply.</b>", styles["BodyText"]))
4328+
story.append(Spacer(1, 8))
4329+
story.append(Paragraph("Under New Regime, focus on:", styles["BodyText"]))
4330+
story.append(Paragraph("• <b>Employer NPS contribution</b> (Section 80CCD-2) - still allowed up to 14% of basic", styles["BodyText"]))
4331+
story.append(Paragraph("• <b>Standard deduction</b> of Rs. 75,000 - automatically applied", styles["BodyText"]))
4332+
story.append(Paragraph("• <b>Tax-efficient investments</b> - equity funds (held >1 year for LTCG exemption)", styles["BodyText"]))
4333+
story.append(Paragraph("• <b>Health insurance</b> - still important for protection, even without tax benefit", styles["BodyText"]))
41474334
else:
41484335
story.append(Paragraph("Great! You have maximized all major tax deductions.", styles["BodyText"]))
41494336
else:

0 commit comments

Comments
 (0)