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(#95): 캐릭터 생성 #101

Merged
merged 22 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7c44b2c
[#95] refactor: 불필요한 엔티티 삭제
kimyu0218 Feb 16, 2025
a375a77
[#95] refactor: 불필요한 객체 삭제 및 일급 컬렉션 정의
kimyu0218 Feb 16, 2025
9b731d9
[#95] refactor: final 제거
kimyu0218 Feb 16, 2025
658f309
[#95] refactor: ValueReports 추가
kimyu0218 Feb 16, 2025
85bf864
[#95] feat: 캐릭터 생성 로직 작성
kimyu0218 Feb 16, 2025
17bd482
[#95] refactor: 불필요한 객체 삭제
kimyu0218 Feb 16, 2025
830c4cf
[#95] feat: 테이블 fetch 추가 및 이름 수정
kimyu0218 Feb 16, 2025
e93a695
[#95] feat: 이벤트 dto 작성
kimyu0218 Feb 16, 2025
709493b
[#95] feat: 아직 완성되지 않은 캐릭터도 리스트로 전달
kimyu0218 Feb 16, 2025
c9e7767
[#95] refactor: 불필요한 객체 삭제
kimyu0218 Feb 16, 2025
85a1eaa
[#95] feat: 캐릭터 생성 이벤트 호출
kimyu0218 Feb 16, 2025
f1d9b9d
[#95] fix: rebase 중 추가된 코드 삭제
kimyu0218 Feb 16, 2025
d10ff59
[#95] chore: 누락된 ddl 추가
kimyu0218 Feb 16, 2025
2b95853
[#95] chore: ddl 업데이트
kimyu0218 Feb 16, 2025
687964c
[#95] feat: 캐릭터 주요 특성, 강점, 단점 추가
pythonstrup Feb 17, 2025
0854646
[#95] chore: 캐릭터 주요 특성, 강점, 단점 sql 스키마
pythonstrup Feb 17, 2025
8af2f98
[#95] test: CharacterTraits 테스트 추가
pythonstrup Feb 17, 2025
60be7a2
[#95] feat: 캐릭터 생성 및 업데이트 로직
pythonstrup Feb 17, 2025
32f9496
[#95] test: 온보딩 캐릭터 생성 테스트 추가
pythonstrup Feb 17, 2025
b9ddddf
[#95] feat: 캐릭터 정책 변경에 따른 캐릭터 목록 조회 수정
pythonstrup Feb 17, 2025
25fac90
[#95] feat: Characters 네이밍 수정 (=> ScoreEvaluator)
pythonstrup Feb 17, 2025
3108ac1
[#95] chore: 캐릭터 목록 조회 API 스펙 수정
pythonstrup Feb 17, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public abstract class BaseEntity {
private Long id;

@Override
public final boolean equals(Object o) {
public boolean equals(Object o) {
if (this == o) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.OneToMany;
import java.time.LocalDate;
import java.util.List;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
Expand Down Expand Up @@ -38,26 +39,40 @@ public class CharacterRecord extends BaseTimeEntity {
@JoinColumn(name = "bundle_id")
private SurveyBundle surveyBundle;

@OneToOne(mappedBy = "characterRecord", cascade = CascadeType.ALL, orphanRemoval = true)
private CharacterValueReport characterValueReport;
@OneToMany(mappedBy = "characterRecord", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<ValueReport> valueReports;

@Builder
private CharacterRecord(
final String characterNo,
final ValueCharacter valueCharacter,
final LocalDate startDate,
final LocalDate endDate,
final Member member,
final SurveyBundle surveyBundle) {
final SurveyBundle surveyBundle,
final List<ValueReport> valueReports) {
this.characterNo = characterNo;
this.valueCharacter = valueCharacter;
this.startDate = startDate;
this.endDate = endDate;
this.member = member;
this.surveyBundle = surveyBundle;

if (valueReports != null) {
this.valueReports = valueReports;
this.valueReports.forEach(it -> it.updateCharacterRecord(this));
}
}

public void updateCharacterValueReport(final CharacterValueReport characterValueReport) {
this.characterValueReport = characterValueReport;
@Builder
public static CharacterRecord of(
final String characterNo,
final ValueCharacter valueCharacter,
final LocalDate startDate,
final LocalDate endDate,
final Member member,
final SurveyBundle surveyBundle,
final List<ValueReport> valueReports) {
return new CharacterRecord(
characterNo, valueCharacter, startDate, endDate, member, surveyBundle, valueReports);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package org.nexters.jaknaesocore.domain.character.model;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.nexters.jaknaesocore.common.model.ScaledBigDecimal;
import org.nexters.jaknaesocore.domain.member.model.Member;
import org.nexters.jaknaesocore.domain.survey.model.Keyword;
import org.nexters.jaknaesocore.domain.survey.model.KeywordMetrics;
import org.nexters.jaknaesocore.domain.survey.model.KeywordScore;
import org.nexters.jaknaesocore.domain.survey.model.SurveyBundle;
import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class Characters {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분이 말씀하신 CharacterProvider입니다.

  • 처음엔 이렇게 작성했다가 여기서 ValueReport도 생성되는 게 이상해서.이름을 변경했습니다.
  • 근데 생각해보니 Character와 완벽하게 일치하는 엔티티가 없어서 CharacterProvider로 해도 문제 없을 것 같네요 😅


private final Long characterNo;
private final Member member;
private final SurveyBundle bundle;
private final List<KeywordScore> scores;
private final List<SurveySubmission> submissions;
private final Map<Keyword, ValueCharacter> valueCharacters;

@Builder
public static Characters of(
final Long characterNo,
final Member member,
final SurveyBundle bundle,
final List<KeywordScore> scores,
final List<SurveySubmission> submissions,
final Map<Keyword, ValueCharacter> valueCharacters) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

캐릭터 생성에 필요한 파라미터입니다.

  • scores: 번들 내부 점수 리스트
  • submissions: 회원의 설문 제출 이력
  • valueCharacters: 캐릭터 목록

return new Characters(characterNo, member, bundle, scores, submissions, valueCharacters);
}

public CharacterRecord provideCharacterRecord() {
final List<ValueReport> valueReports = provideValueReport();
final ValueCharacter valueCharacter = findTopValueCharacter(valueReports);
return CharacterRecord.builder()
.characterNo("TODO 수정")
.valueCharacter(valueCharacter)
.valueReports(valueReports)
.member(member)
.surveyBundle(bundle)
.valueReports(valueReports)
.startDate(bundle.getCreatedAt().toLocalDate())
.endDate(LocalDate.now())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 두 부분은 번들내부의 제출 이력에서 submittedAt을 꺼내서 사용해야될 것 같아요 !!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아...! 번들 생성은 미리 해놓는 거였군요.. 😂

.build();
}

private List<ValueReport> provideValueReport() {
final Map<Keyword, KeywordMetrics> metricsMap = calculateKeywordMetrics(scores);
final Map<Keyword, BigDecimal> weightMap = calculateKeywordWeights(metricsMap);

return ValueReports.report(weightMap, metricsMap, submissions);
}

private Map<Keyword, KeywordMetrics> calculateKeywordMetrics(final List<KeywordScore> scores) {
return scores.stream()
.collect(Collectors.groupingBy(KeywordScore::getKeyword, Collectors.toList()))
.entrySet()
.stream()
.collect(
Collectors.toMap(Map.Entry::getKey, entry -> KeywordMetrics.create(entry.getValue())));
}

private Map<Keyword, BigDecimal> calculateKeywordWeights(
final Map<Keyword, KeywordMetrics> metricsMap) {
int keywordCnt = metricsMap.size();
ScaledBigDecimal sumPerKeyword =
ScaledBigDecimal.of(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(keywordCnt));

Map<Keyword, BigDecimal> weightMap = new HashMap<>();
metricsMap.forEach(
(k, v) -> {
var sum = v.getPositive().subtract(v.getNegative());
weightMap.put(k, sumPerKeyword.divide(sum).getValue());
});
return weightMap;
}

private ValueCharacter findTopValueCharacter(final List<ValueReport> valueReports) {
final ValueReport topReport =
valueReports.stream()
.max(Comparator.comparing(ValueReport::getPercentage))
.orElseThrow(() -> new IllegalStateException("ValueReport가 비어 있습니다."));

return valueCharacters.get(topReport.getKeyword());
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
package org.nexters.jaknaesocore.domain.character.model;

import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.math.BigDecimal;
import java.util.Objects;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.nexters.jaknaesocore.common.model.BaseTimeEntity;
import org.nexters.jaknaesocore.common.model.ScaledBigDecimal;
import org.nexters.jaknaesocore.domain.survey.model.Keyword;

@EqualsAndHashCode
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Embeddable
public class ValueReport {
@Entity
public class ValueReport extends BaseTimeEntity {

@Enumerated(EnumType.STRING)
private Keyword keyword;

private BigDecimal percentage;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "character_record_id")
private CharacterRecord characterRecord;

private ValueReport(final Keyword keyword, final BigDecimal percentage) {
this.keyword = keyword;
this.percentage = percentage;
Expand All @@ -31,6 +38,27 @@ public static ValueReport of(final Keyword keyword, final ScaledBigDecimal perce
return new ValueReport(keyword, percentage.getValue());
}

public void updateCharacterRecord(final CharacterRecord characterRecord) {
this.characterRecord = characterRecord;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ValueReport that = (ValueReport) o;
return keyword == that.keyword && Objects.equals(percentage, that.percentage);
}
Comment on lines +45 to +55
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entity 객체 동일성을 키워드랑 퍼센티지로 계산하는 이유가 무엇인가요? 보통 entity는 식별자를 통해 객체 동일성을 판단해서 여쭤봅니다. 만약 이 비교가 필요한 로직이 있다면 VO를 따로 만드는 게 좋을 것 같습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 Entity에 EqualsAndHashcode를 재정의할 것이라면 Id와 유니크한 필드만 재정의해야된다고 생각합니다 !


@Override
public int hashCode() {
return Objects.hash(keyword, percentage);
}

@Override
public String toString() {
return "ValueReport{" + "keyword=" + keyword + ",percentage=" + percentage + "}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,27 @@
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.Getter;
import org.nexters.jaknaesocore.common.model.ScaledBigDecimal;
import org.nexters.jaknaesocore.domain.survey.model.Keyword;
import org.nexters.jaknaesocore.domain.survey.model.KeywordMetrics;
import org.nexters.jaknaesocore.domain.survey.model.KeywordScore;
import org.nexters.jaknaesocore.domain.survey.model.KeywordScoreNormalizer;
import org.nexters.jaknaesocore.domain.survey.model.SurveySubmission;

@Getter
public class ValueReports {

private static final ScaledBigDecimal PERCENTAGE100 =
ScaledBigDecimal.of(BigDecimal.valueOf(100));

private List<ValueReport> reports;

private ValueReports(final List<ValueReport> reports) {
this.reports = reports;
}

public static ValueReports of(
public static List<ValueReport> report(
final Map<Keyword, BigDecimal> weights,
final Map<Keyword, KeywordMetrics> metrics,
final List<SurveySubmission> submissions) {
Map<Keyword, BigDecimal> percentage = getKeywordPercentage(weights, metrics, submissions);

List<ValueReport> reports =
percentage.entrySet().stream()
.map(it -> ValueReport.of(it.getKey(), ScaledBigDecimal.of(it.getValue())))
.collect(Collectors.toList());
return new ValueReports(reports);
return percentage.entrySet().stream()
.map(it -> ValueReport.of(it.getKey(), ScaledBigDecimal.of(it.getValue())))
.collect(Collectors.toList());
}

private static Map<Keyword, BigDecimal> getKeywordPercentage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ List<CharacterRecord> findByMemberIdAndDeletedAtIsNullWithMemberAndSurveyBundle(
final Long memberId);

@Query(
"SELECT c FROM CharacterRecord c JOIN FETCH c.member cm "
+ "WHERE c.id = :id AND cm.id = :memberId AND c.deletedAt IS NULL")
Optional<CharacterRecord> existsByIdAndMemberIdAndDeletedAtIsNullWithMember(
final Long id, final Long memberId);
"SELECT c FROM CharacterRecord c JOIN FETCH c.valueReports cr "
+ "WHERE c.id = :id AND c.deletedAt IS NULL")
Optional<CharacterRecord> findByIdAndDeletedAtIsNullWithValueReports(final Long id);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.nexters.jaknaesocore.domain.character.repository;

import org.nexters.jaknaesocore.domain.character.model.ValueReport;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ValueReportRepository extends JpaRepository<ValueReport, Long> {}
Loading