From cbc3f47b5472beab59632748c28838ff6d1e8efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EA=B2=BD?= Date: Fri, 24 Jul 2026 13:11:46 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20=EC=8A=AC?= =?UTF-8?q?=EB=A1=AF=20=EB=A7=A4=EC=B9=AD=20=EA=B0=9C=EC=84=A0,=20?= =?UTF-8?q?=EA=B0=80=EC=82=AC=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95,=20=EB=A1=A4=EB=B0=B1=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EB=A0=88=EB=B2=A8=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - creatomate: 썸네일 슬롯 한정 subject 일치 시 space_type 페널티 면제, subject 가중치 상향 (2.0 → 3.0) - lyric 스키마: 트레일링 콤마로 tuple이 되어버린 필드 정의 수정, 중복 genre 필드 제거 - session: 4xx 도메인 예외 롤백 로그를 traceback 없는 WARNING으로 완화 Co-Authored-By: Claude Fable 5 --- app/database/session.py | 17 ++++++++++++----- app/lyric/schemas/lyric.py | 9 ++------- app/utils/creatomate.py | 18 ++++++++++++++++-- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/app/database/session.py b/app/database/session.py index 6b914b3..7f60630 100644 --- a/app/database/session.py +++ b/app/database/session.py @@ -139,11 +139,18 @@ async def get_session() -> AsyncGenerator[AsyncSession, None]: raise except Exception as e: await session.rollback() - logger.error(traceback.format_exc()) - logger.error( - f"[get_session] ROLLBACK - error: {type(e).__name__}: {e}, " - f"duration: {(time.perf_counter() - start_time)*1000:.1f}ms" - ) + # status_code < 500인 도메인 예외(계정 미연동 등)는 정상적인 비즈니스 흐름이므로 ERROR로 남기지 않음 + if getattr(e, "status_code", 500) < 500: + logger.warning( + f"[get_session] ROLLBACK - client error: {type(e).__name__}: {e}, " + f"duration: {(time.perf_counter() - start_time)*1000:.1f}ms" + ) + else: + logger.error(traceback.format_exc()) + logger.error( + f"[get_session] ROLLBACK - error: {type(e).__name__}: {e}, " + f"duration: {(time.perf_counter() - start_time)*1000:.1f}ms" + ) raise finally: total_time = time.perf_counter() - start_time diff --git a/app/lyric/schemas/lyric.py b/app/lyric/schemas/lyric.py index ff476d6..7da6024 100644 --- a/app/lyric/schemas/lyric.py +++ b/app/lyric/schemas/lyric.py @@ -70,11 +70,11 @@ class GenerateLyricRequest(BaseModel): language: str = Field( default="Korean", description="가사 출력 언어 (Korean, English, Chinese, Japanese, Thai, Vietnamese)", - ), + ) orientation: Literal["horizontal", "vertical"] = Field( default="vertical", description="영상 방향 (horizontal: 가로형, vertical: 세로형)", - ), + ) m_id : Optional[int] = Field(None, description="마케팅 인텔리전스 ID 값") industry: str = Field(default="", description="업종 분류 (stay|restaurant|cafe|salon|clinic|fitness|academy|attraction|general)") instrumental: bool = Field(default=False, description="BGM 전용 모드 (가사 생성 안 함)") @@ -83,11 +83,6 @@ class GenerateLyricRequest(BaseModel): description="사용자가 수동으로 고른 음악 장르 (kpop|pop|ballad|hip-hop|rnb|edm|jazz|rock). " "'자동 선택'인 경우 None으로 전달하면 GPT가 가사 무드에 맞춰 추천", ) - genre: Optional[str] = Field( - None, - description="사용자가 수동으로 고른 음악 장르 (kpop|pop|ballad|hip-hop|rnb|edm|jazz|rock). " - "'자동 선택'인 경우 None으로 전달하면 GPT가 가사 무드에 맞춰 추천", - ) class GenerateLyricResponse(BaseModel): diff --git a/app/utils/creatomate.py b/app/utils/creatomate.py index c0662f0..f78e3e8 100644 --- a/app/utils/creatomate.py +++ b/app/utils/creatomate.py @@ -806,7 +806,7 @@ class CreatomateService: """이미지 슬롯에 대한 각 이미지의 매칭 점수를 계산합니다. 점수 = (base * narr_mod + NARR_BONUS * narr) * space_type_penalty - - base: 태그 카테고리 매칭 합산 (최대 5.5) + - base: 태그 카테고리 매칭 합산 (최대 6.5 = space_type 2.0 + subject 3.0 + camera 1.0 + motion 0.5) - narr_mod: [NARR_FLOOR, 1.0] 구간으로 완화된 narrative 계수 - NARR_BONUS * narr: narrative 독립 보너스 (base=0이어도 신호 유지) - space_type_penalty: 슬롯이 요구하는 space_type과 이미지가 불일치하면 @@ -830,6 +830,20 @@ class CreatomateService: for image_tag in image_tag_list ] + # 썸네일 슬롯 한정: subject가 슬롯 요구와 일치하면 space_type 불일치 + # 페널티를 면제한다. 배경: 일부 업종(예: 레스토랑 dining_hall-food_dish)은 + # 태깅 관행상 슬롯이 요구하는 subject(음식 클로즈업)가 실제로는 다른 + # space_type(table_setting/detail_plating 등)으로 붙는다 — space_type과 + # subject가 한 사진에 공존하기 어려운 조합. 이런 슬롯은 space_type 하드 + # 필터가 정작 원하는 subject 이미지를 전부 걸러내는 역효과를 낸다. + # 템플릿 슬롯명을 바꾸지 않고 여기서 흡수(썸네일 한정이라 일반 씬 슬롯의 + # space_type 하드 필터는 그대로 유지). + slot_subject = slot_tag_dict.get("subject") + if slot_name.endswith(THUMBNAIL_SLOT_MARKER) and slot_subject is not None: + for idx, image_tag in enumerate(image_tag_list): + if slot_subject.value in image_tag.get("subject", []): + space_type_match_list[idx] = True + for slot_tag_cate, slot_tag_item in slot_tag_dict.items(): if slot_tag_cate == "narrative_preference": slot_tag_narrative = slot_tag_item @@ -839,7 +853,7 @@ class CreatomateService: case "space_type": weight = 2.0 case "subject": - weight = 2.0 + weight = 3.0 case "camera": weight = 1.0 case "motion_recommended":