diff --git a/redaction-service-v1/redaction-service-api-v1/src/main/java/com/iqser/red/service/redaction/v1/model/UnprocessedManualEntity.java b/redaction-service-v1/redaction-service-api-v1/src/main/java/com/iqser/red/service/redaction/v1/model/UnprocessedManualEntity.java new file mode 100644 index 00000000..baae9ca4 --- /dev/null +++ b/redaction-service-v1/redaction-service-api-v1/src/main/java/com/iqser/red/service/redaction/v1/model/UnprocessedManualEntity.java @@ -0,0 +1,24 @@ +package com.iqser.red.service.redaction.v1.model; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@AllArgsConstructor +@Builder +@EqualsAndHashCode +public class UnprocessedManualEntity { + + private String annotationId; + private String textBefore; + private String textAfter; + private int startOffset; + private int endOffset; + private String closestHeadline; + private float[] color; + private List containingNodeId; +} diff --git a/redaction-service-v1/redaction-service-api-v1/src/main/java/com/iqser/red/service/redaction/v1/resources/UnprocessedManualEntityResource.java b/redaction-service-v1/redaction-service-api-v1/src/main/java/com/iqser/red/service/redaction/v1/resources/UnprocessedManualEntityResource.java new file mode 100644 index 00000000..f5fab646 --- /dev/null +++ b/redaction-service-v1/redaction-service-api-v1/src/main/java/com/iqser/red/service/redaction/v1/resources/UnprocessedManualEntityResource.java @@ -0,0 +1,20 @@ +package com.iqser.red.service.redaction.v1.resources; + +import java.util.List; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions; +import com.iqser.red.service.redaction.v1.model.UnprocessedManualEntity; + +public interface UnprocessedManualEntityResource { + + @GetMapping(value = "/mergeUnprocessedManualEntities", produces = MediaType.APPLICATION_JSON_VALUE) + List mergeUnprocessedManualEntities(@RequestParam(value = "fileId") String fileId, + @RequestParam(value = "dossierId") String dossierId, + @RequestParam(value = "dossierTemplateId") String dosserTemplateId, + @RequestBody ManualRedactions manualRedactions); +} diff --git a/redaction-service-v1/redaction-service-server-v1/build.gradle.kts b/redaction-service-v1/redaction-service-server-v1/build.gradle.kts index 0b75a18e..57cff282 100644 --- a/redaction-service-v1/redaction-service-server-v1/build.gradle.kts +++ b/redaction-service-v1/redaction-service-server-v1/build.gradle.kts @@ -16,7 +16,7 @@ val layoutParserVersion = "0.74.0" val jacksonVersion = "2.15.2" val droolsVersion = "9.44.0.Final" val pdfBoxVersion = "3.0.0" -val persistenceServiceVersion = "2.210.0" +val persistenceServiceVersion = "2.223.0" configurations { all { diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/controller/UnprocessedManualEntityController.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/controller/UnprocessedManualEntityController.java new file mode 100644 index 00000000..14191275 --- /dev/null +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/controller/UnprocessedManualEntityController.java @@ -0,0 +1,29 @@ +package com.iqser.red.service.redaction.v1.server.controller; + +import java.util.List; + +import org.springframework.web.bind.annotation.RestController; + +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions; +import com.iqser.red.service.redaction.v1.model.UnprocessedManualEntity; +import com.iqser.red.service.redaction.v1.resources.UnprocessedManualEntityResource; +import com.iqser.red.service.redaction.v1.server.service.UnprocessedChangesService; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class UnprocessedManualEntityController implements UnprocessedManualEntityResource { + + private final UnprocessedChangesService unprocessedChangesService; + + + @Override + public List mergeUnprocessedManualEntities(String fileId, String dossierId, String dossierTemplateId, ManualRedactions manualRedactions) { + + return unprocessedChangesService.addInformationToUnprocessedManualEntities(fileId, dossierId, dossierTemplateId, manualRedactions); + } + +} diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/service/UnprocessedChangesService.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/service/UnprocessedChangesService.java new file mode 100644 index 00000000..54e2ab05 --- /dev/null +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/service/UnprocessedChangesService.java @@ -0,0 +1,129 @@ +package com.iqser.red.service.redaction.v1.server.service; + +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; + +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions; +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle; +import com.iqser.red.service.redaction.v1.model.UnprocessedManualEntity; +import com.iqser.red.service.redaction.v1.server.model.document.entity.EntityType; +import com.iqser.red.service.redaction.v1.server.model.document.entity.PositionOnPage; +import com.iqser.red.service.redaction.v1.server.model.document.entity.TextEntity; +import com.iqser.red.service.redaction.v1.server.model.document.nodes.Document; +import com.iqser.red.service.redaction.v1.server.model.document.nodes.SemanticNode; +import com.iqser.red.service.redaction.v1.server.service.document.DocumentGraphMapper; +import com.iqser.red.service.redaction.v1.server.service.document.EntityCreationService; +import com.iqser.red.service.redaction.v1.server.service.document.EntityEnrichmentService; +import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService; +import com.iqser.red.service.redaction.v1.server.utils.RedactionSearchUtility; +import com.iqser.red.service.redaction.v1.server.utils.exception.NotFoundException; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UnprocessedChangesService { + + private final RedactionStorageService redactionStorageService; + private final EntityEnrichmentService entityEnrichmentService; + private final DictionaryService dictionaryService; + + + public List addInformationToUnprocessedManualEntities(String fileId, String dossierId, String dossierTemplateId, ManualRedactions manualRedactions) { + + Document document = DocumentGraphMapper.toDocumentGraph(redactionStorageService.getDocumentData(dossierId, fileId)); + List unprocessedManualEntities = new ArrayList<>(); + + for (SemanticNode node : document.streamChildren().toList()) { + + if (manualRedactions.getEntriesToAdd().isEmpty()) { + break; + } + + var iterator = manualRedactions.getEntriesToAdd().iterator(); + while (iterator.hasNext()) { + var manualAddRedaction = iterator.next(); + if (sectionContainsEntry(node, manualAddRedaction.getPositions())) { + TextEntity surroundingText = findSurroundingText(node, manualAddRedaction.getValue(), manualAddRedaction.getPositions()); + unprocessedManualEntities.add(UnprocessedManualEntity.builder() + .annotationId(manualAddRedaction.getAnnotationId()) + .closestHeadline(surroundingText.getDeepestFullyContainingNode().getHeadline().getTextBlock().getSearchText()) + .color(getColor(surroundingText.getType(), dossierTemplateId, surroundingText.applied(), surroundingText.getEntityType().equals(EntityType.HINT))) + .startOffset(surroundingText.getTextRange().start()) + .endOffset(surroundingText.getTextRange().end()) + .containingNodeId(surroundingText.getDeepestFullyContainingNode().getTreeId()) + .textBefore(surroundingText.getTextBefore()) + .textAfter(surroundingText.getTextAfter()) + .build()); + iterator.remove(); + } + } + } + + return unprocessedManualEntities; + } + + + private TextEntity findSurroundingText(SemanticNode node, String value, List toFindPositions) { + + EntityCreationService entityCreationService = new EntityCreationService(entityEnrichmentService); + Set entities = RedactionSearchUtility.findTextRangesByString(value, node.getTextBlock()) + .stream() + .map(boundary -> entityCreationService.forceByTextRange(boundary, "searchHelper", EntityType.RECOMMENDATION, node)) + .collect(Collectors.toSet()); + return getEntityOnCorrectPosition(entities, toFindPositions); + } + + + private boolean sectionContainsEntry(SemanticNode semanticNode, List positions) { + + for (Rectangle position : positions) { + if (semanticNode.containsRectangle(UnprocessedChangesService.toRectangle2D(position), position.getPage())) { + return true; + } + } + return false; + } + + + private TextEntity getEntityOnCorrectPosition(Set entities, List toFindPositions) { + + return entities.stream() + .filter(entity -> entity.getPositionsOnPagePerPage() + .stream() + .map(PositionOnPage::getRectanglePerLine) + .flatMap(Collection::stream) + .allMatch(rectangle2D -> toFindPositionsIntersectRectangle(toFindPositions, rectangle2D))) + .findFirst() + .orElseThrow(() -> new NotFoundException("No matching Entity could be found for positions" + toFindPositions)); + } + + + private static boolean toFindPositionsIntersectRectangle(List toFindPositions, Rectangle2D rectangle2D) { + + return toFindPositions.stream().map(UnprocessedChangesService::toRectangle2D).anyMatch(toFindRectangle -> toFindRectangle.intersects(rectangle2D)); + } + + + private static Rectangle2D toRectangle2D(Rectangle rect) { + + return new Rectangle2D.Double(rect.getTopLeftX() - rect.getWidth(), rect.getTopLeftY() - rect.getHeight(), rect.getWidth(), rect.getHeight()); + } + + private float[] getColor(String type, String dossierTemplateId, boolean isApplied, boolean isHint) { + + if (!isApplied && !isHint) { + return dictionaryService.getNotRedactedColor(dossierTemplateId); + } + return dictionaryService.getColor(type, dossierTemplateId); + } + +}