Add hotel discovery scrape, manual hotel add, and competitor-only filtering
- Scraper: restrict hotel-page scraper to 'competitor' tier only (was own+competitor); own hotel rates come from Newbook API, not Booking.com - Backend: POST /competitors/discover — runs search-results scrape for one date to find market hotels regardless of current backend setting - Backend: POST /competitors/hotels — add hotel manually from Booking.com URL + name + tier; upserts on slug conflict - Frontend (Settings tab): Discover Market Hotels button added below manual date-range scrape - Frontend (Hotels tab): Add Hotel form at top — paste URL (auto-derives name from slug), choose tier, submit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
30583c59a4
commit
c49985bec7
3 changed files with 287 additions and 5 deletions
|
|
@ -295,16 +295,16 @@ def cleanup_stale_batches(db: Session, max_age_minutes: int = 60):
|
|||
|
||||
|
||||
def get_active_hotels(db: Session) -> List[Dict[str, Any]]:
|
||||
"""Return own + competitor hotels with a booking_com_url (for hotel-page scraping).
|
||||
Market-tier hotels are excluded — they were auto-discovered from search results and
|
||||
are not hotels we specifically want to track at rate-plan level."""
|
||||
"""Return competitor hotels with a booking_com_url (for hotel-page scraping).
|
||||
Own and market hotels are excluded — market were auto-discovered from search results;
|
||||
own hotel rates come from the Newbook API, not Booking.com scraping."""
|
||||
rows = db.execute(
|
||||
text("""
|
||||
SELECT id, booking_com_id, name, booking_com_url
|
||||
FROM booking_com_hotels
|
||||
WHERE is_active = TRUE
|
||||
AND booking_com_url IS NOT NULL
|
||||
AND tier IN ('own', 'competitor')
|
||||
AND tier = 'competitor'
|
||||
ORDER BY display_order, id
|
||||
""")
|
||||
).fetchall()
|
||||
|
|
@ -884,6 +884,49 @@ async def _run_manual_scrape_locked(
|
|||
}
|
||||
|
||||
|
||||
async def run_discovery_scrape(db: Session) -> Dict[str, Any]:
|
||||
"""
|
||||
Run a search-results scrape for a single date to discover market hotels.
|
||||
Always uses playwright_local (search-results) regardless of the configured
|
||||
backend — intended for hotel discovery, not rate tracking.
|
||||
Returns new_hotels: count of hotels added to the DB this run.
|
||||
"""
|
||||
config = get_scrape_config(db)
|
||||
if not config:
|
||||
return {'success': False, 'error': 'No scrape location configured.'}
|
||||
|
||||
hotels_before = db.execute(
|
||||
text("SELECT COUNT(*) FROM booking_com_hotels WHERE is_active = TRUE")
|
||||
).scalar() or 0
|
||||
|
||||
batch_id = create_scrape_batch(db, 'discovery')
|
||||
check_in = date.today() + timedelta(days=14)
|
||||
jobs: List[Tuple[date, Optional[int]]] = [(check_in, None)]
|
||||
|
||||
try:
|
||||
agg = await _scrape_dates_concurrent(jobs, config, concurrency=1, batch_id=batch_id)
|
||||
update_scrape_batch(
|
||||
db, batch_id,
|
||||
status='completed' if agg['completed'] else 'failed',
|
||||
hotels_found=agg['hotels'],
|
||||
rates_scraped=agg['rates'],
|
||||
)
|
||||
|
||||
hotels_after = db.execute(
|
||||
text("SELECT COUNT(*) FROM booking_com_hotels WHERE is_active = TRUE")
|
||||
).scalar() or 0
|
||||
|
||||
return {
|
||||
'success': agg['completed'] > 0,
|
||||
'hotels_found': agg['hotels'],
|
||||
'new_hotels': max(0, hotels_after - hotels_before),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Discovery scrape error: {e}")
|
||||
update_scrape_batch(db, batch_id, status='failed', error_message=str(e))
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
# ============================================
|
||||
# QUEUE MANAGEMENT
|
||||
# ============================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue