From 14eaca81c6cdb8aead81fb2b917dc3988ba2823e Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:21:37 +0100 Subject: [PATCH] Update tell position logic and add new endpoint Refactored tell position logic to use `segment` and `puck_in_segment` fields, replacing the previous single `tell_position` field. Introduced a new `/set-tell-positions` endpoint for setting tell positions based on these changes and removed the deprecated endpoint handling the old logic. Enhanced validation and streamlined handling of related logistics and puck events. --- backend/app/routers/puck.py | 160 ++++++++++++++++++++++++++---------- backend/app/schemas.py | 27 +++++- 2 files changed, 138 insertions(+), 49 deletions(-) diff --git a/backend/app/routers/puck.py b/backend/app/routers/puck.py index be18028..c6d5f9d 100644 --- a/backend/app/routers/puck.py +++ b/backend/app/routers/puck.py @@ -6,7 +6,6 @@ from app.schemas import ( Puck as PuckSchema, PuckCreate, PuckUpdate, - SetTellPosition, ) from app.models import ( Puck as PuckModel, @@ -17,7 +16,6 @@ from app.models import ( Dewar as DewarModel, ) from app.dependencies import get_db -from datetime import datetime import logging router = APIRouter() @@ -81,6 +79,121 @@ async def get_pucks_with_tell_position(db: Session = Depends(get_db)): return result +@router.put("/set-tell-positions", status_code=status.HTTP_200_OK) +async def set_tell_positions( + puck_name: str, + segment: str, + puck_in_segment: int, + db: Session = Depends(get_db), +): + """ + Set the tell position for a puck based on the last beamline event. + - Validates `puck_name`, `segment`, and `puck_in_segment`. + - Finds the most recent logistics event of type "beamline" for the associated dewar. + - Ensures the logistics event is associated with a valid slot. + - Creates a new puck event with the specified `tell_position`. + + Args: + puck_name (str): Name of the puck to set the tell position for. + segment (str): Segment label (A-F). + puck_in_segment (int): Position in the segment (1-5). + + Returns: + JSON response containing puck and associated information. + """ + + from datetime import datetime + + # 1. Validate `segment` and `puck_in_segment` + if not segment or segment not in "ABCDEF": + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid segment. Valid segments are A, B, C, D, E, F.", + ) + if not (1 <= puck_in_segment <= 5): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid puck_in_segment. Valid positions are 1 to 5.", + ) + + # Generate tell_position + tell_position = f"{segment}{puck_in_segment}" + + # 2. Find the puck by its name and ensure it exists + puck = db.query(PuckModel).filter(PuckModel.puck_name == puck_name).first() + if not puck: + print(f"DEBUG: Puck '{puck_name}' not found in the database.") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + # This should match the error returned + detail=f"Puck with name '{puck_name}' not found.", + ) + print(f"DEBUG: Found puck: {puck}") + + # 3. Find the dewar associated with the puck + dewar = db.query(DewarModel).filter(DewarModel.id == puck.dewar_id).first() + if not dewar: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Dewar associated with puck '{puck_name}' not found.", + ) + + # 4. Find the most recent logistics event for the dewar (type 'beamline') + # and ensure not null + logistics_event = ( + db.query(LogisticsEventModel) + .filter( + LogisticsEventModel.dewar_id == dewar.id, + LogisticsEventModel.event_type == "beamline", + ) + .order_by(LogisticsEventModel.timestamp.desc()) + .first() + ) + if not logistics_event: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=( + f"No recent 'beamline' logistics event found for dewar '" + f"{dewar.dewar_name}' " + f"(puck '{puck_name}')." + ), + ) + + # 5. Retrieve the slot from the logistics event + slot = db.query(SlotModel).filter(SlotModel.id == logistics_event.slot_id).first() + if not slot: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=( + f"No slot associated with the most recent 'beamline' logistics event " + f"for dewar '{dewar.dewar_name}'." + ), + ) + + # 6. Set the tell position for the puck by creating a PuckEvent + new_puck_event = PuckEventModel( + puck_id=puck.id, + tell_position=tell_position, + event_type="tell_position_set", + timestamp=datetime.utcnow(), + ) + db.add(new_puck_event) + db.commit() + db.refresh(new_puck_event) + + # 7. Return the result + return { + "puck_id": puck.id, + "puck_name": puck_name, + "dewar_id": dewar.id, + "dewar_name": dewar.dewar_name, + "slot_id": slot.id, + "slot_label": slot.label, + "tell_position": tell_position, + "event_timestamp": new_puck_event.timestamp, + } + + @router.get("/{puck_id}", response_model=PuckSchema) async def get_puck(puck_id: str, db: Session = Depends(get_db)): puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first() @@ -132,49 +245,6 @@ async def delete_puck(puck_id: str, db: Session = Depends(get_db)): return -@router.put("/{puck_id}/tell_position", status_code=status.HTTP_200_OK) -async def set_tell_position( - puck_id: int, request: SetTellPosition, db: Session = Depends(get_db) -): - # Get the requested tell_position - tell_position = request.tell_position - - # Define valid positions - valid_positions = [ - f"{letter}{num}" for letter in "ABCDEF" for num in range(1, 6) - ] + ["null", None] - - # Validate tell_position - if tell_position not in valid_positions: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid tell_position value. Must be one of {valid_positions}.", - ) - - # Set the correct tell_position logic - actual_position = None if tell_position in ["null", None] else tell_position - - # Create a new PuckEvent (always a new event, even with null/None) - new_puck_event = PuckEventModel( - puck_id=puck_id, - tell_position=actual_position, - # Null for disassociation, else the valid position - event_type="tell_position_set", - # Example event type - timestamp=datetime.utcnow(), - ) - db.add(new_puck_event) - db.commit() - db.refresh(new_puck_event) - - # Send the response - return { - "message": "New tell position event created successfully", - "tell_position": new_puck_event.tell_position, - "timestamp": new_puck_event.timestamp, - } - - @router.get("/{puck_id}/last-tell-position", status_code=status.HTTP_200_OK) async def get_last_tell_position(puck_id: str, db: Session = Depends(get_db)): # Query the most recent tell_position_set event for the given puck_id diff --git a/backend/app/schemas.py b/backend/app/schemas.py index a46f94d..0e38c70 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -338,8 +338,27 @@ class SlotSchema(BaseModel): class SetTellPosition(BaseModel): - tell_position: str = Field( - ..., - pattern="^[A-F][1-5]$|^null$|^None$", # Use 'pattern' instead of 'regex' - description="Valid values are A1-A5, B1-B5, ..., F1-F5, or null.", + puckname: str # The puck name is required. + segment: Optional[str] = Field( + None, + pattern="^[A-F]$", # Valid segments are A, B, C, D, E, F + description="Segment must be one of A, B, C, D, E, or F." + "Can be null for no tell_position.", ) + puck_in_segment: Optional[int] = Field( + None, + ge=1, + le=5, + description="Puck in segment must be between 1 and 5." + "Can be null for no tell_position.", + ) + + @property + def tell_position(self) -> Optional[str]: + """ + Combines `segment` and `puck_in_segment` to generate the `tell_position`. + If either value is `None`, returns `None` to indicate no `tell_position`. + """ + if self.segment and self.puck_in_segment: + return f"{self.segment}{self.puck_in_segment}" + return None