칼론
경력근거: KOMOJU와 PayVerse를 사용하는 결제 및 주문 흐름을 개발하고 안정적으로 운영합니다
※ 실제 산출물이 아닌 AI 예측 시안입니다.
이 제품이 보여주는 것은 화면에서 API·데이터베이스·결제 대조·배포까지 잇는 풀스택 구현(TypeScript·Node.js·Python·PostgreSQL·Redis·DynamoDB·AWS)과 사람의 선택이 상태 전이를 여는 운영 워크플로 설계이며, 스포츠 유니폼 제작 공정이나 일본 이커머스 도메인 자체의 전문성은 주장하지 않습니다. 다만 이 화면이 말이 되려면 칼론이라는 맥락, 즉 ILB-MAX 로 10,000팀 이상의 제작을 안내해 온 커스텀 유니폼 이커머스와 야구·축구·배구마다 등번호·사이즈·옵션 규칙이 다른 제품, 그리고 KOMOJU·PayVerse 로 결제가 흐르는 주문 흐름이 있어야 합니다. 공고가 말하는 '고객용 웹 화면과 내부 운영에 필요한 관리 기능' 과 '반복되는 운영 업무를 발견해 자동화' 가 정확히 이 화면이 서는 자리입니다. 실제 원단·프린팅 공정이나 일본 결제·세금 규정의 깊이는 도메인 담당자와의 협업이 있어야 하고, 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
이 팀은 Claude Code 와 OpenAI Codex 를 일상 개발에 씁니다. 도구가 낸 결과를 그대로 넣지 않고 동작·영향·예외를 스스로 확인해 검증하는 사람을 찾으므로, 반복 확인 작업을 규칙으로 고정해 사람이 답할 질문만 남기는 실전 도구가 보여줄 대상입니다.
이 팀의 풀스택은 문제를 한 기술 영역에 가두지 않고 화면에서 API 로, API 에서 데이터베이스와 결제 시스템으로, 다시 배포와 운영 환경까지 이어가며 해결하는 개발자를 뜻합니다. 확인 처리대는 사람의 선택이 데이터와 주문 상태를 바꾸는 구간까지 한 사람이 잇는 구현이 필요합니다.
이미 실제 고객과 주문이 흐르는 서비스를 안정적으로 운영하면서, 반복되는 운영 업무를 발견해 자동화하거나 더 효율적인 흐름으로 개선하는 역할을 기대합니다. 메신저와 메모지로 흩어져 있던 주문 확인 요청을 화면 하나에 모아 상태로 관리하는 내부 운영 기능이 그 역할과 정확히 맞닿습니다.
칼론 운영팀 담당자가 주문 하나를 열면, 규칙 엔진이 팀 로스터(uniform_order_line)를 상품·옵션 마스터(option_price_master)와 대조해 같은 팀 등번호 중복, 사이즈 누락, 이 종목에 없는 옵션, 마스터에 없는 미등록 옵션, 등번호 상한 초과를 선수 행마다 확인 질문으로 만듭니다. 질문 카드에는 어느 행·어느 필드가 근거인지와 함께, 마스터와 같은 팀 다른 행에서 규칙으로 뽑은 후보 답이 붙습니다. 담당자가 카드에서 답을 고르거나 값을 직접 채우면 오른쪽 로스터 표의 그 셀이 바뀌고 결제 차액이 다시 계산되며, 규칙으로 후보를 만들 수 없는 질문은 고객 문의로 넘겨 회신 문안 초안에 담깁니다. 미해결 질문이 0이 될 때만 주문 상태가 제작 대기로 넘어가고, 화면을 건너뛰고 확정 API 를 직접 불러도 같은 규칙이 거절합니다. LLM 은 일본어 자유서술 메모를 후보 답 문장으로 바꾸는 변환과 회신 문안 초안만 맡고, 질문 생성과 상태 전이는 규칙표와 외래키·금액 재계산으로 고정합니다.
칼론 운영팀 담당자입니다. ILB-MAX 로 들어온 일본 고객의 팀 유니폼 주문서를 선수 한 명씩 눈으로 훑으며 등번호가 겹치지 않는지, 사이즈가 비어 있지 않은지, 고른 옵션이 이 종목에 실제로 있는지 확인하고, 애매한 건 메신저와 메모지에 적어 두었다가 고객 회신을 기다리는 동안 어느 주문이 무엇 때문에 멈춰 있는지 놓쳐 제작에 잘못 넘어가는 걸, 질문과 답을 한 화면에 모으고 답이 다 채워지기 전에는 제작 이관 자체가 열리지 않게 만들고 싶은 사람입니다.
주문의 결함을 읽고 마는 리포트가 아니라 사람이 답해야 닫히는 질문으로 바꾸면, (a) 규칙 엔진이 같은 주문·같은 마스터에서 항상 같은 질문 목록과 같은 후보 답을 만들고, (b) 담당자가 화면에서 고른 답이 로스터·결제 차액·주문 상태에 그대로 반영되며, (c) 미해결 질문이 남은 주문은 화면 버튼으로든 확정 API 직접 호출로든 제작으로 넘어가지 못해, 운영팀이 확인 대기 중인 주문을 놓치지 않는 실제 운영 화면이 된다는 걸 보여줍니다.
주문 하나를 열면 규칙 엔진이 로스터를 옵션 마스터와 대조해 등번호 중복·사이즈 누락·종목 불일치 옵션·미등록 옵션·등번호 상한 초과를 선수 행마다 확인 질문으로 만들고, 각 질문에 마스터와 같은 팀 다른 행에서 뽑은 후보 답을 붙입니다. 담당자가 질문 카드에서 답을 고르면 옆 로스터 표의 셀이 바뀌고 결제 차액이 다시 계산되며, 미해결 질문이 0이 될 때만 제작 이관이 열리는 화면이 동작합니다. 1주차 입력은 이미 필드로 나뉜 주문서를 가정하고, 일본어 자유서술 메모를 후보 답으로 바꾸는 변환은 2주차로 분리합니다.
고객이 자유서술로 남긴 일본어 디자인·주문 메모를 LLM 이 질문 카드의 후보 답 문장으로 바꿔 담당자가 고를 수 있게 붙이고, 규칙으로도 메모로도 후보를 만들 수 없는 질문은 고객 문의로 넘겨 미해결 질문만 모은 일본어 회신 문안 초안을 만들며, 운영 담당 페르소나 2명이 후보 답이 원문과 맞는지 라벨링해 일치도를 대조하는 단계입니다.
확인 처리대를 Amazon EKS 에 배포하고 업로드된 주문서와 디자인 시안을 Amazon S3 에 두며, 담당자가 확정한 시점의 재계산 합계를 KOMOJU·PayVerse 결제 캡처 금액과 대조해 차액이 남은 주문만 별도 목록으로 올리는 단계입니다.
등번호 중복·사이즈 누락·미등록 옵션을 일부러 섞은 팀 주문 30건을 열면, 질문마다 어느 행·어느 필드가 근거인지와 마스터에서 뽑은 후보 답이 함께 뜨는 걸 시연합니다
같은 주문·같은 마스터면 질문 목록과 후보 답이 항상 똑같이 나오고, 같은 선택 시퀀스를 재생하면 최종 로스터·결제 차액·주문 상태가 항상 같은 값으로 끝나는지 자동 대조합니다
미해결 질문이 남은 주문 30건에 대해 화면 버튼과 확정 API 직접 호출을 모두 시도해 두 경로 다 서버 규칙으로 거절되는 걸 보여주고, 답을 다 채운 주문은 같은 호출이 통과하는 것도 함께 대조합니다
2주차에 운영 담당 페르소나 2명이 후보 답 타당성을 라벨링해 일치도(Cohen's κ, 두 사람 답이 우연 이상으로 겹치는 정도) 하한 0.6 을 목표로 두고, 자유서술 메모에서 만든 후보 중 마스터에 없는 값을 지어낸 비율을 함께 셉니다
위 수치는 전부 작성자가 만든 더미데이터 자가채점이며, 실제 질문 정확도와 운영 담당이 체감하는 확인 대기 단축은 칼론 실 주문·결제 로그와 실 운영 담당이 있어야 측정 가능합니다. 이 제품은 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다
이건 사용자·실데이터 없이 혼자 만든 포트폴리오라, 수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서 '데모 동작 + 실검증 준비 상태' 로 솔직히 설명합니다
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 등번호 중복·마스터에 없는 옵션·단종 옵션·경계값(등번호 0·상한)·사이즈 누락·표기 흔들림을 더미에 일부러 섞어, 쉬운 주문만 통과시키는 게 아님을 보여줍니다
이 포트폴리오가 보여주는 것은 풀스택 제품 구현과 사람의 선택이 상태를 여는 운영 워크플로 설계 기술이지, 스포츠 유니폼 제작 공정이나 일본 이커머스 도메인 자체의 전문성이 아닙니다. 실제 원단·프린팅 공정이나 일본 결제·세금 규정의 깊이는 도메인 담당자 협업이 있어야 하고, 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다
실제 도입 시 달라지는 것을 명시합니다. 칼론 실제 옵션 마스터·가격표·종목 규칙은 더미와 다르므로 규칙표와 기본가·추가가를 실데이터로 다시 맞춰야 하고, 화면을 쓰는 사람도 실 운영 담당으로 교체해야 합니다(도입 단계 과제로 분리합니다)
질문을 너무 잘게 쪼개면 담당자가 답해야 할 카드가 늘어 오히려 손이 더 갑니다. 같은 선수 행에서 나온 질문은 한 카드로 묶고 제작에 지장이 없는 항목은 blocking 에서 빼며, 주문 1건당 답해야 하는 질문 수 자체를 지표로 셉니다. 자유서술 메모가 모호하면 후보 답을 지어내지 않고 고객 문의 카드로 넘깁니다
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
종목별 유니폼 옵션과 추가가·등번호 상한·판매 상태의 마스터이며, 확인 질문의 판정 기준이자 후보 답을 뽑아 오는 출처입니다. 단종 옵션·추가가 결측·옵션명 별칭 표기·등번호 상한 경계를 일부러 섞어, 없는 옵션을 정상으로 보거나 단종 옵션을 후보로 내놓지 않는지 보여줍니다.
| option_code | option_name_raw | sport | add_price_jpy | max_back_number | status |
|---|---|---|---|---|---|
| OPT-BB-STD | 야구 기본 | baseball | 0 | 99 | active |
| OPT-BB-PRO | 야구 프로 | baseball | 1,500 | 99 | active |
| OPT-BB-EMB | 야구 자수 | baseball | — | 99 | active |
| OPT-SC-STD | 축구 기본 | soccer | 0 | 99 | active |
| OPT-SC-PRO | 축구 프로 | soccer | 1,200 | 99 | active |
아래 JSON Schema 에 맞는 종목별 유니폼 옵션 마스터 30행을 JSON 배열로 생성합니다. 야구·축구·배구 옵션과 추가가·등번호 상한을 고루 섞습니다.
{"type":"object","properties":{"option_code":{"type":"string"},"option_name_raw":{"type":"string"},"sport":{"type":"string","enum":["baseball","soccer","volleyball"]},"add_price_jpy":{"type":["integer","null"]},"max_back_number":{"type":"integer"},"status":{"type":"string","enum":["active","discontinued"]}},"required":["option_code","option_name_raw","sport","max_back_number","status"]}
쉬운 데이터만 만들지 않습니다. 하드 케이스를 의도적으로 섞습니다: (1) add_price_jpy 가 null 인 추가가 미입력 2~3건, (2) status=discontinued 인 단종 옵션 2~3건, (3) option_name_raw 가 한글·일본어·괄호 별칭(배구 기본·バレー基本)이라 정규화가 필요한 표기 흔들림 3~4건, (4) max_back_number 가 199 처럼 표준 99 를 벗어난 확장 옵션 1~2건, (5) add_price_jpy=0 인 경계 옵션 여러 건. JSON 배열만 출력합니다.{"type":"object","properties":{"option_code":{"type":"string"},"option_name_raw":{"type":"string"},"sport":{"type":"string","enum":["baseball","soccer","volleyball"]},"add_price_jpy":{"type":["integer","null"]},"max_back_number":{"type":"integer"},"status":{"type":"string","enum":["active","discontinued"]}},"required":["option_code","option_name_raw","sport","max_back_number","status"]}팀 단위 유니폼 주문서의 선수별 로스터 한 행이며 확인 질문이 가리키는 대상입니다. option_price_master 와 대조해 같은 팀 등번호 중복·사이즈 누락·종목 불일치 옵션·미등록 옵션·등번호 상한 초과·단가 결측을 질문으로 뽑고, 담당자가 화면에서 고른 답이 이 표의 셀에 그대로 반영됩니다. 등번호 중복·마스터에 없는 옵션·팀명 별칭·사이즈 누락을 섞어, 정상 주문만 통과시키는 게 아님을 보여줍니다.
| order_id | team_name_raw | team_id | player_name | back_number | sport | size_raw | size | option_code | unit_price_jpy | name_print_raw |
|---|---|---|---|---|---|---|---|---|---|---|
| O-1001 | 横浜ブルーウィングス | T-01 | 田中 | 10 | baseball | L | L | OPT-BB-STD | 8,800 | TANAKA |
| O-1001 | 横浜ブルーウィングス | T-01 | 佐藤 | 7 | baseball | M | M | OPT-BB-PRO | 10,300 | SATO |
| O-1001 | 横浜ブルーウィングス | T-01 | 鈴木 | 10 | baseball | — | OPT-BB-STD | 8,800 | SUZUKI | |
| O-1002 | 大阪FC | T-02 | 山本 | 9 | soccer | XL | XL | OPT-SC-PRO | 9,600 | YAMAMOTO |
| O-1002 | 大阪FC | T-02 | 中村 | 99 | soccer | 3XL | 3XL | OPT-SC-PRO | 9,600 | NAKAMURA |
아래 JSON Schema 에 맞는 팀 유니폼 주문 로스터 30행을 JSON 배열로 생성합니다. 한 order_id 는 한 팀의 여러 선수 행을 갖고 sport 는 그 팀 전체가 같습니다.
{"type":"object","properties":{"order_id":{"type":"string"},"team_name_raw":{"type":"string"},"team_id":{"type":["string","null"]},"player_name":{"type":"string"},"back_number":{"type":["integer","null"]},"sport":{"type":"string","enum":["baseball","soccer","volleyball"]},"size_raw":{"type":"string"},"size":{"type":["string","null"],"enum":["XS","S","M","L","XL","2XL","3XL",null]},"option_code":{"type":["string","null"]},"unit_price_jpy":{"type":["integer","null"]},"name_print_raw":{"type":"string"}},"required":["order_id","team_name_raw","player_name","sport","size_raw","option_code"]}
쉬운 데이터만 만들지 않습니다. 하드 케이스를 의도적으로 섞습니다: (1) 같은 order_id 안에서 back_number 가 겹치는 등번호 중복 2~3건, (2) size 가 null 인 사이즈 미입력 2~3건, (3) option_code 가 option_price_master 에 없는 미등록 참조(예: OPT-XX-999) 2건, (4) 옵션의 sport 와 주문 행의 sport 가 다르거나 단종 옵션을 고른 종목 불일치 2건, (5) back_number 가 옵션 상한(99)을 넘는 100 이상이거나 0 인 경계값 2건, (6) team_name_raw 가 같은 팀의 별칭·일본어 표기(コウベVC)라 team_id 가 null 인 미매핑 2건, (7) unit_price_jpy 가 null 인 단가 미입력 1~2건. JSON 배열만 출력합니다.{"type":"object","properties":{"order_id":{"type":"string"},"team_name_raw":{"type":"string"},"team_id":{"type":["string","null"]},"player_name":{"type":"string"},"back_number":{"type":["integer","null"]},"sport":{"type":"string","enum":["baseball","soccer","volleyball"]},"size_raw":{"type":"string"},"size":{"type":["string","null"],"enum":["XS","S","M","L","XL","2XL","3XL",null]},"option_code":{"type":["string","null"]},"unit_price_jpy":{"type":["integer","null"]},"name_print_raw":{"type":"string"}},"required":["order_id","team_name_raw","player_name","sport","size_raw","option_code"]}담당자가 화면의 질문 카드에서 무엇을 골랐는지 남기는 선택 이력이며, 같은 시퀀스를 재생해 최종 로스터·결제 차액·주문 상태가 똑같이 끝나는지 대조하는 재생 테스트의 입력입니다. 한 질문을 뒤늦게 다시 연 기록, 후보 없이 고객 문의로 넘긴 기록, 담당자 두 명이 한 주문을 나눠 처리한 기록을 섞어, 마지막 상태만 보고 판단하지 않는지 보여줍니다.
| event_id | order_id | question_id | rule_id | action | chosen_value | actor | at | resulting_state |
|---|---|---|---|---|---|---|---|---|
| E-0001 | O-1001 | Q-1001-01 | duplicate_number | pick_candidate | 11 | ops_hana | 2026-07-14T09:12:03+09:00 | resolved |
| E-0002 | O-1001 | Q-1001-02 | missing_size | pick_candidate | L | ops_hana | 2026-07-14T09:12:41+09:00 | resolved |
| E-0003 | O-1001 | Q-1001-01 | duplicate_number | reopen | — | ops_jun | 2026-07-14T10:02:15+09:00 | open |
| E-0004 | O-1001 | Q-1001-01 | duplicate_number | pick_candidate | 12 | ops_jun | 2026-07-14T10:03:02+09:00 | resolved |
| E-0005 | O-1002 | Q-1002-01 | invalid_option_for_sport | pick_candidate | OPT-SC-PRO | ops_jun | 2026-07-14T11:20:47+09:00 | resolved |
아래 JSON Schema 에 맞는 확인 질문 선택 이력 30행을 JSON 배열로 생성합니다. 한 order_id 아래 여러 question_id 가 있고, 한 question_id 는 시간순으로 여러 이벤트를 가질 수 있습니다.
{"type":"object","properties":{"event_id":{"type":"string"},"order_id":{"type":"string"},"question_id":{"type":"string"},"rule_id":{"type":"string","enum":["duplicate_number","missing_size","invalid_option_for_sport","orphan_option","number_out_of_range","free_text_memo"]},"action":{"type":"string","enum":["pick_candidate","manual_input","ask_customer","reopen"]},"chosen_value":{"type":["string","null"]},"actor":{"type":"string"},"at":{"type":"string"},"resulting_state":{"type":"string","enum":["open","resolved","asked_customer"]}},"required":["event_id","order_id","question_id","rule_id","action","actor","at","resulting_state"]}
쉬운 데이터만 만들지 않습니다. 하드 케이스를 의도적으로 섞습니다: (1) 같은 question_id 를 resolved 이후 reopen 했다가 다른 값으로 다시 고른 흐름 3~4건, (2) action=ask_customer 라 chosen_value 가 null 인 기록 3~4건과 하루 뒤 회신을 manual_input 으로 반영한 짝, (3) 같은 order_id 를 actor 두 명이 나눠 처리한 기록 2~3건, (4) chosen_value 가 option_price_master 에 없는 코드라 서버가 거절해야 하는 기록 1~2건, (5) 같은 초 단위 시각에 두 이벤트가 몰린 기록 1건. JSON 배열만 출력합니다.{"type":"object","properties":{"event_id":{"type":"string"},"order_id":{"type":"string"},"question_id":{"type":"string"},"rule_id":{"type":"string","enum":["duplicate_number","missing_size","invalid_option_for_sport","orphan_option","number_out_of_range","free_text_memo"]},"action":{"type":"string","enum":["pick_candidate","manual_input","ask_customer","reopen"]},"chosen_value":{"type":["string","null"]},"actor":{"type":"string"},"at":{"type":"string"},"resulting_state":{"type":"string","enum":["open","resolved","asked_customer"]}},"required":["event_id","order_id","question_id","rule_id","action","actor","at","resulting_state"]}| TypeScript | 질문 카드 목록과 로스터 표를 한 화면에 붙이는 확인 처리대 프런트와 확정 API 를 잇는 공통 언어(스킬의 TypeScript) |
| Node.js | 질문 생성 요청·답 반영·상태 전이를 처리하는 주문 확인 API 서버(스킬의 Node.js) |
| Python | 결함을 질문으로 바꾸는 규칙 엔진과 선택 이력 재생 테스트·더미데이터 증강 스크립트(스킬의 Python) |
| PostgreSQL | 주문 로스터·옵션 마스터·확인 질문과 해결 이력의 단일 진실원이자 제작 이관을 막는 트랜잭션 경계(스킬의 PostgreSQL) |
| Redis | 여러 담당자가 같은 주문을 동시에 열었을 때 질문 카드 점유 잠금과 화면 갱신 알림(스킬의 Redis) |
| DynamoDB | 누가 어떤 질문에 무엇을 골랐는지 남기는 선택 이벤트 로그 적재(스킬의 DynamoDB) |
| AWS | 3주차부터 확인 처리대 배포와 서비스 운영 환경(스킬의 AWS) |
| Amazon EKS | 확인 API 와 화면 컨테이너를 배포·운영하는 오케스트레이션(스킬의 Amazon EKS) |
| Amazon S3 | 업로드된 주문서 파일과 디자인 시안 이미지 저장(스킬의 Amazon S3) |
| KOMOJU | 확정 시점 재계산 합계를 결제 캡처 금액과 대조하는 결제 흐름(스킬의 KOMOJU) |
| PayVerse | 대체 결제 경로의 캡처 금액 대조(스킬의 PayVerse) |
| Claude Code | 코드베이스 탐색·구현·테스트·리팩터링에 쓰는 개발 도구이며, 낸 결과는 동작·영향·예외를 직접 검증합니다(스킬의 Claude Code) |
| OpenAI Codex | 규칙 엔진 테스트 케이스 초안과 리팩터링 보조로 쓰고 최종 판단은 직접 확인합니다(스킬의 OpenAI Codex) |
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 2주차(일본어 자유서술 메모를 후보 답으로 붙이기)로 진행합니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우면 3주차(AWS 배포·결제 캡처 대조)로 진행합니다.
주문 1건이 확인 처리대에서 지금 어떤 상태인지 담는 스냅샷입니다. 규칙 엔진이 만든 확인 질문(questions)과 각 질문의 근거·후보 답·해결 상태, 담당자 선택을 반영한 결제 재계산(payment_delta), 제작 이관 가능 여부(gate)와 주문 상태(status)를 한 스키마에 담아, 사람이 화면에서 답한 것만큼만 상태가 움직였는지 객관 검증합니다.
{"type":"object","properties":{"order_id":{"type":"string"},"status":{"type":"string","enum":["awaiting_review","awaiting_customer","ready_for_production"]},"questions":{"type":"array","items":{"type":"object","properties":{"question_id":{"type":"string"},"line_ref":{"type":"string"},"rule_id":{"type":"string","enum":["duplicate_number","missing_size","invalid_option_for_sport","orphan_option","number_out_of_range","free_text_memo"]},"evidence":{"type":"string"},"candidates":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"source":{"type":"string","enum":["master","other_line","unused_range","memo"]}},"required":["value","label","source"]}},"blocking":{"type":"boolean"},"state":{"type":"string","enum":["open","resolved","asked_customer"]},"resolved_value":{"type":["string","null"]},"resolved_by":{"type":["string","null"]}},"required":["question_id","line_ref","rule_id","evidence","candidates","blocking","state"]}},"payment_delta":{"type":"object","properties":{"stated_total_jpy":{"type":["integer","null"]},"recomputed_total_jpy":{"type":"integer"},"delta_jpy":{"type":"integer"}},"required":["recomputed_total_jpy","delta_jpy"]},"gate":{"type":"object","properties":{"blocking_open":{"type":"integer"},"can_release":{"type":"boolean"},"blocked_reason":{"type":["string","null"]}},"required":["blocking_open","can_release"]}},"required":["order_id","status","questions","payment_delta","gate"]}[
{
"input_ref": "주문 O-1001 을 처음 열었을 때(아직 아무도 답하지 않음)",
"output": {
"order_id": "O-1001",
"status": "awaiting_review",
"questions": [
{
"question_id": "Q-1001-01",
"line_ref": "O-1001/鈴木",
"rule_id": "duplicate_number",
"evidence": "back_number=10 이 같은 order_id 의 田中(back_number=10)과 중복",
"candidates": [
{
"value": "11",
"label": "11번으로 변경(같은 팀에서 아직 쓰지 않는 번호)",
"source": "unused_range"
},
{
"value": "12",
"label": "12번으로 변경(같은 팀에서 아직 쓰지 않는 번호)",
"source": "unused_range"
},
{
"value": "13",
"label": "13번으로 변경(같은 팀에서 아직 쓰지 않는 번호)",
"source": "unused_range"
}
],
"blocking": true,
"state": "open",
"resolved_value": null,
"resolved_by": null
},
{
"question_id": "Q-1001-02",
"line_ref": "O-1001/鈴木",
"rule_id": "missing_size",
"evidence": "size_raw 가 빈 값이라 size=null",
"candidates": [
{
"value": "L",
"label": "L(같은 팀에서 가장 많이 고른 사이즈)",
"source": "other_line"
},
{
"value": "M",
"label": "M",
"source": "other_line"
}
],
"blocking": true,
"state": "open",
"resolved_value": null,
"resolved_by": null
}
],
"payment_delta": {
"stated_total_jpy": 27900,
"recomputed_total_jpy": 27900,
"delta_jpy": 0
},
"gate": {
"blocking_open": 2,
"can_release": false,
"blocked_reason": "답하지 않은 확인 질문이 2건 남아 있습니다"
}
}
},
{
"input_ref": "주문 O-1001 에서 담당자가 등번호 질문만 12번으로 답한 직후",
"output": {
"order_id": "O-1001",
"status": "awaiting_review",
"questions": [
{
"question_id": "Q-1001-01",
"line_ref": "O-1001/鈴木",
"rule_id": "duplicate_number",
"evidence": "back_number=10 이 같은 order_id 의 田中(back_number=10)과 중복",
"candidates": [
{
"value": "11",
"label": "11번으로 변경(같은 팀에서 아직 쓰지 않는 번호)",
"source": "unused_range"
},
{
"value": "12",
"label": "12번으로 변경(같은 팀에서 아직 쓰지 않는 번호)",
"source": "unused_range"
}
],
"blocking": true,
"state": "resolved",
"resolved_value": "12",
"resolved_by": "ops_jun"
},
{
"question_id": "Q-1001-02",
"line_ref": "O-1001/鈴木",
"rule_id": "missing_size",
"evidence": "size_raw 가 빈 값이라 size=null",
"candidates": [
{
"value": "L",
"label": "L(같은 팀에서 가장 많이 고른 사이즈)",
"source": "other_line"
},
{
"value": "M",
"label": "M",
"source": "other_line"
}
],
"blocking": true,
"state": "open",
"resolved_value": null,
"resolved_by": null
}
],
"payment_delta": {
"stated_total_jpy": 27900,
"recomputed_total_jpy": 27900,
"delta_jpy": 0
},
"gate": {
"blocking_open": 1,
"can_release": false,
"blocked_reason": "사이즈 확인 질문 1건이 아직 열려 있습니다"
}
}
},
{
"input_ref": "주문 O-1002 에서 담당자가 단종 옵션 질문에 OPT-SC-PRO 를 골라 마지막 질문까지 닫은 직후",
"output": {
"order_id": "O-1002",
"status": "ready_for_production",
"questions": [
{
"question_id": "Q-1002-01",
"line_ref": "O-1002/小林",
"rule_id": "invalid_option_for_sport",
"evidence": "option_code=OPT-SC-DISC 의 status=discontinued 라 지금 주문에 쓸 수 없음",
"candidates": [
{
"value": "OPT-SC-STD",
"label": "축구 기본(추가가 0엔)",
"source": "master"
},
{
"value": "OPT-SC-PRO",
"label": "축구 프로(추가가 1,200엔, 같은 팀 다른 선수와 동일)",
"source": "master"
}
],
"blocking": true,
"state": "resolved",
"resolved_value": "OPT-SC-PRO",
"resolved_by": "ops_jun"
}
],
"payment_delta": {
"stated_total_jpy": 28400,
"recomputed_total_jpy": 28800,
"delta_jpy": 400
},
"gate": {
"blocking_open": 0,
"can_release": true,
"blocked_reason": null
}
}
},
{
"input_ref": "주문 O-1003 에서 자유서술 메모 '一番いいやつで全員分' 의 뜻을 규칙으로도 메모로도 특정하지 못해 담당자가 고객 문의로 넘긴 상태",
"output": {
"order_id": "O-1003",
"status": "awaiting_customer",
"questions": [
{
"question_id": "Q-1003-01",
"line_ref": "O-1003/伊藤",
"rule_id": "orphan_option",
"evidence": "option_code=OPT-XX-999 는 option_price_master 에 없음",
"candidates": [
{
"value": "OPT-VB-STD",
"label": "배구 기본(추가가 0엔)",
"source": "master"
},
{
"value": "OPT-VB-PRO",
"label": "배구 프로(추가가 1,100엔)",
"source": "master"
}
],
"blocking": true,
"state": "resolved",
"resolved_value": "OPT-VB-PRO",
"resolved_by": "ops_hana"
},
{
"question_id": "Q-1003-02",
"line_ref": "O-1003/메모",
"rule_id": "free_text_memo",
"evidence": "주문 메모 '一番いいやつで全員分' 이 어느 옵션·사이즈를 뜻하는지 마스터로 특정되지 않음",
"candidates": [],
"blocking": true,
"state": "asked_customer",
"resolved_value": null,
"resolved_by": "ops_hana"
}
],
"payment_delta": {
"stated_total_jpy": 15400,
"recomputed_total_jpy": 16500,
"delta_jpy": 1100
},
"gate": {
"blocking_open": 1,
"can_release": false,
"blocked_reason": "고객 회신을 기다리는 질문이 1건 있습니다"
}
}
}
][자동 검증] 1. 출력이 스키마를 통과하는가(형식·필수 필드·rule_id 와 state enum). 2. 같은 주문·같은 마스터를 다시 열면 questions 의 question_id·순서·candidates 가 항상 동일한가(질문 생성 결정론). 3. candidates 의 옵션 코드가 전부 option_price_master 에 실제로 있고 status=active 인가. 단종 옵션이나 없는 코드를 후보로 냈으면 실패. 4. resolved_value 가 그 질문의 candidates 에 있거나 직접 입력 규칙(사이즈 enum·등번호 범위)을 지키는가. 규칙을 벗어난 값이 반영됐으면 실패. 5. gate.blocking_open 이 state 가 resolved 가 아닌 blocking 질문 수와 정확히 같고, can_release 가 blocking_open=0 과 완전히 일치하는가. blocking_open>0 인데 status=ready_for_production 이면 실패. 6. 같은 clarification_resolution_log 시퀀스를 재생하면 최종 questions·payment_delta.recomputed_total_jpy·status 가 항상 같은 값으로 끝나는가. 7. 미해결 상태에서 화면을 건너뛰고 확정 API 를 직접 호출했을 때 서버가 거절하고 status 가 그대로인가(화면 우회 차단). [수동 검증] 1. 운영 담당·기획 페르소나 2명이 질문 문장과 후보 답 타당성을 라벨링 → bootstrap Cohen's κ(두 사람 답이 우연 이상으로 일치하는 정도, 0~1) 하한 0.6 이상. 2. 주문 1건당 담당자가 실제로 답해야 했던 질문 수를 세어, 잘게 쪼개져 손이 더 가는 구간이 없는지 사람 점검.
포트폴리오가 “무엇을 만들지”라면, 시나리오 테스트는 “현장에서 어떻게 판단·소통할지” 입니다. 실제 이 채용공고 맥락의 대본을 읽고 무엇을 물어야 할지 직접 써봅니다.
근거: KOMOJU와 PayVerse를 사용하는 결제 및 주문 흐름을 개발하고 안정적으로 운영합니다