스피커블
경력근거: 카카오톡으로 매일 5분, 언어와 지식을 배우는 '1일1지식' 서비스를 만듭니다.
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고 첫 줄은 “카카오톡으로 매일 5분, 언어와 지식을 배우는 '1일1지식' 서비스를 만듭니다.” 이고, 주요업무에 “인앱결제, 푸시, 소셜 로그인, 스토어 배포 등 앱 기반 기능 운영” 이, 우대사항에 “교육·구독 서비스 경험” 과 “Next.js · Supabase 웹 개발 경험” 이 있습니다. 학습이 앱 화면이 아니라 카톡으로 매일 나가는 구독 상품이라, 결제한 기간과 오늘 나간 발송이 맞는지가 곧 서비스가 제대로 도는지입니다. 공고가 “작은 팀이라 내가 한 일이 숫자로 바로 확인됩니다” 라고 적은 대로, 이 화면은 매일 아침 어긋난 수강권 수 하나로 시작하고 2주차에는 조치한 수강권이 다음 날 풀렸는지를 셉니다. 이 포트폴리오는 판매처마다 다른 결제 내보내기를 번호와 한국 날짜로 맞추고 발송 결과와 대조해 수강권 상태를 계산하는 구현을 보여줍니다. 스피커블이 카톡 학습을 어떤 도구로 보내는지는 공고에 없어 발송 기록의 모양을 제가 정했고, 구독 서비스 운영 전문성은 주장하지 않습니다.
공고 첫 줄이 카카오톡으로 매일 5분 학습을 보내는 1일1지식 서비스를 만든다는 문장입니다. 학습이 앱 화면이 아니라 매일 아침 카톡으로 전달되므로, 결제한 사람에게 오늘 학습이 나갔는지와 끝난 사람에게 발송이 멈췄는지에서 서비스가 제대로 도는지가 바로 드러납니다. 이 편은 그 대조를 수강권마다 판정합니다.
주요업무에 인앱결제·푸시·소셜 로그인·스토어 배포 같은 앱 기반 기능 운영이 있고, 우대사항에 Expo·인앱결제·푸시 구현 경험과 교육·구독 서비스 경험이 있습니다. 앱 인앱결제는 스토어 내보내기가 UTC 시각으로 오고 환불도 스토어에서 일어나서, 자사몰·크몽 기록과 같은 한국 날짜로 맞춰야 구독 기간을 셀 수 있습니다. 3주차에는 같은 판정을 Expo 앱과 푸시로 대표 폰에 보냅니다.
스피커블 앱을 기획부터 개선까지 한 사람이 맡고, 출시 후 데이터와 사용자 반응을 보고 UI/UX 를 다시 설계하며, Next.js · Supabase 웹 개발 경험을 우대합니다. 관리자 페이지는 Next.js 와 Supabase 로 만들고, 2주차에는 CS 담당자가 한 조치가 다음 날 판정에서 풀렸는지 숫자로 남겨 판정 묶음과 버튼 배치를 고칩니다.
스피커블의 1일1지식은 매일 아침 카카오톡으로 5분 학습을 보내는 유료 서비스이고, 수강권은 앱 인앱결제·자사몰·크몽 세 곳에서 팔린다고 봅니다. 이 관리자 페이지는 세 판매처의 결제 내보내기와 그날 아침 카톡 발송 결과 파일을 받아, 번호마다 지금 수강권이 기간 안인지 한국 날짜로 계산하고 오늘 발송, 최근 3일 실패, 환불이나 만료 뒤 발송을 견줍니다. 대표와 CS 담당자는 오늘 학습을 못 받은 수강생을 재발송 목록으로, 실패가 이어진 수강생을 연락 목록으로, 환불·만료 번호를 발송 제외 목록으로 내려받습니다. 카톡 발송과 결제는 지금 쓰는 도구가 그대로 하고, 이 화면은 두 기록이 어긋난 수강권만 보여줍니다.
스피커블 운영을 함께 보는 대표와 CS 담당자. 매일 아침 카톡 발송이 끝난 뒤 관리자 페이지에서 결제 내역과 어긋난 수강권을 확인하고, 못 받은 수강생에게는 다시 보내고 번호가 바뀐 수강생에게는 연락하며 환불·만료된 번호는 발송 대상에서 뺀다
결제와 발송이 어긋나는 까닭은 두 기록이 서로를 모르기 때문입니다. 발송 명단은 결제가 들어올 때 한 번 정해지고, 그 뒤의 환불·만료·번호 변경은 결제 쪽에만 남거나 발송 결과 파일의 실패 줄로만 드러납니다. 세 판매처의 내보내기는 번호 표기와 시각 기준이 서로 달라서, 앱 인앱결제의 UTC 시각을 그대로 읽거나 갱신 결제를 앞선 결제와 따로 보면 수강권이 하루씩 어긋나 보입니다. 매일 아침 두 기록을 번호와 한국 날짜로 맞춰 수강권마다 판정하면, CS 담당자는 환불 문의가 오기 전에 오늘 놓친 수강생에게 다시 보내고, 대표는 환불·만료 뒤에도 나가는 발송을 그날 멈춥니다.
세 판매처 결제 내보내기와 그날 카톡 발송 결과 CSV 를 올리면, 관리자 페이지가 번호마다 지금 수강권을 판정해 다섯 묶음으로 띄웁니다. 대표와 CS 담당자는 오늘 학습을 못 받은 수강생을 재발송 목록 CSV 로, 환불·만료 번호를 발송 제외 목록 CSV 로 내려받고, 실패가 이어진 수강생에게 보낼 문자 초안을 복사합니다. 발송과 결제는 지금 쓰는 도구에서 합니다.
자정 앞뒤 UTC 결제, 기간 마지막 날, 환불 당일 발송, 같은 날 재발송처럼 하루 차이로 판정이 뒤집히는 사례 40건이 Vitest 로 매번 돕니다. CS 담당자가 누른 재발송·연락·발송 제외가 Supabase 에 남고, 다음 날 판정에서 결제대로 나감으로 돌아온 수강권과 다시 걸린 수강권이 나뉩니다.
매일 08:30 판정이 끝나 어긋난 수강권이 생기면 Expo 앱으로 대표와 CS 담당자 폰에 푸시가 가고, 누르면 그 판정 묶음이 열립니다. 앱 인앱결제 환불은 스토어 환불 알림 모양의 JSON 으로 받아 결제 파일을 다시 올리지 않아도 판정에 들어갑니다.
대표나 CS 담당자가 결제 내보내기 세 개와 오늘 발송 결과 CSV 를 올리면 수강권 11개가 다섯 판정으로 나뉘고, 묶음마다 재발송·발송 제외 목록을 바로 내려받습니다. 수강권을 펼치면 결제 시각, 일차, 마지막 날, 최근 3일 발송 결과를 읽습니다.
UTC 로 10월 12일 15:30 에 찍힌 앱 결제는 한국 날짜 10월 13일로 읽어 오늘 미발송으로 잡지 않고, 기간 마지막 날 발송은 정상으로 두며, 환불 당일 아침 발송은 환불 뒤 발송에서 뺍니다. 실패 뒤 재발송이 성공한 날은 실패한 날로 세지 않고, 오늘 아침 한 번 실패하고 재발송이 없는 번호와 phone 이 null 인 크몽 결제는 오늘 미발송으로 올립니다.
번호 칸에 글자가 섞이거나 program_days 가 비거나 paid_at 표기가 다른 줄 30종을 넣어도 화면이 멈추지 않고, 읽지 못한 결제는 판정 목록 대신 묶음 위 확인 칸에 주문번호와 이유로 뜹니다.
하루 차이로 판정이 뒤집히는 사례 40건으로 판정 일치를 냅니다. 단, 정답과 데이터를 모두 제가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 결과는 더미데이터 자가채점이며, 놓친 학습과 환불·만료 뒤 발송이 실제로 줄었는지는 스피커블의 결제 기록과 발송 기록이 있어야 측정됩니다. 이 포트폴리오는 그 측정을 바로 돌릴 수 있는 상태로 만듭니다.
판정 테스트 40건은 제가 만든 결제·발송 더미에 정답도 제가 붙인 것이라, 다 맞아도 제가 정한 규칙을 코드로 옮겼다는 뜻까지입니다. 실제 결제 내보내기에 어떤 번호 표기와 시각이 섞여 오는지는 스피커블 파일을 받아야 압니다.
네 수강권은 날짜 읽기 하나에 판정이 걸려 있습니다. UTC 를 그대로 읽으면 한국 시각 10월 13일 00:30 결제가 10월 12일이 되어 GPA.3340-6612-0057-81934 가 오늘 미발송으로 걸리고, 같은 날 재발송을 보지 않으면 GPA.3392-1185-6620-14073 이 잇따른 실패로 뒤집힙니다. 번호마다 가장 늦은 결제를 고르지 않으면 갱신한 번호의 앞선 주문 SS20260620-0142 가 기간 종료 뒤 발송으로 잡히고, 크몽 결제 KM-5528801 의 phone null 은 일부러 넣은 결측입니다.
fail_reason 원문만으로는 번호가 다른 사람에게 넘어간 것인지 수강생이 채널을 차단한 것인지 확정할 수 없습니다. 잇따른 실패는 연락 목록으로만 보내고 발송 제외로 넘기지 않으며, 원인은 CS 담당자가 수강생 답을 듣고 정합니다.
두 코스를 동시에 듣거나 기간 중간에 미리 갱신한 번호는 가장 늦은 결제 하나로 보는 규칙과 맞지 않습니다. 실제 결제 기록에 그런 번호가 얼마나 있는지 보고 수강권을 번호와 코스 단위로 나눌지는 도입 단계 과제로 둡니다.
실제 발송 결과 파일은 발송 대행사마다 열 이름과 실패 문구가 다르고, 08:00 발송이 끝나기 전에 파일을 받으면 오늘 미발송이 부풀려집니다. 실데이터로 옮길 때는 발송 완료 시각 뒤에 판정을 돌리는 일부터 맞춥니다.
결제·발송 기록을 견주는 판정과 관리자 화면 구현까지가 이 편의 범위이며, 교육 구독 서비스의 운영 경험은 주장하지 않습니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
앱 인앱결제·자사몰·크몽 세 판매처의 결제 내보내기를 한 표로 합친 것으로, 판매처마다 실제 내보내기 모양을 본떠 지원자가 만든 더미 CSV 입니다. 앱 인앱결제는 paid_at·refunded_at 을 UTC(Z) 로, 자사몰과 크몽은 한국 시각으로 적고, 번호도 +82·하이픈·띄어쓰기로 판매처마다 다르게 적습니다. 크몽 구매자가 번호를 남기지 않으면 phone 이 null 입니다.
| order_id | channel | phone | course | paid_at | program_days | refunded_at |
|---|---|---|---|---|---|---|
| SS20260930-0087 | 자사몰 | 010-3381-2045 | 일상회화 | 2026-09-30 19:12 | 100 | — |
| GPA.3318-4471-2093-55120 | 앱 인앱결제 | +82 10-2231-4410 | 비즈니스 영어 | 2026-10-01T15:20:00Z | 100 | — |
| SS20260920-0031 | 자사몰 | 010-8170-3321 | 일상회화 | 2026-09-20 14:02 | 100 | — |
| GPA.3392-1185-6620-14073 | 앱 인앱결제 | +82 10-9022-5718 | 비즈니스 영어 | 2026-09-14T23:55:00Z | 100 | — |
| KM-5519264 | 크몽 | 01044021187 | 여행 영어 | 2026-09-28 10:15 | 30 | 2026-10-12 16:40 |
아래 JSON Schema 에 맞는 결제 행을 더 만들어 주세요. {"type":"object","properties":{"order_id":{"type":"string","description":"판매처가 매긴 주문번호입니다"},"channel":{"type":"string","enum":["앱 인앱결제","자사몰","크몽"]},"phone":{"type":["string","null"],"description":"판매처 내보내기에 적힌 번호 표기 그대로입니다. 크몽 구매자가 번호를 남기지 않으면 null 입니다"},"course":{"type":"string","enum":["일상회화","비즈니스 영어","여행 영어"]},"paid_at":{"type":"string","description":"앱 인앱결제는 ISO 8601 UTC(Z 로 끝남), 자사몰·크몽은 'YYYY-MM-DD HH:mm' 한국 시각입니다"},"program_days":{"type":"integer","minimum":1,"description":"수강권 일수입니다. 100일 여정은 100, 크몽 30회권은 30 입니다"},"refunded_at":{"type":["string","null"],"description":"paid_at 과 같은 표기의 환불 시각입니다. 환불하지 않았으면 null 입니다"}},"required":["order_id","channel","phone","course","paid_at","program_days","refunded_at"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) 앱 인앱결제 paid_at 이 UTC 15:00~23:59 사이라 한국 날짜가 하루 넘어가는 결제, (2) 같은 번호를 다른 표기로 적은 갱신 결제(앞선 결제는 기간이 끝난 뒤 결제), (3) phone 이 null 인 크몽 결제, (4) refunded_at 이 결제 며칠 뒤인 환불, (5) paid_at 날짜에 program_days 를 더한 날이 판정 기준일과 같은 수강권. channel 은 앱 인앱결제·자사몰·크몽 가운데 하나이고 program_days 는 100 또는 30 입니다. kakao_sends 행을 같은 번호로 함께 만드세요. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"order_id":{"type":"string","description":"판매처가 매긴 주문번호입니다"},"channel":{"type":"string","enum":["앱 인앱결제","자사몰","크몽"]},"phone":{"type":["string","null"],"description":"판매처 내보내기에 적힌 번호 표기 그대로입니다. 크몽 구매자가 번호를 남기지 않으면 null 입니다"},"course":{"type":"string","enum":["일상회화","비즈니스 영어","여행 영어"]},"paid_at":{"type":"string","description":"앱 인앱결제는 ISO 8601 UTC(Z 로 끝남), 자사몰·크몽은 'YYYY-MM-DD HH:mm' 한국 시각입니다"},"program_days":{"type":"integer","minimum":1,"description":"수강권 일수입니다. 100일 여정은 100, 크몽 30회권은 30 입니다"},"refunded_at":{"type":["string","null"],"description":"paid_at 과 같은 표기의 환불 시각입니다. 환불하지 않았으면 null 입니다"}},"required":["order_id","channel","phone","course","paid_at","program_days","refunded_at"]}매일 08:00 카톡 학습 발송 결과를 발송 대행 서비스의 결과 내보내기 모양으로 지원자가 만든 더미 CSV 입니다. 최근 3일(10월 11~13일) 줄만 담고 번호는 숫자만 적습니다. 실패한 발송은 대행사가 준 문구를 fail_reason 에 그대로 두고, CS 담당자가 손으로 다시 보낸 줄은 같은 날 sent_time 이 늦은 줄로 남습니다. 오늘 08:00 한 번만 실패하고 재발송이 없는 번호(01033812045)도 있습니다.
| msg_id | phone | send_date | sent_time | result | fail_reason |
|---|---|---|---|---|---|
| K1011-0101 | 01033812045 | 2026-10-11 | 08:00:04 | 성공 | — |
| K1012-0101 | 01033812045 | 2026-10-12 | 08:00:03 | 성공 | — |
| K1013-0101 | 01033812045 | 2026-10-13 | 08:00:05 | 실패 | 수신 차단 |
| K1011-0102 | 01022314410 | 2026-10-11 | 08:00:06 | 성공 | — |
| K1012-0102 | 01022314410 | 2026-10-12 | 08:00:05 | 성공 | — |
아래 JSON Schema 에 맞는 발송 결과 행을 더 만들어 주세요. {"type":"object","properties":{"msg_id":{"type":"string","description":"발송 대행 서비스가 매긴 메시지 번호입니다"},"phone":{"type":"string","description":"숫자만 적은 수신 번호입니다"},"send_date":{"type":"string","format":"date"},"sent_time":{"type":"string","description":"HH:mm:ss 한국 시각입니다. 08:00 일괄 발송 뒤 CS 담당자가 손으로 다시 보낸 줄은 시각이 늦습니다"},"result":{"type":"string","enum":["성공","실패"]},"fail_reason":{"type":["string","null"],"description":"발송 대행사가 준 실패 문구 그대로입니다. 성공이면 null 입니다"}},"required":["msg_id","phone","send_date","sent_time","result","fail_reason"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) 08:00 발송이 실패한 뒤 같은 날 늦은 sent_time 으로 재발송이 성공한 번호, (2) 최근 3일 가운데 2일이 실패인 번호와 1일만 실패인 번호, (3) 환불 당일 08:00 에 나간 발송, (4) 기간이 끝난 다음 날부터 계속 나간 발송, (5) 기간 안인데 오늘 줄이 없는 번호, (6) 오늘 08:00 발송만 실패하고 재발송이 없는 번호. fail_reason 은 '카카오톡 미사용자'·'수신 차단'·'일시적 전송 오류(3018)' 처럼 대행사 문구 그대로 쓰고 성공이면 null 로 두세요. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"msg_id":{"type":"string","description":"발송 대행 서비스가 매긴 메시지 번호입니다"},"phone":{"type":"string","description":"숫자만 적은 수신 번호입니다"},"send_date":{"type":"string","format":"date"},"sent_time":{"type":"string","description":"HH:mm:ss 한국 시각입니다. 08:00 일괄 발송 뒤 CS 담당자가 손으로 다시 보낸 줄은 시각이 늦습니다"},"result":{"type":"string","enum":["성공","실패"]},"fail_reason":{"type":["string","null"],"description":"발송 대행사가 준 실패 문구 그대로입니다. 성공이면 null 입니다"}},"required":["msg_id","phone","send_date","sent_time","result","fail_reason"]}| TypeScript | 판정 엔진 전체의 언어입니다. 번호 표기를 숫자로 맞추고, UTC 와 한국 시각이 섞인 결제 시각을 한국 날짜로 바꿔 기간을 세며, 번호별 가장 늦은 결제를 골라 다섯 판정과 우선순위를 냅니다. |
| Next.js | 대표와 CS 담당자가 아침에 여는 관리자 페이지입니다. 파일 네 개를 받는 업로드 화면, 판정 묶음, 묶음별 목록 CSV 를 만드는 route handler 를 둡니다. |
| Supabase | 날짜별 결제·발송 파일과 판정 결과, 2주차의 조치 기록을 Postgres 표로 쌓고, 3주차에는 매일 08:30 판정을 scheduled Edge Function 으로 돌립니다. |
| 소셜 로그인 | Supabase Auth 의 구글 로그인으로 대표와 CS 담당자 두 계정만 판정 화면에 들어옵니다. |
| 인앱결제 | 앱 인앱결제 줄의 UTC 시각과 스토어 환불 표기를 읽는 부분입니다. 3주차에는 App Store Server Notifications 의 REFUND 와 Google Play 의 voided purchase 모양 JSON 으로 환불을 받아 refunded_at 을 채웁니다. |
| React Native | 3주차에 대표와 CS 담당자가 폰에서 같은 판정 묶음을 여는 앱 화면입니다. |
| Expo (EAS, expo-router) | 3주차 앱을 Expo 로 만들고, expo-router 딥링크로 푸시를 누르면 해당 판정 묶음이 바로 열리며, EAS Build 로 두 사람 폰에 설치합니다. |
| 푸시 | 3주차에 매일 08:30 판정이 끝나고 어긋난 수강권이 1개 이상이면 expo-notifications 로 대표와 CS 담당자 폰에 알립니다. |
| 스토어 배포 | 3주차 앱은 스토어에 공개하지 않고 TestFlight 와 Google Play 내부 테스트 트랙으로 두 사람에게만 배포합니다. |
| Claude Code | 구현 도구입니다. 판정 함수와 하루 차이 사례 40건의 초안을 Claude Code 로 쓰고, 테스트가 통과한 것만 남깁니다. |
| Claude | Claude Haiku 4.5 가 연락 목록에 오른 수강생에게 보낼 문자 초안을 씁니다. |
| UI/UX | 판정 묶음마다 할 일 버튼(재발송 목록, 문자 초안, 발송 제외 목록)을 묶음 머리에 둡니다. 2주차 조치 기록으로 어느 묶음이 다음 날 다시 걸리는지 보고 버튼 순서와 문구를 고칩니다. |
✓ pass → 대표와 CS 담당자가 매일 아침 이 화면에서 세 목록을 내려받으면 1주차로 완결입니다. 더 키우려면 하루 차이 사례 40건을 테스트로 묶고, CS 담당자의 조치가 다음 날 판정에서 풀렸는지 보는 단계로 갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 판정을 매일 08:30 에 자동으로 돌려 대표 폰으로 푸시하고, 앱 결제 환불을 스토어 환불 알림으로 받는 단계로 갑니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · order_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
결제대로 나감결제와 맞음결제했는데 오늘 학습이 안 나감오늘 미발송카톡 발송이 잇따라 실패함연속 발송 실패환불했는데 학습이 계속 나감환불 후 발송기간이 끝났는데 학습이 계속 나감기간 종료 후 발송번호마다 지금 수강권 하나에 카톡 학습 발송 판정 하나를 남기고, 그 근거(결제 시각·기간 계산·발송 줄)를 함께 싣는 형식입니다.
{"type":"object","properties":{"order_id":{"type":"string","description":"번호마다 paid_at 을 한국 시각으로 읽어 가장 늦은 주문, 곧 지금 수강권의 주문번호입니다"},"channel":{"type":"string","enum":["앱 인앱결제","자사몰","크몽"]},"course":{"type":"string"},"phone":{"type":["string","null"],"description":"숫자만 남기고 82 로 시작하면 앞의 82 를 0 으로 바꾼 뒤 010-0000-0000 꼴로 맞춘 번호입니다. 결제에 번호가 없으면 null 입니다"},"as_of_date":{"type":"string","description":"판정 기준일(오늘)입니다"},"paid_date":{"type":"string","description":"정적 계산값입니다. paid_at 을 한국 날짜로 읽은 값이고, Z 로 끝나는 UTC 표기는 9시간을 더해 읽습니다"},"period_end":{"type":"string","description":"정적 계산값입니다. paid_date 에 program_days 를 더한 날이고, 이날까지 학습이 나갑니다"},"day_no":{"type":["integer","null"],"minimum":1,"description":"정적 계산값입니다. as_of_date 에서 paid_date 를 뺀 일수입니다. 환불했거나 as_of_date 가 기간(paid_date 다음 날부터 period_end 까지) 밖이면 null 입니다"},"refund_date":{"type":["string","null"],"description":"정적 계산값입니다. refunded_at 을 한국 날짜로 읽은 값입니다"},"fail_days_recent":{"type":"integer","minimum":0,"maximum":3,"description":"정적 계산값입니다. as_of_date 를 포함한 최근 3일 가운데, 그날 sent_time 이 가장 늦은 발송 줄의 result 가 실패인 날 수입니다"},"sends_after_stop":{"type":["integer","null"],"minimum":0,"description":"정적 계산값입니다. 환불했으면 refund_date 다음 날부터, 아니면 period_end 다음 날부터 send_date 가 그 이후인 발송 줄 수입니다. 그 날이 아직 오지 않았으면 null 입니다"},"verdict":{"type":"string","enum":["결제대로 나감","결제했는데 오늘 학습이 안 나감","카톡 발송이 잇따라 실패함","환불했는데 학습이 계속 나감","기간이 끝났는데 학습이 계속 나감"],"description":"걸린 판정이 여럿이면 환불했는데 학습이 계속 나감, 기간이 끝났는데 학습이 계속 나감, 카톡 발송이 잇따라 실패함, 결제했는데 오늘 학습이 안 나감 순으로 앞선 하나입니다. 하나도 걸리지 않으면 결제대로 나감입니다"},"also_found":{"type":"array","items":{"type":"string","enum":["결제했는데 오늘 학습이 안 나감","카톡 발송이 잇따라 실패함","환불했는데 학습이 계속 나감","기간이 끝났는데 학습이 계속 나감"]}},"evidence":{"type":"array","items":{"type":"object","properties":{"table":{"type":"string","enum":["payments","kakao_sends"]},"row":{"type":["string","null"],"description":"payments 는 order_id, kakao_sends 는 msg_id 입니다. agg 항목이면 null 입니다"},"field":{"type":"string"},"value":{"type":["string","integer","null"]},"agg":{"type":"string","enum":["count","sum","avg","min","max"]}},"required":["table","row","field","value"]}},"notes":{"type":"array","items":{"type":"string"},"description":"판정에 들지 않았지만 CS 담당자가 알아야 할 사실입니다"},"summary":{"type":"string"}},"required":["order_id","channel","course","phone","as_of_date","paid_date","period_end","day_no","refund_date","fail_days_recent","sends_after_stop","verdict","also_found","evidence","notes","summary"]}[
{
"order_id": "GPA.3318-4471-2093-55120",
"channel": "앱 인앱결제",
"course": "비즈니스 영어",
"phone": "010-2231-4410",
"as_of_date": "2026-10-13",
"paid_date": "2026-10-02",
"period_end": "2027-01-10",
"day_no": 11,
"refund_date": null,
"fail_days_recent": 0,
"sends_after_stop": null,
"verdict": "결제했는데 오늘 학습이 안 나감",
"also_found": [],
"evidence": [
{
"table": "payments",
"row": "GPA.3318-4471-2093-55120",
"field": "paid_at",
"value": "2026-10-01T15:20:00Z"
},
{
"table": "payments",
"row": "GPA.3318-4471-2093-55120",
"field": "program_days",
"value": 100
},
{
"table": "payments",
"row": "GPA.3318-4471-2093-55120",
"field": "refunded_at",
"value": null
},
{
"table": "payments",
"row": "GPA.3318-4471-2093-55120",
"field": "phone",
"value": "+82 10-2231-4410"
},
{
"table": "kakao_sends",
"row": "K1012-0102",
"field": "send_date",
"value": "2026-10-12"
},
{
"table": "kakao_sends",
"row": null,
"field": "send_date",
"value": 0,
"agg": "count"
}
],
"notes": [
"paid_at 이 UTC 라 9시간을 더해 한국 시각 10월 2일 00:20 결제로 읽습니다."
],
"summary": "10월 2일 앱에서 결제한 비즈니스 영어 11일차 수강생인데, 오늘 08:00 발송 기록에 이 번호가 없습니다."
},
{
"order_id": "SS20260920-0031",
"channel": "자사몰",
"course": "일상회화",
"phone": "010-8170-3321",
"as_of_date": "2026-10-13",
"paid_date": "2026-09-20",
"period_end": "2026-12-29",
"day_no": 23,
"refund_date": null,
"fail_days_recent": 2,
"sends_after_stop": null,
"verdict": "카톡 발송이 잇따라 실패함",
"also_found": [
"결제했는데 오늘 학습이 안 나감"
],
"evidence": [
{
"table": "payments",
"row": "SS20260920-0031",
"field": "paid_at",
"value": "2026-09-20 14:02"
},
{
"table": "payments",
"row": "SS20260920-0031",
"field": "program_days",
"value": 100
},
{
"table": "kakao_sends",
"row": "K1011-0103",
"field": "result",
"value": "성공"
},
{
"table": "kakao_sends",
"row": "K1012-0103",
"field": "result",
"value": "실패"
},
{
"table": "kakao_sends",
"row": "K1013-0103",
"field": "result",
"value": "실패"
},
{
"table": "kakao_sends",
"row": "K1013-0103",
"field": "fail_reason",
"value": "카카오톡 미사용자"
}
],
"notes": [
"실패 문구가 두 번 다 '카카오톡 미사용자' 라 번호를 다른 사람이 쓰게 됐을 수 있습니다. 연락 목록에 올라 문자 문안 초안이 붙습니다.",
"오늘 줄도 실패라 오늘 미발송에도 걸리지만, 잇따른 실패가 앞서므로 오늘 미발송은 also_found 에 남고 재발송 목록에는 오르지 않습니다."
],
"summary": "최근 3일 가운데 10월 12일과 13일 발송이 '카카오톡 미사용자'로 실패한 일상회화 23일차 수강생입니다."
},
{
"order_id": "SS20260930-0087",
"channel": "자사몰",
"course": "일상회화",
"phone": "010-3381-2045",
"as_of_date": "2026-10-13",
"paid_date": "2026-09-30",
"period_end": "2027-01-08",
"day_no": 13,
"refund_date": null,
"fail_days_recent": 1,
"sends_after_stop": null,
"verdict": "결제했는데 오늘 학습이 안 나감",
"also_found": [],
"evidence": [
{
"table": "payments",
"row": "SS20260930-0087",
"field": "paid_at",
"value": "2026-09-30 19:12"
},
{
"table": "payments",
"row": "SS20260930-0087",
"field": "program_days",
"value": 100
},
{
"table": "kakao_sends",
"row": "K1011-0101",
"field": "result",
"value": "성공"
},
{
"table": "kakao_sends",
"row": "K1012-0101",
"field": "result",
"value": "성공"
},
{
"table": "kakao_sends",
"row": "K1013-0101",
"field": "sent_time",
"value": "08:00:05"
},
{
"table": "kakao_sends",
"row": "K1013-0101",
"field": "result",
"value": "실패"
},
{
"table": "kakao_sends",
"row": "K1013-0101",
"field": "fail_reason",
"value": "수신 차단"
}
],
"notes": [
"오늘 08:00 발송이 '수신 차단' 으로 실패했고 그 뒤 재발송 줄이 없습니다. 실패한 날은 오늘 하루라 잇따른 실패에는 들지 않고, 재발송 목록에 오릅니다."
],
"summary": "9월 30일 자사몰에서 결제한 일상회화 13일차 수강생인데, 오늘 08:00 발송이 '수신 차단' 으로 실패해 학습이 가지 않았습니다."
},
{
"order_id": "GPA.3392-1185-6620-14073",
"channel": "앱 인앱결제",
"course": "비즈니스 영어",
"phone": "010-9022-5718",
"as_of_date": "2026-10-13",
"paid_date": "2026-09-15",
"period_end": "2026-12-24",
"day_no": 28,
"refund_date": null,
"fail_days_recent": 1,
"sends_after_stop": null,
"verdict": "결제대로 나감",
"also_found": [],
"evidence": [
{
"table": "kakao_sends",
"row": "K1011-0104",
"field": "result",
"value": "실패"
},
{
"table": "kakao_sends",
"row": "K1012-0104",
"field": "sent_time",
"value": "08:00:07"
},
{
"table": "kakao_sends",
"row": "K1012-0104",
"field": "result",
"value": "실패"
},
{
"table": "kakao_sends",
"row": "K1012-0911",
"field": "sent_time",
"value": "11:20:41"
},
{
"table": "kakao_sends",
"row": "K1012-0911",
"field": "result",
"value": "성공"
},
{
"table": "kakao_sends",
"row": "K1013-0104",
"field": "result",
"value": "성공"
}
],
"notes": [
"10월 12일은 08:00 실패 뒤 11:20 재발송이 성공해 실패한 날로 세지 않고, 실패한 날은 10월 11일 하루입니다."
],
"summary": "10월 11일과 12일 아침 발송이 실패했지만 12일 재발송과 13일 발송이 성공해 결제대로 나가고 있습니다."
},
{
"order_id": "KM-5519264",
"channel": "크몽",
"course": "여행 영어",
"phone": "010-4402-1187",
"as_of_date": "2026-10-13",
"paid_date": "2026-09-28",
"period_end": "2026-10-28",
"day_no": null,
"refund_date": "2026-10-12",
"fail_days_recent": 0,
"sends_after_stop": 1,
"verdict": "환불했는데 학습이 계속 나감",
"also_found": [],
"evidence": [
{
"table": "payments",
"row": "KM-5519264",
"field": "refunded_at",
"value": "2026-10-12 16:40"
},
{
"table": "kakao_sends",
"row": "K1012-0105",
"field": "send_date",
"value": "2026-10-12"
},
{
"table": "kakao_sends",
"row": "K1012-0105",
"field": "sent_time",
"value": "08:00:09"
},
{
"table": "kakao_sends",
"row": "K1013-0105",
"field": "send_date",
"value": "2026-10-13"
},
{
"table": "kakao_sends",
"row": null,
"field": "send_date",
"value": 1,
"agg": "count"
}
],
"notes": [
"환불 당일 08:00 발송(K1012-0105)은 환불 16:40 보다 앞서 세지 않습니다. 발송 제외 목록에 이 번호가 오릅니다."
],
"summary": "10월 12일 16:40 에 크몽에서 환불한 여행 영어 수강생에게 10월 13일 08:00 에도 학습이 나갔습니다."
},
{
"order_id": "GPA.3305-2290-7718-40261",
"channel": "앱 인앱결제",
"course": "비즈니스 영어",
"phone": "010-7715-0915",
"as_of_date": "2026-10-13",
"paid_date": "2026-07-01",
"period_end": "2026-10-09",
"day_no": null,
"refund_date": null,
"fail_days_recent": 0,
"sends_after_stop": 3,
"verdict": "기간이 끝났는데 학습이 계속 나감",
"also_found": [],
"evidence": [
{
"table": "payments",
"row": "GPA.3305-2290-7718-40261",
"field": "paid_at",
"value": "2026-07-01T00:40:00Z"
},
{
"table": "payments",
"row": "GPA.3305-2290-7718-40261",
"field": "program_days",
"value": 100
},
{
"table": "kakao_sends",
"row": "K1011-0106",
"field": "send_date",
"value": "2026-10-11"
},
{
"table": "kakao_sends",
"row": "K1013-0106",
"field": "send_date",
"value": "2026-10-13"
},
{
"table": "kakao_sends",
"row": null,
"field": "send_date",
"value": 3,
"agg": "count"
}
],
"notes": [
"이 번호에는 7월 1일 뒤의 새 결제가 없어 갱신으로 보지 않습니다."
],
"summary": "7월 1일 결제한 100일 수강권이 10월 9일에 끝났는데 10월 11일부터 오늘까지 학습이 3번 더 나갔습니다."
},
{
"order_id": "GPA.3340-6612-0057-81934",
"channel": "앱 인앱결제",
"course": "일상회화",
"phone": "010-6604-1290",
"as_of_date": "2026-10-13",
"paid_date": "2026-10-13",
"period_end": "2027-01-21",
"day_no": null,
"refund_date": null,
"fail_days_recent": 0,
"sends_after_stop": null,
"verdict": "결제대로 나감",
"also_found": [],
"evidence": [
{
"table": "payments",
"row": "GPA.3340-6612-0057-81934",
"field": "paid_at",
"value": "2026-10-12T15:30:00Z"
},
{
"table": "payments",
"row": "GPA.3340-6612-0057-81934",
"field": "program_days",
"value": 100
},
{
"table": "kakao_sends",
"row": null,
"field": "send_date",
"value": 0,
"agg": "count"
}
],
"notes": [
"UTC 날짜대로 10월 12일 결제로 읽으면 오늘이 1일차가 되어 오늘 미발송으로 잘못 걸립니다. 한국 시각 10월 13일 00:30 결제라 첫 학습은 10월 14일에 나갑니다."
],
"summary": "오늘 00:30(한국 시각)에 앱에서 결제한 수강생이라 첫 학습은 내일 나가고, 오늘 발송이 없는 것은 결제대로입니다."
},
{
"order_id": "KM-5530417",
"channel": "크몽",
"course": "비즈니스 영어",
"phone": "010-5150-7782",
"as_of_date": "2026-10-13",
"paid_date": "2026-09-29",
"period_end": "2027-01-07",
"day_no": 14,
"refund_date": null,
"fail_days_recent": 0,
"sends_after_stop": null,
"verdict": "결제대로 나감",
"also_found": [],
"evidence": [
{
"table": "payments",
"row": "KM-5530417",
"field": "paid_at",
"value": "2026-09-29 20:14"
},
{
"table": "payments",
"row": "KM-5530417",
"field": "phone",
"value": "010 5150 7782"
},
{
"table": "payments",
"row": "SS20260620-0142",
"field": "phone",
"value": "010-5150-7782"
},
{
"table": "payments",
"row": "SS20260620-0142",
"field": "paid_at",
"value": "2026-06-20 11:05"
},
{
"table": "kakao_sends",
"row": "K1013-0108",
"field": "send_date",
"value": "2026-10-13"
}
],
"notes": [
"같은 번호의 앞선 주문 SS20260620-0142 는 9월 28일에 끝났지만 9월 29일 새 결제가 있어 판정하지 않습니다. 두 결제의 번호 표기(010-5150-7782, 010 5150 7782)는 숫자만 남겨 하나로 봅니다."
],
"summary": "9월 28일에 끝난 수강권을 9월 29일 크몽에서 다시 결제한 비즈니스 영어 14일차 수강생이고, 오늘 학습이 나갔습니다."
},
{
"order_id": "KM-5528801",
"channel": "크몽",
"course": "여행 영어",
"phone": null,
"as_of_date": "2026-10-13",
"paid_date": "2026-10-08",
"period_end": "2026-11-07",
"day_no": 5,
"refund_date": null,
"fail_days_recent": 0,
"sends_after_stop": null,
"verdict": "결제했는데 오늘 학습이 안 나감",
"also_found": [],
"evidence": [
{
"table": "payments",
"row": "KM-5528801",
"field": "phone",
"value": null
},
{
"table": "payments",
"row": "KM-5528801",
"field": "paid_at",
"value": "2026-10-08 13:22"
},
{
"table": "payments",
"row": "KM-5528801",
"field": "program_days",
"value": 30
},
{
"table": "kakao_sends",
"row": null,
"field": "send_date",
"value": 0,
"agg": "count"
}
],
"notes": [
"결제에 번호가 없어 발송 기록과 견줄 수 없습니다. 크몽 메시지로 번호를 받아야 발송 대상에 오릅니다."
],
"summary": "10월 8일 크몽에서 여행 영어 30회권을 결제한 5일차 수강생인데, 결제에 번호가 없어 학습이 한 번도 나가지 않았습니다."
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 결제대로 나감·결제했는데 오늘 학습이 안 나감·카톡 발송이 잇따라 실패함·환불했는데 학습이 계속 나감·기간이 끝났는데 학습이 계속 나감 다섯 값 중 하나인지, also_found 가 결제대로 나감을 뺀 네 값 중 하나인지 확인합니다. 2. 필수 필드 order_id·channel·course·phone·as_of_date·paid_date·period_end·day_no·refund_date·fail_days_recent·sends_after_stop·verdict·also_found·evidence·notes·summary 가 모두 존재하는지 확인합니다. 3. day_no 가 null 이 아니면 1 이상 program_days 이하인지, fail_days_recent 가 0 이상 3 이하인지, sends_after_stop 이 null 이 아니면 0 이상인지 확인합니다. 4. 번호 맞추기: payments.phone 과 kakao_sends.phone 은 숫자만 남기고 82 로 시작하면 앞의 82 를 0 으로 바꿔 같은 번호로 봅니다. 번호마다 paid_at 을 한국 시각으로 읽어 가장 늦은 주문 하나를 판정하고 앞선 주문은 판정하지 않으며, phone 이 null 인 결제는 어떤 발송 줄과도 짝짓지 않습니다. SS20260620-0142 와 KM-5530417 은 010-5150-7782 하나로 보고 KM-5530417 만 판정하는지 다시 계산합니다. 5. paid_date·period_end·day_no·refund_date 재계산: paid_at·refunded_at 이 Z 로 끝나면 9시간을 더해 한국 날짜로 읽고, 나머지는 적힌 날짜 그대로 읽습니다. period_end 는 paid_date 에 program_days 를 더한 날이고, 기간 안은 refunded_at 이 null 이면서 as_of_date 가 paid_date 다음 날부터 period_end 사이에 드는 경우입니다. GPA.3340-6612-0057-81934 는 2026-10-12T15:30:00Z 가 한국 시각 10월 13일 00:30 이라 paid_date 가 2026-10-13 이고 기간이 10월 14일에 시작해 day_no 가 null 입니다. 6. 결제했는데 오늘 학습이 안 나감: 기간 안인 번호인데 kakao_sends 에 그 번호의 send_date 가 as_of_date 인 줄이 없거나, 오늘 줄 가운데 sent_time 이 가장 늦은 줄의 result 가 실패이면 걸립니다. 10월 12일까지 나가고 13일 줄이 없는 GPA.3318-4471-2093-55120, 번호가 null 인 KM-5528801, 오늘 08:00 발송이 '수신 차단' 으로 실패하고 재발송이 없는 SS20260930-0087 이 걸리고, 기간이 10월 14일에 시작하는 GPA.3340-6612-0057-81934 는 걸리지 않습니다. 7. 카톡 발송이 잇따라 실패함: 기간 안인 번호의 as_of_date 포함 최근 3일에서, 날마다 sent_time 이 가장 늦은 줄의 result 를 그날 결과로 보고 실패인 날을 센 fail_days_recent 가 2 이상이면 걸립니다. SS20260920-0031 은 10월 12·13일이 실패라 2일로 걸리고, GPA.3392-1185-6620-14073 은 10월 12일 08:00 실패 뒤 11:20 재발송이 성공해 실패한 날이 10월 11일 하루라 걸리지 않습니다. 오늘 줄까지 실패인 SS20260920-0031 은 6번에도 걸리므로 10번의 순서대로 verdict 는 잇따른 실패이고 오늘 미발송은 also_found 에 남습니다. 8. 환불했는데 학습이 계속 나감: refunded_at 이 있는 주문이 그 번호의 가장 늦은 결제이고, refund_date 다음 날 이후 send_date 의 발송 줄 수 sends_after_stop 이 1 이상이면 걸립니다. KM-5519264 는 10월 12일 16:40 환불 뒤 13일 발송 1건으로 걸리고 환불 당일 08:00 발송은 세지 않습니다. 한국 시각 10월 11일 12:10 에 환불한 GPA.3301-7742-0918-36651 은 그날 08:00 발송뿐이라 sends_after_stop 이 0 이고 걸리지 않습니다. 9. 기간이 끝났는데 학습이 계속 나감: 그 번호의 가장 늦은 결제의 period_end 다음 날 이후 send_date 의 발송 줄이 있으면 걸리고, sends_after_stop 이 그 줄 수인지 다시 계산합니다. 10월 9일에 끝난 GPA.3305-2290-7718-40261 은 11~13일 3건으로 걸리고, period_end 가 오늘인 SS20260705-0219 는 오늘 발송이 마지막 날 발송이라 걸리지 않습니다. 10. 한 수강권에 판정이 여럿 걸리면 환불, 기간 종료, 잇따른 실패, 오늘 미발송 순으로 앞선 하나가 verdict 이고 나머지는 also_found 입니다. 하나도 걸리지 않으면 결제대로 나감입니다. 이 순서로 정해졌는지 다시 계산합니다. 11. evidence 의 table·field 가 payments·kakao_sends 에 실재하는 컬럼인지 대조합니다. 12. agg 가 없는 evidence 의 value 가 그 row 의 그 컬럼 값과 같은지 확인합니다. agg 가 count 인 value 는 그 번호의 조건에 맞는 발송 줄 수(오늘 줄, 또는 멈출 날 뒤의 줄)입니다. 13. summary 가 존댓말로 끝나는지 확인합니다. [수동 검증 · 사람이 봅니다] 1. 오늘이 아닌 날 하루만 실패한 번호(GPA.3392-1185-6620-14073 의 10월 11일)는 판정식에 걸리지 않습니다. notes 에 실패가 남은 번호는 CS 담당자가 보고 따로 연락할지 정합니다. 2. 잇따라 실패한 수강생에게 문자를 보낼지 전화할지, 번호 없는 크몽 구매자에게 크몽 메시지를 보낼지는 CS 담당자가 정합니다. 연락 문안 초안은 고쳐 쓰는 출발점입니다. 3. 환불·만료 번호를 발송 대상에서 빼는 일은 대표나 CS 담당자가 발송 도구에서 직접 합니다. 이 화면은 뺄 번호 목록 CSV 만 냅니다. 4. 한 번호로 두 코스를 동시에 듣는 수강생은 이 판정식이 가장 늦은 결제 하나로 봅니다. 그런 번호는 사람이 따로 봅니다. [평가 러너 · 2주차] 1. 자정 앞뒤로 찍힌 UTC 결제, 기간 마지막 날, 환불 당일 발송, 실패 1일과 2일, 같은 날 재발송처럼 하루 차이로 판정이 뒤집히는 사례 40건에서 verdict 가 손으로 낸 정답과 같은 건수를 셉니다. 2. 재발송·연락·발송 제외를 누른 수강권이 다음 날 판정에서 결제대로 나감으로 돌아온 건수와 다시 걸린 건수를 셉니다. [판정식에 더한 정의 · 오너 확인 항목] 1. 기간은 paid_date 다음 날을 1일차로 period_end 까지입니다. 결제 당일에는 학습이 나가지 않는다고 본 정의입니다. 2. 최근 3일은 as_of_date 를 포함한 3일이고, 같은 날 발송 줄이 여럿이면 sent_time 이 가장 늦은 줄의 result 를 그날 결과로 봅니다. 3. 판정 대상은 번호마다 가장 늦은 결제 하나이고, 결제에 번호가 없는 주문은 오늘 발송 줄이 없는 것으로 봅니다. 4. 시각은 Z 로 끝나는 표기만 UTC 로 보고 9시간을 더합니다. 5. 카드 verdict_rule 보다 넓힌 정의 — 오늘 1회 실패 포함: 오늘 미발송은 오늘 줄이 없는 번호뿐 아니라 오늘 줄의 마지막 result 가 실패인 번호도 포함합니다. 판정 이름이 글자 그대로 실패한 발송을 덮고, 결제한 수강생에게 오늘 학습이 안 가는 첫 손해와 맞추기 위한 정의입니다. 실패가 2일 이상이면 잇따른 실패가 앞서고 오늘 미발송은 also_found 에 남습니다.
근거: 카카오톡으로 매일 5분, 언어와 지식을 배우는 '1일1지식' 서비스를 만듭니다.