Spaces:
Running
Running
Upload 23 files
Browse files- app/__init__.py +11 -0
- app/__pycache__/__init__.cpython-313.pyc +0 -0
- app/__pycache__/bot.cpython-313.pyc +0 -0
- app/__pycache__/data_manager.cpython-313.pyc +0 -0
- app/__pycache__/processor.cpython-313.pyc +0 -0
- app/__pycache__/retriever.cpython-313.pyc +0 -0
- app/__pycache__/routes.cpython-313.pyc +0 -0
- app/__pycache__/run_scraper.cpython-313.pyc +0 -0
- app/__pycache__/scraper.cpython-313.pyc +0 -0
- app/bot.py +626 -0
- app/routes.py +22 -0
- app/run_scraper.py +30 -0
- app/scraper.py +408 -0
- app/setup_scraping.py +31 -0
- app/static/css/style.css +571 -0
- app/static/img/jupiter-logo.png +0 -0
- app/templates/base.html +14 -0
- app/templates/index.html +303 -0
- config.py +3 -0
- data/faq_embeddings.npy +3 -0
- data/faqs.csv +156 -0
- requirements.txt +23 -0
- run.py +5 -0
app/__init__.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Flask
|
2 |
+
|
3 |
+
print("Initializing Flask application...")
|
4 |
+
|
5 |
+
app = Flask(__name__)
|
6 |
+
app.config['SECRET_KEY'] = 'your-secret-key-here'
|
7 |
+
|
8 |
+
print("Loading routes...")
|
9 |
+
from app import routes
|
10 |
+
|
11 |
+
print("Application initialized successfully!")
|
app/__pycache__/__init__.cpython-313.pyc
ADDED
Binary file (523 Bytes). View file
|
|
app/__pycache__/bot.cpython-313.pyc
ADDED
Binary file (25.2 kB). View file
|
|
app/__pycache__/data_manager.cpython-313.pyc
ADDED
Binary file (2.42 kB). View file
|
|
app/__pycache__/processor.cpython-313.pyc
ADDED
Binary file (3.26 kB). View file
|
|
app/__pycache__/retriever.cpython-313.pyc
ADDED
Binary file (1.9 kB). View file
|
|
app/__pycache__/routes.cpython-313.pyc
ADDED
Binary file (1.13 kB). View file
|
|
app/__pycache__/run_scraper.cpython-313.pyc
ADDED
Binary file (1.24 kB). View file
|
|
app/__pycache__/scraper.cpython-313.pyc
ADDED
Binary file (20.9 kB). View file
|
|
app/bot.py
ADDED
@@ -0,0 +1,626 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/bot.py
|
2 |
+
from __future__ import annotations
|
3 |
+
|
4 |
+
import logging
|
5 |
+
import os
|
6 |
+
import re
|
7 |
+
import unicodedata
|
8 |
+
import warnings
|
9 |
+
from pathlib import Path
|
10 |
+
from typing import Any, List, Dict, Tuple
|
11 |
+
import json
|
12 |
+
|
13 |
+
import numpy as np
|
14 |
+
import pandas as pd
|
15 |
+
import torch
|
16 |
+
from sentence_transformers import SentenceTransformer, CrossEncoder
|
17 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
18 |
+
from transformers import pipeline, AutoTokenizer, AutoModelForSeq2SeqLM
|
19 |
+
import nltk
|
20 |
+
|
21 |
+
# Download required NLTK data
|
22 |
+
try:
|
23 |
+
nltk.download('punkt', quiet=True)
|
24 |
+
nltk.download('stopwords', quiet=True)
|
25 |
+
except:
|
26 |
+
pass
|
27 |
+
|
28 |
+
warnings.filterwarnings("ignore")
|
29 |
+
|
30 |
+
|
31 |
+
class RequirementError(RuntimeError):
|
32 |
+
pass
|
33 |
+
|
34 |
+
|
35 |
+
class JupiterFAQBot:
|
36 |
+
# ------------------------------------------------------------------ #
|
37 |
+
# Free Models Configuration
|
38 |
+
# ------------------------------------------------------------------ #
|
39 |
+
MODELS = {
|
40 |
+
"bi": "sentence-transformers/all-MiniLM-L6-v2", # Fast semantic search
|
41 |
+
"cross": "cross-encoder/ms-marco-MiniLM-L-6-v2", # Reranking
|
42 |
+
"qa": "deepset/roberta-base-squad2", # Better QA model
|
43 |
+
"summarizer": "facebook/bart-large-cnn", # Better summarization
|
44 |
+
}
|
45 |
+
|
46 |
+
# Retrieval parameters
|
47 |
+
TOP_K = 15 # More candidates for better coverage
|
48 |
+
HIGH_SIM = 0.85 # High confidence threshold
|
49 |
+
CROSS_OK = 0.50 # Cross-encoder threshold
|
50 |
+
MIN_SIM = 0.40 # Minimum similarity to consider
|
51 |
+
|
52 |
+
# Paths
|
53 |
+
EMB_CACHE = Path("data/faq_embeddings.npy")
|
54 |
+
FAQ_PATH = Path("data/faqs.csv")
|
55 |
+
|
56 |
+
# Response templates for better UX
|
57 |
+
CONFIDENCE_LEVELS = {
|
58 |
+
"high": "This information matches your query based on our FAQs:\n\n",
|
59 |
+
"medium": "This appears to be relevant to your question:\n\n",
|
60 |
+
"low": "This may be related to your query and could be helpful:\n\n",
|
61 |
+
"none": (
|
62 |
+
"We couldn't find a direct match for your question. "
|
63 |
+
"However, we can assist with topics such as:\n"
|
64 |
+
"• Account opening and KYC\n"
|
65 |
+
"• Payments and UPI\n"
|
66 |
+
"• Rewards and cashback\n"
|
67 |
+
"• Credit cards and loans\n"
|
68 |
+
"• Investments and savings\n\n"
|
69 |
+
"Please try rephrasing your question or selecting a topic above."
|
70 |
+
)
|
71 |
+
}
|
72 |
+
|
73 |
+
# ------------------------------------------------------------------ #
|
74 |
+
def __init__(self, csv_path: str = None) -> None:
|
75 |
+
logging.basicConfig(format="%(levelname)s | %(message)s", level=logging.INFO)
|
76 |
+
|
77 |
+
# Use provided path or default
|
78 |
+
self.csv_path = csv_path or str(self.FAQ_PATH)
|
79 |
+
|
80 |
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
81 |
+
self.pipe_dev = 0 if self.device.type == "cuda" else -1
|
82 |
+
|
83 |
+
self._load_data(self.csv_path)
|
84 |
+
self._setup_models()
|
85 |
+
self._setup_embeddings()
|
86 |
+
|
87 |
+
logging.info("Jupiter FAQ Bot ready ✔")
|
88 |
+
|
89 |
+
# ------------------------ Text Processing ------------------------- #
|
90 |
+
@staticmethod
|
91 |
+
def _clean(text: str) -> str:
|
92 |
+
"""Clean and normalize text"""
|
93 |
+
if pd.isna(text):
|
94 |
+
return ""
|
95 |
+
text = str(text)
|
96 |
+
text = unicodedata.normalize("NFC", text)
|
97 |
+
# Remove extra whitespace but keep sentence structure
|
98 |
+
text = re.sub(r'\s+', ' ', text)
|
99 |
+
# Keep bullet points and formatting
|
100 |
+
text = re.sub(r'•\s*', '\n• ', text)
|
101 |
+
return text.strip()
|
102 |
+
|
103 |
+
@staticmethod
|
104 |
+
def _preprocess_query(query: str) -> str:
|
105 |
+
"""Preprocess user query for better matching"""
|
106 |
+
# Expand common abbreviations
|
107 |
+
abbreviations = {
|
108 |
+
'kyc': 'know your customer verification',
|
109 |
+
'upi': 'unified payments interface',
|
110 |
+
'fd': 'fixed deposit',
|
111 |
+
'sip': 'systematic investment plan',
|
112 |
+
'neft': 'national electronic funds transfer',
|
113 |
+
'rtgs': 'real time gross settlement',
|
114 |
+
'imps': 'immediate payment service',
|
115 |
+
'emi': 'equated monthly installment',
|
116 |
+
'apr': 'annual percentage rate',
|
117 |
+
'atm': 'automated teller machine',
|
118 |
+
'pin': 'personal identification number',
|
119 |
+
}
|
120 |
+
|
121 |
+
query_lower = query.lower()
|
122 |
+
for abbr, full in abbreviations.items():
|
123 |
+
if abbr in query_lower.split():
|
124 |
+
query_lower = query_lower.replace(abbr, full)
|
125 |
+
|
126 |
+
return query_lower
|
127 |
+
|
128 |
+
# ------------------------ Initialization -------------------------- #
|
129 |
+
def _load_data(self, path: str):
|
130 |
+
"""Load and preprocess FAQ data"""
|
131 |
+
if not Path(path).exists():
|
132 |
+
raise RequirementError(f"CSV not found: {path}")
|
133 |
+
|
134 |
+
df = pd.read_csv(path)
|
135 |
+
|
136 |
+
# Clean all text fields
|
137 |
+
df["question"] = df["question"].apply(self._clean)
|
138 |
+
df["answer"] = df["answer"].apply(self._clean)
|
139 |
+
df["category"] = df["category"].fillna("General")
|
140 |
+
|
141 |
+
# Create searchable text combining question and category
|
142 |
+
df["searchable"] = df["question"].str.lower() + " " + df["category"].str.lower()
|
143 |
+
|
144 |
+
# Remove duplicates
|
145 |
+
df = df.drop_duplicates(subset=["question"]).reset_index(drop=True)
|
146 |
+
|
147 |
+
self.faq = df
|
148 |
+
logging.info(f"Loaded {len(self.faq)} FAQ entries from {len(df['category'].unique())} categories")
|
149 |
+
|
150 |
+
def _setup_models(self):
|
151 |
+
"""Initialize all models"""
|
152 |
+
logging.info("Loading models...")
|
153 |
+
|
154 |
+
# Sentence transformer for embeddings
|
155 |
+
self.bi = SentenceTransformer(self.MODELS["bi"], device=self.device)
|
156 |
+
|
157 |
+
# Cross-encoder for reranking
|
158 |
+
self.cross = CrossEncoder(self.MODELS["cross"], device=self.device)
|
159 |
+
|
160 |
+
# QA model
|
161 |
+
self.qa = pipeline(
|
162 |
+
"question-answering",
|
163 |
+
model=self.MODELS["qa"],
|
164 |
+
device=self.pipe_dev,
|
165 |
+
handle_impossible_answer=True
|
166 |
+
)
|
167 |
+
|
168 |
+
# Summarization model - using BART for better quality
|
169 |
+
self.summarizer = pipeline(
|
170 |
+
"summarization",
|
171 |
+
model=self.MODELS["summarizer"],
|
172 |
+
device=self.pipe_dev,
|
173 |
+
max_length=150,
|
174 |
+
min_length=50
|
175 |
+
)
|
176 |
+
|
177 |
+
logging.info("All models loaded successfully")
|
178 |
+
|
179 |
+
def _setup_embeddings(self):
|
180 |
+
"""Create or load embeddings"""
|
181 |
+
questions = self.faq["searchable"].tolist()
|
182 |
+
|
183 |
+
if self.EMB_CACHE.exists():
|
184 |
+
emb = np.load(self.EMB_CACHE)
|
185 |
+
if len(emb) != len(questions):
|
186 |
+
logging.info("Regenerating embeddings due to data change...")
|
187 |
+
emb = self.bi.encode(questions, show_progress_bar=True, convert_to_tensor=False)
|
188 |
+
np.save(self.EMB_CACHE, emb)
|
189 |
+
else:
|
190 |
+
logging.info("Creating embeddings for the first time...")
|
191 |
+
emb = self.bi.encode(questions, show_progress_bar=True, convert_to_tensor=False)
|
192 |
+
self.EMB_CACHE.parent.mkdir(parents=True, exist_ok=True)
|
193 |
+
np.save(self.EMB_CACHE, emb)
|
194 |
+
|
195 |
+
self.embeddings = emb
|
196 |
+
|
197 |
+
# ------------------------- Retrieval ------------------------------ #
|
198 |
+
def _retrieve_candidates(self, query: str, top_k: int = None) -> List[Dict]:
|
199 |
+
"""Retrieve top candidates using semantic search"""
|
200 |
+
if top_k is None:
|
201 |
+
top_k = self.TOP_K
|
202 |
+
|
203 |
+
# Preprocess query
|
204 |
+
processed_query = self._preprocess_query(query)
|
205 |
+
|
206 |
+
# Encode query
|
207 |
+
query_emb = self.bi.encode([processed_query])
|
208 |
+
|
209 |
+
# Calculate similarities
|
210 |
+
similarities = cosine_similarity(query_emb, self.embeddings)[0]
|
211 |
+
|
212 |
+
# Get top indices
|
213 |
+
top_indices = similarities.argsort()[-top_k:][::-1]
|
214 |
+
|
215 |
+
# Filter by minimum similarity
|
216 |
+
candidates = []
|
217 |
+
for idx in top_indices:
|
218 |
+
if similarities[idx] >= self.MIN_SIM:
|
219 |
+
candidates.append({
|
220 |
+
"idx": int(idx),
|
221 |
+
"question": self.faq.iloc[idx]["question"],
|
222 |
+
"answer": self.faq.iloc[idx]["answer"],
|
223 |
+
"category": self.faq.iloc[idx]["category"],
|
224 |
+
"similarity": float(similarities[idx])
|
225 |
+
})
|
226 |
+
|
227 |
+
return candidates
|
228 |
+
|
229 |
+
def _rerank_candidates(self, query: str, candidates: List[Dict]) -> List[Dict]:
|
230 |
+
"""Rerank candidates using cross-encoder"""
|
231 |
+
if not candidates:
|
232 |
+
return []
|
233 |
+
|
234 |
+
# Prepare pairs for cross-encoder
|
235 |
+
pairs = [[query, c["question"]] for c in candidates]
|
236 |
+
|
237 |
+
# Get cross-encoder scores
|
238 |
+
scores = self.cross.predict(pairs, convert_to_numpy=True)
|
239 |
+
|
240 |
+
# Add scores to candidates
|
241 |
+
for c, score in zip(candidates, scores):
|
242 |
+
c["cross_score"] = float(score)
|
243 |
+
|
244 |
+
# Filter and sort by cross-encoder score
|
245 |
+
reranked = [c for c in candidates if c["cross_score"] >= self.CROSS_OK]
|
246 |
+
reranked.sort(key=lambda x: x["cross_score"], reverse=True)
|
247 |
+
|
248 |
+
return reranked
|
249 |
+
|
250 |
+
def _extract_answer(self, query: str, context: str) -> Dict[str, Any]:
|
251 |
+
"""Extract specific answer using QA model"""
|
252 |
+
try:
|
253 |
+
result = self.qa(question=query, context=context)
|
254 |
+
return {
|
255 |
+
"answer": result["answer"],
|
256 |
+
"score": result["score"],
|
257 |
+
"start": result.get("start", 0),
|
258 |
+
"end": result.get("end", len(result["answer"]))
|
259 |
+
}
|
260 |
+
except Exception as e:
|
261 |
+
logging.warning(f"QA extraction failed: {e}")
|
262 |
+
return {"answer": context, "score": 0.5}
|
263 |
+
|
264 |
+
def _create_friendly_response(self, answers: List[str], confidence: str = "medium") -> str:
|
265 |
+
"""Create a user-friendly response from multiple answers"""
|
266 |
+
if not answers:
|
267 |
+
return self.CONFIDENCE_LEVELS["none"]
|
268 |
+
|
269 |
+
# Remove duplicates while preserving order
|
270 |
+
unique_answers = []
|
271 |
+
seen = set()
|
272 |
+
for ans in answers:
|
273 |
+
normalized = ans.lower().strip()
|
274 |
+
if normalized not in seen:
|
275 |
+
seen.add(normalized)
|
276 |
+
unique_answers.append(ans)
|
277 |
+
|
278 |
+
if len(unique_answers) == 1:
|
279 |
+
# Single answer - return as is with confidence prefix
|
280 |
+
return self.CONFIDENCE_LEVELS[confidence] + unique_answers[0]
|
281 |
+
|
282 |
+
# Multiple answers - need to summarize
|
283 |
+
combined_text = " ".join(unique_answers)
|
284 |
+
|
285 |
+
# If text is short enough, format it nicely
|
286 |
+
if len(combined_text) < 300:
|
287 |
+
response = self.CONFIDENCE_LEVELS[confidence]
|
288 |
+
for i, answer in enumerate(unique_answers):
|
289 |
+
if "•" in answer:
|
290 |
+
# Already has bullets
|
291 |
+
response += answer + "\n\n"
|
292 |
+
else:
|
293 |
+
# Add as paragraph
|
294 |
+
response += answer + "\n\n"
|
295 |
+
return response.strip()
|
296 |
+
|
297 |
+
# Long text - summarize it
|
298 |
+
try:
|
299 |
+
# Prepare text for summarization
|
300 |
+
summary_input = f"Summarize the following information about Jupiter banking services: {combined_text}"
|
301 |
+
|
302 |
+
# Generate summary
|
303 |
+
summary = self.summarizer(summary_input, max_length=150, min_length=50, do_sample=False)
|
304 |
+
summarized_text = summary[0]['summary_text']
|
305 |
+
|
306 |
+
# Make it more conversational
|
307 |
+
response = self.CONFIDENCE_LEVELS[confidence]
|
308 |
+
response += self._make_conversational(summarized_text)
|
309 |
+
|
310 |
+
return response
|
311 |
+
|
312 |
+
except Exception as e:
|
313 |
+
logging.warning(f"Summarization failed: {e}")
|
314 |
+
# Fallback to formatted response
|
315 |
+
return self._format_multiple_answers(unique_answers, confidence)
|
316 |
+
|
317 |
+
def _make_conversational(self, text: str) -> str:
|
318 |
+
"""Make response more conversational and friendly"""
|
319 |
+
# Add appropriate punctuation if missing
|
320 |
+
if text and text[-1] not in '.!?':
|
321 |
+
text += '.'
|
322 |
+
|
323 |
+
# Replace robotic phrases
|
324 |
+
replacements = {
|
325 |
+
"The user": "You",
|
326 |
+
"the user": "you",
|
327 |
+
"It is": "It's",
|
328 |
+
"You will": "You'll",
|
329 |
+
"You can not": "You can't",
|
330 |
+
"Do not": "Don't",
|
331 |
+
}
|
332 |
+
|
333 |
+
for old, new in replacements.items():
|
334 |
+
text = text.replace(old, new)
|
335 |
+
|
336 |
+
return text
|
337 |
+
|
338 |
+
def _format_multiple_answers(self, answers: List[str], confidence: str) -> str:
|
339 |
+
"""Format multiple answers nicely"""
|
340 |
+
response = self.CONFIDENCE_LEVELS[confidence]
|
341 |
+
|
342 |
+
if len(answers) <= 3:
|
343 |
+
# Few answers - show all
|
344 |
+
for answer in answers:
|
345 |
+
if "•" in answer:
|
346 |
+
response += answer + "\n\n"
|
347 |
+
else:
|
348 |
+
response += f"• {answer}\n\n"
|
349 |
+
else:
|
350 |
+
# Many answers - group by category
|
351 |
+
response += "Here are the key points:\n\n"
|
352 |
+
for i, answer in enumerate(answers[:5]): # Limit to 5
|
353 |
+
response += f"{i+1}. {answer}\n\n"
|
354 |
+
|
355 |
+
return response.strip()
|
356 |
+
|
357 |
+
# ------------------------- Main API ------------------------------- #
|
358 |
+
def generate_response(self, query: str) -> str:
|
359 |
+
"""Generate response for user query"""
|
360 |
+
query = self._clean(query)
|
361 |
+
|
362 |
+
# Step 1: Retrieve candidates
|
363 |
+
candidates = self._retrieve_candidates(query)
|
364 |
+
|
365 |
+
if not candidates:
|
366 |
+
return self.CONFIDENCE_LEVELS["none"]
|
367 |
+
|
368 |
+
# Step 2: Check for high similarity match
|
369 |
+
if candidates[0]["similarity"] >= self.HIGH_SIM:
|
370 |
+
return self.CONFIDENCE_LEVELS["high"] + candidates[0]["answer"]
|
371 |
+
|
372 |
+
# Step 3: Rerank candidates
|
373 |
+
reranked = self._rerank_candidates(query, candidates)
|
374 |
+
|
375 |
+
if not reranked:
|
376 |
+
# Use original candidates with lower confidence
|
377 |
+
reranked = candidates[:3]
|
378 |
+
confidence = "low"
|
379 |
+
else:
|
380 |
+
confidence = "high" if reranked[0]["cross_score"] > 0.8 else "medium"
|
381 |
+
|
382 |
+
# Step 4: Extract relevant answers
|
383 |
+
relevant_answers = []
|
384 |
+
|
385 |
+
for candidate in reranked[:5]: # Top 5 reranked
|
386 |
+
# Try QA extraction for more specific answer
|
387 |
+
qa_result = self._extract_answer(query, candidate["answer"])
|
388 |
+
|
389 |
+
if qa_result["score"] > 0.3:
|
390 |
+
# Good QA match
|
391 |
+
relevant_answers.append(qa_result["answer"])
|
392 |
+
else:
|
393 |
+
# Use full answer if QA didn't find specific part
|
394 |
+
relevant_answers.append(candidate["answer"])
|
395 |
+
|
396 |
+
# Step 5: Create final response
|
397 |
+
final_response = self._create_friendly_response(relevant_answers, confidence)
|
398 |
+
|
399 |
+
return final_response
|
400 |
+
|
401 |
+
def suggest_related_queries(self, query: str) -> List[str]:
|
402 |
+
"""Suggest related queries based on similar questions"""
|
403 |
+
candidates = self._retrieve_candidates(query, top_k=10)
|
404 |
+
|
405 |
+
related = []
|
406 |
+
seen = set()
|
407 |
+
|
408 |
+
for candidate in candidates:
|
409 |
+
if candidate["similarity"] >= 0.5 and candidate["similarity"] < 0.9:
|
410 |
+
# Clean question for display
|
411 |
+
clean_q = candidate["question"].strip()
|
412 |
+
if clean_q.lower() not in seen and clean_q.lower() != query.lower():
|
413 |
+
seen.add(clean_q.lower())
|
414 |
+
related.append(clean_q)
|
415 |
+
|
416 |
+
# Return top 5 related queries
|
417 |
+
return related[:5]
|
418 |
+
|
419 |
+
def get_categories(self) -> List[str]:
|
420 |
+
"""Get all available FAQ categories"""
|
421 |
+
return sorted(self.faq["category"].unique().tolist())
|
422 |
+
|
423 |
+
def get_faqs_by_category(self, category: str) -> List[Dict[str, str]]:
|
424 |
+
"""Get all FAQs for a specific category"""
|
425 |
+
cat_faqs = self.faq[self.faq["category"].str.lower() == category.lower()]
|
426 |
+
|
427 |
+
return [
|
428 |
+
{
|
429 |
+
"question": row["question"],
|
430 |
+
"answer": row["answer"]
|
431 |
+
}
|
432 |
+
for _, row in cat_faqs.iterrows()
|
433 |
+
]
|
434 |
+
|
435 |
+
def search_faqs(self, keyword: str) -> List[Dict[str, str]]:
|
436 |
+
"""Simple keyword search in FAQs"""
|
437 |
+
keyword_lower = keyword.lower()
|
438 |
+
|
439 |
+
matches = []
|
440 |
+
for _, row in self.faq.iterrows():
|
441 |
+
if (keyword_lower in row["question"].lower() or
|
442 |
+
keyword_lower in row["answer"].lower()):
|
443 |
+
matches.append({
|
444 |
+
"question": row["question"],
|
445 |
+
"answer": row["answer"],
|
446 |
+
"category": row["category"]
|
447 |
+
})
|
448 |
+
|
449 |
+
return matches[:10] # Limit to 10 results
|
450 |
+
|
451 |
+
|
452 |
+
# Evaluation module
|
453 |
+
class BotEvaluator:
|
454 |
+
"""Evaluate bot performance"""
|
455 |
+
|
456 |
+
def __init__(self, bot: JupiterFAQBot):
|
457 |
+
self.bot = bot
|
458 |
+
|
459 |
+
def create_test_queries(self) -> List[Dict[str, str]]:
|
460 |
+
"""Create test queries based on FAQ categories"""
|
461 |
+
test_queries = [
|
462 |
+
# Account queries
|
463 |
+
{"query": "How do I open an account?", "expected_category": "Account"},
|
464 |
+
{"query": "What is Jupiter savings account?", "expected_category": "Account"},
|
465 |
+
|
466 |
+
# Payment queries
|
467 |
+
{"query": "How to make UPI payment?", "expected_category": "Payments"},
|
468 |
+
{"query": "What is the daily transaction limit?", "expected_category": "Payments"},
|
469 |
+
|
470 |
+
# Rewards queries
|
471 |
+
{"query": "How do I earn cashback?", "expected_category": "Rewards"},
|
472 |
+
{"query": "What are Jewels?", "expected_category": "Rewards"},
|
473 |
+
|
474 |
+
# Investment queries
|
475 |
+
{"query": "Can I invest in mutual funds?", "expected_category": "Investments"},
|
476 |
+
{"query": "What is Magic Spends?", "expected_category": "Magic Spends"},
|
477 |
+
|
478 |
+
# Loan queries
|
479 |
+
{"query": "How to apply for personal loan?", "expected_category": "Jupiter Loans"},
|
480 |
+
{"query": "What is the interest rate?", "expected_category": "Jupiter Loans"},
|
481 |
+
|
482 |
+
# Card queries
|
483 |
+
{"query": "How to get credit card?", "expected_category": "Edge+ Credit Card"},
|
484 |
+
{"query": "Is there any annual fee?", "expected_category": "Edge+ Credit Card"},
|
485 |
+
]
|
486 |
+
|
487 |
+
return test_queries
|
488 |
+
|
489 |
+
def evaluate_retrieval_accuracy(self) -> Dict[str, float]:
|
490 |
+
"""Evaluate how well the bot retrieves relevant information"""
|
491 |
+
test_queries = self.create_test_queries()
|
492 |
+
|
493 |
+
correct = 0
|
494 |
+
total = len(test_queries)
|
495 |
+
|
496 |
+
results = []
|
497 |
+
|
498 |
+
for test in test_queries:
|
499 |
+
response = self.bot.generate_response(test["query"])
|
500 |
+
|
501 |
+
# Check if response mentions expected category content
|
502 |
+
is_correct = test["expected_category"].lower() in response.lower()
|
503 |
+
|
504 |
+
if is_correct:
|
505 |
+
correct += 1
|
506 |
+
|
507 |
+
results.append({
|
508 |
+
"query": test["query"],
|
509 |
+
"expected_category": test["expected_category"],
|
510 |
+
"response": response[:200] + "..." if len(response) > 200 else response,
|
511 |
+
"correct": is_correct
|
512 |
+
})
|
513 |
+
|
514 |
+
accuracy = correct / total if total > 0 else 0
|
515 |
+
|
516 |
+
return {
|
517 |
+
"accuracy": accuracy,
|
518 |
+
"correct": correct,
|
519 |
+
"total": total,
|
520 |
+
"results": results
|
521 |
+
}
|
522 |
+
|
523 |
+
def evaluate_response_quality(self) -> Dict[str, Any]:
|
524 |
+
"""Evaluate the quality of responses"""
|
525 |
+
test_queries = [
|
526 |
+
"What is Jupiter?",
|
527 |
+
"How do I earn rewards?",
|
528 |
+
"Tell me about credit cards",
|
529 |
+
"Can I get a loan?",
|
530 |
+
"How to invest money?"
|
531 |
+
]
|
532 |
+
|
533 |
+
quality_metrics = []
|
534 |
+
|
535 |
+
for query in test_queries:
|
536 |
+
response = self.bot.generate_response(query)
|
537 |
+
|
538 |
+
# Check quality indicators
|
539 |
+
has_greeting = any(phrase in response for phrase in ["Based on", "Here's", "I found"])
|
540 |
+
has_structure = "\n" in response or "•" in response
|
541 |
+
appropriate_length = 50 < len(response) < 500
|
542 |
+
|
543 |
+
quality_score = sum([has_greeting, has_structure, appropriate_length]) / 3
|
544 |
+
|
545 |
+
quality_metrics.append({
|
546 |
+
"query": query,
|
547 |
+
"response_length": len(response),
|
548 |
+
"has_greeting": has_greeting,
|
549 |
+
"has_structure": has_structure,
|
550 |
+
"appropriate_length": appropriate_length,
|
551 |
+
"quality_score": quality_score
|
552 |
+
})
|
553 |
+
|
554 |
+
avg_quality = sum(m["quality_score"] for m in quality_metrics) / len(quality_metrics)
|
555 |
+
|
556 |
+
return {
|
557 |
+
"average_quality_score": avg_quality,
|
558 |
+
"metrics": quality_metrics
|
559 |
+
}
|
560 |
+
|
561 |
+
|
562 |
+
# Utility functions for data preparation
|
563 |
+
def prepare_faq_data(csv_path: str = "data/faqs.csv") -> pd.DataFrame:
|
564 |
+
"""Prepare and validate FAQ data"""
|
565 |
+
df = pd.read_csv(csv_path)
|
566 |
+
|
567 |
+
# Ensure required columns exist
|
568 |
+
required_cols = ["question", "answer", "category"]
|
569 |
+
if not all(col in df.columns for col in required_cols):
|
570 |
+
raise ValueError(f"CSV must contain columns: {required_cols}")
|
571 |
+
|
572 |
+
# Basic stats
|
573 |
+
print(f"Total FAQs: {len(df)}")
|
574 |
+
print(f"Categories: {df['category'].nunique()}")
|
575 |
+
print(f"\nCategory distribution:")
|
576 |
+
print(df['category'].value_counts())
|
577 |
+
|
578 |
+
return df
|
579 |
+
|
580 |
+
|
581 |
+
# Main execution example
|
582 |
+
if __name__ == "__main__":
|
583 |
+
# Initialize bot
|
584 |
+
bot = JupiterFAQBot()
|
585 |
+
|
586 |
+
# Test some queries
|
587 |
+
test_queries = [
|
588 |
+
"How do I open a savings account?",
|
589 |
+
"What are the cashback rates?",
|
590 |
+
"Can I get a personal loan?",
|
591 |
+
"How to use UPI?",
|
592 |
+
"Tell me about investments"
|
593 |
+
]
|
594 |
+
|
595 |
+
print("\n" + "="*50)
|
596 |
+
print("Testing Jupiter FAQ Bot")
|
597 |
+
print("="*50 + "\n")
|
598 |
+
|
599 |
+
for query in test_queries:
|
600 |
+
print(f"Q: {query}")
|
601 |
+
response = bot.generate_response(query)
|
602 |
+
print(f"A: {response}\n")
|
603 |
+
|
604 |
+
# Show related queries
|
605 |
+
related = bot.suggest_related_queries(query)
|
606 |
+
if related:
|
607 |
+
print("Related questions:")
|
608 |
+
for r in related[:3]:
|
609 |
+
print(f" - {r}")
|
610 |
+
print("\n" + "-"*50 + "\n")
|
611 |
+
|
612 |
+
# Run evaluation
|
613 |
+
print("\n" + "="*50)
|
614 |
+
print("Running Evaluation")
|
615 |
+
print("="*50 + "\n")
|
616 |
+
|
617 |
+
evaluator = BotEvaluator(bot)
|
618 |
+
|
619 |
+
# Retrieval accuracy
|
620 |
+
accuracy_results = evaluator.evaluate_retrieval_accuracy()
|
621 |
+
print(f"Retrieval Accuracy: {accuracy_results['accuracy']:.2%}")
|
622 |
+
print(f"Correct: {accuracy_results['correct']}/{accuracy_results['total']}")
|
623 |
+
|
624 |
+
# Response quality
|
625 |
+
quality_results = evaluator.evaluate_response_quality()
|
626 |
+
print(f"\nAverage Response Quality: {quality_results['average_quality_score']:.2%}")
|
app/routes.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import render_template, request, jsonify
|
2 |
+
from app import app
|
3 |
+
from app.bot import JupiterFAQBot
|
4 |
+
|
5 |
+
bot = JupiterFAQBot()
|
6 |
+
|
7 |
+
@app.route('/')
|
8 |
+
def index():
|
9 |
+
return render_template('index.html')
|
10 |
+
|
11 |
+
@app.route('/chat', methods=['POST'])
|
12 |
+
def chat():
|
13 |
+
data = request.json
|
14 |
+
question = data.get('question', '')
|
15 |
+
|
16 |
+
response = bot.generate_response(question)
|
17 |
+
suggestions = bot.suggest_related_queries(question)
|
18 |
+
|
19 |
+
return jsonify({
|
20 |
+
'response': response,
|
21 |
+
'suggestions': suggestions
|
22 |
+
})
|
app/run_scraper.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# run_scraper.py
|
2 |
+
import sys
|
3 |
+
import logging
|
4 |
+
from app.scraper import FAQUpdater
|
5 |
+
|
6 |
+
def main():
|
7 |
+
"""Run the FAQ scraping process"""
|
8 |
+
|
9 |
+
# Setup logging
|
10 |
+
logging.basicConfig(
|
11 |
+
level=logging.INFO,
|
12 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
13 |
+
)
|
14 |
+
|
15 |
+
# Check if force update is requested
|
16 |
+
force_update = '--force' in sys.argv
|
17 |
+
|
18 |
+
# Run updater
|
19 |
+
updater = FAQUpdater()
|
20 |
+
df = updater.check_and_update(force_update=force_update)
|
21 |
+
|
22 |
+
# Display stats
|
23 |
+
stats = updater.get_scraping_stats(df)
|
24 |
+
print(f"\nScraping Statistics:")
|
25 |
+
print(f"Total FAQs: {stats['total_faqs']}")
|
26 |
+
print(f"Categories: {stats['categories']}")
|
27 |
+
print(f"Category Distribution: {stats['category_distribution']}")
|
28 |
+
|
29 |
+
if __name__ == "__main__":
|
30 |
+
main()
|
app/scraper.py
ADDED
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app/scraper.py (Enhanced version)
|
2 |
+
import requests
|
3 |
+
from bs4 import BeautifulSoup
|
4 |
+
import pandas as pd
|
5 |
+
import time
|
6 |
+
import re
|
7 |
+
import json
|
8 |
+
from typing import List, Dict, Optional
|
9 |
+
from pathlib import Path
|
10 |
+
import logging
|
11 |
+
from selenium import webdriver
|
12 |
+
from selenium.webdriver.common.by import By
|
13 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
14 |
+
from selenium.webdriver.support import expected_conditions as EC
|
15 |
+
from selenium.webdriver.chrome.options import Options
|
16 |
+
from selenium.common.exceptions import TimeoutException
|
17 |
+
|
18 |
+
logging.basicConfig(level=logging.INFO)
|
19 |
+
logger = logging.getLogger(__name__)
|
20 |
+
|
21 |
+
|
22 |
+
class JupiterFAQScraper:
|
23 |
+
"""Enhanced scraper for Jupiter Money website"""
|
24 |
+
|
25 |
+
def __init__(self):
|
26 |
+
self.base_url = "https://jupiter.money"
|
27 |
+
self.target_urls = [
|
28 |
+
"https://jupiter.money/savings-account/",
|
29 |
+
"https://jupiter.money/pro-salary-account/",
|
30 |
+
# ... other URLs
|
31 |
+
]
|
32 |
+
|
33 |
+
self.session = requests.Session()
|
34 |
+
self.session.headers.update({
|
35 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
36 |
+
})
|
37 |
+
|
38 |
+
def test_scraping(self) -> Dict[str, bool]:
|
39 |
+
"""Test if scraping actually works"""
|
40 |
+
results = {}
|
41 |
+
|
42 |
+
# Test 1: Can we access the website?
|
43 |
+
try:
|
44 |
+
response = self.session.get(self.base_url, timeout=10)
|
45 |
+
results['website_accessible'] = response.status_code == 200
|
46 |
+
logger.info(f"Website accessible: {results['website_accessible']}")
|
47 |
+
except:
|
48 |
+
results['website_accessible'] = False
|
49 |
+
logger.error("Cannot access Jupiter website")
|
50 |
+
|
51 |
+
# Test 2: Can we find FAQ content?
|
52 |
+
if results['website_accessible']:
|
53 |
+
try:
|
54 |
+
soup = BeautifulSoup(response.content, 'html.parser')
|
55 |
+
# Look for common FAQ indicators
|
56 |
+
faq_indicators = ['faq', 'help', 'support', 'question', 'answer']
|
57 |
+
content = soup.get_text().lower()
|
58 |
+
results['faq_content_found'] = any(indicator in content for indicator in faq_indicators)
|
59 |
+
logger.info(f"FAQ content found: {results['faq_content_found']}")
|
60 |
+
except:
|
61 |
+
results['faq_content_found'] = False
|
62 |
+
|
63 |
+
return results
|
64 |
+
|
65 |
+
def scrape_with_fallback(self) -> pd.DataFrame:
|
66 |
+
"""Try multiple scraping methods with fallback"""
|
67 |
+
all_faqs = []
|
68 |
+
|
69 |
+
# Method 1: Try actual scraping
|
70 |
+
logger.info("Attempting to scrape Jupiter website...")
|
71 |
+
test_results = self.test_scraping()
|
72 |
+
|
73 |
+
if test_results.get('website_accessible'):
|
74 |
+
# Try basic scraping
|
75 |
+
for url in self.target_urls[:3]: # Test with first 3 URLs
|
76 |
+
try:
|
77 |
+
faqs = self.scrape_page_safe(url)
|
78 |
+
all_faqs.extend(faqs)
|
79 |
+
if faqs:
|
80 |
+
logger.info(f"Successfully scraped {len(faqs)} FAQs from {url}")
|
81 |
+
except Exception as e:
|
82 |
+
logger.warning(f"Failed to scrape {url}: {e}")
|
83 |
+
|
84 |
+
# Method 2: If scraping fails or gets too little data, use fallback
|
85 |
+
if len(all_faqs) < 10:
|
86 |
+
logger.warning("Actual scraping yielded insufficient data. Using fallback FAQ data...")
|
87 |
+
all_faqs = self.get_fallback_faqs()
|
88 |
+
|
89 |
+
# Create DataFrame
|
90 |
+
df = pd.DataFrame(all_faqs)
|
91 |
+
if not df.empty:
|
92 |
+
df = df.drop_duplicates(subset=['question'])
|
93 |
+
|
94 |
+
return df
|
95 |
+
|
96 |
+
def scrape_page_safe(self, url: str) -> List[Dict]:
|
97 |
+
"""Safely scrape a page with error handling"""
|
98 |
+
faqs = []
|
99 |
+
|
100 |
+
try:
|
101 |
+
response = self.session.get(url, timeout=10)
|
102 |
+
if response.status_code != 200:
|
103 |
+
logger.warning(f"Got status code {response.status_code} for {url}")
|
104 |
+
return faqs
|
105 |
+
|
106 |
+
soup = BeautifulSoup(response.content, 'html.parser')
|
107 |
+
|
108 |
+
# Strategy 1: Look for structured data
|
109 |
+
scripts = soup.find_all('script', type='application/ld+json')
|
110 |
+
for script in scripts:
|
111 |
+
try:
|
112 |
+
data = json.loads(script.string)
|
113 |
+
if '@type' in data and 'FAQ' in str(data.get('@type')):
|
114 |
+
# Extract FAQ structured data
|
115 |
+
faqs.extend(self.extract_structured_faqs(data))
|
116 |
+
except:
|
117 |
+
continue
|
118 |
+
|
119 |
+
# Strategy 2: Look for FAQ sections
|
120 |
+
faq_sections = soup.find_all(['div', 'section'],
|
121 |
+
class_=re.compile(r'faq|question|help|support', re.I))
|
122 |
+
|
123 |
+
for section in faq_sections[:5]: # Limit to prevent too many
|
124 |
+
faqs.extend(self.extract_section_faqs(section, url))
|
125 |
+
|
126 |
+
except Exception as e:
|
127 |
+
logger.error(f"Error scraping {url}: {e}")
|
128 |
+
|
129 |
+
return faqs
|
130 |
+
|
131 |
+
def extract_structured_faqs(self, data: dict) -> List[Dict]:
|
132 |
+
"""Extract FAQs from structured data"""
|
133 |
+
faqs = []
|
134 |
+
|
135 |
+
if isinstance(data, dict):
|
136 |
+
if data.get('@type') == 'FAQPage':
|
137 |
+
for item in data.get('mainEntity', []):
|
138 |
+
if item.get('@type') == 'Question':
|
139 |
+
faqs.append({
|
140 |
+
'question': self._clean_text(item.get('name', '')),
|
141 |
+
'answer': self._clean_text(
|
142 |
+
item.get('acceptedAnswer', {}).get('text', '')
|
143 |
+
),
|
144 |
+
'category': 'General'
|
145 |
+
})
|
146 |
+
|
147 |
+
return faqs
|
148 |
+
|
149 |
+
def extract_section_faqs(self, section, url: str) -> List[Dict]:
|
150 |
+
"""Extract FAQs from a page section"""
|
151 |
+
faqs = []
|
152 |
+
category = self._get_category_from_url(url)
|
153 |
+
|
154 |
+
# Look for Q&A pairs
|
155 |
+
questions = section.find_all(['h2', 'h3', 'h4', 'dt', 'div'],
|
156 |
+
class_=re.compile(r'question|title|header', re.I))
|
157 |
+
|
158 |
+
for q in questions[:10]: # Limit to prevent too many
|
159 |
+
# Try to find corresponding answer
|
160 |
+
answer = None
|
161 |
+
|
162 |
+
# Check next sibling
|
163 |
+
next_elem = q.find_next_sibling()
|
164 |
+
if next_elem and next_elem.name in ['p', 'div', 'dd']:
|
165 |
+
answer = next_elem
|
166 |
+
|
167 |
+
# Check parent's next sibling
|
168 |
+
if not answer:
|
169 |
+
parent = q.parent
|
170 |
+
if parent:
|
171 |
+
next_elem = parent.find_next_sibling()
|
172 |
+
if next_elem:
|
173 |
+
answer = next_elem.find(['p', 'div'])
|
174 |
+
|
175 |
+
if answer:
|
176 |
+
faqs.append({
|
177 |
+
'question': self._clean_text(q.get_text()),
|
178 |
+
'answer': self._clean_text(answer.get_text()),
|
179 |
+
'category': category
|
180 |
+
})
|
181 |
+
|
182 |
+
return faqs
|
183 |
+
|
184 |
+
def get_fallback_faqs(self) -> List[Dict]:
|
185 |
+
"""Return comprehensive fallback FAQs based on Jupiter's services"""
|
186 |
+
# This is the fallback data that will be used if scraping fails
|
187 |
+
# Based on the FAQs you provided earlier
|
188 |
+
return [
|
189 |
+
# Account
|
190 |
+
{
|
191 |
+
'question': 'What is the Jupiter All-in-1 Savings Account?',
|
192 |
+
'answer': 'The All-in-1 Savings Account on Jupiter powered by Federal Bank helps you manage your money better with faster payments, smart saving tools, and investment insights—all in one place.',
|
193 |
+
'category': 'Account'
|
194 |
+
},
|
195 |
+
{
|
196 |
+
'question': 'How do I open a Jupiter Savings Account?',
|
197 |
+
'answer': 'You can open your Jupiter digital account by following a few simple steps: 1. Install the Jupiter app 2. Tap "Open an all-in-1 Savings Account" while selecting a Jupiter experience 3. Complete your video KYC',
|
198 |
+
'category': 'Account'
|
199 |
+
},
|
200 |
+
# Add more FAQs here from your data...
|
201 |
+
# (Include all the FAQs you provided)
|
202 |
+
]
|
203 |
+
|
204 |
+
def _clean_text(self, text: str) -> str:
|
205 |
+
"""Clean text"""
|
206 |
+
if not text:
|
207 |
+
return ""
|
208 |
+
text = ' '.join(text.split())
|
209 |
+
text = re.sub(r'<[^>]+>', '', text)
|
210 |
+
return text.strip()
|
211 |
+
|
212 |
+
def _get_category_from_url(self, url: str) -> str:
|
213 |
+
"""Get category from URL"""
|
214 |
+
url_lower = url.lower()
|
215 |
+
if 'account' in url_lower:
|
216 |
+
return 'Account'
|
217 |
+
elif 'payment' in url_lower or 'upi' in url_lower:
|
218 |
+
return 'Payments'
|
219 |
+
elif 'card' in url_lower:
|
220 |
+
return 'Cards'
|
221 |
+
elif 'loan' in url_lower:
|
222 |
+
return 'Loans'
|
223 |
+
elif 'invest' in url_lower or 'mutual' in url_lower:
|
224 |
+
return 'Investments'
|
225 |
+
return 'General'
|
226 |
+
|
227 |
+
def run_complete_scraping(self) -> pd.DataFrame:
|
228 |
+
"""Main method to run scraping with all fallbacks"""
|
229 |
+
logger.info("Starting Jupiter FAQ scraping process...")
|
230 |
+
|
231 |
+
# Try scraping with fallback
|
232 |
+
df = self.scrape_with_fallback()
|
233 |
+
|
234 |
+
if df.empty:
|
235 |
+
logger.error("No FAQ data could be obtained!")
|
236 |
+
else:
|
237 |
+
logger.info(f"Total FAQs collected: {len(df)}")
|
238 |
+
|
239 |
+
# Save to CSV
|
240 |
+
self.save_to_csv(df)
|
241 |
+
|
242 |
+
return df
|
243 |
+
|
244 |
+
def save_to_csv(self, df: pd.DataFrame, filename: str = "data/faqs.csv"):
|
245 |
+
"""Save FAQs to CSV"""
|
246 |
+
import os
|
247 |
+
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
248 |
+
|
249 |
+
if not df.empty:
|
250 |
+
df = df[['question', 'answer', 'category']]
|
251 |
+
df.to_csv(filename, index=False)
|
252 |
+
logger.info(f"Saved {len(df)} FAQs to {filename}")
|
253 |
+
|
254 |
+
|
255 |
+
class FAQUpdater:
|
256 |
+
"""Manages FAQ updates with reliability checks"""
|
257 |
+
|
258 |
+
def __init__(self):
|
259 |
+
self.scraper = JupiterFAQScraper()
|
260 |
+
self.faq_file = "data/faqs.csv"
|
261 |
+
|
262 |
+
def check_and_update(self, force_update: bool = False) -> pd.DataFrame:
|
263 |
+
"""Check and update FAQs with verification"""
|
264 |
+
import os
|
265 |
+
from datetime import datetime, timedelta
|
266 |
+
|
267 |
+
# First, check if we have existing FAQ data
|
268 |
+
if os.path.exists(self.faq_file) and not force_update:
|
269 |
+
# Load existing data
|
270 |
+
existing_df = pd.read_csv(self.faq_file)
|
271 |
+
|
272 |
+
# Check file age
|
273 |
+
file_time = datetime.fromtimestamp(os.path.getmtime(self.faq_file))
|
274 |
+
if datetime.now() - file_time < timedelta(days=7):
|
275 |
+
logger.info(f"FAQ data is recent (updated {file_time.strftime('%Y-%m-%d')})")
|
276 |
+
return existing_df
|
277 |
+
|
278 |
+
# Try to update
|
279 |
+
logger.info("Attempting to update FAQ data...")
|
280 |
+
|
281 |
+
# Test if scraping works
|
282 |
+
test_results = self.scraper.test_scraping()
|
283 |
+
|
284 |
+
if not test_results.get('website_accessible'):
|
285 |
+
logger.warning("Cannot access Jupiter website. Using existing/fallback data.")
|
286 |
+
if os.path.exists(self.faq_file):
|
287 |
+
return pd.read_csv(self.faq_file)
|
288 |
+
else:
|
289 |
+
# Use fallback data
|
290 |
+
fallback_faqs = self.scraper.get_fallback_faqs()
|
291 |
+
df = pd.DataFrame(fallback_faqs)
|
292 |
+
self.scraper.save_to_csv(df)
|
293 |
+
return df
|
294 |
+
|
295 |
+
# Try scraping
|
296 |
+
new_df = self.scraper.run_complete_scraping()
|
297 |
+
|
298 |
+
# Verify the scraped data
|
299 |
+
if self.verify_scraped_data(new_df):
|
300 |
+
# Backup old file if exists
|
301 |
+
if os.path.exists(self.faq_file):
|
302 |
+
backup_name = f"data/faqs_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
|
303 |
+
os.rename(self.faq_file, backup_name)
|
304 |
+
logger.info(f"Backed up old FAQs to {backup_name}")
|
305 |
+
|
306 |
+
return new_df
|
307 |
+
else:
|
308 |
+
logger.warning("Scraped data failed verification. Using existing/fallback data.")
|
309 |
+
if os.path.exists(self.faq_file):
|
310 |
+
return pd.read_csv(self.faq_file)
|
311 |
+
else:
|
312 |
+
# Use comprehensive fallback
|
313 |
+
return self.create_comprehensive_fallback()
|
314 |
+
|
315 |
+
def verify_scraped_data(self, df: pd.DataFrame) -> bool:
|
316 |
+
"""Verify if scraped data is valid"""
|
317 |
+
if df.empty:
|
318 |
+
return False
|
319 |
+
|
320 |
+
# Check minimum requirements
|
321 |
+
if len(df) < 20: # Expecting at least 20 FAQs
|
322 |
+
logger.warning(f"Only {len(df)} FAQs scraped, seems too low")
|
323 |
+
return False
|
324 |
+
|
325 |
+
# Check if we have multiple categories
|
326 |
+
if df['category'].nunique() < 3:
|
327 |
+
logger.warning("Not enough FAQ categories")
|
328 |
+
return False
|
329 |
+
|
330 |
+
# Check answer quality
|
331 |
+
avg_answer_length = df['answer'].str.len().mean()
|
332 |
+
if avg_answer_length < 50:
|
333 |
+
logger.warning("Answers seem too short")
|
334 |
+
return False
|
335 |
+
|
336 |
+
return True
|
337 |
+
|
338 |
+
def create_comprehensive_fallback(self) -> pd.DataFrame:
|
339 |
+
"""Create comprehensive fallback FAQ data"""
|
340 |
+
# This includes ALL the FAQs you provided
|
341 |
+
fallback_data = [
|
342 |
+
# Account FAQs
|
343 |
+
{"question": "What is the Jupiter All-in-1 Savings Account?", "answer": "The All-in-1 Savings Account on Jupiter powered by Federal Bank helps you manage your money better with faster payments, smart saving tools, and investment insights—all in one place.", "category": "Account"},
|
344 |
+
{"question": "How do I open a Jupiter Savings Account?", "answer": "You can open your Jupiter digital account by following a few simple steps: 1. Install the Jupiter app 2. Tap 'Open an all-in-1 Savings Account' while selecting a Jupiter experience 3. Complete your video KYC", "category": "Account"},
|
345 |
+
{"question": "Do I earn Jewels for making payments?", "answer": "Yes! You earn up to 1% cashback as Jewels on: • UPI payments • Debit Card spends (online & offline) • Investments in Digital Gold", "category": "Rewards"},
|
346 |
+
{"question": "Can I use my Jupiter Debit Card outside India?", "answer": "Absolutely. You can spend in over 120 countries with 0% forex fee on international transactions using your Jupiter Debit Card.", "category": "Card"},
|
347 |
+
{"question": "Do I earn Jewels on International payments?", "answer": "Yes, you also earn up to 1% cashback on online and offline international spends.", "category": "Rewards"},
|
348 |
+
{"question": "What payment modes are available with the Jupiter account?", "answer": "You can make superfast payments with UPI, IMPS, and debit card—whether it's for recharges, bills, or merchant transactions.", "category": "Payments"},
|
349 |
+
{"question": "Can I invest using my Jupiter account?", "answer": "Yes! You can invest in Mutual Funds and Digital Gold with up to 1.5% extra returns on curated mutual fund plans.", "category": "Investments"},
|
350 |
+
{"question": "What additional benefits do I get with the Savings Account?", "answer": "You earn up to 1% cashback as Jewels on: • Free cheque book • Free IMPS transfers • ATM withdrawals", "category": "Account"},
|
351 |
+
# Include ALL other FAQs from your data here...
|
352 |
+
]
|
353 |
+
|
354 |
+
df = pd.DataFrame(fallback_data)
|
355 |
+
self.scraper.save_to_csv(df)
|
356 |
+
return df
|
357 |
+
|
358 |
+
def get_scraping_stats(self, df: pd.DataFrame) -> Dict:
|
359 |
+
"""Get statistics about FAQ data"""
|
360 |
+
return {
|
361 |
+
'total_faqs': len(df),
|
362 |
+
'categories': df['category'].nunique(),
|
363 |
+
'category_distribution': df['category'].value_counts().to_dict(),
|
364 |
+
'avg_question_length': df['question'].str.len().mean(),
|
365 |
+
'avg_answer_length': df['answer'].str.len().mean(),
|
366 |
+
'data_source': 'scraped' if len(df) > 50 else 'fallback'
|
367 |
+
}
|
368 |
+
|
369 |
+
|
370 |
+
# Create a simple test script
|
371 |
+
def test_scraper():
|
372 |
+
"""Test if the scraper can actually get data"""
|
373 |
+
print("Testing Jupiter FAQ Scraper...")
|
374 |
+
print("-" * 50)
|
375 |
+
|
376 |
+
scraper = JupiterFAQScraper()
|
377 |
+
|
378 |
+
# Test 1: Website accessibility
|
379 |
+
test_results = scraper.test_scraping()
|
380 |
+
print(f"Website accessible: {test_results.get('website_accessible', False)}")
|
381 |
+
print(f"FAQ content found: {test_results.get('faq_content_found', False)}")
|
382 |
+
|
383 |
+
# Test 2: Try scraping one page
|
384 |
+
print("\nTesting page scraping...")
|
385 |
+
test_url = "https://jupiter.money/savings-account/"
|
386 |
+
faqs = scraper.scrape_page_safe(test_url)
|
387 |
+
print(f"FAQs found on {test_url}: {len(faqs)}")
|
388 |
+
|
389 |
+
if faqs:
|
390 |
+
print("\nSample FAQ:")
|
391 |
+
print(f"Q: {faqs[0]['question'][:100]}...")
|
392 |
+
print(f"A: {faqs[0]['answer'][:100]}...")
|
393 |
+
|
394 |
+
# Test 3: Full scraping
|
395 |
+
print("\nRunning full scraping process...")
|
396 |
+
df = scraper.run_complete_scraping()
|
397 |
+
print(f"Total FAQs collected: {len(df)}")
|
398 |
+
|
399 |
+
if not df.empty:
|
400 |
+
print(f"Categories: {df['category'].unique()}")
|
401 |
+
print(f"Data saved to: data/faqs.csv")
|
402 |
+
|
403 |
+
return df
|
404 |
+
|
405 |
+
|
406 |
+
if __name__ == "__main__":
|
407 |
+
# Run the test
|
408 |
+
test_scraper()
|
app/setup_scraping.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# setup_scraping.py
|
2 |
+
import subprocess
|
3 |
+
import sys
|
4 |
+
|
5 |
+
def setup_selenium_driver():
|
6 |
+
"""Install Chrome driver for Selenium"""
|
7 |
+
try:
|
8 |
+
from webdriver_manager.chrome import ChromeDriverManager
|
9 |
+
from selenium import webdriver
|
10 |
+
from selenium.webdriver.chrome.service import Service
|
11 |
+
|
12 |
+
# This will download the driver if needed
|
13 |
+
service = Service(ChromeDriverManager().install())
|
14 |
+
print("Chrome driver installed successfully!")
|
15 |
+
except Exception as e:
|
16 |
+
print(f"Error setting up Chrome driver: {e}")
|
17 |
+
print("You may need to install Chrome/Chromium browser")
|
18 |
+
|
19 |
+
def main():
|
20 |
+
print("Setting up scraping environment...")
|
21 |
+
|
22 |
+
# Install requirements
|
23 |
+
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'selenium', 'webdriver-manager'])
|
24 |
+
|
25 |
+
# Setup driver
|
26 |
+
setup_selenium_driver()
|
27 |
+
|
28 |
+
print("Setup complete! You can now run the scraper.")
|
29 |
+
|
30 |
+
if __name__ == "__main__":
|
31 |
+
main()
|
app/static/css/style.css
ADDED
@@ -0,0 +1,571 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* app/static/css/style.css */
|
2 |
+
|
3 |
+
:root {
|
4 |
+
--primary-color: #2962ff;
|
5 |
+
--primary-light: #768fff;
|
6 |
+
--primary-dark: #0039cb;
|
7 |
+
--secondary-color: #fd7e14;
|
8 |
+
--background-color: #f5f7fb;
|
9 |
+
--chat-bg: #ffffff;
|
10 |
+
--text-primary: #2c3e50;
|
11 |
+
--text-secondary: #34495e;
|
12 |
+
--shadow-color: rgba(0, 0, 0, 0.1);
|
13 |
+
--gradient-1: linear-gradient(135deg, #6B73FF 0%, #000DFF 100%);
|
14 |
+
--gradient-2: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
15 |
+
--gradient-3: linear-gradient(45deg, #2962ff, #768fff);
|
16 |
+
--error-color: #dc3545;
|
17 |
+
--success-color: #28a745;
|
18 |
+
}
|
19 |
+
|
20 |
+
* {
|
21 |
+
margin: 0;
|
22 |
+
padding: 0;
|
23 |
+
box-sizing: border-box;
|
24 |
+
font-family: 'Inter', sans-serif;
|
25 |
+
}
|
26 |
+
|
27 |
+
body {
|
28 |
+
background-color: var(--background-color);
|
29 |
+
height: 100vh;
|
30 |
+
overflow: hidden;
|
31 |
+
}
|
32 |
+
|
33 |
+
/* Main Container */
|
34 |
+
.chat-container {
|
35 |
+
height: 100vh;
|
36 |
+
display: flex;
|
37 |
+
flex-direction: column;
|
38 |
+
}
|
39 |
+
|
40 |
+
/* Header Styling */
|
41 |
+
.chat-header {
|
42 |
+
background: var(--gradient-1);
|
43 |
+
padding: 15px 20px;
|
44 |
+
color: white;
|
45 |
+
box-shadow: 0 2px 10px var(--shadow-color);
|
46 |
+
z-index: 1000;
|
47 |
+
}
|
48 |
+
|
49 |
+
.header-content {
|
50 |
+
display: flex;
|
51 |
+
align-items: center;
|
52 |
+
gap: 12px;
|
53 |
+
}
|
54 |
+
|
55 |
+
.logo {
|
56 |
+
width: 32px;
|
57 |
+
height: 32px;
|
58 |
+
display: flex;
|
59 |
+
align-items: center;
|
60 |
+
justify-content: center;
|
61 |
+
}
|
62 |
+
|
63 |
+
.header-content h1 {
|
64 |
+
font-size: 1.2rem;
|
65 |
+
font-weight: 600;
|
66 |
+
}
|
67 |
+
|
68 |
+
/* Main Content Layout */
|
69 |
+
.main-content {
|
70 |
+
display: flex;
|
71 |
+
height: calc(100vh - 60px);
|
72 |
+
overflow: hidden;
|
73 |
+
}
|
74 |
+
|
75 |
+
/* Categories Sidebar */
|
76 |
+
.categories-sidebar {
|
77 |
+
width: 300px;
|
78 |
+
background: white;
|
79 |
+
padding: 20px;
|
80 |
+
overflow-y: auto;
|
81 |
+
border-right: 1px solid rgba(0,0,0,0.1);
|
82 |
+
}
|
83 |
+
|
84 |
+
.categories-sidebar h2 {
|
85 |
+
font-size: 1.1rem;
|
86 |
+
color: var(--text-primary);
|
87 |
+
margin-bottom: 20px;
|
88 |
+
padding-bottom: 10px;
|
89 |
+
border-bottom: 2px solid var(--primary-light);
|
90 |
+
}
|
91 |
+
|
92 |
+
.categories-accordion {
|
93 |
+
display: flex;
|
94 |
+
flex-direction: column;
|
95 |
+
gap: 12px;
|
96 |
+
}
|
97 |
+
|
98 |
+
.category-item {
|
99 |
+
background: white;
|
100 |
+
border-radius: 12px;
|
101 |
+
overflow: hidden;
|
102 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
103 |
+
transition: all 0.3s ease;
|
104 |
+
}
|
105 |
+
|
106 |
+
.category-header {
|
107 |
+
padding: 15px;
|
108 |
+
display: flex;
|
109 |
+
justify-content: space-between;
|
110 |
+
align-items: center;
|
111 |
+
cursor: pointer;
|
112 |
+
background: linear-gradient(145deg, #ffffff, #f8f9fa);
|
113 |
+
transition: all 0.3s ease;
|
114 |
+
}
|
115 |
+
|
116 |
+
.category-header:hover {
|
117 |
+
background: linear-gradient(145deg, #f8f9fa, #e9ecef);
|
118 |
+
}
|
119 |
+
|
120 |
+
.category-header span {
|
121 |
+
font-weight: 500;
|
122 |
+
color: var(--text-primary);
|
123 |
+
display: flex;
|
124 |
+
align-items: center;
|
125 |
+
gap: 8px;
|
126 |
+
}
|
127 |
+
|
128 |
+
.arrow-icon {
|
129 |
+
width: 20px;
|
130 |
+
height: 20px;
|
131 |
+
fill: var(--text-primary);
|
132 |
+
transition: transform 0.3s ease;
|
133 |
+
}
|
134 |
+
|
135 |
+
.category-item.active .arrow-icon {
|
136 |
+
transform: rotate(180deg);
|
137 |
+
}
|
138 |
+
|
139 |
+
.category-content {
|
140 |
+
display: none;
|
141 |
+
padding: 12px;
|
142 |
+
background: var(--background-color);
|
143 |
+
}
|
144 |
+
|
145 |
+
.category-item.active .category-content {
|
146 |
+
display: flex;
|
147 |
+
flex-direction: column;
|
148 |
+
gap: 8px;
|
149 |
+
}
|
150 |
+
|
151 |
+
.category-content button {
|
152 |
+
background: white;
|
153 |
+
border: none;
|
154 |
+
padding: 12px 16px;
|
155 |
+
text-align: left;
|
156 |
+
border-radius: 8px;
|
157 |
+
cursor: pointer;
|
158 |
+
transition: all 0.3s ease;
|
159 |
+
color: var(--text-primary);
|
160 |
+
font-size: 0.9rem;
|
161 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
162 |
+
position: relative;
|
163 |
+
overflow: hidden;
|
164 |
+
z-index: 0;
|
165 |
+
}
|
166 |
+
|
167 |
+
.category-content button::before {
|
168 |
+
content: '';
|
169 |
+
position: absolute;
|
170 |
+
left: 0;
|
171 |
+
top: 0;
|
172 |
+
height: 100%;
|
173 |
+
width: 100%;
|
174 |
+
background: var(--primary-light); /* subtle hover background */
|
175 |
+
opacity: 0;
|
176 |
+
transition: opacity 0.3s ease;
|
177 |
+
z-index: 0;
|
178 |
+
border-radius: 8px;
|
179 |
+
}
|
180 |
+
|
181 |
+
.category-content button span {
|
182 |
+
position: relative;
|
183 |
+
z-index: 1;
|
184 |
+
transition: color 0.3s ease;
|
185 |
+
color: var(--text-primary);
|
186 |
+
}
|
187 |
+
|
188 |
+
.category-content button:hover::before {
|
189 |
+
opacity: 1;
|
190 |
+
}
|
191 |
+
|
192 |
+
.category-content button:hover span {
|
193 |
+
color: white;
|
194 |
+
}
|
195 |
+
|
196 |
+
|
197 |
+
/* Chat Section */
|
198 |
+
.chat-section {
|
199 |
+
flex: 1;
|
200 |
+
display: flex;
|
201 |
+
flex-direction: column;
|
202 |
+
background: var(--background-color);
|
203 |
+
}
|
204 |
+
|
205 |
+
.chat-messages {
|
206 |
+
flex: 1;
|
207 |
+
padding: 20px;
|
208 |
+
overflow-y: auto;
|
209 |
+
display: flex;
|
210 |
+
flex-direction: column;
|
211 |
+
gap: 15px;
|
212 |
+
}
|
213 |
+
|
214 |
+
/* Message Styling */
|
215 |
+
.message {
|
216 |
+
max-width: 80%;
|
217 |
+
animation: fadeIn 0.3s ease;
|
218 |
+
}
|
219 |
+
|
220 |
+
.message.user {
|
221 |
+
margin-left: auto;
|
222 |
+
}
|
223 |
+
|
224 |
+
.message-content {
|
225 |
+
padding: 15px;
|
226 |
+
border-radius: 12px;
|
227 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
228 |
+
position: relative;
|
229 |
+
}
|
230 |
+
|
231 |
+
.message.system .message-content {
|
232 |
+
background: white;
|
233 |
+
color: var(--text-primary);
|
234 |
+
text-shadow: 0 1px 1px rgba(0,0,0,0.03);
|
235 |
+
border-radius: 12px 12px 12px 0;
|
236 |
+
}
|
237 |
+
|
238 |
+
.message.user .message-content {
|
239 |
+
background: var(--gradient-2);
|
240 |
+
color: white;
|
241 |
+
border-radius: 12px 12px 0 12px;
|
242 |
+
}
|
243 |
+
|
244 |
+
/* Welcome Message */
|
245 |
+
.welcome-message {
|
246 |
+
padding: 15px;
|
247 |
+
background: white;
|
248 |
+
color: var(--text-primary);
|
249 |
+
border-radius: 12px;
|
250 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
251 |
+
}
|
252 |
+
|
253 |
+
.welcome-message h3 {
|
254 |
+
color: var(--primary-color);
|
255 |
+
margin-bottom: 10px;
|
256 |
+
font-size: 1.2rem;
|
257 |
+
}
|
258 |
+
|
259 |
+
/* Suggestions Styling */
|
260 |
+
.suggestions {
|
261 |
+
margin: 15px 0;
|
262 |
+
padding: 15px;
|
263 |
+
background: white;
|
264 |
+
color: var(--text-primary);
|
265 |
+
border-radius: 12px;
|
266 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
267 |
+
}
|
268 |
+
|
269 |
+
.suggestions p {
|
270 |
+
color: var(--text-secondary);
|
271 |
+
font-size: 0.9rem;
|
272 |
+
margin-bottom: 12px;
|
273 |
+
}
|
274 |
+
|
275 |
+
.suggestion-buttons {
|
276 |
+
display: flex;
|
277 |
+
flex-wrap: wrap;
|
278 |
+
gap: 8px;
|
279 |
+
}
|
280 |
+
|
281 |
+
.suggestion-btn {
|
282 |
+
background: var(--background-color);
|
283 |
+
border: none;
|
284 |
+
padding: 8px 16px;
|
285 |
+
border-radius: 20px;
|
286 |
+
cursor: pointer;
|
287 |
+
transition: all 0.3s ease;
|
288 |
+
font-size: 0.9rem;
|
289 |
+
color: var(--primary-color);
|
290 |
+
position: relative;
|
291 |
+
overflow: hidden;
|
292 |
+
}
|
293 |
+
|
294 |
+
.suggestion-btn::before {
|
295 |
+
content: '';
|
296 |
+
position: absolute;
|
297 |
+
top: 0;
|
298 |
+
left: 0;
|
299 |
+
width: 100%;
|
300 |
+
height: 100%;
|
301 |
+
background: var(--gradient-3);
|
302 |
+
opacity: 0;
|
303 |
+
transition: opacity 0.3s ease;
|
304 |
+
}
|
305 |
+
|
306 |
+
.suggestion-btn span {
|
307 |
+
position: relative;
|
308 |
+
z-index: 1;
|
309 |
+
}
|
310 |
+
|
311 |
+
.suggestion-btn:hover {
|
312 |
+
color: white;
|
313 |
+
transform: translateY(-2px);
|
314 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
315 |
+
}
|
316 |
+
|
317 |
+
.suggestion-btn:hover::before {
|
318 |
+
opacity: 1;
|
319 |
+
}
|
320 |
+
|
321 |
+
.chat-input-container {
|
322 |
+
background-color: #121212; /* very dark gray (softer than pure black) */
|
323 |
+
border-top: 1px solid #222222; /* dark border */
|
324 |
+
color: white;
|
325 |
+
}
|
326 |
+
|
327 |
+
.chat-input-form {
|
328 |
+
display: flex;
|
329 |
+
gap: 12px;
|
330 |
+
background: #1e1e1e; /* dark gray background */
|
331 |
+
padding: 5px;
|
332 |
+
border-radius: 30px;
|
333 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.7); /* stronger dark shadow */
|
334 |
+
}
|
335 |
+
|
336 |
+
.chat-input-form input {
|
337 |
+
flex: 1;
|
338 |
+
padding: 12px 20px; /* keep original padding */
|
339 |
+
border: 2px solid #444444; /* dark border */
|
340 |
+
border-radius: 25px;
|
341 |
+
font-size: 0.95rem;
|
342 |
+
background-color: #121212; /* dark input background */
|
343 |
+
color: white;
|
344 |
+
transition: all 0.3s ease;
|
345 |
+
}
|
346 |
+
|
347 |
+
.chat-input-form input::placeholder {
|
348 |
+
color: #888888; /* lighter placeholder */
|
349 |
+
}
|
350 |
+
|
351 |
+
.chat-input-form input:focus {
|
352 |
+
border-color: var(--primary-color);
|
353 |
+
outline: none;
|
354 |
+
box-shadow: 0 0 0 3px rgba(41, 98, 255, 0.5); /* stronger focus glow */
|
355 |
+
}
|
356 |
+
|
357 |
+
.chat-input-form button {
|
358 |
+
background: var(--gradient-1);
|
359 |
+
color: white;
|
360 |
+
border: none;
|
361 |
+
width: 46px;
|
362 |
+
height: 46px;
|
363 |
+
border-radius: 50%;
|
364 |
+
cursor: pointer;
|
365 |
+
display: flex;
|
366 |
+
align-items: center;
|
367 |
+
justify-content: center;
|
368 |
+
transition: all 0.3s ease;
|
369 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
370 |
+
}
|
371 |
+
|
372 |
+
.chat-input-form button:hover {
|
373 |
+
transform: scale(1.05);
|
374 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
375 |
+
}
|
376 |
+
|
377 |
+
/* Loading Animation */
|
378 |
+
.typing-indicator {
|
379 |
+
display: flex;
|
380 |
+
gap: 8px;
|
381 |
+
padding: 10px;
|
382 |
+
}
|
383 |
+
|
384 |
+
.typing-indicator span {
|
385 |
+
width: 8px;
|
386 |
+
height: 8px;
|
387 |
+
background: var(--primary-light);
|
388 |
+
border-radius: 50%;
|
389 |
+
animation: bounce 1s infinite;
|
390 |
+
}
|
391 |
+
|
392 |
+
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
393 |
+
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
394 |
+
|
395 |
+
@keyframes bounce {
|
396 |
+
0%, 100% { transform: translateY(0); }
|
397 |
+
50% { transform: translateY(-8px); }
|
398 |
+
}
|
399 |
+
|
400 |
+
/* List Styling */
|
401 |
+
.response-list {
|
402 |
+
margin-top: 10px;
|
403 |
+
list-style: none;
|
404 |
+
}
|
405 |
+
|
406 |
+
.response-list li {
|
407 |
+
margin: 8px 0;
|
408 |
+
padding: 10px 15px;
|
409 |
+
background: rgba(0,0,0,0.03);
|
410 |
+
border-radius: 8px;
|
411 |
+
position: relative;
|
412 |
+
padding-left: 25px;
|
413 |
+
}
|
414 |
+
|
415 |
+
.response-list li::before {
|
416 |
+
content: '•';
|
417 |
+
position: absolute;
|
418 |
+
left: 10px;
|
419 |
+
color: var(--primary-color);
|
420 |
+
}
|
421 |
+
|
422 |
+
/* Scrollbar Styling */
|
423 |
+
::-webkit-scrollbar {
|
424 |
+
width: 6px;
|
425 |
+
}
|
426 |
+
|
427 |
+
::-webkit-scrollbar-track {
|
428 |
+
background: transparent;
|
429 |
+
}
|
430 |
+
|
431 |
+
::-webkit-scrollbar-thumb {
|
432 |
+
background: rgba(0,0,0,0.2);
|
433 |
+
border-radius: 3px;
|
434 |
+
}
|
435 |
+
|
436 |
+
::-webkit-scrollbar-thumb:hover {
|
437 |
+
background: rgba(0,0,0,0.3);
|
438 |
+
}
|
439 |
+
|
440 |
+
/* Animations */
|
441 |
+
@keyframes fadeIn {
|
442 |
+
from {
|
443 |
+
opacity: 0;
|
444 |
+
transform: translateY(10px);
|
445 |
+
}
|
446 |
+
to {
|
447 |
+
opacity: 1;
|
448 |
+
transform: translateY(0);
|
449 |
+
}
|
450 |
+
}
|
451 |
+
|
452 |
+
/* Error State */
|
453 |
+
.error {
|
454 |
+
color: var(--error-color);
|
455 |
+
padding: 10px;
|
456 |
+
border-radius: 8px;
|
457 |
+
background: rgba(220, 53, 69, 0.1);
|
458 |
+
}
|
459 |
+
|
460 |
+
/* Responsive Design */
|
461 |
+
@media (max-width: 768px) {
|
462 |
+
.main-content {
|
463 |
+
flex-direction: column;
|
464 |
+
}
|
465 |
+
|
466 |
+
.categories-sidebar {
|
467 |
+
width: 100%;
|
468 |
+
max-height: 40vh;
|
469 |
+
}
|
470 |
+
|
471 |
+
.chat-section {
|
472 |
+
height: 60vh;
|
473 |
+
}
|
474 |
+
|
475 |
+
.message {
|
476 |
+
max-width: 90%;
|
477 |
+
}
|
478 |
+
|
479 |
+
.chat-input-form input {
|
480 |
+
padding: 10px 15px;
|
481 |
+
}
|
482 |
+
|
483 |
+
.chat-input-form button {
|
484 |
+
width: 40px;
|
485 |
+
height: 40px;
|
486 |
+
}
|
487 |
+
|
488 |
+
.suggestion-buttons {
|
489 |
+
flex-direction: column;
|
490 |
+
}
|
491 |
+
|
492 |
+
.suggestion-btn {
|
493 |
+
width: 100%;
|
494 |
+
}
|
495 |
+
}
|
496 |
+
|
497 |
+
/* Dark Mode Support */
|
498 |
+
@media (prefers-color-scheme: dark) {
|
499 |
+
:root {
|
500 |
+
--background-color: #1a1a1a;
|
501 |
+
--chat-bg: #2d2d2d;
|
502 |
+
--text-primary: #ffffff;
|
503 |
+
--text-secondary: #cccccc;
|
504 |
+
}
|
505 |
+
|
506 |
+
.categories-sidebar,
|
507 |
+
.category-content button,
|
508 |
+
.message.system .message-content,
|
509 |
+
.suggestions,
|
510 |
+
.welcome-message {
|
511 |
+
background: #2d2d2d;
|
512 |
+
color: var(--text-primary);
|
513 |
+
}
|
514 |
+
|
515 |
+
.category-header {
|
516 |
+
background: #2b2b2b;
|
517 |
+
}
|
518 |
+
|
519 |
+
.chat-input-form input {
|
520 |
+
background: #2d2d2d;
|
521 |
+
border-color: #3d3d3d;
|
522 |
+
color: white;
|
523 |
+
}
|
524 |
+
|
525 |
+
/* CATEGORY SECTION COLOR UPDATES */
|
526 |
+
.category-item {
|
527 |
+
background: #232323; /* slightly darker for distinction */
|
528 |
+
box-shadow: 0 2px 8px rgba(255, 255, 255, 0.05);
|
529 |
+
}
|
530 |
+
|
531 |
+
.category-header {
|
532 |
+
background: linear-gradient(145deg, #2b2b2b, #222222);
|
533 |
+
color: var(--text-primary);
|
534 |
+
}
|
535 |
+
|
536 |
+
.category-header:hover {
|
537 |
+
background: linear-gradient(145deg, #222222, #2b2b2b);
|
538 |
+
}
|
539 |
+
|
540 |
+
.category-header span {
|
541 |
+
color: var(--text-primary);
|
542 |
+
}
|
543 |
+
|
544 |
+
.arrow-icon {
|
545 |
+
fill: var(--text-secondary);
|
546 |
+
}
|
547 |
+
|
548 |
+
.category-content {
|
549 |
+
background: #1f1f1f;
|
550 |
+
}
|
551 |
+
|
552 |
+
.category-content button {
|
553 |
+
background: #2d2d2d;
|
554 |
+
color: var(--text-primary);
|
555 |
+
box-shadow: 0 2px 4px rgba(255, 255, 255, 0.03);
|
556 |
+
}
|
557 |
+
|
558 |
+
.category-content button::before {
|
559 |
+
background: rgba(118, 143, 255, 0.15); /* subtle blue hover */
|
560 |
+
}
|
561 |
+
|
562 |
+
.category-content button:hover span {
|
563 |
+
color: white;
|
564 |
+
}
|
565 |
+
|
566 |
+
.logo {
|
567 |
+
color: #ff4081;
|
568 |
+
fill: #ff4081;
|
569 |
+
}
|
570 |
+
}
|
571 |
+
|
app/static/img/jupiter-logo.png
ADDED
![]() |
app/templates/base.html
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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>Jupiter FAQ Bot</title>
|
7 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
8 |
+
<!-- Add Google Font -->
|
9 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
10 |
+
</head>
|
11 |
+
<body>
|
12 |
+
{% block content %}{% endblock %}
|
13 |
+
</body>
|
14 |
+
</html>
|
app/templates/index.html
ADDED
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends "base.html" %}
|
2 |
+
{% block content %}
|
3 |
+
<div class="chat-container">
|
4 |
+
<!-- Header -->
|
5 |
+
<div class="chat-header">
|
6 |
+
<div class="header-content">
|
7 |
+
<div class="logo">
|
8 |
+
<svg width="32" height="32" viewBox="0 0 40 40">
|
9 |
+
<circle cx="20" cy="20" r="18" fill="#ff7f50"/>
|
10 |
+
<text x="15" y="28" fill="white" font-size="20">J</text>
|
11 |
+
</svg>
|
12 |
+
</div>
|
13 |
+
<h1>Jupiter AI Assistant</h1>
|
14 |
+
</div>
|
15 |
+
</div>
|
16 |
+
|
17 |
+
<!-- Main Content -->
|
18 |
+
<div class="main-content">
|
19 |
+
<!-- Updated Categories Sidebar with Questions from Provided Data Only -->
|
20 |
+
<div class="categories-sidebar">
|
21 |
+
<h2>📚 Quick Topics</h2>
|
22 |
+
<div class="categories-accordion">
|
23 |
+
<!-- Account & KYC -->
|
24 |
+
<div class="category-item">
|
25 |
+
<div class="category-header" onclick="toggleCategory(this)">
|
26 |
+
<span>🏦 Account & KYC</span>
|
27 |
+
<svg class="arrow-icon" viewBox="0 0 24 24">
|
28 |
+
<path d="M7 10l5 5 5-5z"/>
|
29 |
+
</svg>
|
30 |
+
</div>
|
31 |
+
<div class="category-content">
|
32 |
+
<button onclick="askQuestion('What is the Jupiter All-in-1 Savings Account?')">All-in-1 Savings Account</button>
|
33 |
+
<button onclick="askQuestion('How do I open a Jupiter Savings Account?')">Open Jupiter Account</button>
|
34 |
+
<button onclick="askQuestion('What additional benefits do I get with the Savings Account?')">Savings Account Benefits</button>
|
35 |
+
<button onclick="askQuestion('How do I open a Salary account?')">Open Salary Account</button>
|
36 |
+
<button onclick="askQuestion('Which documents are required for opening a Salary account?')">Documents for Salary Account</button>
|
37 |
+
<button onclick="askQuestion('What is the initial deposit when you open a Salary Account?')">Initial Deposit Required</button>
|
38 |
+
</div>
|
39 |
+
</div>
|
40 |
+
|
41 |
+
<!-- Payments & UPI -->
|
42 |
+
<div class="category-item">
|
43 |
+
<div class="category-header" onclick="toggleCategory(this)">
|
44 |
+
<span>💸 Payments & UPI</span>
|
45 |
+
<svg class="arrow-icon" viewBox="0 0 24 24">
|
46 |
+
<path d="M7 10l5 5 5-5z"/>
|
47 |
+
</svg>
|
48 |
+
</div>
|
49 |
+
<div class="category-content">
|
50 |
+
<button onclick="askQuestion('What payment methods does Jupiter support?')">Payment Methods</button>
|
51 |
+
<button onclick="askQuestion('Is there a transaction limit for UPI payments?')">UPI Transaction Limits</button>
|
52 |
+
<button onclick="askQuestion('Are there any charges for UPI transactions?')">UPI Transaction Charges</button>
|
53 |
+
<button onclick="askQuestion('What is UPI Lite and how does it work?')">UPI Lite Feature</button>
|
54 |
+
<button onclick="askQuestion('Can I send money to contacts using other UPI apps?')">Send to Other UPI Apps</button>
|
55 |
+
<button onclick="askQuestion('What types of bills can I pay on Jupiter?')">Bill Payment Types</button>
|
56 |
+
</div>
|
57 |
+
</div>
|
58 |
+
|
59 |
+
<!-- Cards -->
|
60 |
+
<div class="category-item">
|
61 |
+
<div class="category-header" onclick="toggleCategory(this)">
|
62 |
+
<span>💳 Cards</span>
|
63 |
+
<svg class="arrow-icon" viewBox="0 0 24 24">
|
64 |
+
<path d="M7 10l5 5 5-5z"/>
|
65 |
+
</svg>
|
66 |
+
</div>
|
67 |
+
<div class="category-content">
|
68 |
+
<button onclick="askQuestion('How can I apply for the Edge+ CSB Bank RuPay Credit Card?')">Apply for Edge+ Card</button>
|
69 |
+
<button onclick="askQuestion('What is the maximum cashback I can earn for each category?')">Maximum Cashback Limits</button>
|
70 |
+
<button onclick="askQuestion('Are there any fees for the Edge+ card?')">Edge+ Card Fees</button>
|
71 |
+
<button onclick="askQuestion('Can I use my Jupiter Debit Card outside India?')">International Debit Card Usage</button>
|
72 |
+
<button onclick="askQuestion('How to activate Edge Federal Bank Visa Credit Card?')">Activate Visa Card</button>
|
73 |
+
<button onclick="askQuestion('Is Edge Federal Bank Visa Credit Card lifetime free?')">Lifetime Free Card</button>
|
74 |
+
</div>
|
75 |
+
</div>
|
76 |
+
|
77 |
+
<!-- Rewards & Jewels -->
|
78 |
+
<div class="category-item">
|
79 |
+
<div class="category-header" onclick="toggleCategory(this)">
|
80 |
+
<span>💎 Rewards & Jewels</span>
|
81 |
+
<svg class="arrow-icon" viewBox="0 0 24 24">
|
82 |
+
<path d="M7 10l5 5 5-5z"/>
|
83 |
+
</svg>
|
84 |
+
</div>
|
85 |
+
<div class="category-content">
|
86 |
+
<button onclick="askQuestion('Do I earn Jewels for making payments?')">Earn Jewels on Payments</button>
|
87 |
+
<button onclick="askQuestion('How do I earn Jewels on Jupiter?')">How to Earn Jewels</button>
|
88 |
+
<button onclick="askQuestion('What can I redeem Jewels for?')">Redeem Jewels Options</button>
|
89 |
+
<button onclick="askQuestion('How fast can I redeem Jewels on Jupiter?')">Instant Jewels Redemption</button>
|
90 |
+
<button onclick="askQuestion('Do the earned Jewels expire?')">Jewels Expiry Policy</button>
|
91 |
+
<button onclick="askQuestion('Do I earn Jewels on International payments?')">International Payment Rewards</button>
|
92 |
+
</div>
|
93 |
+
</div>
|
94 |
+
|
95 |
+
<!-- Investments -->
|
96 |
+
<div class="category-item">
|
97 |
+
<div class="category-header" onclick="toggleCategory(this)">
|
98 |
+
<span>📈 Investments</span>
|
99 |
+
<svg class="arrow-icon" viewBox="0 0 24 24">
|
100 |
+
<path d="M7 10l5 5 5-5z"/>
|
101 |
+
</svg>
|
102 |
+
</div>
|
103 |
+
<div class="category-content">
|
104 |
+
<button onclick="askQuestion('What investment options are available on Jupiter?')">Investment Options</button>
|
105 |
+
<button onclick="askQuestion('What is Magic Spends and how does it work?')">Magic Spends Feature</button>
|
106 |
+
<button onclick="askQuestion('Can I invest using my Jupiter account?')">Invest with Jupiter</button>
|
107 |
+
<button onclick="askQuestion('What is Digital Gold on Jupiter?')">Digital Gold Investment</button>
|
108 |
+
<button onclick="askQuestion('Can I start investing in Gold with just ₹10?')">₹10 Gold Investment</button>
|
109 |
+
<button onclick="askQuestion('How do No-Penalty SIPs work on Jupiter?')">No-Penalty SIPs</button>
|
110 |
+
</div>
|
111 |
+
</div>
|
112 |
+
|
113 |
+
<!-- Loans -->
|
114 |
+
<div class="category-item">
|
115 |
+
<div class="category-header" onclick="toggleCategory(this)">
|
116 |
+
<span>💰 Loans</span>
|
117 |
+
<svg class="arrow-icon" viewBox="0 0 24 24">
|
118 |
+
<path d="M7 10l5 5 5-5z"/>
|
119 |
+
</svg>
|
120 |
+
</div>
|
121 |
+
<div class="category-content">
|
122 |
+
<button onclick="askQuestion('What types of loans can I get on Jupiter?')">Available Loan Types</button>
|
123 |
+
<button onclick="askQuestion('What are the interest rates applicable on loans with Jupiter?')">Loan Interest Rates</button>
|
124 |
+
<button onclick="askQuestion('What is a loan against mutual funds?')">Loan Against Mutual Funds</button>
|
125 |
+
<button onclick="askQuestion('Who can apply for a Personal Loan on Jupiter?')">Personal Loan Eligibility</button>
|
126 |
+
<button onclick="askQuestion('What is Mini-Loan?')">About Mini-Loan</button>
|
127 |
+
<button onclick="askQuestion('Will I get reminders for my EMI payments?')">EMI Payment Reminders</button>
|
128 |
+
</div>
|
129 |
+
</div>
|
130 |
+
|
131 |
+
<!-- Money Management -->
|
132 |
+
<div class="category-item">
|
133 |
+
<div class="category-header" onclick="toggleCategory(this)">
|
134 |
+
<span>📊 Money Management</span>
|
135 |
+
<svg class="arrow-icon" viewBox="0 0 24 24">
|
136 |
+
<path d="M7 10l5 5 5-5z"/>
|
137 |
+
</svg>
|
138 |
+
</div>
|
139 |
+
<div class="category-content">
|
140 |
+
<button onclick="askQuestion('What can I do in the Money tab on Jupiter?')">Money Tab Features</button>
|
141 |
+
<button onclick="askQuestion('How does Jupiter categorize my spending?')">Spending Categories</button>
|
142 |
+
<button onclick="askQuestion('Can I set Budgets in the Money tab?')">Set Monthly Budgets</button>
|
143 |
+
<button onclick="askQuestion('What is Account Aggregator and how does it work?')">Account Aggregator</button>
|
144 |
+
<button onclick="askQuestion('What is Net Worth in Jupiter and how is it calculated?')">Net Worth Tracker</button>
|
145 |
+
<button onclick="askQuestion('Can I track my credit card expenses in the Money tab?')">Track Credit Card Expenses</button>
|
146 |
+
</div>
|
147 |
+
</div>
|
148 |
+
</div>
|
149 |
+
</div>
|
150 |
+
|
151 |
+
<!-- Chat Section -->
|
152 |
+
<div class="chat-section">
|
153 |
+
<div class="chat-messages" id="chat-messages">
|
154 |
+
<div class="message system">
|
155 |
+
<div class="message-content">
|
156 |
+
<div class="welcome-message">
|
157 |
+
<h3>👋 Welcome to Jupiter!</h3>
|
158 |
+
<p>I'm here to help you with any questions about Jupiter's services. Choose a category from the sidebar or ask me anything!</p>
|
159 |
+
</div>
|
160 |
+
</div>
|
161 |
+
</div>
|
162 |
+
</div>
|
163 |
+
|
164 |
+
<div class="chat-input-container">
|
165 |
+
<form class="chat-input-form" onsubmit="sendMessage(event)">
|
166 |
+
<input type="text" id="user-input" placeholder="Ask me anything about Jupiter..." required>
|
167 |
+
<button type="submit">
|
168 |
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
169 |
+
<path d="M22 2L11 13"></path>
|
170 |
+
<path d="M22 2L15 22L11 13L2 9L22 2Z"></path>
|
171 |
+
</svg>
|
172 |
+
</button>
|
173 |
+
</form>
|
174 |
+
</div>
|
175 |
+
</div>
|
176 |
+
</div>
|
177 |
+
</div>
|
178 |
+
|
179 |
+
<script>
|
180 |
+
function toggleCategory(header) {
|
181 |
+
const categoryItem = header.parentElement;
|
182 |
+
const wasActive = categoryItem.classList.contains('active');
|
183 |
+
|
184 |
+
// Close all categories
|
185 |
+
document.querySelectorAll('.category-item').forEach(item => {
|
186 |
+
item.classList.remove('active');
|
187 |
+
});
|
188 |
+
|
189 |
+
// Open clicked category if it wasn't active
|
190 |
+
if (!wasActive) {
|
191 |
+
categoryItem.classList.add('active');
|
192 |
+
}
|
193 |
+
}
|
194 |
+
|
195 |
+
function formatMessage(text) {
|
196 |
+
if (text.includes("•")) {
|
197 |
+
const parts = text.split("•");
|
198 |
+
let formattedText = parts[0];
|
199 |
+
if (parts.length > 1) {
|
200 |
+
formattedText += '<ul class="response-list">';
|
201 |
+
for (let i = 1; i < parts.length; i++) {
|
202 |
+
if (parts[i].trim()) {
|
203 |
+
formattedText += `<li>${parts[i].trim()}</li>`;
|
204 |
+
}
|
205 |
+
}
|
206 |
+
formattedText += '</ul>';
|
207 |
+
}
|
208 |
+
return formattedText;
|
209 |
+
}
|
210 |
+
return text;
|
211 |
+
}
|
212 |
+
|
213 |
+
function addSuggestions(suggestions) {
|
214 |
+
if (!suggestions || suggestions.length === 0) return;
|
215 |
+
|
216 |
+
const suggestionsDiv = document.createElement('div');
|
217 |
+
suggestionsDiv.className = 'suggestions';
|
218 |
+
suggestionsDiv.innerHTML = '<p>You might also want to know:</p>';
|
219 |
+
|
220 |
+
const buttonsContainer = document.createElement('div');
|
221 |
+
buttonsContainer.className = 'suggestion-buttons';
|
222 |
+
|
223 |
+
suggestions.forEach(suggestion => {
|
224 |
+
const btn = document.createElement('button');
|
225 |
+
btn.className = 'suggestion-btn';
|
226 |
+
btn.innerHTML = `<span>${suggestion}</span>`;
|
227 |
+
btn.onclick = () => askQuestion(suggestion);
|
228 |
+
buttonsContainer.appendChild(btn);
|
229 |
+
});
|
230 |
+
|
231 |
+
suggestionsDiv.appendChild(buttonsContainer);
|
232 |
+
document.getElementById('chat-messages').appendChild(suggestionsDiv);
|
233 |
+
}
|
234 |
+
|
235 |
+
function askQuestion(question) {
|
236 |
+
document.getElementById('user-input').value = question;
|
237 |
+
document.querySelector('.chat-input-form').dispatchEvent(new Event('submit'));
|
238 |
+
}
|
239 |
+
|
240 |
+
function sendMessage(event) {
|
241 |
+
event.preventDefault();
|
242 |
+
|
243 |
+
const input = document.getElementById('user-input');
|
244 |
+
const messages = document.getElementById('chat-messages');
|
245 |
+
|
246 |
+
// Add user message
|
247 |
+
const userMessage = document.createElement('div');
|
248 |
+
userMessage.className = 'message user';
|
249 |
+
userMessage.innerHTML = `<div class="message-content">${input.value}</div>`;
|
250 |
+
messages.appendChild(userMessage);
|
251 |
+
|
252 |
+
// Add loading message
|
253 |
+
const loadingMessage = document.createElement('div');
|
254 |
+
loadingMessage.className = 'message system loading';
|
255 |
+
loadingMessage.innerHTML = `
|
256 |
+
<div class="message-content">
|
257 |
+
<div class="typing-indicator">
|
258 |
+
<span></span><span></span><span></span>
|
259 |
+
</div>
|
260 |
+
</div>
|
261 |
+
`;
|
262 |
+
messages.appendChild(loadingMessage);
|
263 |
+
|
264 |
+
// Scroll to bottom
|
265 |
+
messages.scrollTop = messages.scrollHeight;
|
266 |
+
|
267 |
+
// Send request to server
|
268 |
+
fetch('/chat', {
|
269 |
+
method: 'POST',
|
270 |
+
headers: {
|
271 |
+
'Content-Type': 'application/json',
|
272 |
+
},
|
273 |
+
body: JSON.stringify({
|
274 |
+
question: input.value
|
275 |
+
})
|
276 |
+
})
|
277 |
+
.then(response => response.json())
|
278 |
+
.then(data => {
|
279 |
+
// Remove loading message
|
280 |
+
messages.removeChild(loadingMessage);
|
281 |
+
|
282 |
+
// Add bot response
|
283 |
+
const botMessage = document.createElement('div');
|
284 |
+
botMessage.className = 'message system';
|
285 |
+
botMessage.innerHTML = `<div class="message-content">${formatMessage(data.response)}</div>`;
|
286 |
+
messages.appendChild(botMessage);
|
287 |
+
|
288 |
+
// Add suggestions if any
|
289 |
+
if (data.suggestions) {
|
290 |
+
addSuggestions(data.suggestions);
|
291 |
+
}
|
292 |
+
|
293 |
+
// Clear input and scroll
|
294 |
+
input.value = '';
|
295 |
+
messages.scrollTop = messages.scrollHeight;
|
296 |
+
})
|
297 |
+
.catch(error => {
|
298 |
+
console.error('Error:', error);
|
299 |
+
loadingMessage.innerHTML = '<div class="message-content error">Sorry, something went wrong. Please try again.</div>';
|
300 |
+
});
|
301 |
+
}
|
302 |
+
</script>
|
303 |
+
{% endblock %}
|
config.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
class Config:
|
2 |
+
SECRET_KEY = 'your-secret-key-here'
|
3 |
+
DEBUG = True
|
data/faq_embeddings.npy
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:667151a932f014e87ec59cfe71b461c678b271ad3924c8a4f32514e64264f7e5
|
3 |
+
size 227456
|
data/faqs.csv
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"question","answer","category"
|
2 |
+
"What is the Jupiter All-in-1 Savings Account?","The All-in-1 Savings Account on Jupiter powered by Federal Bank helps you manage your money better with faster payments, smart saving tools, and investment insights—all in one place.",Account
|
3 |
+
"How do I open a Jupiter Savings Account?","You can open your Jupiter digital account by following a few simple steps: 1. Install the Jupiter app 2. Tap 'Open an all-in-1 Savings Account' while selecting a Jupiter experience 3. Complete your video KYC",Account
|
4 |
+
"Do I earn Jewels for making payments?","Yes! You earn up to 1% cashback as Jewels on: • UPI payments • Debit Card spends (online & offline) • Investments in Digital Gold",Rewards
|
5 |
+
"Can I use my Jupiter Debit Card outside India?","Absolutely. You can spend in over 120 countries with 0% forex fee on international transactions using your Jupiter Debit Card.",Card
|
6 |
+
"Do I earn Jewels on International payments?","Yes, you also earn up to 1% cashback on online and offline international spends.",Rewards
|
7 |
+
"What payment modes are available with the Jupiter account?","You can make superfast payments with UPI, IMPS, and debit card—whether it's for recharges, bills, or merchant transactions.",Payments
|
8 |
+
"Can I invest using my Jupiter account?","Yes! You can invest in Mutual Funds and Digital Gold with up to 1.5% extra returns on curated mutual fund plans.",Investments
|
9 |
+
"What additional benefits do I get with the Savings Account?","You earn up to 1% cashback as Jewels on: • Free cheque book • Free IMPS transfers • ATM withdrawals",Account
|
10 |
+
"How is this Salary account different then?","Our Salary Account can also be opened by an employee and does not necessarily need an employer to open one for you.",Salary Account
|
11 |
+
"Why should I switch to Salary account?","It's your Salary Account with extra benefits. Get the most out of your salary. Salary account is an upgraded version of the Basic account. Receive your monthly salary in your new Salary Account to earn maximum rewards on Jupiter, free credit & debit card, Mini Loans starting from 0% interest.",Salary Account
|
12 |
+
"How do I open a Salary account?","To open a Salary account, follow these easy steps: 1. Download the Jupiter app and create your account 2. Complete your video verification / full KYC 3. Submit your company's name and verify your work email address 4. Share your new account details with HR and make sure to get your next salary in your new Salary Account within 60 days. That's it! Once your salary is credited, you'll automatically be upgraded to a Salary account.",Salary Account
|
13 |
+
"I already have a Savings Account on the Jupiter app. How do I upgrade to a Salary account?","To upgrade to a Salary account, please go to 'Settings' on the Jupiter app and follow 3 simple steps. Currently open to select few.",Salary Account
|
14 |
+
"Who all can open a Salary account on Jupiter?","To open a Salary account, please ensure that your in-hand salary is more ₹20,000. However, this is negotiable for teams. Please connect with us to learn more about the Salary account.",Salary Account
|
15 |
+
"How much time does it take to open a Salary account?","It takes only 3 minutes to complete all the steps to open a Salary account.",Salary Account
|
16 |
+
"Can I change my already existing salary account? How?","Create a new account on the app, submit your new bank account details with your HR to receive further salaries in that account and turn yourself into a Salary Account user",Salary Account
|
17 |
+
"Are there any charges for opening a Salary account?","Opening a Salary Account on Jupiter is completely free. There are also no charges for UPI, NEFT, and RTGS transactions.",Salary Account
|
18 |
+
"What is Mini-Loan?","By opening a Salary account, you can instantly withdraw your partial salary before salary day with no interest charges.",Salary Account
|
19 |
+
"I'm a non-salaried individual. How do I upgrade to Salary account?","Keep an eye out – we'll bring the benefits of Salary account to freelancers & other non-salaried individuals soon.",Salary Account
|
20 |
+
"Which documents are required for opening a Salary account?","Your Aadhaar card linked with your phone number and PAN card are required to open a Salary account.",Salary Account
|
21 |
+
"How much does the Corporate Salary Account cost?","It's completely free.",Corporate Salary Account
|
22 |
+
"What if my company already has salary accounts for employees with another bank?","If you have an existing account with another bank, you can continue using it for business banking. You can still use Jupiter Corporate Salary Account for your employees with discounted medical insurance, 0% interest loans, 0 forex fees, and more. Ultimately, you get the best of both worlds.",Corporate Salary Account
|
23 |
+
"Is my employee's money safe?","It is 100% safe. Jupiter Corporate Salary Account is hosted in an RBI-licensed bank (Federal Bank) and the money in the account is insured up to ₹5,00,000.",Corporate Salary Account
|
24 |
+
"Does my company need a minimum number of employees?","We support companies of all sizes. From 2-person businesses to organizations that have over 1000 employees.",Corporate Salary Account
|
25 |
+
"What is the benefit for my company?","Jupiter Corporate Salary Account helps you save up to ₹50,00,000 yearly by cutting down on HRMS costs, payroll software costs and health insurance costs. You can streamline payout processes with our bulk salary payout portal. Your employees can also get loans at 0% interest, and invest in Mutual Funds and Digital Gold on Jupiter.",Corporate Salary Account
|
26 |
+
"Which documents are needed for opening a Salary Account?","Your employees need to have the following to open a Jupiter Corporate Salary Account: Physical PAN card, A smart phone, Mobile number linked to Aadhar card, PAN & Aadhaar should be linked.",Corporate Salary Account
|
27 |
+
"What is the initial deposit when you open a Salary Account?","No initial deposit is required to open a Salary Account.",Corporate Salary Account
|
28 |
+
"Do we need to shift our Current Account to Jupiter?","Absolutely not, your existing relationship with banker is mostly for Business Banking. You shall continue enjoying benefits provided by your existing banker (for business banking) + enjoy additional benefits from Jupiter against your employee's bank account. You get to enjoy the best of both worlds.",Corporate Salary Account
|
29 |
+
"Do employees need to maintain any minimum balance?","While users are required to maintain ₹5,000 as the average monthly balance, charges up to ₹200 for not maintaining of balance. However for Salary Account users, the account is a zero balance account on consistent salary credits.",Corporate Salary Account
|
30 |
+
"Is it a Federal bank account or Jupiter account?","A Federal Bank account is created for each employee. They can use the Jupiter app to pay, invest or save money without any restrictions.",Corporate Salary Account
|
31 |
+
"Can the employee continue with the Jupiter salary account even if he leaves the present company?","Your employees can continue using the same Salary Account with their new employer and keep enjoying our benefits.",Corporate Salary Account
|
32 |
+
"If any employee has an active loan with their current bank, can they still use the Jupiter Corporate Salary Account?","They can continue paying their loan with their existing bank. Jupiter's Autopay feature lets them auto-transfer any amount from their Jupiter account to their existing bank, ensuring their loan payments continue seamlessly.",Corporate Salary Account
|
33 |
+
"What payment methods does Jupiter support?","You can make payments via UPI, your linked bank account, and debit cards. You can also link any bank's RuPay Credit Card to Jupiter UPI and make payments.",Payments
|
34 |
+
"Is it safe to make payments through Jupiter?","Yes. Jupiter uses industry-standard encryption and is compliant with RBI regulations to ensure secure and private transactions.",Payments
|
35 |
+
"Can I pay my bills using Jupiter?","Yes, you can pay utility bills like electricity, gas, DTH, water, and mobile recharges directly from the app.",Payments
|
36 |
+
"Can I link multiple bank accounts to Jupiter?","Yes, you can link more than one bank account and switch between them when making UPI payments.",Payments
|
37 |
+
"Can I track my payments on Jupiter?","Yes, you can track all your payments, see your top spending categories, and get actionable insights to better manage your money.",Payments
|
38 |
+
"How do I earn cashback or rewards on payments?","If you have a PRO Savings Account, you earn 1% cashback on all Debit Card payments and UPI payments from 7 top brands. If you have linked any RuPay Credit Card to Jupiter UPI, you earn up to 2% cashback on every payment. Cashback is credited in the form of Jewels. 5 Jewels = ₹1",Payments
|
39 |
+
"Are there any charges for UPI transactions?","No, UPI payments are free of charge on Jupiter.",Payments
|
40 |
+
"Is there a transaction limit for UPI payments?","Yes, the daily UPI limit is set by your bank and NPCI, which is generally ₹1 Lakh per day.",Payments
|
41 |
+
"What types of bills can I pay on Jupiter?","You can pay your credit card bills, electricity, DTH, broadband, water, gas, mobile prepaid, and more—all in one place.",Bill Payments
|
42 |
+
"What payment modes can I use to pay bills on Jupiter?","You can pay bills through bank transfers linked to your Federal Bank Savings Account, UPI, or your RuPay Credit Cards.",Bill Payments
|
43 |
+
"Do I get rewards for bill payments?","Yes! You earn Jewels for every eligible bill and recharge made with Jupiter.",Bill Payments
|
44 |
+
"What are Jewels and how do they work?","Jewels are credits you earn when you make eligible payments with Jupiter. You can redeem them in cash for instant discounts on future bill payments.\n₹1= 5 Points*\n*As per the rewards policy",Bill Payments
|
45 |
+
"How do I activate Autopay?","To set up Autopay for bills & recharges, use your Jupiter UPI ID as a default payment method on the respective merchant app or website.",Bill Payments
|
46 |
+
"Do I need to upload any documents or enter bill details manually each time?","No paperwork needed! Just link your billing merchant once, and your dues automatically in the future.",Bill Payments
|
47 |
+
"Can I track my past payments?","Yes, your entire payment history is available under the 'Bills & Recharges' section in the app.",Bill Payments
|
48 |
+
"What type of payments can I make with Jupiter UPI?","You can send money, pay contacts, scan QR codes at stores, and shop online—securely and instantly.",UPI Payments
|
49 |
+
"Do I need to open a Jupiter Savings Account to use UPI?","No, you can use Jupiter UPI with your existing bank account by linking it on the Jupiter app.",UPI Payments
|
50 |
+
"What rewards do I get with my Edge CSB Bank RuPay Credit Card?","You can get 2% cashback on UPI & credit card spends with the Edge CSB Bank RuPay Credit Card.",UPI Payments
|
51 |
+
"Can I send money to contacts using other UPI apps?","Absolutely. Jupiter lets you send money to anyone—regardless of the UPI app they use.",UPI Payments
|
52 |
+
"What is UPI Lite and how does it work?","UPI Lite lets you make small-value payments without entering your UPI PIN (Up to ₹5,000). It's perfect for quick spends like groceries or tea.",UPI Payments
|
53 |
+
"Is there a limit on UPI transactions?","To ensure safety of your transactions, NPCI has set the maximum daily UPI payments limit to ₹1 Lakh.",UPI Payments
|
54 |
+
"What is Magic Spends and how does it work?","Magic Spends is an exclusive feature on Jupiter to automatically invest every time you spend. You can select any amount, set a monthly limit, and invest in either Mutual Funds or Digital Gold with every UPI, Debit Card, and Credit Card payment.",Magic Spends
|
55 |
+
"Where can I invest with Magic Spends?","You can invest in a Mutual Fund of your choice or 24K Digital Gold every time you spend.",Magic Spends
|
56 |
+
"Can I customize how much money I auto-invest every time I spend?","Yes, you can auto-invest ₹10 or more in Digital Gold or ₹100 or more in Mutual Funds every time you spend with Magic Spends.",Magic Spends
|
57 |
+
"Can I track my invested amount with Magic Spends?","Yes, you can track the value of your invested amount in real-time for both Mutual Funds and Digital Gold.",Magic Spends
|
58 |
+
"What happens if I don't have enough balance in my account?","If you don't have enough balance while spending, Magic Spends will be skipped. You can continue auto-investing after you add money to your account.",Magic Spends
|
59 |
+
"Can I pause Magic Spends or withdraw my invested amount?","Yes, you can pause Magic Spends anytime through the app. You can also withdraw the money you've invested and it will be credited instantly to your account.",Magic Spends
|
60 |
+
"Are there any fees or charges for Magic Spends?","No, Magic Spends is completely free to set up and Jupiter does not charge any commissions.",Magic Spends
|
61 |
+
"How can I apply for the Edge+ CSB Bank RuPay Credit Card?","You can check your eligibility and credit limit for the credit card instantly. To apply for the card, download the Jupiter app and complete your application anytime. It's 100% digital with no paperwork!",Edge+ Credit Card
|
62 |
+
"What's the maximum cashback I can earn with Edge+?","There is no maximum limit of cashback which can be earned with Edge+, there are limits on accelerated cashback for categories.",Edge+ Credit Card
|
63 |
+
"What is the maximum cashback I can earn for each category?","The maximum cashback applicable under each category is: 10% cashback - ₹1,500 per billing cycle (merchant limit of ₹500); 5% cashback - ₹1,000 per billing cycle; 1% cashback - No Limit",Edge+ Credit Card
|
64 |
+
"How is cashback credited?","The cashback amount is credited to your account instantly in the form of Jewels. 5 Jewels = ₹1 and you can redeem Jewels as cash, gift cards, Digital Gold, and for bill payments.",Edge+ Credit Card
|
65 |
+
"Which brands does Edge+ offer cashback on?","10% cashback on shopping: Amazon, Flipkart, Myntra, Ajio, Zara, Nykaa, Croma, Reliance Trends, Tata Cliq, Reliance Digital; 5% cashback on travel: MakeMyTrip, EaseMyTrip, Yatra, Cleartrip; 1% cashback: On all other offline & online eligible spends. Brand names are indicative and subject to change.",Edge+ Credit Card
|
66 |
+
"Are there any fees for the Edge+ card?","There is only a one-time joining fee of ₹499+GST for the card. You also get a Fraud Protection Plan by OneAssist which includes an Amazon Prime Membership worth ₹1499. The card is lifetime free with no annual fee.",Edge+ Credit Card
|
67 |
+
"How can I start using UPI on my RuPay Credit Card?","Install the Jupiter app and apply for the Edge CSB Bank RuPay Credit Card. Fill in the necessary details. Once approved, you can start using the card to make UPI payments on your RuPay Credit Card.",UPI on RuPay Credit Card
|
68 |
+
"How to use the Edge CSB Bank RuPay Credit Card for UPI payments?","You can enable online transactions on your Edge RuPay Credit Card to use it on any website or app.",UPI on RuPay Credit Card
|
69 |
+
"Can I send money to friends using a Credit Card on UPI?","No, you can only use the Edge CSB Bank RuPay Credit Card to pay online and offline merchants.",UPI on RuPay Credit Card
|
70 |
+
"Who can apply for a RuPay Credit Card?","Anyone between 23 and 60 years and with a good credit score can apply for the Edge CSB Bank Credit Card.",UPI on RuPay Credit Card
|
71 |
+
"Is it safe to use my RuPay Credit Card on UPI?","It is absolutely safe to use your RuPay Credit Card on UPI.",UPI on RuPay Credit Card
|
72 |
+
"Can I withdraw money from UPI Credit Cards?","Currently, ATM withdrawal is not enabled on your Edge RuPay Credit Card.",UPI on RuPay Credit Card
|
73 |
+
"How can I use my RuPay Card for online payments?","Once you enable online transactions on your Edge RuPay Credit Card, you can use it on any website or app to pay.",UPI on RuPay Credit Card
|
74 |
+
"How can I pay a retail merchant offline with UPI on Credit Card?","Edge RuPay Credit Card allows you to make UPI payments with a Credit Card. You can scan any QR code and pay with your credit card.",UPI on RuPay Credit Card
|
75 |
+
"How can I link my Edge CSB Bank Credit Card to UPI?","Once you have activated your credit card on Jupiter, you will be able to use it with Jupiter UPI automatically.",UPI on RuPay Credit Card
|
76 |
+
"Will I earn rewards on UPI Spends using the Edge CSB Bank RuPay Credit Card?","Yes, you can earn assured 2% cashback on UPI transactions.",UPI on RuPay Credit Card
|
77 |
+
"How do I generate UPI PIN for my Edge CSB Bank RuPay Credit Card?","You can easily set your Edge UPI Credit Card PIN by going to 'Credit Card' tab on home screen → Choose 'View Card' → Tap 'Settings' gear on top → Tap 'Reset PIN'. Then change your PIN",UPI on RuPay Credit Card
|
78 |
+
"What is the eligibility criteria for getting the Edge CSB Bank RuPay Credit Card?","Currently users with a good credit score, and who are older than 21 years can be eligible for the Edge RuPay Credit Card.",UPI on RuPay Credit Card
|
79 |
+
"How can I apply for a Edge Federal Bank Visa Credit Card?","Edge Federal Bank Visa Credit card is available exclusively on the Jupiter app. Download the Jupiter app and apply for a Edge Federal Bank Visa Credit Card.",Edge Federal Bank Visa Credit Card
|
80 |
+
"How can I check my Edge Federal Bank Visa Credit Card Outstanding Balance?","On the Jupiter app, go to the 'Credit Cards' tab, here, you will be able to see the current outstanding and available limit.",Edge Federal Bank Visa Credit Card
|
81 |
+
"Can I use my Edge Federal Bank Visa Credit Card for international payments?","Yes, you can do international transactions with your Edge Federal Bank Visa Credit Card.",Edge Federal Bank Visa Credit Card
|
82 |
+
"How to activate Edge Federal Bank Visa Credit Card?","Activate your card by scanning the QR code on the card kit",Edge Federal Bank Visa Credit Card
|
83 |
+
"Who is eligible for Edge Federal Bank Visa Credit Card?","Anyone older than 21 years and with a good credit score can apply for the Credit Card.",Edge Federal Bank Visa Credit Card
|
84 |
+
"What are the fees and charges on the Edge Federal Bank Visa Credit Card?","Edge Federal Bank Visa Credit Card has zero joining fee and zero service charges for transactions. Read MITC at https://jupiter.money/edge-card-mitc/ for more information.",Edge Federal Bank Visa Credit Card
|
85 |
+
"Can I withdraw money using the Edge Federal Bank Visa Credit Card?","Currently, ATM withdrawals are not enabled on your Edge Federal Bank Visa Credit Card.",Edge Federal Bank Visa Credit Card
|
86 |
+
"Is Edge Federal Bank Visa Credit Card lifetime free?","Edge Federal Bank Visa Credit Card's annual fee of ₹999 is waived off on eligible spends of ₹1.2 lacs in the preceding year",Edge Federal Bank Visa Credit Card
|
87 |
+
"What is the credit limit of Edge Federal Bank Visa Credit Card?","Your limit is calculated based on your credit score",Edge Federal Bank Visa Credit Card
|
88 |
+
"How do I earn Jewels on Jupiter?","You earn Jewels when you spend using Jupiter—whether it's with UPI, your debit card, or linked credit cards. 5 Jewels = 1 Rupee",Jupiter Jewels Rewards
|
89 |
+
"What can I redeem Jewels for?","You can choose from the following loan options: Digital Gold, Cash credits, Gift vouchers, Bill payments, Credit card discounts",Jupiter Jewels Rewards
|
90 |
+
"How fast can I redeem Jewels on Jupiter?","Yes, you can redeem the Jewels instantly. You can convert them to cash, gold, vouchers or use them to get discounts on bill payments.",Jupiter Jewels Rewards
|
91 |
+
"Do I get extra rewards with linked accounts or cards?","Yes! For example: PRO Savings Account users get 1% cashback; Edge CSB Bank RuPay Card users get 2% cashback; Linking any RuPay credit card earns you up to 2% cashback on UPI and spends",Jupiter Jewels Rewards
|
92 |
+
"Do the earned Jewels expire?","Your Jewels are safe and redeemable directly on the Jupiter app. Expiry terms, if any, are clearly shown under your rewards dashboard.",Jupiter Jewels Rewards
|
93 |
+
"What types of loans can I get on Jupiter?","You can choose from the following loan options: Personal Loan - Up to ₹5,00,000; Mini Loan - Up to ₹1,00,000; Loan against Mutual Funds - Up to ₹50,00,000",Jupiter Loans
|
94 |
+
"Who can apply for a Personal Loan on Jupiter?","To apply for a Personal Loan, you must: Be an Indian resident; Be 21 to 60 years old; Be either salaried, business owner, or self-employed",Jupiter Loans
|
95 |
+
"What are the interest rates applicable on loans with Jupiter?","Personal Loans start at 1.33% per month; Mini Loans have 0% interest If repaid in 45 days; Loans against Mutual Funds start at 10.5% per annum",Jupiter Loans
|
96 |
+
"Is the loan application process fully digital?","Yes, the entire process, from checking eligibility to receiving funds, is 100% digital on the Jupiter app.",Jupiter Loans
|
97 |
+
"How do I apply for a loan on Jupiter?","Steps to apply: 1. Check your eligibility 2. Select your preferred loan option 3. Confirm the loan details 4. Get money instantly in your account",Jupiter Loans
|
98 |
+
"Will I get reminders for my EMI payments?","Yes, Jupiter sends timely EMI reminders so you never miss a due date.",Jupiter Loans
|
99 |
+
"Is there any support if I face issues?","Yes, you get 24x7 support from our team via the Jupiter app.",Jupiter Loans
|
100 |
+
"What can I use my loan for?","The loans are flexible and can be used for any purpose, including: Laptop or phone purchase; Medical expenses; Travel, wedding, or home renovation; Debt consolidation or emergencies",Jupiter Loans
|
101 |
+
"What is a loan against mutual funds?","A Loan Against Mutual Funds lets you borrow money by pledging your Mutual Funds. On Jupiter, you can get a Loan Against Mutual Funds of up to ₹2 Crore",Loan Against Mutual Funds
|
102 |
+
"How much money can I borrow?","Your loan limit will depend on the amount and type of Mutual Funds in your portfolio",Loan Against Mutual Funds
|
103 |
+
"Can I choose any date for my monthly loan repayment?","You can repay the principal amount anytime within 36 months and the interest in 12 EMIs",Loan Against Mutual Funds
|
104 |
+
"Do I need to pay interest on the whole amount I am eligible for?","No, you pay monthly interest only on the withdrawn amount out of the total credit limit given to you",Loan Against Mutual Funds
|
105 |
+
"What is lien marking/pledging of Mutual Funds?","When you take a Loan Against Mutual Funds, your funds will be lien marked. This means, the lender has the right to hold your units and sell them in case the loan isn't repaid. However, your funds will continue earning returns and will be redeemable once the loan is fully repaid",Loan Against Mutual Funds
|
106 |
+
"Which mutual fund schemes are accepted or can be pledged to take loans?","Your loan limit can be up to 75% of the Net Asset Value of your Debt Mutual Funds, and 45% of the NAV of Equity Mutual Funds. For example, if you have invested ₹3 Lakh in Debt Mutual Funds and ₹1 Lakh in Equity Mutual Funds, your available loan limit will be 75% of ₹3 Lakhs plus 45% of ₹1 Lakh equal to ₹2.70 Lakh",Loan Against Mutual Funds
|
107 |
+
"How do I check my limit?","You can check your limit on the Jupiter app",Loan Against Mutual Funds
|
108 |
+
"Can I pledge any Mutual Fund?","Yes, you can pledge any eligible Mutual Fund. You can also choose the number of units you want to pledge",Loan Against Mutual Funds
|
109 |
+
"When can I redeem the Mutual Funds I have pledged?","Your Mutual Funds will be redeemable once your loan is fully repaid",Loan Against Mutual Funds
|
110 |
+
"What is the tenure of the loan against mutual funds?","You can repay the principal amount in up to 36 months and the interest in 12 EMIs",Loan Against Mutual Funds
|
111 |
+
"What is the credit limit and how is it calculated?","Your credit limit is calculated based on the value of your Mutual Funds portfolio",Loan Against Mutual Funds
|
112 |
+
"What investment options are available on Jupiter?","You can invest in the following: Mutual Funds, Digital Gold, Magic Spends, Fixed Deposits",Investments
|
113 |
+
"Do I need to open a Savings Account to Invest on Jupiter?","Yes, you need to open a Savings Account to invest in Mutual Funds, Magic Spends or Fixed Deposits. But, you can start investing in Digital Gold even without opening a Savings Account",Investments
|
114 |
+
"Is there a fee for investing in Mutual Funds on Jupiter?","No, Jupiter doesn't charge any commissions or extra fees on Mutual Funds",Investments
|
115 |
+
"How do No-Penalty SIPs work on Jupiter ?","No-Penalty SIPs let you skip SIPs without having to pay any extra charges",Investments
|
116 |
+
"What is 'Magic Spends' and how does it work?","Magic Spends lets you auto-invest small amounts like ₹10 or more every time you spend, turning your daily spending into effortless investing",Investments
|
117 |
+
"Can I withdraw from Fixed Deposits before maturity?","Yes, FlexiFD lets you withdraw money in parts without having to break your full FD",Investments
|
118 |
+
"Is my money safe when I invest on Jupiter?","Yes. Jupiter partners with trusted AMCs, RBI-regulated entities, and SEBI-compliant platforms to ensure your investments are 100% safe",Investments
|
119 |
+
"Am I eligible to invest on Jupiter?","We're currently open to folks who have Mutual Fund investment accounts already. If you've invested in Mutual Funds in India, or have signed up to invest on another platform before, you're eligible! Go ahead and sign up on the Jupiter app to get started. In a few weeks, everyone else will also be able to invest on Jupiter — hang in there, and watch this space for updates",Investments
|
120 |
+
"How do I pay less and earn more using the Jupiter App?","On Jupiter, you pay less and earn more on your investments because: 1. Jupiter doesn't charge any hidden commissions of 1.5% over and above your investment amount like other regular funds. 2. Jupiter doesn't charge Penalties of up to Rs 750 that other banks charge in case you miss a SIP because of insufficient balance in your account",Investments
|
121 |
+
"What's the difference between a No-Penalty SIP and a Normal SIP?","In case you miss a SIP because of insufficient balance in your account, your bank charges you ₹250–₹750 as Penalty for that missed SIP. Jupiter just smartly skips your SIP! So, there are no Penalty charges",Investments
|
122 |
+
"What all Mutual Funds does Jupiter have?","Jupiter has 1000+ Mutual Funds from 39 major AMCs across India. Jupiter only provides growth Direct Mutual Funds which are the best commission-free funds in India",Investments
|
123 |
+
"What are Mutual Funds?","Mutual fund is an investment vehicle that brings together money from many people like you and invests it in stocks, bonds or other assets. Example: ICICI Prudential Technology Fund invests your money, majorly in high growth technology companies like Infosys, Wipro, HCL Technologies, etc.",Investments
|
124 |
+
"Why should I invest with Jupiter Money?","Pay less, earn more returns: 1. No hidden commissions of 1.5% like regular funds 2. No penalties up to Rs 750 for missed SIPs. Easy investing: - No-Penalty SIPs auto-skip if funds are low - One-click SIPs and cancellations - No paperwork",Investments
|
125 |
+
"What are Direct Mutual Funds which Jupiter provides?","Direct Mutual Funds are Mutual Funds with Zero Commissions or Brokerage charges. They offer up to 1.5% extra returns compared to other Mutual Funds",Investments
|
126 |
+
"Why should I invest in Mutual Funds?","You can invest in Mutual funds as: 1. You can save up to Rs 46,800 in taxes every year. 2. Your invested money compounds and generates more returns. 3. You build financial discipline.",Investments
|
127 |
+
"Are Mutual Funds regulated?","Mutual Fund companies are regulated by Securities and Exchange Board of India (SEBI). SEBI falls under the control of the Ministry of Finance of India.",Investments
|
128 |
+
"I don't know what Mutual Funds to invest in. What do I do?","We've created Mutual Fund collections to help you: - High Growth - Tax Saving Funds - Better than FD - Low cost index funds - SIP with 500. These are matched to your investment goals.",Investments
|
129 |
+
"What is the smallest amount I can invest in Mutual Funds?","You can start investing in Mutual Funds with as little as Rs. 500. We've put together a Collection containing all Mutual Funds where you can start investing with Rs 500.",Investments
|
130 |
+
"Is investing in Mutual Funds difficult?","Investing in Mutual Funds is simple on Jupiter: - Pick a fund or collection - Choose SIP or One Time investment - Swipe to pay. That's it!",Investments
|
131 |
+
"How long does it take to get started with Mutual Funds on Jupiter?","If you're a verified user on the Jupiter app, you can make your investment in under 5 minutes",Investments
|
132 |
+
"I've never been charged a Penalty before. Why should I choose No-Penalty SIPs?","Even if you're disciplined, No-Penalty SIP gives you flexibility. If you have an emergency or want to use your money elsewhere, just skip your SIP — no charges, no questions asked.",Investments
|
133 |
+
"How is Jupiter not charging the Rs. 250-750 Penalty? Is there any catch or fine-print?","Banks charge this penalty when your SIP mandate fails due to insufficient balance. Since you bank and invest on Jupiter, we detect low balance and pause your SIP, avoiding penalty fees completely.",Investments
|
134 |
+
"Do you mean I don't get same day's NAV elsewhere?","Most platforms take a few working days to allocate units after order placement, and the NAV can change. On Jupiter, if you invest before 2 PM on working days, you get that day's NAV.",Investments
|
135 |
+
"What is Digital Gold on Jupiter?","Digital Gold on Jupiter allows you to buy and sell 24K gold online at live market prices, starting from as low as ₹10. It's 100% digital and secure with no making charges.",Digital Gold
|
136 |
+
"How is my Digital Gold stored?","Every gram of Digital Gold you purchase is stored as 24K physical Gold in lifetime-free vaults by our trusted partners.",Digital Gold
|
137 |
+
"Can I start investing in Gold with just ₹10?","Yes, you can purchase 24K Digital Gold with just ₹10 or more. You can either make a one-time investment or invest regularly with daily, weekly, and monthly SIPs.",Digital Gold
|
138 |
+
"Can I sell my Digital Gold anytime?","Yes, you can instantly sell your Digital Gold at live market prices from the Jupiter app.",Digital Gold
|
139 |
+
"Can I get physical delivery of the gold?","Currently, Jupiter offers only digital storage of gold. Physical delivery is not available.",Digital Gold
|
140 |
+
"How is the price of Digital Gold determined?","Prices are updated live and reflect the current market rate of 24K gold with no making charges.",Digital Gold
|
141 |
+
"Why should I invest in Digital Gold?","Digital Gold lets you invest in 24K pure gold without worrying about storage or safety. Unlike traditional options, you can start small, track your gold value in real time, and sell it anytime you want. It's a smart way to grow your savings and with Jupiter, the process is completely digital.",Digital Gold
|
142 |
+
"Can I set up automatic investments in Digital Gold?","Yes, you can set up a daily, weekly, or monthly SIP to auto-invest in Digital Gold. You can also set up Magic Spends, a feature exclusively on Jupiter, to auto-invest in Gold every time you spend.",Digital Gold
|
143 |
+
"What is Magic Spends and how does it work?","Magic Spends is an exclusive feature on Jupiter to automatically invest every time you spend. You can select any amount, set a monthly limit, and invest in either Mutual Funds or Digital Gold with every UPI, Debit Card, and Credit Card payment.",Magic Spends
|
144 |
+
"Where can I invest with Magic Spends?","You can invest in a Mutual Fund of your choice or 24K Digital Gold every time you spend.",Magic Spends
|
145 |
+
"Can I customize how much money I auto-invest every time I spend?","Yes, you can auto-invest ₹10 or more in Digital Gold or ₹100 or more in Mutual Funds every time you spend with Magic Spends.",Magic Spends
|
146 |
+
"Can I track my invested amount with Magic Spends?","Yes, you can track the value of your invested amount in real-time for both Mutual Funds and Digital Gold.",Magic Spends
|
147 |
+
"What happens if I don't have enough balance in my account?","If you don't have enough balance while spending, Magic Spends will be skipped. You can continue auto-investing after you add money to your account.",Magic Spends
|
148 |
+
"Can I pause Magic Spends or withdraw my invested amount?","Yes, you can pause Magic Spends anytime through the app. You can also withdraw the money you've invested and it will be credited instantly to your account.",Magic Spends
|
149 |
+
"Are there any fees or charges for Magic Spends?","No, Magic Spends is completely free to set up and Jupiter does not charge any commissions.",Magic Spends
|
150 |
+
"What can I do in the Money tab on Jupiter?","You can track your income, expenses, top categories, upcoming bills, and get smart insights on where your money goes — all in one place.",Money Tab
|
151 |
+
"How does Jupiter categorize my spending?","Jupiter auto-categorizes your transactions (like groceries, food, shopping, etc.) using smart tags so you can understand your spending patterns at a glance.",Money Tab
|
152 |
+
"Can I track my credit card expenses in the Money tab?","Yes, you can link your credit cards and view all your spends, dues, and statements in one place, even if they're from other banks.",Money Tab
|
153 |
+
"What is Account Aggregator and how does it work?","Account Aggregator (AA) is a secure framework regulated by the RBI that lets you link and track balances and payments from all your bank accounts. Rest assured, you stay in control and choose what to share and for how long.",Money Tab
|
154 |
+
"Is it safe to link my accounts through Account Aggregator?","Yes, it's completely safe. Jupiter only accesses your data with your consent via licensed Account Aggregators like Finvu, and cannot access it without your permission.",Money Tab
|
155 |
+
"Can I set Budgets in the Money tab?","Yes, you can set a monthly Budgets to control overspending and manage your money better every month. Jupiter lets you know when you're about to cross your monthly Budget so you can plan your expenses better!",Money Tab
|
156 |
+
"What is Net Worth in Jupiter and how is it calculated?","Your Net Worth on Jupiter is the difference between what you own and what you owe. It includes all your linked bank balances, investments (like FDs or mutual funds), and credit card dues or loans. When you link external accounts using Account Aggregator, we give you a real-time, 360° view of your true net worth in one simple number.",Money Tab
|
requirements.txt
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Core dependencies
|
2 |
+
flask
|
3 |
+
pandas
|
4 |
+
numpy
|
5 |
+
torch
|
6 |
+
sentence-transformers
|
7 |
+
transformers
|
8 |
+
scikit-learn
|
9 |
+
|
10 |
+
# Scraping dependencies
|
11 |
+
beautifulsoup4
|
12 |
+
requests
|
13 |
+
selenium
|
14 |
+
webdriver-manager
|
15 |
+
|
16 |
+
# NLP and utilities
|
17 |
+
nltk
|
18 |
+
python-dotenv
|
19 |
+
|
20 |
+
# Optional for notebook demo
|
21 |
+
jupyter
|
22 |
+
matplotlib
|
23 |
+
seaborn
|
run.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from app import app
|
2 |
+
|
3 |
+
if __name__ == '__main__':
|
4 |
+
print("Starting Flask application...")
|
5 |
+
app.run(debug=True, port=5000)
|