from fastapi import APIRouter, HTTPException, Depends from sqlalchemy.orm import Session, joinedload from typing import List, Optional from ..models import Dewar as DewarModel, Slot as SlotModel, LogisticsEvent as LogisticsEventModel from ..schemas import LogisticsEventCreate, SlotSchema, Dewar as DewarSchema from ..database import get_db import logging from datetime import datetime, timedelta router = APIRouter() logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def calculate_time_until_refill(last_refill: Optional[datetime], refill_interval_hours: int = 1) -> int: refill_interval = timedelta(hours=refill_interval_hours) now = datetime.now() if not last_refill: return -1 # Sentinel value indicating no refill event recorded time_until_next_refill = last_refill + refill_interval - now return max(0, int(time_until_next_refill.total_seconds())) @router.post("/dewars/retrieve", response_model=DewarSchema) async def retrieve_dewar(data: LogisticsEventCreate, db: Session = Depends(get_db)): logger.info(f"Received data for retrieve_dewar: {data}") dewar = db.query(DewarModel).filter(DewarModel.unique_id == data.dewar_qr_code).first() if not dewar: raise HTTPException(status_code=404, detail="Dewar not found") new_event = LogisticsEventModel( dewar_id=dewar.id, slot_id=None, event_type="retrieved", timestamp=datetime.now(), ) db.add(new_event) db.commit() db.refresh(dewar) # Get the last retrieved event for the dewar last_retrieved_event = db.query(LogisticsEventModel).filter( LogisticsEventModel.dewar_id == dewar.id, LogisticsEventModel.event_type == "retrieved" ).order_by(LogisticsEventModel.timestamp.desc()).first() if last_retrieved_event: dewar.last_retrieved_timestamp = last_retrieved_event.timestamp.isoformat() logger.info(f"Last retrieved event timestamp for dewar {dewar.unique_id}: " f"{dewar.last_retrieved_timestamp}") else: dewar.last_retrieved_timestamp = None logger.info(f"No retrieved event found for dewar {dewar.unique_id}") return dewar @router.post("/dewar/scan", response_model=dict) async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)): dewar_qr_code = event_data.dewar_qr_code location_qr_code = event_data.location_qr_code transaction_type = event_data.transaction_type dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first() if not dewar: raise HTTPException(status_code=404, detail="Dewar not found") slot = db.query(SlotModel).filter(SlotModel.qr_code == location_qr_code).first() if transaction_type == 'incoming': if not slot or slot.occupied: raise HTTPException(status_code=400, detail="Slot not found or already occupied") slot.dewar_unique_id = dewar.unique_id slot.occupied = True elif transaction_type == 'outgoing': if not slot or not slot.occupied or slot.dewar_unique_id != dewar.unique_id: raise HTTPException(status_code=400, detail="Slot not found or dewar not associated with slot") slot.dewar_unique_id = None slot.occupied = False elif transaction_type == 'beamline': if not slot: raise HTTPException(status_code=400, detail="Beamline location not found") dewar.beamline_location = location_qr_code # Create a logistics event log_event(db, dewar.id, slot.id if slot else None, transaction_type) db.commit() return {"message": "Status updated successfully"} @router.get("/slots", response_model=List[SlotSchema]) async def get_all_slots(db: Session = Depends(get_db)): slots = db.query(SlotModel).options(joinedload(SlotModel.dewar)).all() slots_with_refill_time = [] for slot in slots: time_until_refill = None retrievedTimestamp = None if slot.dewar_unique_id: last_refill_event = db.query(LogisticsEventModel) \ .join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \ .filter( DewarModel.unique_id == slot.dewar.unique_id, LogisticsEventModel.event_type == "refill" ) \ .order_by(LogisticsEventModel.timestamp.desc()) \ .first() if last_refill_event: last_refill = last_refill_event.timestamp time_until_refill = calculate_time_until_refill(last_refill) else: time_until_refill = -1 # Get last retrieved timestamp last_retrieved_event = db.query(LogisticsEventModel) \ .join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \ .filter( DewarModel.unique_id == slot.dewar.unique_id, LogisticsEventModel.event_type == "retrieved" ) \ .order_by(LogisticsEventModel.timestamp.desc()) \ .first() if last_retrieved_event: retrievedTimestamp = last_retrieved_event.timestamp.isoformat() # Determine if the dewar is at the beamline at_beamline = False if slot.dewar_unique_id: at_beamline_event = db.query(LogisticsEventModel) \ .join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \ .filter( DewarModel.unique_id == slot.dewar.unique_id, LogisticsEventModel.event_type == "beamline" ) \ .order_by(LogisticsEventModel.timestamp.desc()) \ .first() at_beamline = bool(at_beamline_event) slot_data = SlotSchema( id=slot.id, qr_code=slot.qr_code, label=slot.label, qr_base=slot.qr_base, occupied=slot.occupied, needs_refill=slot.needs_refill, dewar_unique_id=slot.dewar_unique_id, dewar_name=slot.dewar.dewar_name if slot.dewar else None, time_until_refill=time_until_refill, at_beamline=at_beamline, retrievedTimestamp=retrievedTimestamp, ) logger.info(f"Dewar retrieved on the: {retrievedTimestamp}") slots_with_refill_time.append(slot_data) return slots_with_refill_time @router.post("/dewar/refill", response_model=dict) async def refill_dewar(qr_code: str, db: Session = Depends(get_db)): logger.info(f"Refilling dewar with QR code: {qr_code}") dewar = db.query(DewarModel).filter(DewarModel.unique_id == qr_code.strip()).first() if not dewar: logger.error("Dewar not found") raise HTTPException(status_code=404, detail="Dewar not found") now = datetime.now() new_event = LogisticsEventModel( dewar_id=dewar.id, slot_id=None, event_type="refill", timestamp=now, ) db.add(new_event) db.commit() time_until_refill_seconds = calculate_time_until_refill(now) logger.info(f"Dewar refilled successfully with time_until_refill: {time_until_refill_seconds}") return {"message": "Dewar refilled successfully", "time_until_refill": time_until_refill_seconds} @router.get("/dewars", response_model=List[DewarSchema]) async def get_all_dewars(db: Session = Depends(get_db)): dewars = db.query(DewarModel).all() return dewars @router.get("/dewar/{unique_id}", response_model=DewarSchema) async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)): logger.info(f"Received request for dewar with unique_id: {unique_id}") dewar = db.query(DewarModel).filter(DewarModel.unique_id == unique_id.strip()).first() if not dewar: logger.warning(f"Dewar with unique_id '{unique_id}' not found.") raise HTTPException(status_code=404, detail="Dewar not found") logger.info(f"Returning dewar: {dewar}") return dewar def log_event(db: Session, dewar_id: int, slot_id: Optional[int], event_type: str): new_event = LogisticsEventModel( dewar_id=dewar_id, slot_id=slot_id, event_type=event_type, timestamp=datetime.now() ) db.add(new_event) db.commit() logger.info(f"Logged event: {event_type} for dewar: {dewar_id} in slot: {slot_id if slot_id else 'N/A'}")