From a9d1fc45ee1516ead773c8f3f10e6bcd0c6182cb Mon Sep 17 00:00:00 2001 From: gabino Date: Tue, 12 Aug 2025 12:15:13 -0600 Subject: [PATCH 1/8] Add fecha_nacimiento_date computed field to Person model --- quienesquien/person.py | 16 +++++++++++++-- tests/test_person.py | 45 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/quienesquien/person.py b/quienesquien/person.py index 2c95cf7..939ae66 100644 --- a/quienesquien/person.py +++ b/quienesquien/person.py @@ -1,3 +1,5 @@ +import datetime as dt + from pydantic import ( BaseModel, ConfigDict, @@ -27,12 +29,22 @@ class Person(BaseModel): extra='allow', ) - @computed_field # type: ignore[misc] - @property + @computed_field def peso1(self) -> str: # peso1 is required for backward compatibility with previous version. return str(self.coincidencia) + @computed_field + def fecha_nacimiento_date(self) -> dt.date | None: + if not self.fecha_nacimiento: + return None + try: + return dt.datetime.strptime( + self.fecha_nacimiento, '%d/%m/%Y' + ).date() + except (TypeError, ValueError): + return None + @model_validator(mode='after') def collect_extra_fields(self): if self.model_extra: diff --git a/tests/test_person.py b/tests/test_person.py index 87e3b2d..f52b57a 100644 --- a/tests/test_person.py +++ b/tests/test_person.py @@ -1,8 +1,13 @@ +import datetime as dt +from typing import Any + +import pytest + from quienesquien.person import Person -def test_collect_extra_fields(): - person_data = { +def test_collect_extra_fields() -> None: + person_data: dict[str, Any] = { 'LISTA': 'lista1', 'COINCIDENCIA': 100, 'NOMBRECOMP': 'Juan Pérez', @@ -14,5 +19,37 @@ def test_collect_extra_fields(): assert person.peso1 == '100' assert person.coincidencia == 100 assert person.nombrecomp == 'Juan Pérez' - assert person.campo_extra1 == 'valor1' - assert person.campo_extra2 == 'valor2' + + # Access extra fields through model_extra after lowercase conversion + assert person.model_extra is not None + assert person.model_extra['campo_extra1'] == 'valor1' + assert person.model_extra['campo_extra2'] == 'valor2' + + +@pytest.mark.parametrize( + "fecha_nacimiento_input, expected_fecha_nacimiento, expected_date", + [ + ('13/11/1953', '13/11/1953', dt.date(1953, 11, 13)), + (None, None, None), + ('', '', None), + ('invalid-date', 'invalid-date', None), + ('1953-11-13', '1953-11-13', None), + ('13/11', '13/11', None), + ('13-11-1953', '13-11-1953', None), + ], +) +def test_fecha_nacimiento_date( + fecha_nacimiento_input: str | None, + expected_fecha_nacimiento: str | None, + expected_date: dt.date | None, +) -> None: + person_data: dict[str, Any] = { + 'LISTA': 'PPE', + 'COINCIDENCIA': 100, + 'NOMBRECOMP': 'Andres Manuel López Obrador', + 'FECHA_NACIMIENTO': fecha_nacimiento_input, + } + person = Person(**person_data) + + assert person.fecha_nacimiento == expected_fecha_nacimiento + assert person.fecha_nacimiento_date == expected_date From 43002951bb15a5c40299b71a65556f2ecfa4edc8 Mon Sep 17 00:00:00 2001 From: gabino Date: Tue, 12 Aug 2025 12:52:03 -0600 Subject: [PATCH 2/8] Add is_potential_false_positive method to Person model --- quienesquien/person.py | 15 ++++++++ tests/test_person.py | 83 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/quienesquien/person.py b/quienesquien/person.py index 939ae66..d2265ab 100644 --- a/quienesquien/person.py +++ b/quienesquien/person.py @@ -54,3 +54,18 @@ def collect_extra_fields(self): self.model_extra.clear() self.model_extra.update(lowercase_extra) return self + + def is_potential_false_positive( + self, date_of_birth: dt.date | None = None, curp: str | None = None + ) -> bool: + if ( + date_of_birth + and self.fecha_nacimiento_date is not None + and self.fecha_nacimiento_date != date_of_birth + ): + return True + + if curp and self.curp and curp != self.curp: + return True + + return False diff --git a/tests/test_person.py b/tests/test_person.py index f52b57a..7d76f78 100644 --- a/tests/test_person.py +++ b/tests/test_person.py @@ -27,20 +27,19 @@ def test_collect_extra_fields() -> None: @pytest.mark.parametrize( - "fecha_nacimiento_input, expected_fecha_nacimiento, expected_date", + 'fecha_nacimiento_input, expected_date', [ - ('13/11/1953', '13/11/1953', dt.date(1953, 11, 13)), - (None, None, None), - ('', '', None), - ('invalid-date', 'invalid-date', None), - ('1953-11-13', '1953-11-13', None), - ('13/11', '13/11', None), - ('13-11-1953', '13-11-1953', None), + ('13/11/1953', dt.date(1953, 11, 13)), + (None, None), + ('', None), + ('invalid-date', None), + ('1953-11-13', None), + ('13/11', None), + ('13-11-1953', None), ], ) def test_fecha_nacimiento_date( fecha_nacimiento_input: str | None, - expected_fecha_nacimiento: str | None, expected_date: dt.date | None, ) -> None: person_data: dict[str, Any] = { @@ -51,5 +50,69 @@ def test_fecha_nacimiento_date( } person = Person(**person_data) - assert person.fecha_nacimiento == expected_fecha_nacimiento + assert person.fecha_nacimiento == fecha_nacimiento_input assert person.fecha_nacimiento_date == expected_date + + +@pytest.mark.parametrize( + 'p_nacimiento, p_curp, input_date, input_curp, expected_result', + [ + # Different birth dates - should be potential false positive + ('13/11/1953', None, dt.date(1953, 11, 14), None, True), + # Same birth dates - should not be potential false positive + ('13/11/1953', None, dt.date(1953, 11, 13), None, False), + # Different CURPs - should be potential false positive + (None, 'LOOA531113HDFPBR07', None, 'DIFFERENT_CURP_123', True), + # Same CURPs - should not be potential false positive + (None, 'LOOA531113HDFPBR07', None, 'LOOA531113HDFPBR07', False), + # Both date and CURP different - should be potential false positive + ( + '13/11/1953', + 'LOOA531113HDFPBR07', + dt.date(1953, 11, 14), + 'DIFFERENT_CURP_123', + True, + ), + # Both date and CURP same - should not be potential false positive + ( + '13/11/1953', + 'LOOA531113HDFPBR07', + dt.date(1953, 11, 13), + 'LOOA531113HDFPBR07', + False, + ), + # No data to compare - should not be potential false positive + (None, None, None, None, False), + # Person has data but no input data - should not be false positive + ('13/11/1953', None, None, None, False), + (None, 'LOOA531113HDFPBR07', None, None, False), + ('13/11/1953', 'LOOA531113HDFPBR07', None, None, False), + # Input data but person has no data - should not be false positive + (None, None, dt.date(1953, 11, 13), 'LOOA531113HDFPBR07', False), + ], +) +def test_is_potential_false_positive( + p_nacimiento: str | None, + p_curp: str | None, + input_date: dt.date | None, + input_curp: str | None, + expected_result: bool, +) -> None: + person_data: dict[str, Any] = { + 'LISTA': 'PPE', + 'COINCIDENCIA': 90, + 'NOMBRECOMP': 'Test Person', + } + + if p_nacimiento is not None: + person_data['FECHA_NACIMIENTO'] = p_nacimiento + + if p_curp is not None: + person_data['CURP'] = p_curp + + person = Person(**person_data) + + result = person.is_potential_false_positive( + date_of_birth=input_date, curp=input_curp + ) + assert result == expected_result From 92f815a93a524b342ac5dc8fd4245c2eaa61ab32 Mon Sep 17 00:00:00 2001 From: gabino Date: Tue, 12 Aug 2025 12:52:17 -0600 Subject: [PATCH 3/8] Update version to 1.0.2.dev1 for development release --- quienesquien/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quienesquien/version.py b/quienesquien/version.py index cd7ca49..220ce64 100644 --- a/quienesquien/version.py +++ b/quienesquien/version.py @@ -1 +1 @@ -__version__ = '1.0.1' +__version__ = '1.0.2.dev1' From 7e769af0677921df511c22912a72a4b0343aa693 Mon Sep 17 00:00:00 2001 From: gabino Date: Tue, 12 Aug 2025 13:06:47 -0600 Subject: [PATCH 4/8] Refactor fecha_nacimiento_date from computed field to property in Person model --- quienesquien/person.py | 2 +- quienesquien/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quienesquien/person.py b/quienesquien/person.py index d2265ab..7119795 100644 --- a/quienesquien/person.py +++ b/quienesquien/person.py @@ -34,7 +34,7 @@ def peso1(self) -> str: # peso1 is required for backward compatibility with previous version. return str(self.coincidencia) - @computed_field + @property def fecha_nacimiento_date(self) -> dt.date | None: if not self.fecha_nacimiento: return None diff --git a/quienesquien/version.py b/quienesquien/version.py index 220ce64..303b199 100644 --- a/quienesquien/version.py +++ b/quienesquien/version.py @@ -1 +1 @@ -__version__ = '1.0.2.dev1' +__version__ = '1.0.2.dev2' From 2c9bf6154e14bcce3f8886f57b0fad472f69d99d Mon Sep 17 00:00:00 2001 From: gabino Date: Tue, 12 Aug 2025 13:21:43 -0600 Subject: [PATCH 5/8] Update version to 1.0.2 for stable release --- quienesquien/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quienesquien/version.py b/quienesquien/version.py index 303b199..a6221b3 100644 --- a/quienesquien/version.py +++ b/quienesquien/version.py @@ -1 +1 @@ -__version__ = '1.0.2.dev2' +__version__ = '1.0.2' From 0a8242b8cf0ac89c8c823652e8258181049dbcd9 Mon Sep 17 00:00:00 2001 From: gabino Date: Thu, 14 Aug 2025 10:13:25 -0600 Subject: [PATCH 6/8] Refactor Person model methods: rename fecha_nacimiento_date to fecha_nacimiento_to_date and update is_potential_false_positive to matches_data with improved logic --- quienesquien/person.py | 22 ++++++-------- quienesquien/version.py | 2 +- tests/test_person.py | 64 +++++++++++++++++++++++++---------------- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/quienesquien/person.py b/quienesquien/person.py index 7119795..2a21603 100644 --- a/quienesquien/person.py +++ b/quienesquien/person.py @@ -35,7 +35,7 @@ def peso1(self) -> str: return str(self.coincidencia) @property - def fecha_nacimiento_date(self) -> dt.date | None: + def fecha_nacimiento_to_date(self) -> dt.date | None: if not self.fecha_nacimiento: return None try: @@ -55,17 +55,13 @@ def collect_extra_fields(self): self.model_extra.update(lowercase_extra) return self - def is_potential_false_positive( + def matches_data( self, date_of_birth: dt.date | None = None, curp: str | None = None ) -> bool: - if ( - date_of_birth - and self.fecha_nacimiento_date is not None - and self.fecha_nacimiento_date != date_of_birth - ): - return True - - if curp and self.curp and curp != self.curp: - return True - - return False + if date_of_birth and self.fecha_nacimiento_to_date: + if self.fecha_nacimiento_to_date != date_of_birth: + return False + if curp and self.curp: + if curp != self.curp: + return False + return True diff --git a/quienesquien/version.py b/quienesquien/version.py index a6221b3..6d9efb1 100644 --- a/quienesquien/version.py +++ b/quienesquien/version.py @@ -1 +1 @@ -__version__ = '1.0.2' +__version__ = '1.0.2.dev3' diff --git a/tests/test_person.py b/tests/test_person.py index 7d76f78..82b601a 100644 --- a/tests/test_person.py +++ b/tests/test_person.py @@ -51,47 +51,63 @@ def test_fecha_nacimiento_date( person = Person(**person_data) assert person.fecha_nacimiento == fecha_nacimiento_input - assert person.fecha_nacimiento_date == expected_date + assert person.fecha_nacimiento_to_date == expected_date @pytest.mark.parametrize( 'p_nacimiento, p_curp, input_date, input_curp, expected_result', [ - # Different birth dates - should be potential false positive - ('13/11/1953', None, dt.date(1953, 11, 14), None, True), - # Same birth dates - should not be potential false positive - ('13/11/1953', None, dt.date(1953, 11, 13), None, False), - # Different CURPs - should be potential false positive - (None, 'LOOA531113HDFPBR07', None, 'DIFFERENT_CURP_123', True), - # Same CURPs - should not be potential false positive - (None, 'LOOA531113HDFPBR07', None, 'LOOA531113HDFPBR07', False), - # Both date and CURP different - should be potential false positive + # Different birth dates + ('13/11/1953', None, dt.date(1953, 11, 14), None, False), + # Same birth dates + ('13/11/1953', None, dt.date(1953, 11, 13), None, True), + # Different CURPs + (None, 'LOOA531113HDFPBR07', None, 'DIFFERENT_CURP_123', False), + # Same CURPs + (None, 'LOOA531113HDFPBR07', None, 'LOOA531113HDFPBR07', True), + # Different birth dates and same curp ( '13/11/1953', 'LOOA531113HDFPBR07', dt.date(1953, 11, 14), - 'DIFFERENT_CURP_123', - True, + 'LOOA531113HDFPBR07', + False, ), - # Both date and CURP same - should not be potential false positive + # Same birth dates and different curp ( '13/11/1953', 'LOOA531113HDFPBR07', dt.date(1953, 11, 13), + 'DIFFERENT_CURP_123', + False, + ), + # Both date and CURP different + ( + '13/11/1953', 'LOOA531113HDFPBR07', + dt.date(1953, 11, 14), + 'DIFFERENT_CURP_123', False, ), - # No data to compare - should not be potential false positive - (None, None, None, None, False), - # Person has data but no input data - should not be false positive - ('13/11/1953', None, None, None, False), - (None, 'LOOA531113HDFPBR07', None, None, False), - ('13/11/1953', 'LOOA531113HDFPBR07', None, None, False), - # Input data but person has no data - should not be false positive - (None, None, dt.date(1953, 11, 13), 'LOOA531113HDFPBR07', False), + # Both date and CURP same + ( + '13/11/1953', + 'LOOA531113HDFPBR07', + dt.date(1953, 11, 13), + 'LOOA531113HDFPBR07', + True, + ), + # No data to compare + (None, None, None, None, True), + # Person has data but no input data + ('13/11/1953', None, None, None, True), + (None, 'LOOA531113HDFPBR07', None, None, True), + ('13/11/1953', 'LOOA531113HDFPBR07', None, None, True), + # Input data but person has no data + (None, None, dt.date(1953, 11, 13), 'LOOA531113HDFPBR07', True), ], ) -def test_is_potential_false_positive( +def test_matches_data( p_nacimiento: str | None, p_curp: str | None, input_date: dt.date | None, @@ -112,7 +128,5 @@ def test_is_potential_false_positive( person = Person(**person_data) - result = person.is_potential_false_positive( - date_of_birth=input_date, curp=input_curp - ) + result = person.matches_data(date_of_birth=input_date, curp=input_curp) assert result == expected_result From 1f66684c490738d229d425d6b9519d8455a04427 Mon Sep 17 00:00:00 2001 From: gabino Date: Fri, 15 Aug 2025 16:09:38 -0600 Subject: [PATCH 7/8] Refactor matches_data method in Person model for improved logic --- quienesquien/person.py | 11 ++++------- quienesquien/version.py | 2 +- tests/test_person.py | 14 +++++++------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/quienesquien/person.py b/quienesquien/person.py index 2a21603..0bfde62 100644 --- a/quienesquien/person.py +++ b/quienesquien/person.py @@ -58,10 +58,7 @@ def collect_extra_fields(self): def matches_data( self, date_of_birth: dt.date | None = None, curp: str | None = None ) -> bool: - if date_of_birth and self.fecha_nacimiento_to_date: - if self.fecha_nacimiento_to_date != date_of_birth: - return False - if curp and self.curp: - if curp != self.curp: - return False - return True + dob = self.fecha_nacimiento_to_date + match_dob = bool(dob and dob == date_of_birth) + match_curp = bool(self.curp and self.curp == curp) + return match_dob or match_curp diff --git a/quienesquien/version.py b/quienesquien/version.py index 6d9efb1..2365660 100644 --- a/quienesquien/version.py +++ b/quienesquien/version.py @@ -1 +1 @@ -__version__ = '1.0.2.dev3' +__version__ = '1.0.2.dev4' diff --git a/tests/test_person.py b/tests/test_person.py index 82b601a..d8ff2fd 100644 --- a/tests/test_person.py +++ b/tests/test_person.py @@ -71,7 +71,7 @@ def test_fecha_nacimiento_date( 'LOOA531113HDFPBR07', dt.date(1953, 11, 14), 'LOOA531113HDFPBR07', - False, + True, ), # Same birth dates and different curp ( @@ -79,7 +79,7 @@ def test_fecha_nacimiento_date( 'LOOA531113HDFPBR07', dt.date(1953, 11, 13), 'DIFFERENT_CURP_123', - False, + True, ), # Both date and CURP different ( @@ -98,13 +98,13 @@ def test_fecha_nacimiento_date( True, ), # No data to compare - (None, None, None, None, True), + (None, None, None, None, False), # Person has data but no input data - ('13/11/1953', None, None, None, True), - (None, 'LOOA531113HDFPBR07', None, None, True), - ('13/11/1953', 'LOOA531113HDFPBR07', None, None, True), + ('13/11/1953', None, None, None, False), + (None, 'LOOA531113HDFPBR07', None, None, False), + ('13/11/1953', 'LOOA531113HDFPBR07', None, None, False), # Input data but person has no data - (None, None, dt.date(1953, 11, 13), 'LOOA531113HDFPBR07', True), + (None, None, dt.date(1953, 11, 13), 'LOOA531113HDFPBR07', False), ], ) def test_matches_data( From e940e7990371f6a3785c5cdf6c9785d5902b7cb3 Mon Sep 17 00:00:00 2001 From: gabino Date: Fri, 15 Aug 2025 16:12:57 -0600 Subject: [PATCH 8/8] Update version to 1.0.2 for stable release --- quienesquien/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quienesquien/version.py b/quienesquien/version.py index 2365660..a6221b3 100644 --- a/quienesquien/version.py +++ b/quienesquien/version.py @@ -1 +1 @@ -__version__ = '1.0.2.dev4' +__version__ = '1.0.2'