-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] #709: 솝탬프 파트별 랭킹 파트원 수 고려해서 계산하도록 수정 #713
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
base: dev
Are you sure you want to change the base?
Changes from all commits
f7cd7f4
139e1b3
cb9e4d1
f0e1166
589e1fd
b68036d
268943f
518bdbe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package org.sopt.app.application.rank; | ||
|
|
||
| import java.util.EnumMap; | ||
| import java.util.Map; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.sopt.app.domain.enums.SoptPart; | ||
| import org.springframework.jdbc.core.JdbcTemplate; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class AuthPartMemberCountReader { | ||
|
|
||
| private final JdbcTemplate jdbcTemplate; | ||
|
|
||
| public Map<SoptPart, Long> getCurrentGenerationPartMemberCounts(Long generation) { | ||
| String sql = """ | ||
| SELECT part, COUNT(*) AS member_count | ||
| FROM auth_prod.user_activity_histories | ||
| WHERE generation = ? | ||
| AND is_sopt = true | ||
| AND role = 'MEMBER' | ||
| AND part IN ('IOS', 'ANDROID', 'DESIGN', 'PLAN', 'SERVER', 'WEB') | ||
| GROUP BY part | ||
| """; | ||
|
|
||
| return jdbcTemplate.query(sql, rs -> { | ||
| Map<SoptPart, Long> result = new EnumMap<>(SoptPart.class); | ||
|
|
||
| while (rs.next()) { | ||
| result.put( | ||
| SoptPart.valueOf(rs.getString("part")), | ||
| rs.getLong("member_count") | ||
| ); | ||
| } | ||
|
|
||
| return result; | ||
| }, generation); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,92 @@ | ||
| package org.sopt.app.application.rank; | ||
|
|
||
| import static java.util.Map.Entry.comparingByValue; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.math.RoundingMode; | ||
| import java.util.Comparator; | ||
| import java.util.EnumMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Map.Entry; | ||
| import lombok.AccessLevel; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.sopt.app.application.soptamp.SoptampPointInfo.PartRank; | ||
| import org.sopt.app.application.soptamp.SoptampUserInfo; | ||
| import org.sopt.app.domain.enums.Part; | ||
|
|
||
| import org.sopt.app.domain.enums.SoptPart; | ||
|
|
||
| @RequiredArgsConstructor(access = AccessLevel.PUBLIC) | ||
| public class SoptampPartRankCalculator { | ||
|
|
||
| private final List<SoptampUserInfo> userInfos; | ||
| private static final int POINT_SCALE = 2; | ||
| private static final RoundingMode POINT_ROUNDING_MODE = RoundingMode.HALF_UP; | ||
| private static final BigDecimal ZERO_POINT = BigDecimal.ZERO.setScale(POINT_SCALE, POINT_ROUNDING_MODE); | ||
|
|
||
| private final PartScores partScores = new PartScores(); | ||
| private final List<SoptampUserInfo> userInfos; | ||
| private final Map<SoptPart, Long> partMemberCounts; | ||
|
|
||
| public List<PartRank> calculatePartRank() { | ||
| userInfos.forEach(this::calculatePartScore); | ||
| return Part.getPartsByReturnOrder().stream().map(part -> PartRank.builder() | ||
| PartScores partScores = new PartScores(); | ||
| userInfos.forEach(userInfo -> addPartScore(userInfo, partScores)); | ||
|
|
||
| Map<Part, BigDecimal> averagePoints = calculateAveragePoints(partScores); | ||
| Map<Part, Integer> ranks = calculateRanks(averagePoints); | ||
|
|
||
| return Part.getPartsByReturnOrder().stream() | ||
| .map(part -> PartRank.builder() | ||
|
Comment on lines
+30
to
+37
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
userInfos.stream()
.filter(userInfo -> SoptPart.toPart(userInfo.getPart()) != null)
.collect(Collectors.groupingBy(
userInfo -> SoptPart.toPart(userInfo.getPart()),
Collectors.summingInt(SoptampUserInfo::getTotalPoints)
));
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 좋은 의견 감사합니다 👀 말씀해주신 방식처럼 다만 이번에는 기존
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요게 가독성이 좀 떨어지시면 이 그룹핑 로직 자체를 메서드로 묶어서 처리할 수도 있을 것 같긴해요! |
||
| .part(part.getPartName()) | ||
| .rank(partScores.getRank(part)) | ||
| .points(partScores.getPoints(part)) | ||
| .build()).toList(); | ||
| .rank(ranks.get(part)) | ||
| .points(averagePoints.get(part)) | ||
| .build()) | ||
| .toList(); | ||
| } | ||
|
|
||
| private void addPartScore(SoptampUserInfo userInfo, PartScores partScores) { | ||
| Part part = SoptPart.toPart(userInfo.getPart()); | ||
| if (part == null) { | ||
| return; | ||
| } | ||
| partScores.addPartScore(part, userInfo.getTotalPoints()); | ||
| } | ||
|
|
||
| private void calculatePartScore(SoptampUserInfo userInfo) { | ||
| Part.getAllParts().stream() | ||
| .filter(part -> userInfo.getNickname().startsWith(part.getPartName())) | ||
| .forEach(part -> partScores.addPartScore(part, userInfo.getTotalPoints())); | ||
| private Map<Part, BigDecimal> calculateAveragePoints(PartScores partScores) { | ||
| Map<Part, BigDecimal> averagePoints = new EnumMap<>(Part.class); | ||
|
|
||
| for (Part part : Part.getPartsByReturnOrder()) { | ||
| long totalScore = partScores.getPoints(part); | ||
| long memberCount = partMemberCounts.getOrDefault(SoptPart.valueOf(part.name()), 0L); | ||
|
|
||
| BigDecimal averagePoint = memberCount == 0 ? ZERO_POINT | ||
| : BigDecimal.valueOf(totalScore) | ||
| .divide(BigDecimal.valueOf(memberCount), POINT_SCALE, POINT_ROUNDING_MODE); | ||
|
|
||
| averagePoints.put(part, averagePoint); | ||
| } | ||
|
|
||
| return averagePoints; | ||
| } | ||
|
|
||
| private Map<Part, Integer> calculateRanks(Map<Part, BigDecimal> averagePoints) { | ||
| List<Entry<Part, BigDecimal>> sortedParts = averagePoints.entrySet().stream() | ||
| .sorted(comparingByValue(Comparator.reverseOrder())) | ||
| .toList(); | ||
|
|
||
| Map<Part, Integer> ranks = new EnumMap<>(Part.class); | ||
| BigDecimal previousPoint = null; | ||
| int currentRank = 0; | ||
|
|
||
| for (int i = 0; i < sortedParts.size(); i++) { | ||
| Entry<Part, BigDecimal> entry = sortedParts.get(i); | ||
|
|
||
| if (previousPoint == null || entry.getValue().compareTo(previousPoint) != 0) { | ||
| currentRank = i + 1; | ||
| previousPoint = entry.getValue(); | ||
| } | ||
|
|
||
| ranks.put(entry.getKey(), currentRank); | ||
| } | ||
|
|
||
| return ranks; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@table(schema = "auth_prod", name = "user_activity_histories") 로 엔티티 자체를 만들어서 JPA로 사용하는 방식보다 이렇게 JdbcTemplate을 사용하는 방식이 더 빠르게 작업할 수 있으셔서 이렇게 하신건가요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 맞습니다! 이번 조회가
auth_prod.user_activity_histories에 대한 단순 집계성 쿼리이고, 최종적으로 필요한 값도 엔티티 자체가 아니라 part별 count 값이라서JPA 엔티티/리포지토리를 추가하는 것보다 JdbcTemplate이 더 적절하다고 판단했습니다.향후에 해당 값이나 auth의 데이터를 더 자주 활용하게 된다면 그때에 확장을 고려해보는 게 어떨까 해서 이렇게 작성해보았습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지금 다시 보면서 생각났는데, auth_prod를 하드 코딩할 경우 dev 환경에서 문제가 발생할 수 있겠네요,,,