Wire up direct-rates column; remove dormant pause-on-block flow

Direct-rates sub-row in Market View was dead: the frontend filters
competitors on direct_hotel_id but /matrix and /hotels never returned it.
Add direct_hotel_id to both queries (+ HotelResponse), and only render the
"Direct" sub-row when a hotel actually has a non-null direct rate (was
rendering all-dashes on an empty {} object).

Remove the pause-on-block flow entirely — dormant since rotate-on-block
replaced it (nothing set booking_scraper_paused=true after set_scraper_paused
was dropped): is_scraper_paused, /config/unpause, the /scrape paused guard,
ScraperStatusResponse.paused/pause_until, and the frontend Paused badge +
Unpause button. Trim now-unused datetime import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-06 06:01:56 +00:00
parent 1dc8c0a945
commit 74ae94671b
5 changed files with 15 additions and 103 deletions

View file

@ -53,6 +53,7 @@ class HotelResponse(BaseModel):
notes: Optional[str]
first_seen_at: Optional[datetime]
last_seen_at: Optional[datetime]
direct_hotel_id: Optional[int] = None
class RateResponse(BaseModel):
@ -74,8 +75,6 @@ class RateResponse(BaseModel):
class ScraperStatusResponse(BaseModel):
enabled: bool
paused: bool
pause_until: Optional[str]
backend: str
location_configured: bool
location_name: Optional[str]
@ -98,8 +97,6 @@ async def get_scraper_status(
SELECT config_key, config_value FROM system_config
WHERE config_key IN (
'booking_scraper_enabled',
'booking_scraper_paused',
'booking_scraper_pause_until',
'booking_scraper_backend'
)
""")
@ -138,8 +135,6 @@ async def get_scraper_status(
return ScraperStatusResponse(
enabled=config.get('booking_scraper_enabled', 'false') == 'true',
paused=config.get('booking_scraper_paused', 'false') == 'true',
pause_until=config.get('booking_scraper_pause_until'),
backend=config.get('booking_scraper_backend', 'playwright_local'),
location_configured=location_row is not None,
location_name=location_row.location_name if location_row else None,
@ -349,19 +344,6 @@ async def enable_scraper(
return {"status": "success", "enabled": enabled}
@router.post("/config/unpause")
async def unpause_scraper(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
"""Manually unpause the scraper (clears blocking pause)."""
await db.execute(
text("UPDATE system_config SET config_value = 'false' WHERE config_key = 'booking_scraper_paused'")
)
await db.commit()
return {"status": "success", "message": "Scraper unpaused"}
# ============================================
# MANUAL SCRAPE TRIGGER
# ============================================
@ -420,16 +402,12 @@ async def trigger_manual_scrape(
if not location_result.fetchone():
raise HTTPException(status_code=400, detail="No scrape location configured. Set location first.")
# Check if paused
paused_result = await db.execute(
text("SELECT config_value FROM system_config WHERE config_key = 'booking_scraper_paused'")
)
paused_row = paused_result.fetchone()
if paused_row and paused_row.config_value == 'true':
raise HTTPException(status_code=400, detail="Scraper is currently paused. Use /unpause first or wait for cooldown.")
# Only one scrape at a time — concurrent Chromium runs cause the page
# timeouts that produce partial results
#
# Best-effort early 409: the background task re-acquires the lock and will
# no-op (logging "another scrape is running") if it loses a millisecond-
# window race, so no double-run can slip through here.
from services.booking_scraper import SCRAPE_LOCK
if SCRAPE_LOCK.locked():
raise HTTPException(status_code=409, detail="A scrape is already running. Try again when it finishes.")
@ -463,7 +441,8 @@ async def list_hotels(
query = """
SELECT id, booking_com_id, name, booking_com_url,
star_rating, review_score, review_count,
tier, display_order, notes, first_seen_at, last_seen_at
tier, display_order, notes, first_seen_at, last_seen_at,
direct_hotel_id
FROM booking_com_hotels
WHERE is_active = TRUE
"""
@ -492,7 +471,8 @@ async def list_hotels(
display_order=row.display_order,
notes=row.notes,
first_seen_at=row.first_seen_at,
last_seen_at=row.last_seen_at
last_seen_at=row.last_seen_at,
direct_hotel_id=row.direct_hotel_id
)
for row in result.fetchall()
]
@ -605,7 +585,8 @@ async def get_competitor_matrix(
# Get hotels
hotels_result = await db.execute(
text(f"""
SELECT id, name, tier, display_order, star_rating, review_score, booking_com_url
SELECT id, name, tier, display_order, star_rating, review_score,
booking_com_url, direct_hotel_id
FROM booking_com_hotels
WHERE is_active = TRUE AND {tier_filter.replace('h.', '')}
ORDER BY display_order, name