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-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-rolemesh-transcript/rolemesh_transcript.md`
|
||||
- `skills/ocw-information-entropy-agent/`
|
||||
|
|
@ -38,6 +40,7 @@ That gives you:
|
|||
|
||||
- a generated topic pack
|
||||
- a graph-grounded mentor/practice/evaluator learner session
|
||||
- accessible HTML and text-first learner-session outputs
|
||||
- a visible learning path
|
||||
- progress artifacts
|
||||
- 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 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:
|
||||
|
||||
```bash
|
||||
|
|
@ -441,6 +449,7 @@ What remains heuristic or lightweight:
|
|||
## Recommended Reading
|
||||
|
||||
- [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/course-to-pack.md](docs/course-to-pack.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 .config import load_config
|
||||
from .learner_accessibility import render_accessible_session_outputs
|
||||
from .learner_session import build_graph_grounded_session
|
||||
from .model_provider import ModelProvider
|
||||
from .ocw_skill_agent_demo import load_ocw_skill_context
|
||||
|
|
@ -13,6 +14,8 @@ def run_learner_session_demo(
|
|||
config_path: str | Path,
|
||||
skill_dir: str | Path,
|
||||
out_path: str | Path | None = None,
|
||||
accessible_html_path: str | Path | None = None,
|
||||
accessible_text_path: str | Path | None = None,
|
||||
) -> dict:
|
||||
config = load_config(config_path)
|
||||
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.",
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -36,8 +43,16 @@ def main() -> None:
|
|||
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("--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()
|
||||
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))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.html").exists()
|
||||
assert (tmp_path / "session.txt").exists()
|
||||
assert payload["practice_task"]
|
||||
assert payload["evaluation"]["aggregated"]
|
||||
|
|
|
|||
Loading…
Reference in New Issue