-
-
Notifications
You must be signed in to change notification settings - Fork 115
Feature: Titulo eleitoral #235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
c3b967a
added files brutils/titulo_eleitoral.py, tests/test_titulo_eleitoral.…
VPeron fcb27a9
Merge branch 'main' into titulo_eleitoral
VPeron 28dbd2a
linted with black and codecov.io
VPeron 22caedf
updated push
VPeron 5546384
pushing again formatted code
VPeron 70bc0a9
Update CHANGELOG.md
VPeron 0d97a8b
improved docstrings
VPeron c695c0c
pulling latest changes to local
VPeron 4cb4f2c
formatted new tests
VPeron 47a8683
Update CHANGELOG.md
VPeron c96e4f6
improved readability for _verify_uf, _verify_dv2, _verify_dv1. rename…
VPeron 22f0508
Merge branch 'main' into titulo_eleitoral
VPeron 61d98a3
Update brutils/titulo_eleitoral.py
VPeron 97f9789
Update brutils/titulo_eleitoral.py
VPeron 2b63712
merging dv calculation and verification functions. reducing redundanc…
VPeron 4257f6f
removed tit_ from variable names across the program. refactored calcu…
VPeron 1cfda77
Merge branch 'main' into titulo_eleitoral
VPeron bcc88ee
Merge branch 'main' into titulo_eleitoral
VPeron 699efe8
Update brutils/titulo_eleitoral.py
VPeron ba42802
Update brutils/titulo_eleitoral.py
VPeron 98d63ac
streamlining local remote
VPeron 353cf65
mostly renaming variables and functions for better readability, clean…
VPeron ef35e3b
adapting tests to function name change
VPeron b49abd9
adding tests _verify_dv1 and _verify_dv2 edge cases
VPeron f0a0719
Merge branch 'main' into titulo_eleitoral
VPeron f369850
Merge branch 'main' into titulo_eleitoral
VPeron File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| def is_valid_titulo_eleitoral(numero_titulo: str): | ||
| """ | ||
| Return True when 'numero_titulo' is a valid titulo eleitoral | ||
| brasileiro, False otherwise. | ||
| Input should be a string of proper length. Some specific positional | ||
| characters need to adeher to certain conditions in order to be validated. | ||
| References: https://pt.wikipedia.org/wiki/T%C3%ADtulo_de_eleitor, | ||
| http://clubes.obmep.org.br/blog/a-matematica-nos-documentos-titulo-de-eleitor/ | ||
|
|
||
| Args: | ||
| numero_titulo[str]: string representing the titulo | ||
| eleitoral to be verified | ||
|
|
||
| Returns: | ||
| bool[bool]: boolean indicating whether the given numero_titulo | ||
| is a valid string conforming with the titulo eleitoral build rules | ||
| """ | ||
|
|
||
| # ensure 'numero_titulo' only contains numerical characters | ||
| if not isinstance(numero_titulo, str) or not numero_titulo.isdigit(): | ||
| return False | ||
|
|
||
| # split string into 'numero sequencial', 'unidade federativa' & | ||
| # 'digitos verificadores' | ||
| # edge case: 13-digit length mitigates here. The 9th sequential | ||
| # digit is not used for calculations. | ||
| sequential_number, uf, dig_verifiers = _get_titulo_eleitoral_parts( | ||
| numero_titulo | ||
| ) | ||
|
|
||
| # verify length | ||
| if not _verify_length(numero_titulo, uf): | ||
| return False | ||
|
|
||
| # calculate dv1 | ||
| dv1 = _calculate_dv1(sequential_number) | ||
|
|
||
| verified_dv1 = _verify_dv1(dv1, uf, dig_verifiers) | ||
|
|
||
| # calculate dv2 | ||
| verified_dv2 = _verify_dv2(uf, dv1, dig_verifiers) | ||
|
|
||
| # verify UF | ||
| verified_uf = _verify_uf(uf) | ||
|
|
||
| # return True if all conditions are met, else False | ||
| return all([verified_dv1, verified_dv2, verified_uf]) | ||
|
|
||
|
|
||
| def _get_titulo_eleitoral_parts(input_string: str): | ||
| """split string into 'numero sequencial', 'unidade federativa' & | ||
| 'digitos verificadores' | ||
| here I indexed uf and dig_verifiers from the back due to the | ||
| below reference: | ||
| '- Alguns títulos eleitorais de São Paulo e Minas Gerais podem ter nove | ||
| dígitos em seu número sequencial, em vez de oito. Tal fato não compromete | ||
| o cálculo dos DVs, pois este é feito com base nos oito primeiros números | ||
| sequenciais.' | ||
| http://clubes.obmep.org.br/blog/a-matematica-nos-documentos-titulo-de-eleitor/ | ||
|
|
||
| Args: | ||
| input_string[str]: an example of titulo eleitoral brasileiro | ||
|
|
||
| Returns: | ||
| sequential_number[list]: sequential number sliced from input_string | ||
| uf[list]: Unidade Federal sliced from input_string | ||
| dig_verifiers[list]: DV sliced from input_string | ||
| """ | ||
| sequential_number = input_string[:8] | ||
| uf = input_string[-4:-2] | ||
| dig_verifiers = input_string[-2:] | ||
|
|
||
| return sequential_number, uf, dig_verifiers | ||
|
|
||
|
|
||
| def _verify_length(numero_titulo, uf): | ||
| """ | ||
| verify length of numero_titulo | ||
| considers edge case with 13 digits for SP & MG | ||
| Args: | ||
| numero_titulo[str]: an example of titulo eleitoral brasileiro | ||
| uf[list]: Unidade Federal sliced from input_string | ||
|
|
||
| Returns: | ||
| length_verified[bool]: boolean indicating whether the length of | ||
| numero_titulo is verified, or not. | ||
|
|
||
| """ | ||
| if len(numero_titulo) == 12: | ||
| return True | ||
|
|
||
| # edge case: for SP & MG with 9 digit long 'numero sequencial' | ||
| return len(numero_titulo) == 13 and uf in ["01", "02"] | ||
|
|
||
|
|
||
| def _calculate_dv1(sequential_number): | ||
| """calculate dv1 | ||
| Args: | ||
| sequential_number[list]: sequential number sliced from input_string | ||
| Returns: | ||
| v1[list]: list containing the resulting operation to calculate v1 | ||
| """ | ||
| x1, x2, x3, x4, x5, x6, x7, x8 = range(2, 10) | ||
|
|
||
| v1 = ( | ||
| (int(sequential_number[0]) * x1) | ||
| + (int(sequential_number[1]) * x2) | ||
| + (int(sequential_number[2]) * x3) | ||
| + (int(sequential_number[3]) * x4) | ||
| + (int(sequential_number[4]) * x5) | ||
| + (int(sequential_number[5]) * x6) | ||
| + (int(sequential_number[6]) * x7) | ||
| + (int(sequential_number[7]) * x8) | ||
| ) | ||
|
|
||
| dv1 = v1 % 11 | ||
|
|
||
| return dv1 | ||
|
|
||
|
|
||
| def _verify_dv1(dv1, uf, dig_verifiers): | ||
| """verify dv1 | ||
|
|
||
| Args: | ||
| dv1[int]: calculated digito verificador 1 | ||
| uf[list]: UF slice of the given titulo eleitoral | ||
| dig_verifiers[list]: DV sliced from the given | ||
| titulo eleitoral string | ||
| Returns: | ||
| bool[bool]: boolean indicating whether dv1 has been | ||
| verified or not. | ||
| """ | ||
| # edge case: dv1 mod 11 == 0 and UF is SP or MG | ||
| if dv1 == 0 and uf in ["01", "02"]: | ||
| dv1 = 1 | ||
|
|
||
| # edge case: dv1 == 10, declare as zero instead | ||
| if dv1 == 10: | ||
| dv1 = 0 | ||
|
camilamaia marked this conversation as resolved.
|
||
|
|
||
| # verify dv1 | ||
| return int(dig_verifiers[0]) == dv1 | ||
|
|
||
|
|
||
| def _verify_dv2(uf, dv1, dig_verifiers): | ||
| """calculate dv2 | ||
|
|
||
| Args: | ||
| uf[list]: Unidade Federal sliced from input_string | ||
| dv1[int]: result from v1 mod 11 operation | ||
| dig_verifiers[str]: Digits verifiers from titulo eleitoral | ||
|
|
||
| Returns: | ||
| dv2[int]: result from v2 mod 11 operation | ||
|
|
||
| """ | ||
| x9, x10, x11 = 7, 8, 9 | ||
| v2 = (int(uf[0]) * x9) + (int(uf[1]) * x10) + (dv1 * x11) | ||
|
|
||
| dv2 = v2 % 11 | ||
| # edge case: if UF is "01" or "02" (for SP & MG) AND dv2 == 0 | ||
| # declare dv2 as 1 instead | ||
| if uf in ["01", "02"] and dv2 == 0: | ||
| dv2 = 1 | ||
|
camilamaia marked this conversation as resolved.
|
||
|
|
||
| return int(dig_verifiers[1]) == dv2 | ||
|
|
||
|
|
||
| def _verify_uf(uf): | ||
| """verify UF | ||
|
|
||
| Args: | ||
| uf[list]: Unidade Federal sliced from input_string | ||
| Returns: | ||
| verified_uf[bool]: boolean indicating whether UF has been verified, | ||
| or not. | ||
| """ | ||
|
|
||
| return uf in ["{:02d}".format(i) for i in range(1, 29)] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| import unittest | ||
| from brutils.titulo_eleitoral import ( | ||
| is_valid_titulo_eleitoral, | ||
| _get_titulo_eleitoral_parts, | ||
| _verify_length, | ||
| _verify_dv1, | ||
| _verify_dv2, | ||
| ) | ||
|
|
||
|
|
||
| class TestIsValidTituloEleitoral(unittest.TestCase): | ||
| def test_valid_titulo_eleitoral(self): | ||
| # test a valid titulo eleitoral number | ||
| valid_titulo = "217633460930" | ||
| self.assertTrue(is_valid_titulo_eleitoral(valid_titulo)) | ||
|
|
||
| def test_invalid_titulo_eleitoral(self): | ||
| # test an invalid titulo eleitoral number (dv1 & UF fail) | ||
| invalid_titulo = "123456789011" | ||
| self.assertFalse(is_valid_titulo_eleitoral(invalid_titulo)) | ||
|
|
||
| def test_invalid_length(self): | ||
| # Test an invalid length for titulo eleitoral | ||
| invalid_length_short = "12345678901" | ||
| invalid_length_long = "1234567890123" | ||
| self.assertFalse(is_valid_titulo_eleitoral(invalid_length_short)) | ||
| self.assertFalse(is_valid_titulo_eleitoral(invalid_length_long)) | ||
|
|
||
| def test_invalid_characters(self): | ||
| # Test titulo eleitoral with non-numeric characters | ||
| invalid_characters = "ABCD56789012" | ||
| invalid_characters_space = "217633 460 930" | ||
| self.assertFalse(is_valid_titulo_eleitoral(invalid_characters)) | ||
| self.assertFalse(is_valid_titulo_eleitoral(invalid_characters_space)) | ||
|
|
||
| def test_valid_special_case(self): | ||
| # Test a valid edge case (SP & MG with 13 digits) | ||
| valid_special = "3244567800167" | ||
| self.assertTrue(is_valid_titulo_eleitoral(valid_special)) | ||
|
|
||
|
|
||
| class TestSplitString(unittest.TestCase): | ||
| def test_get_titulo_eleitoral_parts(self): | ||
| input_string = "12345678AB12" | ||
| ( | ||
| tit_sequence, | ||
| tit_unid_fed, | ||
| tit_dig_verifiers, | ||
| ) = _get_titulo_eleitoral_parts(input_string) | ||
| self.assertEqual(tit_sequence, "12345678") | ||
| self.assertEqual(tit_unid_fed, "AB") | ||
| self.assertEqual(tit_dig_verifiers, "12") | ||
|
|
||
|
|
||
| class TestVerifyLength(unittest.TestCase): | ||
| def test_valid_length(self): | ||
| numero_titulo = "123456789012" | ||
| tit_unid_fed = "AB" | ||
| self.assertTrue(_verify_length(numero_titulo, tit_unid_fed)) | ||
|
|
||
| def test_invalid_length(self): | ||
| numero_titulo = "12345678AB123" # Invalid length | ||
| tit_unid_fed = "AB" | ||
| self.assertFalse(_verify_length(numero_titulo, tit_unid_fed)) | ||
|
|
||
|
|
||
| class TestVerifyDv1(unittest.TestCase): | ||
| def test_verify_dv1(self): | ||
| # test dv1 converts from 10 to 0 and UF is "01" (SP) | ||
| self.assertTrue(_verify_dv1(10, "01", "01")) | ||
| # test dv1 converts from 10 to 0 and UF is "02" (MG) | ||
| self.assertTrue(_verify_dv1(10, "02", "01")) | ||
|
|
||
| def test_dv1_ten_edge_case(self): | ||
| # test dv1 is 10, which should be treated as 0 | ||
| self.assertTrue(_verify_dv1(10, "04", "05")) | ||
|
|
||
| def test_dv1_zero_edge_case(self): | ||
| # test dv1 is 0, declare as 1 instead with SP or MG as UF | ||
| self.assertTrue(_verify_dv1(0, "01", "16")) | ||
| self.assertTrue(_verify_dv1(0, "02", "15")) | ||
|
|
||
|
|
||
| class TestVerifyDv2(unittest.TestCase): | ||
| def test_verify_dv2(self): | ||
| self.assertTrue(_verify_dv2("03", 6, "01")) | ||
|
|
||
| def test_dv2_zero_edge_case(self): | ||
| # edge case: if UF is "01" or "02" (for SP & MG) AND dv2 == 0 | ||
| # declare dv2 as 1 instead | ||
| self.assertTrue(_verify_dv2("01", 9, "01")) | ||
| self.assertTrue(_verify_dv2("01", 4, "41")) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.