Add 'last changed' tooltip to bookability date headers

Separate verified_at (last checked) from valid_from (last rate change)
in the matrix response. Column headers show when rates were last checked;
hovering shows both "Checked: X" and "Changed: Y" so users can distinguish
a manual refresh that verified unchanged rates from one that found new prices.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-10 08:44:29 +00:00
parent 418e0d2fcc
commit ed0a027448
2 changed files with 30 additions and 14 deletions

View file

@ -52,14 +52,16 @@ class DateRateInfo(BaseModel):
tariffs: List[TariffInfo] tariffs: List[TariffInfo]
tariff_count: int tariff_count: int
occupancy: Optional[OccupancyInfo] = None occupancy: Optional[OccupancyInfo] = None
valid_from: Optional[str] = None valid_from: Optional[str] = None # when rates last CHANGED
verified_at: Optional[str] = None # when rates last CHECKED
class RateMatrixResponse(BaseModel): class RateMatrixResponse(BaseModel):
categories: List[CategoryInfo] categories: List[CategoryInfo]
dates: List[str] dates: List[str]
matrix: Dict[str, Dict[str, DateRateInfo]] matrix: Dict[str, Dict[str, DateRateInfo]]
date_last_updated: Dict[str, Optional[str]] = {} date_last_updated: Dict[str, Optional[str]] = {} # max verified_at per date
date_last_changed: Dict[str, Optional[str]] = {} # max valid_from per date
# ============================================ # ============================================
@ -346,26 +348,31 @@ async def get_rate_matrix(
tariffs=tariffs_list, tariffs=tariffs_list,
tariff_count=tariffs_data.get('tariff_count', len(tariffs_list)), tariff_count=tariffs_data.get('tariff_count', len(tariffs_list)),
occupancy=existing_occ, occupancy=existing_occ,
valid_from=lv valid_from=vf,
verified_at=lv
) )
# Per-date latest check time (max last_verified_at across all categories for each date) # Per-date header timestamps
# Use last_verified_at so the header reflects when data was last CHECKED, not when rates last changed. date_last_updated: Dict[str, Optional[str]] = {} # last CHECKED (verified_at)
# This makes all dates verified in the same daily run show a consistent timestamp. date_last_changed: Dict[str, Optional[str]] = {} # last CHANGED (valid_from)
date_last_updated: Dict[str, Optional[str]] = {}
for date_str in dates: for date_str in dates:
latest = None latest_checked = None
latest_changed = None
for cat in categories: for cat in categories:
vf = matrix.get(cat.category_id, {}).get(date_str, DateRateInfo(tariffs=[], tariff_count=0)).valid_from entry = matrix.get(cat.category_id, {}).get(date_str, DateRateInfo(tariffs=[], tariff_count=0))
if vf and (latest is None or vf > latest): if entry.verified_at and (latest_checked is None or entry.verified_at > latest_checked):
latest = vf latest_checked = entry.verified_at
date_last_updated[date_str] = latest if entry.valid_from and (latest_changed is None or entry.valid_from > latest_changed):
latest_changed = entry.valid_from
date_last_updated[date_str] = latest_checked
date_last_changed[date_str] = latest_changed
return RateMatrixResponse( return RateMatrixResponse(
categories=categories, categories=categories,
dates=dates, dates=dates,
matrix=matrix, matrix=matrix,
date_last_updated=date_last_updated date_last_updated=date_last_updated,
date_last_changed=date_last_changed
) )

View file

@ -45,6 +45,7 @@ interface RateMatrixData {
dates: string[] dates: string[]
matrix: Record<string, Record<string, DateRateInfo>> matrix: Record<string, Record<string, DateRateInfo>>
date_last_updated?: Record<string, string | null> date_last_updated?: Record<string, string | null>
date_last_changed?: Record<string, string | null>
} }
// Helper functions // Helper functions
@ -432,7 +433,15 @@ const Bookability: React.FC = () => {
{refreshingDate === dateStr ? '...' : '↻'} {refreshingDate === dateStr ? '...' : '↻'}
</button> </button>
{data.date_last_updated?.[dateStr] && ( {data.date_last_updated?.[dateStr] && (
<span style={styles.lastUpdated} title={`Rates last fetched: ${data.date_last_updated[dateStr]}`}> <span
style={styles.lastUpdated}
title={[
`Checked: ${new Date(data.date_last_updated[dateStr]!).toLocaleString('en-GB')}`,
data.date_last_changed?.[dateStr]
? `Changed: ${new Date(data.date_last_changed[dateStr]!).toLocaleString('en-GB')}`
: null,
].filter(Boolean).join('\n')}
>
{formatLastUpdated(data.date_last_updated[dateStr])} {formatLastUpdated(data.date_last_updated[dateStr])}
</span> </span>
)} )}