Skip to content

추출 부분값을 INCOMPLETE 로 수용하고 유저 입력으로 완성한다 - #945

Merged
m-a-king merged 3 commits into
devfrom
feat/944-incomplete-partial-extraction
Aug 15, 2026
Merged

추출 부분값을 INCOMPLETE 로 수용하고 유저 입력으로 완성한다#945
m-a-king merged 3 commits into
devfrom
feat/944-incomplete-partial-extraction

Conversation

@m-a-king

@m-a-king m-a-king commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Situation

  • 파싱 실패 trace 하나를 들여다보다 시작됐다. 로그와 트레이스로 모집단을 세어보니 최근 30일 prod 이미지 등록 18건 중 16건이 실패했다(89%). 같은 기간 링크 등록은 87건 중 85건 성공이라 대비가 뚜렷했다.
  • 원인은 버그가 아니라 계약이었다. 아이템이 완성(READY)되려면 이름·가격·이미지 세 값이 다 있어야 하는데, 사용자가 물건을 직접 찍어 올린 사진에는 가격이 존재하지 않는다.
  • 성공한 2건은 둘 다 쇼핑몰 화면을 캡처한 스크린샷이었고, 실패한 것들은 직접 찍은 실물 사진과 가격 표기가 없는 제품컷이었다. 사실상 "화면 캡처만 통과하는" 기능이 되어 있었다.
  • 실패한 건들이 그 뒤 어떻게 됐는지도 DB 에 남아 있었다. 최근 실패 3건 중 1건은 사용자가 이름과 가격을 직접 입력해 살렸고(수기 수정 버전이 쌓여 있다), 나머지 2건은 그대로 버려졌다. 원해서 올린 아이템인데 파싱이 실패하는 바람에 전부 손으로 치게 만든 셈이다. 이번 변경이 있었다면 그 건은 이름·이미지가 채워진 상태로 도착해 가격만 넣으면 끝났다.
  • 하나라도 비면 등록 자체를 거부하는 것이 불친절하다고 판단했다.

Task

  • 채울 수 있는 만큼 채워 내려주고, 나머지는 사용자가 채우게 한다.
  • 덜 채워진 아이템이 완성된 것처럼 쓰이면 안 된다. 토너먼트에 출전하거나 가격이 빈 카드가 노출되는 일은 막아야 한다.
  • 파싱 기회는 늘리지 않는다(단번 유지). 재시도로 푸는 문제가 아니다.

Action

상태 모델

PENDING·PROCESSING 과 READY 사이에 INCOMPLETE 를 신설했다. 결과는 추출이 무엇을 건졌는지로만 갈린다.

추출이 얻은 것 상태 이후
이름·가격·이미지 전부 READY 바로 사용 가능
일부만 INCOMPLETE 사용자가 나머지를 채우면 READY
하나도 없음 FAILED 무엇을 채우라 할 근거조차 없어 실패로 끝낸다
  • 판정은 도메인이 쥔다. 전이 메서드가 확정된 상태를 반환하고, 서비스와 워커는 그 값으로 이벤트·로그·메트릭을 가른다. 이름이 실제 동작과 어긋나지 않도록 markReadymarkExtracted 로 바꿨다.
  • 완성 경로는 새로 만들지 않았다. 수기 수정(ItemSnapshot.manual)이 이미 "base 값에 입력을 병합해 필수 필드가 차면 READY 새 버전을 쌓는" 로직을 갖고 있어, INCOMPLETE 를 base 로 그대로 태웠다.
  • READY 취급은 그대로 막힌다. 완성 여부를 묻는 게이트가 isReady() 하나로 모여 있어, 토너먼트 출전 자격 검사는 코드 변경 없이 INCOMPLETE 를 걸러낸다.

알림

완료 알림을 재사용할지 새 타입을 팔지 저울질했다.

장점 단점
완료 알림 재사용 클라 변경 없음 "완료" 로 알리고 들어가면 빈 칸이라 사용자를 속인다
알림 없이 화면 갱신만 노이즈 없음 앱을 닫은 사용자는 미완성 아이템을 모른 채 지나간다
새 타입 (채택) 문구가 정확하고 딥링크 분기도 가능 클라가 새 type 을 처리해야 하고 템플릿 시드가 는다
  • 결정적이었던 것은 알림 템플릿이 타입당 하나뿐이라는 점이다. 한 타입 안에서 상태별로 문구를 가를 수 없다.
  • 문구는 제목이 아이템 이름, 본문이 "일부 정보만 찾았어요. 나머지는 직접 채워주세요." 다.
  • 화면 갱신(SSE)도 INCOMPLETE 를 함께 보낸다. 참여자 화면의 로딩바를 멈춰야 하는 건 완료·실패와 같다.

관측

  • 실패와 섞지 않는다. 메트릭 결과 라벨에 incomplete 를 더했다. 섞으면 "우리가 못 끝낸 것"과 "사용자가 마저 채울 것"이 한 숫자가 되어 실패율이 실제보다 나쁘게 보인다.
  • 못 채운 필드를 로그에 남긴다. 기존에는 실패 코드만 남아 이름이 없었는지 가격이 없었는지 사후 판별이 불가능했고, 이번 조사에서도 그게 걸림돌이었다.

Result

  • 사진 등록이 "가격이 찍힌 화면 캡처만 통과"에서 "무엇을 올리든 받는다"로 바뀐다. 조사 계기였던 실패 건(인형을 직접 찍어 올린 사진)은 이제 이름·이미지를 채운 INCOMPLETE 로 안착하고, 사용자가 가격만 넣으면 완성된다.
  • 검증에서 비자명한 부분: 알림 타입 추가가 컴파일과 테스트 두 겹으로 강제된다. 분류 파생 분기는 전수라 새 타입이 컴파일을 깨뜨리고, 푸시 정책 시드는 전 타입 망라를 런타임으로 확인한다. 실제로 이번에 두 곳 모두 빨간불이 떠서 분류를 명시하고 넘어갔다.
  • 배포 순서 주의: 이 PR 이 먼저다. 짝이 되는 extractor PR(추출 결과가 불완전해도 채운 만큼 반환하고 크롭 불가 포맷의 위장 업로드를 고친다 extractor#35)이 부분값을 200 으로 내려보내기 시작하는데, core 가 먼저 배포돼 있지 않으면 그 응답을 READY 불변식 위반으로 받아 FAILED 로 떨어뜨린다. 반대로 이 PR 만 먼저 나가는 것은 안전하다. extractor 가 아직 422 를 주므로 동작이 그대로다.
  • 후속: 계약 정본(infra contracts/extraction-api.md)이 아직 "세 필드 non-null 을 extractor 가 보장한다, 못 채우면 422" 로 되어 있어 별도 PR 로 고쳐야 한다.

연관 이슈

- prod 30일 이미지 등록 18건 중 16건이 실패(89%)했다. READY 불변식이 name·price·imageUrl 을 모두 요구해, 가격이 박혀 있지 않은 사진은 구조적으로 통과할 수 없었다 (성공한 2건은 모두 쇼핑몰 화면 스크린샷이었다)
- 도메인이 결과를 셋으로 가르게 했다: 세 필드를 다 얻으면 READY, 일부만 얻으면 INCOMPLETE, 하나도 못 얻으면 FAILED. markReady 를 markExtracted 로 바꿔 확정된 상태를 반환하고, 워커·서비스가 그 값으로 로그·메트릭·이벤트를 가른다
- INCOMPLETE 의 완성 경로는 기존 ItemSnapshot.manual 을 그대로 쓴다 — base 값에 입력을 병합해 세 필드가 차면 READY 새 버전을 쌓는 로직이 이미 있어 별도 경로를 만들지 않았다
- 알림은 완료 재사용 대신 ITEM_PARSING_INCOMPLETE 를 새로 팠다. 템플릿이 타입당 하나라 한 타입 안에서 문구를 가를 수 없고, "완료" 로 알리면 사용자가 빈 칸을 보고 들어가게 된다
- 메트릭 result 에 incomplete 를 더해 실패와 섞지 않는다. 섞으면 "우리가 못 끝낸 것"과 "사용자가 마저 채울 것"이 한 숫자가 되어 실패율이 실제보다 나쁘게 보인다
- 부분값 로그에는 missing 필드를 남긴다 — 무엇을 사용자에게 물어야 하는지가 이 결과의 핵심인데, 기존 로그(code 만 기록)로는 사후 판별이 불가능했다
- extractor 가 아직 부분값을 422 로 닫으므로 이 커밋만으로 동작이 바뀌지는 않는다. 부분값 200 반환은 extractor 후속이고, core 가 먼저 나가야 안전한 순서다
@m-a-king m-a-king added the feat 외부 가시적 새 기능 label Aug 14, 2026
@m-a-king m-a-king self-assigned this Aug 14, 2026
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review available on request

  • 🔍 Trigger review

Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment @coderabbitai review to review the latest changes. For a full review, comment @coderabbitai full review.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a331c31-628b-439c-98d8-fa0c296745be


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Discord 스레드 연동용 메타데이터입니다. discord-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Discord 알림 연동이 끊깁니다.

…rtial-extraction

# Conflicts:
#	src/main/kotlin/com/depromeet/piki/item/service/AsyncImageParsingWorker.kt
#	src/main/kotlin/com/depromeet/piki/item/service/AsyncItemParsingWorker.kt
@m-a-king
m-a-king merged commit 4458a74 into dev Aug 15, 2026
7 checks passed
@m-a-king
m-a-king deleted the feat/944-incomplete-partial-extraction branch August 15, 2026 13:35
m-a-king added a commit to TeamPiKi/client that referenced this pull request Aug 15, 2026
- 서버가 추출 결과를 일부만 채웠을 때 INCOMPLETE 를 내려준다 (TeamPiKi/core#945). 기존 코드는 FAILED·PENDING·PROCESSING 이 아니면 전부 정상 카드로 그려, 이름·가격이 빈 칸인 카드가 보이고 채우라는 유도가 없었다
- 위시 그리드에서 INCOMPLETE 를 FAILED 와 같은 편집 유도 카드로 보내되 문구만 "일부만 가져왔어요" 로 가른다
- name·price 를 nullable 로 바꾸면서 카드 컴포넌트도 nullable 을 받게 했다. INCOMPLETE 를 앞에서 걸러 실제로 빈 값이 정상 카드에 들어가지는 않는다
- 토너먼트: 담기 후보에서 INCOMPLETE 를 제외하고(서버가 출전을 막는다), 바스켓에서는 클릭 가능하게 둔다(값을 채워야 하므로)
m-a-king added a commit to TeamPiKi/client that referenced this pull request Aug 15, 2026
- 서버가 파싱이 일부만 끝난 경우 ITEM_PARSING_INCOMPLETE 알림과 status=INCOMPLETE SSE 를 보낸다 (TeamPiKi/core#945). 모르는 타입이라 switch default 로 빠져 딥링크가 동작하지 않았다
- 알림함·푸시·SSE 세 라우팅에 케이스를 더한다. 목적지는 기존 파싱 알림과 같다(위시 또는 토너먼트 담기 화면)
- SSE 토스트는 실패와 갱신 대상이 같아 케이스를 합치되 문구만 info 로 가른다 — 실패가 아니라 "채워 주세요" 안내라서다
kanghaeun added a commit to TeamPiKi/client that referenced this pull request Aug 15, 2026
* feat: 위시 아이템 INCOMPLETE 상태 대응

- 서버가 추출 결과를 일부만 채웠을 때 INCOMPLETE 를 내려준다 (TeamPiKi/core#945). 기존 코드는 FAILED·PENDING·PROCESSING 이 아니면 전부 정상 카드로 그려, 이름·가격이 빈 칸인 카드가 보이고 채우라는 유도가 없었다
- 위시 그리드에서 INCOMPLETE 를 FAILED 와 같은 편집 유도 카드로 보내되 문구만 "일부만 가져왔어요" 로 가른다
- name·price 를 nullable 로 바꾸면서 카드 컴포넌트도 nullable 을 받게 했다. INCOMPLETE 를 앞에서 걸러 실제로 빈 값이 정상 카드에 들어가지는 않는다
- 토너먼트: 담기 후보에서 INCOMPLETE 를 제외하고(서버가 출전을 막는다), 바스켓에서는 클릭 가능하게 둔다(값을 채워야 하므로)

* feat: 토너먼트 아이템 상세에 incomplete 상태 처리 추가

* refactor: 리터럴/상수로 섞어 쓰던 곳을 ITEM_STATUS 로 통일

* feat: 토너먼트 시작 차단에 INCOMPLETE 포함

* feat: INCOMPLETE 를 수정 화면으로 링크

* chore: 불필요한 파싱 상태 주석 정리

---------

Co-authored-by: kanghaeun <xgkg0330@jnu.ac.kr>
kanghaeun pushed a commit to TeamPiKi/client that referenced this pull request Aug 15, 2026
- 서버가 파싱이 일부만 끝난 경우 ITEM_PARSING_INCOMPLETE 알림과 status=INCOMPLETE SSE 를 보낸다 (TeamPiKi/core#945). 모르는 타입이라 switch default 로 빠져 딥링크가 동작하지 않았다
- 알림함·푸시·SSE 세 라우팅에 케이스를 더한다. 목적지는 기존 파싱 알림과 같다(위시 또는 토너먼트 담기 화면)
- SSE 토스트는 실패와 갱신 대상이 같아 케이스를 합치되 문구만 info 로 가른다 — 실패가 아니라 "채워 주세요" 안내라서다
kanghaeun added a commit to TeamPiKi/client that referenced this pull request Aug 15, 2026
* feat: INCOMPLETE 파싱 알림 타입·SSE 상태 대응

- 서버가 파싱이 일부만 끝난 경우 ITEM_PARSING_INCOMPLETE 알림과 status=INCOMPLETE SSE 를 보낸다 (TeamPiKi/core#945). 모르는 타입이라 switch default 로 빠져 딥링크가 동작하지 않았다
- 알림함·푸시·SSE 세 라우팅에 케이스를 더한다. 목적지는 기존 파싱 알림과 같다(위시 또는 토너먼트 담기 화면)
- SSE 토스트는 실패와 갱신 대상이 같아 케이스를 합치되 문구만 info 로 가른다 — 실패가 아니라 "채워 주세요" 안내라서다

* chore: 불필요한 파싱 상태 주석 정리

---------

Co-authored-by: kanghaeun <xgkg0330@jnu.ac.kr>
Co-authored-by: kanghaeun <145974230+kanghaeun@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 외부 가시적 새 기능

Projects

None yet

Development

Successfully merging this pull request may close these issues.

추출 부분값을 INCOMPLETE 상태로 수용하고 유저 입력으로 완성한다

1 participant