Add validations and logging for puck beamtime assignment.

Introduced checks to prevent reassigning beamtime if puck samples have recorded events. Updated logging in beamline-related methods to provide more insight. Simplified data structure updates for dewars, pucks, and samples, ensuring consistency with beamtime assignments.
This commit is contained in:
GotthardG
2025-05-09 13:51:01 +02:00
parent 6a0953c913
commit 707c98c5ce
9 changed files with 303 additions and 114 deletions

View File

@ -709,9 +709,18 @@ dewar_to_beamtime = {
for dewar in dewars # Or use actual beamtime ids
}
# Update dewars and their pucks with consistent beamtime
for dewar in dewars:
dewar.beamtime_id = dewar_to_beamtime[dewar.id]
assigned_beamtime_obj = next(
b for b in beamtimes if b.id == dewar_to_beamtime[dewar.id]
)
dewar.beamtimes = [assigned_beamtime_obj]
for puck in pucks:
assigned_beamtime_obj = next(
b for b in beamtimes if b.id == dewar_to_beamtime[puck.dewar_id]
)
puck.beamtimes = [assigned_beamtime_obj]
for puck in pucks:
dewar_id = puck.dewar_id # Assuming puck has dewar_id

View File

@ -51,6 +51,8 @@ from app.crud import (
)
from app.routers.auth import get_current_user
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
dewar_router = APIRouter()
@ -599,6 +601,19 @@ async def assign_beamtime_to_dewar(
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
# Check if any sample (in any puck on this dewar) has sample events
for puck in dewar.pucks:
for sample in puck.samples:
sample_event_exists = (
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).first()
)
if sample_event_exists:
raise HTTPException(
status_code=400,
detail="Cannot change beamtime:"
"at least one sample has events recorded.",
)
# Find the Beamtime instance, if not unassigning
beamtime = (
db.query(BeamtimeModel).filter(BeamtimeModel.id == beamtime_id).first()
@ -609,9 +624,7 @@ async def assign_beamtime_to_dewar(
if beamtime_id == 0:
dewar.beamtimes = []
else:
dewar.beamtimes = [
beamtime
] # assign one; append if you want to support multiple
dewar.beamtimes = [beamtime]
db.commit()
db.refresh(dewar)
@ -621,15 +634,11 @@ async def assign_beamtime_to_dewar(
else:
puck.beamtimes = [beamtime]
for sample in puck.samples:
has_sample_event = (
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).count()
> 0
)
if not has_sample_event:
if beamtime_id == 0:
sample.beamtimes = []
else:
sample.beamtimes = [beamtime]
# Can assume all have no events because of previous check
if beamtime_id == 0:
sample.beamtimes = []
else:
sample.beamtimes = [beamtime]
db.commit()
return {"status": "success", "dewar_id": dewar.id, "beamtime_id": beamtime_id}
@ -746,6 +755,7 @@ async def get_single_shipment(id: int, db: Session = Depends(get_db)):
operation_id="get_dewars_by_beamtime",
)
async def get_dewars_by_beamtime(beamtime_id: int, db: Session = Depends(get_db)):
logger.info(f"get_dewars_by_beamtime called with beamtime_id={beamtime_id}")
beamtime = (
db.query(BeamtimeModel)
.options(joinedload(BeamtimeModel.dewars))
@ -753,5 +763,9 @@ async def get_dewars_by_beamtime(beamtime_id: int, db: Session = Depends(get_db)
.first()
)
if not beamtime:
logger.warning(f"Beamtime {beamtime_id} not found")
raise HTTPException(status_code=404, detail="Beamtime not found")
logger.info(
f"Returning {len(beamtime.dewars)} dewars: {[d.id for d in beamtime.dewars]}"
)
return beamtime.dewars

View File

@ -665,13 +665,25 @@ async def get_pucks_by_slot(slot_identifier: str, db: Session = Depends(get_db))
@router.patch("/puck/{puck_id}/assign-beamtime", operation_id="assignPuckToBeamtime")
async def assign_beamtime_to_puck(
puck_id: int,
beamtime_id: int, # expects ?beamtime_id=123 in the query
beamtime_id: int,
db: Session = Depends(get_db),
):
puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first()
if not puck:
raise HTTPException(status_code=404, detail="Puck not found")
# Check if any sample in this puck has sample events
for sample in puck.samples:
sample_event_exists = (
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).first()
)
if sample_event_exists:
raise HTTPException(
status_code=400,
detail="Cannot change beamtime:"
"at least one sample has events recorded.",
)
beamtime = (
db.query(BeamtimeModel).filter(BeamtimeModel.id == beamtime_id).first()
if beamtime_id
@ -681,22 +693,15 @@ async def assign_beamtime_to_puck(
if beamtime_id == 0:
puck.beamtimes = []
else:
puck.beamtimes = [
beamtime
] # or use .append(beamtime) if you want to support multiple
puck.beamtimes = [beamtime]
db.commit()
db.refresh(puck)
# Update samples as well
for sample in puck.samples:
has_sample_event = (
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).count() > 0
)
if not has_sample_event:
if beamtime_id == 0:
sample.beamtimes = []
else:
sample.beamtimes = [beamtime]
if beamtime_id == 0:
sample.beamtimes = []
else:
sample.beamtimes = [beamtime]
db.commit()
return {"status": "success", "puck_id": puck.id, "beamtime_id": beamtime_id}
@ -707,6 +712,7 @@ async def assign_beamtime_to_puck(
operation_id="get_pucks_by_beamtime",
)
async def get_pucks_by_beamtime(beamtime_id: int, db: Session = Depends(get_db)):
logger.info(f"get_pucks_by_beamtime called with beamtime_id={beamtime_id}")
beamtime = (
db.query(BeamtimeModel)
.options(joinedload(BeamtimeModel.pucks)) # eager load pucks
@ -714,5 +720,9 @@ async def get_pucks_by_beamtime(beamtime_id: int, db: Session = Depends(get_db))
.first()
)
if not beamtime:
logger.warning(f"Beamtime {beamtime_id} not found")
raise HTTPException(status_code=404, detail="Beamtime not found")
logger.info(
f"Returning {len(beamtime.pucks)} pucks: {[p.id for p in beamtime.pucks]}"
)
return beamtime.pucks

View File

@ -168,8 +168,8 @@ async def lifespan(app: FastAPI):
load_slots_data(db)
else: # dev or test environments
print(f"{environment.capitalize()} environment: Regenerating database.")
# Base.metadata.drop_all(bind=engine)
# Base.metadata.create_all(bind=engine)
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)
# from sqlalchemy.engine import reflection
# from app.models import ExperimentParameters # adjust the import as needed
# inspector = reflection.Inspector.from_engine(engine)