Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions scripts/generate_search_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand All @@ -47,6 +50,7 @@
SCHEMA_VERSION = "1.0"
DEFAULT_TRUST_LEVEL = 1
VISIBILITY_VALUES = ("visible", "hidden")
REVIEW_STATUS_VALUES = ("authored", "generated", "reviewed")


# --------------------------------------------------------------------------- #
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -270,6 +284,7 @@ def _now_iso() -> str:
"lesson_count",
"card_count",
"visibility",
"review_status",
)


Expand Down
5 changes: 4 additions & 1 deletion search-index.json
Original file line number Diff line number Diff line change
@@ -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": [
{
Expand All @@ -19,6 +19,7 @@
"python"
],
"visibility": "visible",
"review_status": "authored",
"ai_validated": false,
"trust_level": 1,
"book": null,
Expand All @@ -41,6 +42,7 @@
"grundlagen"
],
"visibility": "visible",
"review_status": "authored",
"ai_validated": false,
"trust_level": 1,
"book": null,
Expand All @@ -64,6 +66,7 @@
"multiple-choice"
],
"visibility": "visible",
"review_status": "authored",
"ai_validated": false,
"trust_level": 1,
"book": null,
Expand Down
24 changes: 24 additions & 0 deletions tests/test_generate_search_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Loading