Market View: per-hotel rate history chart and Booking.com eye link
Replace full-cell link with two small icon buttons in each matrix cell:
- Eye icon links to the hotel's Booking.com page for that check-in date
- LineChart icon opens a history modal (Plotly multi-line chart)
The history chart plots best available rate over scrape runs, with one
line per room type so that changes in which room is cheapest show as
separate traces rather than a single jumpy line.
Backend: GET /competitors/hotels/{id}/rate-history/{stay_date} groups
by scrape batch, takes MIN(rate_gross) per room type per run, returns
series [{room_type, points: [{t, rate}]}].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2abc2b0297
commit
1ffda18872
2 changed files with 158 additions and 11 deletions
|
|
@ -1143,6 +1143,51 @@ async def get_booking_availability(
|
|||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# HOTEL RATE HISTORY
|
||||
# ============================================
|
||||
|
||||
@router.get("/hotels/{hotel_id}/rate-history/{stay_date}")
|
||||
async def get_hotel_rate_history(
|
||||
hotel_id: int,
|
||||
stay_date: date,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Per-room-type best available rate over time for a single stay date.
|
||||
Groups by scrape batch so each x-point is one scrape run, not one row.
|
||||
Returns series: [{room_type, points: [{t, rate}]}]"""
|
||||
result = await db.execute(
|
||||
text("""
|
||||
SELECT
|
||||
COALESCE(b.started_at, date_trunc('hour', r.scraped_at)) AS scrape_time,
|
||||
r.room_type,
|
||||
MIN(r.rate_gross)::float AS best_rate
|
||||
FROM booking_com_rates r
|
||||
LEFT JOIN booking_scrape_log b ON b.batch_id = r.scrape_batch_id
|
||||
WHERE r.hotel_id = :hotel_id
|
||||
AND r.rate_date = :stay_date
|
||||
AND r.rate_gross IS NOT NULL
|
||||
AND r.availability_status = 'available'
|
||||
GROUP BY COALESCE(b.started_at, date_trunc('hour', r.scraped_at)), r.room_type
|
||||
ORDER BY scrape_time
|
||||
"""),
|
||||
{"hotel_id": hotel_id, "stay_date": stay_date},
|
||||
)
|
||||
|
||||
by_room: dict = {}
|
||||
for row in result.mappings():
|
||||
rt = (row["room_type"] or "Unknown").split("\n")[0].strip()
|
||||
by_room.setdefault(rt, []).append({
|
||||
"t": row["scrape_time"].isoformat() if row["scrape_time"] else None,
|
||||
"rate": row["best_rate"],
|
||||
})
|
||||
|
||||
series = [{"room_type": k, "points": v} for k, v in by_room.items()]
|
||||
series.sort(key=lambda s: s["points"][0]["t"] if s["points"] else "")
|
||||
return {"stay_date": str(stay_date), "series": series}
|
||||
|
||||
|
||||
# ============================================
|
||||
# SCRAPE HISTORY
|
||||
# ============================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue