Matrix: past/sold-out cells show last rate strikethrough, icons at bottom

Sold-out cells: amber (was red), show last known available rate with
strikethrough. Past dates: grey, same strikethrough treatment, no eye
icon (Booking.com won't serve past availability), history chart still
accessible. Date column headers also hide the eye link for past dates.

Backend adds a second query returning last_available_rate (most recent
available + non-null rate_gross) per hotel+date for the full range.

Icons are now 12px and stacked below the rate text in a flex-column
cell layout. Price index badge sits between rate and icons.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-08 15:54:38 +00:00
parent 1ffda18872
commit d737940a00
2 changed files with 89 additions and 43 deletions

View file

@ -671,6 +671,32 @@ async def get_competitor_matrix(
'scraped_at': row.scraped_at.isoformat() if row.scraped_at else None,
}
# Last available rate per hotel+date (used for sold-out / past-date display)
last_avail_result = await db.execute(
text(f"""
SELECT DISTINCT ON (r.hotel_id, r.rate_date)
r.hotel_id,
r.rate_date,
r.rate_gross AS last_available_rate
FROM booking_com_rates r
JOIN booking_com_hotels h ON r.hotel_id = h.id
WHERE {tier_filter}
AND h.is_active = TRUE
AND r.rate_date >= :from_date AND r.rate_date <= :to_date
AND r.availability_status = 'available'
AND r.rate_gross IS NOT NULL
ORDER BY r.hotel_id, r.rate_date, r.scraped_at DESC
"""),
{'from_date': start, 'to_date': end}
)
last_avail: Dict[int, Dict[str, float]] = {}
for row in last_avail_result.fetchall():
last_avail.setdefault(row.hotel_id, {})[row.rate_date.isoformat()] = float(row.last_available_rate)
for hotel_id, date_map in rates_by_hotel.items():
for rate_date, cell in date_map.items():
cell['last_available_rate'] = last_avail.get(hotel_id, {}).get(rate_date)
# Most recent scrape touching each date, across ALL hotels — a partial
# scrape may refresh the date without touching the displayed hotels, so
# per-cell scraped_at can lag behind this column-level timestamp