deepagents-printshop 0.1.0__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.
Files changed (37) hide show
  1. agents/content_editor/__init__.py +1 -0
  2. agents/content_editor/agent.py +279 -0
  3. agents/content_editor/content_reviewer.py +327 -0
  4. agents/content_editor/versioned_agent.py +455 -0
  5. agents/latex_specialist/__init__.py +1 -0
  6. agents/latex_specialist/agent.py +531 -0
  7. agents/latex_specialist/latex_analyzer.py +510 -0
  8. agents/latex_specialist/latex_optimizer.py +1192 -0
  9. agents/qa_orchestrator/__init__.py +1 -0
  10. agents/qa_orchestrator/agent.py +603 -0
  11. agents/qa_orchestrator/langgraph_workflow.py +733 -0
  12. agents/qa_orchestrator/pipeline_types.py +72 -0
  13. agents/qa_orchestrator/quality_gates.py +495 -0
  14. agents/qa_orchestrator/workflow_coordinator.py +139 -0
  15. agents/research_agent/__init__.py +1 -0
  16. agents/research_agent/agent.py +258 -0
  17. agents/research_agent/llm_report_generator.py +1023 -0
  18. agents/research_agent/report_generator.py +536 -0
  19. agents/visual_qa/__init__.py +1 -0
  20. agents/visual_qa/agent.py +410 -0
  21. deepagents_printshop-0.1.0.dist-info/METADATA +744 -0
  22. deepagents_printshop-0.1.0.dist-info/RECORD +37 -0
  23. deepagents_printshop-0.1.0.dist-info/WHEEL +4 -0
  24. deepagents_printshop-0.1.0.dist-info/entry_points.txt +2 -0
  25. deepagents_printshop-0.1.0.dist-info/licenses/LICENSE +86 -0
  26. tools/__init__.py +1 -0
  27. tools/change_tracker.py +419 -0
  28. tools/content_type_loader.py +171 -0
  29. tools/graph_generator.py +281 -0
  30. tools/latex_generator.py +374 -0
  31. tools/llm_latex_generator.py +678 -0
  32. tools/magazine_layout.py +462 -0
  33. tools/pattern_injector.py +250 -0
  34. tools/pattern_learner.py +477 -0
  35. tools/pdf_compiler.py +386 -0
  36. tools/version_manager.py +346 -0
  37. tools/visual_qa.py +799 -0
@@ -0,0 +1,510 @@
1
+ """
2
+ LaTeX Analyzer - Milestone 3
3
+
4
+ Analyzes LaTeX document structure, typography, and formatting quality.
5
+ """
6
+
7
+ import re
8
+ import os
9
+ from typing import Dict, List, Tuple, Optional
10
+ from pathlib import Path
11
+ from dataclasses import dataclass
12
+
13
+
14
+ @dataclass
15
+ class LaTeXIssue:
16
+ """Represents a LaTeX formatting issue."""
17
+ severity: str # 'error', 'warning', 'suggestion'
18
+ category: str # 'structure', 'typography', 'tables', 'figures', 'general'
19
+ description: str
20
+ line_number: Optional[int] = None
21
+ suggestion: Optional[str] = None
22
+
23
+
24
+ @dataclass
25
+ class LaTeXAnalysisResult:
26
+ """Results of LaTeX document analysis."""
27
+ structure_score: int # 0-25
28
+ typography_score: int # 0-25
29
+ tables_figures_score: int # 0-25
30
+ best_practices_score: int # 0-25
31
+ overall_score: int # 0-100
32
+ issues: List[LaTeXIssue]
33
+ document_class: str
34
+ packages_used: List[str]
35
+ structure_info: Dict
36
+ suggestions: List[str]
37
+
38
+
39
+ class LaTeXAnalyzer:
40
+ """
41
+ Analyzes LaTeX documents for quality, structure, and formatting issues.
42
+
43
+ Features:
44
+ - Document structure analysis
45
+ - Typography quality assessment
46
+ - Table and figure formatting validation
47
+ - LaTeX best practices checking
48
+ """
49
+
50
+ def __init__(self):
51
+ """Initialize the LaTeX analyzer."""
52
+ self.document_classes = {
53
+ 'article': {'sections': ['section', 'subsection', 'subsubsection']},
54
+ 'report': {'sections': ['chapter', 'section', 'subsection', 'subsubsection']},
55
+ 'book': {'sections': ['part', 'chapter', 'section', 'subsection', 'subsubsection']},
56
+ }
57
+
58
+ self.recommended_packages = {
59
+ 'typography': ['fontenc', 'inputenc', 'microtype'],
60
+ 'tables': ['booktabs', 'array', 'longtable'],
61
+ 'figures': ['graphicx', 'float', 'caption'],
62
+ 'references': ['hyperref', 'cite', 'natbib'],
63
+ 'math': ['amsmath', 'amssymb', 'amsthm']
64
+ }
65
+
66
+ self.deprecated_commands = [
67
+ '\\bf', '\\it', '\\rm', '\\sc', '\\sf', '\\sl', '\\tt',
68
+ '\\centerline', '\\over', '\\above', '\\atop'
69
+ ]
70
+
71
+ def analyze_document(self, latex_content: str) -> LaTeXAnalysisResult:
72
+ """
73
+ Perform comprehensive LaTeX document analysis.
74
+
75
+ Args:
76
+ latex_content: The LaTeX document content
77
+
78
+ Returns:
79
+ Detailed analysis results
80
+ """
81
+ issues = []
82
+
83
+ # Analyze different aspects
84
+ structure_result = self._analyze_structure(latex_content)
85
+ typography_result = self._analyze_typography(latex_content)
86
+ tables_figures_result = self._analyze_tables_figures(latex_content)
87
+ best_practices_result = self._analyze_best_practices(latex_content)
88
+
89
+ # Combine issues
90
+ issues.extend(structure_result['issues'])
91
+ issues.extend(typography_result['issues'])
92
+ issues.extend(tables_figures_result['issues'])
93
+ issues.extend(best_practices_result['issues'])
94
+
95
+ # Calculate scores
96
+ structure_score = structure_result['score']
97
+ typography_score = typography_result['score']
98
+ tables_figures_score = tables_figures_result['score']
99
+ best_practices_score = best_practices_result['score']
100
+
101
+ overall_score = structure_score + typography_score + tables_figures_score + best_practices_score
102
+
103
+ # Extract document info
104
+ doc_class = self._extract_document_class(latex_content)
105
+ packages = self._extract_packages(latex_content)
106
+
107
+ # Generate suggestions
108
+ suggestions = self._generate_suggestions(issues, structure_result, typography_result)
109
+
110
+ return LaTeXAnalysisResult(
111
+ structure_score=structure_score,
112
+ typography_score=typography_score,
113
+ tables_figures_score=tables_figures_score,
114
+ best_practices_score=best_practices_score,
115
+ overall_score=overall_score,
116
+ issues=issues,
117
+ document_class=doc_class,
118
+ packages_used=packages,
119
+ structure_info=structure_result['info'],
120
+ suggestions=suggestions
121
+ )
122
+
123
+ def _analyze_structure(self, content: str) -> Dict:
124
+ """Analyze document structure and hierarchy."""
125
+ issues = []
126
+ score = 25 # Start with full points
127
+
128
+ # Extract document class
129
+ doc_class = self._extract_document_class(content)
130
+
131
+ # Find all sections
132
+ section_patterns = [
133
+ (r'\\part\{([^}]+)\}', 'part'),
134
+ (r'\\chapter\{([^}]+)\}', 'chapter'),
135
+ (r'\\section\{([^}]+)\}', 'section'),
136
+ (r'\\subsection\{([^}]+)\}', 'subsection'),
137
+ (r'\\subsubsection\{([^}]+)\}', 'subsubsection'),
138
+ ]
139
+
140
+ sections_found = []
141
+ for pattern, level in section_patterns:
142
+ matches = re.finditer(pattern, content)
143
+ for match in matches:
144
+ line_num = content[:match.start()].count('\n') + 1
145
+ sections_found.append({
146
+ 'level': level,
147
+ 'title': match.group(1),
148
+ 'line': line_num,
149
+ 'position': match.start()
150
+ })
151
+
152
+ # Sort by position in document
153
+ sections_found.sort(key=lambda x: x['position'])
154
+
155
+ # Check section hierarchy
156
+ hierarchy_issues = self._check_section_hierarchy(sections_found, doc_class)
157
+ issues.extend(hierarchy_issues)
158
+ if hierarchy_issues:
159
+ score -= min(10, len(hierarchy_issues) * 3)
160
+
161
+ # Check for title and author
162
+ if not re.search(r'\\title\{', content):
163
+ issues.append(LaTeXIssue(
164
+ severity='warning',
165
+ category='structure',
166
+ description='Document is missing \\title command',
167
+ suggestion='Add \\title{Your Title Here} in the preamble'
168
+ ))
169
+ score -= 3
170
+
171
+ if not re.search(r'\\author\{', content):
172
+ issues.append(LaTeXIssue(
173
+ severity='suggestion',
174
+ category='structure',
175
+ description='Document is missing \\author command',
176
+ suggestion='Add \\author{Your Name} in the preamble'
177
+ ))
178
+ score -= 2
179
+
180
+ # Check for proper document environment
181
+ if not re.search(r'\\begin\{document\}', content):
182
+ issues.append(LaTeXIssue(
183
+ severity='error',
184
+ category='structure',
185
+ description='Document is missing \\begin{document}',
186
+ suggestion='Add \\begin{document} and \\end{document}'
187
+ ))
188
+ score -= 10
189
+
190
+ return {
191
+ 'score': max(0, score),
192
+ 'issues': issues,
193
+ 'info': {
194
+ 'document_class': doc_class,
195
+ 'sections_found': sections_found,
196
+ 'section_count': len(sections_found)
197
+ }
198
+ }
199
+
200
+ def _analyze_typography(self, content: str) -> Dict:
201
+ """Analyze typography and formatting quality."""
202
+ issues = []
203
+ score = 25 # Start with full points
204
+
205
+ # Check for proper font encoding
206
+ if not re.search(r'\\usepackage\[[^]]*\]\{fontenc\}', content):
207
+ issues.append(LaTeXIssue(
208
+ severity='suggestion',
209
+ category='typography',
210
+ description='Missing font encoding package',
211
+ suggestion='Add \\usepackage[T1]{fontenc} for better font encoding'
212
+ ))
213
+ score -= 2
214
+
215
+ # Check for input encoding
216
+ if not re.search(r'\\usepackage\[[^]]*\]\{inputenc\}', content):
217
+ issues.append(LaTeXIssue(
218
+ severity='suggestion',
219
+ category='typography',
220
+ description='Missing input encoding package',
221
+ suggestion='Add \\usepackage[utf8]{inputenc} for UTF-8 support'
222
+ ))
223
+ score -= 2
224
+
225
+ # Check for microtype (better typography)
226
+ if not re.search(r'\\usepackage.*\{microtype\}', content):
227
+ issues.append(LaTeXIssue(
228
+ severity='suggestion',
229
+ category='typography',
230
+ description='Missing microtype package for better typography',
231
+ suggestion='Add \\usepackage{microtype} for improved spacing'
232
+ ))
233
+ score -= 1
234
+
235
+ # Check for proper spacing issues
236
+ spacing_issues = [
237
+ (r'\s{2,}', 'Multiple consecutive spaces found'),
238
+ (r'[.!?]\w', 'Missing space after sentence ending'),
239
+ (r'\w[.!?][.!?]', 'Multiple consecutive punctuation marks'),
240
+ ]
241
+
242
+ for pattern, description in spacing_issues:
243
+ matches = list(re.finditer(pattern, content))
244
+ if matches:
245
+ issues.append(LaTeXIssue(
246
+ severity='warning',
247
+ category='typography',
248
+ description=f'{description} ({len(matches)} instances)',
249
+ suggestion='Fix spacing issues for better typography'
250
+ ))
251
+ score -= min(3, len(matches))
252
+
253
+ # Check for proper emphasis usage
254
+ if re.search(r'\\textit\{[^}]*\\textit\{', content):
255
+ issues.append(LaTeXIssue(
256
+ severity='warning',
257
+ category='typography',
258
+ description='Nested emphasis commands found',
259
+ suggestion='Avoid nesting \\textit or \\textbf commands'
260
+ ))
261
+ score -= 2
262
+
263
+ # Check line length (if we can detect it)
264
+ lines = content.split('\n')
265
+ long_lines = [i+1 for i, line in enumerate(lines) if len(line) > 100 and not line.strip().startswith('%')]
266
+ if len(long_lines) > 5:
267
+ issues.append(LaTeXIssue(
268
+ severity='suggestion',
269
+ category='typography',
270
+ description=f'Many long lines detected ({len(long_lines)} lines > 100 chars)',
271
+ suggestion='Consider breaking long lines for better readability'
272
+ ))
273
+ score -= 1
274
+
275
+ return {
276
+ 'score': max(0, score),
277
+ 'issues': issues,
278
+ 'info': {
279
+ 'long_lines_count': len(long_lines),
280
+ 'average_line_length': sum(len(line) for line in lines) / len(lines) if lines else 0
281
+ }
282
+ }
283
+
284
+ def _analyze_tables_figures(self, content: str) -> Dict:
285
+ """Analyze table and figure formatting."""
286
+ issues = []
287
+ score = 25
288
+
289
+ # Find tables
290
+ table_patterns = [
291
+ r'\\begin\{tabular\}',
292
+ r'\\begin\{table\}',
293
+ r'\\begin\{longtable\}'
294
+ ]
295
+
296
+ tables_found = 0
297
+ for pattern in table_patterns:
298
+ tables_found += len(re.findall(pattern, content))
299
+
300
+ # Find figures
301
+ figure_patterns = [
302
+ r'\\begin\{figure\}',
303
+ r'\\includegraphics'
304
+ ]
305
+
306
+ figures_found = 0
307
+ for pattern in figure_patterns:
308
+ figures_found += len(re.findall(pattern, content))
309
+
310
+ # Check for booktabs usage in tables
311
+ if tables_found > 0:
312
+ if not re.search(r'\\usepackage.*\{booktabs\}', content):
313
+ issues.append(LaTeXIssue(
314
+ severity='suggestion',
315
+ category='tables',
316
+ description='Consider using booktabs package for professional tables',
317
+ suggestion='Add \\usepackage{booktabs} and use \\toprule, \\midrule, \\bottomrule'
318
+ ))
319
+ score -= 3
320
+
321
+ # Check for old-style table rules
322
+ if re.search(r'\\hline', content) and not re.search(r'\\(top|mid|bottom)rule', content):
323
+ issues.append(LaTeXIssue(
324
+ severity='suggestion',
325
+ category='tables',
326
+ description='Using \\hline instead of booktabs rules',
327
+ suggestion='Replace \\hline with \\toprule, \\midrule, \\bottomrule'
328
+ ))
329
+ score -= 2
330
+
331
+ # Check figure placement
332
+ if figures_found > 0:
333
+ if not re.search(r'\\usepackage.*\{float\}', content):
334
+ issues.append(LaTeXIssue(
335
+ severity='suggestion',
336
+ category='figures',
337
+ description='Consider using float package for better figure placement',
338
+ suggestion='Add \\usepackage{float} for better figure control'
339
+ ))
340
+ score -= 1
341
+
342
+ # Check for proper figure captions
343
+ caption_count = len(re.findall(r'\\caption\{', content))
344
+ if figures_found > caption_count:
345
+ issues.append(LaTeXIssue(
346
+ severity='warning',
347
+ category='figures',
348
+ description=f'Some figures missing captions ({figures_found} figures, {caption_count} captions)',
349
+ suggestion='Add \\caption{...} to all figures'
350
+ ))
351
+ score -= 3
352
+
353
+ return {
354
+ 'score': max(0, score),
355
+ 'issues': issues,
356
+ 'info': {
357
+ 'tables_found': tables_found,
358
+ 'figures_found': figures_found,
359
+ 'caption_count': caption_count if figures_found > 0 else 0
360
+ }
361
+ }
362
+
363
+ def _analyze_best_practices(self, content: str) -> Dict:
364
+ """Analyze LaTeX best practices compliance."""
365
+ issues = []
366
+ score = 25
367
+
368
+ # Check for deprecated commands
369
+ for cmd in self.deprecated_commands:
370
+ if cmd in content:
371
+ issues.append(LaTeXIssue(
372
+ severity='warning',
373
+ category='general',
374
+ description=f'Deprecated command {cmd} found',
375
+ suggestion=f'Replace {cmd} with modern equivalent'
376
+ ))
377
+ score -= 2
378
+
379
+ # Check for proper package loading order
380
+ hyperref_pos = content.find('\\usepackage{hyperref}')
381
+ other_packages = re.findall(r'\\usepackage\{([^}]+)\}', content)
382
+
383
+ if hyperref_pos != -1:
384
+ # hyperref should generally be loaded last
385
+ later_packages = []
386
+ for match in re.finditer(r'\\usepackage\{([^}]+)\}', content[hyperref_pos:]):
387
+ pkg = match.group(1)
388
+ if pkg != 'hyperref':
389
+ later_packages.append(pkg)
390
+
391
+ if later_packages:
392
+ issues.append(LaTeXIssue(
393
+ severity='suggestion',
394
+ category='general',
395
+ description='hyperref package should generally be loaded last',
396
+ suggestion='Move \\usepackage{hyperref} to end of preamble'
397
+ ))
398
+ score -= 1
399
+
400
+ # Check for proper label usage
401
+ labels = re.findall(r'\\label\{([^}]+)\}', content)
402
+ refs = re.findall(r'\\ref\{([^}]+)\}', content)
403
+
404
+ unused_labels = set(labels) - set(refs)
405
+ if unused_labels:
406
+ issues.append(LaTeXIssue(
407
+ severity='suggestion',
408
+ category='general',
409
+ description=f'Unused labels found: {", ".join(list(unused_labels)[:3])}{"..." if len(unused_labels) > 3 else ""}',
410
+ suggestion='Remove unused \\label commands'
411
+ ))
412
+ score -= 1
413
+
414
+ # Check for proper math mode usage
415
+ inline_math_issues = re.findall(r'\$[^$]*\$\$[^$]*\$', content)
416
+ if inline_math_issues:
417
+ issues.append(LaTeXIssue(
418
+ severity='warning',
419
+ category='general',
420
+ description='Possible incorrect math mode usage ($ mixed with $$)',
421
+ suggestion='Use consistent math delimiters: $ for inline, $$ or \\[ \\] for display'
422
+ ))
423
+ score -= 2
424
+
425
+ return {
426
+ 'score': max(0, score),
427
+ 'issues': issues,
428
+ 'info': {
429
+ 'packages_count': len(other_packages),
430
+ 'labels_count': len(labels),
431
+ 'refs_count': len(refs),
432
+ 'unused_labels': list(unused_labels)
433
+ }
434
+ }
435
+
436
+ def _extract_document_class(self, content: str) -> str:
437
+ """Extract document class from LaTeX content."""
438
+ match = re.search(r'\\documentclass(?:\[[^]]*\])?\{([^}]+)\}', content)
439
+ return match.group(1) if match else 'unknown'
440
+
441
+ def _extract_packages(self, content: str) -> List[str]:
442
+ """Extract all packages used in the document."""
443
+ return re.findall(r'\\usepackage(?:\[[^]]*\])?\{([^}]+)\}', content)
444
+
445
+ def _check_section_hierarchy(self, sections: List[Dict], doc_class: str) -> List[LaTeXIssue]:
446
+ """Check if section hierarchy is logical."""
447
+ issues = []
448
+
449
+ if not sections:
450
+ return issues
451
+
452
+ # Define hierarchy levels
453
+ hierarchy = {
454
+ 'article': ['section', 'subsection', 'subsubsection'],
455
+ 'report': ['chapter', 'section', 'subsection', 'subsubsection'],
456
+ 'book': ['part', 'chapter', 'section', 'subsection', 'subsubsection']
457
+ }
458
+
459
+ expected_hierarchy = hierarchy.get(doc_class, hierarchy['article'])
460
+
461
+ # Check for proper nesting
462
+ prev_level_index = -1
463
+ for section in sections:
464
+ level = section['level']
465
+ if level in expected_hierarchy:
466
+ level_index = expected_hierarchy.index(level)
467
+
468
+ # Check if we're skipping levels
469
+ if level_index > prev_level_index + 1:
470
+ issues.append(LaTeXIssue(
471
+ severity='warning',
472
+ category='structure',
473
+ description=f'Section hierarchy skip detected at "{section["title"]}"',
474
+ line_number=section['line'],
475
+ suggestion=f'Consider adding intermediate section levels'
476
+ ))
477
+
478
+ prev_level_index = level_index
479
+
480
+ return issues
481
+
482
+ def _generate_suggestions(self, issues: List[LaTeXIssue], structure_result: Dict, typography_result: Dict) -> List[str]:
483
+ """Generate overall improvement suggestions."""
484
+ suggestions = []
485
+
486
+ # Priority suggestions based on severity
487
+ error_count = sum(1 for issue in issues if issue.severity == 'error')
488
+ warning_count = sum(1 for issue in issues if issue.severity == 'warning')
489
+
490
+ if error_count > 0:
491
+ suggestions.append(f"Fix {error_count} critical LaTeX errors for compilation")
492
+
493
+ if warning_count > 0:
494
+ suggestions.append(f"Address {warning_count} formatting warnings for better quality")
495
+
496
+ # Specific recommendations
497
+ if structure_result['score'] < 20:
498
+ suggestions.append("Improve document structure and section organization")
499
+
500
+ if typography_result['score'] < 20:
501
+ suggestions.append("Enhance typography with proper packages and spacing")
502
+
503
+ # Package recommendations
504
+ common_packages = ['booktabs', 'microtype', 'hyperref']
505
+ missing_packages = [pkg for pkg in common_packages if not any(pkg in issue.description for issue in issues)]
506
+
507
+ if len(missing_packages) > 1:
508
+ suggestions.append(f"Consider adding packages: {', '.join(missing_packages)}")
509
+
510
+ return suggestions[:5] # Limit to top 5 suggestions