조인트(JointInc.)
경력근거: 거래처와의 모든 대화가 회사 자산이 되어 AI가 맥락을 읽어 인사이트를 추출하고 다른 툴들과의 유기적 연동을 추구합니다.
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 Joint 를 거래처와의 모든 대화가 회사 자산이 되어 AI가 맥락을 읽어 인사이트를 추출하는 제품이라 적고, 주요업무 첫 줄에 고객의 문제를 이해하고, 기획부터 배포까지 직접 주도를 둡니다. 회신이 늦은 항목을 찾으려면 거래처별 대화 기록과 응대 약속이 한 계정 안에 같이 있어야 하는데, 사외 대화를 전용 방으로 옮겨 놓은 Joint 가 그 둘을 함께 쥐고 있습니다. 개인 메신저에 흩어진 대화로는 언제 답했는지가 남지 않습니다. 다만 이 포트폴리오는 대화 기록과 약속 시간을 규칙으로 이어 앱 화면으로 세우는 설계를 보여줍니다. 광고와 유통 영업의 거래 관행은 주장하지 않고, 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
공고는 Joint 를 처음부터 사외 협업을 위해 설계한 메신저라 소개하고, 거래처와의 모든 대화가 회사 자산이 되어 AI 가 맥락을 읽는 방향을 밝힙니다. 대화가 한곳에 모여야 언제 답했는지도 남습니다.
주요업무 첫 줄이 고객의 문제를 이해하고 기획부터 배포까지 직접 주도이고, 그다음 줄에 서버와 웹, 모바일 전반의 개발이 있습니다. 규칙과 데이터 모델을 정하고 앱 화면까지 세우는 한 덩어리를 맡는 자리입니다.
자격요건이 최소 한 개 이상의 기술을 이해하고 AI 에게 개발을 맡길 수 있는 사람을 찾고, 회사 소개에는 전사적으로 Claude Code 를 사용한다고 적혀 있습니다. 모델이 쓴 초안을 규칙으로 되받아 검사하는 구성이 이 요구와 맞닿습니다.
Joint 대화방마다 담당자가 답하기로 걸어 둔 일, 곧 챙기기 항목 가운데 아직 열려 있는 것을 메시지 기록과 거래처별 응대 약속에 맞춰 지금 손대야 할 것부터 세우는 모바일 앱 화면입니다. 그 항목이 시작된 메시지 뒤로 우리 쪽 메시지가 나갔는지 찾는 조회, 그 사이 흐른 시간을 세는 계산, 거래처마다 다른 응대 약속 시간과 견주는 비교가 판정을 정합니다. 회신 초안 한 줄은 모델이 쓰고, 인용한 메시지 번호가 실제 대화방에 있는지 규칙이 대조합니다.
거래처 대화방을 여럿 맡아 오가는 광고·유통 영업 담당자
회신이 늦는 이유는 담당자가 잊어서가 아니라 대화방이 열 개를 넘어가면 어디에 답을 안 했는지가 한 목록으로 남지 않기 때문입니다. 챙기기 항목을 메시지 기록과 응대 약속에 맞춰 두면 담당자는 방을 하나씩 열지 않고도 오늘 답할 곳부터 봅니다.
영업 담당자가 손에 들고 보는 앱 화면 하나가 돕니다. 대화 기록과 챙기기 항목, 거래처별 응대 약속을 넣으면 항목마다 판정이 붙고 판정별 묶음과 근거가 하단 탭 첫 화면에 뜹니다. 1주차는 Joint 서버에 붙지 않고 내려받은 파일 세 장을 앱이 읽는 것으로 시작합니다.
챙기기 항목을 40건으로 늘려 손으로 정답을 달고 판정 일치 수를 내며, 어긋난 건이 시간 계산에서 갈라졌는지 회신 조회에서 갈라졌는지 나눠 리포트로 냅니다. 대화방에 그대로 붙여 넣을 회신 초안을 모델이 쓰고, 인용한 메시지 번호가 그 방에 없으면 그 응답을 버립니다.
대화방에 메시지가 들어올 때마다 판정을 다시 돌려 앱 목록을 갱신하고, 판정이 바뀐 항목만 화면 위로 올립니다. 오늘 손대야 할 항목 수를 앱 아이콘 배지로 보여 줍니다.
담당자가 대화 기록과 챙기기 항목, 응대 약속을 넣으면 손대야 할 항목이 앞에 서고, 카드를 열면 마지막 우리 쪽 메시지 시각과 약속 시간이 나란히 놓입니다.
넘길 담당자가 비어 있는 거래처, 약속표에 아직 없는 신규 거래처, 회신은 나갔는데 닫히지 않은 항목을 섞은 입력 30종에서도 앱이 목록을 끝까지 그리고 판정 형식이 흐트러지지 않습니다.
발신 시각이 없거나 열 이름이 어긋난 파일, 글자가 깨져 들어온 파일 12종을 넣으면 앱이 어느 줄에서 멈췄는지 한 화면으로 알려 주고 읽힌 항목에는 판정이 붙습니다.
챙기기 항목을 40건으로 늘려 손으로 정답을 달고 판정 일치 수를 냅니다. 단, 대화와 정답을 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 회신 지연과 이탈은 Joint 에 쌓인 실제 대화와 거래처 반응이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
판정 일치 수는 제가 지어낸 대화에 제가 정답을 붙여 낸 숫자라 성능 증명이 아닙니다. 면접에서는 실제 대화방에서 무엇을 회신으로 볼지부터 이야기하겠습니다.
대화를 짓고 정답을 단 사람이 같으니 숫자가 잘 나오는 것은 당연합니다. 넘길 담당자가 비어 있는 거래처, 약속표에 없는 신규 거래처, 회신은 나갔는데 닫히지 않은 항목, 마감보다 응대 약속이 먼저 걸리는 항목을 더미에 일부러 섞었습니다. 손대야 할 것이 뻔한 항목만 모으면 이 화면을 만든 뜻이 없어집니다.
우리 쪽 메시지가 하나 나갔다고 그것이 거래처가 물은 것에 대한 답이라는 뜻은 아닙니다. 확인 중이라는 한 줄도 메시지로 남으므로, 이미 답했는데 열려 있음은 항목을 닫을 후보를 가리킬 뿐이라는 사유를 카드에 붙여 둡니다.
전화와 메일로 답한 건은 이 표에 남지 않습니다. Joint 밖에서 오간 답까지 세려면 통화 기록과 메일이 함께 들어와야 하고, 그 연결은 도입 단계 과제로 따로 잡습니다.
이 포트폴리오는 대화 기록과 약속 시간을 규칙으로 이어 앱 화면으로 세우는 설계를 보여줍니다. 광고와 유통 영업의 거래 관행은 주장하지 않고, 판정은 시각과 발신 쪽, 약속 시간으로만 합니다.
실제로 도입하면 응대 약속 시간이 계약마다 바뀌고 담당자가 자리를 옮깁니다. 약속이 바뀐 날 이전 항목을 옛 기준으로 볼지는 사람이 정할 문제라, 약속표에 적용 기간을 두는 일은 도입 단계 과제로 분리합니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
거래처 대화방에 오간 메시지 기록입니다. 어느 방에서 누가 언제 무엇을 보냈는지를 담고, 발신 쪽이 우리인지 거래처인지를 나눠 둡니다. 챙기기 항목이 시작된 메시지 뒤로 우리 쪽 메시지가 있는지는 이 표에서 찾습니다.
| message_id | thread_id | account_id | sent_at | sender_side | body_preview |
|---|---|---|---|---|---|
| M-70011 | T-9001 | A-1042 | 2026-09-08T14:05 | 거래처 | 9월 캠페인 견적서 부탁드립니다 |
| M-70012 | T-9001 | A-1042 | 2026-09-08T14:40 | 우리 | 확인해서 오늘 안에 회신드리겠습니다 |
| M-70021 | T-9002 | A-1057 | 2026-09-09T16:00 | 거래처 | 10월 납기 확정 가능한지 알려 주세요 |
| M-70031 | T-9003 | A-1063 | 2026-09-10T08:30 | 거래처 | 소재 시안 확인 부탁드립니다 |
| M-70032 | T-9003 | A-1063 | 2026-09-10T09:12 | 우리 | 시안 확인했습니다. 수정본 오늘 보내드리겠습니다 |
아래 JSON Schema 에 정확히 맞는 거래처 대화방 메시지 40개를 JSON 배열로 만들어 주세요.
{"type":"object","properties":{"message_id":{"type":"string"},"thread_id":{"type":"string"},"account_id":{"type":"string"},"sent_at":{"type":"string"},"sender_side":{"type":"string","enum":["우리","거래처"]},"body_preview":{"type":"string"}},"required":["message_id","thread_id","account_id","sent_at","sender_side","body_preview"]}
조건: message_id 는 M- 로 시작하는 다섯 자리 번호로 서로 다르게 씁니다. thread_id 는 T-9 로 시작하는 네 자리 번호를 열두 개 남짓 돌려 쓰고, 한 방에는 같은 account_id 만 나옵니다. sent_at 은 2026-09-08 부터 2026-09-10 사이의 업무 시간대로 흩뿌립니다. body_preview 는 견적서·납기·시안·정산·샘플 같은 광고와 유통 실무 요청 한 줄로 씁니다.
쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: 거래처 메시지만 있고 우리 쪽 메시지가 하나도 없는 방 4개, 우리 쪽이 확인 중이라고만 답한 방 2개, account_terms 에 없는 신규 account_id 로 시작된 방 2개, 하루가 지난 뒤에야 우리 쪽 답이 나간 방 2개.
JSON 배열만 출력합니다.{"type":"object","properties":{"message_id":{"type":"string"},"thread_id":{"type":"string"},"account_id":{"type":"string"},"sent_at":{"type":"string"},"sender_side":{"type":"string","enum":["우리","거래처"]},"body_preview":{"type":"string"}},"required":["message_id","thread_id","account_id","sent_at","sender_side","body_preview"]}대화방에서 놓치면 안 되는 요청을 담당자가 챙기기로 걸어 둔 항목입니다. 어느 메시지에서 시작됐는지와 언제까지 답하기로 했는지, 지금 열려 있는지 닫혔는지를 담습니다. 열려 있는 항목은 닫힌 시각이 없어 closed_at 을 비워 두었습니다.
| followup_id | thread_id | source_message_id | title | due_at | status | closed_at |
|---|---|---|---|---|---|---|
| F-5001 | T-9001 | M-70011 | 견적서 회신 | 2026-09-09T18:00 | 열림 | — |
| F-5002 | T-9002 | M-70021 | 납기 확정 답변 | 2026-09-12T18:00 | 열림 | — |
| F-5003 | T-9003 | M-70031 | 소재 시안 확인 | 2026-09-11T18:00 | 열림 | — |
| F-5004 | T-9004 | M-70041 | 단가표 회신 | 2026-09-11T12:00 | 열림 | — |
| F-5005 | T-9005 | M-70051 | 계약서 검토 의견 | 2026-09-09T10:00 | 닫힘 | 2026-09-08T15:35 |
아래 JSON Schema 에 정확히 맞는 챙기기 항목 24개를 JSON 배열로 만들어 주세요.
{"type":"object","properties":{"followup_id":{"type":"string"},"thread_id":{"type":"string"},"source_message_id":{"type":"string"},"title":{"type":"string"},"due_at":{"type":"string"},"status":{"type":"string","enum":["열림","닫힘"]},"closed_at":{"type":["string","null"]}},"required":["followup_id","thread_id","source_message_id","title","due_at","status","closed_at"]}
조건: followup_id 는 F-5 로 시작하는 네 자리 번호로 서로 다르게 씁니다. thread_id 와 source_message_id 는 thread_messages 에 실재하는 값만 쓰고, source_message_id 는 sender_side 가 거래처인 메시지를 가리킵니다. title 은 견적서 회신·납기 확정 답변처럼 무엇을 해야 하는지 적은 명사구로 씁니다. due_at 은 시작 메시지보다 뒤 시각으로 둡니다.
쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: 마감이 이미 지났는데 status 가 열림인 행 4건, closed_at 이 비어 있는 열린 행 12건 이상, 마감이 시작 메시지로부터 한 시간 안에 걸린 촉박한 행 2건, 아직 마감이 남아 대기로만 세는 행 3건.
JSON 배열만 출력합니다.{"type":"object","properties":{"followup_id":{"type":"string"},"thread_id":{"type":"string"},"source_message_id":{"type":"string"},"title":{"type":"string"},"due_at":{"type":"string"},"status":{"type":"string","enum":["열림","닫힘"]},"closed_at":{"type":["string","null"]}},"required":["followup_id","thread_id","source_message_id","title","due_at","status","closed_at"]}거래처마다 정해 둔 응대 약속입니다. 몇 시간 안에 답하기로 했는지와 늦어질 때 넘길 담당자를 담습니다. 담당자를 아직 정하지 않은 거래처는 escalation_owner 를 비워 두어 넘길 곳이 없다는 것이 화면에 드러나게 했습니다.
| account_id | account_name | reply_sla_hours | escalation_owner | active_from |
|---|---|---|---|---|
| A-1042 | 오픈서베이 | 8 | 김선호 | 2026-03-02 |
| A-1057 | CJ프레시웨이 | 8 | 이도현 | 2026-01-15 |
| A-1063 | 현대홈쇼핑 | 12 | — | 2026-05-20 |
| A-1071 | 리멤버 | 24 | 박지우 | 2026-02-10 |
| A-1088 | 토스페이먼츠 | 4 | 정하늘 | 2026-06-01 |
아래 JSON Schema 에 정확히 맞는 거래처 응대 약속 행 18개를 JSON 배열로 만들어 주세요.
{"type":"object","properties":{"account_id":{"type":"string"},"account_name":{"type":"string"},"reply_sla_hours":{"type":"integer","minimum":1,"maximum":72},"escalation_owner":{"type":["string","null"]},"active_from":{"type":"string"}},"required":["account_id","account_name","reply_sla_hours","escalation_owner","active_from"]}
조건: account_id 는 A- 로 시작하는 네 자리 번호로 서로 다르게 씁니다. account_name 은 광고와 유통, 플랫폼 업종의 회사 이름처럼 씁니다. reply_sla_hours 는 4·8·12·24·48·72 중에서 고릅니다. active_from 은 2026-01-01 부터 2026-09-01 사이의 날짜로 씁니다.
쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: escalation_owner 가 비어 있는 행 3건, reply_sla_hours 가 72 로 가장 긴 행 2건, reply_sla_hours 가 4 로 촉박한 행 2건, active_from 이 이번 달인 새 거래처 행 2건.
JSON 배열만 출력합니다.{"type":"object","properties":{"account_id":{"type":"string"},"account_name":{"type":"string"},"reply_sla_hours":{"type":"integer","minimum":1,"maximum":72},"escalation_owner":{"type":["string","null"]},"active_from":{"type":"string"}},"required":["account_id","account_name","reply_sla_hours","escalation_owner","active_from"]}| TypeScript | 판정 규칙부터 앱 화면까지 한 언어로 씁니다. 시각과 시간 수가 섞이지 않도록 계산 함수마다 단위를 타입으로 박습니다. |
| React Native | 하단 탭 네 개와 챙기기 목록, 근거 펼침을 그리는 앱 화면입니다. 공고가 말한 모바일 전반의 개발이 이 자리입니다. |
| Node.js | 대화 기록과 챙기기 항목, 응대 약속을 받아 판정을 돌리는 서버입니다. |
| PostgreSQL | 대화 메시지와 챙기기 항목, 거래처 약속을 담습니다. 대화방 번호와 발신 시각에 건 인덱스가 목록을 세우는 시간을 좌우합니다. |
| WebSocket | 3주차에 새 메시지가 들어오면 판정 결과를 앱으로 밀어 보냅니다. 담당자가 목록을 새로 고치지 않아도 순서가 바뀝니다. |
| Vitest | 시간 계산과 회신 조회 규칙을 입력과 기대 판정 표로 놓고 시험합니다. |
| Claude | 회신 초안 한 줄을 쓰는 기본 모델입니다. |
| Claude Code | 판정 규칙 시험과 더미데이터 증강 스크립트를 쓰는 데 씁니다. 공고가 밝힌 전사 Claude Code 사용과 같은 방식으로, 생성한 코드는 시험을 돌려 확인한 뒤 넣습니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 챙기기 40건에 정답을 달아 판정과 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 새 메시지가 들어올 때 목록이 그 자리에서 바뀌는 데까지 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · followup_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
제때 닫힘제때 닫힘기한 지남기한 지남답이 안 갔음회신 없음이미 답했는데 열려 있음닫히지 않음약속에 없는 거래처약속 미등록챙기기 항목 하나에 대해 지금 손대야 하는지와 그 사유를 남기는 형식입니다. 다섯 판정 값은 규칙이 정하고 회신 초안만 모델이 씁니다.
{"type":"object","properties":{"followup_id":{"type":"string"},"thread_id":{"type":"string"},"account_id":{"type":"string"},"account_name":{"type":["string","null"]},"verdict":{"type":"string","enum":["제때 닫힘","기한 지남","답이 안 갔음","이미 답했는데 열려 있음","약속에 없는 거래처"]},"elapsed_hours":{"type":["integer","null"],"minimum":0,"description":"followups.source_message_id 가 가리키는 thread_messages.sent_at 부터 흐른 시간을 시간 단위로 내려 적은 값입니다. 열린 항목은 기준 시각 2026-09-10T18:00 까지, 닫힌 항목은 followups.closed_at 까지 셉니다"},"over_sla_hours":{"type":["integer","null"],"description":"elapsed_hours 에서 account_terms.reply_sla_hours 를 뺀 값입니다. 우리 쪽 회신이 이미 나갔거나 응대 약속이 등록되지 않았으면 null 로 두고, 아직 응대 약속 시간을 넘기지 않아 뺀 값이 0 보다 작으면 넘긴 시간이 없다는 뜻이므로 그때도 null 로 둡니다"},"evidence":{"type":"array","items":{"type":"object","properties":{"dataset":{"type":"string"},"field":{"type":"string"},"agg":{"type":["string","null"],"enum":["count",null]},"value":{"type":["string","number","null"]},"quote":{"type":["string","null"]}},"required":["dataset","field","value"]}},"draft_reply":{"type":["string","null"],"description":"담당자가 대화방에 그대로 붙여 넣을 회신 초안 한 줄입니다"},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["followup_id","thread_id","account_id","account_name","verdict","elapsed_hours","over_sla_hours","evidence","draft_reply","summary","hedge"]}[
{
"followup_id": "F-5005",
"thread_id": "T-9005",
"account_id": "A-1071",
"account_name": "리멤버",
"verdict": "제때 닫힘",
"elapsed_hours": 5,
"over_sla_hours": null,
"evidence": [
{
"dataset": "followups",
"field": "status",
"value": "닫힘"
},
{
"dataset": "followups",
"field": "closed_at",
"value": "2026-09-08T15:35",
"quote": "2026-09-08T15:35"
},
{
"dataset": "followups",
"field": "due_at",
"value": "2026-09-09T10:00",
"quote": "2026-09-09T10:00"
},
{
"dataset": "thread_messages",
"field": "sent_at",
"value": "2026-09-08T15:30",
"quote": "2026-09-08T15:30"
},
{
"dataset": "thread_messages",
"field": "sender_side",
"value": "우리"
},
{
"dataset": "account_terms",
"field": "reply_sla_hours",
"value": 24
}
],
"draft_reply": null,
"summary": "리멤버의 계약서 검토 의견은 9일 10시 마감보다 앞선 8일 15시 35분에 닫혔습니다. 우리 쪽 회신도 그보다 먼저 나갔습니다.",
"hedge": null
},
{
"followup_id": "F-5006",
"thread_id": "T-9006",
"account_id": "A-1088",
"account_name": "토스페이먼츠",
"verdict": "제때 닫힘",
"elapsed_hours": 1,
"over_sla_hours": null,
"evidence": [
{
"dataset": "followups",
"field": "status",
"value": "닫힘"
},
{
"dataset": "followups",
"field": "closed_at",
"value": "2026-09-10T10:25",
"quote": "2026-09-10T10:25"
},
{
"dataset": "followups",
"field": "due_at",
"value": "2026-09-10T17:00",
"quote": "2026-09-10T17:00"
},
{
"dataset": "thread_messages",
"field": "sent_at",
"value": "2026-09-10T10:20",
"quote": "2026-09-10T10:20"
},
{
"dataset": "thread_messages",
"field": "sender_side",
"value": "우리"
},
{
"dataset": "account_terms",
"field": "reply_sla_hours",
"value": 4
}
],
"draft_reply": null,
"summary": "토스페이먼츠의 정산 내역 회신은 오늘 17시 마감보다 앞선 10시 25분에 닫혔습니다. 응대 약속 4시간 안에 답이 나갔습니다.",
"hedge": null
},
{
"followup_id": "F-5001",
"thread_id": "T-9001",
"account_id": "A-1042",
"account_name": "오픈서베이",
"verdict": "기한 지남",
"elapsed_hours": 51,
"over_sla_hours": null,
"evidence": [
{
"dataset": "followups",
"field": "due_at",
"value": "2026-09-09T18:00",
"quote": "2026-09-09T18:00"
},
{
"dataset": "followups",
"field": "status",
"value": "열림"
},
{
"dataset": "thread_messages",
"field": "sent_at",
"value": "2026-09-08T14:05",
"quote": "2026-09-08T14:05"
},
{
"dataset": "thread_messages",
"field": "sent_at",
"value": "2026-09-08T14:40",
"quote": "2026-09-08T14:40"
},
{
"dataset": "thread_messages",
"field": "sender_side",
"value": "우리"
},
{
"dataset": "account_terms",
"field": "reply_sla_hours",
"value": 8
}
],
"draft_reply": "오픈서베이 담당자님, 9월 캠페인 견적서 회신이 늦어져 죄송합니다. 오늘 중으로 확정본 보내드리겠습니다.",
"summary": "오픈서베이의 견적서 회신이 어제 18시 마감을 넘겨 아직 열려 있습니다. 8일 오후에 회신을 약속하는 메시지는 나갔지만 견적서 자체가 아직 안 갔습니다.",
"hedge": null
},
{
"followup_id": "F-5008",
"thread_id": "T-9008",
"account_id": "A-1102",
"account_name": "그리팅",
"verdict": "기한 지남",
"elapsed_hours": 32,
"over_sla_hours": null,
"evidence": [
{
"dataset": "followups",
"field": "due_at",
"value": "2026-09-10T09:00",
"quote": "2026-09-10T09:00"
},
{
"dataset": "followups",
"field": "status",
"value": "열림"
},
{
"dataset": "thread_messages",
"field": "sent_at",
"value": "2026-09-09T09:30",
"quote": "2026-09-09T09:30"
},
{
"dataset": "thread_messages",
"field": "sender_side",
"value": "거래처"
},
{
"dataset": "account_terms",
"field": "reply_sla_hours",
"value": 48
}
],
"draft_reply": "그리팅 담당자님, 촬영 일정 확정이 늦어졌습니다. 오늘 저녁까지 후보 일정 두 개로 정리해 드리겠습니다.",
"summary": "그리팅의 촬영 일정 확정은 오늘 09시 마감을 넘겼고 우리 쪽 메시지가 이 방에 아직 없습니다. 거래처가 물은 지 32시간이 지났고 응대 약속 48시간은 아직 남아 있습니다.",
"hedge": null
},
{
"followup_id": "F-5002",
"thread_id": "T-9002",
"account_id": "A-1057",
"account_name": "CJ프레시웨이",
"verdict": "답이 안 갔음",
"elapsed_hours": 26,
"over_sla_hours": 18,
"evidence": [
{
"dataset": "thread_messages",
"field": "sent_at",
"value": "2026-09-09T16:00",
"quote": "2026-09-09T16:00"
},
{
"dataset": "thread_messages",
"field": "sender_side",
"value": "거래처"
},
{
"dataset": "account_terms",
"field": "reply_sla_hours",
"value": 8
},
{
"dataset": "followups",
"field": "due_at",
"value": "2026-09-12T18:00",
"quote": "2026-09-12T18:00"
},
{
"dataset": "followups",
"field": "status",
"value": "열림"
}
],
"draft_reply": "CJ프레시웨이 담당자님, 10월 납기 확정 건 회신이 늦었습니다. 생산 일정 확인해 오늘 중 확정해 드리겠습니다.",
"summary": "CJ프레시웨이가 납기 확정을 물은 지 26시간이 지났고 이 거래처 응대 약속은 8시간입니다. 마감은 12일이지만 약속 시간을 18시간 넘겼습니다.",
"hedge": null
},
{
"followup_id": "F-5009",
"thread_id": "T-9009",
"account_id": "A-1123",
"account_name": "택스가이드",
"verdict": "답이 안 갔음",
"elapsed_hours": 30,
"over_sla_hours": 6,
"evidence": [
{
"dataset": "thread_messages",
"field": "sent_at",
"value": "2026-09-09T12:00",
"quote": "2026-09-09T12:00"
},
{
"dataset": "thread_messages",
"field": "sender_side",
"value": "거래처"
},
{
"dataset": "account_terms",
"field": "reply_sla_hours",
"value": 24
},
{
"dataset": "account_terms",
"field": "escalation_owner",
"value": null
},
{
"dataset": "followups",
"field": "status",
"value": "열림"
}
],
"draft_reply": "택스가이드 담당자님, 샘플 발송 주소 확인이 늦어졌습니다. 오늘 중 주소를 확정해 발송 예정일을 회신드리겠습니다.",
"summary": "택스가이드가 샘플 발송 주소를 물은 지 30시간이 지났고 응대 약속은 24시간입니다. 우리 쪽 메시지가 아직 이 방에 없습니다.",
"hedge": "이 거래처는 넘길 담당자가 등록돼 있지 않아 누구에게 올릴지 화면이 비워 둡니다."
},
{
"followup_id": "F-5003",
"thread_id": "T-9003",
"account_id": "A-1063",
"account_name": "현대홈쇼핑",
"verdict": "이미 답했는데 열려 있음",
"elapsed_hours": 9,
"over_sla_hours": null,
"evidence": [
{
"dataset": "thread_messages",
"field": "sent_at",
"value": "2026-09-10T09:12",
"quote": "2026-09-10T09:12"
},
{
"dataset": "thread_messages",
"field": "sender_side",
"value": "우리"
},
{
"dataset": "followups",
"field": "status",
"value": "열림"
},
{
"dataset": "followups",
"field": "due_at",
"value": "2026-09-11T18:00",
"quote": "2026-09-11T18:00"
},
{
"dataset": "account_terms",
"field": "escalation_owner",
"value": null
}
],
"draft_reply": null,
"summary": "현대홈쇼핑 방에는 오늘 09시 12분에 우리 쪽 회신이 나갔는데 소재 시안 확인이 아직 열려 있습니다. 닫아 두지 않으면 내일 마감 목록에 그대로 남습니다.",
"hedge": "이 거래처도 넘길 담당자가 비어 있어, 닫는 일을 누가 맡을지는 화면이 정하지 않습니다."
},
{
"followup_id": "F-5004",
"thread_id": "T-9004",
"account_id": "A-1148",
"account_name": null,
"verdict": "약속에 없는 거래처",
"elapsed_hours": 6,
"over_sla_hours": null,
"evidence": [
{
"dataset": "thread_messages",
"field": "account_id",
"value": "A-1148",
"quote": "A-1148"
},
{
"dataset": "thread_messages",
"field": "sent_at",
"value": "2026-09-10T11:20",
"quote": "2026-09-10T11:20"
},
{
"dataset": "thread_messages",
"field": "sender_side",
"value": "거래처"
},
{
"dataset": "followups",
"field": "status",
"value": "열림"
}
],
"draft_reply": null,
"summary": "A-1148 은 응대 약속표에 없는 거래처라 몇 시간 안에 답해야 하는지 기준이 없습니다. 단가표 요청이 6시간째 열려 있습니다.",
"hedge": "이 거래처의 응대 약속을 먼저 등록해야 회신 지연 판정이 붙습니다. 신규 거래처를 언제 약속표에 올릴지는 영업이 정합니다."
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 다섯 값(제때 닫힘·기한 지남·답이 안 갔음·이미 답했는데 열려 있음·약속에 없는 거래처) 중 하나인지 확인합니다. 판정은 규칙이 정하므로 모델이 다른 값을 쓰면 그 응답을 버립니다. 2. followup_id·thread_id·account_id·verdict·evidence·summary 여섯 필드가 샘플에 모두 있는지 확인합니다. 3. elapsed_hours 가 0 이상인지, account_terms.reply_sla_hours 가 1 이상 72 이하인지 범위를 확인합니다. 4. over_sla_hours 를 elapsed_hours 에서 account_terms.reply_sla_hours 를 뺀 값으로 다시 계산해 같은 값인지 봅니다. 5. elapsed_hours 를 source_message_id 가 가리키는 thread_messages.sent_at 부터 센 시간으로 다시 계산합니다. 열린 항목은 기준 시각 2026-09-10T18:00 까지, 닫힌 항목은 followups.closed_at 까지 세고 분 단위는 버립니다. 6. evidence 의 field 가 thread_messages·followups·account_terms 에 실재하는 컬럼 이름인지 대조합니다. 없는 이름을 인용하면 회신 초안이 지어낸 문장이라는 뜻이라 그 응답을 버립니다. 7. agg 가 없는 evidence 값이 그 컬럼에 수록된 행 값과 같은지 대조합니다. 8. evidence 의 quote 가 수록 샘플의 원문 그대로인지 확인합니다. 9. summary 와 draft_reply, hedge 가 존댓말로 끝나는 한국어 문장인지 확인합니다. 10. thread_messages.account_id 가 account_terms.account_id 목록에 없으면 over_sla_hours 는 null 이고 회신 지연을 세지 않습니다. F-5004 가 조건이 성립한 경우이고, F-5002 는 약속이 등록돼 있어 성립하지 않은 경우입니다. 11. source_message_id 뒤에 sender_side 가 우리인 thread_messages 가 있으면 over_sla_hours 는 null 입니다. F-5003 이 조건이 성립한 경우이고, F-5009 는 우리 쪽 메시지가 없어 성립하지 않은 경우입니다. 11-1. 응대 약속 시간을 아직 안 넘겨 elapsed_hours 가 account_terms.reply_sla_hours 이하이면 넘긴 시간이 없다는 뜻이라 over_sla_hours 는 null 입니다. 남은 시간을 뺄셈 값으로 적지 않고 비웁니다. F-5008 이 그런 경우로, 거래처가 물은 지 32시간이 지났지만 이 거래처 응대 약속은 48시간이라 마감만 지났고 약속은 아직 남았습니다. 12. 판정은 약속에 없는 거래처, 기한 지남, 답이 안 갔음, 이미 답했는데 열려 있음, 제때 닫힘 순으로 앞에서 걸리면 뒤를 보지 않습니다. 거래처가 기다린 시간이 긴 쪽을 앞에 둔 순서입니다. 13. 갈래마다 걸리는 조건은 아래와 같습니다. · 약속에 없는 거래처는 thread_messages.account_id 가 account_terms.account_id 목록에 없을 때입니다. · 기한 지남은 followups.due_at 이 기준 시각보다 앞인데 followups.status 가 아직 열림일 때입니다. · 답이 안 갔음은 source_message_id 뒤에 sender_side 가 우리인 thread_messages 가 없고, 그 사이 시간이 account_terms.reply_sla_hours 를 넘을 때입니다. · 이미 답했는데 열려 있음은 source_message_id 뒤에 sender_side 가 우리인 thread_messages 가 있는데 followups.status 는 아직 열림일 때입니다. · 제때 닫힘은 followups.status 가 닫힘이고 closed_at 이 due_at 보다 앞일 때입니다. 14. 판정은 마감이 지났거나 응대 약속을 넘겼거나 이미 답이 나간 항목, 그리고 닫힌 항목에만 붙입니다. 약속 시간이 남아 있고 아직 답하지 않은 항목은 화면 위쪽 대기 수로만 세고 카드로 만들지 않습니다. F-5007 이 그 대기 항목입니다. 15. 마감보다 늦게 닫힌 항목은 더미에 넣지 않았습니다. 그 경우를 늦게 닫힘으로 따로 셀지 제때 닫힘에 함께 둘지는 영업과 정할 문제라 판정 값을 임의로 늘리지 않습니다. [평가 러너 · 2주차] 1. 챙기기 항목을 40건으로 늘려 손으로 정답을 달고 판정 일치 수를 냅니다. 어긋난 건이 시간 계산에서 갈라졌는지 회신 메시지 조회에서 갈라졌는지 나눠 리포트에 답니다. [수동 검증 · 사람이 봅니다] 1. 답이 안 갔음으로 뜬 항목을 표본 20건 따라가며 실제로 회신이 없었는지 봅니다. 전화나 메일로 답한 건이 섞입니다. 2. 거래처별 응대 약속 시간을 분기마다 영업과 다시 봅니다. 계약 조건이 바뀌면 그 시간도 함께 바뀝니다.
근거: 거래처와의 모든 대화가 회사 자산이 되어 AI가 맥락을 읽어 인사이트를 추출하고 다른 툴들과의 유기적 연동을 추구합니다.