오웰헬스
경력근거: 코치의 업무를 돕는 'AI 에이전트'까지, AI 기술을 적극 활용해 정신 건강 문제 해결
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 유저의 정신 건강을 돕는 디스턴싱부터 코치의 업무를 돕는 AI 에이전트까지 만든다고 적고, 주요업무 첫 줄에 코치의 업무를 돕고 자동화하는 에이전트 개발을 둡니다. 이 포트폴리오는 그 첫 줄이 시작되는 자리를 만듭니다. 에이전트가 코치를 대신해 무엇을 하려면 먼저 누구를 먼저 볼지가 정해져야 하고, 그 순서는 활동지 기록과 검사 점수라는 디스턴싱 안쪽 데이터로만 나옵니다. 매달 2,000명이 12주 프로그램을 지나가는 곳에서 코치 한 사람이 수십 명을 익명으로 맡으니, 순서를 정하는 화면이 곧 코치의 하루를 바꿉니다. 다만 이 포트폴리오는 판정 규칙 설계와 화면, 초안 생성에 붙이는 가드를 보여줍니다. 임상 심리 평가나 자살 위험 판단 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
디스턴싱을 이용자 쪽과 코치 쪽으로 나눠 적고, 코치의 업무를 돕고 자동화하는 AI 에이전트 개발을 첫 줄에 둡니다. 이용자용 앱만 만드는 자리가 아니라 코치가 매일 보는 쪽도 같은 사람이 만듭니다.
우울·불안 치료를 목적으로 하는 디지털 인지치료 디스턴싱을 운영하며 매달 2,000명이 쓴다고 밝힙니다. 데이터가 제품 안쪽에서 매일 쌓이고, 그 데이터를 다루는 일이 곧 치료 성과와 붙어 있습니다.
작고 효율적인 팀을 지향하며 모든 사람이 큰 역할을 맡고 그런 역할이 있을 때만 채용한다고 적습니다. 프론트엔드와 백엔드의 경계에 얽매이지 않고 문제 해결에 필요한 기술을 골라 쓰는 사람을 찾습니다.
코치가 맡은 이용자들의 활동지 제출 기록과 2주마다 받는 검사 점수를 프로그램 표준·위험 기준과 나란히 놓으면, 사람마다 오늘 손댈 이유가 한 줄로 붙습니다. 판정은 점수 변화와 제출 기한, 마지막 접속 날짜를 기준 표와 견주는 계산이 정하고, 모델은 코치가 이용자에게 보낼 첫 문장 초안을 씁니다.
석사 이상 심리전문가 코치 한 사람이 수십 명을 1:1로 맡는데, 진행이 100% 익명이라 얼굴도 이름도 모르고 손에 남는 단서는 앱에 찍힌 활동지 제출 기록과 2주마다 다시 받는 정신건강 검사 점수뿐이다
코치가 놓치는 사람이 생기는 이유는 성의가 아니라 순서가 없어서입니다. 활동지 기록과 검사 점수를 프로그램이 정한 주차 기준과 붙여 두면, 오늘 먼저 연락할 사람과 다음 주에 봐도 되는 사람이 갈라져 코치가 목록 위에서부터 손을 댑니다.
코치가 쓰는 화면 하나가 돕니다. 활동지 진행 기록과 검사 점수, 프로그램 표준과 위험 기준 표를 올리면 사람마다 판정이 붙고 판정별 묶음과 근거가 한 화면에 뜹니다. 1주차는 사내 데이터베이스를 직접 붙이지 않고 내려받은 파일을 읽는 것으로 시작합니다.
코치가 손으로 정한 우선순위 60건으로 판정 일치율을 자동으로 내고, 어긋난 건이 점수 계산에서 갈라졌는지 날짜 계산에서 갈라졌는지 나눠 리포트로 냅니다. 사람마다 보낼 첫 문장 초안을 LangGraph 단계로 만들고, 근거 밖 사실이 문장에 들어오면 그 응답을 버립니다.
밤사이 쌓인 활동지 기록과 새 검사 결과로 아침마다 순서를 다시 세우고, 위험 기준을 넘긴 사람이 생기면 코치와 임상 책임자에게 바로 알립니다. 프로그램 안내문에서 그 주차에 맞는 문구를 찾아 초안에 붙입니다.
코치가 진행 기록과 검사 결과를 올리면 오늘 먼저 볼 사람이 순서대로 뜨고, 어느 점수와 어느 날짜가 그 순서를 만들었는지 그 자리에서 펼쳐집니다.
이전 점수가 없는 첫 검사, 회신 기록이 빈 사람, 활동지 0개와 기준선과 똑같은 점수를 섞은 입력 30종을 넣어도 화면이 죽지 않고 그 사람이 왜 판정 없이 남았는지 읽힙니다.
열 이름이 빠지거나 날짜 칸이 비거나 인코딩이 깨진 파일 12종을 올려도 목록 대신 어느 줄이 왜 걸렸는지 이슈로 뜨고 나머지 이용자는 그대로 판정됩니다.
코치가 손으로 정한 우선순위 60건으로 판정 일치율을 냅니다. 단, 기록과 정답을 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 적중률은 디스턴싱에 쌓인 실제 진행 기록과 코치의 연락 이력이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
일치율은 제가 지어낸 활동지 기록과 검사 점수에 제가 매긴 우선순위를 맞춰 본 자가채점입니다. 실제 코치가 정하는 순서에는 통화에서만 드러나는 사정이 섞여 수치가 달라지므로, 면접에서는 이 값을 성능이라 부르지 않고 데모 동작과 실검증 준비 상태로 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 이전 점수가 없는 첫 검사, 활동지 0개, 기준선과 똑같은 점수처럼 결측·악성·경계 케이스를 더미에 일부러 섞어 쉬운 문제만 푸는 함정을 피합니다.
검사 점수가 올랐다는 사실과 상태가 나빠졌다는 사실은 다른 말입니다. 이 화면이 정하는 것은 코치가 연락할 순서까지이고 상태 판단은 통화한 코치와 임상 책임자가 하므로, 판정을 진단으로 읽지 않게 근거가 된 점수와 날짜를 항상 같이 보여줍니다.
활동지가 밀렸다고 프로그램을 놓는 것도 아닙니다. 일이 바빠 미룬 주가 섞이므로 한 주 밀린 것과 두 주 연속 밀린 것을 다르게 세고, 연속 여부는 회차를 쌓아야 보이므로 3주차 과제로 둡니다.
실제 도입에서는 척도가 늘고 프로그램 주차 구성도 바뀝니다. 악화 폭과 기준선을 코드에서 빼 기준 표로 옮겨 두었지만 척도별 해석 차이는 임상 책임자가 정해야 하며, 그것은 도입 단계 과제로 분리합니다.
검사 원문과 상담 대화 내용은 민감정보라 이 포트폴리오에서 다루지 않습니다. 여기서는 제출 개수와 점수 숫자, 날짜만 받아 순서를 정하는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
이용자마다 지금 몇 주차에서 활동지를 몇 개 냈고 마지막으로 언제 들어왔는지 담은 기록입니다. 판정 대상 축이며 코치 회신 기록이 빈 사람과 활동지를 하나도 내지 않은 사람을 의도적으로 포함합니다.
| user_id | coach_id | week_no | week_started_on | worksheet_done | last_active_at | coach_reply_at |
|---|---|---|---|---|---|---|
| D-1802 | C-11 | 11 | 2026-09-05 | 3 | 2026-09-08 | 2026-09-08 |
| D-1877 | C-11 | 7 | 2026-09-02 | 4 | 2026-08-30 | — |
| D-1913 | C-12 | 6 | 2026-08-30 | 0 | 2026-09-05 | 2026-09-05 |
| D-1994 | C-12 | 9 | 2026-09-03 | 4 | 2026-09-07 | 2026-09-07 |
| D-2041 | C-11 | 4 | 2026-08-29 | 2 | 2026-09-06 | 2026-09-05 |
아래 JSON Schema 에 맞는 user_progress 행을 더 만들어 주세요. {"type":"object","properties":{"user_id":{"type":"string"},"coach_id":{"type":"string"},"week_no":{"type":"integer","minimum":1,"maximum":12},"week_started_on":{"type":"string"},"worksheet_done":{"type":"integer","minimum":0},"last_active_at":{"type":["string","null"]},"coach_reply_at":{"type":["string","null"]}},"required":["user_id","coach_id","week_no","week_started_on","worksheet_done"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) coach_reply_at 이 null 인 회신 기록 없는 사람, (2) worksheet_done 이 0 인 한 개도 내지 않은 사람, (3) last_active_at 과 오늘의 차가 그 주차 연락 간격과 똑같은 사람, (4) week_no 가 1 인 첫 주와 12 인 마지막 주. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"user_id":{"type":"string"},"coach_id":{"type":"string"},"week_no":{"type":"integer","minimum":1,"maximum":12},"week_started_on":{"type":"string"},"worksheet_done":{"type":"integer","minimum":0},"last_active_at":{"type":["string","null"]},"coach_reply_at":{"type":["string","null"]}},"required":["user_id","coach_id","week_no","week_started_on","worksheet_done"]}2주마다 다시 받는 정신건강 검사 점수입니다. 직전 점수를 같은 행에 두어 변화를 보게 했고, 첫 검사라 직전 점수가 없는 행과 기준선과 똑같은 점수를 포함합니다.
| user_id | scale | score | prev_score | measured_on |
|---|---|---|---|---|
| D-1802 | PHQ-9 | 5 | 7 | 2026-09-02 |
| D-1877 | PHQ-9 | 13 | 12 | 2026-08-31 |
| D-1913 | GAD-7 | 12 | 12 | 2026-09-04 |
| D-1994 | GAD-7 | 8 | 15 | 2026-09-05 |
| D-1994 | PHQ-9 | 6 | 9 | 2026-09-05 |
아래 JSON Schema 에 맞는 assessment_log 행을 더 만들어 주세요. {"type":"object","properties":{"user_id":{"type":"string"},"scale":{"type":"string","enum":["PHQ-9","GAD-7","ISI","PSS-10","PHQ-15","STAI-S","K-BDI-II","AUDIT-K","PC-PTSD-5","CES-D"]},"score":{"type":"integer","minimum":0},"prev_score":{"type":["integer","null"],"minimum":0},"measured_on":{"type":"string"}},"required":["user_id","scale","score","measured_on"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) prev_score 가 null 인 첫 검사 행, (2) score 가 그 척도 기준선과 똑같은 행, (3) 점수 변화가 악화 기준과 똑같은 행, (4) 한 사람이 두 척도를 같은 날 받은 경우. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"user_id":{"type":"string"},"scale":{"type":"string","enum":["PHQ-9","GAD-7","ISI","PSS-10","PHQ-15","STAI-S","K-BDI-II","AUDIT-K","PC-PTSD-5","CES-D"]},"score":{"type":"integer","minimum":0},"prev_score":{"type":["integer","null"],"minimum":0},"measured_on":{"type":"string"}},"required":["user_id","scale","score","measured_on"]}12주 프로그램이 주차마다 정해 둔 활동지 분량과 제출 기한, 연락 간격입니다. 기준 표라 판정 결과가 아니라 견줄 잣대만 담습니다.
| week_no | stage_name | worksheet_required | due_days | contact_interval_days |
|---|---|---|---|---|
| 1 | 생각과 거리 두기 소개 | 3 | 7 | 3 |
| 2 | 자동적 사고 알아차리기 | 4 | 7 | 3 |
| 3 | 생각 기록지 익히기 | 4 | 7 | 3 |
| 4 | 생각의 근거 따져 보기 | 5 | 7 | 3 |
| 5 | 회피 줄이기 | 5 | 7 | 3 |
아래 JSON Schema 에 맞는 program_standard 행을 더 만들어 주세요. {"type":"object","properties":{"week_no":{"type":"integer","minimum":1,"maximum":12},"stage_name":{"type":"string"},"worksheet_required":{"type":"integer","minimum":0},"due_days":{"type":"integer","minimum":1},"contact_interval_days":{"type":"integer","minimum":1}},"required":["week_no","stage_name","worksheet_required","due_days","contact_interval_days"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) worksheet_required 가 주차마다 2 에서 5 까지 달라지는 경우, (2) contact_interval_days 가 9주차부터 늘어나는 경우. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"week_no":{"type":"integer","minimum":1,"maximum":12},"stage_name":{"type":"string"},"worksheet_required":{"type":"integer","minimum":0},"due_days":{"type":"integer","minimum":1},"contact_interval_days":{"type":"integer","minimum":1}},"required":["week_no","stage_name","worksheet_required","due_days","contact_interval_days"]}척도마다 얼마나 오르면 악화로 보고 몇 점부터 기준선을 넘은 것으로 볼지 정한 표입니다. 기준선을 넘겼을 때 코치가 따를 절차도 한 줄로 적어 둡니다.
| scale | scale_name | worsening_delta | severe_cutoff | escalation_note |
|---|---|---|---|---|
| PHQ-9 | 우울 | 5 | 20 | 당일 안에 연락하고 자살 사고 문항을 따로 확인한 뒤 담당 임상 책임… |
| GAD-7 | 불안 | 5 | 15 | 당일 안에 연락하고 최근 두 주 사이에 무슨 일이 있었는지 먼저 묻습… |
| ISI | 불면 | 6 | 22 | 수면 기록지를 다시 받고 다음 회기를 앞당길지 코치가 정합니다. |
| PSS-10 | 지각된 스트레스 | 6 | 27 | 이번 주 과제 분량을 줄일지 코치가 정하고 그 사유를 기록합니다. |
| PHQ-15 | 신체 증상 | 5 | 15 | 신체 증상이 계속되면 병원 진료를 함께 권합니다. |
아래 JSON Schema 에 맞는 risk_criteria 행을 더 만들어 주세요. {"type":"object","properties":{"scale":{"type":"string","enum":["PHQ-9","GAD-7","ISI","PSS-10","PHQ-15","STAI-S","K-BDI-II","AUDIT-K","PC-PTSD-5","CES-D"]},"scale_name":{"type":"string"},"worsening_delta":{"type":"integer","minimum":1},"severe_cutoff":{"type":"integer","minimum":1},"escalation_note":{"type":"string"}},"required":["scale","scale_name","worsening_delta","severe_cutoff","escalation_note"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) worsening_delta 가 2 처럼 작은 척도와 8 처럼 큰 척도, (2) severe_cutoff 가 4 부터 52 까지 벌어진 경우, (3) escalation_note 가 코치 단독 처리를 막는 척도. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"scale":{"type":"string","enum":["PHQ-9","GAD-7","ISI","PSS-10","PHQ-15","STAI-S","K-BDI-II","AUDIT-K","PC-PTSD-5","CES-D"]},"scale_name":{"type":"string"},"worsening_delta":{"type":"integer","minimum":1},"severe_cutoff":{"type":"integer","minimum":1},"escalation_note":{"type":"string"}},"required":["scale","scale_name","worsening_delta","severe_cutoff","escalation_note"]}| Java | 판정 계산 본체의 언어입니다. 점수 변화와 기한 날수를 견줘 판정을 정하는 규칙이 여기 있습니다. |
| Spring Boot | 판정 실행과 결과 조회를 맡는 API 서버입니다. 규칙 실행은 이쪽 한 곳에만 둡니다. |
| React | 코치가 보는 우선순위 화면입니다. 판정별 묶음과 근거 표를 한 화면에 둡니다. |
| React Native | 이용자 앱입니다. 활동지 제출과 접속 기록이 여기서 남아 이 화면의 입력이 됩니다. |
| TypeScript | 코치 화면과 조회 계층의 언어입니다. 점수와 날수와 개수가 섞이지 않게 타입을 갈라 둡니다. |
| PostgreSQL | 진행 기록과 검사 결과, 회차별 판정을 담는 저장소입니다. 이용자 식별자만 남기고 이름은 담지 않습니다. |
| LangGraph | 첫 문장 초안을 만드는 단계를 그래프로 묶습니다. 판정 읽기, 근거 고르기, 문장 쓰기, 근거 대조를 각각 한 단계로 둡니다. |
| DeepAgents | 코치 한 사람이 맡은 수십 명을 한 번에 훑어 초안을 만드는 작업을 여러 단계로 나눠 돌립니다. |
| RAG | 3주차에 프로그램 안내문에서 그 주차에 맞는 문구를 찾아 초안에 붙입니다. 판정에는 쓰지 않습니다. |
| LLM | 판정이 정해진 뒤 코치가 보낼 첫 문장을 씁니다. 다섯 판정 값과 점수에는 손대지 못합니다. |
| Claude Code | 판정 규칙 시험과 더미데이터 증강 스크립트를 쓰는 데 씁니다. 시험을 먼저 두고 그 시험을 통과한 코드만 넣습니다. |
| Harness | 생성한 코드가 판정 규칙 시험과 경계값 시험을 통과해야 병합되도록 검사 묶음을 저장소에 두고 돌립니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 코치가 손으로 정한 60건과 판정을 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 아침마다 순서를 다시 세우고 위험 신호를 바로 알리는 단계로 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · user_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
코스대로 진행 중코스대로활동지 밀림활동지 밀림접속 끊김접속 끊김증상 악화증상 악화코스 밖 위험 신호위험 신호이용자 한 사람에 대해 코치가 이번 주 손댈 이유와 그 근거를 남기는 형식입니다. 판정 다섯 값은 대조 규칙이 정하고 첫 문장 초안만 모델이 씁니다.
{"type": "object", "properties": {"user_id": {"type": "string"}, "week_no": {"type": "integer", "minimum": 1}, "verdict": {"type": "string", "enum": ["코스대로 진행 중", "활동지 밀림", "접속 끊김", "증상 악화", "코스 밖 위험 신호"]}, "scale": {"type": ["string", "null"], "description": "그 사람의 검사 기록 가운데 이 판정을 대표하는 척도입니다. 점수 판정(증상 악화·코스 밖 위험 신호)이 걸리면 걸린 척도이고, 걸리지 않았으면 measured_on 이 가장 최근인 척도이며 같은 날이면 severe_cutoff 대비 점수 비율이 큰 쪽입니다. 검사 기록이 하나도 없으면 비웁니다"}, "score_delta": {"type": ["integer", "null"], "description": "scale 로 고른 척도의 assessment_log.score 에서 prev_score 를 뺀 값이며 이전 점수가 없으면 비웁니다"}, "worksheet_gap": {"type": ["integer", "null"], "minimum": 0, "description": "program_standard.worksheet_required 에서 user_progress.worksheet_done 을 뺀 값입니다"}, "days_overdue": {"type": ["integer", "null"], "minimum": 0, "description": "오늘에서 week_started_on 에 due_days 를 더한 기한을 뺀 날수이며 기한 안이면 0 입니다"}, "days_since_active": {"type": ["integer", "null"], "minimum": 0, "description": "오늘에서 last_active_at 을 뺀 날수입니다"}, "escalation_note": {"type": ["string", "null"], "description": "코스 밖 위험 신호일 때 같은 scale 의 risk_criteria.escalation_note 를 그대로 옮깁니다"}, "first_line": {"type": ["string", "null"], "description": "코치가 이용자에게 보낼 첫 문장 초안입니다"}, "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": ["user_id", "week_no", "verdict", "evidence", "summary"]}[
{
"user_id": "D-1994",
"week_no": 9,
"verdict": "코스대로 진행 중",
"scale": "GAD-7",
"score_delta": -7,
"worksheet_gap": 0,
"days_overdue": 0,
"days_since_active": 1,
"escalation_note": null,
"first_line": "이번 주 기록지 네 개를 다 채우셨네요. 지난번보다 편해진 부분이 있었는지 한 줄만 남겨 주세요.",
"evidence": [
{
"dataset": "user_progress",
"field": "worksheet_done",
"value": 4
},
{
"dataset": "program_standard",
"field": "worksheet_required",
"value": 4
},
{
"dataset": "assessment_log",
"field": "score",
"value": 8
},
{
"dataset": "assessment_log",
"field": "prev_score",
"value": 15
},
{
"dataset": "user_progress",
"field": "last_active_at",
"value": "2026-09-07"
}
],
"summary": "9주차 활동지 네 개를 모두 냈고 판정 척도로 고른 불안 점수가 15점에서 8점으로 내려갔습니다. 이번 주에 먼저 볼 이유가 없습니다.",
"hedge": null
},
{
"user_id": "D-2168",
"week_no": 1,
"verdict": "코스대로 진행 중",
"scale": "PHQ-9",
"score_delta": null,
"worksheet_gap": 0,
"days_overdue": 0,
"days_since_active": 2,
"escalation_note": null,
"first_line": "첫 주 기록지를 다 채우셨습니다. 다음 검사 때 지금과 견줘 볼 수 있으니 이번 주 느낌도 짧게 남겨 주세요.",
"evidence": [
{
"dataset": "assessment_log",
"field": "prev_score",
"value": null
},
{
"dataset": "assessment_log",
"field": "score",
"value": 14
},
{
"dataset": "user_progress",
"field": "worksheet_done",
"value": 3
},
{
"dataset": "program_standard",
"field": "worksheet_required",
"value": 3
}
],
"summary": "우울 검사가 첫 회차라 견줄 이전 점수가 없어 점수 판정을 건너뜁니다. 1주차 활동지 세 개를 기한 안에 냈고 이틀 전에 들어왔습니다.",
"hedge": "이전 점수가 없는 동안에는 이 사람의 점수 변화가 화면에 뜨지 않으므로, 2주 뒤 재검사 전까지는 활동지와 접속만 보고 판단합니다."
},
{
"user_id": "D-2103",
"week_no": 2,
"verdict": "증상 악화",
"scale": "PHQ-9",
"score_delta": 7,
"worksheet_gap": 1,
"days_overdue": 0,
"days_since_active": 1,
"escalation_note": null,
"first_line": "지난 검사보다 힘든 쪽으로 답하신 문항이 늘었습니다. 오늘 잠깐 이야기 나눌 수 있을까요.",
"evidence": [
{
"dataset": "assessment_log",
"field": "score",
"value": 19
},
{
"dataset": "assessment_log",
"field": "prev_score",
"value": 12
},
{
"dataset": "risk_criteria",
"field": "worsening_delta",
"value": 5
},
{
"dataset": "risk_criteria",
"field": "severe_cutoff",
"value": 20
},
{
"dataset": "user_progress",
"field": "worksheet_done",
"value": 3
},
{
"dataset": "program_standard",
"field": "worksheet_required",
"value": 4
}
],
"summary": "우울 점수가 12점에서 19점으로 7점 올라 악화 기준 5점을 넘겼습니다. 20점 기준선 아래라 절차 안내는 붙이지 않고 오늘 연락 목록 맨 위에 둡니다.",
"hedge": "점수가 오른 것이 곧 상태가 나빠졌다는 뜻은 아니라, 검사 당일 컨디션이나 문항 이해가 달랐을 수 있어 코치가 통화로 먼저 확인합니다."
},
{
"user_id": "D-2041",
"week_no": 4,
"verdict": "활동지 밀림",
"scale": "PHQ-9",
"score_delta": 1,
"worksheet_gap": 3,
"days_overdue": 3,
"days_since_active": 2,
"escalation_note": null,
"first_line": "4주차 기록지가 세 개 남아 있습니다. 한 개만 먼저 채워 보시면 나머지가 훨씬 수월해집니다.",
"evidence": [
{
"dataset": "user_progress",
"field": "worksheet_done",
"value": 2
},
{
"dataset": "program_standard",
"field": "worksheet_required",
"value": 5
},
{
"dataset": "user_progress",
"field": "week_started_on",
"value": "2026-08-29"
},
{
"dataset": "program_standard",
"field": "due_days",
"value": 7
},
{
"dataset": "assessment_log",
"field": "score",
"value": 11
},
{
"dataset": "assessment_log",
"field": "prev_score",
"value": 10
}
],
"summary": "4주차 활동지 다섯 개 중 두 개만 냈고 기한이 사흘 지났습니다. 우울 점수는 1점 올라 악화 기준 5점에 못 미칩니다.",
"hedge": null
},
{
"user_id": "D-1877",
"week_no": 7,
"verdict": "접속 끊김",
"scale": "PHQ-9",
"score_delta": 1,
"worksheet_gap": 0,
"days_overdue": 0,
"days_since_active": 9,
"escalation_note": null,
"first_line": "한동안 안 들어오셨네요. 지난주 감정 다루기까지 잘 오셨으니 오늘은 짧게 한 줄만 남겨 보시겠어요.",
"evidence": [
{
"dataset": "user_progress",
"field": "last_active_at",
"value": "2026-08-30"
},
{
"dataset": "program_standard",
"field": "contact_interval_days",
"value": 3
},
{
"dataset": "user_progress",
"field": "coach_reply_at",
"value": null
},
{
"dataset": "user_progress",
"field": "worksheet_done",
"value": 4
},
{
"dataset": "program_standard",
"field": "worksheet_required",
"value": 4
}
],
"summary": "마지막 접속이 아흐레 전이라 7주차 연락 간격 3일을 크게 넘겼고 그 뒤로 코치 회신 기록도 없습니다. 우울 점수는 1점 올라 악화 기준 5점 아래이고 활동지는 지난 주차까지 다 낸 사람입니다.",
"hedge": "회신 기록이 빈 것이 연락을 안 한 것인지 앱 밖에서 연락한 것인지는 갈리지 않아, 코치가 자기 기록을 한 번 확인한 뒤 연락합니다."
},
{
"user_id": "D-2135",
"week_no": 3,
"verdict": "코스 밖 위험 신호",
"scale": "PHQ-9",
"score_delta": 4,
"worksheet_gap": 0,
"days_overdue": 0,
"days_since_active": 1,
"escalation_note": "당일 안에 연락하고 자살 사고 문항을 따로 확인한 뒤 담당 임상 책임자에게 알립니다.",
"first_line": "오늘 검사 결과를 보고 연락드렸습니다. 지금 통화 가능하실까요.",
"evidence": [
{
"dataset": "assessment_log",
"field": "score",
"value": 20
},
{
"dataset": "risk_criteria",
"field": "severe_cutoff",
"value": 20
},
{
"dataset": "assessment_log",
"field": "prev_score",
"value": 16
},
{
"dataset": "risk_criteria",
"field": "worsening_delta",
"value": 5
},
{
"dataset": "user_progress",
"field": "worksheet_done",
"value": 4
},
{
"dataset": "program_standard",
"field": "worksheet_required",
"value": 4
}
],
"summary": "우울 점수 20점이 기준선 20점과 같아 위험 신호로 올립니다. 4점 오른 것은 악화 기준 5점에 못 미치지만 기준선 쪽이 먼저 걸립니다.",
"hedge": "이 판정은 코치가 오늘 안에 연락할 순서를 정하는 것이지 위험도를 진단하는 것이 아니며, 실제 판단은 통화한 코치와 임상 책임자가 합니다."
},
{
"user_id": "D-2077",
"week_no": 5,
"verdict": "증상 악화",
"scale": "ISI",
"score_delta": 6,
"worksheet_gap": 0,
"days_overdue": 0,
"days_since_active": 1,
"escalation_note": null,
"first_line": "요즘 잠드는 데 걸리는 시간이 늘었다고 답하셨습니다. 이번 주 수면 기록을 함께 볼까요.",
"evidence": [
{
"dataset": "assessment_log",
"field": "score",
"value": 21
},
{
"dataset": "assessment_log",
"field": "prev_score",
"value": 15
},
{
"dataset": "risk_criteria",
"field": "worsening_delta",
"value": 6
},
{
"dataset": "risk_criteria",
"field": "severe_cutoff",
"value": 22
},
{
"dataset": "user_progress",
"field": "worksheet_done",
"value": 5
},
{
"dataset": "program_standard",
"field": "worksheet_required",
"value": 5
}
],
"summary": "불면 점수가 15점에서 21점으로 6점 올라 악화 기준 6점과 같아 걸립니다. 22점 기준선 아래라 절차 안내는 붙이지 않습니다.",
"hedge": "기준과 같은 값을 걸린 것으로 볼지 아닌지는 척도마다 해석이 달라, 임상 책임자와 한 번 맞춰 두는 편이 낫습니다."
},
{
"user_id": "D-1913",
"week_no": 6,
"verdict": "활동지 밀림",
"scale": "GAD-7",
"score_delta": 0,
"worksheet_gap": 5,
"days_overdue": 2,
"days_since_active": 3,
"escalation_note": null,
"first_line": "6주차 행동 실험을 아직 시작 못 하셨네요. 가장 쉬운 것 하나만 골라 오늘 해 보시겠어요.",
"evidence": [
{
"dataset": "user_progress",
"field": "worksheet_done",
"value": 0
},
{
"dataset": "program_standard",
"field": "worksheet_required",
"value": 5
},
{
"dataset": "user_progress",
"field": "last_active_at",
"value": "2026-09-05"
},
{
"dataset": "program_standard",
"field": "contact_interval_days",
"value": 3
},
{
"dataset": "assessment_log",
"field": "score",
"value": 12
},
{
"dataset": "assessment_log",
"field": "prev_score",
"value": 12
}
],
"summary": "6주차 활동지를 하나도 내지 않았고 기한이 이틀 지났습니다. 불안 점수는 12점 그대로라 변화가 없고, 마지막 접속은 사흘 전으로 연락 간격 3일과 같아 접속 끊김에는 걸리지 않습니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 enum 다섯 값(코스대로 진행 중·활동지 밀림·접속 끊김·증상 악화·코스 밖 위험 신호) 중 하나인지 확인합니다. 판정은 대조 규칙이 정하므로 모델이 다른 값을 쓰면 그 응답을 버립니다. 2. user_id·week_no·verdict·evidence·summary 다섯 필드가 모두 있는지 확인합니다. 3. worksheet_gap 과 days_overdue, days_since_active 가 0 이상인지, week_no 가 1 이상인지 범위를 확인합니다. 4. score_delta 를 assessment_log.score 에서 prev_score 를 뺀 값으로, worksheet_gap 을 program_standard.worksheet_required 에서 user_progress.worksheet_done 을 뺀 값으로 다시 계산해 값이 같은지 확인합니다. 5. days_overdue 를 오늘에서 week_started_on 에 due_days 를 더한 기한을 뺀 날수로, days_since_active 를 오늘에서 last_active_at 을 뺀 날수로 다시 계산해 값이 같은지 확인합니다. 6. evidence 의 field 가 user_progress·assessment_log·program_standard·risk_criteria 에 실재하는 컬럼 이름인지 대조합니다. 없는 이름을 인용하면 첫 문장 초안이 지어낸 문장이라는 뜻이라 규칙이 만든 기본 문장으로 되돌립니다. 7. agg 가 없는 evidence 값이 그 컬럼에 수록된 행 값과 같은지 대조합니다. 여러 검사 기록에서 센 수치에는 agg 를 함께 적습니다. 8. summary 와 first_line, hedge 가 존댓말로 끝나는 한국어 문장인지 확인합니다. 9. assessment_log.prev_score 가 비어 있으면 그 사람의 점수 판정을 건너뛰고 score_delta 를 비웁니다. 첫 검사인 D-2168 이 조건이 성립한 경우이고, 이전 점수가 남아 있는 D-2103 은 성립하지 않은 경우입니다. 9-1. 한 사람에게 척도가 둘 이상이면 점수 판정은 척도마다 따로 재고, 걸린 척도가 있으면 그 척도를 scale 에 적습니다. 걸린 척도가 둘 이상이면 severe_cutoff 대비 점수 비율이 큰 쪽을 적습니다. 어느 척도도 안 걸렸으면 measured_on 이 가장 최근인 척도를, 같은 날이면 severe_cutoff 대비 점수 비율이 큰 척도를 적습니다. D-1994(GAD-7 8/15·PHQ-9 6/20)는 같은 날 검사라 비율이 큰 GAD-7 이 남고, D-2103(PHQ-9 7점 상승·GAD-7 1점 상승)은 악화가 걸린 PHQ-9 이 남습니다. 10. score 가 같은 scale 의 severe_cutoff 와 같거나 크면 코스 밖 위험 신호로 두고 그 줄의 escalation_note 를 그대로 옮깁니다. 20점으로 기준선과 같은 D-2135 가 성립한 경우이고, 19점인 D-2103 은 성립하지 않은 경우입니다. 11. 오늘과 last_active_at 의 차가 그 주차의 contact_interval_days 보다 클 때만 접속 끊김으로 둡니다. 아흐레가 지난 D-1877 이 성립한 경우이고, 사흘로 기준과 같은 D-1913 은 성립하지 않은 경우입니다. 12. 증상 악화는 score 에서 prev_score 를 뺀 값이 같은 scale 의 risk_criteria.worsening_delta 이상일 때이고, 활동지 밀림은 worksheet_gap 이 1 이상이거나 days_overdue 가 1 이상일 때입니다. 접속 끊김은 규칙 11번, 코스 밖 위험 신호는 규칙 10번이 정합니다. 13. 판정 순서는 코스 밖 위험 신호, 증상 악화, 접속 끊김, 활동지 밀림, 코스대로 진행 중 순으로 앞에서 걸리면 뒤를 보지 않습니다. 오늘 안에 연락해야 하는 순서입니다. 그래서 D-2103 처럼 활동지가 한 개 밀린 사람도 증상 악화가 앞서면 악화로만 뜹니다. [평가 러너 · 2주차] 1. 코치가 손으로 정한 우선순위 60건으로 판정 일치율을 냅니다. 어긋난 건은 점수 계산에서 갈라졌는지 날짜 계산에서 갈라졌는지 나눠 리포트에 답니다. [수동 검증 · 사람이 봅니다] 1. 증상 악화로 뜬 사람을 코치가 실제로 통화해 보고 화면 판정과 느낌이 맞았는지 표본 20건을 기록합니다. 2. 첫 문장 초안을 코치가 그대로 보낼 만한지 임상 책임자와 함께 읽고, 고쳐 쓴 문장을 다음 회차 예시로 남깁니다.