triflix commited on
Commit
3c4e575
·
verified ·
1 Parent(s): e55585c

Upload 53 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Reference/aboutdata.txt +118 -0
  2. Reference/basemodel.py +46 -0
  3. Reference/basic_portfolio_model.pkl +3 -0
  4. Reference/enhanced_portfolio_model.pkl +3 -0
  5. Reference/enhancedmodel.py +56 -0
  6. Reference/rulebasedmodel.py +314 -0
  7. app/__init__.py +1 -0
  8. app/__pycache__/__init__.cpython-312.pyc +0 -0
  9. app/__pycache__/admin.cpython-312.pyc +0 -0
  10. app/__pycache__/auth.cpython-312.pyc +0 -0
  11. app/__pycache__/crud.cpython-312.pyc +0 -0
  12. app/__pycache__/database.cpython-312.pyc +0 -0
  13. app/__pycache__/main.cpython-312.pyc +0 -0
  14. app/__pycache__/models.cpython-312.pyc +0 -0
  15. app/__pycache__/schemas.cpython-312.pyc +0 -0
  16. app/__pycache__/user.cpython-312.pyc +0 -0
  17. app/admin.py +73 -0
  18. app/auth.py +99 -0
  19. app/core/__pycache__/security.cpython-312.pyc +0 -0
  20. app/core/security.py +40 -0
  21. app/crud.py +48 -0
  22. app/database.py +26 -0
  23. app/main.py +146 -0
  24. app/ml_models/basic_portfolio_model.pkl +3 -0
  25. app/ml_models/enhanced_portfolio_model.pkl +3 -0
  26. app/ml_models/enhanced_portfolio_model111.pkl +3 -0
  27. app/ml_models/financial_advisor.db +0 -0
  28. app/models.py +28 -0
  29. app/schemas.py +139 -0
  30. app/services/__pycache__/chatbot_service.cpython-312.pyc +0 -0
  31. app/services/__pycache__/data_service.cpython-312.pyc +0 -0
  32. app/services/chatbot_service.py +312 -0
  33. app/services/data_service.py +129 -0
  34. app/static/css/custom_styles.css +0 -0
  35. app/static/css/style.css +112 -0
  36. app/static/js/script.js +61 -0
  37. app/templates/admin/dashboard.html +175 -0
  38. app/templates/admin/user_details.html +170 -0
  39. app/templates/auth/login.html +133 -0
  40. app/templates/auth/signup.html +121 -0
  41. app/templates/partials/base.html +30 -0
  42. app/templates/partials/footer.html +6 -0
  43. app/templates/partials/navbar.html +44 -0
  44. app/templates/user/chatbot.html +362 -0
  45. app/templates/user/homepage.html +137 -0
  46. app/templates/user/recommendations.html +240 -0
  47. app/tickers.txt +2099 -0
  48. app/user.py +227 -0
  49. app/user_financial_data.csv +14 -0
  50. diagramcontent.md +54 -0
Reference/aboutdata.txt ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Columns in the dataset:
2
+ ['Name', 'Age', 'City', 'Profession', 'Salary', 'Expenses', 'Savings', 'Lifecycle Stage', 'Risk Appetite', 'Investment Horizon', 'Equity (%)', 'Debt (%)', 'Gold (%)', 'FD/Cash (%)']
3
+
4
+ Preview of the data:
5
+ Name Age City Profession Salary Expenses Savings \
6
+ 0 Rohan Sharma 28 Mumbai Software Engineer 85000 65000 20000
7
+ 1 Priya Patel 45 Delhi Doctor 220000 150000 70000
8
+ 2 Arjun Singh 60 Bangalore Retired Banker 45000 40000 5000
9
+ 3 Anika Reddy 22 Hyderabad Student 15000 14000 1000
10
+ 4 Vikram Mehta 35 Pune Marketing Manager 140000 100000 40000
11
+
12
+ Lifecycle Stage Risk Appetite Investment Horizon Equity (%) Debt (%) \
13
+ 0 Early Career Medium Long-term 45 25
14
+ 1 Mid-Career Medium Medium-term 40 30
15
+ 2 Retired Low Short-term 10 40
16
+ 3 Student Low Short-term 0 30
17
+ 4 Early Career High Long-term 70 15
18
+
19
+ Gold (%) FD/Cash (%)
20
+ 0 10 20
21
+ 1 10 20
22
+ 2 15 35
23
+ 3 5 65
24
+ 4 5 10
25
+
26
+ Data types:
27
+ Name object
28
+ Age int64
29
+ City object
30
+ Profession object
31
+ Salary int64
32
+ Expenses int64
33
+ Savings int64
34
+ Lifecycle Stage object
35
+ Risk Appetite object
36
+ Investment Horizon object
37
+ Equity (%) int64
38
+ Debt (%) int64
39
+ Gold (%) int64
40
+ FD/Cash (%) int64
41
+ dtype: object
42
+
43
+ Unique values in 'City':
44
+ ['Mumbai' 'Delhi' 'Bangalore' 'Hyderabad' 'Pune' 'Chennai' 'Jaipur'
45
+ 'Kochi' 'Kolkata' 'Ahmedabad' 'Gurgaon' 'Lucknow' 'Nagpur' 'Chandigarh'
46
+ 'Surat' 'Indore' 'Bhopal']
47
+
48
+ Unique values in 'Profession':
49
+ ['Software Engineer' 'Doctor' 'Retired Banker' 'Student'
50
+ 'Marketing Manager' 'Teacher' 'Freelancer' 'Architect' 'Business Owner'
51
+ 'Nurse' 'Product Manager' 'CA' 'Data Analyst' 'Retired Professor'
52
+ 'Journalist' 'Graphic Designer' 'Sales Executive' 'HR Manager' 'Intern'
53
+ 'Dentist' 'Lawyer' 'Content Creator' 'Pensioner' 'UX Designer'
54
+ 'Government Employee' 'Fashion Designer' 'Startup Founder'
55
+ 'Homemaker (Investor)' 'Investment Banker' 'IT Consultant'
56
+ 'College Student' 'Pharmacist' 'Textile Business Owner' 'Event Planner'
57
+ 'Film Producer' 'Nutritionist' 'Retired Army Officer'
58
+ 'Social Media Manager' 'Airline Pilot' 'Biotech Researcher'
59
+ 'Real Estate Agent' 'AI Engineer' 'Retired Teacher' 'Financial Analyst'
60
+ 'Software Developer' 'NGO Director' 'Fitness Trainer' 'Digital Marketer'
61
+ 'Content Strategist' 'Retired Engineer' 'UI Developer'
62
+ 'Operations Manager' 'Corporate Lawyer' 'School Principal'
63
+ 'AI Researcher' 'Junior Doctor' 'Finance Manager' 'Fashion Blogger'
64
+ 'HR Consultant' 'Video Editor' 'Small Business Owner' 'Dietitian'
65
+ 'Supply Chain Manager' 'Interior Designer' 'Sales Manager' 'PR Executive'
66
+ 'Logistics Head' 'Content Writer' 'Retired Govt. Employee'
67
+ 'Marketing Lead' 'IT Manager' 'Startup Intern' 'Export Manager'
68
+ 'Fitness Instructor' 'Real Estate Broker' 'Event Manager'
69
+ 'Software Trainee' 'Data Scientist' 'HR Director' 'Hotel Manager'
70
+ 'Social Worker' 'Cybersecurity Expert' 'E-commerce Manager'
71
+ 'Bank Manager' 'Retired IT Manager' 'UX Researcher' 'Product Designer'
72
+ 'Cybersecurity Analyst' 'Podcast Producer' 'E-commerce Seller'
73
+ 'Cloud Architect' 'Corporate Trainer' 'AI Trainer' 'Supply Chain Head'
74
+ 'Product Owner' 'UI/UX Designer' 'Finance Director'
75
+ 'Sustainability Consultant' 'Retired Bank Manager'
76
+ 'Social Media Influencer' 'Blockchain Developer' 'NGO Head'
77
+ 'Data Engineer' 'Event Curator' 'CTO' 'Content Marketer'
78
+ 'Retired Army Major' 'AR/VR Developer' 'Logistics Manager' 'VP Sales'
79
+ 'EdTech Founder' 'SEO Specialist' 'Yoga Instructor' 'IT Director'
80
+ 'App Developer' 'Consultant Cardiologist' 'Freelance Writer'
81
+ 'Cloud Engineer' 'Retired CA' 'Digital Artist' 'Retired Pilot'
82
+ 'Graphic Animator' 'AI Ethicist' 'School Trustee' 'Robotics Engineer'
83
+ 'Fashion Stylist' 'Retired Nurse' 'AI Ethics Consultant' 'Drone Engineer'
84
+ 'Retired Bank Clerk' 'Digital Nomad' 'CFO' 'Sustainability Analyst'
85
+ 'Retired Journalist' 'Social Entrepreneur' 'DevOps Engineer'
86
+ 'School Counselor' 'Retired Army Colonel' 'AR Developer' 'Sales Director'
87
+ 'EdTech Consultant' 'SEO Expert' 'Cardiologist' '3D Artist' 'HR Head'
88
+ 'Animator' 'Fashion Influencer']
89
+
90
+ Unique values in 'Lifecycle Stage':
91
+ ['Early Career' 'Mid-Career' 'Retired' 'Student' 'Late Career']
92
+
93
+ Unique values in 'Risk Appetite':
94
+ ['Medium' 'Low' 'High']
95
+
96
+ Unique values in 'Investment Horizon':
97
+ ['Long-term' 'Medium-term' 'Short-term']
98
+
99
+ Numerical columns summary:
100
+ Salary Expenses Savings Equity (%) Debt (%) \
101
+ count 198.000000 198.000000 198.000000 198.000000 198.000000
102
+ mean 115075.757576 84686.868687 30388.888889 39.469697 28.308081
103
+ std 69388.842695 48499.494260 21425.713424 20.713089 11.534149
104
+ min 8000.000000 7500.000000 500.000000 0.000000 10.000000
105
+ 25% 60000.000000 48000.000000 10000.000000 25.000000 20.000000
106
+ 50% 95000.000000 70000.000000 25000.000000 45.000000 25.000000
107
+ 75% 163750.000000 118750.000000 45000.000000 50.000000 30.000000
108
+ max 310000.000000 230000.000000 90000.000000 70.000000 55.000000
109
+
110
+ Gold (%) FD/Cash (%)
111
+ count 198.000000 198.000000
112
+ mean 8.434343 23.787879
113
+ std 3.427751 16.426902
114
+ min 0.000000 10.000000
115
+ 25% 5.000000 15.000000
116
+ 50% 10.000000 20.000000
117
+ 75% 10.000000 20.000000
118
+ max 15.000000 80.000000
Reference/basemodel.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+
3
+ # Load the basic model pipeline
4
+ pipeline = joblib.load('basic_portfolio_model.pkl')
5
+
6
+ input_data = {
7
+ 'Salary': 150000,
8
+ 'Expenses': 100000,
9
+ 'Savings': 50000,
10
+ 'Lifecycle Stage': 'Mid-Career',
11
+ 'Risk Appetite': 'Medium',
12
+ 'Investment Horizon': 'Long-term'
13
+ }
14
+
15
+ # Convert categorical features (FIXED SYNTAX)
16
+ input_data['Lifecycle Stage'] = pipeline['mappings']['lifecycle'][input_data['Lifecycle Stage']] # Added closing ]
17
+ input_data['Risk Appetite'] = pipeline['mappings']['risk'][input_data['Risk Appetite']] # Added closing ]
18
+ input_data['Investment Horizon'] = pipeline['mappings']['horizon'][input_data['Investment Horizon']] # Added closing ]
19
+
20
+ # Create feature array in correct order
21
+ X = [
22
+ input_data['Salary'],
23
+ input_data['Expenses'],
24
+ input_data['Savings'],
25
+ input_data['Lifecycle Stage'],
26
+ input_data['Risk Appetite'],
27
+ input_data['Investment Horizon']
28
+ ]
29
+
30
+ # Scale and predict
31
+ X_scaled = pipeline['scaler'].transform([X])
32
+ pred = pipeline['model'].predict(X_scaled)[0]
33
+
34
+ # Normalize and format
35
+ total = pred.sum()
36
+ final_allocation = {
37
+ 'Equity': round((pred[0]/total)*100, 1),
38
+ 'Debt': round((pred[1]/total)*100, 1),
39
+ 'Gold': round((pred[2]/total)*100, 1),
40
+ 'FD/Cash': round((pred[3]/total)*100, 1)
41
+ }
42
+
43
+ print("Recommended Portfolio:")
44
+ for asset, perc in final_allocation.items():
45
+ print(f"{asset}: {perc}%")
46
+ print(f"Total: {sum(final_allocation.values())}%")
Reference/basic_portfolio_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ae20f4cf2e1cb3f75623f692aa11f947c83529dbe6335a5634365052bd8d15ca
3
+ size 1817898
Reference/enhanced_portfolio_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2560b79a9705ca3674cd96820fd31fd6799dde89b12d4815afc6887111b52746
3
+ size 1691633
Reference/enhancedmodel.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ import pandas as pd
3
+
4
+ # Load enhanced model pipeline
5
+ pipeline = joblib.load('enhanced_portfolio_model.pkl')
6
+
7
+ # Sample input (MUST include Profession/City)
8
+ test_case = {
9
+ 'Profession': 'Software Engineer',
10
+ 'City': 'Mumbai',
11
+ 'Salary': 150000,
12
+ 'Expenses': 100000,
13
+ 'Savings': 50000,
14
+ 'Lifecycle Stage': 'Mid-Career',
15
+ 'Risk Appetite': 'Medium',
16
+ 'Investment Horizon': 'Long-term'
17
+ }
18
+
19
+ # Convert categorical features
20
+ test_case['Profession'] = pipeline['profession_encoder'].transform([test_case['Profession']])[0]
21
+ test_case['City'] = pipeline['city_encoder'].transform([test_case['City']])[0]
22
+ test_case['Lifecycle Stage'] = pipeline['mappings']['lifecycle'][test_case['Lifecycle Stage']]
23
+ test_case['Risk Appetite'] = pipeline['mappings']['risk'][test_case['Risk Appetite']]
24
+ test_case['Investment Horizon'] = pipeline['mappings']['horizon'][test_case['Investment Horizon']]
25
+
26
+ # Create feature array IN EXACT ORDER:
27
+ # ['Profession', 'City', 'Salary', 'Expenses', 'Savings',
28
+ # 'Lifecycle Stage', 'Risk Appetite', 'Investment Horizon']
29
+ X = [
30
+ test_case['Profession'],
31
+ test_case['City'],
32
+ test_case['Salary'],
33
+ test_case['Expenses'],
34
+ test_case['Savings'],
35
+ test_case['Lifecycle Stage'],
36
+ test_case['Risk Appetite'],
37
+ test_case['Investment Horizon']
38
+ ]
39
+
40
+ # Scale and predict
41
+ X_scaled = pipeline['scaler'].transform([X])
42
+ pred = pipeline['model'].predict(X_scaled)[0]
43
+
44
+ # Normalize to 100%
45
+ total = pred.sum()
46
+ final_allocation = {
47
+ 'Equity': round((pred[0]/total)*100, 1),
48
+ 'Debt': round((pred[1]/total)*100, 1),
49
+ 'Gold': round((pred[2]/total)*100, 1),
50
+ 'FD/Cash': round((pred[3]/total)*100, 1)
51
+ }
52
+
53
+ print("Enhanced Model Recommended Portfolio:")
54
+ for asset, perc in final_allocation.items():
55
+ print(f"{asset}: {perc}%")
56
+ print(f"Total: {sum(final_allocation.values())}%")
Reference/rulebasedmodel.py ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ import math
3
+
4
+ # --- Helper Functions (mostly unchanged) ---
5
+ def get_float(prompt):
6
+ """Helper to get a non-negative float from the user."""
7
+ while True:
8
+ try:
9
+ val_str = input(prompt).strip()
10
+ if not val_str:
11
+ raise ValueError("Input cannot be empty.")
12
+ val = float(val_str)
13
+ if val < 0:
14
+ raise ValueError("Please enter a non-negative number.")
15
+ return val
16
+ except ValueError as e:
17
+ print(f"Invalid input: {e}")
18
+
19
+ def get_int(prompt):
20
+ """Helper to get a non-negative integer from the user."""
21
+ while True:
22
+ try:
23
+ val_str = input(prompt).strip()
24
+ if not val_str:
25
+ raise ValueError("Input cannot be empty.")
26
+ val = int(val_str)
27
+ if val < 0:
28
+ raise ValueError("Please enter a non-negative whole number.")
29
+ return val
30
+ except ValueError as e:
31
+ print(f"Invalid input: {e}")
32
+
33
+ def yes_no(prompt):
34
+ """Helper to get a yes/no response."""
35
+ while True:
36
+ resp = input(prompt + " (y/n): ").strip().lower()
37
+ if resp in ("y", "yes"):
38
+ return True
39
+ if resp in ("n", "no"):
40
+ return False
41
+ print("Please answer 'y' or 'n'.")
42
+
43
+ # --- Simplified Profile and Financials Gathering ---
44
+
45
+ def get_user_profile():
46
+ """Gathers lifecycle stage, risk appetite, and investment horizon."""
47
+ print("\n--- Let's Understand Your Investor Profile ---")
48
+
49
+ # 1. Lifecycle Stage
50
+ print("\nWhich of these best describes your current life stage?")
51
+ lifecycle_options = {
52
+ "1": "Student (Focus: Learning, initial savings)",
53
+ "2": "Early Career (20s-early 30s, Focus: Growth, accumulation)",
54
+ "3": "Mid-Career (Mid 30s-late 40s, Focus: Balancing growth & responsibilities)",
55
+ "4": "Late Career/Pre-Retirement (50s+, Focus: Capital preservation, income generation)",
56
+ "5": "Retired (Focus: Sustainable income, capital preservation)"
57
+ }
58
+ for key, value in lifecycle_options.items():
59
+ print(f"{key}. {value}")
60
+ while True:
61
+ choice = input("Enter number (1-5): ").strip()
62
+ if choice in lifecycle_options:
63
+ lifecycle_stage = lifecycle_options[choice].split('(')[0].strip() # Get "Student", "Early Career" etc.
64
+ break
65
+ else:
66
+ print("Invalid choice. Please enter a number between 1 and 5.")
67
+
68
+ # 2. Risk Appetite
69
+ print("\nHow would you describe your risk tolerance for investments?")
70
+ risk_options = {
71
+ "1": "Low (Prefer safety and capital preservation, okay with lower returns)",
72
+ "2": "Medium (Willing to take some risk for moderate growth, comfortable with some fluctuations)",
73
+ "3": "High (Willing to take significant risk for potentially high returns, can handle large fluctuations)"
74
+ }
75
+ for key, value in risk_options.items():
76
+ print(f"{key}. {value}")
77
+ while True:
78
+ choice = input("Enter number (1-3): ").strip()
79
+ if choice == "1": risk_appetite = "low"; break
80
+ elif choice == "2": risk_appetite = "medium"; break
81
+ elif choice == "3": risk_appetite = "high"; break
82
+ else:
83
+ print("Invalid choice. Please enter 1, 2, or 3.")
84
+
85
+ # 3. Investment Horizon
86
+ print("\nWhat is your general investment time horizon for the majority of your savings?")
87
+ horizon_options = {
88
+ "1": "Short-term (Less than 3 years)",
89
+ "2": "Medium-term (3-7 years)",
90
+ "3": "Long-term (More than 7 years)"
91
+ }
92
+ for key, value in horizon_options.items():
93
+ print(f"{key}. {value}")
94
+ while True:
95
+ choice = input("Enter number (1-3): ").strip()
96
+ if choice == "1": investment_horizon = "short-term"; break
97
+ elif choice == "2": investment_horizon = "medium-term"; break
98
+ elif choice == "3": investment_horizon = "long-term"; break
99
+ else:
100
+ print("Invalid choice. Please enter 1, 2, or 3.")
101
+
102
+ return lifecycle_stage, risk_appetite, investment_horizon
103
+
104
+ def get_financial_details():
105
+ """Gets annual package, monthly in-hand salary, and total monthly expenses."""
106
+ print("\n--- Let's Get Your Financial Details ---")
107
+ annual_package = get_float("What is your approximate annual salary package (CTC)? \u20B9 ")
108
+
109
+ # Simplified monthly in-hand: Ask directly or provide a rough estimate and ask for correction
110
+ print(f"\nBased on an annual package of \u20B9 {annual_package:.2f}, your monthly in-hand salary might be around \u20B9 {annual_package / 14:.2f} to \u20B9 {annual_package / 13:.2f} (this is a rough estimate).")
111
+ monthly_in_hand_salary = get_float("What is your actual average monthly in-hand salary? \u20B9 ")
112
+
113
+ total_monthly_expenses = get_float("What are your total estimated monthly expenses (all inclusive)? \u20B9 ")
114
+
115
+ monthly_savings = monthly_in_hand_salary - total_monthly_expenses
116
+
117
+ print(f"\nBased on your input:")
118
+ print(f" Monthly In-hand Salary: \u20B9 {monthly_in_hand_salary:.2f}")
119
+ print(f" Total Monthly Expenses: \u20B9 {total_monthly_expenses:.2f}")
120
+ if monthly_savings > 0:
121
+ print(f" Calculated Monthly Savings: \u20B9 {monthly_savings:.2f}")
122
+ else:
123
+ print(f" Calculated Monthly Deficit: \u20B9 {abs(monthly_savings):.2f}")
124
+ print(" It seems your expenses exceed your income. Please review your budget.")
125
+
126
+ return monthly_in_hand_salary, total_monthly_expenses, monthly_savings
127
+
128
+ # --- Simplified Investment Allocation Logic ---
129
+ def allocate_savings_simplified(savings, risk_profile, horizon, lifecycle):
130
+ """
131
+ Determines investment allocation percentages based on simplified profile.
132
+ Returns a dictionary of { "Asset Class": percentage, ... }
133
+ """
134
+ allocations = {}
135
+ justification_points = [
136
+ f"This allocation considers your {risk_profile} risk profile, {horizon} horizon, and {lifecycle} stage."
137
+ ]
138
+
139
+ # Prioritize Emergency Fund if not explicitly covered or if savings are first-time
140
+ # For simplicity, this version assumes user will manage EF separately or this is for surplus post-EF.
141
+ # A more robust version would inquire about EF status.
142
+
143
+ if risk_profile == "low":
144
+ allocations = {
145
+ "Fixed Deposits / Recurring Deposits (Safety & Stability)": 0.35,
146
+ "Debt Mutual Funds (Liquid/Short Duration - Better than savings account returns)": 0.30,
147
+ "Gold (SGBs/ETFs - Inflation Hedge, Diversification)": 0.10,
148
+ "Equity MFs (Large Cap/Index Funds - Long-term inflation beating, low volatility equity)": 0.15,
149
+ "Cash / Savings Account (Immediate Liquidity)": 0.10
150
+ }
151
+ justification_points.append("- Focus on capital preservation and stable returns.")
152
+ justification_points.append("- Suitable for short-term goals or very conservative investors.")
153
+ if horizon != "short-term":
154
+ justification_points.append("- Even with a longer horizon, a 'low' risk choice emphasizes safety above all.")
155
+
156
+
157
+ elif risk_profile == "medium":
158
+ allocations = {
159
+ "Equity MFs (Diversified - Large & Mid Cap/Flexi Cap SIPs for growth)": 0.45,
160
+ "Debt Mutual Funds (For stability and portfolio balance)": 0.25,
161
+ "Fixed Deposits / PPF (Secure, long-term component)": 0.10,
162
+ "Gold (SGBs/ETFs - Diversification)": 0.10,
163
+ "Equity MFs (International - For global diversification, if comfortable)": 0.05, # Optional
164
+ "Cash / Savings Account (Buffer)": 0.05
165
+ }
166
+ justification_points.append("- Aims for a balance between growth (equity) and stability (debt, gold).")
167
+ justification_points.append("- Suitable for medium to long-term goals.")
168
+ if lifecycle in ["Early Career", "Student"] and horizon == "long-term":
169
+ justification_points.append("- Your stage and horizon allow for good equity exposure for wealth creation.")
170
+ # Could slightly increase equity for very young, long-term, medium risk.
171
+ # allocations["Equity MFs (Diversified - Large & Mid Cap/Flexi Cap SIPs for growth)"] = 0.50
172
+ # allocations["Debt Mutual Funds (For stability and portfolio balance)"] = 0.20
173
+
174
+ elif risk_profile == "high":
175
+ allocations = {
176
+ "Equity MFs (Aggressive Growth - Mid/Small Cap, Thematic SIPs, with research)": 0.60,
177
+ "Equity MFs (International - Global growth opportunities)": 0.15,
178
+ "Direct Stocks (If experienced & well-researched, otherwise add to Equity MFs)": 0.10, # User needs to self-assess this.
179
+ "Debt Mutual Funds (Strategic, for some diversification)": 0.05,
180
+ "Gold (SGBs/ETFs - Tactical Diversification)": 0.05,
181
+ "Alternative Inv. (REITs/InvITs - very small, if understood & suitable, else to Equity)": 0.05
182
+ }
183
+ justification_points.append("- Focuses on maximizing long-term growth potential, accepting higher volatility.")
184
+ justification_points.append("- Best suited for long-term goals and investors comfortable with significant market swings.")
185
+ if lifecycle not in ["Early Career", "Mid-Career"] and horizon == "long-term":
186
+ justification_points.append(f"- CAUTION: High risk at {lifecycle} stage needs careful consideration of your overall financial stability and nearness to needing funds.")
187
+ if horizon != "long-term":
188
+ justification_points.append(f"- CAUTION: High risk for a {horizon} horizon is generally not advisable. Ensure goals truly allow for this risk.")
189
+
190
+
191
+ # Normalize percentages to ensure they sum to 100%
192
+ current_total_percentage = sum(allocations.values())
193
+ if abs(current_total_percentage - 1.0) > 0.001: # If not already 100%
194
+ factor = 1.0 / current_total_percentage
195
+ normalized_allocations = {k: v * factor for k, v in allocations.items()}
196
+ # Small check to ensure the largest component doesn't become negative if factor is weird (shouldn't happen with positive inputs)
197
+ # And ensure no tiny values are left that make no sense, e.g. less than 0.5% could be merged.
198
+ # For simplicity, we'll assume initial definitions are close enough.
199
+ final_allocations = {}
200
+ temp_sum = 0
201
+ for asset, perc in normalized_allocations.items():
202
+ # Round to sensible points e.g. 1 decimal for percentage display
203
+ # but use more precision for calculation
204
+ final_allocations[asset] = perc
205
+ temp_sum += perc
206
+
207
+ # Final check and adjustment of the largest item if sum isn't perfect due to rounding during normalization.
208
+ if abs(temp_sum - 1.0) > 0.0001:
209
+ diff = 1.0 - temp_sum
210
+ if final_allocations: # Check if dict is not empty
211
+ # Find largest item to adjust
212
+ largest_item_key = max(final_allocations, key=final_allocations.get)
213
+ final_allocations[largest_item_key] += diff
214
+ allocations = final_allocations
215
+
216
+
217
+ return allocations, justification_points
218
+
219
+ def display_investment_plan(monthly_savings, allocations, justification):
220
+ """Displays the final investment plan and justification."""
221
+ print("\n\n--- Your Personalized Investment Allocation Plan ---")
222
+ print(f"Based on your monthly savings of \u20B9 {monthly_savings:.2f}:\n")
223
+
224
+ print("Suggested Allocation:")
225
+ print("---------------------------------------------------------------------------")
226
+ print(f"{'Asset Class/Instrument':<50} | {'Percentage':>12} | {'Amount (₹)':>12}")
227
+ print("---------------------------------------------------------------------------")
228
+ if not allocations: # Should not happen if logic is correct
229
+ print("No specific allocation generated. Please review inputs or consult an advisor.")
230
+ return
231
+
232
+ total_allocated_amount_check = 0
233
+ for asset, percentage in allocations.items():
234
+ amount = monthly_savings * percentage
235
+ total_allocated_amount_check += amount
236
+ print(f"{asset:<50} | {percentage*100:>11.1f}% | {amount:>11.2f}")
237
+ print("---------------------------------------------------------------------------")
238
+ print(f"{'TOTAL':<50} | {'100.0%':>12} | {total_allocated_amount_check:>11.2f}")
239
+
240
+
241
+ print("\nWhy these allocations?")
242
+ for point in justification:
243
+ print(f"- {point}")
244
+ print("\n- Learn more about general investing principles at: https://www.investopedia.com/financial-advisor/asset-allocation/\n"
245
+ " and for Indian context: https://www.amfiindia.com (Investor Education section)")
246
+
247
+
248
+ def provide_general_financial_tips(lifecycle_stage, monthly_savings, original_salary):
249
+ """Provides general financial tips."""
250
+ print("\n--- General Financial Tips ---")
251
+ tips = []
252
+ if monthly_savings <= 0 and original_salary > 0 :
253
+ tips.append("- Your expenses currently meet or exceed your income. Focus on creating a budget to identify areas to cut back or explore ways to increase your income.")
254
+ elif original_salary > 0:
255
+ savings_rate = (monthly_savings / original_salary) * 100
256
+ tips.append(f"- Your current savings rate is approximately {savings_rate:.1f}%. Aim for at least 20-30% if possible, especially in accumulation stages.")
257
+ if savings_rate < 15:
258
+ tips.append("- Consider reviewing discretionary spending to boost your savings rate.")
259
+
260
+ tips.append("- Build & Maintain an Emergency Fund: Aim for 3-6 months of essential living expenses in a safe, liquid account (e.g., savings account, liquid mutual fund). This is your top priority before aggressive investing.")
261
+ tips.append("- Get Adequately Insured: Ensure you have sufficient health insurance for yourself and your family. If you have dependents, a term life insurance policy is crucial.")
262
+ tips.append("- Invest Regularly & Be Disciplined: Consistency through SIPs (Systematic Investment Plans) is key, especially for long-term goals. Don't try to time the market.")
263
+ tips.append("- Review Periodically: Revisit your financial plan and investments at least annually, or when major life events occur (marriage, new job, child, etc.).")
264
+ tips.append("- Understand Your Investments: Before investing in any product, understand its risks, costs (like expense ratios for MFs), and how it fits your goals.")
265
+
266
+ if lifecycle_stage == "Student" or lifecycle_stage == "Early Career":
267
+ tips.append("- Focus on upskilling and career growth to increase your earning potential. Your human capital is your biggest asset at this stage.")
268
+ if lifecycle_stage == "Late Career/Pre-Retirement" or lifecycle_stage == "Retired":
269
+ tips.append("- Plan for healthcare expenses in retirement. Consider if your current investments align with generating regular income if needed.")
270
+
271
+ for tip in tips:
272
+ print(f"* {tip}")
273
+
274
+ # --- Main Program Flow (Simplified) ---
275
+ def main_simplified():
276
+ print("╔══════════════════════════════════════════════════════════════╗")
277
+ print("║ Simplified Monthly Savings Allocator ║")
278
+ print(f"║ Today's Date: {datetime.date.today().strftime('%Y-%m-%d')} ║")
279
+ print("╚══════════════════════════════════════════════════════════════╝")
280
+ print("\nWelcome! This tool will help you allocate your monthly savings.")
281
+ print("This is for educational purposes and NOT financial advice.\n")
282
+
283
+ # 1. Get User Profile
284
+ lifecycle, risk, horizon = get_user_profile()
285
+ print(f"\nYour Profile Summary: Lifecycle: {lifecycle}, Risk: {risk.capitalize()}, Horizon: {horizon.capitalize()}")
286
+
287
+ # 2. Get Financial Details
288
+ salary, expenses, savings = get_financial_details()
289
+
290
+ if savings <= 0:
291
+ print("\nSince you don't have a monthly surplus, investment allocation is not applicable now.")
292
+ print("Focus on budgeting and increasing savings first.")
293
+ else:
294
+ # 3. Allocate Savings
295
+ allocations_dict, justification_list = allocate_savings_simplified(savings, risk, horizon, lifecycle)
296
+
297
+ # 4. Display Plan
298
+ display_investment_plan(savings, allocations_dict, justification_list)
299
+
300
+ # 5. Provide General Tips
301
+ provide_general_financial_tips(lifecycle, savings, salary)
302
+
303
+ # 6. Disclaimer
304
+ print("\n\n--- Disclaimer ---")
305
+ print("The information and suggestions provided by this script are for general guidance and educational purposes ONLY.")
306
+ print("This does NOT constitute financial, investment, tax, or legal advice.")
307
+ print("Investment decisions involve risks. Past performance is not indicative of future results.")
308
+ print("Asset allocation models are generalized and may not be suitable for your individual circumstances.")
309
+ print("It is strongly recommended to consult with a SEBI-registered Investment Adviser or a qualified financial professional for personalized advice.")
310
+
311
+ print("\nBudget calculation and investment suggestions complete. Plan wisely!\n")
312
+
313
+ if __name__ == "__main__":
314
+ main_simplified()
app/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # This file makes the 'app' directory a Python package.
app/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (156 Bytes). View file
 
app/__pycache__/admin.cpython-312.pyc ADDED
Binary file (3.61 kB). View file
 
app/__pycache__/auth.cpython-312.pyc ADDED
Binary file (5.6 kB). View file
 
app/__pycache__/crud.cpython-312.pyc ADDED
Binary file (4.75 kB). View file
 
app/__pycache__/database.cpython-312.pyc ADDED
Binary file (1.36 kB). View file
 
app/__pycache__/main.cpython-312.pyc ADDED
Binary file (6.33 kB). View file
 
app/__pycache__/models.cpython-312.pyc ADDED
Binary file (1.74 kB). View file
 
app/__pycache__/schemas.cpython-312.pyc ADDED
Binary file (7.08 kB). View file
 
app/__pycache__/user.cpython-312.pyc ADDED
Binary file (10.8 kB). View file
 
app/admin.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Request, Depends, HTTPException, Query
2
+ from fastapi.responses import HTMLResponse
3
+ from sqlalchemy.orm import Session
4
+ from typing import List
5
+
6
+ from app import models, schemas, crud
7
+ from app.database import get_db
8
+ from app.auth import get_current_admin_user, get_current_active_user # Import both
9
+ from app.main import templates # Import templates from main.py
10
+
11
+ router = APIRouter()
12
+
13
+ # Route to serve the HTML shell for the dashboard
14
+ @router.get("/dashboard", response_class=HTMLResponse)
15
+ async def admin_dashboard_shell(request: Request):
16
+ # This route loads the page structure.
17
+ # Client-side JS will verify admin status and fetch data.
18
+ return templates.TemplateResponse("admin/dashboard.html", {
19
+ "request": request,
20
+ "title": "Admin Dashboard"
21
+ # No user or users_list passed here initially
22
+ })
23
+
24
+ # New API endpoint to fetch dashboard data (protected)
25
+ @router.get("/api/dashboard-data")
26
+ async def get_admin_dashboard_data(
27
+ db: Session = Depends(get_db),
28
+ current_user: models.User = Depends(get_current_admin_user), # Ensures only admin can access
29
+ search_query: str = Query(None, alias="search")
30
+ ):
31
+ if search_query:
32
+ user = crud.get_user_by_email(db, email=search_query)
33
+ users_list = [schemas.User.from_orm(user)] if user else [] # Convert to schema
34
+ else:
35
+ users = crud.get_users(db, limit=100)
36
+ users_list = [schemas.User.from_orm(u) for u in users] # Convert list to schema
37
+
38
+ # Return data needed by the dashboard template's JS
39
+ return {"users_list": users_list, "search_query": search_query, "admin_email": current_user.email}
40
+
41
+
42
+ # Route to view specific user details (protected)
43
+ # This serves an HTML page, so it will also need the client-side auth check pattern
44
+ @router.get("/users/{user_id}", response_class=HTMLResponse)
45
+ async def admin_view_user_details_shell(request: Request, user_id: int):
46
+ # Serve the shell page. JS will fetch details.
47
+ return templates.TemplateResponse("admin/user_details.html", {
48
+ "request": request,
49
+ "user_id": user_id, # Pass user_id for JS to use
50
+ "title": f"User Details" # Generic title initially
51
+ })
52
+
53
+ # New API endpoint to fetch specific user details (protected)
54
+ @router.get("/api/users/{user_id}")
55
+ async def get_admin_user_details_data(
56
+ user_id: int,
57
+ db: Session = Depends(get_db),
58
+ current_admin: models.User = Depends(get_current_admin_user) # Ensure admin access
59
+ ):
60
+ target_user = crud.get_user(db, user_id=user_id)
61
+ if not target_user:
62
+ raise HTTPException(status_code=404, detail="User not found")
63
+
64
+ user_inputs_orm = crud.get_user_data_inputs_by_user_id(db, user_id=user_id, limit=100)
65
+
66
+ # Convert ORM objects to Pydantic schemas for JSON response
67
+ target_user_schema = schemas.User.from_orm(target_user)
68
+ user_inputs_schema = [schemas.UserDataInputResponse.from_orm(item) for item in user_inputs_orm]
69
+
70
+ return {
71
+ "target_user": target_user_schema,
72
+ "user_inputs": user_inputs_schema
73
+ }
app/auth.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, status, Form
2
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
3
+ from sqlalchemy.orm import Session
4
+ from datetime import timedelta
5
+
6
+ from . import crud, models, schemas
7
+ from .core import security
8
+ from .database import get_db
9
+
10
+ router = APIRouter()
11
+
12
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token") # Points to the token login endpoint
13
+
14
+ ACCESS_TOKEN_EXPIRE_MINUTES = security.ACCESS_TOKEN_EXPIRE_MINUTES
15
+
16
+ async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> models.User:
17
+ credentials_exception = HTTPException(
18
+ status_code=status.HTTP_401_UNAUTHORIZED,
19
+ detail="Could not validate credentials",
20
+ headers={"WWW-Authenticate": "Bearer"},
21
+ )
22
+ payload = security.decode_access_token(token)
23
+ if payload is None:
24
+ raise credentials_exception
25
+ email: str = payload.get("sub")
26
+ if email is None:
27
+ raise credentials_exception
28
+
29
+ user = crud.get_user_by_email(db, email=email)
30
+ if user is None:
31
+ raise credentials_exception
32
+ return user
33
+
34
+ async def get_current_active_user(current_user: models.User = Depends(get_current_user)) -> models.User:
35
+ # Add any active/inactive checks here if needed
36
+ # if not current_user.is_active:
37
+ # raise HTTPException(status_code=400, detail="Inactive user")
38
+ return current_user
39
+
40
+ async def get_current_admin_user(current_user: models.User = Depends(get_current_active_user)) -> models.User:
41
+ if not current_user.is_admin:
42
+ raise HTTPException(
43
+ status_code=status.HTTP_403_FORBIDDEN,
44
+ detail="The user doesn't have enough privileges"
45
+ )
46
+ return current_user
47
+
48
+
49
+ @router.post("/signup", response_model=schemas.User)
50
+ async def signup_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
51
+ db_user = crud.get_user_by_email(db, email=user.email)
52
+ if db_user:
53
+ raise HTTPException(status_code=400, detail="Email already registered")
54
+ if user.password != user.confirm_password:
55
+ raise HTTPException(status_code=400, detail="Passwords do not match")
56
+ return crud.create_user(db=db, user=user)
57
+
58
+ @router.post("/token", response_model=schemas.Token)
59
+ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
60
+ user = crud.get_user_by_email(db, email=form_data.username) # OAuth2 form uses 'username' for email
61
+ if not user or not security.verify_password(form_data.password, user.hashed_password):
62
+ raise HTTPException(
63
+ status_code=status.HTTP_401_UNAUTHORIZED,
64
+ detail="Incorrect email or password",
65
+ headers={"WWW-Authenticate": "Bearer"},
66
+ )
67
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
68
+ access_token = security.create_access_token(
69
+ data={"sub": user.email}, expires_delta=access_token_expires
70
+ )
71
+ return {"access_token": access_token, "token_type": "bearer"}
72
+
73
+ # This is a utility endpoint, usually not directly called by frontend for login
74
+ # It's what OAuth2PasswordRequestForm uses internally.
75
+ # The actual login form should post to /auth/token.
76
+ @router.post("/login", response_model=schemas.Token)
77
+ async def login_user_form(email: str = Form(...), password: str = Form(...), db: Session = Depends(get_db)):
78
+ """
79
+ Login endpoint that takes email and password from form data.
80
+ This is an alternative if not using OAuth2PasswordRequestForm directly in frontend JS.
81
+ Frontend forms would post to this.
82
+ """
83
+ user = crud.get_user_by_email(db, email=email)
84
+ if not user or not security.verify_password(password, user.hashed_password):
85
+ raise HTTPException(
86
+ status_code=status.HTTP_401_UNAUTHORIZED,
87
+ detail="Incorrect email or password",
88
+ headers={"WWW-Authenticate": "Bearer"},
89
+ )
90
+ access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
91
+ access_token = security.create_access_token(
92
+ data={"sub": user.email}, expires_delta=access_token_expires
93
+ )
94
+ return {"access_token": access_token, "token_type": "bearer"}
95
+
96
+
97
+ @router.get("/users/me", response_model=schemas.User)
98
+ async def read_users_me(current_user: models.User = Depends(get_current_active_user)):
99
+ return current_user
app/core/__pycache__/security.cpython-312.pyc ADDED
Binary file (2.44 kB). View file
 
app/core/security.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timedelta
2
+ from typing import Optional
3
+ from jose import JWTError, jwt
4
+ from passlib.context import CryptContext
5
+ from pydantic import BaseModel
6
+
7
+ # Password Hashing
8
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
9
+
10
+ # JWT Configuration (Consider moving to a .env file or config management)
11
+ SECRET_KEY = "your-super-secret-key" # CHANGE THIS IN PRODUCTION!
12
+ ALGORITHM = "HS256"
13
+ ACCESS_TOKEN_EXPIRE_MINUTES = 30 # Token validity period
14
+
15
+ def verify_password(plain_password: str, hashed_password: str) -> bool:
16
+ return pwd_context.verify(plain_password, hashed_password)
17
+
18
+ def get_password_hash(password: str) -> str:
19
+ return pwd_context.hash(password)
20
+
21
+ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
22
+ to_encode = data.copy()
23
+ if expires_delta:
24
+ expire = datetime.utcnow() + expires_delta
25
+ else:
26
+ expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
27
+ to_encode.update({"exp": expire})
28
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
29
+ return encoded_jwt
30
+
31
+ def decode_access_token(token: str) -> Optional[dict]:
32
+ try:
33
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
34
+ return payload
35
+ except JWTError:
36
+ return None
37
+
38
+ class TokenPayload(BaseModel):
39
+ sub: Optional[str] = None # 'sub' is standard for subject (e.g., user email or ID)
40
+ exp: Optional[datetime] = None
app/crud.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy.orm import Session
2
+ from . import models, schemas
3
+ from .core.security import get_password_hash
4
+ from typing import List, Optional
5
+
6
+ # --- User CRUD ---
7
+ def get_user(db: Session, user_id: int) -> Optional[models.User]:
8
+ return db.query(models.User).filter(models.User.id == user_id).first()
9
+
10
+ def get_user_by_email(db: Session, email: str) -> Optional[models.User]:
11
+ return db.query(models.User).filter(models.User.email == email).first()
12
+
13
+ def get_users(db: Session, skip: int = 0, limit: int = 100) -> List[models.User]:
14
+ return db.query(models.User).offset(skip).limit(limit).all()
15
+
16
+ def create_user(db: Session, user: schemas.UserCreate) -> models.User:
17
+ hashed_password = get_password_hash(user.password)
18
+ db_user = models.User(email=user.email, hashed_password=hashed_password)
19
+ db.add(db_user)
20
+ db.commit()
21
+ db.refresh(db_user)
22
+ return db_user
23
+
24
+ def create_admin_user(db: Session, user: schemas.UserCreate) -> models.User:
25
+ hashed_password = get_password_hash(user.password)
26
+ db_user = models.User(email=user.email, hashed_password=hashed_password, is_admin=True)
27
+ db.add(db_user)
28
+ db.commit()
29
+ db.refresh(db_user)
30
+ return db_user
31
+
32
+ # --- UserDataInput CRUD ---
33
+ def create_user_data_input(db: Session, item: schemas.UserDataInputCreate, user_id: int) -> models.UserDataInput:
34
+ db_item = models.UserDataInput(**item.dict(), user_id=user_id)
35
+ db.add(db_item)
36
+ db.commit()
37
+ db.refresh(db_item)
38
+ return db_item
39
+
40
+ def get_user_data_inputs_by_user_id(db: Session, user_id: int, skip: int = 0, limit: int = 100) -> List[models.UserDataInput]:
41
+ return db.query(models.UserDataInput).filter(models.UserDataInput.user_id == user_id).offset(skip).limit(limit).all()
42
+
43
+ def get_all_user_data_inputs(db: Session, skip: int = 0, limit: int = 1000) -> List[models.UserDataInput]:
44
+ """
45
+ Fetches all user data inputs, primarily for admin use.
46
+ Consider pagination for very large datasets.
47
+ """
48
+ return db.query(models.UserDataInput).order_by(models.UserDataInput.timestamp.desc()).offset(skip).limit(limit).all()
app/database.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.ext.declarative import declarative_base
3
+ from sqlalchemy.orm import sessionmaker
4
+ import os
5
+
6
+ DATABASE_URL = "sqlite:///./app/ml_models/financial_advisor.db"
7
+
8
+ # Create the directory for the database if it doesn't exist
9
+ os.makedirs(os.path.dirname(DATABASE_URL.split("///")[-1]), exist_ok=True)
10
+
11
+ engine = create_engine(
12
+ DATABASE_URL, connect_args={"check_same_thread": False}
13
+ )
14
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
15
+
16
+ Base = declarative_base()
17
+
18
+ def get_db():
19
+ db = SessionLocal()
20
+ try:
21
+ yield db
22
+ finally:
23
+ db.close()
24
+
25
+ def create_db_and_tables():
26
+ Base.metadata.create_all(bind=engine)
app/main.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Depends, HTTPException, status
2
+ from fastapi.staticfiles import StaticFiles
3
+ from fastapi.templating import Jinja2Templates
4
+ from fastapi.responses import HTMLResponse, RedirectResponse
5
+ from pathlib import Path
6
+ from sqlalchemy.orm import Session
7
+
8
+ from app import models, schemas, crud, auth # app. is used if main.py is outside app/
9
+ from app.database import engine, get_db, create_db_and_tables
10
+ from app.services import chatbot_service, data_service # app.services
11
+ from app.auth import get_current_active_user, get_current_admin_user, oauth2_scheme # app.auth
12
+
13
+ # Determine the base directory of the 'app' package
14
+ BASE_DIR = Path(__file__).resolve().parent
15
+
16
+ app = FastAPI(title="Financial Advisor Chatbot")
17
+
18
+ # Mount static files (CSS, JS, images)
19
+ # The path "static" here is relative to where main.py is.
20
+ # If main.py is in app/, then app/static/
21
+ app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static")
22
+
23
+ # Setup Jinja2 templates
24
+ # If main.py is in app/, then app/templates/
25
+ templates = Jinja2Templates(directory=BASE_DIR / "templates")
26
+
27
+ # --- Event Handlers ---
28
+ @app.on_event("startup")
29
+ async def startup_event():
30
+ create_db_and_tables()
31
+ # Initialize CSV file with headers if it doesn't exist
32
+ data_service.initialize_csv()
33
+ # Load ML models
34
+ chatbot_service.load_models()
35
+ # Create a default admin user if one doesn't exist (optional)
36
+ db = next(get_db()) # Get a DB session
37
+ try:
38
+ admin_email = "admin@example.com"
39
+ admin_password = "adminpassword" # Change this!
40
+
41
+ admin_user_obj = crud.get_user_by_email(db, email=admin_email)
42
+
43
+ if not admin_user_obj:
44
+ print(f"Admin user '{admin_email}' not found, creating...")
45
+ admin_user_schema = schemas.UserCreate(email=admin_email, password=admin_password, confirm_password=admin_password)
46
+ crud.create_admin_user(db, admin_user_schema)
47
+ print(f"Default admin user '{admin_email}' created with password '{admin_password}'. PLEASE CHANGE THE PASSWORD.")
48
+ elif not admin_user_obj.is_admin:
49
+ # If user exists but is not admin, update them (or log warning)
50
+ print(f"User '{admin_email}' exists but is not admin. Updating status...")
51
+ admin_user_obj.is_admin = True
52
+ # If you also want to ensure the password is set, you might reset it here:
53
+ # admin_user_obj.hashed_password = security.get_password_hash(admin_password)
54
+ db.add(admin_user_obj) # Add the existing object to the session to track changes
55
+ db.commit()
56
+ print(f"User '{admin_email}' updated to be an admin.")
57
+ else:
58
+ print(f"Admin user '{admin_email}' already exists.")
59
+
60
+ except Exception as e:
61
+ print(f"Error during admin user setup: {e}")
62
+ finally:
63
+ db.close()
64
+
65
+
66
+ # --- Include Routers ---
67
+ # Assuming auth.py, user.py, admin.py are in the same directory as main.py (i.e. in 'app')
68
+ # If main.py is in the root, these would be from app.auth, app.user, app.admin
69
+ from . import auth as auth_router # Renaming to avoid conflict with 'auth' module
70
+ # We will create user_router and admin_router later
71
+ from . import user as user_router
72
+ from . import admin as admin_router
73
+
74
+ app.include_router(auth_router.router, prefix="/auth", tags=["Authentication"])
75
+ app.include_router(user_router.router, prefix="/user", tags=["User Pages"]) # User specific pages like /user/home
76
+ app.include_router(admin_router.router, prefix="/admin", tags=["Admin Pages"]) # Admin specific pages like /admin/dashboard
77
+
78
+ # --- Root and Basic Page Routes ---
79
+ @app.get("/", response_class=HTMLResponse)
80
+ async def read_root(request: Request):
81
+ # Redirect to login page by default, or to home if logged in (more complex logic for latter)
82
+ return templates.TemplateResponse("auth/login.html", {"request": request, "title": "Login"})
83
+
84
+ @app.get("/signup-page", response_class=HTMLResponse) # Renamed to avoid conflict if auth router has /signup
85
+ async def signup_page_render(request: Request):
86
+ return templates.TemplateResponse("auth/signup.html", {"request": request, "title": "Sign Up"})
87
+
88
+ @app.get("/login-page", response_class=HTMLResponse) # Renamed to avoid conflict if auth router has /login
89
+ async def login_page_render(request: Request):
90
+ return templates.TemplateResponse("auth/login.html", {"request": request, "title": "Login"})
91
+
92
+ # The actual /home and /admin/dashboard routes are now in user.py and admin.py respectively.
93
+
94
+ # --- Chatbot API endpoint ---
95
+ @app.post("/api/chatbot", response_model=schemas.ChatbotInteractionResponse)
96
+ async def api_chatbot_interact(
97
+ request_data: schemas.ChatbotInteractionRequest,
98
+ db: Session = Depends(get_db),
99
+ current_user: models.User = Depends(get_current_active_user)
100
+ ):
101
+ # Process the interaction using the chatbot service
102
+ response = chatbot_service.process_chatbot_interaction(request_data)
103
+
104
+ # If no error in recommendation, save input and potentially output to DB and CSV
105
+ if "error" not in response.recommendation:
106
+ # Save to DB
107
+ user_input_db = schemas.UserDataInputCreate(
108
+ model_type=request_data.model_type,
109
+ input_data=request_data.inputs
110
+ # output_data=response.recommendation # Optionally store output
111
+ )
112
+ crud.create_user_data_input(db=db, item=user_input_db, user_id=current_user.id)
113
+
114
+ # Save to CSV
115
+ data_service.append_data_to_csv(
116
+ user_id=current_user.id,
117
+ user_email=current_user.email,
118
+ model_type=request_data.model_type,
119
+ input_data=request_data.inputs,
120
+ output_data=response.recommendation # Pass the allocation part
121
+ )
122
+
123
+ return response
124
+
125
+ # --- Logout ---
126
+ @app.get("/logout")
127
+ async def logout(request: Request):
128
+ # For token-based auth, logout is primarily a client-side operation (deleting the token).
129
+ # Server-side, you might blacklist the token if using a more complex setup.
130
+ # Here, we'll just redirect to the root path which serves the login page.
131
+ response = RedirectResponse(url="/", status_code=status.HTTP_302_FOUND) # Changed url to "/"
132
+ # Instruct browser to clear any relevant cookies if they were set by server (not typical for Bearer tokens)
133
+ # response.delete_cookie("access_token_cookie") # If you were setting it as a cookie
134
+ return response
135
+
136
+
137
+ # To run the app (example, usually done with uvicorn command line):
138
+ # if __name__ == "__main__":
139
+ # import uvicorn
140
+ # # Note: Uvicorn should typically be run from the project root, not from within app/
141
+ # # e.g., uvicorn app.main:app --reload
142
+ # # For direct execution from app/main.py (less common for FastAPI projects):
143
+ # # uvicorn.run(app, host="0.0.0.0", port=8000)
144
+ # # Better to run with: uvicorn financial_advisor.app.main:app --reload from the project root
145
+ # # Or if main.py is in the root: uvicorn main:app --reload
146
+ # pass
app/ml_models/basic_portfolio_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ae20f4cf2e1cb3f75623f692aa11f947c83529dbe6335a5634365052bd8d15ca
3
+ size 1817898
app/ml_models/enhanced_portfolio_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a780fe27d28ffee39b771925d3746551b30417e09d513f1899a38091e607e5d0
3
+ size 1738333
app/ml_models/enhanced_portfolio_model111.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2560b79a9705ca3674cd96820fd31fd6799dde89b12d4815afc6887111b52746
3
+ size 1691633
app/ml_models/financial_advisor.db ADDED
Binary file (28.7 kB). View file
 
app/models.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, Boolean, DateTime, JSON, ForeignKey
2
+ from sqlalchemy.orm import relationship
3
+ from sqlalchemy.sql import func
4
+ from .database import Base
5
+
6
+ class User(Base):
7
+ __tablename__ = "users"
8
+
9
+ id = Column(Integer, primary_key=True, index=True)
10
+ email = Column(String, unique=True, index=True, nullable=False)
11
+ hashed_password = Column(String, nullable=False)
12
+ is_admin = Column(Boolean, default=False)
13
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
14
+
15
+ inputs = relationship("UserDataInput", back_populates="owner")
16
+
17
+ class UserDataInput(Base):
18
+ __tablename__ = "user_data_inputs"
19
+
20
+ id = Column(Integer, primary_key=True, index=True)
21
+ user_id = Column(Integer, ForeignKey("users.id"))
22
+ model_type = Column(String, index=True) # "base", "enhanced", "rule_based"
23
+ input_data = Column(JSON) # Stores the dictionary of inputs
24
+ # Output data can also be stored if needed, e.g., portfolio allocation
25
+ # output_data = Column(JSON)
26
+ timestamp = Column(DateTime(timezone=True), server_default=func.now())
27
+
28
+ owner = relationship("User", back_populates="inputs")
app/schemas.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, EmailStr, field_validator, model_validator, ValidationInfo
2
+ from typing import Optional, Dict, Any, List
3
+ from datetime import datetime
4
+
5
+ # --- User Schemas ---
6
+ class UserBase(BaseModel):
7
+ email: EmailStr
8
+
9
+ class UserCreate(UserBase):
10
+ password: str
11
+ confirm_password: Optional[str] = None # For signup form
12
+
13
+ @model_validator(mode='after')
14
+ def passwords_match(self) -> 'UserCreate':
15
+ if self.password is not None and self.confirm_password is not None and self.password != self.confirm_password:
16
+ raise ValueError('Passwords do not match')
17
+ # Ensure confirm_password is not passed to the User model if it's just for validation
18
+ # However, our User model doesn't have confirm_password, so it's fine.
19
+ return self
20
+
21
+ class User(UserBase):
22
+ id: int
23
+ is_admin: bool
24
+ created_at: datetime
25
+
26
+ class Config:
27
+ from_attributes = True
28
+
29
+ # --- UserDataInput Schemas ---
30
+ class UserDataInputBase(BaseModel):
31
+ model_type: str
32
+ input_data: Dict[str, Any]
33
+
34
+ class UserDataInputCreate(UserDataInputBase):
35
+ pass
36
+
37
+ class UserDataInputResponse(UserDataInputBase):
38
+ id: int
39
+ user_id: int
40
+ timestamp: datetime
41
+ # output_data: Optional[Dict[str, Any]] = None # If storing output
42
+
43
+ class Config:
44
+ from_attributes = True
45
+
46
+ class UserWithInputs(User):
47
+ inputs: List[UserDataInputResponse] = []
48
+
49
+ class Config: # Added for consistency, though User already has it.
50
+ from_attributes = True
51
+
52
+
53
+ # --- Token Schemas ---
54
+ class Token(BaseModel):
55
+ access_token: str
56
+ token_type: str
57
+
58
+ class TokenData(BaseModel):
59
+ email: Optional[str] = None
60
+
61
+
62
+ # --- Chatbot Input Schemas (Specific to each model for validation) ---
63
+ # These can be more specific if needed, for now using a generic Dict
64
+ # For example, for Base Model:
65
+ class BaseModeInputSchema(BaseModel):
66
+ Salary: float
67
+ Expenses: float
68
+ Savings: float
69
+ Lifecycle_Stage: str
70
+ Risk_Appetite: str
71
+ Investment_Horizon: str
72
+
73
+ @field_validator('Salary', 'Expenses', 'Savings')
74
+ @classmethod
75
+ def check_non_negative_numeric(cls, v: Any, info: ValidationInfo) -> Any:
76
+ if not isinstance(v, (int, float)):
77
+ raise ValueError(f"{info.field_name} must be a number")
78
+ if v < 0:
79
+ raise ValueError(f"{info.field_name} cannot be negative")
80
+ return v
81
+
82
+ class Config:
83
+ from_attributes = True
84
+
85
+
86
+ class EnhancedModelInputSchema(BaseModel):
87
+ Profession: str
88
+ City: str
89
+ Salary: float
90
+ Expenses: float
91
+ Savings: float
92
+ Lifecycle_Stage: str
93
+ Risk_Appetite: str
94
+ Investment_Horizon: str
95
+
96
+ @field_validator('Salary', 'Expenses', 'Savings')
97
+ @classmethod
98
+ def check_non_negative_numeric(cls, v: Any, info: ValidationInfo) -> Any:
99
+ if not isinstance(v, (int, float)):
100
+ raise ValueError(f"{info.field_name} must be a number")
101
+ if v < 0:
102
+ raise ValueError(f"{info.field_name} cannot be negative")
103
+ return v
104
+
105
+ class Config:
106
+ from_attributes = True
107
+
108
+ class RuleBasedModelInputSchema(BaseModel):
109
+ Lifecycle_Stage: str
110
+ Risk_Appetite: str
111
+ Investment_Horizon: str
112
+ Annual_Salary_Package: float
113
+ Monthly_In_hand_Salary: float
114
+ Total_Monthly_Expenses: float
115
+
116
+ @field_validator('Annual_Salary_Package', 'Monthly_In_hand_Salary', 'Total_Monthly_Expenses')
117
+ @classmethod
118
+ def check_non_negative_numeric(cls, v: Any, info: ValidationInfo) -> Any:
119
+ if not isinstance(v, (int, float)):
120
+ raise ValueError(f"{info.field_name} must be a number")
121
+ if v < 0:
122
+ raise ValueError(f"{info.field_name} cannot be negative")
123
+ return v
124
+
125
+ class Config:
126
+ from_attributes = True
127
+
128
+ # For the chatbot interaction, the form will likely submit a dictionary.
129
+ # The specific schema can be used within the service layer before passing to the model.
130
+ class ChatbotInteractionRequest(BaseModel):
131
+ model_type: str # "base", "enhanced", "rule_based"
132
+ inputs: Dict[str, Any]
133
+
134
+ class ChatbotInteractionResponse(BaseModel):
135
+ model_type: str
136
+ user_inputs: Dict[str, Any]
137
+ recommendation: Dict[str, Any] # e.g., portfolio allocation
138
+ justification: Optional[List[str]] = None # For rule-based
139
+ tips: Optional[List[str]] = None # For rule-based
app/services/__pycache__/chatbot_service.cpython-312.pyc ADDED
Binary file (13.5 kB). View file
 
app/services/__pycache__/data_service.cpython-312.pyc ADDED
Binary file (5.82 kB). View file
 
app/services/chatbot_service.py ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ import pandas as pd
3
+ import os
4
+ from typing import Dict, Any, Tuple, List, Optional
5
+ from app import schemas # Assuming schemas.py is in the same 'app' directory
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # --- Model Loading ---
11
+ # Define paths to the model files, assuming they are in app/ml_models/
12
+ MODEL_DIR = os.path.join(os.path.dirname(__file__), "..", "ml_models") # app/ml_models
13
+ BASE_MODEL_PATH = os.path.join(MODEL_DIR, "basic_portfolio_model.pkl")
14
+ ENHANCED_MODEL_PATH = os.path.join(MODEL_DIR, "enhanced_portfolio_model.pkl")
15
+
16
+ # Ensure the ml_models directory exists
17
+ os.makedirs(MODEL_DIR, exist_ok=True)
18
+
19
+ # Placeholder for loaded models
20
+ base_model_pipeline = None
21
+ enhanced_model_pipeline = None
22
+
23
+ def load_models():
24
+ global base_model_pipeline, enhanced_model_pipeline
25
+ try:
26
+ if os.path.exists(BASE_MODEL_PATH):
27
+ base_model_pipeline = joblib.load(BASE_MODEL_PATH)
28
+ logger.info("Base model loaded successfully.")
29
+ else:
30
+ logger.error(f"Base model file not found at {BASE_MODEL_PATH}")
31
+
32
+ if os.path.exists(ENHANCED_MODEL_PATH):
33
+ enhanced_model_pipeline = joblib.load(ENHANCED_MODEL_PATH)
34
+ logger.info("Enhanced model loaded successfully.")
35
+ else:
36
+ logger.error(f"Enhanced model file not found at {ENHANCED_MODEL_PATH}")
37
+ except Exception as e:
38
+ logger.error(f"Error loading ML models: {e}")
39
+
40
+ # Call load_models when this module is imported so they are ready.
41
+ # load_models() # Will be called from main.py or an init step
42
+
43
+ # --- Base Model Prediction ---
44
+ def predict_base_model(input_data: schemas.BaseModeInputSchema) -> Dict[str, float]:
45
+ if not base_model_pipeline:
46
+ logger.error("Base model not loaded. Cannot predict.")
47
+ # Consider raising an exception or returning an error state
48
+ return {"error": "Base model not available"}
49
+
50
+ try:
51
+ data = input_data.dict()
52
+
53
+ # Convert categorical features using mappings from the loaded pipeline
54
+ # These mappings should exist in the .pkl file if saved correctly
55
+ processed_data = data.copy()
56
+ processed_data['Lifecycle_Stage'] = base_model_pipeline['mappings']['lifecycle'][data['Lifecycle_Stage']]
57
+ processed_data['Risk_Appetite'] = base_model_pipeline['mappings']['risk'][data['Risk_Appetite']]
58
+ processed_data['Investment_Horizon'] = base_model_pipeline['mappings']['horizon'][data['Investment_Horizon']]
59
+
60
+ # Create feature array in correct order (must match training)
61
+ # Order from Reference/basemodel.py: Salary, Expenses, Savings, Lifecycle Stage, Risk Appetite, Investment Horizon
62
+ X = [
63
+ processed_data['Salary'],
64
+ processed_data['Expenses'],
65
+ processed_data['Savings'],
66
+ processed_data['Lifecycle_Stage'], # This is now numerical
67
+ processed_data['Risk_Appetite'], # This is now numerical
68
+ processed_data['Investment_Horizon'] # This is now numerical
69
+ ]
70
+
71
+ X_scaled = base_model_pipeline['scaler'].transform([X])
72
+ pred = base_model_pipeline['model'].predict(X_scaled)[0]
73
+
74
+ total = pred.sum()
75
+ if total == 0: # Avoid division by zero
76
+ return {'Equity': 0, 'Debt': 0, 'Gold': 0, 'FD/Cash': 0, "error": "Prediction resulted in zero total"}
77
+
78
+ final_allocation = {
79
+ 'Equity': round((pred[0]/total)*100, 1),
80
+ 'Debt': round((pred[1]/total)*100, 1),
81
+ 'Gold': round((pred[2]/total)*100, 1),
82
+ 'FD/Cash': round((pred[3]/total)*100, 1)
83
+ }
84
+ return final_allocation
85
+ except KeyError as e:
86
+ logger.error(f"KeyError during base model prediction: {e}. Check mappings in pipeline or input data keys.")
87
+ return {"error": f"Missing data or incorrect mapping for {e}"}
88
+ except Exception as e:
89
+ logger.error(f"Error in base model prediction: {e}")
90
+ return {"error": str(e)}
91
+
92
+
93
+ # --- Enhanced Model Prediction ---
94
+ def predict_enhanced_model(input_data: schemas.EnhancedModelInputSchema) -> Dict[str, float]:
95
+ if not enhanced_model_pipeline:
96
+ logger.error("Enhanced model not loaded. Cannot predict.")
97
+ return {"error": "Enhanced model not available"}
98
+
99
+ try:
100
+ data = input_data.dict()
101
+ processed_data = data.copy()
102
+
103
+ # Convert categorical features
104
+ processed_data['Profession'] = enhanced_model_pipeline['profession_encoder'].transform([data['Profession']])[0]
105
+ processed_data['City'] = enhanced_model_pipeline['city_encoder'].transform([data['City']])[0]
106
+ processed_data['Lifecycle_Stage'] = enhanced_model_pipeline['mappings']['lifecycle'][data['Lifecycle_Stage']]
107
+ processed_data['Risk_Appetite'] = enhanced_model_pipeline['mappings']['risk'][data['Risk_Appetite']]
108
+ processed_data['Investment_Horizon'] = enhanced_model_pipeline['mappings']['horizon'][data['Investment_Horizon']]
109
+
110
+ # Order from Reference/enhancedmodel.py:
111
+ # ['Profession', 'City', 'Salary', 'Expenses', 'Savings', 'Lifecycle Stage', 'Risk Appetite', 'Investment Horizon']
112
+ X = [
113
+ processed_data['Profession'],
114
+ processed_data['City'],
115
+ processed_data['Salary'],
116
+ processed_data['Expenses'],
117
+ processed_data['Savings'],
118
+ processed_data['Lifecycle_Stage'],
119
+ processed_data['Risk_Appetite'],
120
+ processed_data['Investment_Horizon']
121
+ ]
122
+
123
+ X_scaled = enhanced_model_pipeline['scaler'].transform([X])
124
+ pred = enhanced_model_pipeline['model'].predict(X_scaled)[0]
125
+
126
+ total = pred.sum()
127
+ if total == 0:
128
+ return {'Equity': 0, 'Debt': 0, 'Gold': 0, 'FD/Cash': 0, "error": "Prediction resulted in zero total"}
129
+
130
+ final_allocation = {
131
+ 'Equity': round((pred[0]/total)*100, 1),
132
+ 'Debt': round((pred[1]/total)*100, 1),
133
+ 'Gold': round((pred[2]/total)*100, 1),
134
+ 'FD/Cash': round((pred[3]/total)*100, 1)
135
+ }
136
+ return final_allocation
137
+ except KeyError as e:
138
+ logger.error(f"KeyError during enhanced model prediction: {e}. Check mappings/encoders in pipeline or input data keys.")
139
+ return {"error": f"Missing data or incorrect mapping/encoding for {e}"}
140
+ except Exception as e:
141
+ logger.error(f"Error in enhanced model prediction: {e}")
142
+ return {"error": str(e)}
143
+
144
+ # --- Rule-Based Model Logic ---
145
+ # (Adapted from Reference/rulebasedmodel.py)
146
+
147
+ def _allocate_savings_rule_based(
148
+ monthly_savings: float,
149
+ risk_profile: str,
150
+ horizon: str,
151
+ lifecycle: str
152
+ ) -> Tuple[Dict[str, float], List[str]]:
153
+ allocations = {}
154
+ justification_points = [
155
+ f"This allocation considers your {risk_profile} risk profile, {horizon} horizon, and {lifecycle} stage."
156
+ ]
157
+ risk_profile = risk_profile.lower() # Ensure consistency
158
+ horizon = horizon.lower()
159
+ # Lifecycle stage is already in a good format from schema e.g. "Early Career"
160
+
161
+ if risk_profile == "low":
162
+ allocations = {
163
+ "Fixed Deposits / Recurring Deposits": 0.35,
164
+ "Debt Mutual Funds (Liquid/Short Duration)": 0.30,
165
+ "Gold (SGBs/ETFs)": 0.10,
166
+ "Equity MFs (Large Cap/Index Funds)": 0.15,
167
+ "Cash / Savings Account": 0.10
168
+ }
169
+ justification_points.append("- Focus on capital preservation and stable returns.")
170
+ elif risk_profile == "medium":
171
+ allocations = {
172
+ "Equity MFs (Diversified - Large & Mid Cap/Flexi Cap)": 0.45,
173
+ "Debt Mutual Funds": 0.25,
174
+ "Fixed Deposits / PPF": 0.10,
175
+ "Gold (SGBs/ETFs)": 0.10,
176
+ "Equity MFs (International)": 0.05,
177
+ "Cash / Savings Account": 0.05
178
+ }
179
+ justification_points.append("- Aims for a balance between growth and stability.")
180
+ elif risk_profile == "high":
181
+ allocations = {
182
+ "Equity MFs (Aggressive Growth - Mid/Small Cap, Thematic)": 0.60,
183
+ "Equity MFs (International)": 0.15,
184
+ "Direct Stocks (If experienced)": 0.10,
185
+ "Debt Mutual Funds (Strategic)": 0.05,
186
+ "Gold (SGBs/ETFs - Tactical)": 0.05,
187
+ "Alternative Inv. (REITs/InvITs)": 0.05
188
+ }
189
+ justification_points.append("- Focuses on maximizing long-term growth potential.")
190
+
191
+ # Normalize percentages (simplified for brevity, original script had more robust normalization)
192
+ current_total_percentage = sum(allocations.values())
193
+ if abs(current_total_percentage - 1.0) > 0.001 and current_total_percentage != 0:
194
+ factor = 1.0 / current_total_percentage
195
+ allocations = {k: round(v * factor, 3) for k, v in allocations.items()}
196
+ # Adjust last item to make sum exactly 1.0 if needed due to rounding
197
+ sum_val = sum(allocations.values())
198
+ if sum_val != 1.0 and allocations:
199
+ key_to_adjust = list(allocations.keys())[-1]
200
+ allocations[key_to_adjust] += (1.0 - sum_val)
201
+ allocations[key_to_adjust] = round(allocations[key_to_adjust], 3)
202
+
203
+
204
+ # Convert percentage values to actual amounts based on monthly_savings
205
+ # The chatbot response schema expects percentages for recommendation, so we return percentages.
206
+ # The display logic in the template can calculate amounts.
207
+
208
+ # For the ChatbotInteractionResponse, recommendation should be percentages
209
+ # Let's ensure the values are percentages (0 to 100)
210
+ final_percentage_allocations = {k: round(v * 100, 1) for k,v in allocations.items()}
211
+
212
+ return final_percentage_allocations, justification_points
213
+
214
+
215
+ def _get_general_financial_tips(lifecycle_stage: str, monthly_savings: float, monthly_in_hand_salary: float) -> List[str]:
216
+ tips = []
217
+ if monthly_in_hand_salary > 0: # Avoid division by zero if salary is 0
218
+ if monthly_savings <= 0 :
219
+ tips.append("- Your expenses currently meet or exceed your income. Focus on creating a budget.")
220
+ else:
221
+ savings_rate = (monthly_savings / monthly_in_hand_salary) * 100
222
+ tips.append(f"- Your current savings rate is approximately {savings_rate:.1f}%. Aim for at least 20-30%.")
223
+ else: # No salary info or zero salary
224
+ if monthly_savings <= 0:
225
+ tips.append("- Your expenses currently meet or exceed your income. Focus on creating a budget.")
226
+
227
+
228
+ tips.extend([
229
+ "- Build & Maintain an Emergency Fund: Aim for 3-6 months of essential living expenses.",
230
+ "- Get Adequately Insured: Health and Term Life Insurance are crucial.",
231
+ "- Invest Regularly & Be Disciplined (e.g. SIPs).",
232
+ "- Review Periodically: Revisit your financial plan annually or on major life events.",
233
+ "- Understand Your Investments: Know the risks and costs."
234
+ ])
235
+ if lifecycle_stage == "Student" or lifecycle_stage == "Early Career":
236
+ tips.append("- Focus on upskilling and career growth.")
237
+ if lifecycle_stage == "Late Career/Pre-Retirement" or lifecycle_stage == "Retired": # Assuming "Retired" is a lifecycle stage
238
+ tips.append("- Plan for healthcare expenses in retirement.")
239
+ return tips
240
+
241
+ def predict_rule_based_model(input_data: schemas.RuleBasedModelInputSchema) -> Tuple[Dict[str, float], List[str], List[str]]:
242
+ data = input_data.dict()
243
+
244
+ monthly_savings = data['Monthly_In_hand_Salary'] - data['Total_Monthly_Expenses']
245
+
246
+ if monthly_savings <= 0:
247
+ allocation = {"message": "Savings are zero or negative. Focus on budgeting first."}
248
+ justification = ["Investment allocation is not applicable without positive savings."]
249
+ tips = _get_general_financial_tips(data['Lifecycle_Stage'], monthly_savings, data['Monthly_In_hand_Salary'])
250
+ tips.insert(0, "Priority: Increase savings or reduce expenses.")
251
+ return allocation, justification, tips
252
+
253
+ allocation, justification = _allocate_savings_rule_based(
254
+ monthly_savings,
255
+ data['Risk_Appetite'],
256
+ data['Investment_Horizon'],
257
+ data['Lifecycle_Stage']
258
+ )
259
+ tips = _get_general_financial_tips(data['Lifecycle_Stage'], monthly_savings, data['Monthly_In_hand_Salary'])
260
+
261
+ return allocation, justification, tips
262
+
263
+
264
+ # --- Main Chatbot Interaction Logic ---
265
+ def process_chatbot_interaction(
266
+ request: schemas.ChatbotInteractionRequest
267
+ ) -> schemas.ChatbotInteractionResponse:
268
+
269
+ model_type = request.model_type.lower()
270
+ inputs = request.inputs
271
+
272
+ recommendation: Dict[str, Any] = {}
273
+ justification: Optional[List[str]] = None
274
+ tips: Optional[List[str]] = None
275
+
276
+ try:
277
+ if model_type == "base":
278
+ # Validate inputs against BaseModeInputSchema
279
+ validated_inputs = schemas.BaseModeInputSchema(**inputs)
280
+ recommendation = predict_base_model(validated_inputs)
281
+ elif model_type == "enhanced":
282
+ validated_inputs = schemas.EnhancedModelInputSchema(**inputs)
283
+ recommendation = predict_enhanced_model(validated_inputs)
284
+ elif model_type == "rule_based":
285
+ validated_inputs = schemas.RuleBasedModelInputSchema(**inputs)
286
+ recommendation, justification, tips = predict_rule_based_model(validated_inputs)
287
+ else:
288
+ raise ValueError("Invalid model type specified")
289
+
290
+ # Check for errors from prediction functions
291
+ if "error" in recommendation:
292
+ # Propagate the error message
293
+ return schemas.ChatbotInteractionResponse(
294
+ model_type=model_type,
295
+ user_inputs=inputs,
296
+ recommendation=recommendation, # Contains the error message
297
+ justification=None,
298
+ tips=None
299
+ )
300
+
301
+ except Exception as e: # Catch validation errors or other issues
302
+ logger.error(f"Error processing chatbot interaction for {model_type}: {e}")
303
+ recommendation = {"error": f"Failed to process request: {str(e)}"}
304
+
305
+
306
+ return schemas.ChatbotInteractionResponse(
307
+ model_type=model_type,
308
+ user_inputs=inputs, # Return the original inputs for display
309
+ recommendation=recommendation,
310
+ justification=justification,
311
+ tips=tips
312
+ )
app/services/data_service.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import os
3
+ from datetime import datetime
4
+ from typing import Dict, Any, List, Optional
5
+ import logging
6
+
7
+ # Configure logging
8
+ logging.basicConfig(level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # Define the path for the CSV file
12
+ # Ensure this path is correct relative to where the application runs
13
+ # For example, if main.py is in app/, and this service is called from there.
14
+ CSV_FILE_PATH = "user_financial_data.csv"
15
+ # This will create the file in the same directory as main.py if it's in the root,
16
+ # or in the 'app' directory if main.py is in 'app' and this path is used as is.
17
+ # For consistency with the sqlite DB being in the root, let's adjust:
18
+ CSV_FILE_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "user_financial_data.csv")
19
+ # This places it in the project root directory (financial_advisor/)
20
+
21
+ # Define the headers for the CSV file based on 'aboutdata.txt' and additional fields
22
+ # Ensure all possible fields from all models are covered.
23
+ CSV_HEADERS = [
24
+ 'UserID', 'UserEmail', 'ModelType', 'Timestamp',
25
+ 'Name', 'Age', 'City', 'Profession', 'Salary', 'Expenses', 'Savings',
26
+ 'Lifecycle Stage', 'Risk Appetite', 'Investment Horizon',
27
+ 'Annual Salary Package', 'Monthly In-hand Salary', 'Total Monthly Expenses', # For Rule-based
28
+ # Output fields from models (optional, but good for record keeping)
29
+ 'Equity (%)', 'Debt (%)', 'Gold (%)', 'FD/Cash (%)'
30
+ ]
31
+
32
+
33
+ def initialize_csv():
34
+ """Initializes the CSV file with headers if it doesn't exist."""
35
+ # Ensure the directory for the CSV file exists
36
+ os.makedirs(os.path.dirname(CSV_FILE_PATH), exist_ok=True)
37
+
38
+ if not os.path.exists(CSV_FILE_PATH):
39
+ try:
40
+ with open(CSV_FILE_PATH, mode='w', newline='', encoding='utf-8') as file:
41
+ writer = csv.writer(file)
42
+ writer.writerow(CSV_HEADERS)
43
+ logger.info(f"CSV file initialized at {CSV_FILE_PATH}")
44
+ except IOError as e:
45
+ logger.error(f"Error initializing CSV file: {e}")
46
+
47
+ def append_data_to_csv(user_id: int, user_email: str, model_type: str, input_data: Dict[str, Any], output_data: Optional[Dict[str, Any]] = None):
48
+ """
49
+ Appends a new row of data to the CSV file.
50
+ input_data should be a flat dictionary.
51
+ output_data is the portfolio allocation.
52
+ """
53
+ if not os.path.exists(CSV_FILE_PATH):
54
+ initialize_csv()
55
+
56
+ row_data = {header: '' for header in CSV_HEADERS} # Initialize with empty strings
57
+
58
+ # Populate common fields
59
+ row_data['UserID'] = user_id
60
+ row_data['UserEmail'] = user_email
61
+ row_data['ModelType'] = model_type
62
+ row_data['Timestamp'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
63
+
64
+ # Populate fields from input_data
65
+ # Normalize keys from input_data (e.g., 'Lifecycle_Stage' to 'Lifecycle Stage')
66
+ normalized_input_data = {key.replace('_', ' '): value for key, value in input_data.items()}
67
+
68
+ for key, value in normalized_input_data.items():
69
+ if key in row_data:
70
+ row_data[key] = value
71
+ # Handle specific keys that might have different names in input_data
72
+ elif key == "Annual Salary Package" and "Annual_Salary_Package" in normalized_input_data: # from RuleBasedModelInputSchema
73
+ row_data["Annual Salary Package"] = normalized_input_data["Annual_Salary_Package"]
74
+ # Add more specific mappings if needed
75
+
76
+ # Populate fields from output_data (portfolio allocation)
77
+ if output_data:
78
+ for key, value in output_data.items():
79
+ # Ensure the key from output_data matches a header like "Equity (%)"
80
+ header_key = f"{key} (%)"
81
+ if header_key in row_data:
82
+ row_data[header_key] = value
83
+ elif key in row_data: # For keys like 'Equity', 'Debt' if not with '(%)'
84
+ row_data[key] = value
85
+
86
+
87
+ # Ensure the order of data matches CSV_HEADERS
88
+ ordered_row_values = [row_data.get(header, '') for header in CSV_HEADERS]
89
+
90
+ try:
91
+ with open(CSV_FILE_PATH, mode='a', newline='', encoding='utf-8') as file:
92
+ writer = csv.writer(file)
93
+ writer.writerow(ordered_row_values)
94
+ logger.info(f"Data appended to CSV for user {user_email}, model {model_type}")
95
+ except IOError as e:
96
+ logger.error(f"Error appending data to CSV: {e}")
97
+ except Exception as e:
98
+ logger.error(f"An unexpected error occurred while writing to CSV: {e}")
99
+
100
+ # Example usage (for testing this module independently):
101
+ if __name__ == "__main__":
102
+ initialize_csv()
103
+
104
+ # Test data
105
+ sample_input_base = {
106
+ 'Salary': 60000, 'Expenses': 40000, 'Savings': 20000,
107
+ 'Lifecycle_Stage': 'Early Career', 'Risk_Appetite': 'Medium', 'Investment_Horizon': 'Long-term'
108
+ }
109
+ sample_output = {'Equity': 50, 'Debt': 30, 'Gold': 10, 'FD/Cash': 10}
110
+ append_data_to_csv(1, "test@example.com", "base", sample_input_base, sample_output)
111
+
112
+ sample_input_enhanced = {
113
+ 'Profession': 'Engineer', 'City': 'Metropolis',
114
+ 'Salary': 120000, 'Expenses': 70000, 'Savings': 50000,
115
+ 'Lifecycle_Stage': 'Mid-Career', 'Risk_Appetite': 'High', 'Investment_Horizon': 'Long-term'
116
+ }
117
+ append_data_to_csv(2, "another@example.com", "enhanced", sample_input_enhanced, sample_output)
118
+
119
+ sample_input_rule = {
120
+ 'Lifecycle_Stage': 'Late Career',
121
+ 'Risk_Appetite': 'Low',
122
+ 'Investment_Horizon': 'Short-term',
123
+ 'Annual_Salary_Package': 1000000,
124
+ 'Monthly_In_hand_Salary': 60000,
125
+ 'Total_Monthly_Expenses': 40000
126
+ }
127
+ append_data_to_csv(3, "ruleuser@example.com", "rule_based", sample_input_rule, sample_output)
128
+
129
+ logger.info(f"Test data written to {CSV_FILE_PATH}")
app/static/css/custom_styles.css ADDED
File without changes
app/static/css/style.css ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
3
+ background-color: #f8f9fa; /* Light grey background */
4
+ display: flex;
5
+ flex-direction: column;
6
+ min-height: 100vh;
7
+ }
8
+
9
+ .container {
10
+ flex: 1;
11
+ }
12
+
13
+ .navbar {
14
+ box-shadow: 0 2px 4px rgba(0,0,0,.1);
15
+ }
16
+
17
+ .card {
18
+ border: none; /* Softer look, rely on shadow */
19
+ transition: transform .2s ease-in-out, box-shadow .2s ease-in-out;
20
+ }
21
+
22
+ .card:hover {
23
+ transform: translateY(-5px);
24
+ box-shadow: 0 0.5rem 1rem rgba(0,0,0,.15) !important;
25
+ }
26
+
27
+ .card-header {
28
+ font-weight: 500;
29
+ }
30
+
31
+ .btn-primary {
32
+ background-color: #007bff;
33
+ border-color: #007bff;
34
+ }
35
+ .btn-primary:hover {
36
+ background-color: #0056b3;
37
+ border-color: #0056b3;
38
+ }
39
+
40
+ .btn-success {
41
+ background-color: #28a745;
42
+ border-color: #28a745;
43
+ }
44
+ .btn-success:hover {
45
+ background-color: #1e7e34;
46
+ border-color: #1e7e34;
47
+ }
48
+
49
+ .btn-info {
50
+ background-color: #17a2b8;
51
+ border-color: #17a2b8;
52
+ }
53
+ .btn-info:hover {
54
+ background-color: #117a8b;
55
+ border-color: #117a8b;
56
+ }
57
+
58
+
59
+ /* Specific to chatbot results for better visual separation */
60
+ #recommendationDetails ul .list-group-item span.badge {
61
+ font-size: 0.9em;
62
+ }
63
+
64
+ /* Footer styling */
65
+ footer.bg-light {
66
+ background-color: #e9ecef !important; /* A slightly different shade for footer */
67
+ padding-top: 1rem;
68
+ padding-bottom: 1rem;
69
+ }
70
+
71
+ /* Responsive adjustments */
72
+ @media (max-width: 768px) {
73
+ .display-4 {
74
+ font-size: 2.5rem;
75
+ }
76
+ h1 {
77
+ font-size: 1.75rem;
78
+ }
79
+ }
80
+
81
+ /* Ensure body takes full height for sticky footer */
82
+ html, body {
83
+ height: 100%;
84
+ }
85
+
86
+ /* --- Chatbot Interface Styles --- */
87
+ #chatbox {
88
+ height: 400px;
89
+ overflow-y: auto;
90
+ border: 1px solid #ccc;
91
+ padding: 10px;
92
+ margin-bottom: 15px;
93
+ background-color: #f9f9f9;
94
+ border-radius: 5px;
95
+ }
96
+ .chat-message { margin-bottom: 10px; }
97
+ .bot-message { text-align: left; }
98
+ .user-message { text-align: right; }
99
+ .message-bubble {
100
+ display: inline-block;
101
+ padding: 8px 12px;
102
+ border-radius: 15px;
103
+ max-width: 80%;
104
+ word-wrap: break-word; /* Ensure long words break */
105
+ }
106
+ .bot-message .message-bubble { background-color: #e9ecef; color: #333; }
107
+ .user-message .message-bubble { background-color: #0d6efd; color: white; }
108
+ #userInputArea button.option-button { margin: 3px; }
109
+ #userInputArea { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; } /* Added wrap */
110
+ #userInputArea input, #userInputArea select { flex-grow: 1; min-width: 150px; } /* Added min-width */
111
+ #optionsContainer { display: flex; flex-wrap: wrap; gap: 5px; } /* Styling for button container */
112
+ .spinner-border-sm { width: 1rem; height: 1rem; }
app/static/js/script.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global JavaScript functions can be added here.
2
+
3
+ document.addEventListener('DOMContentLoaded', function () {
4
+ // Example: Add a class to the body to indicate JS is enabled
5
+ document.body.classList.add('js-enabled');
6
+
7
+ // You could centralize common functions here if needed,
8
+ // for example, a function to make API calls with error handling.
9
+ // const apiRequest = async (url, method, body = null, headers = {}) => { ... }
10
+
11
+ const logoutButton = document.getElementById('logoutButton');
12
+ if (logoutButton) {
13
+ logoutButton.addEventListener('click', function (event) {
14
+ event.preventDefault(); // Prevent default anchor action
15
+
16
+ localStorage.removeItem('accessToken');
17
+ localStorage.removeItem('tokenType');
18
+
19
+ // Redirect to the server's /logout endpoint, which then redirects to login page
20
+ // This ensures any server-side session cleanup (if implemented later) could also occur.
21
+ window.location.href = '/logout';
22
+ });
23
+ }
24
+
25
+ // Client-side check to update navbar based on token presence
26
+ // This runs on every page load to set the correct initial state for navbar items.
27
+ const accessToken = localStorage.getItem('accessToken');
28
+ const navWelcomeItem = document.getElementById('navWelcomeItem');
29
+ const navLogoutItem = document.getElementById('navLogoutItem');
30
+ const navLoginItem = document.getElementById('navLoginItem');
31
+ const navSignupItem = document.getElementById('navSignupItem');
32
+
33
+ if (accessToken) {
34
+ // User is logged in (according to localStorage)
35
+ if (navWelcomeItem) navWelcomeItem.style.display = 'block'; // Or 'list-item' if needed
36
+ if (navLogoutItem) navLogoutItem.style.display = 'block';
37
+ if (navLoginItem) navLoginItem.style.display = 'none';
38
+ if (navSignupItem) navSignupItem.style.display = 'none';
39
+
40
+ // Optional: Fetch user email to display in welcome message
41
+ // Could be done here or within specific page scripts like homepage.html
42
+ // fetch('/auth/users/me', { headers: {'Authorization': `Bearer ${accessToken}`} })
43
+ // .then(response => response.ok ? response.json() : Promise.reject('Failed'))
44
+ // .then(data => {
45
+ // const welcomeSpan = document.getElementById('navUserWelcome');
46
+ // if (welcomeSpan) welcomeSpan.textContent = `Welcome, ${data.email}`;
47
+ // })
48
+ // .catch(err => console.error("Error fetching user for navbar:", err));
49
+
50
+ } else {
51
+ // User is logged out
52
+ if (navWelcomeItem) navWelcomeItem.style.display = 'none';
53
+ if (navLogoutItem) navLogoutItem.style.display = 'none';
54
+ if (navLoginItem) navLoginItem.style.display = 'block';
55
+ if (navSignupItem) navSignupItem.style.display = 'block';
56
+ }
57
+ });
58
+
59
+ // Function to get CSRF token if using CSRF protection with forms not handled by FastAPI's default
60
+ // function getCookie(name) { ... }
61
+ // Not strictly needed for this JWT setup unless forms post directly without JS.
app/templates/admin/dashboard.html ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "partials/base.html" %}
2
+
3
+ {% block title %}Admin Dashboard - Financial Advisor{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-5">
7
+ <div class="d-flex justify-content-between align-items-center mb-4">
8
+ <h1>Admin Dashboard</h1>
9
+ <span id="adminWelcomeMessage">Welcome, Admin!</span> {# Placeholder #}
10
+ </div>
11
+ <div id="authErrorMessage" class="alert alert-danger" role="alert" style="display: none;">
12
+ Authentication failed or insufficient privileges. Redirecting...
13
+ </div>
14
+ <div id="loadingMessage" class="alert alert-info" role="alert">
15
+ Loading dashboard data...
16
+ </div>
17
+
18
+ <!-- Search Users -->
19
+ <div id="searchCard" class="card shadow-sm mb-4" style="display: none;">
20
+ <div class="card-body">
21
+ <h5 class="card-title">Search Users</h5>
22
+ <form id="searchForm" method="get"> {# Changed action, will be handled by JS or page reload #}
23
+ <div class="input-group">
24
+ <input type="text" class="form-control" placeholder="Search by user email..." name="search" id="searchInput">
25
+ <button class="btn btn-outline-primary" type="submit">Search</button>
26
+ </div>
27
+ </form>
28
+ </div>
29
+ </div>
30
+
31
+ <!-- Users Table -->
32
+ <div id="usersTableCard" class="card shadow-sm" style="display: none;">
33
+ <div class="card-header bg-secondary text-white">
34
+ <h4 class="mb-0">Registered Users</h4>
35
+ </div>
36
+ <div class="card-body">
37
+ <div class="table-responsive">
38
+ <table class="table table-striped table-hover">
39
+ <thead>
40
+ <tr>
41
+ <th scope="col">ID</th>
42
+ <th scope="col">Email</th>
43
+ <th scope="col">Is Admin?</th>
44
+ <th scope="col">Registered At</th>
45
+ <th scope="col">Actions</th>
46
+ </tr>
47
+ </thead>
48
+ <tbody id="usersTableBody">
49
+ <!-- User rows will be inserted here by JS -->
50
+ </tbody>
51
+ </table>
52
+ </div>
53
+ <p id="noUsersMessage" class="text-muted" style="display: none;"></p>
54
+ </div>
55
+ </div>
56
+
57
+ <!-- Potentially add other admin functionalities here, like viewing all data inputs -->
58
+
59
+ </div>
60
+ {% endblock %}
61
+
62
+ {% block scripts_extra %}
63
+ <script>
64
+ document.addEventListener('DOMContentLoaded', async function () {
65
+ const accessToken = localStorage.getItem('accessToken');
66
+ const loadingMessage = document.getElementById('loadingMessage');
67
+ const authErrorMessage = document.getElementById('authErrorMessage');
68
+ const searchCard = document.getElementById('searchCard');
69
+ const usersTableCard = document.getElementById('usersTableCard');
70
+ const usersTableBody = document.getElementById('usersTableBody');
71
+ const noUsersMessage = document.getElementById('noUsersMessage');
72
+ const adminWelcomeMessage = document.getElementById('adminWelcomeMessage');
73
+ const searchForm = document.getElementById('searchForm');
74
+ const searchInput = document.getElementById('searchInput');
75
+
76
+ // 1. Check Authentication and Admin Status
77
+ if (!accessToken) {
78
+ authErrorMessage.textContent = 'No authentication token found. Redirecting to login...';
79
+ authErrorMessage.style.display = 'block';
80
+ loadingMessage.style.display = 'none';
81
+ setTimeout(() => { window.location.href = "{{ url_for('login_page_render') }}"; }, 2000);
82
+ return;
83
+ }
84
+
85
+ try {
86
+ const userMeResponse = await fetch("{{ url_for('read_users_me') }}", {
87
+ headers: { 'Authorization': `Bearer ${accessToken}` }
88
+ });
89
+
90
+ if (!userMeResponse.ok) {
91
+ throw new Error(`Authentication failed: ${userMeResponse.status}`);
92
+ }
93
+
94
+ const currentUser = await userMeResponse.json();
95
+ if (!currentUser.is_admin) {
96
+ throw new Error('User is not authorized to view this page.');
97
+ }
98
+
99
+ // Update welcome message
100
+ if(adminWelcomeMessage) adminWelcomeMessage.textContent = `Welcome, ${currentUser.email} (Admin)`;
101
+
102
+ // 2. Fetch Dashboard Data (handle search query)
103
+ const urlParams = new URLSearchParams(window.location.search);
104
+ const searchQuery = urlParams.get('search');
105
+ if (searchQuery && searchInput) {
106
+ searchInput.value = searchQuery; // Populate search box if query exists
107
+ }
108
+
109
+ const dataUrl = searchQuery
110
+ ? `/admin/api/dashboard-data?search=${encodeURIComponent(searchQuery)}`
111
+ : "/admin/api/dashboard-data";
112
+
113
+ const dashboardDataResponse = await fetch(dataUrl, {
114
+ headers: { 'Authorization': `Bearer ${accessToken}` }
115
+ });
116
+
117
+ if (!dashboardDataResponse.ok) {
118
+ throw new Error(`Failed to fetch dashboard data: ${dashboardDataResponse.status}`);
119
+ }
120
+
121
+ const data = await dashboardDataResponse.json();
122
+
123
+ // 3. Populate Table
124
+ usersTableBody.innerHTML = ''; // Clear previous results
125
+ if (data.users_list && data.users_list.length > 0) {
126
+ noUsersMessage.style.display = 'none';
127
+ data.users_list.forEach(user => {
128
+ const row = usersTableBody.insertRow();
129
+ row.innerHTML = `
130
+ <th scope="row">${user.id}</th>
131
+ <td>${user.email}</td>
132
+ <td>
133
+ <span class="badge bg-${user.is_admin ? 'success' : 'secondary'}">
134
+ ${user.is_admin ? 'Yes' : 'No'}
135
+ </span>
136
+ </td>
137
+ <td>${new Date(user.created_at).toLocaleString()}</td>
138
+ <td>
139
+ <a href="/admin/users/${user.id}" class="btn btn-sm btn-info">View Details</a>
140
+ </td>
141
+ `;
142
+ // Note: Using hardcoded URL /admin/users/${user.id} as url_for isn't available client-side easily.
143
+ // Ensure this matches the route defined in admin.py for admin_view_user_details_shell
144
+ });
145
+ } else {
146
+ noUsersMessage.textContent = searchQuery ? 'No users found matching your search.' : 'No users registered yet.';
147
+ noUsersMessage.style.display = 'block';
148
+ }
149
+
150
+ // Show content now that data is loaded
151
+ loadingMessage.style.display = 'none';
152
+ searchCard.style.display = 'block';
153
+ usersTableCard.style.display = 'block';
154
+
155
+ } catch (error) {
156
+ console.error('Error loading admin dashboard:', error);
157
+ loadingMessage.style.display = 'none';
158
+ authErrorMessage.textContent = `Error: ${error.message}. Redirecting...`;
159
+ authErrorMessage.style.display = 'block';
160
+ localStorage.removeItem('accessToken'); // Clear token on error
161
+ localStorage.removeItem('tokenType');
162
+ setTimeout(() => { window.location.href = "{{ url_for('login_page_render') }}"; }, 3000);
163
+ }
164
+
165
+ // Handle search form submission (reloads the page with query param)
166
+ if(searchForm) {
167
+ searchForm.addEventListener('submit', function(event) {
168
+ // Default form submission with GET will reload the page with the query param
169
+ // The page load logic above will then handle fetching the filtered data.
170
+ // No preventDefault needed unless doing a fetch-based update.
171
+ });
172
+ }
173
+ });
174
+ </script>
175
+ {% endblock %}
app/templates/admin/user_details.html ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "partials/base.html" %}
2
+
3
+ {% block title %}{{ title }} - Financial Advisor{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-5">
7
+ <div class="d-flex justify-content-between align-items-center mb-4">
8
+ <h1 id="pageTitle">User Details</h1> {# Placeholder #}
9
+ <a href="{{ url_for('admin_dashboard_shell') }}" class="btn btn-secondary">&laquo; Back to Admin Dashboard</a>
10
+ </div>
11
+ <div id="authErrorMessage" class="alert alert-danger" role="alert" style="display: none;">
12
+ Authentication failed or insufficient privileges. Redirecting...
13
+ </div>
14
+ <div id="loadingMessage" class="alert alert-info" role="alert">
15
+ Loading user details...
16
+ </div>
17
+
18
+ <div id="userInfoCard" class="card shadow-sm mb-4" style="display: none;">
19
+ <div class="card-body">
20
+ <h5 class="card-title">User Information</h5>
21
+ <dl class="row" id="userInfoList">
22
+ {# User info will be populated here by JS #}
23
+ </dl>
24
+ </div>
25
+ </div>
26
+
27
+ <div id="userInputsCard" class="card shadow-sm" style="display: none;">
28
+ <div class="card-header bg-info text-white">
29
+ <h4 class="mb-0">Submitted Financial Inputs</h4>
30
+ </div>
31
+ <div class="card-body">
32
+ <div class="accordion" id="userDataAccordion">
33
+ {# Accordion items will be populated here by JS #}
34
+ </div>
35
+ <p id="noInputsMessage" class="text-muted" style="display: none;">This user has not submitted any financial inputs yet.</p>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ {% endblock %}
40
+
41
+ {% block scripts_extra %}
42
+ <script>
43
+ document.addEventListener('DOMContentLoaded', async function () {
44
+ const accessToken = localStorage.getItem('accessToken');
45
+ const loadingMessage = document.getElementById('loadingMessage');
46
+ const authErrorMessage = document.getElementById('authErrorMessage');
47
+ const userInfoCard = document.getElementById('userInfoCard');
48
+ const userInputsCard = document.getElementById('userInputsCard');
49
+ const userInfoList = document.getElementById('userInfoList');
50
+ const userDataAccordion = document.getElementById('userDataAccordion');
51
+ const noInputsMessage = document.getElementById('noInputsMessage');
52
+ const pageTitle = document.getElementById('pageTitle');
53
+
54
+ const targetUserIdStr = "{{ user_id }}"; // Get user_id as string from server context
55
+ const targetUserId = parseInt(targetUserIdStr); // Parse it to integer
56
+
57
+ // 1. Check Authentication and Admin Status
58
+ if (!accessToken || isNaN(targetUserId)) { // Also check if targetUserId is valid
59
+ authErrorMessage.textContent = 'No authentication token found. Redirecting to login...';
60
+ authErrorMessage.style.display = 'block';
61
+ loadingMessage.style.display = 'none';
62
+ setTimeout(() => { window.location.href = "{{ url_for('login_page_render') }}"; }, 2000);
63
+ return;
64
+ }
65
+
66
+ try {
67
+ // Verify admin status first (optional but good practice)
68
+ const userMeResponse = await fetch("{{ url_for('read_users_me') }}", {
69
+ headers: { 'Authorization': `Bearer ${accessToken}` }
70
+ });
71
+ if (!userMeResponse.ok) throw new Error(`Authentication failed: ${userMeResponse.status}`);
72
+ const currentUser = await userMeResponse.json();
73
+ if (!currentUser.is_admin) throw new Error('User is not authorized to view this page.');
74
+
75
+ // 2. Fetch User Details Data
76
+ const detailsUrl = `/admin/api/users/${targetUserId}`;
77
+ const detailsResponse = await fetch(detailsUrl, {
78
+ headers: { 'Authorization': `Bearer ${accessToken}` }
79
+ });
80
+
81
+ if (!detailsResponse.ok) {
82
+ throw new Error(`Failed to fetch user details: ${detailsResponse.status}`);
83
+ }
84
+
85
+ const data = await detailsResponse.json();
86
+ const targetUser = data.target_user;
87
+ const userInputs = data.user_inputs;
88
+
89
+ // 3. Populate Page Content
90
+ loadingMessage.style.display = 'none';
91
+
92
+ // Update Title
93
+ if(pageTitle && targetUser) pageTitle.textContent = `User Details: ${targetUser.email}`;
94
+ document.title = `User Details: ${targetUser.email} - Financial Advisor`; // Update browser tab title
95
+
96
+ // Populate User Info Card
97
+ if (userInfoList && targetUser) {
98
+ userInfoList.innerHTML = `
99
+ <dt class="col-sm-3">User ID:</dt>
100
+ <dd class="col-sm-9">${targetUser.id}</dd>
101
+ <dt class="col-sm-3">Email:</dt>
102
+ <dd class="col-sm-9">${targetUser.email}</dd>
103
+ <dt class="col-sm-3">Admin Status:</dt>
104
+ <dd class="col-sm-9">
105
+ <span class="badge bg-${targetUser.is_admin ? 'success' : 'secondary'}">
106
+ ${targetUser.is_admin ? 'Yes' : 'No'}
107
+ </span>
108
+ </dd>
109
+ <dt class="col-sm-3">Registered At:</dt>
110
+ <dd class="col-sm-9">${new Date(targetUser.created_at).toLocaleString()}</dd>
111
+ `;
112
+ userInfoCard.style.display = 'block';
113
+ }
114
+
115
+ // Populate User Inputs Accordion
116
+ if (userDataAccordion) {
117
+ userDataAccordion.innerHTML = ''; // Clear previous
118
+ if (userInputs && userInputs.length > 0) {
119
+ noInputsMessage.style.display = 'none';
120
+ userInputs.forEach((inputItem, index) => {
121
+ const itemId = `item-${index}`;
122
+ const collapseId = `collapse-${index}`;
123
+ const headingId = `heading-${index}`;
124
+
125
+ let inputHtml = '<ul class="list-group">';
126
+ for (const [key, value] of Object.entries(inputItem.input_data)) {
127
+ inputHtml += `<li class="list-group-item"><strong>${key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}:</strong> ${value}</li>`;
128
+ }
129
+ inputHtml += '</ul>';
130
+
131
+ // Add output data if available and needed
132
+ // let outputHtml = '';
133
+ // if (inputItem.output_data) { ... }
134
+
135
+ const accordionItem = document.createElement('div');
136
+ accordionItem.className = 'accordion-item';
137
+ accordionItem.innerHTML = `
138
+ <h2 class="accordion-header" id="${headingId}">
139
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#${collapseId}" aria-expanded="false" aria-controls="${collapseId}">
140
+ Input #${inputItem.id} - Model: <strong>${inputItem.model_type.charAt(0).toUpperCase() + inputItem.model_type.slice(1)}</strong> - Submitted: ${new Date(inputItem.timestamp).toLocaleString()}
141
+ </button>
142
+ </h2>
143
+ <div id="${collapseId}" class="accordion-collapse collapse" aria-labelledby="${headingId}" data-bs-parent="#userDataAccordion">
144
+ <div class="accordion-body">
145
+ <h5>Input Data:</h5>
146
+ ${inputHtml}
147
+ <!-- Add outputHtml here if implemented -->
148
+ </div>
149
+ </div>
150
+ `; // Removed Jinja2 comment from JS string literal
151
+ userDataAccordion.appendChild(accordionItem);
152
+ });
153
+ } else {
154
+ noInputsMessage.style.display = 'block';
155
+ }
156
+ userInputsCard.style.display = 'block';
157
+ }
158
+
159
+ } catch (error) {
160
+ console.error('Error loading user details:', error);
161
+ loadingMessage.style.display = 'none';
162
+ authErrorMessage.textContent = `Error: ${error.message}. Redirecting...`;
163
+ authErrorMessage.style.display = 'block';
164
+ localStorage.removeItem('accessToken'); // Clear token on error
165
+ localStorage.removeItem('tokenType');
166
+ setTimeout(() => { window.location.href = "{{ url_for('login_page_render') }}"; }, 3000);
167
+ }
168
+ });
169
+ </script>
170
+ {% endblock %}
app/templates/auth/login.html ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "partials/base.html" %}
2
+
3
+ {% block title %}Login - Financial Advisor{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="row justify-content-center">
7
+ <div class="col-md-6 col-lg-4">
8
+ <div class="card shadow-sm">
9
+ <div class="card-body">
10
+ <h2 class="card-title text-center mb-4">Login</h2>
11
+
12
+ {% if request.query_params.get("error") %}
13
+ <div class="alert alert-danger" role="alert">
14
+ {{ request.query_params.get("error") }}
15
+ </div>
16
+ {% endif %}
17
+ {% if request.query_params.get("message") %}
18
+ <div class="alert alert-info" role="alert">
19
+ {{ request.query_params.get("message") }}
20
+ </div>
21
+ {% endif %}
22
+
23
+ <form id="loginForm" method="post">
24
+ <!-- FastAPI's OAuth2PasswordRequestForm expects 'username' and 'password' -->
25
+ <div class="mb-3">
26
+ <label for="email" class="form-label">Email address</label>
27
+ <input type="email" class="form-control" id="email" name="username" required>
28
+ </div>
29
+ <div class="mb-3">
30
+ <label for="password" class="form-label">Password</label>
31
+ <div class="input-group">
32
+ <input type="password" class="form-control" id="password" name="password" required>
33
+ <button class="btn btn-outline-secondary" type="button" id="togglePassword">Show</button>
34
+ </div>
35
+ </div>
36
+ <div class="d-grid">
37
+ <button type="submit" class="btn btn-primary">Login</button>
38
+ </div>
39
+ </form>
40
+ <p class="mt-3 text-center">
41
+ Don't have an account? <a href="{{ url_for('signup_page_render') }}">Sign up here</a>
42
+ </p>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ {% endblock %}
48
+
49
+ {% block scripts_extra %}
50
+ <script>
51
+ document.addEventListener('DOMContentLoaded', function () {
52
+ const loginForm = document.getElementById('loginForm');
53
+ const togglePasswordButton = document.getElementById('togglePassword');
54
+ const passwordInput = document.getElementById('password');
55
+
56
+ if (togglePasswordButton && passwordInput) {
57
+ togglePasswordButton.addEventListener('click', function () {
58
+ const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
59
+ passwordInput.setAttribute('type', type);
60
+ this.textContent = type === 'password' ? 'Show' : 'Hide';
61
+ });
62
+ }
63
+
64
+ if (loginForm) {
65
+ loginForm.addEventListener('submit', async function (event) {
66
+ event.preventDefault();
67
+ const formData = new FormData(loginForm);
68
+
69
+ // We will post to /auth/token as expected by OAuth2PasswordRequestForm
70
+ try {
71
+ const response = await fetch("{{ url_for('login_for_access_token') }}", {
72
+ method: 'POST',
73
+ body: formData // FormData will be correctly encoded as application/x-www-form-urlencoded
74
+ });
75
+
76
+ const result = await response.json();
77
+
78
+ if (response.ok) {
79
+ localStorage.setItem('accessToken', result.access_token);
80
+ localStorage.setItem('tokenType', result.token_type);
81
+
82
+ // Fetch user details to check if admin for redirect
83
+ try {
84
+ const userMeResponse = await fetch("{{ url_for('read_users_me') }}", {
85
+ method: 'GET',
86
+ headers: {
87
+ 'Authorization': `Bearer ${result.access_token}`
88
+ }
89
+ });
90
+ if (userMeResponse.ok) {
91
+ const userData = await userMeResponse.json();
92
+ if (userData.is_admin) {
93
+ window.location.href = "{{ url_for('admin_dashboard_shell') }}"; // Corrected route name
94
+ } else {
95
+ window.location.href = "{{ url_for('user_homepage') }}";
96
+ }
97
+ } else {
98
+ // Fallback to user homepage if /users/me fails, or show error
99
+ console.error("Failed to fetch user details for redirect.");
100
+ window.location.href = "{{ url_for('user_homepage') }}";
101
+ }
102
+ } catch (e) {
103
+ console.error("Error fetching user details:", e);
104
+ window.location.href = "{{ url_for('user_homepage') }}"; // Fallback
105
+ }
106
+ } else {
107
+ // Display error message
108
+ const errorDiv = document.createElement('div');
109
+ errorDiv.className = 'alert alert-danger mt-3';
110
+ errorDiv.textContent = result.detail || 'Login failed. Please check your credentials.';
111
+ // Clear previous errors
112
+ const existingError = loginForm.querySelector('.alert-danger');
113
+ if (existingError) {
114
+ existingError.remove();
115
+ }
116
+ loginForm.prepend(errorDiv);
117
+ }
118
+ } catch (error) {
119
+ console.error('Login error:', error);
120
+ const errorDiv = document.createElement('div');
121
+ errorDiv.className = 'alert alert-danger mt-3';
122
+ errorDiv.textContent = 'An unexpected error occurred. Please try again.';
123
+ const existingError = loginForm.querySelector('.alert-danger');
124
+ if (existingError) {
125
+ existingError.remove();
126
+ }
127
+ loginForm.prepend(errorDiv);
128
+ }
129
+ });
130
+ }
131
+ });
132
+ </script>
133
+ {% endblock %}
app/templates/auth/signup.html ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "partials/base.html" %}
2
+
3
+ {% block title %}Sign Up - Financial Advisor{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="row justify-content-center">
7
+ <div class="col-md-6 col-lg-4">
8
+ <div class="card shadow-sm">
9
+ <div class="card-body">
10
+ <h2 class="card-title text-center mb-4">Create Account</h2>
11
+ <form id="signupForm" method="post">
12
+ <div class="mb-3">
13
+ <label for="email" class="form-label">Email address</label>
14
+ <input type="email" class="form-control" id="email" name="email" required>
15
+ </div>
16
+ <div class="mb-3">
17
+ <label for="password" class="form-label">Password</label>
18
+ <div class="input-group">
19
+ <input type="password" class="form-control" id="password" name="password" required>
20
+ <button class="btn btn-outline-secondary" type="button" id="togglePassword">Show</button>
21
+ </div>
22
+ </div>
23
+ <div class="mb-3">
24
+ <label for="confirm_password" class="form-label">Confirm Password</label>
25
+ <div class="input-group">
26
+ <input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
27
+ <button class="btn btn-outline-secondary" type="button" id="toggleConfirmPassword">Show</button>
28
+ </div>
29
+ </div>
30
+ <div class="d-grid">
31
+ <button type="submit" class="btn btn-primary">Sign Up</button>
32
+ </div>
33
+ </form>
34
+ <p class="mt-3 text-center">
35
+ Already have an account? <a href="{{ url_for('login_page_render') }}">Login here</a>
36
+ </p>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ {% endblock %}
42
+
43
+ {% block scripts_extra %}
44
+ <script>
45
+ document.addEventListener('DOMContentLoaded', function () {
46
+ const signupForm = document.getElementById('signupForm');
47
+
48
+ const togglePasswordButton = document.getElementById('togglePassword');
49
+ const passwordInput = document.getElementById('password');
50
+ const toggleConfirmPasswordButton = document.getElementById('toggleConfirmPassword');
51
+ const confirmPasswordInput = document.getElementById('confirm_password');
52
+
53
+ function setupPasswordToggle(button, input) {
54
+ if (button && input) {
55
+ button.addEventListener('click', function () {
56
+ const type = input.getAttribute('type') === 'password' ? 'text' : 'password';
57
+ input.setAttribute('type', type);
58
+ this.textContent = type === 'password' ? 'Show' : 'Hide';
59
+ });
60
+ }
61
+ }
62
+ setupPasswordToggle(togglePasswordButton, passwordInput);
63
+ setupPasswordToggle(toggleConfirmPasswordButton, confirmPasswordInput);
64
+
65
+ if (signupForm) {
66
+ signupForm.addEventListener('submit', async function (event) {
67
+ event.preventDefault();
68
+
69
+ const email = document.getElementById('email').value;
70
+ const password = passwordInput.value;
71
+ const confirm_password = confirmPasswordInput.value;
72
+
73
+ if (password !== confirm_password) {
74
+ displayError('Passwords do not match.');
75
+ return;
76
+ }
77
+
78
+ const formData = {
79
+ email: email,
80
+ password: password,
81
+ confirm_password: confirm_password
82
+ };
83
+
84
+ try {
85
+ const response = await fetch("{{ url_for('signup_user') }}", { // Endpoint from auth.py
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/json',
89
+ },
90
+ body: JSON.stringify(formData)
91
+ });
92
+
93
+ const result = await response.json();
94
+
95
+ if (response.ok) {
96
+ // Redirect to login page with a success message
97
+ window.location.href = "{{ url_for('login_page_render') }}?message=Signup successful! Please login.";
98
+ } else {
99
+ displayError(result.detail || 'Signup failed. Please try again.');
100
+ }
101
+ } catch (error) {
102
+ console.error('Signup error:', error);
103
+ displayError('An unexpected error occurred. Please try again.');
104
+ }
105
+ });
106
+ }
107
+
108
+ function displayError(message) {
109
+ const errorDiv = document.createElement('div');
110
+ errorDiv.className = 'alert alert-danger mt-3';
111
+ errorDiv.textContent = message;
112
+ // Clear previous errors
113
+ const existingError = signupForm.querySelector('.alert-danger');
114
+ if (existingError) {
115
+ existingError.remove();
116
+ }
117
+ signupForm.prepend(errorDiv);
118
+ }
119
+ });
120
+ </script>
121
+ {% endblock %}
app/templates/partials/base.html ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ title }} - Financial Advisor</title>
7
+ <!-- Bootstrap CSS CDN -->
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <!-- Custom CSS -->
10
+ <link rel="stylesheet" href="{{ url_for('static', path='/css/style.css') }}">
11
+ {% block head_extra %}{% endblock %}
12
+ </head>
13
+ <body>
14
+ {% include 'partials/navbar.html' %}
15
+
16
+ <div class="container mt-4">
17
+ {# Removed get_flashed_messages block as it's not standard in FastAPI/Jinja2 alone #}
18
+ {# Specific pages can handle messages via query params or JS #}
19
+ {% block content %}{% endblock %}
20
+ </div>
21
+
22
+ {% include 'partials/footer.html' %}
23
+
24
+ <!-- Bootstrap JS Bundle CDN (includes Popper) -->
25
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
26
+ <!-- Custom JS -->
27
+ <script src="{{ url_for('static', path='/js/script.js') }}"></script>
28
+ {% block scripts_extra %}{% endblock %}
29
+ </body>
30
+ </html>
app/templates/partials/footer.html ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <footer class="bg-light text-center text-lg-start mt-auto py-3">
2
+ <div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.05);">
3
+ © 2024 Financial Advisor Chatbot. All Rights Reserved.
4
+ <p class="small mt-1">Disclaimer: This tool is for educational purposes only and does not constitute financial advice. Consult with a qualified professional before making investment decisions.</p>
5
+ </div>
6
+ </footer>
app/templates/partials/navbar.html ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
2
+ <div class="container-fluid">
3
+ <a class="navbar-brand" href="{{ url_for('read_root') }}">FinAdvisor</a>
4
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
5
+ <span class="navbar-toggler-icon"></span>
6
+ </button>
7
+ <div class="collapse navbar-collapse" id="navbarNav">
8
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
9
+ {% if request.state.user %} {# Check if user is available in request state (set by middleware or dependency) #}
10
+ <li class="nav-item">
11
+ <a class="nav-link {% if request.url.path == url_for('user_homepage') %}active{% endif %}" href="{{ url_for('user_homepage') }}">Home</a>
12
+ </li>
13
+ <li class="nav-item">
14
+ <a class="nav-link {% if request.url.path == url_for('recommendations_page') %}active{% endif %}" href="{{ url_for('recommendations_page') }}">Market Trends</a>
15
+ </li>
16
+ {% if request.state.user.is_admin %}
17
+ <li class="nav-item">
18
+ <a class="nav-link {% if request.url.path.startswith(url_for('admin_dashboard')) %}active{% endif %}" href="{{ url_for('admin_dashboard') }}">Admin Dashboard</a>
19
+ </li>
20
+ {% endif %}
21
+ {% endif %}
22
+ </ul>
23
+ <ul class="navbar-nav ms-auto">
24
+ {# Logged-in user items - Initially hidden, shown by JS if token exists #}
25
+ <li class="nav-item" id="navWelcomeItem" style="display: none;">
26
+ <span class="navbar-text me-2" id="navUserWelcome">
27
+ Welcome!
28
+ </span>
29
+ </li>
30
+ <li class="nav-item" id="navLogoutItem" style="display: none;">
31
+ <a class="nav-link" href="#" id="logoutButton">Logout</a>
32
+ </li>
33
+
34
+ {# Logged-out user items - Initially shown, hidden by JS if token exists #}
35
+ <li class="nav-item" id="navLoginItem">
36
+ <a class="nav-link {% if request.url.path == url_for('login_page_render') or request.url.path == url_for('read_root') %}active{% endif %}" href="{{ url_for('login_page_render') }}">Login</a>
37
+ </li>
38
+ <li class="nav-item" id="navSignupItem">
39
+ <a class="nav-link {% if request.url.path == url_for('signup_page_render') %}active{% endif %}" href="{{ url_for('signup_page_render') }}">Sign Up</a>
40
+ </li>
41
+ </ul>
42
+ </div>
43
+ </div>
44
+ </nav>
app/templates/user/chatbot.html ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "partials/base.html" %}
2
+
3
+ {% block title %}{{ title }} - Chatbot{% endblock %}
4
+
5
+ {% block head_extra %}
6
+ {# Chat styles moved to static/css/style.css #}
7
+ {% endblock %}
8
+
9
+ {% block content %}
10
+ <div class="container mt-5">
11
+ <div class="row justify-content-center">
12
+ <div class="col-md-8 col-lg-7">
13
+ <div class="card shadow-sm">
14
+ <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
15
+ <h4 class="mb-0">{{ title }}</h4>
16
+ <button id="cancelButton" class="btn btn-sm btn-danger" style="display: none;">Cancel</button>
17
+ </div>
18
+ <div class="card-body">
19
+ <div id="chatbox">
20
+ <!-- Chat messages appear here -->
21
+ </div>
22
+
23
+ <div id="startArea">
24
+ <p>Click "Start" to begin the financial assessment.</p>
25
+ <button id="startButton" class="btn btn-success">Start</button>
26
+ </div>
27
+
28
+ <div id="userInputArea" class="mt-3" style="display: none;">
29
+ {# Input elements will be dynamically added here by JS #}
30
+ <input type="text" id="textInput" class="form-control" placeholder="Type your answer...">
31
+ <div id="optionsContainer"></div> {# For buttons #}
32
+ <button id="sendButton" class="btn btn-primary">Send</button>
33
+ </div>
34
+
35
+ <div id="resultArea" class="mt-4" style="display:none;">
36
+ <h4>Recommendation:</h4>
37
+ <div id="recommendationDetails" class="p-3 bg-light rounded"></div>
38
+ <div id="justificationArea" class="mt-3" style="display:none;">
39
+ <h5>Justification:</h5>
40
+ <ul id="justificationList" class="list-group"></ul>
41
+ </div>
42
+ <div id="tipsArea" class="mt-3" style="display:none;">
43
+ <h5>Financial Tips:</h5>
44
+ <ul id="tipsList" class="list-group"></ul>
45
+ </div>
46
+ <div id="errorArea" class="alert alert-danger mt-3" style="display:none;"></div>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ <div class="mt-3">
51
+ <a href="{{ url_for('user_homepage') }}" class="btn btn-secondary">&laquo; Back to Model Selection</a>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ {# Pass form fields data to JavaScript #}
58
+ <script id="formFieldsData" type="application/json">
59
+ {{ form_fields | tojson | safe }}
60
+ </script>
61
+ {% endblock %}
62
+
63
+ {% block scripts_extra %}
64
+ <script>
65
+ document.addEventListener('DOMContentLoaded', function () {
66
+ // --- DOM Elements ---
67
+ const chatbox = document.getElementById('chatbox');
68
+ const startArea = document.getElementById('startArea');
69
+ const startButton = document.getElementById('startButton');
70
+ const userInputArea = document.getElementById('userInputArea');
71
+ const textInput = document.getElementById('textInput');
72
+ const optionsContainer = document.getElementById('optionsContainer');
73
+ const sendButton = document.getElementById('sendButton');
74
+ const cancelButton = document.getElementById('cancelButton');
75
+ const resultArea = document.getElementById('resultArea');
76
+ const recommendationDetails = document.getElementById('recommendationDetails');
77
+ const justificationArea = document.getElementById('justificationArea');
78
+ const justificationList = document.getElementById('justificationList');
79
+ const tipsArea = document.getElementById('tipsArea');
80
+ const tipsList = document.getElementById('tipsList');
81
+ const errorArea = document.getElementById('errorArea');
82
+
83
+ // --- State Variables ---
84
+ let formFields = [];
85
+ let currentQuestionIndex = -1;
86
+ let collectedAnswers = {};
87
+ let isWaitingForUserInput = false;
88
+ const modelType = "{{ model_type }}"; // Get model type from Jinja2 context
89
+
90
+ // --- Initialization ---
91
+ function initializeChat() {
92
+ try {
93
+ const fieldsDataElement = document.getElementById('formFieldsData');
94
+ if (fieldsDataElement) {
95
+ formFields = JSON.parse(fieldsDataElement.textContent);
96
+ } else {
97
+ console.error("Form fields data not found.");
98
+ addBotMessage("Sorry, I couldn't load the questions. Please try again later.");
99
+ return;
100
+ }
101
+ } catch (e) {
102
+ console.error("Error parsing form fields data:", e);
103
+ addBotMessage("Sorry, there was an error setting up the chat. Please try again later.");
104
+ return;
105
+ }
106
+
107
+ addBotMessage(`Hello! I'm the ${modelType.charAt(0).toUpperCase() + modelType.slice(1)} Model Advisor. Let's get started.`);
108
+ startArea.style.display = 'block';
109
+ userInputArea.style.display = 'none';
110
+ cancelButton.style.display = 'none';
111
+ resultArea.style.display = 'none';
112
+ currentQuestionIndex = -1;
113
+ collectedAnswers = {};
114
+ isWaitingForUserInput = false;
115
+ }
116
+
117
+ // --- Chat Helper Functions ---
118
+ function addMessage(sender, text) {
119
+ const messageDiv = document.createElement('div');
120
+ messageDiv.classList.add('chat-message', sender === 'bot' ? 'bot-message' : 'user-message');
121
+
122
+ const bubble = document.createElement('div');
123
+ bubble.classList.add('message-bubble');
124
+ bubble.textContent = text;
125
+
126
+ messageDiv.appendChild(bubble);
127
+ chatbox.appendChild(messageDiv);
128
+ chatbox.scrollTop = chatbox.scrollHeight; // Auto-scroll
129
+ }
130
+
131
+ function addBotMessage(text) {
132
+ addMessage('bot', text);
133
+ }
134
+
135
+ function addUserMessage(text) {
136
+ addMessage('user', text);
137
+ }
138
+
139
+ // --- Chat Flow Functions ---
140
+ function startChat() {
141
+ startArea.style.display = 'none';
142
+ cancelButton.style.display = 'block';
143
+ currentQuestionIndex = 0;
144
+ collectedAnswers = {}; // Reset answers
145
+ askNextQuestion();
146
+ }
147
+
148
+ function askNextQuestion() {
149
+ if (currentQuestionIndex >= formFields.length) {
150
+ submitAnswers();
151
+ return;
152
+ }
153
+
154
+ const field = formFields[currentQuestionIndex];
155
+ addBotMessage(field.label + (field.required ? "" : " (Optional)")); // Don't add asterisk, just ask
156
+
157
+ setupUserInput(field);
158
+ isWaitingForUserInput = true;
159
+ }
160
+
161
+ function setupUserInput(field) {
162
+ userInputArea.style.display = 'flex';
163
+ textInput.style.display = 'none';
164
+ optionsContainer.innerHTML = ''; // Clear previous options
165
+ optionsContainer.style.display = 'none';
166
+ sendButton.style.display = 'none'; // Hide send button by default
167
+
168
+ textInput.type = field.type === 'number' ? 'number' : 'text';
169
+ textInput.value = ''; // Clear previous input
170
+ textInput.min = field.min !== undefined ? field.min : '';
171
+ textInput.placeholder = `Enter ${field.label}...`;
172
+
173
+ if (field.type === 'select') {
174
+ optionsContainer.style.display = 'block';
175
+ field.options.forEach(option => {
176
+ const button = document.createElement('button');
177
+ button.classList.add('btn', 'btn-outline-secondary', 'option-button');
178
+ button.textContent = option;
179
+ button.type = 'button'; // Prevent form submission
180
+ button.onclick = () => handleUserInput(option);
181
+ optionsContainer.appendChild(button);
182
+ });
183
+ } else {
184
+ textInput.style.display = 'block';
185
+ sendButton.style.display = 'block';
186
+ textInput.focus();
187
+ }
188
+ }
189
+
190
+ function handleUserInput(value) {
191
+ if (!isWaitingForUserInput) return;
192
+
193
+ const field = formFields[currentQuestionIndex];
194
+ let processedValue = value;
195
+
196
+ // --- Client-side Validation ---
197
+ if (field.required && (value === null || value === undefined || value === '')) {
198
+ addBotMessage("This field is required. Please provide a value.");
199
+ return; // Re-ask same question implicitly by not advancing index
200
+ }
201
+
202
+ if (field.type === 'number') {
203
+ processedValue = parseFloat(value);
204
+ if (isNaN(processedValue)) {
205
+ addBotMessage("Please enter a valid number.");
206
+ return;
207
+ }
208
+ if (field.min !== undefined && processedValue < field.min) {
209
+ addBotMessage(`Value cannot be less than ${field.min}.`);
210
+ return;
211
+ }
212
+ // Specific check for Salary/Expenses
213
+ if (field.name === 'Expenses' && (modelType === 'base' || modelType === 'enhanced')) {
214
+ const salary = collectedAnswers['Salary']; // Get previously collected salary
215
+ if (salary !== undefined && processedValue > salary) {
216
+ addBotMessage("Warning: Your monthly expenses exceed your monthly salary. Please review your budget or update the values.");
217
+ // Optional: Force re-entry or ask for confirmation
218
+ // For now, just warn and proceed. A better UX might involve more steps.
219
+ }
220
+ }
221
+ }
222
+
223
+ // --- Store Answer and Proceed ---
224
+ isWaitingForUserInput = false; // Stop accepting input until next question
225
+ userInputArea.style.display = 'none'; // Hide input area while processing/asking next
226
+
227
+ addUserMessage(value); // Show user's valid input in chat
228
+ collectedAnswers[field.name] = processedValue;
229
+
230
+ currentQuestionIndex++;
231
+
232
+ // Add a slight delay before asking next question
233
+ setTimeout(askNextQuestion, 500);
234
+ }
235
+
236
+ async function submitAnswers() {
237
+ addBotMessage("Thanks! Processing your information...");
238
+ cancelButton.style.display = 'none'; // Can't cancel now
239
+ userInputArea.style.display = 'none';
240
+
241
+ const requestPayload = {
242
+ model_type: modelType,
243
+ inputs: collectedAnswers
244
+ };
245
+
246
+ const accessToken = localStorage.getItem('accessToken');
247
+ if (!accessToken) {
248
+ addBotMessage('Authentication error. Please login again.');
249
+ // Redirect?
250
+ return;
251
+ }
252
+
253
+ try {
254
+ const response = await fetch("{{ url_for('api_chatbot_interact') }}", {
255
+ method: 'POST',
256
+ headers: {
257
+ 'Content-Type': 'application/json',
258
+ 'Authorization': `Bearer ${accessToken}`
259
+ },
260
+ body: JSON.stringify(requestPayload)
261
+ });
262
+
263
+ const result = await response.json();
264
+ displayResults(result);
265
+
266
+ } catch (error) {
267
+ console.error('Chatbot submission error:', error);
268
+ displayError('An unexpected error occurred while getting your advice.');
269
+ }
270
+ }
271
+
272
+ function displayResults(result) {
273
+ resultArea.style.display = 'block';
274
+ errorArea.style.display = 'none';
275
+ justificationArea.style.display = 'none';
276
+ tipsArea.style.display = 'none';
277
+ recommendationDetails.innerHTML = '';
278
+ justificationList.innerHTML = '';
279
+ tipsList.innerHTML = '';
280
+
281
+ if (result.recommendation && result.recommendation.error) {
282
+ displayError(`Error: ${result.recommendation.error}`);
283
+ } else if (result.detail) { // Handle FastAPI validation errors etc.
284
+ displayError(`Error: ${result.detail}`);
285
+ } else {
286
+ addBotMessage("Here is your personalized recommendation:");
287
+
288
+ // Display Recommendation
289
+ if (result.recommendation.message) { // For cases like no savings
290
+ recommendationDetails.innerHTML = `<p class="text-info">${result.recommendation.message}</p>`;
291
+ } else {
292
+ const ul = document.createElement('ul');
293
+ ul.className = 'list-group';
294
+ for (const [asset, percentage] of Object.entries(result.recommendation)) {
295
+ const li = document.createElement('li');
296
+ li.className = 'list-group-item d-flex justify-content-between align-items-center';
297
+ li.textContent = asset;
298
+ const span = document.createElement('span');
299
+ span.className = 'badge bg-primary rounded-pill';
300
+ span.textContent = `${percentage}%`;
301
+ li.appendChild(span);
302
+ ul.appendChild(li);
303
+ }
304
+ recommendationDetails.appendChild(ul);
305
+ }
306
+
307
+ // Display Justification (Rule-based)
308
+ if (result.justification && result.justification.length > 0) {
309
+ result.justification.forEach(item => {
310
+ const li = document.createElement('li');
311
+ li.className = 'list-group-item';
312
+ li.textContent = item;
313
+ justificationList.appendChild(li);
314
+ });
315
+ justificationArea.style.display = 'block';
316
+ }
317
+
318
+ // Display Tips (Rule-based)
319
+ if (result.tips && result.tips.length > 0) {
320
+ result.tips.forEach(item => {
321
+ const li = document.createElement('li');
322
+ li.className = 'list-group-item';
323
+ li.textContent = item;
324
+ tipsList.appendChild(li);
325
+ });
326
+ tipsArea.style.display = 'block';
327
+ }
328
+ }
329
+ chatbox.scrollTop = chatbox.scrollHeight; // Scroll to show results
330
+ }
331
+
332
+ function displayError(message) {
333
+ errorArea.textContent = message;
334
+ errorArea.style.display = 'block';
335
+ resultArea.style.display = 'block'; // Show result area to display the error
336
+ recommendationDetails.innerHTML = ''; // Clear any partial results
337
+ justificationArea.style.display = 'none';
338
+ tipsArea.style.display = 'none';
339
+ chatbox.scrollTop = chatbox.scrollHeight;
340
+ }
341
+
342
+ function cancelChat() {
343
+ addBotMessage("Chat cancelled. No data was saved.");
344
+ initializeChat(); // Reset to initial state
345
+ }
346
+
347
+ // --- Event Listeners ---
348
+ startButton.addEventListener('click', startChat);
349
+ cancelButton.addEventListener('click', cancelChat);
350
+ sendButton.addEventListener('click', () => handleUserInput(textInput.value));
351
+ textInput.addEventListener('keypress', function (e) {
352
+ if (e.key === 'Enter') {
353
+ e.preventDefault(); // Prevent form submission if inside a form tag
354
+ handleUserInput(textInput.value);
355
+ }
356
+ });
357
+
358
+ // --- Initial Setup ---
359
+ initializeChat();
360
+ });
361
+ </script>
362
+ {% endblock %}
app/templates/user/homepage.html ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "partials/base.html" %}
2
+
3
+ {% block title %}User Dashboard - Financial Advisor{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-5">
7
+ <div class="row mb-4">
8
+ <div class="col">
9
+ <h1>Welcome, <span id="userEmailPlaceholder"></span>!</h1>
10
+ <p>Choose a financial advisory model to get started.</p>
11
+ </div>
12
+ </div>
13
+
14
+ <div id="modelSelectionCards" class="row row-cols-1 row-cols-md-3 g-4" style="display: none;">
15
+ <div class="col">
16
+ <div class="card h-100 shadow-sm">
17
+ <div class="card-body d-flex flex-column">
18
+ <h5 class="card-title">Base Model Advisor</h5>
19
+ <p class="card-text">Get a fundamental portfolio recommendation based on your salary, expenses, savings, lifecycle stage, risk appetite, and investment horizon.</p>
20
+ <a href="{{ url_for('chatbot_page', model_type='base') }}" class="btn btn-primary mt-auto">Start with Base Model</a>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ <div class="col">
25
+ <div class="card h-100 shadow-sm">
26
+ <div class="card-body d-flex flex-column">
27
+ <h5 class="card-title">Enhanced Model Advisor</h5>
28
+ <p class="card-text">Receive a more detailed portfolio recommendation, incorporating your profession and city in addition to the base model inputs.</p>
29
+ <a href="{{ url_for('chatbot_page', model_type='enhanced') }}" class="btn btn-success mt-auto">Start with Enhanced Model</a>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ <div class="col">
34
+ <div class="card h-100 shadow-sm">
35
+ <div class="card-body d-flex flex-column">
36
+ <h5 class="card-title">Rule-Based Advisor</h5>
37
+ <p class="card-text">Get advice based on a predefined set of financial rules and best practices, considering your profile and financial details.</p>
38
+ <a href="{{ url_for('chatbot_page', model_type='rule_based') }}" class="btn btn-info mt-auto">Start with Rule-Based Model</a>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <div id="quickLinksSection" class="row mt-5" style="display: none;">
45
+ <div class="col">
46
+ <h4>Quick Links</h4>
47
+ <ul class="list-group">
48
+ <li class="list-group-item"><a href="{{ url_for('recommendations_page') }}">View Market Trends & Recommendations</a></li>
49
+ <!-- Add more links here if needed, e.g., to view past interactions -->
50
+ </ul>
51
+ </div>
52
+ </div>
53
+ <div id="loadingMessage" class="alert alert-info" role="alert" style="display: none;">
54
+ Loading user data...
55
+ </div>
56
+ <div id="authErrorMessage" class="alert alert-danger" role="alert" style="display: none;">
57
+ Authentication failed. Redirecting to login...
58
+ </div>
59
+ </div>
60
+ {% endblock %}
61
+
62
+ {% block scripts_extra %}
63
+ <script>
64
+ document.addEventListener('DOMContentLoaded', async function () {
65
+ const userEmailPlaceholder = document.getElementById('userEmailPlaceholder');
66
+ const modelSelectionCards = document.getElementById('modelSelectionCards');
67
+ const quickLinksSection = document.getElementById('quickLinksSection');
68
+ const loadingMessage = document.getElementById('loadingMessage');
69
+ const authErrorMessage = document.getElementById('authErrorMessage');
70
+
71
+ const accessToken = localStorage.getItem('accessToken');
72
+
73
+ if (!accessToken) {
74
+ authErrorMessage.textContent = 'No authentication token found. Redirecting to login...';
75
+ authErrorMessage.style.display = 'block';
76
+ setTimeout(() => {
77
+ window.location.href = "{{ url_for('login_page_render') }}";
78
+ }, 2000);
79
+ return;
80
+ }
81
+
82
+ loadingMessage.style.display = 'block';
83
+
84
+ try {
85
+ const response = await fetch("{{ url_for('read_users_me') }}", { // /auth/users/me
86
+ method: 'GET',
87
+ headers: {
88
+ 'Authorization': `Bearer ${accessToken}`,
89
+ 'Accept': 'application/json'
90
+ }
91
+ });
92
+
93
+ loadingMessage.style.display = 'none';
94
+
95
+ if (response.ok) {
96
+ const userData = await response.json();
97
+ if (userEmailPlaceholder) {
98
+ userEmailPlaceholder.textContent = userData.email;
99
+ }
100
+ // Show content that depends on authentication
101
+ if(modelSelectionCards) modelSelectionCards.style.display = 'flex'; // Or 'block' if that's better for layout
102
+ if(quickLinksSection) quickLinksSection.style.display = 'block';
103
+
104
+ // Update navbar if needed (more complex, requires navbar to have placeholders)
105
+ // For example, if navbar had <span id="navUserEmail"></span>
106
+ // const navUserEmail = document.getElementById('navUserEmail');
107
+ // if (navUserEmail) navUserEmail.textContent = `Welcome, ${userData.email}`;
108
+ // const logoutLink = document.getElementById('navLogoutLink'); // Assuming logout link has an ID
109
+ // if (logoutLink) logoutLink.style.display = 'block';
110
+ // const loginLink = document.getElementById('navLoginLink');
111
+ // if (loginLink) loginLink.style.display = 'none';
112
+
113
+
114
+ } else {
115
+ // Handle 401 or other errors from /auth/users/me
116
+ localStorage.removeItem('accessToken');
117
+ localStorage.removeItem('tokenType');
118
+ authErrorMessage.textContent = `Session expired or invalid. Redirecting to login... (Status: ${response.status})`;
119
+ authErrorMessage.style.display = 'block';
120
+ setTimeout(() => {
121
+ window.location.href = "{{ url_for('login_page_render') }}";
122
+ }, 3000);
123
+ }
124
+ } catch (error) {
125
+ console.error('Error fetching user data:', error);
126
+ loadingMessage.style.display = 'none';
127
+ authErrorMessage.textContent = 'Error loading user data. Redirecting to login...';
128
+ authErrorMessage.style.display = 'block';
129
+ localStorage.removeItem('accessToken');
130
+ localStorage.removeItem('tokenType');
131
+ setTimeout(() => {
132
+ window.location.href = "{{ url_for('login_page_render') }}";
133
+ }, 3000);
134
+ }
135
+ });
136
+ </script>
137
+ {% endblock %}
app/templates/user/recommendations.html ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "partials/base.html" %}
2
+
3
+ {% block title %}Market Trends & Recommendations - Financial Advisor{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container mt-5">
7
+ <div class="row mb-4">
8
+ <div class="col">
9
+ <h1>Market Trends & Recommendations</h1>
10
+ <p>Stay updated with the latest market information and our curated recommendations.</p>
11
+ </div>
12
+ </div>
13
+
14
+ <!-- Current Market Prices -->
15
+ <div class="row mb-4">
16
+ <div class="col-md-6">
17
+ <div class="card shadow-sm">
18
+ <div class="card-header bg-info text-white">
19
+ <h4 class="mb-0">Current Stock Prices</h4>
20
+ </div>
21
+ <div class="card-body">
22
+ <input type="text" id="stockSearch" class="form-control mb-3" placeholder="Search stocks...">
23
+ </div>
24
+ <ul class="list-group list-group-flush" id="stockList">
25
+ <!-- Stock search results will be populated here -->
26
+ </ul>
27
+ <!-- Placeholder for Price Information -->
28
+ <div id="stockPriceInfo" class="card-body" style="display: none;">
29
+ <h5>Price Information:</h5>
30
+ <div id="priceDetails"></div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ <div class="col-md-6 mt-3 mt-md-0">
35
+ <div class="card shadow-sm">
36
+ <div class="card-header bg-warning text-dark">
37
+ <h4 class="mb-0">Gold Price</h4>
38
+ </div>
39
+ <div class="card-body">
40
+ {% if market_data.gold_price %}
41
+ <p class="card-text fs-5">{{ market_data.gold_price }}</p>
42
+ {% else %}
43
+ <p class="card-text">Gold price data not available.</p>
44
+ {% endif %}
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
49
+
50
+ <!-- Recommended Stocks -->
51
+ <div class="row">
52
+ <div class="col">
53
+ <div class="card shadow-sm">
54
+ <div class="card-header bg-success text-white">
55
+ <h4 class="mb-0">Recommended Stocks</h4>
56
+ </div>
57
+ <div class="card-body">
58
+ {% if market_data.recommended_stocks %}
59
+ {% for stock in market_data.recommended_stocks %}
60
+ <div class="mb-3 p-2 border-bottom">
61
+ <h5>{{ stock.name }}</h5>
62
+ <p class="mb-1">{{ stock.reason }}</p>
63
+ </div>
64
+ {% endfor %}
65
+ {% else %}
66
+ <p>No specific stock recommendations at this time.</p>
67
+ {% endif %}
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ <div class="mt-4">
73
+ <a href="{{ url_for('user_homepage') }}" class="btn btn-secondary">&laquo; Back to Homepage</a>
74
+ </div>
75
+ </div>
76
+
77
+ <!-- Fixed Deposits Section -->
78
+ <div class="container mt-5">
79
+ <div class="row mb-4">
80
+ <div class="col">
81
+ <div class="card shadow-sm">
82
+ <div class="card-header bg-primary text-white">
83
+ <h4 class="mb-0">Fixed Deposit (FD) Interest Rates in India</h4>
84
+ </div>
85
+ <div class="table-responsive">
86
+ <table class="table table-hover mb-0">
87
+ <thead class="table-light">
88
+ <tr>
89
+ <th scope="col">Bank Name</th>
90
+ <th scope="col">Highest Interest Rate</th>
91
+ <th scope="col">Tenure</th>
92
+ <th scope="col">Senior Citizen Rate (Up to)</th>
93
+ </tr>
94
+ </thead>
95
+ <tbody>
96
+ <tr>
97
+ <td>Airtel Bank</td>
98
+ <td>9.1%</td>
99
+ <td>36 to 60 months</td>
100
+ <td>Varies (up to 9.1%)</td>
101
+ </tr>
102
+ <tr>
103
+ <td>SBI (State Bank of India)</td>
104
+ <td>7.25%</td>
105
+ <td>2 to 3 years</td>
106
+ <td>Up to 7.40%</td>
107
+ </tr>
108
+ <tr>
109
+ <td>Axis Bank</td>
110
+ <td>7.25%</td>
111
+ <td>15 months to <2 years</td>
112
+ <td>Up to 7.95%</td>
113
+ </tr>
114
+ <tr>
115
+ <td>Indian Post (Post Office)</td>
116
+ <td>7.50%</td>
117
+ <td>5 years (Post Office TD)</td>
118
+ <td>Not specified</td>
119
+ </tr>
120
+ <tr>
121
+ <td>HDFC Bank</td>
122
+ <td>7.20%</td>
123
+ <td>4 years 7 months (55 months)</td>
124
+ <td>Up to 7.75%</td>
125
+ </tr>
126
+ <tr>
127
+ <td>ICICI Bank</td>
128
+ <td>7.10%</td>
129
+ <td>15 months to 2 years</td>
130
+ <td>Up to 7.60%</td>
131
+ </tr>
132
+ <tr>
133
+ <td>Canara Bank</td>
134
+ <td>6.70%</td>
135
+ <td>2 to 3 years</td>
136
+ <td>Up to 7.20%</td>
137
+ </tr>
138
+ <tr>
139
+ <td>PNB (Punjab National Bank)</td>
140
+ <td>7.05%</td>
141
+ <td>300 days</td>
142
+ <td>Up to 7.55%</td>
143
+ </tr>
144
+ </tbody>
145
+ </table>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+
152
+ <script>
153
+ document.addEventListener('DOMContentLoaded', function () {
154
+ const stockSearchInput = document.getElementById('stockSearch');
155
+ const stockList = document.getElementById('stockList');
156
+ const stockPriceInfoDiv = document.getElementById('stockPriceInfo');
157
+ const priceDetailsDiv = document.getElementById('priceDetails');
158
+
159
+ // Clear the initial mock stock list items
160
+ stockList.innerHTML = '';
161
+
162
+ if (stockSearchInput) {
163
+ stockSearchInput.addEventListener('input', async function () {
164
+ const searchTerm = stockSearchInput.value;
165
+ if (searchTerm.length > 1) { // Start searching after 2 characters
166
+ try {
167
+ const response = await fetch(`/user/api/stocks/search?query=${encodeURIComponent(searchTerm)}`);
168
+ if (!response.ok) {
169
+ throw new Error(`HTTP error! status: ${response.status}`);
170
+ }
171
+ const tickers = await response.json();
172
+ displayStockSearchResults(tickers);
173
+ } catch (error) {
174
+ console.error("Error fetching stock search results:", error);
175
+ stockList.innerHTML = '<li class="list-group-item">Error fetching results.</li>';
176
+ }
177
+ } else {
178
+ stockList.innerHTML = ''; // Clear list if search term is too short
179
+ stockPriceInfoDiv.style.display = 'none'; // Hide price info
180
+ }
181
+ });
182
+ }
183
+
184
+ function displayStockSearchResults(tickers) {
185
+ stockList.innerHTML = ''; // Clear previous results
186
+ if (tickers.length > 0) {
187
+ tickers.forEach(ticker => {
188
+ const listItem = document.createElement('li');
189
+ listItem.classList.add('list-group-item', 'stock-item');
190
+ listItem.textContent = ticker;
191
+ listItem.style.cursor = 'pointer'; // Indicate clickable
192
+ listItem.addEventListener('click', () => selectStock(ticker));
193
+ stockList.appendChild(listItem);
194
+ });
195
+ } else {
196
+ stockList.innerHTML = '<li class="list-group-item">No matching stocks found.</li>';
197
+ }
198
+ }
199
+
200
+ async function selectStock(tickerSymbol) {
201
+ stockSearchInput.value = tickerSymbol; // Set input value to selected ticker
202
+ stockList.innerHTML = ''; // Clear search results
203
+ stockPriceInfoDiv.style.display = 'block'; // Show price info section
204
+ priceDetailsDiv.innerHTML = 'Loading price data...'; // Loading message
205
+
206
+ try {
207
+ const response = await fetch(`/user/api/stocks/price/${encodeURIComponent(tickerSymbol)}`);
208
+ if (!response.ok) {
209
+ // Attempt to read error message from response body
210
+ const errorText = await response.text();
211
+ throw new Error(`HTTP error! status: ${response.status} - ${errorText}`);
212
+ }
213
+ const priceData = await response.json();
214
+ displayStockPrice(tickerSymbol, priceData);
215
+ } catch (error) {
216
+ console.error("Error fetching stock price:", error);
217
+ priceDetailsDiv.innerHTML = `Error fetching price for ${tickerSymbol}.`;
218
+ }
219
+ }
220
+
221
+ function displayStockPrice(tickerSymbol, priceData) {
222
+ let html = `<h6>${tickerSymbol}</h6>`;
223
+ for (const label in priceData) {
224
+ const [price, date] = priceData[label];
225
+ if (date) {
226
+ html += `<p>${label} price on ${date}: <strong>${price}</strong></p>`;
227
+ } else {
228
+ html += `<p>${label} price: <strong>${price}</strong></p>`;
229
+ }
230
+ }
231
+ priceDetailsDiv.innerHTML = html;
232
+ }
233
+
234
+ // Initial state: clear the list and hide price info on page load
235
+ stockList.innerHTML = '';
236
+ stockPriceInfoDiv.style.display = 'none';
237
+
238
+ });
239
+ </script>
240
+ {% endblock %}
app/tickers.txt ADDED
@@ -0,0 +1,2099 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 20MICRONS.NS
2
+ 21STCENMGM.NS
3
+ 360ONE.NS
4
+ 3IINFOLTD.NS
5
+ 3MINDIA.NS
6
+ 3PLAND.NS
7
+ 5PAISA.NS
8
+ 63MOONS.NS
9
+ A2ZINFRA.NS
10
+ AAATECH.NS
11
+ AADHARHFC.NS
12
+ AAKASH.NS
13
+ AAREYDRUGS.NS
14
+ AARON.NS
15
+ AARTECH.NS
16
+ AARTIDRUGS.NS
17
+ AARTIIND.NS
18
+ AARTIPHARM.NS
19
+ AARTISURF.NS
20
+ AARVEEDEN.NS
21
+ AARVI.NS
22
+ AAVAS.NS
23
+ ABAN.NS
24
+ ABB.NS
25
+ ABBOTINDIA.NS
26
+ ABCAPITAL.NS
27
+ ABDL.NS
28
+ ABFRL.NS
29
+ ABINFRA.NS
30
+ ABMINTLLTD.NS
31
+ ABREL.NS
32
+ ABSLAMC.NS
33
+ ACC.NS
34
+ ACCELYA.NS
35
+ ACCURACY.NS
36
+ ACE.NS
37
+ ACEINTEG.NS
38
+ ACI.NS
39
+ ACL.NS
40
+ ACLGATI.NS
41
+ ACMESOLAR.NS
42
+ ADANIENSOL.NS
43
+ ADANIENT.NS
44
+ ADANIGREEN.NS
45
+ ADANIPORTS.NS
46
+ ADANIPOWER.NS
47
+ ADFFOODS.NS
48
+ ADL.NS
49
+ ADOR.NS
50
+ ADROITINFO.NS
51
+ ADSL.NS
52
+ ADVANIHOTR.NS
53
+ ADVENZYMES.NS
54
+ AEGISLOG.NS
55
+ AEROFLEX.NS
56
+ AETHER.NS
57
+ AFCONS.NS
58
+ AFFLE.NS
59
+ AFFORDABLE.NS
60
+ AFIL.NS
61
+ AFSL.NS
62
+ AGARIND.NS
63
+ AGARWALEYE.NS
64
+ AGI.NS
65
+ AGIIL.NS
66
+ AGRITECH.NS
67
+ AGROPHOS.NS
68
+ AGSTRA.NS
69
+ AHLADA.NS
70
+ AHLEAST.NS
71
+ AHLUCONT.NS
72
+ AIAENG.NS
73
+ AIIL.NS
74
+ AIRAN.NS
75
+ AIROLAM.NS
76
+ AJANTPHARM.NS
77
+ AJAXENGG.NS
78
+ AJMERA.NS
79
+ AJOONI.NS
80
+ AKASH.NS
81
+ AKG.NS
82
+ AKI.NS
83
+ AKSHAR.NS
84
+ AKSHARCHEM.NS
85
+ AKSHOPTFBR.NS
86
+ AKUMS.NS
87
+ AKZOINDIA.NS
88
+ ALANKIT.NS
89
+ ALBERTDAVD.NS
90
+ ALEMBICLTD.NS
91
+ ALICON.NS
92
+ ALIVUS.NS
93
+ ALKALI.NS
94
+ ALKEM.NS
95
+ ALKYLAMINE.NS
96
+ ALLCARGO.NS
97
+ ALLDIGI.NS
98
+ ALMONDZ.NS
99
+ ALOKINDS.NS
100
+ ALPA.NS
101
+ ALPHAGEO.NS
102
+ ALPSINDUS.NS
103
+ AMBER.NS
104
+ AMBICAAGAR.NS
105
+ AMBIKCO.NS
106
+ AMBUJACEM.NS
107
+ AMDIND.NS
108
+ AMIORG.NS
109
+ AMJLAND.NS
110
+ AMNPLST.NS
111
+ AMRUTANJAN.NS
112
+ ANANDRATHI.NS
113
+ ANANTRAJ.NS
114
+ ANDHRAPAP.NS
115
+ ANDHRSUGAR.NS
116
+ ANGELONE.NS
117
+ ANIKINDS.NS
118
+ ANKITMETAL.NS
119
+ ANMOL.NS
120
+ ANSALAPI.NS
121
+ ANTGRAPHIC.NS
122
+ ANUHPHR.NS
123
+ ANUP.NS
124
+ ANURAS.NS
125
+ APARINDS.NS
126
+ APCL.NS
127
+ APCOTEXIND.NS
128
+ APEX.NS
129
+ APLAPOLLO.NS
130
+ APLLTD.NS
131
+ APOLLO.NS
132
+ APOLLOHOSP.NS
133
+ APOLLOPIPE.NS
134
+ APOLLOTYRE.NS
135
+ APOLSINHOT.NS
136
+ APTECHT.NS
137
+ APTUS.NS
138
+ ARCHIDPLY.NS
139
+ ARCHIES.NS
140
+ ARE&M.NS
141
+ ARENTERP.NS
142
+ ARIES.NS
143
+ ARIHANTCAP.NS
144
+ ARIHANTSUP.NS
145
+ ARKADE.NS
146
+ ARMANFIN.NS
147
+ AROGRANITE.NS
148
+ ARROWGREEN.NS
149
+ ARSHIYA.NS
150
+ ARSSINFRA.NS
151
+ ARTEMISMED.NS
152
+ ARTNIRMAN.NS
153
+ ARVEE.NS
154
+ ARVIND.NS
155
+ ARVINDFASN.NS
156
+ ARVSMART.NS
157
+ ASAHIINDIA.NS
158
+ ASAHISONG.NS
159
+ ASAL.NS
160
+ ASALCBR.NS
161
+ ASHAPURMIN.NS
162
+ ASHIANA.NS
163
+ ASHIMASYN.NS
164
+ ASHOKA.NS
165
+ ASHOKAMET.NS
166
+ ASHOKLEY.NS
167
+ ASIANENE.NS
168
+ ASIANHOTNR.NS
169
+ ASIANPAINT.NS
170
+ ASIANTILES.NS
171
+ ASKAUTOLTD.NS
172
+ ASMS.NS
173
+ ASPINWALL.NS
174
+ ASTEC.NS
175
+ ASTERDM.NS
176
+ ASTRAL.NS
177
+ ASTRAMICRO.NS
178
+ ASTRAZEN.NS
179
+ ASTRON.NS
180
+ ATALREAL.NS
181
+ ATAM.NS
182
+ ATGL.NS
183
+ ATHERENERG.NS
184
+ ATL.NS
185
+ ATLANTAA.NS
186
+ ATLASCYCLE.NS
187
+ ATUL.NS
188
+ ATULAUTO.NS
189
+ AUBANK.NS
190
+ AURIONPRO.NS
191
+ AUROPHARMA.NS
192
+ AURUM.NS
193
+ AUSOMENT.NS
194
+ AUTOAXLES.NS
195
+ AUTOIND.NS
196
+ AVADHSUGAR.NS
197
+ AVALON.NS
198
+ AVANTEL.NS
199
+ AVANTEL-RE.NS
200
+ AVANTIFEED.NS
201
+ AVG.NS
202
+ AVL.NS
203
+ AVONMORE.NS
204
+ AVROIND.NS
205
+ AVTNPL.NS
206
+ AWFIS.NS
207
+ AWHCL.NS
208
+ AWL.NS
209
+ AXISBANK.NS
210
+ AXISCADES.NS
211
+ AXITA.NS
212
+ AYMSYNTEX.NS
213
+ AZAD.NS
214
+ BAFNAPH.NS
215
+ BAGFILMS.NS
216
+ BAIDFIN.NS
217
+ BAJAJ-AUTO.NS
218
+ BAJAJCON.NS
219
+ BAJAJELEC.NS
220
+ BAJAJFINSV.NS
221
+ BAJAJHCARE.NS
222
+ BAJAJHFL.NS
223
+ BAJAJHIND.NS
224
+ BAJAJHLDNG.NS
225
+ BAJAJINDEF.NS
226
+ BAJEL.NS
227
+ BAJFINANCE.NS
228
+ BALAJEE.NS
229
+ BALAJITELE.NS
230
+ BALAMINES.NS
231
+ BALAXI.NS
232
+ BALKRISHNA.NS
233
+ BALKRISIND.NS
234
+ BALMLAWRIE.NS
235
+ BALPHARMA.NS
236
+ BALRAMCHIN.NS
237
+ BALUFORGE.NS
238
+ BANARBEADS.NS
239
+ BANARISUG.NS
240
+ BANCOINDIA.NS
241
+ BANDHANBNK.NS
242
+ BANG.NS
243
+ BANKA.NS
244
+ BANKBARODA.NS
245
+ BANKINDIA.NS
246
+ BANSALWIRE.NS
247
+ BANSWRAS.NS
248
+ BARBEQUE.NS
249
+ BASF.NS
250
+ BASML.NS
251
+ BASML-RE1.NS
252
+ BATAINDIA.NS
253
+ BAYERCROP.NS
254
+ BBL.NS
255
+ BBOX.NS
256
+ BBTC.NS
257
+ BBTCL.NS
258
+ BCLIND.NS
259
+ BCONCEPTS.NS
260
+ BDL.NS
261
+ BEARDSELL.NS
262
+ BECTORFOOD.NS
263
+ BEDMUTHA.NS
264
+ BEL.NS
265
+ BEML.NS
266
+ BEPL.NS
267
+ BERGEPAINT.NS
268
+ BESTAGRO.NS
269
+ BFINVEST.NS
270
+ BFUTILITIE.NS
271
+ BGRENERGY.NS
272
+ BHAGCHEM.NS
273
+ BHAGERIA.NS
274
+ BHAGYANGR.NS
275
+ BHANDARI.NS
276
+ BHARATFORG.NS
277
+ BHARATGEAR.NS
278
+ BHARATRAS.NS
279
+ BHARATSE.NS
280
+ BHARATWIRE.NS
281
+ BHARTIARTL.NS
282
+ BHARTIHEXA.NS
283
+ BHEL.NS
284
+ BIGBLOC.NS
285
+ BIKAJI.NS
286
+ BIL.NS
287
+ BINANIIND.NS
288
+ BIOCON.NS
289
+ BIOFILCHEM.NS
290
+ BIRLACABLE.NS
291
+ BIRLACORPN.NS
292
+ BIRLAMONEY.NS
293
+ BIRLANU.NS
294
+ BLACKBUCK.NS
295
+ BLAL.NS
296
+ BLBLIMITED.NS
297
+ BLISSGVS.NS
298
+ BLKASHYAP.NS
299
+ BLS.NS
300
+ BLSE.NS
301
+ BLUECOAST.NS
302
+ BLUEDART.NS
303
+ BLUEJET.NS
304
+ BLUESTARCO.NS
305
+ BODALCHEM.NS
306
+ BOHRAIND.NS
307
+ BOMDYEING.NS
308
+ BOROLTD.NS
309
+ BORORENEW.NS
310
+ BOROSCI.NS
311
+ BOSCHLTD.NS
312
+ BPCL.NS
313
+ BPL.NS
314
+ BRIGADE.NS
315
+ BRITANNIA.NS
316
+ BRNL.NS
317
+ BROOKS.NS
318
+ BSE.NS
319
+ BSHSL.NS
320
+ BSL.NS
321
+ BSOFT.NS
322
+ BTML.NS
323
+ BUTTERFLY.NS
324
+ BVCL.NS
325
+ BYKE.NS
326
+ CALSOFT.NS
327
+ CAMLINFINE.NS
328
+ CAMPUS.NS
329
+ CAMS.NS
330
+ CANBK.NS
331
+ CANFINHOME.NS
332
+ CANTABIL.NS
333
+ CAPACITE.NS
334
+ CAPITALSFB.NS
335
+ CAPLIPOINT.NS
336
+ CAPTRUST.NS
337
+ CARBORUNIV.NS
338
+ CARERATING.NS
339
+ CARRARO.NS
340
+ CARTRADE.NS
341
+ CARYSIL.NS
342
+ CASTROLIND.NS
343
+ CCCL.NS
344
+ CCHHL.NS
345
+ CCL.NS
346
+ CDSL.NS
347
+ CEATLTD.NS
348
+ CEIGALL.NS
349
+ CELEBRITY.NS
350
+ CELLO.NS
351
+ CENTENKA.NS
352
+ CENTEXT.NS
353
+ CENTRALBK.NS
354
+ CENTRUM.NS
355
+ CENTUM.NS
356
+ CENTURYPLY.NS
357
+ CERA.NS
358
+ CEREBRAINT.NS
359
+ CESC.NS
360
+ CEWATER.NS
361
+ CGCL.NS
362
+ CGPOWER.NS
363
+ CHALET.NS
364
+ CHAMBLFERT.NS
365
+ CHEMBOND.NS
366
+ CHEMCON.NS
367
+ CHEMFAB.NS
368
+ CHEMPLASTS.NS
369
+ CHENNPETRO.NS
370
+ CHEVIOT.NS
371
+ CHOICEIN.NS
372
+ CHOLAFIN.NS
373
+ CHOLAHLDNG.NS
374
+ CIEINDIA.NS
375
+ CIFL.NS
376
+ CIGNITITEC.NS
377
+ CINELINE.NS
378
+ CINEVISTA.NS
379
+ CIPLA.NS
380
+ CLEAN.NS
381
+ CLEDUCATE.NS
382
+ CLSEL.NS
383
+ CMSINFO.NS
384
+ COALINDIA.NS
385
+ COASTCORP.NS
386
+ COCHINSHIP.NS
387
+ COFFEEDAY.NS
388
+ COFORGE.NS
389
+ COLPAL.NS
390
+ COMPUSOFT.NS
391
+ COMSYN.NS
392
+ CONCOR.NS
393
+ CONCORDBIO.NS
394
+ CONFIPET.NS
395
+ CONSOFINVT.NS
396
+ CONTROLPR.NS
397
+ CORALFINAC.NS
398
+ CORDSCABLE.NS
399
+ COROMANDEL.NS
400
+ COSMOFIRST.NS
401
+ COUNCODOS.NS
402
+ CPCAP.NS
403
+ CRAFTSMAN.NS
404
+ CREATIVE.NS
405
+ CREATIVEYE.NS
406
+ CREDITACC.NS
407
+ CREST.NS
408
+ CRISIL.NS
409
+ CROMPTON.NS
410
+ CROWN.NS
411
+ CSBBANK.NS
412
+ CSLFINANCE.NS
413
+ CTE.NS
414
+ CUB.NS
415
+ CUBEXTUB.NS
416
+ CUMMINSIND.NS
417
+ CUPID.NS
418
+ CURAA.NS
419
+ CYBERMEDIA.NS
420
+ CYBERTECH.NS
421
+ CYIENT.NS
422
+ CYIENTDLM.NS
423
+ DABUR.NS
424
+ DALBHARAT.NS
425
+ DALMIASUG.NS
426
+ DAMCAPITAL.NS
427
+ DAMODARIND.NS
428
+ DANGEE.NS
429
+ DATAMATICS.NS
430
+ DATAPATTNS.NS
431
+ DAVANGERE.NS
432
+ DBCORP.NS
433
+ DBEIL.NS
434
+ DBL.NS
435
+ DBOL.NS
436
+ DBREALTY.NS
437
+ DBSTOCKBRO.NS
438
+ DCAL.NS
439
+ DCBBANK.NS
440
+ DCI.NS
441
+ DCM.NS
442
+ DCMFINSERV.NS
443
+ DCMNVL.NS
444
+ DCMSHRIRAM.NS
445
+ DCMSRIND.NS
446
+ DCW.NS
447
+ DCXINDIA.NS
448
+ DDEVPLSTIK.NS
449
+ DECCANCE.NS
450
+ DEEDEV.NS
451
+ DEEPAKFERT.NS
452
+ DEEPAKNTR.NS
453
+ DEEPINDS.NS
454
+ DELHIVERY.NS
455
+ DELPHIFX.NS
456
+ DELTACORP.NS
457
+ DELTAMAGNT.NS
458
+ DEN.NS
459
+ DENORA.NS
460
+ DENTA.NS
461
+ DEVIT.NS
462
+ DEVYANI.NS
463
+ DGCONTENT.NS
464
+ DHAMPURSUG.NS
465
+ DHANBANK.NS
466
+ DHANI.NS
467
+ DHANUKA.NS
468
+ DHARMAJ.NS
469
+ DHRUV.NS
470
+ DHUNINV.NS
471
+ DIACABS.NS
472
+ DIAMINESQ.NS
473
+ DIAMONDYD.NS
474
+ DICIND.NS
475
+ DIFFNKG.NS
476
+ DIGIDRIVE.NS
477
+ DIGISPICE.NS
478
+ DIGJAMLMTD.NS
479
+ DIL.NS
480
+ DISHTV.NS
481
+ DIVGIITTS.NS
482
+ DIVISLAB.NS
483
+ DIXON.NS
484
+ DJML.NS
485
+ DLF.NS
486
+ DLINKINDIA.NS
487
+ DMART.NS
488
+ DMCC.NS
489
+ DNAMEDIA.NS
490
+ DODLA.NS
491
+ DOLATALGO.NS
492
+ DOLLAR.NS
493
+ DOLPHIN.NS
494
+ DOMS.NS
495
+ DONEAR.NS
496
+ DPABHUSHAN.NS
497
+ DPSCLTD.NS
498
+ DPWIRES.NS
499
+ DRCSYSTEMS.NS
500
+ DREAMFOLKS.NS
501
+ DREDGECORP.NS
502
+ DRREDDY.NS
503
+ DSSL.NS
504
+ DTIL.NS
505
+ DUCON.NS
506
+ DVL.NS
507
+ DWARKESH.NS
508
+ DYCL.NS
509
+ DYNAMATECH.NS
510
+ DYNPRO.NS
511
+ E2E.NS
512
+ EASEMYTRIP.NS
513
+ ECLERX.NS
514
+ ECOSMOBLTY.NS
515
+ EDELWEISS.NS
516
+ EICHERMOT.NS
517
+ EIDPARRY.NS
518
+ EIEL.NS
519
+ EIFFL.NS
520
+ EIHAHOTELS.NS
521
+ EIHOTEL.NS
522
+ EIMCOELECO.NS
523
+ EKC.NS
524
+ ELDEHSG.NS
525
+ ELECON.NS
526
+ ELECTCAST.NS
527
+ ELECTHERM.NS
528
+ ELGIEQUIP.NS
529
+ ELGIRUBCO.NS
530
+ ELIN.NS
531
+ EMAMILTD.NS
532
+ EMAMIPAP.NS
533
+ EMAMIREAL.NS
534
+ EMBDL.NS
535
+ EMCURE.NS
536
+ EMIL.NS
537
+ EMKAY.NS
538
+ EMMBI.NS
539
+ EMSLIMITED.NS
540
+ EMUDHRA.NS
541
+ ENDURANCE.NS
542
+ ENERGYDEV.NS
543
+ ENGINERSIN.NS
544
+ ENIL.NS
545
+ ENTERO.NS
546
+ EPACK.NS
547
+ EPIGRAL.NS
548
+ EPL.NS
549
+ EQUIPPP.NS
550
+ EQUITASBNK.NS
551
+ ERIS.NS
552
+ ESABINDIA.NS
553
+ ESAFSFB.NS
554
+ ESCORTS.NS
555
+ ESSARSHPNG.NS
556
+ ESSENTIA.NS
557
+ ESTER.NS
558
+ ETERNAL.NS
559
+ ETHOSLTD.NS
560
+ EUREKAFORB.NS
561
+ EUROTEXIND.NS
562
+ EVEREADY.NS
563
+ EVERESTIND.NS
564
+ EXCEL.NS
565
+ EXCELINDUS.NS
566
+ EXICOM.NS
567
+ EXIDEIND.NS
568
+ EXPLEOSOL.NS
569
+ EXXARO.NS
570
+ FACT.NS
571
+ FAIRCHEMOR.NS
572
+ FAZE3Q.NS
573
+ FCL.NS
574
+ FCSSOFT.NS
575
+ FDC.NS
576
+ FEDERALBNK.NS
577
+ FEDFINA.NS
578
+ FEL.NS
579
+ FELDVR.NS
580
+ FIBERWEB.NS
581
+ FIEMIND.NS
582
+ FILATEX.NS
583
+ FILATFASH.NS
584
+ FINCABLES.NS
585
+ FINEORG.NS
586
+ FINOPB.NS
587
+ FINPIPE.NS
588
+ FIRSTCRY.NS
589
+ FIVESTAR.NS
590
+ FLAIR.NS
591
+ FLEXITUFF.NS
592
+ FLUOROCHEM.NS
593
+ FMGOETZE.NS
594
+ FMNL.NS
595
+ FOCUS.NS
596
+ FOODSIN.NS
597
+ FORCEMOT.NS
598
+ FORTIS.NS
599
+ FOSECOIND.NS
600
+ FSC.NS
601
+ FSL.NS
602
+ FUSION.NS
603
+ GABRIEL.NS
604
+ GAEL.NS
605
+ GAIL.NS
606
+ GALAPREC.NS
607
+ GALAXYSURF.NS
608
+ GALLANTT.NS
609
+ GANDHAR.NS
610
+ GANDHITUBE.NS
611
+ GANECOS.NS
612
+ GANESHBE.NS
613
+ GANESHHOUC.NS
614
+ GANGAFORGE.NS
615
+ GANGESSECU.NS
616
+ GARFIBRES.NS
617
+ GARUDA.NS
618
+ GATDVR-RE.NS
619
+ GATECH.NS
620
+ GATECH-RE1.NS
621
+ GATECHDVR.NS
622
+ GATEWAY.NS
623
+ GAYAHWS.NS
624
+ GEECEE.NS
625
+ GEEKAYWIRE.NS
626
+ GENCON.NS
627
+ GENESYS.NS
628
+ GENSOL.NS
629
+ GENUSPAPER.NS
630
+ GENUSPOWER.NS
631
+ GEOJITFSL.NS
632
+ GEPIL.NS
633
+ GESHIP.NS
634
+ GFLLIMITED.NS
635
+ GHCL.NS
636
+ GHCLTEXTIL.NS
637
+ GICHSGFIN.NS
638
+ GICRE.NS
639
+ GILLANDERS.NS
640
+ GILLETTE.NS
641
+ GINNIFILA.NS
642
+ GIPCL.NS
643
+ GKWLIMITED.NS
644
+ GLAND.NS
645
+ GLAXO.NS
646
+ GLENMARK.NS
647
+ GLFL.NS
648
+ GLOBAL.NS
649
+ GLOBALE.NS
650
+ GLOBALVECT.NS
651
+ GLOBE.NS
652
+ GLOBUSSPR.NS
653
+ GLOSTERLTD.NS
654
+ GMBREW.NS
655
+ GMDCLTD.NS
656
+ GMMPFAUDLR.NS
657
+ GMRAIRPORT.NS
658
+ GMRP&UI.NS
659
+ GNA.NS
660
+ GNFC.NS
661
+ GOACARBON.NS
662
+ GOCLCORP.NS
663
+ GOCOLORS.NS
664
+ GODAVARIB.NS
665
+ GODFRYPHLP.NS
666
+ GODHA.NS
667
+ GODIGIT.NS
668
+ GODREJAGRO.NS
669
+ GODREJCP.NS
670
+ GODREJIND.NS
671
+ GODREJPROP.NS
672
+ GOENKA.NS
673
+ GOKEX.NS
674
+ GOKUL.NS
675
+ GOKULAGRO.NS
676
+ GOLDENTOBC.NS
677
+ GOLDIAM.NS
678
+ GOLDTECH.NS
679
+ GOODLUCK.NS
680
+ GOPAL.NS
681
+ GOYALALUM.NS
682
+ GPIL.NS
683
+ GPPL.NS
684
+ GPTHEALTH.NS
685
+ GPTINFRA.NS
686
+ GRANULES.NS
687
+ GRAPHITE.NS
688
+ GRASIM.NS
689
+ GRAVITA.NS
690
+ GREAVESCOT.NS
691
+ GREENLAM.NS
692
+ GREENPANEL.NS
693
+ GREENPLY.NS
694
+ GREENPOWER.NS
695
+ GRINDWELL.NS
696
+ GRINFRA.NS
697
+ GRMOVER.NS
698
+ GROBTEA.NS
699
+ GRPLTD.NS
700
+ GRSE.NS
701
+ GRWRHITECH.NS
702
+ GSFC.NS
703
+ GSLSU.NS
704
+ GSPL.NS
705
+ GSS.NS
706
+ GTECJAINX.NS
707
+ GTL.NS
708
+ GTLINFRA.NS
709
+ GTPL.NS
710
+ GUFICBIO.NS
711
+ GUJALKALI.NS
712
+ GUJAPOLLO.NS
713
+ GUJGASLTD.NS
714
+ GUJRAFFIA.NS
715
+ GUJTHEM.NS
716
+ GULFOILLUB.NS
717
+ GULFPETRO.NS
718
+ GULPOLY.NS
719
+ GVKPIL.NS
720
+ GVPTECH.NS
721
+ GVT&D.NS
722
+ HAL.NS
723
+ HAPPSTMNDS.NS
724
+ HAPPYFORGE.NS
725
+ HARDWYN.NS
726
+ HARIOMPIPE.NS
727
+ HARRMALAYA.NS
728
+ HARSHA.NS
729
+ HATHWAY.NS
730
+ HATSUN.NS
731
+ HAVELLS.NS
732
+ HAVISHA.NS
733
+ HBLENGINE.NS
734
+ HBSL.NS
735
+ HCC.NS
736
+ HCG.NS
737
+ HCL-INSYS.NS
738
+ HCLTECH.NS
739
+ HDFCAMC.NS
740
+ HDFCBANK.NS
741
+ HDFCLIFE.NS
742
+ HEADSUP.NS
743
+ HECPROJECT.NS
744
+ HEG.NS
745
+ HEIDELBERG.NS
746
+ HEMIPROP.NS
747
+ HERANBA.NS
748
+ HERCULES.NS
749
+ HERITGFOOD.NS
750
+ HEROMOTOCO.NS
751
+ HESTERBIO.NS
752
+ HEUBACHIND.NS
753
+ HEXATRADEX.NS
754
+ HEXT.NS
755
+ HFCL.NS
756
+ HGINFRA.NS
757
+ HGS.NS
758
+ HIKAL.NS
759
+ HILTON.NS
760
+ HIMATSEIDE.NS
761
+ HINDALCO.NS
762
+ HINDCOMPOS.NS
763
+ HINDCON.NS
764
+ HINDCOPPER.NS
765
+ HINDMOTORS.NS
766
+ HINDNATGLS.NS
767
+ HINDOILEXP.NS
768
+ HINDPETRO.NS
769
+ HINDUNILVR.NS
770
+ HINDWAREAP.NS
771
+ HINDZINC.NS
772
+ HIRECT.NS
773
+ HISARMETAL.NS
774
+ HITECH.NS
775
+ HITECHCORP.NS
776
+ HITECHGEAR.NS
777
+ HLEGLAS.NS
778
+ HLVLTD.NS
779
+ HMAAGRO.NS
780
+ HMT.NS
781
+ HMVL.NS
782
+ HNDFDS.NS
783
+ HOMEFIRST.NS
784
+ HONASA.NS
785
+ HONAUT.NS
786
+ HONDAPOWER.NS
787
+ HOVS.NS
788
+ HPAL.NS
789
+ HPIL.NS
790
+ HPL.NS
791
+ HSCL.NS
792
+ HTMEDIA.NS
793
+ HUBTOWN.NS
794
+ HUDCO.NS
795
+ HUHTAMAKI.NS
796
+ HYBRIDFIN.NS
797
+ HYUNDAI.NS
798
+ ICDSLTD.NS
799
+ ICEMAKE.NS
800
+ ICICIBANK.NS
801
+ ICICIGI.NS
802
+ ICICIPRULI.NS
803
+ ICIL.NS
804
+ ICRA.NS
805
+ IDBI.NS
806
+ IDEA.NS
807
+ IDEAFORGE.NS
808
+ IDFCFIRSTB.NS
809
+ IEL.NS
810
+ IEX.NS
811
+ IFBAGRO.NS
812
+ IFBIND.NS
813
+ IFCI.NS
814
+ IFGLEXPOR.NS
815
+ IGARASHI.NS
816
+ IGIL.NS
817
+ IGL.NS
818
+ IGPL.NS
819
+ IIFL.NS
820
+ IIFLCAPS.NS
821
+ IITL.NS
822
+ IKIO.NS
823
+ IKS.NS
824
+ IL&FSENGG.NS
825
+ IL&FSTRANS.NS
826
+ IMAGICAA.NS
827
+ IMFA.NS
828
+ IMPAL.NS
829
+ IMPEXFERRO.NS
830
+ INCREDIBLE.NS
831
+ INDBANK.NS
832
+ INDGN.NS
833
+ INDHOTEL.NS
834
+ INDIACEM.NS
835
+ INDIAGLYCO.NS
836
+ INDIAMART.NS
837
+ INDIANB.NS
838
+ INDIANCARD.NS
839
+ INDIANHUME.NS
840
+ INDIASHLTR.NS
841
+ INDIGO.NS
842
+ INDIGOPNTS.NS
843
+ INDNIPPON.NS
844
+ INDOAMIN.NS
845
+ INDOBORAX.NS
846
+ INDOCO.NS
847
+ INDOFARM.NS
848
+ INDORAMA.NS
849
+ INDOSTAR.NS
850
+ INDOTECH.NS
851
+ INDOTHAI.NS
852
+ INDOUS.NS
853
+ INDOWIND.NS
854
+ INDRAMEDCO.NS
855
+ INDSWFTLAB.NS
856
+ INDSWFTLTD.NS
857
+ INDTERRAIN.NS
858
+ INDUSINDBK.NS
859
+ INDUSTOWER.NS
860
+ INFIBEAM.NS
861
+ INFOBEAN.NS
862
+ INFOMEDIA.NS
863
+ INFY.NS
864
+ INGERRAND.NS
865
+ INNOVACAP.NS
866
+ INNOVANA.NS
867
+ INOXGREEN.NS
868
+ INOXINDIA.NS
869
+ INOXWIND.NS
870
+ INSECTICID.NS
871
+ INSPIRISYS.NS
872
+ INTELLECT.NS
873
+ INTENTECH.NS
874
+ INTERARCH.NS
875
+ INTLCONV.NS
876
+ INVENTURE.NS
877
+ IOB.NS
878
+ IOC.NS
879
+ IOLCP.NS
880
+ IONEXCHANG.NS
881
+ IPCALAB.NS
882
+ IPL.NS
883
+ IRB.NS
884
+ IRCON.NS
885
+ IRCTC.NS
886
+ IREDA.NS
887
+ IRFC.NS
888
+ IRIS.NS
889
+ IRISDOREME.NS
890
+ IRMENERGY.NS
891
+ ISFT.NS
892
+ ISGEC.NS
893
+ ISHANCH.NS
894
+ ITC.NS
895
+ ITCHOTELS.NS
896
+ ITDC.NS
897
+ ITDCEM.NS
898
+ ITI.NS
899
+ IVC.NS
900
+ IVP.NS
901
+ IWEL.NS
902
+ IXIGO.NS
903
+ IZMO.NS
904
+ J&KBANK.NS
905
+ JAGRAN.NS
906
+ JAGSNPHARM.NS
907
+ JAIBALAJI.NS
908
+ JAICORPLTD.NS
909
+ JAIPURKURT.NS
910
+ JAMNAAUTO.NS
911
+ JASH.NS
912
+ JAYAGROGN.NS
913
+ JAYBARMARU.NS
914
+ JAYNECOIND.NS
915
+ JAYSREETEA.NS
916
+ JBCHEPHARM.NS
917
+ JBMA.NS
918
+ JCHAC.NS
919
+ JETFREIGHT.NS
920
+ JGCHEM.NS
921
+ JHS.NS
922
+ JINDALPHOT.NS
923
+ JINDALPOLY.NS
924
+ JINDALSAW.NS
925
+ JINDALSTEL.NS
926
+ JINDRILL.NS
927
+ JINDWORLD.NS
928
+ JIOFIN.NS
929
+ JISLDVREQS.NS
930
+ JISLJALEQS.NS
931
+ JITFINFRA.NS
932
+ JKCEMENT.NS
933
+ JKIL.NS
934
+ JKLAKSHMI.NS
935
+ JKPAPER.NS
936
+ JKTYRE.NS
937
+ JLHL.NS
938
+ JMA.NS
939
+ JMFINANCIL.NS
940
+ JNKINDIA.NS
941
+ JOCIL.NS
942
+ JPOLYINVST.NS
943
+ JPPOWER.NS
944
+ JSFB.NS
945
+ JSL.NS
946
+ JSWENERGY.NS
947
+ JSWHL.NS
948
+ JSWINFRA.NS
949
+ JSWSTEEL.NS
950
+ JTEKTINDIA.NS
951
+ JTLIND.NS
952
+ JUBLCPL.NS
953
+ JUBLFOOD.NS
954
+ JUBLINGREA.NS
955
+ JUBLPHARMA.NS
956
+ JUNIPER.NS
957
+ JUSTDIAL.NS
958
+ JWL.NS
959
+ JYOTHYLAB.NS
960
+ JYOTICNC.NS
961
+ JYOTISTRUC.NS
962
+ KABRAEXTRU.NS
963
+ KAJARIACER.NS
964
+ KAKATCEM.NS
965
+ KALAMANDIR.NS
966
+ KALYANI.NS
967
+ KALYANIFRG.NS
968
+ KALYANKJIL.NS
969
+ KAMATHOTEL.NS
970
+ KAMDHENU.NS
971
+ KAMOPAINTS.NS
972
+ KANANIIND.NS
973
+ KANORICHEM.NS
974
+ KANPRPLA.NS
975
+ KANSAINER.NS
976
+ KAPSTON.NS
977
+ KARMAENG.NS
978
+ KARURVYSYA.NS
979
+ KAUSHALYA.NS
980
+ KAVVERITEL.NS
981
+ KAYA.NS
982
+ KAYNES.NS
983
+ KBCGLOBAL.NS
984
+ KCP.NS
985
+ KCPSUGIND.NS
986
+ KDDL.NS
987
+ KEC.NS
988
+ KECL.NS
989
+ KEEPLEARN.NS
990
+ KEI.NS
991
+ KELLTONTEC.NS
992
+ KERNEX.NS
993
+ KESORAMIND.NS
994
+ KEYFINSERV.NS
995
+ KFINTECH.NS
996
+ KHADIM.NS
997
+ KHAICHEM.NS
998
+ KHAITANLTD.NS
999
+ KHANDSE.NS
1000
+ KICL.NS
1001
+ KILITCH.NS
1002
+ KIMS.NS
1003
+ KINGFA.NS
1004
+ KIOCL.NS
1005
+ KIRIINDUS.NS
1006
+ KIRLOSBROS.NS
1007
+ KIRLOSENG.NS
1008
+ KIRLOSIND.NS
1009
+ KIRLPNU.NS
1010
+ KITEX.NS
1011
+ KKCL.NS
1012
+ KMEW.NS
1013
+ KMSUGAR.NS
1014
+ KNRCON.NS
1015
+ KOHINOOR.NS
1016
+ KOKUYOCMLN.NS
1017
+ KOLTEPATIL.NS
1018
+ KOPRAN.NS
1019
+ KOTAKBANK.NS
1020
+ KOTARISUG.NS
1021
+ KOTHARIPET.NS
1022
+ KOTHARIPRO.NS
1023
+ KPEL.NS
1024
+ KPIGREEN.NS
1025
+ KPIL.NS
1026
+ KPITTECH.NS
1027
+ KPRMILL.NS
1028
+ KRBL.NS
1029
+ KREBSBIO.NS
1030
+ KRIDHANINF.NS
1031
+ KRISHANA.NS
1032
+ KRITI.NS
1033
+ KRITIKA.NS
1034
+ KRITINUT.NS
1035
+ KRN.NS
1036
+ KRONOX.NS
1037
+ KROSS.NS
1038
+ KRSNAA.NS
1039
+ KRYSTAL.NS
1040
+ KSB.NS
1041
+ KSCL.NS
1042
+ KSHITIJPOL.NS
1043
+ KSL.NS
1044
+ KSOLVES.NS
1045
+ KTKBANK.NS
1046
+ KUANTUM.NS
1047
+ LAGNAM.NS
1048
+ LAKPRE.NS
1049
+ LAL.NS
1050
+ LALPATHLAB.NS
1051
+ LAMBODHARA.NS
1052
+ LANCORHOL.NS
1053
+ LANDMARK.NS
1054
+ LAOPALA.NS
1055
+ LASA.NS
1056
+ LATENTVIEW.NS
1057
+ LATTEYS.NS
1058
+ LAURUSLABS.NS
1059
+ LAXMICOT.NS
1060
+ LAXMIDENTL.NS
1061
+ LCCINFOTEC.NS
1062
+ LEMONTREE.NS
1063
+ LEXUS.NS
1064
+ LFIC.NS
1065
+ LGBBROSLTD.NS
1066
+ LGHL.NS
1067
+ LIBAS.NS
1068
+ LIBERTSHOE.NS
1069
+ LICHSGFIN.NS
1070
+ LICI.NS
1071
+ LIKHITHA.NS
1072
+ LINC.NS
1073
+ LINCOLN.NS
1074
+ LINDEINDIA.NS
1075
+ LLOYDS-RE1.NS
1076
+ LLOYDSENGG.NS
1077
+ LLOYDSENT.NS
1078
+ LLOYDSME.NS
1079
+ LMW.NS
1080
+ LODHA.NS
1081
+ LOKESHMACH.NS
1082
+ LORDSCHLO.NS
1083
+ LOTUSEYE.NS
1084
+ LOVABLE.NS
1085
+ LOYALTEX.NS
1086
+ LPDC.NS
1087
+ LT.NS
1088
+ LTF.NS
1089
+ LTFOODS.NS
1090
+ LTIM.NS
1091
+ LTTS.NS
1092
+ LUMAXIND.NS
1093
+ LUMAXTECH.NS
1094
+ LUPIN.NS
1095
+ LUXIND.NS
1096
+ LXCHEM.NS
1097
+ LYKALABS.NS
1098
+ LYPSAGEMS.NS
1099
+ M&M.NS
1100
+ M&MFIN.NS
1101
+ MAANALU.NS
1102
+ MACPOWER.NS
1103
+ MADHAV.NS
1104
+ MADHUCON.NS
1105
+ MADRASFERT.NS
1106
+ MAGADSUGAR.NS
1107
+ MAGNUM.NS
1108
+ MAHABANK.NS
1109
+ MAHAPEXLTD.NS
1110
+ MAHASTEEL.NS
1111
+ MAHEPC.NS
1112
+ MAHESHWARI.NS
1113
+ MAHLIFE.NS
1114
+ MAHLOG.NS
1115
+ MAHSCOOTER.NS
1116
+ MAHSEAMLES.NS
1117
+ MAITHANALL.NS
1118
+ MALLCOM.NS
1119
+ MALUPAPER.NS
1120
+ MAMATA.NS
1121
+ MANAKALUCO.NS
1122
+ MANAKCOAT.NS
1123
+ MANAKSIA.NS
1124
+ MANAKSTEEL.NS
1125
+ MANALIPETC.NS
1126
+ MANAPPURAM.NS
1127
+ MANBA.NS
1128
+ MANCREDIT.NS
1129
+ MANGALAM.NS
1130
+ MANGCHEFER.NS
1131
+ MANGLMCEM.NS
1132
+ MANINDS.NS
1133
+ MANINFRA.NS
1134
+ MANKIND.NS
1135
+ MANOMAY.NS
1136
+ MANORAMA.NS
1137
+ MANORG.NS
1138
+ MANUGRAPH.NS
1139
+ MANYAVAR.NS
1140
+ MAPMYINDIA.NS
1141
+ MARALOVER.NS
1142
+ MARATHON.NS
1143
+ MARICO.NS
1144
+ MARINE.NS
1145
+ MARKSANS.NS
1146
+ MARSHALL.NS
1147
+ MARUTI.NS
1148
+ MASFIN.NS
1149
+ MASKINVEST.NS
1150
+ MASTEK.NS
1151
+ MASTERTR.NS
1152
+ MATRIMONY.NS
1153
+ MAWANASUG.NS
1154
+ MAXESTATES.NS
1155
+ MAXHEALTH.NS
1156
+ MAXIND.NS
1157
+ MAXIND-RE.NS
1158
+ MAYURUNIQ.NS
1159
+ MAZDA.NS
1160
+ MAZDOCK.NS
1161
+ MBAPL.NS
1162
+ MBLINFRA.NS
1163
+ MCL.NS
1164
+ MCLEODRUSS.NS
1165
+ MCLOUD.NS
1166
+ MCX.NS
1167
+ MEDANTA.NS
1168
+ MEDIASSIST.NS
1169
+ MEDICAMEQ.NS
1170
+ MEDICO.NS
1171
+ MEDPLUS.NS
1172
+ MEGASOFT.NS
1173
+ MEGASTAR.NS
1174
+ MENONBE.NS
1175
+ MEP.NS
1176
+ METROBRAND.NS
1177
+ METROPOLIS.NS
1178
+ MFML.NS
1179
+ MFSL.NS
1180
+ MGEL.NS
1181
+ MGL.NS
1182
+ MHLXMIRU.NS
1183
+ MHRIL.NS
1184
+ MICEL.NS
1185
+ MIDHANI.NS
1186
+ MINDACORP.NS
1187
+ MINDTECK.NS
1188
+ MIRCELECTR.NS
1189
+ MIRZAINT.NS
1190
+ MITCON.NS
1191
+ MITTAL.NS
1192
+ MKPL.NS
1193
+ MMFL.NS
1194
+ MMP.NS
1195
+ MMTC.NS
1196
+ MOBIKWIK.NS
1197
+ MODIRUBBER.NS
1198
+ MODISONLTD.NS
1199
+ MODTHREAD.NS
1200
+ MOHITIND.NS
1201
+ MOIL.NS
1202
+ MOKSH.NS
1203
+ MOL.NS
1204
+ MOLDTECH.NS
1205
+ MOLDTKPAC.NS
1206
+ MONARCH.NS
1207
+ MONTECARLO.NS
1208
+ MOREPENLAB.NS
1209
+ MOSCHIP.NS
1210
+ MOTHERSON.NS
1211
+ MOTILALOFS.NS
1212
+ MOTISONS.NS
1213
+ MOTOGENFIN.NS
1214
+ MPHASIS.NS
1215
+ MPSLTD.NS
1216
+ MRF.NS
1217
+ MRPL.NS
1218
+ MSPL.NS
1219
+ MSTCLTD.NS
1220
+ MSUMI.NS
1221
+ MTARTECH.NS
1222
+ MTEDUCARE.NS
1223
+ MTNL.NS
1224
+ MUFIN.NS
1225
+ MUFTI.NS
1226
+ MUKANDLTD.NS
1227
+ MUKKA.NS
1228
+ MUKTAARTS.NS
1229
+ MUNJALAU.NS
1230
+ MUNJALSHOW.NS
1231
+ MURUDCERA.NS
1232
+ MUTHOOTCAP.NS
1233
+ MUTHOOTFIN.NS
1234
+ MUTHOOTMF.NS
1235
+ MVGJL.NS
1236
+ NACLIND.NS
1237
+ NAGAFERT.NS
1238
+ NAGREEKCAP.NS
1239
+ NAGREEKEXP.NS
1240
+ NAHARCAP.NS
1241
+ NAHARINDUS.NS
1242
+ NAHARPOLY.NS
1243
+ NAHARSPING.NS
1244
+ NAM-INDIA.NS
1245
+ NARMADA.NS
1246
+ NATCAPSUQ.NS
1247
+ NATCOPHARM.NS
1248
+ NATHBIOGEN.NS
1249
+ NATIONALUM.NS
1250
+ NAUKRI.NS
1251
+ NAVA.NS
1252
+ NAVINFLUOR.NS
1253
+ NAVKARCORP.NS
1254
+ NAVKARURB.NS
1255
+ NAVNETEDUL.NS
1256
+ NAZARA.NS
1257
+ NBCC.NS
1258
+ NBIFIN.NS
1259
+ NCC.NS
1260
+ NCLIND.NS
1261
+ NDGL.NS
1262
+ NDL.NS
1263
+ NDLVENTURE.NS
1264
+ NDRAUTO.NS
1265
+ NDTV.NS
1266
+ NECCLTD.NS
1267
+ NECLIFE.NS
1268
+ NELCAST.NS
1269
+ NELCO.NS
1270
+ NEOGEN.NS
1271
+ NESCO.NS
1272
+ NESTLEIND.NS
1273
+ NETWEB.NS
1274
+ NETWORK18.NS
1275
+ NEULANDLAB.NS
1276
+ NEWGEN.NS
1277
+ NEXTMEDIA.NS
1278
+ NFL.NS
1279
+ NGIL.NS
1280
+ NGLFINE.NS
1281
+ NH.NS
1282
+ NHPC.NS
1283
+ NIACL.NS
1284
+ NIBE.NS
1285
+ NIBL.NS
1286
+ NIITLTD.NS
1287
+ NIITMTS.NS
1288
+ NILAINFRA.NS
1289
+ NILASPACES.NS
1290
+ NILKAMAL.NS
1291
+ NINSYS.NS
1292
+ NIPPOBATRY.NS
1293
+ NIRAJ.NS
1294
+ NIRAJISPAT.NS
1295
+ NITCO.NS
1296
+ NITINSPIN.NS
1297
+ NITIRAJ.NS
1298
+ NIVABUPA.NS
1299
+ NKIND.NS
1300
+ NLCINDIA.NS
1301
+ NMDC.NS
1302
+ NOCIL.NS
1303
+ NOIDATOLL.NS
1304
+ NORBTEAEXP.NS
1305
+ NORTHARC.NS
1306
+ NOVAAGRI.NS
1307
+ NPST.NS
1308
+ NRAIL.NS
1309
+ NRBBEARING.NS
1310
+ NRL.NS
1311
+ NSIL.NS
1312
+ NSLNISP.NS
1313
+ NTPC.NS
1314
+ NTPCGREEN.NS
1315
+ NUCLEUS.NS
1316
+ NURECA.NS
1317
+ NUVAMA.NS
1318
+ NUVOCO.NS
1319
+ NYKAA.NS
1320
+ OAL.NS
1321
+ OBCL.NS
1322
+ OBEROIRLTY.NS
1323
+ OCCL.NS
1324
+ OCCLLTD.NS
1325
+ ODIGMA.NS
1326
+ OFSS.NS
1327
+ OIL.NS
1328
+ OILCOUNTUB.NS
1329
+ OLAELEC.NS
1330
+ OLECTRA.NS
1331
+ OMAXAUTO.NS
1332
+ OMAXE.NS
1333
+ OMINFRAL.NS
1334
+ OMKARCHEM.NS
1335
+ ONELIFECAP.NS
1336
+ ONEPOINT.NS
1337
+ ONESOURCE.NS
1338
+ ONGC.NS
1339
+ ONMOBILE.NS
1340
+ ONWARDTEC.NS
1341
+ OPTIEMUS.NS
1342
+ ORBTEXP.NS
1343
+ ORCHASP.NS
1344
+ ORCHPHARMA.NS
1345
+ ORICONENT.NS
1346
+ ORIENTALTL.NS
1347
+ ORIENTBELL.NS
1348
+ ORIENTCEM.NS
1349
+ ORIENTCER.NS
1350
+ ORIENTELEC.NS
1351
+ ORIENTHOT.NS
1352
+ ORIENTLTD.NS
1353
+ ORIENTPPR.NS
1354
+ ORIENTTECH.NS
1355
+ ORISSAMINE.NS
1356
+ ORTEL.NS
1357
+ ORTINGLOBE.NS
1358
+ OSIAHYPER.NS
1359
+ OSWALAGRO.NS
1360
+ OSWALGREEN.NS
1361
+ OSWALSEEDS.NS
1362
+ PAGEIND.NS
1363
+ PAISALO.NS
1364
+ PAKKA.NS
1365
+ PALASHSECU.NS
1366
+ PALREDTEC.NS
1367
+ PANACEABIO.NS
1368
+ PANACHE.NS
1369
+ PANAMAPET.NS
1370
+ PANSARI.NS
1371
+ PAR.NS
1372
+ PARACABLES.NS
1373
+ PARADEEP.NS
1374
+ PARAGMILK.NS
1375
+ PARAS.NS
1376
+ PARASPETRO.NS
1377
+ PARKHOTELS.NS
1378
+ PARSVNATH.NS
1379
+ PASUPTAC.NS
1380
+ PATANJALI.NS
1381
+ PATELENG.NS
1382
+ PATINTLOG.NS
1383
+ PAVNAIND.NS
1384
+ PAYTM.NS
1385
+ PCBL.NS
1386
+ PCJEWELLER.NS
1387
+ PDMJEPAPER.NS
1388
+ PDSL.NS
1389
+ PEARLPOLY.NS
1390
+ PEL.NS
1391
+ PENIND.NS
1392
+ PENINLAND.NS
1393
+ PERSISTENT.NS
1394
+ PETRONET.NS
1395
+ PFC.NS
1396
+ PFIZER.NS
1397
+ PFOCUS.NS
1398
+ PFS.NS
1399
+ PGEL.NS
1400
+ PGHH.NS
1401
+ PGHL.NS
1402
+ PGIL.NS
1403
+ PHOENIXLTD.NS
1404
+ PIDILITIND.NS
1405
+ PIGL.NS
1406
+ PIIND.NS
1407
+ PILANIINVS.NS
1408
+ PILITA.NS
1409
+ PIONEEREMB.NS
1410
+ PITTIENG.NS
1411
+ PIXTRANS.NS
1412
+ PKTEA.NS
1413
+ PLASTIBLEN.NS
1414
+ PLATIND.NS
1415
+ PLAZACABLE.NS
1416
+ PNB.NS
1417
+ PNBGILTS.NS
1418
+ PNBHOUSING.NS
1419
+ PNC.NS
1420
+ PNCINFRA.NS
1421
+ PNGJL.NS
1422
+ POCL.NS
1423
+ PODDARMENT.NS
1424
+ POKARNA.NS
1425
+ POLICYBZR.NS
1426
+ POLYCAB.NS
1427
+ POLYMED.NS
1428
+ POLYPLEX.NS
1429
+ PONNIERODE.NS
1430
+ POONAWALLA.NS
1431
+ POWERGRID.NS
1432
+ POWERINDIA.NS
1433
+ POWERMECH.NS
1434
+ PPAP.NS
1435
+ PPL.NS
1436
+ PPLPHARMA.NS
1437
+ PRABHA.NS
1438
+ PRAENG.NS
1439
+ PRAJIND.NS
1440
+ PRAKASH.NS
1441
+ PRAKASHSTL.NS
1442
+ PRAXIS.NS
1443
+ PRECAM.NS
1444
+ PRECOT.NS
1445
+ PRECWIRE.NS
1446
+ PREMEXPLN.NS
1447
+ PREMIER.NS
1448
+ PREMIERENE.NS
1449
+ PREMIERPOL.NS
1450
+ PRESTIGE.NS
1451
+ PRICOLLTD.NS
1452
+ PRIMESECU.NS
1453
+ PRIMO.NS
1454
+ PRINCEPIPE.NS
1455
+ PRITI.NS
1456
+ PRITIKAUTO.NS
1457
+ PRIVISCL.NS
1458
+ PROTEAN.NS
1459
+ PROZONER.NS
1460
+ PRSMJOHNSN.NS
1461
+ PRUDENT.NS
1462
+ PRUDMOULI.NS
1463
+ PSB.NS
1464
+ PSPPROJECT.NS
1465
+ PTC.NS
1466
+ PTCIL.NS
1467
+ PTL.NS
1468
+ PUNJABCHEM.NS
1469
+ PURVA.NS
1470
+ PVP.NS
1471
+ PVRINOX.NS
1472
+ PVSL.NS
1473
+ PYRAMID.NS
1474
+ QPOWER.NS
1475
+ QUADFUTURE.NS
1476
+ QUESS.NS
1477
+ QUICKHEAL.NS
1478
+ RACE.NS
1479
+ RACLGEAR.NS
1480
+ RADAAN.NS
1481
+ RADHIKAJWE.NS
1482
+ RADIANTCMS.NS
1483
+ RADICO.NS
1484
+ RADIOCITY.NS
1485
+ RAILTEL.NS
1486
+ RAIN.NS
1487
+ RAINBOW.NS
1488
+ RAJESHEXPO.NS
1489
+ RAJMET.NS
1490
+ RAJRATAN.NS
1491
+ RAJRILTD.NS
1492
+ RAJSREESUG.NS
1493
+ RAJTV.NS
1494
+ RALLIS.NS
1495
+ RAMANEWS.NS
1496
+ RAMAPHO.NS
1497
+ RAMASTEEL.NS
1498
+ RAMCOCEM.NS
1499
+ RAMCOIND.NS
1500
+ RAMCOSYS.NS
1501
+ RAMKY.NS
1502
+ RAMRAT.NS
1503
+ RANASUG.NS
1504
+ RANEHOLDIN.NS
1505
+ RATEGAIN.NS
1506
+ RATNAMANI.NS
1507
+ RATNAVEER.NS
1508
+ RAYMOND.NS
1509
+ RAYMONDLSL.NS
1510
+ RBA.NS
1511
+ RBLBANK.NS
1512
+ RBZJEWEL.NS
1513
+ RCF.NS
1514
+ RCOM.NS
1515
+ RECLTD.NS
1516
+ REDINGTON.NS
1517
+ REDTAPE.NS
1518
+ REFEX.NS
1519
+ REGENCERAM.NS
1520
+ RELAXO.NS
1521
+ RELCHEMQ.NS
1522
+ RELIABLE.NS
1523
+ RELIANCE.NS
1524
+ RELIGARE.NS
1525
+ RELINFRA.NS
1526
+ RELTD.NS
1527
+ REMSONSIND.NS
1528
+ RENUKA.NS
1529
+ REPCOHOME.NS
1530
+ REPL.NS
1531
+ REPRO.NS
1532
+ RESPONIND.NS
1533
+ RETAIL.NS
1534
+ RGL.NS
1535
+ RHFL.NS
1536
+ RHIM.NS
1537
+ RHL.NS
1538
+ RICOAUTO.NS
1539
+ RIIL.NS
1540
+ RISHABH.NS
1541
+ RITCO.NS
1542
+ RITES.NS
1543
+ RKDL.NS
1544
+ RKEC.NS
1545
+ RKFORGE.NS
1546
+ RKSWAMY.NS
1547
+ RML.NS
1548
+ ROHLTD.NS
1549
+ ROLEXRINGS.NS
1550
+ ROLLT.NS
1551
+ ROLTA.NS
1552
+ ROML.NS
1553
+ ROSSARI.NS
1554
+ ROSSELLIND.NS
1555
+ ROSSTECH.NS
1556
+ ROTO.NS
1557
+ ROUTE.NS
1558
+ RPEL.NS
1559
+ RPGLIFE.NS
1560
+ RPOWER.NS
1561
+ RPPINFRA.NS
1562
+ RPPL.NS
1563
+ RPSGVENT.NS
1564
+ RPTECH.NS
1565
+ RRKABEL.NS
1566
+ RSSOFTWARE.NS
1567
+ RSWM.NS
1568
+ RSYSTEMS.NS
1569
+ RTNINDIA.NS
1570
+ RTNPOWER.NS
1571
+ RUBFILA.NS
1572
+ RUBYMILLS.NS
1573
+ RUCHINFRA.NS
1574
+ RUCHIRA.NS
1575
+ RUPA.NS
1576
+ RUSHIL.NS
1577
+ RUSTOMJEE.NS
1578
+ RVHL.NS
1579
+ RVNL.NS
1580
+ RVTH.NS
1581
+ S&SPOWER.NS
1582
+ SABEVENTS.NS
1583
+ SABTNL.NS
1584
+ SADBHAV.NS
1585
+ SADBHIN.NS
1586
+ SADHNANIQ.NS
1587
+ SAFARI.NS
1588
+ SAGARDEEP.NS
1589
+ SAGCEM.NS
1590
+ SAGILITY.NS
1591
+ SAH.NS
1592
+ SAHYADRI.NS
1593
+ SAIL.NS
1594
+ SAILIFE.NS
1595
+ SAKAR.NS
1596
+ SAKHTISUG.NS
1597
+ SAKSOFT.NS
1598
+ SAKUMA.NS
1599
+ SALASAR.NS
1600
+ SALONA.NS
1601
+ SALSTEEL.NS
1602
+ SALZERELEC.NS
1603
+ SAMBHAAV.NS
1604
+ SAMHI.NS
1605
+ SAMMAANCAP.NS
1606
+ SAMPANN.NS
1607
+ SANATHAN.NS
1608
+ SANCO.NS
1609
+ SANDESH.NS
1610
+ SANDHAR.NS
1611
+ SANDUMA.NS
1612
+ SANGAMIND.NS
1613
+ SANGHIIND.NS
1614
+ SANGHVIMOV.NS
1615
+ SANGINITA.NS
1616
+ SANOFI.NS
1617
+ SANOFICONR.NS
1618
+ SANSERA.NS
1619
+ SANSTAR.NS
1620
+ SANWARIA.NS
1621
+ SAPPHIRE.NS
1622
+ SARDAEN.NS
1623
+ SAREGAMA.NS
1624
+ SARLAPOLY.NS
1625
+ SARVESHWAR.NS
1626
+ SASKEN.NS
1627
+ SASTASUNDR.NS
1628
+ SATIA.NS
1629
+ SATIN.NS
1630
+ SATINDLTD.NS
1631
+ SAURASHCEM.NS
1632
+ SBC.NS
1633
+ SBCL.NS
1634
+ SBFC.NS
1635
+ SBGLP.NS
1636
+ SBICARD.NS
1637
+ SBILIFE.NS
1638
+ SBIN.NS
1639
+ SCHAEFFLER.NS
1640
+ SCHAND.NS
1641
+ SCHNEIDER.NS
1642
+ SCI.NS
1643
+ SCILAL.NS
1644
+ SCPL.NS
1645
+ SDBL.NS
1646
+ SEAMECLTD.NS
1647
+ SECMARK.NS
1648
+ SECURKLOUD.NS
1649
+ SEJALLTD.NS
1650
+ SELAN.NS
1651
+ SELMC.NS
1652
+ SEMAC.NS
1653
+ SENCO.NS
1654
+ SENORES.NS
1655
+ SEPC.NS
1656
+ SEQUENT.NS
1657
+ SERVOTECH.NS
1658
+ SESHAPAPER.NS
1659
+ SETCO.NS
1660
+ SETUINFRA.NS
1661
+ SFL.NS
1662
+ SGIL.NS
1663
+ SGL.NS
1664
+ SGLTL.NS
1665
+ SHAH.NS
1666
+ SHAHALLOYS.NS
1667
+ SHAILY.NS
1668
+ SHAKTIPUMP.NS
1669
+ SHALBY.NS
1670
+ SHALPAINTS.NS
1671
+ SHANKARA.NS
1672
+ SHANTI.NS
1673
+ SHANTIGEAR.NS
1674
+ SHARDACROP.NS
1675
+ SHARDAMOTR.NS
1676
+ SHAREINDIA.NS
1677
+ SHEKHAWATI.NS
1678
+ SHEMAROO.NS
1679
+ SHILPAMED.NS
1680
+ SHIVALIK.NS
1681
+ SHIVAMAUTO.NS
1682
+ SHIVAMILLS.NS
1683
+ SHIVATEX.NS
1684
+ SHK.NS
1685
+ SHOPERSTOP.NS
1686
+ SHRADHA.NS
1687
+ SHREDIGCEM.NS
1688
+ SHREECEM.NS
1689
+ SHREEPUSHK.NS
1690
+ SHREERAMA.NS
1691
+ SHRENIK.NS
1692
+ SHREYANIND.NS
1693
+ SHRIPISTON.NS
1694
+ SHRIRAMFIN.NS
1695
+ SHRIRAMPPS.NS
1696
+ SHYAMCENT.NS
1697
+ SHYAMMETL.NS
1698
+ SHYAMTEL.NS
1699
+ SIEMENS.NS
1700
+ SIGACHI.NS
1701
+ SIGIND.NS
1702
+ SIGMA.NS
1703
+ SIGNATURE.NS
1704
+ SIGNPOST.NS
1705
+ SIKKO.NS
1706
+ SIL.NS
1707
+ SILGO.NS
1708
+ SILINV.NS
1709
+ SILLYMONKS.NS
1710
+ SILVERTUC.NS
1711
+ SIMBHALS.NS
1712
+ SIMPLEXINF.NS
1713
+ SINCLAIR.NS
1714
+ SINDHUTRAD.NS
1715
+ SINTERCOM.NS
1716
+ SIRCA.NS
1717
+ SIS.NS
1718
+ SITINET.NS
1719
+ SIYSIL.NS
1720
+ SJS.NS
1721
+ SJVN.NS
1722
+ SKFINDIA.NS
1723
+ SKIPPER.NS
1724
+ SKMEGGPROD.NS
1725
+ SKYGOLD.NS
1726
+ SMARTLINK.NS
1727
+ SMCGLOBAL.NS
1728
+ SMLISUZU.NS
1729
+ SMLT.NS
1730
+ SMSLIFE.NS
1731
+ SMSPHARMA.NS
1732
+ SNOWMAN.NS
1733
+ SOBHA.NS
1734
+ SOFTTECH.NS
1735
+ SOLARA.NS
1736
+ SOLARINDS.NS
1737
+ SOMANYCERA.NS
1738
+ SOMATEX.NS
1739
+ SOMICONVEY.NS
1740
+ SONACOMS.NS
1741
+ SONAMLTD.NS
1742
+ SONATSOFTW.NS
1743
+ SOTL.NS
1744
+ SOUTHBANK.NS
1745
+ SOUTHWEST.NS
1746
+ SPAL.NS
1747
+ SPANDANA.NS
1748
+ SPARC.NS
1749
+ SPCENET.NS
1750
+ SPECIALITY.NS
1751
+ SPECTRUM.NS
1752
+ SPENCERS.NS
1753
+ SPIC.NS
1754
+ SPLIL.NS
1755
+ SPLPETRO.NS
1756
+ SPMLINFRA.NS
1757
+ SPORTKING.NS
1758
+ SRD.NS
1759
+ SREEL.NS
1760
+ SRF.NS
1761
+ SRGHFL.NS
1762
+ SRHHYPOLTD.NS
1763
+ SRM.NS
1764
+ SRPL.NS
1765
+ SSDL.NS
1766
+ SSWL.NS
1767
+ STALLION.NS
1768
+ STANLEY.NS
1769
+ STAR.NS
1770
+ STARCEMENT.NS
1771
+ STARHEALTH.NS
1772
+ STARPAPER.NS
1773
+ STARTECK.NS
1774
+ STCINDIA.NS
1775
+ STEELCAS.NS
1776
+ STEELCITY.NS
1777
+ STEELXIND.NS
1778
+ STEL.NS
1779
+ STERTOOLS.NS
1780
+ STLTECH.NS
1781
+ STOVEKRAFT.NS
1782
+ STYLAMIND.NS
1783
+ STYLEBAAZA.NS
1784
+ STYRENIX.NS
1785
+ SUBEXLTD.NS
1786
+ SUBROS.NS
1787
+ SUDARSCHEM.NS
1788
+ SUKHJITS.NS
1789
+ SULA.NS
1790
+ SUMICHEM.NS
1791
+ SUMIT.NS
1792
+ SUMMITSEC.NS
1793
+ SUNCLAY.NS
1794
+ SUNDARAM.NS
1795
+ SUNDARMFIN.NS
1796
+ SUNDARMHLD.NS
1797
+ SUNDRMBRAK.NS
1798
+ SUNDRMFAST.NS
1799
+ SUNDROP.NS
1800
+ SUNFLAG.NS
1801
+ SUNPHARMA.NS
1802
+ SUNTECK.NS
1803
+ SUNTV.NS
1804
+ SUPERHOUSE.NS
1805
+ SUPERSPIN.NS
1806
+ SUPRAJIT.NS
1807
+ SUPREME.NS
1808
+ SUPREMEENG.NS
1809
+ SUPREMEIND.NS
1810
+ SUPREMEINF.NS
1811
+ SUPRIYA.NS
1812
+ SURAJEST.NS
1813
+ SURAJLTD.NS
1814
+ SURAKSHA.NS
1815
+ SURANASOL.NS
1816
+ SURANAT&P.NS
1817
+ SURYALAXMI.NS
1818
+ SURYAROSNI.NS
1819
+ SURYODAY.NS
1820
+ SUTLEJTEX.NS
1821
+ SUULD.NS
1822
+ SUVEN.NS
1823
+ SUVENPHAR.NS
1824
+ SUVIDHAA.NS
1825
+ SUYOG.NS
1826
+ SUZLON.NS
1827
+ SVLL.NS
1828
+ SVPGLOB.NS
1829
+ SWANENERGY.NS
1830
+ SWARAJENG.NS
1831
+ SWELECTES.NS
1832
+ SWIGGY.NS
1833
+ SWSOLAR.NS
1834
+ SYMPHONY.NS
1835
+ SYNCOMF.NS
1836
+ SYNGENE.NS
1837
+ SYRMA.NS
1838
+ TAINWALCHM.NS
1839
+ TAJGVK.NS
1840
+ TAKE.NS
1841
+ TALBROAUTO.NS
1842
+ TANLA.NS
1843
+ TARACHAND.NS
1844
+ TARAPUR.NS
1845
+ TARC.NS
1846
+ TARIL.NS
1847
+ TARMAT.NS
1848
+ TARSONS.NS
1849
+ TASTYBITE.NS
1850
+ TATACHEM.NS
1851
+ TATACOMM.NS
1852
+ TATACONSUM.NS
1853
+ TATAELXSI.NS
1854
+ TATAINVEST.NS
1855
+ TATAMOTORS.NS
1856
+ TATAPOWER.NS
1857
+ TATASTEEL.NS
1858
+ TATATECH.NS
1859
+ TATVA.NS
1860
+ TBOTEK.NS
1861
+ TBZ.NS
1862
+ TCI.NS
1863
+ TCIEXP.NS
1864
+ TCIFINANCE.NS
1865
+ TCPLPACK.NS
1866
+ TCS.NS
1867
+ TDPOWERSYS.NS
1868
+ TEAMLEASE.NS
1869
+ TECHM.NS
1870
+ TECHNOE.NS
1871
+ TEGA.NS
1872
+ TEJASNET.NS
1873
+ TEMBO.NS
1874
+ TERASOFT.NS
1875
+ TEXINFRA.NS
1876
+ TEXMOPIPES.NS
1877
+ TEXRAIL.NS
1878
+ TFCILTD.NS
1879
+ TFL.NS
1880
+ TGBHOTELS.NS
1881
+ THANGAMAYL.NS
1882
+ THEINVEST.NS
1883
+ THEJO.NS
1884
+ THEMISMED.NS
1885
+ THERMAX.NS
1886
+ THOMASCOOK.NS
1887
+ THOMASCOTT.NS
1888
+ THYROCARE.NS
1889
+ TI.NS
1890
+ TICL.NS
1891
+ TIIL.NS
1892
+ TIINDIA.NS
1893
+ TIJARIA.NS
1894
+ TIL.NS
1895
+ TIMESGTY.NS
1896
+ TIMETECHNO.NS
1897
+ TIMKEN.NS
1898
+ TINNARUBR.NS
1899
+ TIPSFILMS.NS
1900
+ TIPSMUSIC.NS
1901
+ TIRUMALCHM.NS
1902
+ TIRUPATIFL.NS
1903
+ TITAGARH.NS
1904
+ TITAN.NS
1905
+ TMB.NS
1906
+ TNPETRO.NS
1907
+ TNPL.NS
1908
+ TNTELE.NS
1909
+ TOKYOPLAST.NS
1910
+ TOLINS.NS
1911
+ TORNTPHARM.NS
1912
+ TORNTPOWER.NS
1913
+ TOTAL.NS
1914
+ TOUCHWOOD.NS
1915
+ TPHQ.NS
1916
+ TPLPLASTEH.NS
1917
+ TRACXN.NS
1918
+ TRANSRAILL.NS
1919
+ TRANSWORLD.NS
1920
+ TREEHOUSE.NS
1921
+ TREJHARA.NS
1922
+ TREL.NS
1923
+ TRENT.NS
1924
+ TRF.NS
1925
+ TRIDENT.NS
1926
+ TRIGYN.NS
1927
+ TRITURBINE.NS
1928
+ TRIVENI.NS
1929
+ TRU.NS
1930
+ TTKHLTCARE.NS
1931
+ TTKPRESTIG.NS
1932
+ TTL.NS
1933
+ TTML.NS
1934
+ TVSELECT.NS
1935
+ TVSHLTD.NS
1936
+ TVSMOTOR.NS
1937
+ TVSSCS.NS
1938
+ TVSSRICHAK.NS
1939
+ TVTODAY.NS
1940
+ TVVISION.NS
1941
+ UBL.NS
1942
+ UCAL.NS
1943
+ UCOBANK.NS
1944
+ UDAICEMENT.NS
1945
+ UDS.NS
1946
+ UFLEX.NS
1947
+ UFO.NS
1948
+ UGARSUGAR.NS
1949
+ UGROCAP.NS
1950
+ UJJIVANSFB.NS
1951
+ ULTRACEMCO.NS
1952
+ UMAEXPORTS.NS
1953
+ UMANGDAIRY.NS
1954
+ UMESLTD.NS
1955
+ UMIYA-MRO.NS
1956
+ UNICHEMLAB.NS
1957
+ UNIDT.NS
1958
+ UNIECOM.NS
1959
+ UNIENTER.NS
1960
+ UNIINFO.NS
1961
+ UNIMECH.NS
1962
+ UNIONBANK.NS
1963
+ UNIPARTS.NS
1964
+ UNITDSPR.NS
1965
+ UNITECH.NS
1966
+ UNITEDPOLY.NS
1967
+ UNITEDTEA.NS
1968
+ UNIVASTU.NS
1969
+ UNIVCABLES.NS
1970
+ UNIVPHOTO.NS
1971
+ UNOMINDA.NS
1972
+ UPL.NS
1973
+ URAVIDEF.NS
1974
+ URJA.NS
1975
+ USHAMART.NS
1976
+ USK.NS
1977
+ UTIAMC.NS
1978
+ UTKARSHBNK.NS
1979
+ UTTAMSUGAR.NS
1980
+ UYFINCORP.NS
1981
+ V2RETAIL.NS
1982
+ VADILALIND.NS
1983
+ VAIBHAVGBL.NS
1984
+ VAISHALI.NS
1985
+ VAKRANGEE.NS
1986
+ VALIANTLAB.NS
1987
+ VALIANTORG.NS
1988
+ VARDHACRLC.NS
1989
+ VARDMNPOLY.NS
1990
+ VARROC.NS
1991
+ VASCONEQ.NS
1992
+ VASWANI.NS
1993
+ VBL.NS
1994
+ VCL.NS
1995
+ VEDL.NS
1996
+ VEEDOL.NS
1997
+ VENKEYS.NS
1998
+ VENTIVE.NS
1999
+ VENUSPIPES.NS
2000
+ VENUSREM.NS
2001
+ VERANDA.NS
2002
+ VERTOZ.NS
2003
+ VESUVIUS.NS
2004
+ VETO.NS
2005
+ VGUARD.NS
2006
+ VHL.NS
2007
+ VHLTD.NS
2008
+ VIDHIING.NS
2009
+ VIJAYA.NS
2010
+ VIJIFIN.NS
2011
+ VIKASECO.NS
2012
+ VIKASLIFE.NS
2013
+ VIMTALABS.NS
2014
+ VINATIORGA.NS
2015
+ VINCOFE.NS
2016
+ VINDHYATEL.NS
2017
+ VINEETLAB.NS
2018
+ VINNY.NS
2019
+ VINYLINDIA.NS
2020
+ VIPCLOTHNG.NS
2021
+ VIPIND.NS
2022
+ VIPULLTD.NS
2023
+ VIRINCHI.NS
2024
+ VISAKAIND.NS
2025
+ VISASTEEL.NS
2026
+ VISHNU.NS
2027
+ VISHWARAJ.NS
2028
+ VIVIDHA.NS
2029
+ VLEGOV.NS
2030
+ VLSFINANCE.NS
2031
+ VMART.NS
2032
+ VMM.NS
2033
+ VOLTAMP.NS
2034
+ VOLTAS.NS
2035
+ VPRPL.NS
2036
+ VRAJ.NS
2037
+ VRLLOG.NS
2038
+ VSSL.NS
2039
+ VSTIND.NS
2040
+ VSTL.NS
2041
+ VSTTILLERS.NS
2042
+ VTL.NS
2043
+ WAAREEENER.NS
2044
+ WAAREERTL.NS
2045
+ WABAG.NS
2046
+ WALCHANNAG.NS
2047
+ WANBURY.NS
2048
+ WCIL.NS
2049
+ WEALTH.NS
2050
+ WEBELSOLAR.NS
2051
+ WEIZMANIND.NS
2052
+ WEL.NS
2053
+ WELCORP.NS
2054
+ WELENT.NS
2055
+ WELINV.NS
2056
+ WELSPUNLIV.NS
2057
+ WENDT.NS
2058
+ WESTLIFE.NS
2059
+ WEWIN.NS
2060
+ WHEELS.NS
2061
+ WHIRLPOOL.NS
2062
+ WILLAMAGOR.NS
2063
+ WINDLAS.NS
2064
+ WINDMACHIN.NS
2065
+ WINSOME.NS
2066
+ WIPL.NS
2067
+ WIPRO.NS
2068
+ WOCKPHARMA.NS
2069
+ WONDERLA.NS
2070
+ WORTH.NS
2071
+ WSI.NS
2072
+ WSTCSTPAPR.NS
2073
+ XCHANGING.NS
2074
+ XELPMOC.NS
2075
+ XPROINDIA.NS
2076
+ XTGLOBAL.NS
2077
+ YAARI.NS
2078
+ YASHO.NS
2079
+ YATHARTH.NS
2080
+ YATRA.NS
2081
+ YESBANK.NS
2082
+ YUKEN.NS
2083
+ ZAGGLE.NS
2084
+ ZEEL.NS
2085
+ ZEELEARN.NS
2086
+ ZEEMEDIA.NS
2087
+ ZENITHEXPO.NS
2088
+ ZENITHSTL.NS
2089
+ ZENSARTECH.NS
2090
+ ZENTEC.NS
2091
+ ZFCVINDIA.NS
2092
+ ZIMLAB.NS
2093
+ ZODIAC.NS
2094
+ ZODIACLOTH.NS
2095
+ ZOTA.NS
2096
+ ZUARI.NS
2097
+ ZUARIIND.NS
2098
+ ZYDUSLIFE.NS
2099
+ ZYDUSWELL.NS
app/user.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Request, Depends, HTTPException
2
+ from fastapi.responses import HTMLResponse, RedirectResponse
3
+ from sqlalchemy.orm import Session
4
+
5
+ from app import models, schemas, crud
6
+ from app.database import get_db
7
+ from app.auth import get_current_active_user
8
+ from app.main import templates # Import templates from main.py
9
+ import logging
10
+ import yfinance as yf
11
+ from datetime import datetime, timedelta
12
+ import pytz # for timezone handling
13
+ from typing import List
14
+
15
+ logger = logging.getLogger(__name__)
16
+ router = APIRouter()
17
+
18
+ # Define timezone (Asia/Kolkata)
19
+ tz = pytz.timezone('Asia/Kolkata')
20
+
21
+ # Function to read tickers from tickers.txt
22
+ def get_all_tickers():
23
+ tickers = []
24
+ try:
25
+ with open("app/tickers.txt", "r") as f:
26
+ tickers = [line.strip() for line in f if line.strip()]
27
+ except FileNotFoundError:
28
+ logger.error("tickers.txt not found.")
29
+ return tickers
30
+
31
+ # Function to find closest available trading day price on or before the target date
32
+ def get_closest_price(hist, date):
33
+ # Filter dates in hist index that are <= target date
34
+ available_dates = hist.index[hist.index <= date]
35
+ if not available_dates.empty:
36
+ closest_date = available_dates[-1]
37
+ return hist.loc[closest_date]['Close'], closest_date.date()
38
+ else:
39
+ return None, None
40
+
41
+ # Function to fetch historical data and calculate prices
42
+ def fetch_stock_prices(ticker_symbol: str):
43
+ ticker = yf.Ticker(ticker_symbol)
44
+
45
+ # Get today's date as timezone-aware datetime
46
+ today = datetime.now(tz)
47
+
48
+ # Define the dates for 1 month, 6 months, 1 year, and 3 years ago (timezone-aware)
49
+ dates = {
50
+ "Current": today,
51
+ "1 Month Ago": today - timedelta(days=30),
52
+ "6 Months Ago": today - timedelta(days=182),
53
+ "1 Year Ago": today - timedelta(days=365),
54
+ "3 Years Ago": today - timedelta(days=3*365),
55
+ }
56
+
57
+ # Fetch historical data for the last 4 years to cover all dates
58
+ start_date = (today - timedelta(days=4*365)).strftime('%Y-%m-%d')
59
+ end_date = today.strftime('%Y-%m-%d')
60
+ hist = ticker.history(start=start_date, end=end_date)
61
+
62
+ # The index of hist is timezone-aware, ensure it matches tz
63
+ if hist.index.tz is None:
64
+ # If index is naive, localize it
65
+ hist.index = hist.index.tz_localize(tz)
66
+ else:
67
+ # Convert to desired timezone
68
+ hist.index = hist.index.tz_convert(tz)
69
+
70
+ # Retrieve prices for each date
71
+ prices = {}
72
+ for label, date in dates.items():
73
+ price, actual_date = get_closest_price(hist, date)
74
+ if price is not None:
75
+ prices[label] = (f"₹{price:.2f}", actual_date.strftime('%Y-%m-%d') if actual_date else None)
76
+ else:
77
+ prices[label] = ("No data", None)
78
+
79
+ return prices
80
+
81
+ # API endpoint for searching tickers
82
+ @router.get("/api/stocks/search", response_model=List[str])
83
+ async def search_stocks(query: str):
84
+ all_tickers = get_all_tickers()
85
+ # Simple case-insensitive search
86
+ matching_tickers = [ticker for ticker in all_tickers if query.lower() in ticker.lower()]
87
+ return matching_tickers
88
+
89
+ # API endpoint for fetching stock prices
90
+ @router.get("/api/stocks/price/{ticker_symbol}")
91
+ async def get_stock_price(ticker_symbol: str):
92
+ try:
93
+ prices = fetch_stock_prices(ticker_symbol)
94
+ return prices
95
+ except Exception as e:
96
+ logger.error(f"Error fetching price for {ticker_symbol}: {e}")
97
+ raise HTTPException(status_code=500, detail="Could not fetch stock price")
98
+
99
+
100
+ @router.get("/home", response_class=HTMLResponse)
101
+ async def user_homepage(request: Request): # Removed: current_user: models.User = Depends(get_current_active_user)
102
+ # User data will be fetched and displayed by client-side JavaScript
103
+ return templates.TemplateResponse("user/homepage.html", {
104
+ "request": request,
105
+ # "user": current_user, # This will be handled by client-side JS
106
+ "title": "User Homepage"
107
+ })
108
+
109
+ @router.get("/chatbot/{model_type}", response_class=HTMLResponse)
110
+ async def chatbot_page(request: Request, model_type: str): # Removed: current_user: models.User = Depends(get_current_active_user)
111
+ # Client-side JS on chatbot.html already handles token for API submission.
112
+ # This route now just serves the page structure.
113
+ valid_models = ["base", "enhanced", "rule_based"]
114
+ if model_type.lower() not in valid_models:
115
+ raise HTTPException(status_code=404, detail="Model type not found")
116
+
117
+ # Prepare context for the template based on model_type
118
+ # This context will help the template render the correct form fields
119
+ form_fields = []
120
+ page_title = ""
121
+
122
+ if model_type == "base":
123
+ page_title = "Base Model Advisor"
124
+ form_fields = [
125
+ {"name": "Salary", "label": "Monthly Salary (₹)", "type": "number", "required": True, "min": 0},
126
+ {"name": "Expenses", "label": "Monthly Expenses (₹)", "type": "number", "required": True, "min": 0},
127
+ {"name": "Savings", "label": "Monthly Savings (₹)", "type": "number", "required": True, "min": 0},
128
+ {"name": "Lifecycle_Stage", "label": "Lifecycle Stage", "type": "select", "required": True, "options": ["Student", "Early Career", "Mid-Career", "Late Career", "Retired"]},
129
+ {"name": "Risk_Appetite", "label": "Risk Appetite", "type": "select", "required": True, "options": ["Low", "Medium", "High"]},
130
+ {"name": "Investment_Horizon", "label": "Investment Horizon", "type": "select", "required": True, "options": ["Short-term", "Medium-term", "Long-term"]},
131
+ ]
132
+ elif model_type == "enhanced":
133
+ page_title = "Enhanced Model Advisor"
134
+
135
+ # Define the options for dropdowns based on provided lists
136
+ cities = sorted([
137
+ 'Mumbai', 'Delhi', 'Bangalore', 'Hyderabad', 'Pune', 'Chennai', 'Jaipur',
138
+ 'Kochi', 'Kolkata', 'Ahmedabad', 'Gurgaon', 'Lucknow', 'Nagpur', 'Chandigarh',
139
+ 'Surat', 'Indore', 'Bhopal'
140
+ ])
141
+
142
+ professions = sorted([
143
+ 'Software Engineer', 'Doctor', 'Retired Banker', 'Student', 'Marketing Manager', 'Teacher',
144
+ 'Freelancer', 'Architect', 'Business Owner', 'Nurse', 'Product Manager', 'CA', 'Data Analyst',
145
+ 'Retired Professor', 'Journalist', 'Graphic Designer', 'Sales Executive', 'HR Manager', 'Intern',
146
+ 'Dentist', 'Lawyer', 'Content Creator', 'Pensioner', 'UX Designer', 'Government Employee',
147
+ 'Fashion Designer', 'Startup Founder', 'Homemaker (Investor)', 'Investment Banker', 'IT Consultant',
148
+ 'College Student', 'Pharmacist', 'Textile Business Owner', 'Event Planner', 'Film Producer',
149
+ 'Nutritionist', 'Retired Army Officer', 'Social Media Manager', 'Airline Pilot', 'Biotech Researcher',
150
+ 'Real Estate Agent', 'AI Engineer', 'Retired Teacher', 'Financial Analyst', 'Software Developer',
151
+ 'NGO Director', 'Fitness Trainer', 'Digital Marketer', 'Content Strategist', 'Retired Engineer',
152
+ 'UI Developer', 'Operations Manager', 'Corporate Lawyer', 'School Principal', 'AI Researcher',
153
+ 'Junior Doctor', 'Finance Manager', 'Fashion Blogger', 'HR Consultant', 'Video Editor',
154
+ 'Small Business Owner', 'Dietitian', 'Supply Chain Manager', 'Interior Designer', 'Sales Manager',
155
+ 'PR Executive', 'Logistics Head', 'Content Writer', 'Retired Govt. Employee', 'Marketing Lead',
156
+ 'IT Manager', 'Startup Intern', 'Export Manager', 'Fitness Instructor', 'Real Estate Broker',
157
+ 'Event Manager', 'Software Trainee', 'Data Scientist', 'HR Director', 'Hotel Manager',
158
+ 'Social Worker', 'Cybersecurity Expert', 'E-commerce Manager', 'Bank Manager', 'Retired IT Manager',
159
+ 'UX Researcher', 'Product Designer', 'Cybersecurity Analyst', 'Podcast Producer', 'E-commerce Seller',
160
+ 'Cloud Architect', 'Corporate Trainer', 'AI Trainer', 'Supply Chain Head', 'Product Owner',
161
+ 'UI/UX Designer', 'Finance Director', 'Sustainability Consultant', 'Retired Bank Manager',
162
+ 'Social Media Influencer', 'Blockchain Developer', 'NGO Head', 'Data Engineer', 'Event Curator',
163
+ 'CTO', 'Content Marketer', 'Retired Army Major', 'AR/VR Developer', 'Logistics Manager', 'VP Sales',
164
+ 'EdTech Founder', 'SEO Specialist', 'Yoga Instructor', 'IT Director', 'App Developer',
165
+ 'Consultant Cardiologist', 'Freelance Writer', 'Cloud Engineer', 'Retired CA', 'Digital Artist',
166
+ 'Retired Pilot', 'Graphic Animator', 'AI Ethicist', 'School Trustee', 'Robotics Engineer',
167
+ 'Fashion Stylist', 'Retired Nurse', 'AI Ethics Consultant', 'Drone Engineer', 'Retired Bank Clerk',
168
+ 'Digital Nomad', 'CFO', 'Sustainability Analyst', 'Retired Journalist', 'Social Entrepreneur',
169
+ 'DevOps Engineer', 'School Counselor', 'Retired Army Colonel', 'AR Developer', 'Sales Director',
170
+ 'EdTech Consultant', 'SEO Expert', 'Cardiologist', '3D Artist', 'HR Head', 'Animator','Other',
171
+ 'Fashion Influencer'
172
+ ])
173
+
174
+ form_fields = [
175
+ {"name": "Profession", "label": "Profession", "type": "select", "required": True, "options": professions},
176
+ {"name": "City", "label": "City", "type": "select", "required": True, "options": cities},
177
+ {"name": "Salary", "label": "Monthly Salary (₹)", "type": "number", "required": True, "min": 0},
178
+ {"name": "Expenses", "label": "Monthly Expenses (₹)", "type": "number", "required": True, "min": 0},
179
+ {"name": "Savings", "label": "Monthly Savings (₹)", "type": "number", "required": True, "min": 0},
180
+ {"name": "Lifecycle_Stage", "label": "Lifecycle Stage", "type": "select", "required": True, "options": ["Student", "Early Career", "Mid-Career", "Late Career", "Retired"]},
181
+ {"name": "Risk_Appetite", "label": "Risk Appetite", "type": "select", "required": True, "options": ["Low", "Medium", "High"]},
182
+ {"name": "Investment_Horizon", "label": "Investment Horizon", "type": "select", "required": True, "options": ["Short-term", "Medium-term", "Long-term"]},
183
+ ]
184
+ elif model_type == "rule_based":
185
+ page_title = "Rule-Based Advisor"
186
+ form_fields = [
187
+ {"name": "Lifecycle_Stage", "label": "Lifecycle Stage", "type": "select", "required": True, "options": ["Student", "Early Career", "Mid-Career", "Late Career", "Retired"]},
188
+ {"name": "Risk_Appetite", "label": "Risk Appetite", "type": "select", "required": True, "options": ["Low", "Medium", "High"]},
189
+ {"name": "Investment_Horizon", "label": "Investment Horizon", "type": "select", "required": True, "options": ["Short-term", "Medium-term", "Long-term"]},
190
+ {"name": "Annual_Salary_Package", "label": "Annual Salary Package (CTC) (₹)", "type": "number", "required": True, "min": 0},
191
+ {"name": "Monthly_In_hand_Salary", "label": "Actual Monthly In-hand Salary (₹)", "type": "number", "required": True, "min": 0},
192
+ {"name": "Total_Monthly_Expenses", "label": "Total Estimated Monthly Expenses (₹)", "type": "number", "required": True, "min": 0},
193
+ ]
194
+
195
+ return templates.TemplateResponse("user/chatbot.html", {
196
+ "request": request,
197
+ # "user": current_user, # Client-side JS on this page handles API calls with token
198
+ "title": page_title,
199
+ "model_type": model_type,
200
+ "form_fields": form_fields
201
+ })
202
+
203
+ @router.get("/recommendations", response_class=HTMLResponse)
204
+ async def recommendations_page(request: Request): # Removed: current_user: models.User = Depends(get_current_active_user)
205
+ # Client-side JS could be added to this page if it needs to display user-specific info
206
+ # or make authenticated API calls for dynamic market data.
207
+ # For now, it serves static structure with mocked data.
208
+ # Data fetching logic would go into services/market_data_service.py
209
+ # For now, just rendering a template.
210
+ market_data = { # Mock data
211
+ "stocks": [
212
+ {"name": "Reliance Industries", "price": "2,850.50 INR", "change": "+0.5%"},
213
+ {"name": "Tata Consultancy Services", "price": "3,900.75 INR", "change": "-0.2%"},
214
+ {"name": "HDFC Bank", "price": "1,500.20 INR", "change": "+1.1%"},
215
+ ],
216
+ "gold_price": "96,172.27 -745.25 INR per 10g",
217
+ "recommended_stocks": [
218
+ {"name": "Infosys", "reason": "Strong growth potential in IT sector."},
219
+ {"name": "ICICI Bank", "reason": "Good financial performance and outlook."}
220
+ ]
221
+ }
222
+ return templates.TemplateResponse("user/recommendations.html", {
223
+ "request": request,
224
+ # "user": current_user, # Can be fetched by client-side JS if needed
225
+ "title": "Market Trends & Recommendations",
226
+ "market_data": market_data
227
+ })
app/user_financial_data.csv ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ UserID,UserEmail,ModelType,Timestamp,Name,Age,City,Profession,Salary,Expenses,Savings,Lifecycle Stage,Risk Appetite,Investment Horizon,Annual Salary Package,Monthly In-hand Salary,Total Monthly Expenses,Equity (%),Debt (%),Gold (%),FD/Cash (%)
2
+ 2,user1@gmail.com,base,2025-05-08 14:48:49,,,,,60000,20000,40000,Early Career,Medium,Medium-term,,,,35.6,26.3,7.1,31.0
3
+ 2,user1@gmail.com,enhanced,2025-05-08 14:50:49,,,Indore,Software Engineer,60000,40000,20000,Late Career,Medium,Medium-term,,,,32.3,40.7,9.0,18.0
4
+ 2,user1@gmail.com,rule_based,2025-05-08 14:52:36,,,,,,,,Early Career,Medium,Medium-term,700000,,30000,,,,
5
+ 3,user2@gmail.com,base,2025-05-08 15:25:39,,,,,90000,40000,50000,Mid-Career,Medium,Short-term,,,,36.9,27.2,10.5,25.4
6
+ 3,user2@gmail.com,enhanced,2025-05-08 15:28:12,,,Pune,Software Engineer,80000,60000,20000,Late Career,High,Long-term,,,,49.5,34.8,3.9,11.8
7
+ 4,user3@gmail.com,enhanced,2025-05-08 15:36:16,,,Pune,AI Ethics Consultant,90000,60000,30000,Mid-Career,Medium,Short-term,,,,37.2,27.6,11.5,23.7
8
+ 5,user7@gmail.com,base,2025-05-09 19:10:38,,,,,70000,50000,20000,Mid-Career,Medium,Short-term,,,,37.9,26.9,11.7,23.5
9
+ 5,user7@gmail.com,enhanced,2025-05-09 19:11:47,,,Bangalore,AI Engineer,80000,40000,40000,Late Career,High,Medium-term,,,,49.8,34.0,4.0,12.2
10
+ 5,user7@gmail.com,rule_based,2025-05-09 19:13:52,,,,,,,,Mid-Career,High,Short-term,700000,,40000,,,,
11
+ 6,mandar@gmail.com,base,2025-05-09 19:55:19,,,,,60000,40000,20000,Mid-Career,High,Long-term,,,,60.7,19.1,5.0,15.2
12
+ 6,mandar@gmail.com,enhanced,2025-05-09 19:56:30,,,Pune,AI Researcher,70000,40000,30000,Mid-Career,High,Short-term,,,,57.4,15.0,4.5,23.1
13
+ 6,mandar@gmail.com,rule_based,2025-05-09 19:57:28,,,,,,,,Mid-Career,Medium,Short-term,700000,,40000,,,,
14
+ 2,user1@gmail.com,enhanced,2025-05-26 20:39:38,,,Pune,Other,80000,50000,30000,Mid-Career,Medium,Short-term,,,,37.1,27.3,10.7,24.9
diagramcontent.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Project Module Block Diagrams
2
+
3
+ ## Overall Project
4
+
5
+ ```mermaid
6
+ graph LR
7
+ A[admin.py] --> B(auth.py: get_current_user, get_current_active_user)
8
+ A --> C(crud.py: get_user, get_users)
9
+ B --> D(database.py: get_db)
10
+ C --> D
11
+ E[main.py] --> B
12
+ E --> C
13
+ E --> H[user.py]
14
+ F(models.py: User, UserDataInput) --> D
15
+ G(schemas.py: UserCreate, User) --> F
16
+ H --> B
17
+ H --> C
18
+ I[services: chatbot_service.py, data_service.py] --> E
19
+ J[ml_models] --> I
20
+ subgraph app
21
+ A
22
+ B
23
+ C
24
+ D
25
+ E
26
+ F
27
+ G
28
+ H
29
+ I
30
+ J
31
+ end
32
+ ```
33
+
34
+ ## Admin Side
35
+
36
+ ```mermaid
37
+ graph LR
38
+ A[admin.py: admin_dashboard_shell, admin_view_user_details_shell] --> B(auth.py: get_current_admin_user)
39
+ A --> C(crud.py: get_user, get_users)
40
+ C --> D(database.py: get_db)
41
+ B --> D
42
+ style A fill:#f9f,stroke:#333,stroke-width:2px
43
+ ```
44
+
45
+ ## User Side
46
+
47
+ ```mermaid
48
+ graph LR
49
+ H[user.py: user_homepage, chatbot_page, recommendations_page] --> B(auth.py: get_current_active_user)
50
+ H --> C(crud.py: get_user_data_inputs_by_user_id)
51
+ C --> D(database.py: get_db)
52
+ B --> D
53
+ E[main.py: api_chatbot_interact] --> H
54
+ style H fill:#ccf,stroke:#333,stroke-width:2px