AI insights: UK dates, GBP, remove rate parity section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-13 19:57:40 +00:00
parent 9761e88e9b
commit 0563ad7fc2

View file

@ -186,6 +186,17 @@ async def gather_competitor_data(db: AsyncSession, days: int = 14) -> Dict[str,
} }
def _fmt_date(d: str) -> str:
"""Convert YYYY-MM-DD to DD/MM/YYYY for UK display."""
try:
parts = str(d).split('-')
if len(parts) == 3:
return f"{parts[2]}/{parts[1]}/{parts[0]}"
except Exception:
pass
return str(d)
def build_prompt( def build_prompt(
occupancy: List[Dict], occupancy: List[Dict],
revenue: List[Dict], revenue: List[Dict],
@ -195,9 +206,10 @@ def build_prompt(
"""Build system and user prompts from gathered data. Returns (system_msg, user_msg).""" """Build system and user prompts from gathered data. Returns (system_msg, user_msg)."""
system_msg = ( system_msg = (
"You are an AI assistant for a hotel revenue manager. Analyze the data below and provide " "You are an AI assistant for a hotel revenue manager in the UK. Analyze the data below and provide "
"a concise daily briefing (3-5 bullet points). Focus on: occupancy trends, pace vs prior " "a concise daily briefing (3-5 bullet points). Focus on: occupancy trends, pace vs prior "
"year, pricing opportunities, rate parity issues, and anything unusual requiring attention. " "year, pricing opportunities, and anything unusual requiring attention. "
"Use UK date format (DD/MM/YYYY) and GBP (£) for all monetary values. "
"Be specific with numbers and dates. Keep it actionable — no fluff or generic advice." "Be specific with numbers and dates. Keep it actionable — no fluff or generic advice."
) )
@ -223,7 +235,7 @@ def build_prompt(
pace_str = f"{pace:+.0f}%" if pace is not None else "-" pace_str = f"{pace:+.0f}%" if pace is not None else "-"
bud_str = f"{budget_val:.0f}%" if budget_val is not None else "-" bud_str = f"{budget_val:.0f}%" if budget_val is not None else "-"
lines.append(f"{d} | {dow} | {otb_str} | {fc_str} | {py_str} | {pace_str} | {bud_str}") lines.append(f"{_fmt_date(d)} | {dow} | {otb_str} | {fc_str} | {py_str} | {pace_str} | {bud_str}")
lines.append("") lines.append("")
# Revenue summary # Revenue summary
@ -235,9 +247,9 @@ def build_prompt(
total_lost = sum(fc.get('lost_potential', 0) or 0 for fc in revenue) total_lost = sum(fc.get('lost_potential', 0) or 0 for fc in revenue)
lines.append("## Revenue Signals") lines.append("## Revenue Signals")
lines.append(f"14-day forecast: ${total_forecast:,.0f} | OTB: ${total_otb:,.0f} | PY: ${total_py:,.0f}") lines.append(f"14-day forecast: £{total_forecast:,.0f} | OTB: £{total_otb:,.0f} | PY: £{total_py:,.0f}")
if opportunity_days > 0: if opportunity_days > 0:
lines.append(f"Pricing opportunity days: {opportunity_days} | Total lost potential: ${total_lost:,.0f}") lines.append(f"Pricing opportunity days: {opportunity_days} | Total lost potential: £{total_lost:,.0f}")
lines.append("") lines.append("")
# Competitor rates section # Competitor rates section
@ -255,32 +267,16 @@ def build_prompt(
own = own_booking.get(d, {}) own = own_booking.get(d, {})
comp = comp_rates.get(d, {}) comp = comp_rates.get(d, {})
rack_str = f"${rack:.0f}" if rack else "-" rack_str = f"£{rack:.0f}" if rack else "-"
own_str = f"${own['rate']:.0f}" if own.get('rate') else "-" own_str = f"£{own['rate']:.0f}" if own.get('rate') else "-"
comp_str = f"${comp['rate']:.0f}" if comp.get('rate') else "-" comp_str = f"£{comp['rate']:.0f}" if comp.get('rate') else "-"
comp_name = comp.get('name', '-') comp_name = comp.get('name', '-')
note = "" note = ""
if own.get('rate') and comp.get('rate') and own['rate'] < comp['rate']: if own.get('rate') and comp.get('rate') and own['rate'] < comp['rate']:
note = " <- cheapest on B.com" note = " <- cheapest on B.com"
lines.append(f"{d} | {rack_str} | {own_str} | {comp_str} | {comp_name}{note}") lines.append(f"{_fmt_date(d)} | {rack_str} | {own_str} | {comp_str} | {comp_name}{note}")
lines.append("")
# Rate parity flags
parity_flags = []
for d in sorted(rack_rates.keys()):
rack = rack_rates.get(d)
own = own_booking.get(d, {})
if rack and own.get('rate') and rack > 0:
delta_pct = ((own['rate'] - rack) / rack) * 100
if abs(delta_pct) > 5:
parity_flags.append(f"{d}: Rack ${rack:.0f} vs B.com ${own['rate']:.0f} ({delta_pct:+.1f}%)")
if parity_flags:
lines.append("## Rate Parity Flags (own rack vs own Booking.com, >5% delta)")
for flag in parity_flags:
lines.append(flag)
lines.append("") lines.append("")
user_msg = "\n".join(lines) user_msg = "\n".join(lines)
@ -397,10 +393,6 @@ async def generate_insight(db: AsyncSession, triggered_by: str = "scheduler") ->
"occupancy_days": len(occupancy), "occupancy_days": len(occupancy),
"revenue_days": len(revenue), "revenue_days": len(revenue),
"competitor_dates": len(competitor.get('own_booking', {})), "competitor_dates": len(competitor.get('own_booking', {})),
"parity_flags": sum(1 for d in competitor.get('rack', {})
if competitor.get('own_booking', {}).get(d, {}).get('rate')
and competitor['rack'].get(d)
and abs((competitor['own_booking'][d]['rate'] - competitor['rack'][d]) / competitor['rack'][d] * 100) > 5),
"prompt_preview": user_msg[:500], "prompt_preview": user_msg[:500],
} }