Hey guys,
Summary
- Implement a mock pricing endpoint that returns deterministic-but-plausible price recommendations for a given date range and room type.
- Define request/response DTOs, finish the mock pricing engine, expose
POST /api/pricing/quote, and register the router.
- Fix CORS origins and ensure DB tables are created at startup (or via migration) so user signup remains reliable.
Why Now
- This unblocks the Dashboard to show an end-to-end recommendation flow for MVP 0 without waiting on the real model.
Acceptance Criteria
POST /api/pricing/quote returns JSON with fields: items[] (one per date) and modelVersion.
- Each pricing item includes:
date, room_type_code, price_rec, price_min, price_max, drivers[].
- Returns 200 for valid inputs; returns 422 for invalid payloads.
- CORS allows the frontend (
http://localhost:3000) and staging/prod domains.
- Backend is reachable locally on
http://localhost:8000 and the route appears in /docs.
API Contract
- Method:
POST /api/pricing/quote
- Body:
{
"hotel_id": 1,
"room_type_code": "DLX-QUEEN",
"from": "2025-11-10",
"to": "2025-12-10"
}
{
"items": [
{
"date": "2025-11-10",
"room_type_code": "DLX-QUEEN",
"price_rec": 179.0,
"price_min": 159.0,
"price_max": 199.0,
"drivers": ["Weekend uplift", "Seasonality"]
}
],
"modelVersion": "mock-v1"
}
Files to Touch
backend/app/controllers/pricing_controller.py (implement routes)
backend/app/services/mock_pricing_engine.py (complete engine logic)
backend/app/models/dto.py (add pricing request/response DTOs)
backend/main.py (register pricing router, fix CORS)
- Optional:
backend/app/repositories/database.py or backend/main.py startup hook to create tables, or add Alembic.
infra/docs/api-contracts.md (document final contract)
Detailed Implementation Plan
-
Define DTOs
- Add to
backend/app/models/dto.py:
class PricingRequest(BaseModel): hotel_id: int; room_type_code: str; from: date; to: date
class PricingItem(BaseModel): date: str; room_type_code: str; price_rec: float; price_min: float; price_max: float; drivers: List[str] = []
class PricingResponse(BaseModel): items: List[PricingItem]; modelVersion: str
Example (Python):
from datetime import date, timedelta
from typing import List
from pydantic import BaseModel
class PricingRequest(BaseModel):
hotel_id: int
room_type_code: str
from_: date # use from_ to avoid Python keyword
to: date
class PricingItem(BaseModel):
date: str
room_type_code: str
price_rec: float
price_min: float
price_max: float
drivers: List[str] = []
class PricingResponse(BaseModel):
items: List[PricingItem]
modelVersion: str
-
Complete MockPricingEngine
- Implement a simple deterministic function based on Day-of-Week and a small seasonality curve:
- Base = 150
- Weekend uplift (+20) for Friday/Saturday; midweek slight dip (-10) for Tuesday/Wednesday
- Seasonal factor =
+5 * sin(month) (or a simple monthly map)
price_min = price_rec - 20, price_max = price_rec + 20
drivers includes strings like "Weekend uplift", "Midweek softness", "Seasonality" when applicable
- Version string:
mock-v1.
Example (Python):
from datetime import datetime, timedelta
from typing import List
class MockPricingEngine:
def __init__(self, version: str = "mock-v1"):
self.version = version
def quote(self, *, hotel_id: int, room_type_code: str, start_date: str, end_date: str) -> tuple[list[PricingItem], str]:
start = datetime.fromisoformat(start_date)
end = datetime.fromisoformat(end_date)
items: List[PricingItem] = []
cursor = start
while cursor <= end:
dow = cursor.weekday() # 0=Mon..6=Sun
month = cursor.month
base = 150.0
drivers: List[str] = []
# weekend uplift
if dow in (4, 5):
base += 20.0
drivers.append("Weekend uplift")
# midweek softness
if dow in (1, 2):
base -= 10.0
drivers.append("Midweek softness")
# simple monthly seasonality map
seasonality_map = {6: 10, 7: 15, 8: 10, 12: 5}
if month in seasonality_map:
base += seasonality_map[month]
drivers.append("Seasonality")
price_rec = round(base, 2)
price_min = round(price_rec - 20, 2)
price_max = round(price_rec + 20, 2)
items.append(PricingItem(
date=cursor.date().isoformat(),
room_type_code=room_type_code,
price_rec=price_rec,
price_min=price_min,
price_max=price_max,
drivers=drivers,
))
cursor += timedelta(days=1)
return items, self.version
-
Implement Controller and Router
- In
backend/app/controllers/pricing_controller.py:
- Create router:
router = APIRouter(prefix="/pricing", tags=["pricing"])
- Implement
@router.post("/quote", response_model=PricingResponse)
- Validate
from <= to and max range (e.g., 90 days) → return 422 if invalid.
- Call
MockPricingEngine().quote(...) and map to DTO.
Example (Python):
from fastapi import APIRouter, HTTPException
from datetime import timedelta
from app.models.dto import PricingRequest, PricingResponse, PricingItem
from app.services.mock_pricing_engine import MockPricingEngine
router = APIRouter(prefix="/pricing", tags=["pricing"])
@router.post("/quote", response_model=PricingResponse)
def quote(req: PricingRequest) -> PricingResponse:
if req.from_ > req.to:
raise HTTPException(status_code=422, detail="from must be <= to")
if (req.to - req.from_).days > 90:
raise HTTPException(status_code=422, detail="Range too large; max 90 days")
engine = MockPricingEngine()
items, version = engine.quote(
hotel_id=req.hotel_id,
room_type_code=req.room_type_code,
start_date=req.from_.isoformat(),
end_date=req.to.isoformat(),
)
return PricingResponse(items=items, modelVersion=version)
-
Register Router and Fix CORS
- In
backend/main.py:
- Change CORS origin to
allow_origins=["http://localhost:3000"] (no trailing slash) and include staging/prod domains when ready.
- Import and include pricing router under the existing
api prefix: api.include_router(pricing_router).
-
Ensure DB Tables Exist (Users)
- Minimal: call
Base.metadata.create_all(bind=engine) on startup so Users table exists locally.
- Preferred: introduce Alembic migrations and document
alembic upgrade head.
-
Update API Docs
- Update
infra/docs/api-contracts.md with request/response schema above and any constraints (max range 90 days, required fields).
Local Run and Test
- Install deps:
pip install -r backend/requirements.txt
- Start server:
cd backend
python -m uvicorn main:app --reload
-
Health checks:
GET http://localhost:8000/hello
GET http://localhost:8000/api/db-info
- Swagger:
http://localhost:8000/docs
-
Quote example:
curl -X POST \
http://localhost:8000/api/pricing/quote \
-H "Content-Type: application/json" \
-d '{
"hotel_id": 1,
"room_type_code": "DLX-QUEEN",
"from": "2025-11-10",
"to": "2025-11-20"
}'
Out of Scope (for this ticket)
- Real ML model loading and feature stores.
- PMS integration.
- Authentication/authorization on pricing endpoints.
Resources
Definition of Done
- Endpoint implemented, validated, and appears in Swagger.
- Manual curl test returns a non-empty
items list.
- CORS fixed; frontend can call the endpoint locally.
- API contracts doc updated.
Hey guys,
Summary
POST /api/pricing/quote, and register the router.Why Now
Acceptance Criteria
POST /api/pricing/quotereturns JSON with fields:items[](one per date) andmodelVersion.date,room_type_code,price_rec,price_min,price_max,drivers[].http://localhost:3000) and staging/prod domains.http://localhost:8000and the route appears in/docs.API Contract
POST /api/pricing/quote{ "hotel_id": 1, "room_type_code": "DLX-QUEEN", "from": "2025-11-10", "to": "2025-12-10" }{ "items": [ { "date": "2025-11-10", "room_type_code": "DLX-QUEEN", "price_rec": 179.0, "price_min": 159.0, "price_max": 199.0, "drivers": ["Weekend uplift", "Seasonality"] } ], "modelVersion": "mock-v1" }Files to Touch
backend/app/controllers/pricing_controller.py(implement routes)backend/app/services/mock_pricing_engine.py(complete engine logic)backend/app/models/dto.py(add pricing request/response DTOs)backend/main.py(register pricing router, fix CORS)backend/app/repositories/database.pyorbackend/main.pystartup hook to create tables, or add Alembic.infra/docs/api-contracts.md(document final contract)Detailed Implementation Plan
Define DTOs
backend/app/models/dto.py:class PricingRequest(BaseModel): hotel_id: int; room_type_code: str; from: date; to: dateclass PricingItem(BaseModel): date: str; room_type_code: str; price_rec: float; price_min: float; price_max: float; drivers: List[str] = []class PricingResponse(BaseModel): items: List[PricingItem]; modelVersion: strExample (Python):
Complete
MockPricingEngine+5 * sin(month)(or a simple monthly map)price_min = price_rec - 20,price_max = price_rec + 20driversincludes strings like"Weekend uplift","Midweek softness","Seasonality"when applicablemock-v1.Example (Python):
Implement Controller and Router
backend/app/controllers/pricing_controller.py:router = APIRouter(prefix="/pricing", tags=["pricing"])@router.post("/quote", response_model=PricingResponse)from <= toand max range (e.g., 90 days) → return 422 if invalid.MockPricingEngine().quote(...)and map to DTO.Example (Python):
Register Router and Fix CORS
backend/main.py:allow_origins=["http://localhost:3000"](no trailing slash) and include staging/prod domains when ready.apiprefix:api.include_router(pricing_router).Ensure DB Tables Exist (Users)
Base.metadata.create_all(bind=engine)on startup soUserstable exists locally.alembic upgrade head.Update API Docs
infra/docs/api-contracts.mdwith request/response schema above and any constraints (max range 90 days, required fields).Local Run and Test
pip install -r backend/requirements.txtcd backend python -m uvicorn main:app --reloadHealth checks:
GET http://localhost:8000/helloGET http://localhost:8000/api/db-infohttp://localhost:8000/docsQuote example:
Out of Scope (for this ticket)
Resources
Definition of Done
itemslist.