"""Pydantic request/response models for the FastAPI layer.""" from __future__ import annotations from typing import List, Optional from pydantic import BaseModel, Field # --------------------------------------------------------------------------- # Shared # --------------------------------------------------------------------------- class ProductOut(BaseModel): image_id: str image_url: Optional[str] = None image_urls: List[str] = Field(default_factory=list) brand: str product_name: str title: Optional[str] = None category: Optional[str] = None description: Optional[str] = None price_range: Optional[str] = None size_variants: List[str] = Field(default_factory=list) providers: List[str] = Field(default_factory=list) highlights: List[str] = Field(default_factory=list) nutrients: List[str] = Field(default_factory=list) fssai_license: Optional[str] = None product_sku: Optional[str] = None sku_source: Optional[str] = None hsn_code: Optional[str] = None final_selling_price: Optional[float] = None selling_price: Optional[float] = None barcode: Optional[str] = None barcode_type: Optional[str] = None class SourceProductOut(BaseModel): image_id: str image_url: Optional[str] = None image_urls: List[str] = Field(default_factory=list) brand: str product_name: str title: Optional[str] = None category: Optional[str] = None description: Optional[str] = None price_range: Optional[str] = None size_variants: List[str] = Field(default_factory=list) providers: List[str] = Field(default_factory=list) highlights: List[str] = Field(default_factory=list) nutrients: List[str] = Field(default_factory=list) fssai_license: Optional[str] = None product_sku: Optional[str] = None sku_source: Optional[str] = None hsn_code: Optional[str] = None final_selling_price: Optional[float] = None selling_price: Optional[float] = None barcode: Optional[str] = None barcode_type: Optional[str] = None similarity: float # --------------------------------------------------------------------------- # Health # --------------------------------------------------------------------------- class HealthOut(BaseModel): status: str database: bool ollama: bool ollama_model: str embeddings_model: str # --------------------------------------------------------------------------- # Brands / catalog browsing # --------------------------------------------------------------------------- class BrandsOut(BaseModel): brands: List[str] class CategoriesOut(BaseModel): brand: str categories: List[str] class ProductListOut(BaseModel): brand: str total: int limit: int offset: int products: List[ProductOut] class AllProductsOut(BaseModel): total: int limit: int offset: int products: List[ProductOut] # --------------------------------------------------------------------------- # Semantic search (retrieval only, no LLM generation) # --------------------------------------------------------------------------- class SearchOut(BaseModel): query: str brand: Optional[str] = None results: List[SourceProductOut] # --------------------------------------------------------------------------- # RAG chat # --------------------------------------------------------------------------- class ChatTurn(BaseModel): role: str = Field(..., description="'user' or 'assistant'") content: str class ChatRequest(BaseModel): query: str = Field(..., min_length=1, max_length=2000) brand: Optional[str] = Field(None, description="Restrict retrieval to a single brand") category: Optional[str] = Field(None, description="Restrict retrieval to a category") top_k: Optional[int] = Field(None, ge=1, le=15) history: Optional[List[ChatTurn]] = Field(default=None, description="Prior turns for follow-up questions") class ChatResponseOut(BaseModel): answer: str query: str brand: Optional[str] = None detected_category: Optional[str] = Field( None, description="Product category auto-detected from the query and used to scope retrieval, if any" ) sources: List[SourceProductOut] # --------------------------------------------------------------------------- # Catalog generation (admin/ingestion trigger) # --------------------------------------------------------------------------- class CatalogGenerateRequest(BaseModel): brand: str = Field(..., min_length=1, max_length=120) max_products: int = Field(50, ge=1, le=300) class CatalogJobOut(BaseModel): job_id: str brand: str status: str detail: Optional[str] = None