@laitszkin/apollo-toolkit 3.13.2 → 3.14.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.
- package/AGENTS.md +7 -7
- package/CHANGELOG.md +27 -0
- package/CLAUDE.md +8 -8
- package/analyse-app-logs/SKILL.md +3 -3
- package/bin/apollo-toolkit.ts +7 -0
- package/codex/codex-memory-manager/SKILL.md +2 -2
- package/codex/learn-skill-from-conversations/SKILL.md +3 -3
- package/dist/bin/apollo-toolkit.d.ts +2 -0
- package/dist/bin/apollo-toolkit.js +7 -0
- package/dist/lib/cli.d.ts +41 -0
- package/dist/lib/cli.js +655 -0
- package/dist/lib/installer.d.ts +59 -0
- package/dist/lib/installer.js +404 -0
- package/dist/lib/tool-runner.d.ts +19 -0
- package/dist/lib/tool-runner.js +536 -0
- package/dist/lib/tools/architecture.d.ts +2 -0
- package/dist/lib/tools/architecture.js +34 -0
- package/dist/lib/tools/create-specs.d.ts +2 -0
- package/dist/lib/tools/create-specs.js +175 -0
- package/dist/lib/tools/docs-to-voice.d.ts +2 -0
- package/dist/lib/tools/docs-to-voice.js +705 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.d.ts +2 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.js +312 -0
- package/dist/lib/tools/extract-conversations.d.ts +2 -0
- package/dist/lib/tools/extract-conversations.js +105 -0
- package/dist/lib/tools/extract-pdf-text.d.ts +2 -0
- package/dist/lib/tools/extract-pdf-text.js +92 -0
- package/dist/lib/tools/filter-logs.d.ts +2 -0
- package/dist/lib/tools/filter-logs.js +94 -0
- package/dist/lib/tools/find-github-issues.d.ts +2 -0
- package/dist/lib/tools/find-github-issues.js +176 -0
- package/dist/lib/tools/generate-storyboard-images.d.ts +2 -0
- package/dist/lib/tools/generate-storyboard-images.js +419 -0
- package/dist/lib/tools/log-cli-utils.d.ts +35 -0
- package/dist/lib/tools/log-cli-utils.js +233 -0
- package/dist/lib/tools/open-github-issue.d.ts +2 -0
- package/dist/lib/tools/open-github-issue.js +750 -0
- package/dist/lib/tools/read-github-issue.d.ts +2 -0
- package/dist/lib/tools/read-github-issue.js +134 -0
- package/dist/lib/tools/render-error-book.d.ts +2 -0
- package/dist/lib/tools/render-error-book.js +265 -0
- package/dist/lib/tools/render-katex.d.ts +2 -0
- package/dist/lib/tools/render-katex.js +294 -0
- package/dist/lib/tools/review-threads.d.ts +2 -0
- package/dist/lib/tools/review-threads.js +491 -0
- package/dist/lib/tools/search-logs.d.ts +2 -0
- package/dist/lib/tools/search-logs.js +164 -0
- package/dist/lib/tools/sync-memory-index.d.ts +2 -0
- package/dist/lib/tools/sync-memory-index.js +113 -0
- package/dist/lib/tools/validate-openai-agent-config.d.ts +2 -0
- package/dist/lib/tools/validate-openai-agent-config.js +184 -0
- package/dist/lib/tools/validate-skill-frontmatter.d.ts +2 -0
- package/dist/lib/tools/validate-skill-frontmatter.js +118 -0
- package/dist/lib/types.d.ts +82 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/updater.d.ts +34 -0
- package/dist/lib/updater.js +112 -0
- package/dist/lib/utils/format.d.ts +2 -0
- package/dist/lib/utils/format.js +6 -0
- package/dist/lib/utils/terminal.d.ts +12 -0
- package/dist/lib/utils/terminal.js +26 -0
- package/docs-to-voice/SKILL.md +0 -1
- package/generate-spec/SKILL.md +1 -1
- package/katex/SKILL.md +1 -2
- package/lib/cli.ts +780 -0
- package/lib/installer.ts +466 -0
- package/lib/tool-runner.ts +561 -0
- package/lib/tools/architecture.ts +34 -0
- package/lib/tools/create-specs.ts +204 -0
- package/lib/tools/docs-to-voice.ts +799 -0
- package/lib/tools/enforce-video-aspect-ratio.ts +368 -0
- package/lib/tools/extract-conversations.ts +114 -0
- package/lib/tools/extract-pdf-text.ts +99 -0
- package/lib/tools/filter-logs.ts +118 -0
- package/lib/tools/find-github-issues.ts +211 -0
- package/lib/tools/generate-storyboard-images.ts +455 -0
- package/lib/tools/log-cli-utils.ts +262 -0
- package/lib/tools/open-github-issue.ts +930 -0
- package/lib/tools/read-github-issue.ts +179 -0
- package/lib/tools/render-error-book.ts +300 -0
- package/lib/tools/render-katex.ts +325 -0
- package/lib/tools/review-threads.ts +590 -0
- package/lib/tools/search-logs.ts +200 -0
- package/lib/tools/sync-memory-index.ts +114 -0
- package/lib/tools/validate-openai-agent-config.ts +209 -0
- package/lib/tools/validate-skill-frontmatter.ts +124 -0
- package/lib/types.ts +90 -0
- package/lib/updater.ts +165 -0
- package/lib/utils/format.ts +7 -0
- package/lib/utils/terminal.ts +22 -0
- package/open-github-issue/SKILL.md +2 -2
- package/optimise-skill/SKILL.md +1 -1
- package/package.json +13 -4
- package/resources/project-architecture/assets/architecture.css +764 -0
- package/resources/project-architecture/assets/viewer.client.js +144 -0
- package/resources/project-architecture/index.html +42 -0
- package/review-spec-related-changes/SKILL.md +1 -1
- package/solve-issues-found-during-review/SKILL.md +2 -1
- package/tsconfig.json +28 -0
- package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/filter_logs_by_time.py +0 -64
- package/analyse-app-logs/scripts/log_cli_utils.py +0 -112
- package/analyse-app-logs/scripts/search_logs.py +0 -137
- package/analyse-app-logs/tests/test_filter_logs_by_time.py +0 -95
- package/analyse-app-logs/tests/test_search_logs.py +0 -100
- package/codex/codex-memory-manager/scripts/extract_recent_conversations.py +0 -369
- package/codex/codex-memory-manager/scripts/sync_memory_index.py +0 -130
- package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +0 -177
- package/codex/codex-memory-manager/tests/test_memory_template.py +0 -37
- package/codex/codex-memory-manager/tests/test_sync_memory_index.py +0 -84
- package/codex/learn-skill-from-conversations/scripts/extract_recent_conversations.py +0 -369
- package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +0 -177
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/docs-to-voice/scripts/docs_to_voice.py +0 -1385
- package/docs-to-voice/scripts/docs_to_voice.sh +0 -11
- package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +0 -210
- package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +0 -115
- package/docs-to-voice/tests/test_docs_to_voice_settings.py +0 -43
- package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +0 -51
- package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +0 -57
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/generate-spec/scripts/create-specs +0 -215
- package/generate-spec/tests/test_create_specs.py +0 -200
- package/init-project-html/scripts/architecture-bootstrap-render.js +0 -16
- package/init-project-html/scripts/architecture.js +0 -296
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/katex/scripts/render_katex.py +0 -247
- package/katex/scripts/render_katex.sh +0 -11
- package/katex/tests/test_render_katex.py +0 -174
- package/learning-error-book/scripts/render_error_book_json_to_pdf.py +0 -590
- package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +0 -134
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/open-github-issue/scripts/open_github_issue.py +0 -705
- package/open-github-issue/tests/test_open_github_issue.py +0 -381
- package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +0 -763
- package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +0 -177
- package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/find_issues.py +0 -148
- package/read-github-issue/scripts/read_issue.py +0 -108
- package/read-github-issue/tests/test_find_issues.py +0 -127
- package/read-github-issue/tests/test_read_issue.py +0 -109
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/resolve-review-comments/scripts/review_threads.py +0 -425
- package/resolve-review-comments/tests/test_review_threads.py +0 -74
- package/scripts/validate_openai_agent_config.py +0 -209
- package/scripts/validate_skill_frontmatter.py +0 -131
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
- package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +0 -350
- package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +0 -194
- package/weekly-financial-event-report/scripts/extract_pdf_text_pdfkit.swift +0 -99
- package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +0 -64
|
@@ -1,590 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import json
|
|
6
|
-
import os
|
|
7
|
-
from typing import Any, List, Optional, Sequence, Tuple
|
|
8
|
-
from xml.sax.saxutils import escape
|
|
9
|
-
|
|
10
|
-
from reportlab.lib import colors
|
|
11
|
-
from reportlab.lib.pagesizes import A4, letter
|
|
12
|
-
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
|
13
|
-
from reportlab.lib.units import mm
|
|
14
|
-
from reportlab.pdfbase import pdfmetrics
|
|
15
|
-
from reportlab.pdfbase.ttfonts import TTFont
|
|
16
|
-
from reportlab.platypus import (
|
|
17
|
-
HRFlowable,
|
|
18
|
-
PageBreak,
|
|
19
|
-
Paragraph,
|
|
20
|
-
SimpleDocTemplate,
|
|
21
|
-
Spacer,
|
|
22
|
-
Table,
|
|
23
|
-
TableStyle,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
THEME = {
|
|
28
|
-
"ink": colors.HexColor("#1F2937"),
|
|
29
|
-
"muted": colors.HexColor("#6B7280"),
|
|
30
|
-
"line": colors.HexColor("#D1D5DB"),
|
|
31
|
-
"panel": colors.HexColor("#F8FAFC"),
|
|
32
|
-
"panel_alt": colors.HexColor("#EEF2FF"),
|
|
33
|
-
"accent": colors.HexColor("#0F766E"),
|
|
34
|
-
"warning": colors.HexColor("#9A3412"),
|
|
35
|
-
"warning_soft": colors.HexColor("#FFEDD5"),
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def _read_json(path: str) -> Any:
|
|
40
|
-
with open(path, "r", encoding="utf-8") as f:
|
|
41
|
-
return json.load(f)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _ensure_parent_dir(path: str) -> None:
|
|
45
|
-
parent = os.path.dirname(os.path.abspath(path))
|
|
46
|
-
if parent:
|
|
47
|
-
os.makedirs(parent, exist_ok=True)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _detect_cjk_font_path(user_font_path: Optional[str]) -> Tuple[str, int]:
|
|
51
|
-
if user_font_path:
|
|
52
|
-
return user_font_path, 0
|
|
53
|
-
|
|
54
|
-
candidates = [
|
|
55
|
-
"/System/Library/Fonts/PingFang.ttc",
|
|
56
|
-
"/System/Library/Fonts/STHeiti Light.ttc",
|
|
57
|
-
"/System/Library/Fonts/STHeiti Medium.ttc",
|
|
58
|
-
"/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc",
|
|
59
|
-
"/System/Library/Fonts/ヒラギノ角ゴシック W4.ttc",
|
|
60
|
-
"/System/Library/Fonts/ヒラギノ角ゴシック W5.ttc",
|
|
61
|
-
"/System/Library/Fonts/ヒラギノ明朝 ProN.ttc",
|
|
62
|
-
"/Library/Fonts/Arial Unicode.ttf",
|
|
63
|
-
]
|
|
64
|
-
for path in candidates:
|
|
65
|
-
if os.path.exists(path):
|
|
66
|
-
return path, 0
|
|
67
|
-
raise SystemExit("No CJK font found. Re-run with --font-path pointing to a .ttf/.otf/.ttc font file.")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def _register_font(font_path: str, subfont_index: int) -> str:
|
|
71
|
-
font_name = "ErrorBookBodyFont"
|
|
72
|
-
if font_name not in pdfmetrics.getRegisteredFontNames():
|
|
73
|
-
pdfmetrics.registerFont(TTFont(font_name, font_path, subfontIndex=subfont_index))
|
|
74
|
-
return font_name
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _safe_text(value: Any, default: str = "-") -> str:
|
|
78
|
-
if value is None:
|
|
79
|
-
return default
|
|
80
|
-
if isinstance(value, str):
|
|
81
|
-
stripped = value.strip()
|
|
82
|
-
return stripped if stripped else default
|
|
83
|
-
if isinstance(value, (int, float)):
|
|
84
|
-
return str(value)
|
|
85
|
-
if isinstance(value, list):
|
|
86
|
-
parts = [_safe_text(item, default="") for item in value]
|
|
87
|
-
joined = ", ".join(part for part in parts if part)
|
|
88
|
-
return joined if joined else default
|
|
89
|
-
return str(value)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def _markup(value: Any, default: str = "-") -> str:
|
|
93
|
-
return escape(_safe_text(value, default=default)).replace("\n", "<br/>")
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _paragraph(text: Any, style: ParagraphStyle, default: str = "-") -> Paragraph:
|
|
97
|
-
return Paragraph(_markup(text, default=default), style)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _bullet_lines(items: Sequence[Any], styles: dict, empty_text: str = "-") -> List[Any]:
|
|
101
|
-
values = list(items or [])
|
|
102
|
-
if not values:
|
|
103
|
-
return [_paragraph(empty_text, styles["body"])]
|
|
104
|
-
return [Paragraph(f"- {_markup(item)}", styles["bullet"]) for item in values]
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _bullet_paragraph(items: Sequence[Any], style: ParagraphStyle, empty_text: str = "-") -> Paragraph:
|
|
108
|
-
values = list(items or [])
|
|
109
|
-
if not values:
|
|
110
|
-
return Paragraph(_markup(empty_text), style)
|
|
111
|
-
lines = "<br/>".join(f"- {_markup(item)}" for item in values)
|
|
112
|
-
return Paragraph(lines, style)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _table(data: List[List[Any]], col_widths: Sequence[float], table_style: TableStyle) -> Table:
|
|
116
|
-
table = Table(data, colWidths=list(col_widths), repeatRows=1)
|
|
117
|
-
table.setStyle(table_style)
|
|
118
|
-
return table
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _build_styles(font_name: str, font_size: int) -> dict:
|
|
122
|
-
styles = getSampleStyleSheet()
|
|
123
|
-
return {
|
|
124
|
-
"title": ParagraphStyle(
|
|
125
|
-
"ErrorBookTitle",
|
|
126
|
-
parent=styles["Title"],
|
|
127
|
-
fontName=font_name,
|
|
128
|
-
fontSize=24,
|
|
129
|
-
leading=30,
|
|
130
|
-
textColor=THEME["ink"],
|
|
131
|
-
spaceAfter=10,
|
|
132
|
-
),
|
|
133
|
-
"subtitle": ParagraphStyle(
|
|
134
|
-
"ErrorBookSubtitle",
|
|
135
|
-
parent=styles["BodyText"],
|
|
136
|
-
fontName=font_name,
|
|
137
|
-
fontSize=11,
|
|
138
|
-
leading=15,
|
|
139
|
-
textColor=THEME["muted"],
|
|
140
|
-
spaceAfter=8,
|
|
141
|
-
),
|
|
142
|
-
"section": ParagraphStyle(
|
|
143
|
-
"ErrorBookSection",
|
|
144
|
-
parent=styles["Heading1"],
|
|
145
|
-
fontName=font_name,
|
|
146
|
-
fontSize=16,
|
|
147
|
-
leading=20,
|
|
148
|
-
textColor=THEME["accent"],
|
|
149
|
-
spaceBefore=8,
|
|
150
|
-
spaceAfter=6,
|
|
151
|
-
),
|
|
152
|
-
"question": ParagraphStyle(
|
|
153
|
-
"ErrorBookQuestion",
|
|
154
|
-
parent=styles["Heading2"],
|
|
155
|
-
fontName=font_name,
|
|
156
|
-
fontSize=14,
|
|
157
|
-
leading=18,
|
|
158
|
-
textColor=THEME["ink"],
|
|
159
|
-
spaceAfter=4,
|
|
160
|
-
),
|
|
161
|
-
"subhead": ParagraphStyle(
|
|
162
|
-
"ErrorBookSubhead",
|
|
163
|
-
parent=styles["Heading3"],
|
|
164
|
-
fontName=font_name,
|
|
165
|
-
fontSize=11,
|
|
166
|
-
leading=14,
|
|
167
|
-
textColor=THEME["ink"],
|
|
168
|
-
spaceBefore=4,
|
|
169
|
-
spaceAfter=2,
|
|
170
|
-
),
|
|
171
|
-
"body": ParagraphStyle(
|
|
172
|
-
"ErrorBookBody",
|
|
173
|
-
parent=styles["BodyText"],
|
|
174
|
-
fontName=font_name,
|
|
175
|
-
fontSize=font_size,
|
|
176
|
-
leading=int(font_size * 1.55),
|
|
177
|
-
textColor=THEME["ink"],
|
|
178
|
-
spaceAfter=4,
|
|
179
|
-
),
|
|
180
|
-
"bullet": ParagraphStyle(
|
|
181
|
-
"ErrorBookBullet",
|
|
182
|
-
parent=styles["BodyText"],
|
|
183
|
-
fontName=font_name,
|
|
184
|
-
fontSize=font_size,
|
|
185
|
-
leading=int(font_size * 1.5),
|
|
186
|
-
textColor=THEME["ink"],
|
|
187
|
-
leftIndent=10,
|
|
188
|
-
spaceAfter=3,
|
|
189
|
-
),
|
|
190
|
-
"meta": ParagraphStyle(
|
|
191
|
-
"ErrorBookMeta",
|
|
192
|
-
parent=styles["BodyText"],
|
|
193
|
-
fontName=font_name,
|
|
194
|
-
fontSize=10,
|
|
195
|
-
leading=13,
|
|
196
|
-
textColor=THEME["muted"],
|
|
197
|
-
),
|
|
198
|
-
"label": ParagraphStyle(
|
|
199
|
-
"ErrorBookLabel",
|
|
200
|
-
parent=styles["BodyText"],
|
|
201
|
-
fontName=font_name,
|
|
202
|
-
fontSize=10,
|
|
203
|
-
leading=13,
|
|
204
|
-
textColor=THEME["accent"],
|
|
205
|
-
),
|
|
206
|
-
"table": ParagraphStyle(
|
|
207
|
-
"ErrorBookTable",
|
|
208
|
-
parent=styles["BodyText"],
|
|
209
|
-
fontName=font_name,
|
|
210
|
-
fontSize=9,
|
|
211
|
-
leading=12,
|
|
212
|
-
textColor=THEME["ink"],
|
|
213
|
-
),
|
|
214
|
-
"table_head": ParagraphStyle(
|
|
215
|
-
"ErrorBookTableHead",
|
|
216
|
-
parent=styles["BodyText"],
|
|
217
|
-
fontName=font_name,
|
|
218
|
-
fontSize=9,
|
|
219
|
-
leading=12,
|
|
220
|
-
textColor=colors.white,
|
|
221
|
-
),
|
|
222
|
-
"callout": ParagraphStyle(
|
|
223
|
-
"ErrorBookCallout",
|
|
224
|
-
parent=styles["BodyText"],
|
|
225
|
-
fontName=font_name,
|
|
226
|
-
fontSize=10,
|
|
227
|
-
leading=14,
|
|
228
|
-
textColor=THEME["warning"],
|
|
229
|
-
),
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _header_footer(canvas, doc) -> None:
|
|
234
|
-
canvas.saveState()
|
|
235
|
-
canvas.setFont("Helvetica", 9)
|
|
236
|
-
canvas.setFillColor(THEME["muted"])
|
|
237
|
-
canvas.drawString(doc.leftMargin, doc.height + doc.topMargin + 8, doc.title)
|
|
238
|
-
canvas.drawRightString(doc.pagesize[0] - doc.rightMargin, 12, f"Page {canvas.getPageNumber()}")
|
|
239
|
-
canvas.restoreState()
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def _section_heading(text: str, styles: dict) -> List[Any]:
|
|
243
|
-
return [Spacer(1, 6), Paragraph(_markup(text), styles["section"]), HRFlowable(color=THEME["line"], thickness=1, width="100%"), Spacer(1, 6)]
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def _coverage_story(data: dict, styles: dict, width: float) -> List[Any]:
|
|
247
|
-
rows: List[List[Any]] = [[
|
|
248
|
-
Paragraph("Source", styles["table_head"]),
|
|
249
|
-
Paragraph("Questions", styles["table_head"]),
|
|
250
|
-
Paragraph("Notes", styles["table_head"]),
|
|
251
|
-
]]
|
|
252
|
-
for item in data.get("coverage_scope", []):
|
|
253
|
-
rows.append([
|
|
254
|
-
_paragraph(item.get("source_path"), styles["table"]),
|
|
255
|
-
_paragraph(item.get("included_questions", []), styles["table"]),
|
|
256
|
-
_paragraph(item.get("notes"), styles["table"]),
|
|
257
|
-
])
|
|
258
|
-
if len(rows) == 1:
|
|
259
|
-
rows.append([_paragraph("-", styles["table"]), _paragraph("-", styles["table"]), _paragraph("-", styles["table"])])
|
|
260
|
-
|
|
261
|
-
style = TableStyle([
|
|
262
|
-
("BACKGROUND", (0, 0), (-1, 0), THEME["accent"]),
|
|
263
|
-
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
|
|
264
|
-
("FONTNAME", (0, 0), (-1, -1), styles["table"].fontName),
|
|
265
|
-
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
|
266
|
-
("GRID", (0, 0), (-1, -1), 0.6, THEME["line"]),
|
|
267
|
-
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, THEME["panel"]]),
|
|
268
|
-
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
|
269
|
-
("RIGHTPADDING", (0, 0), (-1, -1), 8),
|
|
270
|
-
("TOPPADDING", (0, 0), (-1, -1), 6),
|
|
271
|
-
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
|
|
272
|
-
])
|
|
273
|
-
return _section_heading("Coverage Scope", styles) + [_table(rows, [width * 0.36, width * 0.24, width * 0.40], style)]
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def _overview_story(data: dict, styles: dict) -> List[Any]:
|
|
277
|
-
story = _section_heading("Common Mistake Types Overview", styles)
|
|
278
|
-
entries = data.get("mistake_overview", []) or []
|
|
279
|
-
if not entries:
|
|
280
|
-
return story + [_paragraph("No mistake overview provided.", styles["body"])]
|
|
281
|
-
|
|
282
|
-
for entry in entries:
|
|
283
|
-
box = [
|
|
284
|
-
Paragraph(_markup(entry.get("type")), styles["subhead"]),
|
|
285
|
-
_paragraph(entry.get("summary"), styles["body"]),
|
|
286
|
-
_paragraph(f"Representative questions: {_safe_text(entry.get('representative_questions', []))}", styles["meta"]),
|
|
287
|
-
]
|
|
288
|
-
story.append(
|
|
289
|
-
Table(
|
|
290
|
-
[[box]],
|
|
291
|
-
colWidths=[None],
|
|
292
|
-
style=TableStyle([
|
|
293
|
-
("BACKGROUND", (0, 0), (-1, -1), THEME["panel"]),
|
|
294
|
-
("BOX", (0, 0), (-1, -1), 0.8, THEME["line"]),
|
|
295
|
-
("LEFTPADDING", (0, 0), (-1, -1), 10),
|
|
296
|
-
("RIGHTPADDING", (0, 0), (-1, -1), 10),
|
|
297
|
-
("TOPPADDING", (0, 0), (-1, -1), 8),
|
|
298
|
-
("BOTTOMPADDING", (0, 0), (-1, -1), 8),
|
|
299
|
-
]),
|
|
300
|
-
)
|
|
301
|
-
)
|
|
302
|
-
story.append(Spacer(1, 6))
|
|
303
|
-
return story
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def _concept_story(data: dict, styles: dict, width: float) -> List[Any]:
|
|
307
|
-
story = _section_heading("Conceptual Mistake Highlights", styles)
|
|
308
|
-
concepts = data.get("concept_highlights", []) or []
|
|
309
|
-
if not concepts:
|
|
310
|
-
return story + [_paragraph("No concept highlights provided.", styles["body"])]
|
|
311
|
-
|
|
312
|
-
for concept in concepts:
|
|
313
|
-
rows = [
|
|
314
|
-
[_paragraph("Definition", styles["label"]), _paragraph(concept.get("definition"), styles["body"])],
|
|
315
|
-
[_paragraph("Common misjudgment", styles["label"]), _paragraph(concept.get("common_misjudgment"), styles["body"])],
|
|
316
|
-
[_paragraph("Checklist", styles["label"]), _bullet_paragraph(concept.get("checklist", []), styles["body"])],
|
|
317
|
-
]
|
|
318
|
-
story.append(Paragraph(_markup(concept.get("name"), default="Unnamed concept"), styles["question"]))
|
|
319
|
-
story.append(
|
|
320
|
-
Table(
|
|
321
|
-
rows,
|
|
322
|
-
colWidths=[width * 0.22, width * 0.78],
|
|
323
|
-
style=TableStyle([
|
|
324
|
-
("BACKGROUND", (0, 0), (0, -1), THEME["panel_alt"]),
|
|
325
|
-
("BACKGROUND", (1, 0), (1, -1), colors.white),
|
|
326
|
-
("BOX", (0, 0), (-1, -1), 0.8, THEME["line"]),
|
|
327
|
-
("INNERGRID", (0, 0), (-1, -1), 0.6, THEME["line"]),
|
|
328
|
-
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
|
329
|
-
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
|
330
|
-
("RIGHTPADDING", (0, 0), (-1, -1), 8),
|
|
331
|
-
("TOPPADDING", (0, 0), (-1, -1), 6),
|
|
332
|
-
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
|
|
333
|
-
]),
|
|
334
|
-
)
|
|
335
|
-
)
|
|
336
|
-
story.append(Spacer(1, 8))
|
|
337
|
-
return story
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
def _question_meta(question: dict, styles: dict, width: float) -> Table:
|
|
341
|
-
rows = [
|
|
342
|
-
[_paragraph("Source", styles["label"]), _paragraph(question.get("source_path"), styles["body"])],
|
|
343
|
-
[_paragraph("Locator", styles["label"]), _paragraph(question.get("page_or_locator"), styles["body"])],
|
|
344
|
-
[_paragraph("User answer", styles["label"]), _paragraph(question.get("user_answer"), styles["body"])],
|
|
345
|
-
[_paragraph("Correct answer", styles["label"]), _paragraph(question.get("correct_answer"), styles["body"])],
|
|
346
|
-
[_paragraph("Mistake type", styles["label"]), _paragraph(question.get("mistake_type"), styles["body"])],
|
|
347
|
-
[_paragraph("Concepts", styles["label"]), _paragraph(question.get("concepts", []), styles["body"])],
|
|
348
|
-
]
|
|
349
|
-
return Table(
|
|
350
|
-
rows,
|
|
351
|
-
colWidths=[width * 0.18, width * 0.82],
|
|
352
|
-
style=TableStyle([
|
|
353
|
-
("BACKGROUND", (0, 0), (0, -1), THEME["panel"]),
|
|
354
|
-
("BACKGROUND", (1, 0), (1, -1), colors.white),
|
|
355
|
-
("BOX", (0, 0), (-1, -1), 0.8, THEME["line"]),
|
|
356
|
-
("INNERGRID", (0, 0), (-1, -1), 0.6, THEME["line"]),
|
|
357
|
-
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
|
358
|
-
("LEFTPADDING", (0, 0), (-1, -1), 8),
|
|
359
|
-
("RIGHTPADDING", (0, 0), (-1, -1), 8),
|
|
360
|
-
("TOPPADDING", (0, 0), (-1, -1), 6),
|
|
361
|
-
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
|
|
362
|
-
]),
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
def _steps_block(title: str, steps: Sequence[Any], styles: dict) -> List[Any]:
|
|
367
|
-
return [Paragraph(_markup(title), styles["subhead"])] + _bullet_lines(steps or [], styles, empty_text="No steps provided.")
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
def _mc_options_table(question: dict, styles: dict, width: float) -> Table:
|
|
371
|
-
rows: List[List[Any]] = [[
|
|
372
|
-
Paragraph("Option", styles["table_head"]),
|
|
373
|
-
Paragraph("Text", styles["table_head"]),
|
|
374
|
-
Paragraph("Verdict", styles["table_head"]),
|
|
375
|
-
Paragraph("Reason", styles["table_head"]),
|
|
376
|
-
]]
|
|
377
|
-
for option in question.get("options", []) or []:
|
|
378
|
-
rows.append([
|
|
379
|
-
_paragraph(option.get("label"), styles["table"]),
|
|
380
|
-
_paragraph(option.get("text"), styles["table"]),
|
|
381
|
-
_paragraph(option.get("verdict"), styles["table"]),
|
|
382
|
-
_paragraph(option.get("reason"), styles["table"]),
|
|
383
|
-
])
|
|
384
|
-
if len(rows) == 1:
|
|
385
|
-
rows.append([_paragraph("-", styles["table"]), _paragraph("-", styles["table"]), _paragraph("-", styles["table"]), _paragraph("-", styles["table"])])
|
|
386
|
-
return _table(
|
|
387
|
-
rows,
|
|
388
|
-
[width * 0.10, width * 0.28, width * 0.12, width * 0.50],
|
|
389
|
-
TableStyle([
|
|
390
|
-
("BACKGROUND", (0, 0), (-1, 0), THEME["accent"]),
|
|
391
|
-
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
|
|
392
|
-
("FONTNAME", (0, 0), (-1, -1), styles["table"].fontName),
|
|
393
|
-
("GRID", (0, 0), (-1, -1), 0.6, THEME["line"]),
|
|
394
|
-
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, THEME["panel"]]),
|
|
395
|
-
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
|
396
|
-
("LEFTPADDING", (0, 0), (-1, -1), 6),
|
|
397
|
-
("RIGHTPADDING", (0, 0), (-1, -1), 6),
|
|
398
|
-
("TOPPADDING", (0, 0), (-1, -1), 5),
|
|
399
|
-
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
|
|
400
|
-
]),
|
|
401
|
-
)
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
def _long_key_concepts_table(question: dict, styles: dict, width: float) -> Table:
|
|
405
|
-
rows: List[List[Any]] = [[
|
|
406
|
-
Paragraph("Concept", styles["table_head"]),
|
|
407
|
-
Paragraph("Why it matters", styles["table_head"]),
|
|
408
|
-
]]
|
|
409
|
-
for item in question.get("key_concepts", []) or []:
|
|
410
|
-
rows.append([
|
|
411
|
-
_paragraph(item.get("name"), styles["table"]),
|
|
412
|
-
_paragraph(item.get("why_it_matters"), styles["table"]),
|
|
413
|
-
])
|
|
414
|
-
if len(rows) == 1:
|
|
415
|
-
rows.append([_paragraph("-", styles["table"]), _paragraph("-", styles["table"])])
|
|
416
|
-
return _table(
|
|
417
|
-
rows,
|
|
418
|
-
[width * 0.24, width * 0.76],
|
|
419
|
-
TableStyle([
|
|
420
|
-
("BACKGROUND", (0, 0), (-1, 0), THEME["accent"]),
|
|
421
|
-
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
|
|
422
|
-
("FONTNAME", (0, 0), (-1, -1), styles["table"].fontName),
|
|
423
|
-
("GRID", (0, 0), (-1, -1), 0.6, THEME["line"]),
|
|
424
|
-
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, THEME["panel"]]),
|
|
425
|
-
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
|
426
|
-
("LEFTPADDING", (0, 0), (-1, -1), 6),
|
|
427
|
-
("RIGHTPADDING", (0, 0), (-1, -1), 6),
|
|
428
|
-
("TOPPADDING", (0, 0), (-1, -1), 5),
|
|
429
|
-
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
|
|
430
|
-
]),
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
def _step_comparison_table(question: dict, styles: dict, width: float) -> Table:
|
|
435
|
-
rows: List[List[Any]] = [[
|
|
436
|
-
Paragraph("Step", styles["table_head"]),
|
|
437
|
-
Paragraph("Expected", styles["table_head"]),
|
|
438
|
-
Paragraph("User", styles["table_head"]),
|
|
439
|
-
Paragraph("Gap", styles["table_head"]),
|
|
440
|
-
Paragraph("Fix", styles["table_head"]),
|
|
441
|
-
]]
|
|
442
|
-
for item in question.get("step_comparison", []) or []:
|
|
443
|
-
rows.append([
|
|
444
|
-
_paragraph(item.get("step_no"), styles["table"]),
|
|
445
|
-
_paragraph(item.get("expected_step"), styles["table"]),
|
|
446
|
-
_paragraph(item.get("user_step"), styles["table"]),
|
|
447
|
-
_paragraph(item.get("gap"), styles["table"]),
|
|
448
|
-
_paragraph(item.get("fix"), styles["table"]),
|
|
449
|
-
])
|
|
450
|
-
if len(rows) == 1:
|
|
451
|
-
rows.append([
|
|
452
|
-
_paragraph("-", styles["table"]),
|
|
453
|
-
_paragraph("-", styles["table"]),
|
|
454
|
-
_paragraph("-", styles["table"]),
|
|
455
|
-
_paragraph("-", styles["table"]),
|
|
456
|
-
_paragraph("-", styles["table"]),
|
|
457
|
-
])
|
|
458
|
-
return _table(
|
|
459
|
-
rows,
|
|
460
|
-
[width * 0.08, width * 0.24, width * 0.22, width * 0.20, width * 0.26],
|
|
461
|
-
TableStyle([
|
|
462
|
-
("BACKGROUND", (0, 0), (-1, 0), THEME["warning"]),
|
|
463
|
-
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
|
|
464
|
-
("FONTNAME", (0, 0), (-1, -1), styles["table"].fontName),
|
|
465
|
-
("GRID", (0, 0), (-1, -1), 0.6, THEME["line"]),
|
|
466
|
-
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, THEME["warning_soft"]]),
|
|
467
|
-
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
|
468
|
-
("LEFTPADDING", (0, 0), (-1, -1), 6),
|
|
469
|
-
("RIGHTPADDING", (0, 0), (-1, -1), 6),
|
|
470
|
-
("TOPPADDING", (0, 0), (-1, -1), 5),
|
|
471
|
-
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
|
|
472
|
-
]),
|
|
473
|
-
)
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
def _mc_question_story(question: dict, styles: dict, width: float) -> List[Any]:
|
|
477
|
-
story: List[Any] = [
|
|
478
|
-
Paragraph(_markup(question.get("question_id"), default="Unnamed question"), styles["question"]),
|
|
479
|
-
_question_meta(question, styles, width),
|
|
480
|
-
Spacer(1, 6),
|
|
481
|
-
Paragraph("Stem", styles["subhead"]),
|
|
482
|
-
_paragraph(question.get("stem"), styles["body"]),
|
|
483
|
-
Paragraph("Why it was wrong", styles["subhead"]),
|
|
484
|
-
_paragraph(question.get("why_wrong"), styles["body"]),
|
|
485
|
-
]
|
|
486
|
-
story.extend(_steps_block("Correct solution", question.get("correct_solution_steps", []), styles))
|
|
487
|
-
story.extend([Spacer(1, 4), Paragraph("Option-by-option reasoning", styles["subhead"]), _mc_options_table(question, styles, width), Spacer(1, 10)])
|
|
488
|
-
return story
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
def _long_question_story(question: dict, styles: dict, width: float) -> List[Any]:
|
|
492
|
-
first_wrong = _safe_text(question.get("first_incorrect_step"))
|
|
493
|
-
story: List[Any] = [
|
|
494
|
-
Paragraph(_markup(question.get("question_id"), default="Unnamed question"), styles["question"]),
|
|
495
|
-
_question_meta(question, styles, width),
|
|
496
|
-
Spacer(1, 6),
|
|
497
|
-
Paragraph("Stem", styles["subhead"]),
|
|
498
|
-
_paragraph(question.get("stem"), styles["body"]),
|
|
499
|
-
Paragraph("Why it was wrong", styles["subhead"]),
|
|
500
|
-
_paragraph(question.get("why_wrong"), styles["body"]),
|
|
501
|
-
Spacer(1, 4),
|
|
502
|
-
Table(
|
|
503
|
-
[[Paragraph(f"First incorrect step: {_markup(first_wrong)}", styles["callout"])]],
|
|
504
|
-
colWidths=[width],
|
|
505
|
-
style=TableStyle([
|
|
506
|
-
("BACKGROUND", (0, 0), (-1, -1), THEME["warning_soft"]),
|
|
507
|
-
("BOX", (0, 0), (-1, -1), 0.8, THEME["warning"]),
|
|
508
|
-
("LEFTPADDING", (0, 0), (-1, -1), 10),
|
|
509
|
-
("RIGHTPADDING", (0, 0), (-1, -1), 10),
|
|
510
|
-
("TOPPADDING", (0, 0), (-1, -1), 8),
|
|
511
|
-
("BOTTOMPADDING", (0, 0), (-1, -1), 8),
|
|
512
|
-
]),
|
|
513
|
-
),
|
|
514
|
-
Spacer(1, 6),
|
|
515
|
-
Paragraph("Key concepts involved", styles["subhead"]),
|
|
516
|
-
_long_key_concepts_table(question, styles, width),
|
|
517
|
-
Spacer(1, 6),
|
|
518
|
-
Paragraph("Step-by-step comparison", styles["subhead"]),
|
|
519
|
-
_step_comparison_table(question, styles, width),
|
|
520
|
-
]
|
|
521
|
-
story.extend([Spacer(1, 6)] + _steps_block("Correct solution", question.get("correct_solution_steps", []), styles) + [Spacer(1, 10)])
|
|
522
|
-
return story
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
def _questions_story(data: dict, styles: dict, width: float) -> List[Any]:
|
|
526
|
-
story = _section_heading("Mistake-by-Mistake Analysis & Solutions", styles)
|
|
527
|
-
questions = data.get("questions", []) or []
|
|
528
|
-
if not questions:
|
|
529
|
-
return story + [_paragraph("No question analysis provided.", styles["body"])]
|
|
530
|
-
|
|
531
|
-
builder = _mc_question_story if data.get("book_type") == "mc-question" else _long_question_story
|
|
532
|
-
for question in questions:
|
|
533
|
-
story.extend(builder(question, styles, width))
|
|
534
|
-
return story
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
def _build_story(data: dict, styles: dict, doc: SimpleDocTemplate) -> List[Any]:
|
|
538
|
-
title = _safe_text(data.get("title"), default="Error Book")
|
|
539
|
-
subtitle = f"Type: {_safe_text(data.get('book_type'), default='general')} | Last updated: {_safe_text(data.get('last_updated'))}"
|
|
540
|
-
story: List[Any] = [
|
|
541
|
-
Spacer(1, 8),
|
|
542
|
-
Paragraph(_markup(title), styles["title"]),
|
|
543
|
-
Paragraph(_markup(subtitle), styles["subtitle"]),
|
|
544
|
-
Paragraph(_markup("A structured review of mistakes, concepts, and corrections."), styles["subtitle"]),
|
|
545
|
-
]
|
|
546
|
-
story.extend(_coverage_story(data, styles, doc.width))
|
|
547
|
-
story.extend(_overview_story(data, styles))
|
|
548
|
-
story.extend(_concept_story(data, styles, doc.width))
|
|
549
|
-
story.append(PageBreak())
|
|
550
|
-
story.extend(_questions_story(data, styles, doc.width))
|
|
551
|
-
return story
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
def main() -> int:
|
|
555
|
-
parser = argparse.ArgumentParser(description="Render structured error-book JSON to a polished PDF.")
|
|
556
|
-
parser.add_argument("input_json", help="Input JSON file path")
|
|
557
|
-
parser.add_argument("output_pdf", help="Output PDF file path")
|
|
558
|
-
parser.add_argument("--font-path", default=None, help="Path to a CJK-capable font file (.ttf/.otf/.ttc)")
|
|
559
|
-
parser.add_argument("--font-size", type=int, default=11, help="Base font size (default: 11)")
|
|
560
|
-
parser.add_argument("--pagesize", choices=["a4", "letter"], default="a4", help="Page size (default: a4)")
|
|
561
|
-
parser.add_argument("--margin-mm", type=float, default=16.0, help="Page margin in mm (default: 16)")
|
|
562
|
-
args = parser.parse_args()
|
|
563
|
-
|
|
564
|
-
data = _read_json(args.input_json)
|
|
565
|
-
font_path, subfont_index = _detect_cjk_font_path(args.font_path)
|
|
566
|
-
font_name = _register_font(font_path, subfont_index)
|
|
567
|
-
styles = _build_styles(font_name, args.font_size)
|
|
568
|
-
|
|
569
|
-
page_size = A4 if args.pagesize == "a4" else letter
|
|
570
|
-
margin = args.margin_mm * mm
|
|
571
|
-
|
|
572
|
-
_ensure_parent_dir(args.output_pdf)
|
|
573
|
-
doc = SimpleDocTemplate(
|
|
574
|
-
args.output_pdf,
|
|
575
|
-
pagesize=page_size,
|
|
576
|
-
leftMargin=margin,
|
|
577
|
-
rightMargin=margin,
|
|
578
|
-
topMargin=margin,
|
|
579
|
-
bottomMargin=margin,
|
|
580
|
-
title=_safe_text(data.get("title"), default=os.path.basename(args.output_pdf)),
|
|
581
|
-
author="Apollo Toolkit Learning Error Book Skill",
|
|
582
|
-
)
|
|
583
|
-
|
|
584
|
-
story = _build_story(data, styles, doc)
|
|
585
|
-
doc.build(story, onFirstPage=_header_footer, onLaterPages=_header_footer)
|
|
586
|
-
return 0
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
if __name__ == "__main__":
|
|
590
|
-
raise SystemExit(main())
|