Skip to content

Commit b8bb9d8

Browse files
authored
Merge pull request #143 from howWeather/perf/model-request
feat: defalut 값 설정
2 parents 631a5d7 + 46732b5 commit b8bb9d8

3 files changed

Lines changed: 131 additions & 46 deletions

File tree

src/main/java/com/howWeather/howWeather_backend/domain/ai_model/schedular/ModelScheduler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class ModelScheduler {
4343
* 매일 새벽 5시에 모델 서버로 예측에 필요한 데이터를 암호화된 형태로 전송합니다.
4444
*/
4545
@Transactional
46-
@Scheduled(cron = "0 10 16 * * *")
46+
@Scheduled(cron = "0 0 5 * * *")
4747
public void pushPredictionAESDataToAiServer() {
4848
try {
4949
List<Member> members = memberRepository.findAllByIsDeletedFalse();

src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java

Lines changed: 128 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,110 @@ public List<RecommendPredictDto> getRecommendList(Member member) {
4444
LocalDate targetDate = now.isBefore(LocalTime.of(6, 0)) ? today.minusDays(1) : today;
4545

4646
List<ClothingRecommendation> modelPredictList = getModelPrediction(member.getId(), targetDate);
47-
48-
Closet closet = getClosetWithAll(member);
4947
List<RecommendPredictDto> result = new ArrayList<>();
50-
for (ClothingRecommendation recommendation : modelPredictList) {
51-
RecommendPredictDto dto = makeResultForPredict(closet, recommendation, member);
48+
Closet closet = getClosetWithAll(member);
49+
if (!modelPredictList.isEmpty()) {
50+
log.info("[AI 추천] memberId={} AI 예측 결과 사용 ({}개)", member.getId(), modelPredictList.size());
51+
for (ClothingRecommendation recommendation : modelPredictList) {
52+
RecommendPredictDto dto = makeResultForPredict(closet, recommendation, member);
53+
boolean isUppersExist = dto.getUppersTypeList() != null && !dto.getUppersTypeList().isEmpty();
54+
boolean isFeelingExist = dto.getFeelingList() != null && !dto.getFeelingList().isEmpty();
55+
if (isUppersExist && isFeelingExist) {
56+
result.add(dto);
57+
}
58+
}
59+
} else {
60+
result.addAll(generateFallbackRecommendation(member, targetDate, closet));
61+
}
5262

53-
boolean isUppersExist = dto.getUppersTypeList() != null && !dto.getUppersTypeList().isEmpty();
54-
boolean isFeelingExist = dto.getFeelingList() != null && !dto.getFeelingList().isEmpty();
63+
return result;
64+
}
65+
66+
private List<RecommendPredictDto> generateFallbackRecommendation(Member member, LocalDate targetDate, Closet closet) {
67+
log.warn("[Fallback 추천] memberId={} AI 예측 결과 없음. 대체 추천 로직 시작.", member.getId());
68+
List<RecommendPredictDto> fallbackResult = new ArrayList<>();
5569

56-
if (isUppersExist && isFeelingExist) {
57-
result.add(dto);
70+
try {
71+
final Long MIN_OUTER_TYPE = 10L;
72+
final Long MAX_OUTER_TYPE = 25L;
73+
final Long MIN_UPPER_TYPE = 3L;
74+
final Long MAX_UPPER_TYPE = 9L;
75+
76+
List<Integer> fallbackOuterTypes = closet.getOuterList().stream()
77+
.filter(Outer::isActive)
78+
.filter(outer -> outer.getOuterType() != null &&
79+
outer.getOuterType() >= MIN_OUTER_TYPE &&
80+
outer.getOuterType() <= MAX_OUTER_TYPE)
81+
.map(outer -> outer.getOuterType().intValue())
82+
.distinct()
83+
.collect(Collectors.toList());
84+
85+
List<Integer> fallbackUpperTypes = closet.getUpperList().stream()
86+
.filter(Upper::isActive)
87+
.filter(upper -> upper.getUpperType() != null &&
88+
upper.getUpperType() >= MIN_UPPER_TYPE &&
89+
upper.getUpperType() <= MAX_UPPER_TYPE)
90+
.map(upper -> upper.getUpperType().intValue())
91+
.distinct()
92+
.collect(Collectors.toList());
93+
94+
if (fallbackOuterTypes.isEmpty() && fallbackUpperTypes.isEmpty()) {
95+
log.info("[Fallback 추천] memberId={} 추천할 활성 상의 또는 아우터 없음 (기준: Upper {}-{}, Outer {}-{}). 예외 발생.",
96+
member.getId(), MIN_UPPER_TYPE, MAX_UPPER_TYPE, MIN_OUTER_TYPE, MAX_OUTER_TYPE);
97+
throw new CustomException(ErrorCode.NO_PREDICT_DATA);
5898
}
99+
100+
List<WeatherFeelingDto> fallbackFeelingList = createFallbackFeelingList(member, targetDate);
101+
102+
RecommendPredictDto fallbackDto = RecommendPredictDto.builder()
103+
.feelingList(fallbackFeelingList)
104+
.uppersTypeList(fallbackUpperTypes)
105+
.outersTypeList(fallbackOuterTypes)
106+
.build();
107+
fallbackResult.add(fallbackDto);
108+
log.info("[Fallback 추천] memberId={} 최종 추천 결과: Uppers={}, Outers={}, FeelingsGenerated={}",
109+
member.getId(), fallbackUpperTypes, fallbackOuterTypes, !fallbackFeelingList.isEmpty());
110+
111+
} catch (CustomException e) {
112+
log.error("[Fallback 추천 실패] memberId={} 처리 중 Custom 오류: {}", member.getId(), e.getMessage());
113+
throw new CustomException(ErrorCode.NO_PREDICT_DATA);
114+
} catch (Exception e) {
115+
log.error("[Fallback 추천 실패] memberId={} 알 수 없는 오류 발생: {}", member.getId(), e.getMessage(), e);
116+
throw new CustomException(ErrorCode.NO_PREDICT_DATA);
59117
}
60-
return result;
118+
119+
return fallbackResult;
120+
}
121+
122+
private List<WeatherFeelingDto> createFallbackFeelingList(Member member, LocalDate targetDate) {
123+
List<WeatherFeelingDto> fallbackFeelingList = new ArrayList<>();
124+
String regionName = member.getRegionName() != null ? member.getRegionName() : "서울특별시 용산구";
125+
List<Integer> targetHours = List.of(9, 12, 15, 18, 21);
126+
final int DEFAULT_FEELING = 2;
127+
128+
try {
129+
List<WeatherForecast> forecasts = weatherForecastRepository
130+
.findByRegionNameAndForecastDateAndHourInOrderByHourAsc(regionName, targetDate, targetHours);
131+
132+
if (!forecasts.isEmpty()) {
133+
for (WeatherForecast forecast : forecasts) {
134+
WeatherFeelingDto dto = WeatherFeelingDto.builder()
135+
.date(forecast.getForecastDate())
136+
.time(forecast.getHour())
137+
.feeling(DEFAULT_FEELING)
138+
.temperature(forecast.getTemperature())
139+
.build();
140+
fallbackFeelingList.add(dto);
141+
}
142+
log.info("[Fallback 추천] memberId={} 기본 체감온도(2)로 그래프 생성 완료 ({}개 시간대)", member.getId(), fallbackFeelingList.size());
143+
} else {
144+
log.warn("[Fallback 추천] memberId={} 날씨 예보 데이터가 없어 체감온도 그래프를 생성할 수 없습니다. region={}, date={}, hours={}",
145+
member.getId(), regionName, targetDate, targetHours);
146+
}
147+
} catch (Exception e) {
148+
log.error("[Fallback 추천] memberId={} 날씨 예보 조회 중 오류 발생: {}", member.getId(), e.getMessage(), e);
149+
}
150+
return fallbackFeelingList;
61151
}
62152

63153
private RecommendPredictDto makeResultForPredict(Closet closet, ClothingRecommendation recommendation, Member member) {
@@ -83,7 +173,9 @@ private List<Integer> makeOuterList(Closet closet, List<Integer> outers) {
83173
if (outers.isEmpty()) return new ArrayList<>();
84174

85175
Set<Long> ownedClothTypes = closet.getOuterList().stream()
176+
.filter(Outer::isActive)
86177
.map(Outer::getOuterType)
178+
.filter(Objects::nonNull)
87179
.collect(Collectors.toSet());
88180

89181
Set<Integer> resultSet = new HashSet<>();
@@ -101,7 +193,9 @@ private List<Integer> makeOuterList(Closet closet, List<Integer> outers) {
101193

102194
private List<Integer> makeUpperList(Closet closet, List<Integer> tops) {
103195
Set<Long> ownedClothTypes = closet.getUpperList().stream()
196+
.filter(Upper::isActive)
104197
.map(Upper::getUpperType)
198+
.filter(Objects::nonNull)
105199
.collect(Collectors.toSet());
106200

107201
Set<Integer> resultSet = new HashSet<>();
@@ -119,52 +213,42 @@ private List<Integer> makeUpperList(Closet closet, List<Integer> tops) {
119213
private List<WeatherFeelingDto> makeWeatherFeeling(Map<String, Integer> predictionMap,
120214
ClothingRecommendation recommendation) {
121215
List<WeatherFeelingDto> feelingList = new ArrayList<>();
122-
123216
LocalDate forecastDate = recommendation.getDate();
124-
125217
String regionName = recommendation.getRegionName();
126218

127-
List<Integer> hours = predictionMap.keySet().stream()
128-
.map(Integer::parseInt)
129-
.collect(Collectors.toList());
219+
List<Integer> targetHours = List.of(9, 12, 15, 18, 21);
130220

131221
List<WeatherForecast> forecasts = weatherForecastRepository
132-
.findByRegionNameAndForecastDateAndHourInOrderByCreatedAtDesc(regionName, forecastDate, hours);
133-
134-
Map<Integer, WeatherForecast> hourToForecastMap = forecasts.stream()
135-
.collect(Collectors.toMap(
136-
WeatherForecast::getHour,
137-
forecast -> forecast,
138-
(oldVal, newVal) -> oldVal
139-
));
140-
141-
for (Map.Entry<String, Integer> entry : predictionMap.entrySet()) {
142-
int hour = Integer.parseInt(entry.getKey());
143-
int feeling = entry.getValue();
144-
WeatherForecast forecast = hourToForecastMap.get(hour);
145-
146-
if (forecast != null) {
147-
WeatherFeelingDto dto = WeatherFeelingDto.builder()
148-
.date(forecast.getForecastDate())
149-
.time(hour)
150-
.feeling(feeling)
151-
.temperature(forecast.getTemperature())
152-
.build();
153-
feelingList.add(dto);
154-
} else {
155-
log.warn("날씨 데이터 없음: region={}, date={}, hour={}", regionName, forecastDate, hour);
222+
.findByRegionNameAndForecastDateAndHourInOrderByHourAsc(regionName, forecastDate, targetHours);
223+
224+
Map<String, Integer> safePredictionMap = Optional.ofNullable(predictionMap).orElseGet(Collections::emptyMap);
225+
final int DEFAULT_FEELING = 2;
226+
227+
for (WeatherForecast forecast : forecasts) {
228+
int hour = forecast.getHour();
229+
int feeling = safePredictionMap.getOrDefault(String.valueOf(hour), DEFAULT_FEELING);
230+
231+
WeatherFeelingDto dto = WeatherFeelingDto.builder()
232+
.date(forecast.getForecastDate())
233+
.time(hour)
234+
.feeling(feeling)
235+
.temperature(forecast.getTemperature())
236+
.build();
237+
feelingList.add(dto);
238+
239+
if (feeling == DEFAULT_FEELING && !safePredictionMap.containsKey(String.valueOf(hour))) {
240+
log.debug("AI 체감온도 예측값 없음. 기본값(2) 사용: region={}, date={}, hour={}", regionName, forecastDate, hour);
156241
}
157242
}
243+
244+
if (forecasts.isEmpty()) {
245+
log.warn("날씨 예보 데이터 없음: region={}, date={}, hours={}", regionName, forecastDate, targetHours);
246+
}
158247
return feelingList;
159248
}
160249

161-
162250
private List<ClothingRecommendation> getModelPrediction(Long id, LocalDate now) {
163-
List<ClothingRecommendation> list = clothingRecommendationRepository.findByMemberIdAndDate(id, now);
164-
if (list.isEmpty()) {
165-
throw new CustomException(ErrorCode.NO_PREDICT_DATA);
166-
}
167-
return list;
251+
return clothingRecommendationRepository.findByMemberIdAndDate(id, now);
168252
}
169253

170254
@Transactional
@@ -196,7 +280,6 @@ public void save(ModelClothingRecommendationDto dto, Member member) {
196280
}
197281
}
198282

199-
200283
private Closet getClosetWithAll(Member member) {
201284
Closet closetWithUppers = closetRepository.findClosetWithUppers(member.getId())
202285
.orElseThrow(() -> new CustomException(ErrorCode.CLOSET_NOT_FOUND));

src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/WeatherForecastRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ List<WeatherForecast> findByRegionNameAndForecastDateAndHourInOrderByCreatedAtDe
2222
void deleteByForecastDateGreaterThanEqual(LocalDate baseDate);
2323

2424
List<WeatherForecast> findByRegionNameAndForecastDateAndHourIn(String regionName, LocalDate now, List<Integer> targetHours);
25+
26+
List<WeatherForecast> findByRegionNameAndForecastDateAndHourInOrderByHourAsc(String regionName, LocalDate forecastDate, List<Integer> targetHours);
2527
}

0 commit comments

Comments
 (0)