evolutia 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,16 @@
1
- """
2
- Analizador de complejidad de ejercicios.
3
- Identifica tipo, pasos, conceptos y variables de ejercicios.
4
- """
5
- import re
6
- from typing import Dict, List, Set
7
- from collections import Counter
1
+ """
2
+ Analizador de complejidad de ejercicios.
3
+ Identifica tipo, pasos, conceptos y variables de ejercicios.
4
+ """
5
+ import re
6
+ import logging
7
+ from typing import Dict, List, Set, Optional, TYPE_CHECKING
8
+ from collections import Counter
9
+
10
+ if TYPE_CHECKING:
11
+ from evolutia.cache.exercise_cache import ExerciseAnalysisCache
12
+
13
+ logger = logging.getLogger(__name__)
8
14
 
9
15
  try:
10
16
  from utils.math_extractor import (
@@ -22,73 +28,19 @@ except ImportError:
22
28
  )
23
29
 
24
30
 
25
- class ExerciseAnalyzer:
26
- """Analiza la complejidad y estructura de ejercicios."""
27
-
28
- # Palabras clave para identificación de tipo
29
- DEMOSTRACION_KEYWORDS = [
30
- 'demuestre', 'demuestre que', 'pruebe', 'verifique', 'muestre que'
31
- ]
32
-
33
- CALCULO_KEYWORDS = [
34
- 'calcule', 'calcular', 'encuentre', 'determine', 'evalúe', 'evaluar'
35
- ]
36
-
37
- APLICACION_KEYWORDS = [
38
- 'considere', 'suponga', 'modelo', 'sistema físico', 'aplicación',
39
- 'dispositivo', 'campo', 'potencial'
40
- ]
41
-
42
- STEP_KEYWORDS = [
43
- 'primero', 'luego', 'finalmente', 'ahora', 'a continuación',
44
- 'por tanto', 'por lo tanto', 'en consecuencia', 'así',
45
- 'por otro lado', 'además', 'también'
46
- ]
47
-
48
- # Patrones compilados para búsqueda eficiente
49
- TYPE_PATTERNS = {
50
- 'demostracion': re.compile('|'.join(map(re.escape, DEMOSTRACION_KEYWORDS)), re.IGNORECASE),
51
- 'calculo': re.compile('|'.join(map(re.escape, CALCULO_KEYWORDS)), re.IGNORECASE),
52
- 'aplicacion': re.compile('|'.join(map(re.escape, APLICACION_KEYWORDS)), re.IGNORECASE)
53
- }
54
-
55
- STEP_KEYWORDS_PATTERN = re.compile('|'.join(map(re.escape, STEP_KEYWORDS)), re.IGNORECASE)
56
-
57
- # Conceptos matemáticos comunes
58
- CONCEPT_PATTERNS = {
59
- 'vector_operations': [
60
- r'\\vec', r'\\cdot', r'\\times', r'\\nabla',
61
- r'producto\s+(escalar|vectorial)', r'gradiente', r'divergencia', r'rotacional'
62
- ],
63
- 'coordinate_systems': [
64
- r'coordenadas?\s+(cartesianas?|polares?|cilíndricas?|esféricas?|toroidales?)',
65
- r'\\rho', r'\\theta', r'\\phi', r'\\hat\{e\}_'
66
- ],
67
- 'integrals': [
68
- r'\\int', r'\\oint', r'integral', r'teorema\s+(de\s+)?(Green|Stokes|Gauss)',
69
- r'divergencia', r'rotacional'
70
- ],
71
- 'differential_equations': [
72
- r'\\frac\{d', r'\\partial', r'ecuaci[óo]n\s+diferencial',
73
- r'EDP', r'EDO'
74
- ],
75
- 'linear_algebra': [
76
- r'\\begin\{matrix\}', r'\\begin\{pmatrix\}', r'\\begin\{bmatrix\}',
77
- r'matriz', r'\\mathbf', r'autovalor', r'autovector'
78
- ],
79
- 'complex_numbers': [
80
- r'\\mathbb\{C\}', r'z\s*=', r'n[úu]mero\s+complejo',
81
- r'\\Re', r'\\Im', r'\\arg'
82
- ],
83
- 'series_expansions': [
84
- r'\\sum', r'serie', r'expansi[óo]n', r'Fourier',
85
- r'Taylor', r'\\sum_\{n=0\}'
86
- ]
87
- }
88
-
89
- def __init__(self):
90
- """Inicializa el analizador."""
91
- pass
31
+ class ExerciseAnalyzer:
32
+ """Analiza la complejidad y estructura de ejercicios."""
33
+
34
+ def __init__(self, cache: Optional['ExerciseAnalysisCache'] = None):
35
+ """
36
+ Inicializa el analizador.
37
+
38
+ Args:
39
+ cache: Instancia opcional de ExerciseAnalysisCache para cachear análisis
40
+ """
41
+ pass
42
+
43
+ self.cache = cache
92
44
 
93
45
  def identify_exercise_type(self, content: str) -> str:
94
46
  """
@@ -183,23 +135,34 @@ class ExerciseAnalyzer:
183
135
 
184
136
  return concepts
185
137
 
186
- def analyze(self, exercise: Dict) -> Dict:
187
- """
188
- Analiza un ejercicio completo y retorna metadatos de complejidad.
189
-
190
- Args:
191
- exercise: Diccionario con información del ejercicio
192
- - 'content': Contenido del ejercicio
193
- - 'solution': Contenido de la solución (opcional)
194
-
195
- Returns:
196
- Diccionario con análisis de complejidad
197
- """
198
- content = exercise.get('content', '')
199
- solution = exercise.get('solution', '')
200
-
201
- # Extraer expresiones matemáticas
202
- math_expressions = extract_math_expressions(content)
138
+ def analyze(self, exercise: Dict[str, Optional[str]]) -> Dict[str, Optional[str | int | float | List[str]]]:
139
+ """
140
+ Analiza un ejercicio completo y retorna metadatos de complejidad.
141
+
142
+ Args:
143
+ exercise: Diccionario con información del ejercicio
144
+ - 'content': Contenido del ejercicio
145
+ - 'solution': Contenido de la solución (opcional)
146
+
147
+ Returns:
148
+ Diccionario con análisis de complejidad
149
+ """
150
+ content = exercise.get('content', '')
151
+ solution = exercise.get('solution', '')
152
+
153
+ # Intentar caché primero
154
+ if self.cache:
155
+ cached_analysis = self.cache.get(exercise)
156
+ if cached_analysis:
157
+ logger.info(f"[ExerciseAnalyzer] Análisis obtenido del caché para exercise={exercise.get('label', 'unknown')}")
158
+ return cached_analysis['analysis']
159
+
160
+ # Análisis normal (cache miss)
161
+ if not content:
162
+ return {}
163
+
164
+ # Extraer expresiones matemáticas
165
+ math_expressions = extract_math_expressions(content)
203
166
  if solution:
204
167
  math_expressions.extend(extract_math_expressions(solution))
205
168
 
evolutia/imports.py ADDED
@@ -0,0 +1,175 @@
1
+ """
2
+ Módulo de imports centralizados y condicionales para EvolutIA.
3
+ Gestiona imports de dependencias opcionales (RAG, ML, etc.) de forma centralizada.
4
+ """
5
+ import logging
6
+ from typing import TYPE_CHECKING
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class OptionalImports:
12
+ """
13
+ Gestor de imports opcionales para EvolutIA.
14
+
15
+ Permite importar dependencias opcionales de forma controlada,
16
+ con mensajes de error claros cuando no están disponibles.
17
+ """
18
+
19
+ _imported_modules = {}
20
+
21
+ @classmethod
22
+ def get_chromadb(cls):
23
+ """Importa ChromaDB si está disponible."""
24
+ if 'chromadb' in cls._imported_modules:
25
+ return cls._imported_modules['chromadb']
26
+
27
+ try:
28
+ import chromadb
29
+ from chromadb.config import Settings
30
+ cls._imported_modules['chromadb'] = (chromadb, Settings)
31
+ logger.debug("[OptionalImports] ChromaDB importado exitosamente")
32
+ return chromadb, Settings
33
+ except ImportError:
34
+ logger.warning(
35
+ "[OptionalImports] chromadb no está instalado. "
36
+ "Instala con: pip install -e '.[rag]'"
37
+ )
38
+ return None, None
39
+
40
+ @classmethod
41
+ def get_sentence_transformers(cls):
42
+ """Importa sentence-transformers si está disponible."""
43
+ if 'sentence_transformers' in cls._imported_modules:
44
+ return cls._imported_modules['sentence_transformers']
45
+
46
+ try:
47
+ from sentence_transformers import SentenceTransformer
48
+ cls._imported_modules['sentence_transformers'] = SentenceTransformer
49
+ logger.debug("[OptionalImports] sentence-transformers importado exitosamente")
50
+ return SentenceTransformer
51
+ except ImportError:
52
+ logger.warning(
53
+ "[OptionalImports] sentence-transformers no está instalado. "
54
+ "Instala con: pip install -e '.[rag]'"
55
+ )
56
+ return None
57
+
58
+ @classmethod
59
+ def get_openai(cls):
60
+ """Importa OpenAI si está disponible."""
61
+ if 'openai' in cls._imported_modules:
62
+ return cls._imported_modules['openai']
63
+
64
+ try:
65
+ from openai import OpenAI
66
+ cls._imported_modules['openai'] = OpenAI
67
+ logger.debug("[OptionalImports] OpenAI importado exitosamente")
68
+ return OpenAI
69
+ except ImportError:
70
+ logger.warning(
71
+ "[OptionalImports] openai no está instalado. "
72
+ "Instala con: pip install openai"
73
+ )
74
+ return None
75
+
76
+ @classmethod
77
+ def get_anthropic(cls):
78
+ """Importa Anthropic si está disponible."""
79
+ if 'anthropic' in cls._imported_modules:
80
+ return cls._imported_modules['anthropic']
81
+
82
+ try:
83
+ import anthropic
84
+ cls._imported_modules['anthropic'] = anthropic
85
+ logger.debug("[OptionalImports] Anthropic importado exitosamente")
86
+ return anthropic
87
+ except ImportError:
88
+ logger.warning(
89
+ "[OptionalImports] anthropic no está instalado. "
90
+ "Instala con: pip install anthropic"
91
+ )
92
+ return None
93
+
94
+ @classmethod
95
+ def get_google_generativeai(cls):
96
+ """Importa google-generativeai si está disponible."""
97
+ if 'google_generativeai' in cls._imported_modules:
98
+ return cls._imported_modules['google_generativeai']
99
+
100
+ try:
101
+ import google.generativeai as genai
102
+ cls._imported_modules['google_generativeai'] = genai
103
+ logger.debug("[OptionalImports] google-generativeai importado exitosamente")
104
+ return genai
105
+ except ImportError:
106
+ logger.warning(
107
+ "[OptionalImports] google-generativeai no está instalado. "
108
+ "Instala con: pip install google-generativeai"
109
+ )
110
+ return None
111
+
112
+ @classmethod
113
+ def check_rag_available(cls) -> bool:
114
+ """Verifica si todas las dependencias de RAG están disponibles."""
115
+ chromadb, _ = cls.get_chromadb()
116
+ sentence_transformers = cls.get_sentence_transformers()
117
+
118
+ if chromadb is None or sentence_transformers is None:
119
+ return False
120
+ return True
121
+
122
+ @classmethod
123
+ def get_module(cls, module_name: str):
124
+ """
125
+ Importa un módulo específico por nombre.
126
+
127
+ Args:
128
+ module_name: Nombre del módulo a importar
129
+
130
+ Returns:
131
+ El módulo importado o None si no está disponible
132
+ """
133
+ if module_name in cls._imported_modules:
134
+ return cls._imported_modules[module_name]
135
+
136
+ try:
137
+ module = __import__(module_name)
138
+ cls._imported_modules[module_name] = module
139
+ logger.debug(f"[OptionalImports] {module_name} importado exitosamente")
140
+ return module
141
+ except ImportError:
142
+ logger.warning(f"[OptionalImports] {module_name} no está instalado.")
143
+ return None
144
+
145
+
146
+ # Funciones de conveniencia para compatibilidad con código existente
147
+ def get_chromadb():
148
+ """Importa ChromaDB si está disponible."""
149
+ chromadb_module, settings = OptionalImports.get_chromadb()
150
+ return chromadb_module, settings
151
+
152
+
153
+ def get_sentence_transformers():
154
+ """Importa sentence-transformers si está disponible."""
155
+ return OptionalImports.get_sentence_transformers()
156
+
157
+
158
+ def get_openai():
159
+ """Importa OpenAI si está disponible."""
160
+ return OptionalImports.get_openai()
161
+
162
+
163
+ def get_anthropic():
164
+ """Importa Anthropic si está disponible."""
165
+ return OptionalImports.get_anthropic()
166
+
167
+
168
+ def get_google_generativeai():
169
+ """Importa google-generativeai si está disponible."""
170
+ return OptionalImports.get_google_generativeai()
171
+
172
+
173
+ def check_rag_available() -> bool:
174
+ """Verifica si todas las dependencias de RAG están disponibles."""
175
+ return OptionalImports.check_rag_available()