""" This file defines the data structures (schemas) for the recommender system's API using Pydantic. It ensures that data exchanged with the API (requests and responses) adheres to a specific format, providing benefits like automatic data validation, serialization/deserialization, and clear API documentation. """ from pydantic import BaseModel, Field, validator from typing import List, Optional, Any, Union from src.config.settings import DEFAULT_K # For default 'k' value class MsgPayload(BaseModel): """ Represents the payload for a message. """ msg_id: int # Unique identifier for the message. msg_name: str # Name or description of the message. class RecommendationRequest(BaseModel): """ Defines the structure for a recommendation request. """ user_id: Optional[str] = None # Optional unique identifier for the user making the request. query: str = Field(..., description="The search query or input text for which recommendations are sought.") k: Optional[int] = Field(default=DEFAULT_K, description=f"The number of recommendations to return, defaults to {DEFAULT_K}.") class UserItemRecommendationRequest(BaseModel): """ Defines the structure for an item-based recommendation request. """ id: str = Field(..., description="The item ID (msid) to get recommendations for.") user_id: Optional[str] = Field(None, description="Optional user ID. If not provided, an anonymous ID will be generated.") k: Optional[int] = Field(default=DEFAULT_K, description=f"Number of recommendations to return, defaults to {DEFAULT_K}.") class RetrievedDocument(BaseModel): """ Represents a single document retrieved as part of a recommendation. """ id: str # Unique identifier for the retrieved document (msid, typically a string). hl: str # Headline of the document. synopsis: Optional[str] = None # Synopsis or main content of the document. keywords: Optional[str] = None # Keywords associated with the document. type: Optional[str] = None # Optional type or category of the document. taxonomy: Optional[List[str]] = None # List of taxonomy terms. score: Optional[float] = None # Relevance score of the document. seolocation: Optional[str] = None # Optional SEO location or URL. dl: Optional[str] = None # Optional deeplink. lu: Optional[str] = None # Optional last updated timestamp or string. imageid: Optional[str] = None # Optional image identifier. imgratio: Optional[str] = None # Optional image ratio (e.g., "16:9"). imgsize: Optional[str] = None # Optional image size (e.g., "1024x768"). # summary and smart_tip fields removed as they should only be in summary endpoint response # Create a new model for documents with summary and smart tip class RetrievedDocumentWithSummary(RetrievedDocument): """ Represents a document retrieved as part of a recommendation with additional summary and smart tip fields. """ summary: Optional[str] = "" # Generated summary of the article, defaults to empty string smart_tip: Optional[Any] = None # Smart tip with related articles and summaries. class SmartTipSuggestion(BaseModel): label: str url: str # summary: Optional[str] = "" # Removed as per request class SmartTip(BaseModel): title: str description: str suggestions: List[SmartTipSuggestion] = Field(default_factory=list) class PastFeedbackItem(BaseModel): srcMsid: Optional[str] = "" clickedMsid: Optional[str] = "" resultMsids: List[str] = Field(default_factory=list) # timestamp: Optional[datetime] = None # Optional: if you want to include timestamp class RecommendationResponse(BaseModel): """ Defines the structure for a recommendation response. """ msid: Optional[str] = None # Made optional to handle cases where it might not be set (e.g. query-based recs) retrieved_documents: List[RetrievedDocument] = Field(default_factory=list) # Use RetrievedDocument generated_response: Optional[str] = None # Added to match recommender output clicked_msid: Optional[Union[str, List[Any]]] = None # To match existing logic in routes.py past_feedback: List[PastFeedbackItem] = Field(default_factory=list) @validator('retrieved_documents', pre=True, each_item=True) def ensure_smart_tip_format(cls, v): if isinstance(v, dict) and 'smart_tip' in v and isinstance(v['smart_tip'], str): # Compatibility if old format string is somehow passed v['smart_tip'] = None # Or convert to new format if possible, for now, nullify return v # ...other fields if present... class OutputData(BaseModel): """ Represents a single data item in the output, often a simplified version of a retrieved document. """ id: str # Unique identifier for the output data item (msid, typically a string). headline: Optional[str] = None # Optional headline or title for the data item. type: Optional[str] = None # Optional type or category of the data item. class OutputResponse(BaseModel): """ Defines the structure for a generic output response containing a list of data items. """ data: List[OutputData] # A list of output data items. class FeedbackRecommendationRequest(BaseModel): user_id: str msid: str clicked_msid: Optional[str] = None k: Optional[int] = 5 # Refine RetrievedDocument.smart_tip type RetrievedDocument.model_fields['smart_tip'] = Field(default=None, description="Smart tip with related articles and summaries.") RetrievedDocument.model_rebuild(force=True) # Update the RecommendationResponse model to use RetrievedDocumentWithSummary for summary endpoint class RecommendationResponseWithSummary(BaseModel): """ Defines the structure for a recommendation response with summaries and smart tips. """ msid: Optional[str] = None # Made optional to handle cases where it might not be set (e.g. query-based recs) retrieved_documents: List[RetrievedDocumentWithSummary] = Field(default_factory=list) generated_response: Optional[str] = None # Added to match recommender output clicked_msid: Optional[Union[str, List[Any]]] = None # To match existing logic in routes.py past_feedback: List[PastFeedbackItem] = Field(default_factory=list) @validator('retrieved_documents', pre=True, each_item=True) def ensure_smart_tip_format(cls, v): if isinstance(v, dict) and 'smart_tip' in v and isinstance(v['smart_tip'], str): # Compatibility if old format string is somehow passed v['smart_tip'] = None # Or convert to new format if possible, for now, nullify return v