@@ -0,0 +1,277 @@
1 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Generate PDF reports from knowledge graph data.
2 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
3 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Uses reportlab for PDF generation. Falls back gracefully if not installed.
4 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ No LLM required — pure template-based generation from KG data.
5 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
6 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
7 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
8 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from datetime import datetime
9 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from pathlib import Path
10 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from typing import Any, Dict, List, Optional
11 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
12 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
13 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
14 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
15 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _get_styles():
16 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Import and configure reportlab styles."""
17 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.lib import colors
18 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.lib.pagesizes import letter
19 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
20 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.lib.units import inch
21 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
22 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ styles = getSampleStyleSheet()
23 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
24 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ styles.add(
25 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ParagraphStyle(
26 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "KGTitle",
27 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ parent=styles["Title"],
28 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fontSize=24,
29 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ spaceAfter=20,
30 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ textColor=colors.HexColor("#1a1a2e"),
31 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
32 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
33 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ styles.add(
34 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ParagraphStyle(
35 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "KGHeading2",
36 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ parent=styles["Heading2"],
37 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fontSize=16,
38 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ spaceBefore=16,
39 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ spaceAfter=8,
40 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ textColor=colors.HexColor("#16213e"),
41 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
42 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
43 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ styles.add(
44 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ParagraphStyle(
45 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "KGBody",
46 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ parent=styles["Normal"],
47 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fontSize=10,
48 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ leading=14,
49 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ spaceBefore=4,
50 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ spaceAfter=4,
51 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
52 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
53 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ styles.add(
54 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ParagraphStyle(
55 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "KGBullet",
56 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ parent=styles["Normal"],
57 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fontSize=10,
58 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ leading=14,
59 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ leftIndent=20,
60 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ bulletIndent=10,
61 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ spaceBefore=2,
62 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ spaceAfter=2,
63 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
64 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
65 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
66 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return styles, letter, inch, colors
67 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
68 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
69 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _build_entity_table(nodes: List[dict], colors) -> Any:
70 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Build a table of entities grouped by type."""
71 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.lib.units import inch
72 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.platypus import Table, TableStyle
73 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
74 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ by_type: Dict[str, list] = {}
75 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for n in nodes:
76 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ t = n.get("type", "concept")
77 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ by_type.setdefault(t, []).append(n)
78 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
79 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data = [["Type", "Count", "Examples"]]
80 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for etype, elist in sorted(by_type.items(), key=lambda x: -len(x[1])):
81 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ examples = ", ".join(e.get("name", "") for e in elist[:3])
82 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(elist) > 3:
83 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ examples += f" (+{len(elist) - 3} more)"
84 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data.append([etype.title(), str(len(elist)), examples])
85 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
86 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ table = Table(data, colWidths=[1.2 * inch, 0.8 * inch, 4.0 * inch])
87 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ table.setStyle(
88 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ TableStyle(
89 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ [
90 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e8eaf6")),
91 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
92 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("FONTSIZE", (0, 0), (-1, -1), 9),
93 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
94 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("VALIGN", (0, 0), (-1, -1), "TOP"),
95 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("TOPPADDING", (0, 0), (-1, -1), 4),
96 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
97 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ]
98 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
99 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
100 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return table
101 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
102 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
103 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _build_relationship_table(rels: List[dict], colors, max_rows: int = 30) -> Any:
104 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Build a table of relationships."""
105 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.lib.units import inch
106 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.platypus import Table, TableStyle
107 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
108 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data = [["Source", "Relationship", "Target"]]
109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for r in rels[:max_rows]:
110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data.append([r.get("source", ""), r.get("type", ""), r.get("target", "")])
111 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if len(rels) > max_rows:
112 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data.append(["...", f"({len(rels) - max_rows} more)", "..."])
113 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ table = Table(data, colWidths=[2.0 * inch, 2.0 * inch, 2.0 * inch])
115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ table.setStyle(
116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ TableStyle(
117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ [
118 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e8eaf6")),
119 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
120 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("FONTSIZE", (0, 0), (-1, -1), 9),
121 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
122 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("VALIGN", (0, 0), (-1, -1), "TOP"),
123 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("TOPPADDING", (0, 0), (-1, -1), 4),
124 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
125 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ]
126 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
127 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
128 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return table
129 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
130 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
131 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _build_key_entities_table(rels: List[dict], colors) -> Any:
132 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Build a table of top entities by connection count."""
133 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.lib.units import inch
134 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.platypus import Table, TableStyle
135 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
136 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ degree: Dict[str, int] = {}
137 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for r in rels:
138 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ degree[r.get("source", "")] = degree.get(r.get("source", ""), 0) + 1
139 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ degree[r.get("target", "")] = degree.get(r.get("target", ""), 0) + 1
140 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
141 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ top = sorted(degree.items(), key=lambda x: -x[1])[:10]
142 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not top:
143 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return None
144 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
145 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data = [["Entity", "Connections"]]
146 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for name, deg in top:
147 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ data.append([name, str(deg)])
148 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
149 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ table = Table(data, colWidths=[4.0 * inch, 1.5 * inch])
150 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ table.setStyle(
151 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ TableStyle(
152 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ [
153 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e8eaf6")),
154 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
155 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("FONTSIZE", (0, 0), (-1, -1), 9),
156 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
157 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("TOPPADDING", (0, 0), (-1, -1), 4),
158 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
159 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ]
160 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
161 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
162 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return table
163 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
164 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
165 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def generate_pdf(
166 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ kg_data: dict,
167 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ output_path: Path,
168 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ title: Optional[str] = None,
169 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ diagrams_dir: Optional[Path] = None,
170 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ) -> Path:
171 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Generate a PDF report from knowledge graph data.
172 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Args:
174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ kg_data: Knowledge graph dict with 'nodes' and 'relationships'.
175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ output_path: Path to write the PDF file.
176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ title: Optional report title.
177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ diagrams_dir: Optional directory containing diagram images to embed.
178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Returns:
180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Path to the generated PDF.
181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Raises:
183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ImportError: If reportlab is not installed.
184 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """
185 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.platypus import (
186 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Paragraph,
187 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ SimpleDocTemplate,
188 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Spacer,
189 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
190 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
191 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ styles, letter, inch, colors = _get_styles()
192 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
193 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ output_path = Path(output_path)
194 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ output_path.parent.mkdir(parents=True, exist_ok=True)
195 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
196 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ doc = SimpleDocTemplate(
197 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ str(output_path),
198 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pagesize=letter,
199 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ topMargin=0.75 * inch,
200 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ bottomMargin=0.75 * inch,
201 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ leftMargin=0.75 * inch,
202 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ rightMargin=0.75 * inch,
203 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
204 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
205 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story = []
206 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ nodes = kg_data.get("nodes", [])
207 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ rels = kg_data.get("relationships", [])
208 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
209 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Title
210 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ report_title = title or "Knowledge Graph Report"
211 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Paragraph(report_title, styles["KGTitle"]))
212 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(
213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Paragraph(
214 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f"Generated {datetime.now().strftime('%Y-%m-%d %H:%M')} • "
215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f"{len(nodes)} entities • {len(rels)} relationships",
216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ styles["KGBody"],
217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Spacer(1, 20))
220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Entity breakdown
222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if nodes:
223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Paragraph("Entity Breakdown", styles["KGHeading2"]))
224 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(_build_entity_table(nodes, colors))
225 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Spacer(1, 12))
226 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
227 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Key entities
228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if rels:
229 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ key_table = _build_key_entities_table(rels, colors)
230 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if key_table:
231 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Paragraph("Key Entities (by connections)", styles["KGHeading2"]))
232 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(key_table)
233 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Spacer(1, 12))
234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Embed diagram images
236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if diagrams_dir and diagrams_dir.exists():
237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ _embed_diagrams(story, styles, diagrams_dir, inch)
238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Relationship table
240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if rels:
241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Paragraph("Relationships", styles["KGHeading2"]))
242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(_build_relationship_table(rels, colors))
243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Spacer(1, 12))
244 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
245 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Entity details
246 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if nodes:
247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Paragraph("Entity Details", styles["KGHeading2"]))
248 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for node in sorted(nodes, key=lambda n: n.get("name", "")):
249 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ name = node.get("name", "")
250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ etype = node.get("type", "concept")
251 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ descs = node.get("descriptions", [])
252 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ desc = descs[0] if descs else "No description."
253 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Paragraph(f"<b>{name}</b> <i>({etype})</i>: {desc}", styles["KGBullet"]))
254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ doc.build(story)
256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger.info(f"Generated PDF report: {output_path}")
257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return output_path
258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _embed_diagrams(story, styles, diagrams_dir: Path, inch):
261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Embed diagram PNG images from a directory."""
262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from reportlab.platypus import Image, Paragraph, Spacer
263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pngs = sorted(diagrams_dir.glob("*.png"))
265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not pngs:
266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return
267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
268 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Paragraph("Diagrams", styles["KGHeading2"]))
269 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
270 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for png in pngs:
271 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ img = Image(str(png), width=5 * inch, height=3.5 * inch)
273 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ img.hAlign = "CENTER"
274 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(img)
275 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ story.append(Spacer(1, 8))
276 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except Exception as e:
277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger.warning(f"Could not embed diagram {png.name}: {e}")