레시피온
경력근거: 구독·과금, 결제, 월정산, 다국어를 갖춘 B2B SaaS이며 PACS·모바일 앱 파트너에게 API를 제공합니다.
※ 실제 산출물이 아닌 AI 예측 시안입니다.
이 대조는 레시피온의 과금 구조가 있어야 성립합니다. 판독 모델 10종이 각각 건수로 세어지고 그 건수가 병원별 구독 요금제의 포함 건수와 초과 단가를 거쳐 금액이 되는 구조라, 병원과 모델과 월 세 축으로 묶어야 정산이 맞는지 갈립니다. 판독이 단일 요금이거나 정액이면 이 도구는 할 일이 없습니다. 이 포트폴리오는 정산 대조 규칙과 화면을 보여줍니다. 수의영상의학과 DICOM, PACS 도메인 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
판독 모델 10종이 연 15만 건을 처리하고 그 건수가 병원별 월정산의 근거가 됩니다. 건수와 청구가 갈라지면 정산이 무너집니다.
외부 개발사가 4년간 운영한 시스템을 인수하는 중이고 여러 컴포넌트가 DB 에 직접 접근합니다. 어느 경로가 청구를 만드는지 코드로 좇기 어렵습니다.
PG 연동과 구독 과금, 정산 대조를 우대사항에 명시했습니다. 정산 대조는 이 회사가 사람을 뽑아 맡기려는 일입니다.
동물병원 영상판독 SaaS 의 월정산을 마감할 때 판독 기록과 청구서가 어긋난 병원과 판독 모델을 사유별로 보여주는 관리자 페이지입니다. 판독 로그와 청구 내역과 요금제 세 장을 받아, 병원 코드가 비어 있는 판독 행은 이름 표기를 정규화해 같은 병원으로 이은 뒤 병원과 판독 모델과 정산월로 묶어 견주고, 어긋난 곳마다 어느 숫자와 어느 숫자가 달랐는지를 함께 보여줍니다.
레시피온 운영팀에서 동물병원 월정산을 마감하는 담당자입니다. 병원마다 판독 모델 10종을 각각 몇 건 썼는지와 청구서에 적힌 건수를 표로 하나씩 맞춰 보다가, 숫자가 어긋나면 어느 쪽이 틀렸는지 판독 로그를 다시 뒤지는 자리에 있습니다.
정산이 어긋나는 자리는 대개 다섯입니다. 판독은 있는데 청구가 빠졌거나, 청구는 있는데 판독이 없거나, 건수가 서로 다르거나, 요금제 포함 건수를 넘긴 초과분이 금액에 안 실렸습니다. 모두 판독 로그와 청구 내역과 요금제를 같은 키로 묶으면 규칙으로 갈립니다. 판정은 건수를 먼저 봅니다. 한쪽 표에만 있으면 그 사유가 먼저 서고, 건수가 다르면 덜 청구·더 청구로 갈리며, 건수가 같은 칸에서만 기대 청구액과 실제 청구 금액을 견주어 초과분만 빠진 칸을 포함 건수 초과 미청구로 냅니다.
판독 로그와 청구 내역과 요금제 JSON 세 장을 올리면 화면이 돕니다. 병원 코드가 비어 있는 판독 행은 이름 표기에서 공백과 괄호를 걷어낸 키로 같은 병원의 코드에 잇고, 어느 키에도 못 붙은 행은 미매핑으로 따로 세웁니다. 그렇게 모은 병원 코드 위에서 병원과 모델과 월로 묶어 건수로 갈리는 다섯 사유로 나누고, 각 행에 판독 건수와 청구 건수, 그 차이를 함께 보여줍니다.
1주차 판정에 요금제 포함 건수와 초과 단가를 얹어 기대 청구액을 다시 세우고, 건수는 맞는데 초과분 금액만 빠진 칸을 여섯 번째 사유인 포함 건수 초과 미청구로 붙입니다. 각 행에 초과 건수와 기대 청구액, 실제 청구 금액의 차이가 함께 뜹니다.
2주차 판정 결과를 정산월 단위 리포트로 만들어 병원별 차이와 금액 합계를 한 장으로 내려받습니다. 지난달 판정과 견줘 반복해서 어긋나는 병원을 표시합니다.
정산 담당자가 판독 로그와 청구 내역과 요금제 세 장을 올려 어긋난 병원과 모델을 사유별로 확인하는 것을 시연합니다.
여섯 사유가 각각 최소 한 건씩 나오는 더미로 판정이 갈리고, 각 행에 근거 두 숫자와 그 차이가 함께 뜹니다.
일부러 만든 결측과 중복, 미매핑, 표기흔들림 입력에서도 판정 결과 형식이 안 깨지고 그 행이 사유로 돌아갑니다.
정답을 붙인 더미 100칸으로 여섯 사유가 갈리는 것을 보여줍니다.
여기까지가 이 포트폴리오가 말할 수 있는 전부입니다. 위 100칸은 제가 만든 정산 더미를 스스로 채점한 결과일 뿐이고, 실제 정산 오차율은 레시피온의 판독 로그와 실제 청구서를 넣어야 나옵니다. 대신 그 측정을 그날 바로 돌릴 수 있게 대조 규칙과 평가 러너까지 만들어 둡니다.
이 화면의 100칸 채점은 성능 측정이 아니라 동작 확인입니다. 정산 더미와 정답을 제가 함께 만들었기 때문이며, 면접에서는 그 한계를 먼저 말한 뒤 무엇이 있어야 진짜 측정이 되는지로 넘어갑니다.
여섯 사유의 정답을 제가 직접 붙였으니, 판정이 맞아떨어지는 것은 채점 결과라기보다 출제 결과에 가깝습니다. 중복된 판독 번호와 병원 코드가 비어 있는 미매핑 행, 포함 건수가 0인 경계 요금제, 띄어쓰기만 다른 표기흔들림 병원 이름을 정산 더미에 미리 심어 두고 각 행이 어느 사유로 떨어지는지까지 정답에 적었습니다.
판독 건수가 청구 건수와 다르다고 해서 어느 쪽이 틀렸는지는 이 도구가 정하지 못합니다. 도구는 어긋난 자리와 근거 두 숫자를 보여주고 판단은 담당자가 합니다.
실제 도입에서는 판독 상태 값이 여기 쓴 세 가지보다 많고 재판독과 환불 같은 흐름이 붙습니다. 그때 사유 enum 을 다시 잡아야 합니다.
요금제가 병원마다 중간에 바뀌는 경우는 다루지 않았습니다. 적용 기간을 가진 요금제 이력 표가 있어야 풀립니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
판독 로그입니다. 어느 병원에서 어느 판독 모델을 언제 돌렸고 성공했는지가 한 행입니다. 청구 근거가 되는 건수를 여기서 셉니다.
| reading_no | hospital_id | hospital_name_raw | model_code | read_at | status |
|---|---|---|---|---|---|
| RD-202607-0001 | H001 | 서울숲동물의료센터 | thorax_xray | 2026-07-03T09:12:00+09:00 | success |
| RD-202607-0002 | H001 | 서울숲동물의료센터 | thorax_xray | 2026-07-05T14:20:00+09:00 | success |
| RD-202607-0003 | H002 | 해운대우리동물병원 | joint_angle | 2026-07-06T10:02:00+09:00 | success |
| RD-202607-0004 | H002 | 해운대우리동물병원 | joint_angle | 2026-07-09T11:40:00+09:00 | success |
| RD-202607-0005 | H003 | 대전메디컬동물병원 | abdomen_us | 2026-07-11T15:33:00+09:00 | success |
다음 JSON Schema 에 맞는 판독 로그 행을 더 만들어 주세요. {"type":"object","properties":{"reading_no":{"type":"string"},"hospital_id":{"type":["string","null"]},"hospital_name_raw":{"type":["string","null"]},"model_code":{"type":"string"},"read_at":{"type":"string"},"status":{"type":"string","enum":["success","failed","canceled"]}},"required":["reading_no","model_code","read_at","status"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) hospital_name_raw 가 null 인 결측 행, (2) 같은 reading_no 가 두 번 오는 중복 행, (3) status 가 failed 나 canceled 인 경계 행, (4) hospital_id 가 null 이고 이름만 있는 미매핑 행, (5) 같은 병원인데 띄어쓰기가 다른 표기흔들림 행. 그리고 형식 깨뜨리기 시도 3건(read_at 을 날짜만 쓰기, status 에 없는 값 넣기, reading_no 를 숫자로 넣기)을 따로 표시해 주세요.{"type":"object","properties":{"reading_no":{"type":"string"},"hospital_id":{"type":["string","null"]},"hospital_name_raw":{"type":["string","null"]},"model_code":{"type":"string"},"read_at":{"type":"string"},"status":{"type":"string","enum":["success","failed","canceled"]}},"required":["reading_no","model_code","read_at","status"]}월정산 청구 내역입니다. 어느 병원에 어느 판독 모델을 몇 건으로 얼마 청구했는지가 한 행입니다. 판독 로그와 견주는 상대 표입니다.
| line_no | hospital_id | period | model_code | billed_count | amount |
|---|---|---|---|---|---|
| IL-202607-001 | H001 | 2026-07 | thorax_xray | 2 | 12,000 |
| IL-202607-002 | H002 | 2026-07 | joint_angle | 2 | 15,000 |
| IL-202607-003 | H003 | 2026-07 | abdomen_us | 2 | 18,000 |
| IL-202607-004 | H005 | 2026-07 | thorax_xray | 3 | 36,000 |
| IL-202607-005 | H006 | 2026-07 | thorax_xray | 2 | 0 |
다음 JSON Schema 에 맞는 청구 내역 행을 더 만들어 주세요. {"type":"object","properties":{"line_no":{"type":"string"},"hospital_id":{"type":"string"},"period":{"type":"string"},"model_code":{"type":"string"},"billed_count":{"type":"integer"},"amount":{"type":["integer","null"]}},"required":["line_no","hospital_id","period","model_code","billed_count"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) amount 가 null 인 결측 행, (2) billed_count 가 0 인 경계 행, (3) 판독 로그에 없는 병원과 모델 조합의 행, (4) 지난달 period 행. 그리고 형식 깨뜨리기 시도 3건(billed_count 를 문자열로, period 를 연도만, hospital_id 를 비우기)을 따로 표시해 주세요.{"type":"object","properties":{"line_no":{"type":"string"},"hospital_id":{"type":"string"},"period":{"type":"string"},"model_code":{"type":"string"},"billed_count":{"type":"integer"},"amount":{"type":["integer","null"]}},"required":["line_no","hospital_id","period","model_code","billed_count"]}병원별 구독 요금제입니다. 기본료가 덮어 주는 판독 건수와 그것을 넘겼을 때의 초과 단가가 한 행입니다. 포함 건수와 단가는 병원과 판독 모델 한 칸(정산월 기준)에 적용되며, 기대 청구액은 max(0, 성공 판독 건수 − included_count) × unit_price 로 섭니다. 기본료가 덮는 만큼은 청구 행에 실리지 않고 초과분만 금액이 되므로, 이 식으로 되돌린 금액과 청구 금액을 견주면 초과분이 빠진 칸이 드러납니다.
| hospital_id | plan_code | included_count | unit_price |
|---|---|---|---|
| H001 | BASIC | 1 | 12,000 |
| H002 | BASIC | 1 | 15,000 |
| H003 | BASIC | 1 | 18,000 |
| H004 | BASIC | 1 | 20,000 |
| H005 | TRIAL | 0 | 12,000 |
다음 JSON Schema 에 맞는 요금제 행을 더 만들어 주세요. {"type":"object","properties":{"hospital_id":{"type":"string"},"plan_code":{"type":"string"},"included_count":{"type":["integer","null"]},"unit_price":{"type":"integer"}},"required":["hospital_id","plan_code","unit_price"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) included_count 가 null 인 결측 행, (2) included_count 가 0 인 체험 요금제 경계 행, (3) 판독 로그에 한 번도 안 나오는 병원 행. 그리고 형식 깨뜨리기 시도 2건(unit_price 를 문자열로, included_count 를 음수로)을 따로 표시해 주세요.{"type":"object","properties":{"hospital_id":{"type":"string"},"plan_code":{"type":"string"},"included_count":{"type":["integer","null"]},"unit_price":{"type":"integer"}},"required":["hospital_id","plan_code","unit_price"]}| Java | 판독 로그와 청구 내역과 요금제를 읽어 병원과 모델과 월로 묶고 여섯 사유로 가르는 대조 규칙을 Java 로 씁니다. |
| Spring Boot | 대조 규칙을 REST 로 열어 화면이 정산월을 바꿔 가며 부르게 합니다. |
| PostgreSQL | 판독 로그와 청구 내역을 병원과 모델과 월 키로 색인해 정산월 단위 집계를 한 번에 읽습니다. |
| TypeScript | 정산 담당자가 보는 대조 화면을 만들고 사유별 묶음과 근거 두 숫자를 같은 행에 붙입니다. |
| Kubernetes | 대조 API 와 화면을 같은 차트로 묶어 로컬 클러스터에 올리고 정산월 재계산을 잡으로 돌립니다. |
| CI/CD | 대조 규칙 테스트를 커밋마다 돌려 여섯 사유의 판정이 바뀌지 않았는지 확인합니다. |
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 요금제 초과분 금액 계산을 얹습니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 정산월 리포트와 반복 어긋남 표시를 만듭니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · recon_key + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
matched맞음undercharged덜 청구overcharged더 청구unbilled_reading청구 없음billed_without_reading판독 없음over_plan_limit_unbilled포함 건수 초과 미청구병원과 판독 모델과 정산월로 묶은 한 칸의 판정입니다. 어느 사유로 어긋났는지와 그 판단의 근거가 된 두 숫자를 함께 싣습니다.
{"type":"object","properties":{"recon_key":{"type":"string"},"hospital_id":{"type":["string","null"]},"period":{"type":"string"},"model_code":{"type":"string"},"verdict":{"type":"string","enum":["matched","undercharged","overcharged","unbilled_reading","billed_without_reading","over_plan_limit_unbilled"]},"read_count":{"type":["integer","null"]},"billed_count":{"type":["integer","null"]},"count_gap":{"type":["integer","null"],"description":"read_count 에서 billed_count 를 뺀 값입니다. 한쪽이 없으면 null 입니다."},"unbilled_excess_amount":{"type":["integer","null"],"description":"기대 청구액 max(0, 성공 판독 건수 − included_count) × unit_price 에서 실제 amount 를 뺀 금액입니다. 기본료가 included_count 까지 덮고 그 초과분만 unit_price 로 청구되는 구독 모형이라, 이 값이 0 보다 크면 초과분이 청구 금액에 실리지 않은 것입니다. 그 외에는 null 입니다."},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":"string"},"value":{}},"required":["field","value"]}},"reason":{"type":"string"}},"required":["recon_key","period","model_code","verdict","evidence","reason"]}[
{
"recon_key": "H001|2026-07|thorax_xray",
"hospital_id": "H001",
"period": "2026-07",
"model_code": "thorax_xray",
"verdict": "matched",
"read_count": 2,
"billed_count": 2,
"count_gap": 0,
"unbilled_excess_amount": null,
"evidence": [
{
"field": "reading_no",
"agg": "count",
"value": 2
},
{
"field": "billed_count",
"value": 2
}
],
"reason": "성공한 판독 2건과 청구 2건이 같습니다."
},
{
"recon_key": "H002|2026-07|joint_angle",
"hospital_id": "H002",
"period": "2026-07",
"model_code": "joint_angle",
"verdict": "undercharged",
"read_count": 3,
"billed_count": 2,
"count_gap": 1,
"unbilled_excess_amount": null,
"evidence": [
{
"field": "reading_no",
"agg": "count",
"value": 3
},
{
"field": "billed_count",
"value": 2
},
{
"field": "hospital_name_raw",
"value": "해운대 우리동물병원"
}
],
"reason": "성공한 판독 3건 중 2건만 청구됐습니다. 병원 코드가 비어 있던 표기흔들림 행 하나를 이름 정규화로 같은 병원에 이어 세었습니다."
},
{
"recon_key": "H003|2026-07|abdomen_us",
"hospital_id": "H003",
"period": "2026-07",
"model_code": "abdomen_us",
"verdict": "overcharged",
"read_count": 1,
"billed_count": 2,
"count_gap": -1,
"unbilled_excess_amount": null,
"evidence": [
{
"field": "reading_no",
"agg": "count",
"value": 1
},
{
"field": "billed_count",
"value": 2
}
],
"reason": "성공한 판독은 1건인데 2건이 청구됐습니다. 취소된 판독 1건은 세지 않았습니다."
},
{
"recon_key": "H004|2026-07|cardiac_vhs",
"hospital_id": "H004",
"period": "2026-07",
"model_code": "cardiac_vhs",
"verdict": "unbilled_reading",
"read_count": 2,
"billed_count": null,
"count_gap": null,
"unbilled_excess_amount": null,
"evidence": [
{
"field": "reading_no",
"agg": "count",
"value": 2
},
{
"field": "reading_no",
"value": "RD-202607-0007"
}
],
"reason": "판독 2건이 있는데 그 병원과 모델과 월의 청구 행이 없습니다. 청구 건수가 없어 차이는 비웁니다."
},
{
"recon_key": "H005|2026-07|thorax_xray",
"hospital_id": "H005",
"period": "2026-07",
"model_code": "thorax_xray",
"verdict": "billed_without_reading",
"read_count": null,
"billed_count": 3,
"count_gap": null,
"unbilled_excess_amount": null,
"evidence": [
{
"field": "billed_count",
"value": 3
},
{
"field": "line_no",
"value": "IL-202607-004"
}
],
"reason": "3건이 청구됐는데 그 병원과 모델과 월의 판독 기록이 없습니다."
},
{
"recon_key": "H006|2026-07|thorax_xray",
"hospital_id": "H006",
"period": "2026-07",
"model_code": "thorax_xray",
"verdict": "over_plan_limit_unbilled",
"read_count": 2,
"billed_count": 2,
"count_gap": 0,
"unbilled_excess_amount": 12000,
"evidence": [
{
"field": "reading_no",
"agg": "count",
"value": 2
},
{
"field": "billed_count",
"value": 2
},
{
"field": "included_count",
"value": 1
},
{
"field": "unit_price",
"value": 12000
},
{
"field": "amount",
"value": 0
}
],
"reason": "판독 2건과 청구 2건은 같지만, 체험 요금제 포함 1건을 넘긴 초과 1건의 기대 청구액 12,000원이 청구 금액에 실리지 않아 amount 가 0원입니다."
},
{
"recon_key": "미매핑|2026-07|abdomen_us",
"hospital_id": null,
"period": "2026-07",
"model_code": "abdomen_us",
"verdict": "unbilled_reading",
"read_count": 1,
"billed_count": null,
"count_gap": null,
"unbilled_excess_amount": null,
"evidence": [
{
"field": "reading_no",
"value": "RD-202607-0011"
},
{
"field": "hospital_name_raw",
"value": "신규제휴 동물병원(미등록)"
}
],
"reason": "병원 코드가 비어 있어 어느 병원의 청구와 견줄지 정해지지 않았습니다. 병원 등록이 먼저입니다."
}
][자동 검증 · 1주차] 1. verdict 값이 여섯 enum 안에 있는지 확인합니다. 2. recon_key 와 period, model_code, verdict, evidence, reason 이 모두 있는지 확인합니다. 3. count_gap 은 read_count 에서 billed_count 를 뺀 값과 같은지 다시 계산합니다. 한쪽이 null 이면 count_gap 도 null 인지 확인합니다. 4. evidence 의 field 가 판독 로그와 청구 내역과 요금제 세 표의 컬럼에 실재하는지 확인합니다. 5. agg 가 없는 evidence 항목의 value 가 수록된 행의 그 컬럼 값과 같은지 확인합니다. 6. 모든 reason 이 존댓말로 끝나는지 확인합니다. [자동 검증 · 2주차] 1. 기대 청구액을 max(0, 성공 판독 건수 − included_count) × unit_price 로 다시 계산하고, unbilled_excess_amount 가 그 기대 청구액에서 invoice_lines.amount 를 뺀 값과 같은지 확인합니다. included_count 가 null 인 병원은 기대 청구액을 세울 수 없으므로 unbilled_excess_amount 를 null 로 둡니다. [수동 검증] 1. 표기가 흔들린 병원 행을 같은 병원으로 묶은 판단이 맞는지 사람이 봅니다. 2. 취소와 실패한 판독을 세지 않는 규칙이 실제 과금 정책과 맞는지 정산 담당자에게 확인합니다. [평가 러너 · 2주차] 1. 정답을 붙인 더미 100칸으로 여섯 사유의 정밀도와 재현율을 냅니다. 2. 표기흔들림 정규화의 적중률을 별칭 사전 유무로 나눠 견줍니다. 중복으로 들어온 판독 행은 같은 reading_no 를 한 건으로 세어 어긋난 것이 없으므로 별도 출력을 내지 않습니다. 판정 우선순위는 건수가 먼저입니다. 한쪽 표에만 있으면 청구 없음·판독 없음이 먼저 서고, 건수가 다르면 덜 청구·더 청구로 갈립니다. 포함 건수 초과 미청구는 건수가 같은 칸에서만 나옵니다 — 판독 건수와 청구 건수는 맞는데 기대 청구액보다 amount 가 적은 칸이 그것입니다.
근거: 구독·과금, 결제, 월정산, 다국어를 갖춘 B2B SaaS이며 PACS·모바일 앱 파트너에게 API를 제공합니다.