Make shipment fields optional and refactor test scripts.
Updated the `number_of_pucks` and `number_of_samples` fields in the `schemas.py` to be optional for greater flexibility. Simplified the test Jupyter Notebook by restructuring imports and consolidating function calls for better readability and maintainability.
This commit is contained in:
@ -126,7 +126,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
|
||||
throw new Error('Missing required fields');
|
||||
}
|
||||
|
||||
const createdDewar = await DewarsService.createDewarDewarsPost(newDewarToPost);
|
||||
const createdDewar = await DewarsService.createOrUpdateDewarDewarsPost(selectedShipment.id, newDewarToPost);
|
||||
|
||||
if (createdDewar && selectedShipment) {
|
||||
const updatedShipment = await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(selectedShipment.id, createdDewar.id);
|
||||
|
@ -63,7 +63,6 @@ const SpreadsheetTable = ({
|
||||
shipping_date: null,
|
||||
arrival_date: null,
|
||||
returning_date: null,
|
||||
qrcode: 'N/A',
|
||||
contact_person_id: selectedShipment?.contact_person?.id,
|
||||
return_address_id: selectedShipment?.return_address?.id,
|
||||
dewar_name: '',
|
||||
@ -236,14 +235,23 @@ const SpreadsheetTable = ({
|
||||
'chiphiangles': 31
|
||||
};
|
||||
|
||||
const checkIfDewarExists = async (dewarName) => {
|
||||
const checkIfDewarExists = async (dewarName: string) => {
|
||||
if (!selectedShipment) return null;
|
||||
|
||||
try {
|
||||
// Fetch dewars related to the current shipment via API
|
||||
const shipDewars = await ShipmentsService.getDewarsByShipmentIdShipmentsShipmentIdDewarsGet(selectedShipment.id);
|
||||
return shipDewars.find((d) => d.dewar_name === dewarName);
|
||||
|
||||
// Search for dewar by name within the shipment
|
||||
const existingDewar = shipDewars.find((d) => d.dewar_name === dewarName);
|
||||
|
||||
if (existingDewar) {
|
||||
console.log(`Dewar "${dewarName}" exists with ID: ${existingDewar.id}`);
|
||||
return existingDewar;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch existing dewars:', error);
|
||||
console.error("Failed to fetch existing dewars:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@ -258,75 +266,34 @@ const SpreadsheetTable = ({
|
||||
const puckPositionMap = new Map();
|
||||
const dewarsToReplace = [];
|
||||
|
||||
const dewarNameIdx = fieldToCol['dewarname'];
|
||||
const puckNameIdx = fieldToCol['puckname'];
|
||||
const puckTypeIdx = fieldToCol['pucktype'];
|
||||
const sampleNameIdx = fieldToCol['crystalname'];
|
||||
const proteinNameIdx = fieldToCol['proteinname'];
|
||||
const samplePositionIdx = fieldToCol['positioninpuck'];
|
||||
const priorityIdx = fieldToCol['priority'];
|
||||
const commentsIdx = fieldToCol['comments'];
|
||||
|
||||
for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
|
||||
const row = data[rowIndex];
|
||||
|
||||
if (!row.data) {
|
||||
console.error('Row data is missing');
|
||||
console.error(`Row ${rowIndex}: Missing or invalid data`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract values from the appropriate columns
|
||||
const dewarName = typeof row.data[dewarNameIdx] === 'string' ? row.data[dewarNameIdx].trim() : null;
|
||||
const puckName = row.data[puckNameIdx] !== undefined && row.data[puckNameIdx] !== null ? String(row.data[puckNameIdx]).trim() : null;
|
||||
const puckType = typeof row.data[puckTypeIdx] === 'string' ? row.data[puckTypeIdx] : 'Unipuck';
|
||||
const sampleName = typeof row.data[sampleNameIdx] === 'string' ? row.data[sampleNameIdx].trim() : null;
|
||||
const proteinName = typeof row.data[proteinNameIdx] === 'string' ? row.data[proteinNameIdx].trim() : null;
|
||||
const samplePosition = row.data[samplePositionIdx] !== undefined && row.data[samplePositionIdx] !== null ? Number(row.data[samplePositionIdx]) : null;
|
||||
const priority = row?.data?.[priorityIdx] ? Number(row.data[priorityIdx]) : null;
|
||||
const comments = typeof row.data[commentsIdx] === 'string' ? row.data[commentsIdx].trim() : null;
|
||||
|
||||
// Create data_collection_parameters object
|
||||
const dataCollectionParameters = {
|
||||
directory: row.data[fieldToCol['directory']],
|
||||
oscillation: row.data[fieldToCol['oscillation']] ? parseFloat(row.data[fieldToCol['oscillation']]) : undefined,
|
||||
aperture: row.data[fieldToCol['aperture']] ? row.data[fieldToCol['aperture']].trim() : undefined,
|
||||
exposure: row.data[fieldToCol['exposure']] ? parseFloat(row.data[fieldToCol['exposure']]) : undefined,
|
||||
totalrange: row.data[fieldToCol['totalrange']] ? parseInt(row.data[fieldToCol['totalrange']], 10) : undefined,
|
||||
transmission: row.data[fieldToCol['transmission']] ? parseInt(row.data[fieldToCol['transmission']], 10) : undefined,
|
||||
dose: row.data[fieldToCol['dose']] ? parseFloat(row.data[fieldToCol['dose']]) : undefined,
|
||||
targetresolution: row.data[fieldToCol['targetresolution']] ? parseFloat(row.data[fieldToCol['targetresolution']]) : undefined,
|
||||
datacollectiontype: row.data[fieldToCol['datacollectiontype']],
|
||||
processingpipeline: row.data[fieldToCol['processingpipeline']],
|
||||
spacegroupnumber: row.data[fieldToCol['spacegroupnumber']] ? parseInt(row.data[fieldToCol['spacegroupnumber']], 10) : undefined,
|
||||
cellparameters: row.data[fieldToCol['cellparameters']],
|
||||
rescutkey: row.data[fieldToCol['rescutkey']],
|
||||
rescutvalue: row.data[fieldToCol['rescutvalue']] ? parseFloat(row.data[fieldToCol['rescutvalue']]) : undefined,
|
||||
userresolution: row.data[fieldToCol['userresolution']] ? parseFloat(row.data[fieldToCol['userresolution']]) : undefined,
|
||||
pdbid: row.data[fieldToCol['pdbid']],
|
||||
autoprocfull: row.data[fieldToCol['autoprocfull']] === true,
|
||||
procfull: row.data[fieldToCol['procfull']] === true,
|
||||
adpenabled: row.data[fieldToCol['adpenabled']] === true,
|
||||
noano: row.data[fieldToCol['noano']] === true,
|
||||
ffcscampaign: row.data[fieldToCol['ffcscampaign']] === true,
|
||||
trustedhigh: row.data[fieldToCol['trustedhigh']] ? parseFloat(row.data[fieldToCol['trustedhigh']]) : undefined,
|
||||
autoprocextraparams: row.data[fieldToCol['autoprocextraparams']],
|
||||
chiphiangles: row.data[fieldToCol['chiphiangles']] ? parseFloat(row.data[fieldToCol['chiphiangles']]) : undefined,
|
||||
};
|
||||
const dewarName = sanitizeAndValidateColumn(row, fieldToCol['dewarname'], 'string', 'Dewar Name');
|
||||
const puckName = sanitizeAndValidateColumn(row, fieldToCol['puckname'], 'string', 'Puck Name');
|
||||
const puckType = sanitizeAndValidateColumn(row, fieldToCol['pucktype'], 'string', 'Unipuck', true); // Default: `Unipuck`
|
||||
const sampleName = sanitizeAndValidateColumn(row, fieldToCol['crystalname'], 'string', 'Sample Name');
|
||||
|
||||
if (dewarName && puckName) {
|
||||
let dewar;
|
||||
if (!dewars.has(dewarName)) {
|
||||
// Initialize new dewar object
|
||||
dewar = {
|
||||
...initialNewDewarState,
|
||||
dewar_name: dewarName,
|
||||
contact_person_id: contactPerson.id,
|
||||
return_address_id: returnAddress.id,
|
||||
pucks: []
|
||||
pucks: [],
|
||||
};
|
||||
dewars.set(dewarName, dewar);
|
||||
puckPositionMap.set(dewarName, new Map());
|
||||
|
||||
// Check if the dewar exists in the shipment
|
||||
// Check if dewar exists using backend
|
||||
const existingDewar = await checkIfDewarExists(dewarName);
|
||||
if (existingDewar) {
|
||||
dewarsToReplace.push(existingDewar);
|
||||
@ -335,78 +302,115 @@ const SpreadsheetTable = ({
|
||||
dewar = dewars.get(dewarName);
|
||||
}
|
||||
|
||||
// Handle puck positions
|
||||
let puckPositions = puckPositionMap.get(dewarName);
|
||||
if (!puckPositions.has(puckName)) {
|
||||
puckPositions.set(puckName, puckPositions.size + 1);
|
||||
}
|
||||
|
||||
const puckPosition = puckPositions.get(puckName);
|
||||
|
||||
let puck = dewar.pucks.find(p => p.puck_name === puckName);
|
||||
// Create puck and attach it to the dewar
|
||||
let puck = dewar.pucks.find((p) => p.puck_name === puckName);
|
||||
if (!puck) {
|
||||
puck = {
|
||||
puck_name: puckName,
|
||||
puck_type: puckType,
|
||||
puck_location_in_dewar: puckPosition,
|
||||
samples: []
|
||||
};
|
||||
puck = { puck_name: puckName, puck_type: puckType, puck_location_in_dewar: puckPosition, samples: [] };
|
||||
dewar.pucks.push(puck);
|
||||
}
|
||||
|
||||
// Add sample to puck
|
||||
const sample = {
|
||||
sample_name: sampleName,
|
||||
proteinname: proteinName,
|
||||
position: samplePosition,
|
||||
priority: priority,
|
||||
comments: comments,
|
||||
data_collection_parameters: dataCollectionParameters, // Attach the parameters
|
||||
results: null // Placeholder for results field
|
||||
position: sanitizeIntColumn(row, fieldToCol['positioninpuck'], 'Sample Position'),
|
||||
proteinname: sanitizeAndValidateColumn(row, fieldToCol['proteinname'], 'string', 'Protein Name'),
|
||||
priority: sanitizeIntColumn(row, fieldToCol['priority'], 'Priority'),
|
||||
comments: sanitizeAndValidateColumn(row, fieldToCol['comments'], 'string', 'Comments', true),
|
||||
data_collection_parameters: collectDataParameters(row), // Consolidate data parameters
|
||||
};
|
||||
|
||||
if (isNaN(sample.position)) {
|
||||
console.error(`Invalid sample position for sample ${sample.sample_name} in puck ${puckName}`);
|
||||
} else {
|
||||
puck.samples.push(sample);
|
||||
}
|
||||
puck.samples.push(sample);
|
||||
} else {
|
||||
if (!dewarName) {
|
||||
console.error(`Dewar name is missing in row ${rowIndex}`);
|
||||
}
|
||||
if (!puckName) {
|
||||
console.error(`Puck name is missing in row ${rowIndex}`);
|
||||
}
|
||||
console.error(`Row ${rowIndex} is missing required fields for dewar/puck creation.`);
|
||||
}
|
||||
}
|
||||
|
||||
const dewarsArray = Array.from(dewars.values());
|
||||
|
||||
// Save dewars array for later use in handleConfirmUpdate
|
||||
// Save for update dialog control
|
||||
setDewarsToCreate(dewars);
|
||||
|
||||
if (dewarsArray.length > 0 && dewarsToReplace.length > 0) {
|
||||
setDewarsToReplace(dewarsToReplace);
|
||||
setShowUpdateDialog(true);
|
||||
} else {
|
||||
} else if (dewarsArray.length > 0) {
|
||||
await handleDewarCreation(dewarsArray);
|
||||
}
|
||||
};
|
||||
|
||||
const sanitizeAndValidateColumn = (row, colIndex, type, columnName, isOptional = false) => {
|
||||
const value = row?.data?.[colIndex];
|
||||
const sanitizedValue = type === 'string' ? value?.trim() : value;
|
||||
|
||||
if (!sanitizedValue && !isOptional) {
|
||||
console.error(`${columnName} is missing or invalid.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type === 'number' && isNaN(sanitizedValue)) {
|
||||
console.error(`${columnName} is not a valid number.`);
|
||||
}
|
||||
|
||||
return sanitizedValue;
|
||||
};
|
||||
|
||||
// Utility to sanitize integer columns
|
||||
const sanitizeIntColumn = (row, colIndex, columnName) => {
|
||||
const rawValue = row?.data?.[colIndex];
|
||||
const intValue = parseInt(rawValue, 10);
|
||||
|
||||
if (isNaN(intValue)) {
|
||||
console.error(`${columnName} is not a valid integer.`);
|
||||
}
|
||||
|
||||
return intValue;
|
||||
};
|
||||
|
||||
// Consolidate data collection parameters
|
||||
const collectDataParameters = (row) => ({
|
||||
directory: sanitizeAndValidateColumn(row, fieldToCol['directory'], 'string', 'Directory', true),
|
||||
oscillation: parseFloat(sanitizeAndValidateColumn(row, fieldToCol['oscillation'], 'number', 'Oscillation', true)),
|
||||
aperture: sanitizeAndValidateColumn(row, fieldToCol['aperture'], 'string', 'Aperture', true),
|
||||
exposure: parseFloat(sanitizeAndValidateColumn(row, fieldToCol['exposure'], 'number', 'Exposure', true)),
|
||||
totalrange: sanitizeIntColumn(row, fieldToCol['totalrange'], 'Total Range'),
|
||||
transmission: sanitizeIntColumn(row, fieldToCol['transmission'], 'Transmission'),
|
||||
dose: parseFloat(sanitizeAndValidateColumn(row, fieldToCol['dose'], 'number', 'Dose', true)),
|
||||
targetresolution: parseFloat(sanitizeAndValidateColumn(row, fieldToCol['targetresolution'], 'number', 'Target Resolution', true)),
|
||||
datacollectiontype: sanitizeAndValidateColumn(row, fieldToCol['datacollectiontype'], 'string', 'Data Collection Type', true),
|
||||
processingpipeline: sanitizeAndValidateColumn(row, fieldToCol['processingpipeline'], 'string', 'Processing Pipeline', true),
|
||||
spacegroupnumber: sanitizeIntColumn(row, fieldToCol['spacegroupnumber'], 'Space Group Number'),
|
||||
cellparameters: sanitizeAndValidateColumn(row, fieldToCol['cellparameters'], 'string', 'Cell Parameters', true),
|
||||
rescutkey: sanitizeAndValidateColumn(row, fieldToCol['rescutkey'], 'string', 'Resolution Cut Key', true),
|
||||
rescutvalue: parseFloat(sanitizeAndValidateColumn(row, fieldToCol['rescutvalue'], 'number', 'Resolution Cut Value', true)),
|
||||
userresolution: parseFloat(sanitizeAndValidateColumn(row, fieldToCol['userresolution'], 'number', 'User Resolution', true)),
|
||||
pdbid: sanitizeAndValidateColumn(row, fieldToCol['pdbid'], 'string', 'PDB ID', true),
|
||||
autoprocfull: row.data[fieldToCol['autoprocfull']] === true,
|
||||
procfull: row.data[fieldToCol['procfull']] === true,
|
||||
adpenabled: row.data[fieldToCol['adpenabled']] === true,
|
||||
noano: row.data[fieldToCol['noano']] === true,
|
||||
ffcscampaign: row.data[fieldToCol['ffcscampaign']] === true,
|
||||
trustedhigh: parseFloat(sanitizeAndValidateColumn(row, fieldToCol['trustedhigh'], 'number', 'Trusted High', true)),
|
||||
autoprocextraparams: sanitizeAndValidateColumn(row, fieldToCol['autoprocextraparams'], 'string', 'Autoproc Extra Params', true),
|
||||
chiphiangles: parseFloat(sanitizeAndValidateColumn(row, fieldToCol['chiphiangles'], 'number', 'Chi/Phi Angles', true)),
|
||||
});
|
||||
|
||||
const handleConfirmUpdate = async () => {
|
||||
if (dewarsToReplace.length === 0) return;
|
||||
|
||||
try {
|
||||
for (const dewar of dewarsToReplace) {
|
||||
await DewarsService.deleteDewarDewarsDewarIdDelete(dewar.id);
|
||||
}
|
||||
const dewarsArray = Array.from(dewarsToCreate.values());
|
||||
await handleDewarCreation(dewarsArray);
|
||||
await handleDewarCreation(dewarsArray); // Updated logic handles replacement
|
||||
console.log('Dewars replaced successfully');
|
||||
} catch (error) {
|
||||
console.error('Error replacing dewar', error);
|
||||
if (error instanceof ApiError && error.body) {
|
||||
console.error('Validation errors:', error.body.detail);
|
||||
} else {
|
||||
console.error('Unexpected error:', error);
|
||||
}
|
||||
console.error('Error replacing dewars:', error);
|
||||
}
|
||||
setShowUpdateDialog(false);
|
||||
setDewarsToReplace([]);
|
||||
@ -419,30 +423,18 @@ const SpreadsheetTable = ({
|
||||
setDewarsToCreate(new Map());
|
||||
};
|
||||
|
||||
const handleDewarCreation = async (dewarsArray) => {
|
||||
const handleDewarCreation = async (dewarsArray: any[]) => {
|
||||
for (const dewar of dewarsArray) {
|
||||
try {
|
||||
if (!dewar.pucks || dewar.pucks.length === 0) {
|
||||
console.error(`Dewar ${dewar.dewar_name} does not have any pucks.`);
|
||||
continue;
|
||||
}
|
||||
// Prepare payload and exclude number_of_pucks before sending
|
||||
const { number_of_pucks, number_of_samples, ...payload } = dewar;
|
||||
|
||||
const createdDewar = await DewarsService.createDewarDewarsPost(dewar);
|
||||
// Call the backend to create or update dewars
|
||||
await DewarsService.createOrUpdateDewarDewarsPost(selectedShipment.id, payload);
|
||||
|
||||
if (createdDewar && selectedShipment) {
|
||||
await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(
|
||||
selectedShipment.id,
|
||||
createdDewar.id
|
||||
);
|
||||
console.log(`Dewar ${createdDewar.dewar_name} with ID ${createdDewar.id} created and added to the shipment.`);
|
||||
}
|
||||
console.log(`Dewar "${dewar.dewar_name}" processed successfully.`);
|
||||
} catch (error) {
|
||||
console.error('Error adding dewar', error);
|
||||
if (error instanceof ApiError && error.body) {
|
||||
console.error('Validation errors:', error.body.detail);
|
||||
} else {
|
||||
console.error('Unexpected error:', error);
|
||||
}
|
||||
console.error("Error creating/updating dewar:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user