Spaces:
Runtime error
Runtime error
danielraynaud
commited on
Create utils/helpers.py
Browse files- utils/helpers.py +204 -0
utils/helpers.py
ADDED
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# utils/helpers.py
|
2 |
+
|
3 |
+
import re
|
4 |
+
from datetime import datetime, timedelta
|
5 |
+
from typing import Dict, List, Optional, Union
|
6 |
+
import json
|
7 |
+
import logging
|
8 |
+
|
9 |
+
# Configuração de logging
|
10 |
+
logging.basicConfig(
|
11 |
+
level=logging.INFO,
|
12 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
13 |
+
)
|
14 |
+
logger = logging.getLogger(__name__)
|
15 |
+
|
16 |
+
class ValidationError(Exception):
|
17 |
+
"""Exceção customizada para erros de validação"""
|
18 |
+
pass
|
19 |
+
|
20 |
+
def validate_email(email: str) -> bool:
|
21 |
+
"""Valida formato de email"""
|
22 |
+
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
23 |
+
return bool(re.match(pattern, email))
|
24 |
+
|
25 |
+
def validate_date_format(date_str: str) -> bool:
|
26 |
+
"""Valida formato de data (YYYY-MM-DD)"""
|
27 |
+
try:
|
28 |
+
datetime.strptime(date_str, '%Y-%m-%d')
|
29 |
+
return True
|
30 |
+
except ValueError:
|
31 |
+
return False
|
32 |
+
|
33 |
+
def format_time_delta(minutes: int) -> str:
|
34 |
+
"""Formata duração em minutos para formato legível"""
|
35 |
+
hours = minutes // 60
|
36 |
+
remaining_minutes = minutes % 60
|
37 |
+
|
38 |
+
if hours > 0:
|
39 |
+
return f"{hours}h{remaining_minutes:02d}min"
|
40 |
+
return f"{minutes}min"
|
41 |
+
|
42 |
+
def calculate_study_efficiency(hours_studied: float,
|
43 |
+
questions_answered: int,
|
44 |
+
correct_answers: int) -> float:
|
45 |
+
"""Calcula eficiência de estudo"""
|
46 |
+
if hours_studied <= 0 or questions_answered == 0:
|
47 |
+
return 0.0
|
48 |
+
|
49 |
+
accuracy = correct_answers / questions_answered
|
50 |
+
questions_per_hour = questions_answered / hours_studied
|
51 |
+
|
52 |
+
# Fórmula personalizada para eficiência
|
53 |
+
efficiency = (accuracy * 0.7 + (questions_per_hour / 20) * 0.3) * 100
|
54 |
+
return round(min(efficiency, 100), 2)
|
55 |
+
|
56 |
+
def format_performance_message(score: float) -> str:
|
57 |
+
"""Gera mensagem motivacional baseada no desempenho"""
|
58 |
+
if score >= 90:
|
59 |
+
return "🌟 Excelente! Você está dominando o conteúdo!"
|
60 |
+
elif score >= 80:
|
61 |
+
return "💪 Ótimo trabalho! Continue assim!"
|
62 |
+
elif score >= 70:
|
63 |
+
return "👍 Bom progresso! Mantenha o foco!"
|
64 |
+
elif score >= 60:
|
65 |
+
return "📚 Você está no caminho certo! Continue estudando!"
|
66 |
+
else:
|
67 |
+
return "💡 Não desanime! Cada questão é uma oportunidade de aprendizado!"
|
68 |
+
|
69 |
+
def generate_study_recommendations(weak_areas: List[str],
|
70 |
+
hours_available: float) -> Dict[str, any]:
|
71 |
+
"""Gera recomendações de estudo baseadas em áreas fracas"""
|
72 |
+
total_weight = len(weak_areas)
|
73 |
+
hours_per_area = hours_available / total_weight if total_weight > 0 else 0
|
74 |
+
|
75 |
+
recommendations = {
|
76 |
+
"distribuicao_tempo": {},
|
77 |
+
"prioridades": [],
|
78 |
+
"recursos_sugeridos": []
|
79 |
+
}
|
80 |
+
|
81 |
+
for area in weak_areas:
|
82 |
+
recommendations["distribuicao_tempo"][area] = round(hours_per_area, 1)
|
83 |
+
recommendations["prioridades"].append({
|
84 |
+
"area": area,
|
85 |
+
"foco": ["Revisão teórica", "Questões práticas", "Casos clínicos"]
|
86 |
+
})
|
87 |
+
|
88 |
+
recommendations["recursos_sugeridos"] = [
|
89 |
+
"Material teórico focado",
|
90 |
+
"Questões comentadas",
|
91 |
+
"Vídeo-aulas específicas",
|
92 |
+
"Casos clínicos interativos"
|
93 |
+
]
|
94 |
+
|
95 |
+
return recommendations
|
96 |
+
|
97 |
+
def parse_command_args(command: str) -> Dict[str, str]:
|
98 |
+
"""Processa argumentos de comandos do bot"""
|
99 |
+
parts = command.split()
|
100 |
+
if len(parts) < 1:
|
101 |
+
return {}
|
102 |
+
|
103 |
+
command_dict = {
|
104 |
+
"command": parts[0]
|
105 |
+
}
|
106 |
+
|
107 |
+
# Processa argumentos nomeados (e.g., /comando area=pediatria tempo=2)
|
108 |
+
for part in parts[1:]:
|
109 |
+
if '=' in part:
|
110 |
+
key, value = part.split('=', 1)
|
111 |
+
command_dict[key.lower()] = value
|
112 |
+
else:
|
113 |
+
# Argumentos posicionais
|
114 |
+
if 'args' not in command_dict:
|
115 |
+
command_dict['args'] = []
|
116 |
+
command_dict['args'].append(part)
|
117 |
+
|
118 |
+
return command_dict
|
119 |
+
|
120 |
+
def format_question(question: Dict[str, any],
|
121 |
+
number: Optional[int] = None) -> str:
|
122 |
+
"""Formata questão para exibição"""
|
123 |
+
formatted = ""
|
124 |
+
|
125 |
+
if number is not None:
|
126 |
+
formatted += f"Questão {number}:\n"
|
127 |
+
|
128 |
+
formatted += f"{question['texto']}\n\n"
|
129 |
+
|
130 |
+
for letra, texto in question['opcoes'].items():
|
131 |
+
formatted += f"{letra}) {texto}\n"
|
132 |
+
|
133 |
+
return formatted
|
134 |
+
|
135 |
+
def calculate_remaining_time(target_date: str) -> Dict[str, int]:
|
136 |
+
"""Calcula tempo restante até a data alvo"""
|
137 |
+
target = datetime.strptime(target_date, '%Y-%m-%d').date()
|
138 |
+
today = datetime.now().date()
|
139 |
+
|
140 |
+
difference = target - today
|
141 |
+
|
142 |
+
return {
|
143 |
+
"dias": difference.days,
|
144 |
+
"semanas": difference.days // 7,
|
145 |
+
"meses": difference.days // 30
|
146 |
+
}
|
147 |
+
|
148 |
+
def format_study_summary(progress_data: Dict[str, any]) -> str:
|
149 |
+
"""Formata resumo de estudos para exibição"""
|
150 |
+
summary = "📊 Resumo de Estudos:\n\n"
|
151 |
+
|
152 |
+
# Total de horas
|
153 |
+
total_hours = sum(progress_data.get('horas_por_area', {}).values())
|
154 |
+
summary += f"⏱ Total de horas estudadas: {total_hours:.1f}h\n"
|
155 |
+
|
156 |
+
# Desempenho por área
|
157 |
+
summary += "\n📈 Desempenho por área:\n"
|
158 |
+
for area, score in progress_data.get('desempenho_por_area', {}).items():
|
159 |
+
summary += f"• {area}: {score:.1f}%\n"
|
160 |
+
|
161 |
+
# Média geral
|
162 |
+
if 'media_geral' in progress_data:
|
163 |
+
summary += f"\n🎯 Média geral: {progress_data['media_geral']:.1f}%\n"
|
164 |
+
|
165 |
+
return summary
|
166 |
+
|
167 |
+
def safe_json_loads(json_str: str, default: Union[Dict, List] = None) -> Union[Dict, List]:
|
168 |
+
"""Carrega JSON com segurança"""
|
169 |
+
try:
|
170 |
+
return json.loads(json_str)
|
171 |
+
except Exception as e:
|
172 |
+
logger.error(f"Erro ao carregar JSON: {e}")
|
173 |
+
return default if default is not None else {}
|
174 |
+
|
175 |
+
def sanitize_input(text: str) -> str:
|
176 |
+
"""Sanitiza input do usuário"""
|
177 |
+
# Remove caracteres potencialmente perigosos
|
178 |
+
return re.sub(r'[<>&;]', '', text.strip())
|
179 |
+
|
180 |
+
def format_error_message(error: Exception) -> str:
|
181 |
+
"""Formata mensagem de erro para usuário"""
|
182 |
+
return f"""❌ Ops! Ocorreu um erro:
|
183 |
+
{str(error)}
|
184 |
+
|
185 |
+
Por favor, tente novamente ou use /ajuda para ver os comandos disponíveis."""
|
186 |
+
|
187 |
+
if __name__ == "__main__":
|
188 |
+
# Testes básicos
|
189 |
+
print(validate_email("test@example.com")) # True
|
190 |
+
print(validate_email("invalid-email")) # False
|
191 |
+
|
192 |
+
print(format_time_delta(90)) # "1h30min"
|
193 |
+
print(format_time_delta(45)) # "45min"
|
194 |
+
|
195 |
+
test_question = {
|
196 |
+
"texto": "Qual é o principal sintoma da condição X?",
|
197 |
+
"opcoes": {
|
198 |
+
"A": "Sintoma 1",
|
199 |
+
"B": "Sintoma 2",
|
200 |
+
"C": "Sintoma 3",
|
201 |
+
"D": "Sintoma 4"
|
202 |
+
}
|
203 |
+
}
|
204 |
+
print(format_question(test_question, 1))
|