feat: include supporting data in all export formats

Query supporting data in the export endpoint and pass to markdown, text,
HTML, and PSA export generators. Each format renders text snippets and
screenshot placeholders in its native style.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-17 00:39:01 -04:00
parent 312024e143
commit f7271e22ae
2 changed files with 76 additions and 8 deletions

View File

@@ -407,18 +407,35 @@ async def export_session(
headers={"Content-Disposition": f'attachment; filename="session-export-{session_id}.pdf"'},
)
# Query supporting data for non-PDF formats
from app.models.supporting_data import SessionSupportingData
sd_result = await db.execute(
select(SessionSupportingData)
.where(SessionSupportingData.session_id == session_id)
.order_by(SessionSupportingData.sort_order)
)
supporting_data_items = [
{
"label": sd.label,
"data_type": sd.data_type,
"content": sd.content,
"content_type": sd.content_type,
}
for sd in sd_result.scalars().all()
]
# Generate export based on format
if export_options.format == "markdown":
content = generate_markdown_export(session, export_options)
content = generate_markdown_export(session, export_options, supporting_data=supporting_data_items)
media_type = "text/markdown"
elif export_options.format == "html":
content = generate_html_export(session, export_options)
content = generate_html_export(session, export_options, supporting_data=supporting_data_items)
media_type = "text/html"
elif export_options.format == "psa":
content = generate_psa_export(session, export_options)
content = generate_psa_export(session, export_options, supporting_data=supporting_data_items)
media_type = "text/plain"
else: # text
content = generate_text_export(session, export_options)
content = generate_text_export(session, export_options, supporting_data=supporting_data_items)
media_type = "text/plain"
# Resolve variables in export output

View File

@@ -171,7 +171,7 @@ def _escape_markdown_table(value: str) -> str:
return value.replace("|", "\\|").replace("\n", " ")
def generate_markdown_export(session: Session, options: SessionExport) -> str:
def generate_markdown_export(session: Session, options: SessionExport, supporting_data: list[dict] | None = None) -> str:
"""Generate markdown export."""
if _is_procedural_session(session):
return _generate_procedural_markdown(session, options)
@@ -261,6 +261,22 @@ def generate_markdown_export(session: Session, options: SessionExport) -> str:
lines.append(f"*{decision['timestamp']}*")
lines.append("")
# Supporting Data
if supporting_data:
lines.append("---")
lines.append("")
lines.append("## Supporting Data")
lines.append("")
for sd in supporting_data:
lines.append(f"### {sd['label']}")
if sd["data_type"] == "text_snippet":
lines.append("```")
lines.append(sd["content"])
lines.append("```")
else:
lines.append(f"[Screenshot: {sd['label']}]")
lines.append("")
# Resolution / Outcome Notes
_raw_notes = getattr(session, 'outcome_notes', None)
outcome_notes = _raw_notes if isinstance(_raw_notes, str) else ''
@@ -286,7 +302,7 @@ def generate_markdown_export(session: Session, options: SessionExport) -> str:
return "\n".join(lines)
def generate_text_export(session: Session, options: SessionExport) -> str:
def generate_text_export(session: Session, options: SessionExport, supporting_data: list[dict] | None = None) -> str:
"""Generate plain text export."""
if _is_procedural_session(session):
return _generate_procedural_text(session, options)
@@ -361,6 +377,19 @@ def generate_text_export(session: Session, options: SessionExport) -> str:
if duration_seconds is not None:
lines.append(f" Duration: {_format_step_duration(duration_seconds)}")
# Supporting Data
if supporting_data:
lines.append("")
lines.append("SUPPORTING DATA")
lines.append("-" * 20)
for sd in supporting_data:
lines.append(f"\n {sd['label']}:")
if sd["data_type"] == "text_snippet":
for content_line in sd["content"].splitlines():
lines.append(f" {content_line}")
else:
lines.append(f" [Screenshot: {sd['label']}]")
# Resolution
_raw_notes = getattr(session, 'outcome_notes', None)
outcome_notes = _raw_notes if isinstance(_raw_notes, str) else ''
@@ -382,7 +411,7 @@ def generate_text_export(session: Session, options: SessionExport) -> str:
return "\n".join(lines)
def generate_html_export(session: Session, options: SessionExport) -> str:
def generate_html_export(session: Session, options: SessionExport, supporting_data: list[dict] | None = None) -> str:
"""Generate HTML export."""
if _is_procedural_session(session):
return _generate_procedural_html(session, options)
@@ -472,6 +501,15 @@ def generate_html_export(session: Session, options: SessionExport) -> str:
html_parts.append(f'<p class="timestamp">{html.escape(str(decision["timestamp"]))}</p>')
html_parts.append('</div>')
# Supporting Data
if supporting_data:
html_parts.append('<h2>Supporting Data</h2>')
for sd in supporting_data:
if sd["data_type"] == "text_snippet":
html_parts.append(f'<div class="supporting-item"><h3>{html.escape(sd["label"])}</h3><pre>{html.escape(sd["content"])}</pre></div>')
else:
html_parts.append(f'<div class="supporting-item"><h3>{html.escape(sd["label"])}</h3><img src="data:{html.escape(sd.get("content_type") or "image/png")};base64,{sd["content"]}" alt="{html.escape(sd["label"])}"></div>')
# Resolution
_raw_notes = getattr(session, 'outcome_notes', None)
outcome_notes = _raw_notes if isinstance(_raw_notes, str) else ''
@@ -490,7 +528,7 @@ def generate_html_export(session: Session, options: SessionExport) -> str:
return "\n".join(html_parts)
def generate_psa_export(session: Session, options: SessionExport) -> str:
def generate_psa_export(session: Session, options: SessionExport, supporting_data: list[dict] | None = None) -> str:
"""Generate PSA/ticket note export optimized for ConnectWise and similar PSA tools."""
if _is_procedural_session(session):
return _generate_procedural_psa(session, options)
@@ -561,6 +599,19 @@ def generate_psa_export(session: Session, options: SessionExport) -> str:
lines.append("No steps recorded.")
lines.append("")
# Supporting Data
if supporting_data:
lines.append("--- SUPPORTING DATA ---")
for sd in supporting_data:
if sd["data_type"] == "text_snippet":
lines.append(f"## {sd['label']}")
lines.append("```")
lines.append(sd["content"])
lines.append("```")
else:
lines.append(f"[Screenshot: {sd['label']}]")
lines.append("")
# Resolution — only for completed sessions
if session.completed_at:
lines.append("--- RESOLUTION ---")