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

1"""グラフの画像と処理の「方法」の文をまとめたレポート(HTML)。GUI には依存しない。""" 

2import base64 

3import datetime 

4import html 

5 

6from graphica.core.methods_text import generate_methods_text 

7from typing import TYPE_CHECKING 

8if TYPE_CHECKING: 

9 from graphica.models.project import ProjectModel 

10 

11 

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 ] 

19 

20 

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') 

27 

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>" 

36 

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 )