AIDA / scripts /backfill_booking_statuses.py
destinyebuka's picture
fyp
d778cc5
"""
backfill_booking_statuses.py
────────────────────────────
One-time script to fix all existing bookings whose status should have
automatically advanced but didn't because the transition job didn't exist yet.
Rules applied
─────────────
CONFIRMED β†’ ACTIVE_STAY check-in passed but checkout has NOT yet passed
CONFIRMED β†’ COMPLETED both check-in AND checkout have passed
ACTIVE_STAY β†’ COMPLETED checkout has passed
Run from the AIDA project root:
python scripts/backfill_booking_statuses.py
Set MONGO_URI environment variable (or edit the fallback below) before running.
"""
import asyncio
import os
from datetime import datetime
from motor.motor_asyncio import AsyncIOMotorClient
MONGO_URI = os.getenv("MONGO_URI", "mongodb://localhost:27017")
DB_NAME = os.getenv("MONGO_DB_NAME", "lojiz")
STATUS_CONFIRMED = "Confirmed"
STATUS_ACTIVE_STAY = "ActiveStay"
STATUS_COMPLETED = "Completed"
def _parse_dt(booking: dict, date_field: str, time_field: str, default_hour: int) -> datetime:
raw = booking.get(date_field)
if isinstance(raw, datetime):
base = raw.replace(hour=0, minute=0, second=0, microsecond=0)
elif isinstance(raw, str):
try:
base = datetime.fromisoformat(raw[:10])
except ValueError:
return datetime.max
else:
return datetime.max
time_str = booking.get(time_field)
if time_str:
try:
h, m = (int(x) for x in time_str.strip().split(":"))
return base.replace(hour=h, minute=m)
except Exception:
pass
return base.replace(hour=default_hour, minute=0)
async def run():
client = AsyncIOMotorClient(MONGO_URI)
db = client[DB_NAME]
now = datetime.utcnow()
counts = {"confirmed_to_active": 0, "confirmed_to_completed": 0, "active_to_completed": 0, "skipped": 0}
print(f"[{now.isoformat()}] Starting backfill β€” connecting to {DB_NAME}...")
# ── Pass 1: Fix CONFIRMED bookings ───────────────────────────────────────
confirmed = await db["bookings"].find({"status": STATUS_CONFIRMED}).to_list(length=None)
print(f" Found {len(confirmed)} CONFIRMED bookings to evaluate.")
for b in confirmed:
checkin_dt = _parse_dt(b, "check_in_date", "estimated_arrival_time", default_hour=14)
checkout_dt = _parse_dt(b, "check_out_date", "estimated_departure_time", default_hour=11)
if now < checkin_dt:
counts["skipped"] += 1
continue
if now >= checkout_dt:
new_status = STATUS_COMPLETED
counts["confirmed_to_completed"] += 1
else:
new_status = STATUS_ACTIVE_STAY
counts["confirmed_to_active"] += 1
await db["bookings"].update_one(
{"_id": b["_id"]},
{"$set": {"status": new_status, "updated_at": now}},
)
print(f" Booking {b['_id']}: CONFIRMED β†’ {new_status} "
f"(check_in={checkin_dt.isoformat()}, check_out={checkout_dt.isoformat()})")
# ── Pass 2: Fix ACTIVE_STAY bookings ─────────────────────────────────────
active = await db["bookings"].find({"status": STATUS_ACTIVE_STAY}).to_list(length=None)
print(f" Found {len(active)} ACTIVE_STAY bookings to evaluate.")
for b in active:
checkout_dt = _parse_dt(b, "check_out_date", "estimated_departure_time", default_hour=11)
if now >= checkout_dt:
await db["bookings"].update_one(
{"_id": b["_id"]},
{"$set": {"status": STATUS_COMPLETED, "updated_at": now}},
)
counts["active_to_completed"] += 1
print(f" Booking {b['_id']}: ACTIVE_STAY β†’ COMPLETED "
f"(check_out={checkout_dt.isoformat()})")
else:
counts["skipped"] += 1
# ── Summary ───────────────────────────────────────────────────────────────
print("\n── Backfill complete ──────────────────────────────────────────")
print(f" CONFIRMED β†’ ACTIVE_STAY : {counts['confirmed_to_active']}")
print(f" CONFIRMED β†’ COMPLETED : {counts['confirmed_to_completed']}")
print(f" ACTIVE_STAY β†’ COMPLETED : {counts['active_to_completed']}")
print(f" Skipped (not yet due) : {counts['skipped']}")
total = counts["confirmed_to_active"] + counts["confirmed_to_completed"] + counts["active_to_completed"]
print(f" Total updated : {total}")
print("───────────────────────────────────────────────────────────────")
client.close()
if __name__ == "__main__":
asyncio.run(run())