Projeto Final: Gerenciamento de Notas Escolares (Parte 3)

Nas duas lições anteriores, construímos a camada de dados e a camada de lógica. Nesta lição, unimos tudo — escrevendo uma interface de menu de linha de comando completa. Após completar esta lição, você terá um sistema de gerenciamento de notas escolares verdadeiramente utilizável.


Código Completo do Sistema

Combine o código das três lições anteriores em um arquivo, mais a interface de menu:

Exemplo: Sistema Completo de Gerenciamento de Notas

PYTHON
import json
import os
import statistics

# ====== Configuração ======
DATA_FILE = "grade_system.json"

DEFAULT_DATA = {
    "students": {},
    "subjects": ["Português", "Matemática", "Inglês"],
    "classes": []
}

# ====== Camada de Dados ======

def load_data():
    if not os.path.exists(DATA_FILE):
        save_data(DEFAULT_DATA.copy())
        return DEFAULT_DATA.copy()
    with open(DATA_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

def save_data(data):
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

def add_student(student_id, name, class_name):
    data = load_data()
    if student_id in data["students"]:
        return False, f"Matrícula {student_id} já existe!"
    data["students"][student_id] = {"name": name, "class_name": class_name, "scores": {}}
    if class_name not in data["classes"]:
        data["classes"].append(class_name)
    save_data(data)
    return True, f"Aluno adicionado: {name} ({student_id})"

def delete_student(student_id):
    data = load_data()
    if student_id not in data["students"]:
        return False, f"Matrícula {student_id} não existe!"
    name = data["students"][student_id]["name"]
    del data["students"][student_id]
    save_data(data)
    return True, f"Aluno removido: {name}"

def add_score(student_id, subject, score):
    data = load_data()
    if student_id not in data["students"]:
        return False, f"Matrícula {student_id} não existe!"
    if subject not in data["subjects"]:
        return False, f"Disciplina {subject} não existe!"
    if not isinstance(score, (int, float)) or score < 0 or score > 100:
        return False, "A nota deve estar entre 0 e 100!"
    data["students"][student_id]["scores"][subject] = score
    save_data(data)
    return True, f"Inserida nota de {data['students'][student_id]['name']} em {subject}: {score}"

# ====== Camada de Lógica ======

def get_student_report(student_id):
    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["total"] = sum(scores)
        report["max_score"] = max(scores)
        report["min_score"] = min(scores)
    else:
        report["average"] = report["total"] = report["max_score"] = report["min_score"] = None
    return report

def get_class_ranking(class_name=None):
    data = load_data()
    results = []
    for sid, info in data["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({"id": sid, "name": info["name"], "class": info["class_name"],
                        "total": total, "avg": avg, "count": len(scores)})
    results.sort(key=lambda x: x["total"], reverse=True)
    for i, r in enumerate(results, 1):
        r["rank"] = i
    return results

def get_subject_stats():
    data = load_data()
    stats = {}
    for subject in data["subjects"]:
        scores = []
        for info in data["students"].values():
            if subject in info["scores"]:
                scores.append(info["scores"][subject])
        if scores:
            stats[subject] = {"avg": round(sum(scores) / len(scores), 1),
                              "max": max(scores), "min": min(scores), "count": len(scores)}
    return stats

# ====== Camada de Apresentação ======

def show_main_menu():
    print("\n" + "=" * 40)
    print("Sistema de Gerenciamento de Notas")
    print("=" * 40)
    print("1. Gerenciamento de Alunos")
    print("2. Gerenciamento de Notas")
    print("3. Estatísticas e Relatórios")
    print("4. Visualizar Dados")
    print("5. Sair")
    print("=" * 40)

def show_student_menu():
    print("\n--- Gerenciamento de Alunos ---")
    print("1. Adicionar Aluno")
    print("2. Remover Aluno")
    print("3. Voltar ao Menu Principal")

def show_score_menu():
    print("\n--- Gerenciamento de Notas ---")
    print("1. Inserir Nota")
    print("2. Voltar ao Menu Principal")

def show_stats_menu():
    print("\n--- Estatísticas e Relatórios ---")
    print("1. Boletim Individual")
    print("2. Ranking Geral")
    print("3. Médias por Disciplina")
    print("4. Voltar ao Menu Principal")

def student_management():
    """Submenu de gerenciamento de alunos"""
    while True:
        show_student_menu()
        choice = input("Digite a opção: ").strip()
        
        if choice == "1":
            sid = input("Matrícula: ").strip()
            name = input("Nome: ").strip()
            cls = input("Turma: ").strip()
            success, msg = add_student(sid, name, cls)
            print(f"  {'OK' if success else 'Erro'}: {msg}")
        
        elif choice == "2":
            sid = input("Digite a matrícula para remover: ").strip()
            success, msg = delete_student(sid)
            print(f"  {'OK' if success else 'Erro'}: {msg}")
        
        elif choice == "3":
            break
        
        else:
            print("Opção inválida!")

def score_management():
    """Submenu de gerenciamento de notas"""
    while True:
        show_score_menu()
        choice = input("Digite a opção: ").strip()
        
        if choice == "1":
            sid = input("Matrícula: ").strip()
            subject = input("Disciplina: ").strip()
            try:
                score = float(input("Nota (0-100): ").strip())
            except ValueError:
                print("Erro: A nota deve ser um número!")
                continue
            success, msg = add_score(sid, subject, score)
            print(f"  {'OK' if success else 'Erro'}: {msg}")
        
        elif choice == "2":
            break
        
        else:
            print("Opção inválida!")

def stats_query():
    """Submenu de consulta de estatísticas"""
    while True:
        show_stats_menu()
        choice = input("Digite a opção: ").strip()
        
        if choice == "1":
            sid = input("Digite a matrícula: ").strip()
            report = get_student_report(sid)
            if not report:
                print("Erro: Aluno não encontrado!")
                continue
            print(f"\nBoletim — {report['name']} ({report['class_name']})")
            print("-" * 30)
            for subject, score in report["scores"].items():
                print(f"  {subject}: {score}")
            if report["total"] is not None:
                print(f"  Total: {report['total']}")
                print(f"  Média: {report['average']}")
                print(f"  Maior: {report['max_score']}")
                print(f"  Menor: {report['min_score']}")
            else:
                print("  (Nenhuma nota ainda)")
        
        elif choice == "2":
            ranking = get_class_ranking()
            if not ranking:
                print("Sem dados!")
                continue
            print("\nRanking Geral")
            print(f"{'Rank':<4}{'Nome':<10}{'Turma':<10}{'Total':<8}{'Média':<6}")
            print("-" * 38)
            for r in ranking:
                print(f"{r['rank']:<4}{r['name']:<10}{r['class']:<10}{r['total']:<8}{r['avg']:<6}")
        
        elif choice == "3":
            stats = get_subject_stats()
            if not stats:
                print("Nenhum dado de nota!")
                continue
            print("\nEstatísticas por Disciplina")
            print(f"{'Disciplina':<12}{'Média':<10}{'Máx':<8}{'Mín':<8}{'Contagem':<4}")
            print("-" * 38)
            for subject, info in stats.items():
                print(f"{subject:<12}{info['avg']:<10}{info['max']:<8}{info['min']:<8}{info['count']:<4}")
        
        elif choice == "4":
            break
        
        else:
            print("Opção inválida!")

def view_data():
    """Visualizar dados brutos"""
    data = load_data()
    students = data["students"]
    if not students:
        print("Nenhum dado de aluno ainda")
        return
    
    print(f"\nTotal: {len(students)} alunos")
    for sid, info in sorted(students.items()):
        scores = info["scores"]
        score_str = ", ".join(f"{s}:{v}" for s, v in scores.items()) if scores else "Sem notas"
        print(f"  {sid} {info['name']} ({info['class_name']}) -> {score_str}")

# ====== Programa Principal ======

def main():
    print("Bem-vindo ao Sistema de Gerenciamento de Notas!")
    
    while True:
        show_main_menu()
        choice = input("Selecione opção (1-5): ").strip()
        
        if choice == "1":
            student_management()
        elif choice == "2":
            score_management()
        elif choice == "3":
            stats_query()
        elif choice == "4":
            view_data()
        elif choice == "5":
            print("Obrigado por usar o sistema! Até logo!")
            break
        else:
            print("Opção inválida. Digite 1-5.")


if __name__ == "__main__":
    main()
▶ Experimente

Execução de Exemplo

TEXT
Bem-vindo ao Sistema de Gerenciamento de Notas!

========================================
Sistema de Gerenciamento de Notas
========================================
1. Gerenciamento de Alunos
2. Gerenciamento de Notas
3. Estatísticas e Relatórios
4. Visualizar Dados
5. Sair
========================================
Selecione opção (1-5): 1

--- Gerenciamento de Alunos ---
1. Adicionar Aluno
2. Remover Aluno
3. Voltar ao Menu Principal
Digite a opção: 1
Matrícula: 2024001
Nome: João Silva
Turma: Turma 1
  OK: Aluno adicionado: João Silva (2024001)

Resumo do Projeto

Camada Funções Responsabilidade
Camada de Dados load/save_data E/S de arquivo JSON
Camada de Entidade add/delete_student CRUD de alunos
Camada de Entidade add_score CRUD de notas
Camada de Lógica get_student_report Boletim individual
Camada de Lógica get_class_ranking Cálculo de ranking
Camada de Lógica get_subject_stats Estatísticas por disciplina
Camada de Apresentação main/main_menu Laço do menu
Camada de Apresentação student/score/stats_management Submenus

❓ Perguntas Frequentes

P: O laço while True no menu de comandos não causa um laço infinito? R: Não, porque cada menu tem uma opção (ex.: digitar 0 ou um número específico) que executa break para sair do laço. Desde que cada menu tenha uma opção "sair," é seguro. P: A palavra-chave in é suficiente para busca difusa, ou preciso de expressões regulares? R: Para este projeto, keyword in name é suficiente. Se você precisar de buscas mais complexas (corresponder apenas no início, insensível a maiúsculas, combinações de múltiplas palavras-chave), você precisaria de expressões regulares — abordado na Lição 31. P: Como empacoto o projeto para outros usarem? R: A forma mais simples é enviar o arquivo .py diretamente — qualquer um com Python instalado pode executá-lo. Para criar um executável (.exe), use PyInstaller: pip install pyinstaller && pyinstaller --onefile main.py.


📖 Resumo


📝 Atividades

  1. Iniciante (Dificuldade ⭐): Execute o sistema completo, adicione 3 alunos e suas notas, e gere um relatório de ranking.

  2. Intermediário (Dificuldade ⭐⭐): Adicione um recurso de "exportar relatório" ao sistema que exporte os resultados do ranking para um arquivo ranking.csv (usando o módulo csv).

  3. Avançado (Dificuldade ⭐⭐⭐): Sem consultar o código acima, construa um "Sistema de Gerenciamento de Vendas de Livros" do zero. Recursos incluem: adicionar livro (título, autor, preço unitário, estoque), registrar venda (título do livro, quantidade -> diminui estoque automaticamente), visualizar relatório de estoque (ordenado por volume de vendas). Use JSON para persistência de dados.

100%