retrieved event working

This commit is contained in:
GotthardG 2024-11-22 22:04:44 +01:00
parent c7e6c0390e
commit fc8bb8d200
6 changed files with 155 additions and 112 deletions

View File

@ -11,8 +11,8 @@ slotQRCodes = [
"C1-X10SA", "C2-X10SA", "C3-X10SA", "C4-X10SA", "C5-X10SA",
"D1-X10SA", "D2-X10SA", "D3-X10SA", "D4-X10SA", "D5-X10SA",
"NB1", "NB2", "NB3", "NB4", "NB5", "NB6",
"X10SA-beamline", "X06SA-beamline", "X06DA-beamline",
"X10SA-outgoing", "X06-outgoing"
"X10SA-Beamline", "X06SA-Beamline", "X06DA-Beamline",
"Outgoing X10SA", "Outgoing X06SA"
]
def timedelta_to_str(td: timedelta) -> str:

View File

@ -143,8 +143,7 @@ class Slot(Base):
qr_base = Column(String, nullable=True)
occupied = Column(Boolean, default=False)
needs_refill = Column(Boolean, default=False)
dewar_name = Column(String) # Ensure this field exists
dewar_unique_id = Column(String, ForeignKey('dewars.unique_id'), nullable=True) # Added field
dewar_unique_id = Column(String, ForeignKey('dewars.unique_id'), nullable=True)
dewar = relationship("Dewar", back_populates="slot")
events = relationship("LogisticsEvent", back_populates="slot")
@ -152,8 +151,8 @@ class LogisticsEvent(Base):
__tablename__ = "logistics_events"
id = Column(Integer, primary_key=True, index=True)
dewar_id = Column(Integer, ForeignKey('dewars.id')) # corrected table name
slot_id = Column(Integer, ForeignKey('slots.id')) # corrected table name
dewar_id = Column(Integer, ForeignKey('dewars.id'))
slot_id = Column(Integer, ForeignKey('slots.id'))
event_type = Column(String, index=True)
timestamp = Column(DateTime, default=datetime.utcnow)
dewar = relationship("Dewar", back_populates="events")

View File

@ -1,5 +1,5 @@
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, joinedload
from typing import List, Optional
from ..models import Dewar as DewarModel, Slot as SlotModel, LogisticsEvent as LogisticsEventModel
from ..schemas import LogisticsEventCreate, SlotSchema, Dewar as DewarSchema
@ -13,7 +13,7 @@ logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def calculate_time_until_refill(last_refill: Optional[datetime], refill_interval_hours: int = 24) -> int:
def calculate_time_until_refill(last_refill: Optional[datetime], refill_interval_hours: int = 1) -> int:
refill_interval = timedelta(hours=refill_interval_hours)
now = datetime.now()
@ -24,42 +24,124 @@ def calculate_time_until_refill(last_refill: Optional[datetime], refill_interval
return max(0, int(time_until_next_refill.total_seconds()))
@router.post("/dewars/retrieve", response_model=DewarSchema)
async def retrieve_dewar(data: LogisticsEventCreate, db: Session = Depends(get_db)):
logger.info(f"Received data for retrieve_dewar: {data}")
dewar = db.query(DewarModel).filter(DewarModel.unique_id == data.dewar_qr_code).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
new_event = LogisticsEventModel(
dewar_id=dewar.id,
slot_id=None,
event_type="retrieved",
timestamp=datetime.now(),
)
db.add(new_event)
db.commit()
db.refresh(dewar)
# Get the last retrieved event for the dewar
last_retrieved_event = db.query(LogisticsEventModel).filter(
LogisticsEventModel.dewar_id == dewar.id,
LogisticsEventModel.event_type == "retrieved"
).order_by(LogisticsEventModel.timestamp.desc()).first()
if last_retrieved_event:
dewar.last_retrieved_timestamp = last_retrieved_event.timestamp.isoformat()
logger.info(f"Last retrieved event timestamp for dewar {dewar.unique_id}: "
f"{dewar.last_retrieved_timestamp}")
else:
dewar.last_retrieved_timestamp = None
logger.info(f"No retrieved event found for dewar {dewar.unique_id}")
return dewar
@router.post("/dewar/scan", response_model=dict)
async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)):
dewar_qr_code = event_data.dewar_qr_code
location_qr_code = event_data.location_qr_code
transaction_type = event_data.transaction_type
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
slot = db.query(SlotModel).filter(SlotModel.qr_code == location_qr_code).first()
if transaction_type == 'incoming':
if not slot or slot.occupied:
raise HTTPException(status_code=400, detail="Slot not found or already occupied")
slot.dewar_unique_id = dewar.unique_id
slot.occupied = True
elif transaction_type == 'outgoing':
if not slot or not slot.occupied or slot.dewar_unique_id != dewar.unique_id:
raise HTTPException(status_code=400, detail="Slot not found or dewar not associated with slot")
slot.dewar_unique_id = None
slot.occupied = False
elif transaction_type == 'beamline':
if not slot:
raise HTTPException(status_code=400, detail="Beamline location not found")
dewar.beamline_location = location_qr_code
# Create a logistics event
log_event(db, dewar.id, slot.id if slot else None, transaction_type)
db.commit()
return {"message": "Status updated successfully"}
@router.get("/slots", response_model=List[SlotSchema])
async def get_all_slots(db: Session = Depends(get_db)):
slots = db.query(SlotModel).all()
slots = db.query(SlotModel).options(joinedload(SlotModel.dewar)).all()
slots_with_refill_time = []
for slot in slots:
time_until_refill = None
if slot.dewar_unique_id:
logger.info(f"Fetching last refill event for dewar: {slot.dewar_unique_id}")
retrievedTimestamp = None
try:
last_refill_event = db.query(LogisticsEventModel) \
.join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \
.filter(
DewarModel.unique_id == slot.dewar_unique_id,
LogisticsEventModel.event_type == "refill"
) \
.order_by(LogisticsEventModel.timestamp.desc()) \
.first()
except Exception as e:
logger.error(f"Error querying last refill event: {str(e)}")
last_refill_event = None
if slot.dewar_unique_id:
last_refill_event = db.query(LogisticsEventModel) \
.join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \
.filter(
DewarModel.unique_id == slot.dewar.unique_id,
LogisticsEventModel.event_type == "refill"
) \
.order_by(LogisticsEventModel.timestamp.desc()) \
.first()
if last_refill_event:
last_refill = last_refill_event.timestamp
time_until_refill = calculate_time_until_refill(last_refill)
logger.info(f"Slot ID: {slot.id}, Last Refill: {last_refill}, Time Until Refill: {time_until_refill}")
else:
logger.warning(f"Slot ID: {slot.id} for dewar id '{slot.dewar_unique_id}' has no refill events.")
time_until_refill = -1
else:
logger.warning(f"Slot ID: {slot.id} has no dewar associated.")
time_until_refill = -1
# Ensure Dewar.name is optional if it may not exist in Dewar schema.
dewar_name = slot.dewar.name if slot.dewar and hasattr(slot.dewar, 'name') else None
# Get last retrieved timestamp
last_retrieved_event = db.query(LogisticsEventModel) \
.join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \
.filter(
DewarModel.unique_id == slot.dewar.unique_id,
LogisticsEventModel.event_type == "retrieved"
) \
.order_by(LogisticsEventModel.timestamp.desc()) \
.first()
if last_retrieved_event:
retrievedTimestamp = last_retrieved_event.timestamp.isoformat()
# Determine if the dewar is at the beamline
at_beamline = False
if slot.dewar_unique_id:
at_beamline_event = db.query(LogisticsEventModel) \
.join(DewarModel, DewarModel.id == LogisticsEventModel.dewar_id) \
.filter(
DewarModel.unique_id == slot.dewar.unique_id,
LogisticsEventModel.event_type == "beamline"
) \
.order_by(LogisticsEventModel.timestamp.desc()) \
.first()
at_beamline = bool(at_beamline_event)
slot_data = SlotSchema(
id=slot.id,
@ -69,29 +151,23 @@ async def get_all_slots(db: Session = Depends(get_db)):
occupied=slot.occupied,
needs_refill=slot.needs_refill,
dewar_unique_id=slot.dewar_unique_id,
dewar_name=dewar_name, # Using the optional dewar_name
time_until_refill=(time_until_refill or -1)
dewar_name=slot.dewar.dewar_name if slot.dewar else None,
time_until_refill=time_until_refill,
at_beamline=at_beamline,
retrievedTimestamp=retrievedTimestamp,
)
logger.info(f"Dewar retrieved on the: {retrievedTimestamp}")
slots_with_refill_time.append(slot_data)
return slots_with_refill_time
@router.get("/dewars", response_model=List[DewarSchema])
async def get_all_dewars(db: Session = Depends(get_db)):
dewars = db.query(DewarModel).all()
return dewars
return slots_with_refill_time
@router.post("/dewar/refill", response_model=dict)
async def refill_dewar(qr_code: str, db: Session = Depends(get_db)):
logger.info(f"Refilling dewar with QR code: {qr_code}")
if not isinstance(qr_code, str):
raise HTTPException(status_code=400, detail="Invalid QR code format")
dewar = db.query(DewarModel).filter(DewarModel.unique_id == qr_code.strip()).first()
if not dewar:
logger.error("Dewar not found")
raise HTTPException(status_code=404, detail="Dewar not found")
@ -112,6 +188,13 @@ async def refill_dewar(qr_code: str, db: Session = Depends(get_db)):
return {"message": "Dewar refilled successfully", "time_until_refill": time_until_refill_seconds}
@router.get("/dewars", response_model=List[DewarSchema])
async def get_all_dewars(db: Session = Depends(get_db)):
dewars = db.query(DewarModel).all()
return dewars
@router.get("/dewar/{unique_id}", response_model=DewarSchema)
async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)):
logger.info(f"Received request for dewar with unique_id: {unique_id}")
@ -123,39 +206,13 @@ async def get_dewar_by_unique_id(unique_id: str, db: Session = Depends(get_db)):
return dewar
@router.post("/dewar/scan", response_model=dict)
async def scan_dewar(event_data: LogisticsEventCreate, db: Session = Depends(get_db)):
dewar_qr_code = event_data.dewar_qr_code
location_qr_code = event_data.location_qr_code
transaction_type = event_data.transaction_type
logger.info(f"Scanning dewar {dewar_qr_code} for slot {location_qr_code} with transaction type {transaction_type}")
dewar = db.query(DewarModel).filter(DewarModel.unique_id == dewar_qr_code).first()
if not dewar:
raise HTTPException(status_code=404, detail="Dewar not found")
slot = db.query(SlotModel).filter(SlotModel.qr_code == location_qr_code).first()
if transaction_type == 'incoming':
if not slot or slot.occupied:
raise HTTPException(status_code=400, detail="Slot not found or already occupied")
logger.info(f"Associating dewar {dewar.unique_id} with slot {slot.qr_code}")
slot.dewar_unique_id = dewar.unique_id # Properly associate with the unique_id
slot.occupied = True
elif transaction_type == 'outgoing':
if not slot or not slot.occupied or slot.dewar_unique_id != dewar.unique_id:
raise HTTPException(status_code=400, detail="Slot not found or dewar not associated with slot")
logger.info(f"Disassociating dewar {dewar.unique_id} from slot {slot.qr_code}")
slot.dewar_unique_id = None # Remove the association
slot.occupied = False
log_event(db, dewar.id, slot.id if slot else None, transaction_type)
db.commit()
return {"message": "Status updated successfully"}
def log_event(db: Session, dewar_id: int, slot_id: int, event_type: str):
new_event = LogisticsEventModel(dewar_id=dewar_id, slot_id=slot_id, event_type=event_type)
def log_event(db: Session, dewar_id: int, slot_id: Optional[int], event_type: str):
new_event = LogisticsEventModel(
dewar_id=dewar_id,
slot_id=slot_id,
event_type=event_type,
timestamp=datetime.now()
)
db.add(new_event)
db.commit()
db.commit()
logger.info(f"Logged event: {event_type} for dewar: {dewar_id} in slot: {slot_id if slot_id else 'N/A'}")

View File

@ -286,7 +286,9 @@ class SlotSchema(BaseModel):
needs_refill: bool
dewar_unique_id: Optional[str]
dewar_name: Optional[str]
time_until_refill: Optional[int] # Ensure this field is defined
time_until_refill: Optional[int]
at_beamline: Optional[bool]
retrievedTimestamp: Optional[str]
class Config:
from_attributes = True

View File

@ -2,8 +2,8 @@ import React, { useEffect } from 'react';
import { Box, Typography, Button, Alert } from '@mui/material';
import styled from 'styled-components';
import LocalGasStationIcon from '@mui/icons-material/LocalGasStation';
import LocationOnIcon from '@mui/icons-material/LocationOn'; // New import for location indication
import CountdownTimer from './CountdownTimer';
import QRCode from 'react-qr-code';
export interface SlotData {
id: string;
@ -15,6 +15,7 @@ export interface SlotData {
dewar_name?: string;
needs_refill?: boolean;
time_until_refill?: number;
at_beamline?: boolean; // New property to indicate the dewar is at the beamline
}
interface SlotProps {
@ -28,6 +29,7 @@ interface SlotProps {
interface StyledSlotProps {
isSelected: boolean;
isOccupied: boolean;
atBeamline: boolean;
}
const StyledSlot = styled(Box)<StyledSlotProps>`
@ -35,8 +37,8 @@ const StyledSlot = styled(Box)<StyledSlotProps>`
margin: 8px;
width: 150px;
height: 260px;
background-color: ${({ isSelected, isOccupied }) =>
isSelected ? '#3f51b5' : isOccupied ? '#f44336' : '#4caf50'};
background-color: ${({ isSelected, isOccupied, atBeamline }) =>
atBeamline ? '#ff9800' : isSelected ? '#3f51b5' : isOccupied ? '#f44336' : '#4caf50'};
color: white;
cursor: pointer;
display: flex;
@ -52,12 +54,6 @@ const StyledSlot = styled(Box)<StyledSlotProps>`
}
`;
const QRCodeContainer = styled.div`
padding: 8px;
background-color: white;
border-radius: 8px;
`;
const BottleIcon: React.FC<{ fillHeight: number }> = ({ fillHeight }) => {
const pixelHeight = (276.777 * fillHeight) / 100;
const yPosition = 276.777 - pixelHeight;
@ -80,49 +76,38 @@ const BottleIcon: React.FC<{ fillHeight: number }> = ({ fillHeight }) => {
};
const Slot: React.FC<SlotProps> = ({ data, isSelected, onSelect, onRefillDewar, reloadSlots }) => {
const { id, qr_code, label, qr_base, occupied, needs_refill, time_until_refill, dewar_unique_id, dewar_name, at_beamline } = data;
const calculateFillHeight = (timeUntilRefill?: number) => {
if (timeUntilRefill === undefined || timeUntilRefill <= 0) {
return 0;
}
const maxTime = 86400;
const maxTime = 3600;
return Math.min((timeUntilRefill / maxTime) * 100, 100);
};
const fillHeight = calculateFillHeight(data.time_until_refill);
const fillHeight = calculateFillHeight(time_until_refill);
useEffect(() => {
if (data.time_until_refill !== undefined) {
console.log(`Updated time_until_refill: ${data.time_until_refill}`);
if (time_until_refill !== undefined) {
console.log(`Updated time_until_refill: ${time_until_refill}`);
}
}, [data.time_until_refill]);
}, [time_until_refill]);
const handleRefill = async () => {
if (data.dewar_unique_id) {
await onRefillDewar(data.dewar_unique_id);
if (dewar_unique_id) {
await onRefillDewar(dewar_unique_id);
reloadSlots();
}
};
const { id, qr_code, label, qr_base, occupied, needs_refill, time_until_refill, dewar_unique_id, dewar_name, ...rest } = data;
return (
<StyledSlot
isSelected={isSelected}
isOccupied={occupied}
onClick={() => onSelect(data)}
{...rest}
>
<StyledSlot isSelected={isSelected} isOccupied={occupied} atBeamline={!!at_beamline} onClick={() => onSelect(data)}>
<Typography variant="h6">{label}</Typography>
{dewar_name && <Typography variant="body2">{`Dewar: ${dewar_name}`}</Typography>}
{dewar_unique_id && (
<QRCodeContainer>
<QRCode value={dewar_unique_id} size={64} />
</QRCodeContainer>
)}
{dewar_name && <Typography variant="body2">{`${dewar_name}`}</Typography>} {/* Ensure correct dewar_name */}
{needs_refill && <LocalGasStationIcon />}
{dewar_unique_id && (
<BottleIcon fillHeight={fillHeight} />
)}
{dewar_unique_id && <BottleIcon fillHeight={fillHeight} />}
{at_beamline && <Typography style={{fontSize: 12}}><LocationOnIcon /> At Beamline</Typography>} {/* Indicate at beamline */}
{(dewar_unique_id && time_until_refill !== undefined && time_until_refill !== -1) ? (
<CountdownTimer key={dewar_unique_id} totalSeconds={time_until_refill} />
) : null}
@ -136,6 +121,6 @@ const Slot: React.FC<SlotProps> = ({ data, isSelected, onSelect, onRefillDewar,
)}
</StyledSlot>
);
};
}
export default Slot;