Audit effective category route coverage
This commit is contained in:
@@ -888,6 +888,9 @@ The script does not import ComfyUI. It parses the repo and prints:
|
|||||||
- JSON reference validation for every `scene_pools`, `expression_pools`, and
|
- JSON reference validation for every `scene_pools`, `expression_pools`, and
|
||||||
`composition_pools` reference;
|
`composition_pools` reference;
|
||||||
- item template validation so `{placeholder}` names resolve to `item_axes`.
|
- item template validation so `{placeholder}` names resolve to `item_axes`.
|
||||||
|
- effective category route coverage so each normalized category path has
|
||||||
|
usable item, scene, expression, composition, and hardcore route metadata
|
||||||
|
before runtime fallbacks can hide a gap.
|
||||||
- route documentation validation so critical route modules are listed in this
|
- route documentation validation so critical route modules are listed in this
|
||||||
map and the architecture plan, and registered in `SMOKE_CASES` by their
|
map and the architecture plan, and registered in `SMOKE_CASES` by their
|
||||||
expected smoke cases.
|
expected smoke cases.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ if str(ROOT) not in sys.path:
|
|||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
import category_template_metadata as template_metadata_policy # noqa: E402
|
import category_template_metadata as template_metadata_policy # noqa: E402
|
||||||
|
import category_library as category_policy # noqa: E402
|
||||||
import caption_naturalizer # noqa: E402
|
import caption_naturalizer # noqa: E402
|
||||||
import krea_formatter # noqa: E402
|
import krea_formatter # noqa: E402
|
||||||
import prompt_builder as pb # 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
|
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]:
|
def _smoke_case_names(path: Path) -> set[str]:
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return set()
|
return set()
|
||||||
@@ -743,6 +876,13 @@ def main() -> int:
|
|||||||
return 1
|
return 1
|
||||||
print("OK: hardcore template subcategories define explicit route metadata defaults.")
|
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")
|
print("\n# Routing Documentation Validation")
|
||||||
routing_doc_errors = _routing_doc_errors()
|
routing_doc_errors = _routing_doc_errors()
|
||||||
if routing_doc_errors:
|
if routing_doc_errors:
|
||||||
|
|||||||
Reference in New Issue
Block a user