Projeto Final: Gerenciamento de Notas Escolares (Parte 2)
Na Lição 34, completamos a camada de dados — operações CRUD para alunos e notas. Nesta lição, construímos a camada de lógica sobre a camada de dados: cálculos estatísticos, ranking, análise de distribuição de notas e outras regras de negócio centrais. A Lição 36 adicionará a camada de apresentação (interface de menu) para completar o sistema.
Implementação da Camada de Lógica
Esta camada depende das funções da camada de dados da Lição 34 (load_data, get_all_students, etc.).
Exemplo: Camada de Lógica de Estatísticas de Notas
# ====== Camada de Lógica: Estatísticas e Análise de Notas ======
import statistics
from grade_project_part1 import load_data, get_all_students
def calculate_student_average(student_id):
"""Calcula a média de notas de um aluno"""
data = load_data()
student = data["students"].get(student_id)
if not student:
return None
scores = student["scores"].values()
if not scores:
return None
return sum(scores) / len(scores)
def get_student_report(student_id):
"""Gera um boletim individual"""
data = load_data()
student = data["students"].get(student_id)
if not student:
return None
report = {
"name": student["name"],
"class_name": student["class_name"],
"scores": dict(student["scores"]),
}
scores = list(student["scores"].values())
if scores:
report["average"] = round(sum(scores) / len(scores), 1)
report["max_score"] = max(scores)
report["min_score"] = min(scores)
report["total"] = sum(scores)
else:
report["average"] = None
report["max_score"] = None
report["min_score"] = None
report["total"] = 0
return report
def get_class_ranking(class_name=None):
"""Obtém o ranking da turma (por total de pontos decrescente)"""
data = load_data()
students = data["students"]
# Coleta todos os alunos (ou uma turma específica)
results = []
for sid, info in students.items():
if class_name and info["class_name"] != class_name:
continue
scores = list(info["scores"].values())
total = sum(scores) if scores else 0
avg = round(total / len(scores), 1) if scores else 0
results.append({
"student_id": sid,
"name": info["name"],
"class_name": info["class_name"],
"total": total,
"average": avg,
"score_count": len(scores)
})
# Ordenar por total decrescente
results.sort(key=lambda x: x["total"], reverse=True)
# Adicionar ranking
for i, r in enumerate(results, 1):
r["rank"] = i
return results
def get_subject_averages():
"""Calcula as médias de cada disciplina"""
data = load_data()
subjects = data["subjects"]
students = data["students"]
result = {}
for subject in subjects:
scores = []
for info in students.values():
if subject in info["scores"]:
scores.append(info["scores"][subject])
if scores:
result[subject] = {
"average": round(sum(scores) / len(scores), 1),
"max": max(scores),
"min": min(scores),
"count": len(scores)
}
else:
result[subject] = None
return result
def get_score_distribution(subject=None):
"""Calcula a distribuição de notas em faixas"""
data = load_data()
students = data["students"]
ranges = [
("90-100", 90, 101),
("80-89", 80, 90),
("70-79", 70, 80),
("60-69", 60, 70),
("0-59", 0, 60),
]
if subject:
# Contar distribuição para uma disciplina específica
result = {label: 0 for label, _, _ in ranges}
for info in students.values():
if subject in info["scores"]:
score = info["scores"][subject]
for label, low, high in ranges:
if low <= score < high:
result[label] += 1
break
return result
else:
# Contar distribuição entre todas as disciplinas combinadas
result = {label: 0 for label, _, _ in ranges}
for info in students.values():
for score in info["scores"].values():
for label, low, high in ranges:
if low <= score < high:
result[label] += 1
break
return result
def get_students_by_class():
"""Agrupa alunos por turma"""
data = load_data()
students = data["students"]
classes = {}
for sid, info in students.items():
cls = info["class_name"]
if cls not in classes:
classes[cls] = []
classes[cls].append({
"student_id": sid,
"name": info["name"],
"score_count": len(info["scores"])
})
return classes
Teste da Camada de Lógica
Exemplo: Teste Funcional da Camada de Lógica
if __name__ == "__main__":
print("=== Boletim Individual ===")
report = get_student_report("2024001")
if report:
print(f"Nome: {report['name']}")
print(f"Turma: {report['class_name']}")
for subject, score in report["scores"].items():
print(f" {subject}: {score}")
print(f"Total: {report['total']}")
print(f"Média: {report['average']}")
print(f"Máx: {report['max_score']}")
print(f"Mín: {report['min_score']}")
print("\n=== Ranking da Turma ===")
ranking = get_class_ranking()
for r in ranking[:5]:
print(f"#{r['rank']}: {r['name']} ({r['class_name']}) Total {r['total']}")
print("\n=== Médias por Disciplina ===")
averages = get_subject_averages()
for subject, info in averages.items():
if info:
print(f"{subject}: Média {info['average']}, Máx {info['max']}, Mín {info['min']}")
print("\n=== Distribuição de Notas ===")
dist = get_score_distribution()
for label, count in dist.items():
bar = "#" * count
print(f"{label}: {bar} {count} alunos")
Nota: O
importacima assume que o arquivo da camada de dados se chamagrade_project_part1.py. Modifique a declaração de import se o nome do arquivo for diferente. No sistema completo final, todo o código será mesclado em um único arquivo.
Design de Tratamento de Exceções
A camada de lógica lida com os seguintes casos excepcionais:
| Cenário | Valor de Retorno | Descrição |
|---|---|---|
| Aluno não existe | None ou resultado vazio |
Chamador verifica o valor de retorno |
| Sem dados de nota | Valor estatístico é None |
Chamador exibe "Nenhuma nota ainda" |
| Dados vazios | Lista vazia/dict vazio | Não quebra, exibe mensagem amigável |
| Dados inválidos | Exceção capturada | Protegido por try-except |
❓ Perguntas Frequentes
P: Como entender
key=lambdana ordenação? R: O parâmetrokeyespecifica o critério de ordenação.lambda item: item[1]significa "pegue o segundo elemento de cada item como chave de ordenação." Isso equivale adef get_score(item): return item[1]— lambda é apenas uma abreviação. P: RetornarNoneem vez de 0 não dificulta as coisas para o chamador? R: RetornarNonedistingue entre "nenhuma nota existe" e "a nota é realmente 0" — uma diferença significativa. O chamador pode usarif result is not None:para verificar, o que é mais seguro do que tratar erroneamente "sem dados" como "0 pontos."
📖 Resumo
- A camada de lógica fica sobre a camada de dados, focando em "computação" em vez de "armazenamento"
- Boletim individual: exibe notas por disciplina, total, média, máximo/mínimo
- Ranking da turma: ordenado por total de pontos decrescente, com números de rank gerados automaticamente
- Médias por disciplina: itera notas de todos os alunos, agrega por disciplina
- Distribuição de notas: conta alunos em cada faixa de nota
- Tratamento de exceções: retorna
Noneem vez de erro quando aluno/nota não existe
📝 Atividades
-
Iniciante (Dificuldade ⭐): Chame
get_student_report()para visualizar um boletim completo de um aluno e verifique se a saída está correta. -
Intermediário (Dificuldade ⭐⭐): Adicione uma função
get_top_n(n)à camada de lógica que retorna os N melhores alunos por total de pontos em toda a escola. -
Avançado (Dificuldade ⭐⭐⭐): Adicione uma função
get_subject_pass_rate(subject, pass_score=60)à camada de lógica que calcula a taxa de aprovação para uma determinada disciplina (alunos com nota >= pass_score / total de alunos). Depois calcule e compare as taxas de aprovação entre todas as disciplinas.



