누리하우스
경력근거: 인플루언서 마케팅 플랫폼 'Nurilounge'의 웹 풀스택 개발 (Next.js 기반 프론트엔드 + API)
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 인플루언서 마케팅 플랫폼 Nurilounge 의 웹 풀스택 개발을 맡기면서 배치·크론 기반 데이터 수집 파이프라인과 LLM API 기반 기능 개발을 주요업무에 나란히 적습니다. 이 포트폴리오는 그 두 줄을 골조로 씁니다. 흩어진 플랫폼에서 게시물을 주기적으로 받아 오는 자리가 수집 파이프라인이고, 감액 사유를 브랜드에 회신할 문장으로 옮기는 자리가 LLM API 입니다. 50만 명 크리에이터와 2,600건 캠페인이 쌓인 곳에서는 한 캠페인의 이행 확인이 곧 정산 금액이라, 게시물 수집과 정산 규칙을 한 화면에 세우는 일이 제품 안쪽 업무가 됩니다. 다만 이 포트폴리오는 수집·대조·정산 계산을 설계하고 화면으로 세우는 일을 보여줍니다. 글로벌 인플루언서 계약 실무나 나라별 광고 표기 규제 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
주요업무에 배치·크론 기반 데이터 수집 파이프라인 개발과 이메일 발송 자동화 같은 외부 시스템 연동이 나란히 적혀 있습니다. 화면만 만드는 자리가 아니라 밖에서 데이터를 주기적으로 받아 오는 일이 업무의 한 축입니다.
주요업무에 LLM API를 활용한 서비스 기능 개발을 적고, 우대사항에서 다시 에이전트와 자동화 개발 경험을 묻습니다. 모델을 붙이는 자리를 부가 기능이 아니라 제품 기능으로 봅니다.
인플루언서 마케팅 플랫폼 Nurilounge 의 웹 풀스택 개발을 맡기면서 Next.js App Router 와 Prisma·PostgreSQL, AWS 배포 경험을 우대로 답니다. 클라이언트와 서버를 오가며 기능을 끝까지 완성하는 사람을 찾습니다.
캠페인 담당자가 인스타그램·틱톡·유튜브에서 모아 온 게시물 목록과 캠페인 브리프를 올리면 크리에이터마다 이행 판정과 이번 회차 정산액이 붙습니다. 판정과 금액은 게시 날짜·필수 표기·조회수 합계를 브리프와 견주는 계산 규칙이 정하고, 모델이 쓰는 것은 브랜드에 그대로 보낼 감액 사유 문단 하나입니다.
브랜드 한 곳의 캠페인에 수십 명의 글로벌 크리에이터를 붙여 운영하는 누리라운지 캠페인 담당자 — 게시물이 인스타·틱톡·유튜브에 흩어져 올라오는데 정산일은 다가오고, 누가 약속을 지켰는지는 링크를 하나씩 열어 봐야 안다
정산일마다 확인이 늦어지는 이유는 크리에이터가 많아서가 아니라 약속과 실제 게시물이 서로 다른 곳에 있어서입니다. 브리프의 기한·필수 표기·최소 조회수를 수집한 게시물과 한 줄로 세워 두면 담당자는 링크를 열지 않고도 어긋난 사람부터 봅니다.
캠페인 담당자가 쓰는 화면 하나가 돕니다. 수집한 게시물 목록과 캠페인 브리프, 정산 규칙을 올리면 크리에이터마다 판정이 붙고 판정별 묶음과 근거가 한 화면에 뜹니다. 1주차는 플랫폼 API 를 붙이지 않고 관리자 화면에서 내려받은 파일을 읽는 것으로 시작합니다.
담당자가 손으로 정한 정산 판정을 크리에이터 36명마다 붙여 일치율을 자동으로 내고, 어긋난 건이 날짜 계산에서 갈라졌는지 표기 대조에서 갈라졌는지 나눠 리포트로 냅니다. 감액 사유 문단을 모델이 쓰고 인용한 컬럼이 입력에 없으면 그 응답을 버립니다. 2주차부터 AWS 로 배포해 담당자가 사내 주소로 들어와 씁니다.
배치·크론으로 크리에이터 계정을 돌며 게시물을 받아 오는 수집기를 붙여 사람이 올리지 않아도 판정이 매일 새로 돌고, 회차가 닫히면 정산 요약과 감액 사유를 브랜드 담당자에게 메일로 보냅니다.
담당자가 게시물 목록과 브리프를 올리면 크리에이터마다 판정과 정산액이 뜨고, 어느 날짜와 어느 조회수가 그 금액을 만들었는지 그 자리에서 펼쳐집니다.
최소 조회수가 비어 있는 브리프, 유료 광고 표기 칸이 빈 게시물, 조회수 0회와 124만 회를 섞은 입력 30종을 넣어도 화면이 죽지 않고 그 건이 왜 금액 없이 남았는지 읽힙니다.
열 이름이 빠지거나 날짜 칸이 비거나 인코딩이 깨진 파일 12종을 올려도 목록 대신 어느 줄이 왜 걸렸는지 이슈로 뜨고 나머지 크리에이터는 그대로 판정됩니다.
담당자가 손으로 정한 정산 판정을 크리에이터 36명(브리프 12행에 증강 24행)마다 정답으로 붙여 일치율을 냅니다. 단, 게시물과 정답을 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 적중률은 누리라운지에 쌓인 실제 게시물 수집 결과와 브랜드 회신 이력이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
정산 판정 일치율은 제가 만든 게시물 수집본과 브리프로 낸 자가채점이라 실제 정산 정확도의 증명이 아닙니다. 면접에서는 누리라운지에 쌓인 실제 수집 결과가 들어왔을 때 어느 규칙이 먼저 흔들릴지로 이야기를 잇습니다.
누구에게 얼마를 줄지 제가 미리 정해 두고 맞춰 본 것이라 일치율이 높은 것은 당연합니다. 그래서 최소 조회수가 빈 브리프, 유료 표기 칸이 빈 게시물, 게시 여섯 시간 뒤에 잰 조회수 0회, 기한 당일 게시, 브리프가 요구한 채널과 다른 데 올린 게시물을 더미에 담아 규칙이 어디서 금액을 멈추는지 카드로 드러나게 했습니다.
수집한 조회수는 언제 재느냐에 따라 달라집니다. 게시 직후에 잰 값으로 미달을 확정하면 크리에이터에게 불리하므로 회차 마감일 기준으로만 금액을 확정하고 그 전 판정은 잠정으로 보여줍니다.
표기 칸이 비었다는 사실이 곧 크리에이터가 빠뜨렸다는 뜻은 아닙니다. 플랫폼이 유료 광고 라벨을 화면에만 띄우고 본문에 남기지 않는 경우가 있어, 이 판정은 담당자가 게시물을 한 번 열어 보는 자리로 두고 금액을 자동으로 깎지 않습니다.
실제 도입에서는 플랫폼마다 수집 규격과 조회수 정의가 다르고, 브랜드마다 감액 조항의 문구가 다릅니다. 계수와 조항을 코드에서 빼 정산 규칙 표로 옮겨 두었지만 규칙이 늘어나면 다시 설계해야 하며, 그것은 도입 단계 과제로 분리합니다.
정산금 이체 자체는 법인 계좌와 계약 권한이 있어야 하므로 이 포트폴리오의 범위 밖입니다. 여기서는 얼마를 보낼지 계산하고 그 근거를 브랜드에 회신할 문서로 내는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
배치·크론이 크리에이터 계정을 돌며 받아 온 게시물 목록입니다. 대조의 실행 쪽 축이며 유료 광고 표기 칸이 빈 게시물과 조회수 0회, 124만 회 같은 극단 값을 의도적으로 포함합니다.
| post_id | campaign_id | creator_id | creator_handle | platform | posted_at | hashtags | paid_tag | views | collected_at |
|---|---|---|---|---|---|---|---|---|---|
| PST-9001 | CMP-4102 | CR-2081 | @mia.beauty | tiktok | 2026-03-09T18:22:00 | ["#glowserum","#kbeauty"] | #ad | 26,400 | 2026-03-12T06:00:00 |
| PST-9002 | CMP-4102 | CR-2094 | @jules.skin | 2026-03-05T11:40:00 | ["#glow","#skincare"] | — | 18,200 | 2026-03-12T06:00:00 | |
| PST-9003 | CMP-4102 | CR-2107 | @kayaromero | youtube | 2026-03-06T09:15:00 | ["#glowserum","#ad"] | #ad | 8,400 | 2026-03-12T06:00:00 |
| PST-9004 | CMP-4102 | CR-2115 | @noahpark | 2026-03-04T20:05:00 | ["#glowserum","#kbeauty"] | #ad | 31,500 | 2026-03-12T06:00:00 | |
| PST-9013 | CMP-4102 | CR-2115 | @noahpark | 2026-03-05T21:10:00 | ["#glowserum","#ad"] | #ad | 9,800 | 2026-03-12T06:00:00 |
아래 JSON Schema 에 맞는 post_collect 행을 더 만들어 주세요. {"type":"object","properties":{"post_id":{"type":"string"},"campaign_id":{"type":"string"},"creator_id":{"type":"string"},"creator_handle":{"type":"string"},"platform":{"type":"string","enum":["instagram","tiktok","youtube"]},"posted_at":{"type":"string"},"hashtags":{"type":"array","items":{"type":"string"}},"paid_tag":{"type":["string","null"]},"views":{"type":["integer","null"],"minimum":0},"collected_at":{"type":"string"}},"required":["post_id","campaign_id","creator_id","creator_handle","platform","posted_at","hashtags","collected_at"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) paid_tag 가 null 인 유료 광고 표기 없는 게시물, (2) views 가 0 인 게시 직후 건과 100만 회를 넘는 건, (3) 기한 당일 자정 직전에 올라온 건, (4) 한 크리에이터가 같은 캠페인에 두 건을 올린 경우. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"post_id":{"type":"string"},"campaign_id":{"type":"string"},"creator_id":{"type":"string"},"creator_handle":{"type":"string"},"platform":{"type":"string","enum":["instagram","tiktok","youtube"]},"posted_at":{"type":"string"},"hashtags":{"type":"array","items":{"type":"string"}},"paid_tag":{"type":["string","null"]},"views":{"type":["integer","null"],"minimum":0},"collected_at":{"type":"string"}},"required":["post_id","campaign_id","creator_id","creator_handle","platform","posted_at","hashtags","collected_at"]}캠페인마다 크리에이터와 맺은 약속입니다. 게시 기한·필수 해시태그·게시 플랫폼·최소 조회수가 들어 있고, 협의가 끝나지 않아 최소 조회수가 비어 있는 회차와, 요구한 채널이 실제 게시 채널과 다른 회차를 포함합니다.
| campaign_id | creator_id | creator_handle | due_date | required_hashtags | required_platform | min_views |
|---|---|---|---|---|---|---|
| CMP-4102 | CR-2081 | @mia.beauty | 2026-03-07 | ["#glowserum","#kbeauty"] | tiktok | 20,000 |
| CMP-4102 | CR-2094 | @jules.skin | 2026-03-07 | ["#glowserum","#kbeauty"] | 15,000 | |
| CMP-4102 | CR-2107 | @kayaromero | 2026-03-07 | ["#glowserum"] | youtube | 20,000 |
| CMP-4102 | CR-2115 | @noahpark | 2026-03-07 | ["#glowserum"] | 20,000 | |
| CMP-4102 | CR-2122 | @linh.tran | 2026-03-07 | ["#glowserum"] | tiktok | 20,000 |
아래 JSON Schema 에 맞는 campaign_brief 행을 24행 더 만들어 주세요. {"type":"object","properties":{"campaign_id":{"type":"string"},"creator_id":{"type":"string"},"creator_handle":{"type":"string"},"due_date":{"type":"string"},"required_hashtags":{"type":"array","items":{"type":"string"}},"required_platform":{"type":"string","enum":["instagram","tiktok","youtube"]},"min_views":{"type":["integer","null"],"minimum":0}},"required":["campaign_id","creator_id","creator_handle","due_date","required_hashtags","required_platform"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) min_views 가 null 인 협의 미완 회차, (2) required_hashtags 가 한 개뿐인 회차와 두 개인 회차, (3) required_platform 이 실제 게시 플랫폼과 다른 회차. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"campaign_id":{"type":"string"},"creator_id":{"type":"string"},"creator_handle":{"type":"string"},"due_date":{"type":"string"},"required_hashtags":{"type":"array","items":{"type":"string"}},"required_platform":{"type":"string","enum":["instagram","tiktok","youtube"]},"min_views":{"type":["integer","null"],"minimum":0}},"required":["campaign_id","creator_id","creator_handle","due_date","required_hashtags","required_platform"]}캠페인별 정산 계수 표입니다. 기본료와 조회수 1,000회당 성과급, 하루 늦을 때 깎는 비율을 담아 금액 계산이 코드가 아니라 이 표에서 나오게 합니다.
| campaign_id | brand_name | currency | base_fee | view_bonus_unit | late_penalty_rate |
|---|---|---|---|---|---|
| CMP-4102 | GLOW SERUM | USD | 300 | 12 | 0.1 |
| CMP-4118 | HYDRA MASK | USD | 220 | 9 | 0.05 |
| CMP-4130 | SEOULCON PRE | USD | 450 | 2 | 0.08 |
| CMP-4144 | HAUS OF K | USD | 500 | 0 | 0.1 |
| CMP-4157 | CICA BALM | USD | 180 | 7 | 0.05 |
아래 JSON Schema 에 맞는 payout_rule 행을 더 만들어 주세요. {"type":"object","properties":{"campaign_id":{"type":"string"},"brand_name":{"type":"string"},"currency":{"type":"string","enum":["USD"]},"base_fee":{"type":"number","minimum":0},"view_bonus_unit":{"type":"number","minimum":0},"late_penalty_rate":{"type":"number","minimum":0,"maximum":1}},"required":["campaign_id","brand_name","currency","base_fee","view_bonus_unit","late_penalty_rate"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) view_bonus_unit 이 0 인 성과급 없는 캠페인, (2) late_penalty_rate 이 0.05 에서 0.1 사이로 캠페인마다 다른 경우, (3) base_fee 가 150 달러에서 500 달러까지 벌어진 경우. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"campaign_id":{"type":"string"},"brand_name":{"type":"string"},"currency":{"type":"string","enum":["USD"]},"base_fee":{"type":"number","minimum":0},"view_bonus_unit":{"type":"number","minimum":0},"late_penalty_rate":{"type":"number","minimum":0,"maximum":1}},"required":["campaign_id","brand_name","currency","base_fee","view_bonus_unit","late_penalty_rate"]}| TypeScript | 수집·대조·화면을 한 언어로 씁니다. 조회수와 금액과 날수가 섞이지 않도록 계산 함수마다 단위를 타입으로 박습니다. |
| Next.js | App Router 로 화면과 API 를 한 프로젝트에 둡니다. 파일 업로드와 판정 조회가 같은 라우트 트리 안에 있습니다. |
| React | 담당자가 보는 정산 점검 화면입니다. 판정별 묶음과 근거 표를 한 화면에 둡니다. |
| TanStack Query | 판정 목록과 근거를 서버에서 받아 캐시로 들고 있습니다. 수집기가 새 회차를 넣으면 그 캠페인 키만 다시 받아 옵니다. |
| Zustand | 선택한 캠페인·회차·판정 필터를 화면 전역 상태로 들고 있어 목록과 상세가 같은 조건을 봅니다. |
| Tailwind CSS | 판정 카드와 근거 표의 표시 규칙을 클래스로 고정해 목록·상세·리포트 세 화면이 같은 모양을 씁니다. |
| Node.js | 게시물 수집 작업과 정산 계산이 도는 런타임입니다. |
| Prisma | 게시물·브리프·정산 규칙 세 표의 스키마와 조회를 다룹니다. 캠페인과 크리에이터 두 키를 묶은 인덱스를 여기서 선언합니다. |
| PostgreSQL | 수집한 게시물과 회차별 판정 결과를 담는 저장소입니다. 회차가 쌓여도 지난 판정을 그대로 남깁니다. |
| AWS | 2주차부터 배포·운영을 맡습니다. 수집 원본과 회신 문서를 S3 에 남기고 RDS 로 PostgreSQL 을 띄웁니다. |
| Docker | 수집기와 웹을 같은 이미지 규격으로 묶어 로컬과 AWS 에서 같은 방식으로 돌립니다. |
| Jest | 날수 계산과 성과급 내림 계산처럼 경계에서 갈라지는 함수를 값 단위로 시험합니다. |
| Playwright | 파일을 올려 판정이 뜨고 근거가 펼쳐지는 흐름을 브라우저에서 끝까지 돌려 봅니다. |
| LLM API | 판정이 정해진 뒤 감액 사유를 브랜드에 보낼 문단으로 옮깁니다. 네 판정 값과 금액에는 손대지 못합니다. |
| Claude Code | 수집 파서 시험과 더미데이터 증강 스크립트를 쓰는 데 씁니다. 생성한 코드는 시험을 돌려 확인한 뒤 넣습니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 담당자가 손으로 정한 60건과 판정을 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 게시물을 매일 새벽 자동으로 받아 오고 정산 요약을 메일로 보내는 단계로 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · creator_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
약속대로 이행이행 완료게시 기한 넘김기한 넘김필수 표기 빠뜨림약속 조건 어김약속 조회수 미달조회수 미달크리에이터 한 사람에 대해 이번 회차 이행 판정과 정산액을 남기는 형식입니다. 판정 네 값과 금액은 대조 규칙이 정하고 브랜드에 보낼 문단만 모델이 씁니다.
{"type":"object","properties":{"creator_id":{"type":"string"},"creator_handle":{"type":"string"},"campaign_id":{"type":"string"},"verdict":{"type":"string","enum":["약속대로 이행","게시 기한 넘김","필수 표기 빠뜨림","약속 조회수 미달"]},"late_days":{"type":["integer","null"],"minimum":0,"description":"그 크리에이터의 가장 이른 posted_at 에서 campaign_brief.due_date 를 뺀 날수이며 음수면 0 입니다. 판정과 무관하게 게시물이 있으면 채웁니다"},"breach_kind":{"type":["string","null"],"enum":["플랫폼 불일치","유료 표기 없음","해시태그 누락",null],"description":"필수 표기 빠뜨림으로 걸린 까닭입니다. 브리프의 required_platform 과 다른 데 올렸으면 플랫폼 불일치, paid_tag 가 비었으면 유료 표기 없음, required_hashtags 를 다 담지 않았으면 해시태그 누락이며 이 차례로 먼저 걸린 하나만 씁니다. 다른 판정에서는 비웁니다"},"payout_hold":{"type":["string","null"],"enum":["브랜드 회신 대기","조회수 잠정",null],"description":"payout_usd 를 비운 까닭입니다. 필수 표기 빠뜨림은 브랜드 회신 대기, 게시 후 24시간 안에 수집한 조회수로 미달이 걸린 건은 조회수 잠정입니다. 금액을 낸 건에서는 비웁니다"},"view_bonus_usd":{"type":["number","null"],"minimum":0,"description":"같은 campaign_id·creator_id 의 게시물 views 를 모두 더한 합계를 1000 으로 나눠 내림한 값에 payout_rule.view_bonus_unit 를 곱한 금액이며, 약속 조회수 미달이면 0, 필수 표기 빠뜨림이면 브랜드 회신 전이라 비웁니다"},"late_penalty_usd":{"type":["number","null"],"minimum":0,"description":"payout_rule.base_fee 에 late_penalty_rate 와 late_days 를 곱한 금액입니다"},"payout_usd":{"type":["number","null"],"minimum":0,"description":"base_fee 에 view_bonus_usd 를 더하고 late_penalty_usd 를 뺀 금액입니다. 필수 표기 빠뜨림이면 브랜드 회신 전이라 비우고, 게시 후 24시간 안에 수집한 조회수로 미달이 걸린 건도 다음 수집 회차까지 비웁니다"},"evidence":{"type":"array","items":{"type":"object","properties":{"dataset":{"type":"string"},"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{"type":["string","number","null"]}},"required":["dataset","field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["creator_id","creator_handle","campaign_id","verdict","evidence","summary"]}[
{
"creator_id": "CR-2115",
"creator_handle": "@noahpark",
"campaign_id": "CMP-4102",
"verdict": "약속대로 이행",
"late_days": 0,
"view_bonus_usd": 492,
"late_penalty_usd": 0,
"payout_usd": 792,
"evidence": [
{
"dataset": "campaign_brief",
"field": "due_date",
"value": "2026-03-07"
},
{
"dataset": "post_collect",
"field": "posted_at",
"value": "2026-03-04T20:05:00"
},
{
"dataset": "post_collect",
"field": "views",
"agg": "sum",
"value": 41300
},
{
"dataset": "campaign_brief",
"field": "min_views",
"value": 20000
},
{
"dataset": "payout_rule",
"field": "base_fee",
"value": 300
},
{
"dataset": "payout_rule",
"field": "view_bonus_unit",
"value": 12
}
],
"summary": "기한 사흘 전에 올렸고 필수 표기도 다 붙었습니다. 게시물 두 건을 합친 조회수 41,300회로 성과급 492달러가 붙어 정산액은 792달러입니다.",
"hedge": null,
"breach_kind": null,
"payout_hold": null
},
{
"creator_id": "CR-2122",
"creator_handle": "@linh.tran",
"campaign_id": "CMP-4102",
"verdict": "약속대로 이행",
"late_days": 0,
"view_bonus_usd": 240,
"late_penalty_usd": 0,
"payout_usd": 540,
"evidence": [
{
"dataset": "campaign_brief",
"field": "due_date",
"value": "2026-03-07"
},
{
"dataset": "post_collect",
"field": "posted_at",
"value": "2026-03-07T13:30:00"
},
{
"dataset": "post_collect",
"field": "views",
"value": 20000
},
{
"dataset": "campaign_brief",
"field": "min_views",
"value": 20000
},
{
"dataset": "payout_rule",
"field": "base_fee",
"value": 300
}
],
"summary": "기한 당일에 올렸고 조회수가 약속한 20,000회와 같아 미달로 보지 않습니다. 성과급 240달러를 얹어 540달러가 나갑니다.",
"hedge": "기한 당일과 조회수 같은 값은 어느 쪽으로 볼지가 계약서마다 다르므로 브랜드와 한 번 맞춰 두는 편이 낫습니다.",
"breach_kind": null,
"payout_hold": null
},
{
"creator_id": "CR-2081",
"creator_handle": "@mia.beauty",
"campaign_id": "CMP-4102",
"verdict": "게시 기한 넘김",
"late_days": 2,
"view_bonus_usd": 312,
"late_penalty_usd": 60,
"payout_usd": 552,
"evidence": [
{
"dataset": "campaign_brief",
"field": "due_date",
"value": "2026-03-07"
},
{
"dataset": "post_collect",
"field": "posted_at",
"value": "2026-03-09T18:22:00"
},
{
"dataset": "post_collect",
"field": "views",
"value": 26400
},
{
"dataset": "payout_rule",
"field": "base_fee",
"value": 300
},
{
"dataset": "payout_rule",
"field": "late_penalty_rate",
"value": 0.1
}
],
"summary": "기한보다 이틀 늦게 올려 하루 10%씩 60달러를 깎습니다. 성과급 312달러를 더해 정산액은 552달러입니다.",
"hedge": null,
"breach_kind": null,
"payout_hold": null
},
{
"creator_id": "CR-2094",
"creator_handle": "@jules.skin",
"campaign_id": "CMP-4102",
"verdict": "필수 표기 빠뜨림",
"late_days": 0,
"view_bonus_usd": null,
"late_penalty_usd": 0,
"payout_usd": null,
"evidence": [
{
"dataset": "campaign_brief",
"field": "required_platform",
"value": "instagram"
},
{
"dataset": "post_collect",
"field": "platform",
"value": "instagram"
},
{
"dataset": "post_collect",
"field": "paid_tag",
"value": null
},
{
"dataset": "post_collect",
"field": "hashtags",
"agg": "count",
"value": 2
},
{
"dataset": "campaign_brief",
"field": "required_hashtags",
"agg": "count",
"value": 2
},
{
"dataset": "post_collect",
"field": "views",
"value": 18200
}
],
"summary": "브리프가 정한 인스타그램에 올린 것은 맞지만 유료 광고 표기 칸이 비어 있고 지정 해시태그 두 개도 게시물에 없습니다. 브랜드 회신 전이라 정산액을 비워 둡니다.",
"hedge": "표기 칸이 빈 것이 실제 누락인지 수집 시점에 못 읽은 것인지는 게시물 화면을 열어 한 번 봐야 갈라집니다.",
"breach_kind": "유료 표기 없음",
"payout_hold": "브랜드 회신 대기"
},
{
"creator_id": "CR-2138",
"creator_handle": "@sara.kimm",
"campaign_id": "CMP-4102",
"verdict": "필수 표기 빠뜨림",
"late_days": 0,
"breach_kind": "플랫폼 불일치",
"view_bonus_usd": null,
"late_penalty_usd": 0,
"payout_usd": null,
"payout_hold": "브랜드 회신 대기",
"evidence": [
{
"dataset": "campaign_brief",
"field": "required_platform",
"value": "tiktok"
},
{
"dataset": "post_collect",
"field": "platform",
"value": "instagram"
},
{
"dataset": "post_collect",
"field": "post_id",
"value": "PST-9006"
},
{
"dataset": "post_collect",
"field": "views",
"value": 12000
},
{
"dataset": "campaign_brief",
"field": "min_views",
"value": 10000
},
{
"dataset": "payout_rule",
"field": "base_fee",
"value": 300
}
],
"summary": "브리프는 틱톡을 요구했는데 인스타그램에 올렸습니다. 기한과 조회수는 지켰지만 약속한 채널이 아니라 정산액을 비우고 브랜드에 먼저 알립니다.",
"hedge": "같은 크리에이터가 틱톡에도 올렸는데 수집이 못 잡은 것일 수 있어, 계정을 한 번 열어 본 뒤 확정합니다."
},
{
"creator_id": "CR-2107",
"creator_handle": "@kayaromero",
"campaign_id": "CMP-4102",
"verdict": "약속 조회수 미달",
"late_days": 0,
"view_bonus_usd": 0,
"late_penalty_usd": 0,
"payout_usd": 300,
"evidence": [
{
"dataset": "post_collect",
"field": "views",
"value": 8400
},
{
"dataset": "campaign_brief",
"field": "min_views",
"value": 20000
},
{
"dataset": "payout_rule",
"field": "base_fee",
"value": 300
},
{
"dataset": "payout_rule",
"field": "view_bonus_unit",
"value": 12
}
],
"summary": "조회수 8,400회로 약속한 20,000회에 못 미쳐 성과급을 얹지 않고 기본료 300달러만 정산합니다.",
"hedge": null,
"breach_kind": null,
"payout_hold": null
},
{
"creator_id": "CR-2166",
"creator_handle": "@minji.log",
"campaign_id": "CMP-4118",
"verdict": "약속대로 이행",
"late_days": 0,
"view_bonus_usd": 54,
"late_penalty_usd": 0,
"payout_usd": 274,
"evidence": [
{
"dataset": "campaign_brief",
"field": "min_views",
"value": null
},
{
"dataset": "post_collect",
"field": "views",
"value": 6200
},
{
"dataset": "campaign_brief",
"field": "due_date",
"value": "2026-03-15"
},
{
"dataset": "payout_rule",
"field": "base_fee",
"value": 220
},
{
"dataset": "payout_rule",
"field": "view_bonus_unit",
"value": 9
}
],
"summary": "이 회차는 브리프에 최소 조회수가 적혀 있지 않아 조회수 판정을 건너뜁니다. 기한과 표기를 지켜 기본료 220달러에 성과급 54달러를 더합니다.",
"hedge": "최소 조회수가 빈 것은 협의가 안 끝난 표시일 수 있어, 이 건은 정산 전에 담당자가 브리프를 다시 확인합니다.",
"breach_kind": null,
"payout_hold": null
},
{
"creator_id": "CR-2188",
"creator_handle": "@emma.rt",
"campaign_id": "CMP-4130",
"verdict": "약속 조회수 미달",
"late_days": 0,
"view_bonus_usd": 0,
"late_penalty_usd": 0,
"payout_usd": null,
"evidence": [
{
"dataset": "post_collect",
"field": "views",
"value": 0
},
{
"dataset": "campaign_brief",
"field": "min_views",
"value": 200000
},
{
"dataset": "post_collect",
"field": "posted_at",
"value": "2026-04-01T23:40:00"
},
{
"dataset": "payout_rule",
"field": "base_fee",
"value": 450
},
{
"dataset": "post_collect",
"field": "collected_at",
"value": "2026-04-02T06:00:00"
}
],
"summary": "기한 마지막 날 자정 직전에 올려 게시 6시간 뒤에 잰 조회수가 0회입니다. 약속한 200,000회에 못 미치지만 수집이 너무 일러 정산액은 다음 회차까지 비워 둡니다.",
"hedge": "게시한 지 여섯 시간밖에 안 된 건이라 조회수 판정과 금액은 다음 수집 회차에 다시 봅니다.",
"breach_kind": null,
"payout_hold": "조회수 잠정"
},
{
"creator_id": "CR-2172",
"creator_handle": "@global.dana",
"campaign_id": "CMP-4130",
"verdict": "게시 기한 넘김",
"late_days": 1,
"view_bonus_usd": 2480,
"late_penalty_usd": 36,
"payout_usd": 2894,
"evidence": [
{
"dataset": "campaign_brief",
"field": "due_date",
"value": "2026-04-01"
},
{
"dataset": "post_collect",
"field": "posted_at",
"value": "2026-04-02T15:00:00"
},
{
"dataset": "post_collect",
"field": "views",
"value": 1240000
},
{
"dataset": "payout_rule",
"field": "base_fee",
"value": 450
},
{
"dataset": "payout_rule",
"field": "view_bonus_unit",
"value": 2
}
],
"summary": "하루 늦게 올려 36달러를 깎지만 조회수 124만 회로 성과급이 2,480달러 붙어 정산액은 2,894달러입니다.",
"hedge": null,
"breach_kind": null,
"payout_hold": null
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 enum 네 값(약속대로 이행·게시 기한 넘김·필수 표기 빠뜨림·약속 조회수 미달) 중 하나인지 확인합니다. 판정은 대조 규칙이 정하므로 모델이 다른 값을 쓰면 그 응답을 버립니다. 2. creator_id·creator_handle·campaign_id·verdict·evidence·summary 여섯 필드가 모두 있는지 확인합니다. 3. late_days 와 payout_usd, view_bonus_usd, late_penalty_usd 가 0 이상인지 범위를 확인합니다. 4. late_days 를 그 크리에이터의 가장 이른 posted_at 의 날짜에서 campaign_brief.due_date 를 뺀 날수로 다시 계산해 값이 같은지 확인합니다. 음수면 0 이고, 판정과 무관하게 게시물이 있으면 채웁니다. 5. view_bonus_usd 를 같은 campaign_id·creator_id 게시물의 views 합계 ÷ 1000 을 내림한 값 × payout_rule.view_bonus_unit 로, late_penalty_usd 를 base_fee × late_penalty_rate × late_days 로, payout_usd 를 base_fee + view_bonus_usd − late_penalty_usd 로 다시 계산해 값이 같은지 확인합니다. 6. evidence 의 field 가 post_collect·campaign_brief·payout_rule 에 실재하는 컬럼 이름인지 대조합니다. 없는 이름을 인용하면 회신 문단이 지어낸 문장이라는 뜻이라 규칙이 만든 기본 문단으로 되돌립니다. 7. agg 가 없는 evidence 값이 그 컬럼에 수록된 행 값과 같은지 대조합니다. 게시물 여러 건을 합친 조회수에는 agg 를 함께 적습니다. 8. summary 와 hedge 가 존댓말로 끝나는 한국어 문장인지 확인합니다. 9. campaign_brief.min_views 가 비어 있으면 그 크리에이터의 조회수 판정을 건너뜁니다. CR-2166 이 조건이 성립한 경우이고, 최소 조회수가 적힌 CR-2107 은 성립하지 않은 경우입니다. 10. 필수 표기는 세 가지를 함께 봅니다. (가) 게시물의 platform 이 campaign_brief.required_platform 과 다르면 플랫폼 불일치, (나) paid_tag 가 비어 있으면 유료 표기 없음, (다) 그 크리에이터의 게시물 가운데 required_hashtags 를 다 담지 않은 것이 있으면 해시태그 누락입니다. 셋 중 하나라도 걸리면 필수 표기 빠뜨림이고, breach_kind 에는 이 차례로 먼저 걸린 하나만 적습니다. CR-2138 이 (가), CR-2094 가 (나)와 (다)에 함께 걸려 (나)로 적힌 경우이고, CR-2115 는 셋 다 성립하지 않은 경우입니다. 11. 약속 조회수 미달은 **게시물 합계**로 잽니다 — 같은 campaign_id·creator_id 게시물의 views 를 다 더해 min_views 와 견주고, 합계가 min_views 와 같으면 미달로 보지 않습니다. 게시물 두 건을 합쳐 41,300회가 된 CR-2115 와 20,000회로 값이 같은 CR-2122 가 성립한 경우이고, 8,400회인 CR-2107 은 성립하지 않은 경우입니다. 12. 판정 순서는 필수 표기 빠뜨림, 게시 기한 넘김, 약속 조회수 미달, 약속대로 이행 순으로 앞에서 걸리면 뒤를 보지 않습니다. 브랜드에 먼저 알려야 하는 순서입니다. 13. collected_at 에서 posted_at 을 뺀 시간이 24시간에 못 미치는데 조회수 미달이 걸리면, 아직 오를 조회수라 payout_usd 를 비우고 payout_hold 에 조회수 잠정을 적습니다. CR-2188 이 그 경우입니다. 필수 표기 빠뜨림으로 비운 금액은 payout_hold 가 브랜드 회신 대기입니다. [평가 러너 · 2주차] 1. 담당자가 손으로 정한 정산 판정을 크리에이터 36명(브리프 12행에 증강 24행)마다 정답으로 붙여 판정 일치율을 냅니다. 어긋난 건은 날짜 계산에서 갈라졌는지 표기 대조에서 갈라졌는지 나눠 리포트에 답니다. [수동 검증 · 사람이 봅니다] 1. 필수 표기 빠뜨림으로 뜬 게시물을 담당자가 실제로 열어 보고 표기와 채널이 화면에 있는지 표본 20건을 확인합니다. 2. 감액이 생긴 건을 브랜드에 어떤 문장으로 알릴지 담당자와 맞춰 회신 문단의 어투를 정합니다.