
Improved the backend's value cleaning to differentiate between corrections and defaults, logging metadata for clearer traceability. Updated frontend to display corrected/defaulted fields with visual cues and tooltips for better user feedback. Enhanced data models and response structures to support this richer metadata.
127 lines
4.2 KiB
Python
127 lines
4.2 KiB
Python
import re
|
|
from typing import Any, Optional, List, Dict
|
|
|
|
from pydantic import BaseModel, Field, field_validator
|
|
from typing_extensions import Annotated
|
|
import logging
|
|
from app.schemas import DataCollectionParameters
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SpreadsheetModel(BaseModel):
|
|
dewarname: str = Field(..., alias="dewarname")
|
|
puckname: str = Field(..., alias="puckname")
|
|
pucktype: Optional[str] = Field(None, alias="pucktype")
|
|
crystalname: Annotated[
|
|
str,
|
|
Field(
|
|
...,
|
|
max_length=64,
|
|
title="Crystal Name",
|
|
description="max_length imposed by MTZ file header"
|
|
"format https://www.ccp4.ac.uk/html/mtzformat.html",
|
|
alias="crystalname",
|
|
),
|
|
]
|
|
positioninpuck: int # Only accept positive integers between 1 and 16
|
|
priority: Optional[int]
|
|
comments: Optional[str]
|
|
proteinname: Optional[str] = "" # Alphanumeric validation
|
|
data_collection_parameters: Optional[DataCollectionParameters] = None
|
|
|
|
# Add pucktype validation
|
|
@field_validator("pucktype", mode="before")
|
|
@classmethod
|
|
def validate_pucktype(cls, v):
|
|
if v != "unipuck":
|
|
raise ValueError(f"'{v}' is not valid. Pucktype must be 'unipuck'.")
|
|
return v
|
|
|
|
# Validators
|
|
@field_validator("dewarname", "puckname", mode="before")
|
|
@classmethod
|
|
def dewarname_puckname_characters(cls, v):
|
|
if v:
|
|
v = str(v).strip().replace(" ", "_").upper()
|
|
if re.search("\n", v):
|
|
assert v.isalnum(), "is not valid. newline character detected."
|
|
v = re.sub(r"\.0$", "", v)
|
|
return v
|
|
raise ValueError("Value must be provided for dewarname and puckname.")
|
|
|
|
@field_validator("crystalname", mode="before")
|
|
@classmethod
|
|
def parameter_characters(cls, v):
|
|
v = str(v).replace(" ", "_")
|
|
if re.search("\n", v):
|
|
assert v.isalnum(), "is not valid. newline character detected."
|
|
characters = re.sub("[._+-]", "", v)
|
|
assert (
|
|
characters.isalnum()
|
|
), f" '{v}' is not valid. Only alphanumeric and . _ + - characters allowed."
|
|
return v
|
|
|
|
@field_validator("positioninpuck", mode="before")
|
|
@classmethod
|
|
def positioninpuck_possible(cls, v):
|
|
if not isinstance(v, int) or v < 1 or v > 16:
|
|
raise ValueError(
|
|
f" '{v}' is not valid. Value must be an integer between 1 and 16."
|
|
)
|
|
return v
|
|
|
|
@field_validator("priority", mode="before")
|
|
@classmethod
|
|
def priority_positive(cls, v):
|
|
if v is not None:
|
|
try:
|
|
v = int(v)
|
|
if v <= 0:
|
|
raise ValueError(
|
|
f" '{v}' is not valid. Value must be a positive integer."
|
|
)
|
|
except (ValueError, TypeError) as e:
|
|
raise ValueError(
|
|
f" '{v}' is not valid. Value must be a positive integer."
|
|
) from e
|
|
return v
|
|
|
|
# if not v: # Handles None or empty cases
|
|
# default_value = "{sgPuck}/{sgPosition}"
|
|
# logger.warning(
|
|
# f"'directory' field is empty or None. Assigning default value: "
|
|
# f"{default_value}"
|
|
# )
|
|
# return default_value
|
|
|
|
# class TELLModel(SpreadsheetModel):
|
|
# input_order: int
|
|
# samplemountcount: int = 0
|
|
# samplestatus: str = "not present"
|
|
# puckaddress: str = "---"
|
|
# username: str
|
|
# puck_number: int
|
|
# prefix: Optional[str]
|
|
# folder: Optional[str]
|
|
|
|
|
|
class SpreadsheetResponse(BaseModel):
|
|
data: List[SpreadsheetModel] # Validated data rows as SpreadsheetModel instances
|
|
errors: List[Dict[str, Any]] # Errors encountered during validation
|
|
raw_data: List[
|
|
Dict[str, Any]
|
|
] # perhaps this has to be changed with th actual model !
|
|
addinfo: List[Dict[str, Any]]
|
|
dewars_count: int
|
|
dewars: List[str]
|
|
pucks_count: int
|
|
pucks: List[str]
|
|
samples_count: int
|
|
samples: List[str]
|
|
headers: Optional[List[str]] = None # Add headers if needed
|
|
|
|
|
|
__all__ = ["SpreadsheetModel", "SpreadsheetResponse"]
|