트레블씨투비
경력근거: CreateJump, B2B Partner, JumKi 서비스의 개발 및 운영 (React/Next.js, Node.js/TypeScript)
'K-POP 팬을 위한 팝업스토어 및 굿즈 커머스 솔루션' 이 트레블씨투비의 사업이고, 이 자리가 맡는 첫 업무는 'CreateJump, B2B Partner, JumKi 서비스의 개발 및 운영' 입니다. 우대사항에는 '커머스·주문·재고 시스템 개발 경험' 과 '다국어·글로벌 서비스 개발 경험' 이 있고, 자격요건에는 '해외 출장에 결격 사유가 없는 분' 이 있습니다. 해외 팝업 현장에서 JumKi 사전 주문을 현지 실입고에 맞춰 보는 이 화면은 그 세 줄이 겹치는 자리에서 출발합니다. 공고 맨 앞의 'AI가 짠 코드를 그대로 믿지 않고 직접 검증하고 책임질 수 있어야 합니다' 는 이 편에서 Claude Code 로 짠 누적 판정을 같은 초 결제·실입고 0개 같은 사례 테스트로 고정해 GitHub Actions 에서 돌리는 일이 됩니다. 이 포트폴리오는 주문·재고 누적 판정과 관리자 화면을 Next.js·NestJS·PostgreSQL 로 만드는 풀스택 구현을 보여줍니다. K-POP 굿즈 물류나 해외 팝업 현장 운영의 전문성은 주장하지 않습니다.
담당업무 첫 줄이 CreateJump, B2B Partner, JumKi 서비스의 개발과 운영이고, 회사 소개는 K-POP 팬을 위한 팝업스토어와 굿즈 커머스를 사업으로 듭니다. 이 편은 JumKi 로 받은 팝업 사전 주문을 현지 입고 수량에 맞춰 판정하는 관리자 화면이라, 이 자리가 맡을 서비스의 운영 화면 하나에 해당합니다.
우대사항에 커머스·주문·재고 시스템 개발 경험과 다국어·글로벌 서비스 개발 경험이 있고, 자격요건은 해외 출장에 결격 사유가 없는 사람을 찾습니다. 판매 수량을 결제 순으로, 실입고를 대기 번호 순으로 세는 두 누적 판정이 주문·재고 쪽에 닿고, 팬 앱 언어로 쓰는 안내문 초안이 다국어 쪽에 닿습니다.
공고는 AI 가 짠 코드를 그대로 믿지 않고 직접 확인하고 책임지는 사람을 찾고, 우대사항에 테스트 자동화·CI 구축과 LLM API 로 기능을 출시해 본 경험을 듭니다. 이 편은 Claude Code 로 짠 누적 판정을 같은 초 결제, 누적이 수량과 정확히 같은 주문, 실입고 0개 사례로 고정해 GitHub Actions 에서 돌리고, 3주차에 Claude Haiku 안내문 초안 기능을 붙입니다.
크리에이점프는 트레블씨투비가 해외 투어 팝업스토어를 기획·운영하는 브랜드이고, JumKi 는 팬이 입장 전에 굿즈를 사전 주문하고 대기 번호를 받아 번호가 불리면 카운터에서 받아 가는 앱입니다. 이 화면은 크리에이점프 팝업의 JumKi 사전 주문을 판정합니다. 오픈 전날 밤에는 입고 검수를 마친 현장 매니저가 먼저 열어 못 받을 주문의 처리를 고르고, 팝업 당일 마감 뒤에는 기획사 MD 담당자가 열어 찾아가지 않은 주문의 수량을 다음 날 현장 판매로 풀지 정합니다. 팝업 회차마다 굿즈별로 JumKi 에 올린 판매 수량, 현지 실입고 수량, 픽업 마감 시각을 주문 기록과 카운터 기록에 대어, 사전 주문마다 카운터에서 받을 수 있는지 아니면 어떤 사유로 못 받는지 하나로 판정하고, 그 주문이 결제 순과 대기 번호 순으로 각각 몇 개째인지 보여줍니다. JumKi 의 사전 주문·대기 번호·픽업 흐름은 공고가 아니라 공개된 앱 설명에서 잡은 각도입니다.
크리에이점프에 해외 투어 팝업을 맡긴 기획사 MD 담당자와 현지 팝업을 운영하는 크리에이점프 현장 매니저. 현지 입고 검수를 마친 오픈 전날 밤과 팝업 당일 마감 뒤 JumKi 관리자 화면에서 판정을 함께 보고, 못 받을 주문은 오픈 전에 환불하거나 다음 도시 팝업 픽업으로 돌리고, 찾아가지 않은 주문의 수량은 현장 판매로 푼다
오픈 전날 밤 입고 검수가 끝나면 현장 매니저는 굿즈별 실입고 수량을 받아 들고, JumKi 관리자에서 내려받은 사전 주문 목록을 스프레드시트에서 대기 번호 순으로 세어 내려가며 몇 번째 주문부터 물건이 모자라는지 찾습니다. 같은 초에 들어온 결제를 앱이 둘 다 받아 판매 수량보다 더 판 주문은 결제 시각 순으로 다시 세어야 보이는데, 두 셈은 줄 세우는 기준이 달라 한 시트에서 뒤섞입니다. 결제 순 누적으로 초과 판매를 먼저 걸러 내고 남은 주문을 대기 번호 순으로 세면, 매니저와 MD 담당자는 환불하거나 다른 날로 돌릴 주문만 열어 봅니다. 마감 뒤에는 건넨 시각이 없는 주문이 따로 남아 현장 판매로 풀 수량이 나옵니다.
현장 매니저가 JumKi 주문·팝업 재고·카운터 기록 CSV 세 장을 올리고 판정 기준 시각을 고르면, 사전 주문이 네 판정으로 나뉩니다. 주문을 펼치면 같은 굿즈 주문이 결제 순과 대기 번호 순 두 칸에 서고, 판매 수량과 실입고에 닿는 자리에 선이 그어져 그 주문이 몇 개째인지 보입니다. 매니저와 MD 담당자는 걸린 주문마다 환불·2일차 픽업·다음 도시 픽업·현장 판매로 풀기 가운데 처리를 골라 CSV 로 내려받습니다. 실입고 수량은 입고 검수표가 팝업 회차·굿즈 코드마다 검수를 통과한 개수를 한 줄씩 남긴다고 보고, 그 모양의 CSV 로 받습니다.
NestJS 로 결제 완료 뒤 판매 수량을 차감하는 API 를 JumKi 처럼 만들고, 남은 수량을 읽은 다음 쓰는 차감 방식에 같은 굿즈 동시 결제 200건을 쏩니다. 차감 기록으로 센 초과 판매 주문과 화면의 '앱이 판매 수량을 넘겨 판 주문' 을 견주고, 차감을 Redis 원자 연산 한 번으로 고친 뒤 다시 쏘아 그 판정이 0건으로 떨어지는지 봅니다. 작성자가 판정을 손으로 매긴 주문 40건으로 네 판정이 정답과 맞은 건수도 셉니다. 2주차부터 API 는 AWS ECS 에, 세 표는 RDS PostgreSQL 에 올려 운영합니다.
매니저가 걸린 주문에 처리를 고르면 Claude Haiku 가 그 팬의 앱 언어(en-US·es-MX·ja-JP·ko-KR·pt-BR)로 안내문 초안을 씁니다. 대기 번호와 주문 번호가 글자 그대로 들어 있고 금액이 없는 초안만 남으며, 매니저가 고쳐서 JumKi 공지나 현장 안내에 직접 붙입니다. 보내는 기능은 만들지 않습니다.
현장 매니저가 LA 팝업 이틀 분량 주문 20건과 판정 기준 시각 10월 18일 21시 30분을 넣으면, 1일차 주문 17건 가운데 초과 판매 1건·실입고 부족 4건·미수령 2건이 걸리고 마감 전인 2일차 주문 3건은 받을 수 있음으로 나옵니다. Q-2207 을 펼치면 결제 순 누적 7팩이 판매 등록 6팩을 넘는 자리가 보입니다.
같은 초에 결제된 Q-2205·Q-2207, 결제 순 누적이 판매 수량과 정확히 같은 Q-0841, 대기 번호 순 누적이 실입고와 정확히 같은 Q-0577, 통관 보류로 실입고가 0개인 LA 한정 키링, 번호를 불렀지만 건넨 시각이 비어 있는 Q-1127, 마감 20시 정각에 건넨 Q-0263 처럼 경계에 놓인 주문을 섞어도 판정이 판정식대로 나옵니다.
qty 가 '2개' 같은 글자인 주문, qty 가 0 인 주문, 팝업 재고표에 없는 굿즈 코드로 들어온 주문, paid_at 에 시간대가 빠진 주문, received_qty 칸이 빈 재고 줄, 이렇게 깨진 입력 다섯 종류를 섞어 올려도 다른 굿즈의 판정은 그대로 나옵니다. 깨진 값이 든 굿즈는 누적을 확정할 수 없어 그 굿즈 주문 전체가 목록 맨 위 보류 줄에 깨진 종류를 달고 모입니다.
작성자가 판정을 손으로 매긴 주문 40건으로 화면 판정이 정답과 맞은 건수를 셉니다. 단, 주문과 정답을 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건에서의 동작입니다.
위 수치는 작성자가 만든 주문·입고·카운터 기록의 자가채점이며, 판정이 실제 팝업에서 맞는지는 JumKi 실제 주문 기록과 크리에이점프 현장 입고 검수 결과, 현장 매니저의 확인이 있어야 잽니다. 이 포트폴리오는 그 측정을 바로 돌릴 수 있는 상태까지 만듭니다.
손으로 지은 것은 1주차 LA 팝업 더미이고 2주차 초과 판매는 제 재고 차감 API 에 동시 결제를 쏘아 실제로 생기지만, JumKi 가 결제 시각과 대기 번호를 어떤 순서로 찍는지는 두 경우 모두 제 가정입니다. 샘플 주문 20건과 2주차 평가용 주문 40건·그 정답도 제가 만들었으므로, 맞은 건수가 높게 나와도 제 가정 위에서 제 규칙이 읽은 결과라 성능 증명이 아닙니다.
21:03:07 같은 초에 결제된 Q-2205·Q-2207 은 같은 시각의 순서 규칙 하나로 어느 쪽이 초과 판매가 될지 갈립니다. 결제 순으로는 판매 수량 8개에 정확히 닿지만 대기 번호 순으로 실입고 5개를 넘는 Q-0841, 결제는 늦었지만 대기 번호가 앞서 받을 수 있는 Q-0577, 실입고가 0개인 LA 한정 키링, 건넨 시각이 비어 있는 Q-1127, 마감 20시 정각에 건넨 Q-0263 도 값 하나 차이로 판정이 바뀌는 주문입니다.
결제 순과 대기 번호 순 가운데 무엇으로 줄을 세울지, 같은 초에 결제된 주문을 어느 쪽 먼저 볼지는 제가 정한 규칙입니다. JumKi 가 대기 번호를 결제를 마칠 때 매긴다면 두 순서가 같아져 실입고 부족도 결제 순 하나로 셉니다.
2주차 동시 결제 재현은 제 컴퓨터와 제 AWS 계정에서 만든 부하라 실제 JumKi 결제 흐름의 동시성과 같지 않고, 재현할 때마다 초과 판매 건수가 달라집니다.
실제 도입에서는 판매 수량을 회차별로 나눠 올리는 방식과 입고 검수 수량이 확정되는 시각을 크리에이점프 현장 운영에 맞춰 다시 정합니다. 이 포트폴리오는 주문·재고 누적 판정과 그 관리자 화면을 만드는 풀스택 구현을 보여줍니다. K-POP 굿즈 물류나 해외 팝업 현장 운영의 전문성은 주장하지 않습니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
JumKi 로 받은 LA 팝업 이틀 분량(LA-1018·LA-1019) 사전 주문입니다. 포토카드 랜덤팩 Q-2205·Q-2207 은 같은 초 21:03:07 에 결제됐고, 응원봉 Q-0577 은 대기 번호가 Q-0912 보다 앞서지만 결제는 더 늦습니다. 1일차 응원봉·티셔츠 L·LA 한정 키링은 결제된 수량을 다 더하면 판매 수량과 정확히 같아지는 경계에 놓였습니다. 수량은 판정 흐름이 몇 줄 안에서 보이도록 작게 잡았고, 증강에서는 굿즈당 수백 개 규모로 늘립니다.
| order_id | popup_id | queue_no | sku | qty | paid_at | app_locale |
|---|---|---|---|---|---|---|
| JK-702114 | LA-1018 | Q-0005 | TW-SLOGAN | 2 | 2026-10-15T19:00:41-07:00 | en-US |
| JK-702139 | LA-1018 | Q-0012 | PC-RAND | 2 | 2026-10-15T19:02:11-07:00 | ko-KR |
| JK-702170 | LA-1019 | Q-0019 | PC-RAND | 2 | 2026-10-15T19:02:40-07:00 | en-US |
| JK-702188 | LA-1018 | Q-0031 | LS-V2 | 2 | 2026-10-15T19:05:26-07:00 | es-MX |
| JK-702231 | LA-1018 | Q-0044 | TS-L | 1 | 2026-10-15T19:08:03-07:00 | en-US |
아래 JSON Schema 를 그대로 만족하는 JumKi 사전 주문을 팝업 회차 하나 분량(굿즈 5종, 주문 300건)으로 더 만들어 주세요. {"type":"object","properties":{"order_id":{"type":"string","description":"JumKi 주문 번호입니다"},"popup_id":{"type":"string","description":"픽업할 팝업 회차(도시·날짜)입니다"},"queue_no":{"type":"string","description":"주문을 넣을 때 그 회차 안에서 매긴 대기 번호(Q-네 자리)입니다. 카운터는 이 번호 순으로 부릅니다"},"sku":{"type":"string","description":"굿즈 코드입니다. 주문 한 건은 굿즈 한 종류입니다"},"qty":{"type":"integer","minimum":1},"paid_at":{"type":"string","description":"결제를 마친 시각(ISO 8601, 현지 -07:00)입니다. JumKi 는 이 시각에 판매 수량을 차감한다고 봅니다"},"app_locale":{"type":"string","enum":["en-US","es-MX","ja-JP","ko-KR","pt-BR"],"description":"팬이 쓰는 앱 언어입니다. 판정에는 쓰지 않고 3주차 안내문 초안에 씁니다"}},"required":["order_id","popup_id","queue_no","sku","qty","paid_at","app_locale"]} popup_id·sku 는 함께 만드는 팝업 재고표에 있는 짝만 쓰고, queue_no 는 회차 안에서 서로 다르게 매기며 order_id 가 작을수록 queue_no 도 작게 두세요. qty 는 1~4 로 두고, paid_at 은 사전 판매를 연 시각부터 오픈 전날 23:59 사이로 두세요. 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) paid_at 이 초 단위까지 같은 주문 2~3쌍, (2) 대기 번호는 앞서는데 결제는 몇 시간 늦은 주문, (3) 결제 순 누적이 판매 수량과 정확히 같아지는 굿즈, (4) 결제 순 누적이 판매 수량을 1~3개 넘는 굿즈. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도(qty 가 '2개' 같은 문자열, paid_at 에 시간대가 없는 값)는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"order_id":{"type":"string","description":"JumKi 주문 번호입니다"},"popup_id":{"type":"string","description":"픽업할 팝업 회차(도시·날짜)입니다"},"queue_no":{"type":"string","description":"주문을 넣을 때 그 회차 안에서 매긴 대기 번호(Q-네 자리)입니다. 카운터는 이 번호 순으로 부릅니다"},"sku":{"type":"string","description":"굿즈 코드입니다. 주문 한 건은 굿즈 한 종류입니다"},"qty":{"type":"integer","minimum":1},"paid_at":{"type":"string","description":"결제를 마친 시각(ISO 8601, 현지 -07:00)입니다. JumKi 는 이 시각에 판매 수량을 차감한다고 봅니다"},"app_locale":{"type":"string","enum":["en-US","es-MX","ja-JP","ko-KR","pt-BR"],"description":"팬이 쓰는 앱 언어입니다. 판정에는 쓰지 않고 3주차 안내문 초안에 씁니다"}},"required":["order_id","popup_id","queue_no","sku","qty","paid_at","app_locale"]}팝업 회차마다 굿즈별로 JumKi 에 올린 판매 수량, 현지 입고 검수를 마친 실입고 수량, 픽업 마감 시각입니다. 한국에서 보낸 응원봉은 배송 중 파손으로 1일차 실입고가 판매 수량 8개보다 3개 적고, LA 한정 키링은 통관 보류로 두 회차 모두 실입고가 0개입니다. 1일차 마감은 10월 18일 20시, 2일차 마감은 10월 19일 20시입니다.
| popup_id | sku | sku_name | listed_qty | received_qty | close_at |
|---|---|---|---|---|---|
| LA-1018 | PC-RAND | 투어 포토카드 랜덤팩 | 6 | 6 | 2026-10-18T20:00:00-07:00 |
| LA-1018 | LS-V2 | 공식 응원봉 VER.2 | 8 | 5 | 2026-10-18T20:00:00-07:00 |
| LA-1018 | TS-L | 투어 티셔츠 L | 4 | 4 | 2026-10-18T20:00:00-07:00 |
| LA-1018 | TW-SLOGAN | 슬로건 타월 | 6 | 6 | 2026-10-18T20:00:00-07:00 |
| LA-1018 | KEY-LA | LA 한정 키링 | 3 | 0 | 2026-10-18T20:00:00-07:00 |
아래 JSON Schema 를 그대로 만족하는 팝업 재고를 투어 도시 두 곳, 도시마다 이틀 회차 분량(회차당 굿즈 5종)으로 더 만들어 주세요. {"type":"object","properties":{"popup_id":{"type":"string"},"sku":{"type":"string"},"sku_name":{"type":"string"},"listed_qty":{"type":"integer","minimum":0,"description":"이 회차에 JumKi 로 사전 판매하려고 올린 수량입니다"},"received_qty":{"type":"integer","minimum":0,"description":"현지 입고 검수를 마치고 이 회차 매대로 나눈 실제 수량입니다. 통관 보류로 들어오지 않은 굿즈는 0 입니다"},"close_at":{"type":"string","description":"이 회차의 픽업 마감 시각(ISO 8601, 현지 -07:00)입니다"}},"required":["popup_id","sku","sku_name","listed_qty","received_qty","close_at"]} listed_qty 는 200~1,500 으로 두고, received_qty 는 대개 listed_qty 와 같거나 조금 적게 두세요. close_at 은 회차 날짜의 20:00 현지 시각입니다. 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) 통관 보류로 received_qty 가 0 인 굿즈, (2) 추가 입고로 received_qty 가 listed_qty 보다 많은 굿즈, (3) 배송 중 파손으로 received_qty 가 listed_qty 보다 1~5% 적은 굿즈. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도(received_qty 가 음수, close_at 에 시간대가 없는 값)는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"popup_id":{"type":"string"},"sku":{"type":"string"},"sku_name":{"type":"string"},"listed_qty":{"type":"integer","minimum":0,"description":"이 회차에 JumKi 로 사전 판매하려고 올린 수량입니다"},"received_qty":{"type":"integer","minimum":0,"description":"현지 입고 검수를 마치고 이 회차 매대로 나눈 실제 수량입니다. 통관 보류로 들어오지 않은 굿즈는 0 입니다"},"close_at":{"type":"string","description":"이 회차의 픽업 마감 시각(ISO 8601, 현지 -07:00)입니다"}},"required":["popup_id","sku","sku_name","listed_qty","received_qty","close_at"]}LA-1018 당일 카운터 기록입니다. 팬이 앱에서 도착을 누르면 카운터가 번호를 부르고, 굿즈를 건네면 그 시각을 남깁니다. Q-1127 은 번호를 불렀지만 오지 않아 건넨 시각이 비어 있고, Q-0263 은 오전에 불렸다가 마감 시각 20시 정각에 받아 갔습니다. 도착을 누르지 않은 주문과 아직 열지 않은 2일차 주문은 기록이 없습니다.
| order_id | called_at | picked_up_at |
|---|---|---|
| JK-702114 | 2026-10-18T10:02:10-07:00 | 2026-10-18T10:04:55-07:00 |
| JK-702139 | 2026-10-18T10:06:30-07:00 | 2026-10-18T10:08:12-07:00 |
| JK-702188 | 2026-10-18T10:11:02-07:00 | 2026-10-18T10:13:40-07:00 |
| JK-702231 | 2026-10-18T10:15:20-07:00 | 2026-10-18T10:16:58-07:00 |
| JK-702710 | 2026-10-18T11:02:44-07:00 | 2026-10-18T11:05:31-07:00 |
아래 JSON Schema 를 그대로 만족하는 카운터 기록을 함께 만든 주문의 70% 정도에 하나씩 만들어 주세요. {"type":"object","properties":{"order_id":{"type":"string"},"called_at":{"type":"string","description":"팬이 앱에서 도착을 누른 뒤 카운터가 번호를 부른 시각입니다"},"picked_up_at":{"type":["string","null"],"description":"카운터에서 굿즈를 건넨 시각입니다. 번호를 불렀지만 오지 않았으면 null 입니다"}},"required":["order_id","called_at","picked_up_at"]} order_id 는 주문 표에 있는 것만 쓰고, called_at 은 그 회차 날짜의 10:00~19:59 사이에서 대기 번호 순으로 늘어나게 두며, picked_up_at 은 called_at 뒤 1~10분으로 두세요. 실입고가 모자라 못 받는 주문과 판매 수량을 넘겨 판 주문에는 기록을 만들지 마세요. 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) 번호를 불렀지만 오지 않아 picked_up_at 이 null 인 기록, (2) picked_up_at 이 그 회차 close_at 과 같은 시각인 기록, (3) 오전에 불렸다가 저녁에 받아 간 기록. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도(picked_up_at 이 '받음' 같은 문자열)는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"order_id":{"type":"string"},"called_at":{"type":"string","description":"팬이 앱에서 도착을 누른 뒤 카운터가 번호를 부른 시각입니다"},"picked_up_at":{"type":["string","null"],"description":"카운터에서 굿즈를 건넨 시각입니다. 번호를 불렀지만 오지 않았으면 null 입니다"}},"required":["order_id","called_at","picked_up_at"]}| TypeScript | 결제 순·대기 번호 순 누적 판정, 마감 뒤 미수령 판정, 판정 우선순위를 관리자 화면과 API 가 함께 쓰는 라이브러리 하나로 씁니다. |
| Nx | 관리자 화면(Next.js)·재고 차감 API(NestJS)·판정 라이브러리를 한 저장소에 두고, 판정 라이브러리가 바뀌면 영향받는 앱의 테스트만 다시 돌립니다. |
| React | 판정별 주문 목록과, 주문을 펼치면 열리는 결제 순·대기 번호 순 두 칸 누적표를 컴포넌트로 만듭니다. |
| Next.js | CSV 세 장 올리기, 판정 기준 시각 고르기, 처리 고르기와 CSV 내려받기 화면을 맡습니다. |
| TanStack Query | 판정 기준 시각을 바꾸면 다시 판정한 결과를 받아 오고, 처리를 고르면 목록을 그 자리에서 갱신합니다. |
| Node.js | 2주차에 같은 굿즈 동시 결제 200건을 쏘는 부하 스크립트를 씁니다. |
| NestJS | 2주차에 결제 완료 뒤 판매 수량을 차감하는 API 를 JumKi 처럼 만들어 동시 결제를 재현합니다. |
| Express | 2주차 NestJS API 는 기본 HTTP 어댑터인 Express 위에서 돕니다. |
| PostgreSQL | 주문·팝업 재고·카운터 기록 세 표와 판정 결과, 매니저가 고른 처리를 담습니다. |
| TypeORM | 세 표를 엔티티와 마이그레이션으로 올리고, 2주차 재고 차감 API 도 같은 엔티티를 씁니다. |
| MySQL | 2주차 재현을 MySQL 에서도 돌려, 읽은 다음 쓰는 차감이 데이터베이스 종류와 무관하게 초과 판매를 만드는지 봅니다. |
| Redis | 2주차에 고친 차감은 남은 수량 확인과 차감을 Lua 스크립트 하나로 Redis 에서 한 번에 합니다. |
| AWS | 2주차부터 NestJS API 는 ECS 에, 세 표는 RDS PostgreSQL 에 올려 운영합니다. |
| Vercel | 1주차 관리자 화면을 올려 현장 매니저가 브라우저로 엽니다. |
| Sentry | CSV 를 읽다 판정 보류로 빠진 주문과 판정 실행 오류를 Sentry 로 남깁니다. |
| GitHub Actions | 같은 초 결제, 누적이 판매 수량·실입고와 정확히 같은 주문, 실입고 0개, 마감 시각 정각 픽업 사례 테스트를 PR 마다 돌립니다. |
| Claude Code | 판정 규칙과 화면을 Claude Code 로 짜고, 경계 사례 테스트를 한 번에 돌리는 커스텀 명령을 둡니다. |
| LLM (Claude Haiku) | 3주차에 걸린 주문마다 팬 앱 언어로 안내문 초안을 씁니다. |
| OpenAI | 안내문 초안 호출부를 어댑터 하나로 두어 GPT-4o mini 로 바꿔도 같은 출력 가드를 통과하는지 견줍니다. |
✗ fail → Q-2205 가 걸리면 같은 결제 시각의 순서 규칙(대기 번호가 앞선 주문 먼저)이 빠진 것이니 정렬 비교 함수부터 봅니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 동시 결제를 실제로 일으켜 초과 판매 판정이 그 주문을 잡는지 보는 단계로 갑니다.
✗ fail → 두 목록이 다르면 결제 완료 시각을 재고 차감 시각으로 쓰는 가정이 재현 API 와 어긋난 것이니 paid_at 을 찍는 자리부터 봅니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 걸린 주문마다 팬 앱 언어로 안내문 초안을 붙이는 단계로 넓힙니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · order_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
카운터에서 받을 수 있음받을 수 있음앱이 판매 수량을 넘겨 판 주문초과 판매실입고가 모자라 못 받는 주문실입고 부족마감까지 찾아가지 않은 주문미수령사전 주문 하나에 팝업 사전 주문 판정 하나와 그 근거(결제 순 누적과 판매 수량, 대기 번호 순 누적과 실입고 수량, 마감 시각과 건넨 시각)를 남기는 형식입니다.
{"type":"object","properties":{"order_id":{"type":"string"},"popup_id":{"type":"string"},"queue_no":{"type":"string"},"sku":{"type":"string"},"sku_name":{"type":"string"},"qty":{"type":"integer","minimum":1},"paid_at":{"type":"string"},"judged_at":{"type":"string","description":"매니저가 고른 판정 기준 시각(ISO 8601)입니다. 마감이 지났는지는 이 시각과 close_at 으로 봅니다"},"verdict":{"type":"string","enum":["카운터에서 받을 수 있음","앱이 판매 수량을 넘겨 판 주문","실입고가 모자라 못 받는 주문","마감까지 찾아가지 않은 주문"],"description":"앱이 판매 수량을 넘겨 판 주문, 실입고가 모자라 못 받는 주문, 마감까지 찾아가지 않은 주문 순으로 앞선 하나입니다. 하나도 걸리지 않으면 카운터에서 받을 수 있음입니다"},"listed_qty":{"type":"integer","minimum":0},"cum_qty_by_paid":{"type":"integer","minimum":1,"description":"정적 계산값입니다. 같은 popup_id·sku 의 jumki_orders 를 paid_at 순(같은 시각이면 queue_no 가 작은 주문 먼저)으로 세워 이 주문까지 더한 qty 합계입니다"},"received_qty":{"type":"integer","minimum":0},"cum_qty_by_queue":{"type":["integer","null"],"description":"정적 계산값입니다. 앱이 판매 수량을 넘겨 판 주문을 뺀 같은 popup_id·sku 의 jumki_orders 를 queue_no 순으로 세워 이 주문까지 더한 qty 합계입니다. 이 주문이 판매 수량을 넘겨 판 주문이면 null 입니다"},"close_at":{"type":"string"},"picked_up_at":{"type":["string","null"],"description":"pickups 에서 같은 order_id 의 picked_up_at 입니다. 행이 없거나 값이 비어 있으면 null 입니다"},"evidence":{"type":"array","items":{"type":"object","properties":{"table":{"type":"string","enum":["jumki_orders","popup_stock","pickups"]},"row":{"type":["string","null"],"description":"그 표의 키입니다. jumki_orders·pickups 는 order_id, popup_stock 은 popup_id/sku 입니다. 여러 행을 더하거나 센 항목은 popup_id/sku 이거나 null 입니다"},"field":{"type":"string"},"value":{"type":["string","integer","null"]},"agg":{"type":"string","enum":["count","sum","avg","min","max"]},"order_by":{"type":"string","enum":["paid_at","queue_no"],"description":"agg 가 sum 인 항목에서 어느 순서로 이 주문까지 더했는지입니다"}},"required":["table","row","field"]}},"summary":{"type":"string"}},"required":["order_id","popup_id","queue_no","sku","sku_name","qty","paid_at","judged_at","verdict","listed_qty","cum_qty_by_paid","received_qty","cum_qty_by_queue","close_at","picked_up_at","evidence","summary"]}[
{
"order_id": "JK-702114",
"popup_id": "LA-1018",
"queue_no": "Q-0005",
"sku": "TW-SLOGAN",
"sku_name": "슬로건 타월",
"qty": 2,
"paid_at": "2026-10-15T19:00:41-07:00",
"judged_at": "2026-10-18T21:30:00-07:00",
"verdict": "카운터에서 받을 수 있음",
"listed_qty": 6,
"cum_qty_by_paid": 2,
"received_qty": 6,
"cum_qty_by_queue": 2,
"close_at": "2026-10-18T20:00:00-07:00",
"picked_up_at": "2026-10-18T10:04:55-07:00",
"evidence": [
{
"table": "jumki_orders",
"row": "JK-702114",
"field": "queue_no",
"value": "Q-0005"
},
{
"table": "jumki_orders",
"row": "JK-702114",
"field": "qty",
"value": 2
},
{
"table": "jumki_orders",
"row": "LA-1018/TW-SLOGAN",
"field": "qty",
"agg": "sum",
"order_by": "paid_at",
"value": 2
},
{
"table": "popup_stock",
"row": "LA-1018/TW-SLOGAN",
"field": "listed_qty",
"value": 6
},
{
"table": "jumki_orders",
"row": "LA-1018/TW-SLOGAN",
"field": "qty",
"agg": "sum",
"order_by": "queue_no",
"value": 2
},
{
"table": "popup_stock",
"row": "LA-1018/TW-SLOGAN",
"field": "received_qty",
"value": 6
},
{
"table": "popup_stock",
"row": "LA-1018/TW-SLOGAN",
"field": "close_at",
"value": "2026-10-18T20:00:00-07:00"
},
{
"table": "pickups",
"row": "JK-702114",
"field": "picked_up_at",
"value": "2026-10-18T10:04:55-07:00"
}
],
"summary": "슬로건 타월 실입고 6개 가운데 대기 번호 순으로 2개째이고, 10월 18일 10시 4분에 카운터에서 받아 갔습니다."
},
{
"order_id": "JK-702170",
"popup_id": "LA-1019",
"queue_no": "Q-0019",
"sku": "PC-RAND",
"sku_name": "투어 포토카드 랜덤팩",
"qty": 2,
"paid_at": "2026-10-15T19:02:40-07:00",
"judged_at": "2026-10-18T21:30:00-07:00",
"verdict": "카운터에서 받을 수 있음",
"listed_qty": 6,
"cum_qty_by_paid": 2,
"received_qty": 6,
"cum_qty_by_queue": 2,
"close_at": "2026-10-19T20:00:00-07:00",
"picked_up_at": null,
"evidence": [
{
"table": "jumki_orders",
"row": "JK-702170",
"field": "queue_no",
"value": "Q-0019"
},
{
"table": "jumki_orders",
"row": "JK-702170",
"field": "qty",
"value": 2
},
{
"table": "jumki_orders",
"row": "LA-1019/PC-RAND",
"field": "qty",
"agg": "sum",
"order_by": "paid_at",
"value": 2
},
{
"table": "popup_stock",
"row": "LA-1019/PC-RAND",
"field": "listed_qty",
"value": 6
},
{
"table": "jumki_orders",
"row": "LA-1019/PC-RAND",
"field": "qty",
"agg": "sum",
"order_by": "queue_no",
"value": 2
},
{
"table": "popup_stock",
"row": "LA-1019/PC-RAND",
"field": "received_qty",
"value": 6
},
{
"table": "popup_stock",
"row": "LA-1019/PC-RAND",
"field": "close_at",
"value": "2026-10-19T20:00:00-07:00"
},
{
"table": "pickups",
"row": null,
"field": "order_id",
"agg": "count",
"value": 0
}
],
"summary": "2일차 회차라 마감 전이고, 포토카드 랜덤팩 판매 등록 6팩·실입고 6팩 가운데 2팩째라 카운터에서 받을 수 있습니다."
},
{
"order_id": "JK-714936",
"popup_id": "LA-1018",
"queue_no": "Q-2207",
"sku": "PC-RAND",
"sku_name": "투어 포토카드 랜덤팩",
"qty": 2,
"paid_at": "2026-10-17T21:03:07-07:00",
"judged_at": "2026-10-18T21:30:00-07:00",
"verdict": "앱이 판매 수량을 넘겨 판 주문",
"listed_qty": 6,
"cum_qty_by_paid": 7,
"received_qty": 6,
"cum_qty_by_queue": null,
"close_at": "2026-10-18T20:00:00-07:00",
"picked_up_at": null,
"evidence": [
{
"table": "jumki_orders",
"row": "JK-714936",
"field": "queue_no",
"value": "Q-2207"
},
{
"table": "jumki_orders",
"row": "JK-714936",
"field": "qty",
"value": 2
},
{
"table": "jumki_orders",
"row": "LA-1018/PC-RAND",
"field": "qty",
"agg": "sum",
"order_by": "paid_at",
"value": 7
},
{
"table": "popup_stock",
"row": "LA-1018/PC-RAND",
"field": "listed_qty",
"value": 6
},
{
"table": "jumki_orders",
"row": "JK-714936",
"field": "paid_at",
"value": "2026-10-17T21:03:07-07:00"
},
{
"table": "jumki_orders",
"row": "JK-714930",
"field": "paid_at",
"value": "2026-10-17T21:03:07-07:00"
},
{
"table": "jumki_orders",
"row": "JK-714930",
"field": "queue_no",
"value": "Q-2205"
}
],
"summary": "21시 3분 7초에 같이 결제된 Q-2205 뒤에 놓여 결제 순 누적이 7팩이 되고, 판매 등록 6팩을 1팩 넘습니다."
},
{
"order_id": "JK-706733",
"popup_id": "LA-1018",
"queue_no": "Q-0841",
"sku": "LS-V2",
"sku_name": "공식 응원봉 VER.2",
"qty": 1,
"paid_at": "2026-10-17T22:14:37-07:00",
"judged_at": "2026-10-18T21:30:00-07:00",
"verdict": "실입고가 모자라 못 받는 주문",
"listed_qty": 8,
"cum_qty_by_paid": 8,
"received_qty": 5,
"cum_qty_by_queue": 6,
"close_at": "2026-10-18T20:00:00-07:00",
"picked_up_at": null,
"evidence": [
{
"table": "jumki_orders",
"row": "JK-706733",
"field": "queue_no",
"value": "Q-0841"
},
{
"table": "jumki_orders",
"row": "JK-706733",
"field": "qty",
"value": 1
},
{
"table": "jumki_orders",
"row": "LA-1018/LS-V2",
"field": "qty",
"agg": "sum",
"order_by": "paid_at",
"value": 8
},
{
"table": "popup_stock",
"row": "LA-1018/LS-V2",
"field": "listed_qty",
"value": 8
},
{
"table": "jumki_orders",
"row": "LA-1018/LS-V2",
"field": "qty",
"agg": "sum",
"order_by": "queue_no",
"value": 6
},
{
"table": "popup_stock",
"row": "LA-1018/LS-V2",
"field": "received_qty",
"value": 5
},
{
"table": "jumki_orders",
"row": "JK-705118",
"field": "queue_no",
"value": "Q-0577"
},
{
"table": "jumki_orders",
"row": "JK-705118",
"field": "paid_at",
"value": "2026-10-17T21:40:18-07:00"
},
{
"table": "jumki_orders",
"row": "JK-706733",
"field": "paid_at",
"value": "2026-10-17T22:14:37-07:00"
}
],
"summary": "결제 순 누적 8개는 판매 등록 8개 안이지만, 대기 번호 순으로는 Q-0577 다음 6개째라 실입고 5개를 넘습니다."
},
{
"order_id": "JK-705402",
"popup_id": "LA-1018",
"queue_no": "Q-0620",
"sku": "KEY-LA",
"sku_name": "LA 한정 키링",
"qty": 1,
"paid_at": "2026-10-16T22:31:09-07:00",
"judged_at": "2026-10-18T21:30:00-07:00",
"verdict": "실입고가 모자라 못 받는 주문",
"listed_qty": 3,
"cum_qty_by_paid": 1,
"received_qty": 0,
"cum_qty_by_queue": 1,
"close_at": "2026-10-18T20:00:00-07:00",
"picked_up_at": null,
"evidence": [
{
"table": "jumki_orders",
"row": "JK-705402",
"field": "queue_no",
"value": "Q-0620"
},
{
"table": "jumki_orders",
"row": "JK-705402",
"field": "qty",
"value": 1
},
{
"table": "jumki_orders",
"row": "LA-1018/KEY-LA",
"field": "qty",
"agg": "sum",
"order_by": "paid_at",
"value": 1
},
{
"table": "popup_stock",
"row": "LA-1018/KEY-LA",
"field": "listed_qty",
"value": 3
},
{
"table": "jumki_orders",
"row": "LA-1018/KEY-LA",
"field": "qty",
"agg": "sum",
"order_by": "queue_no",
"value": 1
},
{
"table": "popup_stock",
"row": "LA-1018/KEY-LA",
"field": "received_qty",
"value": 0
}
],
"summary": "LA 한정 키링은 통관 보류로 실입고가 0개라, 대기 번호가 가장 앞선 이 주문부터 받을 수 없습니다."
},
{
"order_id": "JK-708462",
"popup_id": "LA-1018",
"queue_no": "Q-1127",
"sku": "TS-L",
"sku_name": "투어 티셔츠 L",
"qty": 1,
"paid_at": "2026-10-17T13:22:08-07:00",
"judged_at": "2026-10-18T21:30:00-07:00",
"verdict": "마감까지 찾아가지 않은 주문",
"listed_qty": 4,
"cum_qty_by_paid": 4,
"received_qty": 4,
"cum_qty_by_queue": 4,
"close_at": "2026-10-18T20:00:00-07:00",
"picked_up_at": null,
"evidence": [
{
"table": "jumki_orders",
"row": "JK-708462",
"field": "queue_no",
"value": "Q-1127"
},
{
"table": "jumki_orders",
"row": "JK-708462",
"field": "qty",
"value": 1
},
{
"table": "jumki_orders",
"row": "LA-1018/TS-L",
"field": "qty",
"agg": "sum",
"order_by": "paid_at",
"value": 4
},
{
"table": "popup_stock",
"row": "LA-1018/TS-L",
"field": "listed_qty",
"value": 4
},
{
"table": "jumki_orders",
"row": "LA-1018/TS-L",
"field": "qty",
"agg": "sum",
"order_by": "queue_no",
"value": 4
},
{
"table": "popup_stock",
"row": "LA-1018/TS-L",
"field": "received_qty",
"value": 4
},
{
"table": "popup_stock",
"row": "LA-1018/TS-L",
"field": "close_at",
"value": "2026-10-18T20:00:00-07:00"
},
{
"table": "pickups",
"row": "JK-708462",
"field": "called_at",
"value": "2026-10-18T16:05:12-07:00"
},
{
"table": "pickups",
"row": "JK-708462",
"field": "picked_up_at",
"value": null
}
],
"summary": "16시 5분에 번호를 불렀지만 건넨 시각이 비어 있고, 1일차 마감 20시가 지났습니다."
},
{
"order_id": "JK-709904",
"popup_id": "LA-1018",
"queue_no": "Q-1376",
"sku": "TW-SLOGAN",
"sku_name": "슬로건 타월",
"qty": 2,
"paid_at": "2026-10-17T16:40:27-07:00",
"judged_at": "2026-10-18T21:30:00-07:00",
"verdict": "마감까지 찾아가지 않은 주문",
"listed_qty": 6,
"cum_qty_by_paid": 5,
"received_qty": 6,
"cum_qty_by_queue": 5,
"close_at": "2026-10-18T20:00:00-07:00",
"picked_up_at": null,
"evidence": [
{
"table": "jumki_orders",
"row": "JK-709904",
"field": "queue_no",
"value": "Q-1376"
},
{
"table": "jumki_orders",
"row": "JK-709904",
"field": "qty",
"value": 2
},
{
"table": "jumki_orders",
"row": "LA-1018/TW-SLOGAN",
"field": "qty",
"agg": "sum",
"order_by": "paid_at",
"value": 5
},
{
"table": "popup_stock",
"row": "LA-1018/TW-SLOGAN",
"field": "listed_qty",
"value": 6
},
{
"table": "jumki_orders",
"row": "LA-1018/TW-SLOGAN",
"field": "qty",
"agg": "sum",
"order_by": "queue_no",
"value": 5
},
{
"table": "popup_stock",
"row": "LA-1018/TW-SLOGAN",
"field": "received_qty",
"value": 6
},
{
"table": "popup_stock",
"row": "LA-1018/TW-SLOGAN",
"field": "close_at",
"value": "2026-10-18T20:00:00-07:00"
},
{
"table": "pickups",
"row": null,
"field": "order_id",
"agg": "count",
"value": 0
}
],
"summary": "도착을 누른 기록이 없어 카운터 기록이 비어 있고, 1일차 마감 20시가 지났습니다."
},
{
"order_id": "JK-703301",
"popup_id": "LA-1018",
"queue_no": "Q-0263",
"sku": "TW-SLOGAN",
"sku_name": "슬로건 타월",
"qty": 1,
"paid_at": "2026-10-16T10:05:44-07:00",
"judged_at": "2026-10-18T21:30:00-07:00",
"verdict": "카운터에서 받을 수 있음",
"listed_qty": 6,
"cum_qty_by_paid": 3,
"received_qty": 6,
"cum_qty_by_queue": 3,
"close_at": "2026-10-18T20:00:00-07:00",
"picked_up_at": "2026-10-18T20:00:00-07:00",
"evidence": [
{
"table": "jumki_orders",
"row": "JK-703301",
"field": "queue_no",
"value": "Q-0263"
},
{
"table": "jumki_orders",
"row": "JK-703301",
"field": "qty",
"value": 1
},
{
"table": "jumki_orders",
"row": "LA-1018/TW-SLOGAN",
"field": "qty",
"agg": "sum",
"order_by": "paid_at",
"value": 3
},
{
"table": "popup_stock",
"row": "LA-1018/TW-SLOGAN",
"field": "listed_qty",
"value": 6
},
{
"table": "jumki_orders",
"row": "LA-1018/TW-SLOGAN",
"field": "qty",
"agg": "sum",
"order_by": "queue_no",
"value": 3
},
{
"table": "popup_stock",
"row": "LA-1018/TW-SLOGAN",
"field": "received_qty",
"value": 6
},
{
"table": "popup_stock",
"row": "LA-1018/TW-SLOGAN",
"field": "close_at",
"value": "2026-10-18T20:00:00-07:00"
},
{
"table": "pickups",
"row": "JK-703301",
"field": "picked_up_at",
"value": "2026-10-18T20:00:00-07:00"
}
],
"summary": "마감 시각 20시 정각에 건넨 기록이 있어 찾아간 주문으로 봅니다."
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 카운터에서 받을 수 있음·앱이 판매 수량을 넘겨 판 주문·실입고가 모자라 못 받는 주문·마감까지 찾아가지 않은 주문 네 값 가운데 하나인지 확인합니다. 2. 필수 필드 order_id·popup_id·queue_no·sku·sku_name·qty·paid_at·judged_at·verdict·listed_qty·cum_qty_by_paid·received_qty·cum_qty_by_queue·close_at·picked_up_at·evidence·summary 가 모두 존재하는지 확인합니다. 3. qty 와 cum_qty_by_paid 는 1 이상, listed_qty 와 received_qty 는 0 이상인지 확인합니다. 4. 앱이 판매 수량을 넘겨 판 주문: 같은 popup_id·sku 의 jumki_orders 를 paid_at 순(같은 시각이면 queue_no 가 작은 주문 먼저)으로 세워 이 주문까지 qty 를 더한 값으로 cum_qty_by_paid 를 다시 계산하고, cum_qty_by_paid 가 listed_qty 보다 크면 verdict 는 앱이 판매 수량을 넘겨 판 주문입니다. 21:03:07 에 같이 결제된 두 주문 가운데 대기 번호가 뒤인 Q-2207 은 누적 7 이 판매 수량 6 을 넘어 걸리고, Q-2205 는 누적 5 라 걸리지 않습니다. 응원봉 Q-0841 은 결제 순 누적 8 이 판매 수량 8 과 같아 걸리지 않습니다. 5. 실입고가 모자라 못 받는 주문: 4번에 걸린 주문을 뺀 같은 popup_id·sku 의 jumki_orders 를 queue_no 순으로 세워 이 주문까지 qty 를 더한 값으로 cum_qty_by_queue 를 다시 계산하고, 4번에 걸리지 않은 주문의 cum_qty_by_queue 가 received_qty 보다 크면 verdict 는 실입고가 모자라 못 받는 주문입니다. Q-0841 은 Q-0031·Q-0204·Q-0577 다음 누적 6 이 실입고 5 를 넘어 걸리고, Q-0912 보다 결제가 늦은 Q-0577 은 대기 번호가 앞서 누적 5 로 실입고와 같아 걸리지 않습니다. 실입고가 0 인 LA 한정 키링(KEY-LA)은 첫 주문 Q-0620 부터 걸립니다. 4번에 걸린 Q-2207 은 cum_qty_by_queue 가 null 입니다. 6. 마감까지 찾아가지 않은 주문: 4·5번에 걸리지 않은 주문에서 judged_at 이 close_at 보다 늦고 pickups 에 같은 order_id 의 picked_up_at 이 없으면 verdict 는 마감까지 찾아가지 않은 주문입니다. judged_at·close_at·pickups.picked_up_at 으로 이 판정을 다시 계산합니다. 번호를 불렀지만 picked_up_at 이 null 인 Q-1127 과 pickups 에 행이 없는 Q-1376 이 걸리고, 마감 20:00:00 에 건넨 Q-0263 은 건넨 시각이 있어 걸리지 않습니다. close_at 이 judged_at 보다 늦은 LA-1019 의 Q-0019 는 카운터 기록이 없어도 걸리지 않습니다. 7. 한 주문에 여러 판정이 걸리면 4·5·6번 순으로 앞선 하나가 verdict 입니다. 카운터 기록 없이 마감이 지난 Q-2207 은 4번이, Q-0912 는 5번이 verdict 가 되는지 다시 계산합니다. 8. evidence 의 table·field 가 jumki_orders·popup_stock·pickups 에 실재하는 컬럼인지 대조합니다. 9. agg 가 없는 evidence value 가 그 row 의 그 컬럼에 수록된 원문 값 그대로인지 확인합니다. 10. summary 가 존댓말로 끝나는지 확인합니다. [수동 검증 · 사람이 봅니다] 1. 걸린 주문을 환불할지, 2일차나 다음 도시 팝업 픽업으로 돌릴지는 현장 매니저와 기획사 MD 담당자가 정합니다. 2. 마감까지 찾아가지 않은 주문의 수량을 다음 날 현장 판매로 풀지는 마감 뒤 기획사 MD 담당자가 카운터에 남은 수량을 보고 정합니다. 3. received_qty 가 입고 검수표의 수량과 맞는지는 현장 매니저가 검수표로 확인합니다. [평가 러너 · 2주차] 1. 작성자가 판정을 손으로 매긴 주문 40건에서 verdict 가 정답과 같은 건수를 셉니다. 2. 동시 결제 200건 재현에서 재고 차감 기록으로 센 초과 판매 주문과 앱이 판매 수량을 넘겨 판 주문 판정이 같은 주문을 가리키는지 견줍니다. 초과 판매 건수는 재현마다 달라지는 런타임 측정값입니다. [판정식에 더한 정의 · 오너 확인 항목] 1. 카드 판정식은 결제 순만 적어 같은 시각에 결제된 주문의 순서가 비어 있었습니다. 이 편은 queue_no 가 작은 주문을 앞에 둡니다. 2. 대기 번호는 주문을 넣을 때 매기고 paid_at 은 결제를 마친 시각이라 두 순서가 다를 수 있다고 봅니다. Q-0577·Q-0912 가 그 사례입니다. 3. 카드는 판매 수량 초과가 먼저라고만 적었습니다. 실입고 부족과 미수령의 순서는 이 편이 정했고, 건넬 물건이 없는 주문은 찾아가지 않은 것으로 볼 수 없어 실입고 부족을 앞에 둡니다. 4. 마감이 지났는지는 매니저가 고른 판정 기준 시각(judged_at)으로 봅니다. 마감 시각과 같은 시각에 건넨 주문은 건넨 기록이 있으므로 걸리지 않습니다. 5. '누적 합계가 판매 수량·실입고를 넘는' 은 같은 값을 포함하지 않습니다. 6. 굿즈 이름이 없으면 화면이 굿즈를 코드로만 보이고, 번호를 부른 시각이 없으면 불렀는데 오지 않은 주문(Q-1127)과 도착을 누르지 않은 주문(Q-1376)을 가를 수 없고, 앱 언어가 없으면 3주차 안내문 초안을 팬의 언어로 쓸 수 없습니다. 이 셋 때문에 카드 inputs 에 없던 popup_stock.sku_name, pickups.called_at, jumki_orders.app_locale 을 더했고, 판정에는 쓰지 않습니다.