Initial learner accessibilty baseline.
This commit is contained in:
parent
51d2874e2f
commit
0f905b5a22
|
|
@ -30,6 +30,8 @@ Then open:
|
||||||
|
|
||||||
- `examples/ocw-information-entropy-run/learner_progress.html`
|
- `examples/ocw-information-entropy-run/learner_progress.html`
|
||||||
- `examples/ocw-information-entropy-session.json`
|
- `examples/ocw-information-entropy-session.json`
|
||||||
|
- `examples/ocw-information-entropy-session.html`
|
||||||
|
- `examples/ocw-information-entropy-session.txt`
|
||||||
- `examples/ocw-information-entropy-skill-demo/skill_demo.md`
|
- `examples/ocw-information-entropy-skill-demo/skill_demo.md`
|
||||||
- `examples/ocw-information-entropy-rolemesh-transcript/rolemesh_transcript.md`
|
- `examples/ocw-information-entropy-rolemesh-transcript/rolemesh_transcript.md`
|
||||||
- `skills/ocw-information-entropy-agent/`
|
- `skills/ocw-information-entropy-agent/`
|
||||||
|
|
@ -38,6 +40,7 @@ That gives you:
|
||||||
|
|
||||||
- a generated topic pack
|
- a generated topic pack
|
||||||
- a graph-grounded mentor/practice/evaluator learner session
|
- a graph-grounded mentor/practice/evaluator learner session
|
||||||
|
- accessible HTML and text-first learner-session outputs
|
||||||
- a visible learning path
|
- a visible learning path
|
||||||
- progress artifacts
|
- progress artifacts
|
||||||
- a reusable skill grounded in the exported knowledge
|
- a reusable skill grounded in the exported knowledge
|
||||||
|
|
@ -182,6 +185,11 @@ That demo builds a graph-grounded session from the MIT OCW skill bundle and emit
|
||||||
|
|
||||||
The point of this module is architectural as much as demonstrational: it is the session core that future accessibility, model-benchmark, and voice-interaction work should build on.
|
The point of this module is architectural as much as demonstrational: it is the session core that future accessibility, model-benchmark, and voice-interaction work should build on.
|
||||||
|
|
||||||
|
The learner-session demo also writes accessible companion outputs:
|
||||||
|
|
||||||
|
- `examples/ocw-information-entropy-session.html`
|
||||||
|
- `examples/ocw-information-entropy-session.txt`
|
||||||
|
|
||||||
The first benchmark harness for that session core is now:
|
The first benchmark harness for that session core is now:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -441,6 +449,7 @@ What remains heuristic or lightweight:
|
||||||
## Recommended Reading
|
## Recommended Reading
|
||||||
|
|
||||||
- [docs/roadmap.md](docs/roadmap.md)
|
- [docs/roadmap.md](docs/roadmap.md)
|
||||||
|
- [docs/learner-accessibility.md](docs/learner-accessibility.md)
|
||||||
- [docs/local-model-benchmark.md](docs/local-model-benchmark.md)
|
- [docs/local-model-benchmark.md](docs/local-model-benchmark.md)
|
||||||
- [docs/course-to-pack.md](docs/course-to-pack.md)
|
- [docs/course-to-pack.md](docs/course-to-pack.md)
|
||||||
- [docs/learning-graph.md](docs/learning-graph.md)
|
- [docs/learning-graph.md](docs/learning-graph.md)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Learner Accessibility
|
||||||
|
|
||||||
|
Didactopus should make the learner loop usable without assuming visual graph navigation or silent waiting on slow local models.
|
||||||
|
|
||||||
|
The current accessibility baseline is built on the graph-grounded learner session backend.
|
||||||
|
|
||||||
|
## Current Outputs
|
||||||
|
|
||||||
|
Running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m didactopus.learner_session_demo
|
||||||
|
```
|
||||||
|
|
||||||
|
now writes:
|
||||||
|
|
||||||
|
- `examples/ocw-information-entropy-session.json`
|
||||||
|
- `examples/ocw-information-entropy-session.html`
|
||||||
|
- `examples/ocw-information-entropy-session.txt`
|
||||||
|
|
||||||
|
## What The Accessible Outputs Do
|
||||||
|
|
||||||
|
The HTML output is meant to be screen-reader-friendly and keyboard-friendly:
|
||||||
|
|
||||||
|
- skip link to the main content
|
||||||
|
- semantic headings
|
||||||
|
- reading-order sections for study plan, conversation, and evaluation
|
||||||
|
- grounded source fragments rendered as ordinary text instead of only visual diagrams
|
||||||
|
|
||||||
|
The plain-text output is a linearized learner-session transcript that is suitable for:
|
||||||
|
|
||||||
|
- terminal reading
|
||||||
|
- screen-reader reading
|
||||||
|
- low-bandwidth sharing
|
||||||
|
- future text-to-speech pipelines
|
||||||
|
|
||||||
|
## Why This Matters
|
||||||
|
|
||||||
|
Didactopus should help learners work with structure, not just with pictures and dashboards.
|
||||||
|
|
||||||
|
This is especially important for:
|
||||||
|
|
||||||
|
- blind learners
|
||||||
|
- screen-reader users
|
||||||
|
- learners on low-power hardware
|
||||||
|
- situations where audio or text needs to be generated locally
|
||||||
|
|
||||||
|
## Relationship To The Roadmap
|
||||||
|
|
||||||
|
This is the accessibility baseline, not the endpoint.
|
||||||
|
|
||||||
|
Likely next steps:
|
||||||
|
|
||||||
|
- local text-to-speech for mentor, practice, and evaluator turns
|
||||||
|
- speech-to-text for learner answers
|
||||||
|
- explicit spoken structural cues
|
||||||
|
- text-first alternatives for more generated visualizations
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Didactopus Learner Session</title>
|
||||||
|
<style>
|
||||||
|
:root { color-scheme: light; --bg: #f7f4ed; --panel: #fffdf8; --ink: #1e2b31; --muted: #53656d; --line: #d3c8b7; --accent: #155e63; }
|
||||||
|
body { margin: 0; font-family: Georgia, 'Times New Roman', serif; background: var(--bg); color: var(--ink); line-height: 1.55; }
|
||||||
|
a { color: var(--accent); }
|
||||||
|
.skip { position: absolute; left: 12px; top: 12px; background: #fff; padding: 8px 10px; border: 1px solid var(--line); }
|
||||||
|
main { max-width: 980px; margin: 0 auto; padding: 24px; }
|
||||||
|
section { background: var(--panel); border: 1px solid var(--line); border-radius: 16px; padding: 20px; margin-bottom: 18px; }
|
||||||
|
h1, h2, h3 { line-height: 1.2; }
|
||||||
|
ol, ul { padding-left: 22px; }
|
||||||
|
.meta { color: var(--muted); }
|
||||||
|
.turn { border-top: 1px solid var(--line); padding-top: 12px; margin-top: 12px; }
|
||||||
|
.turn:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }
|
||||||
|
.fragment { background: #f3efe5; padding: 10px; border-radius: 10px; margin: 8px 0; }
|
||||||
|
.sr-note { color: var(--muted); font-size: 0.95rem; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a class="skip" href="#session-main">Skip to learner session</a>
|
||||||
|
<main id="session-main" aria-label="Didactopus learner session">
|
||||||
|
<section aria-labelledby="session-title">
|
||||||
|
<h1 id="session-title">Didactopus Learner Session</h1>
|
||||||
|
<p class="sr-note">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.</p>
|
||||||
|
<p><strong>Learner goal:</strong> Help me understand how Shannon entropy leads into channel capacity and thermodynamic entropy.</p>
|
||||||
|
</section>
|
||||||
|
<section aria-labelledby="study-plan-title">
|
||||||
|
<h2 id="study-plan-title">Study Plan</h2>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<h3>Independent Reasoning and Careful Comparison</h3>
|
||||||
|
<p><strong>Status:</strong> mastered</p>
|
||||||
|
<p><strong>Prerequisites:</strong> Course Notes and Reference Texts</p>
|
||||||
|
<p><strong>Supporting lessons:</strong> Independent Reasoning and Careful Comparison</p>
|
||||||
|
<p><strong>Grounding fragments:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><div class="fragment"><strong>Independent Reasoning and Careful Comparison</strong> (lesson_body)<br>- Objective: Explain why the course requires precise comparison of related but non-identical concepts.
|
||||||
|
- Exercise: Write a short note distinguishing Shannon entropy, channel capacity, and thermodynamic entropy.
|
||||||
|
The syllabus framing implies a style of work where analogy is useful but dangerous when used loosely. Learners must compare models carefully, state assumptions, and notice where similar mathematics does not imply identical interpretation.</div></li>
|
||||||
|
<li><div class="fragment"><strong>Independent Reasoning and Careful Comparison</strong> (objective)<br>Explain why the course requires precise comparison of related but non-identical concepts.</div></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3>Thermodynamics and Entropy</h3>
|
||||||
|
<p><strong>Status:</strong> mastered</p>
|
||||||
|
<p><strong>Prerequisites:</strong> Cryptography and Information Hiding</p>
|
||||||
|
<p><strong>Supporting lessons:</strong> Thermodynamics and Entropy</p>
|
||||||
|
<p><strong>Grounding fragments:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><div class="fragment"><strong>Thermodynamics and Entropy</strong> (lesson_body)<br>- Objective: Explain how thermodynamic entropy relates to, and differs from, Shannon entropy.
|
||||||
|
- Exercise: Compare the two entropy notions and identify what is preserved across the analogy.
|
||||||
|
The course uses entropy as a bridge concept between communication theory and physics while insisting on careful interpretation.</div></li>
|
||||||
|
<li><div class="fragment"><strong>Thermodynamics and Entropy</strong> (objective)<br>Explain how thermodynamic entropy relates to, and differs from, Shannon entropy.</div></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3>Shannon Entropy</h3>
|
||||||
|
<p><strong>Status:</strong> mastered</p>
|
||||||
|
<p><strong>Prerequisites:</strong> Counting and Probability</p>
|
||||||
|
<p><strong>Supporting lessons:</strong> Shannon Entropy</p>
|
||||||
|
<p><strong>Grounding fragments:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><div class="fragment"><strong>Shannon Entropy</strong> (lesson_body)<br>- Objective: Explain Shannon entropy as a measure of uncertainty and compare high-entropy and low-entropy sources.
|
||||||
|
- Exercise: Compute the entropy of a Bernoulli source and interpret the result.
|
||||||
|
The course then introduces entropy as a quantitative measure of uncertainty for a source model and uses it to reason about representation cost and surprise.</div></li>
|
||||||
|
<li><div class="fragment"><strong>Shannon Entropy</strong> (objective)<br>Explain Shannon entropy as a measure of uncertainty and compare high-entropy and low-entropy sources.</div></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
<section aria-labelledby="conversation-title">
|
||||||
|
<h2 id="conversation-title">Conversation</h2>
|
||||||
|
<article class="turn" aria-label="Conversation turn">
|
||||||
|
<h3>Learner Goal</h3>
|
||||||
|
<p class="meta">Role: user</p>
|
||||||
|
<p>Help me understand how Shannon entropy leads into channel capacity and thermodynamic entropy.</p>
|
||||||
|
</article>
|
||||||
|
<article class="turn" aria-label="Conversation turn">
|
||||||
|
<h3>Didactopus Mentor</h3>
|
||||||
|
<p class="meta">Role: assistant</p>
|
||||||
|
<p>[stubbed-response] [mentor] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons</p>
|
||||||
|
</article>
|
||||||
|
<article class="turn" aria-label="Conversation turn">
|
||||||
|
<h3>Didactopus Practice Designer</h3>
|
||||||
|
<p class="meta">Role: assistant</p>
|
||||||
|
<p>[stubbed-response] [practice] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons</p>
|
||||||
|
</article>
|
||||||
|
<article class="turn" aria-label="Conversation turn">
|
||||||
|
<h3>Learner Submission</h3>
|
||||||
|
<p class="meta">Role: user</p>
|
||||||
|
<p>Entropy measures uncertainty because more possible outcomes require more information to describe, but one limitation is that thermodynamic entropy is not identical to Shannon entropy.</p>
|
||||||
|
</article>
|
||||||
|
<article class="turn" aria-label="Conversation turn">
|
||||||
|
<h3>Didactopus Evaluator</h3>
|
||||||
|
<p class="meta">Role: assistant</p>
|
||||||
|
<p>[stubbed-response] [evaluator] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons</p>
|
||||||
|
</article>
|
||||||
|
<article class="turn" aria-label="Conversation turn">
|
||||||
|
<h3>Didactopus Mentor</h3>
|
||||||
|
<p class="meta">Role: assistant</p>
|
||||||
|
<p>[stubbed-response] [mentor] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons</p>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
<section aria-labelledby="evaluation-title">
|
||||||
|
<h2 id="evaluation-title">Evaluation Summary</h2>
|
||||||
|
<p><strong>Verdict:</strong> needs_revision</p>
|
||||||
|
<p><strong>Aggregated dimensions:</strong> {"correctness": 0.6000000000000001, "critique": 0.6499999999999999, "explanation": 0.85}</p>
|
||||||
|
<p><strong>Follow-up:</strong> Rework the answer so it states the equality/relationship explicitly and explains why it matters.</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
Didactopus Learner Session
|
||||||
|
|
||||||
|
Learner goal: Help me understand how Shannon entropy leads into channel capacity and thermodynamic entropy.
|
||||||
|
|
||||||
|
Study plan:
|
||||||
|
1. Independent Reasoning and Careful Comparison
|
||||||
|
Status: mastered
|
||||||
|
Prerequisites: Course Notes and Reference Texts
|
||||||
|
Supporting lessons: Independent Reasoning and Careful Comparison
|
||||||
|
Source fragment (lesson_body): - Objective: Explain why the course requires precise comparison of related but non-identical concepts.
|
||||||
|
- Exercise: Write a short note distinguishing Shannon entropy, channel capacity, and thermodynamic entropy.
|
||||||
|
The syllabus framing implies a style of work where analogy is useful but dangerous when used loosely. Learners must compare models carefully, state assumptions, and notice where similar mathematics does not imply identical interpretation.
|
||||||
|
Source fragment (objective): Explain why the course requires precise comparison of related but non-identical concepts.
|
||||||
|
2. Thermodynamics and Entropy
|
||||||
|
Status: mastered
|
||||||
|
Prerequisites: Cryptography and Information Hiding
|
||||||
|
Supporting lessons: Thermodynamics and Entropy
|
||||||
|
Source fragment (lesson_body): - Objective: Explain how thermodynamic entropy relates to, and differs from, Shannon entropy.
|
||||||
|
- Exercise: Compare the two entropy notions and identify what is preserved across the analogy.
|
||||||
|
The course uses entropy as a bridge concept between communication theory and physics while insisting on careful interpretation.
|
||||||
|
Source fragment (objective): Explain how thermodynamic entropy relates to, and differs from, Shannon entropy.
|
||||||
|
3. Shannon Entropy
|
||||||
|
Status: mastered
|
||||||
|
Prerequisites: Counting and Probability
|
||||||
|
Supporting lessons: Shannon Entropy
|
||||||
|
Source fragment (lesson_body): - Objective: Explain Shannon entropy as a measure of uncertainty and compare high-entropy and low-entropy sources.
|
||||||
|
- Exercise: Compute the entropy of a Bernoulli source and interpret the result.
|
||||||
|
The course then introduces entropy as a quantitative measure of uncertainty for a source model and uses it to reason about representation cost and surprise.
|
||||||
|
Source fragment (objective): Explain Shannon entropy as a measure of uncertainty and compare high-entropy and low-entropy sources.
|
||||||
|
|
||||||
|
Conversation:
|
||||||
|
Learner Goal:
|
||||||
|
Help me understand how Shannon entropy leads into channel capacity and thermodynamic entropy.
|
||||||
|
|
||||||
|
Didactopus Mentor:
|
||||||
|
[stubbed-response] [mentor] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons
|
||||||
|
|
||||||
|
Didactopus Practice Designer:
|
||||||
|
[stubbed-response] [practice] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons
|
||||||
|
|
||||||
|
Learner Submission:
|
||||||
|
Entropy measures uncertainty because more possible outcomes require more information to describe, but one limitation is that thermodynamic entropy is not identical to Shannon entropy.
|
||||||
|
|
||||||
|
Didactopus Evaluator:
|
||||||
|
[stubbed-response] [evaluator] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons
|
||||||
|
|
||||||
|
Didactopus Mentor:
|
||||||
|
[stubbed-response] [mentor] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons
|
||||||
|
|
||||||
|
Evaluation summary:
|
||||||
|
Verdict: needs_revision
|
||||||
|
Aggregated dimensions: {"correctness": 0.6000000000000001, "critique": 0.6499999999999999, "explanation": 0.85}
|
||||||
|
Follow-up: Rework the answer so it states the equality/relationship explicitly and explains why it matters.
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
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 = [
|
||||||
|
"<!doctype html>",
|
||||||
|
'<html lang="en">',
|
||||||
|
"<head>",
|
||||||
|
'<meta charset="utf-8">',
|
||||||
|
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
||||||
|
"<title>Didactopus Learner Session</title>",
|
||||||
|
"<style>",
|
||||||
|
":root { color-scheme: light; --bg: #f7f4ed; --panel: #fffdf8; --ink: #1e2b31; --muted: #53656d; --line: #d3c8b7; --accent: #155e63; }",
|
||||||
|
"body { margin: 0; font-family: Georgia, 'Times New Roman', serif; background: var(--bg); color: var(--ink); line-height: 1.55; }",
|
||||||
|
"a { color: var(--accent); }",
|
||||||
|
".skip { position: absolute; left: 12px; top: 12px; background: #fff; padding: 8px 10px; border: 1px solid var(--line); }",
|
||||||
|
"main { max-width: 980px; margin: 0 auto; padding: 24px; }",
|
||||||
|
"section { background: var(--panel); border: 1px solid var(--line); border-radius: 16px; padding: 20px; margin-bottom: 18px; }",
|
||||||
|
"h1, h2, h3 { line-height: 1.2; }",
|
||||||
|
"ol, ul { padding-left: 22px; }",
|
||||||
|
".meta { color: var(--muted); }",
|
||||||
|
".turn { border-top: 1px solid var(--line); padding-top: 12px; margin-top: 12px; }",
|
||||||
|
".turn:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }",
|
||||||
|
".fragment { background: #f3efe5; padding: 10px; border-radius: 10px; margin: 8px 0; }",
|
||||||
|
".sr-note { color: var(--muted); font-size: 0.95rem; }",
|
||||||
|
"</style>",
|
||||||
|
"</head>",
|
||||||
|
"<body>",
|
||||||
|
'<a class="skip" href="#session-main">Skip to learner session</a>',
|
||||||
|
'<main id="session-main" aria-label="Didactopus learner session">',
|
||||||
|
'<section aria-labelledby="session-title">',
|
||||||
|
'<h1 id="session-title">Didactopus Learner Session</h1>',
|
||||||
|
'<p class="sr-note">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.</p>',
|
||||||
|
f"<p><strong>Learner goal:</strong> {_escape(session.get('goal', ''))}</p>",
|
||||||
|
"</section>",
|
||||||
|
'<section aria-labelledby="study-plan-title">',
|
||||||
|
'<h2 id="study-plan-title">Study Plan</h2>',
|
||||||
|
'<ol>',
|
||||||
|
]
|
||||||
|
for step in steps:
|
||||||
|
body.append("<li>")
|
||||||
|
body.append(f"<h3>{_escape(step.get('title', ''))}</h3>")
|
||||||
|
body.append(f"<p><strong>Status:</strong> {_escape(step.get('status', ''))}</p>")
|
||||||
|
body.append(
|
||||||
|
f"<p><strong>Prerequisites:</strong> {_escape(', '.join(step.get('prerequisite_titles', []) or ['none explicit']))}</p>"
|
||||||
|
)
|
||||||
|
body.append(
|
||||||
|
f"<p><strong>Supporting lessons:</strong> {_escape(', '.join(step.get('supporting_lessons', []) or ['none listed']))}</p>"
|
||||||
|
)
|
||||||
|
fragments = step.get("source_fragments", [])[:2]
|
||||||
|
if fragments:
|
||||||
|
body.append("<p><strong>Grounding fragments:</strong></p>")
|
||||||
|
body.append("<ul>")
|
||||||
|
for fragment in fragments:
|
||||||
|
body.append(
|
||||||
|
f'<li><div class="fragment"><strong>{_escape(fragment.get("lesson_title", ""))}</strong> '
|
||||||
|
f'({_escape(fragment.get("kind", "fragment"))})<br>{_escape(fragment.get("text", ""))}</div></li>'
|
||||||
|
)
|
||||||
|
body.append("</ul>")
|
||||||
|
body.append("</li>")
|
||||||
|
body.extend(
|
||||||
|
[
|
||||||
|
"</ol>",
|
||||||
|
"</section>",
|
||||||
|
'<section aria-labelledby="conversation-title">',
|
||||||
|
'<h2 id="conversation-title">Conversation</h2>',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for turn in turns:
|
||||||
|
body.append('<article class="turn" aria-label="Conversation turn">')
|
||||||
|
body.append(f"<h3>{_escape(turn.get('label', turn.get('role', 'Turn')))}</h3>")
|
||||||
|
body.append(f"<p class=\"meta\">Role: {_escape(turn.get('role', ''))}</p>")
|
||||||
|
body.append(f"<p>{_escape(turn.get('content', ''))}</p>")
|
||||||
|
body.append("</article>")
|
||||||
|
body.extend(
|
||||||
|
[
|
||||||
|
"</section>",
|
||||||
|
'<section aria-labelledby="evaluation-title">',
|
||||||
|
'<h2 id="evaluation-title">Evaluation Summary</h2>',
|
||||||
|
f"<p><strong>Verdict:</strong> {_escape(evaluation.get('verdict', ''))}</p>",
|
||||||
|
f"<p><strong>Aggregated dimensions:</strong> {_escape(json.dumps(evaluation.get('aggregated', {}), sort_keys=True))}</p>",
|
||||||
|
f"<p><strong>Follow-up:</strong> {_escape(evaluation.get('follow_up', ''))}</p>",
|
||||||
|
"</section>",
|
||||||
|
"</main>",
|
||||||
|
"</body>",
|
||||||
|
"</html>",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
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)}
|
||||||
|
|
@ -4,6 +4,7 @@ import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
|
from .learner_accessibility import render_accessible_session_outputs
|
||||||
from .learner_session import build_graph_grounded_session
|
from .learner_session import build_graph_grounded_session
|
||||||
from .model_provider import ModelProvider
|
from .model_provider import ModelProvider
|
||||||
from .ocw_skill_agent_demo import load_ocw_skill_context
|
from .ocw_skill_agent_demo import load_ocw_skill_context
|
||||||
|
|
@ -13,6 +14,8 @@ def run_learner_session_demo(
|
||||||
config_path: str | Path,
|
config_path: str | Path,
|
||||||
skill_dir: str | Path,
|
skill_dir: str | Path,
|
||||||
out_path: str | Path | None = None,
|
out_path: str | Path | None = None,
|
||||||
|
accessible_html_path: str | Path | None = None,
|
||||||
|
accessible_text_path: str | Path | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
config = load_config(config_path)
|
config = load_config(config_path)
|
||||||
provider = ModelProvider(config.model_provider)
|
provider = ModelProvider(config.model_provider)
|
||||||
|
|
@ -24,7 +27,11 @@ def run_learner_session_demo(
|
||||||
learner_submission="Entropy measures uncertainty because more possible outcomes require more information to describe, but one limitation is that thermodynamic entropy is not identical to Shannon entropy.",
|
learner_submission="Entropy measures uncertainty because more possible outcomes require more information to describe, but one limitation is that thermodynamic entropy is not identical to Shannon entropy.",
|
||||||
)
|
)
|
||||||
if out_path is not None:
|
if out_path is not None:
|
||||||
Path(out_path).write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
out_path = Path(out_path)
|
||||||
|
out_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
||||||
|
html_path = Path(accessible_html_path) if accessible_html_path is not None else out_path.with_suffix(".html")
|
||||||
|
text_path = Path(accessible_text_path) if accessible_text_path is not None else out_path.with_suffix(".txt")
|
||||||
|
render_accessible_session_outputs(payload, out_html=html_path, out_text=text_path)
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,8 +43,16 @@ def main() -> None:
|
||||||
parser.add_argument("--config", default=str(root / "configs" / "config.example.yaml"))
|
parser.add_argument("--config", default=str(root / "configs" / "config.example.yaml"))
|
||||||
parser.add_argument("--skill-dir", default=str(root / "skills" / "ocw-information-entropy-agent"))
|
parser.add_argument("--skill-dir", default=str(root / "skills" / "ocw-information-entropy-agent"))
|
||||||
parser.add_argument("--out", default=str(root / "examples" / "ocw-information-entropy-session.json"))
|
parser.add_argument("--out", default=str(root / "examples" / "ocw-information-entropy-session.json"))
|
||||||
|
parser.add_argument("--accessible-html", default=None)
|
||||||
|
parser.add_argument("--accessible-text", default=None)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
payload = run_learner_session_demo(args.config, args.skill_dir, args.out)
|
payload = run_learner_session_demo(
|
||||||
|
args.config,
|
||||||
|
args.skill_dir,
|
||||||
|
args.out,
|
||||||
|
args.accessible_html,
|
||||||
|
args.accessible_text,
|
||||||
|
)
|
||||||
print(json.dumps(payload, indent=2))
|
print(json.dumps(payload, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from didactopus.learner_accessibility import (
|
||||||
|
build_accessible_session_html,
|
||||||
|
build_accessible_session_text,
|
||||||
|
render_accessible_session_outputs,
|
||||||
|
)
|
||||||
|
from didactopus.learner_session_demo import run_learner_session_demo
|
||||||
|
|
||||||
|
|
||||||
|
def _session_payload() -> dict:
|
||||||
|
root = Path(__file__).resolve().parents[1]
|
||||||
|
return run_learner_session_demo(
|
||||||
|
root / "configs" / "config.example.yaml",
|
||||||
|
root / "skills" / "ocw-information-entropy-agent",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_accessible_session_html_has_landmarks() -> None:
|
||||||
|
html = build_accessible_session_html(_session_payload())
|
||||||
|
assert 'href="#session-main"' in html
|
||||||
|
assert 'aria-label="Didactopus learner session"' in html
|
||||||
|
assert "Study Plan" in html
|
||||||
|
assert "Conversation" in html
|
||||||
|
assert "Evaluation Summary" in html
|
||||||
|
|
||||||
|
|
||||||
|
def test_accessible_session_text_is_linearized() -> None:
|
||||||
|
text = build_accessible_session_text(_session_payload())
|
||||||
|
assert "Learner goal:" in text
|
||||||
|
assert "Study plan:" in text
|
||||||
|
assert "Conversation:" in text
|
||||||
|
assert "Evaluation summary:" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_accessible_session_outputs_writes_files(tmp_path: Path) -> None:
|
||||||
|
outputs = render_accessible_session_outputs(
|
||||||
|
_session_payload(),
|
||||||
|
out_html=tmp_path / "session.html",
|
||||||
|
out_text=tmp_path / "session.txt",
|
||||||
|
)
|
||||||
|
assert Path(outputs["html"]).exists()
|
||||||
|
assert Path(outputs["text"]).exists()
|
||||||
|
|
@ -35,5 +35,7 @@ def test_run_learner_session_demo_writes_output(tmp_path: Path) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
assert (tmp_path / "session.json").exists()
|
assert (tmp_path / "session.json").exists()
|
||||||
|
assert (tmp_path / "session.html").exists()
|
||||||
|
assert (tmp_path / "session.txt").exists()
|
||||||
assert payload["practice_task"]
|
assert payload["practice_task"]
|
||||||
assert payload["evaluation"]["aggregated"]
|
assert payload["evaluation"]["aggregated"]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue