diff --git a/src/main/java/com/haapyProcess/domain/analysis/controller/AnalysisController.java b/src/main/java/com/haapyProcess/domain/analysis/controller/AnalysisController.java index 8764e38..141cb8b 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/controller/AnalysisController.java +++ b/src/main/java/com/haapyProcess/domain/analysis/controller/AnalysisController.java @@ -47,6 +47,7 @@ public class AnalysisController { | └ diseaseId | 원인 질병 ID | Long | | | └ diseaseName | 원인 질병 이름 | String | | | └ weatherScore | 질환별 날씨 점수 (0=최악, 100=최고) | int | 위험 요인이 없어도 점수는 제공 | + | └ scoreFactors | 점수를 크게 깎은 주요 날씨 지수 이름 배열 (예: 초미세먼지) | List | 영향 큰 순, 최대 2개. 양호하면 빈 배열 | | └ factorGuides | 날씨 원인 및 맞춤 행동 강령 배열 | List | 위험 요인이 없으면 빈 배열 | |    └ factorName | 초과한 날씨 요인 (예: 미세먼지) | String | | |    └ guide | 행동 추천 가이드 | String | | diff --git a/src/main/java/com/haapyProcess/domain/analysis/dto/RiskAnalysisResult.java b/src/main/java/com/haapyProcess/domain/analysis/dto/RiskAnalysisResult.java index 6161c14..97a3f0b 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/dto/RiskAnalysisResult.java +++ b/src/main/java/com/haapyProcess/domain/analysis/dto/RiskAnalysisResult.java @@ -27,6 +27,7 @@ public static class RiskDetail { private Long diseaseId; private String diseaseName; private int weatherScore; // 질환별 날씨 점수 (0=최악, 100=최고) + private List scoreFactors; // 점수를 크게 깎은 주요 날씨 지수 (최대 2개, 영향 큰 순) private List factorGuides; } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/DiseaseRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/DiseaseRiskRule.java index ffc7329..e9d1cf6 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/DiseaseRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/DiseaseRiskRule.java @@ -1,6 +1,7 @@ package com.haapyProcess.domain.analysis.rule; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -19,10 +20,10 @@ public interface DiseaseRiskRule { List evaluateFactorGuides(WeatherResponseDto weather); /** - * 질환별 가중치를 적용한 날씨 점수(0~100)를 반환합니다. + * 질환별 가중치를 적용한 날씨 점수(0~100)와 점수 하락의 주요 원인 지수를 반환합니다. * 0점 = 최악의 날씨, 100점 = 최고의 날씨. * precipPreference는 질병 없는 사용자의 강수 불편도 산정에만 사용됩니다. */ - int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference); + WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference); } \ No newline at end of file diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ArthritisRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ArthritisRiskRule.java index 9bd895f..d272224 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ArthritisRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ArthritisRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -42,13 +44,15 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { // 6시간 기온 급감 (하강 폭만 사용) int tempDrop = WeatherScoreTables.tempChange(weather.getTempDropIn6Hours()); // 강수형태 코드 1(비), 2(비/눈), 3(눈)만 적용 int precip = WeatherScoreTables.precipSensitive(weather.getParsedCurrentPty(), Set.of(1, 2, 3)); - double raw = tempDrop * 0.60 + precip * 0.40; - return 100 - (int) Math.round(raw); + return ScoreBuilder.create() + .add("기온 급강하", tempDrop, 0.60) + .add("강수", precip, 0.40) + .build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/AsthmaRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/AsthmaRiskRule.java index f8b6890..3b2c2d2 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/AsthmaRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/AsthmaRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -44,24 +46,32 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { int pm25 = WeatherScoreTables.pm25(weather.getParsedPm25Value()); int pm10 = WeatherScoreTables.pm10(weather.getParsedPm10Value()); int temp = WeatherScoreTables.temp(weather.getParsedCurrentTemp()); double tempChangeVal = Math.max(weather.getTempDropIn6Hours(), weather.getTempRiseIn6Hours()); int tempChange = WeatherScoreTables.tempChange(tempChangeVal); - double raw; int month = java.time.LocalDate.now().getMonthValue(); boolean pollenOffSeason = (month == 7 || month == 11 || month == 12 || month == 1 || month == 2); if (pollenOffSeason) { // 꽃가루 계수(0.35) 제외 후 나머지 변수를 재정규화 (÷0.65) - raw = (pm25 * 0.30 + pm10 * 0.20 + temp * 0.10 + tempChange * 0.05) / 0.65; - } else { - int pollen = WeatherScoreTables.pollen(weather.getParsedPollenRisk()); - raw = pollen * 0.35 + pm25 * 0.30 + pm10 * 0.20 + temp * 0.10 + tempChange * 0.05; + return ScoreBuilder.create() + .add("초미세먼지", pm25, 0.30 / 0.65) + .add("미세먼지", pm10, 0.20 / 0.65) + .add("기온", temp, 0.10 / 0.65) + .add("기온 급변", tempChange, 0.05 / 0.65) + .build(); } - return 100 - (int) Math.round(raw); + int pollen = WeatherScoreTables.pollen(weather.getParsedPollenRisk()); + return ScoreBuilder.create() + .add("꽃가루", pollen, 0.35) + .add("초미세먼지", pm25, 0.30) + .add("미세먼지", pm10, 0.20) + .add("기온", temp, 0.10) + .add("기온 급변", tempChange, 0.05) + .build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ChildRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ChildRiskRule.java index 5e49f74..7e74a6f 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ChildRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ChildRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -49,12 +51,13 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { - double raw = WeatherScoreTables.pm25(weather.getParsedPm25Value()) * 0.30 - + WeatherScoreTables.pm10(weather.getParsedPm10Value()) * 0.25 - + WeatherScoreTables.temp(weather.getParsedCurrentTemp()) * 0.20 - + WeatherScoreTables.uv(weather.getParsedUvRisk()) * 0.15 - + WeatherScoreTables.precipSensitive(weather.getParsedCurrentPty(), Set.of(1, 2, 3)) * 0.10; - return 100 - (int) Math.round(raw); + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + return ScoreBuilder.create() + .add("초미세먼지", WeatherScoreTables.pm25(weather.getParsedPm25Value()), 0.30) + .add("미세먼지", WeatherScoreTables.pm10(weather.getParsedPm10Value()), 0.25) + .add("기온", WeatherScoreTables.temp(weather.getParsedCurrentTemp()), 0.20) + .add("자외선", WeatherScoreTables.uv(weather.getParsedUvRisk()), 0.15) + .add("강수", WeatherScoreTables.precipSensitive(weather.getParsedCurrentPty(), Set.of(1, 2, 3)), 0.10) + .build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DermatitisRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DermatitisRiskRule.java index 8fab3d8..f49863e 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DermatitisRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DermatitisRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -42,20 +44,19 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { double humidity = weather.getParsedHumidity(); + ScoreBuilder builder = ScoreBuilder.create() + .add("자외선", WeatherScoreTables.uv(weather.getParsedUvRisk()), 0.50); + // 습도 낮음(30% 미만)과 높음(80% 이상)은 반대 방향 조건이라 별도 처리한다. - double humidityScore; if (humidity < 30) { - humidityScore = WeatherScoreTables.humidityDry(humidity) * 0.30; + builder.add("건조", WeatherScoreTables.humidityDry(humidity), 0.30); } else if (humidity >= 80) { - humidityScore = WeatherScoreTables.humidityWet(humidity) * 0.20; - } else { - humidityScore = 0; + builder.add("높은 습도", WeatherScoreTables.humidityWet(humidity), 0.20); } - double raw = WeatherScoreTables.uv(weather.getParsedUvRisk()) * 0.50 + humidityScore; - return 100 - (int) Math.round(raw); + return builder.build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DiabetesRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DiabetesRiskRule.java index fbe555c..161e8b1 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DiabetesRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DiabetesRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -40,10 +42,11 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { // 당뇨는 폭염(탈수)이 핵심이므로 고온 점수를 사용한다. - double raw = WeatherScoreTables.tempHeat(weather.getParsedCurrentTemp()) * 0.65 - + WeatherScoreTables.humidityWet(weather.getParsedHumidity()) * 0.35; - return 100 - (int) Math.round(raw); + return ScoreBuilder.create() + .add("기온", WeatherScoreTables.tempHeat(weather.getParsedCurrentTemp()), 0.65) + .add("높은 습도", WeatherScoreTables.humidityWet(weather.getParsedHumidity()), 0.35) + .build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DryEyeRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DryEyeRiskRule.java index fca6d15..3aafbcd 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DryEyeRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/DryEyeRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -40,9 +42,10 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { - double raw = WeatherScoreTables.humidityDry(weather.getParsedHumidity()) * 0.75 - + WeatherScoreTables.uv(weather.getParsedUvRisk()) * 0.25; - return 100 - (int) Math.round(raw); + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + return ScoreBuilder.create() + .add("건조", WeatherScoreTables.humidityDry(weather.getParsedHumidity()), 0.75) + .add("자외선", WeatherScoreTables.uv(weather.getParsedUvRisk()), 0.25) + .build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ElderlyRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ElderlyRiskRule.java index 90017bb..303c310 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ElderlyRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/ElderlyRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -48,13 +50,14 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { double changeVal = Math.max(weather.getTempDropIn6Hours(), weather.getTempRiseIn6Hours()); // 고령 강수형태는 빙판길 낙상 위험 중심으로 코드 2(비/눈), 3(눈)만 적용 - double raw = WeatherScoreTables.temp(weather.getParsedCurrentTemp()) * 0.30 - + WeatherScoreTables.tempChange(changeVal) * 0.30 - + WeatherScoreTables.pm10(weather.getParsedPm10Value()) * 0.20 - + WeatherScoreTables.precipSensitive(weather.getParsedCurrentPty(), Set.of(2, 3)) * 0.20; - return 100 - (int) Math.round(raw); + return ScoreBuilder.create() + .add("기온", WeatherScoreTables.temp(weather.getParsedCurrentTemp()), 0.30) + .add("기온 급변", WeatherScoreTables.tempChange(changeVal), 0.30) + .add("미세먼지", WeatherScoreTables.pm10(weather.getParsedPm10Value()), 0.20) + .add("강수", WeatherScoreTables.precipSensitive(weather.getParsedCurrentPty(), Set.of(2, 3)), 0.20) + .build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/HeartDiseaseRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/HeartDiseaseRiskRule.java index 91a1ddd..675001e 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/HeartDiseaseRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/HeartDiseaseRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -42,11 +44,12 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { double changeVal = Math.max(weather.getTempDropIn6Hours(), weather.getTempRiseIn6Hours()); - double raw = WeatherScoreTables.pm25(weather.getParsedPm25Value()) * 0.40 - + WeatherScoreTables.temp(weather.getParsedCurrentTemp()) * 0.35 - + WeatherScoreTables.tempChange(changeVal) * 0.25; - return 100 - (int) Math.round(raw); + return ScoreBuilder.create() + .add("초미세먼지", WeatherScoreTables.pm25(weather.getParsedPm25Value()), 0.40) + .add("기온", WeatherScoreTables.temp(weather.getParsedCurrentTemp()), 0.35) + .add("기온 급변", WeatherScoreTables.tempChange(changeVal), 0.25) + .build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/HypertensionRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/HypertensionRiskRule.java index e175654..7a9ed51 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/HypertensionRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/HypertensionRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -46,12 +48,14 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { int temp = WeatherScoreTables.temp(weather.getParsedCurrentTemp()); double changeVal = Math.max(weather.getTempDropIn6Hours(), weather.getTempRiseIn6Hours()); int tempChange = WeatherScoreTables.tempChange(changeVal); - double raw = temp * 0.55 + tempChange * 0.45; - return 100 - (int) Math.round(raw); + return ScoreBuilder.create() + .add("기온", temp, 0.55) + .add("기온 급변", tempChange, 0.45) + .build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/NormalRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/NormalRiskRule.java index 455763d..4598333 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/NormalRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/NormalRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -65,17 +67,14 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { - // 건강 위험도 (공통) - double health = WeatherScoreTables.pm10(weather.getParsedPm10Value()) * 0.35 - + WeatherScoreTables.pm25(weather.getParsedPm25Value()) * 0.35 - + WeatherScoreTables.temp(weather.getParsedCurrentTemp()) * 0.20 - + WeatherScoreTables.uv(weather.getParsedUvRisk()) * 0.10; - - // 강수 불편도 (개인 선호 기반 가산점) - double precip = WeatherScoreTables.precipNormal(weather.getParsedCurrentPty(), precipPreference) * 0.13; - - double raw = Math.min(health + precip, 100.0); - return 100 - (int) Math.round(raw); + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + // 건강 위험도(공통) + 강수 불편도(개인 선호 기반 가산점). 총합은 build()에서 100으로 상한 처리된다. + return ScoreBuilder.create() + .add("미세먼지", WeatherScoreTables.pm10(weather.getParsedPm10Value()), 0.35) + .add("초미세먼지", WeatherScoreTables.pm25(weather.getParsedPm25Value()), 0.35) + .add("기온", WeatherScoreTables.temp(weather.getParsedCurrentTemp()), 0.20) + .add("자외선", WeatherScoreTables.uv(weather.getParsedUvRisk()), 0.10) + .add("강수", WeatherScoreTables.precipNormal(weather.getParsedCurrentPty(), precipPreference), 0.13) + .build(); } } diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/PollenAllergyRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/PollenAllergyRiskRule.java index 693f1e5..b9ba8ee 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/PollenAllergyRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/PollenAllergyRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -46,15 +48,16 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { int month = java.time.LocalDate.now().getMonthValue(); boolean pollenOffSeason = (month == 7 || month == 11 || month == 12 || month == 1 || month == 2); // 꽃가루 비시즌에는 꽃가루 점수를 0점으로 처리한다. int pollen = pollenOffSeason ? 0 : WeatherScoreTables.pollen(weather.getParsedPollenRisk()); - double raw = pollen * 0.90 - + WeatherScoreTables.humidityDry(weather.getParsedHumidity()) * 0.10; - return 100 - (int) Math.round(raw); + return ScoreBuilder.create() + .add("꽃가루", pollen, 0.90) + .add("건조", WeatherScoreTables.humidityDry(weather.getParsedHumidity()), 0.10) + .build(); } } \ No newline at end of file diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/RhinitisRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/RhinitisRiskRule.java index e6a970c..e4c0843 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/RhinitisRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/RhinitisRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -48,11 +50,12 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { - double raw = WeatherScoreTables.pollen(weather.getParsedPollenRisk()) * 0.45 - + WeatherScoreTables.pm10(weather.getParsedPm10Value()) * 0.20 - + WeatherScoreTables.pm25(weather.getParsedPm25Value()) * 0.20 - + WeatherScoreTables.humidityDry(weather.getParsedHumidity()) * 0.15; - return 100 - (int) Math.round(raw); + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + return ScoreBuilder.create() + .add("꽃가루", WeatherScoreTables.pollen(weather.getParsedPollenRisk()), 0.45) + .add("미세먼지", WeatherScoreTables.pm10(weather.getParsedPm10Value()), 0.20) + .add("초미세먼지", WeatherScoreTables.pm25(weather.getParsedPm25Value()), 0.20) + .add("건조", WeatherScoreTables.humidityDry(weather.getParsedHumidity()), 0.15) + .build(); } } \ No newline at end of file diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/StrokeRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/StrokeRiskRule.java index c1f31c2..d026734 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/StrokeRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/StrokeRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -36,11 +38,13 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { int tempDrop = WeatherScoreTables.tempChange(weather.getTempDropIn6Hours()); int temp = WeatherScoreTables.temp(weather.getParsedCurrentTemp()); - double raw = tempDrop * 0.80 + temp * 0.20; - return 100 - (int) Math.round(raw); + return ScoreBuilder.create() + .add("기온 급강하", tempDrop, 0.80) + .add("기온", temp, 0.20) + .build(); } } \ No newline at end of file diff --git a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/SunAllergyRiskRule.java b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/SunAllergyRiskRule.java index 9a4136c..1ef4da0 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/rule/impl/SunAllergyRiskRule.java +++ b/src/main/java/com/haapyProcess/domain/analysis/rule/impl/SunAllergyRiskRule.java @@ -3,6 +3,8 @@ import com.haapyProcess.domain.analysis.criteria.WeatherRiskCriteria; import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult.FactorGuide; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.ScoreBuilder; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.analysis.score.WeatherScoreTables; import com.haapyProcess.domain.member.entity.PrecipPreference; import com.haapyProcess.domain.weather.dto.WeatherResponseDto; @@ -36,8 +38,9 @@ public List evaluateFactorGuides(WeatherResponseDto weather) { } @Override - public int evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { - double raw = WeatherScoreTables.uv(weather.getParsedUvRisk()) * 1.00; - return 100 - (int) Math.round(raw); + public WeatherScore evaluateWeatherScore(WeatherResponseDto weather, PrecipPreference precipPreference) { + return ScoreBuilder.create() + .add("자외선", WeatherScoreTables.uv(weather.getParsedUvRisk()), 1.00) + .build(); } } \ No newline at end of file diff --git a/src/main/java/com/haapyProcess/domain/analysis/score/ScoreBuilder.java b/src/main/java/com/haapyProcess/domain/analysis/score/ScoreBuilder.java new file mode 100644 index 0000000..e6fc041 --- /dev/null +++ b/src/main/java/com/haapyProcess/domain/analysis/score/ScoreBuilder.java @@ -0,0 +1,57 @@ +package com.haapyProcess.domain.analysis.score; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * 날씨 지수별 가중 기여도를 누적해 최종 날씨 점수와 주요 원인 지수를 산출한다. + *

+ * 각 지수의 원점수(클수록 안 좋은 날씨)에 가중치를 곱한 '가중 기여 점수'를 모은 뒤, + * 가중 기여 점수가 임계값 이상인 지수만 영향이 큰 순서로 최대 2개까지 원인으로 노출한다. + */ +public final class ScoreBuilder { + + /** 이 가중 기여 점수 이상인 지수만 점수 하락의 주요 원인으로 노출한다. */ + private static final double FACTOR_THRESHOLD = 20.0; + /** 노출할 원인 지수 최대 개수. */ + private static final int MAX_FACTORS = 2; + + private record Contribution(String name, double weighted) { + } + + private final List contributions = new ArrayList<>(); + + private ScoreBuilder() { + } + + public static ScoreBuilder create() { + return new ScoreBuilder(); + } + + /** + * @param factorName 날씨 지수 이름 (예: "초미세먼지") + * @param rawScore 해당 지수의 원점수 (0~100, 클수록 안 좋음) + * @param weight 가중치 + */ + public ScoreBuilder add(String factorName, int rawScore, double weight) { + contributions.add(new Contribution(factorName, rawScore * weight)); + return this; + } + + public WeatherScore build() { + double raw = Math.min( + contributions.stream().mapToDouble(Contribution::weighted).sum(), + 100.0); + int score = 100 - (int) Math.round(raw); + + List factors = contributions.stream() + .filter(c -> c.weighted() >= FACTOR_THRESHOLD) + .sorted(Comparator.comparingDouble(Contribution::weighted).reversed()) + .limit(MAX_FACTORS) + .map(Contribution::name) + .toList(); + + return new WeatherScore(score, factors); + } +} diff --git a/src/main/java/com/haapyProcess/domain/analysis/score/WeatherScore.java b/src/main/java/com/haapyProcess/domain/analysis/score/WeatherScore.java new file mode 100644 index 0000000..4f0e83d --- /dev/null +++ b/src/main/java/com/haapyProcess/domain/analysis/score/WeatherScore.java @@ -0,0 +1,17 @@ +package com.haapyProcess.domain.analysis.score; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +/** + * 질환별 날씨 점수 산출 결과. + * score는 0(최악)~100(최고), factors는 점수를 크게 깎은 주요 날씨 지수 이름(최대 2개, 영향 큰 순). + */ +@Getter +@AllArgsConstructor +public class WeatherScore { + private final int score; + private final List factors; +} diff --git a/src/main/java/com/haapyProcess/domain/analysis/service/RiskAnalysisService.java b/src/main/java/com/haapyProcess/domain/analysis/service/RiskAnalysisService.java index 1e422ad..f92e0ec 100644 --- a/src/main/java/com/haapyProcess/domain/analysis/service/RiskAnalysisService.java +++ b/src/main/java/com/haapyProcess/domain/analysis/service/RiskAnalysisService.java @@ -2,6 +2,7 @@ import com.haapyProcess.domain.analysis.dto.RiskAnalysisResult; import com.haapyProcess.domain.analysis.rule.DiseaseRiskRule; +import com.haapyProcess.domain.analysis.score.WeatherScore; import com.haapyProcess.domain.healthcondition.entity.HealthCondition; import com.haapyProcess.domain.healthcondition.service.HealthConditionService; import com.haapyProcess.domain.location.entity.LocationType; @@ -99,7 +100,7 @@ private RiskAnalysisResult analyzeRisk(List conditions, Weather for (DiseaseRiskRule rule : targetRules) { List factors = rule.evaluateFactorGuides(weather); - int weatherScore = rule.evaluateWeatherScore(weather, precipPreference); + WeatherScore weatherScore = rule.evaluateWeatherScore(weather, precipPreference); if (factors != null && !factors.isEmpty()) { isRisk = true; @@ -108,7 +109,8 @@ private RiskAnalysisResult analyzeRisk(List conditions, Weather riskDetails.add(new RiskAnalysisResult.RiskDetail( rule.getConditionId(), rule.getDiseaseName(), - weatherScore, + weatherScore.getScore(), + weatherScore.getFactors(), factors == null ? List.of() : factors )); }