AI 분석 난이도 컨텍스트 + 구조화 파싱 — difficulty/level 주입 + Problem 구조화 필드

요약Sprint 248 AI 컨텍스트 풍부화 2차 확장. Wave C: Submission entity에 difficulty/level 컬럼 추가(TypeORM 마이그레이션) → 제출 생성 시 Problem Service에서 복사 저장 → ai-analysis _build_difficulty_context()로 난이도별 루브릭 보정 주입. Wave D: Problem entity에 inputDescription/outputDescription/constraints/examples 구조화 필드 추가 + TypeORM 마이그레이션 → Programmers 크롤러 구조화 파싱 확장(입출력 예시 테이블·제약조건 분리) → ai-analysis <structured_content> 블록 주입. Wave C PR #464 머지 커밋 6882dc7 / Wave D PR #465 커밋 b5b1fbb+87ee8bb. Submission 388 passed·98.69% / AI Analysis 379 passed·98.89% / Problem 213 passed·96.79%. Critic 게이트: Wave D P2 example 셀 sanitize 누락 → 수정 후 CLEAN.

날짜
영향도높음

목표

  • Wave C: AI 분석 프롬프트에 난이도(difficulty/level) 컨텍스트를 주입해 브론즈·플래티넘 기대치 차이를 루브릭에 반영한다.
  • Wave D: Programmers 크롤러를 확장해 입출력 예시·제약조건을 구조화 필드로 분리 저장하고, AI 프롬프트에 <structured_content> 블록으로 주입한다.
  • Sprint 248에서 문제 제목·설명만 주입하던 한계를 극복해 AI가 문제의 전체 스펙을 참조할 수 있도록 한다.

배경

  • Sprint 248에서 그룹 분석 문제 컨텍스트 주입(Wave B)과 Programmers 크롤러(Wave A)를 완료했으나, 두 가지 한계가 남았다.
    1. 난이도 컨텍스트 부재: AI가 브론즈 난이도 코드와 플래티넘 난이도 코드를 동일 기준으로 평가함 — 루브릭 보정 불가.
    2. description 전문(全文) 주입: 크롤링된 원문 HTML을 통째로 주입 — 입출력 예시와 제약조건이 자연어 설명에 뭉쳐 AI 파싱 비용 증가.
  • Submission entity에 difficulty/level 필드가 이미 존재한다는 가정이 있었으나, Wave C 착수 전 실측 확인 → 실제로 없었음 (마이그레이션 필요).

결정

D1. 난이도 컨텍스트 — Submission 복사 저장 방식

  • Problem entity의 difficulty/level을 AI 분석 요청 시마다 조회하는 대신, 제출 생성 시점에 Submission에 복사 저장하는 방식을 선택.
  • 이유: 분석 요청 시 Problem Service 추가 호출을 피하고, 제출 당시 난이도를 스냅샷으로 보존.
  • Submission 마이그레이션: difficulty VARCHAR(20) NULL, level INT NULL 컬럼 추가.
  • ai-analysis _build_difficulty_context(): 레벨과 난이도에 따라 루브릭 보정 문구 생성 (브론즈·실버·골드·플래티넘·다이아 5단계 + Programmers 레벨 5단계).

D2. 구조화 파싱 — Problem entity 별도 필드 + 크롤러 확장

  • Problem entity에 inputDescription TEXT NULL, outputDescription TEXT NULL, constraints TEXT NULL, examples TEXT NULL 4개 구조화 필드 추가.
  • Programmers 크롤러(crawler.service.ts) 확장: div.markdown 파싱 시 입출력 예시 테이블(.example-io) + 제약조건(<h5>)을 별도 파싱 → 구조화 필드에 저장.
  • ai-analysis <structured_content> 블록: 구조화 필드가 있을 때만 <input_spec>, <output_spec>, <constraints>, <examples> 서브블록 주입 — ADR-030 S-5 프롬프트 인젝션 방어 패턴 재활용.

D3. Critic P2 — example 셀 sanitize 적용

  • _format_examples() 함수가 입출력 예시 테이블 셀 값을 _sanitize_problem_field() 없이 직접 주입 → <problem_context> 태그 포함 시 프롬프트 경계 파괴 위험.
  • 수정: 헤더 + 모든 셀 값에 _sanitize_problem_field() 적용.
  • 테스트 2건 추가: 일반 sanitize + 태그 포함 악성 셀 차단.

구현

Wave C — difficulty/level 컨텍스트 주입 (ai-analysis + submission, Oracle 직접)

파일변경 내용
submission/src/submission/submission.entity.tsdifficulty/level nullable 컬럼 추가
submission/src/database/migrations/…AddDifficultyLevel.tsTypeORM 마이그레이션
submission/src/submission/submission.service.tscreate() 시 ProblemServiceClient 응답에서 difficulty/level 저장
ai-analysis/src/prompt.py_build_difficulty_context() 신설 + build_user_prompt() 파라미터 추가
ai-analysis/src/worker.pydifficulty/level 메시지 필드 추출 → analyze_code() 전달
ai-analysis/tests/테스트 업데이트
  • PR #464 squash merge → 6882dc7 (2026-07-15)
  • Submission: 388 passed · Branches 98.69% ≥ 98%
  • AI Analysis: 359 passed · TOTAL 99%+
  • 보안 패치 동봉: multer CVE-2026-5079 — submission 서비스 2.1.1 → 2.2.0 (Sprint 248에서 problem만 패치, submission 누락 수정)

Wave D — 구조화 파싱 + AI 주입 (problem + ai-analysis, Oracle 직접)

파일변경 내용
problem/src/problem/problem.entity.tsinputDescription/outputDescription/constraints/examples 4 필드 추가
problem/src/database/migrations/…AddStructuredContentToProblems.tsTypeORM 마이그레이션
problem/src/crawler/crawler.service.ts구조화 파싱 확장 (입출력 예시 테이블·제약조건 분리)
ai-analysis/src/prompt.py_format_examples() + <structured_content> 블록 (입출력·제약조건)
ai-analysis/src/worker.pystructured content 필드 추출 + analyze_code() 전달
ai-analysis/src/claude_client.py구조화 데이터 파라미터 추가
ai-analysis/src/main.pyProblem Service 조회 시 structured fields 전달
테스트 다수ai-analysis 138줄↑ + problem 163줄↑ 신규 케이스
  • Wave D 커밋: b5b1fbb (Wave D 본체) · ac70dbd (ruff 자동 정렬) · 87ee8bb (Critic P2 sanitize 수정)
  • AI Analysis: 379 passed · TOTAL 98.89% ≥ 98%
  • Problem: 213 passed · Branches 96.79% ≥ 96% · Functions 98.61% ≥ 98%
  • ESLint: Errors 0

Critic 게이트 (Wave D)

등급내용수정
P2 example 셀 sanitize 누락_format_examples() — 셀 값에 _sanitize_problem_field() 미적용 → 악성 태그로 프롬프트 경계 파괴 가능헤더·셀 전체 sanitize 적용 + 테스트 2건 추가 (87ee8bb)
  • Wave C Critic 게이트: CLEAN (Findings 0건)
  • Wave D Critic 게이트: P2 1건 수정 후 CLEAN

검증

항목Wave CWave D
CI✅ 38/38 PASS✅ 38/38 PASS
Submission388 passed · 98.69%
AI Analysis359 passed379 passed · 98.89%
Problem213 passed · Branches 96.79%
Critic✅ CLEAN✅ CLEAN (P2 수정 후)
PR#464 → 6882dc7#465 → b5b1fbb+87ee8bb