From 3b315f2997bb5f12a781db34e856322356f1b249 Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:02:56 +0100 Subject: [PATCH] Add mount_count and unmount_count tracking for samples Introduced `mount_count` and `unmount_count` fields to track mounting events for samples. Updated models, schemas, and front-end components to support dynamic calculation and display of these counts. Enhanced backend queries and API responses to include the new data. --- backend/app/models.py | 10 +++++++ backend/app/routers/sample.py | 33 ++++++++++++++--------- backend/app/schemas.py | 3 +++ frontend/src/components/SampleTracker.tsx | 23 +++++++++++++++- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/backend/app/models.py b/backend/app/models.py index b692f3c..64b0bf8 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -151,6 +151,16 @@ class Sample(Base): puck = relationship("Puck", back_populates="samples") events = relationship("SampleEvent", back_populates="sample") + @property + def mount_count(self) -> int: + # Dynamically calculate mount_count + return len([event for event in self.events if event.event_type == "Mounted"]) + + @property + def unmount_count(self) -> int: + # Dynamically calculate unmount_count + return len([event for event in self.events if event.event_type == "Unmounted"]) + class Slot(Base): __tablename__ = "slots" diff --git a/backend/app/routers/sample.py b/backend/app/routers/sample.py index 248bba7..ae05f8b 100644 --- a/backend/app/routers/sample.py +++ b/backend/app/routers/sample.py @@ -9,6 +9,7 @@ from app.schemas import ( Sample as SampleSchema, SampleEventResponse, SampleEventCreate, + Sample, ) from app.models import ( Puck as PuckModel, @@ -17,6 +18,8 @@ from app.models import ( ) from app.dependencies import get_db import logging +from sqlalchemy.orm import joinedload + router = APIRouter() @@ -43,22 +46,25 @@ async def get_samples_with_events(puck_id: int, db: Session = Depends(get_db)): async def get_all_pucks_with_samples_and_events(db: Session = Depends(get_db)): logging.info("Fetching all pucks with samples and events") - pucks = db.query(PuckModel).all() - logging.info(f"Found {len(pucks)} pucks in the database") - for puck in pucks: - if puck.dewar_id is None: - puck.dewar_id = -1 - logging.info(f"Puck ID: {puck.id}, Name: {puck.puck_name}") + pucks = ( + db.query(PuckModel) + .options( + joinedload(PuckModel.samples).joinedload( + SampleModel.events + ), # Correct nested relationship + joinedload(PuckModel.events), # If Puck has its own events relationship + ) + .all() + ) if not pucks: - raise HTTPException( - status_code=404, detail="No pucks found in the database" - ) # More descriptive + raise HTTPException(status_code=404, detail="No pucks found in the database") + return pucks # Route to post a new sample event -@router.post("/samples/{sample_id}/events", response_model=SampleEventResponse) +@router.post("/samples/{sample_id}/events", response_model=Sample) async def create_sample_event( sample_id: int, event: SampleEventCreate, db: Session = Depends(get_db) ): @@ -77,10 +83,13 @@ async def create_sample_event( db.commit() db.refresh(sample_event) - return ( - sample_event # Response will automatically use the SampleEventResponse schema + # Load events for the sample to be serialized in the response + sample.events = ( + db.query(SampleEventModel).filter(SampleEventModel.sample_id == sample_id).all() ) + return sample # Return the sample, now including `mount_count` + # Route to fetch the last (most recent) sample event @router.get("/samples/{sample_id}/events/last", response_model=SampleEventResponse) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index bf981b2..95ccdb9 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -424,6 +424,9 @@ class Sample(BaseModel): comments: Optional[str] = None data_collection_parameters: Optional[DataCollectionParameters] events: List[SampleEventResponse] = [] + mount_count: Optional[int] = None + unmount_count: Optional[int] = None + # results: Optional[Results] = None class Config: from_attributes = True diff --git a/frontend/src/components/SampleTracker.tsx b/frontend/src/components/SampleTracker.tsx index b51e5d5..c7f9e63 100644 --- a/frontend/src/components/SampleTracker.tsx +++ b/frontend/src/components/SampleTracker.tsx @@ -13,6 +13,8 @@ interface Sample { crystalname?: string; positioninpuck?: number; events: Event[]; + mount_count: number; // Add this + unmount_count: number; // Add this } interface Puck { @@ -32,6 +34,8 @@ const SampleTracker: React.FC = () => { const fetchPucks = async () => { try { const data: Puck[] = await SamplesService.getAllPucksWithSamplesAndEventsSamplesPucksSamplesGet(); + + console.log('Fetched Pucks:', data); // Check for dynamic mount_count and unmount_count setPucks(data); } catch (error) { console.error('Error fetching pucks', error); @@ -110,12 +114,29 @@ const SampleTracker: React.FC = () => { border: sample && sample.events.some((e) => e.event_type === 'Lost') ? '1px solid red' : '1px solid lightgray', + position: 'relative', // Add for overlay positioning }} onMouseEnter={() => sample && setHoveredSample({ name: sample.sample_name, status }) } onMouseLeave={() => setHoveredSample(null)} - > + > + {sample && sample.mount_count > 0 && ( // Render only if mount_count > 0 + + {sample.mount_count} + + )} + ); })}