Spaces:
Runtime error
Runtime error
Create simulado_system.py
Browse files- simulado_system.py +310 -0
simulado_system.py
ADDED
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# simulado_system.py
|
2 |
+
|
3 |
+
import json
|
4 |
+
import uuid
|
5 |
+
import logging
|
6 |
+
from datetime import datetime, timedelta
|
7 |
+
from typing import Dict, List, Optional, Tuple
|
8 |
+
from difflib import SequenceMatcher
|
9 |
+
import numpy as np
|
10 |
+
|
11 |
+
# Configuração de logging
|
12 |
+
logging.basicConfig(
|
13 |
+
level=logging.INFO,
|
14 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
15 |
+
)
|
16 |
+
logger = logging.getLogger(__name__)
|
17 |
+
|
18 |
+
class SimuladoConstants:
|
19 |
+
"""Constantes para configuração de simulados"""
|
20 |
+
TEMPO_PADRAO = 240 # minutos
|
21 |
+
QUESTOES_TOTAIS = 120
|
22 |
+
NOTA_APROVACAO = 60
|
23 |
+
NIVEIS = ["facil", "medio", "dificil", "mixed"]
|
24 |
+
PESO_SIMILARIDADE = 0.7
|
25 |
+
|
26 |
+
class SimuladoSystem:
|
27 |
+
"""Sistema avançado de simulados e casos clínicos"""
|
28 |
+
|
29 |
+
def __init__(self, db_connection):
|
30 |
+
self.conn = db_connection
|
31 |
+
self.areas_simulado = {
|
32 |
+
"ClínicaMédica": 40,
|
33 |
+
"Cirurgia": 20,
|
34 |
+
"Pediatria": 15,
|
35 |
+
"GinecologiaObstetrícia": 15,
|
36 |
+
"MedicinaFamília": 5,
|
37 |
+
"SaúdeMental": 5
|
38 |
+
}
|
39 |
+
self.simulados_ativos = {} # Cache de simulados em andamento
|
40 |
+
|
41 |
+
def create_simulado(self, difficulty: str = "mixed",
|
42 |
+
num_questions: int = SimuladoConstants.QUESTOES_TOTAIS) -> Dict:
|
43 |
+
"""Cria simulado personalizado com mais opções"""
|
44 |
+
try:
|
45 |
+
simulado_id = str(uuid.uuid4())
|
46 |
+
simulado = {
|
47 |
+
"id": simulado_id,
|
48 |
+
"questoes": [],
|
49 |
+
"tempo_sugerido": self._calculate_time(num_questions),
|
50 |
+
"nivel": difficulty,
|
51 |
+
"data_criacao": datetime.now().isoformat(),
|
52 |
+
"status": "criado",
|
53 |
+
"estatisticas": {
|
54 |
+
"questoes_por_area": {},
|
55 |
+
"distribuicao_dificuldade": {}
|
56 |
+
}
|
57 |
+
}
|
58 |
+
|
59 |
+
cursor = self.conn.cursor()
|
60 |
+
|
61 |
+
# Distribuição de questões por área
|
62 |
+
for area, percentual in self.areas_simulado.items():
|
63 |
+
num_area_questions = int((percentual/100) * num_questions)
|
64 |
+
|
65 |
+
# Query considerando dificuldade
|
66 |
+
difficulty_clause = ""
|
67 |
+
if difficulty != "mixed":
|
68 |
+
difficulty_clause = "AND difficulty = ?"
|
69 |
+
params = (area, difficulty, num_area_questions)
|
70 |
+
else:
|
71 |
+
params = (area, num_area_questions)
|
72 |
+
|
73 |
+
cursor.execute(f'''
|
74 |
+
SELECT id, question_text, options, correct_answer,
|
75 |
+
explanation, difficulty, references
|
76 |
+
FROM previous_questions
|
77 |
+
WHERE area = ? {difficulty_clause}
|
78 |
+
ORDER BY RANDOM()
|
79 |
+
LIMIT ?
|
80 |
+
''', params)
|
81 |
+
|
82 |
+
questions = cursor.fetchall()
|
83 |
+
for q in questions:
|
84 |
+
question_data = {
|
85 |
+
"id": q[0],
|
86 |
+
"area": area,
|
87 |
+
"texto": q[1],
|
88 |
+
"opcoes": json.loads(q[2]),
|
89 |
+
"resposta": q[3],
|
90 |
+
"explicacao": q[4],
|
91 |
+
"dificuldade": q[5],
|
92 |
+
"referencias": json.loads(q[6]) if q[6] else []
|
93 |
+
}
|
94 |
+
simulado["questoes"].append(question_data)
|
95 |
+
|
96 |
+
# Atualizar estatísticas
|
97 |
+
simulado["estatisticas"]["questoes_por_area"][area] = \
|
98 |
+
simulado["estatisticas"]["questoes_por_area"].get(area, 0) + 1
|
99 |
+
simulado["estatisticas"]["distribuicao_dificuldade"][q[5]] = \
|
100 |
+
simulado["estatisticas"]["distribuicao_dificuldade"].get(q[5], 0) + 1
|
101 |
+
|
102 |
+
self.simulados_ativos[simulado_id] = simulado
|
103 |
+
return simulado
|
104 |
+
|
105 |
+
except Exception as e:
|
106 |
+
logger.error(f"Erro ao criar simulado: {e}")
|
107 |
+
return None
|
108 |
+
|
109 |
+
def _calculate_time(self, num_questions: int) -> int:
|
110 |
+
"""Calcula tempo sugerido baseado no número de questões"""
|
111 |
+
return int(num_questions * 2) # 2 minutos por questão
|
112 |
+
|
113 |
+
def evaluate_simulado(self, simulado_id: str,
|
114 |
+
respostas: Dict[str, str],
|
115 |
+
tempo_usado: int = None) -> Dict:
|
116 |
+
"""Avalia respostas do simulado com análise detalhada"""
|
117 |
+
try:
|
118 |
+
if simulado_id not in self.simulados_ativos:
|
119 |
+
raise ValueError("Simulado não encontrado")
|
120 |
+
|
121 |
+
simulado = self.simulados_ativos[simulado_id]
|
122 |
+
resultado = {
|
123 |
+
"id": simulado_id,
|
124 |
+
"total_questoes": len(simulado["questoes"]),
|
125 |
+
"corretas": 0,
|
126 |
+
"tempo_usado": tempo_usado,
|
127 |
+
"desempenho_por_area": {},
|
128 |
+
"analise_erros": [],
|
129 |
+
"recomendacoes": [],
|
130 |
+
"timestamp": datetime.now().isoformat()
|
131 |
+
}
|
132 |
+
|
133 |
+
# Análise detalhada por área
|
134 |
+
area_stats = {}
|
135 |
+
for area in self.areas_simulado:
|
136 |
+
area_stats[area] = {
|
137 |
+
"total": 0,
|
138 |
+
"corretas": 0,
|
139 |
+
"erros_comuns": [],
|
140 |
+
"tempo_medio": 0
|
141 |
+
}
|
142 |
+
|
143 |
+
# Avaliação de cada resposta
|
144 |
+
for q_id, resp in respostas.items():
|
145 |
+
questao = next((q for q in simulado["questoes"] if q["id"] == q_id), None)
|
146 |
+
if not questao:
|
147 |
+
continue
|
148 |
+
|
149 |
+
area = questao["area"]
|
150 |
+
area_stats[area]["total"] += 1
|
151 |
+
|
152 |
+
if resp.upper() == questao["resposta"].upper():
|
153 |
+
area_stats[area]["corretas"] += 1
|
154 |
+
resultado["corretas"] += 1
|
155 |
+
else:
|
156 |
+
# Análise do erro
|
157 |
+
area_stats[area]["erros_comuns"].append({
|
158 |
+
"questao_id": q_id,
|
159 |
+
"resposta_dada": resp,
|
160 |
+
"resposta_correta": questao["resposta"],
|
161 |
+
"tema": questao.get("tema", ""),
|
162 |
+
"dificuldade": questao["dificuldade"]
|
163 |
+
})
|
164 |
+
|
165 |
+
# Calcular porcentagens e gerar recomendações
|
166 |
+
for area, stats in area_stats.items():
|
167 |
+
if stats["total"] > 0:
|
168 |
+
percentual = (stats["corretas"] / stats["total"]) * 100
|
169 |
+
resultado["desempenho_por_area"][area] = {
|
170 |
+
"total": stats["total"],
|
171 |
+
"corretas": stats["corretas"],
|
172 |
+
"percentual": percentual,
|
173 |
+
"erros_comuns": stats["erros_comuns"]
|
174 |
+
}
|
175 |
+
|
176 |
+
if percentual < SimuladoConstants.NOTA_APROVACAO:
|
177 |
+
resultado["recomendacoes"].append(
|
178 |
+
self._generate_recommendations(area, stats)
|
179 |
+
)
|
180 |
+
|
181 |
+
# Salvar resultado no banco de dados
|
182 |
+
self._save_resultado(simulado_id, resultado)
|
183 |
+
|
184 |
+
return resultado
|
185 |
+
|
186 |
+
except Exception as e:
|
187 |
+
logger.error(f"Erro ao avaliar simulado: {e}")
|
188 |
+
return None
|
189 |
+
|
190 |
+
def _generate_recommendations(self, area: str, stats: Dict) -> Dict:
|
191 |
+
"""Gera recomendações detalhadas baseadas no desempenho"""
|
192 |
+
erros_comuns = self._analyze_common_errors(stats["erros_comuns"])
|
193 |
+
return {
|
194 |
+
"area": area,
|
195 |
+
"sugestoes": [
|
196 |
+
f"Revisar conceitos básicos de {area}",
|
197 |
+
f"Focar em {', '.join(erros_comuns[:3])}",
|
198 |
+
"Praticar questões similares",
|
199 |
+
"Revisar casos clínicos relacionados"
|
200 |
+
],
|
201 |
+
"recursos": [
|
202 |
+
"banco de questões",
|
203 |
+
"casos clínicos",
|
204 |
+
"revisão teórica",
|
205 |
+
"videoaulas específicas"
|
206 |
+
],
|
207 |
+
"plano_acao": self._create_action_plan(area, stats)
|
208 |
+
}
|
209 |
+
|
210 |
+
def _analyze_common_errors(self, erros: List[Dict]) -> List[str]:
|
211 |
+
"""Analisa padrões comuns de erros"""
|
212 |
+
temas_errados = {}
|
213 |
+
for erro in erros:
|
214 |
+
tema = erro["tema"]
|
215 |
+
if tema in temas_errados:
|
216 |
+
temas_errados[tema] += 1
|
217 |
+
else:
|
218 |
+
temas_errados[tema] = 1
|
219 |
+
|
220 |
+
return sorted(temas_errados.keys(),
|
221 |
+
key=lambda x: temas_errados[x],
|
222 |
+
reverse=True)
|
223 |
+
|
224 |
+
def _create_action_plan(self, area: str, stats: Dict) -> Dict:
|
225 |
+
"""Cria plano de ação personalizado"""
|
226 |
+
return {
|
227 |
+
"prioridade": "alta" if len(stats["erros_comuns"]) > stats["corretas"] else "média",
|
228 |
+
"etapas": [
|
229 |
+
"Revisão teórica dos temas com mais erros",
|
230 |
+
"Resolução de questões comentadas",
|
231 |
+
"Prática com casos clínicos",
|
232 |
+
"Simulado focado na área"
|
233 |
+
],
|
234 |
+
"tempo_sugerido": "2 semanas",
|
235 |
+
"material_sugerido": [
|
236 |
+
"Bibliografia básica",
|
237 |
+
"Questões anteriores comentadas",
|
238 |
+
"Vídeo-aulas específicas"
|
239 |
+
]
|
240 |
+
}
|
241 |
+
|
242 |
+
def _save_resultado(self, simulado_id: str, resultado: Dict) -> None:
|
243 |
+
"""Salva resultado do simulado no banco de dados"""
|
244 |
+
try:
|
245 |
+
cursor = self.conn.cursor()
|
246 |
+
cursor.execute('''
|
247 |
+
INSERT INTO resultados_simulados
|
248 |
+
(simulado_id, data, resultado_json)
|
249 |
+
VALUES (?, ?, ?)
|
250 |
+
''', (
|
251 |
+
simulado_id,
|
252 |
+
datetime.now().isoformat(),
|
253 |
+
json.dumps(resultado)
|
254 |
+
))
|
255 |
+
self.conn.commit()
|
256 |
+
except Exception as e:
|
257 |
+
logger.error(f"Erro ao salvar resultado: {e}")
|
258 |
+
|
259 |
+
def get_simulado_history(self, user_id: str) -> List[Dict]:
|
260 |
+
"""Obtém histórico de simulados do usuário"""
|
261 |
+
try:
|
262 |
+
cursor = self.conn.cursor()
|
263 |
+
cursor.execute('''
|
264 |
+
SELECT simulado_id, data, resultado_json
|
265 |
+
FROM resultados_simulados
|
266 |
+
WHERE user_id = ?
|
267 |
+
ORDER BY data DESC
|
268 |
+
''', (user_id,))
|
269 |
+
|
270 |
+
return [
|
271 |
+
{
|
272 |
+
"id": row[0],
|
273 |
+
"data": row[1],
|
274 |
+
"resultado": json.loads(row[2])
|
275 |
+
}
|
276 |
+
for row in cursor.fetchall()
|
277 |
+
]
|
278 |
+
except Exception as e:
|
279 |
+
logger.error(f"Erro ao obter histórico: {e}")
|
280 |
+
return []
|
281 |
+
|
282 |
+
# [O resto do código do CasoClinicoSystem continua o mesmo...]
|
283 |
+
|
284 |
+
def initialize_simulado_system(db_connection) -> Tuple[SimuladoSystem, CasoClinicoSystem]:
|
285 |
+
"""Inicializa o sistema de simulados"""
|
286 |
+
try:
|
287 |
+
simulado_sys = SimuladoSystem(db_connection)
|
288 |
+
caso_sys = CasoClinicoSystem(db_connection)
|
289 |
+
return simulado_sys, caso_sys
|
290 |
+
except Exception as e:
|
291 |
+
logger.error(f"Erro ao inicializar sistema de simulados: {e}")
|
292 |
+
return None, None
|
293 |
+
|
294 |
+
if __name__ == "__main__":
|
295 |
+
# Código para testes
|
296 |
+
import sqlite3
|
297 |
+
|
298 |
+
try:
|
299 |
+
conn = sqlite3.connect('revalida.db')
|
300 |
+
simulado_sys, caso_sys = initialize_simulado_system(conn)
|
301 |
+
|
302 |
+
# Teste básico
|
303 |
+
simulado = simulado_sys.create_simulado()
|
304 |
+
if simulado:
|
305 |
+
print("Sistema funcionando corretamente")
|
306 |
+
print(f"Simulado criado: {json.dumps(simulado['estatisticas'], indent=2)}")
|
307 |
+
except Exception as e:
|
308 |
+
print(f"Erro nos testes: {e}")
|
309 |
+
finally:
|
310 |
+
conn.close()
|