fix: 컷별 댓글 배정을 컷 우선이 아닌 순위(라운드) 우선으로 변경

컷 하나를 4순위까지 다 채운 뒤 다음 컷으로 넘어가면, 앞 컷의 약한 근거(4순위
좋아요)가 뒤 컷의 강한 근거(3순위 단어 겹침)보다 먼저 댓글을 가로챈다. 라운드를
바깥 루프로 두어(전 컷의 ts → 전 컷의 ai → 전 컷의 word → 전 컷의 like) 순위가
컷 순서를 이기게 했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hehihoho3@gmail.com 2026-08-04 18:47:55 +09:00
parent 011c042778
commit 7c74fec079

View File

@ -97,7 +97,12 @@ def build_cut_picks(cuts, comments, ai_picks: Optional[Dict[int, List[int]]],
단어 겹침이 AI 다음인 이유: Gemini 문맥까지 보고 고르니 정확하지만, 503 등으로 단어 겹침이 AI 다음인 이유: Gemini 문맥까지 보고 고르니 정확하지만, 503 등으로
실패하거나(`ai_picks=None`) 문맥상 연결을 놓칠 (: "와인 뱉는 장면" "싱크대로 달려간 실패하거나(`ai_picks=None`) 문맥상 연결을 놓칠 (: "와인 뱉는 장면" "싱크대로 달려간
이유"는 겹치는 단어가 없다) 그물을 하나 더 치는 것 — 네트워크 없이도 항상 동작한다. 이유"는 겹치는 단어가 없다) 그물을 하나 더 치는 것 — 네트워크 없이도 항상 동작한다.
중복은 컷이 가져간다( 컷은 다음 후보로 밀린다).
컷별로 4단계를 채우고 다음 컷으로 넘어가면 된다 컷의 약한 근거(4순위
좋아요) 컷의 강한 근거(3순위 단어 겹침)보다 먼저 댓글을 가져가 버린다. 그래서
**단계(라운드) 바깥 루프, 컷을 안쪽 루프** 둔다 컷의 ts를 채운 뒤에야
컷의 ai로, 그다음에야 word로 넘어간다. 순위가 순서보다 우선한다.
중복은 컷이 가져간다( 컷은 다음 후보로 밀린다) `used` 라운드·컷을 통틀어 공유.
ai_picks: {컷인덱스: [댓글idx ]} Gemini 실패 None/{} 어느 쪽을 넘겨도 ai_picks: {컷인덱스: [댓글idx ]} Gemini 실패 None/{} 어느 쪽을 넘겨도
타임스탬프만으로 채운다(`ai_pick_cuts` 실패를 None 으로 알린다). 타임스탬프만으로 채운다(`ai_pick_cuts` 실패를 None 으로 알린다).
@ -107,33 +112,43 @@ def build_cut_picks(cuts, comments, ai_picks: Optional[Dict[int, List[int]]],
valid = {c["idx"] for c in comments} valid = {c["idx"] for c in comments}
no_ts = [c for c in comments if not c.get("times")] no_ts = [c for c in comments if not c.get("times")]
used: set = set() used: set = set()
out: List[List[dict]] = [] quota = [quotas[i] if i < len(quotas) else 0 for i in range(len(cuts))]
out: List[List[dict]] = [[] for _ in cuts]
# 1라운드: ⭐시각
for i, cut in enumerate(cuts): 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"])]): for idx in match_ranges(comments, [(cut["start"], cut["end"])]):
if len(picks) >= quota: if len(out[i]) >= quota[i]:
break break
if idx not in used: if idx not in used:
picks.append({"idx": idx, "why": "ts"}) out[i].append({"idx": idx, "why": "ts"})
used.add(idx) used.add(idx)
# 2라운드: 🤖AI
for i in range(len(cuts)):
if len(out[i]) >= quota[i]:
continue
for idx in (ai_picks.get(i) or []): for idx in (ai_picks.get(i) or []):
if len(picks) >= quota: if len(out[i]) >= quota[i]:
break break
if idx in valid and idx not in used: if idx in valid and idx not in used:
picks.append({"idx": idx, "why": "ai"}) out[i].append({"idx": idx, "why": "ai"})
used.add(idx) used.add(idx)
if len(picks) < quota: # 3라운드: 🔤단어겹침
for idx in _word_match_ranked(cut, comments, used): for i, cut in enumerate(cuts):
if len(picks) >= quota: if len(out[i]) >= quota[i]:
break continue
picks.append({"idx": idx, "why": "word"}) for idx in _word_match_ranked(cut, comments, used):
used.add(idx) if len(out[i]) >= quota[i]:
if len(picks) < quota: break
for idx in top_liked(no_ts, used, quota - len(picks)): out[i].append({"idx": idx, "why": "word"})
picks.append({"idx": idx, "why": "like"}) used.add(idx)
used.add(idx) # 4라운드: ➕좋아요
out.append(picks) for i in range(len(cuts)):
if len(out[i]) >= quota[i]:
continue
for idx in top_liked(no_ts, used, quota[i] - len(out[i])):
out[i].append({"idx": idx, "why": "like"})
used.add(idx)
return out return out