""" PriceOye AI Phone Advisor — FastAPI Backend ============================================ Endpoints: POST /chat — send a message, get response + phone cards GET /phones — full phone database GET /phone/{id} — single phone detail GET /health — health check Deploy: uvicorn api.main:app --host 0.0.0.0 --port 7860 """ from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import Optional import uuid from src.conversation import ( ConversationState, handle_message, get_greeting, format_phone_summary ) from src.phone_database import build_database from src.scoring_engine import get_category_scores, get_sub_scores app = FastAPI( title="PriceOye AI Phone Advisor", description="ML-powered phone recommendation engine for Pakistan's market", version="2.0.0", ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) # In-memory session store (replace with Redis for production) SESSIONS: dict[str, ConversationState] = {} PHONE_DB = build_database() # ───────────────────────────────────────────── # SCHEMAS # ───────────────────────────────────────────── class ChatRequest(BaseModel): session_id: Optional[str] = None message: str class ChatResponse(BaseModel): session_id: str text: str followup: Optional[str] = None quick_replies: list[str] phones: Optional[list[dict]] = None # ───────────────────────────────────────────── # ENDPOINTS # ───────────────────────────────────────────── @app.get("/health") def health(): return {"status": "ok", "phones_in_db": len(PHONE_DB), "version": "2.0.0"} @app.post("/chat", response_model=ChatResponse) def chat(req: ChatRequest): # Create or retrieve session session_id = req.session_id or str(uuid.uuid4()) if session_id not in SESSIONS or req.session_id is None: # New session — return greeting greeting = get_greeting() SESSIONS[session_id] = greeting["state"] return ChatResponse( session_id=session_id, text=greeting["text"], quick_replies=greeting["quick_replies"], phones=None, ) state = SESSIONS[session_id] response = handle_message(req.message, state) SESSIONS[session_id] = response["state"] return ChatResponse( session_id=session_id, text=response["text"], followup=response.get("followup"), quick_replies=response.get("quick_replies", []), phones=response.get("phones"), ) @app.get("/phones") def list_phones(): return [ { "id": p.id, "name": p.name, "brand": p.brand, "os": p.os, "price_pkr": p.price_pkr, "price_label": p.price_label, "emoji": p.emoji, "tags": p.tags, "priceoye_url": p.priceoye_url, "whatmobile_url": p.whatmobile_url, "category_scores": {k: round(v, 1) for k, v in get_category_scores(p).items()}, } for p in PHONE_DB ] @app.get("/phone/{phone_id}") def get_phone(phone_id: str, category: str = "camera"): phone = next((p for p in PHONE_DB if p.id == phone_id), None) if not phone: raise HTTPException(status_code=404, detail="Phone not found") cats = get_category_scores(phone) return { "id": phone.id, "name": phone.name, "brand": phone.brand, "os": phone.os, "price_pkr": phone.price_pkr, "price_label": phone.price_label, "emoji": phone.emoji, "tags": phone.tags, "highlights": phone.highlights, "priceoye_url": phone.priceoye_url, "whatmobile_url": phone.whatmobile_url, "category_scores": {k: round(v, 1) for k, v in cats.items()}, "deep_dive": { "category": category, "scores": get_sub_scores(phone, category), }, }