Parity markup and tolerance configurable as % or flat £

- config: parity_markup_value/_unit + parity_tolerance_value/_unit
  (pct|gbp), legacy *_pct keys still read as fallback
- expected rate = newbook + £X or newbook × (1 + X%); breach test uses
  the tolerance in its own unit
- Settings parity tab: unit selects + live worked example line
- Parity Alerts tab: unit-aware description, badge now shows £ deviation
  alongside %

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-06 07:41:55 +00:00
parent 493e87b2dc
commit 9545fd651d
4 changed files with 131 additions and 53 deletions

View file

@ -761,8 +761,8 @@ async def get_rate_parity(
newbook_rates = {row.rate_date: dict(row._mapping) for row in newbook_rates_result.fetchall()}
# Expected-markup config: we deliberately price Booking.com higher to
# cover commission, so parity is measured against newbook × (1 + markup%)
from jobs.check_rate_parity import get_parity_config, deviation_from_expected
# cover commission, so parity is measured against Newbook + markup (% or £)
from jobs.check_rate_parity import get_parity_config, expected_booking_rate, evaluate_parity
from database import SyncSessionLocal
cfg_db = SyncSessionLocal()
try:
@ -787,18 +787,19 @@ async def get_rate_parity(
if not booking_rate or not newbook_rate:
continue
expected_rate = float(newbook_rate) * (1 + cfg["markup_pct"] / 100)
diff_pct = deviation_from_expected(float(booking_rate), float(newbook_rate), cfg["markup_pct"])
if diff_pct is None:
result = evaluate_parity(float(booking_rate), float(newbook_rate), cfg)
if result is None:
continue
diff_pct, diff_gbp, breach = result
if abs(diff_pct) > cfg["tolerance_pct"]:
if breach:
parity_issues.append({
'rate_date': rate_date.isoformat(),
'booking_rate': float(booking_rate),
'newbook_rate': float(newbook_rate),
'expected_rate': round(expected_rate, 2),
'expected_rate': round(expected_booking_rate(float(newbook_rate), cfg), 2),
'difference_pct': round(diff_pct, 2),
'difference_gbp': round(diff_gbp, 2),
'alert_type': 'higher' if diff_pct > 0 else 'lower',
'booking_room_type': booking.get('booking_room_type'),
'availability_status': booking.get('availability_status'),
@ -807,8 +808,10 @@ async def get_rate_parity(
return {
'from_date': start.isoformat(),
'to_date': end.isoformat(),
'expected_markup_pct': cfg["markup_pct"],
'tolerance_pct': cfg["tolerance_pct"],
'markup_value': cfg["markup_value"],
'markup_unit': cfg["markup_unit"],
'tolerance_value': cfg["tolerance_value"],
'tolerance_unit': cfg["tolerance_unit"],
'issues_count': len(parity_issues),
'issues': parity_issues
}