본문으로 건너뛰기
버전: 개발 버전 (최신)

환자 리포트 조회 API

참고: 이 문서는 dta-wi-mono 모노레포의 dta-wi-medi-api 애플리케이션에 구현되어 있습니다.

개요

환자 리포트 조회 API는 환자의 치료 진행 상황, 수면 기록, 설문 결과 등을 종합적으로 조회하는 기능을 제공합니다.

주요 기능

  • 환자 리포트 조회: 인증된 사용자의 환자 리포트 조회
  • 외부 토큰으로 환자 리포트 조회: 외부 액세스 토큰을 사용한 환자 리포트 조회 (공유 링크)

구현 방안

외부 공유 링크(토큰 기반)와 인증된 요청을 처리하는 두 가지 방안을 검토합니다. 구현 시 택 1.

항목인증된 요청외부 공유
엔드포인트GET /v1/user-management/app-users/{appUserId}/reportsGET /v1/user-management/app-users/{appUserId}/reports/external
인증Authorization: Bearer {accessToken}query param: ?token={external_md_access_token}
사용처대시보드 내부공유 링크, 외부 시스템

[GET] /v1/user-management/app-users/{appUserId}/reports - 환자 리포트 조회

환자 리포트 조회

특정 환자의 종합 리포트를 조회합니다.

⚠️ 변경 사항

주의: 이전 버전에서 다음 사항이 변경되었습니다. 클라이언트 코드 수정이 필요합니다.

엔드포인트 변경

  • asis: GET /v1/patients/user-cycles/{userCycleId}/reports (Go API)
  • tobe: GET /v1/user-management/app-users/{appUserId}/reports (TypeScript API)

변경된 path 파라미터

항목LegacyCurrent
Path ParameteruserCycleId (number)appUserId (string UUID)
식별 방식UserCycle ID로 식별User ID로 식별 (활성 UserCycle 자동 조회)

  • HTTP Method: GET
  • 인증: 액세스 토큰 (accessToken) 필요

Headers

HeaderTypeDescriptionRequired
AuthorizationBearer {accessToken}사용자 인증을 통해 발급받은 액세스 토큰 입니다.

Path Parameters

필드타입설명Required
appUserIdstring사용자 UUID

Request 예시

# 활성 주기 리포트 조회 (항상 ACTIVE 주기만 조회)
GET /v1/user-management/app-users/abc-123/reports

Responses

Http Status Code설명Error Code(s)
200 OK조회 성공-
400 Bad Request잘못된 요청VALIDATION_FAILED
401 Unauthorized인증 실패UNAUTHORIZED
404 Not Found환자 미발견USER_NOT_FOUND, USER_CYCLE_NOT_FOUND
500 Internal Server Error서버 내부 오류INTERNAL_SERVER_ERROR
200 OK - 성공
{
"period": {
"startDate": "2024-01-01",
"endDate": "2024-03-31"
},
"user": {
"name": "홍길동",
"email": "patient@example.com",
"phoneNumber": "01012345678",
"birthdate": "1990-01-01",
"siteId": "site-001",
"operationId": "account-001",
"operationName": "김의사",
"currentDayIndex": 15,
"status": "ACTIVE",
"lastLogin": "2024-01-20T10:00:00Z",
"startedAt": "2024-01-10T09:00:00Z",
"endAt": "2024-04-10T09:00:00Z",
"memo": "특이사항 없음",
"medicalRegistrationNumber": "MRN-001"
},
"sleeps": {
"2024-01-15": {
"diary": {
"dns": false,
"lot": 1380,
"ast": 1410,
"aet": 450,
"waso": 30,
"pill": false,
"nap": 0,
"sleepQuality": 4,
"tst": 420,
"se": 93.3,
"dse": 1,
"sol": 30,
"timezoneId": "Europe/Berlin",
"timezoneOffset": 60
},
"goal": {
"targetDate": "2024-01-15",
"durationInMinutes": 450,
"lot": 1380,
"aet": 450,
"timezoneId": "Europe/Berlin",
"timezoneOffset": 60,
"typeId": 1
}
}
},
"questionnaires": {
"ISI": [{ "completed": true, "score": 12, "maxScore": 28, "round": 1 }],
"WIQ": [],
"PHQ9": [{ "completed": true, "score": 5, "maxScore": 27, "round": 1 }],
"GAD7": [{ "completed": true, "score": 3, "maxScore": 21, "round": 1 }],
"ESS": [],
"DBAS": [{ "completed": true, "score": 5.5, "maxScore": 10, "round": 1 }]
},
"summary": {
"treatmentProgress": {
"currentDay": 15,
"totalUsageDays": 15
},
"sleepRecord": {
"count": 14,
"appUsageDays": 15,
"percentage": 93.3
},
"lotSuccess": {
"count": 12,
"appUsageDays": 15,
"percentage": 80.0
},
"aetSuccess": {
"count": 13,
"appUsageDays": 15,
"percentage": 86.7
}
}
}

Response 구조

period (조회 기간)

필드타입설명
startDatestring시작 날짜
endDatestring종료 날짜

user (환자 정보)

필드타입설명
namestring환자 이름
emailstring이메일
phoneNumberstring전화번호
birthdatestring생년월일
siteIdstring사이트 ID
operationIdstring담당 의료진 계정 ID
operationNamestring담당 의료진 이름
currentDayIndesnumber치료 일차
statusstring환자 상태
lastLoginstring마지막 로그인
startedAtstring치료 시작일
endAtstring치료 종료일
memostring메모
medicalRegistrationNumberstring의무 등록 번호

sleeps (수면 기록)

날짜를 키로 하는 맵 형태로 제공됩니다. 각 날짜별로 수면 일지(diary)와 수면 목표(goal) 정보를 포함합니다.

diary (수면 일지):

필드타입설명
dnsbooleanDid Not Sleep (수면하지 않음)
lotnumberLights Out Time (소등 시각, 분 단위)
astnumberActual Sleep Time (실제 수면 시각)
aetnumberActual End Time (실제 기상 시각)
wasonumberWake After Sleep Onset (중간 각성)
pillboolean수면제 복용 여부
napnumber낮잠 시간 (분)
sleepQualitynumber수면 질 평가 (1-5)
tstnumberTotal Sleep Time (총 수면 시간, 분)
senumberSleep Efficiency (수면 효율, %)
dsenumberDaily Sleep Efficiency
solnumberSleep Onset Latency (입면 잠복기)
timezoneIdstring타임존 ID
timezoneOffsetnumber타임존 오프셋 (분)

goal (수면 목표):

필드타입설명
targetDatestring목표 날짜
durationInMinutesnumber목표 수면 시간 (분)
lotnumber목표 소등 시각
aetnumber목표 기상 시각
timezoneIdstring타임존 ID
timezoneOffsetnumber타임존 오프셋 (분)
typeIdnumber수면 목표 타입 ID

questionnaires (설문 결과)

각 설문 타입별로 배열 형태로 제공됩니다.

설문 타입:

  • ISI: Insomnia Severity Index (불면증 심각도 지수)
  • WIQ: Work and Health Interview Questionnaire
  • PHQ9: Patient Health Questionnaire-9 (우울증)
  • GAD7: Generalized Anxiety Disorder-7 (불안장애)
  • ESS: Epworth Sleepiness Scale
  • DBAS: Dysfunctional Beliefs and Attitudes about Sleep
필드타입설명
completedboolean완료 여부
scorenumber점수 (optional)
maxScorenumber최대 점수
roundnumber회차

summary (요약 정보)

treatmentProgress (치료 진행):

필드타입설명
currentDaynumber현재 치료 일차
totalUsageDaysnumber총 앱 사용 일수

sleepRecord (수면 기록 통계):

필드타입설명
countnumber수면 기록 횟수
appUsageDaysnumber앱 사용 일수
percentagenumber수면 기록 비율 (%)

lotSuccess (소등 시각 준수 통계):

필드타입설명
countnumber소등 시각 준수 횟수
appUsageDaysnumber앱 사용 일수
percentagenumber소등 시각 준수 비율 (%)

aetSuccess (기상 시각 준수 통계):

필드타입설명
countnumber기상 시각 준수 횟수
appUsageDaysnumber앱 사용 일수
percentagenumber기상 시각 준수 비율 (%)
400 Bad Request - 잘못된 요청
{
"statusCode": 400,
"code": 1001,
"message": "Bad Request",
"detail": "요청한 정보가 유효하지 않습니다."
}

발생 가능한 에러 메시지:

  • "Invalid appUserId format" - 유효하지 않은 UUID 형식
401 Unauthorized - 인증 실패
{
"statusCode": 401,
"code": 1080,
"message": "UNAUTHORIZED",
"detail": "인증되지 않은 요청입니다"
}

액세스 토큰이 유효하지 않거나 만료된 경우 발생합니다.

404 Not Found - 환자 미발견
{
"statusCode": 404,
"code": 1040,
"message": "NOT_FOUND",
"detail": "유효하지 않은 사용자입니다."
}

해당 appUserId에 해당하는 환자가 존재하지 않거나, 활성 UserCycle이 없는 경우 발생합니다.

500 Internal Server Error - 서버 내부 오류
{
"statusCode": 500,
"code": 1000,
"message": "Internal Server Error",
"detail": "서버 내부 오류가 발생했습니다."
}

[GET] /v1/user-management/app-users/{appUserId}/reports/external - 외부 토큰으로 환자 리포트 조회

외부 토큰으로 환자 리포트 조회

외부 액세스 토큰을 사용하여 환자 리포트를 조회합니다. 일반 인증 토큰이 아닌 별도의 외부 액세스 토큰(external_md_access)으로 인증합니다.

⚠️ 변경 사항

엔드포인트 변경

  • asis: GET /v1/patients/user-cycles/{userCycleId}/reports/external?token=xxx (Go API)
  • tobe: GET /v1/user-management/app-users/{appUserId}/reports/external?token=xxx (TypeScript API)

변경된 파라미터

항목LegacyCurrent
Path ParameteruserCycleId (number)appUserId (string UUID)
토큰 subuserCycleIduserId

  • HTTP Method: GET
  • 인증: 외부 액세스 토큰 (token) 필요 (Authorization 헤더 불필요)

Path Parameters

필드타입설명Required
appUserIdstring사용자 UUID

Query Parameters

필드타입설명Required
tokenstring외부 액세스 토큰 (external_md_access)

Request 예시

# 활성 주기 리포트 (외부 토큰)
GET /v1/user-management/app-users/abc-123/reports/external?token=eyJhbGci...

토큰 검증 로직

  1. 쿼리 파라미터에서 token 값 추출 및 필수 체크
  2. 외부 액세스 토큰의 유효성 검증
  3. 토큰 타입이 external_md_access인지 확인
  4. 토큰의 JWT claims에서 sub (subject) 추출
  5. sub 값이 요청 path의 appUserId와 일치하는지 확인
  6. 검증 성공 시 활성(ACTIVE) UserCycle의 리포트 조회

Responses

Http Status Code설명Error Code(s)
200 OK조회 성공-
400 Bad Request잘못된 요청VALIDATION_FAILED
401 Unauthorized인증 실패UNAUTHORIZED
404 Not Found환자 미발견USER_NOT_FOUND, USER_CYCLE_NOT_FOUND
500 Internal Server Error서버 내부 오류INTERNAL_SERVER_ERROR
200 OK - 성공

응답 형식은 일반 환자 리포트 조회와 동일합니다.

400 Bad Request - 잘못된 요청
{
"statusCode": 400,
"code": 1001,
"message": "Bad Request",
"detail": "잘못된 요청입니다."
}

발생 가능한 에러 메시지:

  • "잘못된 요청입니다." - 토큰 파라미터가 제공되지 않음
  • "유효하지 않은 토큰 정보 입니다." - 토큰의 sub 클레임이 없거나 형식이 잘못됨
  • "유효하지 않은 토큰 정보 입니다." - 토큰의 sub와 appUserId가 일치하지 않음
401 Unauthorized - 인증 실패
{
"statusCode": 401,
"code": 1080,
"message": "UNAUTHORIZED",
"detail": "인증되지 않은 요청입니다"
}

발생 시나리오:

  • JWT 토큰이 만료됨
  • 토큰 서명이 유효하지 않음
  • 토큰 타입이 external_md_access가 아님
404 Not Found - 환자 미발견
{
"statusCode": 404,
"code": 1040,
"message": "NOT_FOUND",
"detail": "유효하지 않은 계정 정보입니다."
}

appUserId에 해당하는 환자 정보를 찾을 수 없는 경우 발생합니다.

500 Internal Server Error - 서버 내부 오류
{
"statusCode": 500,
"code": 1000,
"message": "Internal Server Error",
"detail": "서버 내부 오류가 발생했습니다."
}

토큰 정보

타입설명sub
external_md_access의료진 대시보드 외부 액세스 토큰userId

기존 대비 변경: 토큰의 subuserCycleIduserId로 변경됨. cycle이 변경/추가되어도 토큰 재발급 불필요.


비즈니스 로직

  1. 해당 user의 활성(ACTIVE) UserCycle 자동 조회
  2. 환자의 치료 시작일/종료일 계산 (UserCycle 기준)
  3. 해당 기간의 수면 다이어리 조회
  4. 수면 목표 로그 조회
  5. 설문지 결과 조회 (ISI, WIQ, PHQ9, GAD7, ESS, DBAS)
  6. LOT/AET 목표 달성 성공 횟수 계산
  7. 치료 진행률 및 성공률 통계 계산 (Plan 기반 총 일차)

특이사항:

  • 항상 활성(ACTIVE) 상태의 UserCycle만 조회
  • startDate부터 endDate까지 모든 날짜 슬롯 생성 (데이터 없어도 빈 객체 생성)
  • endDate가 현재 날짜보다 미래인 경우 현재 날짜로 조정
  • 타임존은 UserCycle의 timezoneId 기준으로 처리
  • 총 치료 일차는 Plan 기반으로 계산 (기본: 90일)