From a54c3cdfe7411550c99e734f285365eca98e6a0b Mon Sep 17 00:00:00 2001 From: leejunyeong <140596682+jjuny0326@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:37:09 +0900 Subject: [PATCH] =?UTF-8?q?feature(be):=20=EC=97=B0=EB=A6=BD=C2=B7?= =?UTF-8?q?=EB=8B=A4=EC=84=B8=EB=8C=80=20=EC=A0=84=EC=84=B8=EA=B0=80?= =?UTF-8?q?=EC=9C=A8=20=EC=A3=BC=EC=9D=98=20=EA=B8=B0=EC=A4=80=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/service/RiskEvaluationService.java | 16 ++++-- .../service/RiskEvaluationBoundaryTest.java | 54 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/secondzip/backend/report/service/RiskEvaluationService.java b/backend/src/main/java/com/secondzip/backend/report/service/RiskEvaluationService.java index ad89048..d84a26e 100644 --- a/backend/src/main/java/com/secondzip/backend/report/service/RiskEvaluationService.java +++ b/backend/src/main/java/com/secondzip/backend/report/service/RiskEvaluationService.java @@ -28,6 +28,7 @@ public class RiskEvaluationService { // 전세가율 기준 private static final double JEONSE_RATIO_CAUTION = 0.70; + private static final double MULTI_HOUSING_JEONSE_RATIO_CAUTION = 0.60; private static final double JEONSE_RATIO_DANGER = 0.80; // 선순위채권 부담 기준 (주택가격 90% × 60%) private static final double PRIORITY_DEBT_LIMIT_RATIO = 0.54; @@ -48,6 +49,8 @@ public class RiskEvaluationService { Arrays.asList("SINGLE_FAMILY", "MULTI_FAMILY"); private static final List COLLECTIVE_REGISTRY_TYPES = Arrays.asList("APARTMENT", "MULTI_HOUSEHOLD", "OFFICETEL"); + private static final Set LOWER_JEONSE_RATIO_CAUTION_TYPES = + Set.of("MULTI_HOUSEHOLD"); private static final Set KNOWN_OWNER_TYPES = Set.of("INDIVIDUAL", "CORPORATION", "TRUST_COMPANY"); @@ -543,11 +546,14 @@ private FraudTypeResultDTO buildUnderwaterJeonse( * 실제로 보는 것: 매매 기준가 대비 전세보증금 비율. * * [판정 로직] - * - 80% 이상 -> DANGER / 70~80% -> CAUTION / 70% 미만 -> SAFE + * - 연립·다세대: 80% 이상 -> DANGER / 60~80% -> CAUTION / 60% 미만 -> SAFE + * - 다가구: 다른 세입자의 선순위보증금을 알 수 없어 CAUTION / UNVERIFIED + * - 그 외 유형: 80% 이상 -> DANGER / 70~80% -> CAUTION / 70% 미만 -> SAFE * - 가격 데이터 누락 -> CAUTION / UNVERIFIED */ private JudgementDTO judgeHighJeonseRatio(BuildingData building, PriceData price, Long deposit) { - // 다가구 실거래가는 건물 전체 가격이어서 한 세대 보증금과 직접 나눌 수 없다. + // 다가구는 분모가 건물 전체 가격이지만 분자는 특정 세대의 보증금이다. + // 다른 세입자의 선순위보증금을 모르는 상태에서 안전을 확정하지 않는다. if (building != null && "MULTI_FAMILY".equals(building.getBuildingType())) { return JudgementDTO.unverified(); } @@ -557,8 +563,12 @@ private JudgementDTO judgeHighJeonseRatio(BuildingData building, PriceData price } double ratio = (double) deposit / basePrice; + String buildingType = building != null ? building.getBuildingType() : null; + double cautionThreshold = LOWER_JEONSE_RATIO_CAUTION_TYPES.contains(buildingType) + ? MULTI_HOUSING_JEONSE_RATIO_CAUTION + : JEONSE_RATIO_CAUTION; if (ratio >= JEONSE_RATIO_DANGER) return JudgementDTO.verified(RiskLevel.DANGER); - if (ratio >= JEONSE_RATIO_CAUTION) return JudgementDTO.verified(RiskLevel.CAUTION); + if (ratio >= cautionThreshold) return JudgementDTO.verified(RiskLevel.CAUTION); return JudgementDTO.verified(RiskLevel.SAFE); } diff --git a/backend/src/test/java/com/secondzip/backend/report/service/RiskEvaluationBoundaryTest.java b/backend/src/test/java/com/secondzip/backend/report/service/RiskEvaluationBoundaryTest.java index e1b825c..271665e 100644 --- a/backend/src/test/java/com/secondzip/backend/report/service/RiskEvaluationBoundaryTest.java +++ b/backend/src/test/java/com/secondzip/backend/report/service/RiskEvaluationBoundaryTest.java @@ -117,6 +117,60 @@ static Stream jeonseRatioBoundaries() { ); } + @ParameterizedTest(name = "deposit={0} -> {1}") + @MethodSource("multiHouseholdJeonseRatioBoundaries") + @DisplayName("연립·다세대는 전세가율 60%와 80% 경계값으로 판정한다") + void appliesMultiHouseholdJeonseRatioBoundaries(long deposit, RiskLevel expected) { + BuildingData building = baselineBuilding(); + building.setBuildingType("MULTI_HOUSEHOLD"); + building.setBuildingUse("다세대주택"); + + DetailResultDTO result = detail( + evaluate( + baselineRegistry(), + building, + price(1_000_000_000L), + deposit, + "서울" + ), + DetailType.HIGH_JEONSE_RATIO + ); + + assertEquals(expected, result.getRiskLevel()); + assertEquals(DataStatus.VERIFIED, result.getDataStatus()); + } + + static Stream multiHouseholdJeonseRatioBoundaries() { + return Stream.of( + Arguments.of(599_999_999L, RiskLevel.SAFE), + Arguments.of(600_000_000L, RiskLevel.CAUTION), + Arguments.of(799_999_999L, RiskLevel.CAUTION), + Arguments.of(800_000_000L, RiskLevel.DANGER) + ); + } + + @Test + @DisplayName("다가구는 건물 전체 가격과 한 세대 보증금을 직접 비교하지 않는다") + void multiFamilyJeonseRatioIsUnverified() { + BuildingData building = baselineBuilding(); + building.setBuildingType("MULTI_FAMILY"); + building.setBuildingUse("다가구주택"); + + DetailResultDTO result = detail( + evaluate( + baselineRegistry(), + building, + price(1_000_000_000L), + 100_000_000L, + "서울" + ), + DetailType.HIGH_JEONSE_RATIO + ); + + assertEquals(RiskLevel.CAUTION, result.getRiskLevel()); + assertEquals(DataStatus.UNVERIFIED, result.getDataStatus()); + } + @ParameterizedTest(name = "mortgage={0} -> {1}") @CsvSource({ "0, SAFE",