바이셀스탠다드
경력근거: 외부 파트너(매칭, 결제·정산 등) API 연동 설계 및 직접 구현
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고가 주요업무에 '외부 파트너(매칭, 결제·정산 등) API 연동 설계 및 직접 구현' 과 '클라이언트 이벤트 검증 및 정산·적립 로직을 안정적으로 운영하는 서버 설계' 를 나란히 적었습니다. 파트너가 준 혜택 중 무엇을 보여줄지 고르는 일이 그 둘을 잇는 자리이고, 잘못 고르면 사용자가 헛걸음하거나 받을 수 있던 적립을 놓칩니다. 이 포트폴리오는 그 선정을 분류별 규칙표로 옮깁니다. '트래픽이 적을 때부터 커진 뒤까지 무너지지 않을 구조' 를 요구한 것도 조건을 코드가 아니라 데이터로 두는 이 설계와 맞습니다. 이 포트폴리오가 보여주는 것은 노출 기준을 데이터로 두고 후보와 맞춰 무엇을 보여줄지 정하는 기술이며, 제휴 계약 협상이나 정산 실무는 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
'외부 파트너(매칭, 결제·정산 등) API 연동 설계 및 직접 구현'. 파트너가 준 혜택 중 무엇을 보여줄지 고르는 일이 그 연동의 앞단입니다.
'클라이언트 이벤트 검증 및 정산·적립 로직을 안정적으로 운영하는 서버 설계'. 무엇을 보여줬는지가 나중 정산의 기준이 됩니다.
'트래픽이 적을 때부터 커진 뒤까지 무너지지 않을 구조'. 규칙을 코드가 아니라 표에 두는 이유입니다.
파트너가 준 혜택 후보 중 이 상품에 무엇을 보여줄지 정해 상품별로 묶어 화면에 보여줍니다. 분류별 노출 규칙에 자리 수와 최소 적립률, 중복 허용 여부, 파트너 순서를 두고 후보와 맞춰, 조건을 채운 후보를 순서대로 세워 보여줄 자리를 매기고 그렇지 않으면 적립률 미달·기간 밖·금액 조건 밖·자리 초과 중 어느 쪽인지 사유로 남깁니다. 규칙에 없는 분류는 미등록으로 남겨 자리 수를 먼저 정하게 합니다. 후보는 실 파트너 자료 대신 지원자가 공개된 제휴 혜택 안내 양식으로 만든 것을 가정하며, 결제는 다루지 않고 무엇을 보여줄지까지만 정합니다.
제휴 운영 담당자입니다. 파트너가 혜택을 새로 주거나 조건을 바꿀 때마다 무엇이 실제로 노출되는지 확인해야 하는데, 지금은 상품 페이지를 하나씩 열어 눈으로 보던 자리에 있습니다.
분류마다 자리 수와 최소 적립률, 파트너 순서를 규칙으로 두고 후보와 맞추면, 보여줄 혜택과 뺄 혜택이 요청을 받는 자리에서 갈리고 뺀 이유가 근거로 남는다는 가정입니다. 규칙에 없는 분류는 미등록으로 빠지므로 아직 정하지 않은 상품군이 그대로 드러납니다.
혜택 후보와 분류별 노출 규칙을 넣으면 후보마다 노출·제외를 판정하고 노출 순서를 매기며, 제외면 적립률 미달·기간 밖·금액 조건 밖·자리 초과·분류 미등록 중 어느 쪽인지 붙여 사유별로 묶어 보여주는 화면이 도는 완결품입니다. 후보를 펼치면 걸린 규칙 행이 나옵니다.
공개 양식으로 만든 후보 2000건에 손으로 붙인 정답 노출과 대조해 일치율을 내고, 규칙 때문에 뒤로 밀린 후보 중 적립률이 더 높았던 건수와 그 차액을 함께 냅니다.
파트너 순서 대신 적립률 순서로 세웠을 때 상품별 최고 적립률이 얼마나 오르는지 표로 내고, 파트너 계약 조건과 어긋나는 건을 따로 모아 제휴 담당에게 넘기는 단계입니다.
제휴 운영 담당자가 혜택 후보와 노출 규칙을 넣으면 후보마다 노출 여부와 자리, 제외 사유가 사유별로 묶여 화면에 뜨는 과정을 시연합니다.
공개 양식으로 만든 후보 2000건과 손으로 붙인 정답 노출을 대조해 일치율을 냅니다. 단, 정답과 후보를 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
적립률이 비었거나 분류 표기가 흔들리거나 규칙에 없는 분류가 섞인 입력 30종을 넣어도 화면이 안 깨지고 사유별로 빠지는 비율을 보여줍니다.
파트너 순서를 적립률 순서로 바꿨을 때 상품별 최고 적립률이 얼마나 달라지는지 회귀로 확인합니다.
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 작성자가 만든 후보의 자가채점이며, 실제 노출 정확도와 사용자가 받은 적립은 바이셀스탠다드의 실 파트너 계약과 실 트래픽이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아니므로, 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 붙였으니 잘 맞는 게 당연하므로, 적립률 결측과 기간 만료, 금액 조건 경계, 규칙에 없는 분류를 일부러 섞어 쉬운 후보만 푸는 함정을 피합니다.
이 포트폴리오가 보여주는 것은 노출 기준을 데이터로 두고 후보와 맞춰 무엇을 보여줄지 정하는 기술이며, 제휴 계약 협상이나 정산 실무는 주장하지 않습니다. 결제는 다루지 않고 무엇을 보여줄지까지만 정하며, 판정은 수치와 날짜 비교가 하고 LLM 은 담당자가 읽을 사유 문장만 맡습니다.
파트너 순서를 표에 고정해 두므로 적립률이 더 높은 혜택이 뒤로 밀릴 수 있습니다. 그래서 밀린 차액을 2주차에 세어 규칙을 다시 볼 근거로 냅니다.
실제 도입 때는 파트너 계약 조건이 노출 순서를 정하는 경우가 있어, 규칙표를 제휴 담당과 함께 채우는 일을 도입 단계 과제로 둡니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
파트너가 준 혜택 중 이 상품에 붙을 수 있는 후보를 편 것입니다. 후보 번호와 상품 키, 상품 가격, 상품 분류, 파트너, 혜택 이름, 적립률, 적용 최소·최대 금액, 시작일, 종료일, 중복 허용 여부를 한 행에 둡니다. 한 상품에 후보가 여럿 붙어 자리를 두고 겨룹니다. 적립률이 빈 행, 종료일이 지난 행, 금액 조건에 안 맞는 행, 규칙표에 없는 분류를 일부러 섞어 두어 사유가 갈리게 둡니다.
| cand_no | product_key | product_price | product_category_raw | partner_raw | offer_raw | rate_pct | min_price | max_price | starts_on | ends_on | stackable_flag |
|---|---|---|---|---|---|---|---|---|---|---|---|
| OC-001 | A-10231 | 129,000 | 디지털 | 알파페이 | 카드 청구할인 | 5 | 50,000 | — | 2026-08-01 | 2026-09-30 | true |
| OC-002 | A-10231 | 129,000 | 디지털 | 베타리워드 | 적립 3% | 3 | — | — | 2026-08-10 | 2026-09-10 | true |
| OC-003 | A-10231 | 129,000 | 디지털 | 감마몰 | 적립 1% | 1 | — | — | 2026-08-01 | 2026-12-31 | false |
| OC-004 | B-88 | 34,000 | 리빙 | 알파페이 | 카드 청구할인 | 5 | 50,000 | — | 2026-08-01 | 2026-09-30 | true |
| OC-005 | B-88 | 34,000 | 리빙 | 베타리워드 | 적립 4% | 4 | — | 100,000 | 2026-08-10 | 2026-09-10 | true |
아래 JSON Schema 를 그대로 만족하는 혜택 후보 행을 2000개 더 만들어 주세요. 상품을 삼백 개쯤 두고 상품마다 후보를 둘에서 다섯 개 붙이며, 파트너를 대여섯 곳으로 섞습니다. 적립률을 0.5% 에서 8% 사이로 두고 2할은 적립률을 비웁니다. 몇 행은 종료일을 지난 날짜로, 몇 행은 상품 가격이 최소 금액에 못 미치게 두세요. 규칙표에 없는 분류도 몇 행 넣습니다. 노출 결과나 자리는 넣지 마세요.
{"type": "object", "properties": {"cand_no": {"type": "string"}, "product_key": {"type": "string"}, "product_price": {"type": ["integer", "null"]}, "product_category_raw": {"type": "string"}, "partner_raw": {"type": "string"}, "offer_raw": {"type": "string"}, "rate_pct": {"type": ["number", "null"]}, "min_price": {"type": ["integer", "null"]}, "max_price": {"type": ["integer", "null"]}, "starts_on": {"type": "string"}, "ends_on": {"type": ["string", "null"]}, "stackable_flag": {"type": "boolean"}}, "required": ["cand_no", "product_key", "product_price", "product_category_raw", "partner_raw", "offer_raw", "rate_pct", "min_price", "max_price", "starts_on", "ends_on", "stackable_flag"]}{"type": "object", "properties": {"cand_no": {"type": "string"}, "product_key": {"type": "string"}, "product_price": {"type": ["integer", "null"]}, "product_category_raw": {"type": "string"}, "partner_raw": {"type": "string"}, "offer_raw": {"type": "string"}, "rate_pct": {"type": ["number", "null"]}, "min_price": {"type": ["integer", "null"]}, "max_price": {"type": ["integer", "null"]}, "starts_on": {"type": "string"}, "ends_on": {"type": ["string", "null"]}, "stackable_flag": {"type": "boolean"}}, "required": ["cand_no", "product_key", "product_price", "product_category_raw", "partner_raw", "offer_raw", "rate_pct", "min_price", "max_price", "starts_on", "ends_on", "stackable_flag"]}분류별 노출 규칙표입니다. 규칙 번호와 분류, 보여줄 자리 수, 최소 적립률, 중복 허용 여부, 파트너 순서, 담당을 한 행에 둡니다. 분류마다 자리 수와 하한이 달라 같은 후보도 어느 상품에 붙느냐로 결과가 갈립니다. 후보에 나오는 분류 중 일부는 일부러 넣지 않아 미등록이 도출되게 둡니다.
| rule_no | category_raw | max_slots | min_rate_pct | allow_stack | partner_order | owner_name |
|---|---|---|---|---|---|---|
| PR-01 | 디지털 | 2 | 2 | true | ["알파페이","엡실론","베타리워드","감마몰"] | 제휴운영 |
| PR-02 | 리빙 | 2 | 2 | true | ["베타리워드","알파페이","감마몰"] | 제휴운영 |
| PR-03 | 생활 | 1 | 1 | false | ["델타쿠폰","베타리워드","감마몰"] | 제휴운영 |
| PR-04 | 뷰티 | 2 | 2 | true | ["알파페이","델타쿠폰","베타리워드"] | 제휴운영 |
| PR-05 | 패션 | 2 | 3 | true | ["베타리워드","알파페이"] | 제휴운영 |
아래 JSON Schema 를 그대로 만족하는 노출 규칙 행을 20개 더 만들어 주세요. 분류마다 자리 수를 1 에서 3 사이로, 최소 적립률을 1% 에서 3% 사이로 두고 중복 허용을 참·거짓으로 섞습니다. 파트너 순서는 파트너 이름 두셋을 배열로 적습니다. 후보에 나오는 분류 중 일부는 일부러 넣지 마세요 — 미등록이 있어야 규칙표의 구멍이 드러납니다. 노출 결과는 넣지 마세요.
{"type": "object", "properties": {"rule_no": {"type": "string"}, "category_raw": {"type": "string"}, "max_slots": {"type": "integer"}, "min_rate_pct": {"type": "number"}, "allow_stack": {"type": "boolean"}, "partner_order": {"type": "array", "items": {"type": "string"}}, "owner_name": {"type": "string"}}, "required": ["rule_no", "category_raw", "max_slots", "min_rate_pct", "allow_stack", "partner_order", "owner_name"]}{"type": "object", "properties": {"rule_no": {"type": "string"}, "category_raw": {"type": "string"}, "max_slots": {"type": "integer"}, "min_rate_pct": {"type": "number"}, "allow_stack": {"type": "boolean"}, "partner_order": {"type": "array", "items": {"type": "string"}}, "owner_name": {"type": "string"}}, "required": ["rule_no", "category_raw", "max_slots", "min_rate_pct", "allow_stack", "partner_order", "owner_name"]}| Node.js | 후보와 노출 규칙을 맞춰 조건을 견주고 순서를 매기는 선정기를 맡습니다. |
| TypeScript | 규칙표를 형으로 묶어 판정 결과와 어긋나지 않게 합니다. |
| PostgreSQL | 혜택 후보와 노출 규칙, 판정 결과를 담습니다. |
| React | 상품별 노출 자리와 제외 사유를 보여주는 화면을 맡습니다. |
✗ fail → 미등록이 절반을 넘으면 규칙표를 먼저 채웁니다.
✓ pass → 2주차 손해 계산으로 넘어갑니다.
✗ fail → 어긋난 분류를 모아 하한과 순서를 다시 봅니다.
✓ pass → 3주차 순서 기준 비교로 넘어갑니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
pick_code사유 라벨 · cand_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
shown노출rate_too_low적립률 미달out_of_period기간 밖price_out_of_band금액 조건 밖slot_over자리 초과category_unregistered분류 미등록혜택 후보 하나와 노출 규칙이 만나는 자리마다 보여줄지 정하고 자리를 매깁니다. 못 보여주면 적립률 미달·기간 밖·금액 조건 밖·자리 초과·분류 미등록 중 어느 쪽인지 싣습니다. 노출 계획은 이 사유 코드로 묶습니다.
{"type": "object", "properties": {"verdict_no": {"type": "string"}, "cand_no": {"type": "string"}, "product_key": {"type": "string"}, "partner_raw": {"type": "string"}, "rule_no": {"type": ["string", "null"]}, "pick_code": {"type": "string", "enum": ["shown", "rate_too_low", "out_of_period", "price_out_of_band", "slot_over", "category_unregistered"]}, "pick_rank": {"type": ["integer", "null"], "description": "그 상품에 보여줄 순서입니다. 파트너 우선순위대로 세우고 노출로 정해진 후보에만 1부터 채웁니다."}, "min_rate_pct": {"type": ["number", "null"]}, "owner_name": {"type": ["string", "null"]}, "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": ["verdict_no", "cand_no", "product_key", "partner_raw", "pick_code", "evidence", "summary"]}[
{
"verdict_no": "OP-001",
"cand_no": "OC-001",
"product_key": "A-10231",
"partner_raw": "알파페이",
"rule_no": "PR-01",
"pick_code": "shown",
"pick_rank": 1,
"min_rate_pct": 2,
"owner_name": "제휴운영",
"evidence": [
{
"field": "rate_pct",
"agg": null,
"value": 5
},
{
"field": "partner_order",
"agg": null,
"value": [
"알파페이",
"엡실론",
"베타리워드",
"감마몰"
]
}
],
"summary": "조건을 다 채우고 파트너 순서가 가장 앞이라 첫 자리에 보여줍니다.",
"hedge": null
},
{
"verdict_no": "OP-002",
"cand_no": "OC-011",
"product_key": "A-10231",
"partner_raw": "엡실론",
"rule_no": "PR-01",
"pick_code": "shown",
"pick_rank": 2,
"min_rate_pct": 2,
"owner_name": "제휴운영",
"evidence": [
{
"field": "rate_pct",
"agg": null,
"value": 6
},
{
"field": "max_slots",
"agg": null,
"value": 2
}
],
"summary": "적립률이 더 높지만 파트너 순서가 뒤라 두 번째 자리에 보여줍니다.",
"hedge": "순서를 적립률로 바꿀지 제휴 담당과 정할 여지가 있습니다."
},
{
"verdict_no": "OP-003",
"cand_no": "OC-002",
"product_key": "A-10231",
"partner_raw": "베타리워드",
"rule_no": "PR-01",
"pick_code": "slot_over",
"pick_rank": null,
"min_rate_pct": 2,
"owner_name": "제휴운영",
"evidence": [
{
"field": "max_slots",
"agg": null,
"value": 2
},
{
"field": "cand_no",
"agg": "count",
"value": 4
}
],
"summary": "자리가 둘인데 앞선 후보가 이미 채워 이번에는 못 보여줍니다.",
"hedge": null
},
{
"verdict_no": "OP-004",
"cand_no": "OC-003",
"product_key": "A-10231",
"partner_raw": "감마몰",
"rule_no": "PR-01",
"pick_code": "rate_too_low",
"pick_rank": null,
"min_rate_pct": 2,
"owner_name": "제휴운영",
"evidence": [
{
"field": "rate_pct",
"agg": null,
"value": 1
},
{
"field": "min_rate_pct",
"agg": null,
"value": 2
}
],
"summary": "1% 는 이 분류의 하한 2% 에 못 미쳐 보여줄 값이 없습니다.",
"hedge": null
},
{
"verdict_no": "OP-005",
"cand_no": "OC-004",
"product_key": "B-88",
"partner_raw": "알파페이",
"rule_no": "PR-02",
"pick_code": "price_out_of_band",
"pick_rank": null,
"min_rate_pct": 2,
"owner_name": "제휴운영",
"evidence": [
{
"field": "product_price",
"agg": null,
"value": 34000
},
{
"field": "min_price",
"agg": null,
"value": 50000
}
],
"summary": "5만원부터 되는 혜택이라 3만4천원 상품에는 안 붙습니다.",
"hedge": null
},
{
"verdict_no": "OP-006",
"cand_no": "OC-006",
"product_key": "C-5501",
"partner_raw": "델타쿠폰",
"rule_no": "PR-03",
"pick_code": "out_of_period",
"pick_rank": null,
"min_rate_pct": 1,
"owner_name": "제휴운영",
"evidence": [
{
"field": "ends_on",
"agg": null,
"value": "2026-08-20"
},
{
"field": "rate_pct",
"agg": null,
"value": 7
}
],
"summary": "적립률이 가장 높지만 기간이 끝나 보여주면 사용자가 헛걸음합니다.",
"hedge": null
},
{
"verdict_no": "OP-007",
"cand_no": "OC-007",
"product_key": "C-5501",
"partner_raw": "베타리워드",
"rule_no": "PR-03",
"pick_code": "shown",
"pick_rank": 1,
"min_rate_pct": 1,
"owner_name": "제휴운영",
"evidence": [
{
"field": "rate_pct",
"agg": null,
"value": 2
},
{
"field": "max_slots",
"agg": null,
"value": 1
}
],
"summary": "자리가 하나인 분류라 앞선 후보가 기간에서 빠져 이 후보가 올라옵니다.",
"hedge": null
},
{
"verdict_no": "OP-008",
"cand_no": "OC-010",
"product_key": "E-12",
"partner_raw": "베타리워드",
"rule_no": null,
"pick_code": "category_unregistered",
"pick_rank": null,
"min_rate_pct": null,
"owner_name": null,
"evidence": [
{
"field": "product_category_raw",
"agg": null,
"value": "반려"
},
{
"field": "rule_no",
"agg": "count",
"value": 12
}
],
"summary": "노출 규칙 열두 행에 없는 분류라 몇 개를 어느 순서로 보여줄지 정해 두지 않았습니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. pick_code 가 enum 여섯 값 중 하나인지 확인합니다. 2. verdict_no·cand_no·product_key·partner_raw·pick_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. cand_no 가 후보 목록에 실재하는 번호인지 대조합니다. 4. rule_no 가 있으면 규칙표에 실재하는 행 번호인지 대조합니다. 5. rule_no 가 있으면 min_rate_pct·owner_name 이 그 규칙 행의 값과 같은지 대조합니다. 6. product_category_raw 를 띄어쓰기를 접어 규칙표와 맞추고, 맞는 행이 없으면 pick_code 가 category_unregistered 인지 확인합니다. 7. rate_pct 가 비었거나 min_rate_pct 에 못 미치면 pick_code 가 rate_too_low 인지 다시 확인합니다. 8. 실행 기준일이 starts_on 과 ends_on 사이가 아니거나 ends_on 이 비어 있으면 pick_code 가 out_of_period 인지 다시 계산합니다. 9. product_price 가 min_price 에 못 미치거나 max_price 를 넘으면 pick_code 가 price_out_of_band 인지 다시 확인합니다. 10. 조건을 채운 후보를 partner_order 순서대로 세운 뒤 max_slots 를 넘는 후보의 pick_code 가 slot_over 인지 다시 셉니다. 11. pick_code 가 shown 이면 pick_rank 가 1 부터 max_slots 까지의 값이고 같은 상품 안에서 겹치지 않는지 확인합니다. 12. allow_stack 이 거짓인 분류에서 노출된 후보가 하나뿐인지 확인합니다. 13. evidence 의 field 이름이 후보 목록과 규칙표에 실재하는 컬럼인지 대조합니다. 14. agg 가 없는 evidence 의 value 가 그 컬럼에 수록된 행 값과 같은지 확인합니다. 15. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 공개 양식으로 만든 후보 2000건에 손으로 붙인 정답 노출과 대조해 일치율과 사유 코드별 분포를 내고, 뒤로 밀린 후보 중 적립률이 더 높았던 건수와 차액을 함께 냅니다. [수동 검증 · 사람이 봅니다] 1. 새 분류의 자리 수와 하한을 제휴 담당이 정합니다. 2. 파트너 순서를 적립률 순서로 바꿀지 계약 조건을 보고 정합니다.