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,37 @@
1
+ agents/content_editor/__init__.py,sha256=ruMzTy6J3tEQ2ylke1RUNlb4HD7bC2oeRjqQ0DoEAFg,52
2
+ agents/content_editor/agent.py,sha256=o2d-eee8q9qpeOVkaCMxhd6sv8Vu1yG-VVEu4ZjUxA4,9669
3
+ agents/content_editor/content_reviewer.py,sha256=4Z57bONlPP53zS8ob6mFzQ0HUi6nPFy5E5Y83YlM1Rk,11585
4
+ agents/content_editor/versioned_agent.py,sha256=F6AMM1y4W2_i9az_aY2VyBREpdx5wfZ9UxIYoG6D_3k,17415
5
+ agents/latex_specialist/__init__.py,sha256=XtnLmA4V157Y-bKFAl0XpCowvEzZ0RDXVEfpY_qiYZs,54
6
+ agents/latex_specialist/agent.py,sha256=wAtpGkDjLTaFK8l7kZg3VugjlqGJWNevVTNv6XNgHGU,19271
7
+ agents/latex_specialist/latex_analyzer.py,sha256=a7MZYzblcdDj5igQAWFp9G7pmAwAb4phsh1RA0aveLI,19860
8
+ agents/latex_specialist/latex_optimizer.py,sha256=birJDUngwxP4M0EoLpTQ2aqOVNfuWANxAppINynoE0Q,52405
9
+ agents/qa_orchestrator/__init__.py,sha256=gQtYVxxyQ5QjMFhKSVfAkBiYn1rgK172bf75Ty1-VJA,53
10
+ agents/qa_orchestrator/agent.py,sha256=AE4Fr15lef5pNaPugtXJx2ryAKpRRr7wRMwt6VnEuv0,23753
11
+ agents/qa_orchestrator/langgraph_workflow.py,sha256=0r-fiNCXV90hvrviDOPBUGX9eyJMU5rNJSQvw_OZakg,31186
12
+ agents/qa_orchestrator/pipeline_types.py,sha256=aPBSDQVJrOTGy5-JMeWWzYnW70td_UWuEe2D55w3NyA,2448
13
+ agents/qa_orchestrator/quality_gates.py,sha256=sUpI9ZHtizoNfHVukopqdkTokUBpCSod5vxpsGfXRew,20191
14
+ agents/qa_orchestrator/workflow_coordinator.py,sha256=mcUwkhmEC-6I-myyERJWoAhaHpXbsxXtPQMSqI2yZWg,6067
15
+ agents/research_agent/__init__.py,sha256=BZ_tHXe9oLT6mKcOMpAL3_0SWUQETwuaz0PEADwWhkE,52
16
+ agents/research_agent/agent.py,sha256=LjzM6qKMXRw6f_GH07rWk4Yh2sIpbEIKaMIZt1eTqWE,8259
17
+ agents/research_agent/llm_report_generator.py,sha256=GKaiDj2Q8f8v2-yp9wY-AQbQNs_6W6B9wJUbmI8f600,40938
18
+ agents/research_agent/report_generator.py,sha256=COAaEP-2Jh8viPogC-rWRmRNDTcIFvi_wDZtFn0TL8c,21828
19
+ agents/visual_qa/__init__.py,sha256=27OkVYIjeQ7U9U-2p4EKbBiF-XJ5QfavF1FizXIefXk,62
20
+ agents/visual_qa/agent.py,sha256=VJsi9lsIrLKFaqbz19Zy1eBtooYqc_JpeDgi7HIM1P4,17343
21
+ tools/__init__.py,sha256=wO6yiGkr4FhjajzqqyALijIYlSMC928sraJz9vc7h4g,77
22
+ tools/change_tracker.py,sha256=tJLFByt-CiGv6iTAA7Y5J1kk1_w0itXFxncmwb_NHa8,16137
23
+ tools/content_type_loader.py,sha256=Q4AAucdvSYSq9Gq5nQpkyl6gzEgywIzSQT_1H3S_YrE,5653
24
+ tools/graph_generator.py,sha256=rHAwxym3sAF-hSticYVL0_OLJiWlOituRPd0zSurEnI,10581
25
+ tools/latex_generator.py,sha256=p3EwIRS8BotP8P9o2TR7S2ic_AakNln78GXZ95RToOk,12153
26
+ tools/llm_latex_generator.py,sha256=RMIrXTiIBRhB5pDnW8-gWldOiU_g7jjTeDp7g-1I3gs,26947
27
+ tools/magazine_layout.py,sha256=yg0-njXcPrc5_VWCujd_3dzh5CSzvdTtBPhW17Y_3_o,15987
28
+ tools/pattern_injector.py,sha256=SqQ8qczMuP94KlxcQyirsLUURP5yG8deeFg6hAfw7nU,9080
29
+ tools/pattern_learner.py,sha256=5BIyZNOBgy8QzyHEzvodVO1AILJlo78hzDmo1bFo4cI,18659
30
+ tools/pdf_compiler.py,sha256=Bwd9xgMY1eGx_4dQJzAyhn74hd9K5rWwy-WgbYAqp_4,15627
31
+ tools/version_manager.py,sha256=DGU_5zvfNIis6ou4qQVsXsjneYWuqthmLjvvjYUKvBM,11662
32
+ tools/visual_qa.py,sha256=Ice7cyKX9tGUMETyqR_4v6EM5sgEUIOFbQ7ixaQfSkA,31952
33
+ deepagents_printshop-0.1.0.dist-info/METADATA,sha256=pzY4P-p5ONxSRclxXH1XkcDFHgi2XoMFCt5iweMJ4qY,29157
34
+ deepagents_printshop-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
35
+ deepagents_printshop-0.1.0.dist-info/entry_points.txt,sha256=tjvBcpdgDdScxX04QEH8gDZqMVHMWAFxNqJK7rHHy3Y,64
36
+ deepagents_printshop-0.1.0.dist-info/licenses/LICENSE,sha256=ZdDLHuPyBGNgj2MEuQmnjhbmgBC84BZK2309Tjj2Po0,3308
37
+ deepagents_printshop-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ printshop = agents.qa_orchestrator.agent:main
@@ -0,0 +1,86 @@
1
+ # DeepAgents PrintShop License
2
+
3
+ ## Software License (Apache 2.0)
4
+
5
+ Copyright 2025-2026 DeepAgents PrintShop Contributors
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ ---
20
+
21
+ ## Attribution Requirement for Generated Works
22
+
23
+ Any documents, publications, or media generated using DeepAgents PrintShop
24
+ must include the following attribution in a reasonably visible location
25
+ (such as in the document's footer, credits page, colophon, or acknowledgments):
26
+
27
+ "Generated with DeepAgents PrintShop (https://github.com/kormco/deepagents-printshop)"
28
+
29
+ or the shorter form:
30
+
31
+ "Powered by DeepAgents PrintShop"
32
+
33
+ This attribution requirement applies to:
34
+ - PDF documents generated by the system
35
+ - LaTeX source files distributed to others
36
+ - Published works derived from PrintShop output
37
+ - Any derivative media products
38
+
39
+ This requirement does NOT apply to:
40
+ - Internal drafts and working documents not distributed publicly
41
+ - Documents where you substantially rewrite the generated content
42
+ - Uses where attribution is technically impractical
43
+
44
+ ---
45
+
46
+ ## Sample Content License (CC BY-SA 4.0)
47
+
48
+ The sample content in `artifacts/sample_content/` is licensed under the
49
+ Creative Commons Attribution-ShareAlike 4.0 International License.
50
+
51
+ You are free to:
52
+ - **Share** — copy and redistribute the material in any medium or format
53
+ - **Adapt** — remix, transform, and build upon the material for any purpose
54
+
55
+ Under the following terms:
56
+ - **Attribution** — You must give appropriate credit, provide a link to the
57
+ license, and indicate if changes were made.
58
+ - **ShareAlike** — If you remix, transform, or build upon the material, you
59
+ must distribute your contributions under the same license.
60
+
61
+ Full license text: https://creativecommons.org/licenses/by-sa/4.0/legalcode
62
+
63
+ ---
64
+
65
+ ## Third-Party Content
66
+
67
+ Images in `artifacts/sample_content/images/` are sourced from Pixabay and are
68
+ used under the Pixabay License (https://pixabay.com/service/license-summary/).
69
+ These images are free for commercial use with no attribution required, but
70
+ attribution is appreciated.
71
+
72
+ ---
73
+
74
+ ## Disclaimer
75
+
76
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
77
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
78
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
79
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
80
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
81
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
82
+ SOFTWARE.
83
+
84
+ This software uses third-party AI services (Claude API). Users are responsible
85
+ for compliance with those services' terms of use. Content processed through
86
+ these services is subject to their respective privacy policies.
tools/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Custom tools for the DeepAgents PrintShop document generation system."""
@@ -0,0 +1,419 @@
1
+ """
2
+ Change Tracker - Milestone 2
3
+
4
+ Tracks changes between content versions and generates detailed comparison reports.
5
+ """
6
+
7
+ import difflib
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ from datetime import datetime
12
+ from typing import Dict, List, Tuple, Optional
13
+ import re
14
+
15
+
16
+ class ChangeTracker:
17
+ """
18
+ Tracks and analyzes changes between content versions.
19
+
20
+ Features:
21
+ - Generate text diffs between versions
22
+ - Summarize changes with AI analysis
23
+ - Track quality improvements over time
24
+ - Create detailed change reports
25
+ """
26
+
27
+ def __init__(self, base_dir: str = "artifacts"):
28
+ """
29
+ Initialize the change tracker.
30
+
31
+ Args:
32
+ base_dir: Base directory for artifacts
33
+ """
34
+ self.base_dir = Path(base_dir)
35
+ self.history_dir = self.base_dir / "version_history"
36
+ self.changes_dir = self.history_dir / "changes"
37
+ self.diffs_dir = self.history_dir / "diffs"
38
+
39
+ # Ensure directories exist
40
+ self.changes_dir.mkdir(parents=True, exist_ok=True)
41
+ self.diffs_dir.mkdir(parents=True, exist_ok=True)
42
+
43
+ def generate_diff(self,
44
+ old_content: str,
45
+ new_content: str,
46
+ filename: str = "content") -> Dict:
47
+ """
48
+ Generate a detailed diff between two text contents.
49
+
50
+ Args:
51
+ old_content: Original content
52
+ new_content: Modified content
53
+ filename: Name of the file being compared
54
+
55
+ Returns:
56
+ Dictionary containing diff information
57
+ """
58
+ # Split content into lines
59
+ old_lines = old_content.splitlines(keepends=True)
60
+ new_lines = new_content.splitlines(keepends=True)
61
+
62
+ # Generate unified diff
63
+ unified_diff = list(difflib.unified_diff(
64
+ old_lines,
65
+ new_lines,
66
+ fromfile=f"{filename} (old)",
67
+ tofile=f"{filename} (new)",
68
+ lineterm=""
69
+ ))
70
+
71
+ # Generate HTML diff
72
+ html_diff = difflib.HtmlDiff()
73
+ html_table = html_diff.make_table(
74
+ old_lines,
75
+ new_lines,
76
+ fromdesc=f"{filename} (old)",
77
+ todesc=f"{filename} (new)"
78
+ )
79
+
80
+ # Calculate statistics
81
+ differ = difflib.SequenceMatcher(None, old_content, new_content)
82
+ similarity_ratio = differ.ratio()
83
+
84
+ # Count changes
85
+ additions = sum(1 for line in unified_diff if line.startswith('+') and not line.startswith('+++'))
86
+ deletions = sum(1 for line in unified_diff if line.startswith('-') and not line.startswith('---'))
87
+ modifications = min(additions, deletions)
88
+ net_additions = additions - modifications
89
+ net_deletions = deletions - modifications
90
+
91
+ return {
92
+ "filename": filename,
93
+ "unified_diff": unified_diff,
94
+ "html_diff": html_table,
95
+ "similarity_ratio": similarity_ratio,
96
+ "statistics": {
97
+ "additions": net_additions,
98
+ "deletions": net_deletions,
99
+ "modifications": modifications,
100
+ "total_changes": additions + deletions,
101
+ "similarity_percentage": round(similarity_ratio * 100, 2)
102
+ },
103
+ "old_line_count": len(old_lines),
104
+ "new_line_count": len(new_lines),
105
+ "line_change": len(new_lines) - len(old_lines)
106
+ }
107
+
108
+ def compare_versions(self,
109
+ old_content_dict: Dict[str, str],
110
+ new_content_dict: Dict[str, str],
111
+ old_version: str,
112
+ new_version: str) -> Dict:
113
+ """
114
+ Compare two complete versions and generate comprehensive change analysis.
115
+
116
+ Args:
117
+ old_content_dict: Content of the old version
118
+ new_content_dict: Content of the new version
119
+ old_version: Name of the old version
120
+ new_version: Name of the new version
121
+
122
+ Returns:
123
+ Complete comparison report
124
+ """
125
+ comparison = {
126
+ "old_version": old_version,
127
+ "new_version": new_version,
128
+ "comparison_timestamp": datetime.now().isoformat(),
129
+ "file_changes": {},
130
+ "summary": {
131
+ "files_added": [],
132
+ "files_removed": [],
133
+ "files_modified": [],
134
+ "total_additions": 0,
135
+ "total_deletions": 0,
136
+ "total_modifications": 0,
137
+ "average_similarity": 0
138
+ }
139
+ }
140
+
141
+ # Find all files in both versions
142
+ old_files = set(old_content_dict.keys())
143
+ new_files = set(new_content_dict.keys())
144
+
145
+ # Categorize changes
146
+ added_files = new_files - old_files
147
+ removed_files = old_files - new_files
148
+ common_files = old_files & new_files
149
+
150
+ comparison["summary"]["files_added"] = list(added_files)
151
+ comparison["summary"]["files_removed"] = list(removed_files)
152
+
153
+ # Analyze common files
154
+ similarities = []
155
+ for filename in common_files:
156
+ old_content = old_content_dict[filename]
157
+ new_content = new_content_dict[filename]
158
+
159
+ diff_result = self.generate_diff(old_content, new_content, filename)
160
+ comparison["file_changes"][filename] = diff_result
161
+
162
+ # Check if file was actually modified
163
+ if diff_result["statistics"]["total_changes"] > 0:
164
+ comparison["summary"]["files_modified"].append(filename)
165
+
166
+ # Accumulate statistics
167
+ stats = diff_result["statistics"]
168
+ comparison["summary"]["total_additions"] += stats["additions"]
169
+ comparison["summary"]["total_deletions"] += stats["deletions"]
170
+ comparison["summary"]["total_modifications"] += stats["modifications"]
171
+ similarities.append(diff_result["similarity_ratio"])
172
+
173
+ # Handle added files
174
+ for filename in added_files:
175
+ new_content = new_content_dict[filename]
176
+ line_count = len(new_content.splitlines())
177
+ comparison["file_changes"][filename] = {
178
+ "filename": filename,
179
+ "status": "added",
180
+ "new_line_count": line_count,
181
+ "statistics": {
182
+ "additions": line_count,
183
+ "deletions": 0,
184
+ "modifications": 0,
185
+ "total_changes": line_count
186
+ }
187
+ }
188
+ comparison["summary"]["total_additions"] += line_count
189
+
190
+ # Handle removed files
191
+ for filename in removed_files:
192
+ old_content = old_content_dict[filename]
193
+ line_count = len(old_content.splitlines())
194
+ comparison["file_changes"][filename] = {
195
+ "filename": filename,
196
+ "status": "removed",
197
+ "old_line_count": line_count,
198
+ "statistics": {
199
+ "additions": 0,
200
+ "deletions": line_count,
201
+ "modifications": 0,
202
+ "total_changes": line_count
203
+ }
204
+ }
205
+ comparison["summary"]["total_deletions"] += line_count
206
+
207
+ # Calculate average similarity
208
+ if similarities:
209
+ comparison["summary"]["average_similarity"] = round(sum(similarities) / len(similarities) * 100, 2)
210
+
211
+ return comparison
212
+
213
+ def save_comparison(self,
214
+ comparison: Dict,
215
+ save_diffs: bool = True) -> Tuple[str, str]:
216
+ """
217
+ Save a comparison report to disk.
218
+
219
+ Args:
220
+ comparison: Comparison result from compare_versions
221
+ save_diffs: Whether to save individual diff files
222
+
223
+ Returns:
224
+ Tuple of (comparison_file_path, summary_file_path)
225
+ """
226
+ old_version = comparison["old_version"]
227
+ new_version = comparison["new_version"]
228
+
229
+ # Save main comparison file
230
+ comparison_filename = f"{old_version}_to_{new_version}.json"
231
+ comparison_path = self.changes_dir / comparison_filename
232
+
233
+ with open(comparison_path, 'w', encoding='utf-8') as f:
234
+ json.dump(comparison, f, indent=2, ensure_ascii=False)
235
+
236
+ # Save individual diff files if requested
237
+ if save_diffs:
238
+ for filename, change_info in comparison["file_changes"].items():
239
+ if "unified_diff" in change_info:
240
+ diff_filename = f"{filename}_{old_version}_{new_version}.diff"
241
+ diff_path = self.diffs_dir / diff_filename
242
+
243
+ with open(diff_path, 'w', encoding='utf-8') as f:
244
+ f.writelines(change_info["unified_diff"])
245
+
246
+ # Create human-readable summary
247
+ summary_filename = f"{old_version}_to_{new_version}_summary.md"
248
+ summary_path = self.changes_dir / summary_filename
249
+ self._create_summary_markdown(comparison, summary_path)
250
+
251
+ return str(comparison_path), str(summary_path)
252
+
253
+ def _create_summary_markdown(self, comparison: Dict, output_path: Path):
254
+ """Create a human-readable markdown summary of changes."""
255
+ old_version = comparison["old_version"]
256
+ new_version = comparison["new_version"]
257
+ summary = comparison["summary"]
258
+
259
+ content = f"""# Change Summary: {old_version} → {new_version}
260
+
261
+ **Generated:** {comparison["comparison_timestamp"]}
262
+
263
+ ## Overview
264
+
265
+ | Metric | Count |
266
+ |--------|-------|
267
+ | Files Added | {len(summary["files_added"])} |
268
+ | Files Removed | {len(summary["files_removed"])} |
269
+ | Files Modified | {len(summary["files_modified"])} |
270
+ | Total Additions | +{summary["total_additions"]} lines |
271
+ | Total Deletions | -{summary["total_deletions"]} lines |
272
+ | Net Change | {summary["total_additions"] - summary["total_deletions"]:+d} lines |
273
+ | Average Similarity | {summary["average_similarity"]}% |
274
+
275
+ """
276
+
277
+ # Files added
278
+ if summary["files_added"]:
279
+ content += "## Files Added\n\n"
280
+ for filename in summary["files_added"]:
281
+ file_info = comparison["file_changes"][filename]
282
+ content += f"- **{filename}** (+{file_info['statistics']['additions']} lines)\n"
283
+ content += "\n"
284
+
285
+ # Files removed
286
+ if summary["files_removed"]:
287
+ content += "## Files Removed\n\n"
288
+ for filename in summary["files_removed"]:
289
+ file_info = comparison["file_changes"][filename]
290
+ content += f"- **{filename}** (-{file_info['statistics']['deletions']} lines)\n"
291
+ content += "\n"
292
+
293
+ # Files modified
294
+ if summary["files_modified"]:
295
+ content += "## Files Modified\n\n"
296
+ for filename in summary["files_modified"]:
297
+ file_info = comparison["file_changes"][filename]
298
+ stats = file_info["statistics"]
299
+ similarity = stats["similarity_percentage"]
300
+
301
+ content += f"### {filename}\n"
302
+ content += f"- **Similarity:** {similarity}%\n"
303
+ content += f"- **Changes:** +{stats['additions']} -{stats['deletions']} lines\n"
304
+ content += f"- **Modifications:** {stats['modifications']} lines\n"
305
+
306
+ # Add sample changes if available
307
+ if "unified_diff" in file_info and file_info["unified_diff"]:
308
+ content += f"- **Sample changes:**\n"
309
+ content += " ```diff\n"
310
+ # Show first few diff lines
311
+ diff_lines = file_info["unified_diff"][:10]
312
+ for line in diff_lines:
313
+ if line.startswith(('+', '-', '@')):
314
+ content += f" {line.rstrip()}\n"
315
+ if len(file_info["unified_diff"]) > 10:
316
+ content += " ...\n"
317
+ content += " ```\n"
318
+ content += "\n"
319
+
320
+ with open(output_path, 'w', encoding='utf-8') as f:
321
+ f.write(content)
322
+
323
+ def analyze_quality_progression(self, version_qualities: List[Tuple[str, int]]) -> Dict:
324
+ """
325
+ Analyze quality progression across multiple versions.
326
+
327
+ Args:
328
+ version_qualities: List of (version_name, quality_score) tuples
329
+
330
+ Returns:
331
+ Quality progression analysis
332
+ """
333
+ if len(version_qualities) < 2:
334
+ return {"error": "Need at least 2 versions for progression analysis"}
335
+
336
+ analysis = {
337
+ "versions_analyzed": len(version_qualities),
338
+ "quality_progression": [],
339
+ "overall_improvement": 0,
340
+ "best_version": None,
341
+ "worst_version": None,
342
+ "improvement_trend": "unknown"
343
+ }
344
+
345
+ # Calculate progression
346
+ for i in range(1, len(version_qualities)):
347
+ prev_version, prev_score = version_qualities[i-1]
348
+ curr_version, curr_score = version_qualities[i]
349
+
350
+ improvement = curr_score - prev_score
351
+ analysis["quality_progression"].append({
352
+ "from": prev_version,
353
+ "to": curr_version,
354
+ "improvement": improvement,
355
+ "from_score": prev_score,
356
+ "to_score": curr_score
357
+ })
358
+
359
+ # Overall metrics
360
+ first_score = version_qualities[0][1]
361
+ last_score = version_qualities[-1][1]
362
+ analysis["overall_improvement"] = last_score - first_score
363
+
364
+ # Best and worst versions
365
+ best_score = max(version_qualities, key=lambda x: x[1])
366
+ worst_score = min(version_qualities, key=lambda x: x[1])
367
+ analysis["best_version"] = {"name": best_score[0], "score": best_score[1]}
368
+ analysis["worst_version"] = {"name": worst_score[0], "score": worst_score[1]}
369
+
370
+ # Determine trend
371
+ improvements = [step["improvement"] for step in analysis["quality_progression"]]
372
+ positive_steps = sum(1 for imp in improvements if imp > 0)
373
+ negative_steps = sum(1 for imp in improvements if imp < 0)
374
+
375
+ if positive_steps > negative_steps:
376
+ analysis["improvement_trend"] = "improving"
377
+ elif negative_steps > positive_steps:
378
+ analysis["improvement_trend"] = "declining"
379
+ else:
380
+ analysis["improvement_trend"] = "stable"
381
+
382
+ return analysis
383
+
384
+ def create_change_report(self,
385
+ old_version: str,
386
+ new_version: str,
387
+ old_content: Dict[str, str],
388
+ new_content: Dict[str, str],
389
+ old_quality: Optional[int] = None,
390
+ new_quality: Optional[int] = None) -> str:
391
+ """
392
+ Create a comprehensive change report.
393
+
394
+ Args:
395
+ old_version: Name of the old version
396
+ new_version: Name of the new version
397
+ old_content: Content of the old version
398
+ new_content: Content of the new version
399
+ old_quality: Quality score of old version
400
+ new_quality: Quality score of new version
401
+
402
+ Returns:
403
+ Path to the generated report
404
+ """
405
+ # Generate comparison
406
+ comparison = self.compare_versions(old_content, new_content, old_version, new_version)
407
+
408
+ # Add quality information if provided
409
+ if old_quality is not None and new_quality is not None:
410
+ comparison["quality_analysis"] = {
411
+ "old_quality_score": old_quality,
412
+ "new_quality_score": new_quality,
413
+ "quality_improvement": new_quality - old_quality
414
+ }
415
+
416
+ # Save comparison
417
+ comparison_path, summary_path = self.save_comparison(comparison)
418
+
419
+ return summary_path