From 5aa5fb24c58c0c189f0c7f8953e9741299e4438b Mon Sep 17 00:00:00 2001 From: bermr Date: Fri, 13 Oct 2023 20:07:08 -0300 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20cria=20util=20de=20formata=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20t=C3=ADtulo=20eleitoral?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++++++ README_EN.md | 16 +++++++ brutils/__init__.py | 2 + brutils/voter_registration.py | 77 ++++++++++++++++++++++++++++++++ tests/test_voter_registration.py | 55 +++++++++++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 brutils/voter_registration.py create mode 100644 tests/test_voter_registration.py diff --git a/README.md b/README.md index 62b8a6e..e8b390a 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,8 @@ False - [format_processo_juridico](#format_processo_juridico) - [remove\_symbols\_processo\_juridico](#remove_symbols_processo_juridico) - [generate_processo_juridico](#generate_processo_juridico) +- [Título Eleitoral](#título-eleitoral) + - [format_titulo_eleitoral](#format_titulo_eleitoral) ## CPF @@ -538,6 +540,20 @@ Gera um número de processo válido de acordo com o ano informado e o órgão. P >>> ``` +## Título Eleitoral + +### format_titulo_eleitoral + +Formata título eleitoral (se válido) +```python +>>> from brutils import format_titulo_eleitoral +>>> format_titulo_eleitoral('370834200183') +'3708 3420 01 83' +>>> format_titulo_eleitoral('107381280116') +'1073 8128 01 16' +>>> +``` + # Novos Utilitários e Reportar Bugs Caso queira sugerir novas funcionalidades ou reportar bugs, basta criar diff --git a/README_EN.md b/README_EN.md index bdc58ae..4d5e670 100644 --- a/README_EN.md +++ b/README_EN.md @@ -84,6 +84,8 @@ False - [format_processo_juridico](#format_processo_juridico) - [remove\_symbols\_processo\_juridico](#remove_symbols_processo_juridico) - [generate_processo_juridico](#generate_processo_juridico) +- [Voter Registration Number](#voter-registration-number) + - [format_titulo_eleitoral](#format_titulo_eleitoral) ## CPF @@ -546,6 +548,20 @@ Generates a valid legal process number according to the arguments of _ano_ which >>> ``` +## Voter Registration Number + +### format_titulo_eleitoral + +Formats voter registration number (if valid) +```python +>>> from brutils import format_titulo_eleitoral +>>> format_titulo_eleitoral('370834200183') +'3708 3420 01 83' +>>> format_titulo_eleitoral('107381280116') +'1073 8128 01 16' +>>> +``` + # Feature Request and Bug Report If you want to suggest new features or report bugs, simply create diff --git a/brutils/__init__.py b/brutils/__init__.py index 9b2ba46..b4c1177 100644 --- a/brutils/__init__.py +++ b/brutils/__init__.py @@ -53,3 +53,5 @@ generate_processo_juridico, remove_symbols as remove_symbols_processo_juridico, ) + +from brutils.voter_registration import format as format_titulo_eleitoral diff --git a/brutils/voter_registration.py b/brutils/voter_registration.py new file mode 100644 index 0000000..ba0b271 --- /dev/null +++ b/brutils/voter_registration.py @@ -0,0 +1,77 @@ +import re +from typing import Optional + +# FORMATTING +############ + + +def format(voter_registration_number: str) -> Optional[str]: + """ + Formats a voter registration number with spaces + Returns None with invalid input + Ex: 017746811074 - > 0177 4681 10 74 + 117351120817 - > 1173 5112 08 17 + 6455327 - > 'None' + """ + + if not isinstance(voter_registration_number, str) or not _is_valid( + voter_registration_number + ): + return None + + first_four = voter_registration_number[0:4] + second_four = voter_registration_number[4:8] + federal_unit_numbers = voter_registration_number[8:10] + check_digits = voter_registration_number[10:12] + + return f"{first_four} {second_four} {federal_unit_numbers} {check_digits}" + + +# OPERATIONS +############ + + +def _is_valid(voter_registration_number: str) -> bool: + """ + Checks if the number check digit calculation is correct + Also checks if the Federal Unit identifier is valid (1 - 28) + """ + if not re.match(r"^\d{12}$", voter_registration_number): + return False + + fu_identifier = int(voter_registration_number[8:10]) + + if not (1 <= fu_identifier <= 28): + return False + + check_digits = voter_registration_number[10:12] + calculated_check_digits = _calculate_check_digits(voter_registration_number) + + return check_digits == calculated_check_digits + + +def _calculate_check_digits( + voter_registration_number: str, +): # type: (str) -> str + """ + Calculates voter registration number check digits + """ + first_digit_sum = sum( + int(digit) * (index + 2) + for index, digit in enumerate(voter_registration_number[:8]) + ) + calculated_first_digit = first_digit_sum % 11 + + if calculated_first_digit == 10: + calculated_first_digit = 0 + + second_digit_sum = sum( + int(digit) * (index + 7) + for index, digit in enumerate(voter_registration_number[8:11]) + ) + calculated_second_digit = second_digit_sum % 11 + + if calculated_second_digit == 10: + calculated_second_digit = 0 + + return f"{calculated_first_digit}{calculated_second_digit}" diff --git a/tests/test_voter_registration.py b/tests/test_voter_registration.py new file mode 100644 index 0000000..e521a67 --- /dev/null +++ b/tests/test_voter_registration.py @@ -0,0 +1,55 @@ +import unittest +from brutils import format_titulo_eleitoral + + +class TestVoterRegistrationFormat(unittest.TestCase): + def test_valid_number(self): + # Specific number + response = format_titulo_eleitoral("370834200183") + self.assertEqual( + response, "3708 3420 01 83", "AssertionError for 370834200183" + ) + + def test_valid_numbers_type_and_regexp(self): + # Valid voter registration numbers + valid_numbers = [ + "370834200183", + "107381280116", + "587816450175", + "260577100132", + "248146750108", + ] + for n in valid_numbers: + try: + formatted = format_titulo_eleitoral(n) + self.assertIsInstance(formatted, str) + self.assertRegex(formatted, r"^(\d{4} \d{4} \d{2} \d{2})$") + except: # noqa: E722 + print(f"AssertionError for number: {n}") + raise AssertionError + + def test_invalid_registration_numbers(self): + # Invalid voter registration numbers should return None + invalid_numbers = [ + "123", + "370834200181", + "107381280111", + "587816450179", + "goiaba", + ] + for n in invalid_numbers: + try: + self.assertEqual(format_titulo_eleitoral(n), None) + except: # noqa: E722 + print(f"AssertionError for number: {n}") + raise AssertionError + + def test_non_string_input(self): + # Non-string input should also return None + non_strings = [None, 123, True, ["test@example.com"]] + for value in non_strings: + self.assertEqual(format_titulo_eleitoral(value), None) + + def test_empty_string(self): + # Empty string should return None too + self.assertEqual(format_titulo_eleitoral(""), None) From d09d76fe9244e26bc754de551eca804a0cbb79b0 Mon Sep 17 00:00:00 2001 From: bermr Date: Tue, 17 Oct 2023 20:00:34 -0300 Subject: [PATCH 2/2] test: adiciona testes a fim de cobrir todos os casos --- tests/test_voter_registration.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_voter_registration.py b/tests/test_voter_registration.py index e521a67..8321c49 100644 --- a/tests/test_voter_registration.py +++ b/tests/test_voter_registration.py @@ -53,3 +53,19 @@ def test_non_string_input(self): def test_empty_string(self): # Empty string should return None too self.assertEqual(format_titulo_eleitoral(""), None) + + def test_invalid_fu(self): + # Document with invalid UF should fail + self.assertEqual(format_titulo_eleitoral("370834209983"), None) + + def test_mods_equal_zero(self): + # Ensure that digit calculation works when sum equals 10 + valid_numbers = ["175526800205", "417667660230"] + for n in valid_numbers: + try: + formatted = format_titulo_eleitoral(n) + self.assertIsInstance(formatted, str) + self.assertRegex(formatted, r"^(\d{4} \d{4} \d{2} \d{2})$") + except: # noqa: E722 + print(f"AssertionError for number: {n}") + raise AssertionError