한국모바일결제
경력근거: 모니터링과 알림을 정비해 결제 장애를 사장님보다 먼저 알아차리는 구조 만들기
※ 실제 산출물이 아닌 AI 예측 시안입니다.
한국모바일결제는 이 자리의 첫 번째 일을 전국 매장의 키오스크 결제가 멈추지 않게 하는 것으로 적고, 입사 후 90일 안에 모니터링과 알림을 정비해 결제 장애를 사장님보다 먼저 알아차리는 구조를 만들라고 합니다. 셀프빨래방은 무인 매장이라 결제가 멈춰도 알려 줄 직원이 없고, 손님이 적은 심야와 붐비는 주말 저녁의 결제 간격이 크게 달라 매장·시간대별 기준선 없이는 장애를 가려낼 수 없습니다. 이 포트폴리오는 바로의 결제·키오스크 응답·계좌이체 충전 기록으로 매장별 기준선을 세워 판정하는 Nest.js 서비스와, CS 와 엔지니어가 같이 보는 Next.js 어드민 화면을 보여줍니다. 실제 PG 연동이나 키오스크 하드웨어, 전자금융 보안 점검에 대한 전문성은 주장하지 않습니다.
공고는 입사 후 90일 안에 할 일로 모니터링과 알림을 정비해 결제 장애를 사장님보다 먼저 알아차리는 구조를 꼽고, 전국 매장의 키오스크 결제가 멈추지 않게 하는 것을 이 자리의 첫 번째 일로 적었습니다. 이 포트폴리오는 그 구조의 판정 부분, 곧 어느 매장의 결제가 멈췄는지 가려내는 규칙과 어드민 화면을 만듭니다.
주요업무에 키오스크·단말기 연동, PG 결제·취소·정산 연동, 계좌이체 충전이 함께 있습니다. 카드 승인 실패와 키오스크 응답, 이체 충전 반영을 한 매장 판정으로 묶는 이 포트폴리오의 세 입력이 그 범위와 같습니다.
자격요건은 Node.js(Nest.js, TypeScript) 백엔드와 배포·롤백·모니터링·장애 대응 경험이고, 우대사항에 ECS 배포와 IoT Core·MQTT 개발, Jest 테스트가 있습니다. 키오스크 응답을 MQTT 로 받아 Nest.js 판정 서비스를 ECS 에 올리고 판정 규칙을 Jest 로 고정하는 구성이 여기에 닿습니다.
바로는 한국모바일결제가 운영하는 셀프빨래방 키오스크 결제솔루션으로, 전국 400여 개 매장에서 손님이 카드·간편결제·계좌이체로 충전하고 세탁기를 돌립니다. 이 어드민 화면은 키오스크 결제 기록, 키오스크별 마지막 응답 시각, 계좌이체 충전 기록 세 가지를 받아 매장마다 다섯 판정 중 하나를 붙입니다. 지난 4주 같은 요일 같은 시간대 결제 간격의 중앙값으로 그 매장의 평소 결제 속도를 잡고, 최근 30분 카드 승인 실패율을 지난 4주 실패율과 견주며, 키오스크 응답과 이체 반영이 몇 분 늦었는지 셉니다. AI 프로덕트 엔지니어와 본사 CS 담당자는 같은 화면을 보고 어느 매장 사장님께 먼저 연락할지, 어느 매장은 서버와 PG 쪽을 볼지 나눕니다.
전국 바로 매장의 결제 장애를 맡는 한국모바일결제 AI 프로덕트 엔지니어와 점주 전화를 받는 본사 CS 담당자 — 같은 어드민 화면을 보고 누가 사장님께 먼저 연락하고 누가 서버·PG 를 볼지 나눈다
지금은 결제가 멈춘 매장을 사장님 전화로 알게 되는 일이 많습니다. 키오스크가 응답은 하는데 카드 리더기에서 결제만 멈춘 매장은 서버 쪽에서 보면 멀쩡해 보이고, 늦은 밤 손님이 적은 매장은 결제가 한 시간 없어도 장애인지 원래 한가한지 가르기 어렵습니다. 매장마다 같은 요일·시간대의 평소 결제 간격과 평소 실패율을 기준선으로 잡으면, 결제 기록만으로 멈춘 매장을 사장님 전화보다 먼저 세울 수 있습니다.
엔지니어가 키오스크 결제 기록, 키오스크 응답 기록, 계좌이체 충전 기록 세 파일을 올리면 매장마다 다섯 판정 중 하나와 마지막 결제 뒤 경과 시간·평소 결제 간격·최근 실패율·이체 반영 시간이 서는 Next.js 어드민 화면입니다. 지난 4주 같은 요일·시간대 결제 간격 중앙값과 4주 실패율을 매장별 기준선으로 계산하는 Nest.js 판정 서비스가 이 주에 들어갑니다. 실제 PG 와 키오스크는 연결하지 않고 결제 기록 파일로만 판정합니다.
지원자 계정의 AWS IoT Core 에 가상 키오스크 60대를 붙여 MQTT 로 응답과 결제 이벤트를 보내고, ECS 에 올린 Nest.js 서비스가 1분마다 매장 판정을 다시 계산합니다. 더미 매장 60곳을 판정 시각 네 번씩 찍은 240 판정에 손으로 정답을 붙여 대조하고, 판정 규칙은 Jest 로 고정합니다.
판정이 바뀐 매장마다 CS 가 먼저 사장님께 연락할 건과 엔지니어가 서버·PG 를 볼 건으로 나눠 담당 칸을 채우고, Claude 가 CS 용 사장님 안내 문안 초안과 엔지니어용 원인 메모 초안을 씁니다. 문안은 화면에서 복사해 쓰는 초안이고 문자 발송은 붙이지 않습니다.
엔지니어가 금요일 21시 40분 기록을 올리면 부산 해운대점이 키오스크는 응답하는데 108분째 결제가 없어 결제 끊김으로, 대전 둔산점이 30분 사이 7건 중 5건 승인 실패로 승인 실패 몰림으로, 인천 송도2호점이 K-2 응답 28분 끊김으로 맨 위에 서는 과정을 시연합니다.
CS 담당자가 서울 망원점을 펼쳐 21시 31분 이체 1만 원이 9분째 충전되지 않은 것을 읽고, 엔지니어가 같은 화면에서 대전 둔산점 실패 다섯 건이 모두 K-2 에서 난 것을 보고 단말부터 볼지 정합니다.
충전 반영 시각이 빈 이체와 입금 20초째라 아직 반영 전인 이체, 정확히 10분 전 응답과 정확히 60초 반영, 4주 기록 없이 실패가 몰린 새 매장, 이미 반영이 끝난 3분 20초 지연 이체를 금요일 밤 기록 한 벌에 넣으면 매장마다 판정이 하나씩 서고, 결제 시각이 비거나 결과 코드가 잘린 결제 기록 12종을 섞어도 그 줄만 제외되고 매장 판정은 남은 기록으로 다시 섭니다.
2주차 정답 대조 탭은 더미 매장 60곳을 판정 시각 네 번씩 찍은 240 판정을 띄우고, 정답과 갈린 판정이 4배·3배·10분·1분 문턱 가운데 어디에 몰렸는지 보여줍니다. 결제 기록과 정답을 함께 지었으므로 이 대조가 말하는 것은 다섯 규칙과 판정 차례가 문서와 같이 돈다는 것이고, 실제 장애를 놓치지 않는다는 것은 아닙니다.
위 수치는 더미데이터 자가채점이며, 사장님 전화보다 몇 분 먼저 알아차리는지는 바로 매장의 실제 결제·키오스크 응답·이체 기록과 사장님 장애 문의가 들어온 시각이 있어야 측정됩니다. 실제 결제·응답·이체 기록을 같은 세 표로 내려받으면 판정 서비스를 고치지 않고 다시 돌릴 수 있습니다.
240 판정의 정답은 사장님과 통화해 본 CS 담당자가 아니라 결제 기록을 지은 제가 적었으므로, 일치 수는 실제 장애를 사장님 전화보다 먼저 잡는다는 근거가 되지 못합니다. 시연에서 내세우는 것은 반영이 빈 TR-9009 나 정확히 10분 전에 응답한 광주 첨단점이 어느 판정으로 가는지입니다.
결제가 고르게 들어오는 매장만 있으면 마지막 결제 시각만 봐도 판정이 갈려, 기준선 계산이 틀려도 드러나지 않습니다. 금요일 21시 40분 한 시점에 키오스크는 살아 있는데 결제만 멈춘 해운대점, K-2 한 대에서만 실패가 난 둔산점, 한 대만 응답이 끊긴 송도2호점, 4주 기록 없이 실패가 몰린 동성로점, 기준과 정확히 같은 값을 가진 첨단점, 입금 20초째라 아직 반영 전인 성수점 이체를 겹쳐 두어 규칙 하나가 어긋나면 그 매장의 판정이 바뀌게 했습니다.
결제 간격 기준선은 지난 4주 같은 요일 한 시간 안의 결제로 잡아 표본이 작습니다. 비 오는 날이나 명절처럼 손님이 원래 적은 밤에는 멀쩡한 매장도 결제 끊김에 걸릴 수 있어, 판정은 전화할 매장을 세우는 데서 멈추고 장애인지는 CS 가 사장님과 통화해 정합니다.
이 포트폴리오는 실제 PG 와 키오스크에 연결하지 않고 결제 기록 파일과 가상 키오스크로만 판정합니다. 실서비스에서는 PG 응답 코드 체계와 키오스크 응답 주기가 다를 수 있어, 결과 코드 목록과 응답 문턱은 실제 기록을 본 뒤 맞출 도입 과제로 둡니다.
결제 간격 기준선은 매장 단위라, 키오스크 세 대 중 한 대에서만 결제가 멈추면 나머지 두 대의 결제가 간격을 메워 결제 끊김으로 잡히지 않습니다. 키오스크별 기준선으로 나눌지는 실제 매장의 키오스크당 결제량을 본 뒤 정합니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
바로 키오스크의 카드 결제 기록입니다. 결제 요청 한 건에 한 줄이고, result_code 가 0000 이면 승인, 나머지는 실패입니다. 기준선을 잡을 지난 4주 금요일 21시대 결제와 오늘 밤 결제를 함께 담았고, 한 키오스크에서만 승인 실패가 몰린 매장과 4주 기록이 없는 새 매장, 그 가운데 리더기 오류(E520)가 몰린 매장을 일부러 담았습니다.
| payment_no | store_id | kiosk_id | paid_at | amount | result_code |
|---|---|---|---|---|---|
| PAY-00001 | S-0101 | K-1 | 2026-09-04T21:05:00+09:00 | 5,000 | 0000 |
| PAY-00002 | S-0101 | K-2 | 2026-09-04T21:14:00+09:00 | 4,000 | 0000 |
| PAY-00003 | S-0101 | K-1 | 2026-09-11T21:10:00+09:00 | 5,000 | 0000 |
| PAY-00004 | S-0101 | K-1 | 2026-09-11T21:20:00+09:00 | 3,000 | 0000 |
| PAY-00005 | S-0101 | K-2 | 2026-09-18T21:02:00+09:00 | 5,000 | 0000 |
아래 JSON Schema 에 맞는 셀프빨래방 키오스크 결제 행을 더 만들어 주세요. {"type":"object","properties":{"payment_no":{"type":"string","description":"키오스크 결제 한 건의 번호입니다."},"store_id":{"type":"string","description":"바로 매장 번호입니다."},"kiosk_id":{"type":"string","description":"매장 안 키오스크 번호입니다."},"paid_at":{"type":"string","description":"결제를 요청한 시각입니다. +09:00 오프셋이 붙은 ISO 8601 입니다."},"amount":{"type":"integer","minimum":0,"description":"결제 금액(원)입니다."},"result_code":{"type":"string","description":"카드 승인 결과 코드입니다. 0000 이 승인이고 나머지는 실패입니다(E301 카드사 응답 시간 초과, E412 한도 초과, E520 리더기 오류)."}},"required":["payment_no","store_id","kiosk_id","paid_at","amount","result_code"]} 매장마다 키오스크 1~3대, 금액은 3,000~12,000원 사이 천 원 단위로 쓰고, 평일 낮은 한 시간에 2~5건, 주말·금요일 저녁은 한 시간에 6~12건이 되게 합니다. 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) 키오스크는 응답하는데 결제가 한 시간 넘게 없는 매장, (2) 한 키오스크에서만 30분 사이 실패가 5건 넘게 몰린 매장, (3) 지난 4주 같은 요일 같은 시간대 기록이 없는 새 매장, (4) 손님이 원래 적은 심야 매장. 생성한 행은 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"payment_no":{"type":"string","description":"키오스크 결제 한 건의 번호입니다."},"store_id":{"type":"string","description":"바로 매장 번호입니다."},"kiosk_id":{"type":"string","description":"매장 안 키오스크 번호입니다."},"paid_at":{"type":"string","description":"결제를 요청한 시각입니다. +09:00 오프셋이 붙은 ISO 8601 입니다."},"amount":{"type":"integer","minimum":0,"description":"결제 금액(원)입니다."},"result_code":{"type":"string","description":"카드 승인 결과 코드입니다. 0000 이 승인이고 나머지는 실패입니다(E301 카드사 응답 시간 초과, E412 한도 초과, E520 리더기 오류)."}},"required":["payment_no","store_id","kiosk_id","paid_at","amount","result_code"]}키오스크마다 서버에 마지막으로 응답한 시각입니다. 키오스크 한 대에 한 줄이고, 판정 기준 시각은 이 표의 가장 늦은 last_seen_at 입니다. 한 매장 안에서 한 대만 응답이 끊긴 경우와 정확히 10분 전에 마지막 응답한 키오스크를 일부러 담았습니다.
| store_id | store_name | kiosk_id | last_seen_at |
|---|---|---|---|
| S-0101 | 부산 해운대점 | K-1 | 2026-10-02T21:39:30+09:00 |
| S-0101 | 부산 해운대점 | K-2 | 2026-10-02T21:40:00+09:00 |
| S-0102 | 대전 둔산점 | K-1 | 2026-10-02T21:39:50+09:00 |
| S-0102 | 대전 둔산점 | K-2 | 2026-10-02T21:39:45+09:00 |
| S-0103 | 인천 송도2호점 | K-1 | 2026-10-02T21:39:40+09:00 |
아래 JSON Schema 에 맞는 키오스크 마지막 응답 행을 더 만들어 주세요. {"type":"object","properties":{"store_id":{"type":"string"},"store_name":{"type":"string"},"kiosk_id":{"type":"string"},"last_seen_at":{"type":"string","description":"키오스크가 서버에 마지막으로 응답한 시각입니다. +09:00 오프셋이 붙은 ISO 8601 입니다."}},"required":["store_id","store_name","kiosk_id","last_seen_at"]} 정상 키오스크는 판정 기준 시각 1분 안쪽으로 응답하게 쓰고, store_name 은 「지역 동네점」 꼴로 씁니다. 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) 한 매장 안에서 한 대만 10분 넘게 응답이 없는 경우, (2) 정확히 10분 전에 마지막 응답한 키오스크, (3) 매장 키오스크가 모두 끊긴 경우. 생성한 행은 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"store_id":{"type":"string"},"store_name":{"type":"string"},"kiosk_id":{"type":"string"},"last_seen_at":{"type":"string","description":"키오스크가 서버에 마지막으로 응답한 시각입니다. +09:00 오프셋이 붙은 ISO 8601 입니다."}},"required":["store_id","store_name","kiosk_id","last_seen_at"]}손님이 계좌이체로 충전한 기록입니다. deposit_at 은 입금이 확인된 시각, credited_at 은 키오스크 충전 잔액에 반영된 시각이고, 아직 반영되지 않았으면 credited_at 이 null 입니다. 반영이 정확히 60초 걸린 이체와 이미 반영됐지만 3분 넘게 걸린 이체, 판정 기준 시각 20초 전에 입금돼 아직 반영 전인 이체를 일부러 담았습니다.
| transfer_no | store_id | kiosk_id | amount | deposit_at | credited_at |
|---|---|---|---|---|---|
| TR-9001 | S-0102 | K-1 | 10,000 | 2026-10-02T20:30:00+09:00 | 2026-10-02T20:30:35+09:00 |
| TR-9002 | S-0101 | K-1 | 20,000 | 2026-10-02T20:44:00+09:00 | 2026-10-02T20:44:28+09:00 |
| TR-9003 | S-0107 | K-1 | 10,000 | 2026-10-02T20:50:00+09:00 | 2026-10-02T20:53:20+09:00 |
| TR-9004 | S-0102 | K-2 | 10,000 | 2026-10-02T20:58:00+09:00 | 2026-10-02T20:58:40+09:00 |
| TR-9005 | S-0103 | K-1 | 10,000 | 2026-10-02T21:00:00+09:00 | 2026-10-02T21:00:20+09:00 |
아래 JSON Schema 에 맞는 계좌이체 충전 행을 더 만들어 주세요. {"type":"object","properties":{"transfer_no":{"type":"string","description":"계좌이체 충전 요청 번호입니다."},"store_id":{"type":"string"},"kiosk_id":{"type":"string"},"amount":{"type":"integer","minimum":0},"deposit_at":{"type":"string","description":"손님 이체가 입금으로 확인된 시각입니다."},"credited_at":{"type":["string","null"],"description":"그 금액이 키오스크 충전 잔액에 반영된 시각입니다. 아직 반영되지 않았으면 null 입니다."}},"required":["transfer_no","store_id","kiosk_id","amount","deposit_at","credited_at"]} 금액은 1만~5만 원 사이 만 원 단위로 쓰고, 정상 반영은 입금 뒤 10~50초 안에 끝나게 합니다. 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) credited_at 이 null 인 이체, (2) 반영이 정확히 60초 걸린 이체, (3) 이미 반영됐지만 1분 넘게 걸린 이체, (3-1) 판정 기준 시각 1분 안에 입금돼 아직 credited_at 이 null 인 이체, (4) 판정 기준 시각보다 60분 넘게 앞서 판정 범위 밖에 있는 이체. 생성한 행은 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"transfer_no":{"type":"string","description":"계좌이체 충전 요청 번호입니다."},"store_id":{"type":"string"},"kiosk_id":{"type":"string"},"amount":{"type":"integer","minimum":0},"deposit_at":{"type":"string","description":"손님 이체가 입금으로 확인된 시각입니다."},"credited_at":{"type":["string","null"],"description":"그 금액이 키오스크 충전 잔액에 반영된 시각입니다. 아직 반영되지 않았으면 null 입니다."}},"required":["transfer_no","store_id","kiosk_id","amount","deposit_at","credited_at"]}| Node.js | 결제 간격 기준선, 실패율 견줌, 응답·이체 지연 계산, 판정 차례를 담은 판정 서비스의 런타임입니다. |
| Nest.js | 세 기록을 받는 업로드 모듈과 매장 판정 API, 2주차 1분 주기 재계산 작업을 모듈로 나눈 백엔드입니다. |
| TypeScript | 판정 서비스와 어드민이 같은 판정 타입을 써서, 판정 칸이 바뀌면 양쪽 빌드에서 먼저 드러나게 합니다. |
| Next.js | 판정별 매장 목록과 매장 펼침 화면을 담은 어드민입니다. |
| Jest | 기준선 간격이 1개뿐인 매장, 정확히 10분 전 응답, 정확히 60초 반영, 한 매장 안에서 키오스크 한 대만 끊긴 경우를 테스트로 고정합니다. |
| AWS ECS | 2주차부터 Nest.js 판정 서비스를 ECS 에 배포해 운영하고, 배포·롤백 절차를 문서로 남깁니다. |
| AWS IoT Core·MQTT | 2주차에 가상 키오스크가 MQTT 로 보내는 응답과 결제 이벤트를 받아 응답 기록과 결제 기록에 쌓습니다. |
| React Native | 3주차 이후 점주앱에 같은 판정을 띄울 때 쓸 판정 API 응답 형태를 맞춰 둡니다. |
| Claude Sonnet | 3주차에 사장님 안내 문안과 엔지니어용 원인 메모 초안을 씁니다. |
| Claude Code | 판정 서비스·MQTT 수신·어드민 작업을 나눠 맡기고, 정답 판정 대조와 Jest 를 통과한 코드만 배포합니다. |
✗ fail → CS 가 전화하고 싶은 매장이 묶음 아래에 깔리면 결제 멈춤과 승인 실패 몰림의 앞뒤를 바꿔 다시 돌려 보고, 둘 다 걸린 매장은 상세 칸에 두 사유를 함께 띄웁니다.
✓ pass → MQTT 가상 키오스크와 ECS 배포, 240 판정 정답 대조로 2주차를 엽니다.
✗ fail → 깜빡이는 매장만 모아 4배·3배 문턱과 30분 창 길이를 다시 봅니다.
✓ pass → 3주차 담당 나누기와 사장님 안내 문안으로 넘어갑니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · store_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
키오스크 응답 끊김키오스크 무응답결제가 끊김결제 멈춤카드 승인 실패가 몰림승인 실패 몰림계좌이체 충전 반영 지연이체 충전 지연정상 결제 중정상바로 매장 하나가 키오스크 응답 지연, 평소 결제 간격 대비 공백, 승인 실패율 견줌, 계좌이체 반영 지연을 거쳐 어떤 판정을 받는지 남기는 형식입니다. 매장 판정은 간격·비율·시각 계산이 내리고, 사장님 안내 문안과 원인 메모를 쓰는 3주차에만 모델을 부릅니다.
{"type":"object","properties":{"store_id":{"type":"string"},"store_name":{"type":"string"},"verdict":{"type":"string","enum":["정상 결제 중","결제가 끊김","카드 승인 실패가 몰림","키오스크 응답 끊김","계좌이체 충전 반영 지연"]},"as_of":{"type":"string","description":"판정 기준 시각입니다. kiosk_heartbeats 에서 가장 늦은 last_seen_at 입니다."},"stale_kiosk_ids":{"type":"array","items":{"type":"string"},"description":"last_seen_at 이 as_of 보다 10분 넘게 앞선 키오스크입니다."},"oldest_last_seen_at":{"type":"string"},"minutes_since_oldest_heartbeat":{"type":"number","minimum":0,"description":"as_of − 이 매장 키오스크 last_seen_at 최솟값(분)을 소수 첫째 자리로 반올림합니다(0.25 → 0.3)."},"last_paid_at":{"type":["string","null"]},"minutes_since_last_payment":{"type":["number","null"],"minimum":0,"description":"as_of − 이 매장 paid_at 최댓값(분)을 소수 첫째 자리로 반올림합니다."},"baseline_interval_count":{"type":"integer","minimum":0,"description":"지난 4주 같은 요일의 as_of 와 같은 시(時)에 찍힌 결제를 날마다 시각순으로 세워 얻은 이웃 간격 수입니다."},"baseline_median_interval_min":{"type":["number","null"],"minimum":0,"description":"그 간격들의 중앙값(분)입니다. 간격이 2개 미만이면 null 이고 결제 끊김은 보지 않습니다."},"recent_30m_payments":{"type":"integer","minimum":0},"recent_30m_failures":{"type":"integer","minimum":0,"description":"as_of 앞 30분 결제 가운데 result_code 가 0000 이 아닌 건수입니다."},"recent_fail_rate_pct":{"type":["number","null"],"minimum":0,"maximum":100},"baseline_payments":{"type":"integer","minimum":0,"description":"as_of 28일 전 0시부터 as_of 30분 전까지 이 매장 결제 건수입니다."},"baseline_failures":{"type":"integer","minimum":0},"baseline_fail_rate_pct":{"type":["number","null"],"minimum":0,"maximum":100,"description":"baseline_failures / baseline_payments × 100 입니다. 지난 4주 결제가 없으면 null 이고, 실패 몰림 판정에서는 0 으로 봅니다."},"delayed_transfer_no":{"type":["string","null"],"description":"as_of 앞 60분 안에 입금된 이체 가운데 반영이 1분을 넘었거나, 아직 반영되지 않은 채 입금 뒤 1분이 지난 건입니다."},"credit_delay_sec":{"type":["integer","null"],"minimum":0,"description":"credited_at − deposit_at(초)입니다. credited_at 이 null 이면 null 입니다."},"uncredited_elapsed_sec":{"type":["integer","null"],"minimum":0,"description":"credited_at 이 null 일 때 as_of − deposit_at(초)입니다."},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{}},"required":["field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["store_id","store_name","verdict","as_of","stale_kiosk_ids","oldest_last_seen_at","minutes_since_oldest_heartbeat","last_paid_at","minutes_since_last_payment","baseline_interval_count","baseline_median_interval_min","recent_30m_payments","recent_30m_failures","recent_fail_rate_pct","baseline_payments","baseline_failures","baseline_fail_rate_pct","delayed_transfer_no","evidence","summary"]}[
{
"store_id": "S-0101",
"store_name": "부산 해운대점",
"verdict": "결제가 끊김",
"as_of": "2026-10-02T21:40:00+09:00",
"stale_kiosk_ids": [],
"oldest_last_seen_at": "2026-10-02T21:39:30+09:00",
"minutes_since_oldest_heartbeat": 0.5,
"last_paid_at": "2026-10-02T19:52:00+09:00",
"minutes_since_last_payment": 108,
"baseline_interval_count": 4,
"baseline_median_interval_min": 11,
"recent_30m_payments": 0,
"recent_30m_failures": 0,
"recent_fail_rate_pct": null,
"baseline_payments": 10,
"baseline_failures": 0,
"baseline_fail_rate_pct": 0,
"delayed_transfer_no": null,
"credit_delay_sec": null,
"uncredited_elapsed_sec": null,
"evidence": [
{
"field": "paid_at",
"value": "2026-10-02T19:52:00+09:00"
},
{
"field": "last_seen_at",
"value": "2026-10-02T21:40:00+09:00"
},
{
"field": "paid_at",
"agg": "count",
"value": 8
}
],
"summary": "부산 해운대점은 키오스크 두 대가 1분 안에 응답하는데 마지막 결제가 19시 52분이라 108분째 결제가 없고, 지난 4주 금요일 21시대 결제 간격 중앙값 11분의 4배(44분)를 넘었습니다.",
"hedge": "키오스크는 살아 있으니 카드 리더기나 결제 화면에서 멈췄을 가능성이 큽니다. 같은 시간대 결제 8건에서 간격 4개를 뽑은 기준선이라, 비 오는 날처럼 손님이 원래 적은 날에도 걸릴 수 있습니다."
},
{
"store_id": "S-0102",
"store_name": "대전 둔산점",
"verdict": "카드 승인 실패가 몰림",
"as_of": "2026-10-02T21:40:00+09:00",
"stale_kiosk_ids": [],
"oldest_last_seen_at": "2026-10-02T21:39:45+09:00",
"minutes_since_oldest_heartbeat": 0.3,
"last_paid_at": "2026-10-02T21:38:00+09:00",
"minutes_since_last_payment": 2,
"baseline_interval_count": 6,
"baseline_median_interval_min": 10,
"recent_30m_payments": 7,
"recent_30m_failures": 5,
"recent_fail_rate_pct": 71.4,
"baseline_payments": 10,
"baseline_failures": 1,
"baseline_fail_rate_pct": 10,
"delayed_transfer_no": null,
"credit_delay_sec": null,
"uncredited_elapsed_sec": null,
"evidence": [
{
"field": "result_code",
"value": "E301"
},
{
"field": "result_code",
"agg": "count",
"value": 5
},
{
"field": "payment_no",
"agg": "count",
"value": 7
},
{
"field": "payment_no",
"agg": "count",
"value": 10
}
],
"summary": "대전 둔산점은 최근 30분 결제 7건 중 5건이 카드사 응답 시간 초과(E301)로 실패해 실패율 71.4% 이고, 지난 4주 평균 10.0% 의 3배를 넘었습니다.",
"hedge": "실패 다섯 건이 모두 K-2 에서 났습니다. 같은 매장 K-1 은 승인되고 있어 PG 장애보다 K-2 단말의 통신 문제를 먼저 의심할 만합니다."
},
{
"store_id": "S-0103",
"store_name": "인천 송도2호점",
"verdict": "키오스크 응답 끊김",
"as_of": "2026-10-02T21:40:00+09:00",
"stale_kiosk_ids": [
"K-2"
],
"oldest_last_seen_at": "2026-10-02T21:12:00+09:00",
"minutes_since_oldest_heartbeat": 28,
"last_paid_at": "2026-10-02T21:30:00+09:00",
"minutes_since_last_payment": 10,
"baseline_interval_count": 0,
"baseline_median_interval_min": null,
"recent_30m_payments": 1,
"recent_30m_failures": 0,
"recent_fail_rate_pct": 0,
"baseline_payments": 0,
"baseline_failures": 0,
"baseline_fail_rate_pct": null,
"delayed_transfer_no": null,
"credit_delay_sec": null,
"uncredited_elapsed_sec": null,
"evidence": [
{
"field": "kiosk_id",
"value": "K-2"
},
{
"field": "last_seen_at",
"value": "2026-10-02T21:12:00+09:00"
}
],
"summary": "인천 송도2호점의 키오스크 K-2 는 21시 12분 뒤로 응답이 없어 28분째 끊겨 있습니다.",
"hedge": "같은 매장 K-1 은 21시 30분에 결제를 받았고 지금도 응답합니다. 매장 전체가 아니라 한 대만 멈춘 상황이라 사장님께 전원 확인을 부탁할 수 있습니다."
},
{
"store_id": "S-0104",
"store_name": "서울 망원점",
"verdict": "계좌이체 충전 반영 지연",
"as_of": "2026-10-02T21:40:00+09:00",
"stale_kiosk_ids": [],
"oldest_last_seen_at": "2026-10-02T21:39:20+09:00",
"minutes_since_oldest_heartbeat": 0.7,
"last_paid_at": "2026-10-02T21:35:00+09:00",
"minutes_since_last_payment": 5,
"baseline_interval_count": 0,
"baseline_median_interval_min": null,
"recent_30m_payments": 1,
"recent_30m_failures": 0,
"recent_fail_rate_pct": 0,
"baseline_payments": 0,
"baseline_failures": 0,
"baseline_fail_rate_pct": null,
"delayed_transfer_no": "TR-9009",
"credit_delay_sec": null,
"uncredited_elapsed_sec": 540,
"evidence": [
{
"field": "transfer_no",
"value": "TR-9009"
},
{
"field": "deposit_at",
"value": "2026-10-02T21:31:00+09:00"
},
{
"field": "credited_at",
"value": null
}
],
"summary": "서울 망원점에서 21시 31분에 들어온 계좌이체 1만 원이 9분째 키오스크 충전으로 반영되지 않았습니다.",
"hedge": "입금은 확인됐는데 credited_at 이 비어 있어, 손님이 이체 화면 앞에서 기다리고 있을 수 있습니다. 이 매장은 지난 4주 금요일 21시대 결제 기록이 없어 결제 끊김은 보지 않았습니다."
},
{
"store_id": "S-0107",
"store_name": "수원 영통점",
"verdict": "계좌이체 충전 반영 지연",
"as_of": "2026-10-02T21:40:00+09:00",
"stale_kiosk_ids": [],
"oldest_last_seen_at": "2026-10-02T21:39:10+09:00",
"minutes_since_oldest_heartbeat": 0.8,
"last_paid_at": "2026-10-02T21:33:00+09:00",
"minutes_since_last_payment": 7,
"baseline_interval_count": 0,
"baseline_median_interval_min": null,
"recent_30m_payments": 1,
"recent_30m_failures": 0,
"recent_fail_rate_pct": 0,
"baseline_payments": 0,
"baseline_failures": 0,
"baseline_fail_rate_pct": null,
"delayed_transfer_no": "TR-9003",
"credit_delay_sec": 200,
"uncredited_elapsed_sec": null,
"evidence": [
{
"field": "deposit_at",
"value": "2026-10-02T20:50:00+09:00"
},
{
"field": "credited_at",
"value": "2026-10-02T20:53:20+09:00"
}
],
"summary": "수원 영통점의 20시 50분 계좌이체 1만 원은 3분 20초 뒤에야 충전에 반영돼, 1분 기준을 넘었습니다.",
"hedge": "지금은 반영이 끝난 건이라 손님 환불 문의보다는 충전 처리 큐가 왜 밀렸는지를 엔지니어가 볼 일입니다."
},
{
"store_id": "S-0105",
"store_name": "서울 성수점",
"verdict": "정상 결제 중",
"as_of": "2026-10-02T21:40:00+09:00",
"stale_kiosk_ids": [],
"oldest_last_seen_at": "2026-10-02T21:39:55+09:00",
"minutes_since_oldest_heartbeat": 0.1,
"last_paid_at": "2026-10-02T21:36:00+09:00",
"minutes_since_last_payment": 4,
"baseline_interval_count": 2,
"baseline_median_interval_min": 11,
"recent_30m_payments": 1,
"recent_30m_failures": 0,
"recent_fail_rate_pct": 0,
"baseline_payments": 4,
"baseline_failures": 0,
"baseline_fail_rate_pct": 0,
"delayed_transfer_no": null,
"credit_delay_sec": null,
"uncredited_elapsed_sec": null,
"evidence": [
{
"field": "paid_at",
"value": "2026-10-02T21:36:00+09:00"
},
{
"field": "deposit_at",
"value": "2026-10-02T21:34:30+09:00"
},
{
"field": "credited_at",
"value": "2026-10-02T21:35:02+09:00"
},
{
"field": "transfer_no",
"value": "TR-9011"
}
],
"summary": "서울 성수점은 4분 전 결제가 승인됐고, 반영이 끝난 계좌이체 두 건도 40초 안에 충전에 반영됐습니다.",
"hedge": "21시 39분 40초에 들어온 TR-9011 은 아직 충전 전이지만 입금 20초째라 1분 기준 안이어서 지연으로 세지 않았습니다. 1분이 지나도 반영이 없으면 다음 판정에서 이체 충전 지연으로 넘어갑니다."
},
{
"store_id": "S-0106",
"store_name": "광주 첨단점",
"verdict": "정상 결제 중",
"as_of": "2026-10-02T21:40:00+09:00",
"stale_kiosk_ids": [],
"oldest_last_seen_at": "2026-10-02T21:30:00+09:00",
"minutes_since_oldest_heartbeat": 10,
"last_paid_at": "2026-10-02T21:25:00+09:00",
"minutes_since_last_payment": 15,
"baseline_interval_count": 0,
"baseline_median_interval_min": null,
"recent_30m_payments": 1,
"recent_30m_failures": 0,
"recent_fail_rate_pct": 0,
"baseline_payments": 0,
"baseline_failures": 0,
"baseline_fail_rate_pct": null,
"delayed_transfer_no": null,
"credit_delay_sec": null,
"uncredited_elapsed_sec": null,
"evidence": [
{
"field": "last_seen_at",
"value": "2026-10-02T21:30:00+09:00"
},
{
"field": "deposit_at",
"value": "2026-10-02T21:05:00+09:00"
},
{
"field": "credited_at",
"value": "2026-10-02T21:06:00+09:00"
}
],
"summary": "광주 첨단점은 키오스크 마지막 응답이 정확히 10분 전이고 계좌이체 충전 반영이 정확히 1분 걸려, 두 기준 모두 넘지 않아 정상으로 봤습니다.",
"hedge": "기준은 10분과 1분을 넘을 때만 걸고 같은 값은 정상으로 셉니다. 이번 달 연 매장이라 지난 4주 기록이 없어 결제 끊김은 보지 않았습니다."
},
{
"store_id": "S-0108",
"store_name": "대구 동성로점",
"verdict": "카드 승인 실패가 몰림",
"as_of": "2026-10-02T21:40:00+09:00",
"stale_kiosk_ids": [],
"oldest_last_seen_at": "2026-10-02T21:39:50+09:00",
"minutes_since_oldest_heartbeat": 0.2,
"last_paid_at": "2026-10-02T21:37:00+09:00",
"minutes_since_last_payment": 3,
"baseline_interval_count": 0,
"baseline_median_interval_min": null,
"recent_30m_payments": 6,
"recent_30m_failures": 5,
"recent_fail_rate_pct": 83.3,
"baseline_payments": 0,
"baseline_failures": 0,
"baseline_fail_rate_pct": null,
"delayed_transfer_no": null,
"credit_delay_sec": null,
"uncredited_elapsed_sec": null,
"evidence": [
{
"field": "result_code",
"value": "E520"
},
{
"field": "result_code",
"agg": "count",
"value": 5
},
{
"field": "payment_no",
"agg": "count",
"value": 6
}
],
"summary": "대구 동성로점은 최근 30분 결제 6건 중 5건이 리더기 오류(E520)로 실패했습니다. 지난 4주 기록이 없는 새 매장이라 기준 실패율을 0 으로 두고 실패 5건 기준으로 걸었습니다.",
"hedge": "21시 15분 첫 결제만 승인되고 뒤 다섯 건이 모두 E520 이라, PG 보다 리더기 연결을 먼저 볼 일입니다. 평소 실패율을 아직 모르는 매장이라 손님 카드 문제가 겹쳤을 수도 있습니다."
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 정상 결제 중·결제가 끊김·카드 승인 실패가 몰림·키오스크 응답 끊김·계좌이체 충전 반영 지연 다섯 값 중 하나인지 확인합니다. 2. store_id·store_name·verdict·as_of·stale_kiosk_ids·minutes_since_oldest_heartbeat·last_paid_at·minutes_since_last_payment·baseline_median_interval_min·recent_30m_payments·recent_30m_failures·baseline_payments·baseline_failures·delayed_transfer_no·evidence·summary 필드가 빠짐없이 있는지 확인합니다. 3. 분·초·건수 필드가 0 이상이고 두 실패율이 0 이상 100 이하인지 확인합니다. 4. minutes_since_oldest_heartbeat, minutes_since_last_payment(둘 다 소수 첫째 자리 반올림), baseline_median_interval_min, 두 실패율, credit_delay_sec, uncredited_elapsed_sec 를 세 표의 원래 시각과 결과 코드로 다시 계산합니다. 5. stale_kiosk_ids 가 비어 있지 않으면 verdict 는 키오스크 응답 끊김입니다. 6. 앞 조건에 걸리지 않고 baseline_median_interval_min 이 null 이 아니며 minutes_since_last_payment 가 그 4배보다 크면 verdict 는 결제가 끊김입니다. 7. 앞 두 조건에 걸리지 않고 recent_30m_failures 가 5 이상이며 최근 실패율이 지난 4주 실패율의 3배보다 크면 verdict 는 카드 승인 실패가 몰림입니다. 지난 4주 결제가 없어 baseline_fail_rate_pct 가 null 인 매장은 기준 실패율을 0 으로 보고, 실패 5건 이상이면 몰림으로 셉니다. 8. 앞 세 조건에 걸리지 않고 delayed_transfer_no 가 null 이 아니면(반영이 1분을 넘었거나, credited_at 이 null 인 채 as_of 까지 60초를 넘긴 이체) verdict 는 계좌이체 충전 반영 지연입니다. 어느 조건에도 걸리지 않으면 verdict 는 정상 결제 중입니다. 9. evidence 의 field 이름이 kiosk_payments·kiosk_heartbeats·transfer_charges 에 있는 컬럼인지 대조합니다. 10. evidence 의 value 가 수록된 행의 원문 값과 같은지 확인합니다. 여러 줄을 세어 얻은 수는 agg 를 적습니다. 11. summary 가 존댓말로 끝나는지 확인합니다. [판정 차례 · 위 규칙이 둘 이상 동시에 걸릴 때] 매장 하나에는 판정을 하나만 남깁니다. ① 키오스크 응답 끊김 → ② 결제가 끊김 → ③ 카드 승인 실패가 몰림 → ④ 계좌이체 충전 반영 지연 → ⑤ 정상 결제 중. 응답이 끊긴 키오스크는 결제도 이체 반영도 못 하므로 가장 먼저 보고, 결제가 끊김은 키오스크가 모두 응답할 때만 셉니다. 계좌이체는 as_of 앞 60분 안에 입금된 건만 봅니다. [하드 케이스 · 판정이 갈리는 이유] 서울 망원점 TR-9009 는 credited_at 이 null 인 결측이라 as_of 까지 지난 540초를 uncredited_elapsed_sec 로 적고 충전 반영 지연입니다. 광주 첨단점은 마지막 응답이 정확히 10분 전, 이체 반영이 정확히 60초라 두 기준 모두 넘지 않아 정상이고, 지난 4주 기록이 없어 결제 끊김 기준선이 null 입니다. 서울 망원점과 수원 영통점도 기준선이 null 이라 결제 끊김은 보지 않습니다. 인천 송도2호점은 K-1 은 응답하고 K-2 만 28분째 끊겨, 매장 단위로는 응답 끊김입니다. 서울 성수점 TR-9011 은 입금 20초째라 credited_at 이 비어 있어도 아직 1분 안이라 지연으로 세지 않습니다. 대구 동성로점은 이번 주에 연 매장이라 지난 4주 실패율이 null 이고, 이를 0 으로 보아 최근 30분 실패 5건이 승인 실패 몰림으로 걸립니다. [평가 러너 · 2주차] 더미 매장 60곳을 판정 시각 네 번씩 찍은 240 판정에 손으로 매긴 정답과 자동 판정을 대조해 판정별 일치율을 내고, 어긋난 판정은 따로 모아 4배·3배·10분·1분 문턱을 다시 봅니다. [수동 검증 · 사람이 봅니다] 1. 결제가 끊김으로 걸린 매장이 실제 장애인지 손님이 원래 적은 날인지는 사장님과 통화한 CS 담당자가 판단합니다. 2. 카드 승인 실패가 몰린 매장을 단말 문제로 볼지 PG 문제로 볼지는 엔지니어가 정합니다.