From 956ed2890d8de562ded3551ea3d9fcf47404e910 Mon Sep 17 00:00:00 2001 From: Bryan Ndjeutcha <49378990+snakedye@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:42:30 -0500 Subject: [PATCH] Issue #30 : Support for multiple Registration Number (#73) * Add registration number validation and update FertilizerInspection model * Refactor registration number handling to support structured data format and enhance validation tests * Improve registration number validation and fix website field assertion in tests * Update RegistrationNumber model to allow optional fields and improve validation handling * Fix registration number type validation to use enum member values * Refactor RegistrationNumber model to use string type for registration number and update validation logic; add initial test for label analysis * Remove unused import of Enum from inspection.py * Remove registration number validation from FertilizerInspection and update tests to use RegistrationNumber model * Bump version to 0.0.9 in pyproject.toml * Bump version to 0.0.10 in pyproject.toml and ensure workflow configuration is up to date --- .github/workflows/project-issue-status.yml | 2 +- expected.json | 7 +- pipeline/inspection.py | 30 ++++-- pyproject.toml | 2 +- tests/test_inspection.py | 101 ++++++++++++++++++--- 5 files changed, 115 insertions(+), 27 deletions(-) diff --git a/.github/workflows/project-issue-status.yml b/.github/workflows/project-issue-status.yml index ff2b8a5..caf28a4 100644 --- a/.github/workflows/project-issue-status.yml +++ b/.github/workflows/project-issue-status.yml @@ -16,4 +16,4 @@ on: jobs: issue-status: uses: ai-cfia/github-workflows/.github/workflows/workflow-issue-status.yml@main - secrets: inherit \ No newline at end of file + secrets: inherit diff --git a/expected.json b/expected.json index 95de4ca..0bd91b7 100644 --- a/expected.json +++ b/expected.json @@ -14,7 +14,12 @@ } ], "fertiliser_name": "GreenGrow Fertilizer 20-20-20", - "registration_number": "2018007A", + "registration_number": [ + { + "identifier": "2018007A", + "type": "fertilizer_product" + } + ], "lot_number": "LOT20240901", "weight": [ { diff --git a/pipeline/inspection.py b/pipeline/inspection.py index 28dcf98..397537d 100644 --- a/pipeline/inspection.py +++ b/pipeline/inspection.py @@ -87,6 +87,25 @@ def convert_value(cls, v): return extract_first_number(v) return None +# class syntax + +class RegistrationNumber(BaseModel): + identifier: Optional[str] = Field(..., description="A string composed of 7-digit number followed by an uppercase letter.") + type: Optional[str] = Field(None, description="Type of registration number, either 'ingredient_component' or 'fertilizer_product'.") + + @field_validator("identifier", mode="before") + def check_registration_number_format(cls, v): + if v is not None: + pattern = r"^\d{7}[A-Z]$" + if re.match(pattern, v): + return v + return None + + @field_validator("type", mode="before") + def check_registration_number_type(cls, v): + if v not in ["ingredient_component", "fertilizer_product"]: + return None + return v class GuaranteedAnalysis(BaseModel): title: Optional[str] = None @@ -129,7 +148,7 @@ def convert_specification_values(cls, v): class FertilizerInspection(BaseModel): organizations: List[Organization] = [] fertiliser_name: Optional[str] = None - registration_number: Optional[str] = None + registration_number: List[RegistrationNumber] = [] lot_number: Optional[str] = None weight: List[Value] = [] density: Optional[Value] = None @@ -160,6 +179,7 @@ def validate_npk(cls, v): "instructions_fr", "ingredients_en", "ingredients_fr", + "registration_number", "organizations", "weight", mode="before", @@ -168,11 +188,3 @@ def replace_none_with_empty_list(cls, v): if v is None: v = [] return v - - @field_validator("registration_number", mode="before") - def check_registration_number_format(cls, v): - if v is not None: - pattern = r"^\d{7}[A-Z]$" - if re.match(pattern, v): - return v - return None diff --git a/pyproject.toml b/pyproject.toml index 1d0be01..1cb7496 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "fertiscan_pipeline" -version = "0.0.9" +version = "0.0.10" description = "A pipeline for the FertiScan project" authors = [ { name = "Albert Bryan Ndjeutcha", email = "albert.ndjeutcha@inspection.gc.ca" } diff --git a/tests/test_inspection.py b/tests/test_inspection.py index 2d3e7da..edaefb0 100644 --- a/tests/test_inspection.py +++ b/tests/test_inspection.py @@ -3,6 +3,7 @@ from pipeline.inspection import ( FertilizerInspection, GuaranteedAnalysis, + RegistrationNumber, NutrientValue, Specification, Organization, @@ -234,6 +235,71 @@ def test_invalid_npk(self): inspection.npk, f"Expected None for npk with input {npk}" ) +class TestRegistrationNumber(unittest.TestCase): + def setUp(self): + self.valid_registration_number_data = [ + "1234567A", + "1234567B", + "1234567C", + "1234567D", + "1234567E", + "1234567F", + "1234567G", + "1234567H", + "1234567I", + "1234567J", + "1234567K", + "1234567L", + "1234567M", + "1234567N", + "1234567O", + "1234567P", + "1234567Q", + "1234567R", + "1234567S", + "1234567T", + "1234567U", + "1234567V", + "1234567W", + "1234567X", + "1234567Y", + "1234567Z", + ] + + self.invalid_registration_number_data = [ + "1234567", + "1234567AA", + "1234567A1", + "1234567A ", + "1234567 A", + "1234567A-", + "1234567A.", + "1234567A,", + ] + + self.invalid_registration_type_data = [ + "FERTILIZER", + "INGREDIENT", + "PRODUCT", + "COMPONENT", + ] + + def test_valid_registration_number(self): + for registration_number in self.valid_registration_number_data: + with self.subTest(registration_number=registration_number): + registration = RegistrationNumber(identifier=registration_number) + self.assertEqual(registration.identifier, registration_number) + + def test_invalid_registration_number(self): + for registration_number in self.invalid_registration_number_data: + with self.subTest(registration_number=registration_number): + self.assertIsNone(RegistrationNumber(identifier=registration_number, type="fertilizer_product").identifier) + + def test_invalid_registration_type(self): + for registration_type in self.invalid_registration_type_data: + with self.subTest(registration_type=registration_type): + self.assertIsNone(RegistrationNumber(identifier="1234567A", type=registration_type).type) + class TestGuaranteedAnalysis(unittest.TestCase): def setUp(self): @@ -266,7 +332,12 @@ class TestFertilizerInspectionListFields(unittest.TestCase): def setUp(self): self.default_data = { "fertiliser_name": "Test Fertilizer", - "registration_number": "ABC123", + "registration_number": [ + { + "identifier": "1234567A", + "type": "fertilizer_product", + } + ], "lot_number": "LOT987", "npk": "10-5-20", "guaranteed_analysis_en": None, @@ -293,32 +364,32 @@ def test_replace_none_with_empty_list(self): class TestFertilizerInspectionRegistrationNumber(unittest.TestCase): def test_registration_number_with_less_digits(self): - instance = FertilizerInspection(registration_number="1234") - self.assertIsNone(instance.registration_number) + instance = RegistrationNumber(identifier="1234") + self.assertIsNone(instance.identifier) def test_registration_number_less_than_seven_digits(self): - instance = FertilizerInspection(registration_number="12345A") - self.assertIsNone(instance.registration_number) + instance = RegistrationNumber(identifier="12345A") + self.assertIsNone(instance.identifier) def test_registration_number_seven_digits_no_letter(self): - instance = FertilizerInspection(registration_number="1234567") - self.assertIsNone(instance.registration_number) + instance = RegistrationNumber(identifier="1234567") + self.assertIsNone(instance.identifier) def test_registration_number_seven_digits_with_lowercase_letter(self): - instance = FertilizerInspection(registration_number="1234567a") - self.assertIsNone(instance.registration_number) + instance = RegistrationNumber(identifier="1234567a") + self.assertIsNone(instance.identifier) def test_registration_number_correct_format(self): - instance = FertilizerInspection(registration_number="1234567A") - self.assertEqual(instance.registration_number, "1234567A") + instance = RegistrationNumber(identifier="1234567A") + self.assertEqual(instance.identifier, "1234567A") def test_registration_number_extra_characters(self): - instance = FertilizerInspection(registration_number="12345678B") - self.assertIsNone(instance.registration_number) + instance = RegistrationNumber(identifier="12345678B") + self.assertIsNone(instance.identifier) def test_registration_number_mixed_format(self): - instance = FertilizerInspection(registration_number="12A34567B") - self.assertIsNone(instance.registration_number) + instance = RegistrationNumber(identifier="12A34567B") + self.assertIsNone(instance.identifier) class TestOrganizationPhoneNumberFormat(unittest.TestCase):