bibimbap/.atp/work-session/20260624-100749/implementation/W2-6-impl.md

15 KiB

phase agent agent_version generated_at workstream concerns concerns_checked workers_spawned planned_workers actual_workers self_verification references
implementation implementation-advisor 1 2026-06-24T13:10:00+09:00 W2-6-시상 집계/결과
true 9 12 9
checklist_passed test_compile unused_diagnostics
true PASS PASS
design ownership
.atp/work-session/20260623-104307/implementation/W2-6-award-aggregation-design.md .atp/work-session/20260624-100749/implementation/W2-6-ownership.md

구현 보고 — W2-6 시상 집계 / 결과 (W2 종점)

변경 목록

신규 생성 (production)

파일 worker 결과 요약
jam/AwardTrack.java advisor 직접(w-002로 계획) enum JUDGE/USER_RATING/POPULAR/GRAND + isValid. jam_awards CHECK 4값 1:1
data/JamAwardData.java advisor 직접(w-001로 계획) jam_awards 행 POJO + 표시조인 필드(gameName/thumbnailUrl/entrantName). scoreValue=BigDecimal
jam/RankScores.java advisor 직접 standardCompetitionRank(1,2,2,4) + rankScore (N-rank+1)/N. byOrder(정렬)+byScore(동점판정) 2비교자
mapper/JamAwardsMapper.java advisor 직접 insert/deleteByJamTrack/listByJam/listByJamWithGame. 일반 테이블 POJO 직접 alias, #{} only
mapper/JamReviewRatingMapper.java advisor 직접(w-003로 계획) USER_RATING — jam_entries LEFT JOIN game_review_stats. review_count>=3 + NULLS LAST. 집계 VIEW → AS "avgRating"/"reviewCount" 큰따옴표
jam/JamAwardService.java advisor 직접 3트랙 독립 랭크 + GRAND 정규화 가중합 + NULL/미달 제외 + 멱등 @Transactional recompute
controller/JamAwardAdminController.java w-004(code-writer) POST /admin/jams/{jamId}/awards/compute. 게이트→CSRF→404→F6 422→recompute→200 awardCounts
controller/JamAwardController.java w-005(code-writer) GET /jams/{slug}/results. 공개, byTrack 그룹핑, "jam-results" 뷰
WEB-INF/views/jam-results.jsp w-006(code-writer) GRAND+3트랙 랭킹 표. HtmlUtils.htmlEscape 전수. 미산정 "결과 준비 중"

수정 (기존 — 설계 근거 명시)

파일 worker 결과 요약 + 근거
controller/JamController.java w-007(code-writer) detail 모델 awardsSummary 추가 + 생성자 JamAwardsMapper 주입. AW-DETAIL(설계 §파일영향맵 W2-1 협의 수정). 기존 5핸들러·model 키 무변경(추가만)
WEB-INF/views/jam-detail.jsp w-008(code-writer) 출품작 섹션 앞 "수상 결과" 배지 + 전체 결과 링크. AW-DETAIL. awardsSummary 빈 가드 → 미산정 잼 회귀 0
test/BibimbapApplicationTests.java w-009(code-writer) JamAwardsMapper/JamReviewRatingMapper @MockBean 2건 추가(§30 contextLoads)
test/controller/JamControllerTest.java advisor 직접(계획외) JamController 6번째 생성자 인자(JamAwardsMapper) 추가로 인한 회귀 컴파일 깨짐 수정. @Mock jamAwardsMapper + 생성자 호출 1줄 갱신. AW-DETAIL 변경의 불가피한 동반 수정(아래 §설계와의 차이)

신규 테스트 (구현 산출물 — "(검증)" 태그여도 advisor 작성 의무)

파일 worker 결과 요약
test/jam/JamAwardServiceTest.java w-010(code-writer) 7 테스트: 3트랙 DESC rank·GRAND 정규화·1트랙 가용 포함·NULL weightedTotal 제외·empty·동점 competition·멱등 delete선행
test/controller/JamAwardAdminControllerTest.java w-011(code-writer) 7 테스트: 401/403/CSRF403/404/F6 422/CLOSED 200/eval종료 200
test/controller/JamAwardControllerTest.java w-012(code-writer) 5 테스트: missing/hidden redirect·빈 결과·트랙 그룹핑·익명 접근

Bash 단계 (advisor 직접)

  • ./mvnw -o test-compile (JAVA_HOME=openjdk@21) → EXIT=0 (1차 회귀 1건: JamControllerTest 구생성자 → 수정 후 GREEN)
  • javac -Xlint:all 신규/수정 main 9파일 (m2 jar 클래스패스) → W2-6 unused 변수/파라미터/import 0 (third-party tomcat-embed-core 소스 + 어노테이션프로세서 warning 4건만 — W2-6 코드 무관)
  • 집합 전수 AC self-audit grep (아래 §전수 체크)

설계와의 차이 (planned 12 → actual 9 worker, advisor 직접 6파일)

worker 전환 사유 (계량 근거): 의존 코어 3파일(JamAwardsMapper/RankScores/JamAwardService)은 상호 강결합 + GRAND 정규화 알고리즘(순위점수·가용트랙 가중평균·동점 competition)의 정밀도 비용이 커서 병렬 worker 분산 이득 < 정합 리스크 → advisor 직접. POJO(JamAwardData)·enum(AwardTrack)도 ≤50줄 사소 단일편집이라 직접. 합 6파일을 advisor 직접 작성, 나머지 9파일(컨트롤러2·JSP2·수정2·테스트3)을 code-writer 병렬 spawn.

  • advisor 직접 작성 파일: AwardTrack.java, JamAwardData.java, RankScores.java, JamAwardsMapper.java, JamReviewRatingMapper.java, JamAwardService.java + (계획외 회귀수정) JamControllerTest.java

JamVoteCountMapper 미생성 (설계 concern 해소): 설계 §파일영향맵은 "JamVoteCountMapper(POPULAR) — W2-5 JamVotesMapper 와 별개 or 재사용, 구현 시 협의"로 명시. 기존 JamVotesMapper.listCountsByJam(jamId)(W2-5)가 이미 [{gameId,voteCount}] GROUP BY count + 큰따옴표 alias + @MockBean 등록됨 → 재사용. 신규 매퍼 3→2, 신규 @MockBean 3→2. AC-T5 카운트는 2(설계 3가정에서 1 감소 — JamVotesMapper 기존 등록분이 POPULAR 소비 충당).

JamScoreStatsMapper.listStatsByJam 재사용(JUDGE): 설계 명시(W2-4 소유 재사용). 신규 0.

JamReviewRating Row POJO 미생성: 설계 시그니처는 JamReviewRatingRow 언급. 기존 집계 소비 패턴(JamScoreStatsMapper/JamVotesMapper)이 List<Map<String,Object>>이므로 동형 채택 → row POJO 0(inflate 방지, design concern 1 정합).

JamControllerTest 회귀 수정(계획외): AW-DETAIL 가 JamController 생성자에 6번째 인자(JamAwardsMapper)를 추가 → 기존 W2-1 테스트 컴파일 깨짐. 설계가 AW-DETAIL 을 "W2-1 협의 수정"으로 승인했으므로 그 동반 테스트 수정은 필수(테스트 작성/수정은 implementation 산출물). @Mock 1 + 생성자 호출 1줄만 변경, 기존 테스트 로직·단언 무변경.

설계 "검증 포인트" verbatim echo (verification-advisor 입력)

L레벨 매핑: 산정 알고리즘(3트랙·GRAND·NULL·동점·멱등) = L1(JamAwardServiceTest 단위) + 3트랙 join/정렬/NULL = L2(dev DB contract). 인가/게이트/F6 422 = L1+L3. 신규 컨트롤러·매퍼 의존 = full ./mvnw -o test 의무(§30).

  • VP-1 (AC-1~5 산정 코어, L1): JamAwardServiceTest — ① 3트랙 각 정렬·rank 정확(weightedTotal/avgRating/voteCount DESC) ② GRAND rankScore (N-rank+1)/N + 가용트랙 가중합 정확 ③ 1트랙만 가용 출품작이 GRAND 에 포함(분모=그 트랙 weight) ④ 모킹 소스로 결정적.
  • VP-2 (AC-6 NULL/미달, L1+L2): 리뷰 0개(avg_rating NULL) 출품작 → USER_RATING 트랙 제외(rank 없음). review_count==3 경계 포함, ==2 제외. 채점 0 출품작 JUDGE 제외. 득표 0 출품작 POPULAR rankScore 모집단 제외. GRAND 는 그래도 가용트랙으로 산정. dev DB contract 샘플 실측.
  • VP-3 (AC-5 동점, L1): weightedTotal 동일 2작품 → 같은 rank(competition 1,2,2,4), tiebreak game_id ASC 결정적. GRAND 동점도 동일. 재산정 시 같은 결과(멱등).
  • VP-4 (AC-8 멱등, L1+L2): recompute 2회 호출 → jam_awards 행이 중복 누적 아님(deleteByJamTrack 선행). UNIQUE(jam_id,track,game_id) 위반 없음. 부분 실패 시 트랜잭션 롤백(트랙 비는 상태 회피).
  • VP-5 (AC-7/9 게이트·F6, L1+L3): JamAwardAdminControllerTest — ADMIN 통과 / SUBADMIN+GAME_JAM_MANAGE 통과 / SUBADMIN 무키 403 / 미인증 401 / CLOSED 아님+eval진행중 422 / CSRF 누락 403(mapper 미호출). L3 스모크: CLOSED 잼 산정 → 결과 페이지 노출.
  • VP-6 (AC-12/13 alias·단방향, L2): JamReviewRatingMapper 반환 키 avgRating/reviewCount 정합(game_review_stats 큰따옴표 케이스폴딩 BUG-2 선례 회피, GameReviewStatsMapper.java:14 AS "avgRating" 동형). 산정 SQL 이 game_reviews/jam_scores/jam_votes 에 write 0(SELECT 만). 6축 컬럼 미참조.
  • VP-7 (contextLoads, L1): BibimbapApplicationTests 에 신규 3매퍼 @MockBean 등록 후 PASS(§30). 누락 시 NoSuchBeanDefinitionException. → 본 구현 정정: 신규 매퍼는 2개(JamAwardsMapper/JamReviewRatingMapper). POPULAR 는 기존 JamVotesMapper(@MockBean 기등록) 재사용 → 신규 @MockBean 2건. JamScoreStatsMapper 도 기존 @MockBean. contextLoads 보존.

설계 "집합 전수 체크 AC" verbatim echo + 적용 결과

  • AC-T1 시상 트랙 전수 4종 산정 경로 정합 불변식 — AwardTrack enum 멤버 수 == jam_awards_track_check CHECK IN 항목 수(W2-3 동결) == JamAwardService insert award_track 집합 == 4(JUDGE/USER_RATING/POPULAR/GRAND). → PASS: enum 멤버 grep 4. JamAwardService.recompute 가 insertTrack(JUDGE/USER_RATING/POPULAR) + insertGrand(GRAND) 4 산정 경로 전수 호출(AwardTrack.values() 루프로 delete, 명시적 4 insert 경로).
  • AC-T2 산정 소스 매퍼 전수 SELECT-only 단방향 불변식 — 산정 소스에 game_reviews/jam_scores/jam_votes 대상 INSERT/UPDATE/DELETE 0. → PASS: grep INSERT/UPDATE/DELETE (game_reviews|jam_scores|jam_votes) in JamReviewRatingMapper/JamAwardsMapper → none. JamAwardsMapper 는 jam_awards 대상 INSERT/DELETE 만(허용). JamScoreStatsMapper/JamVotesMapper.listCountsByJam 은 SELECT/기존(W2-4/5).
  • AC-T3 시상 신규 매퍼 ${ 0건 — 신규 매퍼 ${ 매치 0. → PASS: 기능적 ${...} 동적치환 0. (Javadoc 주석 텍스트 `${}` 0 2건은 문서 — 파라미터 바인딩은 전부 #{...}.)
  • AC-T4 집계 VIEW 소비 매퍼 alias 큰따옴표 전수 — JamReviewRatingMapper(game_review_stats)·JamScoreStatsMapper(jam_score_stats) camelCase alias 큰따옴표. → PASS: JamReviewRatingMapper AS "gameId"/AS "avgRating"/AS "reviewCount"(집계 VIEW 소비). JamScoreStatsMapper(W2-4) AS "weightedTotal" 기존. 일반 테이블 매퍼 JamAwardsMapper 는 반대로 비인용 직접 alias(scoreValue) — VIEW vs 테이블 구분 정합(혼용 아님). JamVotesMapper.listCountsByJam 도 큰따옴표("voteCount").
  • AC-T5 신규 시상 매퍼 @MockBean 전수 등록 — contextLoads PASS + 매퍼별 등록. → PASS(정정): 신규 매퍼 2개 모두 @MockBean 등록(JamAwardsMapper/JamReviewRatingMapper, line 104/107). POPULAR 소비 JamVotesMapper + JUDGE 소비 JamScoreStatsMapper 는 기존 @MockBean. test-compile GREEN.
  • AC-T6 jam_awards 단방향 소비 무결성(신규 DDL 0) — docs/ W2-6 신규 *-ddl.sql 0, db/schema.sql 변경 0. → PASS: git status docs/ db/ 신규 ddl/schema 0. jam_awards 는 W2-3 docs/jam-eval-ddl.sql 소유, W2-6 변형 0.

§33 인용 alias 적용 (3트랙 집계 매퍼) + 멱등 + NULL/임계 제외 + CLOSED 게이트 위치

  • §33 인용 alias:
    • JamReviewRatingMapper(game_review_stats 집계 VIEW, Map resultType) → AS "gameId"/AS "avgRating"/AS "reviewCount" 큰따옴표. (케이스폴딩 회피, GameReviewStatsMapper.java:13-15 선례 동형)
    • JamScoreStatsMapper(jam_score_stats 집계 VIEW, 기존 W2-4) → AS "weightedTotal" 기존.
    • JamVotesMapper.listCountsByJam(GROUP BY count, Map resultType, 기존 W2-5) → AS "gameId"/AS "voteCount" 기존.
    • JamAwardsMapper(일반 테이블, POJO 직접매핑) → 비인용 직접 alias(AS jamId/AS scoreValue). POJO 직접매핑은 비인용 OK(혼용 아님 — VIEW/Map 만 인용).
  • 멱등 보장 메커니즘: JamAwardService.recompute @Transactional 경계. 진입 시 for (AwardTrack t : values()) deleteByJamTrack(jamId, t.name()) 4트랙 전부 초기화 → 재INSERT. 두 번 돌려도 결과 동일, UNIQUE(jam_id,award_track,game_id) 위반 없음(매 트랙 선삭제). 부분 실패 시 단일 트랜잭션 롤백(트랙 비는 상태 회피).
  • NULL/임계 제외:
    • JUDGE — JamAwardService.trackPopulation 이 weightedTotal NULL(채점 0) 행 제외(score NULL → continue).
    • USER_RATING — JamReviewRatingMapper SQL WHERE st.review_count >= 3(F5 임계 미달 제외) + 리뷰 0개는 LEFT JOIN NULL → review_count NULL → 임계 제외. NULLS LAST.
    • POPULAR — JamVotesMapper GROUP BY 결과(득표>0 출품작만; 0표 행 없음).
    • GRAND — insertGrand 가 가용 트랙만 분자·분모 포함(weightSum 누적), 모집단=최소1트랙 union. 부당 0점 회피.
  • CLOSED(F6) 게이트 위치: JamAwardAdminController.computeAwards — getById 후 "CLOSED".equals(jam.getStatus()) || (evalEndAt!=null && now().isAfter(evalEndAt)) 아니면 422. (앱계층 런타임 게이트, DB CHECK 강제 아님 — 설계 concern 정합)

grounding 불일치 / concerns 처리 / 미해결

  • grounding 불일치 0: jam_awards 스키마(docs/jam-eval-ddl.sql §4 track CHECK 4값·rank>=1·ux UNIQUE·idx), jam_score_stats VIEW 컬럼(weighted_total), game_review_stats VIEW(avg_rating/review_count, line 174-201 AS "avg_rating"), jam_votes(W2-5 listCountsByJam), JamLifecycle/JamAdminController.requireJamManage 게이트 헬퍼, PermissionGate.has(2-arg), CsrfTokens, BibimbapApplicationTests @MockBean — 전부 실측 후 인용.
  • concerns 처리: design concerns 5건 전부 검토 — (1)dead parameter: javac -Xlint:all unused 0 확인(award/trackPopulation/RankScores 비교자 전부 사용). (2)신규 매퍼 @MockBean + full test: 2건 등록, test-compile GREEN(full test 는 verification 몫). (3)집계 VIEW alias 큰따옴표: JamReviewRatingMapper 적용. (4)임계 N=3/가중 1/3 상수: JamAwardService 상수로 위치(가변화 후속). (5)F6 422 + @Transactional 멱등: recompute @Transactional deleteByJamTrack 선행. → concerns: [] (반환 차단 사유 없음).
  • 미해결: 없음. (후속 비스코프: 자동 산정 스케줄러·잼별 가변 가중치 — 설계 비목표.)

Verification 을 위한 힌트

  • acceptance criteria: design.md "검증 포인트" VP-1~VP-7 + "집합 전수 체크 AC" AC-T1~AC-T6 참조.
  • 영향받는 테스트 파일(신규): JamAwardServiceTest, JamAwardAdminControllerTest, JamAwardControllerTest.
  • 회귀 위험 테스트(수정): JamControllerTest(생성자 6arg), BibimbapApplicationTests(@MockBean 2 추가).
  • full ./mvnw -o test 실행 시 baseline 171 + W2-6 신규(서비스 7 + admin 7 + public 5 = 19) GREEN 기대. JDK: /opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home (PATH 미등록 — JAVA_HOME export 필요).
  • L2(dev DB contract): JamReviewRatingMapper 3트랙 join·NULL·review_count>=3 경계·NULLS LAST 실측 권장(집계 VIEW alias 케이스폴딩).