Skip to content

feat: 피드백 마크다운 리포트 저장·프록시·열람 (reportS3Key 구현) - #214

Merged
Jaeho-Site merged 4 commits into
devfrom
feat/feedback-report
Aug 23, 2026
Merged

feat: 피드백 마크다운 리포트 저장·프록시·열람 (reportS3Key 구현)#214
Jaeho-Site merged 4 commits into
devfrom
feat/feedback-report

Conversation

@Jaeho-Site

Copy link
Copy Markdown
Contributor

개요

callback.feedback.reportS3Key 가 계약(예시 JSON·storage.md 키 예약·Core 저장 컬럼·응답 노출)만 있고 AI 가 무조건 None 을 보내던 gap 을 풀스택으로 닫는다.

  • ai/: chain/feedback_report.py — LLM 미호출 결정론 렌더러(점수 표·패널·요약·키워드·학습 플랜·하이라이트·답변별 코칭·음성 요약 → GFM 문서). FeedbackConsumer._save_reportfeedback/{session_id}/report.md 에 저장 후 키 동봉. 실패는 전부 삼키고 None 폴백 — 부가 산출물이 공용 실패 가드를 타고 피드백 전체를 FAILED 로 승격시키지 않게. 사용자 답변 원문은 리포트에 싣지 않는다.
  • backend/: 소유자 전용 프록시 GET /api/sessions/{id}/feedback/report(text/markdown — presigned 는 내부 MinIO 호스트라 Core 중계, 분석 원문 프록시와 동일 패턴). 키 부재·객체 소실은 422 FEEDBACK_REPORT_NOT_AVAILABLE(S3 NoSuchKey 를 OBJECT_NOT_FOUND 로 분리해 503 오인 방지). 공개 공유 응답(fromPublic)에서 reportFilePath 제거 — 내부 키 비인증 노출 차단. 마이그레이션 없음(report_file_path 는 V1 부터 존재).
  • frontend/: FeedbackAiReport — reportFilePath 있을 때만 노출되는 열람 토글 + Markdown(lazy+sanitize) 렌더 + .md 다운로드. 신규 의존성 0(A5 렌더러 재사용, 메인 청크 사실상 불변).
  • docs: messaging §5.11 reportS3Key 정의, api-conventions 엔드포인트·에러 카탈로그, storage/environment/frontend-types 동기화.

검증

  • ai uv run pytest: 393 passed (+6) · black 클린
  • backend ./gradlew test: 251 passed (+5) — 로컬 Docker 부재 Testcontainers 4건 외 실패 0
  • frontend: 178 passed (+3) · build·lint 클린
  • 렌더 실물 샘플로 계약 확인(사용자 답변 미포함·다행 질문 헤딩 접기·안정 질문 번호)

리뷰 반영

frontend-reviewer W1/S1 반영(재생성 시 리포트 캐시 무효화 — 쿼리키를 detail 계층 하위로, 다운로드 앵커 DOM append). 코드리뷰 10건 중 8건 반영: 키 조립 try 안으로(잘못된 템플릿이 피드백 FAILED 를 만들지 않게), 종료 사유 라벨 누락(DURATION_EXCEEDED), dangling key 503→422, 회사명(사용자 입력) 개행 접기, 다행 질문 헤딩, 키 템플릿 settings SSOT 화, 리포트 컴포넌트 memo, 질문 번호 안정화. 이관 2건(attempt 경합 파일-행 불일치 low, 프록시 소비 레시피 중복 N=2)은 후속 To-do 로 기록.

- chain/feedback_report.py: LLM 미호출 결정론 렌더러 — 점수 표·패널·요약·
  키워드·학습 플랜·하이라이트·답변별 코칭·음성 요약을 GFM 문서로 조립.
  사용자 답변 원문은 미포함(사용자 입력은 마크다운으로 렌더하지 않는 규칙)
- FeedbackConsumer._save_report: feedback/{session_id}/report.md 저장 후
  콜백에 키 동봉. 실패는 전부 삼키고 None 폴백 — 부가 산출물이 공용 가드를
  타고 피드백 전체를 FAILED 로 승격시키지 않게 (키 조립도 try 안)
- 키 템플릿 SSOT 는 settings(FEEDBACK_REPORT_MD_KEY_TEMPLATE) — consumer
  기본값 없음, storage 와 함께 주입될 때만 동작
- 종료 사유 라벨에 DURATION_EXCEEDED 추가, 직군 라벨은 _DOMAIN_KO 재사용,
  다행 질문 헤딩 한 줄 접기, 질문 번호는 전사 내 질문 순서 기준(코칭 일부
  실패에도 안정), 회사명(사용자 입력)은 개행 접기로 구조 주입 차단
- 테스트 +6 (렌더러 3, consumer 저장/실패 폴백/미주입 3)
- GET /api/sessions/{id}/feedback/report: AI 가 저장한 마크다운 리포트를
  소유자 전용으로 중계 (분석 원문 /content 프록시와 동일 패턴 —
  presigned 는 내부 MinIO 호스트라 브라우저 접근 불가)
- 키 부재(AI 저장 실패 폴백·구버전)는 422 FEEDBACK_REPORT_NOT_AVAILABLE 로
  '생성 중'과 구분. 키만 남고 객체가 사라진 정합 붕괴도 같은 422 로 수렴 —
  S3 클라이언트가 NoSuchKey 를 OBJECT_NOT_FOUND 로 분리해 503(인프라 장애)
  오인을 방지
- FeedbackResponse.fromPublic 에서 reportFilePath 제거 — 내부 스토리지 키를
  비인증 공유 응답에 노출하지 않는다 (shareToken 제거와 같은 원칙)
- report_file_path 는 V1 부터 존재 — 마이그레이션 없음. 테스트 +5
- FeedbackAiReport: reportFilePath 가 있을 때만 노출되는 열람 토글 —
  Core 프록시(GET /feedback/report)로 마크다운을 받아 shared/ui/Markdown
  (lazy+sanitize)으로 렌더, .md 다운로드 제공. 신규 의존성 0
- 열람 요청 시에만 fetch(분석 원문 보기와 동일), PDF 캡처 영역 바깥 —
  리포트 파일은 자체 산출물이라 중복 캡처하지 않는다
- 쿼리키를 feedbackKeys.detail 하위로 — 재생성 resetQueries(prefix)가
  리포트 캐시도 함께 비운다. 부모 상태 변화마다 재파싱하지 않게 memo
- openapi 재생성(getSessionFeedbackReport). 테스트 +3
- messaging.md §5.11: reportS3Key 정의 — GFM 리포트 키, null 허용(저장 실패
  폴백), FAILED 콜백에서는 항상 null, Core 저장·프록시 경로
- api-conventions.md: GET /feedback/report 엔드포인트 + 422
  FEEDBACK_REPORT_NOT_AVAILABLE 에러 카탈로그
- storage.md §2: feedback report 키 주석(구현됨), environment.md:
  FEEDBACK_REPORT_MD_KEY_TEMPLATE, frontend-types.md §6.5: GFM 필드에
  리포트 본문 추가
@Jaeho-Site
Jaeho-Site merged commit 5361f5e into dev Aug 23, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant