Przeglądaj źródła

service_api: drop platform-specific fields from shared DayInfo

rooms_to_sell (Booking-only) and booked_net (Airbnb-only) never had
external readers outside their own client. Booking now tracks
rooms_to_sell in an internal _rts dict (evicted alongside its month
cache); Airbnb's dead booked_net overlay in get_calendar is removed
(get_booked_nets already serves that data via its own capability
method). DayInfo carries only cross-platform fields going forward.
ZiB 3 miesięcy temu
rodzic
commit
e25b704525

+ 1 - 9
room_cal/service_api/_airbnb.py

@@ -314,18 +314,10 @@ class _AirbnbClient(ServiceClientBase):
               price=day.get('priceData', {}).get('price'),
               available=day.get('available', False),
               booked=day.get('reservationData') is not None,
-              rooms_to_sell=None,
             )
         self._fetched_months.add((month, year))
     return {
-      d: DayInfo(
-        date=info.date,
-        price=info.price,
-        available=info.available,
-        booked=info.booked,
-        rooms_to_sell=info.rooms_to_sell,
-        booked_net=self._booked_nets.get(d) if info.booked else None,
-      )
+      d: info
       for d, info in self._cache.items()
       if date_from <= d <= date_to
     }

+ 12 - 7
room_cal/service_api/_booking.py

@@ -171,6 +171,7 @@ class _BookingClient(ServiceClientBase):
     self._csrf_token = csrf_token
     self._lang = lang
     self._cache: dict[str, DayInfo] = {}
+    self._rts: dict[str, int] = {}  # date -> rooms_to_sell; internal, not exposed on DayInfo
     self._months: MonthCache[dict[str, DayInfo]] = MonthCache(
       lambda m, y: self._fetch_month_calendar(m, y),
       suspect=self._suspect_month,
@@ -200,6 +201,7 @@ class _BookingClient(ServiceClientBase):
 
   def clear_cache(self) -> None:
     self._cache.clear()
+    self._rts.clear()
     self._months.clear()
 
   def _apply_env(self, env: dict[str, str]) -> None:
@@ -427,8 +429,8 @@ class _BookingClient(ServiceClientBase):
           price=round(float(price_str)) if price_str else None,
           available=(info.get('hidden', 0) == 0),
           booked=(info.get('net_booked', 0) > 0),
-          rooms_to_sell=int(info.get('rooms_to_sell') or 0),
         )
+        self._rts[d] = int(info.get('rooms_to_sell') or 0)
     else:
       # Partner availability API: only rate-level dates; status encodes availability
       # "restricted" = open, "closed" = unavailable, "soldout" = booked
@@ -440,8 +442,8 @@ class _BookingClient(ServiceClientBase):
           price=round(float(price_str)) if price_str else None,
           available=(status == 'restricted'),
           booked=(status == 'soldout'),
-          rooms_to_sell=None,
         )
+        self._rts.pop(d, None)
     return result
 
   async def _fetch_month_calendar(self, month: int, year: int) -> dict[str, DayInfo]:
@@ -478,6 +480,8 @@ class _BookingClient(ServiceClientBase):
     prefix = f'{year}-{month:02d}'
     for d in [k for k in self._cache if k.startswith(prefix)]:
       del self._cache[d]
+    for d in [k for k in self._rts if k.startswith(prefix)]:
+      del self._rts[d]
 
   async def get_calendar(self, date_from: str, date_to: str) -> dict[str, DayInfo]:
     self._check_fresh()
@@ -634,7 +638,7 @@ class _BookingClient(ServiceClientBase):
     if _is_past(d):
       return
     expected_rts = 1 if available else 0
-    if (cached := self._cache.get(d)) and cached.available == available and cached.rooms_to_sell == expected_rts:
+    if (cached := self._cache.get(d)) and cached.available == available and self._rts.get(d) == expected_rts:
       return
     await self._gql_mutate({'input': {
       'checkAddingAVToClosedProperty': False,
@@ -654,7 +658,7 @@ class _BookingClient(ServiceClientBase):
       await self.set_rooms_to_sell(d, 0)
     else:
       cal = await self.get_calendar(d, d)
-      day = cal.get(d, DayInfo(date=d, price=None, available=True, booked=False, rooms_to_sell=None))
+      day = cal.get(d, DayInfo(date=d, price=None, available=True, booked=False))
       await self.set_rooms_to_sell(d, 0 if day.booked else 1)
     self.clear_cache()
     _log.info('booking set_availability date=%s available=%s', d, available)
@@ -684,11 +688,12 @@ class _BookingClient(ServiceClientBase):
   def _rts_issues(self, cal: 'dict[str, DayInfo]', date_from: str, date_to: str) -> list[dict]:
     issues = []
     for d, day in sorted(cal.items()):
-      if not (date_from <= d <= date_to) or day.rooms_to_sell is None:
+      actual_rts = self._rts.get(d)
+      if not (date_from <= d <= date_to) or actual_rts is None:
         continue
       expected = 0 if (not day.available or day.booked) else 1
-      if day.rooms_to_sell != expected:
-        issues.append({'date': d, 'actual_rts': day.rooms_to_sell, 'expected_rts': expected})
+      if actual_rts != expected:
+        issues.append({'date': d, 'actual_rts': actual_rts, 'expected_rts': expected})
     return issues
 
   async def diff_remote_sync(self, date_from: str, date_to: str) -> list[dict]:

+ 3 - 3
room_cal/service_api/client.py

@@ -185,7 +185,7 @@ class ServiceAPI:
   async def set_prices(self, d: date, prices: dict[str, int]) -> None:
     """Set price on each specified service; skips booked dates."""
     ds = d.isoformat()
-    _fallback = DayInfo(date=ds, price=None, available=True, booked=False, rooms_to_sell=None)
+    _fallback = DayInfo(date=ds, price=None, available=True, booked=False)
     cals = await self._gather({
       key: self._clients[key].get_calendar(ds, ds) for key in prices if key in self._clients
     })
@@ -202,7 +202,7 @@ class ServiceAPI:
   async def get_available(self, d: date) -> bool:
     """Return True only when all active (non-failed) services have the date open."""
     ds = d.isoformat()
-    _closed = DayInfo(date=ds, price=None, available=False, booked=False, rooms_to_sell=None)
+    _closed = DayInfo(date=ds, price=None, available=False, booked=False)
     cals = await self._gather({
       key: client.get_calendar(ds, ds) for key, client in self._clients.items()
     })
@@ -214,7 +214,7 @@ class ServiceAPI:
   async def set_available(self, d: date, available: bool) -> None:
     """Open or block a date on all services; skips booked dates."""
     ds = d.isoformat()
-    _fallback = DayInfo(date=ds, price=None, available=True, booked=False, rooms_to_sell=None)
+    _fallback = DayInfo(date=ds, price=None, available=True, booked=False)
     cals = await self._gather({
       key: client.get_calendar(ds, ds) for key, client in self._clients.items()
     })

+ 0 - 2
room_cal/service_api/types.py

@@ -11,8 +11,6 @@ class DayInfo:
   price: int | None
   available: bool
   booked: bool
-  rooms_to_sell: int | None  # Booking.com only; None for Airbnb
-  booked_net: int | None = None  # per-night net from reservation payment data; Airbnb only
 
 
 @dataclass

+ 1 - 1
tests/test_reservation_guards.py

@@ -181,7 +181,7 @@ class TestHadBookings:
   async def test_refetch_invalidates_fetched_months_and_cache(self):
     """Forcing a refetch must also clear _fetched_months and _cache for that month."""
     from room_cal.service_api.types import DayInfo
-    _dummy = DayInfo(date='2026-07-10', price=100, available=True, booked=False, rooms_to_sell=None)
+    _dummy = DayInfo(date='2026-07-10', price=100, available=True, booked=False)
 
     client = _airbnb_client()
     key = (7, 2026)

+ 1 - 1
tests/test_service_cache_ttl.py

@@ -33,7 +33,7 @@ def _days(booked=False):
 
 
 def _day_info(d='2026-07-10'):
-  return DayInfo(date=d, price=100, available=True, booked=False, rooms_to_sell=1)
+  return DayInfo(date=d, price=100, available=True, booked=False)
 
 
 _EXPIRED = -(CACHE_TTL + 1)