Skip to content

Commit 810a171

Browse files
authored
Merge pull request #158 from Rafalkufel/145-invalid-pesel-validation
145 - Correct pesel validation
2 parents fffdadf + 347332a commit 810a171

File tree

4 files changed

+88
-11
lines changed

4 files changed

+88
-11
lines changed

alinka/schemas/document_schema.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pydantic import BaseModel, Field, computed_field, model_validator
55

66
from alinka.constants import ActivityForm, Issue, Reason
7+
from alinka.utils import extract_date_of_birth_from_pesel
78

89

910
class AddressData(BaseModel):
@@ -46,10 +47,7 @@ def calculate_date_of_birth(self) -> "ChildData":
4647
if self.birth_date:
4748
return self
4849
pesel = self.pesel
49-
year_part, month_part, day = int(pesel[0:2]), int(pesel[2:4]), int(pesel[4:6])
50-
year = 2000 + year_part if month_part > 20 else 1900 + year_part
51-
month = month_part - 20 if month_part > 20 else month_part
52-
self.birth_date = date(year, month, day)
50+
self.birth_date = extract_date_of_birth_from_pesel(pesel)
5351
return self
5452

5553

alinka/utils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from datetime import date
2+
3+
4+
def extract_date_of_birth_from_pesel(pesel: str) -> date:
5+
year_part, month_part, day = int(pesel[0:2]), int(pesel[2:4]), int(pesel[4:6])
6+
year = 2000 + year_part if month_part > 20 else 1900 + year_part
7+
month = month_part - 20 if month_part > 20 else month_part
8+
return date(year, month, day)
9+
10+
11+
def is_valid_pesel(pesel: str) -> bool:
12+
if len(pesel) != 11 or not pesel.isdigit():
13+
return False
14+
weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3]
15+
digits = [int(ch) for ch in pesel]
16+
checksum = (10 - sum(w * d for w, d in zip(weights, digits)) % 10) % 10
17+
if checksum != digits[-1]:
18+
return False
19+
year_part, month_part, day = int(pesel[0:2]), int(pesel[2:4]), int(pesel[4:6])
20+
if 1 <= month_part <= 12:
21+
month = month_part
22+
year = 1900 + year_part
23+
elif 21 <= month_part <= 32:
24+
month = month_part - 20
25+
year = 2000 + year_part
26+
else:
27+
return False
28+
29+
try:
30+
return bool(date(year, month, day))
31+
except ValueError:
32+
return False

alinka/widget/validators.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from PySide6.QtGui import QValidator
22

3+
from alinka.utils import is_valid_pesel
4+
35

46
class RequiredValidator(QValidator):
57
default_error_message = "To pole jest wymagane"
@@ -20,15 +22,9 @@ def validate(self, input_str: str, pos: int) -> tuple[QValidator.State, str, int
2022
return QValidator.Invalid, input_str, pos
2123
if len(input_str) > 12:
2224
return QValidator.Invalid, input_str, pos
23-
2425
if len(input_str) < 11:
2526
return QValidator.Intermediate, input_str, pos
26-
27-
weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3]
28-
digits = [int(ch) for ch in input_str]
29-
checksum = (10 - sum(w * d for w, d in zip(weights, digits)) % 10) % 10
30-
31-
if checksum == digits[-1]:
27+
if is_valid_pesel(input_str):
3228
return QValidator.Acceptable, input_str, pos
3329
else:
3430
return QValidator.Intermediate, input_str, pos

tests/test_utils.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from datetime import date
2+
3+
import pytest
4+
5+
from alinka.utils import extract_date_of_birth_from_pesel, is_valid_pesel
6+
7+
8+
class TestValidatePesel:
9+
@pytest.mark.parametrize(
10+
"pesel",
11+
[
12+
"44051401359",
13+
"07290423766",
14+
"02070803628",
15+
],
16+
)
17+
def test_validate_pesel__valid_pesel(self, pesel):
18+
assert is_valid_pesel(pesel)
19+
20+
@pytest.mark.parametrize(
21+
"pesel",
22+
[
23+
"44051401358", # Invalid checksum
24+
"1234567890", # Too short
25+
"83010412345",
26+
"99022912345",
27+
"88888888888", # Invalid date
28+
"abcdefghijk", # Non-digit characters
29+
"123456789012", # Too long
30+
"", # Empty string
31+
],
32+
)
33+
def test_validate_pesel__invalid_pesel(self, pesel):
34+
assert not is_valid_pesel(pesel)
35+
36+
37+
class TestPeselToDateOfBirth:
38+
@pytest.mark.parametrize(
39+
"pesel, expected_date",
40+
[
41+
("40301362951", date(2040, 10, 13)),
42+
("39251058614", date(2039, 5, 10)),
43+
("14230216474", date(2014, 3, 2)),
44+
("39280845434", date(2039, 8, 8)),
45+
("09321992876", date(2009, 12, 19)),
46+
("05212294647", date(2005, 1, 22)),
47+
],
48+
)
49+
def test_extract_date_of_birth_from_pesel__success(self, pesel, expected_date):
50+
assert is_valid_pesel(pesel)
51+
assert extract_date_of_birth_from_pesel(pesel) == expected_date

0 commit comments

Comments
 (0)