Three read-only endpoints under /reporting/: - /hotels hotel dimension table (tier, stars, review score) - /rates full rates fact — all sources, room types, scrape history in one flat table - /occupancy Newbook occupancy per date × room category Rates UNION covers Booking.com scrapes, direct competitor engines (with configured room/rate labels), and own hotel Newbook headline rates. Authenticated via X-API-Key header; key stored in system_config.reporting_api_key. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
2.6 KiB
Python
84 lines
2.6 KiB
Python
"""
|
|
Rate Monitor — FastAPI Backend
|
|
"""
|
|
import logging
|
|
import sys
|
|
import os
|
|
import time
|
|
from contextlib import asynccontextmanager
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
handlers=[logging.StreamHandler(sys.stdout)]
|
|
)
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from database import DATABASE_URL
|
|
from api import bookability, competitors, analysis, direct, reporting
|
|
from scheduler import start_scheduler, shutdown_scheduler
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
try:
|
|
import os, psycopg2
|
|
schema_path = os.path.join(os.path.dirname(__file__), 'schema.sql')
|
|
if os.path.exists(schema_path):
|
|
with open(schema_path) as f:
|
|
sql = f.read()
|
|
conn = psycopg2.connect(DATABASE_URL)
|
|
try:
|
|
conn.autocommit = True
|
|
with conn.cursor() as cur:
|
|
cur.execute(sql)
|
|
logging.getLogger(__name__).info("Schema applied successfully")
|
|
finally:
|
|
conn.close()
|
|
except Exception as e:
|
|
logging.getLogger(__name__).warning(f"Schema init failed: {e}")
|
|
|
|
try:
|
|
from services.booking_scraper import cleanup_stale_batches
|
|
from database import SyncSessionLocal
|
|
db = SyncSessionLocal()
|
|
try:
|
|
cleanup_stale_batches(db, max_age_minutes=10)
|
|
finally:
|
|
db.close()
|
|
except Exception as e:
|
|
logging.getLogger(__name__).warning(f"Stale batch cleanup failed: {e}")
|
|
|
|
start_scheduler()
|
|
yield
|
|
shutdown_scheduler()
|
|
|
|
|
|
app = FastAPI(
|
|
title="Rate Monitor API",
|
|
description="Rate monitoring, competitor scraping, and bookability service",
|
|
version="1.0.0",
|
|
lifespan=lifespan,
|
|
)
|
|
STARTED_AT = str(int(time.time() * 1000))
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.include_router(bookability.router, prefix="/bookability", tags=["Bookability"])
|
|
app.include_router(competitors.router, prefix="/competitors", tags=["Competitors"])
|
|
app.include_router(analysis.router, prefix="/analysis", tags=["Rate Analysis"])
|
|
app.include_router(direct.router, prefix="/direct", tags=["Direct Rates"])
|
|
app.include_router(reporting.router, prefix="/reporting", tags=["Reporting"])
|
|
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
return {"status": "healthy", "service": "rate-monitor-api", "version": os.environ.get("BUILD_VERSION", STARTED_AT)}
|