diff --git a/docs/index.md b/docs/index.md index e0bc72c4..ac082c04 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,7 +41,7 @@ PlanExe generates a **single HTML report** (a self-contained artifact you can op Open the sample report and do this: 1. Read **Executive Summary** to see the top-level deliverables, budget, risks, and next steps. -2. Jump to **Gantt Interactive** to see how the goal gets broken down into many concrete tasks. +2. Jump to **Gantt** to see how the goal gets broken down into many concrete tasks. 3. Open **Premortem** to see what could go wrong and what to do about it. --- diff --git a/worker_plan/tests/test_report_generator.py b/worker_plan/tests/test_report_generator.py new file mode 100644 index 00000000..69b5425b --- /dev/null +++ b/worker_plan/tests/test_report_generator.py @@ -0,0 +1,42 @@ +import tempfile +import unittest +from pathlib import Path + +from worker_plan_internal.report.report_generator import ReportGenerator + + +class TestReportGeneratorAppendHtml(unittest.TestCase): + def test_subtitle_is_rendered_first_in_section_content(self): + with tempfile.TemporaryDirectory() as tmp: + html_path = Path(tmp) / "widget.html" + html_path.write_text( + "" + "
chart
" + "" + ) + rg = ReportGenerator() + rg.append_html("Gantt", html_path, subtitle="Unoptimized waterfall. Parallel work modelled here.") + html = rg.generate_html_report(title="Sample") + + button = html.index('') + subtitle = html.index("

Unoptimized waterfall. Parallel work <not> modelled here.

") + widget = html.index('id="widget"') + self.assertTrue(button < subtitle < widget) + + def test_no_subtitle_by_default(self): + with tempfile.TemporaryDirectory() as tmp: + html_path = Path(tmp) / "widget.html" + html_path.write_text( + "
chart
" + ) + rg = ReportGenerator() + rg.append_html("Gantt", html_path) + html = rg.generate_html_report(title="Sample") + + button = html.index('') + widget = html.index('id="widget"') + self.assertNotIn("

", html[button:widget]) + + +if __name__ == "__main__": + unittest.main() diff --git a/worker_plan/worker_plan_internal/plan/nodes/report.py b/worker_plan/worker_plan_internal/plan/nodes/report.py index 123c2e70..3a067ce2 100644 --- a/worker_plan/worker_plan_internal/plan/nodes/report.py +++ b/worker_plan/worker_plan_internal/plan/nodes/report.py @@ -73,7 +73,7 @@ def run_inner(self): rg = ReportGenerator() rg.append_markdown('Executive Summary', self.input()['executive_summary']['markdown'].path) - rg.append_html('Gantt Interactive', self.input()['create_schedule']['dhtmlx_html'].path) + rg.append_html('Gantt', self.input()['create_schedule']['dhtmlx_html'].path, subtitle='Unoptimized waterfall. Parallel work not modelled here.') rg.append_markdown('Pitch', self.input()['pitch_markdown']['markdown'].path) rg.append_markdown('Project Plan', self.input()['project_plan']['markdown'].path) rg.append_markdown('Strategic Decisions', self.input()['strategic_decisions_markdown']['markdown'].path) diff --git a/worker_plan/worker_plan_internal/report/report_generator.py b/worker_plan/worker_plan_internal/report/report_generator.py index 288efe94..bcbb9201 100644 --- a/worker_plan/worker_plan_internal/report/report_generator.py +++ b/worker_plan/worker_plan_internal/report/report_generator.py @@ -154,15 +154,19 @@ def append_csv(self, document_title: str, file_path: Path, css_classes: list[str markdown_content = f"```csv\n{csv_text}```" self.report_markdown_item_list.append(ReportMarkdownItem(document_title, markdown_content)) - def append_html(self, document_title: str, file_path: Path, css_classes: list[str] = []): + def append_html(self, document_title: str, file_path: Path, css_classes: list[str] = [], subtitle: Optional[str] = None): """Append an HTML document to the report. HTML-only: not added to the markdown report (the markdown output is intended for LLM consumption, and embedded JavaScript/HTML widgets are not useful there). + + subtitle: optional one-line intro shown as the first paragraph of the section, above the embedded HTML. """ with open(file_path, 'r') as f: html_raw = f.read() + subtitle_html = f"

{escape(subtitle)}

\n" if subtitle else "" + # Extract the html_head content between and html_head_match = re.search(r'(.*)', html_raw, re.DOTALL) if html_head_match: @@ -175,11 +179,11 @@ def append_html(self, document_title: str, file_path: Path, css_classes: list[st html_body_match = re.search(r'(.*)', html_raw, re.DOTALL) if html_body_match: html_body = html_body_match.group(1) - self.report_html_item_list.append(ReportDocumentItem(document_title, html_body)) + self.report_html_item_list.append(ReportDocumentItem(document_title, subtitle_html + html_body, css_classes=css_classes)) else: logging.warning(f"Document: '{document_title}'. Could not find HTML_BODY_CONTENT_START and HTML_BODY_CONTENT_END in {file_path}") # If no markers found, use the entire content as the body - self.report_html_item_list.append(ReportDocumentItem(document_title, html_raw, css_classes=css_classes)) + self.report_html_item_list.append(ReportDocumentItem(document_title, subtitle_html + html_raw, css_classes=css_classes)) # Extract the html_body_script content between and html_body_script_match = re.search(r'(.*)', html_raw, re.DOTALL)