retrieved event working
This commit is contained in:
parent
c7e6c0390e
commit
fc8bb8d200
@ -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:
|
||||
|
@ -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")
|
||||
|
@ -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'}")
|
@ -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
|
Binary file not shown.
@ -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;
|
Loading…
x
Reference in New Issue
Block a user