Refactor set_tell_positions logic with updated rules.
Revised the set_tell_positions endpoint to handle updated business rules for puck positioning. Improved event handling to ensure proper nullification, updates, and removal of tell_positions based on the provided payload. Enhanced query performance and normalized puck name processing for consistency.
This commit is contained in:
parent
4f73f41717
commit
6c91bc78da
@ -45,25 +45,34 @@ async def get_pucks(db: Session = Depends(get_db)):
|
||||
|
||||
@router.put("/set-tell-positions", status_code=status.HTTP_200_OK)
|
||||
async def set_tell_positions(
|
||||
pucks: List[SetTellPosition], # Accept a list of SetTellPosition Pydantic models
|
||||
pucks: List[SetTellPosition], # Accept a validated Pydantic model
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Set the tell positions for multiple pucks based on the business rules.
|
||||
Set tell positions for multiple pucks with updated rules.
|
||||
|
||||
Args:
|
||||
pucks (List[SetTellPosition]): A list of puck definitions
|
||||
with potential new positions:
|
||||
- `puck_name` (Optional[str]): The name of the puck to update.
|
||||
- `segment` (Optional[str]): The segment (A-F) in the dewar.
|
||||
- `puck_in_segment` (Optional[int]): The position within the segment (1-5).
|
||||
pucks (List[SetTellPosition]): A list including puck_name,
|
||||
segment, and puck_in_segment.
|
||||
|
||||
Rules:
|
||||
1. If a puck already has a tell_position matching the payload,
|
||||
it is ignored (no new timestamps).
|
||||
2. If a puck is assigned a different position,
|
||||
set the new position while nullifying the previous.
|
||||
3. Pucks that have a last `tell_position_set` event with
|
||||
a non-null `tell_position` but
|
||||
are not in the payload will get
|
||||
a `"puck_removed"` event nullifying their position.
|
||||
4. If the last event for a puck is already null
|
||||
or it has no events, nothing is added or updated.
|
||||
|
||||
Returns:
|
||||
List[dict]: A list of results indicating the status of each puck update.
|
||||
List[dict]: Status information for processed and ignored pucks.
|
||||
"""
|
||||
results = []
|
||||
|
||||
# Helper function: Validate and normalize puck names
|
||||
# Helper function to normalize puck names for database querying
|
||||
def normalize_puck_name(name: str) -> str:
|
||||
return str(name).strip().replace(" ", "_").upper()
|
||||
|
||||
@ -73,33 +82,37 @@ async def set_tell_positions(
|
||||
detail="Payload cannot be empty. Provide at least one puck.",
|
||||
)
|
||||
|
||||
# Normalize payload puck names
|
||||
input_puck_names = {normalize_puck_name(p.puck_name): p for p in pucks}
|
||||
|
||||
# Retrieve all pucks in the database
|
||||
all_pucks_with_last_event = (
|
||||
db.query(PuckModel, PuckEventModel)
|
||||
.outerjoin(PuckEventModel, PuckEventModel.puck_id == PuckModel.id)
|
||||
.filter(PuckEventModel.event_type == "tell_position_set")
|
||||
.order_by(PuckEventModel.puck_id, PuckEventModel.timestamp.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
# Dictionary mapping each puck's ID to its latest event
|
||||
last_events = {}
|
||||
for puck, last_event in all_pucks_with_last_event:
|
||||
if puck.id not in last_events:
|
||||
last_events[puck.id] = last_event
|
||||
|
||||
# Process pucks provided in the payload
|
||||
for puck_data in pucks:
|
||||
try:
|
||||
# Step 1: Extract data from the Pydantic model
|
||||
puckname = puck_data.puckname
|
||||
tell_position = (
|
||||
# Extract data from input
|
||||
puck_name = puck_data.puck_name
|
||||
new_position = (
|
||||
puck_data.tell_position
|
||||
) # Combines `segment` + `puck_in_segment`
|
||||
) # Combined from segment + puck_in_segment
|
||||
|
||||
if not puckname:
|
||||
# Step 3: If `puckname` is missing, clear tell positions for ALL pucks
|
||||
db.query(PuckEventModel).filter(
|
||||
PuckEventModel.tell_position.isnot(None)
|
||||
).update({"tell_position": None})
|
||||
db.commit()
|
||||
# Add the result for the clean-up case
|
||||
results.append(
|
||||
{
|
||||
"action": "clear",
|
||||
"message": "Tell positions cleared for all pucks.",
|
||||
}
|
||||
)
|
||||
break # No need to process further since all tell positions are cleared
|
||||
# Normalize puck name
|
||||
normalized_name = normalize_puck_name(puck_name)
|
||||
|
||||
# Step 2: Normalize puck name for database lookups
|
||||
normalized_name = normalize_puck_name(puckname)
|
||||
|
||||
# Query the puck from the database
|
||||
# Find puck in the database
|
||||
puck = (
|
||||
db.query(PuckModel)
|
||||
.filter(
|
||||
@ -110,86 +123,99 @@ async def set_tell_positions(
|
||||
)
|
||||
|
||||
if not puck:
|
||||
raise ValueError(f"Puck with name '{puckname}' not found.")
|
||||
raise ValueError(f"Puck with name '{puck_name}' not found.")
|
||||
|
||||
# Query the most recent tell_position for this puck
|
||||
last_tell_position_event = (
|
||||
db.query(PuckEventModel)
|
||||
.filter(
|
||||
PuckEventModel.puck_id == puck.id,
|
||||
PuckEventModel.tell_position.isnot(None),
|
||||
# Query the last event for this puck
|
||||
last_event = last_events.get(puck.id)
|
||||
|
||||
# Rule 1: Skip if the last event's tell_position matches the new position
|
||||
if last_event and last_event.tell_position == new_position:
|
||||
results.append(
|
||||
{
|
||||
"puck_name": puck.puck_name,
|
||||
"current_position": new_position,
|
||||
"status": "unchanged",
|
||||
"message": "No change in tell_position. No event created.",
|
||||
}
|
||||
)
|
||||
.order_by(PuckEventModel.timestamp.desc())
|
||||
.first()
|
||||
)
|
||||
continue
|
||||
|
||||
if tell_position:
|
||||
# Step 4: Compare `tell_position` in the payload with the current one
|
||||
if (
|
||||
last_tell_position_event
|
||||
and last_tell_position_event.tell_position == tell_position
|
||||
):
|
||||
# Step 4.1: If positions match, do nothing
|
||||
results.append(
|
||||
{
|
||||
"puck_name": puck.puck_name,
|
||||
"current_position": tell_position,
|
||||
"status": "unchanged",
|
||||
"message": "The tell_position remains the same.",
|
||||
}
|
||||
)
|
||||
continue # Skip to the next puck
|
||||
# Rule 2: If the last tell_position is not None, nullify it with a
|
||||
# puck_removed event
|
||||
if last_event and last_event.tell_position is not None:
|
||||
remove_event = PuckEventModel(
|
||||
puck_id=puck.id,
|
||||
tell_position=None,
|
||||
event_type="puck_removed", # Set event_type to "puck_removed"
|
||||
timestamp=datetime.utcnow(),
|
||||
)
|
||||
db.add(remove_event)
|
||||
|
||||
# Step 4.2: If the position is different, update it
|
||||
if last_tell_position_event:
|
||||
# Clear the previous tell_position (set it to null)
|
||||
last_tell_position_event.tell_position = None
|
||||
db.add(last_tell_position_event)
|
||||
|
||||
# Add a new event with the updated tell_position
|
||||
# Add a new event with the updated tell_position
|
||||
if new_position:
|
||||
new_event = PuckEventModel(
|
||||
puck_id=puck.id,
|
||||
tell_position=tell_position,
|
||||
tell_position=new_position,
|
||||
event_type="tell_position_set",
|
||||
timestamp=datetime.utcnow(),
|
||||
)
|
||||
db.add(new_event)
|
||||
|
||||
results.append(
|
||||
{
|
||||
"puck_name": puck.puck_name,
|
||||
"previous_position": last_tell_position_event.tell_position
|
||||
if last_tell_position_event
|
||||
"new_position": new_position,
|
||||
"previous_position": last_event.tell_position
|
||||
if last_event
|
||||
else None,
|
||||
"new_position": tell_position,
|
||||
"status": "updated",
|
||||
"message": "The tell_position was updated successfully.",
|
||||
}
|
||||
)
|
||||
else:
|
||||
# Step 5: If the new tell_position is None, clear the current one
|
||||
if last_tell_position_event:
|
||||
last_tell_position_event.tell_position = None
|
||||
db.add(last_tell_position_event)
|
||||
|
||||
results.append(
|
||||
{
|
||||
"puck_name": puck.puck_name,
|
||||
"previous_position": last_tell_position_event.tell_position
|
||||
if last_tell_position_event
|
||||
else None,
|
||||
"new_position": None,
|
||||
"status": "cleared",
|
||||
"message": "The tell_position was cleared.",
|
||||
}
|
||||
)
|
||||
|
||||
# Commit transaction for updating tell_position
|
||||
# Commit changes after processing each puck
|
||||
db.commit()
|
||||
|
||||
except Exception as e:
|
||||
# Handle errors for individual pucks and continue processing others
|
||||
results.append({"puck_name": puckname, "error": str(e)})
|
||||
# Handle individual puck errors
|
||||
results.append({"puck_name": puck_data.puck_name, "error": str(e)})
|
||||
|
||||
# Process pucks not included in the payload but present in the DB
|
||||
for puck_id, last_event in last_events.items():
|
||||
puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first()
|
||||
normalized_name = normalize_puck_name(puck.puck_name)
|
||||
|
||||
# Check if the puck is missing from the payload
|
||||
if (
|
||||
normalized_name not in input_puck_names
|
||||
and last_event
|
||||
and last_event.tell_position is not None
|
||||
):
|
||||
try:
|
||||
# Add a puck_removed event
|
||||
remove_event = PuckEventModel(
|
||||
puck_id=puck.id,
|
||||
tell_position=None,
|
||||
event_type="puck_removed", # Set event_type to "puck_removed"
|
||||
timestamp=datetime.utcnow(),
|
||||
)
|
||||
db.add(remove_event)
|
||||
|
||||
# Append to results
|
||||
results.append(
|
||||
{
|
||||
"puck_name": puck.puck_name,
|
||||
"removed_position": last_event.tell_position,
|
||||
"status": "removed",
|
||||
"message": "Puck is not in payload"
|
||||
" and has been marked as removed from tell_position.",
|
||||
}
|
||||
)
|
||||
|
||||
db.commit()
|
||||
|
||||
except Exception as e:
|
||||
# Handle errors for individual puck removal
|
||||
results.append({"puck_name": puck.puck_name, "error": str(e)})
|
||||
|
||||
return results
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user