algomath-extract 1.0.0

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.
Files changed (90) hide show
  1. package/README.md +260 -0
  2. package/bin/algo-extract.js +143 -0
  3. package/bin/algo-generate.js +102 -0
  4. package/bin/algo-help.js +136 -0
  5. package/bin/algo-list.js +56 -0
  6. package/bin/algo-run.js +141 -0
  7. package/bin/algo-status.js +88 -0
  8. package/bin/algo-verify.js +189 -0
  9. package/bin/install.js +349 -0
  10. package/package.json +57 -0
  11. package/requirements.txt +20 -0
  12. package/src/__pycache__/intent.cpython-313.pyc +0 -0
  13. package/src/cli/__pycache__/commands.cpython-313.pyc +0 -0
  14. package/src/cli/cli_entry.py +106 -0
  15. package/src/cli/commands.py +339 -0
  16. package/src/execution/__init__.py +74 -0
  17. package/src/execution/__pycache__/__init__.cpython-313.pyc +0 -0
  18. package/src/execution/__pycache__/display.cpython-313.pyc +0 -0
  19. package/src/execution/__pycache__/errors.cpython-313.pyc +0 -0
  20. package/src/execution/__pycache__/executor.cpython-313.pyc +0 -0
  21. package/src/execution/__pycache__/sandbox.cpython-313.pyc +0 -0
  22. package/src/execution/display.py +261 -0
  23. package/src/execution/errors.py +158 -0
  24. package/src/execution/executor.py +253 -0
  25. package/src/execution/sandbox.py +333 -0
  26. package/src/extraction/__init__.py +102 -0
  27. package/src/extraction/__pycache__/__init__.cpython-313.pyc +0 -0
  28. package/src/extraction/__pycache__/boundaries.cpython-313.pyc +0 -0
  29. package/src/extraction/__pycache__/errors.cpython-313.pyc +0 -0
  30. package/src/extraction/__pycache__/llm_extraction.cpython-313.pyc +0 -0
  31. package/src/extraction/__pycache__/notation.cpython-313.pyc +0 -0
  32. package/src/extraction/__pycache__/parser.cpython-313.pyc +0 -0
  33. package/src/extraction/__pycache__/pdf_processor.cpython-313.pyc +0 -0
  34. package/src/extraction/__pycache__/prompts.cpython-313.pyc +0 -0
  35. package/src/extraction/__pycache__/review.cpython-313.pyc +0 -0
  36. package/src/extraction/__pycache__/schema.cpython-313.pyc +0 -0
  37. package/src/extraction/__pycache__/validation.cpython-313.pyc +0 -0
  38. package/src/extraction/boundaries.py +281 -0
  39. package/src/extraction/errors.py +156 -0
  40. package/src/extraction/llm_extraction.py +225 -0
  41. package/src/extraction/notation.py +240 -0
  42. package/src/extraction/parser.py +402 -0
  43. package/src/extraction/pdf_processor.py +281 -0
  44. package/src/extraction/prompts.py +90 -0
  45. package/src/extraction/review.py +298 -0
  46. package/src/extraction/schema.py +173 -0
  47. package/src/extraction/validation.py +202 -0
  48. package/src/generation/__init__.py +79 -0
  49. package/src/generation/__pycache__/__init__.cpython-313.pyc +0 -0
  50. package/src/generation/__pycache__/code_generator.cpython-313.pyc +0 -0
  51. package/src/generation/__pycache__/errors.cpython-313.pyc +0 -0
  52. package/src/generation/__pycache__/hybrid.cpython-313.pyc +0 -0
  53. package/src/generation/__pycache__/llm_generator.cpython-313.pyc +0 -0
  54. package/src/generation/__pycache__/persistence.cpython-313.pyc +0 -0
  55. package/src/generation/__pycache__/prompts.cpython-313.pyc +0 -0
  56. package/src/generation/__pycache__/review.cpython-313.pyc +0 -0
  57. package/src/generation/__pycache__/templates.cpython-313.pyc +0 -0
  58. package/src/generation/__pycache__/types.cpython-313.pyc +0 -0
  59. package/src/generation/__pycache__/validation.cpython-313.pyc +0 -0
  60. package/src/generation/code_generator.py +375 -0
  61. package/src/generation/errors.py +84 -0
  62. package/src/generation/hybrid.py +210 -0
  63. package/src/generation/llm_generator.py +223 -0
  64. package/src/generation/persistence.py +221 -0
  65. package/src/generation/prompts.py +202 -0
  66. package/src/generation/review.py +254 -0
  67. package/src/generation/templates.py +208 -0
  68. package/src/generation/types.py +196 -0
  69. package/src/generation/validation.py +278 -0
  70. package/src/intent.py +323 -0
  71. package/src/verification/__init__.py +63 -0
  72. package/src/verification/__pycache__/__init__.cpython-313.pyc +0 -0
  73. package/src/verification/__pycache__/checker.cpython-313.pyc +0 -0
  74. package/src/verification/__pycache__/comparison.cpython-313.pyc +0 -0
  75. package/src/verification/__pycache__/explainer.cpython-313.pyc +0 -0
  76. package/src/verification/__pycache__/static_analysis.cpython-313.pyc +0 -0
  77. package/src/verification/checker.py +220 -0
  78. package/src/verification/comparison.py +492 -0
  79. package/src/verification/explainer.py +414 -0
  80. package/src/verification/static_analysis.py +540 -0
  81. package/src/workflows/__init__.py +21 -0
  82. package/src/workflows/__pycache__/__init__.cpython-313.pyc +0 -0
  83. package/src/workflows/__pycache__/extract.cpython-313.pyc +0 -0
  84. package/src/workflows/__pycache__/generate.cpython-313.pyc +0 -0
  85. package/src/workflows/__pycache__/run.cpython-313.pyc +0 -0
  86. package/src/workflows/__pycache__/verify.cpython-313.pyc +0 -0
  87. package/src/workflows/extract.py +181 -0
  88. package/src/workflows/generate.py +155 -0
  89. package/src/workflows/run.py +187 -0
  90. package/src/workflows/verify.py +334 -0
@@ -0,0 +1,375 @@
1
+ """Code generator for algorithms.
2
+
3
+ This module provides the TemplateCodeGenerator class for
4
+ converting structured algorithms into Python code.
5
+ """
6
+
7
+ import ast
8
+ import re
9
+ from dataclasses import dataclass, field
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from src.extraction.schema import Algorithm, Step, StepType
13
+ from src.generation.templates import TemplateRegistry
14
+ from src.generation.types import TypeInferrer, ValidationResult, FunctionSignature
15
+
16
+
17
+ @dataclass
18
+ class GeneratedCode:
19
+ """
20
+ Represents generated Python code with metadata.
21
+
22
+ Attributes:
23
+ source: Complete Python source code
24
+ algorithm_name: Name of the source algorithm
25
+ imports: List of required imports
26
+ validation_result: Syntax validation results
27
+ """
28
+ source: str
29
+ algorithm_name: str
30
+ imports: List[str] = field(default_factory=list)
31
+ validation_result: Optional[ValidationResult] = None
32
+
33
+
34
+ class TemplateCodeGenerator:
35
+ """
36
+ Generate Python code from algorithms using templates.
37
+
38
+ Uses template-based generation for predictable constructs
39
+ like assignments, loops, and conditionals.
40
+ """
41
+
42
+ def __init__(self, template_registry: Optional[TemplateRegistry] = None):
43
+ """
44
+ Initialize the code generator.
45
+
46
+ Args:
47
+ template_registry: Optional custom template registry
48
+ """
49
+ self.templates = template_registry or TemplateRegistry()
50
+ self.type_inferrer = TypeInferrer()
51
+ self._step_map: Dict[int, Step] = {}
52
+
53
+ def generate(self, algorithm: Algorithm) -> GeneratedCode:
54
+ """
55
+ Generate complete Python code from algorithm.
56
+
57
+ Args:
58
+ algorithm: Algorithm to generate code for
59
+
60
+ Returns:
61
+ GeneratedCode with source and metadata
62
+ """
63
+ # Build step lookup
64
+ self._step_map = {step.id: step for step in algorithm.steps}
65
+
66
+ # Generate code sections
67
+ imports = self._generate_imports(algorithm)
68
+ signature = self._generate_signature(algorithm)
69
+ docstring = self._generate_docstring(algorithm)
70
+ body = self._generate_body(algorithm.steps)
71
+ guard = self._generate_execution_guard(algorithm)
72
+
73
+ # Assemble source
74
+ source_lines = []
75
+ if imports:
76
+ source_lines.append(imports)
77
+ source_lines.append("")
78
+
79
+ source_lines.append(signature)
80
+ source_lines.append(f' """{docstring}"""')
81
+ source_lines.append("")
82
+
83
+ if body:
84
+ source_lines.append(body)
85
+ else:
86
+ source_lines.append(" pass")
87
+
88
+ source = '\n'.join(source_lines)
89
+
90
+ # Validate syntax
91
+ validation = self._validate_syntax(source)
92
+
93
+ # Add execution guard
94
+ if guard:
95
+ source += '\n\n' + guard
96
+
97
+ # Extract imports
98
+ import_list = self._extract_imports(imports)
99
+
100
+ return GeneratedCode(
101
+ source=source,
102
+ algorithm_name=algorithm.name,
103
+ imports=import_list,
104
+ validation_result=validation
105
+ )
106
+
107
+ def _generate_imports(self, algorithm: Algorithm) -> str:
108
+ """
109
+ Generate import section based on detected types.
110
+
111
+ Args:
112
+ algorithm: Algorithm to analyze
113
+
114
+ Returns:
115
+ Import statements
116
+ """
117
+ imports = []
118
+
119
+ # Always include typing
120
+ imports.append("from typing import List, Dict, Optional, Union, Tuple, Any")
121
+
122
+ # Check if numpy is needed
123
+ for inp in algorithm.inputs:
124
+ name = inp.get("name", "")
125
+ if self.type_inferrer.infer_variable_type(name, inp) == "np.ndarray":
126
+ imports.append("import numpy as np")
127
+ break
128
+
129
+ # Check if math is needed
130
+ if any("math." in step.description or "sqrt" in step.description
131
+ for step in algorithm.steps):
132
+ imports.append("import math")
133
+
134
+ return '\n'.join(imports)
135
+
136
+ def _generate_signature(self, algorithm: Algorithm) -> str:
137
+ """
138
+ Generate function signature.
139
+
140
+ Args:
141
+ algorithm: Algorithm to generate signature for
142
+
143
+ Returns:
144
+ Function definition line
145
+ """
146
+ sig = self.type_inferrer.infer_function_signature(algorithm)
147
+
148
+ params_str = sig.format_params()
149
+ return f"def {sig.name}({params_str}) -> {sig.return_type}:"
150
+
151
+ def _generate_docstring(self, algorithm: Algorithm) -> str:
152
+ """
153
+ Generate Google-style docstring.
154
+
155
+ Args:
156
+ algorithm: Algorithm to generate docstring for
157
+
158
+ Returns:
159
+ Docstring content
160
+ """
161
+ lines = []
162
+
163
+ # Summary
164
+ lines.append(algorithm.description or f"{algorithm.name} implementation.")
165
+ lines.append("")
166
+
167
+ # Args section
168
+ if algorithm.inputs:
169
+ lines.append("Args:")
170
+ for inp in algorithm.inputs:
171
+ name = inp.get("name", "")
172
+ desc = inp.get("description", "")
173
+ ptype = self.type_inferrer.infer_variable_type(name, inp)
174
+ lines.append(f" {name} ({ptype}): {desc}")
175
+ lines.append("")
176
+
177
+ # Returns section
178
+ if algorithm.outputs:
179
+ lines.append("Returns:")
180
+ for out in algorithm.outputs:
181
+ name = out.get("name", "")
182
+ desc = out.get("description", "")
183
+ lines.append(f" {name}: {desc}")
184
+ lines.append("")
185
+
186
+ return '\n'.join(lines)
187
+
188
+ def _generate_body(self, steps: List[Step], indent: int = 4) -> str:
189
+ """
190
+ Generate function body from steps.
191
+
192
+ Args:
193
+ steps: Algorithm steps
194
+ indent: Indentation level
195
+
196
+ Returns:
197
+ Body code string
198
+ """
199
+ lines = []
200
+
201
+ for step in steps:
202
+ code = self._format_step(step, indent)
203
+ if code:
204
+ lines.append(code)
205
+
206
+ if not lines:
207
+ return ""
208
+
209
+ return '\n'.join(lines)
210
+
211
+ def _format_step(self, step: Step, indent: int = 4) -> str:
212
+ """
213
+ Format a single step into Python code.
214
+
215
+ Args:
216
+ step: Step to format
217
+ indent: Indentation level
218
+
219
+ Returns:
220
+ Formatted code
221
+ """
222
+ indent_str = ' ' * indent
223
+
224
+ if step.type == StepType.ASSIGNMENT:
225
+ target = step.outputs[0] if step.outputs else "result"
226
+ expression = step.expression or "None"
227
+ return f"{indent_str}{target} = {expression}"
228
+
229
+ elif step.type == StepType.LOOP_FOR:
230
+ iter_var = step.iter_var or "i"
231
+ iter_range = step.iter_range or "range(n)"
232
+
233
+ lines = [f"{indent_str}for {iter_var} in {iter_range}:"]
234
+
235
+ # Format body
236
+ body_code = self._format_body(step.body, indent + 4)
237
+ if body_code:
238
+ lines.append(body_code)
239
+ else:
240
+ lines.append(f"{' ' * (indent + 4)}pass")
241
+
242
+ return '\n'.join(lines)
243
+
244
+ elif step.type == StepType.LOOP_WHILE:
245
+ condition = step.condition or "True"
246
+
247
+ lines = [f"{indent_str}while {condition}:"]
248
+
249
+ body_code = self._format_body(step.body, indent + 4)
250
+ if body_code:
251
+ lines.append(body_code)
252
+ else:
253
+ lines.append(f"{' ' * (indent + 4)}pass")
254
+
255
+ return '\n'.join(lines)
256
+
257
+ elif step.type == StepType.CONDITIONAL:
258
+ condition = step.condition or "True"
259
+
260
+ lines = [f"{indent_str}if {condition}:"]
261
+
262
+ # Format if body
263
+ if_body = self._format_body(step.body, indent + 4)
264
+ if if_body:
265
+ lines.append(if_body)
266
+ else:
267
+ lines.append(f"{' ' * (indent + 4)}pass")
268
+
269
+ # Format else body if present
270
+ if step.else_body:
271
+ lines.append(f"{indent_str}else:")
272
+ else_body = self._format_body(step.else_body, indent + 4)
273
+ if else_body:
274
+ lines.append(else_body)
275
+ else:
276
+ lines.append(f"{' ' * (indent + 4)}pass")
277
+
278
+ return '\n'.join(lines)
279
+
280
+ elif step.type == StepType.RETURN:
281
+ expression = step.expression or "None"
282
+ return f"{indent_str}return {expression}"
283
+
284
+ elif step.type == StepType.CALL:
285
+ call_target = step.call_target or "function"
286
+ arguments = ", ".join(step.arguments) if step.arguments else ""
287
+ return f"{indent_str}{call_target}({arguments})"
288
+
289
+ elif step.type == StepType.COMMENT:
290
+ annotation = step.annotation or step.description
291
+ return f"{indent_str}# {annotation}"
292
+
293
+ return f"{indent_str}# TODO: {step.description}"
294
+
295
+ def _format_body(self, body_step_ids: List[int], indent: int) -> str:
296
+ """
297
+ Format body steps.
298
+
299
+ Args:
300
+ body_step_ids: List of step IDs in body
301
+ indent: Indentation level
302
+
303
+ Returns:
304
+ Formatted body code
305
+ """
306
+ lines = []
307
+
308
+ for step_id in body_step_ids:
309
+ step = self._step_map.get(step_id)
310
+ if step:
311
+ code = self._format_step(step, indent)
312
+ if code:
313
+ lines.append(code)
314
+
315
+ if not lines:
316
+ return ""
317
+
318
+ return '\n'.join(lines)
319
+
320
+ def _generate_execution_guard(self, algorithm: Algorithm) -> str:
321
+ """
322
+ Generate execution guard for example usage.
323
+
324
+ Args:
325
+ algorithm: Algorithm to generate guard for
326
+
327
+ Returns:
328
+ Execution guard code
329
+ """
330
+ sig = self.type_inferrer.infer_function_signature(algorithm)
331
+
332
+ lines = [
333
+ 'if __name__ == "__main__":',
334
+ ' # Example usage',
335
+ ' pass',
336
+ ]
337
+
338
+ return '\n'.join(lines)
339
+
340
+ def _validate_syntax(self, code: str) -> ValidationResult:
341
+ """
342
+ Validate Python syntax using ast module.
343
+
344
+ Args:
345
+ code: Code to validate
346
+
347
+ Returns:
348
+ ValidationResult with errors if any
349
+ """
350
+ try:
351
+ ast.parse(code)
352
+ return ValidationResult(is_valid=True, errors=[], warnings=[])
353
+ except SyntaxError as e:
354
+ return ValidationResult(
355
+ is_valid=False,
356
+ errors=[{
357
+ 'message': f"Syntax error: {e.msg}",
358
+ 'line': e.lineno,
359
+ 'type': 'syntax',
360
+ 'text': e.text
361
+ }],
362
+ warnings=[]
363
+ )
364
+
365
+ def _extract_imports(self, imports_section: str) -> List[str]:
366
+ """Extract module names from imports section."""
367
+ imports = []
368
+ for line in imports_section.split('\n'):
369
+ if line.startswith('import '):
370
+ imports.append(line.replace('import ', ''))
371
+ elif line.startswith('from '):
372
+ parts = line.split()
373
+ if len(parts) >= 2:
374
+ imports.append(parts[1])
375
+ return imports
@@ -0,0 +1,84 @@
1
+ """Error classes for code generation.
2
+
3
+ This module defines the error hierarchy for generation failures.
4
+ """
5
+
6
+ from typing import Optional
7
+
8
+
9
+ class GenerationError(Exception):
10
+ """Base class for generation errors."""
11
+
12
+ def __init__(self, message: str,
13
+ line_number: Optional[int] = None,
14
+ context: Optional[str] = None):
15
+ self.message = message
16
+ self.line_number = line_number
17
+ self.context = context
18
+ super().__init__(self.format_message())
19
+
20
+ def format_message(self) -> str:
21
+ """Format error message for display."""
22
+ if self.line_number:
23
+ return f"Generation error at line {self.line_number}: {self.message}"
24
+ return f"Generation error: {self.message}"
25
+
26
+
27
+ class SyntaxGenerationError(GenerationError):
28
+ """Raised when generated code has syntax errors."""
29
+ pass
30
+
31
+
32
+ class ImportGenerationError(GenerationError):
33
+ """Raised when generated code has unresolved imports."""
34
+ pass
35
+
36
+
37
+ class UnsupportedConstructError(GenerationError):
38
+ """Raised when step contains unsupported construct."""
39
+ pass
40
+
41
+
42
+ class LLMGenerationError(GenerationError):
43
+ """Raised when LLM fails to generate code."""
44
+ pass
45
+
46
+
47
+ class ValidationError(GenerationError):
48
+ """Raised when generated code fails validation."""
49
+ pass
50
+
51
+
52
+ def format_error_for_user(error: GenerationError) -> str:
53
+ """
54
+ Format error message for user display.
55
+
56
+ Args:
57
+ error: GenerationError to format
58
+
59
+ Returns:
60
+ Formatted error string
61
+ """
62
+ lines = []
63
+ lines.append(f"Code Generation Error: {error.message}")
64
+
65
+ if error.line_number:
66
+ lines.append(f" Location: Line {error.line_number}")
67
+
68
+ if error.context:
69
+ lines.append(f" Context: {error.context}")
70
+
71
+ lines.append("\n Suggestion: Check the algorithm steps and try again.")
72
+
73
+ return '\n'.join(lines)
74
+
75
+
76
+ __all__ = [
77
+ "GenerationError",
78
+ "SyntaxGenerationError",
79
+ "ImportGenerationError",
80
+ "UnsupportedConstructError",
81
+ "LLMGenerationError",
82
+ "ValidationError",
83
+ "format_error_for_user",
84
+ ]
@@ -0,0 +1,210 @@
1
+ """Hybrid code generator with fallback hierarchy.
2
+
3
+ This module provides the HybridCodeGenerator class that combines
4
+ template-based and LLM-based generation with automatic fallback.
5
+ """
6
+
7
+ import re
8
+ from typing import Any, Dict, Optional
9
+
10
+ from src.extraction.schema import Algorithm, Step, StepType
11
+ from src.generation.code_generator import GeneratedCode, TemplateCodeGenerator
12
+ from src.generation.errors import LLMGenerationError
13
+ from src.generation.llm_generator import LLMCodeGenerator
14
+
15
+
16
+ class HybridGenerationResult:
17
+ """
18
+ Result of hybrid code generation with metadata.
19
+
20
+ Attributes:
21
+ generated: GeneratedCode object
22
+ strategy: Which strategy was used
23
+ fallback_used: Whether fallback was triggered
24
+ steps_generated: Number of steps
25
+ """
26
+ def __init__(self, generated: GeneratedCode, strategy: str,
27
+ fallback_used: bool = False, steps_generated: int = 0):
28
+ self.generated = generated
29
+ self.strategy = strategy
30
+ self.fallback_used = fallback_used
31
+ self.steps_generated = steps_generated
32
+
33
+
34
+ class HybridCodeGenerator:
35
+ """
36
+ Hybrid generator with Template → LLM → Stub fallback hierarchy.
37
+
38
+ Automatically selects the best generation strategy based on
39
+ step complexity and availability.
40
+ """
41
+
42
+ def __init__(self,
43
+ template_gen: Optional[TemplateCodeGenerator] = None,
44
+ llm_gen: Optional[LLMCodeGenerator] = None):
45
+ """
46
+ Initialize hybrid generator.
47
+
48
+ Args:
49
+ template_gen: Optional template generator
50
+ llm_gen: Optional LLM generator
51
+ """
52
+ self.template_gen = template_gen or TemplateCodeGenerator()
53
+ self.llm_gen = llm_gen or LLMCodeGenerator()
54
+ self.used_llm = False
55
+ self.used_template = False
56
+
57
+ def generate(self, algorithm: Algorithm,
58
+ strategy: str = "auto") -> GeneratedCode:
59
+ """
60
+ Generate code using hybrid approach.
61
+
62
+ Args:
63
+ algorithm: Algorithm to generate code for
64
+ strategy: "auto" | "template_only" | "llm_only"
65
+
66
+ Returns:
67
+ GeneratedCode with source and metadata
68
+ """
69
+ if strategy == "template_only":
70
+ return self._template_generate(algorithm)
71
+ elif strategy == "llm_only":
72
+ return self._llm_generate(algorithm)
73
+ else: # auto
74
+ return self._hybrid_generate(algorithm)
75
+
76
+ def _hybrid_generate(self, algorithm: Algorithm) -> GeneratedCode:
77
+ """
78
+ Hybrid generation with automatic fallback.
79
+
80
+ Flow:
81
+ 1. Try template generation for all steps
82
+ 2. If any step fails, try LLM for that step
83
+ 3. If LLM fails, use stub
84
+
85
+ Args:
86
+ algorithm: Algorithm to generate code for
87
+
88
+ Returns:
89
+ GeneratedCode
90
+ """
91
+ # Check if all steps can be handled by templates
92
+ template_compatible = all(
93
+ self._is_template_compatible(step)
94
+ for step in algorithm.steps
95
+ )
96
+
97
+ if template_compatible:
98
+ self.used_template = True
99
+ return self.template_gen.generate(algorithm)
100
+
101
+ # Some steps need LLM
102
+ try:
103
+ self.used_llm = True
104
+ return self.llm_gen.generate(algorithm)
105
+ except LLMGenerationError:
106
+ # Fall back to stub
107
+ return self.llm_gen._generate_stub(algorithm)
108
+
109
+ def _template_generate(self, algorithm: Algorithm) -> GeneratedCode:
110
+ """Generate using template generator only."""
111
+ self.used_template = True
112
+ return self.template_gen.generate(algorithm)
113
+
114
+ def _llm_generate(self, algorithm: Algorithm) -> GeneratedCode:
115
+ """Generate using LLM generator only."""
116
+ self.used_llm = True
117
+ return self.llm_gen.generate_with_fallback(algorithm)
118
+
119
+ def _is_template_compatible(self, step: Step) -> bool:
120
+ """
121
+ Check if step can be handled by templates.
122
+
123
+ Templates handle: ASSIGNMENT, LOOP_FOR, LOOP_WHILE,
124
+ CONDITIONAL, RETURN, CALL, COMMENT
125
+
126
+ LLM handles: Complex expressions, custom logic
127
+
128
+ Args:
129
+ step: Step to check
130
+
131
+ Returns:
132
+ True if template-compatible
133
+ """
134
+ # Check step type
135
+ if step.type not in [
136
+ StepType.ASSIGNMENT,
137
+ StepType.LOOP_FOR,
138
+ StepType.LOOP_WHILE,
139
+ StepType.CONDITIONAL,
140
+ StepType.RETURN,
141
+ StepType.CALL,
142
+ StepType.COMMENT
143
+ ]:
144
+ return False
145
+
146
+ return self._is_simple_expression(step)
147
+
148
+ def _is_simple_expression(self, step: Step) -> bool:
149
+ """
150
+ Check if expression is simple enough for templates.
151
+
152
+ Args:
153
+ step: Step to check
154
+
155
+ Returns:
156
+ True if expression is simple
157
+ """
158
+ if step.expression:
159
+ # Check for complex patterns
160
+ complex_patterns = [
161
+ r'sum\s+of', # Summation
162
+ r'product\s+of', # Product
163
+ r'argmin|argmax', # Optimization
164
+ r'minimum|maximum', # Min/max
165
+ r'forall|exists', # Quantifiers
166
+ ]
167
+
168
+ for pattern in complex_patterns:
169
+ if re.search(pattern, step.expression, re.IGNORECASE):
170
+ return False
171
+
172
+ return True
173
+
174
+ def get_generation_strategy(self) -> str:
175
+ """Return which strategy was used for last generation."""
176
+ if self.used_template:
177
+ return "template"
178
+ elif self.used_llm:
179
+ return "llm"
180
+ else:
181
+ return "stub"
182
+
183
+ def generate_for_workflow(self, algorithm: Algorithm) -> Dict[str, Any]:
184
+ """
185
+ Generate code with metadata for workflow.
186
+
187
+ Args:
188
+ algorithm: Algorithm to generate code for
189
+
190
+ Returns:
191
+ Dict with:
192
+ - generated: GeneratedCode object
193
+ - strategy: "template" | "llm" | "stub"
194
+ - fallback_used: bool
195
+ - steps_generated: int
196
+ """
197
+ generated = self.generate(algorithm)
198
+
199
+ return {
200
+ 'generated': generated,
201
+ 'strategy': self.get_generation_strategy(),
202
+ 'fallback_used': self.used_llm and not self.used_template,
203
+ 'steps_generated': len(algorithm.steps),
204
+ }
205
+
206
+
207
+ __all__ = [
208
+ "HybridCodeGenerator",
209
+ "HybridGenerationResult",
210
+ ]