""" Occupancy report sync — populates newbook_occupancy_report_data so the Bookability matrix can show availability alongside rates. """ import logging from datetime import date, timedelta from sqlalchemy import text logger = logging.getLogger(__name__) async def run_sync_occupancy(days_ahead: int = 365): """ Fetch the Newbook occupancy report from today forward and upsert per-category, per-date availability into newbook_occupancy_report_data. """ from database import AsyncSessionLocal from services.newbook_rates_client import NewbookRatesClient from_date = date.today() to_date = from_date + timedelta(days=days_ahead) async with AsyncSessionLocal() as db: client = await NewbookRatesClient.from_db(db) async with client: report = await client.get_occupancy_report(from_date, to_date) logger.info(f"Occupancy report: {len(report)} categories, {from_date} to {to_date}") vat = float(client.vat_rate or 0) records = 0 for category in report: category_id = str(category.get("category_id") or "") category_name = category.get("category_name") or "" if not category_id: continue for date_str, day in (category.get("occupancy") or {}).items(): try: report_date = date.fromisoformat(date_str) if isinstance(date_str, str) else date_str available = int(day.get("available", 0) or 0) occupied = int(day.get("occupied", 0) or 0) maintenance = int(day.get("maintenance", 0) or 0) allotted = int(day.get("allotted", 0) or 0) revenue_gross = float(day.get("revenue_gross", 0) or 0) revenue_net = day.get("revenue_net") if revenue_net is None: revenue_net = revenue_gross / (1 + vat) if vat else revenue_gross occupancy_pct = (occupied / available * 100) if available > 0 else 0 await db.execute( text(""" INSERT INTO newbook_occupancy_report_data ( date, category_id, category_name, available, occupied, maintenance, allotted, revenue_gross, revenue_net, occupancy_pct, fetched_at ) VALUES ( :date, :category_id, :category_name, :available, :occupied, :maintenance, :allotted, :revenue_gross, :revenue_net, :occupancy_pct, NOW() ) ON CONFLICT (date, category_id) DO UPDATE SET category_name = EXCLUDED.category_name, available = EXCLUDED.available, occupied = EXCLUDED.occupied, maintenance = EXCLUDED.maintenance, allotted = EXCLUDED.allotted, revenue_gross = EXCLUDED.revenue_gross, revenue_net = EXCLUDED.revenue_net, occupancy_pct = EXCLUDED.occupancy_pct, fetched_at = NOW() """), { "date": report_date, "category_id": category_id, "category_name": category_name, "available": available, "occupied": occupied, "maintenance": maintenance, "allotted": allotted, "revenue_gross": round(revenue_gross, 2), "revenue_net": round(revenue_net, 2), "occupancy_pct": round(occupancy_pct, 2), } ) records += 1 except Exception as e: logger.warning(f"Skipping occupancy row {category_id}/{date_str}: {e}") await db.rollback() await db.commit() logger.info(f"Occupancy sync complete: {records} records upserted") return records