Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -1,5 +1,7 @@
package kr.allcll.backend.domain.graduation.check.result;

import static java.util.stream.Collectors.groupingBy;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -56,44 +58,18 @@ public GraduationCheckResponse toResponseFromEntity(GraduationCheck check) {
// 균형교양 이수 영역 조회
Set<BalanceRequiredArea> earnedAreas = graduationCheckBalanceAreaResultRepository.findAllByUserId(userId)
.stream()
.map(
kr.allcll.backend.domain.graduation.check.result.GraduationCheckBalanceAreaResult::getBalanceRequiredArea)
.map(GraduationCheckBalanceAreaResult::getBalanceRequiredArea)
.collect(Collectors.toSet());

// 균형교양 필요 영역 수 조회
Integer requiredAreasCnt = findRequiredAreasCnt(userId);

List<GraduationCategory> categories = categoryResults.stream()
.map(result -> {
if (result.getCategoryType() == CategoryType.BALANCE_REQUIRED) {
return new GraduationCategory(
result.getMajorScope(),
result.getCategoryType(),
result.getMyCredits(),
result.getRequiredCredits(),
result.getRemainingCredits(),
earnedAreas.size(),
requiredAreasCnt,
earnedAreas,
result.getIsSatisfied()
);
}
return new GraduationCategory(
result.getMajorScope(),
result.getCategoryType(),
result.getMyCredits(),
result.getRequiredCredits(),
result.getRemainingCredits(),
null,
null,
null,
result.getIsSatisfied()
);
})
.map(result -> GraduationCategory.of(result, earnedAreas, requiredAreasCnt))
.toList();

// 2. 전필 초과 시 전선으로 학점 보정
List<GraduationCategory> adjustedCategories = adjustMajorCategories(categories);
// 2. 전필 초과 시 전선으로, 전선 초과 시 교양으로 학점 보정
List<GraduationCategory> adjustedCategories = reallocateCredits(categories);

boolean adjustedGraduatable = calculateGraduatable(
check.getCanGraduate(),
Expand Down Expand Up @@ -214,27 +190,119 @@ private Boolean isRequiredByCertRule(String ruleTypeName, GraduationCertType cer
}

private List<GraduationCategory> adjustMajorCategories(List<GraduationCategory> graduationCategories) {
// MAJOR_REQUIRED/MAJOR_ELECTIVE 별 그룹화
Map<MajorScope, List<GraduationCategory>> majorByScope = graduationCategories.stream()
.filter(this::isMajorCategory)
.collect(Collectors.groupingBy(GraduationCategory::majorScope));
return reallocateCredits(graduationCategories);
}

if (majorByScope.isEmpty()) {
private List<GraduationCategory> reallocateCredits(List<GraduationCategory> graduationCategories) {
Map<MajorScope, List<GraduationCategory>> groupedReallocateTargets = graduationCategories.stream()
.filter(this::isReallocateTarget)
.collect(groupingBy(GraduationCategory::majorScope));
Comment on lines +197 to +199
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Send double-major overflow to the shared GENERAL category

If a double-major student needs surplus 복필/복선 credits from the secondary major to satisfy the general-credit requirement, grouping reallocation targets by majorScope makes that overflow land in a synthetic GENERAL(SECONDARY) bucket instead of the real shared GENERAL(PRIMARY) bucket. GraduationChecker.buildDoubleMajorCriteria() only loads one set of non-major criteria, and CompletedCourseDto.determineMajorScope() marks non-major courses as PRIMARY, so the primary GENERAL row is the only one that should absorb this carry. In that flow the response can still show GENERAL(PRIMARY) short and keep canGraduate=false even though policy says those extra major credits should count toward general credits.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

복수전공...을 고려했어야하는건가


if (groupedReallocateTargets.isEmpty()) {
return graduationCategories;
}

// 비전공 카테고리 추가
List<GraduationCategory> result = new ArrayList<>();
List<GraduationCategory> finishReallocate = new ArrayList<>();
for (GraduationCategory category : graduationCategories) {
if (!isMajorCategory(category)) {
result.add(category);
if (!isReallocateTarget(category)) {
finishReallocate.add(category);
}
}

// scope(주전공/복수전공)별로 전필/전선 찾고 학점 adjust
adjustMajorCreditsByScope(majorByScope, result);
adjustReallocateTargetCredits(groupedReallocateTargets, finishReallocate);

return result;
return finishReallocate;
}

private void adjustReallocateTargetCredits(
Map<MajorScope, List<GraduationCategory>> groupedReallocateTargets,
List<GraduationCategory> finishReallocate
) {
for (Map.Entry<MajorScope, List<GraduationCategory>> entry : groupedReallocateTargets.entrySet()) {
MajorScope majorScope = entry.getKey();
List<GraduationCategory> reallocateCategory = entry.getValue();

GraduationCategory majorRequired = findCategory(reallocateCategory, CategoryType.MAJOR_REQUIRED);
GraduationCategory majorElective = findCategory(reallocateCategory, CategoryType.MAJOR_ELECTIVE);
GraduationCategory general = findCategory(reallocateCategory, CategoryType.GENERAL);

boolean hasMajorRequired = hasCategoryType(majorRequired);
boolean hasMajorElective = hasCategoryType(majorElective);
boolean hasGeneral = hasCategoryType(general);

if (!hasMajorRequired) {
majorRequired = GraduationCategory.createEmptyGraduationCategory(
majorScope,
CategoryType.MAJOR_REQUIRED
);
}
if (!hasMajorElective) {
majorElective = GraduationCategory.createEmptyGraduationCategory(
majorScope,
CategoryType.MAJOR_ELECTIVE
);
}
if (!hasGeneral) {
general = GraduationCategory.createEmptyGraduationCategory(
majorScope,
CategoryType.GENERAL
);
}
Comment on lines +245 to +250
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

저도 이 부분 코멘트 남깁니다!

GENERAL은 사용자의 전공 유형에 따라 달라지는 이수구분은 아니라고 이해하고 있습니다.
기준 데이터 기준으로 보면, 전공 영역 이수구분인 전필/전선/복필/복선은 MajorScope에 따라 구분되지만, 교양 영역은 전공 유형과 무관하게 PRIMARY로 적재되어 있습니다!
그래서 현재 코드처럼 GENERAL까지 MajorScope 기준으로 함께 나누어 처리하는 방식은 조금 우려가 됩니당

  1. 단일 전공일 경우, 문제가 없다고 판단됩니다!
  2. 복수 전공일 경우, 우려 되는 지점이 있습니다.

EX)
A. 주전공에서 전필 -> 전선 -> 교양 순으로 학점 이월이 발생하는 경우
B. 복수전공에서 복필 -> 복선 -> 교양 순으로 학점 이월이 발생하는 경우

즉, 주전공 및 복수전공에서 모두 교양으로의 이월 학점이 존재할 경우입니다!

현재처럼 MajorScope별로 나누어 계산하면, 최종적으로 교양 학점이 하나의 공통 GENERAL(PRIMARY)로 합산되지 않고 scope별로 따로 처리될 가능성이 있어 보여요.
그렇게 되면 실제로는 교양 부족분을 메워야 하는 학점이 분산되어 들어가거나, 의도하지 않은 fallback category가 생길 수도 있을 것 같습니다.

개선 방향을 생각해보았는데, 만약 위에 언급드린 내용이 문제가 된다면!!!
->
전필/전선(복필/복선)까지만 기존대로 scope별로 계산한 뒤,
각 scope에서 최종적으로 남는 초과 학점을 마지막에 공통 GENERAL(PRIMARY)로 합산하는 방식으로 개선해볼 수 있을 것 같습니다!!!

모두들 어떻게 생각하시는지 궁금합니당

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

아 헐 그러네~~~~~~~~~~~~~~~
한 사이클에 전필 -> 전선 -> 교양 보다는 전필 -> 전선 / 전선 -> 교양 이렇게 사이클 끊는게 낫겠다싶네요
그런데 하나 더 궁금한게 있는데, 기존에 GENERAL은 어떤식으로 됐던건가요? 제 성적표에 '교양' 이 있는데, 응답에 GENERAL 필드가 없었던 것 같아서요


double adjustedMajorRequiredCredits = Math.min(
majorRequired.earnedCredits(),
majorRequired.requiredCredits()
);
double overMajorRequiredCredits = Math.max(
majorRequired.earnedCredits() - majorRequired.requiredCredits(),
0
);

double electiveWithCarry = majorElective.earnedCredits() + overMajorRequiredCredits;
double adjustedMajorElectiveCredits;
double overMajorElectiveCredits;
if (hasMajorElective) {
adjustedMajorElectiveCredits = Math.min(
electiveWithCarry,
majorElective.requiredCredits()
);
overMajorElectiveCredits = Math.max(
electiveWithCarry - majorElective.requiredCredits(),
0
);
} else {
adjustedMajorElectiveCredits = electiveWithCarry;
overMajorElectiveCredits = 0;
}

double adjustedGeneralCredits = general.earnedCredits() + overMajorElectiveCredits;

boolean shouldAddMajorRequired = isShouldAdd(hasMajorRequired, adjustedMajorRequiredCredits);
boolean shouldAddMajorElective = isShouldAdd(hasMajorElective, overMajorRequiredCredits);
boolean shouldAddGeneral = isShouldAdd(hasGeneral, overMajorElectiveCredits);

if (shouldAddMajorRequired) {
finishReallocate.add(createAdjustedMajorCategory(majorRequired, adjustedMajorRequiredCredits));
}
if (shouldAddMajorElective) {
finishReallocate.add(createAdjustedMajorCategory(majorElective, adjustedMajorElectiveCredits));
}
if (shouldAddGeneral) {
finishReallocate.add(createAdjustedMajorCategory(general, adjustedGeneralCredits));
}
}
}

private static boolean isShouldAdd(boolean hasCategory, double adjustedCredits) {
return hasCategory || adjustedCredits > 0;
}

private static boolean hasCategoryType(GraduationCategory category) {
return category != null;
}

private boolean isReallocateTarget(GraduationCategory graduationCategory) {
return graduationCategory.categoryType().isReallocateTarget();
}

private boolean isMajorCategory(GraduationCategory category) {
Expand Down Expand Up @@ -281,6 +349,16 @@ private boolean calculateGraduatable(boolean originalGraduatable, List<Graduatio
return originalGraduatable && majorSatisfied;
}

private GraduationCategory findCategory(
List<GraduationCategory> categories,
CategoryType categoryType
) {
return categories.stream()
.filter(category -> category.categoryType() == categoryType)
.findFirst()
.orElse(null);
}

private GraduationCategory findMajorRequired(List<GraduationCategory> categories) {
for (GraduationCategory category : categories) {
if (CategoryType.MAJOR_REQUIRED.equals(category.categoryType())) {
Expand All @@ -304,7 +382,7 @@ private GraduationCategory createAdjustedMajorCategory(
double adjustedCredits
) {
double remainingCredits = Math.max(0, graduationCategory.requiredCredits() - adjustedCredits);
boolean isSatisfied = adjustedCredits >= graduationCategory.requiredCredits();
boolean isSatisfied = isSatisfiedCreditCriterion(graduationCategory, adjustedCredits);

return new GraduationCategory(
graduationCategory.majorScope(),
Expand All @@ -319,6 +397,10 @@ private GraduationCategory createAdjustedMajorCategory(
);
}

private static boolean isSatisfiedCreditCriterion(GraduationCategory graduationCategory, double adjustedCredits) {
return adjustedCredits >= graduationCategory.requiredCredits();
}

private EnglishTargetType parseEnglishTargetType(GraduationCertCriteriaResponse response) {
String typeName = response.criteriaTarget().englishTargetType();
return EnglishTargetType.valueOf(typeName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Set;
import kr.allcll.backend.domain.graduation.MajorScope;
import kr.allcll.backend.domain.graduation.balance.BalanceRequiredArea;
import kr.allcll.backend.domain.graduation.check.result.GraduationCheckCategoryResult;
import kr.allcll.backend.domain.graduation.credit.CategoryType;

public record GraduationCategory(
Expand All @@ -17,4 +18,56 @@ public record GraduationCategory(
Boolean satisfied
) {

public static GraduationCategory of(
GraduationCheckCategoryResult categoryResult,
Set<BalanceRequiredArea> earnedAreas,
Integer requiredAreasCnt
) {
if (categoryResult.getCategoryType() == CategoryType.BALANCE_REQUIRED) {
return GraduationCategory.ofBalance(categoryResult, earnedAreas, requiredAreasCnt);
}
return new GraduationCategory(
categoryResult.getMajorScope(),
categoryResult.getCategoryType(),
categoryResult.getMyCredits(),
categoryResult.getRequiredCredits(),
categoryResult.getRemainingCredits(),
null,
null,
null,
categoryResult.getIsSatisfied()
);
}

public static GraduationCategory createEmptyGraduationCategory(MajorScope majorScope, CategoryType categoryType) {
return new GraduationCategory(
majorScope,
categoryType,
0.0,
0,
0.0,
null,
null,
null,
true
);
}

private static GraduationCategory ofBalance(
GraduationCheckCategoryResult balanceCategory,
Set<BalanceRequiredArea> earnedAreas,
Integer requiredAreasCnt
) {
return new GraduationCategory(
balanceCategory.getMajorScope(),
balanceCategory.getCategoryType(),
balanceCategory.getMyCredits(),
balanceCategory.getRequiredCredits(),
balanceCategory.getRemainingCredits(),
earnedAreas.size(),
requiredAreasCnt,
earnedAreas,
balanceCategory.getIsSatisfied()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,20 @@ public enum CategoryType { // 이수구분
MAJOR_REQUIRED,
MAJOR_ELECTIVE
);
private static final Set<CategoryType> REALLOCATE_TARGET_CATEGORIES = EnumSet.of(
MAJOR_REQUIRED,
MAJOR_ELECTIVE,
GENERAL
);

public boolean isMajorCategory() {
return MAJOR_CATEGORIES.contains(this);
}

public boolean isReallocateTarget() {
return REALLOCATE_TARGET_CATEGORIES.contains(this);
}

public boolean isNonMajorCategory() {
return !isMajorCategory();
}
Expand Down Expand Up @@ -64,7 +73,7 @@ private static boolean isMajorBasic(CategoryType categoryType) {
}

private static CategoryType normalizeMajorBasic(int admissionYear) {
if (shouldConvertMajorBasicAsAcademicBasic(admissionYear)){
if (shouldConvertMajorBasicAsAcademicBasic(admissionYear)) {
return ACADEMIC_BASIC;
}
return MAJOR_BASIC;
Expand Down
Loading
Loading