diff --git a/cg/services/order_validation_service/models/order_with_samples.py b/cg/services/order_validation_service/models/order_with_samples.py new file mode 100644 index 0000000000..449615225c --- /dev/null +++ b/cg/services/order_validation_service/models/order_with_samples.py @@ -0,0 +1,10 @@ +from cg.services.order_validation_service.models.order import Order +from cg.services.order_validation_service.models.sample import Sample + + +class OrderWithSamples(Order): + samples: list[Sample] + + @property + def enumerated_samples(self) -> enumerate[Sample]: + return enumerate(self.samples) diff --git a/cg/services/order_validation_service/rules/sample/rules.py b/cg/services/order_validation_service/rules/sample/rules.py index e69de29bb2..91d523d163 100644 --- a/cg/services/order_validation_service/rules/sample/rules.py +++ b/cg/services/order_validation_service/rules/sample/rules.py @@ -0,0 +1,128 @@ +from cg.constants.constants import PrepCategory, Workflow +from cg.services.order_validation_service.constants import WORKFLOW_PREP_CATEGORIES +from cg.services.order_validation_service.errors.sample_errors import ( + ApplicationArchivedError, + ApplicationNotCompatibleError, + ApplicationNotValidError, + ElutionBufferMissingError, + ExtractionMethodMissingError, + InvalidVolumeError, + OccupiedWellError, + OrganismDoesNotExistError, + SampleNameRepeatedError, +) +from cg.services.order_validation_service.rules.utils import ( + is_application_not_compatible, + is_volume_invalid, +) +from cg.services.order_validation_service.workflows.microsalt.models.order import ( + MicrosaltOrder, +) +from cg.services.order_validation_service.workflows.microsalt.rules.sample.utils import ( + PlateSamplesValidator, + get_indices_for_repeated_sample_names, +) +from cg.store.store import Store + + +def validate_application_exists( + order: MicrosaltOrder, store: Store, **kwargs +) -> list[ApplicationNotValidError]: + errors: list[ApplicationNotValidError] = [] + for sample_index, sample in order.enumerated_new_samples: + if not store.get_application_by_tag(sample.application): + error = ApplicationNotValidError(sample_index=sample_index) + errors.append(error) + return errors + + +def validate_applications_not_archived( + order: MicrosaltOrder, store: Store, **kwargs +) -> list[ApplicationArchivedError]: + errors: list[ApplicationArchivedError] = [] + for sample_index, sample in order.enumerated_new_samples: + if store.is_application_archived(sample.application): + error = ApplicationArchivedError(sample_index=sample_index) + errors.append(error) + return errors + + +def validate_volume_interval(order: MicrosaltOrder, **kwargs) -> list[InvalidVolumeError]: + errors: list[InvalidVolumeError] = [] + for sample_index, sample in order.enumerated_new_samples: + if is_volume_invalid(sample): + error = InvalidVolumeError(sample_index=sample_index) + errors.append(error) + return errors + + +def validate_organism_exists( + order: MicrosaltOrder, store: Store, **kwargs +) -> list[OrganismDoesNotExistError]: + errors: list[OrganismDoesNotExistError] = [] + for sample_index, sample in order.enumerated_new_samples: + if not store.get_organism_by_internal_id(sample.organism): + error = OrganismDoesNotExistError(sample_index=sample_index) + errors.append(error) + return errors + + +def validate_buffer_required(order: MicrosaltOrder, **kwargs) -> list[ElutionBufferMissingError]: + errors: list[ElutionBufferMissingError] = [] + for sample_index, sample in order.enumerated_new_samples: + if not sample.elution_buffer: + error = ElutionBufferMissingError(sample_index=sample_index) + errors.append(error) + return errors + + +def validate_extraction_method_required( + order: MicrosaltOrder, **kwargs +) -> list[ExtractionMethodMissingError]: + errors: list[ExtractionMethodMissingError] = [] + for sample_index, sample in order.enumerated_new_samples: + if not sample.extraction_method: + error = ExtractionMethodMissingError(sample_index=sample_index) + errors.append(error) + return errors + + +def validate_application_compatibility( + order: MicrosaltOrder, + store: Store, + **kwargs, +) -> list[ApplicationNotCompatibleError]: + errors: list[ApplicationNotCompatibleError] = [] + workflow: Workflow = order.workflow + allowed_prep_categories: list[PrepCategory] = WORKFLOW_PREP_CATEGORIES[workflow] + for sample_index, sample in order.enumerated_new_samples: + incompatible: bool = is_application_not_compatible( + allowed_prep_categories=allowed_prep_categories, + application_tag=sample.application, + store=store, + ) + if incompatible: + error = ApplicationNotCompatibleError(sample_index=sample_index) + errors.append(error) + return errors + + +def validate_wells_contain_at_most_one_sample( + order: MicrosaltOrder, + **kwargs, +) -> list[OccupiedWellError]: + plate_samples = PlateSamplesValidator(order) + return plate_samples.get_occupied_well_errors() + + +def validate_well_positions_required( + order: MicrosaltOrder, + **kwargs, +) -> list[OccupiedWellError]: + plate_samples = PlateSamplesValidator(order) + return plate_samples.get_well_position_missing_errors() + + +def validate_sample_names_unique(order: MicrosaltOrder, **kwargs) -> list[SampleNameRepeatedError]: + sample_indices: list[int] = get_indices_for_repeated_sample_names(order) + return [SampleNameRepeatedError(sample_index=sample_index) for sample_index in sample_indices] diff --git a/cg/services/order_validation_service/workflows/microsalt/rules/sample/utils.py b/cg/services/order_validation_service/rules/sample/utils.py similarity index 76% rename from cg/services/order_validation_service/workflows/microsalt/rules/sample/utils.py rename to cg/services/order_validation_service/rules/sample/utils.py index 8da8225184..2dca2b5101 100644 --- a/cg/services/order_validation_service/workflows/microsalt/rules/sample/utils.py +++ b/cg/services/order_validation_service/rules/sample/utils.py @@ -4,27 +4,23 @@ OccupiedWellError, WellPositionMissingError, ) -from cg.services.order_validation_service.workflows.microsalt.models.order import ( - MicrosaltOrder, -) -from cg.services.order_validation_service.workflows.microsalt.models.sample import ( - MicrosaltSample, -) +from cg.services.order_validation_service.models.order_with_samples import OrderWithSamples +from cg.services.order_validation_service.models.sample import Sample class PlateSamplesValidator: - def __init__(self, order: MicrosaltOrder): + def __init__(self, order: OrderWithSamples): self.wells: dict[tuple[str, str], list[int]] = {} - self.plate_samples: list[tuple[int, MicrosaltSample]] = [] + self.plate_samples: list[tuple[int, Sample]] = [] self._initialize_wells(order) - def _initialize_wells(self, order: MicrosaltOrder): + def _initialize_wells(self, order: OrderWithSamples): """ Construct a dict with keys being a (container_name, well_position) pair. The value will be a list of sample indices for samples located in the well. """ - for sample_index, sample in order.enumerated_new_samples: + for sample_index, sample in order.enumerated_samples: if sample.is_on_plate: self.plate_samples.append((sample_index, sample)) key: tuple[str, str] = (sample.container_name, sample.well_position) @@ -34,7 +30,7 @@ def _initialize_wells(self, order: MicrosaltOrder): def get_occupied_well_errors(self) -> list[OccupiedWellError]: """Get errors for samples assigned to wells that are already occupied.""" - conflicting_samples: list[MicrosaltSample] = [] + conflicting_samples: list[Sample] = [] for samples_indices in self.wells.values(): if len(samples_indices) > 1: conflicting_samples.extend(samples_indices[1:]) @@ -57,10 +53,10 @@ def get_missing_well_errors(sample_indices: list[int]) -> list[WellPositionMissi return [WellPositionMissingError(sample_index) for sample_index in sample_indices] -def get_indices_for_repeated_sample_names(order: MicrosaltOrder) -> list[int]: +def get_indices_for_repeated_sample_names(order: OrderWithSamples) -> list[int]: counter = Counter([sample.name for sample in order.samples]) indices: list[int] = [] - for index, sample in order.enumerated_new_samples: + for index, sample in order.enumerated_samples: if counter.get(sample.name) > 1: indices.append(index) return indices diff --git a/cg/services/order_validation_service/workflows/microsalt/models/order.py b/cg/services/order_validation_service/workflows/microsalt/models/order.py index 6184bf2e47..7bd36f591d 100644 --- a/cg/services/order_validation_service/workflows/microsalt/models/order.py +++ b/cg/services/order_validation_service/workflows/microsalt/models/order.py @@ -1,4 +1,4 @@ -from cg.services.order_validation_service.models.order import Order +from cg.services.order_validation_service.models.order_with_samples import OrderWithSamples from cg.services.order_validation_service.workflows.microsalt.constants import ( MicrosaltDeliveryType, ) @@ -7,7 +7,7 @@ ) -class MicrosaltOrder(Order): +class MicrosaltOrder(OrderWithSamples): delivery_type: MicrosaltDeliveryType samples: list[MicrosaltSample] diff --git a/cg/services/order_validation_service/workflows/microsalt/rules/sample/__init__.py b/cg/services/order_validation_service/workflows/microsalt/rules/sample/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cg/services/order_validation_service/workflows/microsalt/rules/sample/rules.py b/cg/services/order_validation_service/workflows/microsalt/rules/sample/rules.py deleted file mode 100644 index 91d523d163..0000000000 --- a/cg/services/order_validation_service/workflows/microsalt/rules/sample/rules.py +++ /dev/null @@ -1,128 +0,0 @@ -from cg.constants.constants import PrepCategory, Workflow -from cg.services.order_validation_service.constants import WORKFLOW_PREP_CATEGORIES -from cg.services.order_validation_service.errors.sample_errors import ( - ApplicationArchivedError, - ApplicationNotCompatibleError, - ApplicationNotValidError, - ElutionBufferMissingError, - ExtractionMethodMissingError, - InvalidVolumeError, - OccupiedWellError, - OrganismDoesNotExistError, - SampleNameRepeatedError, -) -from cg.services.order_validation_service.rules.utils import ( - is_application_not_compatible, - is_volume_invalid, -) -from cg.services.order_validation_service.workflows.microsalt.models.order import ( - MicrosaltOrder, -) -from cg.services.order_validation_service.workflows.microsalt.rules.sample.utils import ( - PlateSamplesValidator, - get_indices_for_repeated_sample_names, -) -from cg.store.store import Store - - -def validate_application_exists( - order: MicrosaltOrder, store: Store, **kwargs -) -> list[ApplicationNotValidError]: - errors: list[ApplicationNotValidError] = [] - for sample_index, sample in order.enumerated_new_samples: - if not store.get_application_by_tag(sample.application): - error = ApplicationNotValidError(sample_index=sample_index) - errors.append(error) - return errors - - -def validate_applications_not_archived( - order: MicrosaltOrder, store: Store, **kwargs -) -> list[ApplicationArchivedError]: - errors: list[ApplicationArchivedError] = [] - for sample_index, sample in order.enumerated_new_samples: - if store.is_application_archived(sample.application): - error = ApplicationArchivedError(sample_index=sample_index) - errors.append(error) - return errors - - -def validate_volume_interval(order: MicrosaltOrder, **kwargs) -> list[InvalidVolumeError]: - errors: list[InvalidVolumeError] = [] - for sample_index, sample in order.enumerated_new_samples: - if is_volume_invalid(sample): - error = InvalidVolumeError(sample_index=sample_index) - errors.append(error) - return errors - - -def validate_organism_exists( - order: MicrosaltOrder, store: Store, **kwargs -) -> list[OrganismDoesNotExistError]: - errors: list[OrganismDoesNotExistError] = [] - for sample_index, sample in order.enumerated_new_samples: - if not store.get_organism_by_internal_id(sample.organism): - error = OrganismDoesNotExistError(sample_index=sample_index) - errors.append(error) - return errors - - -def validate_buffer_required(order: MicrosaltOrder, **kwargs) -> list[ElutionBufferMissingError]: - errors: list[ElutionBufferMissingError] = [] - for sample_index, sample in order.enumerated_new_samples: - if not sample.elution_buffer: - error = ElutionBufferMissingError(sample_index=sample_index) - errors.append(error) - return errors - - -def validate_extraction_method_required( - order: MicrosaltOrder, **kwargs -) -> list[ExtractionMethodMissingError]: - errors: list[ExtractionMethodMissingError] = [] - for sample_index, sample in order.enumerated_new_samples: - if not sample.extraction_method: - error = ExtractionMethodMissingError(sample_index=sample_index) - errors.append(error) - return errors - - -def validate_application_compatibility( - order: MicrosaltOrder, - store: Store, - **kwargs, -) -> list[ApplicationNotCompatibleError]: - errors: list[ApplicationNotCompatibleError] = [] - workflow: Workflow = order.workflow - allowed_prep_categories: list[PrepCategory] = WORKFLOW_PREP_CATEGORIES[workflow] - for sample_index, sample in order.enumerated_new_samples: - incompatible: bool = is_application_not_compatible( - allowed_prep_categories=allowed_prep_categories, - application_tag=sample.application, - store=store, - ) - if incompatible: - error = ApplicationNotCompatibleError(sample_index=sample_index) - errors.append(error) - return errors - - -def validate_wells_contain_at_most_one_sample( - order: MicrosaltOrder, - **kwargs, -) -> list[OccupiedWellError]: - plate_samples = PlateSamplesValidator(order) - return plate_samples.get_occupied_well_errors() - - -def validate_well_positions_required( - order: MicrosaltOrder, - **kwargs, -) -> list[OccupiedWellError]: - plate_samples = PlateSamplesValidator(order) - return plate_samples.get_well_position_missing_errors() - - -def validate_sample_names_unique(order: MicrosaltOrder, **kwargs) -> list[SampleNameRepeatedError]: - sample_indices: list[int] = get_indices_for_repeated_sample_names(order) - return [SampleNameRepeatedError(sample_index=sample_index) for sample_index in sample_indices] diff --git a/cg/services/order_validation_service/workflows/microsalt/validation_rules.py b/cg/services/order_validation_service/workflows/microsalt/validation_rules.py index 6c0ae929d5..ed457a14de 100644 --- a/cg/services/order_validation_service/workflows/microsalt/validation_rules.py +++ b/cg/services/order_validation_service/workflows/microsalt/validation_rules.py @@ -1,4 +1,4 @@ -from cg.services.order_validation_service.workflows.microsalt.rules.sample.rules import ( +from cg.services.order_validation_service.rules.sample.rules import ( validate_application_compatibility, validate_application_exists, validate_applications_not_archived, diff --git a/cg/services/order_validation_service/workflows/mutant/models/order.py b/cg/services/order_validation_service/workflows/mutant/models/order.py index e249173d09..5732e22ff7 100644 --- a/cg/services/order_validation_service/workflows/mutant/models/order.py +++ b/cg/services/order_validation_service/workflows/mutant/models/order.py @@ -1,4 +1,4 @@ -from cg.services.order_validation_service.models.order import Order +from cg.services.order_validation_service.models.order_with_samples import OrderWithSamples from cg.services.order_validation_service.workflows.mutant.constants import ( MutantDeliveryType, ) @@ -7,6 +7,6 @@ ) -class MutantOrder(Order): +class MutantOrder(OrderWithSamples): delivery_type: MutantDeliveryType samples: list[MutantSample]