Extract Krea row field policy

This commit is contained in:
2026-06-27 11:42:14 +02:00
parent d7caf1c270
commit 8fc3abc504
5 changed files with 153 additions and 50 deletions
+4 -2
View File
@@ -342,8 +342,10 @@ Already isolated:
wrapper helpers.
- `krea_normal_formatter.py` owns normal metadata single/couple/generic Krea
prose assembly behind `KreaNormalRowRequest`, `KreaNormalRowDependencies`,
and `KreaNormalRowPrompt`; `krea_formatter.py` keeps common row-field
extraction and route selection.
and `KreaNormalRowPrompt`; `krea_formatter.py` keeps route selection.
- `krea_row_fields.py` owns shared normal-row Krea field extraction for item,
scene, pose, expression, composition/source-composition, camera, and style so
normal and configured-cast Krea routes cannot drift independently.
- `krea_pair_formatter.py` owns Insta/OF pair soft/hard Krea prose assembly
behind `KreaPairFormatRequest`, `KreaPairFormatDependencies`, and
`KreaPairPrompts`; `krea_formatter.py` keeps the `_insta_pair_to_krea`
+1
View File
@@ -115,6 +115,7 @@ Core helper ownership:
| `pov_policy.py` | Shared POV slot detection, POV label merging/filtering, builder POV directives, source role-graph viewer replacement, and shared POV composition cleanup used by builder and Krea2 routes. |
| `scene_camera_adapters.py` | Location-aware camera/scene prose such as coworking lounge camera layout. |
| `row_camera.py` | Row-level camera insertion, contextual coworking composition mutation, subject-kind detection, POV label fallback, and POV suppression of normal camera directives. |
| `krea_row_fields.py` | Shared Krea normal-row field extraction for item, scene, pose, expression, composition/source-composition, camera, and style used by normal and configured-cast routes. |
| `krea_cast.py` | Shared formatter cast descriptor parsing, cast labels, cast prose, natural cast descriptor text, and label replacement used by Krea2 and caption routes. |
| `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup. |
| `row_normalization.py` | Final prompt-row and pair metadata normalization: trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output and side-metadata synchronization, and embedded row sanitation. |
+41 -48
View File
@@ -14,6 +14,7 @@ try:
from . import krea_configured_cast_formatter
from . import krea_normal_formatter
from . import krea_pair_formatter
from . import krea_row_fields
from .hardcore_text_cleanup import (
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
@@ -48,6 +49,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
import krea_configured_cast_formatter
import krea_normal_formatter
import krea_pair_formatter
import krea_row_fields
from hardcore_text_cleanup import (
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
@@ -399,6 +401,17 @@ def _style_phrase(row: dict[str, Any], style_mode: str) -> str:
return style or suffix
def _krea_row_field_dependencies() -> krea_row_fields.KreaRowFieldDependencies:
return krea_row_fields.KreaRowFieldDependencies(
clean=_clean,
row_value=_row_value,
camera_phrase=_camera_phrase,
camera_scene_phrase=_camera_scene_phrase,
style_phrase=_style_phrase,
expression_disabled=_expression_disabled,
)
def _krea_normal_row_dependencies() -> krea_normal_formatter.KreaNormalRowDependencies:
return krea_normal_formatter.KreaNormalRowDependencies(
clean=_clean,
@@ -416,33 +429,25 @@ def _krea_normal_row_request_from_row(
detail_level: str,
style_mode: str,
) -> krea_normal_formatter.KreaNormalRowRequest:
subject_type = _clean(row.get("subject_type"))
primary = _clean(row.get("primary_subject"))
item = _row_value(row, "item", ("Sexual pose", "Erotic outfit", "Clothing")) or _clean(row.get("custom_item"))
item = re.sub(r",?\s*(fashion editorial|resort) styling$", "", item, flags=re.IGNORECASE)
scene = _row_value(row, "scene_text", ("Setting", "Scene")) or _clean(row.get("scene"))
pose = _row_value(row, "pose", ("Sexual pose", "Pose"))
expression = ""
if not _expression_disabled(row):
expression = _row_value(row, "character_expression_text") or _row_value(row, "expression", ("Facial expressions", "Facial expression"))
composition = re.sub(r"^vertical\s+", "", _row_value(row, "composition", ("Composition",)), flags=re.IGNORECASE)
camera = _camera_phrase(row)
camera_scene = _camera_scene_phrase(row)
style = _style_phrase(row, style_mode)
fields = krea_row_fields.extract_krea_row_fields(
row,
style_mode,
_krea_row_field_dependencies(),
)
return krea_normal_formatter.KreaNormalRowRequest(
row=row,
detail_level=detail_level,
style_mode=style_mode,
subject_type=subject_type,
primary=primary,
item=item,
scene=scene,
pose=pose,
expression=expression,
composition=composition,
camera=camera,
camera_scene=camera_scene,
style=style,
subject_type=fields.subject_type,
primary=fields.primary,
item=fields.item,
scene=fields.scene,
pose=fields.pose,
expression=fields.expression,
composition=fields.composition,
camera=fields.camera,
camera_scene=fields.camera_scene,
style=fields.style,
)
@@ -505,36 +510,24 @@ def _krea_configured_cast_request_from_row(
detail_level: str,
style_mode: str,
) -> krea_configured_cast_formatter.KreaConfiguredCastRequest:
primary = _clean(row.get("primary_subject"))
item = _row_value(row, "item", ("Sexual pose", "Erotic outfit", "Clothing")) or _clean(row.get("custom_item"))
item = re.sub(r",?\s*(fashion editorial|resort) styling$", "", item, flags=re.IGNORECASE)
scene = _row_value(row, "scene_text", ("Setting", "Scene")) or _clean(row.get("scene"))
expression = ""
if not _expression_disabled(row):
expression = _row_value(row, "character_expression_text") or _row_value(row, "expression", ("Facial expressions", "Facial expression"))
composition = re.sub(r"^vertical\s+", "", _row_value(row, "composition", ("Composition",)), flags=re.IGNORECASE)
source_composition = re.sub(
r"^vertical\s+",
"",
_clean(row.get("source_composition")) or composition,
flags=re.IGNORECASE,
fields = krea_row_fields.extract_krea_row_fields(
row,
style_mode,
_krea_row_field_dependencies(),
)
camera = _camera_phrase(row)
camera_scene = _camera_scene_phrase(row)
style = _style_phrase(row, style_mode)
return _krea_configured_cast_request(
row,
detail_level,
style_mode,
primary,
item,
scene,
expression,
composition,
source_composition,
camera,
camera_scene,
style,
fields.primary,
fields.item,
fields.scene,
fields.expression,
fields.composition,
fields.source_composition,
fields.camera,
fields.camera_scene,
fields.style,
)
+71
View File
@@ -0,0 +1,71 @@
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Any, Callable
@dataclass(frozen=True)
class KreaRowFields:
subject_type: str
primary: str
item: str
scene: str
pose: str
expression: str
composition: str
source_composition: str
camera: str
camera_scene: str
style: str
@dataclass(frozen=True)
class KreaRowFieldDependencies:
clean: Callable[[Any], str]
row_value: Callable[[dict[str, Any], str, tuple[str, ...]], str]
camera_phrase: Callable[[dict[str, Any]], str]
camera_scene_phrase: Callable[[dict[str, Any]], str]
style_phrase: Callable[[dict[str, Any], str], str]
expression_disabled: Callable[[dict[str, Any]], bool]
def _without_vertical_prefix(text: str) -> str:
return re.sub(r"^vertical\s+", "", text, flags=re.IGNORECASE)
def _clean_item_suffix(text: str) -> str:
return re.sub(r",?\s*(fashion editorial|resort) styling$", "", text, flags=re.IGNORECASE)
def extract_krea_row_fields(
row: dict[str, Any],
style_mode: str,
deps: KreaRowFieldDependencies,
) -> KreaRowFields:
item = deps.row_value(row, "item", ("Sexual pose", "Erotic outfit", "Clothing")) or deps.clean(
row.get("custom_item")
)
item = _clean_item_suffix(item)
expression = ""
if not deps.expression_disabled(row):
expression = deps.row_value(row, "character_expression_text", ()) or deps.row_value(
row,
"expression",
("Facial expressions", "Facial expression"),
)
composition = _without_vertical_prefix(deps.row_value(row, "composition", ("Composition",)))
source_composition = _without_vertical_prefix(deps.clean(row.get("source_composition")) or composition)
return KreaRowFields(
subject_type=deps.clean(row.get("subject_type")),
primary=deps.clean(row.get("primary_subject")),
item=item,
scene=deps.row_value(row, "scene_text", ("Setting", "Scene")) or deps.clean(row.get("scene")),
pose=deps.row_value(row, "pose", ("Sexual pose", "Pose")),
expression=expression,
composition=composition,
source_composition=source_composition,
camera=deps.camera_phrase(row),
camera_scene=deps.camera_scene_phrase(row),
style=deps.style_phrase(row, style_mode),
)
+36
View File
@@ -48,6 +48,7 @@ import krea_configured_cast_formatter # noqa: E402
import krea_formatter # noqa: E402
import krea_normal_formatter # noqa: E402
import krea_pair_formatter # noqa: E402
import krea_row_fields # noqa: E402
import location_config # noqa: E402
import loop_nodes # noqa: E402
import pair_builder # noqa: E402
@@ -649,6 +650,40 @@ def smoke_krea_normal_row_routes() -> None:
_expect_krea_normal_route_parity(generic, "krea_normal_generic", "metadata(generic)")
def smoke_krea_row_fields_policy() -> None:
row = {
"subject_type": "configured_cast",
"primary_subject": "woman",
"cast_summary": "1 woman, 1 man",
"item": "lace bodysuit fashion editorial styling",
"pose": "standing close together",
"scene_text": "private room with warm lamps",
"expression": "soft smile",
"expression_enabled": False,
"composition": "vertical tight two-person frame",
"source_composition": "vertical source action frame",
"camera_directive": "Camera: eye-level close-up",
"camera_scene_directive": "Camera-aware scene layout.",
"style": "realistic social photo",
}
fields = krea_row_fields.extract_krea_row_fields(
row,
"preserve",
krea_formatter._krea_row_field_dependencies(),
)
normal_request = krea_formatter._krea_normal_row_request_from_row(row, "balanced", "preserve")
cast_request = krea_formatter._krea_configured_cast_request_from_row(row, "balanced", "preserve")
_expect(fields.item == "lace bodysuit", "Krea row fields did not strip generic styling suffix")
_expect(fields.expression == "", "Krea row fields ignored expression disabled flag")
_expect(fields.composition == "tight two-person frame", "Krea row fields did not normalize composition prefix")
_expect(fields.source_composition == "source action frame", "Krea row fields did not normalize source composition")
_expect(normal_request.item == fields.item, "Normal Krea route item extraction drifted")
_expect(cast_request.item == fields.item, "Configured-cast Krea route item extraction drifted")
_expect(normal_request.expression == cast_request.expression == fields.expression, "Krea route expression extraction drifted")
_expect(normal_request.camera == cast_request.camera == fields.camera, "Krea route camera extraction drifted")
_expect(cast_request.source_composition == fields.source_composition, "Configured-cast source composition drifted")
def smoke_location_config_policy() -> None:
_expect(pb.LOCATION_POOL_PRESETS is location_config.LOCATION_POOL_PRESETS, "Prompt builder location presets are not delegated")
_expect(pb.COMPOSITION_POOL_PRESETS is location_config.COMPOSITION_POOL_PRESETS, "Prompt builder composition presets are not delegated")
@@ -5256,6 +5291,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
("row_camera_policy", smoke_row_camera_policy),
("config_route_location_theme", smoke_config_route_location_theme),
("krea_normal_row_routes", smoke_krea_normal_row_routes),
("krea_row_fields_policy", smoke_krea_row_fields_policy),
("location_config_policy", smoke_location_config_policy),
("row_location_policy", smoke_row_location_policy),
("row_expression_policy", smoke_row_expression_policy),