건강 점수가 '왜' 그렇게 나왔는지까지 설명하게 만들다 — AI 리포트에 근거를 붙인 하루
📚 「AI 헬스케어 파이널 프로젝트」 연재 14편입니다. (13편: 팀원이 보내준 예측 모델을, 우리 서비스에 이식한 날)

📝 한줄 요약
지난 편에서 모델이 “위험하다 / 안전하다”를 판정하게 만들었습니다. 오늘은 그 점수가 ‘왜’ 그렇게 나왔는지까지 설명하게 만든 날입니다. 먼저 검진 결과를 넣으면 뒤에서 알아서 위험도를 계산해주는 흐름을 완성했고, 그다음 “혈압이 이만큼, 콩팥 수치가 저만큼 영향을 줬어요” 같은 변수별 근거 리포트를 붙였습니다. 그 과정에서 라이브러리 충돌, 마이그레이션 사고 같은 함정도 줄줄이 만났고요.
바쁘시면 이것만 읽어도 돼요:
- 검진을 저장하면 뒤에서 자동으로 위험도를 계산 — 사용자는 기다릴 필요 없이 다른 걸 보면 됨(비동기 처리)
- AI 점수에 근거를 붙이는 SHAP — “어떤 항목이 점수에 얼마나 영향을 줬는지”를 변수별로 보여주는 기능
- 또래(같은 나이대)와 내 생활습관을 나란히 비교하는 그래프까지 추가
- 라이브러리 충돌 — 두 도구가 서로 안 맞아 멈춤. 다른 내장 기능으로 우회했더니 결과는 글자 하나까지 똑같음
- 데이터 구조 변경(마이그레이션)을 손으로 짜다 사고 — 자동 도구로만 만들어야 한다는 교훈
- 단위 테스트로는 안 보이고 진짜 화면에서 직접 써봐야 드러나는 문제들
🎯 이런 분들께 도움돼요
- AI가 내린 결론에 “왜?”라는 근거를 붙이고 싶은 분
- 사용자가 기다리지 않게 무거운 계산을 뒤로 미루는 설계가 궁금한 분
- 도구·라이브러리가 서로 안 맞아 막혔을 때 우회하는 법이 궁금한 분
😫 문제 상황 (Before)
지난 편까지 모델은 “당신은 어느 위험 등급입니다”를 알려줄 수 있었습니다. 그런데 막상 그 결과만 받으면 사용자는 답답합니다. “그래서 내가 왜 위험한데?”라는 질문이 남으니까요. 등급만 던지고 끝내는 건 불친절합니다.
그래서 오늘 풀어야 할 숙제는 두 개였습니다. 하나는 검진을 넣으면 알아서 위험도가 계산되는 자동 흐름, 또 하나는 그 점수의 근거를 사람이 이해할 말로 풀어주는 리포트였습니다.
🛠️ 사용한 도구
- 도구: Claude Code
- 모델: Claude Opus
- 특이사항: 큰 작업은 브레인스토밍 → 설계 → 작은 일거리로 쪼개 AI에게 분업. 마지막엔 진짜 화면(docker)을 띄워 직접 시연하며 다듬음
🔧 작업 과정
검진을 넣으면 뒤에서 알아서 계산하게 — 기다리지 않는 설계
오늘할 작업은 우선 ckd예측 모델을 우리 시스템에 결합하는 작업이야
검진을 저장하면 뒤에서 자동으로 위험도를 계산하게 만들고 싶어
처음엔 단순하게 “검진을 넣으면 그 자리에서 점수를 보여주면 되지”라고 생각했습니다. 그런데 위험도 계산은 모델을 돌리는 무거운 일입니다. 사용자가 검진을 저장할 때마다 화면이 멈춰서 몇 초씩 기다리게 하면 답답하겠죠.
그래서 비동기(작업을 뒤로 미뤄 따로 처리하는 방식)로 만들었습니다. 검진을 저장하면 화면은 곧바로 넘어가고, “계산 중”이라고 표시만 둡니다. 그동안 뒤에서 일꾼이 조용히 모델을 돌려 위험도를 채워 넣고요. 실제로 돌려보니 검진 저장 직후엔 위험도가 비어 있다가, 35초쯤 뒤에 점수가 채워졌습니다. 사용자는 그 35초 동안 다른 화면을 보면 됩니다.
💡 무거운 계산은 사용자를 기다리게 하지 마세요. “지금 당장 보여줄 것”과 “뒤에서 채워도 될 것”을 나누는 것만으로 체감 속도가 완전히 달라집니다.
AI 점수에 ‘근거’를 붙이다 — SHAP라는 도구
이제 핵심입니다. “당신은 위험 등급”이라는 결론에 이유를 붙이는 일이요. 여기서 쓴 게 SHAP입니다.
SHAP를 한 줄로 풀면 이렇습니다 — “어떤 항목이 이 점수에 얼마나 영향을 줬는지”를 변수별로 떼어 보여주는 것. 예를 들면 “당신 위험 점수에 혈압이 +30%, 콩팥 수치가 +25%, 혈당이 +15% 영향을 줬어요”처럼요. 그러면 사용자는 자기 점수의 출처를 눈으로 봅니다.
우리 팀의 예측 모델은 두 개였습니다(임상 수치를 보는 모델1, 생활습관을 보는 모델2). 두 모델 모두 팀원이 실험용 노트북에서 SHAP 계산을 해둔 게 있었는데, 그걸 우리 서비스 코드로 옮겨 붙였습니다. 옮기는 작업 자체가 만만치 않았지만, 결국 모델1은 임상 변수 14개, 모델2는 생활습관 변수 12개의 기여도를 뽑아내게 됐습니다.
💡 AI의 결론에 “왜?”를 붙이면 신뢰가 생깁니다. 같은 점수라도 “그냥 위험합니다”와 “혈압·콩팥수치가 이만큼 영향을 줬습니다”는 받아들이는 무게가 다릅니다.
나만 보면 모르니까 — 또래와 비교하는 그래프
근거를 붙이고 나니 한 가지가 더 필요했습니다. “내 생활습관 점수가 0.6”이라고 해도, 그게 좋은 건지 나쁜 건지 혼자선 감이 안 옵니다. 비교 대상이 있어야 의미가 생기죠.
그래서 같은 나이대 사람들의 분포를 옆에 깔았습니다. 미리 연령대별로 점수 분포를 정리해 얼려두고(40대부터 80대까지), “당신은 또래 중 상위 몇 퍼센트”처럼 내 위치를 짚어주는 막대그래프를 그렸죠. 그러면 “내 점수 0.6”이 “또래 평균보단 위, 상위 30% 정도”로 읽힙니다. 그제야 숫자에 의미가 붙습니다.
두 도구가 서로 안 맞아 멈췄다 — 우회로 찾기
PR #20 머지하고 docker rebuild 해줘
백엔드까지 다 만들고 진짜 환경에 올렸는데, SHAP 결과가 텅 비어서 나왔습니다. 원인을 파보니 황당했습니다 — 두 라이브러리(도구)가 서로 안 맞았던 겁니다. SHAP를 계산하는 도구가 다른 도구(transformers)의 최신 버전과 충돌하면서, 계산을 시작도 못 하고 멈춰버린 거였죠.
선택지는 둘이었습니다. 버전을 억지로 맞추느라 씨름하거나, 아예 다른 길을 찾거나. 다행히 우회로가 있었습니다. SHAP 계산을 그 문제의 라이브러리 없이, 모델이 원래 가진 내장 기능으로 직접 할 수 있었거든요. 그래서 충돌하는 도구를 통째로 걷어내고 내장 기능으로 바꿨습니다.
가장 안심된 건, 두 방식의 결과가 소수점까지 똑같았다는 점입니다(차이 0.00). 길만 바꿨지 답은 그대로였던 거죠. 덤으로 무거운 도구 하나를 덜어내 시스템도 가벼워졌습니다.
💡 충돌엔 두 가지 길이 있습니다 — 억지로 맞추거나, 우회하거나. 결과가 같다는 걸 숫자로 확인할 수만 있으면, 우회가 더 깔끔할 때가 많습니다.
데이터 구조를 손으로 바꾸다 사고 났다
머지는 했고 docker rebuild 해줘
리포트를 담으려면 데이터 저장 공간의 구조를 바꿔야 했습니다. 이걸 마이그레이션(데이터베이스 구조를 바꾸는 작업)이라고 부릅니다. 그런데 그 작업 파일을 손으로 짰더니, 서버가 아예 안 켜졌습니다.
원인은 이랬습니다. 그 구조 변경 파일에는 사람 눈엔 안 보이지만 시스템이 꼭 챙기는 정보가 따라붙어야 했는데, 손으로 짜다 그걸 빠뜨린 거였죠. 빠진 정보를 본 시스템은 “이건 옛날 형식이야”라며 켜지길 거부했습니다.
해결은 단순했습니다. 손으로 짜지 말고, 자동 생성 도구를 쓰면 그 정보까지 알아서 채워줬거든요. 그렇게 다시 만드니 서버가 깔끔하게 올라왔습니다.
💡 자동화 도구가 있는 작업을 굳이 손으로 하지 마세요. “직접 짜면 더 잘 통제할 수 있겠지”가 오히려 화근이 됩니다. 도구는 사람이 못 보는 것까지 챙겨줍니다.
”김철수씨로 안됨?” — 직접 화면을 보며 다듬기
꼭 새로 입력해야되나? 김철수씨로 안됨??
그리고 shap과 리포트는 어디서 봐야되는거야?
백엔드가 다 됐어도, 진짜 화면을 띄워 직접 써보기 전엔 안심이 안 됩니다. 그래서 시연용 인물(김철수)의 검진 데이터로 리포트를 띄워봤죠. 여기서부터는 화면을 보며 자잘한 걸 계속 다듬는 작업이었습니다.
대시보드와 챌린지 사이에 '리포트'로 만들어줘
먼저 리포트로 가는 메뉴를 상단에 넣었습니다. 그다음엔 게이지(눈금판) 같은 시각 요소를 손봤고요.
나이대별 그래프가 없네
검진기반이라는 글자 지워주고 수치는 중앙으로 옮기기
예상값 글자 지워주고 수치는 중앙으로
빠졌던 또래 분포 그래프를 채우고, 게이지 위에 떠 있던 군더더기 글자(“검진기반”, “예상값”)를 지우고, 수치를 가운데로 옮겼습니다. 하나하나는 사소해 보여도, 직접 보면서 짚지 않으면 절대 안 나오는 디테일이었습니다.
ai 행동 가이드는 모델2에서 온건가??
모델1에서도 간단한 리포트가 있는데
마지막엔 제가 화면을 보다 의문이 생겼습니다. “이 AI 행동 가이드는 두 모델 중 어디서 온 거지?” 모델1에도 간단한 요약이 있는데 화면엔 안 보였거든요. 그래서 모델1의 검사 권고와 종합 요약도 리포트에 함께 나오도록 채웠습니다.
💡 단위 테스트가 다 통과해도, 진짜 화면을 보면 할 말이 생깁니다. “여기 글자가 거슬린다”, “이 정보가 왜 빠졌지” 같은 건 직접 써봐야만 나옵니다. 시연은 검증의 마지막 관문입니다.
✅ 결과 (After)
Before vs After
| 항목 | Before | After |
|---|---|---|
| 위험도 계산 | (없음) | 검진 저장 → 뒤에서 자동 계산(35초 후 채워짐) |
| 점수 설명 | ”위험 등급입니다”만 | 변수별 근거(혈압·콩팥수치 등 기여도) |
| 비교 기준 | 내 숫자만 | 또래(나이대) 분포와 나란히 |
| 라이브러리 충돌 | SHAP 결과 빈값 | 내장 기능으로 우회(결과 동일 0.00) |
결과물
- 검진을 넣으면 뒤에서 자동으로 위험도를 계산하는 흐름
- 점수의 근거를 변수별로 보여주는 리포트(SHAP) — 임상·생활습관 양쪽 모델
- 또래 분포 비교 그래프 + AI 행동 가이드
- 데이터 구조 변경은 손이 아니라 도구로만 한다는 기록
💬 이 과정에서 배운 AI 활용 팁
효과적이었던 것
- 무거운 일은 뒤로 미루기: “지금 보여줄 것”과 “뒤에서 채울 것”을 나누면 체감 속도가 달라짐
- 결론에 근거 붙이기: AI 점수에 “왜”를 붙이면 신뢰가 생김. SHAP가 그 역할
- 충돌은 우회로도 풀린다: 억지로 맞추지 말고, 같은 결과를 내는 다른 길을 찾기
이렇게 하면 안 돼요
- 자동화 도구가 있는 작업을 손으로 하기: 데이터 구조 변경을 손으로 짜다 서버가 안 켜졌음
- 화면 검증을 건너뛰기: 단위 테스트만 믿으면 거슬리는 디테일·빠진 정보를 못 잡음
🌍 다른 업무에 적용한다면?
“결론에 근거를 붙인다”는 코드가 아니어도 어디서나 통합니다. 보고서에 “이 결론은 이런 근거에서 나왔다”를 붙이면 설득력이 다르고, 평가 점수에 항목별 기여도를 보여주면 받는 사람이 납득합니다. 결론만 던지지 말고 출처를 함께 보여주기 — AI든 사람이든 신뢰는 거기서 나옵니다. 또 “무거운 일은 기다리게 하지 말고 뒤로 미루기”는 업무 흐름을 짤 때 늘 통하는 원칙이고요.
🚀 앞으로의 계획
리포트까지 붙이고 나니, 정작 모델의 바탕인 데이터에 구멍이 보이기 시작했습니다. 위험 등급 중 하나가 학습 데이터에 통째로 빠져 있어서, 그 등급은 모델이 제대로 배운 적이 없던 거죠. 데이터에 구멍이 있으면 AI도 엉뚱한 소리를 합니다. 그래서 다음 편에서는 빠진 등급을 채워 다시 학습시키고, 더불어 챗봇이 왜 가끔 느린지 속도를 추적하는 이야기를 풀어볼게요. 다음 막에서 이어집니다.
📋 재사용 가능한 프롬프트
프롬프트 1: 무거운 작업 뒤로 미루기
[무거운 계산/작업]을 사용자가 기다리지 않게 만들고 싶어. 화면은 바로 넘기고, 뒤에서 따로 처리한 다음 결과가 준비되면 채워 넣는 방식으로 설계해줘.
프롬프트 2: AI 결론에 근거 붙이기
[AI가 내린 점수/판정]이 왜 그렇게 나왔는지, 어떤 항목이 결과에 얼마나 영향을 줬는지 변수별로 보여줘. 사용자가 자기 점수의 출처를 눈으로 볼 수 있게.
프롬프트 3: 라이브러리 충돌 우회
[도구 A]와 [도구 B]가 서로 안 맞아서 멈췄어. 버전을 억지로 맞추는 대신, 같은 결과를 내는 다른 방법이 있는지 찾아줘. 우회한 결과가 원래와 같은지 숫자로 확인까지 해줘.
📌 「AI 헬스케어 파이널 프로젝트」 연재 14편입니다. 다음 막에서 또 이어집니다.