Sulaiman8 commited on
Commit
d407102
·
verified ·
1 Parent(s): 512633e

Update UI and expanded cards

Browse files
Files changed (1) hide show
  1. app.py +202 -237
app.py CHANGED
@@ -10,9 +10,10 @@ from sentence_transformers import CrossEncoder
10
  import gradio as gr
11
  import tempfile
12
  import re
 
13
 
14
  #for adding bank name to the cards in the graph
15
- eligibility_df = pd.read_csv("cards_eligibility(74 cards).csv")
16
  card_to_bank = dict(zip(eligibility_df['Name'], eligibility_df['Bank']))
17
 
18
  #neo4j credentials
@@ -37,7 +38,7 @@ def generate_cypher(user_query, query_intent, include_cobranded):
37
  cypher_prompt = f"""
38
  You are an expert Neo4j Cypher query generator.
39
 
40
- Given a user’s question, graph schema, and **contextual flags**, generate the correct Cypher query. The query should retrurn only the cards c.
41
 
42
  ONLY output the Cypher query. Do NOT explain anything.
43
 
@@ -49,6 +50,14 @@ def generate_cypher(user_query, query_intent, include_cobranded):
49
  - (Feature): Properties = name
50
  - Relationships:
51
  - (Card)-[:HAS_FEATURE]->(Feature)
 
 
 
 
 
 
 
 
52
 
53
  Valid values:
54
  - card_type: 'FD Card' or 'Regular'
@@ -58,31 +67,62 @@ def generate_cypher(user_query, query_intent, include_cobranded):
58
  MANDATORY Condition Rules:
59
  - If FD Card intent is true → include: `c.card_type = 'FD Card'`
60
  - Else → include: `c.card_type = 'Regular'`
 
61
  - If the query uses words like "premium", "elite", "luxury", "exclusive", "infinia", "black", etc. → include: `AND c.premium = true`
 
62
  - If include co-branded is false → include: `AND (c.co_branded IS NULL OR c.co_branded = false)`
63
  - These conditions are **MANDATORY**. If they apply, include them in the `WHERE` clause. Do not skip them.
64
 
65
  ---
66
 
67
  Available features:
68
- "Dining Benefits", "Railway Benefits", "Railway Lounge", "Airport Lounge Access", "Travel Benefits",
69
- "Fuel Surcharge Waiver", "Fuel Benefits", "General Cashback", "General Reward Points", "Movie Benefits",
70
- "Daily Spends (Grocery)", "Utility", "Insurance & Health Benefits", "Forex Markup Fee", "Hotel Benefits",
71
- "Welcome Bonus", "Air Miles", "No Forex Markup Fee", "Flight Discounts"
 
 
 
 
 
72
 
73
- Feature Inclusion Rules:
74
- - Only include relevant features based on user query.
75
- - Don’t add “General Cashback” or “General Reward Points” unless explicitly mentioned.
76
- - If fuel is mentioned, include both `Fuel Benefits` and `Fuel Surcharge Waiver`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  ---
79
 
80
  {context_note}
81
 
82
- User Query: {user_query}
83
  Cypher:
84
  """
85
 
 
 
86
  response = model3.generate_content(cypher_prompt.strip())
87
  cypher_code = response.text.strip()
88
 
@@ -95,7 +135,7 @@ def generate_cypher(user_query, query_intent, include_cobranded):
95
 
96
  return cypher_code
97
 
98
- # print(generate_cypher("Category: Beginner Credit Cards | Best credit cards for first-time users with low fees and easy approval requirements.", True,True))
99
 
100
  #generating embeddings (run only once)
101
  def chunk_text(text, chunk_size=1):
@@ -114,7 +154,7 @@ def get_gemini_embeddings(text_list):
114
  return np.vstack(embeddings)
115
 
116
  # Loading credit card data
117
- df = pd.read_csv("credit_card_data(74 cards).csv")
118
  card_descriptions = dict(zip(df["name"], df["description"]))
119
 
120
  # Chunk all card descriptions
@@ -162,83 +202,7 @@ def eligibility_filter(cards, user_income, user_cibil, user_age,min_joining_fee,
162
 
163
  return eligible_cards
164
 
165
- #selecting suitable labels for the query
166
- def classify_user_query(user_query, labels):
167
- genai.configure(api_key='AIzaSyAHoi9xbYAThtjXlyF_IKFtruoWYoUCjJQ')
168
- model3 = genai.GenerativeModel('gemini-1.5-flash-latest')
169
- print("classifying labels")
170
- prompt = f"""
171
- You are an assistant that classifies a user's credit card search query into predefined categories.
172
-
173
- ## Task:
174
- Given the user query, select the most relevant labels from the list below. Return only labels that are clearly applicable.
175
-
176
- ## Label List:
177
- {json.dumps(labels, indent=2)}
178
-
179
- ## Format:
180
- Return only JSON like this:
181
- {{ "labels": ["Label A", "Label B"] }}
182
-
183
- ## User Query:
184
- "{user_query}"
185
- """
186
- try:
187
- response = model3.generate_content(prompt)
188
- text = re.sub(r"^```(?:json)?\s*|\s*```$", "", response.text.strip(), flags=re.IGNORECASE)
189
- return json.loads(text).get("labels", [])
190
- except Exception as e:
191
- print("Error classifying user query:", e)
192
- return []
193
-
194
- labels_list = [
195
- "Best for International Travel",
196
- "Best for Domestic Travel",
197
- "Best for Vacation",
198
- "Best for International Vacation",
199
- "Best for Frequent Flyers",
200
- "Low/No Forex Markup Fee",
201
- "Best for Air Miles Accumulation",
202
- "Best for flight tickets discount",
203
- "Airport Lounge Access Priority",
204
- "Railway Lounge Access",
205
- "Hotel Benefits & Discounts",
206
- "Hotel Loyalty Program Integration",
207
- "Best for Dining Out / Fine Dining",
208
- "Best for Online Food Ordering",
209
- "Best for Bars, Pubs & Nightlife",
210
- "Dining + Movie Combo Offers",
211
- "Best for Lifestyle & Luxury Perks",
212
- "Best for Online Shoppers",
213
- "Best for Grocery Shopping",
214
- "Best for Utility Bill Payments",
215
- "Best for Fuel Spends",
216
- "Fuel + Grocery Combo",
217
- "Best for E-commerce Platforms",
218
- "Best for Everyday Spends (Household)",
219
- "Best for Cashback",
220
- "Flat Cashback Cards",
221
- "Best for Reward Points Accumulation",
222
- "Accelerated Rewards for Select Categories",
223
- "Rotating Category Reward Cards",
224
- "Includes Travel Insurance",
225
- "Includes Health / Life Insurance",
226
- "Purchase Protection & Extended Warranty",
227
- "Fraud Liability Coverage",
228
- "Concierge & Lifestyle Management Services",
229
- "Best Welcome Bonus",
230
- "Lifetime Free Card",
231
- "Low Annual Fee, High Value",
232
- "No Annual Fee",
233
- "Best for EMI Conversion",
234
- "Best for Air Miles",
235
- "Best for Reward Miles",
236
- "Best for Movie Ticket Discounts",
237
- "Best for Students",
238
- "Best for Beginners / First-time Users",
239
- "Best for Credit Score Building",
240
- "Best for Low Credit Score Applicants"
241
- ]
242
 
243
  #function for retrieving cards from knowledge graph
244
  class Neo4jConnectionError(Exception):
@@ -249,19 +213,10 @@ def run_cypher_query(user_query, query, use_eligibility, user_income, user_cibil
249
 
250
  try:
251
  with driver.session() as session:
252
- classified_labels = classify_user_query(user_query, labels_list)
253
- print("Classified Labels:", classified_labels)
254
 
255
  result = session.run(query)
256
  matched_cards = [record["c"] for record in result]
257
-
258
- if classified_labels:
259
- filtered_cards = [
260
- card["name"] for card in matched_cards
261
- if any(label in card.get("labels", []) for label in classified_labels)
262
- ]
263
- else:
264
- filtered_cards = [card["name"] for card in matched_cards]
265
 
266
  except Exception as e:
267
  raise Neo4jConnectionError("Failed to connect to the Neo4j database.") from e
@@ -380,18 +335,19 @@ def convert_to_direct_query_gradio(user_query,preferences):
380
  preferences_text = "User selected preferences: " + ", ".join(preferences) + "."
381
  # Prompt with examples for indirect-to-direct conversion
382
  prompt = f"""
383
- You are an AI assistant that refines user queries to make them optimized for information retrieval** while keeping the original intent.
384
 
385
  Instructions:
386
  - Identify the main focus from the query.
387
  - Reformat the query in a structured way for better retrieval.
388
  - Ensure the most important feature appears first.
389
  - Use precise keywords that match credit card benefits.
390
- - Include the preferences also in the final query.
391
  - Do NOT introduce new benefits not mentioned by the user.
392
- - If the user has mentioned a vague term try to put it in more direct words covering it's benefits
393
- - Even if the query is empty use the preferences to create a query.
394
-
 
395
  Examples:
396
 
397
  Example 1
@@ -408,10 +364,10 @@ def convert_to_direct_query_gradio(user_query,preferences):
408
 
409
  Now, optimize the following preferences and user query:
410
 
411
-
412
  Preferences: "{preferences_text}"
413
  User Query: "{user_query}"
414
  """
 
415
  print("rewriting")
416
  response = model2.generate_content(prompt)
417
 
@@ -530,7 +486,7 @@ def compare_selected_cards(selected_names, card_lookup):
530
  return "<b style='color:red;'>Something went wrong while comparing. Please try again.</b>"
531
 
532
  # Loading all 55 cards for comparison feature
533
- df_all_cards = pd.read_csv("credit_card_data(74 cards).csv")
534
  all_card_names = df_all_cards["name"].tolist()
535
  all_card_lookup = dict(zip(df_all_cards["name"], df_all_cards["description"]))
536
 
@@ -552,6 +508,7 @@ def find_intent(user_query: str) -> bool:
552
  response = model2.generate_content(prompt)
553
  result = response.text.strip().lower()
554
  return result == "true"
 
555
 
556
 
557
  #for queries enquiring about a card
@@ -576,23 +533,23 @@ def handle_query_classification(user_query):
576
  genai.configure(api_key='AIzaSyDGc7l82E1sa4GarTfGiTXC6dLD7Crk8oo')
577
  model1 = genai.GenerativeModel('gemini-1.5-flash-latest')
578
  prompt = f"""
579
- You are a smart financial assistant.
580
-
581
- ### User's Query:
582
- {user_query}
583
-
584
- ### Task:
585
- Classify the user's intent into one of the following categories:
586
- 1. "retrieve" → If the user is asking for card suggestions, recommendations, or showing cards (e.g., "suggest a card", "need a travel card").
587
- 2. "specific" → If the user is asking about a particular card by name, the card names need not end with "card" keyword, so also consider that (e.g., "Tell me about HDFC Regalia", "Is SBI Elite good?").
588
- 3. "no_retrieval" → If the query is generic, chit-chat, or doesn't need credit card data lookup (e.g., "What is interest in credit cards?").
589
-
590
- Respond in the following JSON format:
591
- {{
592
- "intent": "retrieve" | "specific" | "no_retrieval",
593
- "response": "Only include this if intent is 'no_retrieval'"
594
- }}
595
- """
596
 
597
  raw_response = model1.generate_content(prompt).text.strip()
598
 
@@ -610,6 +567,8 @@ def handle_query_classification(user_query):
610
  print("Raw response from LLM:", raw_response)
611
  raise
612
 
 
 
613
  #passing the card mentioned in the user query
614
  def find_matching_card(user_query):
615
  lowered_query = user_query.lower()
@@ -617,7 +576,14 @@ def find_matching_card(user_query):
617
  if row["name"].lower() in lowered_query:
618
  return row.to_dict()
619
  return None
 
 
 
620
 
 
 
 
 
621
 
622
  #function to pass the retrieved cards and generated response to the UI
623
  def recommend_cards_gradio(user_query, preferences, income, cibil, age,
@@ -710,18 +676,24 @@ def recommend_cards_gradio(user_query, preferences, income, cibil, age,
710
  "No eligible card found"
711
  )
712
 
713
- card_rows = [
714
- [card["name"], card["description"]]
715
- for score, card in sorted(
716
- zip(cross_encoder.predict([[direct_query, card["description"]] for card in cards]), cards),
717
- reverse=True,
718
- key=lambda x: x[0]
719
- )
720
- ]
721
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722
  card_names = [row[0] for row in card_rows]
723
  card_lookup = {row[0]: row[1] for row in card_rows}
724
-
725
  top_card_html = f"""
726
  <div style="
727
  background-color: #fff3e0;
@@ -738,7 +710,7 @@ def recommend_cards_gradio(user_query, preferences, income, cibil, age,
738
  </div>
739
  """
740
 
741
- df_cards = pd.DataFrame(card_rows, columns=["Card Name", "Description"])
742
  temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
743
  df_cards.to_csv(temp_file.name, index=False)
744
 
@@ -754,6 +726,7 @@ def recommend_cards_gradio(user_query, preferences, income, cibil, age,
754
  {},
755
  "Unexpected error occurred, please try again in a while"
756
  )
 
757
 
758
  #function for the chatbot functionality
759
  eligibility_lookup = {}
@@ -819,122 +792,120 @@ def chat_with_gemini(user_query, user_message, chat_history, card_lookup):
819
 
820
  return chat_history, chat_history
821
 
822
- #Interface
823
  with gr.Blocks() as demo:
824
  gr.Markdown("# Credit Card Recommender")
825
  gr.Markdown("Get personalized credit card suggestions based on your lifestyle and eligibility.")
826
-
827
- #Input fields for user query and preferences
828
- with gr.Row():
829
- user_query = gr.Textbox(
830
- label="Enter your query",
831
- info="E.g., 'Best cards for international travel' or 'I want cashback cards with lounge access'"
832
- )
833
- preferences = gr.CheckboxGroup(
834
- choices=["Cashback", "Travel Rewards", "Fuel Benefits", "International Lounge access",
835
- "Domestic Lounge access", "Railway benefits", "Dining", "Shopping"],
836
- label="Credit card categories:",
837
- info="Select the features or benefits you want from your credit card"
838
- )
839
 
840
- with gr.Accordion("Eligibility filters menu", open=False):
841
- with gr.Row():
842
- income = gr.Slider(
843
- minimum=1, maximum=60, step=1,
844
- label="Annual Income (LPA) Minimum requirement is 2.5",
845
- info="Helps filter cards based on your income eligibility (in Lakhs Per Annum)"
846
- )
847
- cibil = gr.Slider(
848
- minimum=300, maximum=900, step=10,
849
- label="CIBIL Score",
850
- info="Most of the cards requires a credit score of 700+"
851
- )
852
- age = gr.Slider(
853
- minimum=18, maximum=75, step=1,
854
- label="Age",
855
- info="Some cards have minimum and maximum age eligibility"
856
- )
857
-
858
- with gr.Row():
859
- min_joining_fee = gr.Number(
860
- label="Min Joining Fee (₹)", value=0,
861
- info="Minimum one-time fee to get the card"
862
- )
863
- max_joining_fee = gr.Number(
864
- label="Max Joining Fee (₹)", value=150000,
865
- info="Maximum one-time fee to get the card"
866
- )
867
-
868
- with gr.Row():
869
- min_annual_fee = gr.Number(
870
- label="Min Annual Fee (₹)", value=0,
871
- info="Minimum yearly fee to be paid"
872
- )
873
- max_annual_fee = gr.Number(
874
- label="Max Annual Fee (₹)", value=150000,
875
- info="Maximum yearly fee to be paid"
876
- )
877
 
878
- # Toggle for eligibility filter and co-branded cards
879
- with gr.Row():
880
- use_eligibility = gr.Checkbox(
881
- label="Apply Eligibility Filter", value=False,
882
- info="Enable this to get recommendations of the cards only for which you are eligible for"
883
- )
884
- include_cobranded = gr.Checkbox(
885
- label="Include Co-branded Cards", value=True,
886
- info="Include cards that are co-branded with airlines, retailers, etc."
887
- )
 
 
 
 
 
 
 
888
 
889
- submit_btn = gr.Button("Recommend Cards", variant='primary')
 
 
 
 
 
 
 
 
890
 
891
- # Outputs
892
- top_card_html = gr.HTML()
893
- card_df = gr.Dataframe(headers=["Card Name", "Description"])
894
- card_file = gr.File(label="Download Full Recommendations (CSV)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
895
 
896
- # States for recommendations and chatbot
897
  card_names_state = gr.State()
898
  card_lookup_state = gr.State()
899
  chat_history = gr.State([])
900
- query=gr.State([])
901
-
902
- # Recommended cards comparison
903
- gr.Markdown("### Compare Recommended Cards")
904
- compare_checkboxes = gr.CheckboxGroup(
905
- choices=[], label="Select 2 or more cards to compare",
906
- info="Pick 2+ cards from the recommended list to see a comparison"
907
- )
908
- compare_btn = gr.Button("Compare Selected Cards", variant='primary')
909
- compare_output = gr.HTML()
910
-
911
- # Full card list comparison
912
- gr.Markdown("### Compare Any Cards from Full List")
913
- full_compare_dropdown = gr.Dropdown(
914
- choices=all_card_names, multiselect=True, label="Select any 2+ cards",
915
- info="Manually compare any cards from the full database"
916
- )
917
- full_compare_btn = gr.Button("Compare Selected Cards", variant='primary')
918
- full_compare_output = gr.HTML()
919
-
920
- # Chatbot UI for follow-up questions
921
- gr.Markdown("### Ask any follow-up question ")
922
- chatbot = gr.Chatbot(type='messages')
923
 
924
- # Function for passing inputs and generating the results
925
  def wrapped_recommend_cards(user_query, preferences, income, cibil, age, min_joining_fee, max_joining_fee,
926
  min_annual_fee, max_annual_fee, use_eligibility,include_cobranded):
927
  top_html, df, file, card_names, card_lookup, direct_query = recommend_cards_gradio(
928
  user_query, preferences, income, cibil, age, min_joining_fee, max_joining_fee,
929
  min_annual_fee, max_annual_fee, use_eligibility,include_cobranded
930
- )
931
-
932
  df_label = f"Found {len(card_names)} cards"
933
-
934
  return top_html, gr.update(value=df, label=df_label), file, card_names, card_lookup, gr.update(choices=card_names, value=[]), direct_query
935
 
936
-
937
- # Button actions and UI interactions
938
  submit_btn.click(
939
  fn=wrapped_recommend_cards,
940
  inputs=[user_query, preferences, income, cibil, age, min_joining_fee, max_joining_fee,
@@ -954,17 +925,11 @@ with gr.Blocks() as demo:
954
  outputs=full_compare_output
955
  )
956
 
957
- # Chatbot function for handling follow-up queries
958
- user_query_for_chat = gr.Textbox(
959
- label="Enter your question",
960
- info="Ask follow-ups like 'Which card has better travel insurance?' or 'Which card has less annual fee'"
961
- )
962
- submit_query_btn = gr.Button("Submit Query", variant='primary')
963
-
964
  submit_query_btn.click(
965
  fn=chat_with_gemini,
966
  inputs=[query,user_query_for_chat, chat_history, card_lookup_state],
967
  outputs=[chatbot, chat_history]
968
  )
969
 
 
970
  demo.launch(share=True)
 
10
  import gradio as gr
11
  import tempfile
12
  import re
13
+ import ast
14
 
15
  #for adding bank name to the cards in the graph
16
+ eligibility_df = pd.read_csv("cards_eligibility_updated.csv")
17
  card_to_bank = dict(zip(eligibility_df['Name'], eligibility_df['Bank']))
18
 
19
  #neo4j credentials
 
38
  cypher_prompt = f"""
39
  You are an expert Neo4j Cypher query generator.
40
 
41
+ Given a user’s question, graph schema, and **contextual flags**, generate the correct Cypher query. The query should return only the cards `c`.
42
 
43
  ONLY output the Cypher query. Do NOT explain anything.
44
 
 
50
  - (Feature): Properties = name
51
  - Relationships:
52
  - (Card)-[:HAS_FEATURE]->(Feature)
53
+
54
+ Feature Inclusion Rules:
55
+ - Only include relevant features based on user query.
56
+ - Forex markup fee and foreign transaction fee are the same.
57
+ - Don’t add “General Cashback” or “General Reward Points” unless explicitly mentioned.
58
+ - If fuel is mentioned, include both `Fuel Benefits` and `Fuel Surcharge Waiver`.
59
+ - **ALWAYS** match features using: `f.name IN [...]` — even if there is only **one** feature.
60
+
61
 
62
  Valid values:
63
  - card_type: 'FD Card' or 'Regular'
 
67
  MANDATORY Condition Rules:
68
  - If FD Card intent is true → include: `c.card_type = 'FD Card'`
69
  - Else → include: `c.card_type = 'Regular'`
70
+ - If the query is based on beginners or students or people with no or low credit history then use FD Card.
71
  - If the query uses words like "premium", "elite", "luxury", "exclusive", "infinia", "black", etc. → include: `AND c.premium = true`
72
+ - If the query includes low spending, without high spending or budget → include: `(c.premium IS NULL OR c.premium = false)`
73
  - If include co-branded is false → include: `AND (c.co_branded IS NULL OR c.co_branded = false)`
74
  - These conditions are **MANDATORY**. If they apply, include them in the `WHERE` clause. Do not skip them.
75
 
76
  ---
77
 
78
  Available features:
79
+ "General Cashback", "Fuel Surcharge Waiver", "Fuel Benefits", "Welcome Bonus",
80
+ "Airport Lounge Access", "General Reward Points", "Domestic Travel Benefits",
81
+ "Movie Benefits", "Flight Discounts", "International Travel Benefits",
82
+ "Hotel Benefits", "Dining Benefits", "Daily Spends (Grocery)", "Railway Benefits",
83
+ "Travel Benefits", "Railway Lounge", "Insurance", "Utility",
84
+ "E-commerce Platform Benefits", "Air Miles", "Spa Access Benefits",
85
+ "Lifestyle & Luxury Perks", "Golf Access & Perks", "Online Shopping Benefits",
86
+ "UPI Transaction Support", "Health Benefits", "EMI Conversion Options",
87
+ "No Forex Markup Fee", "Roadside Assistance", "Rupay Network Support"
88
 
89
+ ---
90
+
91
+ Few-shot Examples:
92
+
93
+ User Query: Show premium cards with airport lounge access
94
+ Cypher:
95
+ MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
96
+ WHERE f.name IN ["Airport Lounge Access"]
97
+ AND c.card_type = 'Regular'
98
+ AND c.premium = true
99
+ RETURN c
100
+
101
+ User Query: I want FD cards with spa access and golf perks
102
+ Cypher:
103
+ MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
104
+ WHERE f.name IN ["Spa Access Benefits", "Golf Access & Perks"]
105
+ AND c.card_type = 'FD Card'
106
+ RETURN c
107
+
108
+ User Query: Cards that support UPI but are not co-branded
109
+ Cypher:
110
+ MATCH (c:Card)-[:HAS_FEATURE]->(f:Feature)
111
+ WHERE f.name IN ["UPI Transaction Support"]
112
+ AND c.card_type = 'Regular'
113
+ AND (c.co_branded IS NULL OR c.co_branded = false)
114
+ RETURN c
115
 
116
  ---
117
 
118
  {context_note}
119
 
120
+ User Query: {user_query}
121
  Cypher:
122
  """
123
 
124
+
125
+
126
  response = model3.generate_content(cypher_prompt.strip())
127
  cypher_code = response.text.strip()
128
 
 
135
 
136
  return cypher_code
137
 
138
+ # print(generate_cypher("I shop a lot, both online and in stores, and I want a credit card that gives me good cashback on my purchases. Which card would be the best for saving money? I need a budget cxard", True,False))
139
 
140
  #generating embeddings (run only once)
141
  def chunk_text(text, chunk_size=1):
 
154
  return np.vstack(embeddings)
155
 
156
  # Loading credit card data
157
+ df = pd.read_csv("credit_card_data_updated.csv")
158
  card_descriptions = dict(zip(df["name"], df["description"]))
159
 
160
  # Chunk all card descriptions
 
202
 
203
  return eligible_cards
204
 
205
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  #function for retrieving cards from knowledge graph
208
  class Neo4jConnectionError(Exception):
 
213
 
214
  try:
215
  with driver.session() as session:
 
 
216
 
217
  result = session.run(query)
218
  matched_cards = [record["c"] for record in result]
219
+ filtered_cards = [card["name"] for card in matched_cards]
 
 
 
 
 
 
 
220
 
221
  except Exception as e:
222
  raise Neo4jConnectionError("Failed to connect to the Neo4j database.") from e
 
335
  preferences_text = "User selected preferences: " + ", ".join(preferences) + "."
336
  # Prompt with examples for indirect-to-direct conversion
337
  prompt = f"""
338
+ You are an AI assistant that refines user queries to make them optimized for information retrieval while keeping the original intent.
339
 
340
  Instructions:
341
  - Identify the main focus from the query.
342
  - Reformat the query in a structured way for better retrieval.
343
  - Ensure the most important feature appears first.
344
  - Use precise keywords that match credit card benefits.
345
+ - Include the preferences also in the final query.
346
  - Do NOT introduce new benefits not mentioned by the user.
347
+ - If the user uses vague terms like "vacation", interpret it as travel-related benefits including: airport lounge access, international/domestic travel, hotel benefits, forex waiver.
348
+ - If the query includes terms like "beginner", "entry-level", or "low credit score", include essential features such as cashback, reward points, and basic offers.
349
+ - If the query is empty, generate a useful retrieval-focused query based solely on preferences.
350
+
351
  Examples:
352
 
353
  Example 1
 
364
 
365
  Now, optimize the following preferences and user query:
366
 
 
367
  Preferences: "{preferences_text}"
368
  User Query: "{user_query}"
369
  """
370
+
371
  print("rewriting")
372
  response = model2.generate_content(prompt)
373
 
 
486
  return "<b style='color:red;'>Something went wrong while comparing. Please try again.</b>"
487
 
488
  # Loading all 55 cards for comparison feature
489
+ df_all_cards = pd.read_csv("credit_card_data_updated.csv")
490
  all_card_names = df_all_cards["name"].tolist()
491
  all_card_lookup = dict(zip(df_all_cards["name"], df_all_cards["description"]))
492
 
 
508
  response = model2.generate_content(prompt)
509
  result = response.text.strip().lower()
510
  return result == "true"
511
+ # print(find_intent("im a beginner to credit cards suggest me suitable cards"))
512
 
513
 
514
  #for queries enquiring about a card
 
533
  genai.configure(api_key='AIzaSyDGc7l82E1sa4GarTfGiTXC6dLD7Crk8oo')
534
  model1 = genai.GenerativeModel('gemini-1.5-flash-latest')
535
  prompt = f"""
536
+ You are a smart financial assistant.
537
+
538
+ ### User's Query:
539
+ {user_query}
540
+
541
+ ### Task:
542
+ Classify the user's intent into one of the following categories:
543
+ 1. "retrieve" → If the user is asking for card suggestions, recommendations, or showing cards (e.g., "suggest a card", "need a travel card").
544
+ 2. "specific" → If the user is asking about a particular card by name, the card names need not end with "card" keyword, so also consider that (e.g., "Tell me about HDFC Regalia", "Is SBI Elite good?").
545
+ 3. "no_retrieval" → If the query is generic, chit-chat, or doesn't need credit card data lookup (e.g., "What is interest in credit cards?").
546
+
547
+ Respond in the following JSON format:
548
+ {{
549
+ "intent": "retrieve" | "specific" | "no_retrieval",
550
+ "response": "Only include this if intent is 'no_retrieval'"
551
+ }}
552
+ """
553
 
554
  raw_response = model1.generate_content(prompt).text.strip()
555
 
 
567
  print("Raw response from LLM:", raw_response)
568
  raise
569
 
570
+ # print(handle_query_classification("what is a credit card"))
571
+
572
  #passing the card mentioned in the user query
573
  def find_matching_card(user_query):
574
  lowered_query = user_query.lower()
 
576
  if row["name"].lower() in lowered_query:
577
  return row.to_dict()
578
  return None
579
+
580
+ with open('for_graph_construction_(expanded labels).json') as f:
581
+ card_feature_data = json.load(f)
582
 
583
+ card_features_lookup = {
584
+ card['card_name']: set(card['features'])
585
+ for card in card_feature_data
586
+ }
587
 
588
  #function to pass the retrieved cards and generated response to the UI
589
  def recommend_cards_gradio(user_query, preferences, income, cibil, age,
 
676
  "No eligible card found"
677
  )
678
 
679
+ match = re.search(r"f\.name IN (\[.*?\])", cypher_query)
680
+ query_features = set(ast.literal_eval(match.group(1))) if match else set()
 
 
 
 
 
 
681
 
682
+ card_rows = []
683
+ for score, card in sorted(
684
+ zip(cross_encoder.predict([[direct_query, card["description"]] for card in cards]), cards),
685
+ reverse=True,
686
+ key=lambda x: x[0]
687
+ ):
688
+ card_name = card["name"]
689
+ card_desc = card["description"]
690
+ matched_features = query_features.intersection(card_features_lookup.get(card_name, set()))
691
+ feature_str = ", ".join(matched_features) if matched_features else "None"
692
+ card_rows.append([card_name, feature_str, card_desc])
693
+
694
+
695
  card_names = [row[0] for row in card_rows]
696
  card_lookup = {row[0]: row[1] for row in card_rows}
 
697
  top_card_html = f"""
698
  <div style="
699
  background-color: #fff3e0;
 
710
  </div>
711
  """
712
 
713
+ df_cards = pd.DataFrame(card_rows, columns=["Card Name", "Matched Features", "Description"])
714
  temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".csv")
715
  df_cards.to_csv(temp_file.name, index=False)
716
 
 
726
  {},
727
  "Unexpected error occurred, please try again in a while"
728
  )
729
+
730
 
731
  #function for the chatbot functionality
732
  eligibility_lookup = {}
 
792
 
793
  return chat_history, chat_history
794
 
795
+ # Interface with Tabs
796
  with gr.Blocks() as demo:
797
  gr.Markdown("# Credit Card Recommender")
798
  gr.Markdown("Get personalized credit card suggestions based on your lifestyle and eligibility.")
799
+
800
+ with gr.Tabs():
 
 
 
 
 
 
 
 
 
 
 
801
 
802
+ with gr.Tab("🧠 Get Recommendations"):
803
+ with gr.Row():
804
+ user_query = gr.Textbox(
805
+ label="Enter your query",
806
+ info="E.g., 'Best cards for international travel' or 'I want cashback cards with lounge access'"
807
+ )
808
+ preferences = gr.CheckboxGroup(
809
+ choices=["Cashback", "Travel Rewards", "Fuel Benefits", "International Lounge access",
810
+ "Domestic Lounge access", "Railway benefits", "Dining", "Shopping"],
811
+ label="Credit card categories:",
812
+ info="Select the features or benefits you want from your credit card"
813
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
 
815
+ with gr.Accordion("Eligibility filters menu", open=False):
816
+ with gr.Row():
817
+ income = gr.Slider(
818
+ minimum=1, maximum=60, step=1,
819
+ label="Annual Income (LPA) Minimum requirement is 2.5",
820
+ info="Helps filter cards based on your income eligibility (in Lakhs Per Annum)"
821
+ )
822
+ cibil = gr.Slider(
823
+ minimum=300, maximum=900, step=10,
824
+ label="CIBIL Score",
825
+ info="Most of the cards requires a credit score of 700+"
826
+ )
827
+ age = gr.Slider(
828
+ minimum=18, maximum=75, step=1,
829
+ label="Age",
830
+ info="Some cards have minimum and maximum age eligibility"
831
+ )
832
 
833
+ with gr.Row():
834
+ min_joining_fee = gr.Number(
835
+ label="Min Joining Fee (₹)", value=0,
836
+ info="Minimum one-time fee to get the card"
837
+ )
838
+ max_joining_fee = gr.Number(
839
+ label="Max Joining Fee (₹)", value=150000,
840
+ info="Maximum one-time fee to get the card"
841
+ )
842
 
843
+ with gr.Row():
844
+ min_annual_fee = gr.Number(
845
+ label="Min Annual Fee (₹)", value=0,
846
+ info="Minimum yearly fee to be paid"
847
+ )
848
+ max_annual_fee = gr.Number(
849
+ label="Max Annual Fee (₹)", value=150000,
850
+ info="Maximum yearly fee to be paid"
851
+ )
852
+
853
+ with gr.Row():
854
+ use_eligibility = gr.Checkbox(
855
+ label="Apply Eligibility Filter", value=False,
856
+ info="Enable this to get recommendations of the cards only for which you are eligible for"
857
+ )
858
+ include_cobranded = gr.Checkbox(
859
+ label="Include Co-branded Cards", value=True,
860
+ info="Include cards that are co-branded with airlines, retailers, etc."
861
+ )
862
+
863
+ submit_btn = gr.Button("Recommend Cards", variant='primary')
864
+
865
+ top_card_html = gr.HTML()
866
+ card_df = gr.Dataframe(headers=["Card Name", "Matched Features", "Description"])
867
+ card_file = gr.File(label="Download Full Recommendations (CSV)")
868
+
869
+ with gr.Tab("🔍 Compare Cards"):
870
+ gr.Markdown("### Compare Recommended Cards")
871
+ compare_checkboxes = gr.CheckboxGroup(
872
+ choices=[], label="Select 2 or more cards to compare",
873
+ info="Pick 2+ cards from the recommended list to see a comparison"
874
+ )
875
+ compare_btn = gr.Button("Compare Selected Cards", variant='primary')
876
+ compare_output = gr.HTML()
877
+
878
+ gr.Markdown("### Compare Any Cards from Full List")
879
+ full_compare_dropdown = gr.Dropdown(
880
+ choices=all_card_names, multiselect=True, label="Select any 2+ cards",
881
+ info="Manually compare any cards from the full database"
882
+ )
883
+ full_compare_btn = gr.Button("Compare Selected Cards", variant='primary')
884
+ full_compare_output = gr.HTML()
885
+
886
+ with gr.Tab("💬 Ask Follow-up Questions"):
887
+ gr.Markdown("### Ask any follow-up question ")
888
+ chatbot = gr.Chatbot(type='messages')
889
+ user_query_for_chat = gr.Textbox(
890
+ label="Enter your question",
891
+ info="Ask follow-ups like 'Which card has better travel insurance?' or 'Which card has less annual fee'"
892
+ )
893
+ submit_query_btn = gr.Button("Submit Query", variant='primary')
894
 
 
895
  card_names_state = gr.State()
896
  card_lookup_state = gr.State()
897
  chat_history = gr.State([])
898
+ query = gr.State([])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
 
 
900
  def wrapped_recommend_cards(user_query, preferences, income, cibil, age, min_joining_fee, max_joining_fee,
901
  min_annual_fee, max_annual_fee, use_eligibility,include_cobranded):
902
  top_html, df, file, card_names, card_lookup, direct_query = recommend_cards_gradio(
903
  user_query, preferences, income, cibil, age, min_joining_fee, max_joining_fee,
904
  min_annual_fee, max_annual_fee, use_eligibility,include_cobranded
905
+ )
 
906
  df_label = f"Found {len(card_names)} cards"
 
907
  return top_html, gr.update(value=df, label=df_label), file, card_names, card_lookup, gr.update(choices=card_names, value=[]), direct_query
908
 
 
 
909
  submit_btn.click(
910
  fn=wrapped_recommend_cards,
911
  inputs=[user_query, preferences, income, cibil, age, min_joining_fee, max_joining_fee,
 
925
  outputs=full_compare_output
926
  )
927
 
 
 
 
 
 
 
 
928
  submit_query_btn.click(
929
  fn=chat_with_gemini,
930
  inputs=[query,user_query_for_chat, chat_history, card_lookup_state],
931
  outputs=[chatbot, chat_history]
932
  )
933
 
934
+
935
  demo.launch(share=True)