From fe2f3ee5cbef7755c7eb9b28f0cb711eb8fd85d2 Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:27:29 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20OcrParseResult,=20OcrResultResponse?= =?UTF-8?q?=EC=97=90=20=EC=B2=98=EB=B0=A9=EC=9D=BC=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../piuda/callcare/domain/ocrresult/dto/OcrParseResult.java | 4 +++- .../domain/ocrresult/dto/response/OcrResultResponse.java | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/piuda/callcare/domain/ocrresult/dto/OcrParseResult.java b/src/main/java/com/piuda/callcare/domain/ocrresult/dto/OcrParseResult.java index ac9150f..3a84a58 100644 --- a/src/main/java/com/piuda/callcare/domain/ocrresult/dto/OcrParseResult.java +++ b/src/main/java/com/piuda/callcare/domain/ocrresult/dto/OcrParseResult.java @@ -1,8 +1,10 @@ package com.piuda.callcare.domain.ocrresult.dto; +import java.time.LocalDate; import java.util.List; public record OcrParseResult( String rawText, - List parsedDrugs + List parsedDrugs, + LocalDate prescriptionDate // 처방일 파싱 성공 시 채워짐, 실패 시 null ) {} diff --git a/src/main/java/com/piuda/callcare/domain/ocrresult/dto/response/OcrResultResponse.java b/src/main/java/com/piuda/callcare/domain/ocrresult/dto/response/OcrResultResponse.java index 9922ded..57c399d 100644 --- a/src/main/java/com/piuda/callcare/domain/ocrresult/dto/response/OcrResultResponse.java +++ b/src/main/java/com/piuda/callcare/domain/ocrresult/dto/response/OcrResultResponse.java @@ -3,6 +3,7 @@ import com.piuda.callcare.domain.ocrresult.dto.ParsedOcrData; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; import java.util.List; @Schema(description = "OCR 분석 결과") @@ -15,5 +16,8 @@ public record OcrResultResponse( String rawText, @Schema(description = "파싱된 약 목록 — 단일 약이면 1개, 표 처방전이면 여러 개") - List parsedDrugs + List parsedDrugs, + + @Schema(description = "처방일 — 처방전에서 파싱 성공 시 채워짐, 실패 시 null (약 등록 시 prescriptionDate 기본값으로 사용)") + LocalDate prescriptionDate ) {} From 9214ad33f3e1cec92f1b1a2e799eb033af766e7e Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:29:11 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20OcrParser=EC=97=90=20=EC=B2=98?= =?UTF-8?q?=EB=B0=A9=EC=9D=BC=20=EC=B6=94=EC=B6=9C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 처방일 키워드("처방일", "처방일자", "처방전발행일") 우선 파싱 없으면 조제일("조제일", "조제일자") fallback YYYY.MM.DD / YYYY-MM-DD / YYYY년MM월DD일 포맷 지원 Co-Authored-By: Claude Sonnet 4.6 --- .../domain/ocrresult/service/OcrParser.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/piuda/callcare/domain/ocrresult/service/OcrParser.java b/src/main/java/com/piuda/callcare/domain/ocrresult/service/OcrParser.java index 0edd1e7..c690fd4 100644 --- a/src/main/java/com/piuda/callcare/domain/ocrresult/service/OcrParser.java +++ b/src/main/java/com/piuda/callcare/domain/ocrresult/service/OcrParser.java @@ -6,6 +6,7 @@ import com.piuda.callcare.domain.ocrresult.enums.OcrType; import org.springframework.stereotype.Component; +import java.time.LocalDate; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -64,6 +65,14 @@ public class OcrParser { "1회\\s*투약량\\s*\\d|1일\\s*투여\\s*횟수\\s*\\d|총\\s*투약\\s*일수\\s*\\d" ); + // 처방일 우선, 없으면 조제일 fallback + private static final Pattern PRESCRIPTION_DATE_PATTERN = Pattern.compile( + "(?:처방일|처방일자|처방전발행일)\\s*[:\\-]?\\s*(\\d{4})\\s*[.년\\-]\\s*(\\d{1,2})\\s*[.월\\-]?\\s*(\\d{1,2})" + ); + private static final Pattern DISPENSE_DATE_PATTERN = Pattern.compile( + "(?:조제일|조제일자)\\s*[:\\-]?\\s*(\\d{4})\\s*[.년\\-]\\s*(\\d{1,2})\\s*[.월\\-]?\\s*(\\d{1,2})" + ); + private static final List DRUG_NAME_KEYWORDS = List.of("명칭", "약품명", "의약품"); private static final List DOSAGE_KEYWORDS = List.of("투약량", "복용량", "1회"); private static final List TIMES_KEYWORDS = List.of("투여횟수", "복용횟수", "횟수"); @@ -86,7 +95,7 @@ public OcrParseResult parse(List fields, OcrType ocrT parsedDrugs = List.of(parseByRegex(rawText)); } - return new OcrParseResult(rawText, parsedDrugs); + return new OcrParseResult(rawText, parsedDrugs, extractPrescriptionDate(rawText)); } // ── rawText 조립 ────────────────────────────────────────────────────────── @@ -463,6 +472,27 @@ private Integer extractTotalDays(String text) { return null; } + // ── 처방일 추출 ─────────────────────────────────────────────────────────── + + // 처방일 우선 파싱, 없으면 조제일 fallback + private LocalDate extractPrescriptionDate(String rawText) { + LocalDate date = tryExtractDate(PRESCRIPTION_DATE_PATTERN, rawText); + return date != null ? date : tryExtractDate(DISPENSE_DATE_PATTERN, rawText); + } + + private LocalDate tryExtractDate(Pattern pattern, String text) { + Matcher m = pattern.matcher(text); + if (!m.find()) return null; + try { + int year = Integer.parseInt(m.group(1)); + int month = Integer.parseInt(m.group(2).trim()); + int day = Integer.parseInt(m.group(3).trim()); + return LocalDate.of(year, month, day); + } catch (Exception e) { + return null; + } + } + // ── 좌표 유틸 ───────────────────────────────────────────────────────────── private double centerX(NaverOcrApiResponse.Field field) { From 9ecc2e0333b4bd70e7bd635ec4c436e65996f5a9 Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:29:16 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20OCR=20=EC=B2=98=EB=B0=A9=EC=9D=BC?= =?UTF-8?q?=20=ED=8C=8C=EC=8B=B1=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EA=B9=8C=EC=A7=80=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OcrResultConverter — prescriptionDate 파라미터 추가 OcrCommandService — parseResult.prescriptionDate() 전달 Co-Authored-By: Claude Sonnet 4.6 --- .../domain/ocrresult/converter/OcrResultConverter.java | 8 +++++--- .../ocrresult/service/command/OcrCommandService.java | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/piuda/callcare/domain/ocrresult/converter/OcrResultConverter.java b/src/main/java/com/piuda/callcare/domain/ocrresult/converter/OcrResultConverter.java index 2931c49..b2f53a8 100644 --- a/src/main/java/com/piuda/callcare/domain/ocrresult/converter/OcrResultConverter.java +++ b/src/main/java/com/piuda/callcare/domain/ocrresult/converter/OcrResultConverter.java @@ -5,17 +5,19 @@ import com.piuda.callcare.domain.ocrresult.entity.OcrResult; import org.springframework.stereotype.Component; +import java.time.LocalDate; import java.util.List; @Component public class OcrResultConverter { - // OcrResult + 파싱된 약 목록 → OcrResultResponse - public OcrResultResponse toResponse(OcrResult ocrResult, List parsedDrugs) { + // OcrResult + 파싱된 약 목록 + 처방일 → OcrResultResponse + public OcrResultResponse toResponse(OcrResult ocrResult, List parsedDrugs, LocalDate prescriptionDate) { return new OcrResultResponse( ocrResult.getId(), ocrResult.getRawText(), - parsedDrugs + parsedDrugs, + prescriptionDate ); } } diff --git a/src/main/java/com/piuda/callcare/domain/ocrresult/service/command/OcrCommandService.java b/src/main/java/com/piuda/callcare/domain/ocrresult/service/command/OcrCommandService.java index 5fd6211..06b3ca0 100644 --- a/src/main/java/com/piuda/callcare/domain/ocrresult/service/command/OcrCommandService.java +++ b/src/main/java/com/piuda/callcare/domain/ocrresult/service/command/OcrCommandService.java @@ -60,6 +60,6 @@ public OcrResultResponse processOcr(Long userId, Long seniorId, MultipartFile im log.info("OCR 처리 완료 - ocrResultId: {}, ocrType: {}, 파싱된 약 수: {}", saved.getId(), ocrType, parseResult.parsedDrugs().size()); - return ocrResultConverter.toResponse(saved, parseResult.parsedDrugs()); + return ocrResultConverter.toResponse(saved, parseResult.parsedDrugs(), parseResult.prescriptionDate()); } } From 08f95068a74407bbbffc83cc4563a42eb6226c03 Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:29:22 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20MedicationNoteGroupResponse=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=ED=82=A4=20=ED=95=84=EB=93=9C=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit startDate → prescriptionDate (처방일 파싱 성공 시 처방일, 실패 시 startDate fallback) Co-Authored-By: Claude Sonnet 4.6 --- .../dto/response/MedicationNoteGroupResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteGroupResponse.java b/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteGroupResponse.java index 37fb4b5..2079c9c 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteGroupResponse.java +++ b/src/main/java/com/piuda/callcare/domain/medication/dto/response/MedicationNoteGroupResponse.java @@ -5,11 +5,11 @@ import java.time.LocalDate; import java.util.List; -@Schema(description = "약물노트 그룹 카드 — 복용 시작일 + 병원 기준 묶음") +@Schema(description = "약물노트 그룹 카드 — 처방일 + 병원 기준 묶음") public record MedicationNoteGroupResponse( - @Schema(description = "복용 시작일 (그룹 키)") - LocalDate startDate, + @Schema(description = "처방일 (파싱 성공 시 처방일, 실패 시 복용 시작일 fallback — 그룹 키)") + LocalDate prescriptionDate, @Schema(description = "병원명 (없으면 null)") String hospitalName, From 74a850f0d7b0905c98e68385707cc6f41fe6d1f8 Mon Sep 17 00:00:00 2001 From: kangcheolung Date: Thu, 9 Jul 2026 23:29:27 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20=EC=95=BD=EB=AC=BC=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20=EA=B7=B8=EB=A3=B9=ED=99=94=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=EC=9D=84=20=EC=B2=98=EB=B0=A9=EC=9D=BC=20=EC=9A=B0=EC=84=A0?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prescriptionDate가 있으면 우선 사용, 없으면 startDate fallback Co-Authored-By: Claude Sonnet 4.6 --- .../medication/service/query/MedicationQueryService.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java b/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java index 4a96a78..d4802e8 100644 --- a/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java +++ b/src/main/java/com/piuda/callcare/domain/medication/service/query/MedicationQueryService.java @@ -63,16 +63,18 @@ public List searchNotes(Long userId, Long seniorId, private List toNoteGroupResponses(List medications) { Map> grouped = new LinkedHashMap<>(); for (Medication m : medications) { - String key = m.getStartDate() + "|" + m.getHospitalName(); + LocalDate groupDate = m.getPrescriptionDate() != null ? m.getPrescriptionDate() : m.getStartDate(); + String key = groupDate + "|" + m.getHospitalName(); grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(m); } return grouped.values().stream() .map(group -> { Medication first = group.get(0); + LocalDate groupDate = first.getPrescriptionDate() != null ? first.getPrescriptionDate() : first.getStartDate(); List items = group.stream() .map(medicationConverter::toNoteItemResponse) .toList(); - return new MedicationNoteGroupResponse(first.getStartDate(), first.getHospitalName(), items); + return new MedicationNoteGroupResponse(groupDate, first.getHospitalName(), items); }) .toList(); }