Skip to content

backend checkpoint: November 5th #32

@naman0r

Description

@naman0r

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"
}
  • Response (200):
{
  "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

  1. 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
  1. 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
  1. 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)
  1. 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).
  2. 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.
  3. 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.

Metadata

Metadata

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions