SearchApi
경력근거: Stripe integrations, subscription logic, usage-based billing
※ 실제 산출물이 아닌 AI 예측 시안입니다.
SearchApi 공고는 이 자리가 맡을 일로 Stripe 연동과 구독 로직, 사용량 기반 과금을 들고, 제품을 100개가 넘는 엔진에서 결과를 돌려주는 실시간 SERP API 로 설명합니다. 엔진마다 단가와 무료 할당량이 갈리고 실패 응답의 종류도 갈리는 제품이라, 사용량과 청구가 맞는지는 엔진 단위로 견줘야 나옵니다. 이 포트폴리오는 그 대조 화면과 판정 규칙을 보여줍니다. Stripe 의 실제 인보이스 발행과 세금 처리 깊이는 주장하지 않고, 판정은 요금제 표와 로그 값의 계산으로만 합니다.
공고가 맡길 일에 Stripe 연동과 구독 로직, 사용량 기반 과금이 나란히 적혀 있습니다. 사용량과 청구서를 맞대는 화면이 그 일의 첫 산출물입니다.
100개가 넘는 검색 엔진에서 구조화된 결과를 실시간으로 돌려주는 제품이라, 엔진마다 단가와 무료 할당량이 다르고 실패 응답의 뜻도 다릅니다.
제품 전체가 Rails 위에 있고 화면은 Hotwire 와 TailwindCSS 로 만듭니다. 백그라운드 계산은 Sidekiq 이 맡는 구성 그대로 대조 화면을 세웁니다.
SearchApi 를 자기 서비스에 붙여 쓰는 고객사 개발자가 여는 청구 대조 화면입니다. 자기 쪽에 쌓인 요청 로그와 상대에게서 받은 월 청구서를 엔진별로 맞대어, 실패 응답에 과금 표시가 붙은 줄과 청구 건수가 더 잡힌 줄, 단가와 무료 할당량이 요금제와 어긋난 줄을 사유별로 세우고 그 계산에 쓴 건수와 단가를 붙입니다. 판정은 Rails 서비스의 규칙이 계산하고 Hotwire 화면이 그 결과를 그립니다.
SearchApi 를 자기 서비스에 붙여 하루 수십만 건씩 검색을 돌리는 고객사 개발자 — 재시도와 실패 응답이 섞인 요청 로그는 자기 쪽에 있고 청구서는 상대편에서 오니, 둘이 맞는지는 월말에 손으로 맞춰 보는 수밖에 없다
요청 로그와 청구서를 엔진별로 요금제 표에 맞대면, 고객사 개발자가 월말에 표를 손으로 맞추지 않고 더 청구된 줄과 그 금액을 그 자리에서 봅니다.
요청 로그 20건과 청구서 10줄, 요금제 10줄을 올리면 더 청구된 줄이 사유별로 뜨는 Rails 화면과 판정 규칙이 돕니다. 실제 Stripe 인보이스 API 연동은 내려받은 청구서 파일을 읽는 것으로 시작하고 3주차로 미룹니다.
Claude Sonnet 이 더 청구된 줄을 상대에게 보낼 문의 초안 한 문단으로 옮기고 JSON 스키마로 형식을 잡습니다. Terraform 으로 AWS 에 올려 화면이 실제 서버에서 돕니다.
Stripe 인보이스를 직접 받아 오고, 대조 계산을 Sidekiq 작업으로 돌려 로그가 커져도 화면이 기다리지 않습니다. 엔진별 판정 결과를 Redis 에 캐시합니다.
고객사 개발자가 요청 로그 20건과 청구서 11줄을 올리면 더 청구된 줄이 사유별로 뜨고 어떤 건수와 단가로 그렇게 계산했는지 근거 표에서 펼쳐집니다.
판정이 규칙과 맞는지 청구서 11줄을 자가채점 러너로 세어 화면 아래에 표시합니다. 정답과 데이터를 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건에서의 동작입니다.
응답 시각이 비어 있거나 요금제 표에 없는 엔진이 섞인 악성·결측 입력 30종을 올려도 목록 대신 어느 줄이 왜 걸렸는지 나오고 응답 형식이 깨지지 않습니다.
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 더미데이터 자가채점이고, 실제 과금 오차율은 SearchApi 의 실제 청구서와 고객사의 실제 로그가 있어야 잽니다. 이 포트폴리오는 그 검증을 바로 돌릴 상태까지 만듭니다.
판정 일치 수는 제가 만든 요청 로그와 청구서를 제가 채점한 값이라 과금 점검의 성능 증명이 아닙니다. 면접에서는 SearchApi 의 실제 청구서와 고객사 로그가 들어왔을 때 무엇부터 재야 하는지로 이야기를 잇습니다.
어느 줄이 더 나갔는지 제가 정해 두고 채점한 값이라 잘 맞는 것은 당연합니다. 그래서 응답을 못 받았는데 과금 표시가 붙은 행, 과금 대상이 하나도 없어 청구가 0 으로 끝난 엔진, 무료 할당량을 안 뺀 줄, 로그에 요청이 한 줄도 없는데 청구된 엔진을 더미에 넣어 사유가 서로 헷갈리는 자리를 먼저 드러냈습니다.
더미의 요청 수는 실제 규모를 그대로 옮기지 않고 사람이 손으로 따라갈 수 있는 크기로 줄였습니다. 로그가 수십만 줄이 되면 집계를 데이터베이스 쪽으로 밀어야 하고, 그 성능은 이 더미로 재지 못합니다.
이 포트폴리오는 대조 화면과 판정 규칙을 보여줍니다. Stripe 인보이스 발행과 세금·환불 처리의 전문성은 주장하지 않고, 판정은 요금제 표와 로그 값의 계산으로만 합니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
실제로 붙이면 재시도가 원래 요청과 어떻게 묶이는지부터 정해야 합니다. 기간 경계에 걸친 요청의 소속과 요금제가 중간에 바뀐 달의 처리는 도입 단계 과제로 따로 잡습니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
고객사 쪽에 쌓이는 요청 기록입니다. 엔진과 응답 상태, 상대가 과금 대상으로 표시했는지를 담고, 응답을 못 받은 요청은 responded_at 이 비어 있습니다. 실제 규모를 그대로 옮기지 않고 한 계정의 청구 기간을 손으로 따라갈 크기로 줄인 표본입니다. 어느 청구 기간에 속하는지는 requested_at 의 연-월로 정하므로, 응답을 못 받아 responded_at 이 빈 timeout 행도 기간이 정해집니다.
| request_id | account_id | engine | status | billed | requested_at | responded_at |
|---|---|---|---|---|---|---|
| req_20260214_0001 | acct_8812 | google_search | success | true | 2026-02-14T09:00:08Z | 2026-02-14T09:00:11Z |
| req_20260214_0002 | acct_8812 | google_search | success | true | 2026-02-14T09:00:11Z | 2026-02-14T09:00:14Z |
| req_20260214_0003 | acct_8812 | google_search | success | true | 2026-02-14T09:00:59Z | 2026-02-14T09:01:02Z |
| req_20260214_0004 | acct_8812 | google_search | success | true | 2026-02-14T09:01:36Z | 2026-02-14T09:01:40Z |
| req_20260214_0005 | acct_8812 | google_shopping | success | true | 2026-02-14T09:02:04Z | 2026-02-14T09:02:08Z |
아래 JSON Schema 에 정확히 맞는 SERP API 요청 기록 40개를 JSON 배열로 만들어 주세요.
{"type":"object","properties":{"request_id":{"type":"string"},"account_id":{"type":"string"},"engine":{"type":"string","enum":["google_search","google_shopping","google_jobs","google_news","google_maps","bing","baidu","amazon","youtube","yandex","google_scholar"]},"status":{"type":"string","enum":["success","timeout","rate_limited","empty_result","invalid_param"]},"billed":{"type":"boolean"},"requested_at":{"type":"string"},"responded_at":{"type":["string","null"]}},"required":["request_id","account_id","engine","status","billed","requested_at","responded_at"]}
조건: request_id 는 req_연월일_일련 형식으로 서로 다르게 씁니다. account_id 는 acct_네자리로 쓰고, engine 은 스키마의 열 값 중에서만 고릅니다. requested_at 은 요청을 보낸 시각이라 모든 행에 있어야 하고, responded_at 은 그보다 몇 초 뒤 같은 날 오전 시각으로 쓰되 응답을 못 받은 행은 비웁니다. 청구 기간은 requested_at 의 연-월로 정합니다.
쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: status 가 timeout 인데 billed 가 참인 행 4건, responded_at 이 비어 있는 행 5건, 성공 응답이 하나도 없는 엔진 1개, 같은 초에 몰려 들어온 재시도 행 3건. 그리고 형식 깨뜨리기 시도 3건(컬럼 이름 오타·쉼표 누락·따옴표 불일치)을 별도 파일로 만들어 화면이 어떻게 걸러 내는지 보게 합니다.
JSON 배열만 출력합니다.{"type":"object","properties":{"request_id":{"type":"string"},"account_id":{"type":"string"},"engine":{"type":"string","enum":["google_search","google_shopping","google_jobs","google_news","google_maps","bing","baidu","amazon","youtube","yandex","google_scholar"]},"status":{"type":"string","enum":["success","timeout","rate_limited","empty_result","invalid_param"]},"billed":{"type":"boolean"},"requested_at":{"type":"string"},"responded_at":{"type":["string","null"]}},"required":["request_id","account_id","engine","status","billed","requested_at","responded_at"]}고객사에 나간 청구서의 엔진별 한 줄입니다. charged_count 는 무료 할당량을 빼기 전 과금 대상으로 잡은 요청 수이고 amount_usd 는 실제로 청구된 금액이라, 두 칸을 요금제와 로그에 각각 견줍니다. 그 기간 로그에 요청이 한 줄도 없는 엔진이 청구된 줄도 그대로 들어 있습니다.
| line_no | account_id | period | plan | engine | charged_count | amount_usd |
|---|---|---|---|---|---|---|
| INV-202602-01 | acct_8812 | 2026-02 | sandbox | google_search | 4 | 0.004 |
| INV-202602-02 | acct_8812 | 2026-02 | sandbox | google_shopping | 3 | 0.004 |
| INV-202602-03 | acct_8812 | 2026-02 | sandbox | amazon | 4 | 0.006 |
| INV-202602-04 | acct_8812 | 2026-02 | sandbox | google_jobs | 2 | 0.01 |
| INV-202602-05 | acct_8812 | 2026-02 | sandbox | bing | 2 | 0.004 |
아래 JSON Schema 에 정확히 맞는 월 청구서 줄 20개를 JSON 배열로 만들어 주세요.
{"type":"object","properties":{"line_no":{"type":"string"},"account_id":{"type":"string"},"period":{"type":"string"},"plan":{"type":"string"},"engine":{"type":"string","enum":["google_search","google_shopping","google_jobs","google_news","google_maps","bing","baidu","amazon","youtube","yandex","google_scholar"]},"charged_count":{"type":"integer","minimum":0},"amount_usd":{"type":"number","minimum":0}},"required":["line_no","account_id","period","plan","engine","charged_count","amount_usd"]}
조건: line_no 는 INV-연월-일련 형식으로 서로 다르게 씁니다. period 는 연-월 형식이고, plan 은 요금제 이름, amount_usd 는 소수 셋째 자리까지 씁니다.
쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: charged_count 와 amount_usd 가 둘 다 0 인 줄 2건, 요금제 단가로 계산한 금액과 어긋난 줄 3건, 무료 할당량을 빼지 않고 계산한 줄 2건, 로그에 없는 엔진이 청구된 줄 1건.
JSON 배열만 출력합니다.{"type":"object","properties":{"line_no":{"type":"string"},"account_id":{"type":"string"},"period":{"type":"string"},"plan":{"type":"string"},"engine":{"type":"string","enum":["google_search","google_shopping","google_jobs","google_news","google_maps","bing","baidu","amazon","youtube","yandex","google_scholar"]},"charged_count":{"type":"integer","minimum":0},"amount_usd":{"type":"number","minimum":0}},"required":["line_no","account_id","period","plan","engine","charged_count","amount_usd"]}요금제와 엔진 한 짝마다 단가와 무료 할당량, 어떤 응답 상태를 과금 대상으로 치는지를 적은 기준표입니다. 판정은 이 표의 값을 읽어 계산하므로 요금제가 바뀌면 코드가 아니라 이 표만 고칩니다.
| rate_no | plan | engine | unit_price | included_quota | billable_status |
|---|---|---|---|---|---|
| RATE-202602-01 | sandbox | google_search | 0.002 | 2 | ["success"] |
| RATE-202602-02 | sandbox | google_shopping | 0.002 | 1 | ["success"] |
| RATE-202602-03 | sandbox | google_jobs | 0.003 | 0 | ["success"] |
| RATE-202602-04 | sandbox | google_news | 0.002 | 1 | ["success"] |
| RATE-202602-05 | sandbox | google_maps | 0.002 | 0 | ["success"] |
아래 JSON Schema 에 맞는 plan_rate 행을 더 만들어 주세요. {"type":"object","properties":{"rate_no":{"type":"string"},"plan":{"type":"string"},"engine":{"type":"string","enum":["google_search","google_shopping","google_jobs","google_news","google_maps","bing","baidu","amazon","youtube","yandex","google_scholar"]},"unit_price":{"type":"number"},"included_quota":{"type":"integer","minimum":0},"billable_status":{"type":"array","items":{"type":"string","enum":["success","timeout","rate_limited","empty_result","invalid_param"]}}},"required":["rate_no","plan","engine","unit_price","included_quota","billable_status"]} unit_price 는 요청 한 건당 달러 단가이고, included_quota 는 달마다 공짜로 주는 건수, billable_status 는 과금 대상으로 치는 응답 상태의 목록입니다.{"type":"object","properties":{"rate_no":{"type":"string"},"plan":{"type":"string"},"engine":{"type":"string","enum":["google_search","google_shopping","google_jobs","google_news","google_maps","bing","baidu","amazon","youtube","yandex","google_scholar"]},"unit_price":{"type":"number"},"included_quota":{"type":"integer","minimum":0},"billable_status":{"type":"array","items":{"type":"string","enum":["success","timeout","rate_limited","empty_result","invalid_param"]}}},"required":["rate_no","plan","engine","unit_price","included_quota","billable_status"]}| Ruby | 요청 로그와 청구서를 엔진별로 맞대어 사유를 계산하는 규칙을 쓰는 언어입니다. |
| Ruby on Rails | 대조 화면과 판정 API 를 한 벌로 세우고 모델로 세 표의 관계를 잡습니다. |
| Hotwire | 사유별 목록과 근거 펼침을 페이지 새로고침 없이 바꿉니다. |
| TailwindCSS | 사이드바와 사유별 목록, 근거 표의 화면을 만듭니다. |
| PostgreSQL | 요청 로그와 청구서, 요금제 세 표를 담고 엔진과 기간 단위로 집계합니다. |
| Redis | 엔진별 판정 결과를 캐시해 화면에 들어올 때마다 다시 세지 않게 합니다. |
| Sidekiq | 청구 기간 전체를 훑는 대조 계산을 백그라운드 작업으로 돌립니다. |
| Stripe | 3주차에 인보이스를 직접 받아 와 청구서 파일 적재를 대신합니다. |
| Terraform | 2주차부터 화면과 데이터베이스를 코드로 올립니다. |
| AWS | 2주차부터 Rails 앱과 PostgreSQL 을 올려 운영합니다. |
| CI/CD | 판정 규칙 테스트를 통과할 때만 배포가 나가게 묶습니다. |
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 문의 초안 생성과 AWS 배포를 붙입니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 Stripe 인보이스 연동과 백그라운드 대조를 넣습니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · line_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
쓴 만큼 청구됨요금제대로실패 응답까지 과금실패 과금요청 수와 청구 건수 차이건수 차이요금제 단가 안 맞음단가 차이무료 할당량 미반영할당량 미반영청구서 한 줄에 대해 요청 로그와 요금제를 맞대 본 결과를 담는 카드입니다. 요금제대로 청구된 줄도 카드로 내보내 고객사 개발자가 무엇이 맞았는지 함께 봅니다.
{"type":"object","properties":{"line_no":{"type":"string"},"account_id":{"type":"string"},"period":{"type":"string"},"engine":{"type":"string","enum":["google_search","google_shopping","google_jobs","google_news","google_maps","bing","baidu","amazon","youtube","yandex","google_scholar"]},"verdict":{"type":"string","enum":["쓴 만큼 청구됨","실패 응답까지 과금","요청 수와 청구 건수 차이","요금제 단가 안 맞음","무료 할당량 미반영"]},"overcharge_usd":{"type":["number","null"],"description":"청구서의 amount_usd 에서 요금제대로 계산한 금액을 뺀 값이며 소수 넷째 자리까지 반올림합니다. 요금제대로의 금액은 그 기간 로그의 과금 대상 요청 수에서 included_quota 를 뺀 건수(0 보다 작으면 0)에 unit_price 를 곱해 냅니다. 그 계산에 들어간 건수와 단가, 할당량은 evidence 에 그대로 싣습니다. 청구서에 그 엔진 줄이 없으면 뺄 값이 없어 null 로 둡니다."},"log_missing":{"type":"boolean","description":"그 (엔진, 기간) 짝의 request_log 행이 한 줄도 없으면 참입니다. 참이면 견줄 로그가 없어 건수 차이의 원인이 과금 오류인지 로그 수집 누락인지 이 화면에서 가리지 못하므로 hedge 에 그 사실을 적습니다."},"summary":{"type":"string"},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":"string","enum":["count"],"description":"이 항목의 value 가 수록 행의 값이 아니라 그 컬럼으로 센 행 수임을 밝힙니다. 없으면 수록 행의 값 그대로입니다."},"value":{"type":["string","number","boolean","null"]},"quote":{"type":"string"}},"required":["field"]}},"hedge":{"type":["string","null"]}},"required":["line_no","account_id","period","engine","verdict","overcharge_usd","summary","evidence","hedge"]}[
{
"line_no": "INV-202602-01",
"account_id": "acct_8812",
"period": "2026-02",
"engine": "google_search",
"verdict": "쓴 만큼 청구됨",
"overcharge_usd": 0,
"summary": "google_search 는 성공 응답 4건이 그대로 과금 대상으로 잡혔고, 거기서 무료 2건을 뺀 2건 몫인 0.004 달러가 청구돼 요금제와 맞습니다.",
"evidence": [
{
"field": "line_no",
"value": "INV-202602-01",
"quote": "INV-202602-01"
},
{
"field": "charged_count",
"value": 4
},
{
"field": "request_id",
"agg": "count",
"value": 4
},
{
"field": "included_quota",
"value": 2
},
{
"field": "unit_price",
"value": 0.002
},
{
"field": "amount_usd",
"value": 0.004
}
],
"hedge": null,
"log_missing": false
},
{
"line_no": "INV-202602-10",
"account_id": "acct_8812",
"period": "2026-02",
"engine": "yandex",
"verdict": "쓴 만큼 청구됨",
"overcharge_usd": 0,
"summary": "yandex 는 결과가 빈 응답 한 건뿐이라 과금 대상이 없고 청구도 0 으로 나갔습니다.",
"evidence": [
{
"field": "line_no",
"value": "INV-202602-10",
"quote": "INV-202602-10"
},
{
"field": "charged_count",
"value": 0
},
{
"field": "amount_usd",
"value": 0
},
{
"field": "status",
"value": "empty_result",
"quote": "empty_result"
},
{
"field": "billed",
"value": false
},
{
"field": "included_quota",
"value": 0
}
],
"hedge": null,
"log_missing": false
},
{
"line_no": "INV-202602-02",
"account_id": "acct_8812",
"period": "2026-02",
"engine": "google_shopping",
"verdict": "실패 응답까지 과금",
"overcharge_usd": 0.004,
"summary": "google_shopping 은 응답을 못 받은 timeout 요청 2건에도 과금 표시가 붙어 0.004 달러가 더 나갔습니다.",
"evidence": [
{
"field": "line_no",
"value": "INV-202602-02",
"quote": "INV-202602-02"
},
{
"field": "request_id",
"value": "req_20260214_0006",
"quote": "req_20260214_0006"
},
{
"field": "status",
"value": "timeout",
"quote": "timeout"
},
{
"field": "billed",
"value": true
},
{
"field": "responded_at",
"value": null
},
{
"field": "request_id",
"agg": "count",
"value": 2
},
{
"field": "unit_price",
"value": 0.002
}
],
"hedge": null,
"log_missing": false
},
{
"line_no": "INV-202602-03",
"account_id": "acct_8812",
"period": "2026-02",
"engine": "amazon",
"verdict": "요청 수와 청구 건수 차이",
"overcharge_usd": 0.004,
"summary": "amazon 은 로그에 남은 과금 대상이 2건인데 청구서에는 4건으로 잡혀 0.004 달러가 더 나갔습니다.",
"evidence": [
{
"field": "line_no",
"value": "INV-202602-03",
"quote": "INV-202602-03"
},
{
"field": "charged_count",
"value": 4
},
{
"field": "request_id",
"agg": "count",
"value": 2
},
{
"field": "included_quota",
"value": 1
},
{
"field": "unit_price",
"value": 0.002
},
{
"field": "amount_usd",
"value": 0.006
}
],
"hedge": "이 계정 로그가 청구 기간을 통째로 담고 있다는 전제에서 센 값입니다. 로그 수집이 끊긴 구간이 있으면 차이의 원인이 달라집니다.",
"log_missing": false
},
{
"line_no": "INV-202602-04",
"account_id": "acct_8812",
"period": "2026-02",
"engine": "google_jobs",
"verdict": "요금제 단가 안 맞음",
"overcharge_usd": 0.004,
"summary": "google_jobs 는 2건에 요금제 단가 0.003 달러를 곱한 0.006 달러여야 하는데 청구서에는 0.01 달러가 적혀 있어 건당 0.005 달러로 계산된 셈입니다.",
"evidence": [
{
"field": "line_no",
"value": "INV-202602-04",
"quote": "INV-202602-04"
},
{
"field": "charged_count",
"value": 2
},
{
"field": "unit_price",
"value": 0.003
},
{
"field": "included_quota",
"value": 0
},
{
"field": "amount_usd",
"value": 0.01
}
],
"hedge": null,
"log_missing": false
},
{
"line_no": "INV-202602-05",
"account_id": "acct_8812",
"period": "2026-02",
"engine": "bing",
"verdict": "무료 할당량 미반영",
"overcharge_usd": 0.002,
"summary": "bing 은 성공 2건에서 무료 1건을 뺀 1건 몫인 0.002 달러가 나가야 하는데 2건 몫이 그대로 청구돼 0.002 달러가 더 나갔습니다.",
"evidence": [
{
"field": "line_no",
"value": "INV-202602-05",
"quote": "INV-202602-05"
},
{
"field": "charged_count",
"value": 2
},
{
"field": "included_quota",
"value": 1
},
{
"field": "unit_price",
"value": 0.002
},
{
"field": "amount_usd",
"value": 0.004
}
],
"hedge": null,
"log_missing": false
},
{
"line_no": "INV-202602-11",
"account_id": "acct_8812",
"period": "2026-02",
"engine": "google_scholar",
"verdict": "요청 수와 청구 건수 차이",
"overcharge_usd": 0.006,
"log_missing": true,
"summary": "google_scholar 는 2026-02 로그에 요청이 한 줄도 없는데 3건이 청구돼 0.006 달러가 근거 없이 나갔습니다.",
"evidence": [
{
"field": "line_no",
"value": "INV-202602-11",
"quote": "INV-202602-11"
},
{
"field": "charged_count",
"value": 3
},
{
"field": "request_id",
"agg": "count",
"value": 0
},
{
"field": "unit_price",
"value": 0.002
},
{
"field": "included_quota",
"value": 0
},
{
"field": "amount_usd",
"value": 0.006
}
],
"hedge": "이 엔진은 요청 로그가 한 줄도 없어 실제로 안 쓴 것인지 로그 수집이 그 엔진만 빠진 것인지 이 화면에서는 가리지 못합니다. 금액 차이는 로그가 그 기간의 전부라는 전제에서 낸 값이라, 상대에게 묻기 전에 수집 설정부터 확인합니다."
}
][자동 검증 · 사람 없이 러너가 돌립니다] 1. verdict 가 스키마가 선언한 다섯 값 안에 있는지 확인합니다. 2. line_no·engine·period·verdict·overcharge_usd·log_missing·summary·evidence 필드가 샘플에 있는지 확인합니다. 3. invoice_line 의 charged_count 와 amount_usd 가 0 이상인지 확인합니다. 4. overcharge_usd 를 amount_usd 빼기 요금제대로 계산한 금액으로 다시 계산해 소수 넷째 자리까지 반올림한 값과 같은지 봅니다. 요금제대로의 금액은 (그 기간 로그의 과금 대상 요청 수 − included_quota, 음수면 0) × unit_price 입니다. 5. evidence 의 field 가 세 데이터셋의 컬럼에 실재하는지 확인합니다. 6. evidence 의 quote 가 수록 샘플의 원문 그대로인지 확인합니다. 7. summary 가 존댓말로 끝나는지 확인합니다. 8. 조건 규칙으로 request_log 의 status 가 plan_rate 의 billable_status 목록에 없으면 billed 는 거짓이어야 합니다. 성립 사례는 req_20260214_0020 이고, 불성립 사례는 req_20260214_0006 입니다. 9. 청구서 한 줄과 견줄 로그는 **엔진과 기간이 둘 다 같은 행**입니다. 로그 행의 기간은 requested_at 의 앞 일곱 글자(연-월)로 정합니다 — 응답을 못 받아 responded_at 이 빈 timeout 행도 이 규약으로 기간이 정해집니다. 그 짝의 로그 행이 한 줄도 없으면 log_missing 을 참으로 둡니다. 10. 과금 대상 요청 수는 그 짝의 로그 행 가운데 status 가 plan_rate.billable_status 에 든 행 수입니다. billed 칸은 상대가 매긴 값이라 이 집계에 쓰지 않고, 8번처럼 어긋남을 찾는 데만 씁니다. [판정 순서 · 한 줄이 여러 사유에 걸릴 때] ① billable_status 밖 상태인데 billed 가 참인 로그 행이 있으면 실패 응답까지 과금입니다. ② charged_count 가 과금 대상 요청 수와 다르면 요청 수와 청구 건수 차이입니다. ③ included_quota 가 1 이상이고 amount_usd 가 charged_count × unit_price(할당량을 빼지 않은 금액)와 같으면 무료 할당량 미반영입니다. ④ 그렇지 않은데 amount_usd 가 (charged_count − included_quota) × unit_price 와 다르면 요금제 단가 안 맞음입니다. ⑤ 어디에도 안 걸리면 쓴 만큼 청구됨입니다. ③을 ④보다 먼저 보는 까닭은, 할당량을 안 빼면 건당 단가가 커 보여 단가 오류로 잘못 떨어지기 때문입니다 — bing(INV-202602-05)이 그 경우로, 건당 0.004 달러로 계산된 것처럼 보이지만 실제 원인은 무료 1건을 안 뺀 것입니다. 반대로 google_jobs(INV-202602-04)는 할당량이 0 이라 ③에 걸리지 않고 ④로 떨어집니다. [수동 검증 · 사람이 봅니다] 1. 요금제 표가 실제 계약 조건과 같은 시점 값인지 봅니다. 2. 문의 초안이 상대에게 그대로 보낼 만한 문장인지 봅니다. [평가 러너 · 2주차] 청구서 11줄에 규칙으로 만든 정답표를 붙여 판정 일치를 세고, 갈라진 줄은 건수 집계에서 갈렸는지 금액 계산에서 갈렸는지 나눠 리포트에 답니다.
근거: Stripe integrations, subscription logic, usage-based billing