
    di?                    v   U d Z ddlmZ ddlmZmZmZmZ ddlm	Z	m
Z
 ddlmZ ddlmZ ddlmZmZmZ ddlmZ dd	lmZ dd
lZdd
lZdd
lZddlmZ ddlmZ  edd      Z e edd
      Z!e!e!jE                  ddg      d        Z# e$ ejJ                  d ejJ                  dd                  Z& ejJ                  dd      Z' ee'      Z( ede'i e$ ejJ                  dd             e$ ejJ                  dd             e$ ejJ                  dd             d
 e) e$ ejJ                  d!d"                   e) e$ ejJ                  d#d"                   e) e$ ejJ                  d$d"                  %      Z* ee*d&d'd(i)      Z+i a,d*e-d+<   g a.d,e-d-<    e       Z/dUdVd/Z0dWd0Z1dXd1Z2dYd2Z3 G d3 d4e	      Z4 G d5 d6e	      Z5 G d7 d8e	      Z6 G d9 d:e	      Z7ejq                  d;d<      dZd=       Z9ejq                  d>d?      d[d@       Z:ejq                  dAdB      d\d]dC       Z;ejq                  dDdE      d^dF       Z<ejq                  dGdH      d_d`dI       Z=ejq                  dJdK      dadL       Z>ejq                  dMdN      dbdO       Z?dP Z@eAdQk(  r  eBdRe&        ej                  dSd.e&T       y
y
)ca  
Recommender MCP Server (HTTP transport via fastmcp_http)
--------------------------------------------------------
- Exposes Model Context Protocol tools backed by the in-memory RL recommender.
- Served over plain HTTP so LangChain/Ollama clients can talk to it easily.

Run locally:
    python mcp_server.py  # binds on $PORT or 7860
Then connect from an MCP-compatible client (e.g., the provided Ollama chatbot).
    )annotations)AnyDictListOptional)	BaseModelField)FastMCPHttpServer)DataController)
FeedbackDBProductCatalogUserDemographics)UnifiedRecommendationEnv)MABSimulationN)datetime)Lockrecommender-mcpz&HTTP MCP server for the RL recommenderdescription	flask_app/GET)methodsc                     dddddfS )Nokr   http)statusserver	transport    r!       mcp_server.py_rootr$   %   s!     '
 	 	r"   PORTMCP_SERVER_PORT7860RECS_DB_PATHzrecommender_system.dbdb_pathRECS_MAX_ARMS50RECS_MAX_STEPS100RECS_N_SUGGESTIONS3RECS_USE_CLUSTERING0RECS_USE_USER_CONTEXTRECS_USE_PRICE_OPT)data_sourcemax_arms	max_stepsn_suggestionsseeduse_clusteringuse_user_contextuse_price_optimizationepsilon_greedyepsilon皙?)environmentalgorithm_typealgorithm_paramszDict[int, int]_PRODUCT_ID_TO_ARM	List[int]_ARM_TO_PRODUCT_IDFc                   t         5  t        r| s
	 ddd       y	 t        j                  t        j
                        }|D cg c]  }d|v st        |d          }}|dt        j
                   at        t              D ci c]  \  }}||
 c}}addd       yc c}w # t        $ r g }Y Qw xY wc c}}w # 1 sw Y   yxY w)z>
    Ensure the product-id<->arm mappings are up to date.
    Nlimit
product_id)
_MAPPING_LOCKrB   data_controllerget_all_products_ENVr5   int	ExceptionrD   	enumerate)forceproductspproduct_idsidxpids         r#   _refresh_product_arm_mappingrV   J   s     
e 
	&77dmm7LH9AWA\UVEV3q/KW )4==97@AS7TU7T83c3h7TU 
 X 	K	 V 
sR   
B:(B#	BBB#%B:B4B:B##B1.B:0B11	B::Cc                T    | t         vrt        d       t         j                  |       S )z)Map a product ID to its bandit arm index.TrP   )rB   rV   get)rU   s    r#   _product_id_to_armrZ   ]   s$    
$$$40!!#&&r"   c                j    t         s
t                d| cxk  rt        t               k  rt         |    S  yy)z-Map an arm index back to the real product ID.r   N)rD   rV   len)arm_idxs    r#   _arm_to_product_idr^   c   s1    $&G-c,--!'** .r"   c                ,    t        d       t        d|  S )z(Get available product IDs from database.TrX   N)rV   rD   rF   s    r#   _available_products_from_dbr`   k   s     t,fu%%r"   c                      e Zd ZU  ed      Zded<    ed      Zded<    ed      Zded<    ed	d
      Zded<    edd      Z	ded<    edd      Z
ded<   y)ProductDatazHuman-readable product namer   strproduct_namezBrand or maker
brand_namez/Top-level category (e.g., apparel, electronics)categoryr   zUnit price in USD)ger   floatpriceNzOptional long descriptiondefaultr   Optional[str]r   zOptional image URL	image_url)__name__
__module____qualname__r	   rd   __annotations__re   rf   ri   r   rm   r!   r"   r#   rb   rb   s   sh    *GHL#H(89J9&WXHcXA+>?E5?!&tA\!]K]$T?STI}Tr"   rb   c                      e Zd ZU  ed      Zded<    edd      Zded	<    edd
      Zded<    edd      Zded<    edd      Z	ded<   y)UserDatazUser's display namer   rc   nameNzOptional genderrj   rl   genderzOptional age group or stringagezOptional emailemailz Optional list of preference tagszOptional[List[str]]preferences)
rn   ro   rp   r	   rt   rq   ru   rv   rw   rx   r!   r"   r#   rs   rs   {   s[    "78D#8!$<MNFMNt9WXCX ;KLE=L',TGi'jK$jr"   rs   c                      e Zd ZU  ed      Zded<    edddd	      Zded
<    edd      Zded<    edd      Zded<   y)RecommendationRequestzUnique user idr   rM   user_id      2   zHow many items to recommend)rk   rg   ler   n_recommendationsr<   zBandit / policy typerj   rc   r@   NzAlgorithm hyperparameterszOptional[Dict[str, Any]]rA   )	rn   ro   rp   r	   r{   rq   r   r@   rA   r!   r"   r#   rz   rz      sP    %56GS6"1rGdese(8F\]NC]16tQl1m.mr"   rz   c                  L    e Zd ZU ded<   ded<    ed      Zded<   dZd	ed
<   y)FeedbackDatarM   r{   item_idz.One of: view, cart, purchase, remove_from_cartr   rc   
event_typeNrl   	timestamp)rn   ro   rp   rq   r	   r   r   r!   r"   r#   r   r      s&    LL(XYJY#I}#r"   r   get_system_statuszget status of the MCP serverc                    K   	 ddt         j                         t         j                         t         j                         dS # t        $ r} dd|  dcY d} ~ S d} ~ ww xY ww)z-
    Return basic server status from DB.
    r   r   )r   r   usersrQ   eventserrorzstatus failed: r   messageN)rJ   get_user_countget_product_countget_total_feedbackrN   )es    r#   r   r      se     
	E'$335'99;%88:
 	
  E!s.CDDEs4   A =A A 	AAAA AA create_userz9Handles the resources can be used for creating a new userc                  K   	 t        d| j                  | j                  | j                  d      }t        j                  |      }|dk(  rdddS d|t        j                  |      d	S # t        $ r}dd
| dcY d}~S d}~ww xY ww)z
    Create a user (DB).
    Nr   )r{   rt   ru   rv   user_cluster_idr   zFailed to create userr   success)r   r{   userzcreate_user failed: )r   rt   ru   rv   rJ   add_userget_userrN   )r   rowuidr   s       r#   r   r      s     
J;;
 &&s+"9%2IJJ#_=U=UVY=Z[[ J!0DQC.HIIJsA   BAA* BA* )B*	B3B :B;B BB	get_usersz6can get list of users has a param limit = 50 (default)c                   K   	 t         j                  |       }dt        |      |dS # t        $ r}dd| dcY d}~S d}~ww xY ww)z
    List users (DB).
    rF   r   )r   total_usersr   r   zget_users failed: r   N)rJ   get_all_usersr\   rN   )rG   r   r   s      r#   r   r      sT     
H--E-:#CJOO H!0B1#.FGGHs,   A$) A	A?AAAAcreate_productz9Handles the resources can be used for creating a productsc           	     R  K   	 t        d| j                  | j                  | j                  | j                  | j
                  | j                        }t        j                  |      }|sdddS t        d       d|j                  d	S # t        $ r}dd
| dcY d}~S d}~ww xY ww)z 
    Create a product (DB).
    N)rH   rd   re   rf   ri   r   rm   r   zFailed to create productr   TrX   r   )r   productzcreate_product failed: )r   rd   re   rf   ri   r   rm   rJ   add_productrV   __dict__rN   )r   r   r   r   s       r#   r   r      s     
M --))%%--++''
 ((-%2LMM$40#== M!0Gs.KLLMsA   B'A)B	 -B'.B	 B'		B$BB$B'B$$B'get_productsz}can get list of products has a param limit = 50 (default) also has optional params such as category and brand None by defaultc                   K   	 | rt         j                  | |      }n0|rt         j                  ||      }nt         j                  |      }dt	        |      |dS # t
        $ r}dd| dcY d}~S d}~ww xY ww)z1
    List products (DB) with simple filters.
    rF   r   )r   total_productsrQ   r   zget_products failed: r   N)rJ   get_products_by_categoryget_products_by_brandrK   r\   rN   )rf   brandrG   itemsr   s        r#   r   r      s     
	K#<<XU<SE#99%u9ME#4454AE#s5zuUU K!0EaS.IJJKs5   A;AA A;	A8&A3-A8.A;3A88A;submit_feedbackzIHandles the MAB reward by feedback and updates the recommender simulationc                  K   	 t        | j                        }t        | j                        }t        |      }|	dd| ddS t	        ||| j
                  t        j                         d      }t        j                  |      }|sdddS t        j                  ||| j
                  | j                  t        j                  j                         nt        j                  | j                        d	g      }t        j!                  |       d
dddd}|j#                  | j
                  d      }d}	d}
t        j$                  }|rht'        |d      r\t'        |d      rP|t)        |j*                        k  r8t        |j*                  |         }	|	dkD  rt-        |j.                  |   |	z        }
d||||	|
ddS # t0        $ r}dd| dcY d}~S d}~ww xY ww)z
    Record a user interaction event (DB + MABSimulation update).
    This updates the MABSimulation recommender with the feedback.
    Nr   zProduct z! is not available as a bandit armr   r}   )r{   rH   r   sessionactive_userz#Failed to save feedback to database)r{   r   r   r         ?gQ?r>   g      )purchasecartviewremove_from_cartg        r   
arm_countsarm_rewardsr   z,Feedback processed and MABSimulation updated)r   r   r{   rewardnqr   zsubmit_feedback failed: )rM   r   r{   rZ   r   r   r   utcnowrJ   add_feedbackpd	DataFramer   	Timestampnow_SIMULATIONupdate_algorithmrY   current_algorithmhasattrr\   r   rh   r   rN   )feedbackrU   r   	arm_indexfbr   event_dfreward_mappingr   r   r   	algorithmr   s                r#   r   r      s    9N(""#(""#&s+	%HSEAb2cdd **OO%
 ))"-%2WXX << "--/7/A/A/I))+r||\d\n\nOo	"
 !  	$$X. '*4Z^_##H$7$7= 11	L9giQ^>_3y3344	,,Y78q5i33I>BCA  E
 	
  N!0H.LMMNsN   G?F8 GAF8 GD+F8 7G8	GGG	GGGget_recommendationszFGet personalized recommendations using MABSimulation recommender classc                  K   	 | j                   xs dj                         }| j                  xs i }|t        j                   k7  s|rt        j	                  ||       	 t
        j                  d| j                  i      \  }}t        t
        j                        }|sdddS |D cg c]  }t        |       }}|D cg c]  }||	 }}|sdd	dS t        d
t        t        | j                        t!        |                  }	t        j#                  t        j$                  | j                  |	||      }
|
D cg c]  }t'        |       }}|D cg c]  }||	 }}|sdddS g }t        j$                  }|rt)        |d      rt)        |d      rt+        j,                  dd      5  t+        j.                  |j0                  |j2                  t+        j4                  |j0                        |j2                  dk7        }ddd       |D ]b  }t        |      }|B|t!              k  r4t        dt        dt7        ||                     }|j9                  |       R|j9                  d       d ndgt!        |      z  }d| j                  t;        |t<              r|n|j?                         t        j                   t        j$                  r$t        j$                  j@                  jB                  nd|t!        |      ddS # t        $ r t
        j                         \  }}Y w xY wc c}w c c}w c c}w c c}w # 1 sw Y   <xY w# t        $ r}dd| dcY d}~S d}~ww xY ww)z
    Get personalized product recommendations using the MABSimulation recommender.
    This uses the UnifiedRecommendationEnv and MABSimulation classes for intelligent recommendations.
    r<   r{   )optionsrF   r   zNo available products in DBr   Nz)No valid bandit arms mapped from productsr}   )r   r{   r   input_arms_idxobsz3Algorithm returned no valid product recommendationsr   r   ignore)divideinvalidr   )outwherer>   r   g      ?gffffff?r   z-Recommendations generated using MABSimulation)r   r{   recommendationsr@   algorithm_classconfidence_scorestotal_recommendationsr   zget_recommendations failed: )"r@   lowerrA   r   set_algorithmrL   resetr{   rN   r`   r5   rZ   maxminrM   r   r\   invoke_agentr   r^   r   nperrstater   r   r   
zeros_likerh   append
isinstancelisttolist	__class__rn   )request	algo_typealgo_paramsr   infoproduct_candidatesrU   arm_candidatesarmr   recommended_armsr   r   r   avg_rewardsr]   
confidencer   s                     r#   r   r   (  sJ    NR++?/?FFH	..4"222k%%i=	%

Iw+G
HIC
 9t}}M!%2OPP 0B
/As#/A 	 
 *8K#3?#K%2]^^  3s7+D+D'Es>GZ#[\ '33!33OO/) 4 
 0@
/?s#/? 	 
 +:M/3S_3/M%2ghh 11	M:wyR^?_Hh? ii))((i&;&;<#..!3	 @ ',S1&7S5E+E!$S#c5W9M3N*O!PJ%,,Z8%,,S1 ' "%O(< <  2<_d2SYhYoYoYq)88S^SpSp{<<FFOOvz!2%(%9F	
 		
q  	%

IC	%
 L"
 N @?6  R!0LQC.PQQRs   M*AM %L  M #M*$M (L+:M  L0L0M M*A(M =L5M L:L:!M )M**AM -AL?C>M M* L($M 'L((M ?M	M 	M'M"M'M*"M''M*c                 0    t         t        d      t         S )zz
    Expose the underlying Flask app so external servers (uvicorn, gunicorn)
    can host the FastMCP HTTP transport.
    z,FastMCPHttpServer did not expose a Flask app)
_FLASK_APPRuntimeErrorr!   r"   r#   http_appr     s    
 IJJr"   __main__u"   🚀 Starting MCP HTTP server on :z0.0.0.0)hostregister_serverport)F)rP   boolreturnNone)rU   rM   r   Optional[int])r]   rM   r   r   )rG   rM   r   rC   )r   Dict[str, Any])r   rs   r   r   )r~   )rG   rM   r   r   )r   rb   r   r   )NNr~   )rf   rl   r   rl   rG   rM   r   r   )r   r   r   r   )r   rz   r   r   )D__doc__
__future__r   typingr   r   r   r   pydanticr   r	   fastmcp_http.serverr
   dbr   	db.modelsr   r   r   'rl_recommender.UnifiedRecommendationEnvr   rl_recommender.simulationr   pandasr   numpyr   osr   	threadingr   r   getattrr   router$   rM   getenvr&   DB_PATHrJ   r   rL   r   rB   rq   rD   rI   rV   rZ   r^   r`   rb   rs   rz   r   toolr   r   r   r   r   r   r   r   rn   printrun_httpr!   r"   r#   <module>r     s>  	 # , , % 1  B B L 3   	  
 
,:b	c V[$/
cE7+ , ibii			2CV(LMN "))N$;
< )  G$?D12)")),e45ibii 4c:;	IBII&;SABC#ibii(?EFGIBII.BC$H IJ	 #% &( N ' " I "V&'&U) Uky knI n$9 $  .LME NE ](cdJ eJ& [&^_H `H +fgM hM, ^  *i  jK jK ,wx>N y>N@ "0xySR zSRr z	..?
@A
OOEOP r"   