RED-7784 - Apply unprocessed manual changes in reports and redactions

* Add endpoint used to calculate information aboout unprocessed manual entities like textBefore/textAfter/offsets etc.
This commit is contained in:
Andrei Isvoran 2023-10-23 17:46:23 +03:00
parent db2b663547
commit 6930d94274
5 changed files with 203 additions and 1 deletions

View File

@ -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<Integer> containingNodeId;
}

View File

@ -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<UnprocessedManualEntity> mergeUnprocessedManualEntities(@RequestParam(value = "fileId") String fileId,
@RequestParam(value = "dossierId") String dossierId,
@RequestParam(value = "dossierTemplateId") String dosserTemplateId,
@RequestBody ManualRedactions manualRedactions);
}

View File

@ -16,7 +16,7 @@ val layoutParserVersion = "0.74.0"
val jacksonVersion = "2.15.2" val jacksonVersion = "2.15.2"
val droolsVersion = "9.44.0.Final" val droolsVersion = "9.44.0.Final"
val pdfBoxVersion = "3.0.0" val pdfBoxVersion = "3.0.0"
val persistenceServiceVersion = "2.210.0" val persistenceServiceVersion = "2.223.0"
configurations { configurations {
all { all {

View File

@ -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<UnprocessedManualEntity> mergeUnprocessedManualEntities(String fileId, String dossierId, String dossierTemplateId, ManualRedactions manualRedactions) {
return unprocessedChangesService.addInformationToUnprocessedManualEntities(fileId, dossierId, dossierTemplateId, manualRedactions);
}
}

View File

@ -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<UnprocessedManualEntity> addInformationToUnprocessedManualEntities(String fileId, String dossierId, String dossierTemplateId, ManualRedactions manualRedactions) {
Document document = DocumentGraphMapper.toDocumentGraph(redactionStorageService.getDocumentData(dossierId, fileId));
List<UnprocessedManualEntity> 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<Rectangle> toFindPositions) {
EntityCreationService entityCreationService = new EntityCreationService(entityEnrichmentService);
Set<TextEntity> 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<Rectangle> positions) {
for (Rectangle position : positions) {
if (semanticNode.containsRectangle(UnprocessedChangesService.toRectangle2D(position), position.getPage())) {
return true;
}
}
return false;
}
private TextEntity getEntityOnCorrectPosition(Set<TextEntity> entities, List<Rectangle> 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<Rectangle> 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);
}
}