| """ |
| Pricing endpoints – integrates the ARF Bayesian pricing calculator. |
| """ |
|
|
| from fastapi import APIRouter, HTTPException, Depends |
| from pydantic import BaseModel |
| import logging |
|
|
| from arf_pricing_calculator.core.pricing_engine import PricingEngine |
| from arf_pricing_calculator.ingestion.questionnaire_parser import parse_input_dict |
| from arf_pricing_calculator.types import PricingOutput |
| from app.core.usage_tracker import enforce_quota |
|
|
| logger = logging.getLogger(__name__) |
| router = APIRouter() |
|
|
|
|
| class PricingEstimateRequest(BaseModel): |
| """Request body for single pricing estimate.""" |
| input: dict |
| customer_id: str = "default" |
| force: bool = False |
|
|
|
|
| class PricingRunRequest(BaseModel): |
| """Request body for multi‑run pricing with learning.""" |
| input: dict |
| customer_id: str = "default" |
| runs: int = 1 |
| cooldown_hours: int = 24 |
| force: bool = False |
|
|
|
|
| @router.post("/pricing/estimate", response_model=PricingOutput) |
| async def estimate_pricing( |
| req: PricingEstimateRequest, |
| quota: dict = Depends(enforce_quota), |
| ): |
| """ |
| Single pricing estimate – no learning, no buffer update. |
| """ |
| try: |
| |
| pricing_input = parse_input_dict(req.input) |
| |
| engine = PricingEngine(calibration_buffer=[]) |
| output = engine.estimate(pricing_input) |
| return output |
| except Exception as e: |
| logger.exception("Pricing estimate failed") |
| raise HTTPException(status_code=400, detail=str(e)) |
|
|
|
|
| @router.post("/pricing/run", response_model=list[PricingOutput]) |
| async def run_pricing( |
| req: PricingRunRequest, |
| quota: dict = Depends(enforce_quota), |
| ): |
| """ |
| Multi‑run pricing with cooldown and buffer persistence. |
| Each run’s simulated outcome is added to the buffer, so subsequent runs |
| see an updated posterior. |
| """ |
| |
| |
| from arf_pricing_calculator.storage.buffer import load_buffer, add_event |
| from arf_pricing_calculator.orchestration.cooldown import enforce_cooldown, is_cooldown_active |
|
|
| outputs = [] |
| buffer = load_buffer() |
|
|
| for i in range(req.runs): |
| if not req.force and is_cooldown_active( |
| req.customer_id, req.cooldown_hours): |
| raise HTTPException(status_code=429, |
| detail=f"Cooldown active after {i} runs") |
|
|
| pricing_input = parse_input_dict(req.input) |
| engine = PricingEngine(calibration_buffer=buffer) |
| out = engine.estimate(pricing_input) |
|
|
| |
| |
| import random |
| outcome = "success" if random.random() > out.risk_score else "failure" |
|
|
| event = { |
| "run_id": out.run_history_id, |
| "customer_id": req.customer_id, |
| "outcome": outcome, |
| "price": out.recommended_price, |
| "value": out.expected_value, |
| "risk_score": out.risk_score, |
| "run_number": i + 1, |
| } |
| add_event(event) |
| buffer = load_buffer() |
|
|
| outputs.append(out) |
|
|
| if i < req.runs - 1: |
| enforce_cooldown(req.customer_id, req.cooldown_hours) |
|
|
| return outputs |
|
|