Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨Feat: 영양소 및 음식 조회 (Best, Worst) 로직 구현 (#18) #21

Merged
merged 15 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
5e6a059
:sparkles: feat: 7일, 1개월 간 영양성분 총합 조회 구현 (#18)
synoti21 Oct 5, 2023
73441ef
:bug: fix: 잘못된 영양소 비율 계산 로직 수정 (#18)
synoti21 Oct 11, 2023
13367fb
:white_check_mark: test: 영양소 비율 계산 테스트 코드 수정 (#18)
synoti21 Oct 11, 2023
2b25968
:sparkles: feat: 음식 생성 날짜 현재 날짜로 수정 (#18)
synoti21 Oct 11, 2023
85edad8
:bug: fix: 잘못된 날짜 기간 설정 수정 (#18)
synoti21 Oct 11, 2023
05d85dd
:sparkles: feat: Worst 3 음식 조회 로직 구현 (#18)
synoti21 Oct 11, 2023
14c8223
:sparkles: feat: Best, Worst 음식 선정 위한 임시 Dto 구현 (#18)
synoti21 Oct 11, 2023
fe65090
:bulb: chore: Best3, Worst3 선정 기준 논의 주석 추가 (#18)
synoti21 Oct 11, 2023
dfb2503
:white_check_mark: test: Worst3 선정 테스트 코드 구현 (#18)
synoti21 Oct 11, 2023
f2ecba0
:pencil2: fix: Best3 선정 기준에서 getProtein() -> getFat() (#18)
synoti21 Oct 11, 2023
ea0adcb
:recycle: refactor: 영양성분 총합 Dto 중 누락된 속성 추가 (#18)
synoti21 Oct 11, 2023
ce61bdd
:recycle: refactor: Dto 수정에 따른 영양성분 합 조회 코드 수정 (#18)
synoti21 Oct 11, 2023
dc44397
:recycle: refactor: ResponseFoodRankDto에서 Food Domain -> Dto로 변경 (#18)
synoti21 Oct 11, 2023
d19b639
:white_check_mark: test: Dto로 수정에 따른 테스트 코드 수정 (#18)
synoti21 Oct 11, 2023
9faba4c
:recycle: refactor: CreatedDate 어노테이션 제거 (#18)
synoti21 Oct 11, 2023
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
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();
synoti21 marked this conversation as resolved.
Show resolved Hide resolved
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 기준 논의 필요 **
synoti21 marked this conversation as resolved.
Show resolved Hide resolved

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());
}
}