Skip to content
Merged
Show file tree
Hide file tree
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 Oct 7, 2023
fcb27a9
Merge branch 'main' into titulo_eleitoral
VPeron Oct 7, 2023
28dbd2a
linted with black and codecov.io
VPeron Oct 7, 2023
22caedf
updated push
VPeron Oct 7, 2023
5546384
pushing again formatted code
VPeron Oct 8, 2023
70bc0a9
Update CHANGELOG.md
VPeron Oct 8, 2023
0d97a8b
improved docstrings
VPeron Oct 8, 2023
c695c0c
pulling latest changes to local
VPeron Oct 8, 2023
4cb4f2c
formatted new tests
VPeron Oct 8, 2023
47a8683
Update CHANGELOG.md
VPeron Oct 9, 2023
c96e4f6
improved readability for _verify_uf, _verify_dv2, _verify_dv1. rename…
VPeron Oct 9, 2023
22f0508
Merge branch 'main' into titulo_eleitoral
VPeron Oct 9, 2023
61d98a3
Update brutils/titulo_eleitoral.py
VPeron Oct 10, 2023
97f9789
Update brutils/titulo_eleitoral.py
VPeron Oct 10, 2023
2b63712
merging dv calculation and verification functions. reducing redundanc…
VPeron Oct 10, 2023
4257f6f
removed tit_ from variable names across the program. refactored calcu…
VPeron Oct 10, 2023
1cfda77
Merge branch 'main' into titulo_eleitoral
VPeron Oct 10, 2023
bcc88ee
Merge branch 'main' into titulo_eleitoral
VPeron Oct 11, 2023
699efe8
Update brutils/titulo_eleitoral.py
VPeron Oct 11, 2023
ba42802
Update brutils/titulo_eleitoral.py
VPeron Oct 11, 2023
98d63ac
streamlining local remote
VPeron Oct 11, 2023
353cf65
mostly renaming variables and functions for better readability, clean…
VPeron Oct 11, 2023
ef35e3b
adapting tests to function name change
VPeron Oct 11, 2023
b49abd9
adding tests _verify_dv1 and _verify_dv2 edge cases
VPeron Oct 14, 2023
f0a0719
Merge branch 'main' into titulo_eleitoral
VPeron Oct 14, 2023
f369850
Merge branch 'main' into titulo_eleitoral
VPeron Oct 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Utilitário `is_valid_titulo_eleitoral` [#235](https://github.com/brazilian-utils/brutils-python/pull/235)
- Suporte ao Python 3.12 [#245](https://github.com/brazilian-utils/brutils-python/pull/245)
- Utilitário `generate_processo_juridico` [#208](https://github.com/brazilian-utils/brutils-python/pull/208)
- Utilitário `get_license_plate_format` [#243](https://github.com/brazilian-utils/brutils-python/pull/243)
Expand Down
179 changes: 179 additions & 0 deletions brutils/titulo_eleitoral.py
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"]
Comment thread
camilamaia marked this conversation as resolved.


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
Comment thread
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
Comment thread
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)]
96 changes: 96 additions & 0 deletions tests/test_titulo_eleitoral.py
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()