AishwaryaNJ's picture
Add comprehensive tests for pricing engine and API endpoints
1e11bce
Raw
History Blame Contribute Delete
3.91 kB
from __future__ import annotations
import json
from contextlib import asynccontextmanager
from datetime import UTC, datetime
import pandas as pd
from fastapi import FastAPI, HTTPException
from app.config import get_settings
from app.pricing_engine import PricingEngine
from app.schemas import (
KagglePricingRequest,
KagglePricingResponse,
MonitoringSummary,
OrderEvent,
PricingRequest,
PricingResponse,
)
engine: PricingEngine | None = None
@asynccontextmanager
async def lifespan(_: FastAPI):
global engine
settings = get_settings()
if settings.model_path.exists():
engine = PricingEngine(settings)
else:
engine = None
yield
app = FastAPI(title="Dynamic Pricing Engine", version="1.0.0", lifespan=lifespan)
@app.get("/health")
def health() -> dict[str, object]:
settings = get_settings()
return {
"status": "ok",
"model_loaded": engine is not None,
"dataset_profile": getattr(engine, "dataset_profile", None),
"supported_endpoints": {
"synthetic": "/price/recommend",
"kaggle_retail": "/price/recommend/kaggle",
},
"model_path": str(settings.model_path),
"metrics_path": str(settings.metrics_path),
}
@app.post("/price/recommend", response_model=PricingResponse)
def recommend_price(request: PricingRequest) -> PricingResponse:
if engine is None:
raise HTTPException(status_code=503, detail="Model is not loaded. Train the model first.")
try:
recommendation = engine.recommend_price(request)
except ValueError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
return recommendation.response
@app.post("/price/recommend/kaggle", response_model=KagglePricingResponse)
def recommend_kaggle_price(request: KagglePricingRequest) -> KagglePricingResponse:
if engine is None:
raise HTTPException(status_code=503, detail="Model is not loaded. Train the model first.")
try:
recommendation = engine.recommend_kaggle_price(request)
except ValueError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
return recommendation.response
@app.post("/events/order")
def register_order(event: OrderEvent) -> dict[str, object]:
if engine is None:
raise HTTPException(status_code=503, detail="Model is not loaded. Train the model first.")
is_flash_sale = engine.register_order_event(event)
return {
"sku_id": event.sku_id,
"flash_sale_active": is_flash_sale,
"registered_at": datetime.now(UTC).isoformat(),
}
@app.get("/monitoring/summary", response_model=MonitoringSummary)
def monitoring_summary() -> MonitoringSummary:
if engine is None:
raise HTTPException(status_code=503, detail="Model is not loaded. Train the model first.")
settings = get_settings()
average_recommended_price = None
last_price_update = None
if settings.price_history_path.exists():
history = pd.read_csv(settings.price_history_path)
if not history.empty:
average_recommended_price = float(history["recommended_price"].mean())
last_price_update = pd.to_datetime(history["generated_at"].iloc[-1]).to_pydatetime()
return MonitoringSummary(
tracked_skus=len(engine.flash_sale_tracker.events),
recent_order_events=engine.flash_sale_tracker.recent_event_count(),
flash_sale_skus=engine.flash_sale_tracker.flash_sale_skus(),
average_recommended_price=average_recommended_price,
last_price_update=last_price_update,
)
@app.get("/metrics")
def metrics() -> dict[str, object]:
settings = get_settings()
if not settings.metrics_path.exists():
raise HTTPException(status_code=404, detail="Metrics file not found.")
return json.loads(settings.metrics_path.read_text(encoding="utf-8"))