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.
This commit is contained in:
GotthardG 2025-01-20 11:02:56 +01:00
parent 3d804c1635
commit 3b315f2997
4 changed files with 56 additions and 13 deletions

View File

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

View File

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

View File

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

View File

@ -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)}
></div>
>
{sample && sample.mount_count > 0 && ( // Render only if mount_count > 0
<span
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
color: 'white',
fontSize: '8px',
fontWeight: 'bold',
}}
>
{sample.mount_count}
</span>
)}
</div>
);
})}
</div>