Skip to content

Commit 996bc6d

Browse files
authored
Merge pull request #105 from My-Medi/develop
Merge to Main from Develop
2 parents b2c020b + 0a5122f commit 996bc6d

14 files changed

Lines changed: 285 additions & 80 deletions

File tree

src/main/java/com/my_medi/api/report/controller/UserReportApiController.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.my_medi.api.report.controller;
22

33
import com.my_medi.api.common.dto.ApiResponseDto;
4+
import com.my_medi.api.healthCheckup.dto.ComparingHealthCheckupResponseDto;
5+
import com.my_medi.api.report.dto.*;
46
import com.my_medi.api.report.dto.EditReportRequestDto;
57
import com.my_medi.api.report.dto.ReportResponseDto;
68
import com.my_medi.api.report.dto.WriteReportRequestDto;
@@ -13,6 +15,7 @@
1315
import com.my_medi.domain.report.service.ReportQueryService;
1416
import com.my_medi.domain.user.entity.User;
1517
import com.my_medi.infra.gpt.dto.HealthReportData;
18+
import com.my_medi.infra.gpt.dto.TotalReportData;
1619
import com.my_medi.infra.gpt.dto.HealthTermResponse;
1720
import com.my_medi.infra.gpt.service.OpenAIService;
1821
import io.swagger.v3.oas.annotations.Operation;
@@ -40,10 +43,11 @@ public ApiResponseDto<UserReportDto> getUserReport(@AuthUser User user,
4043
return ApiResponseDto.onSuccess(ReportConverter.toUserReportDto(report));
4144
}
4245

43-
@Operation(summary = "사용자 본인의 건강리포트 개수를 조회합니다.")
44-
@GetMapping("/count")
45-
public ApiResponseDto<Long> getUserReportCount(@AuthUser User user) {
46-
return ApiResponseDto.onSuccess(reportQueryService.getReportCountByUser(user));
46+
@Operation(summary = "사용자의 가장 최근 리포트의 요약본을 가져옵니다.")
47+
@GetMapping("/summary")
48+
public ApiResponseDto<ReportSummaryDto> getUserReportSummary(@AuthUser User user) {
49+
Report report = reportQueryService.getLatestReportByUserId(user.getId());
50+
return ApiResponseDto.onSuccess(ReportConverter.toUserReportSummaryDto(report));
4751
}
4852

4953
@Operation(summary = "사용자가 본인의 건강리포트를 생성합니다.")
@@ -72,10 +76,21 @@ public ApiResponseDto<ReportResponseDto.ReportResultDto> comparingReport(@AuthUs
7276

7377
@Operation(summary = "GPT API를 사용하여 건강검진 이미지를 원하는 데이터대로 추출합니다.")
7478
@PostMapping(value = "/parsing", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
75-
public ApiResponseDto<HealthReportData> parsingHealthReportImage(@RequestPart MultipartFile imageFile) {
79+
public ApiResponseDto<WriteReportRequestDto> parsingHealthReportImage(@RequestPart MultipartFile imageFile) {
80+
HealthReportData healthReportData = openAIService.parseHealthReport(ImageUtil.convertToByte(imageFile));
7681
return ApiResponseDto.onSuccess(
77-
openAIService.parseHealthReport(ImageUtil.convertToByte(imageFile))
82+
ReportConverter.toWriteReportRequestDto(healthReportData)
7883
);
7984
}
8085

86+
@Operation(summary = "LLM이 건강상태를 반영하여 건강검진 결과를 분석합니다.")
87+
@GetMapping("/total")
88+
public ApiResponseDto<TotalReportData> getTotalReport(
89+
@AuthUser User user,
90+
@RequestParam Integer round) {
91+
92+
TotalReportData dto = openAIService.buildTotalReport(user.getId(), round);
93+
return ApiResponseDto.onSuccess(dto);
94+
}
95+
8196
}

src/main/java/com/my_medi/api/report/mapper/ReportConverter.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,21 @@
88
import com.my_medi.api.report.dto.*;
99
import com.my_medi.common.util.BirthDateUtil;
1010

11+
import com.my_medi.common.util.EnumConvertUtil;
1112
import com.my_medi.domain.healthCheckup.entity.HealthCheckup;
1213
import com.my_medi.domain.member.entity.Gender;
1314
import com.my_medi.domain.report.entity.*;
15+
import com.my_medi.domain.report.enums.BloodPressureStatus;
16+
import com.my_medi.domain.report.enums.ImagingTestStatus;
17+
import com.my_medi.domain.report.enums.UrineTestStatus;
18+
import com.my_medi.domain.report.enums.interview.LifestyleHabitsStatus;
19+
import com.my_medi.domain.report.enums.interview.PositiveNegativeStatus;
20+
import com.my_medi.infra.gpt.dto.HealthReportData;
1421
import lombok.extern.slf4j.Slf4j;
1522

23+
import java.util.HashSet;
1624
import java.util.List;
25+
import java.util.Set;
1726
import java.util.function.Function;
1827

1928
import static com.my_medi.api.healthCheckup.mapper.HealthCheckupMapper.*;
@@ -354,5 +363,61 @@ public static ReportResultDto toReportResultDto(List<HealthCheckup> healthChecku
354363
.build();
355364
}
356365

366+
public static WriteReportRequestDto toWriteReportRequestDto(HealthReportData healthReportData) {
367+
return WriteReportRequestDto.builder()
368+
.hospitalName(null)
369+
.checkupDate(healthReportData.getCheckupDate())
370+
.measurementDto(
371+
MeasurementDto.builder()
372+
.height(healthReportData.getMeasurement().getHeight())
373+
.weight(healthReportData.getMeasurement().getWeight())
374+
.bmi(healthReportData.getMeasurement().getBmi())
375+
.waist(healthReportData.getMeasurement().getWaistCircumference())
376+
.vision(healthReportData.getMeasurement().getVision())
377+
.build()
378+
)
379+
.bloodPressureDto(
380+
BloodPressureDto.builder()
381+
.systolic(healthReportData.getBloodPressure().getSystolic())
382+
.diastolic(healthReportData.getBloodPressure().getDiastolic())
383+
.bloodPressureStatus(EnumConvertUtil.convertOrNull(BloodPressureStatus.class, healthReportData.getBloodPressure().getStatus()))
384+
.build()
385+
)
386+
.bloodTestDto(
387+
BloodTestDto.builder()
388+
//간장질환
389+
.alt(healthReportData.getBloodTest().getAlt())
390+
.ast(healthReportData.getBloodTest().getAst())
391+
// 이상지혈증
392+
.totalCholesterol(roundOrNull(healthReportData.getBloodTest().getTotalCholesterol()))
393+
.hdl(roundOrNull(healthReportData.getBloodTest().getHdlCholesterol()))
394+
.ldl(roundOrNull(healthReportData.getBloodTest().getLdlCholesterol()))
395+
.triglyceride(roundOrNull(healthReportData.getBloodTest().getTriglycerides()))
396+
//혈색소
397+
.hemoglobin(healthReportData.getBloodTest().getHemoglobin())
398+
//당뇨병
399+
.fastingGlucose(roundOrNull(healthReportData.getBloodTest().getGlucose()))
400+
//신장질환
401+
.creatinine(healthReportData.getBloodTest().getCreatinine())
402+
.egfr(roundOrNull(healthReportData.getBloodTest().getEgfr()))
403+
.build()
404+
)
405+
.urineTestDto(
406+
UrineTestDto.builder()
407+
.urineTestStatus(EnumConvertUtil.convertOrNull(UrineTestStatus.class, healthReportData.getUrineTest().getProtein()))
408+
.build()
409+
)
410+
.imagingTestDto(
411+
ImagingTestDto.builder()
412+
.imagingTestStatus(EnumConvertUtil.convertOrNull(ImagingTestStatus.class, healthReportData.getImagingTest().getChestXray()))
413+
.build()
414+
)
415+
.build();
416+
}
417+
418+
private static Integer roundOrNull(Double value) {
419+
return value != null ? (int) Math.round(value) : null;
420+
}
421+
357422

358423
}

src/main/java/com/my_medi/common/consts/Prompt.java

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,30 +42,18 @@ public class Prompt {
4242
"gammaGtp": "감마지티피(IU/L, 숫자만)"
4343
},
4444
"urineTest": {
45-
"protein": "요단백",
46-
"glucose": "요당"
45+
"protein": "정상 | 경계 | 단백뇨 의심",
4746
},
4847
"imagingTest": {
49-
"chestXray": "흉부촬영 결과",
50-
"pastMedicalHistory": "과거병력",
51-
"currentMedication": "복용약물"
52-
},
53-
"interview": {
54-
"smoking": "흡연상태",
55-
"drinking": "음주상태",
56-
"exercise": "운동상태",
57-
"familyHistory": "가족력"
58-
},
59-
"additionalTest": {
60-
"hepatitisB": "B형간염",
61-
"depression": "우울증",
62-
"cognitiveFunction": "인지기능장애",
63-
"osteoporosis": "골밀도검사",
64-
"additionalNotes": "기타 추가 검사"
48+
"chestXray": "정상 | 비활동성 폐결핵 | 질환 의심 | 기타",
6549
}
6650
}
6751
68-
숫자 값은 반드시 숫자만 추출하고, 없는 정보는 null로 표시해주세요.
52+
규칙:
53+
- 'urineTest.protein'과 'imagingTest.chestXray'는 **반드시 문자열(String)** 로 반환합니다.
54+
- 각각 위에 제시된 옵션 문자열 중 하나만 반환하세요. (예: "정상")
55+
- 해당 정보를 확정할 수 없으면 null을 사용하세요.
56+
- 숫자 값은 반드시 숫자만 추출하고, 없는 정보는 null로 표시해주세요.
6957
""";
7058

7159
public static String HEALTH_TERM_PROMPT = """
@@ -91,4 +79,62 @@ public class Prompt {
9179
용어: %s
9280
""";
9381

82+
83+
public static final String TOTAL_REPORT_PROMPT = """
84+
당신은 건강검진 리포트를 한국어로 요약합니다.
85+
아래의 [입력 데이터]만 근거로, 다음 JSON 스키마에 **정확히** 맞춰 응답하세요.
86+
87+
- 반드시 [입력 데이터]의 사실만 사용하세요.
88+
- 예시 문구/상투어를 재사용하지 마세요.
89+
- 근거가 없으면 그 항목은 빈 배열([]) 또는 null로 남기세요.
90+
- JSON 외의 설명 금지.
91+
92+
{
93+
"majorAbnormalItems": ["문장", ... 최대 6개],
94+
"lifestyleAdvice": ["식사", "운동", "카페인/음주 습관", "수면" 각 1문장],
95+
"top3Risks": [
96+
{ "rank": 1, "title": "위험명", "indicators": "관련 지표", "description": "설명" },
97+
{ "rank": 2, "title": "위험명", "indicators": "관련 지표", "description": "설명" },
98+
{ "rank": 3, "title": "위험명", "indicators": "관련 지표", "description": "설명" }
99+
],
100+
"summary": "4문장으로 요약",
101+
"recommendedActions": ["행동 권장 문구1","행동 권장 문구2","행동 권장 문구3"]
102+
}
103+
스키마:
104+
{
105+
"majorAbnormalItems": [
106+
"공복혈당 : 107 mg/dL → 정상기준 (70~99)을 초과해 당뇨 전단계로 분류됩니다.",
107+
"LDL콜레스테롤 : 153 mg/dL → 경계 수치로, 심혈관 질환 위험이 조금씩 생기기 시작한 상태에요.",
108+
"중성지방 : 186 mg/dL → 높은 편으로, 특히 식습관과 관련이 많아요.",
109+
"ALT (간 수치) : 42 IU/L → 간 기능 이상을 의심할 수 있어요.",
110+
"BMI : 26.1 → 과체중 범위로, 체중관리가 필요할 수 있어요.",
111+
"혈압 : 132/87 mmHg → 고혈압 전단계입니다."
112+
],
113+
"lifestyleAdvice": [
114+
"야식이나 당류 위주의 식사가 잡다면, 공복혈당과 중성지방 수치를 올릴 수 있어요. 하루 1끼 채소 중심 식단으로 바꿔보는 건 어떨까요?",
115+
"현재 활동량이 적다면, 걷기부터라도 시작해보세요. 주 3회 이상, 30분 정도의 유산소 운동이 간 수치와 혈당 조절에 큰 도움이 돼요.",
116+
"음주나 잦은 카페인 섭취는 간 수치와 체중 증가에 영향을 줄 수 있어요. 음주는 주 1회 이하, 하루 1~2잔 이내로 조절하는 걸 추천드려요.",
117+
"만성피로를 느끼시나요? 수면 시간이 부족하면 혈압과 혈당 모두 영향을 받을 수 있어요. 매일 6~7시간 이상의 숙면이 중요해요."
118+
],
119+
"top3Risks": [
120+
{ "rank": 1, "title": "대사증후군", "indicators": "(공복혈당 ↑, 중성지방 ↑, HDL ↓, 복부비만(BMI 기준))", "description": "여러 지표가 함께 경고를 보내고 있어요. 특히 HDL(좋은 콜레스테롤) 수치가 낮은 것은 체내 지방 대사에 문제가 있다는 신호입니다. 지금부터 식단 개선과 유산소 운동이 필요해요!" },
121+
{ "rank": 2, "title": "알코올성 지방간", "indicators": "(AST/ALT ↑, 감마-GTP ↑)", "description": "간 수치가 전반적으로 높고, 술을 거의 마시지 않는다면 지방간 가능성을 의심할 수 있어요. 
기름진 음식, 야식 등을 줄이고 간 해독을 위한 영양관리도 필요해요." },
122+
{ "rank": 3, "title": "만성 신장 기능 저하 위험", "indicators": "(크레아티닌 ↑, eGFR ↓)", "description": "현재로선 심각한 단계는 아니지만, 사구체여과율(eGFR) 이 살짝 낮아요. 평소 수분 섭취량과 단백질 섭취량을 점검하고, 앞으로도 꾸준한 모니터링이 필요해요." }
123+
],
124+
"summary": "전체적으로 보면, 지금은 건강을 다시 한 번 점검해야 할 시기인 것 같아요. 아직은 대부분 경고 단계이지만 관리로 충분히 회복 가능한 상태입니다.
특히 혈당, 중성지방, 간 수치가 동시에 높게 나온 경우는 생활 습관과 식단의 영향을 강하게 받고 있다는 신호예요!! 지금부터 차근차근 바꿔나간다면 건강을 충분히 회복할 수 있어요!",
125+
"recommendedActions": [
126+
"영양사와 상담을 통해 식단 가이드를 받아보시고,",
127+
"운동처방사와 함께 운동 루틴을 짜보는 것도 좋아요.",
128+
"향후 6개월 뒤 재검진을 통해 변화된 수치를 체크해보는 걸 추천드려요!"
129+
]
130+
}
131+
132+
133+
[입력 데이터]
134+
```json
135+
%s
136+
""";
137+
138+
139+
94140
}

src/main/java/com/my_medi/common/util/EnumConvertUtil.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ public static <E extends Enum<E> & KeyedEnum> E convert(Class<E> enumClass, Stri
1515
.orElseThrow(() -> new RuntimeException("Key not found in " + enumClass.getSimpleName()));
1616
}
1717

18+
public static <E extends Enum<E> & KeyedEnum> E convertOrNull(Class<E> enumClass, String key) {
19+
return Arrays.stream(enumClass.getEnumConstants())
20+
.filter(e -> e.getKey().equals(key))
21+
.findFirst()
22+
.orElse(null);
23+
}
24+
1825
public static Specialty getRandomSpecialty() {
1926
Specialty[] values = Specialty.values();
2027
int randomIndex = ThreadLocalRandom.current().nextInt(values.length);

src/main/java/com/my_medi/common/util/ParseUtil.java

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -94,34 +94,12 @@ public static HealthReportData.BloodTestData parseBloodTest(JsonNode node) {
9494
public static HealthReportData.UrineTestData parseUrineTest(JsonNode node) {
9595
return HealthReportData.UrineTestData.builder()
9696
.protein(parseString(node.path("protein")))
97-
.glucose(parseString(node.path("glucose")))
9897
.build();
9998
}
10099

101100
public static HealthReportData.ImagingTestData parseImagingTest(JsonNode node) {
102101
return HealthReportData.ImagingTestData.builder()
103102
.chestXray(parseString(node.path("chestXray")))
104-
.pastMedicalHistory(parseString(node.path("pastMedicalHistory")))
105-
.currentMedication(parseString(node.path("currentMedication")))
106-
.build();
107-
}
108-
109-
public static HealthReportData.InterviewData parseInterview(JsonNode node) {
110-
return HealthReportData.InterviewData.builder()
111-
.smoking(parseString(node.path("smoking")))
112-
.drinking(parseString(node.path("drinking")))
113-
.exercise(parseString(node.path("exercise")))
114-
.familyHistory(parseString(node.path("familyHistory")))
115-
.build();
116-
}
117-
118-
public static HealthReportData.AdditionalTestData parseAdditionalTest(JsonNode node) {
119-
return HealthReportData.AdditionalTestData.builder()
120-
.hepatitisB(parseString(node.path("hepatitisB")))
121-
.depression(parseString(node.path("depression")))
122-
.cognitiveFunction(parseString(node.path("cognitiveFunction")))
123-
.osteoporosis(parseString(node.path("osteoporosis")))
124-
.additionalNotes(parseString(node.path("additionalNotes")))
125103
.build();
126104
}
127105

src/main/java/com/my_medi/domain/report/enums/BloodPressureStatus.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.my_medi.domain.report.enums;
22

3+
import com.my_medi.common.interfaces.KeyedEnum;
34
import lombok.Getter;
45
import lombok.RequiredArgsConstructor;
56

67
@Getter
78
@RequiredArgsConstructor
8-
public enum BloodPressureStatus {
9+
public enum BloodPressureStatus implements KeyedEnum {
910
NORMAL("정상"),
1011
PREHYPERTENSION("고혈압 전단계"),
1112
HYPERTENSION("고혈압 의심"),

src/main/java/com/my_medi/domain/report/enums/ImagingTestStatus.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.my_medi.domain.report.enums;
22

3+
import com.my_medi.common.interfaces.KeyedEnum;
34
import lombok.Getter;
45
import lombok.RequiredArgsConstructor;
56

67
@Getter
78
@RequiredArgsConstructor
8-
public enum ImagingTestStatus {
9+
public enum ImagingTestStatus implements KeyedEnum {
910
NORMAL("정상"),
1011
INACTIVE_PULMONARY_TUBERCULOSIS("비활동성 폐결핵"),
1112
DISEASE("질환 의심"),

src/main/java/com/my_medi/domain/report/enums/UrineTestStatus.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.my_medi.domain.report.enums;
22

3+
import com.my_medi.common.interfaces.KeyedEnum;
34
import lombok.Getter;
45
import lombok.RequiredArgsConstructor;
56

67
@Getter
78
@RequiredArgsConstructor
8-
public enum UrineTestStatus {
9+
public enum UrineTestStatus implements KeyedEnum {
910
NORMAL("정상"),
1011
BORDERLINE("경계"),
1112
PROTEINURIA("단백뇨 의심");

src/main/java/com/my_medi/domain/report/service/ReportQueryService.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.my_medi.domain.report.service;
22

3-
import com.my_medi.api.healthCheckup.dto.ComparingHealthCheckupResponseDto;
4-
import com.my_medi.api.report.dto.ReportResponseDto;
53
import com.my_medi.api.report.dto.ReportResponseDto.ReportResultDto;
64
import com.my_medi.domain.report.entity.Report;
75
import com.my_medi.domain.user.entity.User;
@@ -14,4 +12,5 @@ public interface ReportQueryService {
1412
Report getLatestReportByUserId(Long userId);
1513

1614
long getReportCountByUser(User user);
15+
1716
}

src/main/java/com/my_medi/infra/gpt/dto/HealthReportData.java

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ public class HealthReportData {
1515
private BloodTestData bloodTest;
1616
private UrineTestData urineTest;
1717
private ImagingTestData imagingTest;
18-
private InterviewData interview;
19-
private AdditionalTestData additionalTest;
2018

2119
@Data
2220
@Builder
@@ -57,33 +55,11 @@ public static class BloodTestData {
5755
@Builder
5856
public static class UrineTestData {
5957
private String protein;
60-
private String glucose;
6158
}
6259

6360
@Data
6461
@Builder
6562
public static class ImagingTestData {
6663
private String chestXray;
67-
private String pastMedicalHistory;
68-
private String currentMedication;
69-
}
70-
71-
@Data
72-
@Builder
73-
public static class InterviewData {
74-
private String smoking;
75-
private String drinking;
76-
private String exercise;
77-
private String familyHistory;
78-
}
79-
80-
@Data
81-
@Builder
82-
public static class AdditionalTestData {
83-
private String hepatitisB;
84-
private String depression;
85-
private String cognitiveFunction;
86-
private String osteoporosis;
87-
private String additionalNotes;
8864
}
8965
}

0 commit comments

Comments
 (0)