diff --git a/backend/app/services/scene_reference_service.py b/backend/app/services/scene_reference_service.py index d6f9e545..a7be130d 100644 --- a/backend/app/services/scene_reference_service.py +++ b/backend/app/services/scene_reference_service.py @@ -1008,8 +1008,13 @@ class SceneReferenceService: composite 키로 ', wearing ' 문자열을 추가. CharacterOutlook 쿼리 실패 시 warning log만 찍고 기본 short_id - map만 반환한다 (non-fatal). 기존 내부 로직과 동일한 fallback 순서 - 유지: t2i_prompt → description → name. + map만 반환한다 (non-fatal). fallback 순서: description → name. + + ★계약 (2026-07-02): entity `t2i_prompt` 는 reference 이미지 생성 전용 + 프롬프트(스타일 래퍼: "Photorealistic product photo, isolated object, + plain neutral background" 등 포함)라 in-scene 치환 텍스트로 절대 사용 + 금지 — 씬 프롬프트 문장 중간에 스플라이스되면 스타일 지시 충돌로 + 부양 오브젝트/패널 콜라주 등 렌더 오염을 일으킨다. Returns: {short_id: description, 'C##O##': composite, ...} """ @@ -1019,7 +1024,7 @@ class SceneReferenceService: for e in entities: sid = e.get("short_id", "") if sid: - text_map[sid] = e.get("t2i_prompt") or e.get("description") or e.get("name", "") + text_map[sid] = e.get("description") or e.get("name", "") # C##O## 합성 키 (인물+아웃룩 설명 합침) try: @@ -1033,12 +1038,8 @@ class SceneReferenceService: ).first() if char and outfit_ent and char.get("short_id") and outfit_ent.short_id: ck = f"{char['short_id']}{outfit_ent.short_id}" - char_desc = ( - char.get("t2i_prompt") or char.get("description") or char.get("name", "") - ) - outfit_desc = ( - outfit_ent.t2i_prompt or outfit_ent.description or outfit_ent.name - ) + char_desc = char.get("description") or char.get("name", "") + outfit_desc = outfit_ent.description or outfit_ent.name text_map[ck] = f"{char_desc}, wearing {outfit_desc}" except Exception as exc: logger.warning("Failed to build C##O## text map: %s", exc) diff --git a/backend/tests/services/test_scene_reference_service.py b/backend/tests/services/test_scene_reference_service.py index 7da04273..73f1c888 100644 --- a/backend/tests/services/test_scene_reference_service.py +++ b/backend/tests/services/test_scene_reference_service.py @@ -26,12 +26,13 @@ def svc() -> SceneReferenceService: def test_build_entity_text_map_basic_short_ids(svc): - """short_id 기반 fallback: t2i_prompt → description → name (B.16).""" + """short_id 기반 fallback: description → name — t2i_prompt 미사용 (2026-07-02 계약).""" # CharacterOutlook 조회는 빈 리스트로 (composite 키 생성 없음) svc._db.query.return_value.filter.return_value.all.return_value = [] entities = [ - {"id": "e1", "short_id": "C01", "t2i_prompt": "korean woman, 30s"}, + {"id": "e1", "short_id": "C01", "t2i_prompt": "REF_GEN_ONLY portrait prompt", + "description": "korean woman, 30s"}, {"id": "e2", "short_id": "L05", "description": "traditional kitchen"}, {"id": "e3", "short_id": "P03", "name": "wooden cane"}, {"id": "e4", "short_id": "", "t2i_prompt": "skipped"}, # no short_id → skip @@ -44,8 +45,31 @@ def test_build_entity_text_map_basic_short_ids(svc): assert "" not in result +def test_build_entity_text_map_never_uses_t2i_prompt(svc): + """★계약: ref 생성용 t2i_prompt 는 in-scene 치환 텍스트로 절대 미사용. + + (필드 선택 순서 검증 — 문자열 의미 판정 아님.) t2i_prompt 만 있고 + description/name 이 비면 빈 문자열로 남는다(스타일 래퍼 스플라이스 금지). + """ + svc._db.query.return_value.filter.return_value.all.return_value = [] + + entities = [ + {"id": "e1", "short_id": "P01", "t2i_prompt": "REF_GEN_ONLY product shot", + "description": "generic modern smartphone", "name": "휴대전화"}, + {"id": "e2", "short_id": "P02", "t2i_prompt": "REF_GEN_ONLY product shot", + "name": "메모지"}, + {"id": "e3", "short_id": "P03", "t2i_prompt": "REF_GEN_ONLY product shot"}, + ] + result = svc.build_entity_text_map(entities) + + assert result["P01"] == "generic modern smartphone" # description-first + assert result["P02"] == "메모지" # name fallback + assert result["P03"] == "" # t2i_prompt 로 안 떨어짐 + assert all("REF_GEN_ONLY" not in v for v in result.values()) + + def test_build_entity_text_map_composite_keys_from_outlook(svc): - """CharacterOutlook 링크 → C##O## 합성 키 생성 (B.16).""" + """CharacterOutlook 링크 → C##O## 합성 키 생성 (B.16, description-first).""" co = MagicMock() co.character_id = "c_uuid" co.outlook_id = "o_uuid" @@ -53,8 +77,8 @@ def test_build_entity_text_map_composite_keys_from_outlook(svc): outfit = MagicMock() outfit.id = "o_uuid" outfit.short_id = "O02" - outfit.t2i_prompt = "green hanbok" - outfit.description = None + outfit.t2i_prompt = "REF_GEN_ONLY outfit sheet" + outfit.description = "green hanbok" outfit.name = None # 첫 query: CharacterOutlook.filter().all() → [co] @@ -66,7 +90,8 @@ def test_build_entity_text_map_composite_keys_from_outlook(svc): svc._db.query.return_value = outer_query entities = [ - {"id": "c_uuid", "short_id": "C01", "t2i_prompt": "korean woman"}, + {"id": "c_uuid", "short_id": "C01", "t2i_prompt": "REF_GEN_ONLY portrait", + "description": "korean woman"}, ] result = svc.build_entity_text_map(entities)