Repository navigation
feat: 영상 행동 분석 검증·Web 응답과 종료 자격·OFF 복구·멱등성 보완 - #120
Merged
Merged
Conversation
- saveInterview의 null/IN_PROGRESS → COMPLETED 보정을 없앴다. 새 기록의 잘못된 상태는 claim 전에 422로 거절해 면접은 진행 중으로 남는다. - 이미 종료된 면접은 다시 온 요청의 상태와 상관없이 첫 기록·질문 PK·최종 상태를 그대로 둔다. - handleResult는 저장 커밋 뒤 DB 상태를 다시 읽어 COMPLETED만 채점·분석을 준비한다. 중단 기록은 보존하고 정상 응답한다. - 공통 정책 객체 InterviewFeedbackPolicy를 두고 FeedbackService도 같은 판단을 쓴다. - 모르는 상태 문자열은 400 대신 null로 받아 422로 거절한다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 분석 서버 validation.py와 같은 규칙으로 BehaviorData를 타입·의미까지 검증한다(비율·coverage·사유·timeline 합·이벤트·경고·품질 판정). - 종료 문서 포장 상태 일치, 공개 오류 코드·retryable 사전, videoId·durationMs 묶음, NUL 문자를 검증한다. - Web PR #189의 계약 fixture 18개를 테스트 리소스로 가져와 정상·부정 변형을 검사한다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 새 요청·재전송·작업 조회 복구·자동/사용자 재분석·조회·늦은 콜백이 모두 COMPLETED 음성 면접만 통과한다. 중단·텍스트는 정책 사유로 닫고 진행 중은 기다린다. - GET/retry 응답에 reasonCode·error·behavior·latestAnalysisId·behaviorAnalysisId·showingPreviousResult를 더했다. 상태와 표시 결과를 따로 고른다. - sweep을 3단계로 나눠 꺼져 있어도 맡긴 요청의 재전송·조회는 계속한다. 꺼져 있으면 자동 재분석 대기로 보이지 않는다. - 콜백·작업 조회는 요청 행을 잠그고 같은 검증기를 쓴다. 같은 문서는 no-op, 상충 문서는 처음 것을 두고 따로 남기며, 결과 없이 닫은 행의 늦은 첫 결과는 받는다. - 요청 생성 경로는 면접 행 잠금 아래에서 자격·진행 중 요청·사용자 한도를 다시 본다. - 콜백 본문은 인증 뒤 UTF-8 1MiB까지만 읽고 넘으면 413이다. 영상 경로 본문은 로그에 싣지 않는다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
[P2] 이벤트 필드가 null이면 검증기가 NPE로 종료됩니다. |
Contributor
|
[P2] 작업 조회 복구 경로에서는 행동 지표가 로그에 노출됩니다. |
danjee2009
reviewed
Oct 20, 2026
danjee2009
left a comment
Member
There was a problem hiding this comment.
검토에서 확인한 문제 1건입니다. 유효하지 않은 분석 결과가 영구 오류로 표시되지만 재분석 가능 상태로 저장될 수 있습니다.
… 불일치 - 이벤트 type·sourceMetric이 null이면 불변 Map 조회에서 NPE로 500이 됐다. 문자열을 먼저 검사하고, 규칙이 못 다룬 모양도 계약 위반으로 돌려 따로 남긴다. - 외부 호출 로그가 /analysis/video 요청·작업 조회 응답 본문(행동 수치)을 남겼다. 이 경로는 본문을 생략한다. - INVALID_RESULT를 retryable=false로 남긴다. 화면의 error.retryable과 canRetry가 같은 판단(VideoPublicErrors.userRetryable)을 쓴다. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2-dayeon
approved these changes
Oct 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
기준과 결과
ae5471b1bab73ec2155bc4d8687824cb4ba86f51(작업 중 feat: 문항별 예상 꼬리질문 저장·조회와 기업 코멘트 진단 로그 #119·fix/#117-retry-job-snapshot #118 병합으로4223095→040ee7e→ae5471b로 두 번 갱신, 최종 기준에 rebase)f6bd95c3741cab5b4f51b5c27da9e5e7b95faed2(커밋 3개)codex/behavior-feedback@29148cb의docs/behavior-analysis(00·01·04·contracts), Analysisorigin/main@8b33027의behavior/validation.py·video-api.md, 이 저장소의docs/abandoned-interview-feedback/resultJSONB·unmatched_callback·부분 unique index를 그대로 쓴다.구현 항목
A. 종료 자격
saveInterview의 null/IN_PROGRESS → COMPLETED 보정 제거. 새 기록은 COMPLETED/ABANDONED만 받고 그 외는 claim 전 422(면접은 IN_PROGRESS 유지). 모르는 상태 문자열도 400이 아니라 422.handleResult는 커밋 뒤 DB 상태를 다시 읽어 COMPLETED만onTranscriptSaved. ABANDONED는 기록 보존·정상 응답(outbox 정리).InterviewFeedbackPolicy(RUN/SKIP/HOLD, 409 메시지)를 만들고FeedbackService도 이를 쓴다. 영상은VideoAnalysisEligibility로 텍스트 면접을 추가 제외.INTERVIEW_ABANDONED/TEXT_INTERVIEW로 외부 호출 없이 닫고, IN_PROGRESS는 닫지 않고 10분 간격으로 미룬다. 업로드 자체는 상태와 무관하게 보존(변경 없음).B. BehaviorData 검증기
analysis/behavior/:BehaviorValidator(validation.py 포팅 + JSON Schema 타입·범위),VideoResultDocument(포장 상태 일치·ready/partial/unavailable/failed 모양·videoId/durationMs 묶음·품질 판정 일치·NUL 거부),VideoPublicErrors(공개 코드·retryable 사전, BEHAVIOR_NOT_MEASURABLE=false).1, data=behavior-v1, model=mp-face-pose-v1, thresholds=behavior-policy-v1고정. 숫자 문자열·문자열 boolean·정수 자리 실수·NaN 거부.C. Web 응답
reasonCode/error/behavior/latestAnalysisId/behaviorAnalysisId/showingPreviousResult추가(null도 키 유지). 최신 요청 상태와 같은 영상의 최신 유효 ready/partial을 따로 고른다.RESULT_SCHEMA_UNSUPPORTED.canRetry는 PARTIAL/FAILED에서만 true.D. OFF·재활성화
sweep()3단계 분리: 신규 요청·자동 재분석은 ON일 때만, 기존 PENDING의 같은 requestId 재전송·job GET은 항상(OFF인데 요청 토큰도 없으면 실패로 닫지 않고 미룸).autoReanalysisPending에 enabled 반영(OFF면 FAILED·canRetry=false). 요청 없는 OFF 영상은NONE/SERVICE_DISABLED.E. 멱등성·경쟁·수신 한도
SELECT … FOR UPDATE뒤 반영. 같은 종료 문서(키 순서·숫자 표기 차이 포함)는 no-op, 상충 문서는 첫 결과 유지 +INVALID_BODY로 별도 보관.result에 남겨 로컬로만 닫은 행(result 없음)과 구분 → 로컬 FAILED 행에는 늦은 첫 결과를 저장. 호환용result응답에는 완료 문서만.acknowledge는 기존대로 PENDING 조건부라 콜백 선도착 뒤 jobId를 덮지 않음(테스트 추가).명세에 추가한 기술 가정
INTERVIEW_ABANDONED,TEXT_INTERVIEW,INTERVIEW_NOT_FOUND(error_retryable=false). 화면에는 나가지 않는다(중단은 409, 텍스트는 NONE).UNKNOWN_ERROR.VIDEO_ANALYSIS_UPLOADED_AFTER유지, 의도적 OFF 뒤 재활성화는 재활성화 시각으로 갱신 → OFF 기간 무요청 업로드는 신규 전송 차단, 요청 이력 있는 영상의 retry는 유지. (application.yaml 주석 반영)eventsTruncated=true면 이벤트가 정확히 60개여야 한다(Analysis 집계 동작·Web 파서와 같음).c1479ec)은 세션 확정 상태를 실어 보낸다. 상태 없는 옛 outbox payload는 새 면접이면 422로 남으므로 배포 전 Chat outbox에 status 누락 건이 있는지 확인 필요. 과거 fallback으로 COMPLETED가 된 행은 API DB에 표식이 없어 식별 불가 — 일괄 승격·강등하지 않았다.실행 결과
./gradlew test(격리 PostgreSQL 18 컨테이너, CI와 같은 env): 1092 tests, 0 failures, 0 skipped.BehaviorContractTest69(fixture 9 + 행동 부정 32 + 포장 부정 22 + 기타),VideoAnalysisViewContractTest9,VideoAnalysisPersistenceTest28,VideoAnalysisServiceTest45,InterviewResultIdempotencyTest8, 컨트롤러·로깅·역직렬화 추가.PostgreSQL 경쟁 증거 (실제 두 연결)
두_연결이_상충하는_결과를_겹쳐_보내도_먼저_잠근_쪽만_반영한다: 연결 A가 요청 행을 잠근 채 ready 저장, 연결 B의 failed 콜백이 500ms 이상 대기(isNotDone) → A 커밋 뒤 B는 CONFLICT, 행은 READY, 별도 보관 1건.재분석_한도는_면접_잠금_아래에서_다시_세어_인덱스가_못_잡는_경쟁도_막는다: 탭 A가 면접 행을 잠근 채 두 번째 USER 묶음(종료 상태, PENDING 아님)을 만드는 동안 탭 B retry가 대기 → B는 "2번까지" 409. 부분 unique index로는 잡히지 않는 경우.두_탭이_동시에_재분석을_눌러도_한_번만_받는다,완료와_중단이_동시에_와도_먼저_확정한_상태만_남는다(4연결 동시 저장).Web 계약 호환
api-*.json9개와 의미값까지 일치.validateVideoAnalysis.ts원본(import 확장자만 추가)을 Node 24로 실행: API 응답 9/9 통과, 부정 변형 12/12 거부.실제 연동 여부
남은 미확정·활성화 차단 항목
이 PR은 검토용이며 병합·배포·운영 데이터 정리 승인을 뜻하지 않는다.
🤖 Generated with Claude Code