Files
catalogue_backend/app/api/schemas.py
2026-08-13 15:54:29 +05:30

174 lines
5.1 KiB
Python

"""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 BrandCardOut(BaseModel):
"""A brand as shown on the home page card grid.
`name` is the same string GET /api/brands returns, so the frontend can
pass it straight back to /api/brands/{brand}/products.
"""
name: str
slug: str
product_count: int
category_count: int
categories: List[str] = Field(default_factory=list)
image_url: Optional[str] = None
initials: str
class BrandCardsOut(BaseModel):
total_brands: int
total_products: int
brands: List[BrandCardOut]
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