168 lines
6.7 KiB
Python
168 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import json
|
|
import yaml
|
|
from .course_schema import NormalizedCourse, ConceptCandidate, DraftPack
|
|
|
|
|
|
def build_source_corpus(course: NormalizedCourse) -> dict:
|
|
fragments = []
|
|
for module in course.modules:
|
|
for lesson in module.lessons:
|
|
body = lesson.body.strip()
|
|
if body:
|
|
fragments.append(
|
|
{
|
|
"fragment_id": f"{module.title}::{lesson.title}::body".lower().replace(" ", "-"),
|
|
"kind": "lesson_body",
|
|
"module_title": module.title,
|
|
"lesson_title": lesson.title,
|
|
"text": body,
|
|
"source_refs": list(lesson.source_refs),
|
|
"objectives": list(lesson.objectives),
|
|
"exercises": list(lesson.exercises),
|
|
"key_terms": list(lesson.key_terms),
|
|
}
|
|
)
|
|
for idx, objective in enumerate(lesson.objectives, start=1):
|
|
fragments.append(
|
|
{
|
|
"fragment_id": f"{module.title}::{lesson.title}::objective-{idx}".lower().replace(" ", "-"),
|
|
"kind": "objective",
|
|
"module_title": module.title,
|
|
"lesson_title": lesson.title,
|
|
"text": objective,
|
|
"source_refs": list(lesson.source_refs),
|
|
}
|
|
)
|
|
for idx, exercise in enumerate(lesson.exercises, start=1):
|
|
fragments.append(
|
|
{
|
|
"fragment_id": f"{module.title}::{lesson.title}::exercise-{idx}".lower().replace(" ", "-"),
|
|
"kind": "exercise",
|
|
"module_title": module.title,
|
|
"lesson_title": lesson.title,
|
|
"text": exercise,
|
|
"source_refs": list(lesson.source_refs),
|
|
}
|
|
)
|
|
|
|
return {
|
|
"course_title": course.title,
|
|
"rights_note": course.rights_note,
|
|
"sources": [
|
|
{
|
|
"source_path": src.source_path,
|
|
"source_type": src.source_type,
|
|
"title": src.title,
|
|
"metadata": getattr(src, "metadata", {}),
|
|
}
|
|
for src in course.source_records
|
|
],
|
|
"fragments": fragments,
|
|
}
|
|
|
|
|
|
def build_draft_pack(
|
|
course: NormalizedCourse,
|
|
concepts: list[ConceptCandidate],
|
|
author: str,
|
|
license_name: str,
|
|
review_flags: list[str],
|
|
conflicts: list[str] | None = None,
|
|
) -> DraftPack:
|
|
pack_name = course.title.lower().replace(" ", "-")
|
|
pack = {
|
|
"name": pack_name,
|
|
"display_name": course.title,
|
|
"version": "0.1.0-draft",
|
|
"schema_version": "1",
|
|
"didactopus_min_version": "0.1.0",
|
|
"didactopus_max_version": "0.9.99",
|
|
"description": f"Draft topic pack generated from multi-course inputs for '{course.title}'.",
|
|
"author": author,
|
|
"license": license_name,
|
|
"dependencies": [],
|
|
"overrides": [],
|
|
"profile_templates": {},
|
|
"cross_pack_links": [],
|
|
"supporting_artifacts": ["source_corpus.json"],
|
|
}
|
|
concepts_yaml = {
|
|
"concepts": [
|
|
{
|
|
"id": c.id,
|
|
"title": c.title,
|
|
"description": c.description,
|
|
"prerequisites": c.prerequisites,
|
|
"mastery_signals": c.mastery_signals,
|
|
"mastery_profile": {},
|
|
}
|
|
for c in concepts
|
|
]
|
|
}
|
|
roadmap = {
|
|
"stages": [
|
|
{
|
|
"id": f"stage-{i+1}",
|
|
"title": module.title,
|
|
"concepts": [c.id for c in concepts if module.title in c.source_modules and c.title in c.source_lessons],
|
|
"checkpoint": [ex for lesson in module.lessons for ex in lesson.exercises[:2]],
|
|
}
|
|
for i, module in enumerate(course.modules)
|
|
]
|
|
}
|
|
project_items = []
|
|
for module in course.modules:
|
|
for lesson in module.lessons:
|
|
text = f"{lesson.title}\n{lesson.body}".lower()
|
|
if "project" in text or "capstone" in text:
|
|
project_items.append({
|
|
"id": lesson.title.lower().replace(" ", "-"),
|
|
"title": lesson.title,
|
|
"difficulty": "review-required",
|
|
"prerequisites": [],
|
|
"deliverables": ["project artifact"],
|
|
})
|
|
projects = {"projects": project_items}
|
|
rubrics = {"rubrics": [{"id": "draft-rubric", "title": "Draft Rubric", "criteria": ["correctness", "explanation"]}]}
|
|
attribution = {
|
|
"rights_note": course.rights_note,
|
|
"sources": [
|
|
{"source_path": src.source_path, "source_type": src.source_type, "title": src.title}
|
|
for src in course.source_records
|
|
],
|
|
}
|
|
return DraftPack(
|
|
pack=pack,
|
|
concepts=concepts_yaml,
|
|
roadmap=roadmap,
|
|
projects=projects,
|
|
rubrics=rubrics,
|
|
review_report=review_flags,
|
|
attribution=attribution,
|
|
conflicts=conflicts or [],
|
|
)
|
|
|
|
|
|
def write_draft_pack(pack: DraftPack, outdir: str | Path) -> None:
|
|
out = Path(outdir)
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
(out / "pack.yaml").write_text(yaml.safe_dump(pack.pack, sort_keys=False), encoding="utf-8")
|
|
(out / "concepts.yaml").write_text(yaml.safe_dump(pack.concepts, sort_keys=False), encoding="utf-8")
|
|
(out / "roadmap.yaml").write_text(yaml.safe_dump(pack.roadmap, sort_keys=False), encoding="utf-8")
|
|
(out / "projects.yaml").write_text(yaml.safe_dump(pack.projects, sort_keys=False), encoding="utf-8")
|
|
(out / "rubrics.yaml").write_text(yaml.safe_dump(pack.rubrics, sort_keys=False), encoding="utf-8")
|
|
review_lines = ["# Review Report", ""] + [f"- {flag}" for flag in pack.review_report] if pack.review_report else ["# Review Report", "", "- none"]
|
|
(out / "review_report.md").write_text("\n".join(review_lines), encoding="utf-8")
|
|
conflict_lines = ["# Conflict Report", ""] + [f"- {flag}" for flag in pack.conflicts] if pack.conflicts else ["# Conflict Report", "", "- none"]
|
|
(out / "conflict_report.md").write_text("\n".join(conflict_lines), encoding="utf-8")
|
|
(out / "license_attribution.json").write_text(json.dumps(pack.attribution, indent=2), encoding="utf-8")
|
|
|
|
|
|
def write_source_corpus(course: NormalizedCourse, outdir: str | Path) -> None:
|
|
out = Path(outdir)
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
(out / "source_corpus.json").write_text(json.dumps(build_source_corpus(course), indent=2), encoding="utf-8")
|