컷별 댓글 배정 순수 로직 — quotas_for, build_cut_picks

배정 규칙(타임스탐프 우선·중복 제거·Gemini 폴백)을 댓글 수집·HTTP 처리와
분리해 순수 함수로 구현. 기준 카드 길이 3초(CARD_SEC)로 컷별 쿼터 계산.
타임스탠프 매칭은 match_ranges() 이용, Gemini 없이 검증 가능.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hehihoho3@gmail.com 2026-08-04 11:44:59 +09:00
parent 47362a814e
commit df8bc7baec

54
capcut_agent/recommend.py Normal file
View File

@ -0,0 +1,54 @@
"""컷별 댓글 추천 — 자동 탭 검토 화면용.
별도 모듈인가: 배정 규칙(타임스탬프 우선·중복 제거·모드 분기) 댓글 수집
(`comments.py`)과도, HTTP 처리(`server/app.py`)와도 책임이 다르다. 순수 함수로 떼어놔야
Gemini 없이 단위 검증이 된다.
설계 근거는 docs/superpowers/specs/2026-08-04-컷별-댓글-추천-design.md.
"""
from __future__ import annotations
from typing import Dict, List
from .comments import match_ranges
# 카드 1장이 차지하는 기준 시간(초). pipeline._load_comment_cards(min_sec) 과 같은 값.
CARD_SEC = 3.0
def quotas_for(cuts) -> List[int]:
"""컷별 카드 장수 — max(1, round(컷길이 / CARD_SEC)). 짧은 컷도 최소 1장."""
return [max(1, round((c["end"] - c["start"]) / CARD_SEC)) for c in cuts]
def build_cut_picks(cuts, comments, ai_picks: Dict[int, List[int]],
quotas) -> List[List[dict]]:
"""컷별 추천 확정 — 타임스탬프 우선, 남는 자리만 AI, 전 컷 통틀어 중복 금지.
타임스탬프가 AI보다 먼저인 이유: 컷의 **원본 구간** 집어 언급한 댓글은
근거가 확실하다. 추측(AI) 이기게 이유가 없다.
중복은 컷이 가져간다( 컷은 다음 후보로 밀린다).
ai_picks: {컷인덱스: [댓글idx ]} Gemini 실패 {} 넘기면 타임스탬프만으로 채운다.
Returns: 컷별 [{"idx": int, "why": "ts"|"ai"}]
"""
valid = {c["idx"] for c in comments}
used: set = set()
out: List[List[dict]] = []
for i, cut in enumerate(cuts):
quota = quotas[i] if i < len(quotas) else 0
picks: List[dict] = []
for idx in match_ranges(comments, [(cut["start"], cut["end"])]):
if len(picks) >= quota:
break
if idx not in used:
picks.append({"idx": idx, "why": "ts"})
used.add(idx)
for idx in (ai_picks.get(i) or []):
if len(picks) >= quota:
break
if idx in valid and idx not in used:
picks.append({"idx": idx, "why": "ai"})
used.add(idx)
out.append(picks)
return out