mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 09:06:24 +00:00
Chore: 회의록 상세조회 API - db 변경사항 적용
This commit is contained in:
parent
d0aa6353da
commit
7e2094bcbc
File diff suppressed because it is too large
Load Diff
@ -45,21 +45,6 @@ public class AgendaSection {
|
||||
*/
|
||||
private String aiSummaryShort;
|
||||
|
||||
/**
|
||||
* 논의사항 (JSON 형태로 저장)
|
||||
*/
|
||||
private String discussions;
|
||||
|
||||
/**
|
||||
* 결정사항 (JSON 형태로 저장)
|
||||
*/
|
||||
private String decisions;
|
||||
|
||||
/**
|
||||
* 의견 (JSON 형태로 저장)
|
||||
*/
|
||||
private String opinions;
|
||||
|
||||
/**
|
||||
* 보류사항 (JSON 형태로 저장)
|
||||
*/
|
||||
@ -70,6 +55,11 @@ public class AgendaSection {
|
||||
*/
|
||||
private String todos;
|
||||
|
||||
/**
|
||||
* 요약 (안건 내용)
|
||||
*/
|
||||
private String summary;
|
||||
|
||||
/**
|
||||
* 생성일시
|
||||
*/
|
||||
|
||||
@ -81,6 +81,11 @@ public class Minutes {
|
||||
*/
|
||||
private LocalDateTime finalizedAt;
|
||||
|
||||
/**
|
||||
* 결정사항
|
||||
*/
|
||||
private String decisions;
|
||||
|
||||
/**
|
||||
* 회의록 확정 가능 여부 검증
|
||||
*
|
||||
|
||||
@ -136,6 +136,11 @@ public class MinutesDTO {
|
||||
*/
|
||||
private final List<TodoInfo> todos;
|
||||
|
||||
/**
|
||||
* 결정사항
|
||||
*/
|
||||
private final String decisions;
|
||||
|
||||
// 중첩 클래스들
|
||||
|
||||
@Getter
|
||||
|
||||
@ -371,6 +371,10 @@ public class MinutesService implements
|
||||
log.warn("섹션 정보 변환 실패 - minutesId: {}", minutes.getMinutesId(), e);
|
||||
}
|
||||
|
||||
// decisions 값 로깅
|
||||
log.info("Minutes decisions 값 확인 - minutesId: {}, decisions: {}",
|
||||
minutes.getMinutesId(), minutes.getDecisions());
|
||||
|
||||
return MinutesDTO.builder()
|
||||
.minutesId(minutes.getMinutesId())
|
||||
.meetingId(minutes.getMeetingId())
|
||||
@ -387,6 +391,7 @@ public class MinutesService implements
|
||||
.participantCount(participantCount)
|
||||
.memo("") // 메모 필드는 추후 구현
|
||||
.sections(sectionDTOs) // 섹션 정보 추가
|
||||
.decisions(minutes.getDecisions()) // decisions 필드 추가
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -840,8 +840,10 @@ public class MinutesController {
|
||||
.todoCount(todoProgress.getTotalCount())
|
||||
.build();
|
||||
|
||||
// 결정사항 추출
|
||||
List<MinutesDetailResponse.Decision> decisions = extractDecisions(agendas);
|
||||
// minutes 테이블의 decisions 텍스트 사용
|
||||
String decisions = minutesDTO.getDecisions() != null ? minutesDTO.getDecisions() : "";
|
||||
log.info("Dashboard decisions 값 확인 - minutesId: {}, decisions: {}",
|
||||
minutesDTO.getMinutesId(), decisions);
|
||||
|
||||
// AI 기반 관련회의록 조회 (캐시 우선)
|
||||
List<MinutesDetailResponse.RelatedMinutes> relatedMinutes = getRelatedMinutesFromAI(minutesDTO.getMinutesId());
|
||||
@ -883,29 +885,12 @@ public class MinutesController {
|
||||
|
||||
MinutesSection minutesSection = (MinutesSection) section;
|
||||
|
||||
// AI 요약 정보 구성 (현재는 기본값 사용)
|
||||
MinutesDetailResponse.AiSummary aiSummary = MinutesDetailResponse.AiSummary.builder()
|
||||
.content(minutesSection.getContent() != null ? minutesSection.getContent() : "AI 요약 정보 없음")
|
||||
.generatedAt(LocalDateTime.now().minusMinutes(30))
|
||||
.modifiedAt(LocalDateTime.now().minusMinutes(10))
|
||||
.build();
|
||||
|
||||
// 안건 상세 내용 구성
|
||||
MinutesDetailResponse.AgendaDetails details = MinutesDetailResponse.AgendaDetails.builder()
|
||||
.discussions(parseDiscussions(minutesSection.getContent()))
|
||||
.decisions(parseDecisions(minutesSection.getContent()))
|
||||
.build();
|
||||
|
||||
return MinutesDetailResponse.AgendaInfo.builder()
|
||||
.agendaId(minutesSection.getSectionId())
|
||||
.title(minutesSection.getTitle() != null ? minutesSection.getTitle() : "제목 없음")
|
||||
.orderIndex(minutesSection.getOrder() != null ? minutesSection.getOrder() : 1)
|
||||
.isVerified(minutesSection.isVerified())
|
||||
.verifiedBy(minutesSection.isVerified() ? "시스템" : null)
|
||||
.verifiedAt(minutesSection.isVerified() ? LocalDateTime.now().minusHours(1) : null)
|
||||
.aiSummary(aiSummary)
|
||||
.details(details)
|
||||
.relatedMinutes(new ArrayList<>()) // 관련 회의록은 별도 로직 필요
|
||||
.aiSummary("") // MinutesSection에는 AI 요약이 없음
|
||||
.content(minutesSection.getContent() != null ? minutesSection.getContent() : "")
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -913,69 +898,15 @@ public class MinutesController {
|
||||
* AgendaSection을 AgendaInfo로 변환
|
||||
*/
|
||||
private MinutesDetailResponse.AgendaInfo convertAgendaSectionToAgendaInfo(AgendaSection agendaSection) {
|
||||
// AI 요약 정보 구성
|
||||
MinutesDetailResponse.AiSummary aiSummary = MinutesDetailResponse.AiSummary.builder()
|
||||
.content(agendaSection.getAiSummaryShort() != null ? agendaSection.getAiSummaryShort() : "")
|
||||
.generatedAt(agendaSection.getCreatedAt() != null ? agendaSection.getCreatedAt() : LocalDateTime.now())
|
||||
.modifiedAt(agendaSection.getUpdatedAt() != null ? agendaSection.getUpdatedAt() : LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
// 안건 상세 내용 구성
|
||||
// discussions는 이제 TEXT 타입이므로 별도 파싱 로직 적용
|
||||
List<String> discussionsList = parseDiscussionsText(agendaSection.getDiscussions());
|
||||
// decisions는 JSON 문자열로 저장되어 있으므로 파싱 필요
|
||||
List<String> decisionsList = parseJsonToList(agendaSection.getDecisions());
|
||||
|
||||
MinutesDetailResponse.AgendaDetails details = MinutesDetailResponse.AgendaDetails.builder()
|
||||
.discussions(discussionsList)
|
||||
.decisions(decisionsList)
|
||||
.build();
|
||||
|
||||
return MinutesDetailResponse.AgendaInfo.builder()
|
||||
.agendaId(agendaSection.getId())
|
||||
.title(agendaSection.getAgendaTitle() != null ? agendaSection.getAgendaTitle() : "제목 없음")
|
||||
.orderIndex(agendaSection.getAgendaNumber() != null ? agendaSection.getAgendaNumber() : 1)
|
||||
.isVerified(true) // agenda_sections는 기본적으로 검증된 데이터
|
||||
.verifiedBy("AI")
|
||||
.verifiedAt(agendaSection.getCreatedAt())
|
||||
.aiSummary(aiSummary)
|
||||
.details(details)
|
||||
.relatedMinutes(new ArrayList<>()) // 관련 회의록은 별도 로직 필요
|
||||
.aiSummary(agendaSection.getAiSummaryShort() != null ? agendaSection.getAiSummaryShort() : "")
|
||||
.content(agendaSection.getSummary() != null ? agendaSection.getSummary() : "")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 문자열을 List<String>으로 파싱
|
||||
*/
|
||||
private List<String> parseJsonToList(String json) {
|
||||
if (json == null || json.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
try {
|
||||
// 간단한 JSON 배열 파싱
|
||||
json = json.trim();
|
||||
if (json.startsWith("[") && json.endsWith("]")) {
|
||||
json = json.substring(1, json.length() - 1);
|
||||
if (json.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 쉼표로 분리하고 따옴표 제거
|
||||
return Arrays.stream(json.split(","))
|
||||
.map(s -> s.trim())
|
||||
.map(s -> s.startsWith("\"") && s.endsWith("\"") ? s.substring(1, s.length() - 1) : s)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// JSON 형식이 아니면 그대로 한 줄로 반환
|
||||
return List.of(json);
|
||||
} catch (Exception e) {
|
||||
log.warn("JSON 파싱 실패: {}", json, e);
|
||||
return List.of(json); // 파싱 실패시 원본 텍스트를 그대로 반환
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private MinutesDetailResponse.SimpleTodo convertToSimpleTodo(Object todo) {
|
||||
@ -1016,10 +947,10 @@ public class MinutesController {
|
||||
List<MinutesDetailResponse.KeyPoint> keyPoints = new ArrayList<>();
|
||||
for (int i = 0; i < agendas.size() && i < 4; i++) {
|
||||
MinutesDetailResponse.AgendaInfo agenda = agendas.get(i);
|
||||
if (agenda.getAiSummary() != null) {
|
||||
if (agenda.getAiSummary() != null && !agenda.getAiSummary().isEmpty()) {
|
||||
keyPoints.add(MinutesDetailResponse.KeyPoint.builder()
|
||||
.index(i + 1)
|
||||
.content(agenda.getAiSummary().getContent())
|
||||
.content(agenda.getAiSummary())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@ -1032,85 +963,6 @@ public class MinutesController {
|
||||
return keyPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* 회의록 섹션 내용에서 논의사항 추출
|
||||
*/
|
||||
private List<String> parseDiscussions(String content) {
|
||||
if (content == null || content.trim().isEmpty()) {
|
||||
return List.of("논의 내용 없음");
|
||||
}
|
||||
|
||||
// 간단한 패턴으로 논의사항 추출 (실제로는 AI 파싱 필요)
|
||||
Pattern pattern = Pattern.compile("논의[::]\\s*(.+?)(?=결정|\\n|$)", Pattern.CASE_INSENSITIVE);
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
List<String> discussions = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
discussions.add(matcher.group(1).trim());
|
||||
}
|
||||
|
||||
if (discussions.isEmpty()) {
|
||||
// 전체 내용을 논의사항으로 처리
|
||||
discussions.add(content.length() > 100 ? content.substring(0, 100) + "..." : content);
|
||||
}
|
||||
|
||||
return discussions;
|
||||
}
|
||||
|
||||
/**
|
||||
* AgendaSection의 discussions TEXT 필드 파싱
|
||||
*/
|
||||
private List<String> parseDiscussionsText(String discussions) {
|
||||
if (discussions == null || discussions.trim().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// **논의 사항:** 형식으로 저장된 텍스트 파싱
|
||||
List<String> discussionsList = new ArrayList<>();
|
||||
|
||||
// 줄바꿈으로 분리하고 - 로 시작하는 항목 추출
|
||||
String[] lines = discussions.split("\n");
|
||||
for (String line : lines) {
|
||||
line = line.trim();
|
||||
if (line.startsWith("-") || line.startsWith("•") || line.startsWith("*")) {
|
||||
// 불릿 포인트 제거
|
||||
String item = line.substring(1).trim();
|
||||
if (!item.isEmpty()) {
|
||||
discussionsList.add(item);
|
||||
}
|
||||
} else if (!line.isEmpty() && !line.contains("논의") && !line.contains(":**")) {
|
||||
// 불릿 포인트가 없는 텍스트도 포함
|
||||
discussionsList.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (discussionsList.isEmpty()) {
|
||||
// 구조화되지 않은 경우 전체 텍스트 반환
|
||||
discussionsList.add(discussions.trim());
|
||||
}
|
||||
|
||||
return discussionsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 회의록 섹션 내용에서 결정사항 추출
|
||||
*/
|
||||
private List<String> parseDecisions(String content) {
|
||||
if (content == null || content.trim().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 간단한 패턴으로 결정사항 추출 (실제로는 AI 파싱 필요)
|
||||
Pattern pattern = Pattern.compile("결정[::]\\s*(.+?)(?=논의|\\n|$)", Pattern.CASE_INSENSITIVE);
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
List<String> decisions = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
decisions.add(matcher.group(1).trim());
|
||||
}
|
||||
|
||||
return decisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 담당자 이름 조회 (실제로는 User 서비스에서 조회 필요)
|
||||
@ -1163,26 +1015,6 @@ public class MinutesController {
|
||||
return List.of("#AI회의록", "#음성인식", "#협업도구", "#스타트업", "#베타출시");
|
||||
}
|
||||
|
||||
private List<MinutesDetailResponse.Decision> extractDecisions(List<MinutesDetailResponse.AgendaInfo> agendas) {
|
||||
List<MinutesDetailResponse.Decision> decisions = new ArrayList<>();
|
||||
|
||||
for (MinutesDetailResponse.AgendaInfo agenda : agendas) {
|
||||
if (agenda.getDetails() != null && agenda.getDetails().getDecisions() != null) {
|
||||
for (String decision : agenda.getDetails().getDecisions()) {
|
||||
decisions.add(MinutesDetailResponse.Decision.builder()
|
||||
.content(decision)
|
||||
.decidedBy("김민준")
|
||||
.decidedAt(LocalDateTime.now().minusHours(2))
|
||||
.background("안건 논의 결과")
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 실제 데이터만 사용
|
||||
|
||||
return decisions;
|
||||
}
|
||||
|
||||
// === 샘플 데이터 생성 메소드들 ===
|
||||
|
||||
@ -1297,7 +1129,7 @@ public class MinutesController {
|
||||
.keyPoints(keyPoints)
|
||||
.keywords(result.getKeywords() != null ? result.getKeywords() : dashboard.getKeywords())
|
||||
.stats(dashboard.getStats())
|
||||
.decisions(convertAiDecisions(result.getDecisions()))
|
||||
.decisions(dashboard.getDecisions()) // AI 분석에서는 decisions를 변경하지 않음
|
||||
.todoProgress(dashboard.getTodoProgress())
|
||||
.relatedMinutes(convertAiRelatedMinutes(result.getRelatedMinutes()))
|
||||
.build();
|
||||
@ -1326,23 +1158,6 @@ public class MinutesController {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 결정사항을 Response 형식으로 변환
|
||||
*/
|
||||
private List<MinutesDetailResponse.Decision> convertAiDecisions(List<AiAnalysisDTO.Decision> aiDecisions) {
|
||||
if (aiDecisions == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return aiDecisions.stream()
|
||||
.map(decision -> MinutesDetailResponse.Decision.builder()
|
||||
.content(decision.getContent())
|
||||
.decidedBy("AI 분석")
|
||||
.decidedAt(LocalDateTime.now())
|
||||
.background("AI가 회의록에서 추출한 결정사항")
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 관련회의록을 Response 형식으로 변환
|
||||
@ -1366,10 +1181,6 @@ public class MinutesController {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Response 객체 필드 복사 (불변 객체 업데이트용)
|
||||
*/
|
||||
|
||||
/**
|
||||
* AI 분석 요청 이벤트 발행
|
||||
*/
|
||||
|
||||
@ -72,7 +72,7 @@ public class MinutesDetailResponse {
|
||||
private List<KeyPoint> keyPoints; // 핵심내용
|
||||
private List<String> keywords; // 키워드 태그
|
||||
private Statistics stats; // 통계 정보
|
||||
private List<Decision> decisions; // 결정사항
|
||||
private String decisions; // 결정사항 (minutes 테이블의 decisions 텍스트)
|
||||
private TodoProgress todoProgress; // Todo 진행상황
|
||||
private List<RelatedMinutes> relatedMinutes; // 관련회의록
|
||||
}
|
||||
@ -97,16 +97,6 @@ public class MinutesDetailResponse {
|
||||
private int todoCount;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class Decision {
|
||||
private String content;
|
||||
private String decidedBy;
|
||||
private LocalDateTime decidedAt;
|
||||
private String background;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@ -155,36 +145,8 @@ public class MinutesDetailResponse {
|
||||
private String agendaId;
|
||||
private String title;
|
||||
private int orderIndex;
|
||||
private boolean isVerified;
|
||||
private String verifiedBy;
|
||||
private LocalDateTime verifiedAt;
|
||||
|
||||
// AI 요약
|
||||
private AiSummary aiSummary;
|
||||
|
||||
// 안건 상세 내용
|
||||
private AgendaDetails details;
|
||||
|
||||
// 관련회의록
|
||||
private List<RelatedMinutes> relatedMinutes;
|
||||
private String aiSummary; // agenda_sections.ai_summary_short
|
||||
private String content; // agenda_sections.summary
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AiSummary {
|
||||
private String content;
|
||||
private LocalDateTime generatedAt;
|
||||
private LocalDateTime modifiedAt;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AgendaDetails {
|
||||
private List<String> discussions; // 논의 사항
|
||||
private List<String> decisions; // 결정 사항
|
||||
}
|
||||
}
|
||||
@ -39,21 +39,15 @@ public class AgendaSectionEntity extends BaseTimeEntity {
|
||||
@Column(name = "ai_summary_short", columnDefinition = "TEXT")
|
||||
private String aiSummaryShort;
|
||||
|
||||
@Column(name = "discussions", columnDefinition = "TEXT")
|
||||
private String discussions;
|
||||
|
||||
@Column(name = "decisions", columnDefinition = "json")
|
||||
private String decisions;
|
||||
|
||||
@Column(name = "pending_items", columnDefinition = "json")
|
||||
private String pendingItems;
|
||||
|
||||
@Column(name = "opinions", columnDefinition = "json")
|
||||
private String opinions;
|
||||
|
||||
@Column(name = "todos", columnDefinition = "json")
|
||||
private String todos;
|
||||
|
||||
@Column(name = "summary", columnDefinition = "TEXT")
|
||||
private String summary;
|
||||
|
||||
/**
|
||||
* Domain 객체로 변환
|
||||
*/
|
||||
@ -65,11 +59,9 @@ public class AgendaSectionEntity extends BaseTimeEntity {
|
||||
.agendaNumber(this.agendaNumber)
|
||||
.agendaTitle(this.agendaTitle)
|
||||
.aiSummaryShort(this.aiSummaryShort)
|
||||
.discussions(this.discussions)
|
||||
.decisions(this.decisions)
|
||||
.pendingItems(this.pendingItems)
|
||||
.opinions(this.opinions)
|
||||
.todos(this.todos)
|
||||
.summary(this.summary)
|
||||
.createdAt(this.getCreatedAt())
|
||||
.updatedAt(this.getUpdatedAt())
|
||||
.build();
|
||||
@ -86,11 +78,9 @@ public class AgendaSectionEntity extends BaseTimeEntity {
|
||||
.agendaNumber(section.getAgendaNumber())
|
||||
.agendaTitle(section.getAgendaTitle())
|
||||
.aiSummaryShort(section.getAiSummaryShort())
|
||||
.discussions(section.getDiscussions())
|
||||
.decisions(section.getDecisions())
|
||||
.pendingItems(section.getPendingItems())
|
||||
.opinions(section.getOpinions())
|
||||
.todos(section.getTodos())
|
||||
.summary(section.getSummary())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@ -55,6 +55,12 @@ public class MinutesEntity extends BaseTimeEntity {
|
||||
@Column(name = "finalized_at")
|
||||
private LocalDateTime finalizedAt;
|
||||
|
||||
@Column(name = "decisions", columnDefinition = "TEXT")
|
||||
private String decisions;
|
||||
|
||||
@Column(name = "user_id", length = 100)
|
||||
private String userId;
|
||||
|
||||
public Minutes toDomain() {
|
||||
return Minutes.builder()
|
||||
.minutesId(this.minutesId)
|
||||
@ -70,6 +76,7 @@ public class MinutesEntity extends BaseTimeEntity {
|
||||
.lastModifiedAt(this.getUpdatedAt())
|
||||
.finalizedBy(this.finalizedBy)
|
||||
.finalizedAt(this.finalizedAt)
|
||||
.decisions(this.decisions)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -88,6 +95,7 @@ public class MinutesEntity extends BaseTimeEntity {
|
||||
.createdBy(minutes.getCreatedBy())
|
||||
.finalizedBy(minutes.getFinalizedBy())
|
||||
.finalizedAt(minutes.getFinalizedAt())
|
||||
.decisions(minutes.getDecisions())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user