Rate Analysis: fix hotel detail 500 + timeline 422, match frontend shapes; signed % delta badge

- /analysis/hotel/{id}: lead_bucket alias in ORDER BY CASE broke Postgres; replaced
  bucketed curve with per-days_ahead curve and reshaped response to the frontend
  HotelAnalysis interface (strategy/advance_curve/dow_breakdown/sold_out_pattern)
- /analysis/hotel/{id}/timeline: accept ?date= (was rate_date, 422) and return
  flat TimelineEntry array
- /analysis/hotels: alias to hotel_id/hotel_name/date_count for the selector
- strategy pcts default 0 (frontend calls .toFixed), added peak_months
- Market View badge now shows +/-% vs our rate instead of 100-index

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-05 15:01:57 +00:00
parent ea9484e3fa
commit 349236cd7e
2 changed files with 108 additions and 97 deletions

View file

@ -1247,13 +1247,13 @@ const RateMatrixTab: React.FC = () => {
// Price index badge for competitor rows
let priceIndexBadge: React.ReactNode = null
if (hotel.tier === 'competitor' && rate?.rate_gross && ownRateByDate[d]) {
const idx = Math.round((rate.rate_gross / ownRateByDate[d]!) * 100)
const bg = idx > 105 ? '#dcfce7' : idx < 85 ? '#fee2e2' : idx < 95 ? '#fef3c7' : '#f1f5f9'
const fg = idx > 105 ? '#16a34a' : idx < 85 ? '#dc2626' : idx < 95 ? '#d97706' : '#64748b'
const delta = Math.round((rate.rate_gross / ownRateByDate[d]! - 1) * 100)
const bg = delta > 5 ? '#dcfce7' : delta < -15 ? '#fee2e2' : delta < -5 ? '#fef3c7' : '#f1f5f9'
const fg = delta > 5 ? '#16a34a' : delta < -15 ? '#dc2626' : delta < -5 ? '#d97706' : '#64748b'
priceIndexBadge = (
<span style={{ display: 'block', fontSize: 9, fontWeight: 700, color: fg, background: bg,
borderRadius: 4, padding: '0 3px', lineHeight: '14px', marginTop: 1 }}>
{idx}%
{delta > 0 ? '+' : ''}{delta}%
</span>
)
}