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)
|
@router.put("/set-tell-positions", status_code=status.HTTP_200_OK)
|
||||||
async def set_tell_positions(
|
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),
|
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:
|
Args:
|
||||||
pucks (List[SetTellPosition]): A list of puck definitions
|
pucks (List[SetTellPosition]): A list including puck_name,
|
||||||
with potential new positions:
|
segment, and puck_in_segment.
|
||||||
- `puck_name` (Optional[str]): The name of the puck to update.
|
|
||||||
- `segment` (Optional[str]): The segment (A-F) in the dewar.
|
Rules:
|
||||||
- `puck_in_segment` (Optional[int]): The position within the segment (1-5).
|
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:
|
Returns:
|
||||||
List[dict]: A list of results indicating the status of each puck update.
|
List[dict]: Status information for processed and ignored pucks.
|
||||||
"""
|
"""
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
# Helper function: Validate and normalize puck names
|
# Helper function to normalize puck names for database querying
|
||||||
def normalize_puck_name(name: str) -> str:
|
def normalize_puck_name(name: str) -> str:
|
||||||
return str(name).strip().replace(" ", "_").upper()
|
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.",
|
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:
|
for puck_data in pucks:
|
||||||
try:
|
try:
|
||||||
# Step 1: Extract data from the Pydantic model
|
# Extract data from input
|
||||||
puckname = puck_data.puckname
|
puck_name = puck_data.puck_name
|
||||||
tell_position = (
|
new_position = (
|
||||||
puck_data.tell_position
|
puck_data.tell_position
|
||||||
) # Combines `segment` + `puck_in_segment`
|
) # Combined from segment + puck_in_segment
|
||||||
|
|
||||||
if not puckname:
|
# Normalize puck name
|
||||||
# Step 3: If `puckname` is missing, clear tell positions for ALL pucks
|
normalized_name = normalize_puck_name(puck_name)
|
||||||
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
|
|
||||||
|
|
||||||
# Step 2: Normalize puck name for database lookups
|
# Find puck in the database
|
||||||
normalized_name = normalize_puck_name(puckname)
|
|
||||||
|
|
||||||
# Query the puck from the database
|
|
||||||
puck = (
|
puck = (
|
||||||
db.query(PuckModel)
|
db.query(PuckModel)
|
||||||
.filter(
|
.filter(
|
||||||
@ -110,86 +123,99 @@ async def set_tell_positions(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not puck:
|
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
|
# Query the last event for this puck
|
||||||
last_tell_position_event = (
|
last_event = last_events.get(puck.id)
|
||||||
db.query(PuckEventModel)
|
|
||||||
.filter(
|
# Rule 1: Skip if the last event's tell_position matches the new position
|
||||||
PuckEventModel.puck_id == puck.id,
|
if last_event and last_event.tell_position == new_position:
|
||||||
PuckEventModel.tell_position.isnot(None),
|
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())
|
continue
|
||||||
.first()
|
|
||||||
)
|
|
||||||
|
|
||||||
if tell_position:
|
# Rule 2: If the last tell_position is not None, nullify it with a
|
||||||
# Step 4: Compare `tell_position` in the payload with the current one
|
# puck_removed event
|
||||||
if (
|
if last_event and last_event.tell_position is not None:
|
||||||
last_tell_position_event
|
remove_event = PuckEventModel(
|
||||||
and last_tell_position_event.tell_position == tell_position
|
puck_id=puck.id,
|
||||||
):
|
tell_position=None,
|
||||||
# Step 4.1: If positions match, do nothing
|
event_type="puck_removed", # Set event_type to "puck_removed"
|
||||||
results.append(
|
timestamp=datetime.utcnow(),
|
||||||
{
|
)
|
||||||
"puck_name": puck.puck_name,
|
db.add(remove_event)
|
||||||
"current_position": tell_position,
|
|
||||||
"status": "unchanged",
|
|
||||||
"message": "The tell_position remains the same.",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
continue # Skip to the next puck
|
|
||||||
|
|
||||||
# Step 4.2: If the position is different, update it
|
# Add a new event with the updated tell_position
|
||||||
if last_tell_position_event:
|
if new_position:
|
||||||
# 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
|
|
||||||
new_event = PuckEventModel(
|
new_event = PuckEventModel(
|
||||||
puck_id=puck.id,
|
puck_id=puck.id,
|
||||||
tell_position=tell_position,
|
tell_position=new_position,
|
||||||
event_type="tell_position_set",
|
event_type="tell_position_set",
|
||||||
timestamp=datetime.utcnow(),
|
timestamp=datetime.utcnow(),
|
||||||
)
|
)
|
||||||
db.add(new_event)
|
db.add(new_event)
|
||||||
|
|
||||||
results.append(
|
results.append(
|
||||||
{
|
{
|
||||||
"puck_name": puck.puck_name,
|
"puck_name": puck.puck_name,
|
||||||
"previous_position": last_tell_position_event.tell_position
|
"new_position": new_position,
|
||||||
if last_tell_position_event
|
"previous_position": last_event.tell_position
|
||||||
|
if last_event
|
||||||
else None,
|
else None,
|
||||||
"new_position": tell_position,
|
|
||||||
"status": "updated",
|
"status": "updated",
|
||||||
"message": "The tell_position was updated successfully.",
|
"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(
|
# Commit changes after processing each puck
|
||||||
{
|
|
||||||
"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
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Handle errors for individual pucks and continue processing others
|
# Handle individual puck errors
|
||||||
results.append({"puck_name": puckname, "error": str(e)})
|
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
|
return results
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user