evolutia 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -3,9 +3,15 @@ Analizador de complejidad de ejercicios.
3
3
  Identifica tipo, pasos, conceptos y variables de ejercicios.
4
4
  """
5
5
  import re
6
- from typing import Dict, List, Set
6
+ import logging
7
+ from typing import Dict, List, Set, Optional, TYPE_CHECKING
7
8
  from collections import Counter
8
9
 
10
+ if TYPE_CHECKING:
11
+ from evolutia.cache.exercise_cache import ExerciseAnalysisCache
12
+
13
+ logger = logging.getLogger(__name__)
14
+
9
15
  try:
10
16
  from utils.math_extractor import (
11
17
  extract_math_expressions,
@@ -25,70 +31,36 @@ except ImportError:
25
31
  class ExerciseAnalyzer:
26
32
  """Analiza la complejidad y estructura de ejercicios."""
27
33
 
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
34
  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)
35
+ 'demostracion': re.compile(r'(?i)(demuestre|pruebe|verifique|muestre|justifique|demostraci[oó]n)'),
36
+ 'calculo': re.compile(r'(?i)(calcule|halle|encuentre|resuelva|eval[uú]e|calcular|obtenga|determinar)'),
37
+ 'aplicacion': re.compile(r'(?i)(aplicaci[oó]n|problema|vida real|modelo|f[íi]sic[ao]|ingenier[íi]a|econom[íi]a|contexto)')
53
38
  }
54
39
 
55
- STEP_KEYWORDS_PATTERN = re.compile('|'.join(map(re.escape, STEP_KEYWORDS)), re.IGNORECASE)
40
+ STEP_KEYWORDS_PATTERN = re.compile(
41
+ r'(?i)(primero|luego|despu[ée]s|finalmente|entonces|por lo tanto|conclusi[oó]n|paso|seguidamente)',
42
+ re.MULTILINE
43
+ )
56
44
 
57
- # Conceptos matemáticos comunes
58
45
  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
- ]
46
+ 'integrals': [r'(?i)integral', r'\\int', r'\\iint', r'\\iiint', r'\\oint'],
47
+ 'derivatives': [r'(?i)derivada', r'\\frac{d}{d', r'\\[dp]artial', r'\''],
48
+ 'limits': [r'(?i)l[íi]mite', r'\\lim'],
49
+ 'series': [r'(?i)serie', r'(?i)sucesi[oó]n', r'\\sum', r'convergencia'],
50
+ 'vectors': [r'(?i)vector', r'\\vec', r'\\mathbf', r'producto punto', r'producto cruz'],
51
+ 'matrices': [r'(?i)matriz', r'(?i)determinante', r'\\begin{pmatrix}', r'\\begin{bmatrix}', r'autovalor'],
52
+ 'coordinate_systems': [r'(?i)coordenadas', r'(?i)polares', r'(?i)esf[ée]ricas', r'(?i)cil[íi]ndricas', r'jacobian[oa]'],
53
+ 'vector_operations': [r'(?i)gradiente', r'(?i)divergencia', r'(?i)rotacional', r'\\nabla', r'teorema de stokes', r'teorema de green', r'teorema de la divergencia']
87
54
  }
88
55
 
89
- def __init__(self):
90
- """Inicializa el analizador."""
91
- pass
56
+ def __init__(self, cache: Optional['ExerciseAnalysisCache'] = None):
57
+ """
58
+ Inicializa el analizador.
59
+
60
+ Args:
61
+ cache: Instancia opcional de ExerciseAnalysisCache para cachear análisis
62
+ """
63
+ self.cache = cache
92
64
 
93
65
  def identify_exercise_type(self, content: str) -> str:
94
66
  """
@@ -183,7 +155,7 @@ class ExerciseAnalyzer:
183
155
 
184
156
  return concepts
185
157
 
186
- def analyze(self, exercise: Dict) -> Dict:
158
+ def analyze(self, exercise: Dict[str, Optional[str]]) -> Dict[str, Optional[str | int | float | List[str]]]:
187
159
  """
188
160
  Analiza un ejercicio completo y retorna metadatos de complejidad.
189
161
 
@@ -198,6 +170,17 @@ class ExerciseAnalyzer:
198
170
  content = exercise.get('content', '')
199
171
  solution = exercise.get('solution', '')
200
172
 
173
+ # Intentar caché primero
174
+ if self.cache:
175
+ cached_analysis = self.cache.get(exercise)
176
+ if cached_analysis:
177
+ logger.info(f"[ExerciseAnalyzer] Análisis obtenido del caché para exercise={exercise.get('label', 'unknown')}")
178
+ return cached_analysis['analysis']
179
+
180
+ # Análisis normal (cache miss)
181
+ if not content:
182
+ return {}
183
+
201
184
  # Extraer expresiones matemáticas
202
185
  math_expressions = extract_math_expressions(content)
203
186
  if solution:
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()