ChAbhishek28 commited on
Commit
f844095
Β·
1 Parent(s): ba4e1dd

Restore scenario analysis feature - core project functionality

Browse files

βœ… Restored Files:
- scenario_analysis_service.py - Main scenario analysis engine
- policy_chart_generator.py - Policy visualization charts
- policy_impact_simulator.py - Policy impact simulation
- policy_simulator_api.py - API endpoints for policy simulation
- policy_chat_interface.py - Interactive policy chat

πŸ”§ Fixed Imports:
- Restored scenario_service import in rag_service.py
- Added ScenarioAnalysisService to app.py
- Fixed module not found errors

🎯 Features Available:
- Government scenario analysis
- Policy impact visualization
- Interactive policy simulations
- Chart generation for policy data

app.py CHANGED
@@ -11,6 +11,7 @@ from hybrid_llm_service import HybridLLMService
11
  from voice_service import VoiceService
12
  from rag_service import search_documents
13
  from lancedb_service import LanceDBService
 
14
  import config
15
  from dotenv import load_dotenv
16
 
@@ -68,6 +69,7 @@ app.add_middleware(
68
  llm_service = None
69
  voice_service = None
70
  lancedb_service = None
 
71
 
72
  def get_llm_service():
73
  global llm_service
@@ -87,6 +89,12 @@ def get_lancedb_service():
87
  lancedb_service = LanceDBService()
88
  return lancedb_service
89
 
 
 
 
 
 
 
90
  # Health check endpoint
91
  @app.get("/health")
92
  async def health_check():
 
11
  from voice_service import VoiceService
12
  from rag_service import search_documents
13
  from lancedb_service import LanceDBService
14
+ from scenario_analysis_service import ScenarioAnalysisService
15
  import config
16
  from dotenv import load_dotenv
17
 
 
69
  llm_service = None
70
  voice_service = None
71
  lancedb_service = None
72
+ scenario_service = None
73
 
74
  def get_llm_service():
75
  global llm_service
 
89
  lancedb_service = LanceDBService()
90
  return lancedb_service
91
 
92
+ def get_scenario_service():
93
+ global scenario_service
94
+ if scenario_service is None:
95
+ scenario_service = ScenarioAnalysisService()
96
+ return scenario_service
97
+
98
  # Health check endpoint
99
  @app.get("/health")
100
  async def health_check():
policy_chart_generator.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Chart Generation for Policy Impact Simulator
3
+ Creates visual charts and graphs for policy analysis results
4
+ """
5
+
6
+ import matplotlib.pyplot as plt
7
+ import numpy as np
8
+ from typing import Dict, Any, List
9
+ import os
10
+ import base64
11
+ import io
12
+ from datetime import datetime
13
+
14
+ class PolicyChartGenerator:
15
+ """Generates charts and graphs for policy impact analysis"""
16
+
17
+ def __init__(self):
18
+ # Set matplotlib to use non-interactive backend
19
+ plt.switch_backend('Agg')
20
+
21
+ def generate_scenario_comparison_chart(self, variants: Dict[str, Any], title: str = "Policy Scenario Analysis") -> str:
22
+ """Generate a bar chart comparing scenario variants"""
23
+
24
+ # Extract scenario data
25
+ scenario_names = []
26
+ costs = []
27
+ colors = []
28
+
29
+ color_map = {
30
+ 'best_case': '#28a745', # Green
31
+ 'base_case': '#007bff', # Blue
32
+ 'worst_case': '#dc3545' # Red
33
+ }
34
+
35
+ for scenario_type, data in variants.items():
36
+ if isinstance(data, dict) and 'total_cost' in data:
37
+ scenario_names.append(scenario_type.replace('_', ' ').title())
38
+ costs.append(data['total_cost'])
39
+ colors.append(color_map.get(scenario_type, '#6c757d'))
40
+
41
+ # Create chart
42
+ fig, ax = plt.subplots(figsize=(10, 6))
43
+ bars = ax.bar(scenario_names, costs, color=colors, alpha=0.8)
44
+
45
+ # Customize chart
46
+ ax.set_title(title, fontsize=16, fontweight='bold', pad=20)
47
+ ax.set_ylabel('Total Cost (β‚Ή Crores)', fontsize=12)
48
+ ax.set_xlabel('Scenario', fontsize=12)
49
+
50
+ # Add value labels on bars
51
+ for bar, cost in zip(bars, costs):
52
+ height = bar.get_height()
53
+ ax.text(bar.get_x() + bar.get_width()/2., height + max(costs) * 0.01,
54
+ f'β‚Ή{cost:,.0f}', ha='center', va='bottom', fontweight='bold')
55
+
56
+ # Format y-axis
57
+ ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'β‚Ή{x:,.0f}'))
58
+
59
+ # Add grid
60
+ ax.grid(True, alpha=0.3, axis='y')
61
+ ax.set_axisbelow(True)
62
+
63
+ # Tight layout
64
+ plt.tight_layout()
65
+
66
+ # Convert to base64 string
67
+ return self._fig_to_base64(fig)
68
+
69
+ def generate_yearly_breakdown_chart(self, yearly_data: List[Dict], title: str = "Yearly Impact Breakdown") -> str:
70
+ """Generate a line chart showing yearly breakdown"""
71
+
72
+ years = []
73
+ impacts = []
74
+ beneficiaries = []
75
+
76
+ for year_data in yearly_data:
77
+ years.append(f"Year {year_data.get('year', 0)}")
78
+ impacts.append(year_data.get('impact', 0))
79
+ beneficiaries.append(year_data.get('affected_beneficiaries', 0))
80
+
81
+ # Create dual-axis chart
82
+ fig, ax1 = plt.subplots(figsize=(12, 6))
83
+
84
+ # Plot impact on primary axis
85
+ color1 = '#007bff'
86
+ ax1.set_xlabel('Years', fontsize=12)
87
+ ax1.set_ylabel('Impact (β‚Ή Crores)', fontsize=12, color=color1)
88
+ line1 = ax1.plot(years, impacts, color=color1, marker='o', linewidth=3, markersize=8, label='Financial Impact')
89
+ ax1.tick_params(axis='y', labelcolor=color1)
90
+ ax1.grid(True, alpha=0.3)
91
+
92
+ # Create secondary axis for beneficiaries
93
+ ax2 = ax1.twinx()
94
+ color2 = '#28a745'
95
+ ax2.set_ylabel('Beneficiaries', fontsize=12, color=color2)
96
+ line2 = ax2.plot(years, beneficiaries, color=color2, marker='s', linewidth=3, markersize=8, linestyle='--', label='Affected Population')
97
+ ax2.tick_params(axis='y', labelcolor=color2)
98
+
99
+ # Add title
100
+ ax1.set_title(title, fontsize=16, fontweight='bold', pad=20)
101
+
102
+ # Add value labels
103
+ for i, (year, impact, beneficiary) in enumerate(zip(years, impacts, beneficiaries)):
104
+ ax1.annotate(f'β‚Ή{impact:.1f}', (i, impact), textcoords="offset points", xytext=(0,10), ha='center', fontweight='bold')
105
+ ax2.annotate(f'{beneficiary:,}', (i, beneficiary), textcoords="offset points", xytext=(0,-15), ha='center', fontweight='bold', color=color2)
106
+
107
+ # Add legend
108
+ lines1, labels1 = ax1.get_legend_handles_labels()
109
+ lines2, labels2 = ax2.get_legend_handles_labels()
110
+ ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
111
+
112
+ plt.tight_layout()
113
+ return self._fig_to_base64(fig)
114
+
115
+ def generate_ascii_chart(self, variants: Dict[str, Any], width: int = 50) -> str:
116
+ """Generate ASCII chart for text-based display"""
117
+
118
+ if not variants:
119
+ return "No data available for chart generation"
120
+
121
+ # Extract costs
122
+ costs = []
123
+ names = []
124
+ for scenario_type, data in variants.items():
125
+ if isinstance(data, dict) and 'total_cost' in data:
126
+ costs.append(data['total_cost'])
127
+ names.append(scenario_type.replace('_', ' ').title())
128
+
129
+ if not costs:
130
+ return "No cost data available"
131
+
132
+ max_cost = max(costs) if costs else 1
133
+
134
+ chart = "πŸ“Š Policy Scenario Comparison (β‚Ή Crores)\n"
135
+ chart += "=" * (width + 20) + "\n"
136
+
137
+ for name, cost in zip(names, costs):
138
+ # Calculate bar length
139
+ bar_length = int((cost / max_cost) * width) if max_cost > 0 else 0
140
+ bar = "β–ˆ" * bar_length
141
+
142
+ # Add emoji based on scenario type
143
+ emoji = "🟒" if "Best" in name else "πŸ”΄" if "Worst" in name else "πŸ”΅"
144
+
145
+ chart += f"{emoji} {name:12} β”‚{bar:<{width}} β‚Ή{cost:,.0f}\n"
146
+
147
+ chart += "=" * (width + 20) + "\n"
148
+ return chart
149
+
150
+ def generate_implementation_timeline_chart(self, timeline_data: Dict[str, Any]) -> str:
151
+ """Generate ASCII timeline chart for implementation phases"""
152
+
153
+ phases = [
154
+ "πŸ“‹ Planning Phase (Months 1-3)",
155
+ "βš–οΈ Legal Review (Months 2-4)",
156
+ "πŸ’° Budget Approval (Months 4-6)",
157
+ "πŸ”„ System Updates (Months 6-8)",
158
+ "πŸ“’ Communication (Months 8-10)",
159
+ "πŸš€ Implementation (Months 10-12)"
160
+ ]
161
+
162
+ timeline = "πŸ—“οΈ Implementation Timeline\n"
163
+ timeline += "=" * 60 + "\n"
164
+
165
+ for i, phase in enumerate(phases, 1):
166
+ progress_bar = "β–“" * (i * 2) + "β–‘" * ((6 - i) * 2)
167
+ timeline += f" {phase}\n"
168
+ timeline += f" [{progress_bar}] {i}/6 phases\n\n"
169
+
170
+ complexity = timeline_data.get('complexity', 'Medium')
171
+ timeline += f"πŸ”§ Implementation Complexity: {complexity}\n"
172
+ timeline += f"⏱️ Estimated Duration: 12 months\n"
173
+ timeline += f"πŸ’‘ Key Success Factors: Budget approval, stakeholder buy-in, system readiness\n"
174
+
175
+ return timeline
176
+
177
+ def _fig_to_base64(self, fig) -> str:
178
+ """Convert matplotlib figure to base64 string"""
179
+
180
+ buffer = io.BytesIO()
181
+ fig.savefig(buffer, format='png', dpi=300, bbox_inches='tight')
182
+ buffer.seek(0)
183
+
184
+ # Convert to base64
185
+ img_base64 = base64.b64encode(buffer.read()).decode('utf-8')
186
+
187
+ # Close figure to free memory
188
+ plt.close(fig)
189
+
190
+ return f"data:image/png;base64,{img_base64}"
191
+
192
+ def generate_comprehensive_report_charts(self, analysis_data: Dict[str, Any]) -> Dict[str, str]:
193
+ """Generate all charts for a comprehensive policy analysis report"""
194
+
195
+ charts = {}
196
+
197
+ # Scenario comparison chart
198
+ if 'variants' in analysis_data:
199
+ charts['scenario_comparison'] = self.generate_scenario_comparison_chart(
200
+ analysis_data['variants'],
201
+ f"Policy Impact: {analysis_data.get('parameter_name', 'Analysis')}"
202
+ )
203
+
204
+ # ASCII version for text display
205
+ charts['scenario_ascii'] = self.generate_ascii_chart(analysis_data['variants'])
206
+
207
+ # Yearly breakdown chart
208
+ if 'scenario_projections' in analysis_data:
209
+ charts['yearly_breakdown'] = self.generate_yearly_breakdown_chart(
210
+ analysis_data['scenario_projections'],
211
+ "Financial Impact Over Time"
212
+ )
213
+
214
+ # Implementation timeline
215
+ if 'implementation' in analysis_data:
216
+ charts['implementation_timeline'] = self.generate_implementation_timeline_chart(
217
+ analysis_data['implementation']
218
+ )
219
+
220
+ return charts
221
+
222
+ # Standalone functions for easy integration
223
+ def generate_policy_charts(analysis_data: Dict[str, Any]) -> Dict[str, str]:
224
+ """Generate charts for policy analysis data"""
225
+ generator = PolicyChartGenerator()
226
+ return generator.generate_comprehensive_report_charts(analysis_data)
227
+
228
+ def generate_ascii_scenario_chart(variants: Dict[str, Any]) -> str:
229
+ """Generate ASCII chart for immediate text display"""
230
+ generator = PolicyChartGenerator()
231
+ return generator.generate_ascii_chart(variants)
policy_chat_interface.py ADDED
@@ -0,0 +1,668 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Policy Impact Simulator Chat Integration
3
+ Provides natural language interface for policy impact simulation
4
+ """
5
+
6
+ import re
7
+ import json
8
+ import logging
9
+ from typing import Dict, Any, Optional, List
10
+ from datetime import datetime, timedelta
11
+ from dataclasses import dataclass
12
+
13
+ from policy_impact_simulator import (
14
+ PolicyImpactSimulator,
15
+ PolicyScenario,
16
+ PolicyParameter
17
+ )
18
+ from policy_chart_generator import PolicyChartGenerator
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ @dataclass
23
+ class PolicyQuery:
24
+ """Parsed policy query from natural language"""
25
+ intent: str
26
+ parameter: Optional[PolicyParameter]
27
+ current_value: Optional[float]
28
+ proposed_value: Optional[float]
29
+ years: int = 5
30
+ raw_query: str = ""
31
+
32
+ class PolicySimulatorChatInterface:
33
+ """Chat interface for policy impact simulation"""
34
+
35
+ def __init__(self):
36
+ self.simulator = PolicyImpactSimulator()
37
+ self.chart_generator = PolicyChartGenerator()
38
+ self.conversation_context = {}
39
+
40
+ # Enhanced pattern matching for natural language queries
41
+ self.patterns = {
42
+ "simulate_dr": [
43
+ r"simulate.*dearness.*relief.*(\d+).*to.*(\d+)",
44
+ r"dr.*increase.*from.*(\d+).*to.*(\d+)",
45
+ r"what.*if.*dr.*changes.*(\d+).*(\d+)",
46
+ r"impact.*dearness.*(\d+).*percent.*(\d+).*percent",
47
+ r"dr.*analysis.*(\d+).*(\d+)",
48
+ r"dearness.*relief.*(\d+).*(\d+)",
49
+ r"impact.*increasing.*dr.*by.*(\d+)",
50
+ r"show.*impact.*dr.*by.*(\d+)",
51
+ r"dr.*increase.*by.*(\d+).*percent",
52
+ r"increase.*dr.*by.*(\d+)"
53
+ ],
54
+ "simulate_pension": [
55
+ r"simulate.*basic.*pension.*(\d+).*to.*(\d+)",
56
+ r"pension.*increase.*from.*(\d+).*to.*(\d+)",
57
+ r"what.*if.*pension.*changes.*(\d+).*(\d+)",
58
+ r"impact.*basic.*pension.*(\d+).*(\d+)",
59
+ r"pension.*boost.*(\d+).*(\d+)",
60
+ r"basic.*pension.*(\d+).*(\d+)",
61
+ r"analyze.*minimum.*pension.*changes",
62
+ r"analyze.*basic.*pension",
63
+ r"minimum.*pension.*analysis",
64
+ r"basic.*pension.*impact"
65
+ ],
66
+ "simulate_medical": [
67
+ r"simulate.*medical.*allowance.*(\d+).*to.*(\d+)",
68
+ r"medical.*increase.*from.*(\d+).*to.*(\d+)",
69
+ r"what.*if.*medical.*changes.*(\d+).*(\d+)",
70
+ r"medical.*allowance.*(\d+).*(\d+)"
71
+ ],
72
+ "scenario_analysis": [
73
+ r"scenario.*analysis.*pension",
74
+ r"do.*scenario.*analysis",
75
+ r"pension.*scenario.*analysis",
76
+ r"analyze.*pension.*scenarios",
77
+ r"scenario.*analysis.*dr",
78
+ r"pension.*rules.*scenario",
79
+ r"government.*policy.*scenario",
80
+ r"policy.*scenario.*analysis"
81
+ ],
82
+ "interactive_form": [
83
+ r"start.*scenario.*analysis",
84
+ r"interactive.*scenario",
85
+ r"step.*by.*step.*analysis",
86
+ r"guided.*analysis",
87
+ r"scenario.*form"
88
+ ],
89
+ "general_policy": [
90
+ r"policy.*impact.*simulation",
91
+ r"simulate.*policy.*change",
92
+ r"what.*if.*we.*change",
93
+ r"impact.*analysis.*policy",
94
+ r"policy.*analysis",
95
+ r"government.*policy.*impact"
96
+ ],
97
+ "compare_scenarios": [
98
+ r"compare.*scenarios",
99
+ r"which.*is.*better.*policy",
100
+ r"cost.*comparison.*policies",
101
+ r"scenario.*comparison",
102
+ r"best.*case.*worst.*case"
103
+ ]
104
+ }
105
+
106
+ def process_policy_query(self, query: str, user_id: str = "default") -> Dict[str, Any]:
107
+ """
108
+ Process natural language policy query and return simulation results
109
+ """
110
+ try:
111
+ # Parse the query
112
+ parsed_query = self._parse_policy_query(query)
113
+
114
+ if not parsed_query:
115
+ return self._get_help_response()
116
+
117
+ # Handle different intents
118
+ if parsed_query.intent == "simulate":
119
+ return self._handle_simulation_request(parsed_query, user_id)
120
+ elif parsed_query.intent == "compare":
121
+ return self._handle_comparison_request(parsed_query, user_id)
122
+ elif parsed_query.intent == "help":
123
+ return self._get_help_response()
124
+ elif parsed_query.intent == "scenario_analysis":
125
+ return self._handle_scenario_analysis_request(parsed_query, user_id)
126
+ elif parsed_query.intent == "interactive_form":
127
+ return self._start_interactive_form(user_id)
128
+ else:
129
+ return self._get_clarification_response(query)
130
+
131
+ except Exception as e:
132
+ logger.error(f"Policy query processing error: {e}")
133
+ return {
134
+ "type": "error",
135
+ "message": f"Sorry, I encountered an error processing your policy query: {str(e)}",
136
+ "suggestions": ["Try rephrasing your question", "Use specific numbers and policy parameters"]
137
+ }
138
+
139
+ def _parse_policy_query(self, query: str) -> Optional[PolicyQuery]:
140
+ """Parse natural language query into structured policy query"""
141
+ query_lower = query.lower()
142
+
143
+ # Check for explicit help requests (more specific to avoid false positives)
144
+ help_patterns = [r"^help", r"how.*do.*i", r"what.*can.*you", r"guide.*me", r"need.*help"]
145
+ if any(re.search(pattern, query_lower) for pattern in help_patterns):
146
+ return PolicyQuery(intent="help", parameter=None, current_value=None, proposed_value=None, raw_query=query)
147
+
148
+ # Check for comparison requests
149
+ if any(word in query_lower for word in ["compare", "vs", "versus", "which is better"]):
150
+ return PolicyQuery(intent="compare", parameter=None, current_value=None, proposed_value=None, raw_query=query)
151
+
152
+ # Try to match simulation patterns
153
+ for intent, patterns in self.patterns.items():
154
+ for pattern in patterns:
155
+ match = re.search(pattern, query_lower)
156
+ if match:
157
+ return self._extract_simulation_params(intent, match, query)
158
+
159
+ # Check for general policy simulation intent
160
+ if any(word in query_lower for word in ["simulate", "impact", "policy", "change", "effect"]):
161
+ return PolicyQuery(intent="simulate", parameter=None, current_value=None, proposed_value=None, raw_query=query)
162
+
163
+ return None
164
+
165
+ def _extract_simulation_params(self, intent: str, match, raw_query: str) -> PolicyQuery:
166
+ """Extract simulation parameters from regex match"""
167
+ try:
168
+ # Handle new intent types that don't need parameter extraction
169
+ if intent in ["scenario_analysis", "interactive_form"]:
170
+ return PolicyQuery(
171
+ intent=intent,
172
+ parameter=None,
173
+ current_value=None,
174
+ proposed_value=None,
175
+ raw_query=raw_query
176
+ )
177
+
178
+ groups = match.groups()
179
+
180
+ # Map intent to parameter for simulation intents
181
+ parameter_mapping = {
182
+ "simulate_dr": PolicyParameter.DEARNESS_RELIEF,
183
+ "simulate_pension": PolicyParameter.BASIC_PENSION,
184
+ "simulate_medical": PolicyParameter.MEDICAL_ALLOWANCE
185
+ }
186
+
187
+ parameter = parameter_mapping.get(intent, PolicyParameter.DEARNESS_RELIEF)
188
+
189
+ # Handle different pattern types
190
+ if len(groups) == 0:
191
+ # No numbers - provide default analysis scenario
192
+ if parameter == PolicyParameter.DEARNESS_RELIEF:
193
+ current_value = 12.0 # Current DR is 12%
194
+ proposed_value = 18.0 # Standard 6% increase
195
+ elif parameter == PolicyParameter.BASIC_PENSION:
196
+ current_value = 6000.0 # Current basic pension is β‚Ή6,000
197
+ proposed_value = 8000.0 # Standard β‚Ή2,000 increase
198
+ elif parameter == PolicyParameter.MEDICAL_ALLOWANCE:
199
+ current_value = 1000.0 # Current medical allowance is β‚Ή1,000
200
+ proposed_value = 1500.0 # Standard β‚Ή500 increase
201
+ else:
202
+ current_value = 0.0
203
+ proposed_value = 1.0
204
+ elif len(groups) == 1:
205
+ # Single number - treat as percentage increase
206
+ increase_amount = float(groups[0])
207
+
208
+ # Set current values based on parameter type
209
+ if parameter == PolicyParameter.DEARNESS_RELIEF:
210
+ current_value = 12.0 # Current DR is 12%
211
+ proposed_value = current_value + increase_amount
212
+ elif parameter == PolicyParameter.BASIC_PENSION:
213
+ current_value = 6000.0 # Current basic pension is β‚Ή6,000
214
+ proposed_value = current_value + increase_amount
215
+ elif parameter == PolicyParameter.MEDICAL_ALLOWANCE:
216
+ current_value = 1000.0 # Current medical allowance is β‚Ή1,000
217
+ proposed_value = current_value + increase_amount
218
+ else:
219
+ current_value = increase_amount
220
+ proposed_value = increase_amount * 1.5 # Default 50% increase
221
+ else:
222
+ # Two numbers - from X to Y
223
+ current_value = float(groups[0]) if len(groups) > 0 else None
224
+ proposed_value = float(groups[1]) if len(groups) > 1 else None
225
+
226
+ # Extract years if mentioned
227
+ years_match = re.search(r"(\d+).*years?", raw_query.lower())
228
+ years = int(years_match.group(1)) if years_match else 5
229
+
230
+ return PolicyQuery(
231
+ intent="simulate",
232
+ parameter=parameter,
233
+ current_value=current_value,
234
+ proposed_value=proposed_value,
235
+ years=min(years, 10), # Cap at 10 years
236
+ raw_query=raw_query
237
+ )
238
+
239
+ except Exception as e:
240
+ logger.error(f"Parameter extraction error: {e}")
241
+ return PolicyQuery(intent="simulate", parameter=None, current_value=None, proposed_value=None, raw_query=raw_query)
242
+
243
+ def _handle_simulation_request(self, parsed_query: PolicyQuery, user_id: str) -> Dict[str, Any]:
244
+ """Handle policy simulation request"""
245
+ try:
246
+ # If missing parameters, provide clarification with specific guidance
247
+ if not all([parsed_query.parameter, parsed_query.current_value, parsed_query.proposed_value]):
248
+ return self._get_clarification_response(parsed_query.raw_query)
249
+
250
+ # Create scenario
251
+ scenario = PolicyScenario(
252
+ parameter=parsed_query.parameter,
253
+ current_value=parsed_query.current_value,
254
+ proposed_value=parsed_query.proposed_value,
255
+ effective_date=datetime.now() + timedelta(days=90), # 3 months from now
256
+ affected_population=self._estimate_affected_population(parsed_query.parameter),
257
+ annual_growth_rate=0.03,
258
+ inflation_rate=0.06
259
+ )
260
+
261
+ # Run simulation
262
+ result = self.simulator.simulate_policy_impact(scenario, parsed_query.years, True)
263
+
264
+ # Format for chat response
265
+ return self._format_simulation_response(result, parsed_query)
266
+
267
+ except Exception as e:
268
+ logger.error(f"Simulation request error: {e}")
269
+ return {
270
+ "type": "error",
271
+ "message": f"Simulation failed: {str(e)}",
272
+ "raw_query": parsed_query.raw_query
273
+ }
274
+
275
+ def _estimate_affected_population(self, parameter: PolicyParameter) -> int:
276
+ """Estimate affected population based on parameter type"""
277
+ population_estimates = {
278
+ PolicyParameter.DEARNESS_RELIEF: 510000, # All pensioners
279
+ PolicyParameter.BASIC_PENSION: 450000, # Basic pension recipients
280
+ PolicyParameter.MEDICAL_ALLOWANCE: 510000, # All pensioners
281
+ PolicyParameter.PENSION_FACTOR: 510000,
282
+ PolicyParameter.MINIMUM_PENSION: 200000 # Lower income pensioners
283
+ }
284
+ return population_estimates.get(parameter, 400000)
285
+
286
+ def _format_simulation_response(self, result: Dict[str, Any], query: PolicyQuery) -> Dict[str, Any]:
287
+ """Format simulation results for chat display"""
288
+ if "error" in result:
289
+ return {
290
+ "type": "error",
291
+ "message": f"Simulation error: {result['error']}"
292
+ }
293
+
294
+ total_impact = result.get("total_impact", {})
295
+ projections = result.get("scenario_projections", [])
296
+ clause_analysis = result.get("clause_analysis", {})
297
+
298
+ # Create summary message
299
+ summary = f"""
300
+ 🎯 **Policy Impact Simulation Results**
301
+
302
+ **Parameter**: {result.get('parameter_name', 'Unknown')}
303
+ **Change**: {result.get('current_value')} β†’ {result.get('proposed_value')}
304
+ **Effective Date**: {result.get('effective_date', '').split('T')[0]}
305
+
306
+ πŸ“Š **Financial Impact Over {result.get('projection_years')} Years**:
307
+ β€’ **Total Additional Cost**: β‚Ή{total_impact.get('total_additional_cost_crores', 0):.1f} crores
308
+ β€’ **Percentage Increase**: {total_impact.get('percentage_increase', 0):.1f}%
309
+ β€’ **Annual Average**: β‚Ή{total_impact.get('annual_average_impact_crores', 0):.1f} crores
310
+ β€’ **Cost per Beneficiary**: β‚Ή{total_impact.get('cost_per_beneficiary_annual', 0):,.0f} per year
311
+
312
+ πŸ“ˆ **Year-by-Year Breakdown**:
313
+ """
314
+
315
+ # Add yearly breakdown
316
+ for i, proj in enumerate(projections[:3]): # Show first 3 years
317
+ summary += f"Year {proj.year}: β‚Ή{proj.impact/10000000:.1f} crores impact ({proj.affected_beneficiaries:,} beneficiaries)\n"
318
+
319
+ if len(projections) > 3:
320
+ summary += f"... and {len(projections)-3} more years\n"
321
+
322
+ # Add clause information
323
+ if clause_analysis:
324
+ clause_diff = clause_analysis.get("clause_diff", {})
325
+ summary += f"""
326
+ βš–οΈ **Policy Changes**:
327
+ β€’ **Change Type**: {clause_diff.get('change_type', 'Unknown').title()}
328
+ β€’ **Magnitude**: {clause_diff.get('change_percentage', 0):.1f}% change
329
+ β€’ **Affected Clauses**: {len(clause_analysis.get('affected_clauses', []))} clauses modified
330
+ """
331
+
332
+ # Add scenario variants
333
+ variants = result.get("variants", {})
334
+ charts = []
335
+
336
+ if variants:
337
+ best_case = sum(p.impact for p in variants.get("best_case", [])) / 10000000
338
+ worst_case = sum(p.impact for p in variants.get("worst_case", [])) / 10000000
339
+ base_case = total_impact.get('total_additional_cost_crores', 0)
340
+
341
+ summary += f"""
342
+ πŸ“Š **Scenario Analysis**:
343
+ β€’ **Best Case**: β‚Ή{best_case:.1f} crores
344
+ β€’ **Base Case**: β‚Ή{base_case:.1f} crores
345
+ β€’ **Worst Case**: β‚Ή{worst_case:.1f} crores
346
+ """
347
+
348
+ # Generate charts
349
+ try:
350
+ # Scenario comparison chart
351
+ scenario_data = {
352
+ 'best_case': {'total_cost': best_case},
353
+ 'base_case': {'total_cost': base_case},
354
+ 'worst_case': {'total_cost': worst_case}
355
+ }
356
+
357
+ chart_title = f"{result.get('parameter_name', 'Policy')} Impact Analysis"
358
+ scenario_chart = self.chart_generator.generate_scenario_comparison_chart(
359
+ scenario_data, chart_title
360
+ )
361
+
362
+ if scenario_chart:
363
+ charts.append({
364
+ "type": "scenario_comparison",
365
+ "title": chart_title,
366
+ "data": scenario_chart
367
+ })
368
+
369
+ # Year-by-year breakdown chart
370
+ if projections:
371
+ # Convert projections to the format expected by chart generator
372
+ yearly_data = []
373
+ for proj in projections:
374
+ yearly_data.append({
375
+ 'year': proj.year,
376
+ 'impact': proj.impact / 10000000, # Convert to crores
377
+ 'beneficiaries': proj.affected_beneficiaries
378
+ })
379
+
380
+ trend_chart = self.chart_generator.generate_yearly_breakdown_chart(
381
+ yearly_data, f"{result.get('parameter_name', 'Policy')} 5-Year Impact"
382
+ )
383
+
384
+ if trend_chart:
385
+ charts.append({
386
+ "type": "yearly_trend",
387
+ "title": "5-Year Financial Impact Trend",
388
+ "data": trend_chart
389
+ })
390
+
391
+ except Exception as e:
392
+ logger.error(f"Chart generation error: {e}")
393
+
394
+ return {
395
+ "type": "policy_simulation",
396
+ "message": summary,
397
+ "simulation_id": result.get("scenario_id"),
398
+ "detailed_results": result,
399
+ "charts": charts,
400
+ "export_options": ["CSV", "PDF", "JSON"],
401
+ "follow_up_suggestions": [
402
+ "Compare with other policy scenarios",
403
+ "Analyze implementation timeline",
404
+ "Export detailed evidence pack",
405
+ "Simulate different effective dates"
406
+ ]
407
+ }
408
+
409
+ def _get_interactive_form(self, parsed_query: PolicyQuery) -> Dict[str, Any]:
410
+ """Provide interactive form for missing parameters"""
411
+ available_parameters = [
412
+ {"id": "dearness_relief", "name": "Dearness Relief (%)", "current": 12.0, "unit": "%"},
413
+ {"id": "basic_pension", "name": "Basic Pension (β‚Ή)", "current": 6000, "unit": "β‚Ή"},
414
+ {"id": "medical_allowance", "name": "Medical Allowance (β‚Ή)", "current": 1000, "unit": "β‚Ή"},
415
+ {"id": "pension_factor", "name": "Pension Factor", "current": 1.0, "unit": "multiplier"},
416
+ {"id": "minimum_pension", "name": "Minimum Pension (β‚Ή)", "current": 3500, "unit": "β‚Ή"}
417
+ ]
418
+
419
+ return {
420
+ "type": "interactive_form",
421
+ "message": "I'd be happy to help you simulate policy impact! Please provide the following details:",
422
+ "form_fields": [
423
+ {
424
+ "name": "parameter",
425
+ "label": "Policy Parameter",
426
+ "type": "select",
427
+ "options": available_parameters,
428
+ "required": True
429
+ },
430
+ {
431
+ "name": "current_value",
432
+ "label": "Current Value",
433
+ "type": "number",
434
+ "required": True
435
+ },
436
+ {
437
+ "name": "proposed_value",
438
+ "label": "Proposed Value",
439
+ "type": "number",
440
+ "required": True
441
+ },
442
+ {
443
+ "name": "years",
444
+ "label": "Projection Years",
445
+ "type": "number",
446
+ "default": 5,
447
+ "min": 1,
448
+ "max": 10
449
+ }
450
+ ],
451
+ "examples": [
452
+ "Simulate DR increase from 12% to 18% over 5 years",
453
+ "What if basic pension changes from β‚Ή6000 to β‚Ή8000?",
454
+ "Impact of medical allowance increase to β‚Ή2000"
455
+ ]
456
+ }
457
+
458
+ def _handle_comparison_request(self, parsed_query: PolicyQuery, user_id: str) -> Dict[str, Any]:
459
+ """Handle policy comparison request"""
460
+ sample_comparisons = [
461
+ {
462
+ "name": "DR vs Basic Pension Increase",
463
+ "scenarios": [
464
+ {"parameter": "DR", "change": "12% β†’ 18%", "impact": "β‚Ή85.2 crores"},
465
+ {"parameter": "Basic Pension", "change": "β‚Ή6000 β†’ β‚Ή8000", "impact": "β‚Ή108.0 crores"}
466
+ ],
467
+ "recommendation": "DR increase is more cost-effective"
468
+ },
469
+ {
470
+ "name": "Short vs Long Term Impact",
471
+ "scenarios": [
472
+ {"period": "3 years", "total_impact": "β‚Ή150.5 crores"},
473
+ {"period": "10 years", "total_impact": "β‚Ή628.3 crores"}
474
+ ],
475
+ "recommendation": "Long-term planning essential"
476
+ }
477
+ ]
478
+
479
+ return {
480
+ "type": "policy_comparison",
481
+ "message": "Here are some policy comparison examples. Would you like to compare specific scenarios?",
482
+ "comparisons": sample_comparisons,
483
+ "custom_comparison": {
484
+ "description": "I can help you compare up to 5 different policy scenarios",
485
+ "example": "Compare DR increase vs pension boost vs medical allowance increase"
486
+ }
487
+ }
488
+
489
+ def _get_help_response(self) -> Dict[str, Any]:
490
+ """Provide help information"""
491
+ return {
492
+ "type": "help",
493
+ "message": """
494
+ 🎯 **Policy Impact Simulator Help**
495
+
496
+ I can help you simulate the financial impact of government policy changes. Here's what I can do:
497
+
498
+ **πŸ“Š Simulation Capabilities**:
499
+ β€’ Dearness Relief (DR) changes
500
+ β€’ Basic pension adjustments
501
+ β€’ Medical allowance modifications
502
+ β€’ Pension factor changes
503
+ β€’ Minimum pension guarantees
504
+
505
+ **πŸ’¬ How to Ask**:
506
+ β€’ "Simulate DR increase from 12% to 18%"
507
+ β€’ "What if basic pension changes from β‚Ή6000 to β‚Ή8000?"
508
+ β€’ "Impact of medical allowance increase to β‚Ή2000 over 5 years"
509
+ β€’ "Compare DR increase vs pension boost"
510
+
511
+ **πŸ“ˆ What You Get**:
512
+ β€’ Total financial impact over 3-10 years
513
+ β€’ Year-by-year breakdown
514
+ β€’ Best/base/worst case scenarios
515
+ β€’ Affected population estimates
516
+ β€’ Policy clause analysis
517
+ β€’ Implementation timeline
518
+ β€’ Exportable evidence pack
519
+
520
+ **πŸš€ Quick Examples**:
521
+ Try asking: "Show me the impact of increasing DR by 6%"
522
+ """,
523
+ "quick_actions": [
524
+ {"label": "Sample DR Simulation", "query": "simulate DR from 12 to 18 percent"},
525
+ {"label": "Basic Pension Impact", "query": "what if basic pension increases to 8000"},
526
+ {"label": "Compare Scenarios", "query": "compare policy scenarios"},
527
+ {"label": "View Parameters", "query": "show available policy parameters"}
528
+ ]
529
+ }
530
+
531
+ def _get_clarification_response(self, query: str) -> Dict[str, Any]:
532
+ """Request clarification for unclear queries"""
533
+
534
+ # Provide specific guidance based on the query content
535
+ query_lower = query.lower()
536
+
537
+ if "da" in query_lower or "dearness allowance" in query_lower or "dr" in query_lower:
538
+ specific_message = """🎯 **DA/DR Impact Analysis**
539
+
540
+ I can help you analyze the Dearness Allowance (DA) impact! To provide accurate calculations, please specify:
541
+
542
+ πŸ“Š **Required Details:**
543
+ β€’ **Current DA Rate**: What's the existing DA percentage? (e.g., 12%)
544
+ β€’ **Proposed DA Rate**: What should the new DA be? (e.g., 18% for a 6% increase)
545
+ β€’ **Base Pension Amount**: What's the basic pension amount? (e.g., β‚Ή6,000)
546
+ β€’ **Analysis Period**: How many years to analyze? (default: 5 years)
547
+
548
+ πŸ’‘ **Quick Examples:**
549
+ β€’ "Show DA impact from 12% to 18% for pension β‚Ή6000"
550
+ β€’ "Analyze 6% DA increase from current 12% to 18%"
551
+ β€’ "DA simulation: current 12%, increase to 18%, basic pension β‚Ή6000"
552
+
553
+ πŸ“ˆ **What You'll Get:**
554
+ β€’ Financial impact over 5 years with charts
555
+ β€’ Cost per beneficiary calculations
556
+ β€’ Best/Base/Worst case scenarios
557
+ β€’ Implementation timeline and evidence pack"""
558
+
559
+ elif "pension" in query_lower and ("basic" in query_lower or "minimum" in query_lower):
560
+ specific_message = """🎯 **Basic Pension Impact Analysis**
561
+
562
+ I can help analyze basic pension changes! Please provide:
563
+
564
+ πŸ“Š **Required Details:**
565
+ β€’ **Current Basic Pension**: What's the existing amount? (e.g., β‚Ή6,000)
566
+ β€’ **Proposed Basic Pension**: What should the new amount be? (e.g., β‚Ή8,000)
567
+ β€’ **Analysis Period**: How many years to analyze? (default: 5 years)
568
+
569
+ πŸ’‘ **Quick Examples:**
570
+ β€’ "Show basic pension impact from β‚Ή6000 to β‚Ή8000"
571
+ β€’ "Analyze pension increase to β‚Ή8000 over 5 years"
572
+ β€’ "Basic pension simulation: current β‚Ή6000, increase to β‚Ή8000"
573
+
574
+ πŸ“ˆ **What You'll Get:**
575
+ β€’ Financial impact projections with charts
576
+ β€’ Affected population estimates
577
+ β€’ Cost analysis and implementation timeline"""
578
+
579
+ else:
580
+ specific_message = """🎯 **Policy Impact Simulation Help**
581
+
582
+ I understand you want to analyze policy impact! To provide accurate calculations, please specify:
583
+
584
+ πŸ“Š **Choose Your Analysis:**
585
+ β€’ **DA/DR Changes**: Dearness Allowance adjustments (e.g., "DA from 12% to 18%")
586
+ β€’ **Basic Pension**: Minimum pension amount changes (e.g., "Pension from β‚Ή6000 to β‚Ή8000")
587
+ β€’ **Medical Allowance**: Healthcare support changes (e.g., "Medical allowance to β‚Ή1500")
588
+
589
+ πŸ’‘ **Format Your Request:**
590
+ Include: Current value β†’ Proposed value β†’ Base amount (if applicable)
591
+
592
+ πŸ“ˆ **Quick Examples:**
593
+ β€’ "Show DA impact from 12% to 18% for pension β‚Ή6000"
594
+ β€’ "Analyze basic pension increase from β‚Ή6000 to β‚Ή8000"
595
+ β€’ "Medical allowance impact from β‚Ή1000 to β‚Ή1500" """
596
+
597
+ return {
598
+ "type": "clarification",
599
+ "message": specific_message,
600
+ "original_query": query,
601
+ "suggestions": [
602
+ "πŸ“Š Use the format: 'Show [policy] impact from [current] to [proposed]'",
603
+ "πŸ“ˆ Include specific numbers and amounts",
604
+ "⏱️ Specify time period if different from 5 years"
605
+ ],
606
+ "quick_actions": [
607
+ {"text": "πŸ“ˆ DA Analysis Example", "query": "Show DA impact from 12% to 18% for pension β‚Ή6000"},
608
+ {"text": "πŸ’° Pension Analysis Example", "query": "Show basic pension impact from β‚Ή6000 to β‚Ή8000"},
609
+ {"text": "πŸ“‹ Start Interactive Form", "query": "start scenario analysis"}
610
+ ]
611
+ }
612
+
613
+ def _handle_scenario_analysis_request(self, parsed_query: PolicyQuery, user_id: str) -> Dict[str, Any]:
614
+ """Handle general scenario analysis requests"""
615
+ return {
616
+ "type": "scenario_analysis_help",
617
+ "message": """🎯 **Scenario Analysis for Rajasthan Pension Policies**
618
+
619
+ I can help you analyze different scenarios for pension policy changes. Here are the most common analyses:
620
+
621
+ πŸ“Š **Available Scenario Analyses:**
622
+ β€’ **Dearness Relief (DR)**: Analyze inflation adjustments (current: 12%)
623
+ β€’ **Basic Pension**: Analyze minimum pension changes (current: β‚Ή6,000)
624
+ β€’ **Medical Allowance**: Analyze healthcare support changes (current: β‚Ή1,000)
625
+ β€’ **Pension Factor**: Analyze salary multiplier changes (current: 1.0x)
626
+
627
+ πŸ’¬ **How to Request Analysis:**
628
+ β€’ "Simulate DR increase from 12% to 18% over 5 years"
629
+ β€’ "What if basic pension increases to β‚Ή8,000?"
630
+ β€’ "Compare best and worst case scenarios for medical allowance"
631
+
632
+ πŸ“ˆ **What You'll Get:**
633
+ β€’ Financial impact projections (3-10 years)
634
+ β€’ Best/Base/Worst case scenarios
635
+ β€’ Affected population estimates
636
+ β€’ Implementation timeline and complexity
637
+ β€’ Exportable evidence packs
638
+
639
+ πŸš€ **Try These Examples:**""",
640
+ "quick_actions": [
641
+ {"text": "πŸ“ˆ Analyze DR Scenarios", "query": "Show DR scenario analysis from 12% to 18%"},
642
+ {"text": "πŸ’° Analyze Pension Scenarios", "query": "Show basic pension scenarios from 6000 to 8000"},
643
+ {"text": "πŸ₯ Analyze Medical Allowance", "query": "Show medical allowance scenarios"},
644
+ {"text": "πŸ“‹ Start Interactive Form", "query": "start scenario analysis"}
645
+ ]
646
+ }
647
+
648
+ def _start_interactive_form(self, user_id: str) -> Dict[str, Any]:
649
+ """Start interactive scenario analysis form"""
650
+ try:
651
+ from scenario_chat_form import start_scenario_analysis_form
652
+ return start_scenario_analysis_form(user_id)
653
+ except Exception as e:
654
+ logger.error(f"Interactive form start failed: {e}")
655
+ return {
656
+ "type": "error",
657
+ "message": "Sorry, I couldn't start the interactive form. Let me help you with a quick simulation instead.",
658
+ "fallback_form": self._get_clarification_response("")
659
+ }
660
+
661
+ # Usage integration function
662
+ def process_policy_chat_query(query: str, user_id: str = "default") -> Dict[str, Any]:
663
+ """
664
+ Main function to process policy-related chat queries
665
+ Use this in your main chat system
666
+ """
667
+ interface = PolicySimulatorChatInterface()
668
+ return interface.process_policy_query(query, user_id)
policy_impact_simulator.py ADDED
@@ -0,0 +1,475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Policy Impact Simulator Service
3
+ Analyzes policy changes and their financial/social impact over time periods
4
+ """
5
+
6
+ import json
7
+ import logging
8
+ from typing import Dict, List, Any, Optional, Tuple
9
+ from datetime import datetime, timedelta
10
+ import pandas as pd
11
+ import numpy as np
12
+ from dataclasses import dataclass
13
+ from enum import Enum
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ class PolicyParameter(Enum):
18
+ """Policy parameters that can be simulated"""
19
+ PENSION_FACTOR = "pension_factor"
20
+ DEARNESS_RELIEF = "dearness_relief"
21
+ BASIC_PENSION = "basic_pension"
22
+ MINIMUM_PENSION = "minimum_pension"
23
+ COMMUTATION_FACTOR = "commutation_factor"
24
+ MEDICAL_ALLOWANCE = "medical_allowance"
25
+ FAMILY_PENSION = "family_pension"
26
+ GRATUITY_LIMIT = "gratuity_limit"
27
+ HRA_PERCENTAGE = "hra_percentage"
28
+ AGE_LIMIT = "retirement_age"
29
+
30
+ class ScenarioType(Enum):
31
+ """Types of scenarios for impact analysis"""
32
+ BEST_CASE = "best"
33
+ BASE_CASE = "base"
34
+ WORST_CASE = "worst"
35
+
36
+ @dataclass
37
+ class PolicyScenario:
38
+ """Configuration for a policy scenario"""
39
+ parameter: PolicyParameter
40
+ current_value: float
41
+ proposed_value: float
42
+ effective_date: datetime
43
+ affected_population: int
44
+ annual_growth_rate: float = 0.03 # Default 3% annual growth
45
+ inflation_rate: float = 0.06 # Default 6% inflation
46
+
47
+ @dataclass
48
+ class ImpactProjection:
49
+ """Financial impact projection"""
50
+ year: int
51
+ baseline_cost: float
52
+ scenario_cost: float
53
+ impact: float
54
+ affected_beneficiaries: int
55
+ notes: str
56
+
57
+ class PolicyImpactSimulator:
58
+ """Simulates financial and social impact of policy changes"""
59
+
60
+ def __init__(self):
61
+ self.policy_definitions = self._load_policy_definitions()
62
+ self.historical_data = self._load_historical_data()
63
+
64
+ def _load_policy_definitions(self) -> Dict[PolicyParameter, Dict]:
65
+ """Load policy parameter definitions and constraints"""
66
+ return {
67
+ PolicyParameter.PENSION_FACTOR: {
68
+ "name": "Pension Factor",
69
+ "description": "Multiplier for calculating monthly pension",
70
+ "unit": "multiplier",
71
+ "typical_range": (0.5, 2.0),
72
+ "current_rajasthan": 1.0,
73
+ "impact_type": "direct_benefit"
74
+ },
75
+ PolicyParameter.DEARNESS_RELIEF: {
76
+ "name": "Dearness Relief (DR)",
77
+ "description": "Percentage increase to counter inflation",
78
+ "unit": "percentage",
79
+ "typical_range": (0, 50),
80
+ "current_rajasthan": 12.0,
81
+ "impact_type": "cost_adjustment"
82
+ },
83
+ PolicyParameter.BASIC_PENSION: {
84
+ "name": "Basic Pension Amount",
85
+ "description": "Minimum pension amount per month",
86
+ "unit": "rupees",
87
+ "typical_range": (3000, 15000),
88
+ "current_rajasthan": 6000,
89
+ "impact_type": "direct_benefit"
90
+ },
91
+ PolicyParameter.MINIMUM_PENSION: {
92
+ "name": "Minimum Pension Guarantee",
93
+ "description": "Guaranteed minimum pension amount",
94
+ "unit": "rupees",
95
+ "typical_range": (2000, 10000),
96
+ "current_rajasthan": 3500,
97
+ "impact_type": "safety_net"
98
+ },
99
+ PolicyParameter.MEDICAL_ALLOWANCE: {
100
+ "name": "Medical Allowance",
101
+ "description": "Monthly medical expense allowance",
102
+ "unit": "rupees",
103
+ "typical_range": (500, 5000),
104
+ "current_rajasthan": 1000,
105
+ "impact_type": "healthcare_benefit"
106
+ }
107
+ }
108
+
109
+ def _load_historical_data(self) -> Dict:
110
+ """Load historical policy implementation data"""
111
+ return {
112
+ "rajasthan_pensioners": {
113
+ "2020": 450000,
114
+ "2021": 465000,
115
+ "2022": 480000,
116
+ "2023": 495000,
117
+ "2024": 510000
118
+ },
119
+ "average_pension": {
120
+ "2020": 8500,
121
+ "2021": 9200,
122
+ "2022": 9800,
123
+ "2023": 10400,
124
+ "2024": 11000
125
+ },
126
+ "budget_allocation": {
127
+ "2020": 3850000000, # 385 crores
128
+ "2021": 4280000000, # 428 crores
129
+ "2022": 4700000000, # 470 crores
130
+ "2023": 5100000000, # 510 crores
131
+ "2024": 5600000000 # 560 crores
132
+ }
133
+ }
134
+
135
+ def simulate_policy_impact(
136
+ self,
137
+ scenario: PolicyScenario,
138
+ years: int = 5,
139
+ include_variants: bool = True
140
+ ) -> Dict[str, Any]:
141
+ """
142
+ Simulate the impact of a policy change over specified years
143
+
144
+ Args:
145
+ scenario: Policy scenario configuration
146
+ years: Number of years to project
147
+ include_variants: Include best/worst case scenarios
148
+
149
+ Returns:
150
+ Complete impact analysis with projections and metadata
151
+ """
152
+ try:
153
+ # Generate baseline projections
154
+ baseline_projections = self._generate_baseline_projections(scenario, years)
155
+
156
+ # Generate scenario projections
157
+ scenario_projections = self._generate_scenario_projections(scenario, years)
158
+
159
+ # Calculate variants if requested
160
+ variants = {}
161
+ if include_variants:
162
+ variants = self._generate_scenario_variants(scenario, years)
163
+
164
+ # Generate clause differences and timeline
165
+ clause_analysis = self._analyze_clause_changes(scenario)
166
+
167
+ # Create evidence pack
168
+ evidence_pack = self._create_evidence_pack(
169
+ scenario, baseline_projections, scenario_projections, variants
170
+ )
171
+
172
+ return {
173
+ "scenario_id": f"policy_sim_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
174
+ "parameter": scenario.parameter.value,
175
+ "parameter_name": self.policy_definitions[scenario.parameter]["name"],
176
+ "current_value": scenario.current_value,
177
+ "proposed_value": scenario.proposed_value,
178
+ "effective_date": scenario.effective_date.isoformat(),
179
+ "projection_years": years,
180
+ "baseline_projections": baseline_projections,
181
+ "scenario_projections": scenario_projections,
182
+ "variants": variants,
183
+ "total_impact": self._calculate_total_impact(baseline_projections, scenario_projections),
184
+ "clause_analysis": clause_analysis,
185
+ "evidence_pack": evidence_pack,
186
+ "generated_at": datetime.now().isoformat(),
187
+ "assumptions": self._document_assumptions(scenario)
188
+ }
189
+
190
+ except Exception as e:
191
+ logger.error(f"Policy simulation error: {e}")
192
+ return {"error": str(e)}
193
+
194
+ def _generate_baseline_projections(self, scenario: PolicyScenario, years: int) -> List[ImpactProjection]:
195
+ """Generate baseline (no change) projections"""
196
+ projections = []
197
+ base_population = scenario.affected_population
198
+ current_avg_benefit = self._estimate_current_benefit(scenario)
199
+
200
+ for year in range(1, years + 1):
201
+ # Account for population growth and inflation
202
+ year_population = int(base_population * (1 + scenario.annual_growth_rate) ** year)
203
+ year_benefit = current_avg_benefit * (1 + scenario.inflation_rate) ** year
204
+
205
+ annual_cost = year_population * year_benefit * 12 # Monthly to annual
206
+
207
+ projections.append(ImpactProjection(
208
+ year=year,
209
+ baseline_cost=annual_cost,
210
+ scenario_cost=annual_cost, # Same for baseline
211
+ impact=0,
212
+ affected_beneficiaries=year_population,
213
+ notes=f"Baseline year {year}: No policy change, inflation adjustment only"
214
+ ))
215
+
216
+ return projections
217
+
218
+ def _generate_scenario_projections(self, scenario: PolicyScenario, years: int) -> List[ImpactProjection]:
219
+ """Generate scenario (with change) projections"""
220
+ projections = []
221
+ base_population = scenario.affected_population
222
+ current_benefit = self._estimate_current_benefit(scenario)
223
+ new_benefit = self._calculate_new_benefit(scenario)
224
+
225
+ for year in range(1, years + 1):
226
+ year_population = int(base_population * (1 + scenario.annual_growth_rate) ** year)
227
+
228
+ # Baseline cost (what it would have been)
229
+ baseline_benefit = current_benefit * (1 + scenario.inflation_rate) ** year
230
+ baseline_cost = year_population * baseline_benefit * 12
231
+
232
+ # Scenario cost (with policy change)
233
+ scenario_benefit = new_benefit * (1 + scenario.inflation_rate) ** year
234
+ scenario_cost = year_population * scenario_benefit * 12
235
+
236
+ impact = scenario_cost - baseline_cost
237
+
238
+ projections.append(ImpactProjection(
239
+ year=year,
240
+ baseline_cost=baseline_cost,
241
+ scenario_cost=scenario_cost,
242
+ impact=impact,
243
+ affected_beneficiaries=year_population,
244
+ notes=f"Year {year}: Policy change impact β‚Ή{impact/10000000:.1f} crores"
245
+ ))
246
+
247
+ return projections
248
+
249
+ def _generate_scenario_variants(self, scenario: PolicyScenario, years: int) -> Dict[str, List[ImpactProjection]]:
250
+ """Generate best/worst case scenario variants"""
251
+ variants = {}
252
+
253
+ # Best case: Lower growth, higher efficiency
254
+ best_scenario = PolicyScenario(
255
+ parameter=scenario.parameter,
256
+ current_value=scenario.current_value,
257
+ proposed_value=scenario.proposed_value,
258
+ effective_date=scenario.effective_date,
259
+ affected_population=int(scenario.affected_population * 0.9), # 10% fewer beneficiaries
260
+ annual_growth_rate=scenario.annual_growth_rate * 0.8, # 20% lower growth
261
+ inflation_rate=scenario.inflation_rate * 0.9 # 10% lower inflation
262
+ )
263
+ variants["best_case"] = self._generate_scenario_projections(best_scenario, years)
264
+
265
+ # Worst case: Higher growth, implementation challenges
266
+ worst_scenario = PolicyScenario(
267
+ parameter=scenario.parameter,
268
+ current_value=scenario.current_value,
269
+ proposed_value=scenario.proposed_value * 1.1, # 10% higher due to implementation costs
270
+ effective_date=scenario.effective_date,
271
+ affected_population=int(scenario.affected_population * 1.2), # 20% more beneficiaries
272
+ annual_growth_rate=scenario.annual_growth_rate * 1.3, # 30% higher growth
273
+ inflation_rate=scenario.inflation_rate * 1.1 # 10% higher inflation
274
+ )
275
+ variants["worst_case"] = self._generate_scenario_projections(worst_scenario, years)
276
+
277
+ return variants
278
+
279
+ def _analyze_clause_changes(self, scenario: PolicyScenario) -> Dict[str, Any]:
280
+ """Analyze what policy clauses would change"""
281
+ parameter_def = self.policy_definitions[scenario.parameter]
282
+
283
+ return {
284
+ "affected_clauses": self._identify_affected_clauses(scenario.parameter),
285
+ "clause_diff": {
286
+ "before": f"{parameter_def['name']}: {scenario.current_value} {parameter_def['unit']}",
287
+ "after": f"{parameter_def['name']}: {scenario.proposed_value} {parameter_def['unit']}",
288
+ "change_type": "increase" if scenario.proposed_value > scenario.current_value else "decrease",
289
+ "change_magnitude": abs(scenario.proposed_value - scenario.current_value),
290
+ "change_percentage": ((scenario.proposed_value - scenario.current_value) / scenario.current_value) * 100
291
+ },
292
+ "effective_timeline": {
293
+ "announcement_date": (scenario.effective_date - timedelta(days=90)).isoformat(),
294
+ "legislative_process": (scenario.effective_date - timedelta(days=60)).isoformat(),
295
+ "notification_date": (scenario.effective_date - timedelta(days=30)).isoformat(),
296
+ "effective_date": scenario.effective_date.isoformat(),
297
+ "first_payment": (scenario.effective_date + timedelta(days=30)).isoformat()
298
+ },
299
+ "legal_references": self._get_legal_references(scenario.parameter)
300
+ }
301
+
302
+ def _identify_affected_clauses(self, parameter: PolicyParameter) -> List[str]:
303
+ """Identify which policy clauses would be affected"""
304
+ clause_mapping = {
305
+ PolicyParameter.PENSION_FACTOR: [
306
+ "Rajasthan Civil Services (Pension) Rules, 1996 - Rule 35",
307
+ "Pension calculation formula - Clause 4.2.1",
308
+ "Service weightage provisions - Clause 6.1"
309
+ ],
310
+ PolicyParameter.DEARNESS_RELIEF: [
311
+ "Dearness Relief calculation - Rule 42",
312
+ "Inflation adjustment mechanism - Clause 3.4",
313
+ "Automatic revision provisions - Rule 43"
314
+ ],
315
+ PolicyParameter.BASIC_PENSION: [
316
+ "Minimum pension guarantee - Rule 28",
317
+ "Basic pension structure - Clause 2.1",
318
+ "Pension floor provisions - Rule 29"
319
+ ],
320
+ PolicyParameter.MEDICAL_ALLOWANCE: [
321
+ "Medical benefits - Rule 52",
322
+ "Healthcare allowance - Clause 8.3",
323
+ "Medical reimbursement - Rule 53"
324
+ ]
325
+ }
326
+ return clause_mapping.get(parameter, ["General pension provisions"])
327
+
328
+ def _get_legal_references(self, parameter: PolicyParameter) -> List[str]:
329
+ """Get relevant legal references for the parameter"""
330
+ references = {
331
+ PolicyParameter.PENSION_FACTOR: [
332
+ "Rajasthan Civil Services (Pension) Rules, 1996",
333
+ "Rajasthan Government Resolution No. F.2(5)FD/Rules/96",
334
+ "Central Civil Services (Pension) Rules, 2021 - Reference"
335
+ ],
336
+ PolicyParameter.DEARNESS_RELIEF: [
337
+ "Rajasthan Civil Services (Revised Pay) Rules, 2017",
338
+ "Dearness Allowance calculation guidelines",
339
+ "RCS(RP) Rules notification dated 15.08.2017"
340
+ ]
341
+ }
342
+ return references.get(parameter, ["Rajasthan Civil Services (Pension) Rules, 1996"])
343
+
344
+ def _estimate_current_benefit(self, scenario: PolicyScenario) -> float:
345
+ """Estimate current monthly benefit amount"""
346
+ parameter_def = self.policy_definitions[scenario.parameter]
347
+
348
+ if scenario.parameter == PolicyParameter.BASIC_PENSION:
349
+ return scenario.current_value
350
+ elif scenario.parameter == PolicyParameter.PENSION_FACTOR:
351
+ # Average salary * factor
352
+ return 25000 * scenario.current_value # Assumed average salary
353
+ elif scenario.parameter == PolicyParameter.DEARNESS_RELIEF:
354
+ # DR percentage of basic pension
355
+ base_pension = 8000 # Assumed base
356
+ return base_pension * (scenario.current_value / 100)
357
+ else:
358
+ return parameter_def.get("current_rajasthan", 5000)
359
+
360
+ def _calculate_new_benefit(self, scenario: PolicyScenario) -> float:
361
+ """Calculate new monthly benefit amount after policy change"""
362
+ if scenario.parameter == PolicyParameter.BASIC_PENSION:
363
+ return scenario.proposed_value
364
+ elif scenario.parameter == PolicyParameter.PENSION_FACTOR:
365
+ return 25000 * scenario.proposed_value
366
+ elif scenario.parameter == PolicyParameter.DEARNESS_RELIEF:
367
+ base_pension = 8000
368
+ return base_pension * (scenario.proposed_value / 100)
369
+ else:
370
+ return scenario.proposed_value
371
+
372
+ def _calculate_total_impact(self, baseline: List[ImpactProjection], scenario: List[ImpactProjection]) -> Dict[str, float]:
373
+ """Calculate total financial impact"""
374
+ total_additional_cost = sum(proj.impact for proj in scenario)
375
+ total_baseline_cost = sum(proj.baseline_cost for proj in baseline)
376
+
377
+ return {
378
+ "total_additional_cost_crores": total_additional_cost / 10000000, # Convert to crores
379
+ "total_baseline_cost_crores": total_baseline_cost / 10000000,
380
+ "percentage_increase": (total_additional_cost / total_baseline_cost) * 100 if total_baseline_cost > 0 else 0,
381
+ "annual_average_impact_crores": (total_additional_cost / len(scenario)) / 10000000,
382
+ "cost_per_beneficiary_annual": total_additional_cost / (scenario[0].affected_beneficiaries * len(scenario))
383
+ }
384
+
385
+ def _create_evidence_pack(self, scenario, baseline, projections, variants) -> Dict[str, Any]:
386
+ """Create exportable evidence pack"""
387
+ return {
388
+ "summary_stats": {
389
+ "policy_parameter": self.policy_definitions[scenario.parameter]["name"],
390
+ "change_magnitude": f"{scenario.current_value} β†’ {scenario.proposed_value}",
391
+ "affected_population": scenario.affected_population,
392
+ "projection_period": f"{len(projections)} years",
393
+ "total_impact": f"β‚Ή{sum(p.impact for p in projections)/10000000:.1f} crores"
394
+ },
395
+ "yearly_breakdown": [
396
+ {
397
+ "year": p.year,
398
+ "baseline_crores": p.baseline_cost / 10000000,
399
+ "scenario_crores": p.scenario_cost / 10000000,
400
+ "impact_crores": p.impact / 10000000,
401
+ "beneficiaries": p.affected_beneficiaries
402
+ }
403
+ for p in projections
404
+ ],
405
+ "scenario_comparison": {
406
+ "best_case_total": sum(p.impact for p in variants.get("best_case", [])) / 10000000 if variants else 0,
407
+ "base_case_total": sum(p.impact for p in projections) / 10000000,
408
+ "worst_case_total": sum(p.impact for p in variants.get("worst_case", [])) / 10000000 if variants else 0
409
+ },
410
+ "export_formats": {
411
+ "csv_data": "Ready for CSV export",
412
+ "chart_data": "Ready for visualization",
413
+ "pdf_report": "Formatted for official reporting"
414
+ }
415
+ }
416
+
417
+ def _document_assumptions(self, scenario: PolicyScenario) -> Dict[str, Any]:
418
+ """Document all assumptions used in the simulation"""
419
+ return {
420
+ "demographic_assumptions": {
421
+ "affected_population": scenario.affected_population,
422
+ "annual_growth_rate": f"{scenario.annual_growth_rate*100:.1f}%",
423
+ "population_source": "Rajasthan pension department estimates"
424
+ },
425
+ "economic_assumptions": {
426
+ "inflation_rate": f"{scenario.inflation_rate*100:.1f}%",
427
+ "salary_growth": "Aligned with inflation",
428
+ "implementation_efficiency": "100% (no leakage assumed)"
429
+ },
430
+ "policy_assumptions": {
431
+ "effective_date": scenario.effective_date.strftime("%Y-%m-%d"),
432
+ "implementation_delay": "None assumed",
433
+ "administrative_costs": "Not included in projections",
434
+ "compliance_rate": "100% (full implementation assumed)"
435
+ },
436
+ "data_sources": [
437
+ "Rajasthan Finance Department budget documents",
438
+ "Pension disbursement historical data",
439
+ "National Sample Survey Office reports",
440
+ "Reserve Bank of India inflation projections"
441
+ ]
442
+ }
443
+
444
+ # Usage example and helper functions
445
+ def create_sample_scenarios() -> List[PolicyScenario]:
446
+ """Create sample policy scenarios for testing"""
447
+ return [
448
+ PolicyScenario(
449
+ parameter=PolicyParameter.DEARNESS_RELIEF,
450
+ current_value=12.0,
451
+ proposed_value=18.0,
452
+ effective_date=datetime(2025, 4, 1),
453
+ affected_population=510000,
454
+ annual_growth_rate=0.03,
455
+ inflation_rate=0.06
456
+ ),
457
+ PolicyScenario(
458
+ parameter=PolicyParameter.BASIC_PENSION,
459
+ current_value=6000,
460
+ proposed_value=8000,
461
+ effective_date=datetime(2025, 7, 1),
462
+ affected_population=450000,
463
+ annual_growth_rate=0.025,
464
+ inflation_rate=0.055
465
+ ),
466
+ PolicyScenario(
467
+ parameter=PolicyParameter.MEDICAL_ALLOWANCE,
468
+ current_value=1000,
469
+ proposed_value=2000,
470
+ effective_date=datetime(2025, 10, 1),
471
+ affected_population=510000,
472
+ annual_growth_rate=0.035,
473
+ inflation_rate=0.065
474
+ )
475
+ ]
policy_simulator_api.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Policy Impact Simulator API endpoints
3
+ Provides REST API access to policy simulation functionality
4
+ """
5
+
6
+ from fastapi import APIRouter, HTTPException, Query
7
+ from pydantic import BaseModel, Field
8
+ from typing import List, Optional, Dict, Any
9
+ from datetime import datetime, date
10
+ from enum import Enum
11
+
12
+ from policy_impact_simulator import (
13
+ PolicyImpactSimulator,
14
+ PolicyScenario,
15
+ PolicyParameter,
16
+ create_sample_scenarios
17
+ )
18
+
19
+ router = APIRouter(prefix="/api/policy-simulator", tags=["policy-simulator"])
20
+
21
+ # Pydantic models for API
22
+ class PolicyParameterEnum(str, Enum):
23
+ pension_factor = "pension_factor"
24
+ dearness_relief = "dearness_relief"
25
+ basic_pension = "basic_pension"
26
+ minimum_pension = "minimum_pension"
27
+ commutation_factor = "commutation_factor"
28
+ medical_allowance = "medical_allowance"
29
+ family_pension = "family_pension"
30
+ gratuity_limit = "gratuity_limit"
31
+ hra_percentage = "hra_percentage"
32
+ retirement_age = "retirement_age"
33
+
34
+ class PolicySimulationRequest(BaseModel):
35
+ parameter: PolicyParameterEnum = Field(..., description="Policy parameter to simulate")
36
+ current_value: float = Field(..., description="Current value of the parameter")
37
+ proposed_value: float = Field(..., description="Proposed new value")
38
+ effective_date: date = Field(..., description="When the policy change takes effect")
39
+ affected_population: int = Field(..., description="Number of people affected")
40
+ annual_growth_rate: float = Field(0.03, description="Annual population growth rate")
41
+ inflation_rate: float = Field(0.06, description="Expected inflation rate")
42
+ projection_years: int = Field(5, description="Number of years to project", ge=1, le=10)
43
+ include_variants: bool = Field(True, description="Include best/worst case scenarios")
44
+
45
+ class QuickSimulationRequest(BaseModel):
46
+ scenario_name: str = Field(..., description="Name of predefined scenario")
47
+ years: int = Field(5, description="Projection years", ge=1, le=10)
48
+
49
+ # Initialize simulator
50
+ simulator = PolicyImpactSimulator()
51
+
52
+ @router.post("/simulate")
53
+ async def simulate_policy_impact(request: PolicySimulationRequest):
54
+ """
55
+ Simulate the impact of a policy change
56
+
57
+ Provides comprehensive analysis including:
58
+ - Baseline vs scenario projections
59
+ - Best/base/worst case scenarios
60
+ - Clause analysis and timeline
61
+ - Exportable evidence pack
62
+ """
63
+ try:
64
+ # Convert API request to internal scenario
65
+ scenario = PolicyScenario(
66
+ parameter=PolicyParameter(request.parameter.value),
67
+ current_value=request.current_value,
68
+ proposed_value=request.proposed_value,
69
+ effective_date=datetime.combine(request.effective_date, datetime.min.time()),
70
+ affected_population=request.affected_population,
71
+ annual_growth_rate=request.annual_growth_rate,
72
+ inflation_rate=request.inflation_rate
73
+ )
74
+
75
+ # Run simulation
76
+ result = simulator.simulate_policy_impact(
77
+ scenario=scenario,
78
+ years=request.projection_years,
79
+ include_variants=request.include_variants
80
+ )
81
+
82
+ if "error" in result:
83
+ raise HTTPException(status_code=500, detail=result["error"])
84
+
85
+ return {
86
+ "success": True,
87
+ "simulation_result": result,
88
+ "api_version": "1.0",
89
+ "generated_at": datetime.now().isoformat()
90
+ }
91
+
92
+ except Exception as e:
93
+ raise HTTPException(status_code=500, detail=f"Simulation failed: {str(e)}")
94
+
95
+ @router.post("/quick-simulate")
96
+ async def quick_simulate(request: QuickSimulationRequest):
97
+ """
98
+ Run a quick simulation using predefined scenarios
99
+
100
+ Available scenarios:
101
+ - dr_increase_6_percent: Increase DR from 12% to 18%
102
+ - basic_pension_boost: Increase basic pension from β‚Ή6000 to β‚Ή8000
103
+ - medical_allowance_double: Double medical allowance to β‚Ή2000
104
+ """
105
+ try:
106
+ predefined_scenarios = {
107
+ "dr_increase_6_percent": PolicyScenario(
108
+ parameter=PolicyParameter.DEARNESS_RELIEF,
109
+ current_value=12.0,
110
+ proposed_value=18.0,
111
+ effective_date=datetime(2025, 4, 1),
112
+ affected_population=510000
113
+ ),
114
+ "basic_pension_boost": PolicyScenario(
115
+ parameter=PolicyParameter.BASIC_PENSION,
116
+ current_value=6000,
117
+ proposed_value=8000,
118
+ effective_date=datetime(2025, 7, 1),
119
+ affected_population=450000
120
+ ),
121
+ "medical_allowance_double": PolicyScenario(
122
+ parameter=PolicyParameter.MEDICAL_ALLOWANCE,
123
+ current_value=1000,
124
+ proposed_value=2000,
125
+ effective_date=datetime(2025, 10, 1),
126
+ affected_population=510000
127
+ )
128
+ }
129
+
130
+ if request.scenario_name not in predefined_scenarios:
131
+ raise HTTPException(
132
+ status_code=400,
133
+ detail=f"Unknown scenario. Available: {list(predefined_scenarios.keys())}"
134
+ )
135
+
136
+ scenario = predefined_scenarios[request.scenario_name]
137
+ result = simulator.simulate_policy_impact(scenario, request.years, True)
138
+
139
+ return {
140
+ "success": True,
141
+ "scenario_name": request.scenario_name,
142
+ "simulation_result": result
143
+ }
144
+
145
+ except Exception as e:
146
+ raise HTTPException(status_code=500, detail=str(e))
147
+
148
+ @router.get("/parameters")
149
+ async def get_policy_parameters():
150
+ """Get available policy parameters with their definitions"""
151
+ try:
152
+ parameters = {}
153
+ for param in PolicyParameter:
154
+ param_def = simulator.policy_definitions.get(param, {})
155
+ parameters[param.value] = {
156
+ "name": param_def.get("name", param.value),
157
+ "description": param_def.get("description", ""),
158
+ "unit": param_def.get("unit", ""),
159
+ "typical_range": param_def.get("typical_range", (0, 100)),
160
+ "current_rajasthan": param_def.get("current_rajasthan", 0),
161
+ "impact_type": param_def.get("impact_type", "benefit")
162
+ }
163
+
164
+ return {
165
+ "success": True,
166
+ "parameters": parameters,
167
+ "total_count": len(parameters)
168
+ }
169
+
170
+ except Exception as e:
171
+ raise HTTPException(status_code=500, detail=str(e))
172
+
173
+ @router.get("/scenarios/samples")
174
+ async def get_sample_scenarios():
175
+ """Get sample scenarios for testing and demonstration"""
176
+ try:
177
+ samples = create_sample_scenarios()
178
+
179
+ scenarios_data = []
180
+ for i, scenario in enumerate(samples):
181
+ scenarios_data.append({
182
+ "id": f"sample_{i+1}",
183
+ "parameter": scenario.parameter.value,
184
+ "parameter_name": simulator.policy_definitions[scenario.parameter]["name"],
185
+ "current_value": scenario.current_value,
186
+ "proposed_value": scenario.proposed_value,
187
+ "effective_date": scenario.effective_date.date().isoformat(),
188
+ "affected_population": scenario.affected_population,
189
+ "description": f"Sample scenario: {simulator.policy_definitions[scenario.parameter]['name']} change"
190
+ })
191
+
192
+ return {
193
+ "success": True,
194
+ "sample_scenarios": scenarios_data,
195
+ "usage": "Use these scenarios with /quick-simulate endpoint"
196
+ }
197
+
198
+ except Exception as e:
199
+ raise HTTPException(status_code=500, detail=str(e))
200
+
201
+ @router.get("/analysis/{simulation_id}")
202
+ async def get_detailed_analysis(simulation_id: str):
203
+ """Get detailed analysis for a completed simulation"""
204
+ # This would typically fetch from a database
205
+ # For now, return a placeholder response
206
+ return {
207
+ "success": True,
208
+ "message": "Detailed analysis endpoint - would fetch stored simulation results",
209
+ "simulation_id": simulation_id,
210
+ "note": "In production, this would retrieve complete analysis from database"
211
+ }
212
+
213
+ @router.post("/compare")
214
+ async def compare_scenarios(scenarios: List[PolicySimulationRequest]):
215
+ """Compare multiple policy scenarios side by side"""
216
+ try:
217
+ if len(scenarios) > 5:
218
+ raise HTTPException(status_code=400, detail="Maximum 5 scenarios can be compared")
219
+
220
+ comparison_results = []
221
+
222
+ for i, request in enumerate(scenarios):
223
+ scenario = PolicyScenario(
224
+ parameter=PolicyParameter(request.parameter.value),
225
+ current_value=request.current_value,
226
+ proposed_value=request.proposed_value,
227
+ effective_date=datetime.combine(request.effective_date, datetime.min.time()),
228
+ affected_population=request.affected_population,
229
+ annual_growth_rate=request.annual_growth_rate,
230
+ inflation_rate=request.inflation_rate
231
+ )
232
+
233
+ result = simulator.simulate_policy_impact(scenario, request.projection_years, False)
234
+ comparison_results.append({
235
+ "scenario_index": i + 1,
236
+ "parameter": request.parameter.value,
237
+ "impact_summary": result.get("total_impact", {}),
238
+ "first_year_impact": result["scenario_projections"][0].impact if result.get("scenario_projections") else 0
239
+ })
240
+
241
+ # Create comparison summary
242
+ total_impacts = [r.get("impact_summary", {}).get("total_additional_cost_crores", 0) for r in comparison_results]
243
+
244
+ return {
245
+ "success": True,
246
+ "comparison_count": len(scenarios),
247
+ "scenarios": comparison_results,
248
+ "summary": {
249
+ "highest_impact": max(total_impacts) if total_impacts else 0,
250
+ "lowest_impact": min(total_impacts) if total_impacts else 0,
251
+ "total_combined_impact": sum(total_impacts),
252
+ "recommendation": "Detailed comparison analysis completed"
253
+ }
254
+ }
255
+
256
+ except Exception as e:
257
+ raise HTTPException(status_code=500, detail=str(e))
258
+
259
+ @router.get("/export/{simulation_id}")
260
+ async def export_simulation_data(
261
+ simulation_id: str,
262
+ format: str = Query("json", regex="^(json|csv|pdf)$")
263
+ ):
264
+ """Export simulation results in various formats"""
265
+ try:
266
+ # This would typically fetch the simulation from database
267
+ # For now, return export information
268
+
269
+ export_info = {
270
+ "json": "Complete simulation data in JSON format",
271
+ "csv": "Yearly projections and impact data in CSV format",
272
+ "pdf": "Formatted policy impact report in PDF format"
273
+ }
274
+
275
+ return {
276
+ "success": True,
277
+ "simulation_id": simulation_id,
278
+ "export_format": format,
279
+ "description": export_info.get(format, "Unknown format"),
280
+ "download_url": f"/api/policy-simulator/download/{simulation_id}/{format}",
281
+ "note": "In production, would generate actual downloadable files"
282
+ }
283
+
284
+ except Exception as e:
285
+ raise HTTPException(status_code=500, detail=str(e))
scenario_analysis_service.py ADDED
@@ -0,0 +1,602 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import matplotlib.pyplot as plt
2
+ import seaborn as sns
3
+ import plotly.graph_objects as go
4
+ import plotly.express as px
5
+ import pandas as pd
6
+ import numpy as np
7
+ import networkx as nx
8
+ from datetime import datetime, timedelta
9
+ import base64
10
+ import io
11
+ import json
12
+ import logging
13
+ from typing import Dict, List, Optional, Tuple, Any
14
+ import asyncio
15
+
16
+ logger = logging.getLogger("voicebot")
17
+
18
+ class ScenarioAnalysisService:
19
+ def __init__(self):
20
+ """Initialize the scenario analysis service"""
21
+ # Set matplotlib to use non-interactive backend
22
+ plt.switch_backend('Agg')
23
+ # Set style for better looking plots
24
+ sns.set_style("whitegrid")
25
+ plt.style.use('seaborn-v0_8')
26
+
27
+ async def analyze_government_scenario(self, scenario_data: Dict[str, Any]) -> Dict[str, Any]:
28
+ """
29
+ Analyze government scenarios and create appropriate visualizations
30
+ """
31
+ try:
32
+ scenario_type = scenario_data.get("type", "").lower()
33
+ data = scenario_data.get("data", {})
34
+ title = scenario_data.get("title", "Government Scenario Analysis")
35
+
36
+ logger.info(f"πŸ” Analyzing scenario: {scenario_type}")
37
+
38
+ # Route to appropriate analysis method based on scenario type
39
+ if scenario_type in ["budget", "financial", "expenditure"]:
40
+ return await self._analyze_budget_scenario(data, title)
41
+ elif scenario_type in ["policy", "implementation", "timeline"]:
42
+ return await self._analyze_policy_scenario(data, title)
43
+ elif scenario_type in ["organization", "hierarchy", "structure"]:
44
+ return await self._analyze_organizational_scenario(data, title)
45
+ elif scenario_type in ["performance", "metrics", "kpi"]:
46
+ return await self._analyze_performance_scenario(data, title)
47
+ elif scenario_type in ["workflow", "process", "flow"]:
48
+ return await self._analyze_workflow_scenario(data, title)
49
+ else:
50
+ return await self._analyze_general_scenario(data, title, scenario_type)
51
+
52
+ except Exception as e:
53
+ logger.error(f"❌ Error in scenario analysis: {str(e)}")
54
+ return {
55
+ "success": False,
56
+ "error": str(e),
57
+ "analysis": "Failed to analyze scenario"
58
+ }
59
+
60
+ async def _analyze_budget_scenario(self, data: Dict, title: str) -> Dict[str, Any]:
61
+ """Analyze budget and financial scenarios"""
62
+ try:
63
+ # Create sample data if not provided
64
+ if not data:
65
+ departments = ['Health', 'Education', 'Infrastructure', 'Defense', 'Social Welfare']
66
+ budgets = [2500, 3000, 4000, 5000, 1500]
67
+ data = {"departments": departments, "budgets": budgets}
68
+
69
+ # Create multiple visualizations
70
+ images = []
71
+ analysis_text = []
72
+
73
+ # 1. Pie Chart for Budget Distribution
74
+ fig, ax = plt.subplots(figsize=(10, 8))
75
+ colors = plt.cm.Set3(np.linspace(0, 1, len(data["departments"])))
76
+ wedges, texts, autotexts = ax.pie(
77
+ data["budgets"],
78
+ labels=data["departments"],
79
+ autopct='%1.1f%%',
80
+ colors=colors,
81
+ startangle=90
82
+ )
83
+ ax.set_title(f"{title} - Budget Distribution", fontsize=16, fontweight='bold')
84
+
85
+ # Convert to base64
86
+ buffer = io.BytesIO()
87
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=300)
88
+ buffer.seek(0)
89
+ pie_image = base64.b64encode(buffer.getvalue()).decode()
90
+ images.append({"type": "pie_chart", "data": pie_image})
91
+ plt.close()
92
+
93
+ # 2. Bar Chart for Budget Comparison
94
+ fig, ax = plt.subplots(figsize=(12, 8))
95
+ bars = ax.bar(data["departments"], data["budgets"], color=colors)
96
+ ax.set_title(f"{title} - Department-wise Budget Allocation", fontsize=16, fontweight='bold')
97
+ ax.set_xlabel("Departments", fontsize=12)
98
+ ax.set_ylabel("Budget (in Crores)", fontsize=12)
99
+
100
+ # Add value labels on bars
101
+ for bar in bars:
102
+ height = bar.get_height()
103
+ ax.annotate(f'β‚Ή{height}Cr',
104
+ xy=(bar.get_x() + bar.get_width() / 2, height),
105
+ xytext=(0, 3),
106
+ textcoords="offset points",
107
+ ha='center', va='bottom')
108
+
109
+ plt.xticks(rotation=45)
110
+ buffer = io.BytesIO()
111
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=300)
112
+ buffer.seek(0)
113
+ bar_image = base64.b64encode(buffer.getvalue()).decode()
114
+ images.append({"type": "bar_chart", "data": bar_image})
115
+ plt.close()
116
+
117
+ # Generate analysis
118
+ total_budget = sum(data["budgets"])
119
+ max_dept = data["departments"][data["budgets"].index(max(data["budgets"]))]
120
+ min_dept = data["departments"][data["budgets"].index(min(data["budgets"]))]
121
+
122
+ analysis_text = [
123
+ f"πŸ“Š **Budget Analysis Summary:**",
124
+ f"β€’ Total Budget: β‚Ή{total_budget} Crores",
125
+ f"β€’ Highest Allocation: {max_dept} (β‚Ή{max(data['budgets'])} Cr)",
126
+ f"β€’ Lowest Allocation: {min_dept} (β‚Ή{min(data['budgets'])} Cr)",
127
+ f"β€’ Average Allocation: β‚Ή{total_budget/len(data['budgets']):.1f} Crores",
128
+ "",
129
+ f"πŸ’‘ **Key Insights:**",
130
+ f"β€’ {max_dept} receives {max(data['budgets'])/total_budget*100:.1f}% of total budget",
131
+ f"β€’ Budget distribution shows focus on {max_dept} and infrastructure development",
132
+ f"β€’ Consider rebalancing if {min_dept} requires more funding"
133
+ ]
134
+
135
+ return {
136
+ "success": True,
137
+ "analysis": "\n".join(analysis_text),
138
+ "images": images,
139
+ "scenario_type": "budget",
140
+ "total_budget": total_budget
141
+ }
142
+
143
+ except Exception as e:
144
+ logger.error(f"❌ Error in budget analysis: {str(e)}")
145
+ raise e
146
+
147
+ async def _analyze_policy_scenario(self, data: Dict, title: str) -> Dict[str, Any]:
148
+ """Analyze policy implementation scenarios"""
149
+ try:
150
+ # Create timeline visualization
151
+ if not data:
152
+ phases = ['Planning', 'Approval', 'Implementation', 'Monitoring', 'Evaluation']
153
+ durations = [30, 15, 90, 60, 30] # days
154
+ data = {"phases": phases, "durations": durations}
155
+
156
+ # Create Gantt chart-like visualization
157
+ fig, ax = plt.subplots(figsize=(14, 8))
158
+
159
+ # Calculate start dates
160
+ start_date = datetime.now()
161
+ start_dates = []
162
+ current_date = start_date
163
+
164
+ for duration in data["durations"]:
165
+ start_dates.append(current_date)
166
+ current_date += timedelta(days=duration)
167
+
168
+ # Create horizontal bar chart
169
+ colors = plt.cm.viridis(np.linspace(0, 1, len(data["phases"])))
170
+ for i, (phase, duration, start, color) in enumerate(zip(data["phases"], data["durations"], start_dates, colors)):
171
+ ax.barh(i, duration, left=(start - start_date).days, color=color, alpha=0.7)
172
+ ax.text((start - start_date).days + duration/2, i, f'{phase}\n({duration} days)',
173
+ ha='center', va='center', fontweight='bold')
174
+
175
+ ax.set_yticks(range(len(data["phases"])))
176
+ ax.set_yticklabels(data["phases"])
177
+ ax.set_xlabel("Timeline (Days from Start)")
178
+ ax.set_title(f"{title} - Policy Implementation Timeline", fontsize=16, fontweight='bold')
179
+ ax.grid(axis='x', alpha=0.3)
180
+
181
+ buffer = io.BytesIO()
182
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=300)
183
+ buffer.seek(0)
184
+ timeline_image = base64.b64encode(buffer.getvalue()).decode()
185
+ plt.close()
186
+
187
+ # Create network diagram for stakeholder relationships
188
+ fig, ax = plt.subplots(figsize=(12, 10))
189
+ G = nx.Graph()
190
+
191
+ # Add nodes (stakeholders)
192
+ stakeholders = ['Ministry', 'State Govt', 'Local Bodies', 'Citizens', 'NGOs', 'Private Sector']
193
+ G.add_nodes_from(stakeholders)
194
+
195
+ # Add edges (relationships)
196
+ relationships = [
197
+ ('Ministry', 'State Govt'), ('State Govt', 'Local Bodies'),
198
+ ('Local Bodies', 'Citizens'), ('Ministry', 'NGOs'),
199
+ ('Private Sector', 'Ministry'), ('NGOs', 'Citizens')
200
+ ]
201
+ G.add_edges_from(relationships)
202
+
203
+ # Draw network
204
+ pos = nx.spring_layout(G, k=2, iterations=50)
205
+ nx.draw(G, pos, ax=ax, with_labels=True, node_color='lightblue',
206
+ node_size=3000, font_size=10, font_weight='bold',
207
+ edge_color='gray', width=2)
208
+
209
+ ax.set_title(f"{title} - Stakeholder Network", fontsize=16, fontweight='bold')
210
+ ax.axis('off')
211
+
212
+ buffer = io.BytesIO()
213
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=300)
214
+ buffer.seek(0)
215
+ network_image = base64.b64encode(buffer.getvalue()).decode()
216
+ plt.close()
217
+
218
+ images = [
219
+ {"type": "timeline", "data": timeline_image},
220
+ {"type": "network", "data": network_image}
221
+ ]
222
+
223
+ total_duration = sum(data["durations"])
224
+ critical_phase = data["phases"][data["durations"].index(max(data["durations"]))]
225
+
226
+ analysis_text = [
227
+ f"πŸ“‹ **Policy Implementation Analysis:**",
228
+ f"β€’ Total Implementation Time: {total_duration} days",
229
+ f"β€’ Critical Phase: {critical_phase} ({max(data['durations'])} days)",
230
+ f"β€’ Number of Phases: {len(data['phases'])}",
231
+ "",
232
+ f"πŸ”— **Stakeholder Network:**",
233
+ f"β€’ {len(stakeholders)} key stakeholders identified",
234
+ f"β€’ {len(relationships)} critical relationships mapped",
235
+ "",
236
+ f"⚠️ **Risk Factors:**",
237
+ f"β€’ {critical_phase} phase requires most attention",
238
+ f"β€’ Coordination between stakeholders is crucial",
239
+ f"β€’ Monitor progress at each phase transition"
240
+ ]
241
+
242
+ return {
243
+ "success": True,
244
+ "analysis": "\n".join(analysis_text),
245
+ "images": images,
246
+ "scenario_type": "policy",
247
+ "total_duration": total_duration
248
+ }
249
+
250
+ except Exception as e:
251
+ logger.error(f"❌ Error in policy analysis: {str(e)}")
252
+ raise e
253
+
254
+ async def _analyze_organizational_scenario(self, data: Dict, title: str) -> Dict[str, Any]:
255
+ """Analyze organizational structure scenarios"""
256
+ try:
257
+ # Create organizational hierarchy chart
258
+ fig, ax = plt.subplots(figsize=(14, 10))
259
+
260
+ # Create hierarchical layout
261
+ G = nx.DiGraph()
262
+
263
+ # Sample organizational structure
264
+ if not data:
265
+ hierarchy = {
266
+ 'Secretary': ['Joint Secretary 1', 'Joint Secretary 2'],
267
+ 'Joint Secretary 1': ['Director 1', 'Director 2'],
268
+ 'Joint Secretary 2': ['Director 3', 'Director 4'],
269
+ 'Director 1': ['Deputy Director 1', 'Deputy Director 2'],
270
+ 'Director 2': ['Deputy Director 3'],
271
+ 'Director 3': ['Deputy Director 4', 'Deputy Director 5'],
272
+ 'Director 4': ['Deputy Director 6']
273
+ }
274
+ data = {"hierarchy": hierarchy}
275
+
276
+ # Build graph
277
+ for parent, children in data["hierarchy"].items():
278
+ for child in children:
279
+ G.add_edge(parent, child)
280
+
281
+ # Create hierarchical layout
282
+ pos = nx.nx_agraph.graphviz_layout(G, prog='dot') if hasattr(nx, 'nx_agraph') else nx.spring_layout(G)
283
+
284
+ # Draw organizational chart
285
+ nx.draw(G, pos, ax=ax, with_labels=True, node_color='lightcoral',
286
+ node_size=4000, font_size=8, font_weight='bold',
287
+ edge_color='darkgray', arrows=True, arrowsize=20,
288
+ bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.8))
289
+
290
+ ax.set_title(f"{title} - Organizational Structure", fontsize=16, fontweight='bold')
291
+ ax.axis('off')
292
+
293
+ buffer = io.BytesIO()
294
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=300)
295
+ buffer.seek(0)
296
+ org_image = base64.b64encode(buffer.getvalue()).decode()
297
+ plt.close()
298
+
299
+ images = [{"type": "organization_chart", "data": org_image}]
300
+
301
+ # Calculate organizational metrics
302
+ total_positions = len(G.nodes())
303
+ levels = len(set(nx.shortest_path_length(G, 'Secretary').values())) if 'Secretary' in G.nodes() else 0
304
+ span_of_control = sum(len(children) for children in data["hierarchy"].values()) / len(data["hierarchy"])
305
+
306
+ analysis_text = [
307
+ f"🏒 **Organizational Analysis:**",
308
+ f"β€’ Total Positions: {total_positions}",
309
+ f"β€’ Organizational Levels: {levels}",
310
+ f"β€’ Average Span of Control: {span_of_control:.1f}",
311
+ "",
312
+ f"πŸ“ˆ **Structure Insights:**",
313
+ f"β€’ Hierarchical structure with clear reporting lines",
314
+ f"β€’ {len(data['hierarchy'])} management positions",
315
+ f"β€’ Balanced distribution of responsibilities",
316
+ "",
317
+ f"πŸ’‘ **Recommendations:**",
318
+ f"β€’ Consider flattening structure if span > 7",
319
+ f"β€’ Ensure clear role definitions at each level",
320
+ f"β€’ Regular review of reporting relationships"
321
+ ]
322
+
323
+ return {
324
+ "success": True,
325
+ "analysis": "\n".join(analysis_text),
326
+ "images": images,
327
+ "scenario_type": "organization",
328
+ "total_positions": total_positions
329
+ }
330
+
331
+ except Exception as e:
332
+ logger.error(f"❌ Error in organizational analysis: {str(e)}")
333
+ raise e
334
+
335
+ async def _analyze_performance_scenario(self, data: Dict, title: str) -> Dict[str, Any]:
336
+ """Analyze performance metrics scenarios"""
337
+ try:
338
+ # Create performance dashboard
339
+ if not data:
340
+ metrics = ['Efficiency', 'Quality', 'Timeliness', 'Cost', 'Satisfaction']
341
+ current = [75, 82, 68, 85, 78]
342
+ target = [85, 90, 80, 80, 85]
343
+ data = {"metrics": metrics, "current": current, "target": target}
344
+
345
+ # Create multi-subplot figure
346
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
347
+
348
+ # 1. Performance vs Target comparison
349
+ x = np.arange(len(data["metrics"]))
350
+ width = 0.35
351
+
352
+ ax1.bar(x - width/2, data["current"], width, label='Current', color='skyblue', alpha=0.8)
353
+ ax1.bar(x + width/2, data["target"], width, label='Target', color='orange', alpha=0.8)
354
+ ax1.set_xlabel('Metrics')
355
+ ax1.set_ylabel('Score (%)')
356
+ ax1.set_title('Performance vs Target')
357
+ ax1.set_xticks(x)
358
+ ax1.set_xticklabels(data["metrics"], rotation=45)
359
+ ax1.legend()
360
+ ax1.grid(axis='y', alpha=0.3)
361
+
362
+ # 2. Radar chart
363
+ angles = np.linspace(0, 2 * np.pi, len(data["metrics"]), endpoint=False)
364
+ angles = np.concatenate((angles, [angles[0]]))
365
+
366
+ current_scores = data["current"] + [data["current"][0]]
367
+ target_scores = data["target"] + [data["target"][0]]
368
+
369
+ ax2 = plt.subplot(2, 2, 2, projection='polar')
370
+ ax2.plot(angles, current_scores, 'o-', linewidth=2, label='Current', color='blue')
371
+ ax2.fill(angles, current_scores, alpha=0.25, color='blue')
372
+ ax2.plot(angles, target_scores, 'o-', linewidth=2, label='Target', color='red')
373
+ ax2.fill(angles, target_scores, alpha=0.25, color='red')
374
+ ax2.set_xticks(angles[:-1])
375
+ ax2.set_xticklabels(data["metrics"])
376
+ ax2.set_title('Performance Radar')
377
+ ax2.legend()
378
+
379
+ # 3. Gap analysis
380
+ gaps = [target - current for current, target in zip(data["current"], data["target"])]
381
+ colors = ['red' if gap > 0 else 'green' for gap in gaps]
382
+
383
+ ax3.bar(data["metrics"], gaps, color=colors, alpha=0.7)
384
+ ax3.set_xlabel('Metrics')
385
+ ax3.set_ylabel('Gap (Target - Current)')
386
+ ax3.set_title('Performance Gap Analysis')
387
+ ax3.axhline(y=0, color='black', linestyle='-', alpha=0.3)
388
+ plt.setp(ax3.xaxis.get_majorticklabels(), rotation=45)
389
+
390
+ # 4. Performance trend (simulated)
391
+ months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
392
+ overall_trend = [70, 72, 75, 76, 78, np.mean(data["current"])]
393
+
394
+ ax4.plot(months, overall_trend, marker='o', linewidth=3, markersize=8, color='green')
395
+ ax4.set_xlabel('Month')
396
+ ax4.set_ylabel('Overall Performance (%)')
397
+ ax4.set_title('Performance Trend')
398
+ ax4.grid(True, alpha=0.3)
399
+
400
+ plt.tight_layout()
401
+
402
+ buffer = io.BytesIO()
403
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=300)
404
+ buffer.seek(0)
405
+ performance_image = base64.b64encode(buffer.getvalue()).decode()
406
+ plt.close()
407
+
408
+ images = [{"type": "performance_dashboard", "data": performance_image}]
409
+
410
+ # Calculate performance metrics
411
+ avg_current = np.mean(data["current"])
412
+ avg_target = np.mean(data["target"])
413
+ overall_gap = avg_target - avg_current
414
+ critical_areas = [metric for metric, gap in zip(data["metrics"], gaps) if gap > 10]
415
+
416
+ analysis_text = [
417
+ f"πŸ“Š **Performance Analysis:**",
418
+ f"β€’ Current Average Performance: {avg_current:.1f}%",
419
+ f"β€’ Target Average Performance: {avg_target:.1f}%",
420
+ f"β€’ Overall Performance Gap: {overall_gap:.1f}%",
421
+ "",
422
+ f"πŸ” **Key Findings:**",
423
+ f"β€’ Best Performing Area: {data['metrics'][data['current'].index(max(data['current']))]}",
424
+ f"β€’ Areas Needing Improvement: {', '.join(critical_areas) if critical_areas else 'None critical'}",
425
+ f"β€’ Performance is {'on track' if overall_gap < 5 else 'needs attention'}",
426
+ "",
427
+ f"🎯 **Action Items:**",
428
+ f"β€’ Focus on areas with gaps > 10%",
429
+ f"β€’ Maintain strong performance in current best areas",
430
+ f"β€’ Set monthly improvement targets"
431
+ ]
432
+
433
+ return {
434
+ "success": True,
435
+ "analysis": "\n".join(analysis_text),
436
+ "images": images,
437
+ "scenario_type": "performance",
438
+ "overall_gap": overall_gap
439
+ }
440
+
441
+ except Exception as e:
442
+ logger.error(f"❌ Error in performance analysis: {str(e)}")
443
+ raise e
444
+
445
+ async def _analyze_workflow_scenario(self, data: Dict, title: str) -> Dict[str, Any]:
446
+ """Analyze workflow and process scenarios"""
447
+ try:
448
+ # Create workflow diagram
449
+ fig, ax = plt.subplots(figsize=(16, 10))
450
+
451
+ # Sample workflow if no data provided
452
+ if not data:
453
+ steps = ['Application', 'Verification', 'Approval', 'Processing', 'Dispatch']
454
+ connections = [('Application', 'Verification'), ('Verification', 'Approval'),
455
+ ('Approval', 'Processing'), ('Processing', 'Dispatch')]
456
+ times = [2, 5, 3, 7, 1] # days
457
+ data = {"steps": steps, "connections": connections, "times": times}
458
+
459
+ # Create workflow graph
460
+ G = nx.DiGraph()
461
+ for i, step in enumerate(data["steps"]):
462
+ G.add_node(step, time=data["times"][i])
463
+
464
+ for connection in data["connections"]:
465
+ G.add_edge(connection[0], connection[1])
466
+
467
+ # Layout for workflow
468
+ pos = nx.spring_layout(G, k=3, iterations=50)
469
+
470
+ # Draw workflow
471
+ node_colors = plt.cm.RdYlGn_r(np.array(data["times"])/max(data["times"]))
472
+ nx.draw(G, pos, ax=ax, with_labels=True, node_color=node_colors,
473
+ node_size=4000, font_size=10, font_weight='bold',
474
+ edge_color='darkblue', arrows=True, arrowsize=20,
475
+ bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.9))
476
+
477
+ # Add time labels
478
+ for i, (node, (x, y)) in enumerate(pos.items()):
479
+ ax.text(x, y-0.15, f'{data["times"][i]} days',
480
+ ha='center', va='center', fontsize=8,
481
+ bbox=dict(boxstyle="round,pad=0.2", facecolor="yellow", alpha=0.7))
482
+
483
+ ax.set_title(f"{title} - Workflow Process", fontsize=16, fontweight='bold')
484
+ ax.axis('off')
485
+
486
+ # Add colorbar for time scale
487
+ sm = plt.cm.ScalarMappable(cmap=plt.cm.RdYlGn_r,
488
+ norm=plt.Normalize(vmin=min(data["times"]), vmax=max(data["times"])))
489
+ sm.set_array([])
490
+ cbar = plt.colorbar(sm, ax=ax, shrink=0.8)
491
+ cbar.set_label('Processing Time (days)', rotation=270, labelpad=20)
492
+
493
+ buffer = io.BytesIO()
494
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=300)
495
+ buffer.seek(0)
496
+ workflow_image = base64.b64encode(buffer.getvalue()).decode()
497
+ plt.close()
498
+
499
+ images = [{"type": "workflow_diagram", "data": workflow_image}]
500
+
501
+ # Calculate workflow metrics
502
+ total_time = sum(data["times"])
503
+ bottleneck = data["steps"][data["times"].index(max(data["times"]))]
504
+ fastest_step = data["steps"][data["times"].index(min(data["times"]))]
505
+
506
+ analysis_text = [
507
+ f"⚑ **Workflow Analysis:**",
508
+ f"β€’ Total Process Time: {total_time} days",
509
+ f"β€’ Number of Steps: {len(data['steps'])}",
510
+ f"β€’ Average Step Time: {total_time/len(data['steps']):.1f} days",
511
+ "",
512
+ f"🚦 **Process Insights:**",
513
+ f"β€’ Bottleneck: {bottleneck} ({max(data['times'])} days)",
514
+ f"β€’ Fastest Step: {fastest_step} ({min(data['times'])} days)",
515
+ f"β€’ Process Efficiency: {'Good' if total_time < 20 else 'Needs Improvement'}",
516
+ "",
517
+ f"πŸ”§ **Optimization Opportunities:**",
518
+ f"β€’ Focus on reducing {bottleneck} processing time",
519
+ f"β€’ Consider parallel processing where possible",
520
+ f"β€’ Implement automation for routine steps"
521
+ ]
522
+
523
+ return {
524
+ "success": True,
525
+ "analysis": "\n".join(analysis_text),
526
+ "images": images,
527
+ "scenario_type": "workflow",
528
+ "total_time": total_time
529
+ }
530
+
531
+ except Exception as e:
532
+ logger.error(f"❌ Error in workflow analysis: {str(e)}")
533
+ raise e
534
+
535
+ async def _analyze_general_scenario(self, data: Dict, title: str, scenario_type: str) -> Dict[str, Any]:
536
+ """Analyze general scenarios with basic visualizations"""
537
+ try:
538
+ # Create simple visualization
539
+ fig, ax = plt.subplots(figsize=(12, 8))
540
+
541
+ if not data:
542
+ categories = ['Category A', 'Category B', 'Category C', 'Category D']
543
+ values = [25, 35, 20, 20]
544
+ data = {"categories": categories, "values": values}
545
+
546
+ # Create bar chart
547
+ colors = plt.cm.tab10(np.linspace(0, 1, len(data["categories"])))
548
+ bars = ax.bar(data["categories"], data["values"], color=colors, alpha=0.8)
549
+
550
+ ax.set_title(f"{title} - {scenario_type.title()} Analysis", fontsize=16, fontweight='bold')
551
+ ax.set_xlabel("Categories")
552
+ ax.set_ylabel("Values")
553
+
554
+ # Add value labels
555
+ for bar in bars:
556
+ height = bar.get_height()
557
+ ax.annotate(f'{height}',
558
+ xy=(bar.get_x() + bar.get_width() / 2, height),
559
+ xytext=(0, 3),
560
+ textcoords="offset points",
561
+ ha='center', va='bottom')
562
+
563
+ plt.xticks(rotation=45)
564
+ ax.grid(axis='y', alpha=0.3)
565
+
566
+ buffer = io.BytesIO()
567
+ plt.savefig(buffer, format='png', bbox_inches='tight', dpi=300)
568
+ buffer.seek(0)
569
+ general_image = base64.b64encode(buffer.getvalue()).decode()
570
+ plt.close()
571
+
572
+ images = [{"type": "general_analysis", "data": general_image}]
573
+
574
+ total_value = sum(data["values"])
575
+ max_category = data["categories"][data["values"].index(max(data["values"]))]
576
+
577
+ analysis_text = [
578
+ f"πŸ“Š **{scenario_type.title()} Analysis:**",
579
+ f"β€’ Total Value: {total_value}",
580
+ f"β€’ Highest Category: {max_category}",
581
+ f"β€’ Number of Categories: {len(data['categories'])}",
582
+ "",
583
+ f"πŸ“ˆ **Key Insights:**",
584
+ f"β€’ {max_category} shows the highest value",
585
+ f"β€’ Distribution across {len(data['categories'])} categories",
586
+ f"β€’ Further analysis may be needed based on specific requirements"
587
+ ]
588
+
589
+ return {
590
+ "success": True,
591
+ "analysis": "\n".join(analysis_text),
592
+ "images": images,
593
+ "scenario_type": scenario_type,
594
+ "total_value": total_value
595
+ }
596
+
597
+ except Exception as e:
598
+ logger.error(f"❌ Error in general analysis: {str(e)}")
599
+ raise e
600
+
601
+ # Global instance
602
+ scenario_service = ScenarioAnalysisService()