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
|
||||
parsers live in `category_cast_config.py`; `prompt_builder.py` keeps public
|
||||
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
|
||||
profile config parsing live in `generation_profile_config.py`;
|
||||
`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_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. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
|
||||
@@ -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
|
||||
|
||||
try:
|
||||
from .filter_config import (
|
||||
build_ethnicity_list_json,
|
||||
build_filter_config_json,
|
||||
)
|
||||
from .generation_profile_config import (
|
||||
build_generation_profile_json,
|
||||
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_filter_config_json,
|
||||
)
|
||||
except ImportError: # Allows local smoke tests from the repository root.
|
||||
from generation_profile_config import (
|
||||
build_generation_profile_json,
|
||||
generation_profile_choices,
|
||||
)
|
||||
from prompt_builder import (
|
||||
build_ethnicity_list_json,
|
||||
build_filter_config_json,
|
||||
)
|
||||
|
||||
|
||||
SXCP_GENERATION_PROFILE = "SXCP_GENERATION_PROFILE"
|
||||
|
||||
+52
-194
@@ -25,6 +25,7 @@ try:
|
||||
)
|
||||
from . import camera_config as camera_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 generation_profile_config as generation_profile_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 category_cast_config as category_cast_policy
|
||||
import filter_config as filter_policy
|
||||
import generate_prompt_batches as g
|
||||
import generation_profile_config as generation_profile_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_MODE_CHOICES = seed_policy.SEED_MODE_CHOICES
|
||||
|
||||
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_FILTER_CHOICES = filter_policy.ETHNICITY_FILTER_CHOICES
|
||||
ETHNICITY_LIST_KEYS = filter_policy.ETHNICITY_LIST_KEYS
|
||||
ETHNICITY_BASE_LIST_KEYS = filter_policy.ETHNICITY_BASE_LIST_KEYS
|
||||
EUROPEAN_REGIONAL_LIST_KEYS = filter_policy.EUROPEAN_REGIONAL_LIST_KEYS
|
||||
MEDITERRANEAN_REGIONAL_LIST_KEYS = filter_policy.MEDITERRANEAN_REGIONAL_LIST_KEYS
|
||||
|
||||
CHARACTER_LABEL_CHOICES = [
|
||||
"auto_chain",
|
||||
@@ -1114,38 +1067,21 @@ def build_filter_config_json(
|
||||
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,
|
||||
return filter_policy.build_filter_config_json(
|
||||
ethnicity=ethnicity,
|
||||
figure=figure,
|
||||
no_plus_women=no_plus_women,
|
||||
no_black=no_black,
|
||||
include_european=include_european,
|
||||
include_mediterranean_mena=include_mediterranean_mena,
|
||||
include_latina=include_latina,
|
||||
include_east_asian=include_east_asian,
|
||||
include_southeast_asian=include_southeast_asian,
|
||||
include_south_asian=include_south_asian,
|
||||
include_black_african=include_black_african,
|
||||
include_indigenous=include_indigenous,
|
||||
include_mixed=include_mixed,
|
||||
include_plus_size=include_plus_size,
|
||||
)
|
||||
|
||||
|
||||
@@ -1260,31 +1196,15 @@ def build_thematic_location_json(
|
||||
|
||||
|
||||
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
|
||||
return filter_policy.ethnicity_text_from_value(value)
|
||||
|
||||
|
||||
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
|
||||
return filter_policy.is_valid_ethnicity_filter(value)
|
||||
|
||||
|
||||
def normalize_ethnicity_filter(value: Any, default: str = "any", allow_random: bool = False) -> str:
|
||||
text = _ethnicity_text_from_value(value)
|
||||
if text.lower() in CHARACTER_RANDOM_TOKENS:
|
||||
return "random" if allow_random else default
|
||||
return text if _is_valid_ethnicity_filter(text) else default
|
||||
return filter_policy.normalize_ethnicity_filter(value, default, allow_random)
|
||||
|
||||
|
||||
def build_ethnicity_list_json(
|
||||
@@ -1313,98 +1233,36 @@ def build_ethnicity_list_json(
|
||||
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,
|
||||
}
|
||||
return filter_policy.build_ethnicity_list_json(
|
||||
include_european=include_european,
|
||||
include_mediterranean_mena=include_mediterranean_mena,
|
||||
include_latina=include_latina,
|
||||
include_east_asian=include_east_asian,
|
||||
include_southeast_asian=include_southeast_asian,
|
||||
include_south_asian=include_south_asian,
|
||||
include_black_african=include_black_african,
|
||||
include_indigenous=include_indigenous,
|
||||
include_mixed=include_mixed,
|
||||
include_asian=include_asian,
|
||||
include_white_asian=include_white_asian,
|
||||
include_western_european=include_western_european,
|
||||
include_french_european=include_french_european,
|
||||
include_germanic_european=include_germanic_european,
|
||||
include_nordic_european=include_nordic_european,
|
||||
include_celtic_european=include_celtic_european,
|
||||
include_slavic_european=include_slavic_european,
|
||||
include_baltic_european=include_baltic_european,
|
||||
include_alpine_european=include_alpine_european,
|
||||
include_balkan_european=include_balkan_european,
|
||||
include_greek_mediterranean=include_greek_mediterranean,
|
||||
include_italian_mediterranean=include_italian_mediterranean,
|
||||
include_iberian_mediterranean=include_iberian_mediterranean,
|
||||
strict_excludes=strict_excludes,
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
return filter_policy.parse_filter_config(filter_config)
|
||||
|
||||
|
||||
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 category_cast_config # noqa: E402
|
||||
import category_library # noqa: E402
|
||||
import filter_config # noqa: E402
|
||||
import __init__ as sxcp_nodes # noqa: E402
|
||||
import generation_profile_config # 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")
|
||||
|
||||
|
||||
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:
|
||||
categories = category_library.load_category_library()
|
||||
_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),
|
||||
("category_cast_config_policy", smoke_category_cast_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),
|
||||
("hardcore_category_routes", smoke_hardcore_category_routes),
|
||||
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
|
||||
|
||||
Reference in New Issue
Block a user