Cover JSON subcategory generation matrix
This commit is contained in:
@@ -284,6 +284,43 @@ def _character_cast_subjects(subjects: list[str] | tuple[str, ...]) -> str:
|
||||
return cast
|
||||
|
||||
|
||||
def _exact_subcategory_selector(category: dict[str, Any], subcategory: dict[str, Any]) -> str:
|
||||
return f"{category.get('name')} / {subcategory.get('name')}"
|
||||
|
||||
|
||||
def _matrix_cast_for_route(category: dict[str, Any], subcategory: dict[str, Any]) -> tuple[int, int, str]:
|
||||
subject_type = str(subcategory.get("subject_type") or category.get("subject_type") or "woman")
|
||||
if subject_type == "woman":
|
||||
return 1, 0, ""
|
||||
if subject_type == "man":
|
||||
return 0, 1, ""
|
||||
if subject_type == "couple":
|
||||
return 1, 1, ""
|
||||
|
||||
min_people = int(subcategory.get("min_people") or 0)
|
||||
women_count = int(subcategory.get("min_women") or 0)
|
||||
men_count = int(subcategory.get("min_men") or 0)
|
||||
if min_people <= 1 and not women_count and not men_count:
|
||||
women_count = 1
|
||||
elif min_people >= 4:
|
||||
women_count = max(women_count, 2)
|
||||
men_count = max(men_count, min_people - women_count)
|
||||
elif min_people >= 3:
|
||||
women_count = max(women_count, 1)
|
||||
men_count = max(men_count, min_people - women_count)
|
||||
elif min_people >= 2:
|
||||
women_count = max(women_count, 1)
|
||||
men_count = max(men_count, min_people - women_count)
|
||||
required_total = max(1, min_people)
|
||||
if women_count + men_count < required_total:
|
||||
men_count += required_total - (women_count + men_count)
|
||||
if women_count == 0 and men_count == 0:
|
||||
women_count = 1
|
||||
|
||||
cast = _character_cast_subjects(["woman"] * women_count + ["man"] * men_count)
|
||||
return women_count, men_count, cast
|
||||
|
||||
|
||||
def _action_filter(focus: str, hardcore_position_config: str | dict[str, Any] | None = "") -> str:
|
||||
kwargs = {
|
||||
"allow_toys": False,
|
||||
@@ -304,6 +341,22 @@ def _action_filter(focus: str, hardcore_position_config: str | dict[str, Any] |
|
||||
)
|
||||
|
||||
|
||||
def _broad_hardcore_filter() -> str:
|
||||
return pb.build_hardcore_action_filter_json(
|
||||
focus="keep_pool",
|
||||
allow_toys=True,
|
||||
allow_double=True,
|
||||
allow_penetration=True,
|
||||
allow_foreplay=True,
|
||||
allow_interaction=True,
|
||||
allow_manual=True,
|
||||
allow_oral=True,
|
||||
allow_outercourse=True,
|
||||
allow_anal=True,
|
||||
allow_climax=True,
|
||||
)
|
||||
|
||||
|
||||
def _position_filter(focus: str, family: str, positions: list[str] | tuple[str, ...] | str) -> str:
|
||||
position_config = pb.build_hardcore_position_pool_json(
|
||||
combine_mode="replace",
|
||||
@@ -1691,6 +1744,20 @@ def smoke_row_category_route_policy() -> None:
|
||||
_expect(casual_route["content_axis"] == "content", "Non-pose category should use content seed axis")
|
||||
_expect(casual_route["is_pose_category"] is False, "Non-pose category should not be marked as pose content")
|
||||
|
||||
exposed_route = row_category_route.select_category_item_route(
|
||||
category_choice="custom_random",
|
||||
subcategory_choice="Provocative erotic clothes / Sheer exposed",
|
||||
seed_config=seed_cfg,
|
||||
seed=2302,
|
||||
row_number=1,
|
||||
women_count=1,
|
||||
men_count=0,
|
||||
hardcore_position_config={},
|
||||
)
|
||||
_expect(exposed_route["subcategory"]["slug"] == "sheer_exposed", "Row category route selected wrong exposed category")
|
||||
_expect(exposed_route["content_axis"] == "content", "Exposed clothing slug should not be treated as pose content")
|
||||
_expect(exposed_route["is_pose_category"] is False, "Exposed clothing slug should not be marked as pose content")
|
||||
|
||||
|
||||
def smoke_row_generation_policy() -> None:
|
||||
_expect(pb._ratio_or_none(-1) is None, "Prompt builder ratio helper should treat negative as unset")
|
||||
@@ -4311,6 +4378,58 @@ def smoke_category_library_route() -> None:
|
||||
)
|
||||
|
||||
|
||||
def smoke_category_subcategory_matrix() -> None:
|
||||
categories = category_library.load_category_library()
|
||||
cases: list[tuple[dict[str, Any], dict[str, Any]]] = []
|
||||
for category in categories:
|
||||
for subcategory in category.get("subcategories") or []:
|
||||
if subcategory.get("item_templates") or subcategory.get("items"):
|
||||
cases.append((category, subcategory))
|
||||
_expect(len(cases) >= 30, "category matrix should cover all configured JSON subcategories")
|
||||
|
||||
hardcore_filter = _broad_hardcore_filter()
|
||||
for index, (category, subcategory) in enumerate(cases, start=6101):
|
||||
category_slug = str(category.get("slug") or "")
|
||||
subcategory_slug = str(subcategory.get("slug") or "")
|
||||
name = f"category_matrix.{category_slug}.{subcategory_slug}"
|
||||
women_count, men_count, cast = _matrix_cast_for_route(category, subcategory)
|
||||
row = _prompt_row(
|
||||
name=name,
|
||||
category=str(category.get("name") or ""),
|
||||
subcategory=_exact_subcategory_selector(category, subcategory),
|
||||
seed=index,
|
||||
character_cast=cast,
|
||||
women_count=women_count,
|
||||
men_count=men_count,
|
||||
hardcore_position_config=hardcore_filter if category_slug == "hardcore_sexual_poses" else "",
|
||||
)
|
||||
|
||||
_expect(row.get("source") == "json_category", f"{name}.source should be json_category")
|
||||
_expect(row.get("category_slug") == category_slug, f"{name}.category_slug drifted to {row.get('category_slug')}")
|
||||
_expect(
|
||||
row.get("subcategory_slug") == subcategory_slug,
|
||||
f"{name}.subcategory_slug drifted to {row.get('subcategory_slug')}",
|
||||
)
|
||||
_expect_text(f"{name}.item", row.get("item"), 8)
|
||||
_expect_text(f"{name}.scene_text", row.get("scene_text"), 8)
|
||||
_expect_text(f"{name}.composition", row.get("composition"), 8)
|
||||
_expect(isinstance(row.get("item_axis_values"), dict), f"{name}.item_axis_values missing")
|
||||
_expect(isinstance(row.get("formatter_hints"), dict), f"{name}.formatter_hints missing")
|
||||
|
||||
if category_slug == "hardcore_sexual_poses":
|
||||
_expect(row.get("content_seed_axis") == "pose", f"{name}.content_seed_axis should be pose")
|
||||
_expect_text(f"{name}.source_role_graph", row.get("source_role_graph") or row.get("role_graph"), 20)
|
||||
_expect_text(f"{name}.action_family", row.get("action_family"), 3)
|
||||
_expect_text(f"{name}.position_family", row.get("position_family"), 3)
|
||||
_expect(isinstance(row.get("position_keys"), list), f"{name}.position_keys missing")
|
||||
_expect(isinstance(row.get("item_template_metadata"), dict), f"{name}.item_template_metadata missing")
|
||||
_expect(row.get("item_template_metadata"), f"{name}.item_template_metadata should not be empty")
|
||||
else:
|
||||
_expect(row.get("content_seed_axis") == "content", f"{name}.content_seed_axis should be content")
|
||||
|
||||
_expect_formatter_outputs(row, name, target="single")
|
||||
|
||||
|
||||
def smoke_hardcore_category_routes() -> None:
|
||||
cast = _character_cast()
|
||||
cases = [
|
||||
@@ -7219,6 +7338,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
||||
("hardcore_position_config_policy", smoke_hardcore_position_config_policy),
|
||||
("row_route_metadata_policy", smoke_row_route_metadata_policy),
|
||||
("category_library_route", smoke_category_library_route),
|
||||
("category_subcategory_matrix", smoke_category_subcategory_matrix),
|
||||
("hardcore_category_routes", smoke_hardcore_category_routes),
|
||||
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
|
||||
("pair_options_policy", smoke_pair_options_policy),
|
||||
|
||||
Reference in New Issue
Block a user