RED-6369: Rules Refactor

* added id to ImageNode
* added Manual Redaction Handling to Rules
* minor rename
This commit is contained in:
Kilian Schuettler 2023-04-19 13:52:27 +02:00
parent 0c61ce3c43
commit 62e82b45a6
9 changed files with 126 additions and 16 deletions

View File

@ -12,7 +12,7 @@ import lombok.NoArgsConstructor;
@CompiledJson @CompiledJson
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class EntityRecogintionEntity { public class EntityRecognitionEntity {
private String value; private String value;
private int startOffset; private int startOffset;

View File

@ -16,6 +16,6 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
public class NerEntities { public class NerEntities {
private Map<Integer, List<EntityRecogintionEntity>> data = new HashMap<>(); private Map<Integer, List<EntityRecognitionEntity>> data = new HashMap<>();
} }

View File

@ -161,9 +161,9 @@ public class EntityNode {
} }
public boolean matchesResizedAnnotationId(String resizedAnnotationId) { public boolean matchesAnnotationId(String manualRedactionId) {
return getRedactionPositionsPerPage().stream().anyMatch(entityPosition -> entityPosition.getId().equals(resizedAnnotationId)); return getRedactionPositionsPerPage().stream().anyMatch(entityPosition -> entityPosition.getId().equals(manualRedactionId));
} }

View File

@ -4,6 +4,7 @@ import static java.lang.String.format;
import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import java.awt.geom.Rectangle2D;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -37,6 +38,7 @@ import com.iqser.red.service.redaction.v1.server.document.graph.nodes.TableNode;
import com.iqser.red.service.redaction.v1.server.document.graph.textblock.AtomicTextBlock; import com.iqser.red.service.redaction.v1.server.document.graph.textblock.AtomicTextBlock;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence; import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.PdfImage; import com.iqser.red.service.redaction.v1.server.redaction.model.PdfImage;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.AbstractTextContainer; import com.iqser.red.service.redaction.v1.server.tableextraction.model.AbstractTextContainer;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.Cell; import com.iqser.red.service.redaction.v1.server.tableextraction.model.Cell;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.Table; import com.iqser.red.service.redaction.v1.server.tableextraction.model.Table;
@ -254,10 +256,12 @@ public class DocumentGraphFactory {
private void addImage(SectionNode sectionNode, PdfImage image, Context context) { private void addImage(SectionNode sectionNode, PdfImage image, Context context) {
Rectangle2D position = RectangleTransformations.toRectangle2D(image.getPosition());
PageNode page = getPage(image.getPage(), context); PageNode page = getPage(image.getPage(), context);
ImageNode imageNode = ImageNode.builder() ImageNode imageNode = ImageNode.builder()
.id(IdBuilder.buildId(Set.of(page), List.of(position)))
.imageType(image.getImageType()) .imageType(image.getImageType())
.position(RectangleTransformations.toRectangle2D(image.getPosition())) .position(position)
.transparency(image.isHasTransparency()) .transparency(image.isHasTransparency())
.page(page) .page(page)
.tableOfContents(context.tableOfContents()) .tableOfContents(context.tableOfContents())

View File

@ -112,6 +112,12 @@ public class RectangleTransformations {
} }
public static Rectangle2D toRectangle2D(com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle rect) {
return new Rectangle2D.Double(rect.getTopLeftX() - rect.getWidth(), rect.getTopLeftY() - rect.getHeight(), rect.getWidth(), rect.getHeight());
}
private static class Rectangle2DUnion implements Collector<Rectangle2D, Area, Rectangle2D> { private static class Rectangle2DUnion implements Collector<Rectangle2D, Area, Rectangle2D> {
@Override @Override

View File

@ -30,11 +30,12 @@ import lombok.experimental.FieldDefaults;
public class ImageNode implements SemanticNode { public class ImageNode implements SemanticNode {
List<Integer> tocId; List<Integer> tocId;
String id;
ImageType imageType; ImageType imageType;
boolean transparency; boolean transparency;
Rectangle2D position; Rectangle2D position;
boolean redaction; boolean redaction;
boolean ignored; boolean ignored;
@Builder.Default @Builder.Default

View File

@ -19,6 +19,7 @@ public class PropertiesMapper {
properties.put("imageType", image.getImageType().toString()); properties.put("imageType", image.getImageType().toString());
properties.put("transparency", String.valueOf(image.isTransparency())); properties.put("transparency", String.valueOf(image.isTransparency()));
properties.put("position", RectangleTransformations.toString(image.getPosition())); properties.put("position", RectangleTransformations.toString(image.getPosition()));
properties.put("id", image.getId());
return properties; return properties;
} }
@ -54,6 +55,7 @@ public class PropertiesMapper {
builder.imageType(parseImageType(properties.get("imageType"))); builder.imageType(parseImageType(properties.get("imageType")));
builder.transparency(Boolean.parseBoolean(properties.get("transparency"))); builder.transparency(Boolean.parseBoolean(properties.get("transparency")));
builder.position(parseRectangle2D(properties.get("position"))); builder.position(parseRectangle2D(properties.get("position")));
builder.id(properties.get("id"));
} }
@ -73,7 +75,7 @@ public class PropertiesMapper {
} }
private static ImageType parseImageType(String imageType) { public static ImageType parseImageType(String imageType) {
return switch (imageType) { return switch (imageType) {
case "LOGO" -> ImageType.LOGO; case "LOGO" -> ImageType.LOGO;

View File

@ -0,0 +1,45 @@
package com.iqser.red.service.redaction.v1.server.document.services;
import java.util.NoSuchElementException;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction;
import com.iqser.red.service.redaction.v1.server.document.graph.entity.EntityNode;
import com.iqser.red.service.redaction.v1.server.document.graph.entity.RedactionPosition;
import com.iqser.red.service.redaction.v1.server.document.graph.factory.RectangleTransformations;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class ManualRedactionApplicationService {
private final EntityCreationService entityCreationService;
public void resizeEntityAndReinsert(EntityNode entityToBeResized, ManualResizeRedaction manualResizeRedaction) {
RedactionPosition redactionPositionToBeResized = entityToBeResized.getRedactionPositionsPerPage()
.stream()
.filter(redactionPosition -> redactionPosition.getId().equals(manualResizeRedaction.getValue()))
.findFirst()
.orElseThrow(() -> new NoSuchElementException("No redaction position with matching annotation id found!"));
redactionPositionToBeResized.setRectanglePerLine(manualResizeRedaction.getPositions().stream().map(RectangleTransformations::toRectangle2D).toList());
int newStartOffset;
if (manualResizeRedaction.getValue().length() > entityToBeResized.getValue().length()) {
newStartOffset = entityToBeResized.getBoundary().start() - manualResizeRedaction.getValue().indexOf(entityToBeResized.getValue());
} else {
newStartOffset = entityToBeResized.getBoundary().start() + entityToBeResized.getValue().indexOf(manualResizeRedaction.getValue());
}
entityToBeResized.setResized(true);
entityToBeResized.getBoundary().setStart(newStartOffset);
entityToBeResized.getBoundary().setEnd(newStartOffset + manualResizeRedaction.getValue().length());
entityToBeResized.removeFromGraph();
entityCreationService.addEntityToGraph(entityToBeResized, entityToBeResized.getDeepestFullyContainingNode().getTableOfContents());
}
}

View File

@ -3,6 +3,7 @@ package drools
import static java.lang.String.format; import static java.lang.String.format;
import static com.iqser.red.service.redaction.v1.server.document.services.CharSequenceSearchUtils.anyMatch; import static com.iqser.red.service.redaction.v1.server.document.services.CharSequenceSearchUtils.anyMatch;
import static com.iqser.red.service.redaction.v1.server.document.services.EntityEnrichmentService.setFields; import static com.iqser.red.service.redaction.v1.server.document.services.EntityEnrichmentService.setFields;
import static com.iqser.red.service.redaction.v1.server.document.mapper.PropertiesMapper.parseImageType;
import java.util.List; import java.util.List;
@ -21,9 +22,15 @@ import com.iqser.red.service.redaction.v1.server.document.services.EntityCreatio
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary; import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryModel; import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.DictionaryModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.IdRemoval;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualForceRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualImageRecategorization;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationStatus;
import com.iqser.red.service.redaction.v1.server.document.services.ManualRedactionApplicationService;
global DocumentGraph document global DocumentGraph document
global EntityCreationService entityCreationService global EntityCreationService entityCreationService
global ManualRedactionApplicationService manualRedactionApplicationService
global Dictionary dictionary global Dictionary dictionary
// --------------------------------------- queries ------------------------------------------------------------------- // --------------------------------------- queries -------------------------------------------------------------------
@ -32,6 +39,60 @@ query "getFileAttributes"
$fileAttribute: FileAttribute() $fileAttribute: FileAttribute()
end end
// --------------------------------------- manual redaction rules -------------------------------------------------------------------
rule "Apply manual resize redaction"
salience 200
when
$resizeRedactions: ManualResizeRedaction($id: annotationId)
$entityToBeResized: EntityNode(matchesAnnotationId($id))
then
manualRedactionApplicationService.resizeEntityAndReinsert($entityToBeResized, $resizeRedactions);
retract($resizeRedactions);
update($entityToBeResized);
end
rule "Apply id removals that are valid and not in forced redactions to Entity"
salience 200
when
IdRemoval(status == AnnotationStatus.APPROVED, !removeFromDictionary, requestDate != null, $id: annotationId)
not ManualForceRedaction($id == annotationId, status == AnnotationStatus.APPROVED, requestDate != null)
$entityToBeRemoved: EntityNode(matchesAnnotationId($id))
then
$entityToBeRemoved.removeFromGraph();
retract($entityToBeRemoved);
end
rule "Apply id removals that are valid and not in forced redactions to Image"
salience 200
when
IdRemoval(status == AnnotationStatus.APPROVED, !removeFromDictionary, requestDate != null, $id: annotationId)
not ManualForceRedaction($id == annotationId, status == AnnotationStatus.APPROVED, requestDate != null)
$entityToBeRemoved: ImageNode($id == id)
then
$entityToBeRemoved.setIgnored(true);
end
rule "Apply force redaction"
salience 200
when
ManualForceRedaction($id: annotationId, status == AnnotationStatus.APPROVED, requestDate != null, $legalBasis: legalBasis)
$entityToForce: EntityNode(matchesAnnotationId($id))
then
$entityToForce.setLegalBasis($legalBasis);
$entityToForce.setRedaction(true);
$entityToForce.setSkipRemoveEntitiesContainedInLarger(true);
end
rule "Apply image recategorization"
salience 200
when
ManualImageRecategorization($id: annotationId, status == AnnotationStatus.APPROVED, $imageType: type)
$image: ImageNode($id == id)
then
$image.setImageType(parseImageType($imageType));
end
// --------------------------------------- merging rules ------------------------------------------------------------------- // --------------------------------------- merging rules -------------------------------------------------------------------
rule "merge intersecting Entities of same type" rule "merge intersecting Entities of same type"
@ -107,7 +168,6 @@ rule "run local dictionary search"
entityNodes.forEach(entityNode -> insert(entityNode)); entityNodes.forEach(entityNode -> insert(entityNode));
end end
// --------------------------------------- CBI rules ------------------------------------------------------------------- // --------------------------------------- CBI rules -------------------------------------------------------------------
rule "1: Redact CBI Authors (Non vertebrate study)" rule "1: Redact CBI Authors (Non vertebrate study)"
@ -167,7 +227,6 @@ rule "5: Add FALSE_POSITIVE Entity for genitive CBI_author"
insert(falsePositive); insert(falsePositive);
end end
rule "6: Create Entity from Author(s) cells in Tables with Author(s) header and add Authorname as Recommendation" rule "6: Create Entity from Author(s) cells in Tables with Author(s) header and add Authorname as Recommendation"
agenda-group "LOCAL_DICTIONARY_ADDS" agenda-group "LOCAL_DICTIONARY_ADDS"
@ -203,7 +262,6 @@ rule "8: Add recommendation for Addresses in Test Organism sections"
entities.forEach(entity -> insert(entity)); entities.forEach(entity -> insert(entity));
end end
rule "9: Add recommendation for Addresses in Test Animals sections" rule "9: Add recommendation for Addresses in Test Animals sections"
when when
@ -226,7 +284,6 @@ rule "12: Recommend CTL/BL laboratory that start with BL or CTL"
entities.forEach(entity -> dictionary.addLocalDictionaryEntry("PII", entity.getValue(), false)); entities.forEach(entity -> dictionary.addLocalDictionaryEntry("PII", entity.getValue(), false));
end end
// --------------------------------------- PII rules ------------------------------------------------------------------- // --------------------------------------- PII rules -------------------------------------------------------------------
@ -242,8 +299,6 @@ rule "10: Redacted PII Personal Identification Information (Non vertebrate study
update($entity) update($entity)
end end
rule "11: Redacted PII Personal Identification Information (Vertebrate study)" rule "11: Redacted PII Personal Identification Information (Vertebrate study)"
no-loop true no-loop true
@ -278,7 +333,6 @@ rule "13: Redact Emails by RegEx (Vertebrate study)"
entities.forEach(entity -> insert(entity)); entities.forEach(entity -> insert(entity));
end end
rule "14: Redact line after contact information (Non vertebrate study)" rule "14: Redact line after contact information (Non vertebrate study)"
agenda-group "LOCAL_DICTIONARY_ADDS" agenda-group "LOCAL_DICTIONARY_ADDS"
@ -313,7 +367,6 @@ rule "14: Redact line after contact information (Non vertebrate study)"
entities.forEach(entity -> dictionary.addLocalDictionaryEntry("PII", entity.getValue(), false)); entities.forEach(entity -> dictionary.addLocalDictionaryEntry("PII", entity.getValue(), false));
end end
rule "15: Redact line after contact information (Vertebrate study)" rule "15: Redact line after contact information (Vertebrate study)"
agenda-group "LOCAL_DICTIONARY_ADDS" agenda-group "LOCAL_DICTIONARY_ADDS"
@ -346,7 +399,6 @@ rule "15: Redact line after contact information (Vertebrate study)"
entities.forEach(entity -> dictionary.addLocalDictionaryEntry("PII", entity.getValue(), false)); entities.forEach(entity -> dictionary.addLocalDictionaryEntry("PII", entity.getValue(), false));
end end
rule "16: redact line between contact keywords" rule "16: redact line between contact keywords"
agenda-group "LOCAL_DICTIONARY_ADDS" agenda-group "LOCAL_DICTIONARY_ADDS"