feat: export improvements — Phase A + Phase B #75

Merged
chihlasm merged 19 commits from feat/export-phase-a into main 2026-02-13 23:30:32 +00:00
Showing only changes of commit 0a67a846de - Show all commits

View File

@@ -133,6 +133,42 @@ def _get_outcome_label(session: Session) -> str | None:
return OUTCOME_LABELS.get(outcome, str(outcome).replace("_", " ").title())
def _build_summary_fields(session: Session) -> dict[str, str]:
"""Build auto-populated summary fields from session data.
Empty fields are left blank — users fill them in via the editable preview.
"""
tree_name = session.tree_snapshot.get("name", "Troubleshooting Session")
tree_desc = session.tree_snapshot.get("description", "")
issue = f"{tree_name}: {tree_desc}" if tree_desc else tree_name
if session.completed_at:
status = "Resolved" if getattr(session, "outcome", None) == "resolved" else \
f"Completed — {_get_outcome_label(session) or 'Unknown'}"
else:
step_count = len(session.decisions) if session.decisions else 0
status = f"In Progress — paused at step {step_count}" if step_count else "In Progress"
_raw_notes = getattr(session, 'outcome_notes', None)
resolution = (_raw_notes if isinstance(_raw_notes, str) else '').strip()
_raw_next = getattr(session, 'next_steps', None)
next_steps = (_raw_next if isinstance(_raw_next, str) else '').strip()
return {
"issue": issue,
"impact": "",
"status": status,
"resolution": resolution,
"next_steps": next_steps,
}
def _escape_markdown_table(value: str) -> str:
"""Escape value for use in a markdown table cell."""
return value.replace("|", "\\|").replace("\n", " ")
def generate_markdown_export(session: Session, options: SessionExport) -> str:
"""Generate markdown export."""
lines = []
@@ -157,6 +193,22 @@ def generate_markdown_export(session: Session, options: SessionExport) -> str:
lines.append("---")
lines.append("")
if options.include_summary:
summary = _build_summary_fields(session)
esc = _escape_markdown_table
lines.append("## Summary")
lines.append("")
lines.append("| Field | Details |")
lines.append("|-------|---------|")
lines.append(f"| Issue | {esc(summary['issue'])} |")
lines.append(f"| Impact | {esc(summary['impact'])} |")
lines.append(f"| Status | {esc(summary['status'])} |")
lines.append(f"| Resolution | {esc(summary['resolution'])} |")
lines.append(f"| Next Steps | {esc(summary['next_steps'])} |")
lines.append("")
lines.append("---")
lines.append("")
# Scratchpad / Evidence section
scratchpad = getattr(session, 'scratchpad', '') or ''
if scratchpad.strip():
@@ -252,6 +304,17 @@ def generate_text_export(session: Session, options: SessionExport) -> str:
lines.append(f"Outcome: {outcome_label}")
lines.append("")
if options.include_summary:
summary = _build_summary_fields(session)
lines.append("SUMMARY")
lines.append("-" * 20)
lines.append(f"Issue: {summary['issue']}")
lines.append(f"Impact: {summary['impact']}")
lines.append(f"Status: {summary['status']}")
lines.append(f"Resolution: {summary['resolution']}")
lines.append(f"Next Steps: {summary['next_steps']}")
lines.append("")
# Scratchpad / Evidence section
scratchpad = getattr(session, 'scratchpad', '') or ''
if scratchpad.strip():
@@ -350,6 +413,17 @@ def generate_html_export(session: Session, options: SessionExport) -> str:
html_parts.append(f'<p><strong>Outcome:</strong> {html.escape(outcome_label)}</p>')
html_parts.append('</div>')
if options.include_summary:
summary = _build_summary_fields(session)
html_parts.append('<h2>Summary</h2>')
html_parts.append('<table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">')
for label, value in [("Issue", summary["issue"]), ("Impact", summary["impact"]),
("Status", summary["status"]), ("Resolution", summary["resolution"]),
("Next Steps", summary["next_steps"])]:
html_parts.append(f'<tr><td style="padding: 6px 12px; border: 1px solid #ddd; font-weight: bold; width: 120px;">{html.escape(label)}</td>')
html_parts.append(f'<td style="padding: 6px 12px; border: 1px solid #ddd;">{html.escape(value)}</td></tr>')
html_parts.append('</table>')
# Scratchpad / Evidence section
scratchpad = getattr(session, 'scratchpad', '') or ''
if scratchpad.strip():
@@ -427,6 +501,16 @@ def generate_psa_export(session: Session, options: SessionExport) -> str:
lines.append(f"Outcome: {outcome_label}")
lines.append("")
if options.include_summary:
summary = _build_summary_fields(session)
lines.append("--- SUMMARY ---")
lines.append(f"Issue: {summary['issue']}")
lines.append(f"Impact: {summary['impact']}")
lines.append(f"Status: {summary['status']}")
lines.append(f"Resolution: {summary['resolution']}")
lines.append(f"Next Steps: {summary['next_steps']}")
lines.append("")
# Problem section
lines.append("--- PROBLEM ---")
lines.append(tree_description if tree_description else "No description provided.")