# File: src/booking_api.py # Purpose: Mock hospital booking API (FastAPI) that processes and confirms appointments import json import uuid import random from datetime import datetime from pathlib import Path from fastapi import FastAPI, HTTPException from pydantic import BaseModel import sys sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from config import AVAILABLE_SLOTS, DEPARTMENTS, BOOKING_LOG_PATH app = FastAPI(title="Hospital Booking API", version="1.0") # In-memory store simulating a database _booked_slots: dict[str, list[str]] = {} # { "department::date": [slot1, slot2] } class BookingRequest(BaseModel): patient_name: str department: str date: str # YYYY-MM-DD slot: str class BookingResponse(BaseModel): success: bool appointment_id: str | None = None reason: str | None = None patient_name: str | None = None department: str | None = None date: str | None = None slot: str | None = None doctor: str | None = None def _log_booking(record: dict): """Append booking attempt to JSONL log for audit trail.""" BOOKING_LOG_PATH.parent.mkdir(parents=True, exist_ok=True) with open(BOOKING_LOG_PATH, "a") as f: f.write(json.dumps(record) + "\n") @app.post("/book", response_model=BookingResponse) def book_appointment(req: BookingRequest) -> BookingResponse: """ Book an appointment. Returns a confirmed appointment_id on success or an error reason on failure. This endpoint simulates real constraints: - Slot must be valid. - Department must be valid. - Slots can be taken (random simulation for demo). """ # Validate department if req.department not in DEPARTMENTS: result = BookingResponse(success=False, reason=f"Unknown department: {req.department}") _log_booking({"timestamp": datetime.now().isoformat(), **result.dict()}) return result # Validate slot if req.slot not in AVAILABLE_SLOTS: result = BookingResponse(success=False, reason=f"Invalid time slot: {req.slot}") _log_booking({"timestamp": datetime.now().isoformat(), **result.dict()}) return result # Check slot availability (80% success rate in simulation) key = f"{req.department}::{req.date}" taken_slots = _booked_slots.get(key, []) if req.slot in taken_slots or random.random() < 0.20: result = BookingResponse( success=False, reason="Slot unavailable. Please choose another time." ) _log_booking({ "timestamp": datetime.now().isoformat(), "patient": req.patient_name, **result.dict() }) return result # Confirm booking appointment_id = f"APT-{uuid.uuid4().hex[:6].upper()}" doctor = f"Dr. Sample_{random.randint(1, 5)}" # Persist to in-memory store _booked_slots.setdefault(key, []).append(req.slot) result = BookingResponse( success=True, appointment_id=appointment_id, patient_name=req.patient_name, department=req.department, date=req.date, slot=req.slot, doctor=doctor, ) _log_booking({ "timestamp": datetime.now().isoformat(), **result.dict() }) print(f"[BookingAPI] Confirmed: {appointment_id} for {req.patient_name}") return result @app.get("/slots") def get_available_slots(department: str, date: str) -> dict: """Return remaining open slots for a department on a given date.""" key = f"{department}::{date}" taken = _booked_slots.get(key, []) open_slots = [s for s in AVAILABLE_SLOTS if s not in taken] return {"department": department, "date": date, "available_slots": open_slots} @app.get("/health") def health(): return {"status": "ok"} if __name__ == "__main__": import importlib try: uvicorn = importlib.import_module("uvicorn") except ImportError as exc: raise RuntimeError( "uvicorn is required to run the booking API directly. " "Install it with 'pip install uvicorn'." ) from exc uvicorn.run("src.booking_api:app", host="0.0.0.0", port=8000, reload=True)