From e088e2ef1a5bc41dc515deb96f0628cb16b99c1f Mon Sep 17 00:00:00 2001 From: bofandra Date: Thu, 1 Oct 2026 00:41:23 +0700 Subject: [PATCH 1/8] Keep Module 2 before identity-operator syntax --- week-02/exercises.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/week-02/exercises.md b/week-02/exercises.md index 3d3fbf5..173527e 100644 --- a/week-02/exercises.md +++ b/week-02/exercises.md @@ -17,9 +17,11 @@ Answer: 1. How many class definitions are there? 2. How many `Book` instances are there? -3. Is `book_1 is book_2` expected to be `True` or `False`? +3. Are `book_1` and `book_2` two separate instances created from the same class? Explain without using Python identity operators yet. 4. What is the conceptual relationship between `Book` and `book_1`? +> Python's `is` identity operator is introduced formally in Module 11. Module 2 only requires the concept that separate constructor calls create separate instances. + --- ## Exercise 2 — Build a Class with `__init__()` From 953b0acaecd0948f1a165441ca51aaaa756642a7 Mon Sep 17 00:00:00 2001 From: bofandra Date: Thu, 1 Oct 2026 00:41:37 +0700 Subject: [PATCH 2/8] Clarify has-a versus composition in mastery feedback --- MASTERY_CHECKS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MASTERY_CHECKS.md b/MASTERY_CHECKS.md index 086f131..9a7f81c 100644 --- a/MASTERY_CHECKS.md +++ b/MASTERY_CHECKS.md @@ -186,7 +186,7 @@ Classify each relationship: “Car–Engine” and “Doctor–Person.”
Feedback -Car **has an** Engine, so composition/reference is appropriate. Doctor **is a** Person, so inheritance may be appropriate if that specialization is meaningful in the model. +Car **has an** Engine, so a whole–part has-a relationship is appropriate; the Python implementation will typically hold an Engine reference, while exact lifecycle ownership depends on the model. Doctor **is a** Person, so inheritance may be appropriate if that specialization is meaningful in the model.
From bef2c6446f4bb32cb843b0ed2560723eb4ac1b8b Mon Sep 17 00:00:00 2001 From: bofandra Date: Thu, 1 Oct 2026 00:41:40 +0700 Subject: [PATCH 3/8] Align Module 3 mastery with validation progression --- SELF_ASSESSMENT_GUIDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SELF_ASSESSMENT_GUIDE.md b/SELF_ASSESSMENT_GUIDE.md index cfe325c..2c32aba 100644 --- a/SELF_ASSESSMENT_GUIDE.md +++ b/SELF_ASSESSMENT_GUIDE.md @@ -18,7 +18,7 @@ For each module, aim to reach all four levels: | 0 | write small Python functions, conditions, loops, and collection operations without step-by-step help | | 1 | identify meaningful objects and explain identity, state, behavior, and collaboration | | 2 | define classes, create instances, and explain `__init__`, `self`, and instance state | -| 3 | assign responsibilities and implement behavior that changes valid object state | +| 3 | assign responsibilities and implement meaningful behavior that reads or changes object state, while recognizing that validation rules are introduced in Module 4 | | 4 | protect state behind meaningful operations and distinguish a class invariant from a state-transition rule | | 5 | distinguish general references/associations, whole–part has-a relationships, and is-a inheritance | | 6 | use inheritance only when the subtype relationship is meaningful | From 7a9206a424f60926281b023894f1af60fd990faa Mon Sep 17 00:00:00 2001 From: bofandra Date: Thu, 1 Oct 2026 00:42:49 +0700 Subject: [PATCH 4/8] Guard assessment concept progression --- scripts/validate_course.py | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/scripts/validate_course.py b/scripts/validate_course.py index 7bf42bc..01cc9da 100644 --- a/scripts/validate_course.py +++ b/scripts/validate_course.py @@ -652,6 +652,57 @@ def validate_assessment_structure(errors: list[str]) -> None: ) break + +def validate_assessment_concept_progression(errors: list[str]) -> None: + """Keep learner-facing assessments from requiring concepts before they are taught.""" + + rules = [ + (4, "@property", "property-based encapsulation"), + (9, "@abstractmethod", "abstract/deferred class implementation"), + (9, "from abc import", "abstract/deferred class implementation"), + (10, "__mro__", "method resolution order"), + (12, "__eq__", "operator overloading/equality implementation"), + (12, "__add__", "operator overloading/addition implementation"), + (13, "raise ValueError", "explicit exception raising"), + (13, "except ValueError", "exception handling"), + (13, "try:", "exception handling"), + (13, "assert ", "assertion-based internal checks"), + (15, "Strategy-style", "design-pattern implementation"), + (15, "Factory Method-style", "design-pattern implementation"), + ] + + for module in list(range(1, 8)) + list(range(9, 16)): + folder = ROOT / f"week-{module:02d}" + for name in ["exercises.md", "quiz.md", "assignment.md"]: + path = folder / name + if not path.exists(): + continue + + text = path.read_text(encoding="utf-8") + rel = path.relative_to(ROOT) + + for introduced_in, fragment, label in rules: + if module < introduced_in and fragment in text: + fail( + errors, + f"{rel}: requires {label} before Module {introduced_in}", + ) + + if module < 11: + code_fragments = re.findall(r"`([^`\n]+)`", text) + python_blocks = re.findall( + r"```python\s*(.*?)```", + text, + flags=re.DOTALL, + ) + for fragment in code_fragments + python_blocks: + if re.search(r"\s+is(?:\s+not)?\s+", fragment): + fail( + errors, + f"{rel}: uses Python identity operator before Module 11", + ) + break + def validate_public_assessment_safety(errors: list[str]) -> None: for path in sorted(ROOT.glob("week-*/instructor-notes.md")): text = path.read_text(encoding="utf-8").lower() @@ -686,6 +737,7 @@ def main() -> int: validate_local_setup_guide(errors) validate_publication_metadata(errors) validate_assessment_structure(errors) + validate_assessment_concept_progression(errors) validate_public_assessment_safety(errors) validate_open_course_neutrality(errors) @@ -718,6 +770,7 @@ def main() -> int: print("- local reproducibility guide: OK") print("- citation/accessibility metadata: OK") print("- assignment rubric / quiz structure: OK") + print("- assessment concept progression: OK") print("- learner-facing assessment neutrality: OK") print("- public assessment-key guard: OK") print("- gradebook calculator self-test: OK") From 817a5c2a9e61a1402290064bf0fd296636ee08bc Mon Sep 17 00:00:00 2001 From: bofandra Date: Thu, 1 Oct 2026 00:42:52 +0700 Subject: [PATCH 5/8] Document assessment progression audit --- EXECUTION_AUDIT.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EXECUTION_AUDIT.md b/EXECUTION_AUDIT.md index f5148d7..4aa8898 100644 --- a/EXECUTION_AUDIT.md +++ b/EXECUTION_AUDIT.md @@ -16,7 +16,8 @@ The audit checks the public course repository end-to-end: - relative Markdown links; - public learner-onboarding navigation from `README.md` and `START_HERE.md`; - Previous / Course Map / Next navigation across Module 0–16; -- accidental publication of module quiz answer keys. +- accidental publication of module quiz answer keys; +- learner-facing assessment concept progression, so later syntax/mechanisms do not become early requirements. ## Result @@ -32,6 +33,7 @@ Module 12 models.py/main.py PASS Relative Markdown links PASS Learner onboarding nav PASS Module course navigation PASS +Assessment progression PASS Public quiz-key guard PASS ``` From e597696bcdefef23e0b1ff1823a95835c1094dd6 Mon Sep 17 00:00:00 2001 From: bofandra Date: Thu, 1 Oct 2026 00:42:55 +0700 Subject: [PATCH 6/8] Record assessment progression cleanup --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de021c5..2afc987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,13 @@ The GitHub Actions validation workflow passed on `main` after the audit and lear ## Unreleased +### Assessment progression + +- removed premature use of Python's `is` identity operator from Module 2 exercises; Module 2 now assesses separate-instance reasoning without Module 11 syntax; +- clarified Module 5 mastery feedback so whole–part has-a is not automatically equated with strict composition/lifecycle ownership; +- aligned the Module 3 self-assessment checkpoint with the deliberate decision to postpone validation rules until Module 4; +- added a validator guard that prevents selected later-course mechanisms from becoming learner-facing assessment requirements before their introduction. + ### Reference discipline - added `SOURCE_BOUNDARY.md` to distinguish formal concepts, minimal Python implementation bridges, and out-of-scope additions; From eb9a8c0ef5deb7409cd5da0c48768b60d9561b12 Mon Sep 17 00:00:00 2001 From: bofandra Date: Thu, 1 Oct 2026 00:44:15 +0700 Subject: [PATCH 7/8] Refine assessment progression guard --- scripts/validate_course.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/scripts/validate_course.py b/scripts/validate_course.py index 01cc9da..632a984 100644 --- a/scripts/validate_course.py +++ b/scripts/validate_course.py @@ -4,6 +4,7 @@ from __future__ import annotations +import ast import contextlib import csv import io @@ -661,8 +662,6 @@ def validate_assessment_concept_progression(errors: list[str]) -> None: (9, "@abstractmethod", "abstract/deferred class implementation"), (9, "from abc import", "abstract/deferred class implementation"), (10, "__mro__", "method resolution order"), - (12, "__eq__", "operator overloading/equality implementation"), - (12, "__add__", "operator overloading/addition implementation"), (13, "raise ValueError", "explicit exception raising"), (13, "except ValueError", "exception handling"), (13, "try:", "exception handling"), @@ -671,6 +670,28 @@ def validate_assessment_concept_progression(errors: list[str]) -> None: (15, "Factory Method-style", "design-pattern implementation"), ] + implementation_patterns = [ + (12, re.compile(r"def\\s+__eq__\\s*\\("), "operator overloading/equality implementation"), + (12, re.compile(r"implement[^\\n]*`__eq__\\(\\)`", re.IGNORECASE), "operator overloading/equality implementation"), + (12, re.compile(r"def\\s+__add__\\s*\\("), "operator overloading/addition implementation"), + (12, re.compile(r"implement[^\\n]*`__add__\\(\\)`", re.IGNORECASE), "operator overloading/addition implementation"), + ] + + def uses_identity_operator(source: str) -> bool: + try: + tree = ast.parse(source) + except SyntaxError: + try: + tree = ast.parse(source, mode="eval") + except SyntaxError: + return False + + return any( + isinstance(node, ast.Compare) + and any(isinstance(op, (ast.Is, ast.IsNot)) for op in node.ops) + for node in ast.walk(tree) + ) + for module in list(range(1, 8)) + list(range(9, 16)): folder = ROOT / f"week-{module:02d}" for name in ["exercises.md", "quiz.md", "assignment.md"]: @@ -688,6 +709,13 @@ def validate_assessment_concept_progression(errors: list[str]) -> None: f"{rel}: requires {label} before Module {introduced_in}", ) + for introduced_in, pattern, label in implementation_patterns: + if module < introduced_in and pattern.search(text): + fail( + errors, + f"{rel}: requires {label} before Module {introduced_in}", + ) + if module < 11: code_fragments = re.findall(r"`([^`\n]+)`", text) python_blocks = re.findall( @@ -696,7 +724,7 @@ def validate_assessment_concept_progression(errors: list[str]) -> None: flags=re.DOTALL, ) for fragment in code_fragments + python_blocks: - if re.search(r"\s+is(?:\s+not)?\s+", fragment): + if uses_identity_operator(fragment): fail( errors, f"{rel}: uses Python identity operator before Module 11", From c6005817a7e9582ff8b04342b14f3e75694613c7 Mon Sep 17 00:00:00 2001 From: bofandra Date: Thu, 1 Oct 2026 00:44:57 +0700 Subject: [PATCH 8/8] Fix progression guard regexes --- scripts/validate_course.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/validate_course.py b/scripts/validate_course.py index 632a984..408ad4a 100644 --- a/scripts/validate_course.py +++ b/scripts/validate_course.py @@ -671,9 +671,9 @@ def validate_assessment_concept_progression(errors: list[str]) -> None: ] implementation_patterns = [ - (12, re.compile(r"def\\s+__eq__\\s*\\("), "operator overloading/equality implementation"), + (12, re.compile(r"def\s+__eq__\s*\("), "operator overloading/equality implementation"), (12, re.compile(r"implement[^\\n]*`__eq__\\(\\)`", re.IGNORECASE), "operator overloading/equality implementation"), - (12, re.compile(r"def\\s+__add__\\s*\\("), "operator overloading/addition implementation"), + (12, re.compile(r"def\s+__add__\s*\("), "operator overloading/addition implementation"), (12, re.compile(r"implement[^\\n]*`__add__\\(\\)`", re.IGNORECASE), "operator overloading/addition implementation"), ]