Skip to content

Shared-curriculum sub-modules: umbrella #396

Description

@alexeygrigorev

Design doc: _docs/planning/shared-curriculum-submodules-design.md (committed 7a9816e5) — read it in full before implementing any phase; this issue is a summary and phase tracker, not the source of truth.

Motivation

LLM Zoomcamp's real content already has modules that informally split into parts (module 1: "Part 1: RAG" / "Part 2: Agents" in its README prose; module 3 has five parts; module 4 has two) — but there is currently zero machine-readable representation of this anywhere in the pipeline. module.yaml has a flat units: list, and the website builds a flat SharedModule -> SharedLesson graph with no second level.

Verified in the real content: the current single homework for module 1 spans both parts (Q1-5 are Part 1, Q6 is Part 2) — so the model must support a module keeping ONE homework across its parts, not force a split. Separately, Part 2 already advertises a standalone workshop entry point, so a future cohort anchoring a homework to just one part is realistic, not hypothetical. Both shapes (homework on the whole module, homework on one part) are first-class requirements, not one common case and one rare edge case.

Design summary

  • SharedSubModule — a new, flat (one level only, no self-referential tree) model, required FK to SharedModule. Rejected a self-referential parent FK on SharedModule itself: every existing SharedModule query (module page, family syllabus count, sitemap, inventory, the two-segment router) would need a parent__isnull=True guard it doesn't have today, and SharedModule's own constraints (slug/position uniqueness, the cohort-identifier collision check) are root-shaped.
  • SharedLesson.sub_module — new nullable FK, validated to belong to the lesson's module. A module with no sub-modules is simply all-None — zero change for the common case.
  • Homework anchoringCohortSharedModule (the per-cohort homework placement) gains a nullable sub_module FK: a homework anchors to either the whole module OR one specific sub-module, per cohort, never both, never neither. Because homework is cohort-owned and the shared module graph isn't, two different cohorts can anchor differently against the same shared content (2026 keeps one module-level homework; a future 2027 could split it) with no change to the shared rows.
  • Three DB constraints ship in the first phase (not deferred): one module-level homework per module per cohort, one homework per sub-module per cohort, and each homework anchored at most once — see the design doc section 4 for the exact conditional-unique SQL shape (a plain 3-column unique constraint does NOT work here; Postgres treats NULLs as distinct).
  • Course-repository side: module.yaml gains an optional sub_modules: list (nested, exclusive with units:); cohort.yaml homework bindings gain an optional sub_module: <slug>. No schema version bump — additive schema-2 extension, safe because both the parser and the zoomcamp-ops checker already reject unknown keys (that strictness IS the rollout safety net). Folders stay flat; no lesson/image/code file moves — the grouping lives only in module.yaml, matching how authors already number parts in ranges (module 4's 0611 gap). Full field-by-field YAML shape and checker rule list in the design doc's "Course-repository representation" section.
  • llm-zoomcamp's real 2026 content needs zero changes to ship this — all seven of its homework bindings stay module-level anchors. Splitting sub_modules: into module.yaml for modules 1/3/4 is Phase D, separate from and not required by Phases A-C.

Phases (in dependency order)

Phase Summary
A Additive DB models only: SharedSubModule, nullable SharedLesson.sub_module, nullable CohortSharedModule.sub_module, the three constraints, clean() validation, admin. Zero renderer changes. Ships safely with zero sub-modules in real data.
B Website importer/parser support for optional sub_modules: in module.yaml and optional sub_module: in cohort homework bindings, plus all-or-nothing and lesson-contiguity import-time validators. Absent keys mean today's behavior, unchanged.
C Rendering: module page and rail group lessons by sub-module when present (flat fallback otherwise), place each anchored homework after its anchor's last lesson, ModuleFlowItem gains an optional sub-module, anchor-aware homework breadcrumb, inventory report gains the anchor.
D Course-repository side, in this exact order (each step independently revertible): (1) zoomcamp-ops checker rules/fixtures for the new schema + tests green; (2) website Phase B/C deployed and dry-run tested against the checker's fixture; (3) the pinned checker-workflow commit llm-zoomcamp's CI calls gets updated to the new pin (a reviewed change); (4) llm-zoomcamp content: author sub_modules: for modules 1, 3, 4 in three separate commits, CI green, merge, import. cohorts/2026/cohort.yaml is not touched — Homework 1 keeps closing the whole module.

Open questions (design doc has full detail; flagging here for visibility)

  1. Should a module ever be allowed both a module-level AND a sub-module-level homework anchor in the same cohort? Schema permits it (each has a well-defined rendering); if it should be forbidden, that's an opt-in zoomcamp-ops checker rule (mixed_homework_anchors), not a DB constraint. Not enabled by default.
  2. Phase D timing — author real sub_modules: content for llm-zoomcamp as soon as B/C ship, or leave the capability built but unused for now?
  3. Sub-module pages: this design gives parts headings + fragment anchors on the existing module page, not their own routes/URLs. Confirm that's sufficient (a standalone "Part 2: Agents" workshop page could be added later without touching content, since the sub-module slug is already a stable identity).
  4. Sub-module slug/title convention: proposed ordinal-free slugs (rag, agents) with README-matching titles ("Part 1: RAG") — confirm, or prefer ordinal-free titles with the website deriving "Part N" from list position.

Each phase will be filed as its own groomed sub-issue per _docs/PROCESS.md, dependent on the previous phase.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Important follow-upcoursesArea: coursesenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions