Add dow_align param to /forecast/revenue and /forecast/rooms

Allows callers to switch between 364-day DOW-aligned prior year
and same calendar-date prior year comparison.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-21 18:28:55 +00:00
parent 185b2ec163
commit 454f34d390

View file

@ -16,18 +16,25 @@ router = APIRouter()
logger = logging.getLogger(__name__)
def get_prior_year_date(target_date: date) -> date:
def get_prior_year_date(target_date: date, dow_align: bool = True) -> date:
"""
Get prior year date with 364-day offset for day-of-week alignment.
52 weeks = 364 days, so Monday aligns with Monday.
Get prior year comparison date.
dow_align=True: 364-day (52-week) offset keeps same weekday.
dow_align=False: same calendar date last year.
"""
if dow_align:
return target_date - timedelta(days=364)
try:
return target_date.replace(year=target_date.year - 1)
except ValueError: # Feb 29 in leap year → Feb 28
return target_date.replace(year=target_date.year - 1, day=28)
@router.get("/forecast/rooms")
async def get_rooms_forecast(
start_date: str = Query(..., description="Start date (YYYY-MM-DD)"),
days: int = Query(7, ge=1, le=365, description="Number of days to forecast"),
dow_align: bool = Query(True, description="Use 364-day DOW-aligned offset for prior year (false = same calendar date)"),
db: AsyncSession = Depends(get_db),
api_key: dict = Depends(get_api_key_auth)
):
@ -75,7 +82,7 @@ async def get_rooms_forecast(
current = start
while current <= end:
lead_days = (current - today).days if current >= today else 0
prior_date = get_prior_year_date(current)
prior_date = get_prior_year_date(current, dow_align)
total_rooms = available_by_date.get(current, fallback_total_rooms)
try:
@ -287,6 +294,7 @@ async def get_revenue_forecast(
start_date: str = Query(..., description="Start date (YYYY-MM-DD)"),
days: int = Query(7, ge=1, le=365, description="Number of days to forecast"),
type: str = Query("all", description="Revenue type: all, accom, dry, wet"),
dow_align: bool = Query(True, description="Use 364-day DOW-aligned offset for prior year (false = same calendar date)"),
db: AsyncSession = Depends(get_db),
api_key: dict = Depends(get_api_key_auth)
):
@ -340,8 +348,8 @@ async def get_revenue_forecast(
actual_by_date = {row.date: row for row in actual_result.fetchall()}
# Get prior year revenue
prior_start = get_prior_year_date(start)
prior_end = get_prior_year_date(end)
prior_start = get_prior_year_date(start, dow_align)
prior_end = get_prior_year_date(end, dow_align)
prior_result = await db.execute(
text("""
SELECT date, accommodation, dry, wet
@ -413,7 +421,7 @@ async def get_revenue_forecast(
while current <= end:
is_past = current < today
lead_days = (current - today).days if current >= today else 0
prior_date = get_prior_year_date(current)
prior_date = get_prior_year_date(current, dow_align)
# Prior year actual
prior_row = prior_by_date.get(prior_date)