Coverage for graphica/core/report_export.py: 100.0%
17 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 16:38 +0000
1"""グラフの画像と処理の「方法」の文をまとめたレポート(HTML)。GUI には依存しない。"""
2import base64
3import datetime
4import html
6from graphica.core.methods_text import generate_methods_text
7from typing import TYPE_CHECKING
8if TYPE_CHECKING:
9 from graphica.models.project import ProjectModel
12def collect_methods_sections(project: "ProjectModel") -> list[tuple[str, str]]:
13 """履歴を持つデータセットごとの (名前, 方法の文)。元データは含めない。"""
14 return [
15 (ds.name, generate_methods_text(ds, project))
16 for ds in project.datasets
17 if ds.provenance
18 ]
21def generate_html_report(image_png_bytes: bytes, methods_sections: list[tuple[str, str]], title: str = "") -> str:
22 """画像を base64 で埋め込んだ1ファイルで完結する HTML。title が空なら既定のタイトル。"""
23 title_text = title.strip() or "実験レポート"
24 title_html = html.escape(title_text)
25 generated_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
26 image_b64 = base64.b64encode(image_png_bytes).decode('ascii')
28 if methods_sections:
29 items = "".join(
30 f"<li><b>{html.escape(name)}</b>: {html.escape(text)}</li>"
31 for name, text in methods_sections
32 )
33 methods_html = f"<ul>{items}</ul>"
34 else:
35 methods_html = "<p>(処理履歴を持つデータセットはありません)</p>"
37 return (
38 "<!doctype html>\n"
39 "<html><head><meta charset=\"utf-8\">"
40 f"<title>{title_html}</title>\n"
41 "<style>\n"
42 "body { font-family: sans-serif; max-width: 900px; margin: 2em auto; padding: 0 1em; }\n"
43 "img { max-width: 100%; border: 1px solid #ccc; }\n"
44 "</style></head><body>\n"
45 f"<h1>{title_html}</h1>\n"
46 f"<p>生成日時: {generated_at}</p>\n"
47 f"<img src=\"data:image/png;base64,{image_b64}\" alt=\"plot\">\n"
48 "<h2>方法</h2>\n"
49 f"{methods_html}\n"
50 "</body></html>\n"
51 )