diff --git a/Guia1/src/__pycache__/__init__.cpython-312.pyc b/Guia1/src/__pycache__/__init__.cpython-312.pyc index 5080441..c2e8914 100644 Binary files a/Guia1/src/__pycache__/__init__.cpython-312.pyc and b/Guia1/src/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia1/src/__pycache__/main.cpython-312.pyc b/Guia1/src/__pycache__/main.cpython-312.pyc index 6f42861..c4de38c 100644 Binary files a/Guia1/src/__pycache__/main.cpython-312.pyc and b/Guia1/src/__pycache__/main.cpython-312.pyc differ diff --git a/Guia1/src/config/__pycache__/__init__.cpython-312.pyc b/Guia1/src/config/__pycache__/__init__.cpython-312.pyc index 48f2979..88833c4 100644 Binary files a/Guia1/src/config/__pycache__/__init__.cpython-312.pyc and b/Guia1/src/config/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia1/src/config/__pycache__/settings.cpython-312.pyc b/Guia1/src/config/__pycache__/settings.cpython-312.pyc index 0fed221..818d552 100644 Binary files a/Guia1/src/config/__pycache__/settings.cpython-312.pyc and b/Guia1/src/config/__pycache__/settings.cpython-312.pyc differ diff --git a/Guia1/src/models/__pycache__/__init__.cpython-312.pyc b/Guia1/src/models/__pycache__/__init__.cpython-312.pyc index f2285cc..3d35f9b 100644 Binary files a/Guia1/src/models/__pycache__/__init__.cpython-312.pyc and b/Guia1/src/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia1/src/models/__pycache__/record.cpython-312.pyc b/Guia1/src/models/__pycache__/record.cpython-312.pyc index 42aff45..5a010ae 100644 Binary files a/Guia1/src/models/__pycache__/record.cpython-312.pyc and b/Guia1/src/models/__pycache__/record.cpython-312.pyc differ diff --git a/Guia1/src/models/record.py b/Guia1/src/models/record.py index 5c5bc4c..8f9a1cf 100644 --- a/Guia1/src/models/record.py +++ b/Guia1/src/models/record.py @@ -1,8 +1,22 @@ class Record: def __init__(self, record_id: int, name: str, address: str): - self._id = record_id - self._name = name - self._address = address + try: + id_convertido = int(record_id) + except (ValueError, TypeError): + raise ValueError("ID deve ser um número inteiro válido.") + + if id_convertido < 0: + raise ValueError(f'ID deve ser inteiro E positivo.') + + if not name or name.strip() == '': + raise ValueError(f'Nome não pode ser vazio.') + + if not address or address.strip() == '': + raise ValueError(f'Endereço não pode ser vazio.') + + self._id = id_convertido + self._name = name.strip() + self._address = address.strip() @property def id(self): @@ -15,6 +29,7 @@ def name(self): @property def address(self): return self._address - + + def __repr__(self): return f"Record(id={self._id}, name='{self._name}', address='{self._address}')" \ No newline at end of file diff --git a/Guia1/src/repositories/__pycache__/__init__.cpython-312.pyc b/Guia1/src/repositories/__pycache__/__init__.cpython-312.pyc index 219943d..bfbeb34 100644 Binary files a/Guia1/src/repositories/__pycache__/__init__.cpython-312.pyc and b/Guia1/src/repositories/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia1/src/repositories/__pycache__/abstract_repository.cpython-312.pyc b/Guia1/src/repositories/__pycache__/abstract_repository.cpython-312.pyc index 75a73ff..b805afd 100644 Binary files a/Guia1/src/repositories/__pycache__/abstract_repository.cpython-312.pyc and b/Guia1/src/repositories/__pycache__/abstract_repository.cpython-312.pyc differ diff --git a/Guia1/src/repositories/__pycache__/record_repository.cpython-312.pyc b/Guia1/src/repositories/__pycache__/record_repository.cpython-312.pyc index f76d38a..47e921e 100644 Binary files a/Guia1/src/repositories/__pycache__/record_repository.cpython-312.pyc and b/Guia1/src/repositories/__pycache__/record_repository.cpython-312.pyc differ diff --git a/Guia1/src/repositories/record_repository.py b/Guia1/src/repositories/record_repository.py index bded279..afaba5b 100644 --- a/Guia1/src/repositories/record_repository.py +++ b/Guia1/src/repositories/record_repository.py @@ -10,15 +10,35 @@ def __init__(self, file_path: str): def load_all(self): data = FileLoader.load_csv(self._file_path) - self._records = [ - Record(int(row["id"]), row["name"], row["address"]) - for row in data - ] + self._records = [] + for row in data: + try: + novo_registro = Record(row["id"], row["name"], row["address"]) + self._records.append(novo_registro) + except ValueError: + print(f"Registro inválido ignorado: {{'id': '{row['id']}', 'name': '{row['name']}', 'address': '{row['address']}'}}") + continue + return self._records def search(self, term: str): - term = term.lower() - return [ - r for r in self._records - if term in r.name.lower() or term in r.address.lower() - ] \ No newline at end of file + term_lista = term.lower().split() + + if not term_lista: + return [] + + resultados = [] + + for r in self._records: + palavras_do_registro = r.name.lower().split() + r.address.lower().split() + print(f'{palavras_do_registro}') + contem_todos = True + for palavra in term_lista: + if palavra not in palavras_do_registro: + contem_todos = False + break + + if contem_todos: + resultados.append(r) + + return resultados \ No newline at end of file diff --git a/Guia1/src/services/__pycache__/__init__.cpython-312.pyc b/Guia1/src/services/__pycache__/__init__.cpython-312.pyc index 7156b34..123b4f6 100644 Binary files a/Guia1/src/services/__pycache__/__init__.cpython-312.pyc and b/Guia1/src/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia1/src/services/__pycache__/record_service.cpython-312.pyc b/Guia1/src/services/__pycache__/record_service.cpython-312.pyc index 29288bf..88163aa 100644 Binary files a/Guia1/src/services/__pycache__/record_service.cpython-312.pyc and b/Guia1/src/services/__pycache__/record_service.cpython-312.pyc differ diff --git a/Guia1/src/utils/__pycache__/__init__.cpython-312.pyc b/Guia1/src/utils/__pycache__/__init__.cpython-312.pyc index 2548ac6..34da656 100644 Binary files a/Guia1/src/utils/__pycache__/__init__.cpython-312.pyc and b/Guia1/src/utils/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia1/src/utils/__pycache__/file_loader.cpython-312.pyc b/Guia1/src/utils/__pycache__/file_loader.cpython-312.pyc index 6b5ca6a..73497f3 100644 Binary files a/Guia1/src/utils/__pycache__/file_loader.cpython-312.pyc and b/Guia1/src/utils/__pycache__/file_loader.cpython-312.pyc differ diff --git a/Guia1/tests/__pycache__/__init__.cpython-312.pyc b/Guia1/tests/__pycache__/__init__.cpython-312.pyc index d4029fa..36a4562 100644 Binary files a/Guia1/tests/__pycache__/__init__.cpython-312.pyc and b/Guia1/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia1/tests/__pycache__/test_runner.cpython-312.pyc b/Guia1/tests/__pycache__/test_runner.cpython-312.pyc index 5cc7ad8..683638b 100644 Binary files a/Guia1/tests/__pycache__/test_runner.cpython-312.pyc and b/Guia1/tests/__pycache__/test_runner.cpython-312.pyc differ diff --git a/Guia1/tests/test_runner.py b/Guia1/tests/test_runner.py index f8004a8..acbadee 100644 --- a/Guia1/tests/test_runner.py +++ b/Guia1/tests/test_runner.py @@ -53,7 +53,7 @@ def test_search_multiple_terms(self): try: self.service.get_all_records() - results = self.service.search("joao rua a") + results = self.service.search("joão rua a") if len(results) == 0: print("FALHA: Nenhum resultado encontrado") @@ -61,7 +61,7 @@ def test_search_multiple_terms(self): for r in results: text = (r.name + " " + r.address).lower() - if "joao" not in text or "rua" not in text or "a" not in text: + if "joão" not in text or "rua" not in text or "a" not in text: print("FALHA: Resultado incorreto na busca") return diff --git a/Guia2/src/__pycache__/__init__.cpython-314.pyc b/Guia2/src/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..822db3f Binary files /dev/null and b/Guia2/src/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia2/src/folha_pagamento/__pycache__/__init__.cpython-314.pyc b/Guia2/src/folha_pagamento/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..2c67547 Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia2/src/folha_pagamento/__pycache__/desenvolvedor.cpython-314.pyc b/Guia2/src/folha_pagamento/__pycache__/desenvolvedor.cpython-314.pyc new file mode 100644 index 0000000..7aba32b Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/desenvolvedor.cpython-314.pyc differ diff --git a/Guia2/src/folha_pagamento/__pycache__/estagiario.cpython-314.pyc b/Guia2/src/folha_pagamento/__pycache__/estagiario.cpython-314.pyc new file mode 100644 index 0000000..f57ee22 Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/estagiario.cpython-314.pyc differ diff --git a/Guia2/src/folha_pagamento/__pycache__/funcionario.cpython-314.pyc b/Guia2/src/folha_pagamento/__pycache__/funcionario.cpython-314.pyc new file mode 100644 index 0000000..ed28de1 Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/funcionario.cpython-314.pyc differ diff --git a/Guia2/src/folha_pagamento/__pycache__/gerente.cpython-314.pyc b/Guia2/src/folha_pagamento/__pycache__/gerente.cpython-314.pyc new file mode 100644 index 0000000..8d8dcb0 Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/gerente.cpython-314.pyc differ diff --git a/Guia2/src/folha_pagamento/desenvolvedor.py b/Guia2/src/folha_pagamento/desenvolvedor.py index 5c5d3c9..f284a47 100644 --- a/Guia2/src/folha_pagamento/desenvolvedor.py +++ b/Guia2/src/folha_pagamento/desenvolvedor.py @@ -1,6 +1,28 @@ from folha_pagamento.funcionario import Funcionario -# Desenvolva a classe Desenvolvedor aqui. +class Desenvolvedor(Funcionario): + def __init__(self, nome, matricula, salario_base, linguagem, senioridade): + super().__init__(nome, matricula, salario_base) + self.linguagem = linguagem + self.senioridade = senioridade -class Desenvolvedor: - pass \ No newline at end of file + def calcular_bonus(self): + if self.senioridade == 'junior': + return (self.salario_base * 0.05) + elif self.senioridade == 'pleno': + return (self.salario_base * 0.1) + elif self.senioridade == 'senior': + return (self.salario_base * 0.15) + + def calcular_descontos(self): + return (self.salario_base * 0.08) + + def calcular_adicionais(self): + if self.linguagem == 'Python': + return 500 + elif self.linguagem == 'Java': + return 400 + elif self.linguagem == 'JavaScript': + return 350 + else: + return 200 \ No newline at end of file diff --git a/Guia2/src/folha_pagamento/estagiario.py b/Guia2/src/folha_pagamento/estagiario.py index d50a433..6214b3e 100644 --- a/Guia2/src/folha_pagamento/estagiario.py +++ b/Guia2/src/folha_pagamento/estagiario.py @@ -1,6 +1,21 @@ from folha_pagamento.funcionario import Funcionario -# Desenvolva a classe Estagiario aqui. +class Estagiario(Funcionario): + def __init__(self, nome, matricula, salario_base, curso, carga_horaria): + super().__init__(nome, matricula, salario_base) + self.curso = curso + self.carga_horaria = carga_horaria -class Estagiario: - pass \ No newline at end of file + def calcular_bonus(self) -> float: + return (self.salario_base * 0.03) + + def calcular_descontos(self) -> float: + return (self.salario_base * 0.02) + + def calcular_adicionais(self) -> float: + if self.carga_horaria <= 20: + return 150 + elif self.carga_horaria <= 30: + return 250 + else: + return 350 \ No newline at end of file diff --git a/Guia2/src/folha_pagamento/gerente.py b/Guia2/src/folha_pagamento/gerente.py index 31819a1..7f4bce9 100644 --- a/Guia2/src/folha_pagamento/gerente.py +++ b/Guia2/src/folha_pagamento/gerente.py @@ -1,6 +1,26 @@ from folha_pagamento.funcionario import Funcionario -# Desenvolva a classe Gerente aqui. +class Gerente(Funcionario): + def __init__(self, nome, matricula, salario_base, setor, qtd_equipe): + super().__init__(nome, matricula, salario_base) + self.setor = setor + self.qtd_equipe = qtd_equipe -class Gerente: - pass \ No newline at end of file + def calcular_bonus(self): + if self.qtd_equipe <= 5: + return (self.salario_base * 0.1) + elif 5 < self.qtd_equipe <= 10: + return (self.salario_base * 0.15) + elif self.qtd_equipe > 10: + return (self.salario_base * 0.2) + + def calcular_descontos(self): + return (self.salario_base * 0.12) + + def calcular_adicionais(self): + if self.qtd_equipe > 10: + return 2000 + elif self.qtd_equipe > 5: + return 1000 + else: + return 500 \ No newline at end of file diff --git a/Guia2/tests/__pycache__/__init__.cpython-314.pyc b/Guia2/tests/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..4de7bd1 Binary files /dev/null and b/Guia2/tests/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia2/tests/__pycache__/test_desenvolvedor.cpython-314-pytest-9.0.3.pyc b/Guia2/tests/__pycache__/test_desenvolvedor.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 0000000..1612223 Binary files /dev/null and b/Guia2/tests/__pycache__/test_desenvolvedor.cpython-314-pytest-9.0.3.pyc differ diff --git a/Guia2/tests/__pycache__/test_estagiario.cpython-314-pytest-9.0.3.pyc b/Guia2/tests/__pycache__/test_estagiario.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 0000000..a287e66 Binary files /dev/null and b/Guia2/tests/__pycache__/test_estagiario.cpython-314-pytest-9.0.3.pyc differ diff --git a/Guia2/tests/__pycache__/test_gerente.cpython-314-pytest-9.0.3.pyc b/Guia2/tests/__pycache__/test_gerente.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 0000000..1271208 Binary files /dev/null and b/Guia2/tests/__pycache__/test_gerente.cpython-314-pytest-9.0.3.pyc differ diff --git a/Guia3/.gitignore b/Guia3/.gitignore new file mode 100644 index 0000000..4b3bf0c --- /dev/null +++ b/Guia3/.gitignore @@ -0,0 +1,13 @@ +__pycache__/ +*.pyc +*.pyo +*.pyd +venv/ +.venv/ +.venv +env/ +.env +.pytest_cache/ +.pytest_cache +.coverage +htmlcov/ diff --git a/Guia3/README.md b/Guia3/README.md new file mode 100644 index 0000000..c6877eb --- /dev/null +++ b/Guia3/README.md @@ -0,0 +1,252 @@ +# Guia 3 — Sistema de Quiz + +## Contexto + +Você faz parte da equipe responsável por desenvolver um **Sistema de Quiz** educativo. + +O sistema deve permitir a criação de quizzes (questionários) com perguntas de múltipla escolha ou discursivas, validação de respostas e cálculo de pontuação. + +O projeto já possui a estrutura de pastas definidas. Sua missão é **implementar/completar** as classes seguindo o **diagrama UML** e as regras abaixo, passando em todos os testes. + +--- + +## Diagrama UML + +```mermaid +classDiagram + direction TB + + class Pergunta { + <> + -String texto + -String? explicacao_geral //Pode ser None + +validar_resposta(resposta) boolean + +get_explicacao() String + +get_tipo() String + } + + class PerguntaMultiplaEscolha { + -List~Alternativa~ alternativas + +validar_resposta(int indice) boolean + +get_alternativa_correta() Alternativa + } + + class PerguntaDiscursiva { + -String? resposta_esperada //Pode ser None + +validar_resposta(String texto) boolean + } + + class Alternativa { + +String texto + +boolean correta + +String? explicacao //Pode ser None + } + + class Resposta { + <> + -Pergunta pergunta + -boolean esta_correta + -float pontuacao_obtida + +calcular_pontuacao() float + } + + class RespostaObjetiva { + -int indice_escolhido + -Alternativa? alternativa_selecionada //Pode ser None + } + + class RespostaDiscursiva { + -String texto_resposta + } + + class Questionario { + -String titulo + -List~Pergunta~ perguntas + +adicionar_pergunta(Pergunta p) + +criar_attempt(String usuario) QuizAttempt + } + + class TentativaQuestionario { + -Questionario questionario + -String usuario + -DateTime? data_inicio //Pode ser None + -DateTime? data_fim //Pode ser None + -List~Resposta~ respostas + +registrar_resposta(int indice_pergunta, Object valor) + +finalizar() Tuple~float, String~ + +calcular_pontuacao() float + +is_finalizado() boolean + } + + %% Relacionamentos + Pergunta <|-- PerguntaMultiplaEscolha + Pergunta <|-- PerguntaDiscursiva + Resposta <|-- RespostaObjetiva + Resposta <|-- RespostaDiscursiva + + PerguntaMultiplaEscolha "1" *-- "2..*" Alternativa + Questionario "1" *-- "0..*" Pergunta + Questionario "1" --> "0..*" TentativaQuestionario + TentativaQuestionario "1" *-- "0..*" Resposta + Resposta "1" --> "1" Pergunta +``` + +--- + +## Descrição das Classes + +### Pergunta (Classe Abstrata) + +Classe base para todas as perguntas do sistema. + +Atributos: +- texto: String — Enunciado da pergunta. +- explicacao_geral: String? (opcional) — Texto explicativo mostrado após a correção. + +Métodos: +- validar_resposta(resposta) → boolean — Valida a resposta (implementado nas subclasses). +- get_explicacao() → String — Retorna a explicação geral. +- get_tipo() → String — Retorna o tipo ("multipla_escolha" ou "discursiva"). + +### PerguntaMultiplaEscolha + +Herda de Pergunta. Perguntas com alternativas. + +Atributos: +- alternativas: List[Alternativa] — Lista de alternativas. + +Métodos: +- validar_resposta(int indice) → boolean — Valida o índice escolhido. +- get_alternativa_correta() → Alternativa — Retorna a alternativa correta. + +### PerguntaDiscursiva + +Herda de Pergunta. Perguntas com resposta em texto livre. + +Atributos: +- resposta_esperada: String? (opcional) — Resposta considerada correta. +- case_sensitive: boolean — Diferencia maiúsculas/minúsculas. + +Métodos: +- validar_resposta(String texto) → boolean — Compara texto do usuário. + +### Alternativa + +Representa uma opção em perguntas de múltipla escolha. + +Atributos: +- texto: String — Texto da alternativa. +- correta: boolean — Indica se é correta. +- explicacao: String? (opcional) — Explicação da alternativa. + +### Resposta (Classe Abstrata) + +Classe base para respostas dadas pelo usuário. + +Atributos: +- pergunta: Pergunta — Pergunta respondida. +- esta_correta: boolean — Se a resposta está correta. +- pontuacao_obtida: float — Pontuação obtida. + +Métodos: +- calcular_pontuacao() → float — Calcula pontuação. + +### RespostaObjetiva + +Herda de Resposta. Para perguntas de múltipla escolha. + +Atributos: +- indice_escolhido: int — Índice escolhido. +- alternativa_selecionada: Alternativa? (opcional) + +### RespostaDiscursiva + +Herda de Resposta. Para perguntas discursivas. + +Atributos: +- texto_resposta: String — Texto digitado pelo usuário. + +### Quiz + +Modelo/template do quiz (criado uma vez). + +Atributos: +- titulo: String — Título do quiz. +- perguntas: List[Pergunta] — Lista de perguntas. + +Métodos: +- adicionar_pergunta(Pergunta p) +- criar_attempt(String usuario) → QuizAttempt + +### QuizAttempt + +Representa uma tentativa de responder o quiz. + +Atributos: +- quiz: Quiz — Quiz original. +- usuario: String — Usuário. +- data_inicio: DateTime? +- data_fim: DateTime? +- respostas: List[Resposta] + +Métodos: +- registrar_resposta(int indice_pergunta, Object valor) +- finalizar() → (float, String) +- calcular_pontuacao() → float +- is_finalizado() → boolean + +--- + +## Como prepara o ambiente e rodar os testes? + +#### 1. Criar ambiente virutal + +Na pasta do projeto ..\Guia3> executar o comando: + +```bash +python -m venv .venv +``` + +#### 2. Ativar Ambiente Virtual + +Isso garante que qualquer modificação precise ser feita, seja realizada em um Ambiente Virtual controlado e não produza conflitos entre pacotes de outros projetos. + +PowerShell do Windows: +```bash +.\.venv\Scripts\activate +``` +macOS / Linux: +```bash +source .venv/bin/activate +``` +#### 3. Instalar dependências + +Na pasta do projeto ..\Guia3> executar o comando: + +```bash +pip install -r requirements.txt +``` + +#### Rodar exemplo + +Na pasta do projeto ..\Guia3> executar o comando: + +```bash +python main.py +``` + +#### Rodar testes + +Na pasta do projeto ..\Guia3> executar o comando: + +bash +``` +pytest -v +``` + +ou + +bash +``` +python -m pytest -v +``` \ No newline at end of file diff --git a/Guia3/main.py b/Guia3/main.py new file mode 100644 index 0000000..e9e667d --- /dev/null +++ b/Guia3/main.py @@ -0,0 +1,8 @@ +from Guia3.src import * + +def main(): + pass + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Guia3/pytest.ini b/Guia3/pytest.ini new file mode 100644 index 0000000..97a0030 --- /dev/null +++ b/Guia3/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +pythonpath = src +testpaths = tests \ No newline at end of file diff --git a/Guia3/requirements.txt b/Guia3/requirements.txt new file mode 100644 index 0000000..b820918 --- /dev/null +++ b/Guia3/requirements.txt @@ -0,0 +1,2 @@ +pytest>=8.0.0 +pytest-cov>=4.0.0 diff --git a/Guia3/src/__init__.py b/Guia3/src/__init__.py new file mode 100644 index 0000000..9283af9 --- /dev/null +++ b/Guia3/src/__init__.py @@ -0,0 +1,9 @@ +from .alternativa import Alternativa +from .pergunta import Pergunta +from .perguntadiscursiva import PerguntaDiscursiva +from .perguntamultiplaescolha import PerguntaMultiplaEscolha +from .questionario import Questionario +from .resposta import Resposta +from .respostadiscursiva import RespostaDiscursiva +from .respostaobjetiva import RespostaObjetiva +from .tentativaquestionario import TentativaQuestionario \ No newline at end of file diff --git a/Guia3/src/alternativa.py b/Guia3/src/alternativa.py new file mode 100644 index 0000000..4a0caa5 --- /dev/null +++ b/Guia3/src/alternativa.py @@ -0,0 +1,7 @@ +from typing import List, Tuple, Dict + +class Alternativa: + def __init__(self, texto, correta, explicacao = None): + self.texto = texto + self.correta = correta + self.explicacao = explicacao diff --git a/Guia3/src/pergunta.py b/Guia3/src/pergunta.py new file mode 100644 index 0000000..3914a15 --- /dev/null +++ b/Guia3/src/pergunta.py @@ -0,0 +1,18 @@ +from typing import List, Tuple, Dict +from abc import ABC, abstractmethod + +class Pergunta(ABC): + def __init__(self, texto, explicacao_geral = None): + self.texto = texto + self.explicacao_geral = explicacao_geral + + def get_explicacao(self): + return self.explicacao_geral + + @abstractmethod + def validar_resposta(self, resposta): + pass + + @abstractmethod + def get_tipo(self): + return self.get_tipo \ No newline at end of file diff --git a/Guia3/src/perguntadiscursiva.py b/Guia3/src/perguntadiscursiva.py new file mode 100644 index 0000000..c348ac5 --- /dev/null +++ b/Guia3/src/perguntadiscursiva.py @@ -0,0 +1,24 @@ +from typing import List, Tuple, Dict +from pergunta import Pergunta + + +class PerguntaDiscursiva(Pergunta): + def __init__(self, texto, resposta_esperada = None, case_sensitive = False): + super().__init__(texto) + self.resposta_esperada = resposta_esperada + self.case_sensitive = case_sensitive + + def validar_resposta(self, texto): + if self.resposta_esperada is None: + return False + + resp_usuario = texto.strip() + resp_esperada = self.resposta_esperada.strip() + + if not self.case_sensitive: + return resp_usuario.lower() == resp_esperada.lower() + + return resp_usuario == resp_esperada + + def get_tipo(self): + return "discursiva" diff --git a/Guia3/src/perguntamultiplaescolha.py b/Guia3/src/perguntamultiplaescolha.py new file mode 100644 index 0000000..5df295a --- /dev/null +++ b/Guia3/src/perguntamultiplaescolha.py @@ -0,0 +1,19 @@ +from typing import List, Tuple, Dict +from pergunta import Pergunta + +class PerguntaMultiplaEscolha(Pergunta): + def __init__(self, texto, alternativas, explicacao_geral = None): + super().__init__(texto, explicacao_geral) + self.alternativas = alternativas + + def validar_resposta(self, indice): + return self.alternativas[indice].correta + + def get_alternativa_correta(self): + for alternativa in self.alternativas: + if alternativa.correta: + return alternativa + return None + + def get_tipo(self): + return "multipla_escolha" \ No newline at end of file diff --git a/Guia3/src/questionario.py b/Guia3/src/questionario.py new file mode 100644 index 0000000..12b71ab --- /dev/null +++ b/Guia3/src/questionario.py @@ -0,0 +1,13 @@ +from typing import List, Tuple, Dict +from src.tentativaquestionario import TentativaQuestionario + +class Questionario: + def __init__(self, titulo): + self.titulo = titulo + self.perguntas = [] + + def adicionar_pergunta(self, pergunta): + self.perguntas.append(pergunta) + + def criar_attempt(self, usuario): + return TentativaQuestionario(self, usuario) \ No newline at end of file diff --git a/Guia3/src/resposta.py b/Guia3/src/resposta.py new file mode 100644 index 0000000..be525ef --- /dev/null +++ b/Guia3/src/resposta.py @@ -0,0 +1,15 @@ +from typing import List, Tuple, Dict +from abc import ABC, abstractmethod + +class Resposta(ABC): + def __init__(self, pergunta): + if type(self) is Resposta: + raise TypeError(f'Não é permitido instanciar a classe abstrata Resposta diretamente.') + + self.pergunta = pergunta + self.esta_correta = False + self.pontuacao_obtida = 0.0 + + @abstractmethod + def calcular_pontuacao(self): + pass \ No newline at end of file diff --git a/Guia3/src/respostadiscursiva.py b/Guia3/src/respostadiscursiva.py new file mode 100644 index 0000000..db680c3 --- /dev/null +++ b/Guia3/src/respostadiscursiva.py @@ -0,0 +1,16 @@ +from typing import List, Tuple, Dict +from resposta import Resposta + +class RespostaDiscursiva(Resposta): + def __init__(self, pergunta, texto_resposta): + super().__init__(pergunta) + self.texto_resposta = texto_resposta + self.esta_correta = pergunta.validar_resposta(texto_resposta) + + def calcular_pontuacao(self): + if self.esta_correta: + self.pontuacao_obtida = 1.0 + else: + self.pontuacao_obtida = 0.0 + + return self.pontuacao_obtida \ No newline at end of file diff --git a/Guia3/src/respostaobjetiva.py b/Guia3/src/respostaobjetiva.py new file mode 100644 index 0000000..78ac518 --- /dev/null +++ b/Guia3/src/respostaobjetiva.py @@ -0,0 +1,19 @@ +from typing import List, Tuple, Dict +from resposta import Resposta + +class RespostaObjetiva(Resposta): + def __init__(self, pergunta, indice_escolhido): + super().__init__(pergunta) + self.indice_escolhido = indice_escolhido + self.esta_correta = pergunta.validar_resposta(indice_escolhido) + self.alternativa_selecionada = pergunta.alternativas[indice_escolhido] + + def calcular_pontuacao(self): + if self.esta_correta: + self.pontuacao_obtida = 1.0 + else: + self.pontuacao_obtida = 0.0 + + return self.pontuacao_obtida + + \ No newline at end of file diff --git a/Guia3/src/tentativaquestionario.py b/Guia3/src/tentativaquestionario.py new file mode 100644 index 0000000..4ae0c67 --- /dev/null +++ b/Guia3/src/tentativaquestionario.py @@ -0,0 +1,37 @@ +from datetime import datetime +from src.respostadiscursiva import RespostaDiscursiva +from src.respostaobjetiva import RespostaObjetiva + +class TentativaQuestionario: + def __init__(self, questionario, usuario): + self.questionario = questionario + self.usuario = usuario + self.respostas = [] + self.data_inicio = datetime.now() + self.data_fim = None + self.finalizado = False + + def registrar_resposta(self, indice_pergunta, valor): + pergunta = self.questionario.perguntas[indice_pergunta] + if pergunta.get_tipo() == "multipla_escolha": + nova_resposta = RespostaObjetiva(pergunta, valor) + else: + nova_resposta = RespostaDiscursiva(pergunta, valor) + + self.respostas.append(nova_resposta) + + def calcular_pontuacao(self): + total = 0.0 + for resp in self.respostas: + total += resp.calcular_pontuacao() + return total + + def finalizar(self): + self.data_fim = datetime.now() + self.finalizado = True + nota_final = self.calcular_pontuacao() + + return (nota_final, "Finalizado") + + def is_finalizado(self): + return self.finalizado \ No newline at end of file diff --git a/Guia3/tests/__init__.py b/Guia3/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Guia3/tests/test_alternativa.py b/Guia3/tests/test_alternativa.py new file mode 100644 index 0000000..41b5c6d --- /dev/null +++ b/Guia3/tests/test_alternativa.py @@ -0,0 +1,21 @@ +import pytest + +from src.alternativa import Alternativa + + +def test_criar_alternativa(): + alt = Alternativa( + texto="Python", + correta=True, + explicacao="Linguagem interpretada" + ) + + assert alt.texto == "Python" + assert alt.correta is True + assert alt.explicacao == "Linguagem interpretada" + + +def test_alternativa_sem_explicacao(): + alt = Alternativa("Java", False) + + assert alt.explicacao is None \ No newline at end of file diff --git a/Guia3/tests/test_pergunta.py b/Guia3/tests/test_pergunta.py new file mode 100644 index 0000000..160c4db --- /dev/null +++ b/Guia3/tests/test_pergunta.py @@ -0,0 +1,8 @@ +import pytest + +from src.pergunta import Pergunta + + +def test_nao_instanciar_pergunta_abstract(): + with pytest.raises(TypeError): + Pergunta("Pergunta abstrata") \ No newline at end of file diff --git a/Guia3/tests/test_perguntadiscursiva.py b/Guia3/tests/test_perguntadiscursiva.py new file mode 100644 index 0000000..04505a7 --- /dev/null +++ b/Guia3/tests/test_perguntadiscursiva.py @@ -0,0 +1,39 @@ +from src.perguntadiscursiva import PerguntaDiscursiva + + +def test_validar_resposta_correta(): + pergunta = PerguntaDiscursiva( + texto="O que é POO?", + resposta_esperada="Programação Orientada a Objetos" + ) + + resposta = "Programação Orientada a Objetos" + + assert pergunta.validar_resposta(resposta) is True + + +def test_validar_resposta_errada(): + pergunta = PerguntaDiscursiva( + texto="O que é POO?", + resposta_esperada="Programação Orientada a Objetos" + ) + + resposta = "Banco de dados" + + assert pergunta.validar_resposta(resposta) is False + + +def test_pergunta_sem_resposta_esperada(): + pergunta = PerguntaDiscursiva( + texto="Explique encapsulamento." + ) + + assert pergunta.resposta_esperada is None + + +def test_get_tipo(): + pergunta = PerguntaDiscursiva( + texto="Explique herança." + ) + + assert pergunta.get_tipo() == "discursiva" \ No newline at end of file diff --git a/Guia3/tests/test_perguntamultiplaescolha.py b/Guia3/tests/test_perguntamultiplaescolha.py new file mode 100644 index 0000000..45f3d32 --- /dev/null +++ b/Guia3/tests/test_perguntamultiplaescolha.py @@ -0,0 +1,51 @@ +from src.alternativa import Alternativa +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha + + +def criar_pergunta(): + alternativas = [ + Alternativa("Java", False), + Alternativa("Python", True), + Alternativa("C", False), + ] + + return PerguntaMultiplaEscolha( + texto="Qual linguagem é interpretada?", + alternativas=alternativas, + explicacao_geral="Python normalmente é interpretada." + ) + + +def test_validar_resposta_correta(): + pergunta = criar_pergunta() + + assert pergunta.validar_resposta(1) is True + + +def test_validar_resposta_errada(): + pergunta = criar_pergunta() + + assert pergunta.validar_resposta(0) is False + + +def test_get_alternativa_correta(): + pergunta = criar_pergunta() + + correta = pergunta.get_alternativa_correta() + + assert correta.texto == "Python" + assert correta.correta is True + + +def test_get_tipo(): + pergunta = criar_pergunta() + + assert pergunta.get_tipo() == "multipla_escolha" + + +def test_get_explicacao(): + pergunta = criar_pergunta() + + assert pergunta.get_explicacao() == ( + "Python normalmente é interpretada." + ) \ No newline at end of file diff --git a/Guia3/tests/test_questionario.py b/Guia3/tests/test_questionario.py new file mode 100644 index 0000000..c592025 --- /dev/null +++ b/Guia3/tests/test_questionario.py @@ -0,0 +1,27 @@ +import pytest + +from src.questionario import Questionario +from src.perguntadiscursiva import PerguntaDiscursiva +from src.tentativaquestionario import TentativaQuestionario + + +def test_adicionar_pergunta(): + questionario = Questionario("Quiz POO") + + pergunta = PerguntaDiscursiva( + texto="O que é encapsulamento?" + ) + + questionario.adicionar_pergunta(pergunta) + + assert len(questionario.perguntas) == 1 + + +def test_criar_attempt(): + questionario = Questionario("Quiz Redes") + + tentativa = questionario.criar_attempt("valter") + + assert isinstance(tentativa, TentativaQuestionario) + assert tentativa.usuario == "valter" + diff --git a/Guia3/tests/test_resposta.py b/Guia3/tests/test_resposta.py new file mode 100644 index 0000000..fd9a6d6 --- /dev/null +++ b/Guia3/tests/test_resposta.py @@ -0,0 +1,8 @@ +import pytest + +from src.resposta import Resposta + + +def test_nao_instanciar_resposta_abstract(): + with pytest.raises(TypeError): + Resposta(None) \ No newline at end of file diff --git a/Guia3/tests/test_respostadiscursiva.py b/Guia3/tests/test_respostadiscursiva.py new file mode 100644 index 0000000..b1201d4 --- /dev/null +++ b/Guia3/tests/test_respostadiscursiva.py @@ -0,0 +1,42 @@ +from src.perguntadiscursiva import PerguntaDiscursiva +from src.respostadiscursiva import RespostaDiscursiva + + +def criar_pergunta(): + return PerguntaDiscursiva( + texto="O que significa CPU?", + resposta_esperada="Central Processing Unit" + ) + + +def test_resposta_discursiva_correta(): + pergunta = criar_pergunta() + + resposta = RespostaDiscursiva( + pergunta=pergunta, + texto_resposta="Central Processing Unit" + ) + + assert resposta.esta_correta is True + + +def test_resposta_discursiva_errada(): + pergunta = criar_pergunta() + + resposta = RespostaDiscursiva( + pergunta=pergunta, + texto_resposta="Memória RAM" + ) + + assert resposta.esta_correta is False + + +def test_calcular_pontuacao(): + pergunta = criar_pergunta() + + resposta = RespostaDiscursiva( + pergunta=pergunta, + texto_resposta="Central Processing Unit" + ) + + assert resposta.calcular_pontuacao() == 1.0 \ No newline at end of file diff --git a/Guia3/tests/test_respostaobjetiva.py b/Guia3/tests/test_respostaobjetiva.py new file mode 100644 index 0000000..e2a0739 --- /dev/null +++ b/Guia3/tests/test_respostaobjetiva.py @@ -0,0 +1,59 @@ +from src.alternativa import Alternativa +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha +from src.respostaobjetiva import RespostaObjetiva + + +def criar_pergunta(): + alternativas = [ + Alternativa("HTTP", False), + Alternativa("TCP/IP", True), + ] + + return PerguntaMultiplaEscolha( + texto="Qual protocolo é base da internet?", + alternativas=alternativas + ) + + +def test_resposta_objetiva_correta(): + pergunta = criar_pergunta() + + resposta = RespostaObjetiva( + pergunta=pergunta, + indice_escolhido=1 + ) + + assert resposta.esta_correta is True + + +def test_resposta_objetiva_errada(): + pergunta = criar_pergunta() + + resposta = RespostaObjetiva( + pergunta=pergunta, + indice_escolhido=0 + ) + + assert resposta.esta_correta is False + + +def test_calcular_pontuacao_correta(): + pergunta = criar_pergunta() + + resposta = RespostaObjetiva( + pergunta=pergunta, + indice_escolhido=1 + ) + + assert resposta.calcular_pontuacao() == 1.0 + + +def test_calcular_pontuacao_errada(): + pergunta = criar_pergunta() + + resposta = RespostaObjetiva( + pergunta=pergunta, + indice_escolhido=0 + ) + + assert resposta.calcular_pontuacao() == 0.0 \ No newline at end of file diff --git a/Guia3/tests/test_tentativaquestionario.py b/Guia3/tests/test_tentativaquestionario.py new file mode 100644 index 0000000..e0f4ede --- /dev/null +++ b/Guia3/tests/test_tentativaquestionario.py @@ -0,0 +1,90 @@ +from src.alternativa import Alternativa +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha +from src.perguntadiscursiva import PerguntaDiscursiva +from src.questionario import Questionario +from src.tentativaquestionario import TentativaQuestionario + + +def criar_questionario(): + q = Questionario("Quiz") + + p1 = PerguntaMultiplaEscolha( + texto="2 + 2?", + alternativas=[ + Alternativa("3", False), + Alternativa("4", True), + ] + ) + + p2 = PerguntaDiscursiva( + texto="Sigla CPU", + resposta_esperada="Central Processing Unit" + ) + + q.adicionar_pergunta(p1) + q.adicionar_pergunta(p2) + + return q + + +def test_registrar_resposta_objetiva(): + q = criar_questionario() + + tentativa = TentativaQuestionario( + questionario=q, + usuario="valter" + ) + + tentativa.registrar_resposta(0, 1) + + assert len(tentativa.respostas) == 1 + + +def test_registrar_resposta_discursiva(): + q = criar_questionario() + + tentativa = TentativaQuestionario( + questionario=q, + usuario="valter" + ) + + tentativa.registrar_resposta( + 1, + "Central Processing Unit" + ) + + assert len(tentativa.respostas) == 1 + + +def test_calcular_pontuacao(): + q = criar_questionario() + + tentativa = TentativaQuestionario( + questionario=q, + usuario="valter" + ) + + tentativa.registrar_resposta(0, 1) + tentativa.registrar_resposta( + 1, + "Central Processing Unit" + ) + + assert tentativa.calcular_pontuacao() == 2.0 + + +def test_finalizar(): + q = criar_questionario() + + tentativa = TentativaQuestionario( + questionario=q, + usuario="valter" + ) + + tentativa.registrar_resposta(0, 1) + + pontuacao, feedback = tentativa.finalizar() + + assert pontuacao >= 0 + assert isinstance(feedback, str) + assert tentativa.is_finalizado() is True \ No newline at end of file diff --git a/Guia4/.gitignore b/Guia4/.gitignore new file mode 100644 index 0000000..4b3bf0c --- /dev/null +++ b/Guia4/.gitignore @@ -0,0 +1,13 @@ +__pycache__/ +*.pyc +*.pyo +*.pyd +venv/ +.venv/ +.venv +env/ +.env +.pytest_cache/ +.pytest_cache +.coverage +htmlcov/ diff --git a/Guia4/README.md b/Guia4/README.md new file mode 100644 index 0000000..ca95454 --- /dev/null +++ b/Guia4/README.md @@ -0,0 +1,173 @@ +# Guia 4 — Serviço LLM + +## Contexto +Você faz parte da equipe responsável por desenvolver um **Sistema de Quiz** educativo. +O sistema deve permitir a criação de quizzes com perguntas de múltipla escolha ou **discursivas**, validação de respostas e cálculo de pontuação. + +Agora, **perguntas discursivas** deverão ser corrigidas por um serviço de LLM (Groq) via API. +Sua missão é implementar/completar as classes seguindo o **diagrama UML** complementar e as regras abaixo. + +--- + +## Diagrama UML + +### 1. Diagrama Principal (mantido exatamente como estava) + +```mermaid +classDiagram + direction TB + class Pergunta { + <> + -String texto + -String? explicacao_geral + +validar_resposta(resposta) boolean + +get_explicacao() String + +get_tipo() String + } + class PerguntaMultiplaEscolha { + -List~Alternativa~ alternativas + +validar_resposta(int indice) boolean + +get_alternativa_correta() Alternativa + } + class PerguntaDiscursiva { + -String? resposta_esperada + +validar_resposta(String texto) boolean + } + class Alternativa { + +String texto + +boolean correta + +String? explicacao + } + class Resposta { + <> + -Pergunta pergunta + -boolean esta_correta + -float pontuacao_obtida + +calcular_pontuacao() float + } + class RespostaObjetiva { + -int indice_escolhido + -Alternativa? alternativa_selecionada + } + class RespostaDiscursiva { + -String texto_resposta + } + class Questionario { + -String titulo + -List~Pergunta~ perguntas + +adicionar_pergunta(Pergunta p) + +criar_attempt(String usuario) QuizAttempt + } + class TentativaQuestionario { + -Questionario questionario + -String usuario + -DateTime? data_inicio + -DateTime? data_fim + -List~Resposta~ respostas + +registrar_resposta(int indice_pergunta, Object valor) + +finalizar() Tuple~float, String~ + +calcular_pontuacao() float + +is_finalizado() boolean + } + + %% Relacionamentos + Pergunta <|-- PerguntaMultiplaEscolha + Pergunta <|-- PerguntaDiscursiva + Resposta <|-- RespostaObjetiva + Resposta <|-- RespostaDiscursiva + + PerguntaMultiplaEscolha "1" *-- "2..*" Alternativa + Questionario "1" *-- "0..*" Pergunta + Questionario "1" --> "0..*" TentativaQuestionario + TentativaQuestionario "1" *-- "0..*" Resposta + Resposta "1" --> "1" Pergunta +``` + +### 2. Diagrama Complementar — Integração com LLM (Novo) + +```mermaid +classDiagram + direction TB + + class LLMService { + <> + -String api_key + -String model + -String base_url + +__init__(api_key: str, model: str = "llama3-70b-8192") + +corrigir_resposta(pergunta: PerguntaDiscursiva, resposta_aluno: str) -> Dict + -_fazer_chamada_api(prompt: str) -> str + -_tratar_erro(e: Exception) -> None + } + + class Correcao { + <> + +corrigir_discursiva(pergunta: PerguntaDiscursiva, resposta_aluno: str, service: LLMService = None) -> Dict + +criar_prompt_correcao(pergunta: PerguntaDiscursiva, resposta_aluno: str) -> str + } + + LLMService o-- Correcao : "é usado por" +``` + +--- + +## Nova Descrição das Classes + +### LLMService (Classe de Serviço) +Responsável pela **conexão direta** com o serviço Groq. + +**Responsabilidades:** +- Guardar a API Key (deve ser carregada via variável de ambiente `GROQ_API_KEY`) +- Realizar as chamadas HTTP para a API do Groq +- **Tratar todos os erros** (timeout, rate limit, autenticação, JSON inválido, etc.) internamente +- Montar o prompt adequado para correção de questões discursivas +- Retornar apenas o resultado limpo (nunca expor detalhes da API para o resto da aplicação) + +**Métodos principais:** +- `corrigir_resposta(pergunta: PerguntaDiscursiva, resposta_aluno: str) → Dict` + - Retorna dicionário com: `{"correta": bool, "pontuacao": float, "feedback": str, "explicacao": str}` + +### CorrecaoUtil (Classe Utilitária) +Classe de alto nível que a aplicação deve usar. + +**Responsabilidades:** +- Abstrair o uso do `LLMService` +- Criar o prompt de forma inteligente +- Fornecer interface simples e limpa para o resto do sistema +- Possibilitar uso de mock em testes + +**Método principal:** +- `corrigir_discursiva(...)` + +--- + +## Regras de Implementação Importantes + +1. **Não modificar** as classes existentes do diagrama principal. +2. A classe `PerguntaDiscursiva` **não deve** conhecer o `LLMService` diretamente. +3. Toda correção de discursiva deve passar pela `Correcao`. +4. O `LLMService` deve: + - Usar a biblioteca `groq` (ou `requests`) + - Tratar erros internamente + - Ter fallback ou mensagem clara em caso de falha na API +5. A API Key **nunca** deve ficar hard-coded. + +--- + +## Como preparar o ambiente + +```bash +# 1. Criar venv +python -m venv .venv + +# 2. Ativar +# Windows +.\.venv\Scripts\activate +# Linux/macOS +source .venv/bin/activate + +# 3. Instalar dependências +pip install -r requirements.txt +``` + +--- \ No newline at end of file diff --git a/Guia4/main.py b/Guia4/main.py new file mode 100644 index 0000000..e9e667d --- /dev/null +++ b/Guia4/main.py @@ -0,0 +1,8 @@ +from Guia3.src import * + +def main(): + pass + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Guia4/requirements.txt b/Guia4/requirements.txt new file mode 100644 index 0000000..a0ca34d --- /dev/null +++ b/Guia4/requirements.txt @@ -0,0 +1,4 @@ +pytest>=8.0.0 +pytest-cov>=4.0.0 +groq +python-dotenv \ No newline at end of file diff --git a/Guia4/src/__init__.py b/Guia4/src/__init__.py new file mode 100644 index 0000000..9283af9 --- /dev/null +++ b/Guia4/src/__init__.py @@ -0,0 +1,9 @@ +from .alternativa import Alternativa +from .pergunta import Pergunta +from .perguntadiscursiva import PerguntaDiscursiva +from .perguntamultiplaescolha import PerguntaMultiplaEscolha +from .questionario import Questionario +from .resposta import Resposta +from .respostadiscursiva import RespostaDiscursiva +from .respostaobjetiva import RespostaObjetiva +from .tentativaquestionario import TentativaQuestionario \ No newline at end of file diff --git a/Guia4/src/alternativa.py b/Guia4/src/alternativa.py new file mode 100644 index 0000000..4dde61f --- /dev/null +++ b/Guia4/src/alternativa.py @@ -0,0 +1,4 @@ +from typing import List, Tuple, Dict + +class Alternativa: + pass \ No newline at end of file diff --git a/Guia4/src/correcao.py b/Guia4/src/correcao.py new file mode 100644 index 0000000..995d283 --- /dev/null +++ b/Guia4/src/correcao.py @@ -0,0 +1,37 @@ +import json +from typing import Dict +from perguntadiscursiva import PerguntaDiscursiva +from llmservice import LLMService + +class Correcao: + + @staticmethod + def corrigir_discursiva(pergunta: PerguntaDiscursiva, resposta_aluno: str, service: LLMService = None) -> Dict: + if service is None: + service = LLMService() + + prompt = Correcao.criar_prompt_correcao(pergunta, resposta_aluno) + + try: + resposta_texto = service.fazer_chamada_api(prompt) + return json.loads(resposta_texto) + # No arquivo src/correcao.py, mude temporariamente para: + except Exception as e: + print(f"\n🚨 ERRO REAL DA API: {e}\n") # <-- Adicione essa linha para diagnosticar + return { + "correta": False, + "pontuacao": 0.0, + "feedback": "Erro na comunicação com o serviço de correção.", + "explicacao": "Não foi possível validar a resposta no momento." + } + + @staticmethod + def criar_prompt_correcao(pergunta: PerguntaDiscursiva, resposta_aluno: str) -> str: + return ( + "Avalie a resposta do aluno com base nos critérios fornecidos.\n" + f"Enunciado da Questão: {pergunta.texto}\n" + f"Resposta Esperada: {pergunta.resposta_esperada}\n" + f"Resposta enviada pelo Aluno: {resposta_aluno}\n\n" + "Retorne obrigatoriamente um formato JSON com as chaves exatas:\n" + '{"correta": bool, "pontuacao": float, "feedback": "str", "explicacao": "str"}' + ) \ No newline at end of file diff --git a/Guia4/src/llmservice.py b/Guia4/src/llmservice.py new file mode 100644 index 0000000..7b78f3d --- /dev/null +++ b/Guia4/src/llmservice.py @@ -0,0 +1,30 @@ +import os +import json +from typing import Dict +from groq import Groq +from perguntadiscursiva import PerguntaDiscursiva + +class LLMService: + def __init__(self, api_key: str = None, model: str = "llama-3.3-70b-versatile", groq_obj: Groq = None): + self.api_key = api_key or os.environ.get("GROQ_API_KEY") + self.model = model + self.groq_obj = groq_obj or (Groq(api_key=self.api_key) if self.api_key else None) + + def fazer_chamada_api(self, prompt: str) -> str: + if not isinstance(prompt, str): + raise TypeError("Erro na entrada de dados. O prompt deve ser uma string.") + + if not self.groq_obj: + raise ValueError("Cliente Groq não foi inicializado. Verifique a API Key.") + + response = self.groq_obj.chat.completions.create( + model=self.model, + temperature=0.0, + messages=[ + {"role": "system", "content": "Você é um professor avaliador. Responda APENAS com um JSON estruturado."}, + {"role": "user", "content": prompt}, + ] + ) + return response.choices[0].message.content + + \ No newline at end of file diff --git a/Guia4/src/pergunta.py b/Guia4/src/pergunta.py new file mode 100644 index 0000000..5b3763d --- /dev/null +++ b/Guia4/src/pergunta.py @@ -0,0 +1,4 @@ +from typing import List, Tuple, Dict + +class Pergunta: + pass \ No newline at end of file diff --git a/Guia4/src/perguntadiscursiva.py b/Guia4/src/perguntadiscursiva.py new file mode 100644 index 0000000..f4c26af --- /dev/null +++ b/Guia4/src/perguntadiscursiva.py @@ -0,0 +1,4 @@ +from typing import List, Tuple, Dict + +class PerguntaDiscursiva: + pass \ No newline at end of file diff --git a/Guia4/src/perguntamultiplaescolha.py b/Guia4/src/perguntamultiplaescolha.py new file mode 100644 index 0000000..bcbe94d --- /dev/null +++ b/Guia4/src/perguntamultiplaescolha.py @@ -0,0 +1,4 @@ +from typing import List, Tuple, Dict + +class PerguntaMultiplaEscolha: + pass \ No newline at end of file diff --git a/Guia4/src/questionario.py b/Guia4/src/questionario.py new file mode 100644 index 0000000..7525582 --- /dev/null +++ b/Guia4/src/questionario.py @@ -0,0 +1,4 @@ +from typing import List, Tuple, Dict + +class Questionario: + pass diff --git a/Guia4/src/resposta.py b/Guia4/src/resposta.py new file mode 100644 index 0000000..846d771 --- /dev/null +++ b/Guia4/src/resposta.py @@ -0,0 +1,4 @@ +from typing import List, Tuple, Dict + +class Resposta: + pass \ No newline at end of file diff --git a/Guia4/src/respostadiscursiva.py b/Guia4/src/respostadiscursiva.py new file mode 100644 index 0000000..4ea6dbb --- /dev/null +++ b/Guia4/src/respostadiscursiva.py @@ -0,0 +1,4 @@ +from typing import List, Tuple, Dict + +class RespostaDiscursiva: + pass \ No newline at end of file diff --git a/Guia4/src/respostaobjetiva.py b/Guia4/src/respostaobjetiva.py new file mode 100644 index 0000000..72ed2d0 --- /dev/null +++ b/Guia4/src/respostaobjetiva.py @@ -0,0 +1,4 @@ +from typing import List, Tuple, Dict + +class RespostaObjetiva: + pass \ No newline at end of file diff --git a/Guia4/src/tentativaquestionario.py b/Guia4/src/tentativaquestionario.py new file mode 100644 index 0000000..9947dd1 --- /dev/null +++ b/Guia4/src/tentativaquestionario.py @@ -0,0 +1,4 @@ +from typing import List, Tuple, Dict + +class TentativaQuestionario: + pass \ No newline at end of file diff --git a/Guia4/tests/__init__.py b/Guia4/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Guia4/tests/teste_correcao.py b/Guia4/tests/teste_correcao.py new file mode 100644 index 0000000..bd28356 --- /dev/null +++ b/Guia4/tests/teste_correcao.py @@ -0,0 +1,69 @@ +import pytest +import json +from unittest.mock import MagicMock +from src.perguntadiscursiva import PerguntaDiscursiva +from src.llmservice import LLMService +from src.correcao import Correcao + +def test_criar_prompt_correcao(): + """Garante que o prompt está sendo montado com os dados corretos da questão.""" + pergunta = PerguntaDiscursiva( + texto="O que é encapsulamento em POO?", + resposta_esperada="Proteger os atributos internos de uma classe usando métodos públicos." + ) + resposta_aluno = "É esconder as variáveis e usar getters e setters." + + prompt = Correcao.criar_prompt_correcao(pergunta, resposta_aluno) + + # Valida se as informações cruciais foram injetadas na string do prompt + assert "O que é encapsulamento em POO?" in prompt + assert "Proteger os atributos internos" in prompt + assert "É esconder as variáveis" in prompt + + +def test_corrigir_discursiva_com_sucesso_via_mock(): + """Testa o fluxo feliz: a API responde um JSON válido e a classe Correcao o decodifica.""" + pergunta = PerguntaDiscursiva(texto="O que é uma classe?", resposta_esperada="Um molde para objetos.") + resposta_aluno = "É a estrutura que define um objeto." + + # 1. Cria um mock do LLMService + mock_service = MagicMock(spec=LLMService) + + # Simula a string JSON que o modelo retornaria na vida real + json_retorno_llm = json.dumps({ + "correta": True, + "pontuacao": 1.0, + "feedback": "Resposta muito boa e direta.", + "explicacao": "O aluno compreendeu que a classe funciona como a definição estrutural." + }) + + # Configura o método público para retornar essa string + mock_service.fazer_chamada_api.return_value = json_retorno_llm + + # 2. Executa a correção injetando o nosso mock + resultado = Correcao.corrigir_discursiva(pergunta, resposta_aluno, service=mock_service) + + # 3. Asserts + assert resultado["correta"] is True + assert resultado["pontuacao"] == 1.0 + assert resultado["feedback"] == "Resposta muito boa e direta." + + # Garante que a classe Correcao chamou o método exato que sobrou no diagrama do professor + mock_service.fazer_chamada_api.assert_called_once() + + +def test_fallback_quando_api_retorna_erro(): + """Testa se o sistema lida com falhas de rede/API sem quebrar o programa.""" + pergunta = PerguntaDiscursiva(texto="O que é polimorfismo?", resposta_esperada="Métodos com mesma assinatura e comportamentos diferentes.") + + mock_service = MagicMock(spec=LLMService) + # Simula o método disparando um erro de conexão/timeout + mock_service.fazer_chamada_api.side_effect = Exception("Erro de conexão com o servidor do Groq") + + # Executa a correção que vai falhar internamente na chamada da API + resultado = Correcao.corrigir_discursiva(pergunta, "Não lembro", service=mock_service) + + # Verifica se o bloco 'except' capturou a falha e retornou o dicionário padrão de erro + assert resultado["correta"] is False + assert resultado["pontuacao"] == 0.0 + assert "Erro na comunicação" in resultado["feedback"] \ No newline at end of file