First pass at knowledge graph learner loop improvements.
This commit is contained in:
parent
41ca57d60f
commit
d6268260c3
24
README.md
24
README.md
|
|
@ -21,6 +21,7 @@ If you only want the shortest path to "show me Didactopus helping someone learn,
|
|||
```bash
|
||||
pip install -e .
|
||||
python -m didactopus.ocw_information_entropy_demo
|
||||
python -m didactopus.learner_session_demo
|
||||
python -m didactopus.ocw_progress_viz
|
||||
python -m didactopus.ocw_skill_agent_demo
|
||||
```
|
||||
|
|
@ -28,6 +29,7 @@ python -m didactopus.ocw_skill_agent_demo
|
|||
Then open:
|
||||
|
||||
- `examples/ocw-information-entropy-run/learner_progress.html`
|
||||
- `examples/ocw-information-entropy-session.json`
|
||||
- `examples/ocw-information-entropy-skill-demo/skill_demo.md`
|
||||
- `examples/ocw-information-entropy-rolemesh-transcript/rolemesh_transcript.md`
|
||||
- `skills/ocw-information-entropy-agent/`
|
||||
|
|
@ -35,6 +37,7 @@ Then open:
|
|||
That gives you:
|
||||
|
||||
- a generated topic pack
|
||||
- a graph-grounded mentor/practice/evaluator learner session
|
||||
- a visible learning path
|
||||
- progress artifacts
|
||||
- a reusable skill grounded in the exported knowledge
|
||||
|
|
@ -67,6 +70,7 @@ python -m didactopus.ocw_information_entropy_demo
|
|||
3. Render the learner progress views:
|
||||
|
||||
```bash
|
||||
python -m didactopus.learner_session_demo
|
||||
python -m didactopus.ocw_progress_viz
|
||||
python -m didactopus.ocw_progress_viz --full-map
|
||||
```
|
||||
|
|
@ -88,6 +92,7 @@ What you get:
|
|||
- a domain pack for the topic
|
||||
- a guided curriculum path
|
||||
- a deterministic learner run over that path
|
||||
- a graph-grounded learner session with mentor, practice, evaluation, and next-step turns
|
||||
- a capability export
|
||||
- a reusable skill bundle
|
||||
- visual progress artifacts
|
||||
|
|
@ -159,6 +164,24 @@ Instead of only reading notes, you can get:
|
|||
|
||||
In the best case, that makes learning feel more like active skill-building and less like either passive consumption or answer outsourcing.
|
||||
|
||||
### Current learner-session backbone
|
||||
|
||||
The main mentor-style backend now has a dedicated demo entry point:
|
||||
|
||||
```bash
|
||||
python -m didactopus.learner_session_demo
|
||||
```
|
||||
|
||||
That demo builds a graph-grounded session from the MIT OCW skill bundle and emits:
|
||||
|
||||
- a learner goal
|
||||
- a grounded mentor response
|
||||
- a practice prompt
|
||||
- evaluator feedback
|
||||
- a recommended next step
|
||||
|
||||
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.
|
||||
|
||||
## What Is In This Repository
|
||||
|
||||
- `src/didactopus/`
|
||||
|
|
@ -409,6 +432,7 @@ What remains heuristic or lightweight:
|
|||
|
||||
## Recommended Reading
|
||||
|
||||
- [docs/roadmap.md](docs/roadmap.md)
|
||||
- [docs/course-to-pack.md](docs/course-to-pack.md)
|
||||
- [docs/learning-graph.md](docs/learning-graph.md)
|
||||
- [docs/agentic-learner-loop.md](docs/agentic-learner-loop.md)
|
||||
|
|
|
|||
|
|
@ -25,10 +25,13 @@ Simple packs stored under `domain-packs/` for development, examples, and curated
|
|||
### 2. External Git repositories
|
||||
A contributor can publish a domain pack as its own Git repository and users can clone or vendor it.
|
||||
|
||||
Didactopus can also use an external Git repository as a source-material repository rather than only as a finished pack repository. The recommended pattern is one repository per course, with a root `didactopus-course.yaml` manifest plus a local source tree and `sources.yaml`.
|
||||
|
||||
Example patterns:
|
||||
- `didactopus-pack-statistics`
|
||||
- `didactopus-pack-electronics`
|
||||
- `didactopus-pack-evolutionary-biology`
|
||||
- `didactopus-mit-ocw-6-050j`
|
||||
|
||||
### 3. Package index model
|
||||
Eventually, packs could be distributed through a registry or package index. A manifest should identify:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
# Course Repositories
|
||||
|
||||
Didactopus can ingest a checked-out course repository as a local source bundle.
|
||||
|
||||
## Recommended default
|
||||
|
||||
Use one repository per course-derived source set.
|
||||
|
||||
That keeps:
|
||||
|
||||
- license scope clear
|
||||
- `sources.yaml` course-specific
|
||||
- attribution and compliance artifacts isolated
|
||||
- Git history focused on one source set
|
||||
- removal or relicensing easier if a source needs to be withdrawn
|
||||
|
||||
## Recommended repository shape
|
||||
|
||||
```text
|
||||
didactopus-mit-ocw-6-050j/
|
||||
didactopus-course.yaml
|
||||
course/
|
||||
course-home.md
|
||||
syllabus.md
|
||||
unit-sequence.md
|
||||
sources.yaml
|
||||
README.md
|
||||
```
|
||||
|
||||
## Manifest file
|
||||
|
||||
The root manifest is `didactopus-course.yaml`.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
course_id: mit-ocw-information-entropy
|
||||
display_name: MIT OCW Information and Entropy
|
||||
source_dir: course
|
||||
source_inventory: sources.yaml
|
||||
license_family: CC BY-NC-SA 4.0
|
||||
generated_pack_dir: ../../domain-packs/mit-ocw-information-entropy
|
||||
generated_run_dir: ../../examples/ocw-information-entropy-run
|
||||
generated_skill_dir: ../../skills/ocw-information-entropy-agent
|
||||
```
|
||||
|
||||
## How Didactopus uses it
|
||||
|
||||
Didactopus treats the repo as a normal local source directory. The manifest only resolves which paths to use.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
python -m didactopus.ocw_information_entropy_demo \
|
||||
--course-repo /path/to/didactopus-mit-ocw-6-050j
|
||||
```
|
||||
|
||||
That command resolves:
|
||||
|
||||
- the source directory
|
||||
- the source inventory
|
||||
- the generated pack directory
|
||||
- the generated run directory
|
||||
- the generated skill directory
|
||||
|
||||
all relative to the checked-out repository.
|
||||
|
||||
If you want Didactopus to create the repository structure and copy the current source bundle into it, use:
|
||||
|
||||
```bash
|
||||
python -m didactopus.ocw_information_entropy_demo \
|
||||
--course-repo-target /path/to/new-course-repo
|
||||
```
|
||||
|
||||
That bootstraps:
|
||||
|
||||
- `didactopus-course.yaml`
|
||||
- `course/`
|
||||
- `sources.yaml`
|
||||
- `generated/pack/`
|
||||
- `generated/run/`
|
||||
- `generated/skill/`
|
||||
|
||||
and then runs ingestion against that new course-repository directory.
|
||||
|
||||
## Multi-course repositories
|
||||
|
||||
Do not use a multi-course repository as the default pattern.
|
||||
|
||||
If you later need one, treat it only as a container of isolated per-course subtrees:
|
||||
|
||||
```text
|
||||
ocw-collection/
|
||||
courses/
|
||||
course-a/
|
||||
didactopus-course.yaml
|
||||
course/
|
||||
sources.yaml
|
||||
course-b/
|
||||
didactopus-course.yaml
|
||||
course/
|
||||
sources.yaml
|
||||
```
|
||||
|
||||
Each course should still keep its own:
|
||||
|
||||
- `didactopus-course.yaml`
|
||||
- `sources.yaml`
|
||||
- source tree
|
||||
- generated attribution/compliance outputs
|
||||
|
||||
## MIT OCW example
|
||||
|
||||
The current in-repo reference example is:
|
||||
|
||||
- [didactopus-course.yaml](/home/netuser/dev/Didactopustry1/examples/ocw-information-entropy/didactopus-course.yaml)
|
||||
- [course](/home/netuser/dev/Didactopustry1/examples/ocw-information-entropy/course)
|
||||
- [sources.yaml](/home/netuser/dev/Didactopustry1/examples/ocw-information-entropy/sources.yaml)
|
||||
24
docs/faq.md
24
docs/faq.md
|
|
@ -37,6 +37,7 @@ Run:
|
|||
```bash
|
||||
pip install -e .
|
||||
python -m didactopus.ocw_information_entropy_demo
|
||||
python -m didactopus.learner_session_demo
|
||||
python -m didactopus.ocw_progress_viz
|
||||
python -m didactopus.ocw_skill_agent_demo
|
||||
```
|
||||
|
|
@ -44,6 +45,7 @@ python -m didactopus.ocw_skill_agent_demo
|
|||
That gives you, with minimal setup:
|
||||
|
||||
- a generated topic pack
|
||||
- a graph-grounded mentor/practice/evaluator learner session
|
||||
- a guided curriculum path
|
||||
- a learner progress view
|
||||
- a capability export
|
||||
|
|
@ -118,6 +120,27 @@ Right now the value is in:
|
|||
|
||||
The deterministic demos show the shape of a mentor workflow, and the RoleMesh transcript path shows the same pattern with a live local-LLM-backed learner role.
|
||||
|
||||
## What is the new learner-session demo?
|
||||
|
||||
It is the current graph-grounded mentor-loop backbone.
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
python -m didactopus.learner_session_demo
|
||||
```
|
||||
|
||||
That demo loads the MIT OCW skill bundle, retrieves grounded concept neighborhoods and source fragments, and emits a single learner session containing:
|
||||
|
||||
- a learner goal
|
||||
- mentor guidance
|
||||
- a practice task
|
||||
- learner submission
|
||||
- evaluator feedback
|
||||
- a next-step recommendation
|
||||
|
||||
This is the backend shape the repository should now treat as the base for future accessibility, benchmarking, and voice-interaction work.
|
||||
|
||||
## How should I use it if I am taking a course and do not want to hire a tutor?
|
||||
|
||||
Use it as a structured study companion:
|
||||
|
|
@ -208,6 +231,7 @@ QA is heuristic: coverage alignment, evaluator alignment, path quality, semantic
|
|||
|
||||
Start with:
|
||||
|
||||
- `docs/roadmap.md`
|
||||
- `README.md`
|
||||
- `docs/course-to-pack.md`
|
||||
- `docs/learning-graph.md`
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ For a new MIT OCW-derived example, mirror the existing pattern:
|
|||
|
||||
```text
|
||||
examples/<course-slug>/
|
||||
course-source.md
|
||||
didactopus-course.yaml
|
||||
course/
|
||||
...
|
||||
sources.yaml
|
||||
```
|
||||
|
||||
|
|
@ -88,6 +90,7 @@ Harder candidates:
|
|||
|
||||
The MIT OCW Information and Entropy demo is the reference implementation of this pattern:
|
||||
|
||||
- source file: `examples/ocw-information-entropy/6-050j-information-and-entropy.md`
|
||||
- course manifest: `examples/ocw-information-entropy/didactopus-course.yaml`
|
||||
- source tree: `examples/ocw-information-entropy/course/`
|
||||
- source inventory: `examples/ocw-information-entropy/sources.yaml`
|
||||
- generated pack: `domain-packs/mit-ocw-information-entropy/`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,201 @@
|
|||
# Roadmap
|
||||
|
||||
This document summarizes the current prioritized improvement roadmap for Didactopus as a learner-facing system.
|
||||
|
||||
The ordering is intentional. The project should first strengthen the graph-grounded mentor loop that defines the real learner task, then use that stable backbone for local-model evaluation, accessibility work, and broader UX improvements.
|
||||
|
||||
## Priorities
|
||||
|
||||
### 1. Graph-grounded conversational mentor loop
|
||||
|
||||
Status: in progress
|
||||
|
||||
Why first:
|
||||
|
||||
- It defines the actual learner-facing interaction Didactopus is trying to support.
|
||||
- It makes later benchmarking and accessibility work target a real session model rather than an abstract idea.
|
||||
- It uses the graph and source-corpus artifacts already present in the repository.
|
||||
|
||||
Near-term scope:
|
||||
|
||||
- continue strengthening the learner session backend
|
||||
- make mentor, practice, and evaluator turns consistently source-grounded
|
||||
- improve trust-preserving feedback behavior
|
||||
- extend the session flow beyond one short interaction
|
||||
|
||||
Current code anchors:
|
||||
|
||||
- `didactopus.learner_session`
|
||||
- `didactopus.learner_session_demo`
|
||||
- `didactopus.graph_retrieval`
|
||||
- `didactopus.ocw_rolemesh_transcript_demo`
|
||||
|
||||
### 2. Local-model adequacy benchmark for constrained hardware
|
||||
|
||||
Status: planned
|
||||
|
||||
Why next:
|
||||
|
||||
- The learner loop should be benchmarked as soon as its task shape is stable.
|
||||
- Adequate local models on low-cost hardware would materially improve access in underserved regions.
|
||||
- Didactopus does not need a single perfect model; it needs role-adequate behavior.
|
||||
|
||||
Primary questions:
|
||||
|
||||
- Which models are adequate for `mentor`, `practice`, and `evaluator` roles?
|
||||
- What latency, memory, and throughput are acceptable on Raspberry Pi-class hardware?
|
||||
- Which roles can degrade gracefully to smaller models?
|
||||
|
||||
Expected outputs:
|
||||
|
||||
- benchmark tasks grounded in the MIT OCW pack
|
||||
- per-role adequacy scores
|
||||
- recommended deployment profiles for low-end, laptop, and stronger local systems
|
||||
|
||||
### 3. Accessibility-first learner interaction
|
||||
|
||||
Status: planned
|
||||
|
||||
Why high priority:
|
||||
|
||||
- Didactopus has clear potential for learners who do not have access to enough teachers or tutors.
|
||||
- Blind learners and other accessibility-focused use cases benefit directly from structured, guided interaction.
|
||||
- Voice and text accessibility can build on the same learner-session backend.
|
||||
|
||||
Target features:
|
||||
|
||||
- screen-reader-friendly learner output
|
||||
- accessible HTML alternatives to purely visual artifacts
|
||||
- text-first navigation of concept neighborhoods and progress
|
||||
- explicit structural cues in explanations and feedback
|
||||
|
||||
### 4. Voice interaction with local STT and TTS
|
||||
|
||||
Status: planned
|
||||
|
||||
Why after accessibility baseline:
|
||||
|
||||
- The project should first ensure that the session structure is accessible in text.
|
||||
- Voice interaction is more useful once the mentor loop and pending-response behavior are stable.
|
||||
|
||||
Target features:
|
||||
|
||||
- speech-to-text input for learner answers
|
||||
- text-to-speech output for mentor, practice, and evaluator turns
|
||||
- spoken waiting notices during slow local-model responses
|
||||
- repeat, interrupt, and slow-down controls
|
||||
|
||||
### 5. Learner workbench UI
|
||||
|
||||
Status: planned
|
||||
|
||||
Why important:
|
||||
|
||||
- The repository has review-focused interfaces and generated artifacts, but the learner path is still fragmented.
|
||||
- A dedicated learner workbench would make Didactopus more usable as a personal mentor rather than only a pipeline/demo system.
|
||||
|
||||
Target features:
|
||||
|
||||
- current concept and why-it-matters view
|
||||
- prerequisite chain and supporting lessons
|
||||
- grounded source excerpts
|
||||
- active practice task
|
||||
- evaluator feedback
|
||||
- recommended next step
|
||||
|
||||
### 6. Adaptive diagnostics and practice refinement
|
||||
|
||||
Status: planned
|
||||
|
||||
Why this matters:
|
||||
|
||||
- Learners need clearer answers to “what am I weak at?” and “what should I do next?”
|
||||
- The repository already has evidence and evaluator machinery that can be surfaced in learner terms.
|
||||
|
||||
Target features:
|
||||
|
||||
- weak-dimension summaries by concept
|
||||
- misconception tracking
|
||||
- remedial branch suggestions
|
||||
- hint ladders and difficulty control
|
||||
- oral, short-answer, and compare-and-contrast practice modes
|
||||
|
||||
### 7. Source-grounded citation transparency
|
||||
|
||||
Status: planned
|
||||
|
||||
Why it matters:
|
||||
|
||||
- Trust depends on showing what is grounded in source material and what is model inference.
|
||||
- This is especially important for learners using local models with variable quality.
|
||||
|
||||
Target features:
|
||||
|
||||
- lesson and source-fragment references in explanations
|
||||
- explicit distinction between cited source support and model inference
|
||||
- easier inspection of concept-to-source provenance
|
||||
|
||||
### 8. Pack quality, review, and concept-graph curation improvements
|
||||
|
||||
Status: planned
|
||||
|
||||
Why later:
|
||||
|
||||
- These are important, but they mainly improve the quality of the learning substrate rather than the immediate learner interaction.
|
||||
- The graph-first path should first prove out the learner experience it supports.
|
||||
|
||||
Target features:
|
||||
|
||||
- concept merge and split workflows
|
||||
- alias handling across packs
|
||||
- impact analysis for concept edits
|
||||
- stronger review support for noisy or broad concepts
|
||||
- improved source coverage QA
|
||||
|
||||
### 9. Incremental re-ingestion and course updates
|
||||
|
||||
Status: planned
|
||||
|
||||
Why useful:
|
||||
|
||||
- External course repositories are now part of the intended workflow.
|
||||
- Didactopus should avoid full rebuilds when only part of a source tree changes.
|
||||
|
||||
Target features:
|
||||
|
||||
- changed-file detection
|
||||
- stable concept and fragment IDs where possible
|
||||
- graph and pack diffs
|
||||
- preservation of learner evidence across source updates
|
||||
|
||||
### 10. Richer multimodal and notation support
|
||||
|
||||
Status: longer-term
|
||||
|
||||
Why longer-term:
|
||||
|
||||
- This work is valuable but more specialized and technically demanding than the earlier roadmap items.
|
||||
|
||||
Examples:
|
||||
|
||||
- spoken math rendering improvements
|
||||
- diagram descriptions
|
||||
- accessible handling of image-heavy source materials
|
||||
- EPUB and other learner-friendly export targets
|
||||
|
||||
## Guiding Principles
|
||||
|
||||
- Use the graph and source corpus before relying on model prior knowledge.
|
||||
- Optimize for guided learning, not answer offloading.
|
||||
- Prefer role-adequate local models over chasing a single best model.
|
||||
- Keep accessibility and low-cost deployment in scope from the start, not as cleanup work.
|
||||
- Preserve provenance and license compliance as first-class constraints.
|
||||
|
||||
## Suggested Implementation Sequence
|
||||
|
||||
1. Strengthen `didactopus.learner_session` into the standard session backend.
|
||||
2. Build a small model-benchmark harness around that backend.
|
||||
3. Add accessible learner HTML and text-first outputs.
|
||||
4. Add local TTS and STT support to the same session flow.
|
||||
5. Expand adaptive practice and diagnostics.
|
||||
6. Improve review, impact analysis, and incremental update support.
|
||||
|
|
@ -13,6 +13,8 @@ Without a workspace layer, users have to manually track:
|
|||
|
||||
The current code reduces that friction by giving review work a registry and import lifecycle.
|
||||
|
||||
For course-repository workflows, review export can also target a checked-out course repo's generated pack directory, so the reviewed pack lands back inside the course repository rather than in an unrelated ad hoc folder.
|
||||
|
||||
## Current implementation
|
||||
|
||||
`didactopus.workspace_manager.WorkspaceManager` currently supports:
|
||||
|
|
@ -49,3 +51,11 @@ The review bridge server exposes workspace operations through local HTTP endpoin
|
|||
- import draft pack
|
||||
|
||||
These endpoints are used to connect ingestion outputs to the review workflow without manual file shuffling.
|
||||
|
||||
## Course-repo targeting
|
||||
|
||||
If a course is managed as its own repository with `didactopus-course.yaml`, promoted-pack export can target that repository directly. The current review export layer exposes a helper for this pattern:
|
||||
|
||||
- `export_promoted_pack_to_course_repo(...)`
|
||||
|
||||
That helper resolves the repo manifest and writes the promoted pack into the repo's configured generated pack directory.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,241 @@
|
|||
{
|
||||
"goal": "Help me understand how Shannon entropy leads into channel capacity and thermodynamic entropy.",
|
||||
"study_plan": {
|
||||
"skill": "ocw-information-entropy-agent",
|
||||
"task": "Help me understand how Shannon entropy leads into channel capacity and thermodynamic entropy.",
|
||||
"steps": [
|
||||
{
|
||||
"concept_key": "mit-ocw-information-and-entropy::independent-reasoning-and-careful-comparison",
|
||||
"title": "Independent Reasoning and Careful Comparison",
|
||||
"status": "mastered",
|
||||
"prerequisites": [
|
||||
"mit-ocw-information-and-entropy::course-notes-and-reference-texts"
|
||||
],
|
||||
"prerequisite_titles": [
|
||||
"Course Notes and Reference Texts"
|
||||
],
|
||||
"supporting_lessons": [
|
||||
"Independent Reasoning and Careful Comparison"
|
||||
],
|
||||
"source_fragments": [
|
||||
{
|
||||
"lesson_title": "Independent Reasoning and Careful Comparison",
|
||||
"kind": "lesson_body",
|
||||
"text": "- Objective: Explain why the course requires precise comparison of related but non-identical concepts.\n- Exercise: Write a short note distinguishing Shannon entropy, channel capacity, and thermodynamic entropy.\nThe 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."
|
||||
},
|
||||
{
|
||||
"lesson_title": "Independent Reasoning and Careful Comparison",
|
||||
"kind": "objective",
|
||||
"text": "Explain why the course requires precise comparison of related but non-identical concepts."
|
||||
}
|
||||
],
|
||||
"recommended_action": "Use Independent Reasoning and Careful Comparison as the primary teaching anchor."
|
||||
},
|
||||
{
|
||||
"concept_key": "mit-ocw-information-and-entropy::thermodynamics-and-entropy",
|
||||
"title": "Thermodynamics and Entropy",
|
||||
"status": "mastered",
|
||||
"prerequisites": [
|
||||
"mit-ocw-information-and-entropy::cryptography-and-information-hiding"
|
||||
],
|
||||
"prerequisite_titles": [
|
||||
"Cryptography and Information Hiding"
|
||||
],
|
||||
"supporting_lessons": [
|
||||
"Thermodynamics and Entropy"
|
||||
],
|
||||
"source_fragments": [
|
||||
{
|
||||
"lesson_title": "Thermodynamics and Entropy",
|
||||
"kind": "lesson_body",
|
||||
"text": "- Objective: Explain how thermodynamic entropy relates to, and differs from, Shannon entropy.\n- Exercise: Compare the two entropy notions and identify what is preserved across the analogy.\nThe course uses entropy as a bridge concept between communication theory and physics while insisting on careful interpretation."
|
||||
},
|
||||
{
|
||||
"lesson_title": "Thermodynamics and Entropy",
|
||||
"kind": "objective",
|
||||
"text": "Explain how thermodynamic entropy relates to, and differs from, Shannon entropy."
|
||||
}
|
||||
],
|
||||
"recommended_action": "Use Thermodynamics and Entropy as the primary teaching anchor."
|
||||
},
|
||||
{
|
||||
"concept_key": "mit-ocw-information-and-entropy::shannon-entropy",
|
||||
"title": "Shannon Entropy",
|
||||
"status": "mastered",
|
||||
"prerequisites": [
|
||||
"mit-ocw-information-and-entropy::counting-and-probability"
|
||||
],
|
||||
"prerequisite_titles": [
|
||||
"Counting and Probability"
|
||||
],
|
||||
"supporting_lessons": [
|
||||
"Shannon Entropy"
|
||||
],
|
||||
"source_fragments": [
|
||||
{
|
||||
"lesson_title": "Shannon Entropy",
|
||||
"kind": "lesson_body",
|
||||
"text": "- Objective: Explain Shannon entropy as a measure of uncertainty and compare high-entropy and low-entropy sources.\n- Exercise: Compute the entropy of a Bernoulli source and interpret the result.\nThe course then introduces entropy as a quantitative measure of uncertainty for a source model and uses it to reason about representation cost and surprise."
|
||||
},
|
||||
{
|
||||
"lesson_title": "Shannon Entropy",
|
||||
"kind": "objective",
|
||||
"text": "Explain Shannon entropy as a measure of uncertainty and compare high-entropy and low-entropy sources."
|
||||
}
|
||||
],
|
||||
"recommended_action": "Use Shannon Entropy as the primary teaching anchor."
|
||||
}
|
||||
],
|
||||
"guided_path_reference": [
|
||||
"mit-ocw-information-and-entropy::mit-ocw-6-050j-information-and-entropy-course-home",
|
||||
"mit-ocw-information-and-entropy::information-and-entropy",
|
||||
"mit-ocw-information-and-entropy::ultimate-limits-to-communication-and-computation",
|
||||
"mit-ocw-information-and-entropy::open-textbooks-problem-sets-and-programming-work",
|
||||
"mit-ocw-information-and-entropy::mit-ocw-6-050j-information-and-entropy-syllabus",
|
||||
"mit-ocw-information-and-entropy::prerequisites-and-mathematical-background",
|
||||
"mit-ocw-information-and-entropy::assessment-structure",
|
||||
"mit-ocw-information-and-entropy::course-notes-and-reference-texts",
|
||||
"mit-ocw-information-and-entropy::independent-reasoning-and-careful-comparison",
|
||||
"mit-ocw-information-and-entropy::mit-ocw-6-050j-information-and-entropy-unit-sequence",
|
||||
"mit-ocw-information-and-entropy::counting-and-probability",
|
||||
"mit-ocw-information-and-entropy::shannon-entropy",
|
||||
"mit-ocw-information-and-entropy::mutual-information",
|
||||
"mit-ocw-information-and-entropy::source-coding-and-compression",
|
||||
"mit-ocw-information-and-entropy::huffman-coding",
|
||||
"mit-ocw-information-and-entropy::channel-capacity",
|
||||
"mit-ocw-information-and-entropy::channel-coding",
|
||||
"mit-ocw-information-and-entropy::error-correcting-codes",
|
||||
"mit-ocw-information-and-entropy::cryptography-and-information-hiding",
|
||||
"mit-ocw-information-and-entropy::thermodynamics-and-entropy"
|
||||
]
|
||||
},
|
||||
"primary_concept": {
|
||||
"concept_key": "mit-ocw-information-and-entropy::independent-reasoning-and-careful-comparison",
|
||||
"title": "Independent Reasoning and Careful Comparison",
|
||||
"status": "mastered",
|
||||
"prerequisites": [
|
||||
"mit-ocw-information-and-entropy::course-notes-and-reference-texts"
|
||||
],
|
||||
"prerequisite_titles": [
|
||||
"Course Notes and Reference Texts"
|
||||
],
|
||||
"supporting_lessons": [
|
||||
"Independent Reasoning and Careful Comparison"
|
||||
],
|
||||
"source_fragments": [
|
||||
{
|
||||
"lesson_title": "Independent Reasoning and Careful Comparison",
|
||||
"kind": "lesson_body",
|
||||
"text": "- Objective: Explain why the course requires precise comparison of related but non-identical concepts.\n- Exercise: Write a short note distinguishing Shannon entropy, channel capacity, and thermodynamic entropy.\nThe 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."
|
||||
},
|
||||
{
|
||||
"lesson_title": "Independent Reasoning and Careful Comparison",
|
||||
"kind": "objective",
|
||||
"text": "Explain why the course requires precise comparison of related but non-identical concepts."
|
||||
}
|
||||
],
|
||||
"recommended_action": "Use Independent Reasoning and Careful Comparison as the primary teaching anchor."
|
||||
},
|
||||
"secondary_concept": {
|
||||
"concept_key": "mit-ocw-information-and-entropy::thermodynamics-and-entropy",
|
||||
"title": "Thermodynamics and Entropy",
|
||||
"status": "mastered",
|
||||
"prerequisites": [
|
||||
"mit-ocw-information-and-entropy::cryptography-and-information-hiding"
|
||||
],
|
||||
"prerequisite_titles": [
|
||||
"Cryptography and Information Hiding"
|
||||
],
|
||||
"supporting_lessons": [
|
||||
"Thermodynamics and Entropy"
|
||||
],
|
||||
"source_fragments": [
|
||||
{
|
||||
"lesson_title": "Thermodynamics and Entropy",
|
||||
"kind": "lesson_body",
|
||||
"text": "- Objective: Explain how thermodynamic entropy relates to, and differs from, Shannon entropy.\n- Exercise: Compare the two entropy notions and identify what is preserved across the analogy.\nThe course uses entropy as a bridge concept between communication theory and physics while insisting on careful interpretation."
|
||||
},
|
||||
{
|
||||
"lesson_title": "Thermodynamics and Entropy",
|
||||
"kind": "objective",
|
||||
"text": "Explain how thermodynamic entropy relates to, and differs from, Shannon entropy."
|
||||
}
|
||||
],
|
||||
"recommended_action": "Use Thermodynamics and Entropy as the primary teaching anchor."
|
||||
},
|
||||
"practice_task": "[stubbed-response] [practice] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons",
|
||||
"evaluation": {
|
||||
"concept_key": "mit-ocw-information-and-entropy::independent-reasoning-and-careful-comparison",
|
||||
"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.",
|
||||
"verdict": "needs_revision",
|
||||
"aggregated": {
|
||||
"correctness": 0.6000000000000001,
|
||||
"explanation": 0.85,
|
||||
"critique": 0.6499999999999999
|
||||
},
|
||||
"evaluators": [
|
||||
{
|
||||
"name": "rubric",
|
||||
"dimensions": {
|
||||
"correctness": 0.8,
|
||||
"explanation": 0.85
|
||||
},
|
||||
"notes": "Heuristic scaffold rubric score."
|
||||
},
|
||||
{
|
||||
"name": "symbolic_rule",
|
||||
"dimensions": {
|
||||
"correctness": 0.4
|
||||
},
|
||||
"notes": "Stub symbolic evaluator."
|
||||
},
|
||||
{
|
||||
"name": "critique",
|
||||
"dimensions": {
|
||||
"critique": 0.6499999999999999
|
||||
},
|
||||
"notes": "Stub critique evaluator."
|
||||
}
|
||||
],
|
||||
"skill_reference": {
|
||||
"skill_name": "ocw-information-entropy-agent",
|
||||
"mastered_by_demo_agent": true,
|
||||
"supporting_lessons": [
|
||||
"Independent Reasoning and Careful Comparison"
|
||||
]
|
||||
},
|
||||
"follow_up": "Rework the answer so it states the equality/relationship explicitly and explains why it matters."
|
||||
},
|
||||
"turns": [
|
||||
{
|
||||
"role": "user",
|
||||
"label": "Learner Goal",
|
||||
"content": "Help me understand how Shannon entropy leads into channel capacity and thermodynamic entropy."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"label": "Didactopus Mentor",
|
||||
"content": "[stubbed-response] [mentor] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"label": "Didactopus Practice Designer",
|
||||
"content": "[stubbed-response] [practice] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"label": "Learner Submission",
|
||||
"content": "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."
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"label": "Didactopus Evaluator",
|
||||
"content": "[stubbed-response] [evaluator] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"label": "Didactopus Mentor",
|
||||
"content": "[stubbed-response] [mentor] Concept: Independent Reasoning and Careful Comparison Prerequisites: Course Notes and Reference Texts Supporting lessons"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -7,6 +7,33 @@
|
|||
"skill": "ocw-information-entropy-agent",
|
||||
"task": "Help a learner connect Shannon entropy, channel capacity, and thermodynamic entropy.",
|
||||
"steps": [
|
||||
{
|
||||
"concept_key": "mit-ocw-information-and-entropy::independent-reasoning-and-careful-comparison",
|
||||
"title": "Independent Reasoning and Careful Comparison",
|
||||
"status": "mastered",
|
||||
"prerequisites": [
|
||||
"mit-ocw-information-and-entropy::course-notes-and-reference-texts"
|
||||
],
|
||||
"prerequisite_titles": [
|
||||
"Course Notes and Reference Texts"
|
||||
],
|
||||
"supporting_lessons": [
|
||||
"Independent Reasoning and Careful Comparison"
|
||||
],
|
||||
"source_fragments": [
|
||||
{
|
||||
"lesson_title": "Independent Reasoning and Careful Comparison",
|
||||
"kind": "lesson_body",
|
||||
"text": "- Objective: Explain why the course requires precise comparison of related but non-identical concepts.\n- Exercise: Write a short note distinguishing Shannon entropy, channel capacity, and thermodynamic entropy.\nThe 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."
|
||||
},
|
||||
{
|
||||
"lesson_title": "Independent Reasoning and Careful Comparison",
|
||||
"kind": "objective",
|
||||
"text": "Explain why the course requires precise comparison of related but non-identical concepts."
|
||||
}
|
||||
],
|
||||
"recommended_action": "Use Independent Reasoning and Careful Comparison as the primary teaching anchor."
|
||||
},
|
||||
{
|
||||
"concept_key": "mit-ocw-information-and-entropy::thermodynamics-and-entropy",
|
||||
"title": "Thermodynamics and Entropy",
|
||||
|
|
@ -14,6 +41,24 @@
|
|||
"prerequisites": [
|
||||
"mit-ocw-information-and-entropy::cryptography-and-information-hiding"
|
||||
],
|
||||
"prerequisite_titles": [
|
||||
"Cryptography and Information Hiding"
|
||||
],
|
||||
"supporting_lessons": [
|
||||
"Thermodynamics and Entropy"
|
||||
],
|
||||
"source_fragments": [
|
||||
{
|
||||
"lesson_title": "Thermodynamics and Entropy",
|
||||
"kind": "lesson_body",
|
||||
"text": "- Objective: Explain how thermodynamic entropy relates to, and differs from, Shannon entropy.\n- Exercise: Compare the two entropy notions and identify what is preserved across the analogy.\nThe course uses entropy as a bridge concept between communication theory and physics while insisting on careful interpretation."
|
||||
},
|
||||
{
|
||||
"lesson_title": "Thermodynamics and Entropy",
|
||||
"kind": "objective",
|
||||
"text": "Explain how thermodynamic entropy relates to, and differs from, Shannon entropy."
|
||||
}
|
||||
],
|
||||
"recommended_action": "Use Thermodynamics and Entropy as the primary teaching anchor."
|
||||
},
|
||||
{
|
||||
|
|
@ -21,26 +66,44 @@
|
|||
"title": "Course Synthesis",
|
||||
"status": "review-needed",
|
||||
"prerequisites": [
|
||||
"mit-ocw-information-and-entropy::thermodynamics-and-entropy"
|
||||
"mit-ocw-information-and-entropy::reversible-computation-and-quantum-computation"
|
||||
],
|
||||
"recommended_action": "Review prerequisites before teaching Course Synthesis."
|
||||
"prerequisite_titles": [
|
||||
"Reversible Computation and Quantum Computation"
|
||||
],
|
||||
"supporting_lessons": [
|
||||
"Course Synthesis"
|
||||
],
|
||||
"source_fragments": [
|
||||
{
|
||||
"lesson_title": "Course Synthesis",
|
||||
"kind": "lesson_body",
|
||||
"text": "- Objective: Synthesize the course by connecting entropy, coding, reliability, secrecy, and physical interpretation in one coherent narrative.\n- Exercise: Produce a final study guide that links source coding, channel coding, secrecy, thermodynamic analogies, and computation.\nThe end of the course asks the learner to unify the mathematical and physical perspectives rather than treating the units as disconnected topics."
|
||||
},
|
||||
{
|
||||
"concept_key": "mit-ocw-information-and-entropy::shannon-entropy",
|
||||
"title": "Shannon Entropy",
|
||||
"status": "mastered",
|
||||
"prerequisites": [
|
||||
"mit-ocw-information-and-entropy::counting-and-probability"
|
||||
"lesson_title": "Course Synthesis",
|
||||
"kind": "objective",
|
||||
"text": "Synthesize the course by connecting entropy, coding, reliability, secrecy, and physical interpretation in one coherent narrative."
|
||||
}
|
||||
],
|
||||
"recommended_action": "Use Shannon Entropy as the primary teaching anchor."
|
||||
"recommended_action": "Review prerequisites before teaching Course Synthesis."
|
||||
}
|
||||
],
|
||||
"guided_path_reference": [
|
||||
"mit-ocw-information-and-entropy::mit-ocw-6-050j-information-and-entropy",
|
||||
"mit-ocw-information-and-entropy::mit-ocw-6-050j-information-and-entropy-course-home",
|
||||
"mit-ocw-information-and-entropy::information-and-entropy",
|
||||
"mit-ocw-information-and-entropy::ultimate-limits-to-communication-and-computation",
|
||||
"mit-ocw-information-and-entropy::open-textbooks-problem-sets-and-programming-work",
|
||||
"mit-ocw-information-and-entropy::mit-ocw-6-050j-information-and-entropy-syllabus",
|
||||
"mit-ocw-information-and-entropy::prerequisites-and-mathematical-background",
|
||||
"mit-ocw-information-and-entropy::assessment-structure",
|
||||
"mit-ocw-information-and-entropy::course-notes-and-reference-texts",
|
||||
"mit-ocw-information-and-entropy::independent-reasoning-and-careful-comparison",
|
||||
"mit-ocw-information-and-entropy::mit-ocw-6-050j-information-and-entropy-unit-sequence",
|
||||
"mit-ocw-information-and-entropy::counting-and-probability",
|
||||
"mit-ocw-information-and-entropy::shannon-entropy",
|
||||
"mit-ocw-information-and-entropy::mutual-information",
|
||||
"mit-ocw-information-and-entropy::data-compression",
|
||||
"mit-ocw-information-and-entropy::source-coding-and-compression",
|
||||
"mit-ocw-information-and-entropy::huffman-coding",
|
||||
"mit-ocw-information-and-entropy::channel-capacity",
|
||||
"mit-ocw-information-and-entropy::channel-coding",
|
||||
|
|
@ -52,8 +115,25 @@
|
|||
"explanation": {
|
||||
"concept_key": "mit-ocw-information-and-entropy::channel-capacity",
|
||||
"title": "Channel Capacity",
|
||||
"explanation": "Channel Capacity is represented in the Information and Entropy skill as part of a progression from foundational probability ideas toward communication limits and physical interpretation. It depends on huffman-coding. The current demo learner already mastered this concept, with evaluator means {'correctness': 0.8400000000000001, 'explanation': 0.85, 'critique': 0.7999999999999999}, so the skill can use it as a stable explanation anchor.",
|
||||
"source_description": "- Objective: Explain Channel Capacity as a limit on reliable communication over noisy channels.\n- Exercise: State why reliable transmission above capacity is impossible in the long run.\nThis lesson develops Channel Capacity, Reliable Commun"
|
||||
"explanation": "Channel Capacity is represented in the Information and Entropy skill as part of a progression from foundational probability ideas toward communication limits and physical interpretation. It depends on Huffman Coding. It is grounded by lessons such as Channel Capacity. The current demo learner already mastered this concept, with evaluator means {'correctness': 0.8400000000000001, 'explanation': 0.85, 'critique': 0.7999999999999999}, so the skill can use it as a stable explanation anchor.",
|
||||
"source_description": "- Objective: Explain channel capacity as a limit on reliable communication over a noisy channel.\n- Exercise: State why reliable transmission above capacity is impossible in the long run.\nThe course treats capacity as a fundamental upper bou",
|
||||
"grounding": {
|
||||
"supporting_lessons": [
|
||||
"Channel Capacity"
|
||||
],
|
||||
"source_fragments": [
|
||||
{
|
||||
"lesson_title": "Channel Capacity",
|
||||
"kind": "lesson_body",
|
||||
"text": "- Objective: Explain channel capacity as a limit on reliable communication over a noisy channel.\n- Exercise: State why reliable transmission above capacity is impossible in the long run.\nThe course treats capacity as a fundamental upper bound and frames noisy communication in terms of rates, inference, and uncertainty reduction."
|
||||
},
|
||||
{
|
||||
"lesson_title": "Channel Capacity",
|
||||
"kind": "objective",
|
||||
"text": "Explain channel capacity as a limit on reliable communication over a noisy channel."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"evaluation": {
|
||||
"concept_key": "mit-ocw-information-and-entropy::thermodynamics-and-entropy",
|
||||
|
|
@ -90,7 +170,10 @@
|
|||
],
|
||||
"skill_reference": {
|
||||
"skill_name": "ocw-information-entropy-agent",
|
||||
"mastered_by_demo_agent": true
|
||||
"mastered_by_demo_agent": true,
|
||||
"supporting_lessons": [
|
||||
"Thermodynamics and Entropy"
|
||||
]
|
||||
},
|
||||
"follow_up": "Extend the answer with an explicit limitation or assumption."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
- Description: Use the generated MIT OCW Information and Entropy pack, concept ordering, and learner artifacts to mentor or evaluate information-theory work.
|
||||
|
||||
## Study Plan
|
||||
- Independent Reasoning and Careful Comparison (mastered): Use Independent Reasoning and Careful Comparison as the primary teaching anchor.
|
||||
- Thermodynamics and Entropy (mastered): Use Thermodynamics and Entropy as the primary teaching anchor.
|
||||
- Course Synthesis (review-needed): Review prerequisites before teaching Course Synthesis.
|
||||
- Shannon Entropy (mastered): Use Shannon Entropy as the primary teaching anchor.
|
||||
|
||||
## Explanation Demo
|
||||
Channel Capacity is represented in the Information and Entropy skill as part of a progression from foundational probability ideas toward communication limits and physical interpretation. It depends on huffman-coding. The current demo learner already mastered this concept, with evaluator means {'correctness': 0.8400000000000001, 'explanation': 0.85, 'critique': 0.7999999999999999}, so the skill can use it as a stable explanation anchor.
|
||||
Channel Capacity is represented in the Information and Entropy skill as part of a progression from foundational probability ideas toward communication limits and physical interpretation. It depends on Huffman Coding. It is grounded by lessons such as Channel Capacity. The current demo learner already mastered this concept, with evaluator means {'correctness': 0.8400000000000001, 'explanation': 0.85, 'critique': 0.7999999999999999}, so the skill can use it as a stable explanation anchor.
|
||||
- Supporting lessons: ['Channel Capacity']
|
||||
|
||||
## Evaluation Demo
|
||||
- Verdict: acceptable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
course_id: mit-ocw-information-entropy
|
||||
display_name: MIT OCW Information and Entropy
|
||||
source_dir: course
|
||||
source_inventory: sources.yaml
|
||||
license_family: CC BY-NC-SA 4.0
|
||||
generated_pack_dir: ../../domain-packs/mit-ocw-information-entropy
|
||||
generated_run_dir: ../../examples/ocw-information-entropy-run
|
||||
generated_skill_dir: ../../skills/ocw-information-entropy-agent
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
import yaml
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class CourseRepoManifest(BaseModel):
|
||||
course_id: str
|
||||
display_name: str
|
||||
source_dir: str
|
||||
source_inventory: str
|
||||
license_family: str = ""
|
||||
generated_pack_dir: str | None = None
|
||||
generated_run_dir: str | None = None
|
||||
generated_skill_dir: str | None = None
|
||||
|
||||
|
||||
class ResolvedCourseRepo(BaseModel):
|
||||
repo_root: str
|
||||
manifest_path: str
|
||||
course_id: str
|
||||
display_name: str
|
||||
license_family: str = ""
|
||||
source_dir: str
|
||||
source_inventory: str
|
||||
generated_pack_dir: str | None = None
|
||||
generated_run_dir: str | None = None
|
||||
generated_skill_dir: str | None = None
|
||||
|
||||
|
||||
def course_repo_manifest_path(path: str | Path) -> Path:
|
||||
candidate = Path(path)
|
||||
if candidate.is_dir():
|
||||
return candidate / "didactopus-course.yaml"
|
||||
return candidate
|
||||
|
||||
|
||||
def load_course_repo_manifest(path: str | Path) -> CourseRepoManifest:
|
||||
manifest_path = course_repo_manifest_path(path)
|
||||
data = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) or {}
|
||||
return CourseRepoManifest.model_validate(data)
|
||||
|
||||
|
||||
def resolve_course_repo(path: str | Path) -> ResolvedCourseRepo:
|
||||
manifest_path = course_repo_manifest_path(path).resolve()
|
||||
repo_root = manifest_path.parent
|
||||
manifest = load_course_repo_manifest(manifest_path)
|
||||
|
||||
def _resolve(relpath: str | None) -> str | None:
|
||||
if relpath is None:
|
||||
return None
|
||||
return str((repo_root / relpath).resolve())
|
||||
|
||||
return ResolvedCourseRepo(
|
||||
repo_root=str(repo_root),
|
||||
manifest_path=str(manifest_path),
|
||||
course_id=manifest.course_id,
|
||||
display_name=manifest.display_name,
|
||||
license_family=manifest.license_family,
|
||||
source_dir=_resolve(manifest.source_dir) or "",
|
||||
source_inventory=_resolve(manifest.source_inventory) or "",
|
||||
generated_pack_dir=_resolve(manifest.generated_pack_dir),
|
||||
generated_run_dir=_resolve(manifest.generated_run_dir),
|
||||
generated_skill_dir=_resolve(manifest.generated_skill_dir),
|
||||
)
|
||||
|
||||
|
||||
def initialize_course_repo(
|
||||
target_dir: str | Path,
|
||||
course_id: str,
|
||||
display_name: str,
|
||||
license_family: str = "",
|
||||
source_dir_name: str = "course",
|
||||
source_inventory_name: str = "sources.yaml",
|
||||
generated_pack_dir: str = "generated/pack",
|
||||
generated_run_dir: str = "generated/run",
|
||||
generated_skill_dir: str = "generated/skill",
|
||||
) -> ResolvedCourseRepo:
|
||||
target = Path(target_dir)
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
manifest_path = target / "didactopus-course.yaml"
|
||||
if not manifest_path.exists():
|
||||
payload = {
|
||||
"course_id": course_id,
|
||||
"display_name": display_name,
|
||||
"source_dir": source_dir_name,
|
||||
"source_inventory": source_inventory_name,
|
||||
"license_family": license_family,
|
||||
"generated_pack_dir": generated_pack_dir,
|
||||
"generated_run_dir": generated_run_dir,
|
||||
"generated_skill_dir": generated_skill_dir,
|
||||
}
|
||||
manifest_path.write_text(yaml.safe_dump(payload, sort_keys=False), encoding="utf-8")
|
||||
(target / source_dir_name).mkdir(parents=True, exist_ok=True)
|
||||
inventory_path = target / source_inventory_name
|
||||
if not inventory_path.exists():
|
||||
inventory_path.write_text("sources: []\n", encoding="utf-8")
|
||||
return resolve_course_repo(target)
|
||||
|
||||
|
||||
def populate_course_repo_sources(
|
||||
target_dir: str | Path,
|
||||
course_source: str | Path,
|
||||
source_inventory: str | Path,
|
||||
source_dir_name: str = "course",
|
||||
source_inventory_name: str = "sources.yaml",
|
||||
overwrite: bool = True,
|
||||
) -> ResolvedCourseRepo:
|
||||
target = Path(target_dir)
|
||||
source_path = Path(course_source)
|
||||
inventory_path = Path(source_inventory)
|
||||
target_source = target / source_dir_name
|
||||
target_inventory = target / source_inventory_name
|
||||
|
||||
if overwrite and target_source.exists():
|
||||
shutil.rmtree(target_source)
|
||||
target_source.mkdir(parents=True, exist_ok=True)
|
||||
if source_path.is_dir():
|
||||
for child in sorted(source_path.iterdir()):
|
||||
if child.is_dir():
|
||||
shutil.copytree(child, target_source / child.name, dirs_exist_ok=True)
|
||||
else:
|
||||
shutil.copy2(child, target_source / child.name)
|
||||
else:
|
||||
shutil.copy2(source_path, target_source / source_path.name)
|
||||
|
||||
shutil.copy2(inventory_path, target_inventory)
|
||||
return resolve_course_repo(target)
|
||||
|
||||
|
||||
def bootstrap_course_repo(
|
||||
target_dir: str | Path,
|
||||
course_id: str,
|
||||
display_name: str,
|
||||
course_source: str | Path,
|
||||
source_inventory: str | Path,
|
||||
license_family: str = "",
|
||||
overwrite: bool = True,
|
||||
) -> ResolvedCourseRepo:
|
||||
resolved = initialize_course_repo(
|
||||
target_dir=target_dir,
|
||||
course_id=course_id,
|
||||
display_name=display_name,
|
||||
license_family=license_family,
|
||||
)
|
||||
return populate_course_repo_sources(
|
||||
target_dir=resolved.repo_root,
|
||||
course_source=course_source,
|
||||
source_inventory=source_inventory,
|
||||
overwrite=overwrite,
|
||||
)
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .model_provider import ModelProvider
|
||||
from .ocw_skill_agent_demo import (
|
||||
SkillContext,
|
||||
_match_concepts,
|
||||
build_skill_grounded_study_plan,
|
||||
evaluate_submission_with_skill,
|
||||
)
|
||||
from .role_prompts import system_prompt_for_role
|
||||
|
||||
|
||||
def _grounding_block(step: dict) -> str:
|
||||
fragments = step.get("source_fragments", []) or []
|
||||
fragment_lines = [fragment.get("text", "") for fragment in fragments if fragment.get("text")]
|
||||
lines = [
|
||||
f"Concept: {step.get('title', '')}",
|
||||
f"Prerequisites: {', '.join(step.get('prerequisite_titles', []) or ['none explicit'])}",
|
||||
f"Supporting lessons: {', '.join(step.get('supporting_lessons', []) or [step.get('title', '')])}",
|
||||
]
|
||||
if fragment_lines:
|
||||
lines.append("Grounding fragments:")
|
||||
lines.extend(f"- {line}" for line in fragment_lines)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _generate_role_text(
|
||||
provider: ModelProvider,
|
||||
*,
|
||||
role: str,
|
||||
prompt: str,
|
||||
temperature: float = 0.2,
|
||||
max_tokens: int = 220,
|
||||
) -> str:
|
||||
return provider.generate(
|
||||
prompt,
|
||||
role=role,
|
||||
system_prompt=system_prompt_for_role(role),
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
).text.strip()
|
||||
|
||||
|
||||
@dataclass
|
||||
class LearnerSessionTurn:
|
||||
role: str
|
||||
label: str
|
||||
content: str
|
||||
|
||||
|
||||
def build_graph_grounded_session(
|
||||
context: SkillContext,
|
||||
provider: ModelProvider,
|
||||
learner_goal: str,
|
||||
learner_submission: str,
|
||||
) -> dict:
|
||||
study_plan = build_skill_grounded_study_plan(context, learner_goal)
|
||||
steps = study_plan.get("steps", [])
|
||||
if not steps:
|
||||
raise ValueError("No grounded study-plan steps available for learner session.")
|
||||
|
||||
primary = steps[0]
|
||||
secondary = steps[1] if len(steps) > 1 else primary
|
||||
mentor_prompt = (
|
||||
f"{_grounding_block(primary)}\n\n"
|
||||
f"{_grounding_block(secondary)}\n\n"
|
||||
f"Learner goal: {learner_goal}\n"
|
||||
"Respond as Didactopus mentor. Give a short grounded orientation, explain why these concepts come first, "
|
||||
"and ask one focused question that keeps the learner doing the reasoning."
|
||||
)
|
||||
mentor_text = _generate_role_text(
|
||||
provider,
|
||||
role="mentor",
|
||||
prompt=mentor_prompt,
|
||||
temperature=0.2,
|
||||
max_tokens=260,
|
||||
)
|
||||
|
||||
practice_prompt = (
|
||||
f"{_grounding_block(primary)}\n\n"
|
||||
f"Learner goal: {learner_goal}\n"
|
||||
"Create one reasoning-heavy practice task for the learner. Keep it grounded in the supporting lessons and do not provide the full solution."
|
||||
)
|
||||
practice_text = _generate_role_text(
|
||||
provider,
|
||||
role="practice",
|
||||
prompt=practice_prompt,
|
||||
temperature=0.3,
|
||||
max_tokens=220,
|
||||
)
|
||||
|
||||
evaluation = evaluate_submission_with_skill(context, primary["concept_key"].split("::", 1)[-1], learner_submission)
|
||||
evaluator_prompt = (
|
||||
f"{_grounding_block(primary)}\n\n"
|
||||
f"Practice task: {practice_text}\n"
|
||||
f"Learner submission: {learner_submission}\n"
|
||||
f"Deterministic evaluator result: verdict={evaluation['verdict']}, aggregated={evaluation['aggregated']}\n"
|
||||
"Respond as Didactopus evaluator. Summarize strengths, real gaps, and one next revision target without pretending supported caveats are missing."
|
||||
)
|
||||
evaluator_text = _generate_role_text(
|
||||
provider,
|
||||
role="evaluator",
|
||||
prompt=evaluator_prompt,
|
||||
temperature=0.2,
|
||||
max_tokens=240,
|
||||
)
|
||||
|
||||
next_step_prompt = (
|
||||
f"{_grounding_block(primary)}\n\n"
|
||||
f"{_grounding_block(secondary)}\n\n"
|
||||
f"Evaluator feedback: {evaluator_text}\n"
|
||||
"Respond as Didactopus mentor. Give the next study action and explain why it follows from the grounded concept path."
|
||||
)
|
||||
next_step_text = _generate_role_text(
|
||||
provider,
|
||||
role="mentor",
|
||||
prompt=next_step_prompt,
|
||||
temperature=0.2,
|
||||
max_tokens=220,
|
||||
)
|
||||
|
||||
turns = [
|
||||
LearnerSessionTurn(role="user", label="Learner Goal", content=learner_goal),
|
||||
LearnerSessionTurn(role="assistant", label="Didactopus Mentor", content=mentor_text),
|
||||
LearnerSessionTurn(role="assistant", label="Didactopus Practice Designer", content=practice_text),
|
||||
LearnerSessionTurn(role="user", label="Learner Submission", content=learner_submission),
|
||||
LearnerSessionTurn(role="assistant", label="Didactopus Evaluator", content=evaluator_text),
|
||||
LearnerSessionTurn(role="assistant", label="Didactopus Mentor", content=next_step_text),
|
||||
]
|
||||
|
||||
return {
|
||||
"goal": learner_goal,
|
||||
"study_plan": study_plan,
|
||||
"primary_concept": primary,
|
||||
"secondary_concept": secondary,
|
||||
"practice_task": practice_text,
|
||||
"evaluation": evaluation,
|
||||
"turns": [turn.__dict__ for turn in turns],
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from .config import load_config
|
||||
from .learner_session import build_graph_grounded_session
|
||||
from .model_provider import ModelProvider
|
||||
from .ocw_skill_agent_demo import load_ocw_skill_context
|
||||
|
||||
|
||||
def run_learner_session_demo(
|
||||
config_path: str | Path,
|
||||
skill_dir: str | Path,
|
||||
out_path: str | Path | None = None,
|
||||
) -> dict:
|
||||
config = load_config(config_path)
|
||||
provider = ModelProvider(config.model_provider)
|
||||
context = load_ocw_skill_context(skill_dir)
|
||||
payload = build_graph_grounded_session(
|
||||
context=context,
|
||||
provider=provider,
|
||||
learner_goal="Help me understand how Shannon entropy leads into channel capacity and thermodynamic 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:
|
||||
Path(out_path).write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
||||
return payload
|
||||
|
||||
|
||||
def main() -> None:
|
||||
import argparse
|
||||
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
parser = argparse.ArgumentParser(description="Run a graph-grounded learner session demo for Didactopus.")
|
||||
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"))
|
||||
args = parser.parse_args()
|
||||
payload = run_learner_session_demo(args.config, args.skill_dir, args.out)
|
||||
print(json.dumps(payload, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -6,6 +6,7 @@ from pathlib import Path
|
|||
from .agentic_loop import AgenticStudentState, integrate_attempt
|
||||
from .artifact_registry import validate_pack
|
||||
from .course_ingestion_compliance import build_pack_compliance_manifest, load_sources, write_manifest
|
||||
from .course_repo import bootstrap_course_repo, resolve_course_repo
|
||||
from .document_adapters import adapt_documents
|
||||
from .evaluator_pipeline import LearnerAttempt
|
||||
from .graph_builder import build_concept_graph
|
||||
|
|
@ -134,6 +135,58 @@ def _select_target_concept(pack_name: str, concepts: list, preferred_id: str = "
|
|||
return f"{pack_name}::{ids[-1]}"
|
||||
|
||||
|
||||
def resolve_ocw_demo_paths(
|
||||
root: Path,
|
||||
course_repo: str | Path | None = None,
|
||||
course_source: str | Path | None = None,
|
||||
source_inventory: str | Path | None = None,
|
||||
pack_dir: str | Path | None = None,
|
||||
run_dir: str | Path | None = None,
|
||||
skill_dir: str | Path | None = None,
|
||||
) -> dict[str, str]:
|
||||
if course_repo is not None:
|
||||
repo = resolve_course_repo(course_repo)
|
||||
return {
|
||||
"course_source": str(Path(course_source) if course_source is not None else Path(repo.source_dir)),
|
||||
"source_inventory": str(Path(source_inventory) if source_inventory is not None else Path(repo.source_inventory)),
|
||||
"pack_dir": str(Path(pack_dir) if pack_dir is not None else Path(repo.generated_pack_dir or (root / "domain-packs" / repo.course_id))),
|
||||
"run_dir": str(Path(run_dir) if run_dir is not None else Path(repo.generated_run_dir or (root / "examples" / f"{repo.course_id}-run"))),
|
||||
"skill_dir": str(Path(skill_dir) if skill_dir is not None else Path(repo.generated_skill_dir or (root / "skills" / f"{repo.course_id}-agent"))),
|
||||
}
|
||||
return {
|
||||
"course_source": str(Path(course_source) if course_source is not None else root / "examples" / "ocw-information-entropy" / "course"),
|
||||
"source_inventory": str(Path(source_inventory) if source_inventory is not None else root / "examples" / "ocw-information-entropy" / "sources.yaml"),
|
||||
"pack_dir": str(Path(pack_dir) if pack_dir is not None else root / "domain-packs" / "mit-ocw-information-entropy"),
|
||||
"run_dir": str(Path(run_dir) if run_dir is not None else root / "examples" / "ocw-information-entropy-run"),
|
||||
"skill_dir": str(Path(skill_dir) if skill_dir is not None else root / "skills" / "ocw-information-entropy-agent"),
|
||||
}
|
||||
|
||||
|
||||
def bootstrap_ocw_course_repo_target(
|
||||
target_dir: str | Path,
|
||||
root: Path,
|
||||
course_source: str | Path | None = None,
|
||||
source_inventory: str | Path | None = None,
|
||||
) -> dict[str, str]:
|
||||
source_path = Path(course_source) if course_source is not None else root / "examples" / "ocw-information-entropy" / "course"
|
||||
inventory_path = Path(source_inventory) if source_inventory is not None else root / "examples" / "ocw-information-entropy" / "sources.yaml"
|
||||
resolved = bootstrap_course_repo(
|
||||
target_dir=target_dir,
|
||||
course_id="mit-ocw-information-entropy",
|
||||
display_name="MIT OCW Information and Entropy",
|
||||
course_source=source_path,
|
||||
source_inventory=inventory_path,
|
||||
license_family="CC BY-NC-SA 4.0",
|
||||
)
|
||||
return {
|
||||
"course_source": resolved.source_dir,
|
||||
"source_inventory": resolved.source_inventory,
|
||||
"pack_dir": resolved.generated_pack_dir or str(Path(resolved.repo_root) / "generated" / "pack"),
|
||||
"run_dir": resolved.generated_run_dir or str(Path(resolved.repo_root) / "generated" / "run"),
|
||||
"skill_dir": resolved.generated_skill_dir or str(Path(resolved.repo_root) / "generated" / "skill"),
|
||||
}
|
||||
|
||||
|
||||
def run_ocw_information_entropy_demo(
|
||||
course_source: str | Path,
|
||||
source_inventory: str | Path,
|
||||
|
|
@ -233,35 +286,40 @@ def main() -> None:
|
|||
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
parser = argparse.ArgumentParser(description="Generate a domain pack and skill bundle from MIT OCW Information and Entropy.")
|
||||
parser.add_argument(
|
||||
"--course-source",
|
||||
default=str(root / "examples" / "ocw-information-entropy" / "course"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--source-inventory",
|
||||
default=str(root / "examples" / "ocw-information-entropy" / "sources.yaml"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pack-dir",
|
||||
default=str(root / "domain-packs" / "mit-ocw-information-entropy"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--run-dir",
|
||||
default=str(root / "examples" / "ocw-information-entropy-run"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skill-dir",
|
||||
default=str(root / "skills" / "ocw-information-entropy-agent"),
|
||||
)
|
||||
parser.add_argument("--course-repo")
|
||||
parser.add_argument("--course-repo-target")
|
||||
parser.add_argument("--course-source")
|
||||
parser.add_argument("--source-inventory")
|
||||
parser.add_argument("--pack-dir")
|
||||
parser.add_argument("--run-dir")
|
||||
parser.add_argument("--skill-dir")
|
||||
args = parser.parse_args()
|
||||
|
||||
summary = run_ocw_information_entropy_demo(
|
||||
if args.course_repo_target:
|
||||
resolved = bootstrap_ocw_course_repo_target(
|
||||
target_dir=args.course_repo_target,
|
||||
root=root,
|
||||
course_source=args.course_source,
|
||||
source_inventory=args.source_inventory,
|
||||
)
|
||||
else:
|
||||
resolved = resolve_ocw_demo_paths(
|
||||
root,
|
||||
course_repo=args.course_repo,
|
||||
course_source=args.course_source,
|
||||
source_inventory=args.source_inventory,
|
||||
pack_dir=args.pack_dir,
|
||||
run_dir=args.run_dir,
|
||||
skill_dir=args.skill_dir,
|
||||
)
|
||||
|
||||
summary = run_ocw_information_entropy_demo(
|
||||
course_source=resolved["course_source"],
|
||||
source_inventory=resolved["source_inventory"],
|
||||
pack_dir=resolved["pack_dir"],
|
||||
run_dir=resolved["run_dir"],
|
||||
skill_dir=resolved["skill_dir"],
|
||||
)
|
||||
print(json.dumps(summary, indent=2))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
import json, yaml
|
||||
from .course_repo import resolve_course_repo
|
||||
from .review_schema import ReviewSession
|
||||
|
||||
def export_review_state_json(session: ReviewSession, path: str | Path) -> None:
|
||||
|
|
@ -34,6 +35,13 @@ def export_promoted_pack(session: ReviewSession, outdir: str | Path) -> None:
|
|||
(outdir / "license_attribution.json").write_text(json.dumps(session.draft_pack.attribution, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def export_promoted_pack_to_course_repo(session: ReviewSession, course_repo: str | Path, outdir: str | Path | None = None) -> Path:
|
||||
resolved = resolve_course_repo(course_repo)
|
||||
target = Path(outdir) if outdir is not None else Path(resolved.generated_pack_dir or (Path(resolved.repo_root) / "generated" / "pack"))
|
||||
export_promoted_pack(session, target)
|
||||
return target
|
||||
|
||||
|
||||
def export_review_ui_data(session: ReviewSession, outdir: str | Path) -> None:
|
||||
outdir = Path(outdir)
|
||||
outdir.mkdir(parents=True, exist_ok=True)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
from pathlib import Path
|
||||
|
||||
from didactopus.course_repo import bootstrap_course_repo, load_course_repo_manifest, resolve_course_repo
|
||||
from didactopus.ocw_information_entropy_demo import bootstrap_ocw_course_repo_target, resolve_ocw_demo_paths
|
||||
|
||||
|
||||
def test_load_and_resolve_course_repo_manifest(tmp_path: Path) -> None:
|
||||
repo = tmp_path / "repo"
|
||||
repo.mkdir()
|
||||
(repo / "course").mkdir()
|
||||
(repo / "sources.yaml").write_text("sources: []\n", encoding="utf-8")
|
||||
(repo / "didactopus-course.yaml").write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"course_id: sample-course",
|
||||
"display_name: Sample Course",
|
||||
"source_dir: course",
|
||||
"source_inventory: sources.yaml",
|
||||
"license_family: CC BY-NC-SA 4.0",
|
||||
"generated_pack_dir: generated/pack",
|
||||
"generated_run_dir: generated/run",
|
||||
"generated_skill_dir: generated/skill",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
manifest = load_course_repo_manifest(repo)
|
||||
resolved = resolve_course_repo(repo)
|
||||
|
||||
assert manifest.course_id == "sample-course"
|
||||
assert resolved.course_id == "sample-course"
|
||||
assert resolved.source_dir.endswith("/repo/course")
|
||||
assert resolved.generated_pack_dir.endswith("/repo/generated/pack")
|
||||
|
||||
|
||||
def test_resolve_ocw_demo_paths_from_course_repo_manifest() -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
resolved = resolve_ocw_demo_paths(
|
||||
root,
|
||||
course_repo=root / "examples" / "ocw-information-entropy",
|
||||
)
|
||||
|
||||
assert resolved["course_source"].endswith("/examples/ocw-information-entropy/course")
|
||||
assert resolved["source_inventory"].endswith("/examples/ocw-information-entropy/sources.yaml")
|
||||
assert resolved["pack_dir"].endswith("/domain-packs/mit-ocw-information-entropy")
|
||||
|
||||
|
||||
def test_bootstrap_course_repo_copies_source_bundle(tmp_path: Path) -> None:
|
||||
source_dir = tmp_path / "source"
|
||||
source_dir.mkdir()
|
||||
(source_dir / "lesson.md").write_text("# T\n\n## M\n### L\nBody.", encoding="utf-8")
|
||||
inventory = tmp_path / "sources.yaml"
|
||||
inventory.write_text("sources: []\n", encoding="utf-8")
|
||||
|
||||
resolved = bootstrap_course_repo(
|
||||
target_dir=tmp_path / "repo",
|
||||
course_id="sample-course",
|
||||
display_name="Sample Course",
|
||||
course_source=source_dir,
|
||||
source_inventory=inventory,
|
||||
license_family="CC BY-NC-SA 4.0",
|
||||
)
|
||||
|
||||
assert Path(resolved.manifest_path).exists()
|
||||
assert Path(resolved.source_dir, "lesson.md").exists()
|
||||
assert Path(resolved.source_inventory).exists()
|
||||
|
||||
|
||||
def test_bootstrap_ocw_course_repo_target_returns_generated_paths(tmp_path: Path) -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
resolved = bootstrap_ocw_course_repo_target(
|
||||
target_dir=tmp_path / "repo",
|
||||
root=root,
|
||||
)
|
||||
assert resolved["course_source"].endswith("/repo/course")
|
||||
assert resolved["pack_dir"].endswith("/repo/generated/pack")
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
from pathlib import Path
|
||||
|
||||
from didactopus.config import load_config
|
||||
from didactopus.learner_session import build_graph_grounded_session
|
||||
from didactopus.learner_session_demo import run_learner_session_demo
|
||||
from didactopus.model_provider import ModelProvider
|
||||
from didactopus.ocw_skill_agent_demo import load_ocw_skill_context
|
||||
|
||||
|
||||
def test_build_graph_grounded_session_uses_grounded_steps() -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
context = load_ocw_skill_context(root / "skills" / "ocw-information-entropy-agent")
|
||||
provider = ModelProvider(load_config(root / "configs" / "config.example.yaml").model_provider)
|
||||
|
||||
payload = build_graph_grounded_session(
|
||||
context=context,
|
||||
provider=provider,
|
||||
learner_goal="Help me connect Shannon entropy and channel capacity.",
|
||||
learner_submission="Entropy measures uncertainty because unlikely outcomes carry more information, but one limitation is that idealized source models may not match physical systems.",
|
||||
)
|
||||
|
||||
assert payload["study_plan"]["steps"]
|
||||
assert payload["primary_concept"]["supporting_lessons"]
|
||||
assert payload["evaluation"]["verdict"] in {"acceptable", "needs_revision"}
|
||||
assert len(payload["turns"]) == 6
|
||||
assert any("Grounding fragments" in turn["content"] or "Concept:" in turn["content"] for turn in payload["turns"])
|
||||
|
||||
|
||||
def test_run_learner_session_demo_writes_output(tmp_path: Path) -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
payload = run_learner_session_demo(
|
||||
root / "configs" / "config.example.yaml",
|
||||
root / "skills" / "ocw-information-entropy-agent",
|
||||
tmp_path / "session.json",
|
||||
)
|
||||
|
||||
assert (tmp_path / "session.json").exists()
|
||||
assert payload["practice_task"]
|
||||
assert payload["evaluation"]["aggregated"]
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
from pathlib import Path
|
||||
from didactopus.review_schema import DraftPackData, ConceptReviewEntry, ReviewSession
|
||||
from didactopus.review_export import export_review_state_json, export_promoted_pack, export_review_ui_data
|
||||
from didactopus.review_export import export_review_state_json, export_promoted_pack, export_promoted_pack_to_course_repo, export_review_ui_data
|
||||
|
||||
|
||||
def test_exports(tmp_path: Path) -> None:
|
||||
|
|
@ -18,3 +18,34 @@ def test_exports(tmp_path: Path) -> None:
|
|||
assert (tmp_path / "review_session.json").exists()
|
||||
assert (tmp_path / "review_data.json").exists()
|
||||
assert (tmp_path / "promoted" / "pack.yaml").exists()
|
||||
|
||||
|
||||
def test_export_promoted_pack_to_course_repo(tmp_path: Path) -> None:
|
||||
repo = tmp_path / "repo"
|
||||
repo.mkdir()
|
||||
(repo / "course").mkdir()
|
||||
(repo / "sources.yaml").write_text("sources: []\n", encoding="utf-8")
|
||||
(repo / "didactopus-course.yaml").write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"course_id: sample-course",
|
||||
"display_name: Sample Course",
|
||||
"source_dir: course",
|
||||
"source_inventory: sources.yaml",
|
||||
"generated_pack_dir: generated/pack",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
session = ReviewSession(
|
||||
reviewer="R",
|
||||
draft_pack=DraftPackData(
|
||||
pack={"name": "test", "version": "0.1.0-draft"},
|
||||
concepts=[ConceptReviewEntry(concept_id="c1", title="C1", status="trusted")],
|
||||
attribution={"rights_note": "REVIEW REQUIRED"},
|
||||
),
|
||||
)
|
||||
|
||||
target = export_promoted_pack_to_course_repo(session, repo)
|
||||
assert target.exists()
|
||||
assert (target / "pack.yaml").exists()
|
||||
|
|
|
|||
Loading…
Reference in New Issue