From 454f34d3902783f194a7e23b834292bb02d00216 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Tue, 21 Jul 2026 18:28:55 +0000 Subject: [PATCH] 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 --- backend/api/public.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/backend/api/public.py b/backend/api/public.py index 689f9b6..51ee7d6 100644 --- a/backend/api/public.py +++ b/backend/api/public.py @@ -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. """ - return target_date - timedelta(days=364) + 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)