from __future__ import annotations import html import json from pathlib import Path def _escape(value: object) -> str: return html.escape(str(value)) def build_accessible_session_text(session: dict) -> str: lines = [ "Didactopus Learner Session", "", f"Learner goal: {session.get('goal', '')}", "", "Study plan:", ] for index, step in enumerate(session.get("study_plan", {}).get("steps", []), start=1): lines.extend( [ f"{index}. {step.get('title', '')}", f" Status: {step.get('status', '')}", f" Prerequisites: {', '.join(step.get('prerequisite_titles', []) or ['none explicit'])}", f" Supporting lessons: {', '.join(step.get('supporting_lessons', []) or ['none listed'])}", ] ) for fragment in step.get("source_fragments", [])[:2]: lines.append(f" Source fragment ({fragment.get('kind', 'fragment')}): {fragment.get('text', '')}") lines.extend( [ "", "Conversation:", ] ) for turn in session.get("turns", []): lines.extend( [ f"{turn.get('label', turn.get('role', 'Turn'))}:", str(turn.get("content", "")), "", ] ) evaluation = session.get("evaluation", {}) lines.extend( [ "Evaluation summary:", f"Verdict: {evaluation.get('verdict', '')}", f"Aggregated dimensions: {json.dumps(evaluation.get('aggregated', {}), sort_keys=True)}", f"Follow-up: {evaluation.get('follow_up', '')}", ] ) return "\n".join(lines).strip() + "\n" def build_accessible_session_html(session: dict) -> str: steps = session.get("study_plan", {}).get("steps", []) turns = session.get("turns", []) evaluation = session.get("evaluation", {}) body = [ "", '', "", '', '', "Didactopus Learner Session", "", "", "", '', '
', '
', '

Didactopus Learner Session

', '

This page is structured for keyboard and screen-reader use. It presents the learner goal, study plan, grounded source fragments, and conversation turns in reading order.

', f"

Learner goal: {_escape(session.get('goal', ''))}

", "
", '
', '

Study Plan

', '
    ', ] for step in steps: body.append("
  1. ") body.append(f"

    {_escape(step.get('title', ''))}

    ") body.append(f"

    Status: {_escape(step.get('status', ''))}

    ") body.append( f"

    Prerequisites: {_escape(', '.join(step.get('prerequisite_titles', []) or ['none explicit']))}

    " ) body.append( f"

    Supporting lessons: {_escape(', '.join(step.get('supporting_lessons', []) or ['none listed']))}

    " ) fragments = step.get("source_fragments", [])[:2] if fragments: body.append("

    Grounding fragments:

    ") body.append("
      ") for fragment in fragments: body.append( f'
    • {_escape(fragment.get("lesson_title", ""))} ' f'({_escape(fragment.get("kind", "fragment"))})
      {_escape(fragment.get("text", ""))}
    • ' ) body.append("
    ") body.append("
  2. ") body.extend( [ "
", "
", '
', '

Conversation

', ] ) for turn in turns: body.append('
') body.append(f"

{_escape(turn.get('label', turn.get('role', 'Turn')))}

") body.append(f"

Role: {_escape(turn.get('role', ''))}

") body.append(f"

{_escape(turn.get('content', ''))}

") body.append("
") body.extend( [ "
", '
', '

Evaluation Summary

', f"

Verdict: {_escape(evaluation.get('verdict', ''))}

", f"

Aggregated dimensions: {_escape(json.dumps(evaluation.get('aggregated', {}), sort_keys=True))}

", f"

Follow-up: {_escape(evaluation.get('follow_up', ''))}

", "
", "
", "", "", ] ) return "\n".join(body) def render_accessible_session_outputs( session: dict, *, out_html: str | Path, out_text: str | Path, ) -> dict[str, str]: out_html = Path(out_html) out_text = Path(out_text) out_html.write_text(build_accessible_session_html(session), encoding="utf-8") out_text.write_text(build_accessible_session_text(session), encoding="utf-8") return {"html": str(out_html), "text": str(out_text)}