-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adds ira calculator with unit tests (#229)
* feat: adds ira calculator with unit tests * refac: removes magic numbers * refac: adds typing to discipline dict * refac: adds messages to captured exceptions * fix: changes math for ira calculation * fix: fixes math again * feat: adds testing for some disciplines during first semester * feat: adds more input validation * refac: adds comments that explain the calculation * refac: changes variable names to english and return value * refac: corrects error handling
- Loading branch information
1 parent
b3e8235
commit 8442bd6
Showing
2 changed files
with
216 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,93 @@ | ||
from typing import TypedDict | ||
|
||
class Discipline(TypedDict): | ||
""" | ||
TypedDict que define uma disciplina. | ||
Attributes: | ||
grade: a menção obtida pelo aluno na disciplina. | ||
number_of_credits: a quantidade de créditos que a disciplina tem. | ||
semester: qual o semestre em que o aluno realizou a disciplina. O valor mínimo é 1, e o máximo é 6. | ||
""" | ||
grade: str | ||
number_of_credits: int | ||
semester: int | ||
|
||
class IraCalculator: | ||
""" | ||
Classe que calcula o valor do IRA a partir de um conjunto de disciplinas. | ||
Atualmente, o cálculo está sendo baseado com base no | ||
recurso do seguinte link: 'https://deg.unb.br/images/legislacao/resolucao_ceg_0001_2020.pdf' | ||
Para uma disciplina, nos interessam as seguintes variáveis: | ||
E -> Equivalência da menção de disciplina (isto é, SS=5, MS=4,..., SR=0); | ||
C -> Número de créditos daquela disciplina; | ||
S -> Semestre em que aquela disciplina foi cursada, sendo 6 o seu valor máximo. | ||
Realiza-se o somatório de E*C*S para cada disciplina, e depois divide-se pelo somatório de C*S para cada uma delas. | ||
""" | ||
|
||
def __init__(self) -> None: | ||
self.grade_map = { | ||
'SS': 5, | ||
'MS': 4, | ||
'MM': 3, | ||
'MI': 2, | ||
'II': 1, | ||
'SR': 0, | ||
} | ||
|
||
self.semester_range = { | ||
'min': 1, | ||
'max': 6, | ||
} | ||
|
||
|
||
def get_ira_value(self, disciplines: list[Discipline]) -> float: | ||
""" | ||
Obter o valor do IRA a partir de um conjunto de menções. | ||
:param disciplines: A lista de disciplinas que um aluno pegou. | ||
:returns: Um float com o valor calculado do IRA. | ||
""" | ||
|
||
numerator: int = 0 | ||
denominator: int = 0 | ||
|
||
# para o cálculo do IRA, o maior valor possível para semestre é 6, mesmo | ||
# que o estudante esteja num semestre maior que esse | ||
MAX_SEMESTER_NUMBER: int = self.semester_range['max'] | ||
|
||
for discipline in disciplines: | ||
|
||
## validação da entrada | ||
try: | ||
if discipline['number_of_credits'] <= 0: | ||
raise ValueError("O número de créditos da disciplina é menor ou igual a 0.") | ||
|
||
discipline['semester'] = min(discipline['semester'], MAX_SEMESTER_NUMBER) | ||
|
||
if not (self.semester_range['min'] <= discipline['semester'] <= self.semester_range['max']): | ||
raise ValueError( | ||
f"O semestre está fora do intervalo delimitado entre {self.semester_range['min']} e {self.semester_range['max']}." | ||
) | ||
|
||
if discipline['grade'].upper() not in self.grade_map.keys(): | ||
raise ValueError(f"A menção {discipline['grade']} não existe.") | ||
|
||
except TypeError: | ||
raise TypeError("O tipo de dado passado como parâmetro está incorreto.") | ||
|
||
|
||
## cálculo do IRA | ||
numerator += self.grade_map[discipline['grade'].upper()] * \ | ||
discipline['number_of_credits'] * \ | ||
discipline['semester'] | ||
|
||
denominator += discipline['number_of_credits'] * discipline['semester'] | ||
|
||
return float(numerator / denominator) | ||
|
||
|
||
|
This file contains 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,123 @@ | ||
from django.test import TestCase | ||
from utils.ira_calculator import IraCalculator, Discipline | ||
|
||
|
||
class IraTestCase(TestCase): | ||
def setUp(self): | ||
self.ira_calc = IraCalculator() | ||
|
||
def test_one_discipline_with_MM(self): | ||
args: list[Discipline] = [ | ||
{ | ||
'grade': 'MM', | ||
'semester': 1, | ||
'number_of_credits': 2, | ||
} | ||
] | ||
|
||
self.assertEqual(self.ira_calc.get_ira_value(args), 3) | ||
|
||
def test_discipline_with_left_out_of_bounds_semester_value(self): | ||
args: list[Discipline] = [ | ||
{ | ||
'grade': 'MM', | ||
'semester': 0, | ||
'number_of_credits': 2, | ||
}, | ||
] | ||
|
||
self.assertRaises(ValueError, self.ira_calc.get_ira_value, args) | ||
|
||
def test_discipline_with_incorrect_semester_type(self): | ||
args: list[Discipline] = [ | ||
{ | ||
'grade': 'MM', | ||
'semester': '0', | ||
'number_of_credits': 2, | ||
}, | ||
] | ||
|
||
self.assertRaises(TypeError, self.ira_calc.get_ira_value, args) | ||
|
||
|
||
def test_inexistent_grade(self): | ||
args: list[Discipline] = [ | ||
{ | ||
'grade': 'NE', | ||
'semester': 3, | ||
'number_of_credits': 2, | ||
}, | ||
] | ||
self.assertRaises(ValueError, self.ira_calc.get_ira_value, args) | ||
|
||
def test_multiple_disciplines_during_first_semester(self): | ||
args: list[Discipline] = [ | ||
{ | ||
'grade': 'MM', | ||
'semester': 1, | ||
'number_of_credits': 4 | ||
}, | ||
{ | ||
'grade': 'MS', | ||
'semester': 1, | ||
'number_of_credits': 6 | ||
}, | ||
{ | ||
'grade': 'SS', | ||
'semester': 1, | ||
'number_of_credits': 6 | ||
}, | ||
{ | ||
'grade': 'SS', | ||
'semester': 1, | ||
'number_of_credits': 4 | ||
}, | ||
{ | ||
'grade': 'MS', | ||
'semester': 1, | ||
'number_of_credits': 4 | ||
}, | ||
] | ||
|
||
self.assertEqual(self.ira_calc.get_ira_value(args), 4.25) | ||
|
||
def test_negative_number_of_credits(self): | ||
args: list[Discipline] = [ | ||
{ | ||
'grade': 'MM', | ||
'semester': 3, | ||
'number_of_credits': -1, | ||
}, | ||
] | ||
self.assertRaises(ValueError, self.ira_calc.get_ira_value, args) | ||
|
||
def test_null_number_of_credits(self): | ||
args: list[Discipline] = [ | ||
{ | ||
'grade': 'MM', | ||
'semester': 3, | ||
'number_of_credits': -1, | ||
}, | ||
] | ||
self.assertRaises(ValueError, self.ira_calc.get_ira_value, args) | ||
|
||
def test_none_number_of_credits(self): | ||
args: list[Discipline] = [ | ||
{ | ||
'grade': 'MM', | ||
'semester': 2, | ||
'number_of_credits': None, | ||
}, | ||
] | ||
self.assertRaises(TypeError, self.ira_calc.get_ira_value, args) | ||
|
||
def tests_discipline_with_lowercase_grade_value(self): | ||
args: list[Discipline] = [ | ||
{ | ||
'grade': 'mm', | ||
'semester': 1, | ||
'number_of_credits': 2, | ||
} | ||
] | ||
|
||
self.assertEqual(self.ira_calc.get_ira_value(args), 3) |