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.
This commit is contained in:
GotthardG 2024-12-19 13:21:37 +01:00
parent deeee02211
commit 14eaca81c6
2 changed files with 138 additions and 49 deletions

View File

@ -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

View File

@ -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