```
Add duplicate detection for spreadsheet data processing Implemented logic to detect and handle duplicate 'positioninpuck' entries within the same puck during spreadsheet processing. Updated backend to validate duplicates and provide detailed error messages. Enhanced frontend to visually highlight duplicate errors and allow better user feedback during cell editing. ```
This commit is contained in:
parent
7861082a02
commit
e63af3e66d
@ -152,9 +152,7 @@ class SampleSpreadsheetImporter:
|
|||||||
model = []
|
model = []
|
||||||
errors = []
|
errors = []
|
||||||
raw_data = []
|
raw_data = []
|
||||||
headers = []
|
|
||||||
|
|
||||||
# Skip the first 3 rows
|
|
||||||
rows = list(sheet.iter_rows(min_row=4, values_only=True))
|
rows = list(sheet.iter_rows(min_row=4, values_only=True))
|
||||||
logger.debug(f"Starting to process {len(rows)} rows from the sheet")
|
logger.debug(f"Starting to process {len(rows)} rows from the sheet")
|
||||||
|
|
||||||
@ -200,6 +198,8 @@ class SampleSpreadsheetImporter:
|
|||||||
"chiphiangles",
|
"chiphiangles",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
duplicate_check = {}
|
||||||
|
|
||||||
for index, row in enumerate(rows):
|
for index, row in enumerate(rows):
|
||||||
if not any(row):
|
if not any(row):
|
||||||
logger.debug(f"Skipping empty row at index {index}")
|
logger.debug(f"Skipping empty row at index {index}")
|
||||||
@ -241,13 +241,12 @@ class SampleSpreadsheetImporter:
|
|||||||
corrected = True
|
corrected = True
|
||||||
defaulted_columns.append(column_name)
|
defaulted_columns.append(column_name)
|
||||||
|
|
||||||
# Update the record with cleaned value (store only the cleaned part,
|
# Update the record with cleaned value
|
||||||
# not the tuple)
|
|
||||||
record[column_name] = cleaned_value
|
record[column_name] = cleaned_value
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Validation error for row {index + 4}"
|
f"Validation error for row {index + 4},"
|
||||||
f", column '{column_name}': {str(e)}"
|
f" column '{column_name}': {str(e)}"
|
||||||
)
|
)
|
||||||
errors.append(
|
errors.append(
|
||||||
{
|
{
|
||||||
@ -258,6 +257,37 @@ class SampleSpreadsheetImporter:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Validate duplicate 'positioninpuck' within the same puck
|
||||||
|
dewarname = record.get("dewarname")
|
||||||
|
puckname = record.get("puckname")
|
||||||
|
positioninpuck = record.get("positioninpuck")
|
||||||
|
|
||||||
|
if (
|
||||||
|
dewarname and puckname and positioninpuck is not None
|
||||||
|
): # Only check if all required fields exist
|
||||||
|
duplicate_key = f"{dewarname}-{puckname}"
|
||||||
|
if duplicate_key not in duplicate_check:
|
||||||
|
duplicate_check[duplicate_key] = set()
|
||||||
|
|
||||||
|
if positioninpuck in duplicate_check[duplicate_key]:
|
||||||
|
# Add error for duplicate position in the same puck
|
||||||
|
logger.warning(
|
||||||
|
f"Duplicate position '{positioninpuck}' found in puck"
|
||||||
|
f" '{puckname}' (dewar: '{dewarname}')"
|
||||||
|
)
|
||||||
|
errors.append(
|
||||||
|
{
|
||||||
|
"row": index + 4, # Adjust row number for 1-based indexing
|
||||||
|
"column": "positioninpuck", # The problematic column
|
||||||
|
"value": positioninpuck, # The value causing the issue
|
||||||
|
"message": f"Duplicate position '{positioninpuck}'"
|
||||||
|
f" found in puck '{puckname}' of dewar '{dewarname}'.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
duplicate_check[duplicate_key].add(positioninpuck)
|
||||||
|
|
||||||
# Build metadata for the row
|
# Build metadata for the row
|
||||||
raw_data.append(
|
raw_data.append(
|
||||||
{
|
{
|
||||||
@ -267,9 +297,7 @@ class SampleSpreadsheetImporter:
|
|||||||
defaulted_columns
|
defaulted_columns
|
||||||
), # True if any defaults were applied
|
), # True if any defaults were applied
|
||||||
"corrected": corrected, # True if any value was corrected
|
"corrected": corrected, # True if any value was corrected
|
||||||
# List of corrected columns (if any)
|
|
||||||
"corrected_columns": corrected_columns,
|
"corrected_columns": corrected_columns,
|
||||||
# List of defaulted columns (if any)
|
|
||||||
"defaulted_columns": defaulted_columns,
|
"defaulted_columns": defaulted_columns,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -83,15 +83,18 @@ const SpreadsheetTable = ({
|
|||||||
}, [selectedShipment]);
|
}, [selectedShipment]);
|
||||||
|
|
||||||
const generateErrorMap = (errorsList) => {
|
const generateErrorMap = (errorsList) => {
|
||||||
const errorMap = new Map();
|
const errorMap = new Map();
|
||||||
if (Array.isArray(errorsList)) {
|
if (Array.isArray(errorsList)) {
|
||||||
errorsList.forEach((error) => {
|
errorsList.forEach((error) => {
|
||||||
const key = `${error.row}-${headers[error.cell]}`;
|
const colIndex = headers.findIndex(header => header === error.column);
|
||||||
errorMap.set(key, error.message);
|
if (colIndex > -1) {
|
||||||
});
|
const key = `${error.row}-${headers[colIndex]}`;
|
||||||
}
|
errorMap.set(key, error.message);
|
||||||
return errorMap;
|
}
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
return errorMap;
|
||||||
|
};
|
||||||
|
|
||||||
const errorMap = generateErrorMap(localErrors);
|
const errorMap = generateErrorMap(localErrors);
|
||||||
|
|
||||||
@ -139,7 +142,6 @@ const SpreadsheetTable = ({
|
|||||||
|
|
||||||
if (response && response.is_valid !== undefined) {
|
if (response && response.is_valid !== undefined) {
|
||||||
if (response.is_valid) {
|
if (response.is_valid) {
|
||||||
// If valid, update the value (and use corrected_value if returned)
|
|
||||||
const correctedValue = response.corrected_value ?? newValue;
|
const correctedValue = response.corrected_value ?? newValue;
|
||||||
currentRow.data[colIndex] = correctedValue;
|
currentRow.data[colIndex] = correctedValue;
|
||||||
updatedRawData[rowIndex] = currentRow;
|
updatedRawData[rowIndex] = currentRow;
|
||||||
@ -148,7 +150,12 @@ const SpreadsheetTable = ({
|
|||||||
|
|
||||||
// Remove the error and mark as non-editable
|
// Remove the error and mark as non-editable
|
||||||
const updatedErrors = localErrors.filter(
|
const updatedErrors = localErrors.filter(
|
||||||
(error) => !(error.row === currentRow.row_num && error.cell === colIndex)
|
(error) =>
|
||||||
|
!(
|
||||||
|
error.row === currentRow.row_num &&
|
||||||
|
error.cell === colIndex &&
|
||||||
|
error.message.toLowerCase().includes("duplicate position")
|
||||||
|
)
|
||||||
);
|
);
|
||||||
setLocalErrors(updatedErrors); // Update error list
|
setLocalErrors(updatedErrors); // Update error list
|
||||||
|
|
||||||
@ -188,7 +195,11 @@ const SpreadsheetTable = ({
|
|||||||
handleCellEdit(rowIndex, colIndex);
|
handleCellEdit(rowIndex, colIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const allCellsValid = () => nonEditableCells.size === raw_data.length * headers.length;
|
const allCellsValid = () =>
|
||||||
|
nonEditableCells.size === raw_data.length * headers.length &&
|
||||||
|
!localErrors.some((error) =>
|
||||||
|
error.message.toLowerCase().includes("duplicate position")
|
||||||
|
);
|
||||||
|
|
||||||
const fieldToCol = {
|
const fieldToCol = {
|
||||||
'dewarname': 0,
|
'dewarname': 0,
|
||||||
@ -514,6 +525,7 @@ const SpreadsheetTable = ({
|
|||||||
const key = `${row.row_num}-${header}`;
|
const key = `${row.row_num}-${header}`;
|
||||||
const errorMessage = errorMap.get(key);
|
const errorMessage = errorMap.get(key);
|
||||||
const isInvalid = !!errorMessage;
|
const isInvalid = !!errorMessage;
|
||||||
|
const isDuplicateError = errorMessage?.toLowerCase().includes("duplicate position"); // Detect duplicate-specific messages
|
||||||
const cellValue = row.data[colIndex];
|
const cellValue = row.data[colIndex];
|
||||||
const editingValue = editingCell[`${rowIndex}-${colIndex}`];
|
const editingValue = editingCell[`${rowIndex}-${colIndex}`];
|
||||||
const isCellCorrected = row.corrected_columns?.includes(header); // Use corrected metadata
|
const isCellCorrected = row.corrected_columns?.includes(header); // Use corrected metadata
|
||||||
@ -525,13 +537,21 @@ const SpreadsheetTable = ({
|
|||||||
align="center"
|
align="center"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: isDefaultAssigned
|
backgroundColor: isDefaultAssigned
|
||||||
? "#e6fbe6" // Light green
|
? "#e6fbe6" // Light green for default values
|
||||||
: isCellCorrected
|
: isCellCorrected
|
||||||
? "#fff8e1" // Light yellow
|
? "#fff8e1" // Light yellow for corrections
|
||||||
: "transparent",
|
: isDuplicateError
|
||||||
color: isDefaultAssigned ? "#1b5e20" : "inherit",
|
? "#fdecea" // Light red for duplicate errors
|
||||||
fontWeight: isCellCorrected || isDefaultAssigned ? "bold" : "normal",
|
: "transparent", // No specific color for valid cells
|
||||||
cursor: isInvalid ? "pointer" : "default",
|
color: isDefaultAssigned
|
||||||
|
? "#1b5e20" // Dark green for default values
|
||||||
|
: isDuplicateError
|
||||||
|
? "#d32f2f" // Bright red for duplicates
|
||||||
|
: "inherit",
|
||||||
|
fontWeight: isDefaultAssigned || isCellCorrected || isDuplicateError
|
||||||
|
? "bold"
|
||||||
|
: "normal",
|
||||||
|
cursor: isInvalid ? "pointer" : "default", // Allow editing invalid cells
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -540,7 +560,9 @@ const SpreadsheetTable = ({
|
|||||||
? "This value was automatically assigned as a default."
|
? "This value was automatically assigned as a default."
|
||||||
: isCellCorrected
|
: isCellCorrected
|
||||||
? `Field "${header}" was auto-corrected.`
|
? `Field "${header}" was auto-corrected.`
|
||||||
: errorMessage || ""
|
: isDuplicateError
|
||||||
|
? `Duplicate value detected in "${header}". Please ensure values are unique in this column.`
|
||||||
|
: errorMessage || ""
|
||||||
}
|
}
|
||||||
arrow
|
arrow
|
||||||
disableHoverListener={!isDefaultAssigned && !isCellCorrected && !isInvalid}
|
disableHoverListener={!isDefaultAssigned && !isCellCorrected && !isInvalid}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user