
    ɫxiG                         d Z ddlZddlZddlZddlmZ ddlmZmZm	Z	m
Z
 	 ddlmZ  G d d      Z G d d      Zy# e$ r dZY w xY w)	z
Credit Card Recommender Engine
Provides grounded recommendations based on JSON card data.

Optionally uses Gemini (via google-generativeai) to generate more natural
chat responses, while still grounding all facts in the JSON data.
    N)Path)DictListOptionalTuplec                       e Zd ZddefdZededefd       Zededee   fd       Z	ededefd       Z
ded	edefd
Zded	edefdZded	edee   fdZd	edefdZy)CardRecommendercards_json_pathc                     t        |dd      5 }t        j                  |      | _        ddd       | j                  j	                  dg       | _        y# 1 sw Y   +xY w)z%Initialize recommender with card datarzutf-8)encodingNcards)openjsonloaddatagetr   )selfr
   fs      5/var/www/html/chatbot/card-advisor-bot/recommender.py__init__zCardRecommender.__init__   sJ    /39 	%Q		!DI	%YY]]7B/
	% 	%s   AAtextreturnc                 F    | xs dj                         j                         S )zNormalize text for comparison )lowerstrip)r   s    r   normalize_textzCardRecommender.normalize_text   s      
!!#))++    c                 N   t         j                  |       j                  dd      j                  dd      }t        j                  d|      }|r&t        t        |j                  d            dz        S t        j                  d|      }|rt        |j                  d            S y)	u   
        Extract monthly income from text
        Accepts: "1.5L", "150000", "2 lakh", "2l", "₹2,00,000", "2.5 lakhs"
        , u   ₹z+(\d+(?:\.\d+)?)\s*(lakh|lakhs|lac|lacs|l)\b   i z(\d{2,})N)	r	   r   replaceresearchroundfloatgroupint)r   t
lakh_match	num_matchs       r   extract_income_inrz"CardRecommender.extract_income_inr!   s     **4088cBJJ5RUV YYMqQ
z//23f<== IIk1-	yq)**r   c                 ,   t         j                  |       t        fddD              ryt        fddD              ryt        fddD              ryt        fd	d
D              ryt        fddD              ryt        fddD              ryy)z2Detect user's primary card goal from their messagec              3   &   K   | ]  }|v  
 y wN .0wordr+   s     r   	<genexpr>z.CardRecommender.detect_goal.<locals>.<genexpr>;   s     ETtqyE   )cashbackz	cash backcbr8   c              3   &   K   | ]  }|v  
 y wr1   r2   r3   s     r   r6   z.CardRecommender.detect_goal.<locals>.<genexpr>>   s     VTtqyVr7   )travelflighthotelmileslounger;   c              3   &   K   | ]  }|v  
 y wr1   r2   r3   s     r   r6   z.CardRecommender.detect_goal.<locals>.<genexpr>A   s     PTtqyPr7   )forexzinternational spendmarkupc              3   &   K   | ]  }|v  
 y wr1   r2   r3   s     r   r6   z.CardRecommender.detect_goal.<locals>.<genexpr>D   s     KTtqyKr7   )tataneu	bigbasketcromarD   c              3   &   K   | ]  }|v  
 y wr1   r2   r3   s     r   r6   z.CardRecommender.detect_goal.<locals>.<genexpr>G   s     @Ttqy@r7   )upirupayzru payrJ   c              3   &   K   | ]  }|v  
 y wr1   r2   r3   s     r   r6   z.CardRecommender.detect_goal.<locals>.<genexpr>K   s     FTtqyFr7   )best	recommendsuggestunknown)r	   r   any)r   r+   s    @r   detect_goalzCardRecommender.detect_goal5   s     **40 E%DEEV%UVVP%OPPK%JKK@%?@@ F%EFFr   carduserc                     |j                  d      }t        |t        t        f      r=|j                  di       j                  d      }t        |t        t        f      r||k  ryy)z9Check if user meets card eligibility (only checks income)monthlyIncomeeligibilitymin_monthly_incomeFT)r   
isinstancer*   r(   )r   rR   rS   monthly_income
min_incomes        r   card_eligibility_passz%CardRecommender.card_eligibility_passP   sU    /2nsEl3-4889MNJ*sEl38Sr   c                    d}|j                  dd      }|j                  dd      }|j                  di       j                  d      }|j                  di       j                  d	d
      }|j                  di       j                  di       j                  d	d
      }|j                  di       j                  di       j                  d	d
      }	|j                  di       j                  d      }
|dk(  r |dk(  s|r|dz  }|dk  r|dz  }|dv r|dz  }n|dk(  rM|dv r|dz  }|r|dz  }|	r|dz  }t        |
t        t        f      r|t	        dd|
dz  z
        z  }|dkD  r|dz  }n|dk(  r0d| j                  |j                  dd            v r|dz  }|rX|dz  }nR|dk(  r0d| j                  |j                  dd            v r|dz  }|r#|dz  }n|s|dk(  r|d z  }|dk  r|dz  }|r|d!z  }|j                  d"      }t        |t        t        f      rR|j                  d#i       j                  d$      }t        |t        t        f      r||z  }|d%k\  r|d&z  }|S |d'k\  r|dz  }|S )(z&Score a card based on user preferencesg        goalrO   
annual_feer   reward_modeltypecashback_benefitsincludedFtravel_benefitsdomestic_loungeinternational_loungeforex_markup_percentr8   <   i     )pointsr>   
   r;   )r>   ri   #      '     rD   namer   rJ   network      rU   rV   rW   g      ?   g      ?)r   rX   r*   r(   maxr   )r   rR   rS   scorer]   r^   reward_typehas_cashback
lounge_domlounge_intlrA   rY   rZ   ratios                 r   
score_cardzCardRecommender.score_cardY   s   xx	*XXlA.
hh~r266v>xx 3R8<<ZOXX/4889JBOSST^`ef
hh0"599:PRTUYYZdfkl*B/334JK :j(LT!11X11%#u.QUQY//E!
V^,,TXXfb-ABBW_$--dhhy".EFF {j8T!
 /2nsEl3-4889MNJ*sEl3&3C<QJE  c\QJEr   c                     g }|j                  dd      }|j                  di       xs i }|j                  di       xs i }|j                  di       xs i }|j                  dg       xs g }|j                  dd      xs d}	|j                  d	i       xs i }
|j                  d
      }|dk(  rz|j                  dg       xs g }|r-t        |d       }|j                  |d    d|d    d       nT|j                  di       j                  d      r2|j                  d       n|dk(  r|j                  di       j                  d      }|j                  di       j                  d      }|r|j                  d       |r|j                  d       |j                  d      }t        |t        t
        f      r|j                  d| d       nu|dk(  rZ|j                  dg       xs g }t         fd|D        d       }|rC|j                  d!d"      }|j                  |d    d#| d$       n|d%k(  r|j                  d&       |j                  d'      }|r|j                  d(|        |j                  d)|	d*       |
j                  d'      d+k(  rQ|
j                  d,      }|
j                  d-d.      }t        |t        t
        f      r|j                  d/|d*d0|        |r|j                  d1|d*d2       |r&|j                  d3d4j                  |d d5               t               }g }|D ])  }||vs|j                  |       |j                  |       + |d d6 S )7z
        Build 3-6 grounded reasons for card recommendation.
        These are short factual snippets that the LLM can expand on.
        r]   rO   r_   rc   rV   best_forr^   r   
fee_waiverrW   r8   cashback_rulesc                 &    | j                  dd      S )Nrate_percentr   )r   )r   s    r   <lambda>z/CardRecommender.build_reasons.<locals>.<lambda>   s    QUU>15M r   )keyr   z% cashback on categoryz spendsra   rb   z+Cashback-focused rewards on everyday spendsr;   rd   re   z"Access to domestic airport loungesz'Access to international airport loungesrf   zForex markup around z% on international spendsrD   c              3   j   K   | ]*  }d j                  |j                  dd            v s'| , yw)rD   r   r   N)r   r   )r4   r   r   s     r   r6   z0CardRecommender.build_reasons.<locals>.<genexpr>   s/     ZqVt/B/B155UWCX/Y%YZs   (33Ncurrency_namerewardsz
% back in z on Tata brandsrJ   z2RuPay network (UPI-ready if enabled by the issuer)r`   zRewards structure focused on u   Annual fee around ₹r!   spend_based	thresholdperiodyearu/   Annual fee can be waived on spends of about ₹z per u"   Designed for income from about ₹z
 per monthzWorks well for: z,    rs   )
r   rt   appendrX   r*   r(   nextjoinsetadd)r   rR   rS   reasonsr]   r_   r;   rV   r}   r^   r~   rZ   rules	best_rulerx   ry   rA   	tata_rulecurrencyrv   r   r   seenunique_reasonsr   s   `                        r   build_reasonszCardRecommender.build_reasons   sg   
  xx	*xx39r+R06Bhh}b17R88J+1rXXlA.3!
XXlB/52
 __%9:
 : $$%5r:@bE+MN	)N";!<N9U_K`Jaahij-r266zBLMX$5r:>>zJJ **%;R@DDZPKCDHIJJ56E%#u.!5eW<UVWV^ $$%5r:@bEZEZI '++OYG)N";!<JxjP_`aW_NNOP #&&v.NN:;-HI.z!n=>>>&!]2"{3I^^Hf5F)c5\2!PQZ[\P]]bcibjklNN?
1~ZXYNN-dii!.E-FGH u$& 	)A}%%a(	) bq!!r   c                 x   | j                   D cg c]  }| j                  ||      s| }}|r|n| j                   }d}t        d      }|D ]  }| j                  ||      }||kD  s|}|}  | j	                  ||      }	d}
|j                  d      }|st        |t        t        f      rd|dd}
||	|
dS c c}w )z`
        Recommend the single best card for user
        Returns: {card, reasons, note}
        Nz-infrU   u   Note: based on ₹r!   z5/month, this is the closest fit from available cards.)rR   r   note)r   r[   r(   r{   r   r   rX   r*   )r   rS   celigiblepool	best_card
best_scorerR   ru   r   r   rY   s               r   recommend_one_cardz"CardRecommender.recommend_one_card   s      $zzQ!T-G-G4-PAQQ#x 	6]
 	!DOOD$/Ez!"
 			! $$Y5 /2J~U|D'q'99noD 
 	
- Rs
   B7B7N)z
cards.json)__name__
__module____qualname__strr   staticmethodr   r   r*   r.   rQ   r   boolr[   r(   r{   r   r   r   r2   r   r   r	   r	      s    0 0 ,S ,S , ,  #  & # #  4$ d t @t @4 @E @DN"$ N"d N"tCy N"` 
t  
  
r   r	   c            	           e Zd ZdZd Zdedee   dee   defdZ	dedefd	Z
dedee   dee   dee   fd
Zd ZdedefdZy)ConversationSessionz5Manages conversation state for asking max 2 questionsc                 h    d| _         d| _        i | _        t               | _        d| _        d| _        y )Nidler   F)stepaskedrS   r	   recommender_llm_configuredfinishedr   s    r   r   zConversationSession.__init__  s1    	
	*,$r   rR   r   r   r   c                 J   d|j                  d       d|j                  d       d|j                  d       d|j                  dd	      d
g}|r|j                  d|        dj                  d |D              }dt        j                  j                  |       d| dS )zGBuild a grounded prompt for the LLM to create a natural-sounding reply.zName: ro   zIssuer: issuerz	Network: rp   u   Annual fee: ₹r^   r   r!   zNote: 
c              3   &   K   | ]	  }d |   yw)- Nr2   )r4   r   s     r   r6   z8ConversationSession._build_llm_prompt.<locals>.<genexpr>$  s      ;a2aS ;s   zYou are a friendly, concise credit card assistant inside a chat UI.
ONLY use the factual details given below about the recommended card. Do NOT invent benefits, fees, limits, or numbers that are not listed. Do not use emojis.

Recommended card details:
z'

Grounded reasons for recommendation:
u  

Write a natural chat reply in a warm, helpful tone:
- Start with: Best pick: *<card name>*
- Then explain 2–3 key benefits, using or paraphrasing the reasons above
- If there is a note, include it in a separate friendly sentence.
- You may add 1 short closing sentence inviting the user to ask another question.
Use simple markdown where helpful (like *bold* for the card name or key phrases). Do not add any extra factual details beyond what is provided.)r   r   r   oslinesep)r   rR   r   r   
card_linesreasons_texts         r   _build_llm_promptz%ConversationSession._build_llm_prompt  s     TXXf%&'txx)*++,-dhh|Q7:;	

 tfo.yy ;7 ;;* zzz*+ ,5n LL	
r   	base_textc                 T   t         |S t        j                  d      }|s|S 	 | j                  st        j                  |       d| _        t        j
                  d      }d| d}|j                  |      }t        |dd      }|s|S |j                         S # t        $ r |cY S w xY w)	z
        Ask the LLM to rephrase a simple system-style reply into a more
        natural chat response, while keeping meaning the same.
        Falls back to the original text on any error or if LLM is unavailable.
        NGEMINI_API_KEYapi_keyTgemini-flash-latestzRewrite the following assistant reply so it sounds like a natural, friendly chat message in 1-3 short sentences. Keep the meaning and any important numbers the same. Do not add new facts or emojis. Here is the reply:

""r   )
genair   getenvr   	configureGenerativeModelgenerate_contentgetattrr   	Exception)r   r   r   modelpromptresponser   s          r   _llm_rephrasez!ConversationSession._llm_rephrase7  s     =)),-	''0'+$))*?@E Kr#  --f5H8VT2D  ::< 		s   A%B 	B B'&B'c                 d   t         yt        j                  d      }|sy	 | j                  st        j                  |       d| _        t        j
                  d      }| j                  |||      }|j                  |      }t        |dd      }|sy|j                         S # t        $ r Y yw xY w)z
        If Gemini is available and configured via GEMINI_API_KEY, ask it to
        craft the final chat response. Falls back to None on any error.
        Nr   r   Tr   r   )r   r   r   r   r   r   r   r   r   r   r   )	r   rR   r   r   r   r   r   r   r   s	            r   _maybe_llm_responsez'ConversationSession._maybe_llm_responseX  s    
 =)),-	''0'+$))*?@E++D'4@F--f5H8VT2D::< 		s   A1B# B# #	B/.B/c                 <    d| _         d| _        i | _        d| _        y)zReset conversation stater   r   FN)r   r   rS   r   r   s    r   resetzConversationSession.resett  s    	
	r   r   c                 <   t         j                  |      }|dv r#| j                          d}| j                  |      S | j                  dk(  r*t         j                  |      | j                  d<   d| _        nn| j                  dk(  r_t         j                  |      }|r5|| j                  d<   d| _        |dk  r-d	| _        d
}| j                  |      S d}| j                  |      S | j                  dk(  rt         j                  |      }| j                  j                  d      r| j                  j                  d      dk(  r|| j                  d<   | j                  j                  d      dk(  r>| j                  dk  r/| xj                  dz  c_
        d| _        d}| j                  |      S | j                  j                  d      dv }|rmt        | j                  j                  d      t        t        f      s>| j                  dk  r/| xj                  dz  c_
        d| _        d}| j                  |      S | j                  j                  | j                        }|d   }|d   }	|d   }
| j!                  ||	|
      }|rd| _        d	| _        |S d|d    dg}|
r|j#                  |
       |	D ]  }|j#                  d|         |j#                  d       |j#                  d       d| _        d	| _        dj%                  |      S d}| j                  |      S )zs
        Process incoming message and return bot response
        Asks max 2 questions before recommending
        )r   restartz
start overz`Reset done. Tell me what you want: cashback, travel, Tata/Neu, RuPay/UPI, or 'best card for me'.ask_goalr]   r   
ask_incomerU   rm   Tz7Income is too low for the cards available in this demo.zGPlease share your *monthly income in INR* (for example, 80000 or 1.2L).rO   r   r#   uV   Quick one: what do you want most — *cashback* or *travel perks* (lounges and miles)?)r;   rO   rD   rJ   r8   zTWhat is your *monthly income in INR*? You can just send a number like 80000 or 1.5L.rR   r   r   zBest pick: *ro   *r   r   zXIf you want, tell me your typical spend split (online/offline/travel) and I'll re-check.r   zjTell me whether you care more about cashback, travel, Tata/Neu, RuPay/UPI, or just say 'best card for me'.)r	   r   r   r   r   rQ   rS   r.   r   r   r   rX   r*   r(   r   r   r   r   r   )r   r   
normalizedbaseincomeinferred_goalneeds_incomeresultrR   r   r   
llm_answerlinesreasons                 r   process_messagez#ConversationSession.process_message{  s   
 %33D9
 ;;JJLuD%%d++ 99
" / ; ;D ADIIfDIYY,&$77=F-3		/*"	E>$(DMTD--d33`))$// 99+77=M99==(DIIMM&,AY,N$1		&! yy}}V$	1::>JJ!OJ *DIsD--d33  99==04ffLJtyy}}_/MPSUZ|$\::>JJ!OJ ,DIqD--d33 %%88CF&>DY'G&>D 11$FJ"	 $!! $DL>34ET"! ,r&]+,LLLLstDI DM99U##{!!$''r   N)r   r   r   __doc__r   r   r   r   r   r   r   r   r   r   r2   r   r   r   r     s    ?
d 
T#Y 
hsm 
X[ 
<s s B tCy QT ZbcfZg 8R(C R(C R(r   r   )r   r   r   r%   pathlibr   typingr   r   r   r   google.generativeaigenerativeair   ImportErrorr	   r   r2   r   r   <module>r      sV     	 	  . .'
v
 v
r( ({  Es   = AA