Parity: best-available comparison as default, term-matching optional
parity_match_mode config ('best_available' default | 'match_terms').
Best available = cheapest bookable non-dinner direct tariff vs the BC
lead-in (already BC's best available) — simplest like-for-like. Term
matching kept as an option for days where BC's cheapest basis differs
from direct's. Settings gains a comparison-basis select.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
3b501f95c5
commit
ad0e82465c
2 changed files with 42 additions and 11 deletions
|
|
@ -24,6 +24,10 @@ Dates back within tolerance auto-resolve; acknowledged dates stay quiet.
|
|||
|
||||
Config (system_config):
|
||||
parity_check_enabled true/false (default true)
|
||||
parity_match_mode 'best_available' (default: cheapest bookable
|
||||
non-dinner tariff vs the BC lead-in, which is
|
||||
already BC's best available) or 'match_terms'
|
||||
(flex vs flex, prepaid vs prepaid)
|
||||
parity_markup_value expected Booking.com premium over Newbook (default 0)
|
||||
parity_markup_unit 'pct' or 'gbp' (default pct)
|
||||
parity_tolerance_value allowed deviation from expected before alerting (default 2)
|
||||
|
|
@ -74,6 +78,7 @@ def get_parity_config(db) -> dict:
|
|||
enabled_raw = (cfg.get('parity_check_enabled') or 'true').lower()
|
||||
return {
|
||||
"enabled": enabled_raw in ('true', '1', 'yes', 'enabled'),
|
||||
"match_mode": 'match_terms' if (cfg.get('parity_match_mode') or '').lower() == 'match_terms' else 'best_available',
|
||||
"markup_value": num('parity_markup_value', 0.0, 'parity_expected_markup_pct'),
|
||||
"markup_unit": unit('parity_markup_unit'),
|
||||
"tolerance_value": num('parity_tolerance_value', 2.0, 'parity_tolerance_pct'),
|
||||
|
|
@ -133,19 +138,24 @@ def _candidate_tariffs(tariffs_data, days_ahead: int) -> list:
|
|||
return out
|
||||
|
||||
|
||||
def _pick_comparable(candidates: list, booking_flex: bool, booking_breakfast: bool):
|
||||
"""Cheapest tariff matching the Booking.com rate's basis, tiered fallback.
|
||||
All our tariffs include breakfast, so board matching reduces to excluding
|
||||
dinner-inclusive tariffs unless nothing else exists.
|
||||
def _pick_comparable(candidates: list, booking_flex: bool, booking_breakfast: bool,
|
||||
match_mode: str = 'best_available'):
|
||||
"""Newbook tariff to compare against the Booking.com lead-in.
|
||||
best_available: cheapest bookable non-dinner tariff (the BC lead-in is
|
||||
already BC's best available, so this is BAR vs BAR).
|
||||
match_terms: cheapest tariff matching the BC rate's flex/prepaid basis,
|
||||
falling back to best available. All our tariffs include breakfast, so
|
||||
board matching reduces to excluding dinner-inclusive tariffs unless
|
||||
nothing else exists.
|
||||
Returns (tariff, match_quality) or (None, None)."""
|
||||
if not candidates:
|
||||
return None, None
|
||||
want_prepaid = not booking_flex
|
||||
tiers = [
|
||||
([c for c in candidates if c["prepaid"] == want_prepaid and not c["dinner"]], 'matched'),
|
||||
([c for c in candidates if not c["dinner"]], 'any'),
|
||||
(candidates, 'any'),
|
||||
]
|
||||
tiers = []
|
||||
if match_mode == 'match_terms':
|
||||
want_prepaid = not booking_flex
|
||||
tiers.append(([c for c in candidates if c["prepaid"] == want_prepaid and not c["dinner"]], 'matched'))
|
||||
tiers.append(([c for c in candidates if not c["dinner"]], 'best available'))
|
||||
tiers.append((candidates, 'best available'))
|
||||
for pool, quality in tiers:
|
||||
if pool:
|
||||
return min(pool, key=lambda c: c["rate"]), quality
|
||||
|
|
@ -197,7 +207,7 @@ def gather_comparisons(db, start: date, end: date, cfg: dict) -> list:
|
|||
for cat in cat_rows:
|
||||
candidates.extend(_candidate_tariffs(cat["tariffs_data"], days_ahead))
|
||||
|
||||
chosen, quality = _pick_comparable(candidates, booking_flex, booking_breakfast)
|
||||
chosen, quality = _pick_comparable(candidates, booking_flex, booking_breakfast, cfg["match_mode"])
|
||||
if chosen:
|
||||
newbook_rate = chosen["rate"]
|
||||
tariff_name = chosen["name"]
|
||||
|
|
@ -305,6 +315,7 @@ def run_parity_check() -> dict:
|
|||
db.commit()
|
||||
summary = {
|
||||
"status": "ok",
|
||||
"match_mode": cfg["match_mode"],
|
||||
"dates_compared": len(comparisons),
|
||||
"created": created,
|
||||
"updated": updated,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue