스위트스팟
경력근거: 포인트·결제·재화 등 데이터 정합성이 중요한 시스템을 다뤄봤거나 트랜잭션 설계를 이해하고 있으신 분
※ 실제 산출물이 아닌 AI 예측 시안입니다.
이 도구가 보여주는 것은 포인트·재화 원장의 트랜잭션 설계와 멱등 처리, 잔액 불변식 기반 정합성 검증, LLM 출력 검증 기술이며, 팝업스토어나 리테일 도메인 전문성을 주장하지 않습니다. 다만 이 사슬이 말이 되려면 스위트스팟 팝가라는 맥락, 즉 방문·후기·사전예약으로 포인트가 쌓이고 웨이팅·굿즈 구매로 재화가 빠지는 B2C·B2B 서비스라는 문제가 있어야 의미가 있습니다. ‘규칙을 어긴 거래는 사유 문구로도 적용 못 한다’ 는 각도는 다른 결제·정산 도메인으로 옮길 수 있지만, 적립·차감 소스와 재화 종류가 있어야 규칙이 구체화됩니다. 실제 포인트 정책의 세부(소멸·한도·프로모션)는 운영 데이터가 있어야 하고, 면접에서 모르는 부분은 모른다고 말하는 편이 낫습니다.
‘팝가 서비스 백엔드 설계·개발·운영’ 이 주력 영역입니다. 포인트·재화 원장 API 와 트랜잭션 규칙 엔진을 한 사람이 설계·운영하는 제품이 이 업무를 그대로 씁니다.
‘포인트·결제·재화 등 데이터 정합성이 중요한 시스템, 트랜잭션 설계 이해’ 가 우대사항입니다. 멱등 처리·잔액 불변식·이중적립 차단이 이 우대를 골조로 보여줍니다.
‘지표 정의·측정 포함한 데이터 기반 기능 개선’ 과 ‘성과를 지표로 설명’ 이 업무입니다. 정합성 위반 차단율·잔액 오차를 지표로 정의하고 대시보드로 보여주는 제품이 필요합니다.
팝업 포인트·재화 지갑의 적립·차감 거래 스트림을 받아, 멱등 처리로 이중적립을 막고 잔액 불변식(음수 금지)으로 초과 차감을 막으며, 미등록 지갑·동결 지갑·0 이하 금액 같은 위반 거래를 트랜잭션 규칙으로 차단합니다. 적용·차단·재생 판정과 최종 잔액은 규칙(원장 리플레이)이 결정하고 LLM 은 거래 내역 요약과 차단 사유 문구만 붙여, 규칙을 어긴 거래가 그럴듯한 문구로 적용되지 못하게 합니다. 1주차만으로 완성품이고, 정합성 재계산 검증(2주차)·재화 다종과 소멸·한도 정책, 정산 리포트(3주차)로 키웁니다. (근거는 채용공고 정렬입니다.)
스위트스팟 팝가 PE. 방문·후기·사전예약으로 쌓인 포인트와 웨이팅·굿즈 구매로 빠지는 재화를 손으로 맞추며 이중적립·잔액 음수를 사후에 잡던 걸 끝내고, 거래를 멱등 규칙으로 처리하고 위반은 걸러진 상태로 잔액과 내역을 받고 싶은 사람입니다.
포인트·재화 거래를 멱등 규칙과 잔액 불변식으로 처리하게 하면, (a) 같은 idempotency_key 재요청은 재생으로 처리돼 이중적립이 막히고, (b) 잔액을 음수로 만드는 차감과 미등록·동결 지갑 거래가 차단되며, (c) LLM 은 거래 요약·사유 문구만 붙여 규칙 위반을 그럴듯하게 포장하지 못해, 운영자가 잔액을 사후 대조 없이 신뢰할 원장을 만든다는 걸 보여줍니다.
포인트·재화 거래 스트림을 받아 멱등 처리로 이중적립을 막고, 잔액 음수·미등록 지갑·동결 지갑·0 이하 금액을 트랜잭션 규칙으로 차단하며, 갱신된 잔액과 거래 내역·차단 사유 문구를 보여주는 동작하는 원장 도구입니다. 사유 생성이 적용 판정과 잔액을 바꾸지 못합니다. 저장소는 PostgreSQL, 화면은 React 대시보드로 시작합니다.
운영자 페르소나 2명이 적용·차단·사유가 타당한지 라벨링하고, 원장 리플레이로 최종 잔액을 독립 재계산해 대조하며, 위반 통과율·잔액 오차를 보여주는 대시보드입니다.
재화 종류(포인트·코인)를 늘리고 소멸·적립 한도 정책 규칙을 얹으며, 기간별 적립·차감·차단 사유 분포를 정산 리포트로 뽑는 확장 단계입니다.
거래 스트림 30건을 넣으면, 멱등 처리로 이중적립을 막고 위반은 차단하며 갱신된 잔액과 사유 문구를 붙이는 걸 시연합니다
최종 잔액이 원장 리플레이 재계산과 일치하는지 자동 대조합니다. 규칙을 어긴 거래는 LLM 사유가 있어도 적용되지 않는 걸 보여줍니다
일부러 심은 이중적립·잔액 부족·미등록 지갑·0 이하 금액 30종을 넣으면 100% 차단되고 출력 형식이 안 깨지는 걸 보여줍니다
내가 만든 정답 30세트 중 적용·차단 판정과 사유가 일치한 게 27세트(90%). 단, 정답과 데이터를 모두 내가 만든 합성 기준이라 ‘성능 검증’ 이 아니라 ‘데모 조건 동작’ 임을 밝힙니다
위 수치는 전부 작성자가 만든 더미데이터 자가채점이며, 실제 원장 정합성·이상 거래 탐지 품질은 스위트스팟 팝가 실 거래 로그와 실 운영자, 동시 차감 경합이 있어야 측정 가능합니다. 이 도구는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
이건 운영·실데이터 없이 혼자 만든 포트폴리오라, 수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서 ‘데모 동작 + 실검증 준비 상태’ 로 솔직히 설명합니다
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 이중적립·잔액 부족·미등록 지갑·별칭 표기를 더미에 일부러 섞어, 쉬운 거래만 처리하는 게 아님을 보여줍니다
이 포트폴리오가 보여주는 것은 포인트·재화 원장의 트랜잭션 설계·멱등·정합성 검증 기술이지 팝업스토어·리테일 도메인 전문성이 아닙니다. 실제 포인트 정책의 세부는 운영 데이터가 있어야 하고, 면접에서 모르는 부분은 모른다고 말하는 편이 낫습니다
이 데모는 거래를 시간순 단일 스트림으로 리플레이해 판정하므로, 실제 서비스의 동시 차감 경합은 잡지 못합니다. 그건 DB 트랜잭션 격리·행 잠금으로 풀어야 하고 여기선 단일 스트림을 가정한다고 명시합니다
실제 도입 시 달라지는 것 명시합니다. 실 적립·차감 소스 체계와 재화 종류·정책은 더미와 다르므로 규칙·소스 사전을 다시 맞춰야 하고, 검수자도 실 운영자로 교체해야 합니다(도입 단계 과제로 분리)
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
포인트·재화 지갑 마스터. 시작 잔액·재화 종류·상태를 담습니다. 동결 지갑과 잔액 0·큰 잔액 경계를 일부러 섞어, 규칙이 동결 차감을 막고 경계 잔액을 정확히 다루는지 보여줍니다. 미등록(orphan) 지갑은 거래 쪽에서 마스터에 없는 wallet_id 로 테스트합니다.
| wallet_id | user_id | opening_balance | currency | status |
|---|---|---|---|---|
| w_01 | u_01 | 0 | point | active |
| w_02 | u_02 | 1,200 | point | active |
| w_03 | u_03 | 50 | coin | active |
| w_04 | u_04 | 800 | point | frozen |
| w_05 | u_05 | 300 | point | active |
아래 JSON Schema 에 맞는 지갑 마스터 20행 JSON 배열로 생성합니다.
{"type":"object","properties":{"wallet_id":{"type":"string"},"user_id":{"type":"string"},"opening_balance":{"type":"integer"},"currency":{"type":"string","enum":["point","coin"]},"status":{"type":"string","enum":["active","frozen"]}},"required":["wallet_id","user_id","opening_balance","currency","status"]}
쉬운 데이터만 만들지 말 것. 하드 케이스 의도적 혼입: (1) status 가 frozen 인 동결 지갑 2~3개(차감 차단 테스트), (2) opening_balance 가 0 인 지갑 1~2개(첫 차감이 바로 잔액 부족), (3) coin 지갑을 섞어 재화 종류별 잔액이 분리되게. wallet_id 는 유니크. JSON 배열만.{"type":"object","properties":{"wallet_id":{"type":"string"},"user_id":{"type":"string"},"opening_balance":{"type":"integer"},"currency":{"type":"string","enum":["point","coin"]},"status":{"type":"string","enum":["active","frozen"]}},"required":["wallet_id","user_id","opening_balance","currency","status"]}적립·차감 거래 스트림. 지갑·유형·금액·소스·멱등키·시각을 담습니다. 같은 idempotency_key 중복(이중적립)·잔액을 음수로 만드는 차감·미등록 wallet_id(orphan)·0 이하 금액·소스 별칭 표기 흔들림을 일부러 섞어, 규칙이 이중적립·초과 차감을 막고 미매핑을 정직하게 다루는지 보여줍니다.
| txn_id | wallet_id | type | amount | source_raw | source_id | idempotency_key | created_at |
|---|---|---|---|---|---|---|---|
| t_01 | w_02 | credit | 100 | 방문 인증 | visit | k1 | 2026-05-10T09:00:00 |
| t_02 | w_02 | credit | 100 | 방문 인증 | visit | k1 | 2026-05-10T09:00:03 |
| t_03 | w_02 | debit | 1,500 | 굿즈 구매 | goods | k2 | 2026-05-10T10:00:00 |
| t_04 | w_05 | debit | 300 | 사전예약 차감 | reserve | k3 | 2026-05-10T11:00:00 |
| t_05 | w_99 | credit | 50 | 후기 작성 | review | k4 | 2026-05-10T12:00:00 |
아래 JSON Schema 에 맞는 거래 스트림 25행 JSON 배열로 생성합니다. wallet_id 는 wallet 의 id 를 참조하되 일부는 마스터에 없는 값이어야 하고, created_at 시간순으로 리플레이됩니다.
{"type":"object","properties":{"txn_id":{"type":"string"},"wallet_id":{"type":"string"},"type":{"type":"string","enum":["credit","debit"]},"amount":{"type":"integer"},"source_raw":{"type":"string"},"source_id":{"type":["string","null"]},"idempotency_key":{"type":"string"},"created_at":{"type":"string"}},"required":["txn_id","wallet_id","type","amount","source_raw","source_id","idempotency_key","created_at"]}
쉬운 데이터만 만들지 말 것. 하드 케이스 의도적 혼입: (1) 같은 idempotency_key 를 쓰는 적립 2~3쌍(두 번째는 재생 처리돼 중복 적립되면 안 됨), (2) 잔액을 음수로 만드는 큰 debit 2~3건, (3) 마스터에 없는 wallet_id(orphan) 1~2건, (4) frozen 지갑에 대한 debit 1건, (5) amount 가 0 이나 음수인 거래 1~2건(무효 금액), (6) source_raw 가 사전에 없는 표기라 source_id 가 null 인 미매핑 1~2건. JSON 배열만.{"type":"object","properties":{"txn_id":{"type":"string"},"wallet_id":{"type":"string"},"type":{"type":"string","enum":["credit","debit"]},"amount":{"type":"integer"},"source_raw":{"type":"string"},"source_id":{"type":["string","null"]},"idempotency_key":{"type":"string"},"created_at":{"type":"string"}},"required":["txn_id","wallet_id","type","amount","source_raw","source_id","idempotency_key","created_at"]}| TypeScript | 원장 도메인 타입(지갑·거래·재화·불변식) 공유로 적립·차감 경로를 타입 안전하게 다룸 |
| Node.js | 포인트·재화 원장 API 와 트랜잭션 규칙 엔진을 오케스트레이션하는 서버 |
| PostgreSQL | wallet / point_transaction 단일 진실원 + UNIQUE(idempotency_key)·CHECK(balance>=0) 제약으로 이중적립·잔액 음수를 DB 층에서도 차단 |
| React | 지갑 잔액·거래 내역·차단 사유를 확인하는 운영 대시보드 |
| GCP | 2주차부터 Cloud Run 으로 원장 API 를 배포·운영(우대의 GCP 반영) |
| Claude Code | React 대시보드와 강건성 테스트 세트 생성 보조, 생성된 코드·설계를 직접 검토·수정 |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 2주차(잔액 재계산 검증)로 진행합니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우면 3주차(재화 다종·소멸/한도 정책·정산 리포트)로 진행합니다.
거래별 원장 적용 결과. 적용 여부(status)·최종 잔액(balance_after)·위반 종류(violation)·인용 규칙(cited_rule)·사람이 읽을 사유 문구(reason)를 한 스키마에 담아, 규칙을 어긴 거래를 사유 문구로 적용시키지 않았는지, 잔액이 규칙 재계산과 맞는지 객관 검증합니다.
{"type":"object","properties":{"txn_id":{"type":"string"},"status":{"type":"string","enum":["applied","blocked","replayed"]},"applied":{"type":"boolean"},"balance_after":{"type":["integer","null"]},"violation":{"type":["string","null"],"enum":["double_credit","insufficient_balance","orphan_wallet","frozen_wallet","invalid_amount",null]},"cited_rule":{"type":["string","null"]},"reason":{"type":"string"}},"required":["txn_id","status","applied","balance_after","violation","reason"]}[
{
"input_ref": "t_01→t_02: w_02 에 같은 idempotency_key(k1) 적립 두 번",
"output": {
"txn_id": "t_02",
"status": "replayed",
"applied": false,
"balance_after": 1300,
"violation": "double_credit",
"cited_rule": "idempotency_key k1 은 t_01 에서 이미 처리됨",
"reason": "이미 처리된 방문 인증 적립이라 중복 적립하지 않았습니다. 잔액은 1300포인트로 그대로입니다."
}
},
{
"input_ref": "t_03: w_02 잔액 1300 에서 1500 차감 시도",
"output": {
"txn_id": "t_03",
"status": "blocked",
"applied": false,
"balance_after": null,
"violation": "insufficient_balance",
"cited_rule": "잔액 1300 < 차감 1500, 잔액 음수 금지",
"reason": "굿즈 구매 1500포인트는 남은 잔액 1300포인트보다 커서 차감하지 않았습니다."
}
},
{
"input_ref": "t_04: w_05 잔액 300 에서 300 차감(경계, 잔액 0)",
"output": {
"txn_id": "t_04",
"status": "applied",
"applied": true,
"balance_after": 0,
"violation": null,
"cited_rule": null,
"reason": "사전예약 차감 300포인트를 적용했습니다. 잔액은 0포인트입니다."
}
},
{
"input_ref": "t_05: 마스터에 없는 w_99 적립",
"output": {
"txn_id": "t_05",
"status": "blocked",
"applied": false,
"balance_after": null,
"violation": "orphan_wallet",
"cited_rule": "wallet_id w_99 가 지갑 마스터에 없음",
"reason": "등록되지 않은 지갑(w_99)이라 후기 작성 적립을 적용하지 않고 미매핑으로 표시했습니다."
}
}
][자동 검증] 1. 출력이 스키마를 통과하는가(형식·필수 필드·violation enum). 2. 거래를 created_at 순서로 리플레이해 최종 잔액을 독립 재계산한 값과 balance_after 가 일치하는가(엔진 잔액이 규칙 재계산과 어긋나면 검출). 3. status·violation 이 실제 규칙 판정과 일치하고, 위반 거래가 applied=true 로 새어나가지 않았는가(사유 문구로 위반 통과 차단). 4. 같은 idempotency_key 재요청이 replayed 로 처리돼 잔액이 두 번 반영되지 않았는가(이중적립 차단). 5. reason 이 status·violation 과 모순되지 않는가(근거 밖 사유 검출). [수동 검증] 1. 운영자 페르소나 2명이 적용·차단·사유 타당성 라벨링 → bootstrap Cohen’s κ(두 사람 답이 우연 이상으로 일치하는 정도, 0~1) 하한 ≥ 0.6. 2. 위반 통과·잔액 오차 5% 사람 점검.
근거: 포인트·결제·재화 등 데이터 정합성이 중요한 시스템을 다뤄봤거나 트랜잭션 설계를 이해하고 있으신 분