Browse Source

diff: clamp prices/closed/rule categories to today-forward

Past closed days and past list prices are meaningless to users — only
reservation data (including as-booked prices) has historical value.
prices, closed, and rule_* diff categories now compare
max(start_date, today) onward; reservations and double_bookings keep
the full range.

Deletes both Booking.com past-date special cases (past-price skip and
past-closed stripping) — nothing to police in a window we don't
compare, and no service-specific code remains in diff.
ZiB 4 months ago
parent
commit
7f085ac722
1 changed files with 31 additions and 14 deletions
  1. 31 14
      room_cal/actions/diff.py

+ 31 - 14
room_cal/actions/diff.py

@@ -23,6 +23,11 @@ ACTION_SPEC = ('diff', [])
 _PRICE_PREFIX = 'Price: $'
 
 
+def _clamp_future(start_date: date) -> date:
+  """Past dates are not compared — only reservation data has historical value."""
+  return max(start_date, local_today())
+
+
 async def _timed(key: str, coro, timings: dict):
   t0 = time.perf_counter()
   result = await coro
@@ -48,7 +53,9 @@ async def diff(
     rule_closed           — {pipeline_name: [...]} Room Closed (rule-sourced) vs rule output
     orphans         — Room Closed/Discount events with missing or unrecognized source
 
-  No writes are performed.
+  No writes are performed. Past dates are compared only for reservation data
+  (reservations, double_bookings); prices, closed, and rule categories clamp
+  to today-forward — historical values there are meaningless to users.
   """
   from room_cal.service_api import ServiceAPI as _ServiceAPI
   if api is None:
@@ -86,6 +93,10 @@ async def _diff_prices(
   """
   from room_cal.reservations import Reservation
 
+  start_date = _clamp_future(start_date)
+  if start_date > end_date:
+    return []
+
   from datetime import datetime
   backend = get_backend()
   start_dt = datetime(start_date.year, start_date.month, start_date.day)
@@ -115,11 +126,9 @@ async def _diff_prices(
         for d in iter_dates(e.start.date(), last_night):
           booked_svc_by_date[d] = svc
 
-  today = local_today()
   results: list[dict] = []
-  for ds in (d.isoformat() for d in iter_dates(start_date, end_date)):
-    d = date.fromisoformat(ds)
-    is_past = d < today
+  for d in iter_dates(start_date, end_date):
+    ds = d.isoformat()
     local = cal_prices.get(ds)
     remote = api_prices.get(ds, {})
     has_diff = False
@@ -129,8 +138,6 @@ async def _diff_prices(
       rp = remote.get(svc)
       if lp is None and rp is None:
         continue
-      if svc == 'booking' and is_past and rp is None:
-        continue
       booked_svc = booked_svc_by_date.get(d)
       if booked_svc is not None and svc != booked_svc:
         continue
@@ -157,6 +164,10 @@ async def _diff_closed(
 
   Each row: {'date', 'local': list[str]|None, 'remote': list[str]|None}
   """
+  start_date = _clamp_future(start_date)
+  if start_date > end_date:
+    return []
+
   closed_cal = Closed()
   cal_events = await closed_cal.list(start_date, end_date)
   cal_by_date: dict[str, list[str]] = {}
@@ -168,18 +179,12 @@ async def _diff_closed(
   api_by_date = await api.get_closed_by_date(start_date, end_date)
   api_by_date = {ds: sorted(svcs) for ds, svcs in api_by_date.items()}
 
-  today = local_today()
   all_dates = sorted(set(cal_by_date) | set(api_by_date))
   results: list[dict] = []
   for ds in all_dates:
     local = cal_by_date.get(ds)
     remote = api_by_date.get(ds)
-    if date.fromisoformat(ds) < today and (remote is None or 'booking' not in remote):
-      local_cmp = [s for s in (local or []) if s != 'booking'] or None
-      remote_cmp = [s for s in (remote or []) if s != 'booking'] or None
-    else:
-      local_cmp, remote_cmp = local, remote
-    if local_cmp != remote_cmp:
+    if local != remote:
       results.append({'date': date.fromisoformat(ds), 'local': local, 'remote': remote})
   return results
 
@@ -400,6 +405,10 @@ async def _diff_rule_discounts(
 
   Returns {pipeline_name: [{'date', 'local', 'rule'}]}, only pipelines with diffs.
   """
+  start_date = _clamp_future(start_date)
+  if start_date > end_date:
+    return {}
+
   disc_cal = Discount()
   room_events = await disc_cal.list(start_date, end_date)
 
@@ -438,6 +447,10 @@ async def _diff_rule_closed(
 
   Returns {pipeline_name: [{'date', 'in_room': bool, 'in_rule': bool}]}, only pipelines with diffs.
   """
+  start_date = _clamp_future(start_date)
+  if start_date > end_date:
+    return {}
+
   closed_cal = Closed()
   room_events = await closed_cal.list(start_date, end_date)
 
@@ -477,6 +490,10 @@ async def _diff_rule_prices(
   Returns {pipeline_name: [{'date', 'local_base', 'rule_min'}]}, only pipelines with diffs.
   As-booked dates are excluded: the booking price is always authoritative.
   """
+  start_date = _clamp_future(start_date)
+  if start_date > end_date:
+    return {}
+
   price_events, res_events = await asyncio.gather(
     CalPrice().list(start_date, end_date),
     Reservation().list(start_date, end_date),