Skip to content

Commit 4b8f45b

Browse files
committed
Merge branch 'dev'
2 parents 849e100 + f7b2a73 commit 4b8f45b

3 files changed

Lines changed: 153 additions & 88 deletions

File tree

src/main/java/ceos/ipx/domain/cases/controller/CaseController.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
import ceos.ipx.domain.cases.dto.response.CaseDetailResponse;
88
import ceos.ipx.domain.cases.dto.response.CaseListResponse;
99
import ceos.ipx.domain.cases.dto.response.CaseUpdateResponse;
10+
import ceos.ipx.domain.cases.dto.response.ComponentListResponse;
1011
import ceos.ipx.domain.cases.dto.response.RecentCaseListResponse;
1112
import ceos.ipx.domain.cases.service.CaseService;
12-
import ceos.ipx.domain.report.dto.request.ReportCreateRequest;
13+
import ceos.ipx.domain.cases.service.component.ComponentService;
1314
import ceos.ipx.domain.report.dto.request.ReportUpdateRequest;
1415
import ceos.ipx.domain.report.dto.response.ReportCreateResponse;
1516
import ceos.ipx.domain.report.dto.response.ReportDetailResponse;
@@ -32,8 +33,6 @@
3233
import org.springframework.web.bind.annotation.RequestMapping;
3334
import org.springframework.web.bind.annotation.RequestParam;
3435
import org.springframework.web.bind.annotation.RestController;
35-
import ceos.ipx.domain.cases.dto.response.ComponentListResponse;
36-
import ceos.ipx.domain.cases.service.component.ComponentService;
3736

3837
@Tag(name = "Case", description = "사건 관리 API")
3938
@RestController
@@ -160,20 +159,22 @@ public ApiResponse<ReportDetailResponse> getReport(
160159
}
161160

162161
@Operation(
163-
summary = "분석 리포트 생성",
162+
summary = "분석 리포트 자동 생성",
164163
description = """
165-
신규성 분석과 진보성 분석이 모두 존재하는 사건의 분석 리포트를 최초 생성합니다.
164+
신규성 분석과 진보성 분석 결과를 기반으로 분석 리포트를 자동 생성합니다.
165+
별도의 요청 본문은 필요하지 않습니다.
166+
작성자명은 로그인한 사용자명으로 설정합니다.
167+
신규성·진보성 충족 여부와 종합 결론은 저장된 분석 결과를 기반으로 생성합니다.
166168
이미 분석 리포트가 존재하는 경우 RP001을 반환합니다.
167169
"""
168170
)
169171
@PostMapping("/{caseId}/report")
170-
public ResponseEntity<ApiResponse<ReportCreateResponse>> saveReport(
172+
public ResponseEntity<ApiResponse<ReportCreateResponse>> createReport(
171173
@AuthenticationPrincipal Long userId,
172-
@PathVariable Long caseId,
173-
@Valid @RequestBody ReportCreateRequest request
174+
@PathVariable Long caseId
174175
) {
175176
ReportCreateResponse response =
176-
reportService.saveReport(userId, caseId, request);
177+
reportService.createReport(userId, caseId);
177178

178179
return ResponseEntity
179180
.status(HttpStatus.CREATED)

src/main/java/ceos/ipx/domain/report/dto/request/ReportCreateRequest.java

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/main/java/ceos/ipx/domain/report/service/ReportService.java

Lines changed: 143 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import ceos.ipx.domain.analysis.inventivestep.entity.InventiveStepAnalysis;
66
import ceos.ipx.domain.analysis.inventivestep.repository.InventiveArgumentRepository;
77
import ceos.ipx.domain.analysis.inventivestep.repository.InventiveStepAnalysisRepository;
8+
import ceos.ipx.domain.analysis.novelty.entity.ComparisonResult;
89
import ceos.ipx.domain.analysis.novelty.entity.NoveltyAnalysis;
910
import ceos.ipx.domain.analysis.novelty.entity.NoveltyComparison;
1011
import ceos.ipx.domain.analysis.novelty.repository.NoveltyAnalysisRepository;
@@ -14,7 +15,6 @@
1415
import ceos.ipx.domain.cases.entity.PriorArt;
1516
import ceos.ipx.domain.cases.repository.CaseRepository;
1617
import ceos.ipx.domain.cases.repository.InventionComponentRepository;
17-
import ceos.ipx.domain.report.dto.request.ReportCreateRequest;
1818
import ceos.ipx.domain.report.dto.request.ReportUpdateRequest;
1919
import ceos.ipx.domain.report.dto.response.ReportCreateResponse;
2020
import ceos.ipx.domain.report.dto.response.ReportDetailResponse;
@@ -46,32 +46,76 @@ public class ReportService {
4646
private final InventiveArgumentRepository inventiveArgumentRepository;
4747
private final ReportRepository reportRepository;
4848

49+
/**
50+
* 저장된 신규성·진보성 분석 결과를 바탕으로
51+
* 분석 리포트를 자동 생성한다.
52+
*/
4953
@Transactional
50-
public ReportCreateResponse saveReport(
54+
public ReportCreateResponse createReport(
5155
Long userId,
52-
Long caseId,
53-
ReportCreateRequest request
56+
Long caseId
5457
) {
5558
Case caseEntity = caseRepository.findById(caseId)
5659
.orElseThrow(() ->
5760
new BusinessException(ErrorCode.CASE_NOT_FOUND)
5861
);
5962

6063
validateCaseOwner(caseEntity, userId);
61-
validateAnalysisResults(caseEntity);
6264

6365
if (reportRepository.findByCaseEntity(caseEntity).isPresent()) {
6466
throw new BusinessException(
6567
ErrorCode.REPORT_ALREADY_EXISTS
6668
);
6769
}
6870

71+
NoveltyAnalysis noveltyAnalysis =
72+
noveltyAnalysisRepository.findByCaseEntity(caseEntity)
73+
.orElseThrow(() ->
74+
new BusinessException(
75+
ErrorCode.NOVELTY_ANALYSIS_NOT_FOUND
76+
)
77+
);
78+
79+
InventiveStepAnalysis inventiveStepAnalysis =
80+
inventiveStepAnalysisRepository
81+
.findByCaseEntity(caseEntity)
82+
.orElseThrow(() ->
83+
new BusinessException(
84+
ErrorCode.INVENTIVE_STEP_ANALYSIS_NOT_FOUND
85+
)
86+
);
87+
88+
List<NoveltyComparison> comparisons =
89+
noveltyComparisonRepository
90+
.findAllByAnalysis(noveltyAnalysis);
91+
92+
List<InventiveArgument> arguments =
93+
inventiveArgumentRepository
94+
.findAllByAnalysis(inventiveStepAnalysis);
95+
96+
boolean noveltySatisfied =
97+
isNoveltySatisfied(comparisons);
98+
99+
List<InventiveArgument> recommendedArguments =
100+
getRecommendedArguments(arguments);
101+
102+
boolean inventiveSatisfied =
103+
!recommendedArguments.isEmpty();
104+
105+
String overallConclusion =
106+
buildOverallConclusion(
107+
noveltyAnalysis,
108+
recommendedArguments,
109+
noveltySatisfied,
110+
inventiveSatisfied
111+
);
112+
69113
Report report = Report.builder()
70114
.caseEntity(caseEntity)
71-
.authorName(request.authorName().trim())
72-
.noveltySatisfied(request.noveltySatisfied())
73-
.inventiveSatisfied(request.inventiveSatisfied())
74-
.overallConclusion(request.overallConclusion().trim())
115+
.authorName(caseEntity.getUser().getName())
116+
.noveltySatisfied(noveltySatisfied)
117+
.inventiveSatisfied(inventiveSatisfied)
118+
.overallConclusion(overallConclusion)
75119
.build();
76120

77121
reportRepository.save(report);
@@ -81,7 +125,10 @@ public ReportCreateResponse saveReport(
81125
reportRepository.flush();
82126
caseRepository.flush();
83127

84-
return ReportCreateResponse.of(report, caseEntity);
128+
return ReportCreateResponse.of(
129+
report,
130+
caseEntity
131+
);
85132
}
86133

87134
@Transactional
@@ -111,7 +158,10 @@ public ReportUpdateResponse updateReport(
111158

112159
reportRepository.flush();
113160

114-
return ReportUpdateResponse.of(report, caseEntity);
161+
return ReportUpdateResponse.of(
162+
report,
163+
caseEntity
164+
);
115165
}
116166

117167
public ReportDetailResponse getReport(
@@ -198,6 +248,88 @@ public ReportDetailResponse getReport(
198248
.build();
199249
}
200250

251+
/**
252+
* 신규성 비교 결과 중 NOVEL 판정이 하나라도 있으면
253+
* 신규성을 충족한 것으로 판단한다.
254+
*/
255+
private boolean isNoveltySatisfied(
256+
List<NoveltyComparison> comparisons
257+
) {
258+
return comparisons.stream()
259+
.anyMatch(comparison ->
260+
comparison.getComparisonResult()
261+
== ComparisonResult.NOVEL
262+
);
263+
}
264+
265+
/**
266+
* 진보성 논리 중 AI가 추천한 항목만 반환한다.
267+
*/
268+
private List<InventiveArgument> getRecommendedArguments(
269+
List<InventiveArgument> arguments
270+
) {
271+
return arguments.stream()
272+
.filter(argument ->
273+
Boolean.TRUE.equals(
274+
argument.getRecommended()
275+
)
276+
)
277+
.toList();
278+
}
279+
280+
/**
281+
* 신규성 분석 결론과 추천된 진보성 논리를 조합해
282+
* 리포트의 종합 결론을 자동 생성한다.
283+
*/
284+
private String buildOverallConclusion(
285+
NoveltyAnalysis noveltyAnalysis,
286+
List<InventiveArgument> recommendedArguments,
287+
boolean noveltySatisfied,
288+
boolean inventiveSatisfied
289+
) {
290+
String noveltyConclusion =
291+
noveltyAnalysis.getConclusionText().trim();
292+
293+
String inventiveArgumentLabels =
294+
recommendedArguments.stream()
295+
.map(argument ->
296+
argument.getArgumentType().getLabel()
297+
)
298+
.collect(Collectors.joining(", "));
299+
300+
if (noveltySatisfied && inventiveSatisfied) {
301+
return String.format(
302+
"%s 또한 %s 논리가 도출되어, "
303+
+ "본 발명은 신규성과 진보성을 모두 충족할 가능성이 "
304+
+ "있는 것으로 판단됩니다.",
305+
noveltyConclusion,
306+
inventiveArgumentLabels
307+
);
308+
}
309+
310+
if (noveltySatisfied) {
311+
return String.format(
312+
"%s 다만 적용 가능한 진보성 논리가 확인되지 않아, "
313+
+ "진보성 충족 여부는 추가 검토가 필요합니다.",
314+
noveltyConclusion
315+
);
316+
}
317+
318+
if (inventiveSatisfied) {
319+
return String.format(
320+
"%s %s 논리를 통한 진보성 주장은 가능하나, "
321+
+ "신규성 부정 가능성에 대한 추가 검토가 필요합니다.",
322+
noveltyConclusion,
323+
inventiveArgumentLabels
324+
);
325+
}
326+
327+
return String.format(
328+
"%s 신규성과 진보성 모두 추가 검토가 필요한 것으로 판단됩니다.",
329+
noveltyConclusion
330+
);
331+
}
332+
201333
private ReportDetailResponse.ComponentResponse
202334
toComponentResponse(
203335
InventionComponent component
@@ -390,24 +522,4 @@ private void validateCaseOwner(
390522
);
391523
}
392524
}
393-
394-
private void validateAnalysisResults(
395-
Case caseEntity
396-
) {
397-
noveltyAnalysisRepository.findByCaseEntity(caseEntity)
398-
.orElseThrow(() ->
399-
new BusinessException(
400-
ErrorCode.NOVELTY_ANALYSIS_NOT_FOUND
401-
)
402-
);
403-
404-
inventiveStepAnalysisRepository
405-
.findByCaseEntity(caseEntity)
406-
.orElseThrow(() ->
407-
new BusinessException(
408-
ErrorCode
409-
.INVENTIVE_STEP_ANALYSIS_NOT_FOUND
410-
)
411-
);
412-
}
413525
}

0 commit comments

Comments
 (0)