Skip to content

Commit

Permalink
Merge pull request #21 from CAUSOLDOUTMEN/feature/18-feat-nutrition_rank
Browse files Browse the repository at this point in the history
feat: 영양소 및 음식 조회 (Best, Worst) 로직 구현 (#18)
  • Loading branch information
win-luck authored Oct 11, 2023
2 parents 5c94104 + 9faba4c commit 831f20f
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/main/java/com/diareat/diareat/food/domain/Food.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public class Food {
@JoinColumn(name = "favorite_food_id")
private FavoriteFood favoriteFood;

@CreatedDate
private LocalDate date;

private LocalTime time;
Expand All @@ -42,6 +41,7 @@ public static Food createFood(String name, User user, BaseNutrition baseNutritio
Food food = new Food();
food.name = name;
food.user = user;
food.date = LocalDate.now();
food.time = LocalTime.now();
food.baseNutrition = baseNutrition;
return food;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.diareat.diareat.food.dto;

import com.diareat.diareat.food.domain.Food;
import com.diareat.diareat.user.domain.BaseNutrition;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;

@Getter
@AllArgsConstructor
public class ResponseFoodRankDto {

private Long userId;
private List<ResponseFoodDto> rankFoodList;
private LocalDate startDate; //해당 날짜로부터 7일전까지
private boolean isBest; //isBest = true 이면 Best 3, false 이면 Worst 3

public static ResponseFoodRankDto of(Long userId, List<ResponseFoodDto> rankFoodList, LocalDate startDate, boolean isBest) {
return new ResponseFoodRankDto(userId, rankFoodList, startDate, isBest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDate;

@Getter
@AllArgsConstructor
public class ResponseNutritionSumByDateDto {

Long userId;
LocalDate checkDate; //조회한 날짜
int nutritionSumType; //조회할 기간을 나타내는 코드. {1: 특정 날짜, 7: 최근 7일간, 30: 최근 한달간}

int totalKcal;
int totalCarbohydrate;
int totalProtein;
Expand All @@ -16,7 +23,7 @@ public class ResponseNutritionSumByDateDto {
double ratioProtein;
double ratioFat;

public static ResponseNutritionSumByDateDto of (int totalKcal, int totalCarbohydrate, int totalProtein, int totalFat, double ratioKcal, double ratioCarbohydrate, double ratioProtein, double ratioFat){
return new ResponseNutritionSumByDateDto(totalKcal, totalCarbohydrate, totalProtein, totalFat, ratioKcal, ratioCarbohydrate, ratioProtein, ratioFat);
public static ResponseNutritionSumByDateDto of (Long userId, LocalDate checkDate, int nutritionSumType, int totalKcal, int totalCarbohydrate, int totalProtein, int totalFat, double ratioKcal, double ratioCarbohydrate, double ratioProtein, double ratioFat){
return new ResponseNutritionSumByDateDto(userId, checkDate, nutritionSumType, totalKcal, totalCarbohydrate, totalProtein, totalFat, ratioKcal, ratioCarbohydrate, ratioProtein, ratioFat);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@

public interface FoodRepository extends JpaRepository<Food, Long> {
List<Food> findAllByUserIdAndDate(Long userId, LocalDate date); //유저가 특정 날짜에 먹은 음식 반환
List<Food> findAllByUserIdAndDateBetween(Long userId, LocalDate startDate, LocalDate endDate); //유저가 특정 기간 내에 먹은 음식 반환
}
98 changes: 70 additions & 28 deletions src/main/java/com/diareat/diareat/food/service/FoodService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.rmi.registry.LocateRegistry;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -94,50 +96,67 @@ public void deleteFavoriteFood(Long favoriteFoodId) {
// 유저의 특정 날짜에 먹은 음식들의 영양성분별 총합 조회 (섭취영양소/기준영양소 및 비율까지 계산해서 반환, dto 구체적 협의 필요)
public ResponseNutritionSumByDateDto getNutritionSumByDate(Long userId, LocalDate date) {
List<Food> foodList = foodRepository.findAllByUserIdAndDate(userId, date);
User targetUser = getUserById(userId);
int totalKcal = 0;
int totalCarbohydrate = 0;
int totalProtein = 0;
int totalFat = 0;

for (Food food : foodList) {
BaseNutrition targetFoodNutrition = food.getBaseNutrition();
totalKcal += targetFoodNutrition.getKcal();
totalCarbohydrate += targetFoodNutrition.getCarbohydrate();
totalProtein += targetFoodNutrition.getProtein();
totalFat += targetFoodNutrition.getFat();
}

double ratioKcal = Math.round((totalKcal*1.0)/(targetUser.getBaseNutrition().getKcal()*1.0))*10.0;
double ratioCarbohydrate = Math.round((totalCarbohydrate*1.0)/(targetUser.getBaseNutrition().getCarbohydrate()*1.0))*10.0;
double ratioProtein = Math.round((totalProtein*1.0)/(targetUser.getBaseNutrition().getProtein()*1.0))*10.0;
double ratioFat = Math.round((totalFat*1.0)/(targetUser.getBaseNutrition().getFat()*1.0))*10.0;

return ResponseNutritionSumByDateDto.of(totalKcal,totalCarbohydrate, totalProtein, totalFat, ratioKcal, ratioCarbohydrate, ratioProtein, ratioFat);
return calculateNutritionSumAndRatio(userId, foodList, date, 1);
}

@Transactional
@Transactional(readOnly = true)
// 유저의 최근 7일간의 영양성분별 총합 조회 (섭취영양소/기준영양소 및 비율까지 계산해서 반환, dto 구체적 협의 필요)
public void getNutritionSumByWeek(Long userId) {
public ResponseNutritionSumByDateDto getNutritionSumByWeek(Long userId) {
LocalDate endDate = LocalDate.now();
List<Food> foodList = foodRepository.findAllByUserIdAndDateBetween(userId, endDate.minusWeeks(1), endDate);

return calculateNutritionSumAndRatio(userId, foodList, endDate, 7);
}

@Transactional
@Transactional(readOnly = true)
// 유저의 최근 1개월간의 영양성분별 총합 조회 (섭취영양소/기준영양소 및 비율까지 계산해서 반환, dto 구체적 협의 필요)
public void getNutritionSumByMonth(Long userId) {
public ResponseNutritionSumByDateDto getNutritionSumByMonth(Long userId) {
LocalDate endDate = LocalDate.now();
List<Food> foodList = foodRepository.findAllByUserIdAndDateBetween(userId, endDate.minusWeeks(1), endDate);

return calculateNutritionSumAndRatio(userId, foodList, endDate, 30);
}

@Transactional
@Transactional(readOnly = true)
// 유저의 최근 7일간의 Best 3 음식 조회 (dto 구체적 협의 필요)
public void getBestFoodByWeek(Long userId) {
public ResponseFoodRankDto getBestFoodByWeek(Long userId, LocalDate endDate) {
List<Food> foodList = foodRepository.findAllByUserIdAndDateBetween(userId, endDate.minusWeeks(1), endDate);

List<Food> top3Foods = foodList.stream()
.sorted(Comparator.comparingDouble((Food food) ->
0.7 * food.getBaseNutrition().getProtein()- 0.3 * food.getBaseNutrition().getFat()).reversed())
.limit(3)
.collect(Collectors.toList()); //고단백 저지방일수록 점수를 높게 측정되도록 기준을 잡은 후, 그 기준을 기반으로 정렬
//사용한 기준은, 고단백과 저지방의 점수 반영 비율을 7:3으로 측정하고, 단백질량이 높을 수록, 지방량이 낮을 수록 점수가 높음. 이후, 내림차순 정렬
// ** Best 3 기준 논의 필요 **

List<ResponseFoodDto> top3FoodsDtoList = top3Foods.stream()
.map(food -> ResponseFoodDto.of(food.getId(), food.getUser().getId(), food.getName(), food.getDate(), food.getTime(), food.getBaseNutrition(), food.isFavorite())).collect(Collectors.toList());

return ResponseFoodRankDto.of(userId, top3FoodsDtoList, endDate, true);
}

@Transactional
@Transactional(readOnly = true)
// 유저의 최근 7일간의 Worst 3 음식 조회 (dto 구체적 협의 필요)
public void getWorstFoodByWeek(Long userId) {
public ResponseFoodRankDto getWorstFoodByWeek(Long userId, LocalDate endDate) {

List<Food> foodList = foodRepository.findAllByUserIdAndDateBetween(userId, endDate.minusWeeks(1), endDate);

List<Food> worst3Foods = foodList.stream()
.sorted(Comparator.comparingDouble((Food food) ->
0.7 * food.getBaseNutrition().getFat() + 0.3 * food.getBaseNutrition().getCarbohydrate()).reversed())
.limit(3)
.collect(Collectors.toList());
//반대로 고지방 고탄수의 경우를 7:3으로 측정하고, 지방이 높을 수록 점수가 급격히 높아짐. 이 경우는 점수가 높은 것이 안좋음.
//(수정) https://blog.nongshim.com/1961, 탄수화물이 더 영향을 미친다고 하는데...흠...
// ** 이점은 논의가 필요할 듯? **
// 우선 임시로 지방 비율을 높게 설정

List<ResponseFoodDto> worst3FoodDtoList = worst3Foods.stream()
.map(food -> ResponseFoodDto.of(food.getId(), food.getUser().getId(), food.getName(), food.getDate(), food.getTime(), food.getBaseNutrition(), food.isFavorite())).collect(Collectors.toList());


return ResponseFoodRankDto.of(userId, worst3FoodDtoList, endDate, false);
}

private User getUserById(Long userId){
Expand All @@ -155,6 +174,29 @@ private FavoriteFood getFavoriteFoodById(Long foodId){
.orElseThrow(() -> new FoodException(ResponseCode.FOOD_NOT_FOUND));
}

private ResponseNutritionSumByDateDto calculateNutritionSumAndRatio(Long userId, List<Food> foodList, LocalDate checkDate, int nutritionSumType){
User targetUser = getUserById(userId);
int totalKcal = 0;
int totalCarbohydrate = 0;
int totalProtein = 0;
int totalFat = 0;

for (Food food : foodList) {
BaseNutrition targetFoodNutrition = food.getBaseNutrition();
totalKcal += targetFoodNutrition.getKcal();
totalCarbohydrate += targetFoodNutrition.getCarbohydrate();
totalProtein += targetFoodNutrition.getProtein();
totalFat += targetFoodNutrition.getFat();
}

double ratioKcal = Math.round((((double) totalKcal /(double) targetUser.getBaseNutrition().getKcal())*100.0)*10.0)/10.0;
double ratioCarbohydrate = Math.round((((double) totalCarbohydrate /(double) targetUser.getBaseNutrition().getCarbohydrate())*100.0)*10.0)/10.0;
double ratioProtein = Math.round((((double) totalProtein /(double) targetUser.getBaseNutrition().getProtein())*100.0)*10.0)/10.0;
double ratioFat =Math.round((((double) totalFat /(double) targetUser.getBaseNutrition().getFat())*100.0)*10.0)/10.0;

return ResponseNutritionSumByDateDto.of(userId, checkDate, nutritionSumType, totalKcal,totalCarbohydrate, totalProtein, totalFat, ratioKcal, ratioCarbohydrate, ratioProtein, ratioFat);
}


/**
* 메서드 구현 유의사항
Expand Down
70 changes: 62 additions & 8 deletions src/test/java/com/diareat/diareat/service/FoodServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.diareat.diareat.food.repository.FoodRepository;
import com.diareat.diareat.food.service.FoodService;
import com.diareat.diareat.user.domain.BaseNutrition;
import com.diareat.diareat.user.domain.User;
import com.diareat.diareat.user.dto.CreateUserDto;
import com.diareat.diareat.user.repository.UserRepository;
import com.diareat.diareat.user.service.UserService;
Expand All @@ -17,6 +18,7 @@
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

Expand Down Expand Up @@ -153,24 +155,76 @@ void testDeleteFavoriteFood() {
@Test
void testNutritionSumByDate(){
//given
BaseNutrition testFoodNutrition = BaseNutrition.createNutrition(100,150,200,250);
BaseNutrition testFoodNutrition = BaseNutrition.createNutrition(1400,150,200,250);
Long userId = userService.saveUser(CreateUserDto.of("testUser", "testPassword",1, 180, 80, 18));
Long foodId = foodService.saveFood(CreateFoodDto.of(userId,"testFood", testFoodNutrition));
Food food = foodRepository.getReferenceById(foodId);

//when
ResponseNutritionSumByDateDto responseNutritionSumByDateDto = foodService.getNutritionSumByDate(userId,food.getDate());

assertNotNull(responseNutritionSumByDateDto);
assertEquals(100, responseNutritionSumByDateDto.getTotalKcal());
assertEquals(1400, responseNutritionSumByDateDto.getTotalKcal());
assertEquals(150, responseNutritionSumByDateDto.getTotalCarbohydrate());
assertEquals(200, responseNutritionSumByDateDto.getTotalProtein());
assertEquals(250, responseNutritionSumByDateDto.getTotalFat());

assertEquals(Math.round((100*1.0)/(2000*1.0))*10.0, responseNutritionSumByDateDto.getRatioKcal());
assertEquals(Math.round((150*1.0)/(300*1.0))*10.0, responseNutritionSumByDateDto.getRatioCarbohydrate());
assertEquals(Math.round((200*1.0)/(80*1.0))*10.0, responseNutritionSumByDateDto.getRatioProtein());
assertEquals(Math.round((250*1.0)/(80*1.0))*10.0, responseNutritionSumByDateDto.getRatioFat());
assertEquals(Math.round((((double)1400 / (double)2000) * 100.0)*100.0)/100.0, responseNutritionSumByDateDto.getRatioKcal());
assertEquals(Math.round((((double)150 / (double)300) * 100.0)*100.0)/100.0, responseNutritionSumByDateDto.getRatioCarbohydrate());
assertEquals(Math.round((((double)200 / (double)80) * 100.0)*100.0)/100.0, responseNutritionSumByDateDto.getRatioProtein());
assertEquals(Math.round((((double)250 / (double)80) * 100.0)*100.0)/100.0, responseNutritionSumByDateDto.getRatioFat());
}

@Test
void testNutritionSumByWeekAndMonth(){
//given
BaseNutrition testFoodNutrition = BaseNutrition.createNutrition(100,150,200,250);
Long userId = userService.saveUser(CreateUserDto.of("testUser", "testPassword",1, 180, 80, 18));
Long foodId = foodService.saveFood(CreateFoodDto.of(userId,"testFood", testFoodNutrition));

}

@Test
void getBest3FoodTest() {
// given
Long userId = userService.saveUser(CreateUserDto.of("testUser", "testPassword", 1, 180, 80, 18));
foodService.saveFood(CreateFoodDto.of(userId, "Food1", BaseNutrition.createNutrition(100, 100 ,10, 1)));
foodService.saveFood(CreateFoodDto.of(userId, "Food2", BaseNutrition.createNutrition(100, 100 ,8, 2)));
foodService.saveFood(CreateFoodDto.of(userId, "Food3", BaseNutrition.createNutrition(100, 100 ,6, 3)));
foodService.saveFood(CreateFoodDto.of(userId, "Food4", BaseNutrition.createNutrition(100, 100 ,4, 4)));
Long foodId = foodService.saveFood(CreateFoodDto.of(userId, "Food5", BaseNutrition.createNutrition(100, 100 ,2, 5)));

Food testFood = foodRepository.getReferenceById(foodId);

// when
ResponseFoodRankDto response = foodService.getBestFoodByWeek(userId, testFood.getDate());
List<ResponseFoodDto> top3Foods = response.getRankFoodList();

// then
assertEquals(3, top3Foods.size());
assertEquals("Food1", top3Foods.get(0).getName());
assertEquals("Food2", top3Foods.get(1).getName());
assertEquals("Food3", top3Foods.get(2).getName());
}

@Test
void getWorst3FoodsTest() {
// given
Long userId = userService.saveUser(CreateUserDto.of("testUser", "testPassword", 1, 180, 80, 18));
foodService.saveFood(CreateFoodDto.of(userId, "Food1", BaseNutrition.createNutrition(100, 50 ,10, 1)));
foodService.saveFood(CreateFoodDto.of(userId, "Food2", BaseNutrition.createNutrition(100, 100 ,8, 20)));
foodService.saveFood(CreateFoodDto.of(userId, "Food3", BaseNutrition.createNutrition(100, 80 ,6, 7)));
foodService.saveFood(CreateFoodDto.of(userId, "Food4", BaseNutrition.createNutrition(100, 100 ,4, 5)));
Long foodId = foodService.saveFood(CreateFoodDto.of(userId, "Food5", BaseNutrition.createNutrition(100, 90 ,2, 6)));

Food testFood = foodRepository.getReferenceById(foodId);

// when
ResponseFoodRankDto response = foodService.getWorstFoodByWeek(userId, testFood.getDate());
List<ResponseFoodDto> top3Foods = response.getRankFoodList();

// then
assertEquals(3, top3Foods.size());
assertEquals("Food2", top3Foods.get(0).getName());
assertEquals("Food4", top3Foods.get(1).getName());
assertEquals("Food5", top3Foods.get(2).getName());
}
}

0 comments on commit 831f20f

Please sign in to comment.