diff --git a/scripts/generate_search_index.py b/scripts/generate_search_index.py index fbb7b7a..7fb17f7 100644 --- a/scripts/generate_search_index.py +++ b/scripts/generate_search_index.py @@ -25,6 +25,9 @@ * ``tags`` — from the manifest, else ``[]`` * ``visibility`` — consumer-display hint (engine schema 1.8); absent or out-of-enum normalizes to ``"visible"`` + * ``review_status`` - three-state review standing (engine schema 1.9), + derived from ORIGIN; absent or out-of-enum normalizes to ``"authored"``. + Consumers derive "advertisable" as ``review_status != "generated"`` * ``ai_validated``— ``true`` if the set/lesson carries an ``ai_validation`` block * ``trust_level`` — from ``recommended-repos.json``, else ``1`` @@ -47,6 +50,7 @@ SCHEMA_VERSION = "1.0" DEFAULT_TRUST_LEVEL = 1 VISIBILITY_VALUES = ("visible", "hidden") +REVIEW_STATUS_VALUES = ("authored", "generated", "reviewed") # --------------------------------------------------------------------------- # @@ -128,6 +132,15 @@ def load_recommended_trust() -> dict[str, int]: return trust +def normalize_review_status(raw_status: object) -> str: + """Engine-parity projection of the manifest ``review_status`` flag. + + Absent or out-of-enum folds back to ``"authored"`` (legacy hand-written + content), so consumers filter without their own defaulting. + """ + return raw_status if raw_status in REVIEW_STATUS_VALUES else "authored" + + def normalize_visibility(raw_visibility: object) -> str: """Engine-parity projection of the manifest ``visibility`` flag. @@ -207,6 +220,7 @@ def build_set_entry(root_set: dict) -> tuple[dict, list[str]]: "card_count": card_count, "tags": merged.get("tags") or [], "visibility": normalize_visibility(merged.get("visibility")), + "review_status": normalize_review_status(merged.get("review_status")), "ai_validated": has_ai_validation(set_manifest, lessons), "trust_level": None, # filled in by caller (repo-level) "book": merged.get("book"), @@ -270,6 +284,7 @@ def _now_iso() -> str: "lesson_count", "card_count", "visibility", + "review_status", ) diff --git a/search-index.json b/search-index.json index e9d2f0a..e961efc 100644 --- a/search-index.json +++ b/search-index.json @@ -1,6 +1,6 @@ { "repo": "astrapi69/alc-programming", - "generated": "2026-07-31T15:48:40Z", + "generated": "2026-07-31T16:08:57Z", "schema_version": "1.0", "sets": [ { @@ -19,6 +19,7 @@ "python" ], "visibility": "visible", + "review_status": "authored", "ai_validated": false, "trust_level": 1, "book": null, @@ -41,6 +42,7 @@ "grundlagen" ], "visibility": "visible", + "review_status": "authored", "ai_validated": false, "trust_level": 1, "book": null, @@ -64,6 +66,7 @@ "multiple-choice" ], "visibility": "visible", + "review_status": "authored", "ai_validated": false, "trust_level": 1, "book": null, diff --git a/tests/test_generate_search_index.py b/tests/test_generate_search_index.py index 8d85182..4494249 100644 --- a/tests/test_generate_search_index.py +++ b/tests/test_generate_search_index.py @@ -85,3 +85,27 @@ def test_every_index_entry_carries_visibility() -> None: assert index["sets"], "index carries no sets" for entry in index["sets"]: assert entry["visibility"] in ("visible", "hidden") + + +def test_absent_review_status_defaults_to_authored() -> None: + assert gsi.normalize_review_status(None) == "authored" + + +def test_review_status_states_pass_through() -> None: + for state in ("authored", "generated", "reviewed"): + assert gsi.normalize_review_status(state) == state + + +def test_out_of_enum_review_status_normalizes_to_authored() -> None: + assert gsi.normalize_review_status("verified") == "authored" + + +def test_every_index_entry_carries_review_status() -> None: + """The field has to reach the INDEX, not just the manifest: the index is + what consumers read, so a badge counting advertisable sets would + otherwise count every set (engine#94).""" + index, build_errors = gsi.build_index() + assert not build_errors + assert index["sets"], "index carries no sets" + for entry in index["sets"]: + assert entry["review_status"] in ("authored", "generated", "reviewed")