Showing
1 changed file
with
587 additions
and
0 deletions
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +""" | ||
| 3 | +生成覆盖全部允许block类型的演示 IR,用于验证 HTML 与 PDF 渲染。 | ||
| 4 | + | ||
| 5 | +执行后会在 `final_reports/ir` 写入一份带时间戳的 IR, | ||
| 6 | +并分别在 `final_reports/html` 与 `final_reports/pdf` 输出对应的渲染文件。 | ||
| 7 | +""" | ||
| 8 | + | ||
| 9 | +from __future__ import annotations | ||
| 10 | + | ||
| 11 | +import json | ||
| 12 | +import sys | ||
| 13 | +from datetime import datetime | ||
| 14 | +from pathlib import Path | ||
| 15 | + | ||
| 16 | +# 允许直接以脚本形式运行 | ||
| 17 | +ROOT = Path(__file__).resolve().parents[2] | ||
| 18 | +if str(ROOT) not in sys.path: | ||
| 19 | + sys.path.insert(0, str(ROOT)) | ||
| 20 | + | ||
| 21 | +from ReportEngine.core import DocumentComposer | ||
| 22 | +from ReportEngine.ir import IRValidator | ||
| 23 | +from ReportEngine.ir.schema import ENGINE_AGENT_TITLES | ||
| 24 | +from ReportEngine.renderers import HTMLRenderer, PDFRenderer | ||
| 25 | +from ReportEngine.utils.config import settings | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def build_inline_marks_demo() -> dict: | ||
| 29 | + """生成覆盖全部内联标记的 paragraph block。""" | ||
| 30 | + return { | ||
| 31 | + "type": "paragraph", | ||
| 32 | + "inlines": [ | ||
| 33 | + {"text": "这一段覆盖全部内联标记:"}, | ||
| 34 | + {"text": "粗体", "marks": [{"type": "bold"}]}, | ||
| 35 | + {"text": " / 斜体", "marks": [{"type": "italic"}]}, | ||
| 36 | + {"text": " / 下划线", "marks": [{"type": "underline"}]}, | ||
| 37 | + {"text": " / 删除线", "marks": [{"type": "strike"}]}, | ||
| 38 | + {"text": " / 代码", "marks": [{"type": "code"}]}, | ||
| 39 | + { | ||
| 40 | + "text": " / 链接", | ||
| 41 | + "marks": [ | ||
| 42 | + { | ||
| 43 | + "type": "link", | ||
| 44 | + "href": "https://example.com/demo", | ||
| 45 | + "title": "示例链接", | ||
| 46 | + } | ||
| 47 | + ], | ||
| 48 | + }, | ||
| 49 | + {"text": " / 颜色", "marks": [{"type": "color", "value": "#c0392b"}]}, | ||
| 50 | + { | ||
| 51 | + "text": " / 字体", | ||
| 52 | + "marks": [ | ||
| 53 | + { | ||
| 54 | + "type": "font", | ||
| 55 | + "family": "Georgia, serif", | ||
| 56 | + "size": "15px", | ||
| 57 | + "weight": "600", | ||
| 58 | + } | ||
| 59 | + ], | ||
| 60 | + }, | ||
| 61 | + {"text": " / 高亮", "marks": [{"type": "highlight"}]}, | ||
| 62 | + {"text": " / 下标", "marks": [{"type": "subscript"}]}, | ||
| 63 | + {"text": " / 上标", "marks": [{"type": "superscript"}]}, | ||
| 64 | + {"text": " / 行内公式", "marks": [{"type": "math", "value": "E=mc^2"}]}, | ||
| 65 | + {"text": "。"}, | ||
| 66 | + ], | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + | ||
| 70 | +def build_widget_block() -> dict: | ||
| 71 | + """构造一个合法的 Chart.js widget block。""" | ||
| 72 | + return { | ||
| 73 | + "type": "widget", | ||
| 74 | + "widgetId": "demo-volume-trend", | ||
| 75 | + "widgetType": "chart.js/line", | ||
| 76 | + "props": { | ||
| 77 | + "type": "line", | ||
| 78 | + "options": { | ||
| 79 | + "responsive": True, | ||
| 80 | + "plugins": {"legend": {"position": "bottom"}}, | ||
| 81 | + "scales": {"y": {"title": {"display": True, "text": "提及量"}}}, | ||
| 82 | + }, | ||
| 83 | + }, | ||
| 84 | + "data": { | ||
| 85 | + "labels": ["T0", "T0+6h", "T0+12h", "T0+18h", "T0+24h"], | ||
| 86 | + "datasets": [ | ||
| 87 | + { | ||
| 88 | + "label": "主流媒体", | ||
| 89 | + "data": [12, 18, 23, 30, 26], | ||
| 90 | + "borderColor": "#2980b9", | ||
| 91 | + "backgroundColor": "rgba(41,128,185,0.18)", | ||
| 92 | + "tension": 0.25, | ||
| 93 | + "fill": False, | ||
| 94 | + }, | ||
| 95 | + { | ||
| 96 | + "label": "社交平台", | ||
| 97 | + "data": [8, 10, 15, 28, 40], | ||
| 98 | + "borderColor": "#c0392b", | ||
| 99 | + "backgroundColor": "rgba(192,57,43,0.2)", | ||
| 100 | + "tension": 0.35, | ||
| 101 | + "fill": False, | ||
| 102 | + }, | ||
| 103 | + ], | ||
| 104 | + }, | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + | ||
| 108 | +def build_chapters() -> list[dict]: | ||
| 109 | + """构造覆盖所有 block 类型的章节列表。""" | ||
| 110 | + inline_demo = build_inline_marks_demo() | ||
| 111 | + | ||
| 112 | + bullet_list = { | ||
| 113 | + "type": "list", | ||
| 114 | + "listType": "bullet", | ||
| 115 | + "items": [ | ||
| 116 | + [ | ||
| 117 | + { | ||
| 118 | + "type": "paragraph", | ||
| 119 | + "inlines": [{"text": "社交媒体热度在 48 小时内翻倍"}], | ||
| 120 | + } | ||
| 121 | + ], | ||
| 122 | + [ | ||
| 123 | + { | ||
| 124 | + "type": "paragraph", | ||
| 125 | + "inlines": [{"text": "主流媒体报道集中在早间时段"}], | ||
| 126 | + }, | ||
| 127 | + { | ||
| 128 | + "type": "list", | ||
| 129 | + "listType": "ordered", | ||
| 130 | + "items": [ | ||
| 131 | + [ | ||
| 132 | + { | ||
| 133 | + "type": "paragraph", | ||
| 134 | + "inlines": [{"text": "07:00-09:00:首轮报道"}], | ||
| 135 | + } | ||
| 136 | + ], | ||
| 137 | + [ | ||
| 138 | + { | ||
| 139 | + "type": "paragraph", | ||
| 140 | + "inlines": [{"text": "10:00-12:00:评论扩散"}], | ||
| 141 | + } | ||
| 142 | + ], | ||
| 143 | + ], | ||
| 144 | + }, | ||
| 145 | + ], | ||
| 146 | + [ | ||
| 147 | + { | ||
| 148 | + "type": "paragraph", | ||
| 149 | + "inlines": [{"text": "地方政务号开始回应并同步线下通稿"}], | ||
| 150 | + } | ||
| 151 | + ], | ||
| 152 | + ], | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + task_list = { | ||
| 156 | + "type": "list", | ||
| 157 | + "listType": "task", | ||
| 158 | + "items": [ | ||
| 159 | + [ | ||
| 160 | + { | ||
| 161 | + "type": "paragraph", | ||
| 162 | + "inlines": [{"text": "跟踪权威辟谣素材是否上线"}], | ||
| 163 | + } | ||
| 164 | + ], | ||
| 165 | + [ | ||
| 166 | + { | ||
| 167 | + "type": "paragraph", | ||
| 168 | + "inlines": [{"text": "监测新增关联关键词与长尾问题"}], | ||
| 169 | + } | ||
| 170 | + ], | ||
| 171 | + [ | ||
| 172 | + { | ||
| 173 | + "type": "paragraph", | ||
| 174 | + "inlines": [{"text": "准备 FAQ 供客服统一答复"}], | ||
| 175 | + } | ||
| 176 | + ], | ||
| 177 | + ], | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + table_block = { | ||
| 181 | + "type": "table", | ||
| 182 | + "caption": "核心信源与传播路径", | ||
| 183 | + "zebra": True, | ||
| 184 | + "colgroup": [{"width": "22%"}, {"width": "38%"}, {"width": "40%"}], | ||
| 185 | + "rows": [ | ||
| 186 | + { | ||
| 187 | + "cells": [ | ||
| 188 | + { | ||
| 189 | + "align": "center", | ||
| 190 | + "blocks": [ | ||
| 191 | + { | ||
| 192 | + "type": "paragraph", | ||
| 193 | + "inlines": [{"text": "时间节点", "marks": [{"type": "bold"}]}], | ||
| 194 | + } | ||
| 195 | + ], | ||
| 196 | + }, | ||
| 197 | + { | ||
| 198 | + "align": "center", | ||
| 199 | + "blocks": [ | ||
| 200 | + { | ||
| 201 | + "type": "paragraph", | ||
| 202 | + "inlines": [{"text": "事件内容", "marks": [{"type": "bold"}]}], | ||
| 203 | + } | ||
| 204 | + ], | ||
| 205 | + }, | ||
| 206 | + { | ||
| 207 | + "align": "center", | ||
| 208 | + "blocks": [ | ||
| 209 | + { | ||
| 210 | + "type": "paragraph", | ||
| 211 | + "inlines": [{"text": "主要渠道", "marks": [{"type": "bold"}]}], | ||
| 212 | + } | ||
| 213 | + ], | ||
| 214 | + }, | ||
| 215 | + ] | ||
| 216 | + }, | ||
| 217 | + { | ||
| 218 | + "cells": [ | ||
| 219 | + {"blocks": [{"type": "paragraph", "inlines": [{"text": "T0"}]}]}, | ||
| 220 | + { | ||
| 221 | + "blocks": [ | ||
| 222 | + { | ||
| 223 | + "type": "paragraph", | ||
| 224 | + "inlines": [{"text": "线下冲突视频首次上传"}], | ||
| 225 | + } | ||
| 226 | + ] | ||
| 227 | + }, | ||
| 228 | + { | ||
| 229 | + "blocks": [ | ||
| 230 | + { | ||
| 231 | + "type": "paragraph", | ||
| 232 | + "inlines": [{"text": "短视频平台 / 私聊转发"}], | ||
| 233 | + } | ||
| 234 | + ] | ||
| 235 | + }, | ||
| 236 | + ] | ||
| 237 | + }, | ||
| 238 | + { | ||
| 239 | + "cells": [ | ||
| 240 | + {"blocks": [{"type": "paragraph", "inlines": [{"text": "T0+6h"}]}]}, | ||
| 241 | + { | ||
| 242 | + "blocks": [ | ||
| 243 | + { | ||
| 244 | + "type": "paragraph", | ||
| 245 | + "inlines": [{"text": "登上热搜,出现二次剪辑"}], | ||
| 246 | + } | ||
| 247 | + ] | ||
| 248 | + }, | ||
| 249 | + { | ||
| 250 | + "blocks": [ | ||
| 251 | + { | ||
| 252 | + "type": "paragraph", | ||
| 253 | + "inlines": [{"text": "微博 / 朋友圈"}], | ||
| 254 | + } | ||
| 255 | + ] | ||
| 256 | + }, | ||
| 257 | + ] | ||
| 258 | + }, | ||
| 259 | + { | ||
| 260 | + "cells": [ | ||
| 261 | + {"blocks": [{"type": "paragraph", "inlines": [{"text": "T0+18h"}]}]}, | ||
| 262 | + { | ||
| 263 | + "blocks": [ | ||
| 264 | + { | ||
| 265 | + "type": "paragraph", | ||
| 266 | + "inlines": [{"text": "官方回应并发布事实澄清"}], | ||
| 267 | + } | ||
| 268 | + ] | ||
| 269 | + }, | ||
| 270 | + { | ||
| 271 | + "blocks": [ | ||
| 272 | + { | ||
| 273 | + "type": "paragraph", | ||
| 274 | + "inlines": [{"text": "政务号 / 新闻客户端"}], | ||
| 275 | + } | ||
| 276 | + ] | ||
| 277 | + }, | ||
| 278 | + ] | ||
| 279 | + }, | ||
| 280 | + { | ||
| 281 | + "cells": [ | ||
| 282 | + {"blocks": [{"type": "paragraph", "inlines": [{"text": "T0+24h"}]}]}, | ||
| 283 | + { | ||
| 284 | + "blocks": [ | ||
| 285 | + { | ||
| 286 | + "type": "paragraph", | ||
| 287 | + "inlines": [{"text": "专家解读,舆论重心转向责任归属"}], | ||
| 288 | + } | ||
| 289 | + ] | ||
| 290 | + }, | ||
| 291 | + { | ||
| 292 | + "blocks": [ | ||
| 293 | + { | ||
| 294 | + "type": "paragraph", | ||
| 295 | + "inlines": [{"text": "视频号直播 / 行业社群"}], | ||
| 296 | + } | ||
| 297 | + ] | ||
| 298 | + }, | ||
| 299 | + ] | ||
| 300 | + }, | ||
| 301 | + ], | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + blockquote_block = { | ||
| 305 | + "type": "blockquote", | ||
| 306 | + "variant": "accent", | ||
| 307 | + "blocks": [ | ||
| 308 | + { | ||
| 309 | + "type": "paragraph", | ||
| 310 | + "inlines": [{"text": "“公众最关心的信息是真相与责任边界。”"}], | ||
| 311 | + }, | ||
| 312 | + { | ||
| 313 | + "type": "paragraph", | ||
| 314 | + "inlines": [{"text": "—— 模拟引用,验证引用块样式"}], | ||
| 315 | + }, | ||
| 316 | + ], | ||
| 317 | + } | ||
| 318 | + | ||
| 319 | + engine_quote_block = { | ||
| 320 | + "type": "engineQuote", | ||
| 321 | + "engine": "insight", | ||
| 322 | + "title": ENGINE_AGENT_TITLES["insight"], | ||
| 323 | + "blocks": [ | ||
| 324 | + { | ||
| 325 | + "type": "paragraph", | ||
| 326 | + "inlines": [ | ||
| 327 | + { | ||
| 328 | + "text": "模型认为 24 小时内保持回应频次,可避免信息真空。", | ||
| 329 | + "marks": [{"type": "bold"}], | ||
| 330 | + } | ||
| 331 | + ], | ||
| 332 | + }, | ||
| 333 | + { | ||
| 334 | + "type": "paragraph", | ||
| 335 | + "inlines": [ | ||
| 336 | + {"text": "建议同时准备简短 FAQ,便于多渠道统一口径。"} | ||
| 337 | + ], | ||
| 338 | + }, | ||
| 339 | + ], | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | + swot_block = { | ||
| 343 | + "type": "swotTable", | ||
| 344 | + "title": "舆论场 SWOT 速览", | ||
| 345 | + "summary": "覆盖当前情绪分布、潜在风险与机会。", | ||
| 346 | + "strengths": [ | ||
| 347 | + {"title": "官方快速响应", "detail": "首条澄清视频 3 小时内上线"}, | ||
| 348 | + {"title": "同城媒体配合", "impact": "高", "score": 8}, | ||
| 349 | + ], | ||
| 350 | + "weaknesses": [ | ||
| 351 | + {"title": "早期谣言存量大", "detail": "相关转发仍占 30%"}, | ||
| 352 | + "外部专家尚未统一口径", | ||
| 353 | + ], | ||
| 354 | + "opportunities": [ | ||
| 355 | + { | ||
| 356 | + "title": "社区共建讨论", | ||
| 357 | + "detail": "自发组织“辟谣志愿者”话题,情绪正向", | ||
| 358 | + }, | ||
| 359 | + {"title": "公益合作窗口", "impact": "中"}, | ||
| 360 | + ], | ||
| 361 | + "threats": [ | ||
| 362 | + {"title": "跨平台剪辑继续发酵", "impact": "高", "score": 9}, | ||
| 363 | + {"title": "个别自媒体煽动情绪", "evidence": "存在地域标签化倾向"}, | ||
| 364 | + ], | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + callout_block = { | ||
| 368 | + "type": "callout", | ||
| 369 | + "tone": "warning", | ||
| 370 | + "title": "排版边界提示", | ||
| 371 | + "blocks": [ | ||
| 372 | + { | ||
| 373 | + "type": "paragraph", | ||
| 374 | + "inlines": [ | ||
| 375 | + {"text": "callout 内部仅放轻量内容,超出部分会自动溢出到外层。"} | ||
| 376 | + ], | ||
| 377 | + }, | ||
| 378 | + { | ||
| 379 | + "type": "list", | ||
| 380 | + "listType": "bullet", | ||
| 381 | + "items": [ | ||
| 382 | + [ | ||
| 383 | + { | ||
| 384 | + "type": "paragraph", | ||
| 385 | + "inlines": [{"text": "支持嵌套列表 / 表格 / 数学公式"}], | ||
| 386 | + } | ||
| 387 | + ], | ||
| 388 | + [ | ||
| 389 | + { | ||
| 390 | + "type": "paragraph", | ||
| 391 | + "inlines": [{"text": "可在这里放置提醒或操作步骤"}], | ||
| 392 | + } | ||
| 393 | + ], | ||
| 394 | + ], | ||
| 395 | + }, | ||
| 396 | + ], | ||
| 397 | + } | ||
| 398 | + | ||
| 399 | + code_block = { | ||
| 400 | + "type": "code", | ||
| 401 | + "lang": "json", | ||
| 402 | + "caption": "演示代码块", | ||
| 403 | + "content": '{\n "event": "热点示例",\n "topic": "公共事件",\n "status": "monitoring"\n}', | ||
| 404 | + } | ||
| 405 | + | ||
| 406 | + math_block = { | ||
| 407 | + "type": "math", | ||
| 408 | + "latex": r"E = mc^2", | ||
| 409 | + "displayMode": True, | ||
| 410 | + } | ||
| 411 | + | ||
| 412 | + figure_block = { | ||
| 413 | + "type": "figure", | ||
| 414 | + "img": { | ||
| 415 | + "src": "https://dummyimage.com/600x320/eeeeee/333333&text=Placeholder", | ||
| 416 | + "alt": "占位示意图", | ||
| 417 | + "width": 600, | ||
| 418 | + "height": 320, | ||
| 419 | + }, | ||
| 420 | + "caption": "图像外链被替换为友好提示,可验证 figure 占位效果。", | ||
| 421 | + "responsive": True, | ||
| 422 | + } | ||
| 423 | + | ||
| 424 | + widget_block = build_widget_block() | ||
| 425 | + | ||
| 426 | + chapter_1 = { | ||
| 427 | + "chapterId": "S1", | ||
| 428 | + "title": "封面与目录", | ||
| 429 | + "anchor": "overview", | ||
| 430 | + "order": 10, | ||
| 431 | + "blocks": [ | ||
| 432 | + {"type": "heading", "level": 2, "text": "一、封面与目录", "anchor": "overview"}, | ||
| 433 | + { | ||
| 434 | + "type": "paragraph", | ||
| 435 | + "inlines": [ | ||
| 436 | + { | ||
| 437 | + "text": "模拟社会公共热点事件的摘要,便于快速确认排版与字体效果。", | ||
| 438 | + } | ||
| 439 | + ], | ||
| 440 | + }, | ||
| 441 | + inline_demo, | ||
| 442 | + { | ||
| 443 | + "type": "kpiGrid", | ||
| 444 | + "items": [ | ||
| 445 | + {"label": "24h提及量", "value": "98K", "delta": "+41%", "deltaTone": "up"}, | ||
| 446 | + {"label": "正向占比", "value": "32%", "delta": "+5pp", "deltaTone": "up"}, | ||
| 447 | + {"label": "负向占比", "value": "18%", "delta": "-3pp", "deltaTone": "down"}, | ||
| 448 | + {"label": "高频渠道", "value": "短视频 / 微博"}, | ||
| 449 | + ], | ||
| 450 | + "cols": 4, | ||
| 451 | + }, | ||
| 452 | + {"type": "toc"}, | ||
| 453 | + {"type": "hr"}, | ||
| 454 | + ], | ||
| 455 | + } | ||
| 456 | + | ||
| 457 | + chapter_2 = { | ||
| 458 | + "chapterId": "S2", | ||
| 459 | + "title": "块类型演示", | ||
| 460 | + "anchor": "blocks-showcase", | ||
| 461 | + "order": 20, | ||
| 462 | + "blocks": [ | ||
| 463 | + { | ||
| 464 | + "type": "heading", | ||
| 465 | + "level": 2, | ||
| 466 | + "text": "二、块类型演示", | ||
| 467 | + "anchor": "blocks-showcase", | ||
| 468 | + }, | ||
| 469 | + { | ||
| 470 | + "type": "paragraph", | ||
| 471 | + "inlines": [ | ||
| 472 | + {"text": "以下内容逐一覆盖 paragraph/list/table/swot/table/widget 等全部块类型。"} | ||
| 473 | + ], | ||
| 474 | + }, | ||
| 475 | + { | ||
| 476 | + "type": "heading", | ||
| 477 | + "level": 3, | ||
| 478 | + "text": "2.1 列表与表格", | ||
| 479 | + "anchor": "lists-and-tables", | ||
| 480 | + }, | ||
| 481 | + bullet_list, | ||
| 482 | + task_list, | ||
| 483 | + table_block, | ||
| 484 | + { | ||
| 485 | + "type": "heading", | ||
| 486 | + "level": 3, | ||
| 487 | + "text": "2.2 高阶块与富媒体", | ||
| 488 | + "anchor": "advanced-blocks", | ||
| 489 | + }, | ||
| 490 | + blockquote_block, | ||
| 491 | + callout_block, | ||
| 492 | + engine_quote_block, | ||
| 493 | + swot_block, | ||
| 494 | + widget_block, | ||
| 495 | + code_block, | ||
| 496 | + math_block, | ||
| 497 | + figure_block, | ||
| 498 | + { | ||
| 499 | + "type": "hr", | ||
| 500 | + "variant": "dashed", | ||
| 501 | + }, | ||
| 502 | + { | ||
| 503 | + "type": "paragraph", | ||
| 504 | + "align": "justify", | ||
| 505 | + "inlines": [ | ||
| 506 | + { | ||
| 507 | + "text": "本章节的 inline math 兜底验证:", | ||
| 508 | + }, | ||
| 509 | + {"text": "p(t)=p_0 e^{\\lambda t}", "marks": [{"type": "math"}]}, | ||
| 510 | + {"text": ";以上覆盖所有允许块及标记。"}, | ||
| 511 | + ], | ||
| 512 | + }, | ||
| 513 | + ], | ||
| 514 | + } | ||
| 515 | + | ||
| 516 | + return [chapter_1, chapter_2] | ||
| 517 | + | ||
| 518 | + | ||
| 519 | +def validate_chapters(chapters: list[dict]) -> None: | ||
| 520 | + """使用 IRValidator 校验章节结构,发现错误时抛出异常。""" | ||
| 521 | + validator = IRValidator() | ||
| 522 | + for chapter in chapters: | ||
| 523 | + ok, errors = validator.validate_chapter(chapter) | ||
| 524 | + if not ok: | ||
| 525 | + raise ValueError(f"{chapter.get('chapterId', 'unknown')} 校验失败: {errors}") | ||
| 526 | + | ||
| 527 | + | ||
| 528 | +def render_and_save(document_ir: dict, timestamp: str) -> tuple[Path, Path, Path]: | ||
| 529 | + """将 IR 保存为 JSON,并渲染 HTML / PDF,返回三个路径。""" | ||
| 530 | + ir_dir = Path(settings.DOCUMENT_IR_OUTPUT_DIR) | ||
| 531 | + html_dir = Path(settings.OUTPUT_DIR) / "html" | ||
| 532 | + pdf_dir = Path(settings.OUTPUT_DIR) / "pdf" | ||
| 533 | + ir_dir.mkdir(parents=True, exist_ok=True) | ||
| 534 | + html_dir.mkdir(parents=True, exist_ok=True) | ||
| 535 | + pdf_dir.mkdir(parents=True, exist_ok=True) | ||
| 536 | + | ||
| 537 | + ir_path = ir_dir / f"report_ir_all_blocks_demo_{timestamp}.json" | ||
| 538 | + ir_path.write_text(json.dumps(document_ir, ensure_ascii=False, indent=2), encoding="utf-8") | ||
| 539 | + | ||
| 540 | + html_renderer = HTMLRenderer() | ||
| 541 | + html_content = html_renderer.render(document_ir) | ||
| 542 | + html_path = html_dir / f"report_html_all_blocks_demo_{timestamp}.html" | ||
| 543 | + html_path.write_text(html_content, encoding="utf-8") | ||
| 544 | + | ||
| 545 | + pdf_renderer = PDFRenderer() | ||
| 546 | + pdf_path = pdf_dir / f"report_pdf_all_blocks_demo_{timestamp}.pdf" | ||
| 547 | + pdf_renderer.render_to_pdf(document_ir, pdf_path) | ||
| 548 | + | ||
| 549 | + return ir_path, html_path, pdf_path | ||
| 550 | + | ||
| 551 | + | ||
| 552 | +def main() -> int: | ||
| 553 | + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | ||
| 554 | + report_id = f"all-blocks-demo-{timestamp}" | ||
| 555 | + metadata = { | ||
| 556 | + "title": "社会公共热点事件渲染测试", | ||
| 557 | + "subtitle": "覆盖全部 IR 块类型的示例数据", | ||
| 558 | + "query": "公共事件渲染能力自检", | ||
| 559 | + "toc": {"title": "目录", "depth": 3}, | ||
| 560 | + "hero": { | ||
| 561 | + "summary": "用于验证 Report Engine 在 HTML / PDF 渲染时对各类区块的兼容性。", | ||
| 562 | + "kpis": [ | ||
| 563 | + {"label": "示例块数量", "value": "14", "delta": "+100%", "tone": "up"}, | ||
| 564 | + {"label": "图表数", "value": "1", "delta": "安全检查", "tone": "neutral"}, | ||
| 565 | + ], | ||
| 566 | + "highlights": ["覆盖全部 block", "含行内/块级公式", "Chart.js 数据有效"], | ||
| 567 | + "actions": ["重新生成", "导出 PDF"], | ||
| 568 | + }, | ||
| 569 | + } | ||
| 570 | + | ||
| 571 | + chapters = build_chapters() | ||
| 572 | + validate_chapters(chapters) | ||
| 573 | + | ||
| 574 | + composer = DocumentComposer() | ||
| 575 | + document_ir = composer.build_document(report_id, metadata, chapters) | ||
| 576 | + | ||
| 577 | + ir_path, html_path, pdf_path = render_and_save(document_ir, timestamp) | ||
| 578 | + | ||
| 579 | + print("✅ 演示 IR 生成完成") | ||
| 580 | + print(f"IR: {ir_path}") | ||
| 581 | + print(f"HTML: {html_path}") | ||
| 582 | + print(f"PDF: {pdf_path}") | ||
| 583 | + return 0 | ||
| 584 | + | ||
| 585 | + | ||
| 586 | +if __name__ == "__main__": | ||
| 587 | + raise SystemExit(main()) |
-
Please register or login to post a comment