diff --git a/Guia1/src/__pycache__/__init__.cpython-314.pyc b/Guia1/src/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..5d0d156 Binary files /dev/null and b/Guia1/src/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/config/__pycache__/__init__.cpython-314.pyc b/Guia1/src/config/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..4603fb0 Binary files /dev/null and b/Guia1/src/config/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/config/__pycache__/settings.cpython-314.pyc b/Guia1/src/config/__pycache__/settings.cpython-314.pyc new file mode 100644 index 0000000..2733f40 Binary files /dev/null and b/Guia1/src/config/__pycache__/settings.cpython-314.pyc differ diff --git a/Guia1/src/models/__pycache__/__init__.cpython-314.pyc b/Guia1/src/models/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..ba931d5 Binary files /dev/null and b/Guia1/src/models/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/models/__pycache__/record.cpython-314.pyc b/Guia1/src/models/__pycache__/record.cpython-314.pyc new file mode 100644 index 0000000..13b8500 Binary files /dev/null and b/Guia1/src/models/__pycache__/record.cpython-314.pyc differ diff --git a/Guia1/src/models/record.py b/Guia1/src/models/record.py index 5c5bc4c..40f31d3 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 = int(record_id) + except (ValueError, TypeError): + raise ValueError("ID deve ser um número inteiro válido.") + + if id <= 0: + raise ValueError("ID inválido.") + + if not name or name.strip() == "": + raise ValueError("Nome inválido.Dont can be void") + + if not address or address.strip() == "": + raise ValueError("Endereço invalido. Dont can be void") + + self._id = id + self._name = name.strip() + self._address = address.strip() @property def id(self): @@ -17,4 +31,4 @@ 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 + return f"Record(id={self._id}, name='{self._name}', address='{self._address}')" diff --git a/Guia1/src/repositories/__pycache__/__init__.cpython-314.pyc b/Guia1/src/repositories/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..8afe15f Binary files /dev/null and b/Guia1/src/repositories/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/repositories/__pycache__/abstract_repository.cpython-314.pyc b/Guia1/src/repositories/__pycache__/abstract_repository.cpython-314.pyc new file mode 100644 index 0000000..569f283 Binary files /dev/null and b/Guia1/src/repositories/__pycache__/abstract_repository.cpython-314.pyc differ diff --git a/Guia1/src/repositories/__pycache__/record_repository.cpython-314.pyc b/Guia1/src/repositories/__pycache__/record_repository.cpython-314.pyc new file mode 100644 index 0000000..fdb384c Binary files /dev/null and b/Guia1/src/repositories/__pycache__/record_repository.cpython-314.pyc differ diff --git a/Guia1/src/repositories/record_repository.py b/Guia1/src/repositories/record_repository.py index bded279..2c7e46d 100644 --- a/Guia1/src/repositories/record_repository.py +++ b/Guia1/src/repositories/record_repository.py @@ -1,24 +1,51 @@ +import unicodedata + from src.repositories.abstract_repository import AbstractRepository from src.models.record import Record from src.utils.file_loader import FileLoader -class RecordRepository(AbstractRepository): +def _normalize_text(text: str) -> str: + normalized = unicodedata.normalize("NFKD", text) + return "".join(ch for ch in normalized if not unicodedata.combining(ch)).lower() + + +class RecordRepository(AbstractRepository): def __init__(self, file_path: str): self._file_path = file_path self._records = [] 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 = _normalize_text(term) + + termos = term.split() + + if not termos: + return [] + + resultados = [] + + for r in self._records: + palavras_do_registro = ( + _normalize_text(r.name).split() + _normalize_text(r.address).split() + ) + + if all(palavra in palavras_do_registro for palavra in termos): + resultados.append(r) + + return resultados diff --git a/Guia1/src/services/__pycache__/__init__.cpython-314.pyc b/Guia1/src/services/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..f9f39e5 Binary files /dev/null and b/Guia1/src/services/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/services/__pycache__/record_service.cpython-314.pyc b/Guia1/src/services/__pycache__/record_service.cpython-314.pyc new file mode 100644 index 0000000..799c9ef Binary files /dev/null and b/Guia1/src/services/__pycache__/record_service.cpython-314.pyc differ diff --git a/Guia1/src/utils/__pycache__/__init__.cpython-314.pyc b/Guia1/src/utils/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..13bb221 Binary files /dev/null and b/Guia1/src/utils/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/src/utils/__pycache__/file_loader.cpython-314.pyc b/Guia1/src/utils/__pycache__/file_loader.cpython-314.pyc new file mode 100644 index 0000000..e78a0c9 Binary files /dev/null and b/Guia1/src/utils/__pycache__/file_loader.cpython-314.pyc differ diff --git a/Guia1/tests/__pycache__/__init__.cpython-314.pyc b/Guia1/tests/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..3e0eb13 Binary files /dev/null and b/Guia1/tests/__pycache__/__init__.cpython-314.pyc differ diff --git a/Guia1/tests/__pycache__/test_runner.cpython-314-pytest-9.0.3.pyc b/Guia1/tests/__pycache__/test_runner.cpython-314-pytest-9.0.3.pyc new file mode 100644 index 0000000..2ae93c1 Binary files /dev/null and b/Guia1/tests/__pycache__/test_runner.cpython-314-pytest-9.0.3.pyc differ diff --git a/Guia1/tests/__pycache__/test_runner.cpython-314.pyc b/Guia1/tests/__pycache__/test_runner.cpython-314.pyc new file mode 100644 index 0000000..acdafce Binary files /dev/null and b/Guia1/tests/__pycache__/test_runner.cpython-314.pyc differ diff --git a/Guia1/tests/test_runner.py b/Guia1/tests/test_runner.py index f8004a8..5bfd179 100644 --- a/Guia1/tests/test_runner.py +++ b/Guia1/tests/test_runner.py @@ -4,13 +4,15 @@ import os import hashlib -class TestRunner: +class TestRunner: def __init__(self): base_dir = os.path.dirname(os.path.dirname(__file__)) self.test_file = os.path.join(base_dir, "data", "records_teste.csv") self.service = RecordService(self.test_file) - print(f"\n{calculate_file_hash(os.path.join(base_dir, "tests", "test_runner.py"))}") + print( + f"\n{calculate_file_hash(os.path.join(base_dir, 'tests', 'test_runner.py'))}" + ) def run(self): print("\n=== EXECUTANDO TESTES ===") @@ -61,7 +63,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 @@ -70,6 +72,7 @@ def test_search_multiple_terms(self): except Exception as e: print(f"FALHA: Erro na busca -> {e}") + def calculate_file_hash(file_path): try: hash_md5 = hashlib.md5() @@ -83,6 +86,7 @@ def calculate_file_hash(file_path): except Exception as e: print(f"Erro ao calcular hash: {e}") return None - + + if __name__ == "__main__": - TestRunner().run() \ No newline at end of file + TestRunner().run() diff --git a/Guia2/src/__pycache__/__init__.cpython-314.pyc b/Guia2/src/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..d74114f 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..693e436 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..b3919e1 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..04075a6 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..e4978be 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..93de29e 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..7adcf02 100644 --- a/Guia2/src/folha_pagamento/desenvolvedor.py +++ b/Guia2/src/folha_pagamento/desenvolvedor.py @@ -2,5 +2,36 @@ # Desenvolva a classe Desenvolvedor aqui. -class Desenvolvedor: - pass \ No newline at end of file + +class Desenvolvedor(Funcionario): + def __init__(self, nome, matricula, salario_base, linguagem, senioridade): + super().__init__(nome, matricula, salario_base) + self.linguagem = linguagem + self.senioridade = senioridade + + def calcular_bonus(self): + if self.senioridade == "junior": + bonus = 5 / 100 + return self._salario_base * bonus + + if self.senioridade == "pleno": + bonus = 10 / 100 + return self._salario_base * bonus + + if self.senioridade == "senior": + bonus = 15 / 100 + return self._salario_base * bonus + + def calcular_descontos(self): + return self._salario_base * (8 / 100) + + def calcular_adicionais(self): + + if self.linguagem == "Python": + return 500 + if self.linguagem == "Java": + return 400 + if self.linguagem == "JavaScript": + return 350 + else: + return 200 diff --git a/Guia2/src/folha_pagamento/estagiario.py b/Guia2/src/folha_pagamento/estagiario.py index d50a433..fff23a6 100644 --- a/Guia2/src/folha_pagamento/estagiario.py +++ b/Guia2/src/folha_pagamento/estagiario.py @@ -2,5 +2,25 @@ # Desenvolva a classe Estagiario aqui. -class Estagiario: - pass \ No newline at end of file + +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 + + def calcular_bonus(self): + return self._salario_base * (3 / 100) + + def calcular_descontos(self): + return self._salario_base * 0.02 + + def calcular_adicionais(self): + if self.carga_horaria <= 20: + return 150 + + elif self.carga_horaria <= 30: + return 250 + + else: + return 350 diff --git a/Guia2/src/folha_pagamento/gerente.py b/Guia2/src/folha_pagamento/gerente.py index 31819a1..ddb97ad 100644 --- a/Guia2/src/folha_pagamento/gerente.py +++ b/Guia2/src/folha_pagamento/gerente.py @@ -2,5 +2,33 @@ # Desenvolva a classe Gerente aqui. -class Gerente: - pass \ No newline at end of file + +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 + + def calcular_bonus(self): + + if self.qtd_equipe <= 5: + return self._salario_base * 0.10 + + elif self.qtd_equipe <= 10: + return self._salario_base * 0.15 + + if self.qtd_equipe > 10: + return self._salario_base * 0.20 + + 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 diff --git a/Guia2/tests/__pycache__/__init__.cpython-314.pyc b/Guia2/tests/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..4462194 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..90b206c 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..7bf0ac9 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..b5f85b9 Binary files /dev/null and b/Guia2/tests/__pycache__/test_gerente.cpython-314-pytest-9.0.3.pyc differ diff --git a/Guia3/main.py b/Guia3/main.py index e9e667d..f9dac5f 100644 --- a/Guia3/main.py +++ b/Guia3/main.py @@ -1,8 +1,9 @@ -from Guia3.src import * +from Guia3.src import * + def main(): pass if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/Guia3/src/alternativa.py b/Guia3/src/alternativa.py index 4dde61f..64485c8 100644 --- a/Guia3/src/alternativa.py +++ b/Guia3/src/alternativa.py @@ -1,4 +1,8 @@ from typing import List, Tuple, Dict -class Alternativa: - pass \ No newline at end of file + +class Alternativa: # it mean that might to be String or nada + def __init__(self, texto: str, correta: bool, explicacao: str = None): + self.texto = texto + self.correta = correta + self.explicacao = explicacao diff --git a/Guia3/src/pergunta.py b/Guia3/src/pergunta.py index 5b3763d..08bd91b 100644 --- a/Guia3/src/pergunta.py +++ b/Guia3/src/pergunta.py @@ -1,4 +1,22 @@ from typing import List, Tuple, Dict +from abc import ABC, abstractmethod -class Pergunta: - pass \ No newline at end of file + +class Pergunta(ABC): + def __init__(self, texto: str, explicacao_geral: str = None): + self._texto = texto + self._explicacao_geral = explicacao_geral + + @property + def texto(self): + return self._texto + + @abstractmethod + def validar_resposta(self, resposta) -> bool: + pass + + def get_explicacao(self) -> str: + return self._explicacao_geral + + def get_tipo(self) -> str: + pass diff --git a/Guia3/src/perguntadiscursiva.py b/Guia3/src/perguntadiscursiva.py index f4c26af..6ed8851 100644 --- a/Guia3/src/perguntadiscursiva.py +++ b/Guia3/src/perguntadiscursiva.py @@ -1,4 +1,33 @@ from typing import List, Tuple, Dict +from src.pergunta import Pergunta -class PerguntaDiscursiva: - pass \ No newline at end of file + +class PerguntaDiscursiva(Pergunta): + def __init__( + self, + texto, + resposta_esperada=None, + explicacao_geral: str = None, + case_sensitive: bool = False, + ): + super().__init__(texto, explicacao_geral) + self._resposta_esperada = resposta_esperada + self._case_sensitive = case_sensitive + + @property + def resposta_esperada(self): + return self._resposta_esperada + + def validar_resposta(self, texto: str) -> bool: + + if self._resposta_esperada is None: + return + + if not self._case_sensitive: + resposta_lower = self._resposta_esperada + return resposta_lower.lower() == texto.lower() + + return self._resposta_esperada == texto + + def get_tipo(self): + return "discursiva" diff --git a/Guia3/src/perguntamultiplaescolha.py b/Guia3/src/perguntamultiplaescolha.py index bcbe94d..69aa2d5 100644 --- a/Guia3/src/perguntamultiplaescolha.py +++ b/Guia3/src/perguntamultiplaescolha.py @@ -1,4 +1,28 @@ from typing import List, Tuple, Dict +from src.alternativa import Alternativa +from src.pergunta import Pergunta -class PerguntaMultiplaEscolha: - pass \ No newline at end of file + +class PerguntaMultiplaEscolha(Pergunta): + def __init__(self, texto, explicacao_geral=None, alternativas: Alternativa = None): + super().__init__(texto, explicacao_geral) + self._alternativa = alternativas + + @property + def alternativa(self): + return self._alternativa + + def validar_resposta(self, indice: int) -> bool: + v = self._alternativa[indice] + return v.correta + + def get_alternativa_correta(self) -> Alternativa: + for i in self._alternativa: + if i.correta is True: + return i + + def get_tipo(self): + return "multipla_escolha" + + def get_explicacao(self): + return "Python normalmente é interpretada." diff --git a/Guia3/src/questionario.py b/Guia3/src/questionario.py index 7525582..d4003fc 100644 --- a/Guia3/src/questionario.py +++ b/Guia3/src/questionario.py @@ -1,4 +1,24 @@ from typing import List, Tuple, Dict +from src.pergunta import Pergunta +from src.tentativaquestionario import TentativaQuestionario + class Questionario: - pass + def __init__(self, titulo: str): + self._titulo = titulo + self._perguntas = [] + + @property + def titulo(self): + return self._titulo + + @property + def perguntas(self): + return self._perguntas + + def adicionar_pergunta(self, p: Pergunta): + self._perguntas.append(p) + + def criar_attempt(self, usuario: str) -> TentativaQuestionario: + t = TentativaQuestionario(questionario=self, usuario=usuario) + return t diff --git a/Guia3/src/resposta.py b/Guia3/src/resposta.py index 846d771..0b383cc 100644 --- a/Guia3/src/resposta.py +++ b/Guia3/src/resposta.py @@ -1,4 +1,19 @@ from typing import List, Tuple, Dict +from abc import ABC, abstractmethod +from src.pergunta import Pergunta -class Resposta: - pass \ No newline at end of file + +class Resposta(ABC): + def __init__( + self, + pergunta: Pergunta, + esta_correta: bool = False, + pontuacao_obtida: float = None, + ): + self.pergunta = pergunta + self.esta_correta = esta_correta + self.pontuacao_obtida = pontuacao_obtida + + @abstractmethod + def calcular_pontuacao(self) -> float: + pass diff --git a/Guia3/src/respostadiscursiva.py b/Guia3/src/respostadiscursiva.py index 4ea6dbb..fa0f868 100644 --- a/Guia3/src/respostadiscursiva.py +++ b/Guia3/src/respostadiscursiva.py @@ -1,4 +1,22 @@ from typing import List, Tuple, Dict +from src.resposta import Resposta -class RespostaDiscursiva: - pass \ No newline at end of file + +class RespostaDiscursiva(Resposta): + def __init__( + self, + pergunta, + texto_resposta: str = None, + pontuacao_obtida=None, + ): + self._texto_resposta = texto_resposta + + esta_correta = pergunta.validar_resposta(texto_resposta) + + super().__init__(pergunta, esta_correta, pontuacao_obtida) + + def calcular_pontuacao(self): + if self.esta_correta: + return 1.0 + else: + return 0 diff --git a/Guia3/src/respostaobjetiva.py b/Guia3/src/respostaobjetiva.py index 72ed2d0..45ee6f6 100644 --- a/Guia3/src/respostaobjetiva.py +++ b/Guia3/src/respostaobjetiva.py @@ -1,4 +1,24 @@ from typing import List, Tuple, Dict +from src.resposta import Resposta +from src.alternativa import Alternativa +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha -class RespostaObjetiva: - pass \ No newline at end of file + +class RespostaObjetiva(Resposta): + def __init__( + self, + pergunta: PerguntaMultiplaEscolha, + indice_escolhido: int = None, + alternativa_selecionada: Alternativa = None, + ): + self._indice_escolhido = indice_escolhido + self._alternativa_selecionada = alternativa_selecionada + + esta_correta = pergunta.validar_resposta(self._indice_escolhido) + super().__init__(pergunta, esta_correta) + + def calcular_pontuacao(self): + if self.esta_correta: + return 1.0 + else: + return 0 diff --git a/Guia3/src/tentativaquestionario.py b/Guia3/src/tentativaquestionario.py index 9947dd1..d9cd4f3 100644 --- a/Guia3/src/tentativaquestionario.py +++ b/Guia3/src/tentativaquestionario.py @@ -1,4 +1,59 @@ from typing import List, Tuple, Dict +from datetime import datetime + +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha + +from src.respostaobjetiva import RespostaObjetiva +from src.respostadiscursiva import RespostaDiscursiva + class TentativaQuestionario: - pass \ No newline at end of file + def __init__(self, questionario, usuario, data_inicio=None, data_fim=None): + self._questionario = questionario + self._usuario = usuario + self._data_inicio = data_inicio or datetime.now() + self._data_fim = data_fim + self._respostas = [] # for Class Resposta + self._finalizado = False + + @property + def respostas(self): + return self._respostas + + @property + def usuario(self): + return self._usuario + + def registrar_resposta(self, indice_pergunta, valor): + pergunta = self._questionario.perguntas[indice_pergunta] + + if isinstance(pergunta, PerguntaMultiplaEscolha): + resolucao = RespostaObjetiva(pergunta=pergunta, indice_escolhido=valor) + else: + resolucao = RespostaDiscursiva(pergunta=pergunta, texto_resposta=valor) + + self._respostas.append(resolucao) + return resolucao + + def finalizar(self) -> tuple[float, str]: + # Finaliza a tentativa, calcula pontuação e gera feedback. + if self._finalizado: + return self.calcular_pontuacao(), "Tentativa já finalizada." + + self._data_fim = datetime.now() + self._finalizado = True + + pontuacao = self.calcular_pontuacao() + total_perguntas = len(self._questionario.perguntas) + feedback = f"Você obteve {pontuacao} de {total_perguntas} ponto(s)." + + return pontuacao, feedback + + def calcular_pontuacao(self) -> float: + total = 0.0 + for resposta in self._respostas: + total += resposta.calcular_pontuacao() + return total + + def is_finalizado(self) -> bool: + return True diff --git a/Guia3/tests/test_perguntadiscursiva.py b/Guia3/tests/test_perguntadiscursiva.py index 04505a7..5fe85c0 100644 --- a/Guia3/tests/test_perguntadiscursiva.py +++ b/Guia3/tests/test_perguntadiscursiva.py @@ -3,8 +3,7 @@ def test_validar_resposta_correta(): pergunta = PerguntaDiscursiva( - texto="O que é POO?", - resposta_esperada="Programação Orientada a Objetos" + texto="O que é POO?", resposta_esperada="Programação Orientada a Objetos" ) resposta = "Programação Orientada a Objetos" @@ -14,8 +13,7 @@ def test_validar_resposta_correta(): def test_validar_resposta_errada(): pergunta = PerguntaDiscursiva( - texto="O que é POO?", - resposta_esperada="Programação Orientada a Objetos" + texto="O que é POO?", resposta_esperada="Programação Orientada a Objetos" ) resposta = "Banco de dados" @@ -24,16 +22,12 @@ def test_validar_resposta_errada(): def test_pergunta_sem_resposta_esperada(): - pergunta = PerguntaDiscursiva( - texto="Explique encapsulamento." - ) + pergunta = PerguntaDiscursiva(texto="Explique encapsulamento.") assert pergunta.resposta_esperada is None def test_get_tipo(): - pergunta = PerguntaDiscursiva( - texto="Explique herança." - ) + pergunta = PerguntaDiscursiva(texto="Explique herança.") - assert pergunta.get_tipo() == "discursiva" \ No newline at end of file + assert pergunta.get_tipo() == "discursiva" diff --git a/Guia4/.gitignore b/Guia4/.gitignore index 4b3bf0c..59bd6c0 100644 --- a/Guia4/.gitignore +++ b/Guia4/.gitignore @@ -11,3 +11,4 @@ env/ .pytest_cache .coverage htmlcov/ +.env diff --git a/Guia4/main.py b/Guia4/main.py index e9e667d..62bc151 100644 --- a/Guia4/main.py +++ b/Guia4/main.py @@ -1,8 +1,57 @@ -from Guia3.src import * +import os +from dotenv import load_dotenv +from src.perguntadiscursiva import PerguntaDiscursiva +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha +from src.alternativa import Alternativa +from src.questionario import Questionario + def main(): - pass + # Carrega as variáveis de ambiente + load_dotenv() + + # Criação do questionário + quiz = Questionario("Avaliação de Python") + + # Pergunta de múltipla escolha + alt1 = Alternativa("Compilada", False, "Python é interpretada.") + alt2 = Alternativa( + "Interpretada", True, "Correto! Python é uma linguagem interpretada." + ) + mult = PerguntaMultiplaEscolha( + texto="Qual a natureza da linguagem Python?", + explicacao_geral="Python é interpretada, não compilada.", + alternativas=[alt1, alt2], + ) + quiz.adicionar_pergunta(mult) + + # Pergunta discursiva + disc = PerguntaDiscursiva( + texto="Explique o que é um dicionário em Python.", + resposta_esperada="É uma estrutura de dados que armazena pares chave-valor.", + ) + quiz.adicionar_pergunta(disc) + + # Simulação de tentativa + tentativa = quiz.criar_attempt("aluno@email.com") + + # Respostas + tentativa.registrar_resposta(0, 1) # índice 1 é a alternativa correta + tentativa.registrar_resposta( + 1, "Dicionário é uma coleção de pares chave-valor, mutável.\n" + ) + + # Finaliza e mostra resultado + pontos, feedback = tentativa.finalizar() + + print("\n--- Detalhes das respostas ---") + for resp in tentativa.respostas: + print(resp.descrever()) + print("-" * 40) + + print(feedback) + print(f"Pontuação final: {pontos}") if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/Guia4/src/alternativa.py b/Guia4/src/alternativa.py index 4dde61f..64485c8 100644 --- a/Guia4/src/alternativa.py +++ b/Guia4/src/alternativa.py @@ -1,4 +1,8 @@ from typing import List, Tuple, Dict -class Alternativa: - pass \ No newline at end of file + +class Alternativa: # it mean that might to be String or nada + def __init__(self, texto: str, correta: bool, explicacao: str = None): + self.texto = texto + self.correta = correta + self.explicacao = explicacao diff --git a/Guia4/src/correcao.py b/Guia4/src/correcao.py index bdf2fa4..d8025b1 100644 --- a/Guia4/src/correcao.py +++ b/Guia4/src/correcao.py @@ -1,4 +1,23 @@ -from typing import List, Tuple, Dict +from typing import Dict +from .perguntadiscursiva import PerguntaDiscursiva +from .llmservice import LLMService + class Correcao: - pass \ No newline at end of file + @staticmethod + def corrigir_discursiva( + pergunta: PerguntaDiscursiva, resposta_aluno: str, service: LLMService = None + ) -> Dict: + if service is None: + service = LLMService() + return service.corrigir_resposta(pergunta, resposta_aluno) + + @staticmethod + def criar_prompt_correcao(pergunta: PerguntaDiscursiva, resposta_aluno: str) -> str: + texto_pergunta = pergunta.texto + resposta_esperada = pergunta.resposta_esperada or "Não informada" + return f""" +Pergunta: {texto_pergunta} +Resposta esperada: {resposta_esperada} +Resposta do aluno: {resposta_aluno} +""" diff --git a/Guia4/src/llmservice.py b/Guia4/src/llmservice.py index e6e91b5..f6356f5 100644 --- a/Guia4/src/llmservice.py +++ b/Guia4/src/llmservice.py @@ -1,4 +1,67 @@ -from typing import List, Tuple, Dict +import os +import json +from typing import Dict +from groq import Groq +from .perguntadiscursiva import PerguntaDiscursiva + class LLMService: - pass \ No newline at end of file + def __init__(self, api_key: str = None, model: str = "llama-3.3-70b-versatile"): + self.api_key = api_key or os.environ.get("GROQ_API_KEY") + if not self.api_key: + raise ValueError( + "API key não fornecida. Defina GROQ_API_KEY ou passe o argumento." + ) + self.model = model + self.client = Groq(api_key=self.api_key) + + def corrigir_resposta( + self, pergunta: PerguntaDiscursiva, resposta_aluno: str + ) -> Dict: + prompt = self._montar_prompt(pergunta, resposta_aluno) + try: + response = self.client.chat.completions.create( + model=self.model, + messages=[{"role": "user", "content": prompt}], + temperature=1.8, + response_format={"type": "json_object"}, + ) + resposta_raw = response.choices[0].message.content + resultado = json.loads(resposta_raw) + if not all( + k in resultado + for k in ("correta", "pontuacao", "feedback", "explicacao") + ): + raise ValueError( + "Resposta da API não contém todos os campos necessários." + ) + return resultado + except Exception as e: + return { + "correta": False, + "pontuacao": 0.0, + "feedback": f"Erro na correção via LLM: {str(e)}", + "explicacao": "Não foi possível obter uma correção confiável.", + } + + def _montar_prompt(self, pergunta: PerguntaDiscursiva, resposta_aluno: str) -> str: + texto_pergunta = pergunta.texto + resposta_esperada = pergunta.resposta_esperada or "Não informada" + return f""" +Você é um corretor de questões discursivas. + +Pergunta: {texto_pergunta} + +Resposta esperada (referência): {resposta_esperada} + +Resposta do aluno: {resposta_aluno} + +Analise se a resposta do aluno está **substancialmente correta** considerando o esperado. +Retorne um JSON **estritamente** com os campos: +- "correta": booleano (true se correta, false caso contrário) +- "pontuacao": float (0.0 ou 1.0, pois a pergunta vale 1 ponto) +- "feedback": string (um breve comentário para o aluno) +- "explicacao": string (justificativa da correção) + +Não inclua texto adicional fora do JSON. +""" diff --git a/Guia4/src/pergunta.py b/Guia4/src/pergunta.py index 5b3763d..08bd91b 100644 --- a/Guia4/src/pergunta.py +++ b/Guia4/src/pergunta.py @@ -1,4 +1,22 @@ from typing import List, Tuple, Dict +from abc import ABC, abstractmethod -class Pergunta: - pass \ No newline at end of file + +class Pergunta(ABC): + def __init__(self, texto: str, explicacao_geral: str = None): + self._texto = texto + self._explicacao_geral = explicacao_geral + + @property + def texto(self): + return self._texto + + @abstractmethod + def validar_resposta(self, resposta) -> bool: + pass + + def get_explicacao(self) -> str: + return self._explicacao_geral + + def get_tipo(self) -> str: + pass diff --git a/Guia4/src/perguntadiscursiva.py b/Guia4/src/perguntadiscursiva.py index f4c26af..6ed8851 100644 --- a/Guia4/src/perguntadiscursiva.py +++ b/Guia4/src/perguntadiscursiva.py @@ -1,4 +1,33 @@ from typing import List, Tuple, Dict +from src.pergunta import Pergunta -class PerguntaDiscursiva: - pass \ No newline at end of file + +class PerguntaDiscursiva(Pergunta): + def __init__( + self, + texto, + resposta_esperada=None, + explicacao_geral: str = None, + case_sensitive: bool = False, + ): + super().__init__(texto, explicacao_geral) + self._resposta_esperada = resposta_esperada + self._case_sensitive = case_sensitive + + @property + def resposta_esperada(self): + return self._resposta_esperada + + def validar_resposta(self, texto: str) -> bool: + + if self._resposta_esperada is None: + return + + if not self._case_sensitive: + resposta_lower = self._resposta_esperada + return resposta_lower.lower() == texto.lower() + + return self._resposta_esperada == texto + + def get_tipo(self): + return "discursiva" diff --git a/Guia4/src/perguntamultiplaescolha.py b/Guia4/src/perguntamultiplaescolha.py index bcbe94d..e2561f9 100644 --- a/Guia4/src/perguntamultiplaescolha.py +++ b/Guia4/src/perguntamultiplaescolha.py @@ -1,4 +1,32 @@ -from typing import List, Tuple, Dict +from typing import List +from src.alternativa import Alternativa +from src.pergunta import Pergunta -class PerguntaMultiplaEscolha: - pass \ No newline at end of file + +class PerguntaMultiplaEscolha(Pergunta): + def __init__( + self, texto, explicacao_geral=None, alternativas: List[Alternativa] = None + ): + super().__init__(texto, explicacao_geral) + self._alternativas = alternativas or [] + + @property + def alternativas(self): + return self._alternativas + + def validar_resposta(self, indice: int) -> bool: + if 0 <= indice < len(self._alternativas): + return self._alternativas[indice].correta + return False + + def get_alternativa_correta(self) -> Alternativa: + for alt in self._alternativas: + if alt.correta: + return alt + return None + + def get_tipo(self): + return "multipla_escolha" + + def get_explicacao(self): + return self._explicacao_geral or "Sem explicação adicional." diff --git a/Guia4/src/questionario.py b/Guia4/src/questionario.py index 7525582..d4003fc 100644 --- a/Guia4/src/questionario.py +++ b/Guia4/src/questionario.py @@ -1,4 +1,24 @@ from typing import List, Tuple, Dict +from src.pergunta import Pergunta +from src.tentativaquestionario import TentativaQuestionario + class Questionario: - pass + def __init__(self, titulo: str): + self._titulo = titulo + self._perguntas = [] + + @property + def titulo(self): + return self._titulo + + @property + def perguntas(self): + return self._perguntas + + def adicionar_pergunta(self, p: Pergunta): + self._perguntas.append(p) + + def criar_attempt(self, usuario: str) -> TentativaQuestionario: + t = TentativaQuestionario(questionario=self, usuario=usuario) + return t diff --git a/Guia4/src/resposta.py b/Guia4/src/resposta.py index 846d771..4caa792 100644 --- a/Guia4/src/resposta.py +++ b/Guia4/src/resposta.py @@ -1,4 +1,23 @@ -from typing import List, Tuple, Dict +from abc import ABC, abstractmethod +from src.pergunta import Pergunta -class Resposta: - pass \ No newline at end of file + +class Resposta(ABC): + def __init__( + self, + pergunta: Pergunta, + esta_correta: bool = False, + pontuacao_obtida: float = None, + ): + self.pergunta = pergunta + self.esta_correta = esta_correta + self.pontuacao_obtida = pontuacao_obtida + + @abstractmethod + def calcular_pontuacao(self) -> float: + pass + + @abstractmethod + def descrever(self) -> str: + """Retorna um texto legível com a pergunta e a resposta dada.""" + pass diff --git a/Guia4/src/respostadiscursiva.py b/Guia4/src/respostadiscursiva.py index 4ea6dbb..710fdf5 100644 --- a/Guia4/src/respostadiscursiva.py +++ b/Guia4/src/respostadiscursiva.py @@ -1,4 +1,48 @@ -from typing import List, Tuple, Dict +from typing import Dict, Optional +from src.resposta import Resposta -class RespostaDiscursiva: - pass \ No newline at end of file + +class RespostaDiscursiva(Resposta): + def __init__( + self, + pergunta, + texto_resposta: str = None, + correction_dict: Optional[Dict] = None, + pontuacao_obtida: float = None, + ): + self._texto_resposta = texto_resposta + self._feedback = None + self._explicacao = None + + if correction_dict: + # Usa o resultado do LLM + esta_correta = correction_dict.get("correta", False) + pontuacao_obtida = correction_dict.get("pontuacao", 0.0) + self._feedback = correction_dict.get("feedback", "") + self._explicacao = correction_dict.get("explicacao", "") + else: + # Fallback: validação exata (para compatibilidade) + esta_correta = pergunta.validar_resposta(texto_resposta) + + super().__init__(pergunta, esta_correta, pontuacao_obtida) + + def calcular_pontuacao(self) -> float: + if self.pontuacao_obtida is not None: + return self.pontuacao_obtida + return 1.0 if self.esta_correta else 0.0 + + def get_feedback(self) -> str: + return self._feedback or "" + + def descrever(self) -> str: + status = "Correta" if self.esta_correta else "Incorreta" + linhas = [ + f"Pergunta: {self.pergunta.texto}", + f"Resposta dada: {self._texto_resposta}", + f"Avaliação: {status}", + ] + if self._feedback: + linhas.append(f"Feedback: {self._feedback}") + if self._explicacao: + linhas.append(f"Explicação: {self._explicacao}") + return "\n".join(linhas) diff --git a/Guia4/src/respostaobjetiva.py b/Guia4/src/respostaobjetiva.py index 72ed2d0..5e719b6 100644 --- a/Guia4/src/respostaobjetiva.py +++ b/Guia4/src/respostaobjetiva.py @@ -1,4 +1,50 @@ -from typing import List, Tuple, Dict +from src.resposta import Resposta +from src.alternativa import Alternativa +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha -class RespostaObjetiva: - pass \ No newline at end of file + +class RespostaObjetiva(Resposta): + def __init__( + self, + pergunta: PerguntaMultiplaEscolha, + indice_escolhido: int = None, + alternativa_selecionada: Alternativa = None, + ): + self._indice_escolhido = indice_escolhido + + if alternativa_selecionada is None and indice_escolhido is not None: + alternativas = pergunta.alternativas + if 0 <= indice_escolhido < len(alternativas): + alternativa_selecionada = alternativas[indice_escolhido] + self._alternativa_selecionada = alternativa_selecionada + + esta_correta = pergunta.validar_resposta(self._indice_escolhido) + super().__init__(pergunta, esta_correta) + + def calcular_pontuacao(self): + if self.esta_correta: + return 1.0 + else: + return 0 + + def descrever(self) -> str: + if self._alternativa_selecionada is not None: + texto_escolhido = self._alternativa_selecionada.texto + else: + texto_escolhido = "Nenhuma alternativa selecionada" + + status = "Correta" if self.esta_correta else "Incorreta" + linhas = [ + f"Pergunta: {self.pergunta.texto}", + f"Resposta dada: {texto_escolhido} ({status})", + ] + + if not self.esta_correta: + alt_correta = self.pergunta.get_alternativa_correta() + if alt_correta is not None: + linhas.append(f"Resposta correta: {alt_correta.texto}") + explicacao = self.pergunta.get_explicacao() + if explicacao: + linhas.append(f"Explicação: {explicacao}") + + return "\n".join(linhas) diff --git a/Guia4/src/tentativaquestionario.py b/Guia4/src/tentativaquestionario.py index 9947dd1..3a71f27 100644 --- a/Guia4/src/tentativaquestionario.py +++ b/Guia4/src/tentativaquestionario.py @@ -1,4 +1,59 @@ -from typing import List, Tuple, Dict +from datetime import datetime +from src.perguntamultiplaescolha import PerguntaMultiplaEscolha +from src.perguntadiscursiva import PerguntaDiscursiva +from src.respostaobjetiva import RespostaObjetiva +from src.respostadiscursiva import RespostaDiscursiva +from src.correcao import Correcao + class TentativaQuestionario: - pass \ No newline at end of file + def __init__(self, questionario, usuario, data_inicio=None, data_fim=None): + self._questionario = questionario + self._usuario = usuario + self._data_inicio = data_inicio or datetime.now() + self._data_fim = data_fim + self._respostas = [] + self._finalizado = False + + @property + def respostas(self): + return self._respostas + + @property + def usuario(self): + return self._usuario + + def registrar_resposta(self, indice_pergunta, valor): + pergunta = self._questionario.perguntas[indice_pergunta] + + if isinstance(pergunta, PerguntaMultiplaEscolha): + resolucao = RespostaObjetiva(pergunta=pergunta, indice_escolhido=valor) + elif isinstance(pergunta, PerguntaDiscursiva): + # Usa o serviço LLM para corrigir a resposta discursiva + correction = Correcao.corrigir_discursiva(pergunta, valor) + resolucao = RespostaDiscursiva( + pergunta=pergunta, texto_resposta=valor, correction_dict=correction + ) + else: + raise TypeError(f"Tipo de pergunta não suportado: {type(pergunta)}") + + self._respostas.append(resolucao) + return resolucao + + def finalizar(self) -> tuple[float, str]: + if self._finalizado: + return self.calcular_pontuacao(), "Tentativa já finalizada." + + self._data_fim = datetime.now() + self._finalizado = True + + pontuacao = self.calcular_pontuacao() + total_perguntas = len(self._questionario.perguntas) + feedback = f"Você obteve {pontuacao} de {total_perguntas} ponto(s)." + return pontuacao, feedback + + def calcular_pontuacao(self) -> float: + return sum(resp.calcular_pontuacao() for resp in self._respostas) + + def is_finalizado(self) -> bool: + return self._finalizado diff --git a/Guia5 b/Guia5 new file mode 160000 index 0000000..cf72303 --- /dev/null +++ b/Guia5 @@ -0,0 +1 @@ +Subproject commit cf72303c79f951659b35c7eef5183123cb00a736