Extract filter config policy
This commit is contained in:
@@ -107,6 +107,9 @@ Already isolated:
|
|||||||
- category/cast route preset schemas, config JSON builders, choice lists, and
|
- category/cast route preset schemas, config JSON builders, choice lists, and
|
||||||
parsers live in `category_cast_config.py`; `prompt_builder.py` keeps public
|
parsers live in `category_cast_config.py`; `prompt_builder.py` keeps public
|
||||||
delegate wrappers for existing nodes and tests.
|
delegate wrappers for existing nodes and tests.
|
||||||
|
- ethnicity/filter choices, advanced filter JSON, ethnicity-list JSON, filter
|
||||||
|
parsing, and ethnicity normalization live in `filter_config.py`; character
|
||||||
|
routes and builder filters use `prompt_builder.py` delegate wrappers.
|
||||||
- generation profile presets, override normalization, trigger policy, and
|
- generation profile presets, override normalization, trigger policy, and
|
||||||
profile config parsing live in `generation_profile_config.py`;
|
profile config parsing live in `generation_profile_config.py`;
|
||||||
`prompt_builder.py` keeps public delegate wrappers.
|
`prompt_builder.py` keeps public delegate wrappers.
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ Core helper ownership:
|
|||||||
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
|
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
|
||||||
| `category_cast_config.py` | Category preset and cast preset schemas, category/cast config JSON builders, choice lists, and config parsers used by route nodes. |
|
| `category_cast_config.py` | Category preset and cast preset schemas, category/cast config JSON builders, choice lists, and config parsers used by route nodes. |
|
||||||
| `camera_config.py` | Camera option schema, direct/orbit/Qwen camera JSON builders, camera config parsing, plain camera directive text, and camera caption labels. |
|
| `camera_config.py` | Camera option schema, direct/orbit/Qwen camera JSON builders, camera config parsing, plain camera directive text, and camera caption labels. |
|
||||||
|
| `filter_config.py` | Ethnicity/filter choices, advanced filter JSON, ethnicity-list JSON, filter parsing, and ethnicity normalization used by builder and character routes. |
|
||||||
| `generation_profile_config.py` | Generation profile presets, profile option overrides, trigger policy, expression/pose/clothing config normalization, and profile config parsing. |
|
| `generation_profile_config.py` | Generation profile presets, profile option overrides, trigger policy, expression/pose/clothing config normalization, and profile config parsing. |
|
||||||
| `seed_config.py` | Seed axis salts/aliases, seed mode choices, global/axis lock JSON builders, seed config parsing, row seed math, and deterministic axis RNG construction. |
|
| `seed_config.py` | Seed axis salts/aliases, seed mode choices, global/axis lock JSON builders, seed config parsing, row seed math, and deterministic axis RNG construction. |
|
||||||
| `location_config.py` | Location/composition preset schemas, themed location packs, custom location/composition parsing, pool merge behavior, and location/composition config parsing. |
|
| `location_config.py` | Location/composition preset schemas, themed location packs, custom location/composition parsing, pool merge behavior, and location/composition config parsing. |
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
ETHNICITY_FILTER_CHOICES = [
|
||||||
|
"any",
|
||||||
|
"european",
|
||||||
|
"mediterranean_mena",
|
||||||
|
"latina",
|
||||||
|
"east_asian",
|
||||||
|
"southeast_asian",
|
||||||
|
"south_asian",
|
||||||
|
"black_african",
|
||||||
|
"indigenous",
|
||||||
|
"mixed",
|
||||||
|
"asian",
|
||||||
|
"white_asian",
|
||||||
|
"western_european",
|
||||||
|
"french_european",
|
||||||
|
"germanic_european",
|
||||||
|
"nordic_european",
|
||||||
|
"celtic_european",
|
||||||
|
"slavic_european",
|
||||||
|
"baltic_european",
|
||||||
|
"alpine_european",
|
||||||
|
"balkan_european",
|
||||||
|
"greek_mediterranean",
|
||||||
|
"italian_mediterranean",
|
||||||
|
"iberian_mediterranean",
|
||||||
|
]
|
||||||
|
ETHNICITY_LIST_KEYS = tuple(choice for choice in ETHNICITY_FILTER_CHOICES if choice != "any")
|
||||||
|
ETHNICITY_BASE_LIST_KEYS = (
|
||||||
|
"european",
|
||||||
|
"mediterranean_mena",
|
||||||
|
"latina",
|
||||||
|
"east_asian",
|
||||||
|
"southeast_asian",
|
||||||
|
"south_asian",
|
||||||
|
"black_african",
|
||||||
|
"indigenous",
|
||||||
|
"mixed",
|
||||||
|
)
|
||||||
|
EUROPEAN_REGIONAL_LIST_KEYS = (
|
||||||
|
"western_european",
|
||||||
|
"french_european",
|
||||||
|
"germanic_european",
|
||||||
|
"nordic_european",
|
||||||
|
"celtic_european",
|
||||||
|
"slavic_european",
|
||||||
|
"baltic_european",
|
||||||
|
"alpine_european",
|
||||||
|
"balkan_european",
|
||||||
|
)
|
||||||
|
MEDITERRANEAN_REGIONAL_LIST_KEYS = (
|
||||||
|
"greek_mediterranean",
|
||||||
|
"italian_mediterranean",
|
||||||
|
"iberian_mediterranean",
|
||||||
|
)
|
||||||
|
ETHNICITY_RANDOM_TOKENS = {"", "random", "auto", "global", "from_global", "default"}
|
||||||
|
|
||||||
|
|
||||||
|
def ethnicity_text_from_value(value: Any) -> str:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return str(value.get("ethnicity") or "").strip()
|
||||||
|
text = str(value or "").strip()
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
if text.startswith("{"):
|
||||||
|
try:
|
||||||
|
raw = json.loads(text)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return text
|
||||||
|
if isinstance(raw, dict):
|
||||||
|
return str(raw.get("ethnicity") or "").strip()
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_ethnicity_filter(value: Any) -> bool:
|
||||||
|
text = ethnicity_text_from_value(value)
|
||||||
|
return text == "any" or text in ETHNICITY_FILTER_CHOICES or "+" in text
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_ethnicity_filter(value: Any, default: str = "any", allow_random: bool = False) -> str:
|
||||||
|
text = ethnicity_text_from_value(value)
|
||||||
|
if text.lower() in ETHNICITY_RANDOM_TOKENS:
|
||||||
|
return "random" if allow_random else default
|
||||||
|
return text if is_valid_ethnicity_filter(text) else default
|
||||||
|
|
||||||
|
|
||||||
|
def build_filter_config_json(
|
||||||
|
ethnicity: str = "any",
|
||||||
|
figure: str = "curvy",
|
||||||
|
no_plus_women: bool = False,
|
||||||
|
no_black: bool = False,
|
||||||
|
include_european: bool = True,
|
||||||
|
include_mediterranean_mena: bool = True,
|
||||||
|
include_latina: bool = True,
|
||||||
|
include_east_asian: bool = True,
|
||||||
|
include_southeast_asian: bool = True,
|
||||||
|
include_south_asian: bool = True,
|
||||||
|
include_black_african: bool = True,
|
||||||
|
include_indigenous: bool = True,
|
||||||
|
include_mixed: bool = True,
|
||||||
|
include_plus_size: bool = True,
|
||||||
|
) -> str:
|
||||||
|
include_flags = {
|
||||||
|
"european": include_european,
|
||||||
|
"mediterranean_mena": include_mediterranean_mena,
|
||||||
|
"latina": include_latina,
|
||||||
|
"east_asian": include_east_asian,
|
||||||
|
"southeast_asian": include_southeast_asian,
|
||||||
|
"south_asian": include_south_asian,
|
||||||
|
"black_african": include_black_african,
|
||||||
|
"indigenous": include_indigenous,
|
||||||
|
"mixed": include_mixed,
|
||||||
|
}
|
||||||
|
selected_ethnicities = [key for key, enabled in include_flags.items() if enabled]
|
||||||
|
disabled_ethnicities = [key for key, enabled in include_flags.items() if not enabled]
|
||||||
|
enabled_ethnicities = list(selected_ethnicities)
|
||||||
|
if enabled_ethnicities:
|
||||||
|
enabled_ethnicities.extend(f"exclude_{key}" for key in disabled_ethnicities)
|
||||||
|
if 0 < len(selected_ethnicities) < len(include_flags):
|
||||||
|
ethnicity = "+".join(enabled_ethnicities)
|
||||||
|
elif not is_valid_ethnicity_filter(ethnicity):
|
||||||
|
ethnicity = "any"
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"ethnicity": ethnicity,
|
||||||
|
"ethnicity_includes": selected_ethnicities,
|
||||||
|
"figure": figure if figure in ("curvy", "balanced", "bombshell", "random") else "curvy",
|
||||||
|
"include_plus_size": bool(include_plus_size),
|
||||||
|
"include_black_african": bool(include_black_african),
|
||||||
|
"no_plus_women": not bool(include_plus_size) or bool(no_plus_women),
|
||||||
|
"no_black": not bool(include_black_african) or bool(no_black),
|
||||||
|
},
|
||||||
|
ensure_ascii=True,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_ethnicity_list_json(
|
||||||
|
include_european: bool = False,
|
||||||
|
include_mediterranean_mena: bool = False,
|
||||||
|
include_latina: bool = False,
|
||||||
|
include_east_asian: bool = False,
|
||||||
|
include_southeast_asian: bool = False,
|
||||||
|
include_south_asian: bool = False,
|
||||||
|
include_black_african: bool = False,
|
||||||
|
include_indigenous: bool = False,
|
||||||
|
include_mixed: bool = False,
|
||||||
|
include_asian: bool = False,
|
||||||
|
include_white_asian: bool = False,
|
||||||
|
include_western_european: bool = False,
|
||||||
|
include_french_european: bool = False,
|
||||||
|
include_germanic_european: bool = False,
|
||||||
|
include_nordic_european: bool = False,
|
||||||
|
include_celtic_european: bool = False,
|
||||||
|
include_slavic_european: bool = False,
|
||||||
|
include_baltic_european: bool = False,
|
||||||
|
include_alpine_european: bool = False,
|
||||||
|
include_balkan_european: bool = False,
|
||||||
|
include_greek_mediterranean: bool = False,
|
||||||
|
include_italian_mediterranean: bool = False,
|
||||||
|
include_iberian_mediterranean: bool = False,
|
||||||
|
strict_excludes: bool = True,
|
||||||
|
) -> dict[str, str]:
|
||||||
|
include_flags = {
|
||||||
|
"european": include_european,
|
||||||
|
"mediterranean_mena": include_mediterranean_mena,
|
||||||
|
"latina": include_latina,
|
||||||
|
"east_asian": include_east_asian,
|
||||||
|
"southeast_asian": include_southeast_asian,
|
||||||
|
"south_asian": include_south_asian,
|
||||||
|
"black_african": include_black_african,
|
||||||
|
"indigenous": include_indigenous,
|
||||||
|
"mixed": include_mixed,
|
||||||
|
"asian": include_asian,
|
||||||
|
"white_asian": include_white_asian,
|
||||||
|
"western_european": include_western_european,
|
||||||
|
"french_european": include_french_european,
|
||||||
|
"germanic_european": include_germanic_european,
|
||||||
|
"nordic_european": include_nordic_european,
|
||||||
|
"celtic_european": include_celtic_european,
|
||||||
|
"slavic_european": include_slavic_european,
|
||||||
|
"baltic_european": include_baltic_european,
|
||||||
|
"alpine_european": include_alpine_european,
|
||||||
|
"balkan_european": include_balkan_european,
|
||||||
|
"greek_mediterranean": include_greek_mediterranean,
|
||||||
|
"italian_mediterranean": include_italian_mediterranean,
|
||||||
|
"iberian_mediterranean": include_iberian_mediterranean,
|
||||||
|
}
|
||||||
|
selected = [key for key in ETHNICITY_LIST_KEYS if include_flags.get(key)]
|
||||||
|
if not selected or set(selected) == set(ETHNICITY_LIST_KEYS):
|
||||||
|
ethnicity = "any"
|
||||||
|
else:
|
||||||
|
tokens = list(selected)
|
||||||
|
if strict_excludes:
|
||||||
|
protected: set[str] = set()
|
||||||
|
if "asian" in selected:
|
||||||
|
protected.update(("east_asian", "southeast_asian", "south_asian"))
|
||||||
|
if "white_asian" in selected:
|
||||||
|
protected.update(("european", "east_asian", "southeast_asian", "south_asian", "mixed"))
|
||||||
|
if any(key in selected for key in EUROPEAN_REGIONAL_LIST_KEYS):
|
||||||
|
protected.add("european")
|
||||||
|
if any(key in selected for key in MEDITERRANEAN_REGIONAL_LIST_KEYS):
|
||||||
|
protected.add("mediterranean_mena")
|
||||||
|
if "mixed" in selected:
|
||||||
|
protected.update(ETHNICITY_BASE_LIST_KEYS)
|
||||||
|
tokens.extend(
|
||||||
|
f"exclude_{key}"
|
||||||
|
for key in ETHNICITY_BASE_LIST_KEYS
|
||||||
|
if key not in selected and key not in protected
|
||||||
|
)
|
||||||
|
ethnicity = "+".join(tokens)
|
||||||
|
filter_config = {
|
||||||
|
"ethnicity": ethnicity,
|
||||||
|
"ethnicity_includes": selected,
|
||||||
|
}
|
||||||
|
summary = "any ethnicity" if ethnicity == "any" else "ethnicity list: " + ", ".join(selected)
|
||||||
|
return {
|
||||||
|
"ethnicity": ethnicity,
|
||||||
|
"filter_config": json.dumps(filter_config, ensure_ascii=True, sort_keys=True),
|
||||||
|
"summary": summary,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_filter_config(filter_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||||
|
defaults = {
|
||||||
|
"ethnicity": "any",
|
||||||
|
"figure": "curvy",
|
||||||
|
"no_plus_women": False,
|
||||||
|
"no_black": False,
|
||||||
|
"include_plus_size": True,
|
||||||
|
"include_black_african": True,
|
||||||
|
}
|
||||||
|
if not filter_config:
|
||||||
|
return defaults
|
||||||
|
if isinstance(filter_config, dict):
|
||||||
|
raw = filter_config
|
||||||
|
else:
|
||||||
|
text = str(filter_config).strip()
|
||||||
|
if not text.startswith("{"):
|
||||||
|
raw = {"ethnicity": text}
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
raw = json.loads(text)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise ValueError(f"Invalid filter_config JSON: {exc}") from exc
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raise ValueError("filter_config must be a JSON object")
|
||||||
|
parsed = {**defaults, **raw}
|
||||||
|
parsed["ethnicity"] = normalize_ethnicity_filter(parsed.get("ethnicity"), "any")
|
||||||
|
parsed["figure"] = parsed["figure"] if parsed.get("figure") in ("curvy", "balanced", "bombshell", "random") else "curvy"
|
||||||
|
parsed["include_plus_size"] = bool(parsed.get("include_plus_size"))
|
||||||
|
parsed["include_black_african"] = bool(parsed.get("include_black_african"))
|
||||||
|
parsed["no_plus_women"] = bool(parsed.get("no_plus_women"))
|
||||||
|
parsed["no_black"] = bool(parsed.get("no_black"))
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
_ethnicity_text_from_value = ethnicity_text_from_value
|
||||||
|
_is_valid_ethnicity_filter = is_valid_ethnicity_filter
|
||||||
|
_parse_filter_config = parse_filter_config
|
||||||
@@ -3,23 +3,23 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from .filter_config import (
|
||||||
|
build_ethnicity_list_json,
|
||||||
|
build_filter_config_json,
|
||||||
|
)
|
||||||
from .generation_profile_config import (
|
from .generation_profile_config import (
|
||||||
build_generation_profile_json,
|
build_generation_profile_json,
|
||||||
generation_profile_choices,
|
generation_profile_choices,
|
||||||
)
|
)
|
||||||
from .prompt_builder import (
|
except ImportError: # Allows local smoke tests from the repository root.
|
||||||
|
from filter_config import (
|
||||||
build_ethnicity_list_json,
|
build_ethnicity_list_json,
|
||||||
build_filter_config_json,
|
build_filter_config_json,
|
||||||
)
|
)
|
||||||
except ImportError: # Allows local smoke tests from the repository root.
|
|
||||||
from generation_profile_config import (
|
from generation_profile_config import (
|
||||||
build_generation_profile_json,
|
build_generation_profile_json,
|
||||||
generation_profile_choices,
|
generation_profile_choices,
|
||||||
)
|
)
|
||||||
from prompt_builder import (
|
|
||||||
build_ethnicity_list_json,
|
|
||||||
build_filter_config_json,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
SXCP_GENERATION_PROFILE = "SXCP_GENERATION_PROFILE"
|
SXCP_GENERATION_PROFILE = "SXCP_GENERATION_PROFILE"
|
||||||
|
|||||||
+51
-193
@@ -25,6 +25,7 @@ try:
|
|||||||
)
|
)
|
||||||
from . import camera_config as camera_policy
|
from . import camera_config as camera_policy
|
||||||
from . import category_cast_config as category_cast_policy
|
from . import category_cast_config as category_cast_policy
|
||||||
|
from . import filter_config as filter_policy
|
||||||
from . import generate_prompt_batches as g
|
from . import generate_prompt_batches as g
|
||||||
from . import generation_profile_config as generation_profile_policy
|
from . import generation_profile_config as generation_profile_policy
|
||||||
from . import location_config as location_policy
|
from . import location_config as location_policy
|
||||||
@@ -65,6 +66,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
|||||||
)
|
)
|
||||||
import camera_config as camera_policy
|
import camera_config as camera_policy
|
||||||
import category_cast_config as category_cast_policy
|
import category_cast_config as category_cast_policy
|
||||||
|
import filter_config as filter_policy
|
||||||
import generate_prompt_batches as g
|
import generate_prompt_batches as g
|
||||||
import generation_profile_config as generation_profile_policy
|
import generation_profile_config as generation_profile_policy
|
||||||
import location_config as location_policy
|
import location_config as location_policy
|
||||||
@@ -107,60 +109,11 @@ SEED_AXIS_ALIASES = seed_policy.SEED_AXIS_ALIASES
|
|||||||
SEED_LOCK_AXES = seed_policy.SEED_LOCK_AXES
|
SEED_LOCK_AXES = seed_policy.SEED_LOCK_AXES
|
||||||
SEED_MODE_CHOICES = seed_policy.SEED_MODE_CHOICES
|
SEED_MODE_CHOICES = seed_policy.SEED_MODE_CHOICES
|
||||||
|
|
||||||
ETHNICITY_FILTER_CHOICES = [
|
ETHNICITY_FILTER_CHOICES = filter_policy.ETHNICITY_FILTER_CHOICES
|
||||||
"any",
|
ETHNICITY_LIST_KEYS = filter_policy.ETHNICITY_LIST_KEYS
|
||||||
"european",
|
ETHNICITY_BASE_LIST_KEYS = filter_policy.ETHNICITY_BASE_LIST_KEYS
|
||||||
"mediterranean_mena",
|
EUROPEAN_REGIONAL_LIST_KEYS = filter_policy.EUROPEAN_REGIONAL_LIST_KEYS
|
||||||
"latina",
|
MEDITERRANEAN_REGIONAL_LIST_KEYS = filter_policy.MEDITERRANEAN_REGIONAL_LIST_KEYS
|
||||||
"east_asian",
|
|
||||||
"southeast_asian",
|
|
||||||
"south_asian",
|
|
||||||
"black_african",
|
|
||||||
"indigenous",
|
|
||||||
"mixed",
|
|
||||||
"asian",
|
|
||||||
"white_asian",
|
|
||||||
"western_european",
|
|
||||||
"french_european",
|
|
||||||
"germanic_european",
|
|
||||||
"nordic_european",
|
|
||||||
"celtic_european",
|
|
||||||
"slavic_european",
|
|
||||||
"baltic_european",
|
|
||||||
"alpine_european",
|
|
||||||
"balkan_european",
|
|
||||||
"greek_mediterranean",
|
|
||||||
"italian_mediterranean",
|
|
||||||
"iberian_mediterranean",
|
|
||||||
]
|
|
||||||
ETHNICITY_LIST_KEYS = tuple(choice for choice in ETHNICITY_FILTER_CHOICES if choice != "any")
|
|
||||||
ETHNICITY_BASE_LIST_KEYS = (
|
|
||||||
"european",
|
|
||||||
"mediterranean_mena",
|
|
||||||
"latina",
|
|
||||||
"east_asian",
|
|
||||||
"southeast_asian",
|
|
||||||
"south_asian",
|
|
||||||
"black_african",
|
|
||||||
"indigenous",
|
|
||||||
"mixed",
|
|
||||||
)
|
|
||||||
EUROPEAN_REGIONAL_LIST_KEYS = (
|
|
||||||
"western_european",
|
|
||||||
"french_european",
|
|
||||||
"germanic_european",
|
|
||||||
"nordic_european",
|
|
||||||
"celtic_european",
|
|
||||||
"slavic_european",
|
|
||||||
"baltic_european",
|
|
||||||
"alpine_european",
|
|
||||||
"balkan_european",
|
|
||||||
)
|
|
||||||
MEDITERRANEAN_REGIONAL_LIST_KEYS = (
|
|
||||||
"greek_mediterranean",
|
|
||||||
"italian_mediterranean",
|
|
||||||
"iberian_mediterranean",
|
|
||||||
)
|
|
||||||
|
|
||||||
CHARACTER_LABEL_CHOICES = [
|
CHARACTER_LABEL_CHOICES = [
|
||||||
"auto_chain",
|
"auto_chain",
|
||||||
@@ -1114,38 +1067,21 @@ def build_filter_config_json(
|
|||||||
include_mixed: bool = True,
|
include_mixed: bool = True,
|
||||||
include_plus_size: bool = True,
|
include_plus_size: bool = True,
|
||||||
) -> str:
|
) -> str:
|
||||||
include_flags = {
|
return filter_policy.build_filter_config_json(
|
||||||
"european": include_european,
|
ethnicity=ethnicity,
|
||||||
"mediterranean_mena": include_mediterranean_mena,
|
figure=figure,
|
||||||
"latina": include_latina,
|
no_plus_women=no_plus_women,
|
||||||
"east_asian": include_east_asian,
|
no_black=no_black,
|
||||||
"southeast_asian": include_southeast_asian,
|
include_european=include_european,
|
||||||
"south_asian": include_south_asian,
|
include_mediterranean_mena=include_mediterranean_mena,
|
||||||
"black_african": include_black_african,
|
include_latina=include_latina,
|
||||||
"indigenous": include_indigenous,
|
include_east_asian=include_east_asian,
|
||||||
"mixed": include_mixed,
|
include_southeast_asian=include_southeast_asian,
|
||||||
}
|
include_south_asian=include_south_asian,
|
||||||
selected_ethnicities = [key for key, enabled in include_flags.items() if enabled]
|
include_black_african=include_black_african,
|
||||||
disabled_ethnicities = [key for key, enabled in include_flags.items() if not enabled]
|
include_indigenous=include_indigenous,
|
||||||
enabled_ethnicities = list(selected_ethnicities)
|
include_mixed=include_mixed,
|
||||||
if enabled_ethnicities:
|
include_plus_size=include_plus_size,
|
||||||
enabled_ethnicities.extend(f"exclude_{key}" for key in disabled_ethnicities)
|
|
||||||
if 0 < len(selected_ethnicities) < len(include_flags):
|
|
||||||
ethnicity = "+".join(enabled_ethnicities)
|
|
||||||
elif not _is_valid_ethnicity_filter(ethnicity):
|
|
||||||
ethnicity = "any"
|
|
||||||
return json.dumps(
|
|
||||||
{
|
|
||||||
"ethnicity": ethnicity,
|
|
||||||
"ethnicity_includes": selected_ethnicities,
|
|
||||||
"figure": figure if figure in ("curvy", "balanced", "bombshell", "random") else "curvy",
|
|
||||||
"include_plus_size": bool(include_plus_size),
|
|
||||||
"include_black_african": bool(include_black_african),
|
|
||||||
"no_plus_women": not bool(include_plus_size) or bool(no_plus_women),
|
|
||||||
"no_black": not bool(include_black_african) or bool(no_black),
|
|
||||||
},
|
|
||||||
ensure_ascii=True,
|
|
||||||
sort_keys=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1260,31 +1196,15 @@ def build_thematic_location_json(
|
|||||||
|
|
||||||
|
|
||||||
def _ethnicity_text_from_value(value: Any) -> str:
|
def _ethnicity_text_from_value(value: Any) -> str:
|
||||||
if isinstance(value, dict):
|
return filter_policy.ethnicity_text_from_value(value)
|
||||||
return str(value.get("ethnicity") or "").strip()
|
|
||||||
text = str(value or "").strip()
|
|
||||||
if not text:
|
|
||||||
return ""
|
|
||||||
if text.startswith("{"):
|
|
||||||
try:
|
|
||||||
raw = json.loads(text)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
return text
|
|
||||||
if isinstance(raw, dict):
|
|
||||||
return str(raw.get("ethnicity") or "").strip()
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def _is_valid_ethnicity_filter(value: Any) -> bool:
|
def _is_valid_ethnicity_filter(value: Any) -> bool:
|
||||||
text = _ethnicity_text_from_value(value)
|
return filter_policy.is_valid_ethnicity_filter(value)
|
||||||
return text == "any" or text in ETHNICITY_FILTER_CHOICES or "+" in text
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_ethnicity_filter(value: Any, default: str = "any", allow_random: bool = False) -> str:
|
def normalize_ethnicity_filter(value: Any, default: str = "any", allow_random: bool = False) -> str:
|
||||||
text = _ethnicity_text_from_value(value)
|
return filter_policy.normalize_ethnicity_filter(value, default, allow_random)
|
||||||
if text.lower() in CHARACTER_RANDOM_TOKENS:
|
|
||||||
return "random" if allow_random else default
|
|
||||||
return text if _is_valid_ethnicity_filter(text) else default
|
|
||||||
|
|
||||||
|
|
||||||
def build_ethnicity_list_json(
|
def build_ethnicity_list_json(
|
||||||
@@ -1313,98 +1233,36 @@ def build_ethnicity_list_json(
|
|||||||
include_iberian_mediterranean: bool = False,
|
include_iberian_mediterranean: bool = False,
|
||||||
strict_excludes: bool = True,
|
strict_excludes: bool = True,
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
include_flags = {
|
return filter_policy.build_ethnicity_list_json(
|
||||||
"european": include_european,
|
include_european=include_european,
|
||||||
"mediterranean_mena": include_mediterranean_mena,
|
include_mediterranean_mena=include_mediterranean_mena,
|
||||||
"latina": include_latina,
|
include_latina=include_latina,
|
||||||
"east_asian": include_east_asian,
|
include_east_asian=include_east_asian,
|
||||||
"southeast_asian": include_southeast_asian,
|
include_southeast_asian=include_southeast_asian,
|
||||||
"south_asian": include_south_asian,
|
include_south_asian=include_south_asian,
|
||||||
"black_african": include_black_african,
|
include_black_african=include_black_african,
|
||||||
"indigenous": include_indigenous,
|
include_indigenous=include_indigenous,
|
||||||
"mixed": include_mixed,
|
include_mixed=include_mixed,
|
||||||
"asian": include_asian,
|
include_asian=include_asian,
|
||||||
"white_asian": include_white_asian,
|
include_white_asian=include_white_asian,
|
||||||
"western_european": include_western_european,
|
include_western_european=include_western_european,
|
||||||
"french_european": include_french_european,
|
include_french_european=include_french_european,
|
||||||
"germanic_european": include_germanic_european,
|
include_germanic_european=include_germanic_european,
|
||||||
"nordic_european": include_nordic_european,
|
include_nordic_european=include_nordic_european,
|
||||||
"celtic_european": include_celtic_european,
|
include_celtic_european=include_celtic_european,
|
||||||
"slavic_european": include_slavic_european,
|
include_slavic_european=include_slavic_european,
|
||||||
"baltic_european": include_baltic_european,
|
include_baltic_european=include_baltic_european,
|
||||||
"alpine_european": include_alpine_european,
|
include_alpine_european=include_alpine_european,
|
||||||
"balkan_european": include_balkan_european,
|
include_balkan_european=include_balkan_european,
|
||||||
"greek_mediterranean": include_greek_mediterranean,
|
include_greek_mediterranean=include_greek_mediterranean,
|
||||||
"italian_mediterranean": include_italian_mediterranean,
|
include_italian_mediterranean=include_italian_mediterranean,
|
||||||
"iberian_mediterranean": include_iberian_mediterranean,
|
include_iberian_mediterranean=include_iberian_mediterranean,
|
||||||
}
|
strict_excludes=strict_excludes,
|
||||||
selected = [key for key in ETHNICITY_LIST_KEYS if include_flags.get(key)]
|
|
||||||
if not selected or set(selected) == set(ETHNICITY_LIST_KEYS):
|
|
||||||
ethnicity = "any"
|
|
||||||
else:
|
|
||||||
tokens = list(selected)
|
|
||||||
if strict_excludes:
|
|
||||||
protected: set[str] = set()
|
|
||||||
if "asian" in selected:
|
|
||||||
protected.update(("east_asian", "southeast_asian", "south_asian"))
|
|
||||||
if "white_asian" in selected:
|
|
||||||
protected.update(("european", "east_asian", "southeast_asian", "south_asian", "mixed"))
|
|
||||||
if any(key in selected for key in EUROPEAN_REGIONAL_LIST_KEYS):
|
|
||||||
protected.add("european")
|
|
||||||
if any(key in selected for key in MEDITERRANEAN_REGIONAL_LIST_KEYS):
|
|
||||||
protected.add("mediterranean_mena")
|
|
||||||
if "mixed" in selected:
|
|
||||||
protected.update(ETHNICITY_BASE_LIST_KEYS)
|
|
||||||
tokens.extend(
|
|
||||||
f"exclude_{key}"
|
|
||||||
for key in ETHNICITY_BASE_LIST_KEYS
|
|
||||||
if key not in selected and key not in protected
|
|
||||||
)
|
)
|
||||||
ethnicity = "+".join(tokens)
|
|
||||||
filter_config = {
|
|
||||||
"ethnicity": ethnicity,
|
|
||||||
"ethnicity_includes": selected,
|
|
||||||
}
|
|
||||||
summary = "any ethnicity" if ethnicity == "any" else "ethnicity list: " + ", ".join(selected)
|
|
||||||
return {
|
|
||||||
"ethnicity": ethnicity,
|
|
||||||
"filter_config": json.dumps(filter_config, ensure_ascii=True, sort_keys=True),
|
|
||||||
"summary": summary,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_filter_config(filter_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
def _parse_filter_config(filter_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||||
defaults = {
|
return filter_policy.parse_filter_config(filter_config)
|
||||||
"ethnicity": "any",
|
|
||||||
"figure": "curvy",
|
|
||||||
"no_plus_women": False,
|
|
||||||
"no_black": False,
|
|
||||||
"include_plus_size": True,
|
|
||||||
"include_black_african": True,
|
|
||||||
}
|
|
||||||
if not filter_config:
|
|
||||||
return defaults
|
|
||||||
if isinstance(filter_config, dict):
|
|
||||||
raw = filter_config
|
|
||||||
else:
|
|
||||||
text = str(filter_config).strip()
|
|
||||||
if not text.startswith("{"):
|
|
||||||
raw = {"ethnicity": text}
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
raw = json.loads(text)
|
|
||||||
except json.JSONDecodeError as exc:
|
|
||||||
raise ValueError(f"Invalid filter_config JSON: {exc}") from exc
|
|
||||||
if not isinstance(raw, dict):
|
|
||||||
raise ValueError("filter_config must be a JSON object")
|
|
||||||
parsed = {**defaults, **raw}
|
|
||||||
parsed["ethnicity"] = normalize_ethnicity_filter(parsed.get("ethnicity"), "any")
|
|
||||||
parsed["figure"] = parsed["figure"] if parsed.get("figure") in ("curvy", "balanced", "bombshell", "random") else "curvy"
|
|
||||||
parsed["include_plus_size"] = bool(parsed.get("include_plus_size"))
|
|
||||||
parsed["include_black_african"] = bool(parsed.get("include_black_african"))
|
|
||||||
parsed["no_plus_women"] = bool(parsed.get("no_plus_women"))
|
|
||||||
parsed["no_black"] = bool(parsed.get("no_black"))
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_hardcore_position_family(value: Any, default: str = "any") -> str:
|
def _normalize_hardcore_position_family(value: Any, default: str = "any") -> str:
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ if str(ROOT) not in sys.path:
|
|||||||
import caption_naturalizer # noqa: E402
|
import caption_naturalizer # noqa: E402
|
||||||
import category_cast_config # noqa: E402
|
import category_cast_config # noqa: E402
|
||||||
import category_library # noqa: E402
|
import category_library # noqa: E402
|
||||||
|
import filter_config # noqa: E402
|
||||||
import __init__ as sxcp_nodes # noqa: E402
|
import __init__ as sxcp_nodes # noqa: E402
|
||||||
import generation_profile_config # noqa: E402
|
import generation_profile_config # noqa: E402
|
||||||
import krea_formatter # noqa: E402
|
import krea_formatter # noqa: E402
|
||||||
@@ -620,6 +621,46 @@ def smoke_generation_profile_config_policy() -> None:
|
|||||||
_expect(fallback.get("trigger") == "sxcpinup_coloredpencil", "Generation profile parser lost default trigger")
|
_expect(fallback.get("trigger") == "sxcpinup_coloredpencil", "Generation profile parser lost default trigger")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_filter_config_policy() -> None:
|
||||||
|
_expect(pb.ETHNICITY_FILTER_CHOICES is filter_config.ETHNICITY_FILTER_CHOICES, "Prompt builder ethnicity choices are not delegated")
|
||||||
|
_expect("french_european" in filter_config.ETHNICITY_LIST_KEYS, "Ethnicity list keys lost regional choices")
|
||||||
|
|
||||||
|
advanced = json.loads(
|
||||||
|
pb.build_filter_config_json(
|
||||||
|
include_european=True,
|
||||||
|
include_mediterranean_mena=False,
|
||||||
|
include_latina=False,
|
||||||
|
include_east_asian=False,
|
||||||
|
include_southeast_asian=False,
|
||||||
|
include_south_asian=False,
|
||||||
|
include_black_african=True,
|
||||||
|
include_indigenous=False,
|
||||||
|
include_mixed=False,
|
||||||
|
include_plus_size=False,
|
||||||
|
figure="bad",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_expect(advanced.get("ethnicity_includes") == ["european", "black_african"], "Advanced filter selected ethnicity list changed")
|
||||||
|
_expect("exclude_latina" in advanced.get("ethnicity", ""), "Advanced filter ethnicity excludes changed")
|
||||||
|
_expect(advanced.get("figure") == "curvy", "Advanced filter invalid figure fallback changed")
|
||||||
|
_expect(advanced.get("no_plus_women") is True, "Advanced filter plus-size exclusion changed")
|
||||||
|
|
||||||
|
ethnicity_list = pb.build_ethnicity_list_json(include_french_european=True, include_asian=True, strict_excludes=True)
|
||||||
|
_expect("french_european" in ethnicity_list["ethnicity"], "Ethnicity list lost regional include")
|
||||||
|
_expect("asian" in ethnicity_list["ethnicity"], "Ethnicity list lost umbrella Asian include")
|
||||||
|
_expect("exclude_european" not in ethnicity_list["ethnicity"], "Ethnicity list should protect European when regional Europe is selected")
|
||||||
|
_expect("exclude_east_asian" not in ethnicity_list["ethnicity"], "Ethnicity list should protect East Asian when Asian is selected")
|
||||||
|
_expect("filter_config" in ethnicity_list, "Ethnicity list lost filter_config output")
|
||||||
|
|
||||||
|
parsed_text = pb._parse_filter_config("french_european")
|
||||||
|
_expect(parsed_text.get("ethnicity") == "french_european", "Filter parser text shortcut changed")
|
||||||
|
parsed_bad = filter_config.parse_filter_config({"ethnicity": "bad", "figure": "bad"})
|
||||||
|
_expect(parsed_bad.get("ethnicity") == "any", "Filter parser invalid ethnicity fallback changed")
|
||||||
|
_expect(parsed_bad.get("figure") == "curvy", "Filter parser invalid figure fallback changed")
|
||||||
|
_expect(pb.normalize_ethnicity_filter("random", "any", allow_random=True) == "random", "Ethnicity random normalization changed")
|
||||||
|
_expect(pb.normalize_ethnicity_filter("random", "any", allow_random=False) == "any", "Ethnicity default normalization changed")
|
||||||
|
|
||||||
|
|
||||||
def smoke_category_library_route() -> None:
|
def smoke_category_library_route() -> None:
|
||||||
categories = category_library.load_category_library()
|
categories = category_library.load_category_library()
|
||||||
_expect(len(categories) >= 3, "category library should load JSON categories")
|
_expect(len(categories) >= 3, "category library should load JSON categories")
|
||||||
@@ -2529,6 +2570,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("location_config_policy", smoke_location_config_policy),
|
("location_config_policy", smoke_location_config_policy),
|
||||||
("category_cast_config_policy", smoke_category_cast_config_policy),
|
("category_cast_config_policy", smoke_category_cast_config_policy),
|
||||||
("generation_profile_config_policy", smoke_generation_profile_config_policy),
|
("generation_profile_config_policy", smoke_generation_profile_config_policy),
|
||||||
|
("filter_config_policy", smoke_filter_config_policy),
|
||||||
("category_library_route", smoke_category_library_route),
|
("category_library_route", smoke_category_library_route),
|
||||||
("hardcore_category_routes", smoke_hardcore_category_routes),
|
("hardcore_category_routes", smoke_hardcore_category_routes),
|
||||||
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
|
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
|
||||||
|
|||||||
Reference in New Issue
Block a user