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:
parent
6a0953c913
commit
707c98c5ce
@ -709,9 +709,18 @@ dewar_to_beamtime = {
|
|||||||
for dewar in dewars # Or use actual beamtime ids
|
for dewar in dewars # Or use actual beamtime ids
|
||||||
}
|
}
|
||||||
|
|
||||||
# Update dewars and their pucks with consistent beamtime
|
|
||||||
for dewar in dewars:
|
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:
|
for puck in pucks:
|
||||||
dewar_id = puck.dewar_id # Assuming puck has dewar_id
|
dewar_id = puck.dewar_id # Assuming puck has dewar_id
|
||||||
|
@ -51,6 +51,8 @@ from app.crud import (
|
|||||||
)
|
)
|
||||||
from app.routers.auth import get_current_user
|
from app.routers.auth import get_current_user
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
dewar_router = APIRouter()
|
dewar_router = APIRouter()
|
||||||
|
|
||||||
@ -599,6 +601,19 @@ async def assign_beamtime_to_dewar(
|
|||||||
if not dewar:
|
if not dewar:
|
||||||
raise HTTPException(status_code=404, detail="Dewar not found")
|
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
|
# Find the Beamtime instance, if not unassigning
|
||||||
beamtime = (
|
beamtime = (
|
||||||
db.query(BeamtimeModel).filter(BeamtimeModel.id == beamtime_id).first()
|
db.query(BeamtimeModel).filter(BeamtimeModel.id == beamtime_id).first()
|
||||||
@ -609,9 +624,7 @@ async def assign_beamtime_to_dewar(
|
|||||||
if beamtime_id == 0:
|
if beamtime_id == 0:
|
||||||
dewar.beamtimes = []
|
dewar.beamtimes = []
|
||||||
else:
|
else:
|
||||||
dewar.beamtimes = [
|
dewar.beamtimes = [beamtime]
|
||||||
beamtime
|
|
||||||
] # assign one; append if you want to support multiple
|
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(dewar)
|
db.refresh(dewar)
|
||||||
@ -621,15 +634,11 @@ async def assign_beamtime_to_dewar(
|
|||||||
else:
|
else:
|
||||||
puck.beamtimes = [beamtime]
|
puck.beamtimes = [beamtime]
|
||||||
for sample in puck.samples:
|
for sample in puck.samples:
|
||||||
has_sample_event = (
|
# Can assume all have no events because of previous check
|
||||||
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).count()
|
if beamtime_id == 0:
|
||||||
> 0
|
sample.beamtimes = []
|
||||||
)
|
else:
|
||||||
if not has_sample_event:
|
sample.beamtimes = [beamtime]
|
||||||
if beamtime_id == 0:
|
|
||||||
sample.beamtimes = []
|
|
||||||
else:
|
|
||||||
sample.beamtimes = [beamtime]
|
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"status": "success", "dewar_id": dewar.id, "beamtime_id": beamtime_id}
|
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",
|
operation_id="get_dewars_by_beamtime",
|
||||||
)
|
)
|
||||||
async def get_dewars_by_beamtime(beamtime_id: int, db: Session = Depends(get_db)):
|
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 = (
|
beamtime = (
|
||||||
db.query(BeamtimeModel)
|
db.query(BeamtimeModel)
|
||||||
.options(joinedload(BeamtimeModel.dewars))
|
.options(joinedload(BeamtimeModel.dewars))
|
||||||
@ -753,5 +763,9 @@ async def get_dewars_by_beamtime(beamtime_id: int, db: Session = Depends(get_db)
|
|||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
if not beamtime:
|
if not beamtime:
|
||||||
|
logger.warning(f"Beamtime {beamtime_id} not found")
|
||||||
raise HTTPException(status_code=404, detail="Beamtime 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
|
return beamtime.dewars
|
||||||
|
@ -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")
|
@router.patch("/puck/{puck_id}/assign-beamtime", operation_id="assignPuckToBeamtime")
|
||||||
async def assign_beamtime_to_puck(
|
async def assign_beamtime_to_puck(
|
||||||
puck_id: int,
|
puck_id: int,
|
||||||
beamtime_id: int, # expects ?beamtime_id=123 in the query
|
beamtime_id: int,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first()
|
puck = db.query(PuckModel).filter(PuckModel.id == puck_id).first()
|
||||||
if not puck:
|
if not puck:
|
||||||
raise HTTPException(status_code=404, detail="Puck not found")
|
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 = (
|
beamtime = (
|
||||||
db.query(BeamtimeModel).filter(BeamtimeModel.id == beamtime_id).first()
|
db.query(BeamtimeModel).filter(BeamtimeModel.id == beamtime_id).first()
|
||||||
if beamtime_id
|
if beamtime_id
|
||||||
@ -681,22 +693,15 @@ async def assign_beamtime_to_puck(
|
|||||||
if beamtime_id == 0:
|
if beamtime_id == 0:
|
||||||
puck.beamtimes = []
|
puck.beamtimes = []
|
||||||
else:
|
else:
|
||||||
puck.beamtimes = [
|
puck.beamtimes = [beamtime]
|
||||||
beamtime
|
|
||||||
] # or use .append(beamtime) if you want to support multiple
|
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(puck)
|
db.refresh(puck)
|
||||||
# Update samples as well
|
|
||||||
for sample in puck.samples:
|
for sample in puck.samples:
|
||||||
has_sample_event = (
|
if beamtime_id == 0:
|
||||||
db.query(SampleEvent).filter(SampleEvent.sample_id == sample.id).count() > 0
|
sample.beamtimes = []
|
||||||
)
|
else:
|
||||||
if not has_sample_event:
|
sample.beamtimes = [beamtime]
|
||||||
if beamtime_id == 0:
|
|
||||||
sample.beamtimes = []
|
|
||||||
else:
|
|
||||||
sample.beamtimes = [beamtime]
|
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"status": "success", "puck_id": puck.id, "beamtime_id": beamtime_id}
|
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",
|
operation_id="get_pucks_by_beamtime",
|
||||||
)
|
)
|
||||||
async def get_pucks_by_beamtime(beamtime_id: int, db: Session = Depends(get_db)):
|
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 = (
|
beamtime = (
|
||||||
db.query(BeamtimeModel)
|
db.query(BeamtimeModel)
|
||||||
.options(joinedload(BeamtimeModel.pucks)) # eager load pucks
|
.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()
|
.first()
|
||||||
)
|
)
|
||||||
if not beamtime:
|
if not beamtime:
|
||||||
|
logger.warning(f"Beamtime {beamtime_id} not found")
|
||||||
raise HTTPException(status_code=404, detail="Beamtime 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
|
return beamtime.pucks
|
||||||
|
@ -168,8 +168,8 @@ async def lifespan(app: FastAPI):
|
|||||||
load_slots_data(db)
|
load_slots_data(db)
|
||||||
else: # dev or test environments
|
else: # dev or test environments
|
||||||
print(f"{environment.capitalize()} environment: Regenerating database.")
|
print(f"{environment.capitalize()} environment: Regenerating database.")
|
||||||
# Base.metadata.drop_all(bind=engine)
|
Base.metadata.drop_all(bind=engine)
|
||||||
# Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
# from sqlalchemy.engine import reflection
|
# from sqlalchemy.engine import reflection
|
||||||
# from app.models import ExperimentParameters # adjust the import as needed
|
# from app.models import ExperimentParameters # adjust the import as needed
|
||||||
# inspector = reflection.Inspector.from_engine(engine)
|
# inspector = reflection.Inspector.from_engine(engine)
|
||||||
|
@ -84,8 +84,31 @@ const App: React.FC = () => {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<LoginView />} />
|
<Route path="/login" element={<LoginView />} />
|
||||||
<Route path="/" element={<ProtectedRoute element={<HomePage />} />} />
|
<Route path="/" element={<ProtectedRoute element={<HomePage />} />} />
|
||||||
<Route path="/shipments" element={<ProtectedRoute element={<ShipmentView pgroups={pgroups} activePgroup={activePgroup} />} />} />
|
<Route path="/shipments"
|
||||||
<Route path="/planning" element={<ProtectedRoute element={<PlanningView />} />} />
|
element={
|
||||||
|
<ProtectedRoute
|
||||||
|
element={
|
||||||
|
<ShipmentView
|
||||||
|
pgroups={pgroups}
|
||||||
|
activePgroup={activePgroup}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/planning"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute
|
||||||
|
element={
|
||||||
|
<PlanningView
|
||||||
|
pgroups={pgroups}
|
||||||
|
activePgroup={activePgroup}
|
||||||
|
onPgroupChange={handlePgroupChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/results/:beamtimeId"
|
path="/results/:beamtimeId"
|
||||||
element={
|
element={
|
||||||
|
@ -5,6 +5,7 @@ import timeGridPlugin from '@fullcalendar/timegrid';
|
|||||||
import interactionPlugin from '@fullcalendar/interaction';
|
import interactionPlugin from '@fullcalendar/interaction';
|
||||||
import '../styles/Calendar.css';
|
import '../styles/Calendar.css';
|
||||||
import { BeamtimesService, DewarsService, PucksService } from '../../openapi';
|
import { BeamtimesService, DewarsService, PucksService } from '../../openapi';
|
||||||
|
import Chip from '@mui/material/Chip'
|
||||||
|
|
||||||
const beamlineColors: { [key: string]: string } = {
|
const beamlineColors: { [key: string]: string } = {
|
||||||
X06SA: '#FF5733',
|
X06SA: '#FF5733',
|
||||||
@ -18,8 +19,15 @@ interface CustomEvent extends EventInput {
|
|||||||
beamtime_shift: string;
|
beamtime_shift: string;
|
||||||
beamtime_id?: number;
|
beamtime_id?: number;
|
||||||
isSubmitted?: boolean;
|
isSubmitted?: boolean;
|
||||||
|
activePgroup?: string;
|
||||||
|
pgroups?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CalendarProps {
|
||||||
|
activePgroup: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const experimentModes = ['SDU-Scheduled', 'SDU-queued', 'Remote', 'In-person'];
|
const experimentModes = ['SDU-Scheduled', 'SDU-queued', 'Remote', 'In-person'];
|
||||||
|
|
||||||
const darkenColor = (color: string, percent: number): string => {
|
const darkenColor = (color: string, percent: number): string => {
|
||||||
@ -34,7 +42,7 @@ const darkenColor = (color: string, percent: number): string => {
|
|||||||
return `#${newColor}`;
|
return `#${newColor}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Calendar: React.FC = () => {
|
const Calendar = ({ activePgroup }: CalendarProps) => {
|
||||||
const [events, setEvents] = useState<CustomEvent[]>([]);
|
const [events, setEvents] = useState<CustomEvent[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [fetchError, setFetchError] = useState<string | null>(null);
|
const [fetchError, setFetchError] = useState<string | null>(null);
|
||||||
@ -59,6 +67,7 @@ const Calendar: React.FC = () => {
|
|||||||
setFetchError(null);
|
setFetchError(null);
|
||||||
try {
|
try {
|
||||||
const beamtimes = await BeamtimesService.getMyBeamtimesProtectedBeamtimesMyBeamtimesGet();
|
const beamtimes = await BeamtimesService.getMyBeamtimesProtectedBeamtimesMyBeamtimesGet();
|
||||||
|
console.log('Loaded beamtimes:', beamtimes);
|
||||||
const grouped: { [key: string]: any[] } = {};
|
const grouped: { [key: string]: any[] } = {};
|
||||||
beamtimes.forEach((beamtime: any) => {
|
beamtimes.forEach((beamtime: any) => {
|
||||||
const key = `${beamtime.start_date}|${beamtime.beamline}|${beamtime.pgroups}`;
|
const key = `${beamtime.start_date}|${beamtime.beamline}|${beamtime.pgroups}`;
|
||||||
@ -70,8 +79,9 @@ const Calendar: React.FC = () => {
|
|||||||
const shifts = group.map((bt: any) => bt.shift).join(" + ");
|
const shifts = group.map((bt: any) => bt.shift).join(" + ");
|
||||||
const ids = group.map((bt: any) => bt.id);
|
const ids = group.map((bt: any) => bt.id);
|
||||||
const first = group[0];
|
const first = group[0];
|
||||||
|
console.log(`[DEBUG] pgroups: ${first.pgroups}`); // Ensure the value of pgroups here is correct
|
||||||
return {
|
return {
|
||||||
id: `${first.beamline}-${first.start_date}-${first.pgroups}`, // ensure uniqueness
|
id: `${first.beamline}-${first.start_date}-${first.pgroups}`,
|
||||||
title: `${first.beamline}: ${shifts}`,
|
title: `${first.beamline}: ${shifts}`,
|
||||||
start: first.start_date,
|
start: first.start_date,
|
||||||
end: first.end_date,
|
end: first.end_date,
|
||||||
@ -82,6 +92,9 @@ const Calendar: React.FC = () => {
|
|||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
textColor: '#fff',
|
textColor: '#fff',
|
||||||
beamtimes: group,
|
beamtimes: group,
|
||||||
|
extendedProps: {
|
||||||
|
pgroups: first.pgroups, // Check that this is a valid, comma-separated string
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setEvents(formattedEvents);
|
setEvents(formattedEvents);
|
||||||
@ -89,6 +102,7 @@ const Calendar: React.FC = () => {
|
|||||||
|
|
||||||
// Fetch associations for all
|
// Fetch associations for all
|
||||||
const assoc: { [id: string]: { dewars: string[]; pucks: string[] } } = {};
|
const assoc: { [id: string]: { dewars: string[]; pucks: string[] } } = {};
|
||||||
|
console.log('Fetched associations after loading events:', assoc);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.values(grouped).map(async (group) => {
|
Object.values(grouped).map(async (group) => {
|
||||||
@ -103,6 +117,8 @@ const Calendar: React.FC = () => {
|
|||||||
DewarsService.getDewarsByBeamtime(beamtimeId),
|
DewarsService.getDewarsByBeamtime(beamtimeId),
|
||||||
PucksService.getPucksByBeamtime(beamtimeId),
|
PucksService.getPucksByBeamtime(beamtimeId),
|
||||||
]);
|
]);
|
||||||
|
console.log(`Dewars for beamtime ${beamtimeId}:`, dewars);
|
||||||
|
console.log(`Pucks for beamtime ${beamtimeId}:`, pucks);
|
||||||
dewars.forEach((d: any) => dewarsSet.add(d.id));
|
dewars.forEach((d: any) => dewarsSet.add(d.id));
|
||||||
pucks.forEach((p: any) => pucksSet.add(p.id));
|
pucks.forEach((p: any) => pucksSet.add(p.id));
|
||||||
})
|
})
|
||||||
@ -115,8 +131,10 @@ const Calendar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
console.log("Final eventAssociations:", assoc);
|
||||||
setEventAssociations(assoc);
|
setEventAssociations(assoc);
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFetchError('Failed to load beamtime data. Please try again later.');
|
setFetchError('Failed to load beamtime data. Please try again later.');
|
||||||
setEvents([]);
|
setEvents([]);
|
||||||
@ -250,39 +268,38 @@ const Calendar: React.FC = () => {
|
|||||||
? eventInfo.event.extendedProps.beamtimes.length
|
? eventInfo.event.extendedProps.beamtimes.length
|
||||||
: 1;
|
: 1;
|
||||||
const minHeight = beamtimesInGroup * 26;
|
const minHeight = beamtimesInGroup * 26;
|
||||||
const beamline = eventInfo.event.extendedProps.beamline || 'Unknown';
|
const beamline = eventInfo.event.extendedProps.beamline || 'Unknown';
|
||||||
const isSelected = selectedEventId === eventInfo.event.id;
|
const isSelected = selectedEventId === eventInfo.event.id;
|
||||||
const isSubmitted = eventInfo.event.extendedProps.isSubmitted;
|
const isSubmitted = eventInfo.event.extendedProps.isSubmitted;
|
||||||
const assoc = eventAssociations[eventInfo.event.id] || { dewars: [], pucks: [] };
|
const assoc = eventAssociations[eventInfo.event.id] || { dewars: [], pucks: [] };
|
||||||
const backgroundColor = isSubmitted
|
const backgroundColor = isSubmitted
|
||||||
? darkenColor(beamlineColors[beamline] || beamlineColors.Unknown, -20)
|
? darkenColor(beamlineColors[beamline] || beamlineColors.Unknown, -20)
|
||||||
: isSelected
|
: isSelected
|
||||||
? '#FFD700'
|
? '#FFD700'
|
||||||
: (beamlineColors[beamline] || beamlineColors.Unknown);
|
: (beamlineColors[beamline] || beamlineColors.Unknown);
|
||||||
|
return (
|
||||||
return (
|
<div
|
||||||
<div
|
style={{
|
||||||
style={{
|
backgroundColor: backgroundColor,
|
||||||
backgroundColor: backgroundColor,
|
color: 'white',
|
||||||
color: 'white',
|
border: isSelected ? '2px solid black' : 'none',
|
||||||
border: isSelected ? '2px solid black' : 'none',
|
borderRadius: '3px',
|
||||||
borderRadius: '3px',
|
display: 'flex',
|
||||||
display: 'flex',
|
justifyContent: 'space-between',
|
||||||
justifyContent: 'space-between',
|
alignItems: 'center',
|
||||||
alignItems: 'center',
|
height: '100%',
|
||||||
height: '100%',
|
width: '100%',
|
||||||
width: '100%',
|
cursor: 'pointer',
|
||||||
cursor: 'pointer',
|
overflow: 'hidden',
|
||||||
overflow: 'hidden',
|
boxSizing: 'border-box',
|
||||||
boxSizing: 'border-box',
|
padding: '0 6px',
|
||||||
padding: '0 6px',
|
minHeight: `${minHeight}px`,
|
||||||
minHeight: `${minHeight}px`,
|
}}
|
||||||
}}
|
>
|
||||||
>
|
|
||||||
<span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
<span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||||
{eventInfo.event.title}
|
{eventInfo.event.title}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ display: 'flex', alignItems: 'center', gap: 6, marginLeft: 8 }}>
|
<span style={{ display: 'flex', alignItems: 'center', gap: 6, marginLeft: 8 }}>
|
||||||
<span title="Dewars" style={{ display: 'flex', alignItems: 'center', fontSize: 13 }}>
|
<span title="Dewars" style={{ display: 'flex', alignItems: 'center', fontSize: 13 }}>
|
||||||
🧊
|
🧊
|
||||||
<span style={{
|
<span style={{
|
||||||
@ -311,13 +328,32 @@ const Calendar: React.FC = () => {
|
|||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
}}>{assoc.pucks.length}</span>
|
}}>{assoc.pucks.length}</span>
|
||||||
</span>
|
</span>
|
||||||
|
{eventInfo.event.extendedProps?.pgroups && eventInfo.event.extendedProps.pgroups.split(',')
|
||||||
|
.map((pgroup: string) => (
|
||||||
|
<Chip
|
||||||
|
key={pgroup.trim()}
|
||||||
|
label={pgroup.trim()}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
marginLeft: 0.5,
|
||||||
|
marginRight: 0.5,
|
||||||
|
backgroundColor: pgroup.trim() === activePgroup ? '#19d238' : '#b0b0b0',
|
||||||
|
color: pgroup.trim() === activePgroup ? 'white' : 'black',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
borderRadius: '8px',
|
||||||
|
height: '20px',
|
||||||
|
fontSize: '12px',
|
||||||
|
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)',
|
||||||
|
mr: 1,
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Used in Dewar/Puck assign status reporting
|
|
||||||
function getAssignedEventForDewar(dewarId: string) {
|
function getAssignedEventForDewar(dewarId: string) {
|
||||||
return Object.entries(eventAssociations).find(([eid, assoc]) =>
|
return Object.entries(eventAssociations).find(([eid, assoc]) =>
|
||||||
assoc.dewars.includes(dewarId)
|
assoc.dewars.includes(dewarId)
|
||||||
|
@ -39,13 +39,13 @@ interface ProcessingResults {
|
|||||||
resolution: number;
|
resolution: number;
|
||||||
unit_cell: string;
|
unit_cell: string;
|
||||||
spacegroup: string;
|
spacegroup: string;
|
||||||
rmerge: number;
|
rmerge: CCPoint[];
|
||||||
rmeas: number;
|
rmeas: CCPoint[];
|
||||||
isig: number;
|
isig: CCPoint[];
|
||||||
cc: CCPoint[];
|
cc: CCPoint[];
|
||||||
cchalf: CCPoint[];
|
cchalf: CCPoint[];
|
||||||
completeness: number;
|
completeness: CCPoint[];
|
||||||
multiplicity: number;
|
multiplicity: CCPoint[];
|
||||||
nobs: number;
|
nobs: number;
|
||||||
total_refl: number;
|
total_refl: number;
|
||||||
unique_refl: number;
|
unique_refl: number;
|
||||||
@ -81,13 +81,13 @@ const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath,
|
|||||||
resolution: res.result?.resolution ?? 0,
|
resolution: res.result?.resolution ?? 0,
|
||||||
unit_cell: res.result?.unit_cell || 'N/A',
|
unit_cell: res.result?.unit_cell || 'N/A',
|
||||||
spacegroup: res.result?.spacegroup || 'N/A',
|
spacegroup: res.result?.spacegroup || 'N/A',
|
||||||
rmerge: res.result?.rmerge ?? 0,
|
rmerge: res.result?.rmerge || [],
|
||||||
rmeas: res.result?.rmeas ?? 0,
|
rmeas: res.result?.rmeas || [],
|
||||||
isig: res.result?.isig ?? 0,
|
isig: res.result?.isig || [],
|
||||||
cc: res.result?.cc || [],
|
cc: res.result?.cc || [],
|
||||||
cchalf: res.result?.cchalf || [],
|
cchalf: res.result?.cchalf || [],
|
||||||
completeness: res.result?.completeness ?? 0,
|
completeness: res.result?.completeness || [],
|
||||||
multiplicity: res.result?.multiplicity ?? 0,
|
multiplicity: res.result?.multiplicity || [],
|
||||||
nobs: res.result?.nobs ?? 0,
|
nobs: res.result?.nobs ?? 0,
|
||||||
total_refl: res.result?.total_refl ?? 0,
|
total_refl: res.result?.total_refl ?? 0,
|
||||||
unique_refl: res.result?.unique_refl ?? 0,
|
unique_refl: res.result?.unique_refl ?? 0,
|
||||||
@ -111,15 +111,45 @@ const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath,
|
|||||||
{ field: 'resolution', headerName: 'Resolution (Å)', flex: 1 },
|
{ field: 'resolution', headerName: 'Resolution (Å)', flex: 1 },
|
||||||
{ field: 'unit_cell', headerName: 'Unit Cell (Å)', flex: 1.5 },
|
{ field: 'unit_cell', headerName: 'Unit Cell (Å)', flex: 1.5 },
|
||||||
{ field: 'spacegroup', headerName: 'Spacegroup', flex: 1 },
|
{ field: 'spacegroup', headerName: 'Spacegroup', flex: 1 },
|
||||||
{ field: 'rmerge', headerName: 'Rmerge', flex: 1 },
|
{
|
||||||
{ field: 'rmeas', headerName: 'Rmeas', flex: 1 },
|
field: 'rmerge',
|
||||||
{ field: 'isig', headerName: 'I/sig(I)', flex: 1 },
|
headerName: 'Rmerge',
|
||||||
|
flex: 1,
|
||||||
|
valueGetter: (params: GridValueGetterParams<ProcessingResults, string>) =>
|
||||||
|
params.row?.rmerge
|
||||||
|
? Array.isArray(params.row.rmerge)
|
||||||
|
? params.row.rmerge.map((value: CCPoint) => `${value.value.toFixed(2)}@${value.resolution.toFixed(2)}`).join(', ')
|
||||||
|
: params.row.rmerge.toFixed(2)
|
||||||
|
: 'N/A',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rmeas',
|
||||||
|
headerName: 'Rmeas',
|
||||||
|
flex: 1,
|
||||||
|
valueGetter: (params: GridValueGetterParams<ProcessingResults, string>) =>
|
||||||
|
params.row?.rmeas
|
||||||
|
? Array.isArray(params.row.rmeas)
|
||||||
|
? params.row.rmeas.map((value: CCPoint) => `${value.value.toFixed(2)}@${value.resolution.toFixed(2)}`).join(', ')
|
||||||
|
: params.row.rmeas.toFixed(2)
|
||||||
|
: 'N/A',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'isig',
|
||||||
|
headerName: 'I/sig(I)',
|
||||||
|
flex: 1,
|
||||||
|
valueGetter: (params: GridValueGetterParams<ProcessingResults, string>) =>
|
||||||
|
params.row?.isig
|
||||||
|
? Array.isArray(params.row.isig)
|
||||||
|
? params.row.isig.map((value: CCPoint) => `${value.value.toFixed(2)}@${value.resolution.toFixed(2)}`).join(', ')
|
||||||
|
: params.row.isig.toFixed(2)
|
||||||
|
: 'N/A',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'cc',
|
field: 'cc',
|
||||||
headerName: 'CC',
|
headerName: 'CC',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
valueGetter: (params: GridValueGetterParams<ProcessingResults, string>) =>
|
valueGetter: (params: GridValueGetterParams<ProcessingResults, string>) =>
|
||||||
Array.isArray(params.row?.cc)
|
params.row?.cc && Array.isArray(params.row.cc)
|
||||||
? params.row.cc.map((point: CCPoint) => `${point.value.toFixed(2)}@${point.resolution.toFixed(2)}`).join(', ')
|
? params.row.cc.map((point: CCPoint) => `${point.value.toFixed(2)}@${point.resolution.toFixed(2)}`).join(', ')
|
||||||
: '',
|
: '',
|
||||||
},
|
},
|
||||||
@ -128,18 +158,39 @@ const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath,
|
|||||||
headerName: 'CC(1/2)',
|
headerName: 'CC(1/2)',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
valueGetter: (params: GridValueGetterParams<ProcessingResults, string>) =>
|
valueGetter: (params: GridValueGetterParams<ProcessingResults, string>) =>
|
||||||
Array.isArray(params.row?.cchalf)
|
params.row?.cchalf && Array.isArray(params.row.cchalf)
|
||||||
? params.row.cchalf.map((point: CCPoint) => `${point.value.toFixed(2)}@${point.resolution.toFixed(2)}`).join(', ')
|
? params.row.cchalf.map((point: CCPoint) => `${point.value.toFixed(2)}@${point.resolution.toFixed(2)}`).join(', ')
|
||||||
: '',
|
: '',
|
||||||
},
|
},
|
||||||
{ field: 'completeness', headerName: 'Completeness (%)', flex: 1 },
|
{
|
||||||
{ field: 'multiplicity', headerName: 'Multiplicity', flex: 1 },
|
field: 'completeness',
|
||||||
|
headerName: 'Completeness (%)',
|
||||||
|
flex: 1,
|
||||||
|
valueGetter: (params: GridValueGetterParams<ProcessingResults, string>) =>
|
||||||
|
params.row?.completeness
|
||||||
|
? Array.isArray(params.row.completeness)
|
||||||
|
? params.row.completeness.map((value: CCPoint) => `${value.value.toFixed(2)}@${value.resolution.toFixed(2)}`).join(', ')
|
||||||
|
: params.row.completeness.toFixed(2)
|
||||||
|
: 'N/A',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'multiplicity',
|
||||||
|
headerName: 'Multiplicity',
|
||||||
|
flex: 1,
|
||||||
|
valueGetter: (params: GridValueGetterParams<ProcessingResults, string>) =>
|
||||||
|
params.row?.multiplicity
|
||||||
|
? Array.isArray(params.row.multiplicity)
|
||||||
|
? params.row.multiplicity.map((value: CCPoint) => `${value.value.toFixed(2)}@${value.resolution.toFixed(2)}`).join(', ')
|
||||||
|
: params.row.multiplicity.toFixed(2)
|
||||||
|
: 'N/A',
|
||||||
|
},
|
||||||
{ field: 'nobs', headerName: 'N obs.', flex: 1 },
|
{ field: 'nobs', headerName: 'N obs.', flex: 1 },
|
||||||
{ field: 'total_refl', headerName: 'Total Reflections', flex: 1 },
|
{ field: 'total_refl', headerName: 'Total Reflections', flex: 1 },
|
||||||
{ field: 'unique_refl', headerName: 'Unique Reflections', flex: 1 },
|
{ field: 'unique_refl', headerName: 'Unique Reflections', flex: 1 },
|
||||||
{ field: 'comments', headerName: 'Comments', flex: 2 },
|
{ field: 'comments', headerName: 'Comments', flex: 2 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
const updateHeight = () => {
|
const updateHeight = () => {
|
||||||
if (containerRef.current) {
|
if (containerRef.current) {
|
||||||
const newHeight = containerRef.current.offsetHeight;
|
const newHeight = containerRef.current.offsetHeight;
|
||||||
@ -311,31 +362,71 @@ const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange, basePath,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{processingResult && processingResult.length > 0 && (
|
{processingResult && processingResult.length > 0 && (
|
||||||
<div style={{width: 400, marginTop: '16px' }}>
|
<div style={{width: 400, marginTop: '16px'}}>
|
||||||
<Typography variant="h6" gutterBottom>CC and CC(1/2) vs Resolution</Typography>
|
<Typography variant="h6" gutterBottom>Processing Metrics vs Resolution</Typography>
|
||||||
<LineChart
|
<LineChart
|
||||||
xAxis={[
|
xAxis={[
|
||||||
{
|
{
|
||||||
data: processingResult[0].cc
|
data: processingResult[0].cc
|
||||||
.map((point) => point.resolution) // Grab the resolution values
|
.map((point) => point.resolution) // Use resolution values for the x-axis
|
||||||
.reverse(), // Reverse the data for resolution
|
.reverse(), // Reverse the resolution values to go from high-res to low-res
|
||||||
label: 'Resolution (Å)',
|
label: 'Resolution (Å)',
|
||||||
reverse: true, // This ensures the visual flip on the chart, low-res to right and high-res to left
|
reverse: true, // Flip visually so low-res is to the right
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
series={[
|
series={[
|
||||||
{
|
{
|
||||||
data: processingResult[0].cc
|
data: processingResult[0].cc
|
||||||
.map((point) => point.value)
|
.map((point) => point.value) // Map CC values
|
||||||
.reverse(), // Reverse the CC values to match the reversed resolution
|
.reverse(), // Reverse order for visual consistency
|
||||||
label: 'CC',
|
label: 'CC',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: processingResult[0].cchalf
|
data: processingResult[0].cchalf
|
||||||
.map((point) => point.value)
|
.map((point) => point.value) // Map CC(1/2) values
|
||||||
.reverse(), // Reverse the CC(1/2) values to match the reversed resolution
|
.reverse(),
|
||||||
label: 'CC(1/2)',
|
label: 'CC(1/2)',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
data: Array.isArray(processingResult[0].rmerge)
|
||||||
|
? processingResult[0].rmerge
|
||||||
|
.map((point: CCPoint) => point.value) // Map Rmerge values
|
||||||
|
.reverse()
|
||||||
|
: [], // Handle edge case where Rmerge isn't an array
|
||||||
|
label: 'Rmerge',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: Array.isArray(processingResult[0].rmeas)
|
||||||
|
? processingResult[0].rmeas
|
||||||
|
.map((point: CCPoint) => point.value) // Map Rmeas values
|
||||||
|
.reverse()
|
||||||
|
: [],
|
||||||
|
label: 'Rmeas',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: Array.isArray(processingResult[0].isig)
|
||||||
|
? processingResult[0].isig
|
||||||
|
.map((point: CCPoint) => point.value) // Map I/sig(I) values
|
||||||
|
.reverse()
|
||||||
|
: [],
|
||||||
|
label: 'I/sig(I)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: Array.isArray(processingResult[0].completeness)
|
||||||
|
? processingResult[0].completeness
|
||||||
|
.map((point: CCPoint) => point.value) // Map Completeness values
|
||||||
|
.reverse()
|
||||||
|
: [],
|
||||||
|
label: 'Completeness (%)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: Array.isArray(processingResult[0].multiplicity)
|
||||||
|
? processingResult[0].multiplicity
|
||||||
|
.map((point: CCPoint) => point.value) // Map Multiplicity values
|
||||||
|
.reverse()
|
||||||
|
: [],
|
||||||
|
label: 'Multiplicity',
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
height={300}
|
height={300}
|
||||||
/>
|
/>
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
// Planning.tsx
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CustomCalendar from '../components/Calendar.tsx';
|
import CustomCalendar from '../components/Calendar.tsx';
|
||||||
|
|
||||||
const PlanningView: React.FC = () => {
|
interface PlanningViewProps {
|
||||||
return <CustomCalendar />;
|
onPgroupChange?: (pgroup: string) => void;
|
||||||
//return <div>Welcome to the Planning Page</div>;
|
activePgroup: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlanningView: React.FC<PlanningViewProps> = ({ onPgroupChange, activePgroup }) => {
|
||||||
|
return <CustomCalendar
|
||||||
|
activePgroup={activePgroup}
|
||||||
|
onPgroupChange={onPgroupChange}
|
||||||
|
/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PlanningView;
|
export default PlanningView;
|
@ -446,21 +446,21 @@
|
|||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ExecuteTime": {
|
"ExecuteTime": {
|
||||||
"end_time": "2025-05-08T13:31:36.929465Z",
|
"end_time": "2025-05-08T15:17:09.428353Z",
|
||||||
"start_time": "2025-05-08T13:31:36.925054Z"
|
"start_time": "2025-05-08T15:17:09.424769Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": "sample_id = 44",
|
"source": "sample_id = 106",
|
||||||
"id": "54d4d46ca558e7b9",
|
"id": "54d4d46ca558e7b9",
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"execution_count": 28
|
"execution_count": 36
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ExecuteTime": {
|
"ExecuteTime": {
|
||||||
"end_time": "2025-05-08T13:31:40.023546Z",
|
"end_time": "2025-05-08T15:17:16.752451Z",
|
||||||
"start_time": "2025-05-08T13:31:39.978510Z"
|
"start_time": "2025-05-08T15:17:16.719047Z"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
@ -478,7 +478,7 @@
|
|||||||
" sample_event_create = SampleEventCreate(\n",
|
" sample_event_create = SampleEventCreate(\n",
|
||||||
" sample_id=sample_id,\n",
|
" sample_id=sample_id,\n",
|
||||||
" #event_type=\"Centering\" # Valid event type\n",
|
" #event_type=\"Centering\" # Valid event type\n",
|
||||||
" event_type=\"Collecting\" # Valid event type\n",
|
" event_type=\"Mounting\" # Valid event type\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Debug the payload before sending\n",
|
" # Debug the payload before sending\n",
|
||||||
@ -512,7 +512,7 @@
|
|||||||
"DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): localhost:8000\n",
|
"DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): localhost:8000\n",
|
||||||
"/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/urllib3/connectionpool.py:1103: InsecureRequestWarning: Unverified HTTPS request is being made to host 'localhost'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n",
|
"/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/urllib3/connectionpool.py:1103: InsecureRequestWarning: Unverified HTTPS request is being made to host 'localhost'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings\n",
|
||||||
" warnings.warn(\n",
|
" warnings.warn(\n",
|
||||||
"DEBUG:urllib3.connectionpool:https://localhost:8000 \"POST /samples/samples/44/events HTTP/1.1\" 200 884\n"
|
"DEBUG:urllib3.connectionpool:https://localhost:8000 \"POST /samples/samples/106/events HTTP/1.1\" 200 718\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -520,25 +520,25 @@
|
|||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"Payload being sent to API:\n",
|
"Payload being sent to API:\n",
|
||||||
"{\"event_type\":\"Collecting\"}\n",
|
"{\"event_type\":\"Mounting\"}\n",
|
||||||
"API response:\n",
|
"API response:\n",
|
||||||
"('id', 44)\n",
|
"('id', 106)\n",
|
||||||
"('sample_name', 'Sample044')\n",
|
"('sample_name', 'Sample106')\n",
|
||||||
"('position', 7)\n",
|
"('position', 13)\n",
|
||||||
"('puck_id', 7)\n",
|
"('puck_id', 11)\n",
|
||||||
"('crystalname', None)\n",
|
"('crystalname', None)\n",
|
||||||
"('proteinname', None)\n",
|
"('proteinname', None)\n",
|
||||||
"('positioninpuck', None)\n",
|
"('positioninpuck', None)\n",
|
||||||
"('priority', None)\n",
|
"('priority', None)\n",
|
||||||
"('comments', None)\n",
|
"('comments', None)\n",
|
||||||
"('data_collection_parameters', None)\n",
|
"('data_collection_parameters', None)\n",
|
||||||
"('events', [SampleEventResponse(event_type='Mounting', id=87, sample_id=44, timestamp=datetime.datetime(2025, 5, 7, 10, 16)), SampleEventResponse(event_type='Unmounting', id=88, sample_id=44, timestamp=datetime.datetime(2025, 5, 7, 10, 16, 50)), SampleEventResponse(event_type='Collecting', id=507, sample_id=44, timestamp=datetime.datetime(2025, 5, 8, 13, 31, 40, 6059))])\n",
|
"('events', [SampleEventResponse(event_type='Mounting', id=452, sample_id=106, timestamp=datetime.datetime(2025, 5, 8, 15, 17, 16, 743011))])\n",
|
||||||
"('mount_count', 0)\n",
|
"('mount_count', 0)\n",
|
||||||
"('unmount_count', 0)\n"
|
"('unmount_count', 0)\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"execution_count": 29
|
"execution_count": 37
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user