o
    di?                     @  s   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dZe edd
Z!e!d
urse!j"ddgddd Z#e$e%de%ddZ&e%ddZ'ee'Z(ede'ie$e%dde$e%dde$e%d d!d
e)e$e%d"d#e)e$e%d$d#e)e$e%d%d#d&Z*ee*d'd(d)id*Z+i a,d+e-d,< g a.d-e-d.< e Z/dmdnd4d5Z0dod9d:Z1dpd<d=Z2dqd?d@Z3G dAdB dBe	Z4G dCdD dDe	Z5G dEdF dFe	Z6G dGdH dHe	Z7ej8dIdJddrdLdIZ9ej8dMdNddsdPdMZ:ej8dQdRddtdudTdQZ;ej8dUdVddvdXdUZ<ej8dYdZddwdxd^dYZ=ej8d_d`ddydbd_Z>ej8dcddddzdfdcZ?dgdh Z@eAdikr~eBdje&  ejCdkd/e&dl d
S d
S ){a  
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descriptionZ	flask_app/GET)methodsc                   C  s   dddddfS )Nokr   http)statusserver	transport    r   r   r   W/Users/divyeshpatel/Desktop/sahana/Recommender/recommender_rl/recommender/mcp_server.py_root%   s   r    ZPORTMCP_SERVER_PORTZ7860ZRECS_DB_PATHzrecommender_system.dbZdb_pathZRECS_MAX_ARMSZ50ZRECS_MAX_STEPSZ100ZRECS_N_SUGGESTIONS3ZRECS_USE_CLUSTERING0ZRECS_USE_USER_CONTEXTZRECS_USE_PRICE_OPT)Zdata_sourcemax_armsZ	max_stepsZn_suggestionsseedZuse_clusteringZuse_user_contextZuse_price_optimizationepsilon_greedyepsilon皙?)Zenvironmentalgorithm_typealgorithm_paramszDict[int, int]_PRODUCT_ID_TO_ARM	List[int]_ARM_TO_PRODUCT_IDFforceboolreturnNonec              	   C  s   t B tr| s	 W d   dS ztjtjd}dd |D }W n ty+   g }Y nw |dtj add ttD aW d   dS 1 sGw   Y  dS )z>
    Ensure the product-id<->arm mappings are up to date.
    Nlimitc                 S  s    g | ]}d |v rt |d  qS )
product_id)int).0pr   r   r   
<listcomp>V   s     z0_refresh_product_arm_mapping.<locals>.<listcomp>c                 S  s   i | ]\}}||qS r   r   )r6   idxpidr   r   r   
<dictcomp>[   s    z0_refresh_product_arm_mapping.<locals>.<dictcomp>)	_MAPPING_LOCKr+   data_controllerget_all_products_ENVr$   	Exceptionr-   	enumerate)r.   productsZproduct_idsr   r   r   _refresh_product_arm_mappingJ   s   "rC   r:   r5   Optional[int]c                 C  s   | t vr	tdd t | S )z)Map a product ID to its bandit arm index.Tr.   )r+   rC   get)r:   r   r   r   _product_id_to_arm]   s   

rG   arm_idxc                 C  s2   t st  d|   krtt k rt |  S  dS dS )z-Map an arm index back to the real product ID.r   N)r-   rC   len)rH   r   r   r   _arm_to_product_idc   s   rJ   r3   c                 C  s   t dd td|  S )z(Get available product IDs from database.TrE   N)rC   r-   r2   r   r   r   _available_products_from_dbk   s   
rK   c                   @  s   e Zd ZU eddZded< eddZded< eddZded< ed	d
dZded< edddZ	ded< edddZ
ded< dS )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   rN   __annotations__rO   rP   rS   r   rW   r   r   r   r   rL   s   s   
 rL   c                   @  sp   e Zd ZU eddZded< edddZded	< edd
dZded< edddZded< edddZ	ded< dS )UserDatazUser's display namer   rM   nameNzOptional genderrT   rV   genderzOptional age group or stringagezOptional emailemailz Optional list of preference tagszOptional[List[str]]preferences)
rX   rY   rZ   r   r]   r[   r^   r_   r`   ra   r   r   r   r   r\   {   s   
 r\   c                   @  s`   e Zd ZU eddZded< eddddd	Zded
< edddZded< edddZded< dS )RecommendationRequestzUnique user idr   r5   user_id      2   zHow many items to recommend)rU   rQ   ler   n_recommendationsr&   zBandit / policy typerT   rM   r)   NzAlgorithm hyperparameterszOptional[Dict[str, Any]]r*   )	rX   rY   rZ   r   rc   r[   rh   r)   r*   r   r   r   r   rb      s
   
 rb   c                   @  s<   e Zd ZU ded< ded< eddZded< dZd	ed
< dS )FeedbackDatar5   rc   item_idz.One of: view, cart, purchase, remove_from_cartr   rM   
event_typeNrV   	timestamp)rX   rY   rZ   r[   r   rk   rl   r   r   r   r   ri      s
   
 ri   get_system_statuszget status of the MCP serverDict[str, Any]c               
     sV   zddt  t  t  dW S  ty* }  zdd|  dW  Y d} ~ S d} ~ ww )z-
    Return basic server status from DB.
    r   r   )r   r   usersrB   eventserrorzstatus failed: r   messageN)r=   Zget_user_countZget_product_countZget_total_feedbackr@   )er   r   r   rm      s   create_userz9Handles the resources can be used for creating a new useruserc              
     s   z%t d| j| j| jdd}t|}|dkrdddW S d|t|d	W S  ty@ } zdd
| dW  Y d}~S d}~ww )z
    Create a user (DB).
    Nr   )rc   r]   r^   r_   Zuser_cluster_idrq   zFailed to create userrr   success)r   rc   rv   zcreate_user failed: )r   r]   r^   r_   r=   Zadd_userZget_userr@   )rv   rowuidrt   r   r   r   ru      s"   
	get_usersz6can get list of users has a param limit = 50 (default)rf   c              
     sV   zt j| d}dt||dW S  ty* } zdd| dW  Y d}~S d}~ww )z
    List users (DB).
    r2   rx   )r   Ztotal_usersro   rq   zget_users failed: rr   N)r=   Zget_all_usersrI   r@   )r3   ro   rt   r   r   r   r{      s   create_productz9Handles the resources can be used for creating a productsproductc              
     s   z*t d| j| j| j| j| j| jd}t|}|s dddW S t	dd d|j
d	W S  tyE } zdd
| dW  Y d}~S d}~ww )z 
    Create a product (DB).
    N)r4   rN   rO   rP   rS   r   rW   rq   zFailed to create productrr   TrE   rx   )r   r}   zcreate_product failed: )r   rN   rO   rP   rS   r   rW   r=   Zadd_productrC   __dict__r@   )r}   ry   r   rt   r   r   r   r|      s(   
	
get_productsz}can get list of products has a param limit = 50 (default) also has optional params such as category and brand None by defaultrP   rV   brandc              
     s~   z#| rt j| |d}n|rt j||d}nt j|d}dt||dW S  ty> } zdd| dW  Y d}~S d}~ww )z1
    List products (DB) with simple filters.
    r2   rx   )r   Ztotal_productsrB   rq   zget_products failed: rr   N)r=   Zget_products_by_categoryZget_products_by_brandr>   rI   r@   )rP   r   r3   itemsrt   r   r   r   r      s   submit_feedbackzIHandles the MAB reward by feedback and updates the recommender simulationfeedbackc              
     sn  zt | j}t | j}t|}|du rdd| ddW S t||| jt dd}t	|}|s7dddW S t
||| j| jdu rGt
j nt
| jd	g}t| d
dddd}|| jd}d}	d}
tj}|rt|drt|dr|t|jk rt |j| }	|	dkrt|j| |	 }
d||||	|
ddW S  ty } zdd| dW  Y d}~S d}~ww )z
    Record a user interaction event (DB + MABSimulation update).
    This updates the MABSimulation recommender with the feedback.
    Nrq   zProduct z! is not available as a bandit armrr   re   )rc   r4   rk   sessionZactive_userz#Failed to save feedback to database)rc   rj   rk   rl         ?gQ?r(   g      )ZpurchaseZcartviewZremove_from_cartg        r   
arm_countsarm_rewardsrx   z,Feedback processed and MABSimulation updated)r   rj   rc   rewardnqrs   zsubmit_feedback failed: )r5   rj   rc   rG   r   rk   r   utcnowr=   Zadd_feedbackpdZ	DataFramerl   Z	Timestampnow_SIMULATIONZupdate_algorithmrF   current_algorithmhasattrrI   r   rR   r   r@   )r   r:   rz   Z	arm_indexZfbr   Zevent_dfZreward_mappingr   r   r   	algorithmrt   r   r   r   r      s\   




	get_recommendationszFGet personalized recommendations using MABSimulation recommender classrequestc              
     sl  z| j pd }| jpi }|tj ks|rt|| ztjd| jid\}}W n ty8   t \}}Y nw t	tj
d}|sGdddW S dd	 |D }d
d	 |D }|s]dddW S tdtt| jt|}tjtj| j|||d}dd	 |D }	dd	 |	D }	|	sdddW S g }
tj}|rt|drt|drtjddd tj|j|jt|j|jdkd}W d   n1 sw   Y  |	D ]'}t|}|dur|t|k rtdtdt|| }|
| q|
d qndgt|	 }
d| jt|	tr|	n|	 tj tjrtjjj nd|
t|	ddW S  ty5 } zdd| dW  Y d}~S d}~ww ) z
    Get personalized product recommendations using the MABSimulation recommender.
    This uses the UnifiedRecommendationEnv and MABSimulation classes for intelligent recommendations.
    r&   rc   )optionsr2   rq   zNo available products in DBrr   c                 S     g | ]}t |qS r   )rG   r6   r:   r   r   r   r8   A      z'get_recommendations.<locals>.<listcomp>c                 S     g | ]}|d ur|qS Nr   r6   Zarmr   r   r   r8   D      z)No valid bandit arms mapped from productsre   )r   rc   rh   Zinput_arms_idxobsc                 S  r   r   )rJ   r   r   r   r   r8   U  r   c                 S  r   r   r   r   r   r   r   r8   X  r   z3Algorithm returned no valid product recommendationsr   r   ignore)divideinvalidr   )outwhereNr(   r   g      ?gffffff?rx   z-Recommendations generated using MABSimulation)r   rc   recommendationsr)   Zalgorithm_classconfidence_scoresZtotal_recommendationsrs   zget_recommendations failed: )!r)   lowerr*   r   Zset_algorithmr?   resetrc   r@   rK   r$   maxminr5   rh   rI   Zinvoke_agentr   r   npZerrstater   r   r   Z
zeros_likerG   rR   append
isinstancelisttolist	__class__rX   )r   Z	algo_typeZalgo_paramsr   infoZproduct_candidatesZarm_candidatesrh   Zrecommended_armsr   r   r   Zavg_rewardsr:   rH   Z
confidencert   r   r   r   r   (  s   
	

c                   C  s   t du rtdt S )zz
    Expose the underlying Flask app so external servers (uvicorn, gunicorn)
    can host the FastMCP HTTP transport.
    Nz,FastMCPHttpServer did not expose a Flask app)
_FLASK_APPRuntimeErrorr   r   r   r   http_app  s   r   __main__u"   🚀 Starting MCP HTTP server on :z0.0.0.0)hostZregister_serverport)F)r.   r/   r0   r1   )r:   r5   r0   rD   )rH   r5   r0   rD   )r3   r5   r0   r,   )r0   rn   )rv   r\   r0   rn   )rf   )r3   r5   r0   rn   )r}   rL   r0   rn   )NNrf   )rP   rV   r   rV   r3   r5   r0   rn   )r   ri   r0   rn   )r   rb   r0   rn   )D__doc__
__future__r   typingr   r   r   r   Zpydanticr   r   Zfastmcp_http.serverr	   Zdbr
   Z	db.modelsr   r   r   Z'rl_recommender.UnifiedRecommendationEnvr   Zrl_recommender.simulationr   Zpandasr   Znumpyr   osr   	threadingr   r   getattrr   router    r5   getenvr!   ZDB_PATHr=   r/   r?   r   r+   r[   r-   r<   rC   rG   rJ   rK   rL   r\   rb   ri   Ztoolrm   ru   r{   r|   r   r   r   r   rX   printZrun_httpr   r   r   r   <module>   s    




	
@Y
