Audit effective category route coverage
This commit is contained in:
@@ -22,6 +22,7 @@ if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
import category_template_metadata as template_metadata_policy # noqa: E402
|
||||
import category_library as category_policy # noqa: E402
|
||||
import caption_naturalizer # noqa: E402
|
||||
import krea_formatter # noqa: E402
|
||||
import prompt_builder as pb # noqa: E402
|
||||
@@ -380,6 +381,138 @@ def _hardcore_template_metadata_errors(paths: list[Path]) -> list[tuple[str, str
|
||||
return errors
|
||||
|
||||
|
||||
def _item_candidates_for_coverage(subcategory: dict[str, Any]) -> list[Any]:
|
||||
items = subcategory.get("items")
|
||||
if isinstance(items, list) and items:
|
||||
return items
|
||||
return [subcategory.get("name") or subcategory.get("slug") or ""]
|
||||
|
||||
|
||||
def _configured_pool_errors(
|
||||
*,
|
||||
category: dict[str, Any],
|
||||
subcategory: dict[str, Any],
|
||||
item: Any,
|
||||
path: str,
|
||||
pool_label: str,
|
||||
direct_key: str,
|
||||
pool_key: str,
|
||||
pool_library: dict[str, list[Any]],
|
||||
inherit_key: str,
|
||||
) -> list[tuple[str, str, str]]:
|
||||
try:
|
||||
entries = category_policy.configured_pool(
|
||||
category,
|
||||
subcategory,
|
||||
item,
|
||||
direct_key,
|
||||
pool_key,
|
||||
pool_library,
|
||||
inherit_key,
|
||||
)
|
||||
except Exception as exc:
|
||||
return [("(category library)", f"{path}.{pool_label}", f"cannot resolve effective pool: {exc}")]
|
||||
if not entries:
|
||||
return [("(category library)", f"{path}.{pool_label}", "missing effective configured entries")]
|
||||
return []
|
||||
|
||||
|
||||
def _effective_category_coverage_errors(paths: list[Path]) -> list[tuple[str, str, str]]:
|
||||
# `paths` is accepted to keep this check grouped with the other category
|
||||
# validations. The effective route check uses the normalized loader because
|
||||
# generation also consumes normalized category objects.
|
||||
_ = paths
|
||||
categories = category_policy.load_category_library()
|
||||
scene_pools = category_policy.load_scene_pool_library()
|
||||
expression_pools = category_policy.load_expression_pool_library()
|
||||
composition_pools = category_policy.load_composition_pool_library()
|
||||
errors: list[tuple[str, str, str]] = []
|
||||
|
||||
if not categories:
|
||||
return [("(category library)", "categories", "no categories loaded")]
|
||||
|
||||
for category in categories:
|
||||
category_slug = str(category.get("slug") or category.get("name") or "category")
|
||||
category_path = f"categories.{category_slug}"
|
||||
subject_type = str(category.get("subject_type") or "").strip()
|
||||
if not subject_type:
|
||||
errors.append(("(category library)", f"{category_path}.subject_type", "missing subject_type"))
|
||||
|
||||
subcategories = category.get("subcategories")
|
||||
if not isinstance(subcategories, list) or not subcategories:
|
||||
errors.append(("(category library)", f"{category_path}.subcategories", "missing subcategories"))
|
||||
continue
|
||||
|
||||
for subcategory in subcategories:
|
||||
if not isinstance(subcategory, dict):
|
||||
errors.append(("(category library)", f"{category_path}.subcategories", "subcategory must be an object"))
|
||||
continue
|
||||
sub_slug = str(subcategory.get("slug") or subcategory.get("name") or "subcategory")
|
||||
sub_path = f"{category_path}.subcategories.{sub_slug}"
|
||||
effective_subject = str(subcategory.get("subject_type") or subject_type).strip()
|
||||
if not effective_subject:
|
||||
errors.append(("(category library)", f"{sub_path}.subject_type", "missing effective subject_type"))
|
||||
|
||||
has_items = isinstance(subcategory.get("items"), list) and bool(subcategory.get("items"))
|
||||
has_templates = isinstance(subcategory.get("item_templates"), list) and bool(subcategory.get("item_templates"))
|
||||
if not has_items and not has_templates:
|
||||
errors.append(("(category library)", f"{sub_path}.items", "missing items or item_templates"))
|
||||
|
||||
for item_index, item in enumerate(_item_candidates_for_coverage(subcategory)):
|
||||
item_path = f"{sub_path}.items[{item_index}]"
|
||||
errors.extend(
|
||||
_configured_pool_errors(
|
||||
category=category,
|
||||
subcategory=subcategory,
|
||||
item=item,
|
||||
path=item_path,
|
||||
pool_label="scenes",
|
||||
direct_key="scenes",
|
||||
pool_key="scene_pools",
|
||||
pool_library=scene_pools,
|
||||
inherit_key="inherit_scenes",
|
||||
)
|
||||
)
|
||||
errors.extend(
|
||||
_configured_pool_errors(
|
||||
category=category,
|
||||
subcategory=subcategory,
|
||||
item=item,
|
||||
path=item_path,
|
||||
pool_label="expressions",
|
||||
direct_key="expressions",
|
||||
pool_key="expression_pools",
|
||||
pool_library=expression_pools,
|
||||
inherit_key="inherit_expressions",
|
||||
)
|
||||
)
|
||||
errors.extend(
|
||||
_configured_pool_errors(
|
||||
category=category,
|
||||
subcategory=subcategory,
|
||||
item=item,
|
||||
path=item_path,
|
||||
pool_label="compositions",
|
||||
direct_key="compositions",
|
||||
pool_key="composition_pools",
|
||||
pool_library=composition_pools,
|
||||
inherit_key="inherit_compositions",
|
||||
)
|
||||
)
|
||||
|
||||
if category_slug == "hardcore_sexual_poses" and has_templates:
|
||||
metadata = subcategory.get("item_template_metadata")
|
||||
if not isinstance(metadata, dict):
|
||||
errors.append(("(category library)", f"{sub_path}.item_template_metadata", "missing route metadata"))
|
||||
continue
|
||||
normalized = template_metadata_policy.template_metadata(metadata)
|
||||
if not template_metadata_policy.template_action_family(normalized):
|
||||
errors.append(("(category library)", f"{sub_path}.item_template_metadata", "missing normalized action_family"))
|
||||
if not template_metadata_policy.template_position_family(normalized):
|
||||
errors.append(("(category library)", f"{sub_path}.item_template_metadata", "missing normalized position_family"))
|
||||
return errors
|
||||
|
||||
|
||||
def _smoke_case_names(path: Path) -> set[str]:
|
||||
if not path.exists():
|
||||
return set()
|
||||
@@ -743,6 +876,13 @@ def main() -> int:
|
||||
return 1
|
||||
print("OK: hardcore template subcategories define explicit route metadata defaults.")
|
||||
|
||||
print("\n# Effective Category Route Coverage Validation")
|
||||
category_coverage_errors = _effective_category_coverage_errors(category_paths)
|
||||
if category_coverage_errors:
|
||||
print_table(("Source", "Path", "Issue"), category_coverage_errors)
|
||||
return 1
|
||||
print("OK: category routes define effective item, scene, expression, composition, and route metadata coverage.")
|
||||
|
||||
print("\n# Routing Documentation Validation")
|
||||
routing_doc_errors = _routing_doc_errors()
|
||||
if routing_doc_errors:
|
||||
|
||||
Reference in New Issue
Block a user