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

@ -777,8 +777,12 @@ const ParityAlertsTab: React.FC = () => {
},
})
const markup = parityConfig?.['parity_expected_markup_pct'] ?? '0'
const tolerance = parityConfig?.['parity_tolerance_pct'] ?? '2'
const markupUnit = parityConfig?.['parity_markup_unit'] ?? 'pct'
const toleranceUnit = parityConfig?.['parity_tolerance_unit'] ?? 'pct'
const markupVal = parityConfig?.['parity_markup_value'] ?? parityConfig?.['parity_expected_markup_pct'] ?? '0'
const toleranceVal = parityConfig?.['parity_tolerance_value'] ?? parityConfig?.['parity_tolerance_pct'] ?? '2'
const markupLabel = markupUnit === 'gbp' ? `£${markupVal}` : `${markupVal}%`
const toleranceLabel = toleranceUnit === 'gbp' ? `£${toleranceVal}` : `${toleranceVal}%`
const thStyle: React.CSSProperties = {
textAlign: 'left', padding: '8px 12px', fontSize: 11, fontWeight: 600,
@ -793,7 +797,7 @@ const ParityAlertsTab: React.FC = () => {
<div>
<div style={{ fontSize: 12, color: 'var(--text-mid)', marginBottom: 14, maxWidth: 720 }}>
Dates where our Booking.com rate deviates from the expected level
(Newbook rate + {markup}% markup, ±{tolerance}% tolerance). Checked daily at 06:45
(Newbook rate + {markupLabel} markup, ±{toleranceLabel} tolerance). Checked daily at 06:45
adjust the markup and tolerance in Settings Rate Parity. Acknowledge a date once
dealt with; alerts auto-resolve when the rates come back in line.
</div>
@ -850,11 +854,21 @@ const ParityAlertsTab: React.FC = () => {
<td style={tdStyle}>{a.newbook_rate != null ? `£${a.newbook_rate.toFixed(2)}` : '—'}</td>
<td style={tdStyle}>{a.booking_com_rate != null ? `£${a.booking_com_rate.toFixed(2)}` : '—'}</td>
<td style={tdStyle}>
{a.difference_pct != null && (
<span style={badgeStyle(a.alert_type === 'higher' ? 'warning' : 'error')}>
{a.difference_pct > 0 ? '+' : ''}{a.difference_pct.toFixed(1)}% vs expected
</span>
)}
{a.difference_pct != null && (() => {
// expected = booking / (1 + dev%); £ deviation derived from that
const expected = a.booking_com_rate != null
? a.booking_com_rate / (1 + a.difference_pct / 100)
: null
const devGbp = expected != null && a.booking_com_rate != null
? a.booking_com_rate - expected
: null
return (
<span style={badgeStyle(a.alert_type === 'higher' ? 'warning' : 'error')}>
{a.difference_pct > 0 ? '+' : ''}{a.difference_pct.toFixed(1)}%
{devGbp != null ? ` (${devGbp > 0 ? '+' : ''}£${Math.abs(devGbp).toFixed(2)})` : ''} vs expected
</span>
)
})()}
</td>
<td style={mergeStyles(tdStyle, { color: 'var(--text-mid)', fontSize: 12 })}>{a.room_category || '—'}</td>
<td style={tdStyle}>