Skip to content

Commit

Permalink
Merge pull request #63 from KUSITMS-MOAMOA/feat/#62
Browse files Browse the repository at this point in the history
[Feat/#62] 역량 분석 재요청 기능 구현
  • Loading branch information
oosedus authored Nov 7, 2024
2 parents 79cd297 + dafc86f commit 482fd55
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
@Getter
@AllArgsConstructor
public enum AnalysisSuccessStatus implements BaseSuccessStatus {
ANALYSIS_GET_SUCCESS(HttpStatus.CREATED, "S502", "역량별 경험 조회가 성공적으로 완료되었습니다."),
ANALYSIS_POST_SUCCESS(HttpStatus.CREATED, "S501", "역량별 경험 분석이 성공적으로 완료되었습니다."),
ANALYSIS_GET_SUCCESS(HttpStatus.OK, "S502", "역량별 경험 조회가 성공적으로 완료되었습니다."),
ANALYSIS_UPDATE_SUCCESS(HttpStatus.OK, "S701", "역량별 경험 수정이 성공적으로 완료되었습니다."),
ANALYSIS_DELETE_SUCCESS(HttpStatus.OK, "S702", "역량별 경험 삭제가 성공적으로 완료되었습니다."),
KEYWORD_LIST_GET_SUCCESS(HttpStatus.OK, "S505", "역량 키워드 리스트 조회가 성공적으로 완료되었습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
public class AnalysisController {
private final AnalysisService analysisService;

@PostMapping("/{recordId}")
public ResponseEntity<ApiResponse<AnalysisResponse.AnalysisDto>> postAnalysis(
@UserId Long userId,
@PathVariable(name = "recordId") Long recordId
) {
AnalysisResponse.AnalysisDto analysisResponse = analysisService.postAnalysis(userId, recordId);
return ApiResponse.success(AnalysisSuccessStatus.ANALYSIS_POST_SUCCESS, analysisResponse);
}

@GetMapping("/{analysisId}")
public ResponseEntity<ApiResponse<AnalysisResponse.AnalysisDto>> getAnalysis(
@UserId Long userId,
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/corecord/dev/domain/analysis/entity/Analysis.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class Analysis extends BaseEntity {
private String comment;

@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "record_id", nullable = false)
@JoinColumn(name = "record_id", nullable = true)
private Record record;

@BatchSize(size = 3)
Expand All @@ -41,4 +41,17 @@ public void updateContent(String content) {
if (content != null && !content.isEmpty())
this.content = content;
}

public void updateComment(String comment) {
if (!comment.isEmpty()) {
this.comment = comment;
}
}

public void addAbility(Ability ability) {
if (ability != null) {
this.abilityList.add(ability);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
@AllArgsConstructor
public enum AnalysisErrorStatus implements BaseErrorStatus {
INVALID_KEYWORD(HttpStatus.BAD_REQUEST, "E400_INVALID_KEYWORD", "역량 분석에 존재하지 않는 키워드입니다."),
OVERFLOW_ANALYSIS_CONTENT(HttpStatus.BAD_REQUEST, "E0400_OVERFLOW_CONTENT", "경험 기록 내용은 500자 이내여야 합니다."),
OVERFLOW_ANALYSIS_COMMENT(HttpStatus.BAD_REQUEST, "E0400_OVERFLOW_COMMENT", "경험 기록 코멘트는 200자 이내여야 합니다."),
OVERFLOW_ANALYSIS_KEYWORD_CONTENT(HttpStatus.BAD_REQUEST, "E0400_OVERFLOW_KEYWORD_CONTENT", "경험 기록 키워드별 내용은 200자 이내여야 합니다."),
USER_ANALYSIS_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "E401_ANALYSIS_UNAUTHORIZED", "유저가 역량 분석에 대한 권한이 없습니다."),
ANALYSIS_NOT_FOUND(HttpStatus.NOT_FOUND, "E0404_ANALYSIS", "존재하지 않는 역량 분석입니다."),
INVALID_ABILITY_ANALYSIS(HttpStatus.BAD_REQUEST, "E400_INVALID_ANALYSIS", "역량 분석 데이터 파싱 중 오류가 발생했습니다.")
INVALID_ABILITY_ANALYSIS(HttpStatus.BAD_REQUEST, "E400_INVALID_ANALYSIS", "역량 분석 데이터 파싱 중 오류가 발생했습니다."),
;

private final HttpStatus httpStatus;
Expand Down
164 changes: 147 additions & 17 deletions src/main/java/corecord/dev/domain/analysis/service/AnalysisService.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
import corecord.dev.domain.record.entity.Record;
import corecord.dev.domain.record.exception.enums.RecordErrorStatus;
import corecord.dev.domain.record.exception.model.RecordException;
import corecord.dev.domain.record.repository.RecordRepository;
import corecord.dev.domain.user.entity.User;
import corecord.dev.domain.user.repository.UserRepository;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -36,40 +38,85 @@ public class AnalysisService {
private final AnalysisRepository analysisRepository;
private final AbilityRepository abilityRepository;
private final UserRepository userRepository;
private final RecordRepository recordRepository;
private final ClovaService clovaService;
private final EntityManager entityManager;


/*
* CLOVA STUDIO룰 활용해 역량 분석 객체를 생성 후 반환
* @param record
* @param user
* @return
*/
@Transactional
public void createAnalysis(Record record, User user) {
public Analysis createAnalysis(Record record, User user) {

// MEMO 경험 기록이라면, CLOVA STUDIO를 이용해 요약 진행
String content = record.isMemoType()
? generateMemoSummary(record.getContent())
: record.getContent();
String content = getRecordContent(record);

// CLOVA STUDIO API 호출
String apiResponse = generateAbilityAnalysis(content);
AnalysisAiResponse response = parseAnalysisAiResponse(apiResponse);
AnalysisAiResponse response = callClovaStudioApi(content);

// Analysis 객체 생성 및 저장
Analysis analysis = AnalysisConverter.toAnalysis(content, response.getComment(), record);
analysisRepository.save(analysis);

// Ability 객체 생성 및 저장
int abilityCount = 0;
for (Map.Entry<String, String> entry : response.getKeywordList().entrySet()) {
Keyword keyword = Keyword.getName(entry.getKey());
parseAndSaveAbilities(response.getKeywordList(), analysis, user);

if (keyword == null) continue;
return analysis;
}

Ability ability = AnalysisConverter.toAbility(keyword, entry.getValue(), analysis, user);
abilityRepository.save(ability);
abilityCount++;
}
/*
* CLOVA STUDIO를 활용해 역량 분석을 재수행함 Analysis 객체 데이터 교체 후 반환
* @param record
* @param user
* @return
*/
@Transactional
public Analysis recreateAnalysis(Record record, User user) {
Analysis analysis = record.getAnalysis();

if (abilityCount < 1 || abilityCount > 3) {
throw new AnalysisException(AnalysisErrorStatus.INVALID_ABILITY_ANALYSIS);
}
// MEMO 경험 기록이라면, CLOVA STUDIO를 이용해 요약 진행
String content = getRecordContent(record);

// CLOVA STUDIO API 호출
AnalysisAiResponse response = callClovaStudioApi(content);

// Analysis 객체 수정
analysis.updateContent(content);
analysis.updateComment(response.getComment());

// 기존 Ability 객체 삭제
deleteOriginAbilityList(analysis);

// Ability 객체 생성 및 저장
parseAndSaveAbilities(response.getKeywordList(), analysis, user);

return analysis;
}

/*
* recordId를 받아, 해당 경험 기록에 대한 역량 분석을 수행 후 생성된 역량 분석 상세 정보를 반환
* @param userId
* @param recordId
* @return
*/
@Transactional
public AnalysisResponse.AnalysisDto postAnalysis(Long userId, Long recordId) {
User user = findUserById(userId);
Record record = findRecordById(recordId);

// User-Record 권한 유효성 검증
validIsUserAuthorizedForRecord(user, record);

// 역량 분석 API 호출
Analysis analysis = record.getAnalysis() == null ?
createAnalysis(record, user) :
recreateAnalysis(record, user); // 기존 Analysis 객체가 있을 경우 교체

return AnalysisConverter.toAnalysisDto(analysis);
}

/*
Expand Down Expand Up @@ -158,6 +205,47 @@ public AnalysisResponse.GraphDto getKeywordGraph(Long userId) {

return AnalysisConverter.toGraphDto(keywordGraph);
}

// CLOVA STUDIO를 통해 얻은 키워드 정보 파싱
@Transactional
public void parseAndSaveAbilities(Map<String, String> keywordList, Analysis analysis, User user) {
int abilityCount = 0;
for (Map.Entry<String, String> entry : keywordList.entrySet()) {
Keyword keyword = Keyword.getName(entry.getKey());

if (keyword == null) continue;

Ability ability = AnalysisConverter.toAbility(keyword, entry.getValue(), analysis, user);
abilityRepository.save(ability);
analysis.addAbility(ability);
abilityCount++;
}

if (abilityCount < 1 || abilityCount > 3) {
throw new AnalysisException(AnalysisErrorStatus.INVALID_ABILITY_ANALYSIS);
}
}

private AnalysisAiResponse callClovaStudioApi(String content) {
String apiResponse = generateAbilityAnalysis(content);
AnalysisAiResponse response = parseAnalysisAiResponse(apiResponse);

// 글자 수 validation
validAnalysisCommentLength(response.getComment());
validAnalysisKeywordContentLength(response.getKeywordList());

return response;
}

private String getRecordContent(Record record) {
String content = record.isMemoType()
? generateMemoSummary(record.getContent())
: record.getContent();

validAnalysisContentLength(content);

return content;
}

private String generateAbilityAnalysis(String content) {
ClovaRequest clovaRequest = ClovaRequest.createAnalysisRequest(content);
Expand All @@ -174,6 +262,30 @@ private void validIsUserAuthorizedForAnalysis(User user, Analysis analysis) {
throw new RecordException(RecordErrorStatus.USER_RECORD_UNAUTHORIZED);
}

private void validIsUserAuthorizedForRecord(User user, Record record) {
if (!record.getUser().equals(user))
throw new RecordException(RecordErrorStatus.USER_RECORD_UNAUTHORIZED);
}

private void validAnalysisContentLength(String content) {
if (content.isEmpty() || content.length() > 500)
throw new AnalysisException(AnalysisErrorStatus.OVERFLOW_ANALYSIS_CONTENT);
}

private void validAnalysisCommentLength(String comment) {
if (comment.isEmpty() || comment.length() > 200)
throw new AnalysisException(AnalysisErrorStatus.OVERFLOW_ANALYSIS_COMMENT);
}

private void validAnalysisKeywordContentLength(Map<String, String> keywordList) {
for (Map.Entry<String, String> entry : keywordList.entrySet()) {
String keyContent = entry.getValue();

if (keyContent.isEmpty() || keyContent.length() > 200)
throw new AnalysisException(AnalysisErrorStatus.OVERFLOW_ANALYSIS_KEYWORD_CONTENT);
}
}

private void updateAbilityContents(Analysis analysis, Map<String, String> abilityMap) {
abilityMap.forEach((keyword, content) -> {
Keyword key = Keyword.getName(keyword);
Expand All @@ -199,6 +311,19 @@ private AnalysisAiResponse parseAnalysisAiResponse(String aiResponse) {
}
}

private void deleteOriginAbilityList(Analysis analysis) {
List<Ability> abilityList = analysis.getAbilityList();

if (!abilityList.isEmpty()) {
// 연관된 abilities 삭제
abilityRepository.deleteAll(abilityList);

// Analysis에서 abilities 리스트 비우기
analysis.getAbilityList().clear();
entityManager.flush();
}
}

private List<String> findKeywordList(User user) {
return analysisRepository.getKeywordList(user).stream()
.map(Keyword::getValue)
Expand All @@ -215,6 +340,11 @@ private Analysis findAnalysisById(Long analysisId) {
.orElseThrow(() -> new AnalysisException(AnalysisErrorStatus.ANALYSIS_NOT_FOUND));
}

private Record findRecordById(Long recordId) {
return recordRepository.findRecordById(recordId)
.orElseThrow(() -> new RecordException(RecordErrorStatus.RECORD_NOT_FOUND));
}

private List<AnalysisResponse.KeywordStateDto> findKeywordGraph(User user) {
return abilityRepository.findKeywordStateDtoList(user);
}
Expand Down

0 comments on commit 482fd55

Please sign in to comment.