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:
parent
185b2ec163
commit
454f34d390
1 changed files with 16 additions and 8 deletions
|
|
@ -16,18 +16,25 @@ router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
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.
|
Get prior year comparison date.
|
||||||
52 weeks = 364 days, so Monday aligns with Monday.
|
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")
|
@router.get("/forecast/rooms")
|
||||||
async def get_rooms_forecast(
|
async def get_rooms_forecast(
|
||||||
start_date: str = Query(..., description="Start date (YYYY-MM-DD)"),
|
start_date: str = Query(..., description="Start date (YYYY-MM-DD)"),
|
||||||
days: int = Query(7, ge=1, le=365, description="Number of days to forecast"),
|
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),
|
db: AsyncSession = Depends(get_db),
|
||||||
api_key: dict = Depends(get_api_key_auth)
|
api_key: dict = Depends(get_api_key_auth)
|
||||||
):
|
):
|
||||||
|
|
@ -75,7 +82,7 @@ async def get_rooms_forecast(
|
||||||
current = start
|
current = start
|
||||||
while current <= end:
|
while current <= end:
|
||||||
lead_days = (current - today).days if current >= today else 0
|
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)
|
total_rooms = available_by_date.get(current, fallback_total_rooms)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -287,6 +294,7 @@ async def get_revenue_forecast(
|
||||||
start_date: str = Query(..., description="Start date (YYYY-MM-DD)"),
|
start_date: str = Query(..., description="Start date (YYYY-MM-DD)"),
|
||||||
days: int = Query(7, ge=1, le=365, description="Number of days to forecast"),
|
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"),
|
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),
|
db: AsyncSession = Depends(get_db),
|
||||||
api_key: dict = Depends(get_api_key_auth)
|
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()}
|
actual_by_date = {row.date: row for row in actual_result.fetchall()}
|
||||||
|
|
||||||
# Get prior year revenue
|
# Get prior year revenue
|
||||||
prior_start = get_prior_year_date(start)
|
prior_start = get_prior_year_date(start, dow_align)
|
||||||
prior_end = get_prior_year_date(end)
|
prior_end = get_prior_year_date(end, dow_align)
|
||||||
prior_result = await db.execute(
|
prior_result = await db.execute(
|
||||||
text("""
|
text("""
|
||||||
SELECT date, accommodation, dry, wet
|
SELECT date, accommodation, dry, wet
|
||||||
|
|
@ -413,7 +421,7 @@ async def get_revenue_forecast(
|
||||||
while current <= end:
|
while current <= end:
|
||||||
is_past = current < today
|
is_past = current < today
|
||||||
lead_days = (current - today).days if current >= today else 0
|
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 year actual
|
||||||
prior_row = prior_by_date.get(prior_date)
|
prior_row = prior_by_date.get(prior_date)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue