Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.pinHouse.server.core.response.response.pageable;

import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "무한스크롤 요청 DTO")
@Schema(name = "[요청][페이징] 무한 스크롤 페이징", description = "무한 스크롤 방식의 페이징 처리를 위한 요청 DTO")
public record SliceRequest(

@Schema(description = "페이지 시작", example = "1")
@Schema(description = "페이지 번호 (0부터 시작)", example = "0", defaultValue = "1")
int page,
Comment on lines +8 to 9
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

페이지 번호 기본값 불일치 수정 필요

설명과 예시는 0부터 시작한다고 명시되어 있지만, defaultValue는 "1"로 설정되어 있습니다. 이는 API 문서를 혼란스럽게 만들 수 있습니다.

🔎 제안하는 수정
-        @Schema(description = "페이지 번호 (0부터 시작)", example = "0", defaultValue = "1")
+        @Schema(description = "페이지 번호 (0부터 시작)", example = "0", defaultValue = "0")
         int page,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Schema(description = "페이지 번호 (0부터 시작)", example = "0", defaultValue = "1")
int page,
@Schema(description = "페이지 번호 (0부터 시작)", example = "0", defaultValue = "0")
int page,
🤖 Prompt for AI Agents
In
src/main/java/com/pinHouse/server/core/response/response/pageable/SliceRequest.java
around lines 8-9, the @Schema for the page field documents "페이지 번호 (0부터 시작)" and
example "0" but sets defaultValue = "1"; update the annotation so the
defaultValue matches the documented zero-based indexing (e.g., change
defaultValue to "0") or adjust the description/example to reflect a 1-based
default—pick the correct convention for the API and make the annotation
consistent.


@Schema(description = "조회할 개수", example = "10")
@Schema(description = "한 페이지에 조회할 데이터 개수", example = "10", defaultValue = "10")
int offSet) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.pinHouse.server.core.util;

import com.pinHouse.server.platform.Location;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
* 거리 계산 유틸리티 클래스
* Haversine 공식을 사용하여 두 지점 간의 거리를 계산합니다.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class DistanceCalculator {

/** 지구 반지름 (km) */
private static final double EARTH_RADIUS_KM = 6371.0;

/**
* 두 지점 간 거리(km) 계산 (Haversine 공식)
*
* @param location1 첫 번째 위치 (위도, 경도)
* @param location2 두 번째 위치 (위도, 경도)
* @return 두 지점 간의 거리 (km)
*/
public static double calculateDistanceKm(Location location1, Location location2) {
if (location1 == null || location2 == null) {
throw new IllegalArgumentException("Location cannot be null");
}

double lat1 = location1.getLatitude();
double lon1 = location1.getLongitude();
double lat2 = location2.getLatitude();
double lon2 = location2.getLongitude();

return calculateDistanceKm(lat1, lon1, lat2, lon2);
}

/**
* 두 지점 간 거리(km) 계산 (Haversine 공식)
*
* @param lat1 첫 번째 지점의 위도
* @param lon1 첫 번째 지점의 경도
* @param lat2 두 번째 지점의 위도
* @param lon2 두 번째 지점의 경도
* @return 두 지점 간의 거리 (km)
*/
public static double calculateDistanceKm(double lat1, double lon1, double lat2, double lon2) {
double dLat = Math.toRadians(lat2 - lat1);
double dLon = Math.toRadians(lon2 - lon1);
double radLat1 = Math.toRadians(lat1);
double radLat2 = Math.toRadians(lat2);

double h = Math.pow(Math.sin(dLat / 2), 2)
+ Math.pow(Math.sin(dLon / 2), 2) * Math.cos(radLat1) * Math.cos(radLat2);

return 2 * EARTH_RADIUS_KM * Math.asin(Math.sqrt(h));
}
}
56 changes: 56 additions & 0 deletions src/main/java/com/pinHouse/server/core/util/TimeFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.pinHouse.server.core.util;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
* 시간 포맷팅 유틸리티 클래스
* 분(minutes) 단위 시간을 "X시간 Y분" 형식의 문자열로 변환합니다.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class TimeFormatter {

/**
* 시간을 "X시간 Y분" 형식으로 포맷팅
*
* @param totalMinutes 총 시간(분)
* @return 포맷팅된 시간 문자열 (예: "1시간 30분", "45분")
* - 0 이하면 "0분" 반환
* - 60분 미만이면 "X분" 형식
* - 60분 이상이면 "X시간" 또는 "X시간 Y분" 형식
*/
public static String formatTime(int totalMinutes) {
if (totalMinutes <= 0) {
return "0분";
}

if (totalMinutes < 60) {
return totalMinutes + "분";
}

int hours = totalMinutes / 60;
int minutes = totalMinutes % 60;

if (minutes == 0) {
return hours + "시간";
}

return hours + "시간 " + minutes + "분";
}

/**
* 시간을 "X시간 Y분" 형식으로 포맷팅 (null 반환 버전)
*
* @param totalMinutes 총 시간(분)
* @return 포맷팅된 시간 문자열 (예: "1시간 30분", "45분")
* - 0 이하면 null 반환
* - 나머지는 formatTime()과 동일
*/
public static String formatTimeOrNull(int totalMinutes) {
if (totalMinutes <= 0) {
return null;
}

return formatTime(totalMinutes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package com.pinHouse.server.platform.diagnostic.diagnosis.application.dto;

import com.pinHouse.server.platform.diagnostic.diagnosis.domain.entity.Diagnosis;
import com.pinHouse.server.platform.diagnostic.rule.domain.entity.EvaluationContext;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.time.LocalDateTime;
import java.util.List;

/**
* 진단 결과 상세 조회 응답 DTO
* - 진단에 입력된 정보
* - 최종 진단 결과
*/
@Schema(name = "[응답][진단] 청약 진단 상세 결과", description = "청약 진단 입력 정보와 최종 결과를 함께 응답하는 DTO")
@Builder
public record DiagnosisDetailResponse(

// === 진단 메타 정보 ===
@Schema(description = "진단 ID", example = "101")
Long diagnosisId,

@Schema(description = "진단 일시", example = "2025-12-28T10:30:00")
LocalDateTime diagnosedAt,

// === 1. 성별/나이 ===
@Schema(description = "성별", example = "여자")
String gender,

@Schema(description = "나이 (만 나이)", example = "28")
int age,

// === 2. 소득 ===
@Schema(description = "가구 소득 수준", example = "소득 1분위")
String incomeLevel,

@Schema(description = "월 소득 (원)", example = "2000000")
int monthPay,

// === 3. 대학생 여부 ===
@Schema(description = "대학생 여부", example = "false")
boolean isCollegeStudent,

// === 4. 청약 저축 ===
@Schema(description = "청약통장 보유 여부", example = "true")
boolean hasSubscription,

@Schema(description = "청약통장 가입 기간", example = "2년 이상")
String subscriptionYears,

@Schema(description = "청약통장 납입 횟수", example = "24회 이상")
String subscriptionCount,

@Schema(description = "청약통장 예치금", example = "600만원 이하")
String subscriptionAmount,

// === 5. 결혼 여부 ===
@Schema(description = "결혼 여부", example = "true")
boolean isMarried,

@Schema(description = "결혼 기간 (년)", example = "5")
Integer marriedYears,

// === 6. 자녀 여부 ===
@Schema(description = "태아 수", example = "0")
int unbornChildren,

@Schema(description = "6세 이하 자녀 수", example = "1")
int under6Children,

@Schema(description = "7세 이상 미성년 자녀 수", example = "1")
int over7MinorChildren,

@Schema(description = "총 자녀 수", example = "2")
int totalChildren,

// === 7. 세대 정보 ===
@Schema(description = "세대주 여부", example = "true")
boolean isHouseholdHead,

@Schema(description = "1인 가구 여부", example = "false")
boolean isSingleHousehold,

@Schema(description = "전체 세대원 수", example = "4")
int householdSize,

// === 8. 주택 소유 여부 ===
@Schema(description = "주택 소유 상태", example = "우리집 가구원 모두 주택을 소유하고 있지 않아요")
String housingStatus,

@Schema(description = "무주택 기간 (년)", example = "5")
int housingYears,

// === 9. 자동차 소유 여부 ===
@Schema(description = "자동차 보유 여부", example = "false")
boolean ownsCar,

@Schema(description = "자동차 가격 (원)", example = "0")
long carValue,

// === 10. 총 자산 ===
@Schema(description = "부동산/토지 자산 (원)", example = "0")
long propertyAsset,

@Schema(description = "자동차 자산 (원)", example = "0")
long carAsset,

@Schema(description = "금융 자산 (원)", example = "50000000")
long financialAsset,

@Schema(description = "총 자산 (원)", example = "50000000")
long totalAssets,

// === 최종 진단 결과 ===
@Schema(description = "최종 자격 여부", example = "true")
boolean eligible,

@Schema(description = "진단 결과 메시지", example = "추천 임대주택이 있습니다")
String diagnosisResult,

@Schema(description = "추천 임대주택 후보 리스트", example = "[\"공공임대 : 장기\", \"민간임대 : 단기\"]")
List<String> recommended
) {

/// 정적 팩토리 메서드
public static DiagnosisDetailResponse from(EvaluationContext context) {

Diagnosis diagnosis = context.getDiagnosis();

// 추천 후보 계산
List<String> recommended = context.getCurrentCandidates().isEmpty() ?
List.of("해당 없음") :
context.getCurrentCandidates().stream()
.map((c -> c.noticeType().getValue() + " : " + c.supplyType().getValue()))
.toList();

// 총 자녀 수 계산
int totalChildren = diagnosis.getUnbornChildrenCount()
+ diagnosis.getUnder6ChildrenCount()
+ diagnosis.getOver7MinorChildrenCount();

return DiagnosisDetailResponse.builder()
// 메타 정보
.diagnosisId(diagnosis.getId())
.diagnosedAt(diagnosis.getCreatedAt())
// 1. 성별/나이
.gender(diagnosis.getGender() != null ? diagnosis.getGender().getValue() : "미입력")
.age(diagnosis.getAge())
// 2. 소득
.incomeLevel(diagnosis.getIncomeLevel() != null ? diagnosis.getIncomeLevel().getValue() : "미입력")
.monthPay(diagnosis.getMonthPay())
// 3. 대학생 여부
.isCollegeStudent(diagnosis.getEducationStatus() != null)
// 4. 청약 저축
.hasSubscription(diagnosis.isHasAccount())
.subscriptionYears(diagnosis.getAccountYears() != null ? diagnosis.getAccountYears().getDescription() : "미입력")
.subscriptionCount(diagnosis.getAccountDeposit() != null ? diagnosis.getAccountDeposit().getDescription() : "미입력")
.subscriptionAmount(diagnosis.getAccount() != null ? diagnosis.getAccount().getValue() : "미입력")
// 5. 결혼 여부
.isMarried(diagnosis.isMaritalStatus())
.marriedYears(diagnosis.getMarriageYears())
// 6. 자녀 여부
.unbornChildren(diagnosis.getUnbornChildrenCount())
.under6Children(diagnosis.getUnder6ChildrenCount())
.over7MinorChildren(diagnosis.getOver7MinorChildrenCount())
.totalChildren(totalChildren)
// 7. 세대 정보
.isHouseholdHead(diagnosis.isHouseholdHead())
.isSingleHousehold(diagnosis.isSingle())
.householdSize(diagnosis.getFamilyCount())
// 8. 주택 소유 여부
.housingStatus(diagnosis.getHousingStatus() != null ? diagnosis.getHousingStatus().getDescription() : "미입력")
.housingYears(diagnosis.getHousingYears())
// 9. 자동차 소유 여부
.ownsCar(diagnosis.isHasCar())
.carValue(diagnosis.getCarValue())
// 10. 총 자산
.propertyAsset(diagnosis.getPropertyAsset())
.carAsset(diagnosis.getCarAsset())
.financialAsset(diagnosis.getFinancialAsset())
.totalAssets(diagnosis.getTotalAsset())
// 최종 진단 결과
.eligible(!recommended.contains("해당 없음"))
.diagnosisResult(!recommended.contains("해당 없음") ?
"추천 임대주택이 있습니다" : "모든 조건 미충족")
.recommended(recommended)
.build();
}
}
Loading
Loading