RED-6009: Document Tree Structure
*renamed old TextBlock and Table Classes *added subsections to enable separate rules for tables and text-only sections *added ManualRedaction Tests *finished default rules
This commit is contained in:
parent
86fe535460
commit
1f7caeebf6
@ -12,7 +12,7 @@
|
|||||||
<artifactId>redaction-service-server-v1</artifactId>
|
<artifactId>redaction-service-server-v1</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<drools.version>7.73.0.Final</drools.version>
|
<drools.version>8.37.0.Final</drools.version>
|
||||||
<kie.version>7.73.0.Final</kie.version>
|
<kie.version>7.73.0.Final</kie.version>
|
||||||
<locationtech.version>1.19.0</locationtech.version>
|
<locationtech.version>1.19.0</locationtech.version>
|
||||||
<javaassist.version>3.29.2-GA</javaassist.version>
|
<javaassist.version>3.29.2-GA</javaassist.version>
|
||||||
@ -64,7 +64,12 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.drools</groupId>
|
<groupId>org.drools</groupId>
|
||||||
<artifactId>drools-core</artifactId>
|
<artifactId>drools-engine</artifactId>
|
||||||
|
<version>${drools.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.drools</groupId>
|
||||||
|
<artifactId>drools-mvel</artifactId>
|
||||||
<version>${drools.version}</version>
|
<version>${drools.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.m
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -33,7 +33,7 @@ public abstract class AbstractPageBlock {
|
|||||||
public abstract String getText();
|
public abstract String getText();
|
||||||
|
|
||||||
|
|
||||||
public boolean containsBlock(TextBlock other) {
|
public boolean containsBlock(ClassificationTextBlock other) {
|
||||||
|
|
||||||
return this.minX <= other.getPdfMinX() && this.maxX >= other.getPdfMaxX() && this.minY >= other.getPdfMinY() && this.maxY <= other.getPdfMaxY();
|
return this.minX <= other.getPdfMinX() && this.maxX >= other.getPdfMaxX() && this.minY >= other.getPdfMinY() && this.maxY <= other.getPdfMaxY();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.m
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@ -13,7 +13,7 @@ import lombok.Data;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class Footer {
|
public class Footer {
|
||||||
|
|
||||||
private List<TextBlock> textBlocks;
|
private List<ClassificationTextBlock> textBlocks;
|
||||||
|
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.m
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@ -13,7 +13,7 @@ import lombok.Data;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class Header {
|
public class Header {
|
||||||
|
|
||||||
private List<TextBlock> textBlocks;
|
private List<ClassificationTextBlock> textBlocks;
|
||||||
|
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Table;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -24,32 +24,32 @@ public class Section implements Comparable {
|
|||||||
|
|
||||||
SearchableText searchableText = new SearchableText();
|
SearchableText searchableText = new SearchableText();
|
||||||
pageBlocks.forEach(block -> {
|
pageBlocks.forEach(block -> {
|
||||||
if (block instanceof TextBlock) {
|
if (block instanceof ClassificationTextBlock) {
|
||||||
searchableText.addAll(((TextBlock) block).getSequences());
|
searchableText.addAll(((ClassificationTextBlock) block).getSequences());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return searchableText;
|
return searchableText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<Table> getTables() {
|
public List<TablePageBlock> getTables() {
|
||||||
|
|
||||||
List<Table> tables = new ArrayList<>();
|
List<TablePageBlock> tables = new ArrayList<>();
|
||||||
pageBlocks.forEach(block -> {
|
pageBlocks.forEach(block -> {
|
||||||
if (block instanceof Table) {
|
if (block instanceof TablePageBlock) {
|
||||||
tables.add((Table) block);
|
tables.add((TablePageBlock) block);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return tables;
|
return tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<TextBlock> getTextBlocks() {
|
public List<ClassificationTextBlock> getTextBlocks() {
|
||||||
|
|
||||||
List<TextBlock> textBlocks = new ArrayList<>();
|
List<ClassificationTextBlock> textBlocks = new ArrayList<>();
|
||||||
pageBlocks.forEach(block -> {
|
pageBlocks.forEach(block -> {
|
||||||
if (block instanceof TextBlock) {
|
if (block instanceof ClassificationTextBlock) {
|
||||||
textBlocks.add((TextBlock) block);
|
textBlocks.add((ClassificationTextBlock) block);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return textBlocks;
|
return textBlocks;
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
|
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ import lombok.NoArgsConstructor;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class Cell extends Rectangle {
|
public class Cell extends Rectangle {
|
||||||
|
|
||||||
private List<TextBlock> textBlocks = new ArrayList<>();
|
private List<ClassificationTextBlock> textBlocks = new ArrayList<>();
|
||||||
|
|
||||||
private List<Cell> headerCells = new ArrayList<>();
|
private List<Cell> headerCells = new ArrayList<>();
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ public class Cell extends Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addTextBlock(TextBlock textBlock) {
|
public void addTextBlock(ClassificationTextBlock textBlock) {
|
||||||
|
|
||||||
textBlocks.add(textBlock);
|
textBlocks.add(textBlock);
|
||||||
}
|
}
|
||||||
@ -45,11 +45,11 @@ public class Cell extends Rectangle {
|
|||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
Iterator<TextBlock> itty = textBlocks.iterator();
|
Iterator<ClassificationTextBlock> itty = textBlocks.iterator();
|
||||||
TextPositionSequence previous = null;
|
TextPositionSequence previous = null;
|
||||||
while (itty.hasNext()) {
|
while (itty.hasNext()) {
|
||||||
|
|
||||||
TextBlock textBlock = itty.next();
|
ClassificationTextBlock textBlock = itty.next();
|
||||||
|
|
||||||
for (TextPositionSequence word : textBlock.getSequences()) {
|
for (TextPositionSequence word : textBlock.getSequences()) {
|
||||||
if (previous != null) {
|
if (previous != null) {
|
||||||
|
|||||||
@ -12,14 +12,14 @@ import java.util.stream.Collectors;
|
|||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Table extends AbstractPageBlock {
|
public class TablePageBlock extends AbstractPageBlock {
|
||||||
|
|
||||||
private final TreeMap<CellPosition, Cell> cells = new TreeMap<>();
|
private final TreeMap<CellPosition, Cell> cells = new TreeMap<>();
|
||||||
|
|
||||||
@ -34,14 +34,14 @@ public class Table extends AbstractPageBlock {
|
|||||||
private List<List<Cell>> rows;
|
private List<List<Cell>> rows;
|
||||||
|
|
||||||
|
|
||||||
public Table(List<Cell> cells, Rectangle area, int rotation) {
|
public TablePageBlock(List<Cell> cells, Rectangle area, int rotation) {
|
||||||
|
|
||||||
addCells(cells);
|
addCells(cells);
|
||||||
minX = area.getLeft();
|
minX = area.getLeft();
|
||||||
minY = area.getBottom();
|
minY = area.getBottom();
|
||||||
maxX = area.getRight();
|
maxX = area.getRight();
|
||||||
maxY = area.getTop();
|
maxY = area.getTop();
|
||||||
classification = "Table";
|
classification = "TablePageBlock";
|
||||||
this.rotation = rotation;
|
this.rotation = rotation;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -224,7 +224,7 @@ public class Table extends AbstractPageBlock {
|
|||||||
* Calculates the structure of the table. For spanning rows and columns multiple cells with the same values will be inserted.
|
* Calculates the structure of the table. For spanning rows and columns multiple cells with the same values will be inserted.
|
||||||
*
|
*
|
||||||
* @param cells The found cells
|
* @param cells The found cells
|
||||||
* @return Table Structure
|
* @return TablePageBlock Structure
|
||||||
*/
|
*/
|
||||||
private List<List<Cell>> calculateStructure(List<Cell> cells) {
|
private List<List<Cell>> calculateStructure(List<Cell> cells) {
|
||||||
|
|
||||||
@ -299,7 +299,7 @@ public class Table extends AbstractPageBlock {
|
|||||||
}
|
}
|
||||||
if (column != null && column.getTextBlocks() != null) {
|
if (column != null && column.getTextBlocks() != null) {
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
for (TextBlock textBlock : column.getTextBlocks()) {
|
for (ClassificationTextBlock textBlock : column.getTextBlocks()) {
|
||||||
if (!first) {
|
if (!first) {
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
}
|
}
|
||||||
@ -331,7 +331,7 @@ public class Table extends AbstractPageBlock {
|
|||||||
sb.append(i == 0 ? "\n<th>" : "\n<td>");
|
sb.append(i == 0 ? "\n<th>" : "\n<td>");
|
||||||
if (column != null && column.getTextBlocks() != null) {
|
if (column != null && column.getTextBlocks() != null) {
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
for (TextBlock textBlock : column.getTextBlocks()) {
|
for (ClassificationTextBlock textBlock : column.getTextBlocks()) {
|
||||||
if (!first) {
|
if (!first) {
|
||||||
sb.append("<br />");
|
sb.append("<br />");
|
||||||
}
|
}
|
||||||
@ -16,7 +16,7 @@ import lombok.NoArgsConstructor;
|
|||||||
@Builder
|
@Builder
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class TextBlock extends AbstractPageBlock {
|
public class ClassificationTextBlock extends AbstractPageBlock {
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private List<TextPositionSequence> sequences = new ArrayList<>();
|
private List<TextPositionSequence> sequences = new ArrayList<>();
|
||||||
@ -175,7 +175,7 @@ public class TextBlock extends AbstractPageBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public TextBlock(float minX, float maxX, float minY, float maxY, List<TextPositionSequence> sequences, int rotation, int indexOnPage) {
|
public ClassificationTextBlock(float minX, float maxX, float minY, float maxY, List<TextPositionSequence> sequences, int rotation, int indexOnPage) {
|
||||||
|
|
||||||
this.indexOnPage = indexOnPage;
|
this.indexOnPage = indexOnPage;
|
||||||
this.minX = minX;
|
this.minX = minX;
|
||||||
@ -187,23 +187,23 @@ public class TextBlock extends AbstractPageBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public TextBlock union(TextPositionSequence r) {
|
public ClassificationTextBlock union(TextPositionSequence r) {
|
||||||
|
|
||||||
TextBlock union = this.copy();
|
ClassificationTextBlock union = this.copy();
|
||||||
union.add(r);
|
union.add(r);
|
||||||
return union;
|
return union;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public TextBlock union(TextBlock r) {
|
public ClassificationTextBlock union(ClassificationTextBlock r) {
|
||||||
|
|
||||||
TextBlock union = this.copy();
|
ClassificationTextBlock union = this.copy();
|
||||||
union.add(r);
|
union.add(r);
|
||||||
return union;
|
return union;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void add(TextBlock r) {
|
public void add(ClassificationTextBlock r) {
|
||||||
|
|
||||||
if (r.getMinX() < minX) {
|
if (r.getMinX() < minX) {
|
||||||
minX = r.getMinX();
|
minX = r.getMinX();
|
||||||
@ -238,9 +238,9 @@ public class TextBlock extends AbstractPageBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public TextBlock copy() {
|
public ClassificationTextBlock copy() {
|
||||||
|
|
||||||
return new TextBlock(minX, maxX, minY, maxY, sequences, rotation, indexOnPage);
|
return new ClassificationTextBlock(minX, maxX, minY, maxY, sequences, rotation, indexOnPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ public class SectionText {
|
|||||||
@Builder.Default
|
@Builder.Default
|
||||||
private Set<Image> images = new HashSet<>();
|
private Set<Image> images = new HashSet<>();
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private List<TextBlock> textBlocks = new ArrayList<>();
|
private List<ClassificationTextBlock> textBlocks = new ArrayList<>();
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private Map<String, CellValue> tabularData = new HashMap<>();
|
private Map<String, CellValue> tabularData = new HashMap<>();
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import lombok.Data;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class UnclassifiedText {
|
public class UnclassifiedText {
|
||||||
|
|
||||||
private List<TextBlock> textBlocks;
|
private List<ClassificationTextBlock> textBlocks;
|
||||||
|
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
|||||||
@ -14,8 +14,8 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Orientation;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Orientation;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Ruling;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Ruling;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.StringFrequencyCounter;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.StringFrequencyCounter;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.RulingTextDirAdjustUtil;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.RulingTextDirAdjustUtil;
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ public class BlockificationService {
|
|||||||
/**
|
/**
|
||||||
* This method is building blocks by expanding the minX/maxX and minY/maxY value on each word that is not split by the conditions.
|
* This method is building blocks by expanding the minX/maxX and minY/maxY value on each word that is not split by the conditions.
|
||||||
* This method must use text direction adjusted postions (DirAdj). Where {0,0} is on the upper left. Never try to change this!
|
* This method must use text direction adjusted postions (DirAdj). Where {0,0} is on the upper left. Never try to change this!
|
||||||
* Rulings (Table lines) must be adjusted to the text directions as well, when checking if a block is split by a ruling.
|
* Rulings (TablePageBlock lines) must be adjusted to the text directions as well, when checking if a block is split by a ruling.
|
||||||
*
|
*
|
||||||
* @param textPositions The words of a page.
|
* @param textPositions The words of a page.
|
||||||
* @param horizontalRulingLines Horizontal table lines.
|
* @param horizontalRulingLines Horizontal table lines.
|
||||||
@ -64,7 +64,7 @@ public class BlockificationService {
|
|||||||
prevOrientation = chunkBlockList1.get(chunkBlockList1.size() - 1).getOrientation();
|
prevOrientation = chunkBlockList1.get(chunkBlockList1.size() - 1).getOrientation();
|
||||||
}
|
}
|
||||||
|
|
||||||
TextBlock cb1 = buildTextBlock(chunkWords, indexOnPage);
|
ClassificationTextBlock cb1 = buildTextBlock(chunkWords, indexOnPage);
|
||||||
indexOnPage++;
|
indexOnPage++;
|
||||||
|
|
||||||
chunkBlockList1.add(cb1);
|
chunkBlockList1.add(cb1);
|
||||||
@ -106,17 +106,17 @@ public class BlockificationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextBlock cb1 = buildTextBlock(chunkWords, indexOnPage);
|
ClassificationTextBlock cb1 = buildTextBlock(chunkWords, indexOnPage);
|
||||||
if (cb1 != null) {
|
if (cb1 != null) {
|
||||||
chunkBlockList1.add(cb1);
|
chunkBlockList1.add(cb1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<AbstractPageBlock> itty = chunkBlockList1.iterator();
|
Iterator<AbstractPageBlock> itty = chunkBlockList1.iterator();
|
||||||
|
|
||||||
TextBlock previousLeft = null;
|
ClassificationTextBlock previousLeft = null;
|
||||||
TextBlock previousRight = null;
|
ClassificationTextBlock previousRight = null;
|
||||||
while (itty.hasNext()) {
|
while (itty.hasNext()) {
|
||||||
TextBlock block = (TextBlock) itty.next();
|
ClassificationTextBlock block = (ClassificationTextBlock) itty.next();
|
||||||
|
|
||||||
if (previousLeft != null && block.getOrientation().equals(Orientation.LEFT)) {
|
if (previousLeft != null && block.getOrientation().equals(Orientation.LEFT)) {
|
||||||
if (previousLeft.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousLeft.getMinY()) {
|
if (previousLeft.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousLeft.getMinY()) {
|
||||||
@ -142,9 +142,9 @@ public class BlockificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
itty = chunkBlockList1.iterator();
|
itty = chunkBlockList1.iterator();
|
||||||
TextBlock previous = null;
|
ClassificationTextBlock previous = null;
|
||||||
while (itty.hasNext()) {
|
while (itty.hasNext()) {
|
||||||
TextBlock block = (TextBlock) itty.next();
|
ClassificationTextBlock block = (ClassificationTextBlock) itty.next();
|
||||||
|
|
||||||
if (previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation().equals(Orientation.LEFT) && equalsWithThreshold(block.getMaxY(),
|
if (previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation().equals(Orientation.LEFT) && equalsWithThreshold(block.getMaxY(),
|
||||||
previous.getMaxY()) || previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation()
|
previous.getMaxY()) || previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation()
|
||||||
@ -167,9 +167,9 @@ public class BlockificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TextBlock buildTextBlock(List<TextPositionSequence> wordBlockList, int indexOnPage) {
|
private ClassificationTextBlock buildTextBlock(List<TextPositionSequence> wordBlockList, int indexOnPage) {
|
||||||
|
|
||||||
TextBlock textBlock = null;
|
ClassificationTextBlock textBlock = null;
|
||||||
|
|
||||||
FloatFrequencyCounter lineHeightFrequencyCounter = new FloatFrequencyCounter();
|
FloatFrequencyCounter lineHeightFrequencyCounter = new FloatFrequencyCounter();
|
||||||
FloatFrequencyCounter fontSizeFrequencyCounter = new FloatFrequencyCounter();
|
FloatFrequencyCounter fontSizeFrequencyCounter = new FloatFrequencyCounter();
|
||||||
@ -186,7 +186,7 @@ public class BlockificationService {
|
|||||||
styleFrequencyCounter.add(wordBlock.getFontStyle());
|
styleFrequencyCounter.add(wordBlock.getFontStyle());
|
||||||
|
|
||||||
if (textBlock == null) {
|
if (textBlock == null) {
|
||||||
textBlock = new TextBlock(wordBlock.getMinXDirAdj(),
|
textBlock = new ClassificationTextBlock(wordBlock.getMinXDirAdj(),
|
||||||
wordBlock.getMaxXDirAdj(),
|
wordBlock.getMaxXDirAdj(),
|
||||||
wordBlock.getMinYDirAdj(),
|
wordBlock.getMinYDirAdj(),
|
||||||
wordBlock.getMaxYDirAdj(),
|
wordBlock.getMaxYDirAdj(),
|
||||||
@ -194,7 +194,7 @@ public class BlockificationService {
|
|||||||
wordBlock.getRotation(),
|
wordBlock.getRotation(),
|
||||||
indexOnPage);
|
indexOnPage);
|
||||||
} else {
|
} else {
|
||||||
TextBlock spatialEntity = textBlock.union(wordBlock);
|
ClassificationTextBlock spatialEntity = textBlock.union(wordBlock);
|
||||||
textBlock.resize(spatialEntity.getMinX(), spatialEntity.getMinY(), spatialEntity.getWidth(), spatialEntity.getHeight());
|
textBlock.resize(spatialEntity.getMinX(), spatialEntity.getMinY(), spatialEntity.getWidth(), spatialEntity.getHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.FloatFrequencyCounter;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.FloatFrequencyCounter;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Table;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.PositionUtils;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.PositionUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -77,8 +77,8 @@ public class BodyTextFrameService {
|
|||||||
|
|
||||||
for (AbstractPageBlock container : page.getTextBlocks()) {
|
for (AbstractPageBlock container : page.getTextBlocks()) {
|
||||||
|
|
||||||
if (container instanceof TextBlock) {
|
if (container instanceof ClassificationTextBlock) {
|
||||||
TextBlock textBlock = (TextBlock) container;
|
ClassificationTextBlock textBlock = (ClassificationTextBlock) container;
|
||||||
if (textBlock.getMostPopularWordFont() == null || textBlock.getMostPopularWordStyle() == null) {
|
if (textBlock.getMostPopularWordFont() == null || textBlock.getMostPopularWordStyle() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -94,15 +94,15 @@ public class BodyTextFrameService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container instanceof Table) {
|
if (container instanceof TablePageBlock) {
|
||||||
Table table = (Table) container;
|
TablePageBlock table = (TablePageBlock) container;
|
||||||
for (List<Cell> row : table.getRows()) {
|
for (List<Cell> row : table.getRows()) {
|
||||||
for (Cell cell : row) {
|
for (Cell cell : row) {
|
||||||
|
|
||||||
if (cell == null || cell.getTextBlocks() == null) {
|
if (cell == null || cell.getTextBlocks() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (TextBlock textBlock : cell.getTextBlocks()) {
|
for (ClassificationTextBlock textBlock : cell.getTextBlocks()) {
|
||||||
expandRectangle(textBlock, page, expansionsRectangle);
|
expandRectangle(textBlock, page, expansionsRectangle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ public class BodyTextFrameService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void expandRectangle(TextBlock textBlock, Page page, BodyTextFrameExpansionsRectangle expansionsRectangle) {
|
private void expandRectangle(ClassificationTextBlock textBlock, Page page, BodyTextFrameExpansionsRectangle expansionsRectangle) {
|
||||||
|
|
||||||
if (page.getPageWidth() > page.getPageHeight() && page.getRotation() != 0) {
|
if (page.getPageWidth() > page.getPageHeight() && page.getRotation() != 0) {
|
||||||
if (textBlock.getPdfMinY() < expansionsRectangle.minX) {
|
if (textBlock.getPdfMinY() < expansionsRectangle.minX) {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.PositionUtils;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.PositionUtils;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -41,14 +41,14 @@ public class ClassificationService {
|
|||||||
public void classifyPage(Page page, Document document, List<Float> headlineFontSizes) {
|
public void classifyPage(Page page, Document document, List<Float> headlineFontSizes) {
|
||||||
|
|
||||||
for (AbstractPageBlock textBlock : page.getTextBlocks()) {
|
for (AbstractPageBlock textBlock : page.getTextBlocks()) {
|
||||||
if (textBlock instanceof TextBlock) {
|
if (textBlock instanceof ClassificationTextBlock) {
|
||||||
classifyBlock((TextBlock) textBlock, page, document, headlineFontSizes);
|
classifyBlock((ClassificationTextBlock) textBlock, page, document, headlineFontSizes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void classifyBlock(TextBlock textBlock, Page page, Document document, List<Float> headlineFontSizes) {
|
public void classifyBlock(ClassificationTextBlock textBlock, Page page, Document document, List<Float> headlineFontSizes) {
|
||||||
|
|
||||||
var bodyTextFrame = page.getBodyTextFrame();
|
var bodyTextFrame = page.getBodyTextFrame();
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ public class ClassificationService {
|
|||||||
document.setHeadlines(true);
|
document.setHeadlines(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!textBlock.getText().startsWith("Table ") && !textBlock.getText().startsWith("Figure ") && PositionUtils.isWithinBodyTextFrame(bodyTextFrame,
|
} else if (!textBlock.getText().startsWith("TablePageBlock ") && !textBlock.getText().startsWith("Figure ") && PositionUtils.isWithinBodyTextFrame(bodyTextFrame,
|
||||||
textBlock) && textBlock.getMostPopularWordStyle().equals("bold") && !document.getFontStyleCounter()
|
textBlock) && textBlock.getMostPopularWordStyle().equals("bold") && !document.getFontStyleCounter()
|
||||||
.getMostPopular()
|
.getMostPopular()
|
||||||
.equals("bold") && PositionUtils.getApproxLineCount(textBlock) < 2.9 && textBlock.getSequences()
|
.equals("bold") && PositionUtils.getApproxLineCount(textBlock) < 2.9 && textBlock.getSequences()
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.CleanRulings;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.CleanRulings;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.parsing.PDFLinesTextStripper;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.parsing.PDFLinesTextStripper;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.FileUtils;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.FileUtils;
|
||||||
@ -154,11 +154,11 @@ public class PdfSegmentationService {
|
|||||||
|
|
||||||
// Collect all statistics for the page, except from blocks inside tables, as tables will always be added to BodyTextFrame.
|
// Collect all statistics for the page, except from blocks inside tables, as tables will always be added to BodyTextFrame.
|
||||||
for (AbstractPageBlock textBlock : page.getTextBlocks()) {
|
for (AbstractPageBlock textBlock : page.getTextBlocks()) {
|
||||||
if (textBlock instanceof TextBlock) {
|
if (textBlock instanceof ClassificationTextBlock) {
|
||||||
if (((TextBlock) textBlock).getSequences() == null) {
|
if (((ClassificationTextBlock) textBlock).getSequences() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (TextPositionSequence word : ((TextBlock) textBlock).getSequences()) {
|
for (TextPositionSequence word : ((ClassificationTextBlock) textBlock).getSequences()) {
|
||||||
page.getTextHeightCounter().add(word.getTextHeight());
|
page.getTextHeightCounter().add(word.getTextHeight());
|
||||||
page.getFontCounter().add(word.getFont());
|
page.getFontCounter().add(word.getFont());
|
||||||
page.getFontSizeCounter().add(word.getFontSize());
|
page.getFontSizeCounter().add(word.getFontSize());
|
||||||
|
|||||||
@ -18,8 +18,8 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Table;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.UnclassifiedText;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.UnclassifiedText;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -39,11 +39,11 @@ public class SectionsBuilderService {
|
|||||||
AbstractPageBlock prev = null;
|
AbstractPageBlock prev = null;
|
||||||
|
|
||||||
String lastHeadline = "";
|
String lastHeadline = "";
|
||||||
Table previousTable = null;
|
TablePageBlock previousTable = null;
|
||||||
for (Page page : document.getPages()) {
|
for (Page page : document.getPages()) {
|
||||||
List<TextBlock> header = new ArrayList<>();
|
List<ClassificationTextBlock> header = new ArrayList<>();
|
||||||
List<TextBlock> footer = new ArrayList<>();
|
List<ClassificationTextBlock> footer = new ArrayList<>();
|
||||||
List<TextBlock> unclassifiedText = new ArrayList<>();
|
List<ClassificationTextBlock> unclassifiedText = new ArrayList<>();
|
||||||
for (AbstractPageBlock current : page.getTextBlocks()) {
|
for (AbstractPageBlock current : page.getTextBlocks()) {
|
||||||
|
|
||||||
if (current.getClassification() == null) {
|
if (current.getClassification() == null) {
|
||||||
@ -53,17 +53,17 @@ public class SectionsBuilderService {
|
|||||||
current.setPage(page.getPageNumber());
|
current.setPage(page.getPageNumber());
|
||||||
|
|
||||||
if (current.getClassification().equals("Header")) {
|
if (current.getClassification().equals("Header")) {
|
||||||
header.add((TextBlock) current);
|
header.add((ClassificationTextBlock) current);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current.getClassification().equals("Footer")) {
|
if (current.getClassification().equals("Footer")) {
|
||||||
footer.add((TextBlock) current);
|
footer.add((ClassificationTextBlock) current);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current.getClassification().equals("Other")) {
|
if (current.getClassification().equals("Other")) {
|
||||||
unclassifiedText.add((TextBlock) current);
|
unclassifiedText.add((ClassificationTextBlock) current);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ public class SectionsBuilderService {
|
|||||||
previousTable = chunkBlock.getTables().get(chunkBlock.getTables().size() - 1);
|
previousTable = chunkBlock.getTables().get(chunkBlock.getTables().size() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (current instanceof Table table) {
|
if (current instanceof TablePageBlock table) {
|
||||||
// Distribute header information for subsequent tables
|
// Distribute header information for subsequent tables
|
||||||
mergeTableMetadata(table, previousTable);
|
mergeTableMetadata(table, previousTable);
|
||||||
previousTable = table;
|
previousTable = table;
|
||||||
@ -212,7 +212,7 @@ public class SectionsBuilderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void mergeTableMetadata(Table currentTable, Table previousTable) {
|
private void mergeTableMetadata(TablePageBlock currentTable, TablePageBlock previousTable) {
|
||||||
|
|
||||||
// Distribute header information for subsequent tables
|
// Distribute header information for subsequent tables
|
||||||
if (previousTable != null && hasInvalidHeaderInformation(currentTable) && hasValidHeaderInformation(previousTable)) {
|
if (previousTable != null && hasInvalidHeaderInformation(currentTable) && hasValidHeaderInformation(previousTable)) {
|
||||||
@ -245,39 +245,39 @@ public class SectionsBuilderService {
|
|||||||
Section section = new Section();
|
Section section = new Section();
|
||||||
|
|
||||||
for (AbstractPageBlock container : wordBlockList) {
|
for (AbstractPageBlock container : wordBlockList) {
|
||||||
if (container instanceof Table table) {
|
if (container instanceof TablePageBlock table) {
|
||||||
|
|
||||||
if (lastHeadline == null || lastHeadline.isEmpty()) {
|
if (lastHeadline == null || lastHeadline.isEmpty()) {
|
||||||
table.setHeadline("Text in table");
|
table.setHeadline("Text in table");
|
||||||
} else {
|
} else {
|
||||||
table.setHeadline("Table in: " + lastHeadline);
|
table.setHeadline("TablePageBlock in: " + lastHeadline);
|
||||||
}
|
}
|
||||||
|
|
||||||
section.getPageBlocks().add(table);
|
section.getPageBlocks().add(table);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextBlock wordBlock = (TextBlock) container;
|
ClassificationTextBlock wordBlock = (ClassificationTextBlock) container;
|
||||||
section.getPageBlocks().add(wordBlock);
|
section.getPageBlocks().add(wordBlock);
|
||||||
}
|
}
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean hasValidHeaderInformation(Table table) {
|
private boolean hasValidHeaderInformation(TablePageBlock table) {
|
||||||
|
|
||||||
return !hasInvalidHeaderInformation(table);
|
return !hasInvalidHeaderInformation(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean hasInvalidHeaderInformation(Table table) {
|
private boolean hasInvalidHeaderInformation(TablePageBlock table) {
|
||||||
|
|
||||||
return table.getRows().stream().flatMap(row -> row.stream().filter(cell -> CollectionUtils.isNotEmpty(cell.getHeaderCells()))).findAny().isEmpty();
|
return table.getRows().stream().flatMap(row -> row.stream().filter(cell -> CollectionUtils.isNotEmpty(cell.getHeaderCells()))).findAny().isEmpty();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<Cell> getRowWithNonHeaderCells(Table table) {
|
private List<Cell> getRowWithNonHeaderCells(TablePageBlock table) {
|
||||||
|
|
||||||
for (int i = table.getRowCount() - 1; i >= 0; i--) { // Non header rows are most likely at bottom of table
|
for (int i = table.getRowCount() - 1; i >= 0; i--) { // Non header rows are most likely at bottom of table
|
||||||
List<Cell> row = table.getRows().get(i);
|
List<Cell> row = table.getRows().get(i);
|
||||||
|
|||||||
@ -19,8 +19,8 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.CleanRulings;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.CleanRulings;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Rectangle;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Rectangle;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Ruling;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Ruling;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Table;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.Utils;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils.Utils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -83,10 +83,10 @@ public class TableExtractionService {
|
|||||||
|
|
||||||
List<Cell> cells = findCells(cleanRulings.getHorizontal(), cleanRulings.getVertical());
|
List<Cell> cells = findCells(cleanRulings.getHorizontal(), cleanRulings.getVertical());
|
||||||
|
|
||||||
List<TextBlock> toBeRemoved = new ArrayList<>();
|
List<ClassificationTextBlock> toBeRemoved = new ArrayList<>();
|
||||||
|
|
||||||
for (AbstractPageBlock abstractPageBlock : page.getTextBlocks()) {
|
for (AbstractPageBlock abstractPageBlock : page.getTextBlocks()) {
|
||||||
TextBlock textBlock = (TextBlock) abstractPageBlock;
|
ClassificationTextBlock textBlock = (ClassificationTextBlock) abstractPageBlock;
|
||||||
for (Cell cell : cells) {
|
for (Cell cell : cells) {
|
||||||
if (cell.hasMinimumSize() && cell.intersects(textBlock.getPdfMinX(),
|
if (cell.hasMinimumSize() && cell.intersects(textBlock.getPdfMinX(),
|
||||||
textBlock.getPdfMinY(),
|
textBlock.getPdfMinY(),
|
||||||
@ -104,7 +104,7 @@ public class TableExtractionService {
|
|||||||
|
|
||||||
List<Rectangle> spreadsheetAreas = findSpreadsheetsFromCells(cells).stream().filter(r -> r.getWidth() > 0f && r.getHeight() > 0f).collect(Collectors.toList());
|
List<Rectangle> spreadsheetAreas = findSpreadsheetsFromCells(cells).stream().filter(r -> r.getWidth() > 0f && r.getHeight() > 0f).collect(Collectors.toList());
|
||||||
|
|
||||||
List<Table> tables = new ArrayList<>();
|
List<TablePageBlock> tables = new ArrayList<>();
|
||||||
for (Rectangle area : spreadsheetAreas) {
|
for (Rectangle area : spreadsheetAreas) {
|
||||||
|
|
||||||
List<Cell> overlappingCells = new ArrayList<>();
|
List<Cell> overlappingCells = new ArrayList<>();
|
||||||
@ -113,16 +113,16 @@ public class TableExtractionService {
|
|||||||
overlappingCells.add(c);
|
overlappingCells.add(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tables.add(new Table(overlappingCells, area, page.getRotation()));
|
tables.add(new TablePageBlock(overlappingCells, area, page.getRotation()));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Table table : tables) {
|
for (TablePageBlock table : tables) {
|
||||||
int position = -1;
|
int position = -1;
|
||||||
|
|
||||||
Iterator<AbstractPageBlock> itty = page.getTextBlocks().iterator();
|
Iterator<AbstractPageBlock> itty = page.getTextBlocks().iterator();
|
||||||
while (itty.hasNext()) {
|
while (itty.hasNext()) {
|
||||||
AbstractPageBlock textBlock = itty.next();
|
AbstractPageBlock textBlock = itty.next();
|
||||||
if (textBlock instanceof TextBlock ? table.containsBlock((TextBlock) textBlock) : table.contains(textBlock) && position == -1) {
|
if (textBlock instanceof ClassificationTextBlock ? table.containsBlock((ClassificationTextBlock) textBlock) : table.contains(textBlock) && position == -1) {
|
||||||
position = page.getTextBlocks().indexOf(textBlock);
|
position = page.getTextBlocks().indexOf(textBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils;
|
package com.iqser.red.service.redaction.v1.server.layoutparsing.classification.utils;
|
||||||
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
|
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ public final class PositionUtils {
|
|||||||
|
|
||||||
// TODO This currently uses pdf coord system. In the futher this should use java coord system.
|
// TODO This currently uses pdf coord system. In the futher this should use java coord system.
|
||||||
// Note: DirAdj (TextDirection Adjusted) can not be user for this.
|
// Note: DirAdj (TextDirection Adjusted) can not be user for this.
|
||||||
public boolean isWithinBodyTextFrame(Rectangle btf, TextBlock textBlock) {
|
public boolean isWithinBodyTextFrame(Rectangle btf, ClassificationTextBlock textBlock) {
|
||||||
|
|
||||||
if (btf == null || textBlock == null) {
|
if (btf == null || textBlock == null) {
|
||||||
return false;
|
return false;
|
||||||
@ -32,7 +32,7 @@ public final class PositionUtils {
|
|||||||
|
|
||||||
// TODO This currently uses pdf coord system. In the futher this should use java coord system.
|
// TODO This currently uses pdf coord system. In the futher this should use java coord system.
|
||||||
// Note: DirAdj (TextDirection Adjusted) can not be user for this.
|
// Note: DirAdj (TextDirection Adjusted) can not be user for this.
|
||||||
public boolean isOverBodyTextFrame(Rectangle btf, TextBlock textBlock, int rotation) {
|
public boolean isOverBodyTextFrame(Rectangle btf, ClassificationTextBlock textBlock, int rotation) {
|
||||||
|
|
||||||
if (btf == null || textBlock == null) {
|
if (btf == null || textBlock == null) {
|
||||||
return false;
|
return false;
|
||||||
@ -61,7 +61,7 @@ public final class PositionUtils {
|
|||||||
|
|
||||||
// TODO This currently uses pdf coord system. In the futher this should use java coord system.
|
// TODO This currently uses pdf coord system. In the futher this should use java coord system.
|
||||||
// Note: DirAdj (TextDirection Adjusted) can not be user for this.
|
// Note: DirAdj (TextDirection Adjusted) can not be user for this.
|
||||||
public boolean isUnderBodyTextFrame(Rectangle btf, TextBlock textBlock, int rotation) {
|
public boolean isUnderBodyTextFrame(Rectangle btf, ClassificationTextBlock textBlock, int rotation) {
|
||||||
|
|
||||||
if (btf == null || textBlock == null) {
|
if (btf == null || textBlock == null) {
|
||||||
return false;
|
return false;
|
||||||
@ -90,7 +90,7 @@ public final class PositionUtils {
|
|||||||
|
|
||||||
// TODO This currently uses pdf coord system. In the futher this should use java coord system.
|
// TODO This currently uses pdf coord system. In the futher this should use java coord system.
|
||||||
// Note: DirAdj (TextDirection Adjusted) can not be user for this.
|
// Note: DirAdj (TextDirection Adjusted) can not be user for this.
|
||||||
public boolean isTouchingUnderBodyTextFrame(Rectangle btf, TextBlock textBlock) {
|
public boolean isTouchingUnderBodyTextFrame(Rectangle btf, ClassificationTextBlock textBlock) {
|
||||||
|
|
||||||
//TODO Currently this is not working for rotated pages.
|
//TODO Currently this is not working for rotated pages.
|
||||||
|
|
||||||
@ -107,13 +107,13 @@ public final class PositionUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public float getHeightDifferenceBetweenChunkWordAndDocumentWord(TextBlock textBlock, Float documentMostPopularWordHeight) {
|
public float getHeightDifferenceBetweenChunkWordAndDocumentWord(ClassificationTextBlock textBlock, Float documentMostPopularWordHeight) {
|
||||||
|
|
||||||
return textBlock.getMostPopularWordHeight() - documentMostPopularWordHeight;
|
return textBlock.getMostPopularWordHeight() - documentMostPopularWordHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Float getApproxLineCount(TextBlock textBlock) {
|
public Float getApproxLineCount(ClassificationTextBlock textBlock) {
|
||||||
|
|
||||||
return textBlock.getHeight() / textBlock.getMostPopularWordHeight();
|
return textBlock.getHeight() / textBlock.getMostPopularWordHeight();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,10 +10,11 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.Ato
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.DocumentData;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.DocumentData;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.PageData;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.PageData;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.TableOfContentsData;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.TableOfContentsData;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCellNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCellNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.AtomicTextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.AtomicTextBlock;
|
||||||
@ -69,6 +70,7 @@ public class DocumentDataMapper {
|
|||||||
case TABLE -> PropertiesMapper.buildTableProperties((TableNode) entry.getNode());
|
case TABLE -> PropertiesMapper.buildTableProperties((TableNode) entry.getNode());
|
||||||
case TABLE_CELL -> PropertiesMapper.buildTableCellProperties((TableCellNode) entry.getNode());
|
case TABLE_CELL -> PropertiesMapper.buildTableCellProperties((TableCellNode) entry.getNode());
|
||||||
case IMAGE -> PropertiesMapper.buildImageProperties((ImageNode) entry.getNode());
|
case IMAGE -> PropertiesMapper.buildImageProperties((ImageNode) entry.getNode());
|
||||||
|
case SECTION -> PropertiesMapper.buildSectionProperties((SectionNode) entry.getNode());
|
||||||
default -> new HashMap<>();
|
default -> new HashMap<>();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -18,8 +18,8 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.Doc
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.PageData;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.PageData;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.TableOfContentsData;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.TableOfContentsData;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.FooterNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.FooterNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeaderNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeaderNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeadlineNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeadlineNode;
|
||||||
@ -71,7 +71,7 @@ public class DocumentGraphMapper {
|
|||||||
List<PageNode> pages = Arrays.stream(entryData.getPages()).map(pageNumber -> getPage(pageNumber, context)).toList();
|
List<PageNode> pages = Arrays.stream(entryData.getPages()).map(pageNumber -> getPage(pageNumber, context)).toList();
|
||||||
|
|
||||||
SemanticNode node = switch (entryData.getType()) {
|
SemanticNode node = switch (entryData.getType()) {
|
||||||
case SECTION -> buildSection(context);
|
case SECTION -> buildSection(context, entryData.getProperties());
|
||||||
case PARAGRAPH -> buildParagraph(context, terminal);
|
case PARAGRAPH -> buildParagraph(context, terminal);
|
||||||
case HEADLINE -> buildHeadline(context, terminal);
|
case HEADLINE -> buildHeadline(context, terminal);
|
||||||
case HEADER -> buildHeader(context, terminal);
|
case HEADER -> buildHeader(context, terminal);
|
||||||
@ -134,7 +134,7 @@ public class DocumentGraphMapper {
|
|||||||
|
|
||||||
TableNode.TableNodeBuilder builder = TableNode.builder();
|
TableNode.TableNodeBuilder builder = TableNode.builder();
|
||||||
PropertiesMapper.parseTableProperties(properties, builder);
|
PropertiesMapper.parseTableProperties(properties, builder);
|
||||||
return TableNode.builder().tableOfContents(context.tableOfContents()).build();
|
return builder.tableOfContents(context.tableOfContents()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -150,10 +150,11 @@ public class DocumentGraphMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private SectionNode buildSection(Context context) {
|
private SectionNode buildSection(Context context, Map<String, String> properties) {
|
||||||
|
|
||||||
return SectionNode.builder().tableOfContents(context.tableOfContents()).build();
|
|
||||||
|
|
||||||
|
var builder = SectionNode.builder();
|
||||||
|
PropertiesMapper.parseSectionProperties(properties, builder);
|
||||||
|
return builder.tableOfContents(context.tableOfContents()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCellNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCellNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleTransformations;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleTransformations;
|
||||||
@ -50,6 +51,14 @@ public class PropertiesMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Map<String, String> buildSectionProperties(SectionNode node) {
|
||||||
|
|
||||||
|
Map<String, String> properties = new HashMap<>();
|
||||||
|
properties.put("excludesTables", String.valueOf(node.isExcludesTables()));
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void parseImageProperties(Map<String, String> properties, ImageNode.ImageNodeBuilder builder) {
|
public static void parseImageProperties(Map<String, String> properties, ImageNode.ImageNodeBuilder builder) {
|
||||||
|
|
||||||
builder.imageType(parseImageType(properties.get("imageType")));
|
builder.imageType(parseImageType(properties.get("imageType")));
|
||||||
@ -75,6 +84,12 @@ public class PropertiesMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void parseSectionProperties(Map<String, String> properties, SectionNode.SectionNodeBuilder builder) {
|
||||||
|
|
||||||
|
builder.excludesTables(Boolean.parseBoolean(properties.get("excludesTables")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static ImageType parseImageType(String imageType) {
|
public static ImageType parseImageType(String imageType) {
|
||||||
|
|
||||||
return switch (imageType) {
|
return switch (imageType) {
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory;
|
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory;
|
||||||
|
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
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.awt.geom.Rectangle2D;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -15,8 +14,6 @@ import java.util.NoSuchElementException;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Footer;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Footer;
|
||||||
@ -24,11 +21,11 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Table;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.FooterNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.FooterNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeaderNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeaderNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeadlineNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeadlineNode;
|
||||||
@ -45,7 +42,9 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.Re
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.TextPositionOperations;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.TextPositionOperations;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
|
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
|
||||||
|
|
||||||
@Service
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
public class DocumentGraphFactory {
|
public class DocumentGraphFactory {
|
||||||
|
|
||||||
public static final double TABLE_CELL_MERGE_CONTENTS_SIZE_THRESHOLD = 0.05;
|
public static final double TABLE_CELL_MERGE_CONTENTS_SIZE_THRESHOLD = 0.05;
|
||||||
@ -57,7 +56,7 @@ public class DocumentGraphFactory {
|
|||||||
DocumentGraph documentGraph = new DocumentGraph();
|
DocumentGraph documentGraph = new DocumentGraph();
|
||||||
Context context = new Context(new TableOfContents(documentGraph), new HashMap<>(), new LinkedList<>(), new LinkedList<>(), textBlockFactory);
|
Context context = new Context(new TableOfContents(documentGraph), new HashMap<>(), new LinkedList<>(), new LinkedList<>(), textBlockFactory);
|
||||||
|
|
||||||
document.getPages().stream().map(this::buildPage).forEach(page -> context.pages().put(page, new AtomicInteger(1)));
|
document.getPages().stream().map(DocumentGraphFactory::buildPage).forEach(page -> context.pages().put(page, new AtomicInteger(1)));
|
||||||
document.getSections().stream().flatMap(section -> section.getImages().stream()).forEach(image -> context.images().add(image));
|
document.getSections().stream().flatMap(section -> section.getImages().stream()).forEach(image -> context.images().add(image));
|
||||||
addSections(document, context);
|
addSections(document, context);
|
||||||
addHeaderAndFooterToEachPage(document, context);
|
addHeaderAndFooterToEachPage(document, context);
|
||||||
@ -72,68 +71,11 @@ public class DocumentGraphFactory {
|
|||||||
|
|
||||||
private void addSections(Document document, Context context) {
|
private void addSections(Document document, Context context) {
|
||||||
|
|
||||||
document.getSections().forEach(section -> addSection(null, section.getPageBlocks(), section.getImages(), context));
|
document.getSections().forEach(section -> SectionNodeFactory.addSection(null, section.getPageBlocks(), section.getImages(), context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addSection(SemanticNode parentNode, List<AbstractPageBlock> pageBlocks, List<ClassifiedImage> images, Context context) {
|
public void addTable(SemanticNode parentNode, TablePageBlock table, Context context) {
|
||||||
|
|
||||||
Map<Integer, List<AbstractPageBlock>> blocksPerPage = pageBlocks.stream().collect(groupingBy(AbstractPageBlock::getPage));
|
|
||||||
SectionNode sectionNode = SectionNode.builder().entities(new HashSet<>()).tableOfContents(context.tableOfContents()).build();
|
|
||||||
|
|
||||||
context.sections().add(sectionNode);
|
|
||||||
blocksPerPage.keySet().forEach(pageNumber -> addSectionNodeToPageNode(context, sectionNode, pageNumber));
|
|
||||||
|
|
||||||
List<Integer> tocId;
|
|
||||||
if (parentNode == null) {
|
|
||||||
tocId = context.tableOfContents.createNewMainEntryAndReturnId(NodeType.SECTION, sectionNode);
|
|
||||||
} else {
|
|
||||||
tocId = context.tableOfContents.createNewChildEntryAndReturnId(parentNode.getTocId(), NodeType.SECTION, sectionNode);
|
|
||||||
}
|
|
||||||
sectionNode.setTocId(tocId);
|
|
||||||
Set<AbstractPageBlock> alreadyMerged = new HashSet<>();
|
|
||||||
for (AbstractPageBlock abstractPageBlock : pageBlocks) {
|
|
||||||
|
|
||||||
if (alreadyMerged.contains(abstractPageBlock)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abstractPageBlock instanceof TextBlock) {
|
|
||||||
List<TextBlock> textBlocks = findTextBlocksWithSameClassificationAndAlignsY(abstractPageBlock, pageBlocks);
|
|
||||||
alreadyMerged.addAll(textBlocks);
|
|
||||||
addParagraphOrHeadline(sectionNode, (TextBlock) abstractPageBlock, context, textBlocks);
|
|
||||||
}
|
|
||||||
if (abstractPageBlock instanceof Table) {
|
|
||||||
addTable(sectionNode, (Table) abstractPageBlock, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (ClassifiedImage image : images) {
|
|
||||||
|
|
||||||
addImage(sectionNode, image, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static List<TextBlock> findTextBlocksWithSameClassificationAndAlignsY(AbstractPageBlock atc, List<AbstractPageBlock> pageBlocks) {
|
|
||||||
|
|
||||||
return pageBlocks.stream()
|
|
||||||
.filter(abstractTextContainer -> !abstractTextContainer.equals(atc))
|
|
||||||
.filter(abstractTextContainer -> abstractTextContainer.getPage() == atc.getPage())
|
|
||||||
.filter(abstractTextContainer -> abstractTextContainer instanceof TextBlock)
|
|
||||||
.filter(abstractTextContainer -> abstractTextContainer.intersectsY(atc))
|
|
||||||
.map(abstractTextContainer -> (TextBlock) abstractTextContainer)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void addSectionNodeToPageNode(Context context, SectionNode sectionNode, Integer pageNumber) {
|
|
||||||
|
|
||||||
PageNode page = getPage(pageNumber, context);
|
|
||||||
page.getMainBody().add(sectionNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void addTable(SemanticNode parentNode, Table table, Context context) {
|
|
||||||
|
|
||||||
PageNode page = getPage(table.getPage(), context);
|
PageNode page = getPage(table.getPage(), context);
|
||||||
TableNode tableNode = TableNode.builder().tableOfContents(context.tableOfContents()).numberOfCols(table.getColCount()).numberOfRows(table.getRowCount()).build();
|
TableNode tableNode = TableNode.builder().tableOfContents(context.tableOfContents()).numberOfCols(table.getColCount()).numberOfRows(table.getRowCount()).build();
|
||||||
@ -151,11 +93,11 @@ public class DocumentGraphFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addTableCells(List<List<Cell>> rows, SemanticNode parentNode, Context context, int pageNumber) {
|
private void addTableCells(List<List<Cell>> rows, TableNode tableNode, Context context, int pageNumber) {
|
||||||
|
|
||||||
for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
|
for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
|
||||||
for (int colIndex = 0; colIndex < rows.get(rowIndex).size(); colIndex++) {
|
for (int colIndex = 0; colIndex < rows.get(rowIndex).size(); colIndex++) {
|
||||||
addTableCell(rows.get(rowIndex).get(colIndex), rowIndex, colIndex, parentNode, pageNumber, context);
|
addTableCell(rows.get(rowIndex).get(colIndex), rowIndex, colIndex, tableNode, pageNumber, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,7 +132,7 @@ public class DocumentGraphFactory {
|
|||||||
tableCellNode.setTerminal(true);
|
tableCellNode.setTerminal(true);
|
||||||
|
|
||||||
} else if (firstTextBlockIsHeadline(cell)) {
|
} else if (firstTextBlockIsHeadline(cell)) {
|
||||||
addSection(tableCellNode, cell.getTextBlocks().stream().map(tb -> (AbstractPageBlock) tb).toList(), Collections.emptyList(), context);
|
SectionNodeFactory.addSection(tableCellNode, cell.getTextBlocks().stream().map(tb -> (AbstractPageBlock) tb).toList(), emptyList(), context);
|
||||||
tableCellNode.setTerminal(false);
|
tableCellNode.setTerminal(false);
|
||||||
|
|
||||||
} else if (cellAreaIsSmallerThanPageAreaTimesThreshold(cell, page)) {
|
} else if (cellAreaIsSmallerThanPageAreaTimesThreshold(cell, page)) {
|
||||||
@ -220,18 +162,24 @@ public class DocumentGraphFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addParagraphOrHeadline(SemanticNode parentNode, TextBlock originalTextBlock, Context context) {
|
public static boolean pageBlockIsHeadline(AbstractPageBlock abstractPageBlock) {
|
||||||
|
|
||||||
addParagraphOrHeadline(parentNode, originalTextBlock, context, Collections.emptyList());
|
return abstractPageBlock instanceof ClassificationTextBlock && abstractPageBlock.getClassification() != null && abstractPageBlock.getClassification().startsWith("H");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addParagraphOrHeadline(SemanticNode parentNode, TextBlock originalTextBlock, Context context, List<TextBlock> textBlocksToMerge) {
|
private void addParagraphOrHeadline(SemanticNode parentNode, ClassificationTextBlock originalTextBlock, Context context) {
|
||||||
|
|
||||||
|
addParagraphOrHeadline(parentNode, originalTextBlock, context, emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addParagraphOrHeadline(SemanticNode parentNode, ClassificationTextBlock originalTextBlock, Context context, List<ClassificationTextBlock> textBlocksToMerge) {
|
||||||
|
|
||||||
PageNode page = getPage(originalTextBlock.getPage(), context);
|
PageNode page = getPage(originalTextBlock.getPage(), context);
|
||||||
|
|
||||||
SemanticNode node;
|
SemanticNode node;
|
||||||
if (originalTextBlock.getClassification() != null && originalTextBlock.getClassification().startsWith("H")) {
|
if (pageBlockIsHeadline(originalTextBlock)) {
|
||||||
node = HeadlineNode.builder().tableOfContents(context.tableOfContents()).build();
|
node = HeadlineNode.builder().tableOfContents(context.tableOfContents()).build();
|
||||||
} else {
|
} else {
|
||||||
node = ParagraphNode.builder().tableOfContents(context.tableOfContents()).build();
|
node = ParagraphNode.builder().tableOfContents(context.tableOfContents()).build();
|
||||||
@ -239,7 +187,7 @@ public class DocumentGraphFactory {
|
|||||||
|
|
||||||
page.getMainBody().add(node);
|
page.getMainBody().add(node);
|
||||||
|
|
||||||
List<TextBlock> textBlocks = new LinkedList<>(textBlocksToMerge);
|
List<ClassificationTextBlock> textBlocks = new LinkedList<>(textBlocksToMerge);
|
||||||
textBlocks.add(originalTextBlock);
|
textBlocks.add(originalTextBlock);
|
||||||
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), node, context, page);
|
AtomicTextBlock textBlock = context.textBlockFactory.buildAtomicTextBlock(TextPositionOperations.mergeAndSortTextPositionSequenceByYThenX(textBlocks), node, context, page);
|
||||||
|
|
||||||
@ -256,7 +204,7 @@ public class DocumentGraphFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addImage(SectionNode sectionNode, ClassifiedImage image, Context context) {
|
public void addImage(SectionNode sectionNode, ClassifiedImage image, Context context) {
|
||||||
|
|
||||||
Rectangle2D position = RectangleTransformations.toRectangle2D(image.getPosition());
|
Rectangle2D position = RectangleTransformations.toRectangle2D(image.getPosition());
|
||||||
PageNode page = getPage(image.getPage(), context);
|
PageNode page = getPage(image.getPage(), context);
|
||||||
@ -277,13 +225,13 @@ public class DocumentGraphFactory {
|
|||||||
|
|
||||||
private void addHeaderAndFooterToEachPage(Document document, Context context) {
|
private void addHeaderAndFooterToEachPage(Document document, Context context) {
|
||||||
|
|
||||||
Map<Integer, List<TextBlock>> headers = document.getHeaders()
|
Map<Integer, List<ClassificationTextBlock>> headers = document.getHeaders()
|
||||||
.stream()
|
.stream()
|
||||||
.map(Header::getTextBlocks)
|
.map(Header::getTextBlocks)
|
||||||
.flatMap(List::stream)
|
.flatMap(List::stream)
|
||||||
.collect(groupingBy(AbstractPageBlock::getPage, toList()));
|
.collect(groupingBy(AbstractPageBlock::getPage, toList()));
|
||||||
|
|
||||||
Map<Integer, List<TextBlock>> footers = document.getFooters()
|
Map<Integer, List<ClassificationTextBlock>> footers = document.getFooters()
|
||||||
.stream()
|
.stream()
|
||||||
.map(Footer::getTextBlocks)
|
.map(Footer::getTextBlocks)
|
||||||
.flatMap(List::stream)
|
.flatMap(List::stream)
|
||||||
@ -307,7 +255,7 @@ public class DocumentGraphFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addFooter(List<TextBlock> textBlocks, Context context) {
|
private void addFooter(List<ClassificationTextBlock> textBlocks, Context context) {
|
||||||
|
|
||||||
PageNode page = getPage(textBlocks.get(0).getPage(), context);
|
PageNode page = getPage(textBlocks.get(0).getPage(), context);
|
||||||
FooterNode footer = FooterNode.builder().tableOfContents(context.tableOfContents()).build();
|
FooterNode footer = FooterNode.builder().tableOfContents(context.tableOfContents()).build();
|
||||||
@ -322,7 +270,7 @@ public class DocumentGraphFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addHeader(List<TextBlock> textBlocks, Context context) {
|
public void addHeader(List<ClassificationTextBlock> textBlocks, Context context) {
|
||||||
|
|
||||||
PageNode page = getPage(textBlocks.get(0).getPage(), context);
|
PageNode page = getPage(textBlocks.get(0).getPage(), context);
|
||||||
HeaderNode header = HeaderNode.builder().tableOfContents(context.tableOfContents()).build();
|
HeaderNode header = HeaderNode.builder().tableOfContents(context.tableOfContents()).build();
|
||||||
@ -374,7 +322,7 @@ public class DocumentGraphFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private PageNode getPage(int pageIndex, Context context) {
|
public PageNode getPage(int pageIndex, Context context) {
|
||||||
|
|
||||||
return context.pages.keySet()
|
return context.pages.keySet()
|
||||||
.stream()
|
.stream()
|
||||||
|
|||||||
@ -0,0 +1,187 @@
|
|||||||
|
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory;
|
||||||
|
|
||||||
|
import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory.DocumentGraphFactory.pageBlockIsHeadline;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.stream.Collectors.groupingBy;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.AbstractPageBlock;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class SectionNodeFactory {
|
||||||
|
|
||||||
|
public void addSection(SemanticNode parentNode, List<AbstractPageBlock> pageBlocks, List<ClassifiedImage> images, DocumentGraphFactory.Context context) {
|
||||||
|
|
||||||
|
if (pageBlocks.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<Integer, List<AbstractPageBlock>> blocksPerPage = pageBlocks.stream().collect(groupingBy(AbstractPageBlock::getPage));
|
||||||
|
SectionNode sectionNode = SectionNode.builder().entities(new HashSet<>()).tableOfContents(context.tableOfContents()).build();
|
||||||
|
|
||||||
|
context.sections().add(sectionNode);
|
||||||
|
blocksPerPage.keySet().forEach(pageNumber -> addSectionNodeToPageNode(context, sectionNode, pageNumber));
|
||||||
|
|
||||||
|
List<Integer> tocId;
|
||||||
|
if (parentNode == null) {
|
||||||
|
tocId = context.tableOfContents().createNewMainEntryAndReturnId(NodeType.SECTION, sectionNode);
|
||||||
|
} else {
|
||||||
|
tocId = context.tableOfContents().createNewChildEntryAndReturnId(parentNode.getTocId(), NodeType.SECTION, sectionNode);
|
||||||
|
}
|
||||||
|
sectionNode.setTocId(tocId);
|
||||||
|
|
||||||
|
addFirstHeadlineDirectlyToSection(pageBlocks, context, sectionNode);
|
||||||
|
if (containsTablesAndTextBlocks(pageBlocks)) {
|
||||||
|
splitPageBlocksIntoSubSections(pageBlocks).forEach(subSectionPageBlocks -> addSection(sectionNode, subSectionPageBlocks, emptyList(), context));
|
||||||
|
} else {
|
||||||
|
addTablesOrParagraphsOrHeadlinesToSection(pageBlocks, context, sectionNode);
|
||||||
|
if (listIsTextBlockOnly(pageBlocks)) {
|
||||||
|
sectionNode.setExcludesTables(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ClassifiedImage image : images) {
|
||||||
|
DocumentGraphFactory.addImage(sectionNode, image, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean listIsTextBlockOnly(List<AbstractPageBlock> pageBlocks) {
|
||||||
|
|
||||||
|
return pageBlocks.stream().allMatch(abstractPageBlock -> abstractPageBlock instanceof ClassificationTextBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void addFirstHeadlineDirectlyToSection(List<AbstractPageBlock> pageBlocks, DocumentGraphFactory.Context context, SectionNode sectionNode) {
|
||||||
|
|
||||||
|
if (pageBlockIsHeadline(pageBlocks.get(0))) {
|
||||||
|
addTablesOrParagraphsOrHeadlinesToSection(List.of(pageBlocks.get(0)), context, sectionNode);
|
||||||
|
pageBlocks.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void addTablesOrParagraphsOrHeadlinesToSection(List<AbstractPageBlock> pageBlocks, DocumentGraphFactory.Context context, SectionNode sectionNode) {
|
||||||
|
|
||||||
|
Set<AbstractPageBlock> alreadyMerged = new HashSet<>();
|
||||||
|
for (AbstractPageBlock abstractPageBlock : pageBlocks) {
|
||||||
|
|
||||||
|
if (alreadyMerged.contains(abstractPageBlock)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abstractPageBlock instanceof ClassificationTextBlock) {
|
||||||
|
List<ClassificationTextBlock> textBlocks = findTextBlocksWithSameClassificationAndAlignsY(abstractPageBlock, pageBlocks);
|
||||||
|
alreadyMerged.addAll(textBlocks);
|
||||||
|
DocumentGraphFactory.addParagraphOrHeadline(sectionNode, (ClassificationTextBlock) abstractPageBlock, context, textBlocks);
|
||||||
|
}
|
||||||
|
if (abstractPageBlock instanceof TablePageBlock) {
|
||||||
|
DocumentGraphFactory.addTable(sectionNode, (TablePageBlock) abstractPageBlock, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean containsTablesAndTextBlocks(List<AbstractPageBlock> pageBlocks) {
|
||||||
|
|
||||||
|
return pageBlocks.stream().anyMatch(pageBlock -> pageBlock instanceof TablePageBlock) && pageBlocks.stream()
|
||||||
|
.anyMatch(pageBlock -> pageBlock instanceof ClassificationTextBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function splits the list of PageBlocks around TablePageBlocks, such that SubSections can be created, that don't include tables.
|
||||||
|
* First, the function splits the list into coherent Lists, which include only either type.
|
||||||
|
* If a Table is directly preceded by a Headline, it is moved to the Table list.
|
||||||
|
*
|
||||||
|
* @param pageBlocks a List of AbstractPageBlocks, which have at least one TablePageBlock and one ClassificationTextBlock
|
||||||
|
* @return List of Lists of AbstractPageBlocks, which include either a single Headline ClassificationTextBlock and a TablePageBlock or only ClassificationTextBlocks.
|
||||||
|
*/
|
||||||
|
private List<List<AbstractPageBlock>> splitPageBlocksIntoSubSections(List<AbstractPageBlock> pageBlocks) {
|
||||||
|
|
||||||
|
List<List<AbstractPageBlock>> splitList = splitIntoCoherentList(pageBlocks);
|
||||||
|
movePrecedingHeadlineToTableList(splitList);
|
||||||
|
return splitList.stream().filter(list -> !list.isEmpty()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void movePrecedingHeadlineToTableList(List<List<AbstractPageBlock>> splitList) {
|
||||||
|
|
||||||
|
for (int i = 0; i < splitList.size(); i++) {
|
||||||
|
if (listIsTableOnly(splitList.get(i))) {
|
||||||
|
if (i > 0) {
|
||||||
|
List<AbstractPageBlock> previousList = splitList.get(i - 1);
|
||||||
|
AbstractPageBlock lastPageBlockInPreviousList = previousList.get(previousList.size() - 1);
|
||||||
|
if (pageBlockIsHeadline(lastPageBlockInPreviousList)) {
|
||||||
|
previousList.remove(i - 1);
|
||||||
|
splitList.get(i).add(0, lastPageBlockInPreviousList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean listIsTableOnly(List<AbstractPageBlock> abstractPageBlocks) {
|
||||||
|
|
||||||
|
return abstractPageBlocks.stream().allMatch(abstractPageBlock -> abstractPageBlock instanceof TablePageBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param pageBlocks a List of AbstractPageBlocks, which have at least one TablePageBlock and one ClassificationTextBlock
|
||||||
|
* @return List of Lists of AbstractPageBlocks, which are exclusively of type ClassificationTextBlock or TablePageBlock
|
||||||
|
*/
|
||||||
|
private static List<List<AbstractPageBlock>> splitIntoCoherentList(List<AbstractPageBlock> pageBlocks) {
|
||||||
|
|
||||||
|
List<List<AbstractPageBlock>> splitList = new LinkedList<>();
|
||||||
|
List<AbstractPageBlock> currentList = new LinkedList<>();
|
||||||
|
splitList.add(currentList);
|
||||||
|
|
||||||
|
Class<? extends AbstractPageBlock> lastPageBlockClass = pageBlocks.get(0).getClass();
|
||||||
|
for (AbstractPageBlock pageBlock : pageBlocks) {
|
||||||
|
if (lastPageBlockClass.isInstance(pageBlock)) {
|
||||||
|
currentList.add(pageBlock);
|
||||||
|
} else {
|
||||||
|
currentList = new LinkedList<>();
|
||||||
|
currentList.add(pageBlock);
|
||||||
|
splitList.add(currentList);
|
||||||
|
lastPageBlockClass = pageBlock.getClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return splitList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static List<ClassificationTextBlock> findTextBlocksWithSameClassificationAndAlignsY(AbstractPageBlock atc, List<AbstractPageBlock> pageBlocks) {
|
||||||
|
|
||||||
|
return pageBlocks.stream()
|
||||||
|
.filter(abstractTextContainer -> !abstractTextContainer.equals(atc))
|
||||||
|
.filter(abstractTextContainer -> abstractTextContainer.getPage() == atc.getPage())
|
||||||
|
.filter(abstractTextContainer -> abstractTextContainer instanceof ClassificationTextBlock)
|
||||||
|
.filter(abstractTextContainer -> abstractTextContainer.intersectsY(atc))
|
||||||
|
.map(abstractTextContainer -> (ClassificationTextBlock) abstractTextContainer)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void addSectionNodeToPageNode(DocumentGraphFactory.Context context, SectionNode sectionNode, Integer pageNumber) {
|
||||||
|
|
||||||
|
PageNode page = DocumentGraphFactory.getPage(pageNumber, context);
|
||||||
|
page.getMainBody().add(sectionNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph;
|
|||||||
|
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -105,7 +106,7 @@ public class Boundary implements Comparable<Boundary> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Boundary merge(List<Boundary> boundaries) {
|
public static Boundary merge(Collection<Boundary> boundaries) {
|
||||||
|
|
||||||
int minStart = boundaries.stream().mapToInt(Boundary::start).min().orElseThrow(IllegalArgumentException::new);
|
int minStart = boundaries.stream().mapToInt(Boundary::start).min().orElseThrow(IllegalArgumentException::new);
|
||||||
int maxEnd = boundaries.stream().mapToInt(Boundary::end).max().orElseThrow(IllegalArgumentException::new);
|
int maxEnd = boundaries.stream().mapToInt(Boundary::end).max().orElseThrow(IllegalArgumentException::new);
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import java.util.List;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
@ -84,7 +85,7 @@ public class TableOfContents {
|
|||||||
|
|
||||||
public boolean hasParentById(List<Integer> tocId) {
|
public boolean hasParentById(List<Integer> tocId) {
|
||||||
|
|
||||||
return entryExists(getParentId(tocId));
|
return !tocId.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -45,14 +45,15 @@ public class RedactionEntity {
|
|||||||
boolean dossierDictionaryEntry;
|
boolean dossierDictionaryEntry;
|
||||||
Set<Engine> engines;
|
Set<Engine> engines;
|
||||||
Set<RedactionEntity> references;
|
Set<RedactionEntity> references;
|
||||||
int matchedRule;
|
@Builder.Default
|
||||||
|
List<Integer> matchedRules = new LinkedList<>();
|
||||||
String redactionReason;
|
String redactionReason;
|
||||||
String legalBasis;
|
String legalBasis;
|
||||||
|
|
||||||
// inferred on graph insertion
|
// inferred on graph insertion
|
||||||
String value;
|
String value;
|
||||||
CharSequence textBefore;
|
String textBefore;
|
||||||
CharSequence textAfter;
|
String textAfter;
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
Set<PageNode> pages = new HashSet<>();
|
Set<PageNode> pages = new HashSet<>();
|
||||||
List<RedactionPosition> redactionPositionsPerPage;
|
List<RedactionPosition> redactionPositionsPerPage;
|
||||||
@ -76,13 +77,30 @@ public class RedactionEntity {
|
|||||||
.dossierDictionaryEntry(false)
|
.dossierDictionaryEntry(false)
|
||||||
.engines(new HashSet<>())
|
.engines(new HashSet<>())
|
||||||
.references(new HashSet<>())
|
.references(new HashSet<>())
|
||||||
.matchedRule(-1)
|
|
||||||
.redactionReason("")
|
.redactionReason("")
|
||||||
.legalBasis("")
|
.legalBasis("")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean occursInNodeOfType(Class<? extends SemanticNode> clazz) {
|
||||||
|
|
||||||
|
return intersectingNodes.stream().anyMatch(clazz::isInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isType(String type) {
|
||||||
|
|
||||||
|
return this.type.equals(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isAnyType(List<String> types) {
|
||||||
|
|
||||||
|
return types.contains(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addIntersectingNode(SemanticNode containingNode) {
|
public void addIntersectingNode(SemanticNode containingNode) {
|
||||||
|
|
||||||
intersectingNodes.add(containingNode);
|
intersectingNodes.add(containingNode);
|
||||||
@ -91,10 +109,29 @@ public class RedactionEntity {
|
|||||||
|
|
||||||
public void removeFromGraph() {
|
public void removeFromGraph() {
|
||||||
|
|
||||||
getIntersectingNodes().forEach(node -> node.getEntities().remove(this));
|
intersectingNodes.forEach(node -> node.getEntities().remove(this));
|
||||||
|
pages.forEach(page -> page.getEntities().remove(this));
|
||||||
|
|
||||||
|
intersectingNodes = new LinkedList<>();
|
||||||
deepestFullyContainingNode = null;
|
deepestFullyContainingNode = null;
|
||||||
getPages().forEach(page -> page.getEntities().remove(this));
|
pages = new HashSet<>();
|
||||||
setRemoved(true);
|
removed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addMatchedRule(int ruleNumber) {
|
||||||
|
|
||||||
|
matchedRules.add(ruleNumber);
|
||||||
|
engines.add(Engine.RULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getMatchedRule() {
|
||||||
|
|
||||||
|
if (matchedRules.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return matchedRules.get(matchedRules.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -155,9 +192,15 @@ public class RedactionEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addReference(RedactionEntity redactionEntity) {
|
public void addReference(RedactionEntity reference) {
|
||||||
|
|
||||||
references.add(redactionEntity);
|
references.add(reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addReferences(List<RedactionEntity> references) {
|
||||||
|
|
||||||
|
this.references.addAll(references);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph;
|
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes;
|
||||||
|
|
||||||
import java.awt.geom.Rectangle2D;
|
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.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.iqser.red.service.redaction.v1.server.exception.NotFoundException;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlockCollector;
|
||||||
|
|
||||||
@ -35,6 +34,8 @@ public class DocumentGraph implements SemanticNode {
|
|||||||
TableOfContents tableOfContents;
|
TableOfContents tableOfContents;
|
||||||
Integer numberOfPages;
|
Integer numberOfPages;
|
||||||
TextBlock textBlock;
|
TextBlock textBlock;
|
||||||
|
@Builder.Default
|
||||||
|
Set<RedactionEntity> entities = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
public TextBlock buildTextBlock() {
|
public TextBlock buildTextBlock() {
|
||||||
@ -55,12 +56,6 @@ public class DocumentGraph implements SemanticNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Set<RedactionEntity> getEntities() {
|
|
||||||
|
|
||||||
return streamAllSubNodes().map(SemanticNode::getEntities).flatMap(Set::stream).collect(Collectors.toUnmodifiableSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Integer> getTocId() {
|
public List<Integer> getTocId() {
|
||||||
|
|
||||||
@ -71,7 +66,17 @@ public class DocumentGraph implements SemanticNode {
|
|||||||
@Override
|
@Override
|
||||||
public void setTocId(List<Integer> tocId) {
|
public void setTocId(List<Integer> tocId) {
|
||||||
|
|
||||||
throw new UnsupportedOperationException("DocumentGraph is always the root of the Table of Contents");
|
throw new UnsupportedOperationException("DocumentGraph is always the root of the TablePageBlock of Contents");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HeadlineNode getHeadline() {
|
||||||
|
|
||||||
|
return streamAllSubNodes().filter(node -> node instanceof HeadlineNode)
|
||||||
|
.map(node -> (HeadlineNode) node)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new NotFoundException("No Headlines found in this document!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ public class HeadlineNode implements SemanticNode {
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SemanticNode getHeadline() {
|
public HeadlineNode getHeadline() {
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.exception.NotFoundException;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
@ -30,6 +29,7 @@ public class SectionNode implements SemanticNode {
|
|||||||
TextBlock textBlock;
|
TextBlock textBlock;
|
||||||
@EqualsAndHashCode.Exclude
|
@EqualsAndHashCode.Exclude
|
||||||
TableOfContents tableOfContents;
|
TableOfContents tableOfContents;
|
||||||
|
boolean excludesTables;
|
||||||
|
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
@EqualsAndHashCode.Exclude
|
@EqualsAndHashCode.Exclude
|
||||||
@ -55,10 +55,11 @@ public class SectionNode implements SemanticNode {
|
|||||||
|
|
||||||
public HeadlineNode getHeadline() {
|
public HeadlineNode getHeadline() {
|
||||||
|
|
||||||
return streamChildren().filter(node -> node instanceof HeadlineNode)
|
return streamChildren()//
|
||||||
.map(node -> (HeadlineNode) node)
|
.filter(node -> node instanceof HeadlineNode)//
|
||||||
.findFirst()
|
.map(node -> (HeadlineNode) node)//
|
||||||
.orElseThrow(() -> new NotFoundException("Section has no Headline!"));
|
.findFirst()//
|
||||||
|
.orElseGet(() -> getParent().getHeadline());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,7 +91,7 @@ public interface SemanticNode {
|
|||||||
*
|
*
|
||||||
* @return First HeadlineNode found
|
* @return First HeadlineNode found
|
||||||
*/
|
*/
|
||||||
default SemanticNode getHeadline() {
|
default HeadlineNode getHeadline() {
|
||||||
|
|
||||||
return getParent().getHeadline();
|
return getParent().getHeadline();
|
||||||
}
|
}
|
||||||
@ -157,6 +157,24 @@ public interface SemanticNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default boolean hasEntitiesOfType(String type) {
|
||||||
|
|
||||||
|
return getEntities().stream().anyMatch(redactionEntity -> redactionEntity.getType().equals(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default List<RedactionEntity> getEntitiesOfType(String type) {
|
||||||
|
|
||||||
|
return getEntities().stream().filter(redactionEntity -> redactionEntity.getType().equals(type)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default List<RedactionEntity> getEntitiesOfType(List<String> types) {
|
||||||
|
|
||||||
|
return getEntities().stream().filter(redactionEntity -> redactionEntity.isAnyType(types)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Each AtomicTextBlock has an index on its page, this returns the number of the first AtomicTextBlock underneath this node.
|
* Each AtomicTextBlock has an index on its page, this returns the number of the first AtomicTextBlock underneath this node.
|
||||||
* If this node does not have any AtomicTexBlocks underneath it, e.g. an empty TableCell. It returns -1.
|
* If this node does not have any AtomicTexBlocks underneath it, e.g. an empty TableCell. It returns -1.
|
||||||
@ -184,8 +202,8 @@ public interface SemanticNode {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string A String which the ClassificationTextBlock might contain
|
* @param string A String which the TextBlock might contain
|
||||||
* @return true, if this node's ClassificationTextBlock contains the string
|
* @return true, if this node's TextBlock contains the string
|
||||||
*/
|
*/
|
||||||
default boolean containsString(String string) {
|
default boolean containsString(String string) {
|
||||||
|
|
||||||
@ -193,6 +211,26 @@ public interface SemanticNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param strings A List of Strings which the TextBlock might contain
|
||||||
|
* @return true, if this node's TextBlock contains all strings
|
||||||
|
*/
|
||||||
|
default boolean containsStrings(List<String> strings) {
|
||||||
|
|
||||||
|
return strings.stream().allMatch(this::containsString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string A String which the ClassificationTextBlock might contain
|
||||||
|
* @return true, if this node's ClassificationTextBlock contains the string
|
||||||
|
*/
|
||||||
|
default boolean containsStringIgnoreCase(String string) {
|
||||||
|
|
||||||
|
return buildTextBlock().getSearchText().toLowerCase().contains(string.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param strings A List of Strings which the ClassificationTextBlock might contain
|
* @param strings A List of Strings which the ClassificationTextBlock might contain
|
||||||
* @return true, if this node's ClassificationTextBlock contains any of the strings
|
* @return true, if this node's ClassificationTextBlock contains any of the strings
|
||||||
@ -203,6 +241,16 @@ public interface SemanticNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param strings A List of Strings which the ClassificationTextBlock might contain
|
||||||
|
* @return true, if this node's ClassificationTextBlock contains any of the strings
|
||||||
|
*/
|
||||||
|
default boolean containsAnyStringIgnoreCase(List<String> strings) {
|
||||||
|
|
||||||
|
return strings.stream().anyMatch(this::containsStringIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is used during insertion of EntityNodes into the graph, it checks if the boundary of the RedactionEntity intersects or even contains the RedactionEntity.
|
* This function is used during insertion of EntityNodes into the graph, it checks if the boundary of the RedactionEntity intersects or even contains the RedactionEntity.
|
||||||
* It sets the fields accordingly and recursively calls this function on all its children.
|
* It sets the fields accordingly and recursively calls this function on all its children.
|
||||||
@ -213,13 +261,13 @@ public interface SemanticNode {
|
|||||||
|
|
||||||
TextBlock textBlock = buildTextBlock();
|
TextBlock textBlock = buildTextBlock();
|
||||||
if (textBlock.getBoundary().intersects(redactionEntity.getBoundary())) {
|
if (textBlock.getBoundary().intersects(redactionEntity.getBoundary())) {
|
||||||
|
|
||||||
if (textBlock.containsBoundary(redactionEntity.getBoundary())) {
|
if (textBlock.containsBoundary(redactionEntity.getBoundary())) {
|
||||||
redactionEntity.setDeepestFullyContainingNode(this);
|
redactionEntity.setDeepestFullyContainingNode(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
redactionEntity.addIntersectingNode(this);
|
redactionEntity.addIntersectingNode(this);
|
||||||
streamChildren().forEach(node -> node.addThisToEntityIfIntersects(redactionEntity));
|
streamChildren().filter(semanticNode -> semanticNode.getBoundary().intersects(redactionEntity.getBoundary()))
|
||||||
|
.forEach(node -> node.addThisToEntityIfIntersects(redactionEntity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes;
|
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
||||||
@ -26,8 +30,8 @@ public class TableNode implements SemanticNode {
|
|||||||
List<Integer> tocId;
|
List<Integer> tocId;
|
||||||
TableOfContents tableOfContents;
|
TableOfContents tableOfContents;
|
||||||
|
|
||||||
Integer numberOfRows;
|
int numberOfRows;
|
||||||
Integer numberOfCols;
|
int numberOfCols;
|
||||||
|
|
||||||
TextBlock textBlock;
|
TextBlock textBlock;
|
||||||
|
|
||||||
@ -36,12 +40,95 @@ public class TableNode implements SemanticNode {
|
|||||||
Set<RedactionEntity> entities = new HashSet<>();
|
Set<RedactionEntity> entities = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
|
public Stream<RedactionEntity> streamEntitiesWhereRowContainsStringsIgnoreCase(List<String> strings) {
|
||||||
|
|
||||||
|
return IntStream.range(0, numberOfRows)
|
||||||
|
.boxed()
|
||||||
|
.filter(row -> rowContainsStringsIgnoreCase(row, strings))
|
||||||
|
.flatMap(this::streamRow)
|
||||||
|
.map(TableCellNode::getEntities)
|
||||||
|
.flatMap(Collection::stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean rowContainsStringsIgnoreCase(Integer row, List<String> strings) {
|
||||||
|
|
||||||
|
String rowText = streamRow(row).map(TableCellNode::buildTextBlock).collect(new TextBlockCollector()).getSearchText().toLowerCase();
|
||||||
|
return strings.stream().map(String::toLowerCase).allMatch(rowText::contains);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Stream<RedactionEntity> streamEntitiesWhereRowHasHeaderAndValue(String header, String value) {
|
||||||
|
|
||||||
|
List<Integer> vertebrateStudyCols = streamHeaders().filter(headerNode -> headerNode.containsString(header)).map(TableCellNode::getCol).toList();
|
||||||
|
return streamTableCells().filter(tableCellNode -> vertebrateStudyCols.stream()
|
||||||
|
.anyMatch(vertebrateStudyCol -> getCell(tableCellNode.getRow(), vertebrateStudyCol).containsString(value)))
|
||||||
|
.map(TableCellNode::getEntities)
|
||||||
|
.flatMap(Collection::stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Stream<RedactionEntity> streamEntitiesWhereRowHasHeaderAndAnyValue(String header, List<String> values) {
|
||||||
|
|
||||||
|
List<Integer> vertebrateStudyCols = streamHeaders().filter(headerNode -> headerNode.containsString(header)).map(TableCellNode::getCol).toList();
|
||||||
|
return streamTableCells().filter(tableCellNode -> vertebrateStudyCols.stream()
|
||||||
|
.anyMatch(vertebrateStudyCol -> getCell(tableCellNode.getRow(), vertebrateStudyCol).containsAnyString(values)))
|
||||||
|
.map(TableCellNode::getEntities)
|
||||||
|
.flatMap(Collection::stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Stream<RedactionEntity> streamEntitiesWhereRowContainsEntitiesOfType(String type) {
|
||||||
|
|
||||||
|
List<Integer> rowsWithEntityOfType = getEntities().stream()
|
||||||
|
.filter(redactionEntity -> redactionEntity.getType().equals(type))
|
||||||
|
.map(RedactionEntity::getIntersectingNodes)
|
||||||
|
.filter(node -> node instanceof TableCellNode)
|
||||||
|
.map(node -> (TableCellNode) node)
|
||||||
|
.map(TableCellNode::getRow)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return rowsWithEntityOfType.stream().flatMap(this::streamRow).map(TableCellNode::getEntities).flatMap(Collection::stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TableCellNode getCell(int row, int col) {
|
||||||
|
|
||||||
|
if (numberOfRows - row < 0 || numberOfCols - col < 0) {
|
||||||
|
throw new IllegalArgumentException(format("row %d, col %d is out of bounds for number of rows of %d and number of cols %d", row, col, numberOfRows, numberOfCols));
|
||||||
|
}
|
||||||
|
int idx = row * numberOfCols + col;
|
||||||
|
return (TableCellNode) tableOfContents.getEntryById(tocId).getChildren().get(idx).getNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Stream<TableCellNode> streamTableCells() {
|
public Stream<TableCellNode> streamTableCells() {
|
||||||
|
|
||||||
return streamChildren().map(node -> (TableCellNode) node);
|
return streamChildren().map(node -> (TableCellNode) node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Stream<TableCellNode> streamTableCellsWithHeader(String header) {
|
||||||
|
|
||||||
|
return streamHeaders().filter(tableCellNode -> tableCellNode.buildTextBlock().getSearchText().contains(header))
|
||||||
|
.map(TableCellNode::getCol)
|
||||||
|
.flatMap(this::streamCol)
|
||||||
|
.filter(tableCellNode -> !tableCellNode.isHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Stream<TableCellNode> streamCol(int col) {
|
||||||
|
|
||||||
|
return streamTableCells().filter(tableCellNode -> tableCellNode.getCol() == col);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Stream<TableCellNode> streamRow(int row) {
|
||||||
|
|
||||||
|
return streamTableCells().filter(tableCellNode -> tableCellNode.getRow() == row);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Stream<TableCellNode> streamHeaders() {
|
public Stream<TableCellNode> streamHeaders() {
|
||||||
|
|
||||||
return streamTableCells().filter(TableCellNode::isHeader);
|
return streamTableCells().filter(TableCellNode::isHeader);
|
||||||
@ -54,6 +141,24 @@ public class TableNode implements SemanticNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasHeader(String header) {
|
||||||
|
|
||||||
|
return streamHeaders().anyMatch(tableCellNode -> tableCellNode.buildTextBlock().getSearchText().contains(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasRowWithHeaderAndValue(String header, String value) {
|
||||||
|
|
||||||
|
return streamTableCellsWithHeader(header).anyMatch(tableCellNode -> tableCellNode.containsString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasRowWithHeaderAndAnyValue(String header, List<String> values) {
|
||||||
|
|
||||||
|
return streamTableCellsWithHeader(header).anyMatch(tableCellNode -> tableCellNode.containsAnyString(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TextBlock buildTextBlock() {
|
public TextBlock buildTextBlock() {
|
||||||
|
|
||||||
@ -67,7 +172,7 @@ public class TableNode implements SemanticNode {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|
||||||
return tocId.toString() + ": " + NodeType.TABLE + ": " + buildTextBlock().buildSummary();
|
return tocId.toString() + ": " + NodeType.TABLE + ": #cols: " + numberOfCols + ", #rows: " + numberOfRows + ", " + buildTextBlock().buildSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
||||||
@ -162,10 +163,7 @@ public class ConcatenatedTextBlock implements TextBlock {
|
|||||||
private Map<PageNode, List<Rectangle2D>> mergeEntityPositionsWithSamePageNode(Map<PageNode, List<Rectangle2D>> map1, Map<PageNode, List<Rectangle2D>> map2) {
|
private Map<PageNode, List<Rectangle2D>> mergeEntityPositionsWithSamePageNode(Map<PageNode, List<Rectangle2D>> map1, Map<PageNode, List<Rectangle2D>> map2) {
|
||||||
|
|
||||||
Map<PageNode, List<Rectangle2D>> mergedMap = new HashMap<>(map1);
|
Map<PageNode, List<Rectangle2D>> mergedMap = new HashMap<>(map1);
|
||||||
map2.forEach((pageNode, rectangles) -> mergedMap.merge(pageNode, rectangles, (rects1, rects2) -> {
|
map2.forEach((pageNode, rectangles) -> mergedMap.merge(pageNode, rectangles, (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).toList()));
|
||||||
rects1.addAll(rects2);
|
|
||||||
return rects1;
|
|
||||||
}));
|
|
||||||
return mergedMap;
|
return mergedMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.services;
|
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.services;
|
||||||
|
|
||||||
|
import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtils.getExpandedEndByRegex;
|
||||||
|
import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtils.getExpandedStartByRegex;
|
||||||
import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.boundaryIsSurroundedBySeparators;
|
import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.boundaryIsSurroundedBySeparators;
|
||||||
import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.isWhiteSpacesOrSeparatorsOnly;
|
import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.isWhiteSpacesOrSeparatorsOnly;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -16,12 +20,15 @@ import org.springframework.stereotype.Service;
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionPosition;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCellNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.CharSequenceSearchUtils;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtils;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -37,8 +44,8 @@ public class EntityCreationService {
|
|||||||
public Set<RedactionEntity> betweenStrings(String start, String stop, String type, EntityType entityType, SemanticNode node) {
|
public Set<RedactionEntity> betweenStrings(String start, String stop, String type, EntityType entityType, SemanticNode node) {
|
||||||
|
|
||||||
TextBlock textBlock = node.buildTextBlock();
|
TextBlock textBlock = node.buildTextBlock();
|
||||||
List<Boundary> startBoundaries = CharSequenceSearchUtils.findBoundariesByString(start, textBlock);
|
List<Boundary> startBoundaries = RedactionSearchUtils.findBoundariesByString(start, textBlock);
|
||||||
List<Boundary> stopBoundaries = CharSequenceSearchUtils.findBoundariesByString(stop, textBlock);
|
List<Boundary> stopBoundaries = RedactionSearchUtils.findBoundariesByString(stop, textBlock);
|
||||||
|
|
||||||
List<Boundary> entityBoundaries = new LinkedList<>();
|
List<Boundary> entityBoundaries = new LinkedList<>();
|
||||||
if (startBoundaries.isEmpty() || stopBoundaries.isEmpty()) {
|
if (startBoundaries.isEmpty() || stopBoundaries.isEmpty()) {
|
||||||
@ -85,7 +92,7 @@ public class EntityCreationService {
|
|||||||
public Set<RedactionEntity> lineAfterString(String string, String type, EntityType entityType, SemanticNode node) {
|
public Set<RedactionEntity> lineAfterString(String string, String type, EntityType entityType, SemanticNode node) {
|
||||||
|
|
||||||
TextBlock textBlock = node.buildTextBlock();
|
TextBlock textBlock = node.buildTextBlock();
|
||||||
List<Boundary> boundaries = CharSequenceSearchUtils.findBoundariesByString(string, textBlock);
|
List<Boundary> boundaries = RedactionSearchUtils.findBoundariesByString(string, textBlock);
|
||||||
return boundaries.stream()
|
return boundaries.stream()
|
||||||
.map(boundary -> toLineAfterBoundary(textBlock, boundary))
|
.map(boundary -> toLineAfterBoundary(textBlock, boundary))
|
||||||
.filter(boundary -> isValidEntityBoundary(textBlock, boundary))
|
.filter(boundary -> isValidEntityBoundary(textBlock, boundary))
|
||||||
@ -96,7 +103,14 @@ public class EntityCreationService {
|
|||||||
|
|
||||||
public Set<RedactionEntity> byRegex(String regexPattern, String type, EntityType entityType, SemanticNode node) {
|
public Set<RedactionEntity> byRegex(String regexPattern, String type, EntityType entityType, SemanticNode node) {
|
||||||
|
|
||||||
List<Boundary> boundaries = CharSequenceSearchUtils.findBoundariesByRegex(regexPattern, node.buildTextBlock());
|
List<Boundary> boundaries = RedactionSearchUtils.findBoundariesByRegex(regexPattern, node.buildTextBlock());
|
||||||
|
return boundaries.stream().map(boundary -> byBoundary(boundary, type, entityType, node)).collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Set<RedactionEntity> byKeyword(String keyword, String type, EntityType entityType, SemanticNode node) {
|
||||||
|
|
||||||
|
List<Boundary> boundaries = RedactionSearchUtils.findBoundariesByString(keyword, node.buildTextBlock());
|
||||||
return boundaries.stream().map(boundary -> byBoundary(boundary, type, entityType, node)).collect(Collectors.toUnmodifiableSet());
|
return boundaries.stream().map(boundary -> byBoundary(boundary, type, entityType, node)).collect(Collectors.toUnmodifiableSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,10 +123,24 @@ public class EntityCreationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RedactionEntity byPrefixExpansionRegex(RedactionEntity entity, String regexPattern) {
|
||||||
|
|
||||||
|
int expandedStart = getExpandedStartByRegex(entity, regexPattern);
|
||||||
|
return byBoundary(new Boundary(expandedStart, entity.getBoundary().end()), entity.getType(), entity.getEntityType(), entity.getDeepestFullyContainingNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RedactionEntity bySuffixExpansionRegex(RedactionEntity entity, String regexPattern) {
|
||||||
|
|
||||||
|
int expandedEnd = getExpandedEndByRegex(entity, regexPattern);
|
||||||
|
return byBoundary(new Boundary(entity.getBoundary().start(), expandedEnd), entity.getType(), entity.getEntityType(), entity.getDeepestFullyContainingNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public RedactionEntity byBoundary(Boundary boundary, String type, EntityType entityType, SemanticNode node) {
|
public RedactionEntity byBoundary(Boundary boundary, String type, EntityType entityType, SemanticNode node) {
|
||||||
|
|
||||||
RedactionEntity entity = RedactionEntity.initialEntityNode(boundary, type, entityType);
|
RedactionEntity entity = RedactionEntity.initialEntityNode(boundary, type, entityType);
|
||||||
addEntityToGraph(entity, node.getTableOfContents());
|
addEntityToGraph(entity, node);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,25 +157,65 @@ public class EntityCreationService {
|
|||||||
RedactionEntity mergedEntity = RedactionEntity.initialEntityNode(Boundary.merge(entitiesToMerge.stream().map(RedactionEntity::getBoundary).toList()), type, entityType);
|
RedactionEntity mergedEntity = RedactionEntity.initialEntityNode(Boundary.merge(entitiesToMerge.stream().map(RedactionEntity::getBoundary).toList()), type, entityType);
|
||||||
mergedEntity.setRedaction(entitiesToMerge.stream().anyMatch(RedactionEntity::isRedaction));
|
mergedEntity.setRedaction(entitiesToMerge.stream().anyMatch(RedactionEntity::isRedaction));
|
||||||
mergedEntity.addEngines(entitiesToMerge.stream().flatMap(entityNode -> entityNode.getEngines().stream()).collect(Collectors.toSet()));
|
mergedEntity.addEngines(entitiesToMerge.stream().flatMap(entityNode -> entityNode.getEngines().stream()).collect(Collectors.toSet()));
|
||||||
|
entitiesToMerge.stream().map(RedactionEntity::getMatchedRules).flatMap(Collection::stream).forEach(mergedEntity::addMatchedRule);
|
||||||
|
|
||||||
RedactionEntity entityWithHigherRuleNumber = entitiesToMerge.stream().max(Comparator.comparingInt(RedactionEntity::getMatchedRule)).orElse(entitiesToMerge.get(0));
|
RedactionEntity entityWithHigherRuleNumber = entitiesToMerge.stream().max(Comparator.comparingInt(RedactionEntity::getMatchedRule)).orElse(entitiesToMerge.get(0));
|
||||||
mergedEntity.setRedactionReason(entityWithHigherRuleNumber.getRedactionReason());
|
mergedEntity.setRedactionReason(entityWithHigherRuleNumber.getRedactionReason());
|
||||||
mergedEntity.setMatchedRule(entityWithHigherRuleNumber.getMatchedRule());
|
|
||||||
mergedEntity.setLegalBasis(entityWithHigherRuleNumber.getLegalBasis());
|
mergedEntity.setLegalBasis(entityWithHigherRuleNumber.getLegalBasis());
|
||||||
|
|
||||||
addEntityToGraph(mergedEntity, node.getTableOfContents());
|
addEntityToGraph(mergedEntity, node);
|
||||||
return mergedEntity;
|
return mergedEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RedactionEntity byTableCellAsHighlight(TableCellNode tableCellNode, String type, EntityType entityType) {
|
||||||
|
|
||||||
|
RedactionEntity highlightEntity = RedactionEntity.initialEntityNode(new Boundary(tableCellNode.getBoundary().start(), tableCellNode.getBoundary().start()),
|
||||||
|
type,
|
||||||
|
entityType);
|
||||||
|
|
||||||
|
String positionId = IdBuilder.buildId(tableCellNode.getBBox().keySet(), tableCellNode.getBBox().values().stream().toList());
|
||||||
|
highlightEntity.setRedactionPositionsPerPage(tableCellNode.getBBox()
|
||||||
|
.entrySet()
|
||||||
|
.stream()
|
||||||
|
.map(entry -> new RedactionPosition(positionId, entry.getKey(), List.of(entry.getValue())))
|
||||||
|
.toList());
|
||||||
|
addEntityToGraph(highlightEntity, tableCellNode);
|
||||||
|
return highlightEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isValidEntityBoundary(TextBlock textBlock, Boundary boundary) {
|
public boolean isValidEntityBoundary(TextBlock textBlock, Boundary boundary) {
|
||||||
|
|
||||||
return boundaryIsSurroundedBySeparators(textBlock, boundary) && !isWhiteSpacesOrSeparatorsOnly(textBlock, boundary);
|
return boundaryIsSurroundedBySeparators(textBlock, boundary) && !isWhiteSpacesOrSeparatorsOnly(textBlock, boundary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addEntityToGraph(RedactionEntity entity, SemanticNode node) {
|
||||||
|
|
||||||
|
TableOfContents tableOfContents = node.getTableOfContents();
|
||||||
|
try {
|
||||||
|
addEntityToGraph(entity, tableOfContents);
|
||||||
|
entity.addIntersectingNode(tableOfContents.getRoot().getNode());
|
||||||
|
tableOfContents.getRoot().getNode().getEntities().add(entity);
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
entity.setDeepestFullyContainingNode(tableOfContents.getRoot().getNode());
|
||||||
|
entityEnrichmentService.enrichEntity(entity, entity.getDeepestFullyContainingNode().buildTextBlock());
|
||||||
|
entity.addIntersectingNode(tableOfContents.getRoot().getNode());
|
||||||
|
addToPages(entity);
|
||||||
|
addEntityToNodeEntitySets(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addEntityToGraph(RedactionEntity entity, TableOfContents tableOfContents) {
|
public void addEntityToGraph(RedactionEntity entity, TableOfContents tableOfContents) {
|
||||||
|
|
||||||
tableOfContents.getRoot().getNode().addThisToEntityIfIntersects(entity);
|
SemanticNode containingNode = tableOfContents.streamChildrenNodes(Collections.emptyList())
|
||||||
|
.filter(node -> node.buildTextBlock().containsBoundary(entity.getBoundary()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new NoSuchElementException("No containing Node found!"));
|
||||||
|
|
||||||
|
containingNode.addThisToEntityIfIntersects(entity);
|
||||||
|
|
||||||
TextBlock textBlock = entity.getDeepestFullyContainingNode().buildTextBlock();
|
TextBlock textBlock = entity.getDeepestFullyContainingNode().buildTextBlock();
|
||||||
entityEnrichmentService.enrichEntity(entity, textBlock);
|
entityEnrichmentService.enrichEntity(entity, textBlock);
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.services;
|
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.services;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
|
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
|
||||||
@ -29,7 +27,7 @@ public class EntityEnrichmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private CharSequence findTextAfter(int index, TextBlock textBlock) {
|
private String findTextAfter(int index, TextBlock textBlock) {
|
||||||
|
|
||||||
int endOffset = Math.min(index + redactionServiceSettings.getSurroundingWordsOffsetWindow(), textBlock.getBoundary().end());
|
int endOffset = Math.min(index + redactionServiceSettings.getSurroundingWordsOffsetWindow(), textBlock.getBoundary().end());
|
||||||
String textAfter = textBlock.subSequence(index, endOffset).toString();
|
String textAfter = textBlock.subSequence(index, endOffset).toString();
|
||||||
@ -44,7 +42,7 @@ public class EntityEnrichmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private CharSequence findTextBefore(int index, TextBlock textBlock) {
|
private String findTextBefore(int index, TextBlock textBlock) {
|
||||||
|
|
||||||
int offsetBefore = Math.max(index - redactionServiceSettings.getSurroundingWordsOffsetWindow(), textBlock.getBoundary().start());
|
int offsetBefore = Math.max(index - redactionServiceSettings.getSurroundingWordsOffsetWindow(), textBlock.getBoundary().start());
|
||||||
String textBefore = textBlock.subSequence(offsetBefore, index).toString();
|
String textBefore = textBlock.subSequence(offsetBefore, index).toString();
|
||||||
@ -90,27 +88,4 @@ public class EntityEnrichmentService {
|
|||||||
return startWithSpace ? " " + result : result;
|
return startWithSpace ? " " + result : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void setFields(RedactionEntity redactionEntity, int matchedRule, String redactionReason, String legalBasis, Engine engine) {
|
|
||||||
|
|
||||||
if (redactionEntity.getMatchedRule() == -1 && matchedRule != -1) {
|
|
||||||
redactionEntity.setMatchedRule(matchedRule);
|
|
||||||
}
|
|
||||||
if (redactionEntity.getRedactionReason().equals("") && redactionReason != null) {
|
|
||||||
redactionEntity.setRedactionReason(redactionReason);
|
|
||||||
}
|
|
||||||
if (redactionEntity.getLegalBasis().equals("") && legalBasis != null) {
|
|
||||||
redactionEntity.setLegalBasis(legalBasis);
|
|
||||||
}
|
|
||||||
if (engine != null) {
|
|
||||||
redactionEntity.addEngine(engine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void setFields(Collection<RedactionEntity> redactionEntities, int matchedRule, String redactionReason, String legalBasis, Engine engine) {
|
|
||||||
|
|
||||||
redactionEntities.forEach(entityNode -> setFields(entityNode, matchedRule, redactionReason, legalBasis, engine));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import org.springframework.stereotype.Service;
|
|||||||
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.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionPosition;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionPosition;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleTransformations;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleTransformations;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -22,7 +23,7 @@ public class ManualRedactionApplicationService {
|
|||||||
|
|
||||||
RedactionPosition redactionPositionToBeResized = entityToBeResized.getRedactionPositionsPerPage()
|
RedactionPosition redactionPositionToBeResized = entityToBeResized.getRedactionPositionsPerPage()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(redactionPosition -> redactionPosition.getId().equals(manualResizeRedaction.getValue()))
|
.filter(redactionPosition -> redactionPosition.getId().equals(manualResizeRedaction.getAnnotationId()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> new NoSuchElementException("No redaction position with matching annotation id found!"));
|
.orElseThrow(() -> new NoSuchElementException("No redaction position with matching annotation id found!"));
|
||||||
|
|
||||||
@ -38,8 +39,9 @@ public class ManualRedactionApplicationService {
|
|||||||
entityToBeResized.setResized(true);
|
entityToBeResized.setResized(true);
|
||||||
entityToBeResized.getBoundary().setStart(newStartOffset);
|
entityToBeResized.getBoundary().setStart(newStartOffset);
|
||||||
entityToBeResized.getBoundary().setEnd(newStartOffset + manualResizeRedaction.getValue().length());
|
entityToBeResized.getBoundary().setEnd(newStartOffset + manualResizeRedaction.getValue().length());
|
||||||
|
SemanticNode nodeToInsertInto = entityToBeResized.getDeepestFullyContainingNode().getTableOfContents().getRoot().getNode();
|
||||||
entityToBeResized.removeFromGraph();
|
entityToBeResized.removeFromGraph();
|
||||||
entityCreationService.addEntityToGraph(entityToBeResized, entityToBeResized.getDeepestFullyContainingNode().getTableOfContents());
|
entityCreationService.addEntityToGraph(entityToBeResized, nodeToInsertInto);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,6 +99,16 @@ public class RectangleTransformations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle toAnnotationRectangle(Rectangle2D rectangle2D, int pageNumber) {
|
||||||
|
|
||||||
|
return new com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle((float) rectangle2D.getMaxX(),
|
||||||
|
(float) rectangle2D.getMaxY(),
|
||||||
|
(float) rectangle2D.getWidth(),
|
||||||
|
(float) rectangle2D.getHeight(),
|
||||||
|
pageNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String toString(Rectangle2D rectangle2D) {
|
public static String toString(Rectangle2D rectangle2D) {
|
||||||
|
|
||||||
return format("%f,%f,%f,%f", rectangle2D.getX(), rectangle2D.getY(), rectangle2D.getWidth(), rectangle2D.getHeight());
|
return format("%f,%f,%f,%f", rectangle2D.getX(), rectangle2D.getY(), rectangle2D.getWidth(), rectangle2D.getHeight());
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils;
|
package com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils;
|
||||||
|
|
||||||
import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.boundaryIsSurroundedBySeparators;
|
import static java.lang.String.format;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -8,10 +8,11 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.utils.Patterns;
|
import com.iqser.red.service.redaction.v1.server.redaction.utils.Patterns;
|
||||||
|
|
||||||
public class CharSequenceSearchUtils {
|
public class RedactionSearchUtils {
|
||||||
|
|
||||||
public static boolean anyMatch(CharSequence charSequence, String regexPattern) {
|
public static boolean anyMatch(CharSequence charSequence, String regexPattern) {
|
||||||
|
|
||||||
@ -20,6 +21,12 @@ public class CharSequenceSearchUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean exactMatch(CharSequence charSequence, String regexPattern) {
|
||||||
|
|
||||||
|
return charSequence.toString().matches(regexPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean anyMatch(TextBlock textBlock, String regexPattern) {
|
public static boolean anyMatch(TextBlock textBlock, String regexPattern) {
|
||||||
|
|
||||||
Pattern pattern = Patterns.getCompiledPattern(regexPattern, false);
|
Pattern pattern = Patterns.getCompiledPattern(regexPattern, false);
|
||||||
@ -31,7 +38,49 @@ public class CharSequenceSearchUtils {
|
|||||||
|
|
||||||
Pattern pattern = Patterns.getCompiledPattern(regexPattern, false);
|
Pattern pattern = Patterns.getCompiledPattern(regexPattern, false);
|
||||||
Matcher matcher = pattern.matcher(searchText);
|
Matcher matcher = pattern.matcher(searchText);
|
||||||
return new Boundary(matcher.start(), matcher.end());
|
if (matcher.find()) {
|
||||||
|
return new Boundary(matcher.start(), matcher.end());
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(format("Charsequence %s does not contain any matches for pattern %s", searchText, regexPattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int getExpandedEndByRegex(RedactionEntity entity, String regexPattern) {
|
||||||
|
|
||||||
|
int expandedEnd;
|
||||||
|
if (anyMatch(entity.getTextAfter(), regexPattern)) {
|
||||||
|
Boundary postfixBoundary = findFirstBoundary(regexPattern, entity.getTextAfter());
|
||||||
|
expandedEnd = postfixBoundary.end() + entity.getBoundary().end();
|
||||||
|
} else {
|
||||||
|
expandedEnd = entity.getBoundary().end();
|
||||||
|
}
|
||||||
|
return expandedEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int getExpandedStartByRegex(RedactionEntity entity, String regexPattern) {
|
||||||
|
|
||||||
|
int expandedStart;
|
||||||
|
if (anyMatch(entity.getTextBefore(), regexPattern)) {
|
||||||
|
Boundary prefixBoundary = findFirstBoundary(regexPattern, entity.getTextBefore());
|
||||||
|
expandedStart = prefixBoundary.start() + entity.getBoundary().start() - entity.getTextBefore().length();
|
||||||
|
} else {
|
||||||
|
expandedStart = entity.getBoundary().start();
|
||||||
|
}
|
||||||
|
return expandedStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<Boundary> findBoundariesByRegex(String regexPattern, CharSequence searchText) {
|
||||||
|
|
||||||
|
Pattern pattern = Patterns.getCompiledPattern(regexPattern, false);
|
||||||
|
Matcher matcher = pattern.matcher(searchText);
|
||||||
|
List<Boundary> boundaries = new LinkedList<>();
|
||||||
|
while (matcher.find()) {
|
||||||
|
boundaries.add(new Boundary(matcher.start(), matcher.end()));
|
||||||
|
}
|
||||||
|
return boundaries;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -43,7 +92,7 @@ public class CharSequenceSearchUtils {
|
|||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
boundaries.add(new Boundary(matcher.start() + textBlock.getBoundary().start(), matcher.end() + textBlock.getBoundary().start()));
|
boundaries.add(new Boundary(matcher.start() + textBlock.getBoundary().start(), matcher.end() + textBlock.getBoundary().start()));
|
||||||
}
|
}
|
||||||
return boundaries.stream().filter(boundary -> boundaryIsSurroundedBySeparators(textBlock, boundary)).toList();
|
return boundaries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3,12 +3,12 @@ package com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
||||||
|
|
||||||
public class TextPositionOperations {
|
public class TextPositionOperations {
|
||||||
|
|
||||||
public static List<TextPositionSequence> mergeAndSortTextPositionSequenceByYThenX(List<TextBlock> textBlocks) {
|
public static List<TextPositionSequence> mergeAndSortTextPositionSequenceByYThenX(List<ClassificationTextBlock> textBlocks) {
|
||||||
|
|
||||||
return textBlocks.stream()//
|
return textBlocks.stream()//
|
||||||
.flatMap(tb -> tb.getSequences().stream())//
|
.flatMap(tb -> tb.getSequences().stream())//
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequ
|
|||||||
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionEntity;
|
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionEntity;
|
||||||
import com.iqser.red.service.redaction.v1.server.client.model.NerEntities;
|
import com.iqser.red.service.redaction.v1.server.client.model.NerEntities;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
|
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.iqser.red.service.redaction.v1.server.redaction.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class SemanticNodeRuleConditions {
|
||||||
|
|
||||||
|
public boolean hasEntitiesOfType(SemanticNode semanticNode, String type) {
|
||||||
|
|
||||||
|
return semanticNode.getEntities().stream().anyMatch(redactionEntity -> redactionEntity.getType().equals(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasEntitiesOfType(SemanticNode semanticNode, List<String> types) {
|
||||||
|
|
||||||
|
return types.stream().allMatch(type -> hasEntitiesOfType(semanticNode, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
|
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ import lombok.NoArgsConstructor;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CellValue {
|
public class CellValue {
|
||||||
|
|
||||||
private List<TextBlock> textBlocks = new ArrayList<>();
|
private List<ClassificationTextBlock> textBlocks = new ArrayList<>();
|
||||||
|
|
||||||
private int rowSpanStart;
|
private int rowSpanStart;
|
||||||
|
|
||||||
@ -27,9 +27,9 @@ public class CellValue {
|
|||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
Iterator<TextBlock> itty = textBlocks.iterator();
|
Iterator<ClassificationTextBlock> itty = textBlocks.iterator();
|
||||||
while (itty.hasNext()) {
|
while (itty.hasNext()) {
|
||||||
TextBlock textBlock = itty.next();
|
ClassificationTextBlock textBlock = itty.next();
|
||||||
TextPositionSequence previous = null;
|
TextPositionSequence previous = null;
|
||||||
for (TextPositionSequence word : textBlock.getSequences()) {
|
for (TextPositionSequence word : textBlock.getSequences()) {
|
||||||
if (previous != null) {
|
if (previous != null) {
|
||||||
|
|||||||
@ -23,8 +23,8 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations
|
|||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionArea;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionArea;
|
||||||
import com.iqser.red.service.redaction.v1.model.ArgumentType;
|
import com.iqser.red.service.redaction.v1.model.ArgumentType;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.RedTextPosition;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.RedTextPosition;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextPositionSequence;
|
||||||
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.SearchImplementation;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
|
||||||
@ -1354,7 +1354,7 @@ public class Section {
|
|||||||
entity.setRedactionReason(reason);
|
entity.setRedactionReason(reason);
|
||||||
entity.setTargetSequences(value.getTextBlocks()
|
entity.setTargetSequences(value.getTextBlocks()
|
||||||
.stream()
|
.stream()
|
||||||
.map(TextBlock::getSequences)
|
.map(ClassificationTextBlock::getSequences)
|
||||||
.flatMap(Collection::stream)
|
.flatMap(Collection::stream)
|
||||||
.collect(Collectors.toList())); // Make sure no other cells with same content are highlighted
|
.collect(Collectors.toList())); // Make sure no other cells with same content are highlighted
|
||||||
entity.setLegalBasis(legalBasis);
|
entity.setLegalBasis(legalBasis);
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations
|
|||||||
import com.iqser.red.service.redaction.v1.server.client.RulesClient;
|
import com.iqser.red.service.redaction.v1.server.client.RulesClient;
|
||||||
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionEntity;
|
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionEntity;
|
||||||
import com.iqser.red.service.redaction.v1.server.exception.RulesValidationException;
|
import com.iqser.red.service.redaction.v1.server.exception.RulesValidationException;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
||||||
@ -36,7 +36,9 @@ import io.micrometer.core.annotation.Timed;
|
|||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||||
@ -103,8 +105,9 @@ public class DroolsExecutionService {
|
|||||||
|
|
||||||
kieSession.getAgenda().getAgendaGroup("LOCAL_DICTIONARY_ADDS").setFocus();
|
kieSession.getAgenda().getAgendaGroup("LOCAL_DICTIONARY_ADDS").setFocus();
|
||||||
kieSession.fireAllRules();
|
kieSession.fireAllRules();
|
||||||
|
List<FileAttribute> resultingFileAttributes = getFileAttributes(kieSession);
|
||||||
return getFileAttributes(kieSession);
|
kieSession.dispose();
|
||||||
|
return resultingFileAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -127,7 +130,7 @@ public class DroolsExecutionService {
|
|||||||
List<FileAttribute> fileAttributes = new LinkedList<>();
|
List<FileAttribute> fileAttributes = new LinkedList<>();
|
||||||
QueryResults entitiesResult = kieSession.getQueryResults("getFileAttributes");
|
QueryResults entitiesResult = kieSession.getQueryResults("getFileAttributes");
|
||||||
for (QueryResultsRow resultsRow : entitiesResult) {
|
for (QueryResultsRow resultsRow : entitiesResult) {
|
||||||
fileAttributes.add((FileAttribute) resultsRow.get("$result"));
|
fileAttributes.add((FileAttribute) resultsRow.get("$fileAttribute"));
|
||||||
}
|
}
|
||||||
return fileAttributes;
|
return fileAttributes;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,9 +11,9 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionPosition;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionPosition;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleTransformations;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RectangleTransformations;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
||||||
@ -75,7 +75,11 @@ public class RedactionLogCreatorService {
|
|||||||
private RedactionLogEntry createRedactionLogEntry(RedactionEntity entity, String dossierTemplateId) {
|
private RedactionLogEntry createRedactionLogEntry(RedactionEntity entity, String dossierTemplateId) {
|
||||||
|
|
||||||
Set<String> referenceIds = new HashSet<>();
|
Set<String> referenceIds = new HashSet<>();
|
||||||
entity.getReferences().forEach(ref -> ref.getRedactionPositionsPerPage().forEach(pos -> referenceIds.add(pos.getId())));
|
entity.getReferences()
|
||||||
|
.stream()
|
||||||
|
.filter(redactionEntity -> !redactionEntity.isRemoved())
|
||||||
|
.forEach(ref -> ref.getRedactionPositionsPerPage().forEach(pos -> referenceIds.add(pos.getId())));
|
||||||
|
int sectionNumber = entity.getDeepestFullyContainingNode().getTocId().isEmpty() ? -1 : entity.getDeepestFullyContainingNode().getTocId().get(0);
|
||||||
|
|
||||||
return RedactionLogEntry.builder()
|
return RedactionLogEntry.builder()
|
||||||
.color(getColor(entity.getType(), dossierTemplateId, entity.isRedaction()))
|
.color(getColor(entity.getType(), dossierTemplateId, entity.isRedaction()))
|
||||||
@ -88,7 +92,7 @@ public class RedactionLogCreatorService {
|
|||||||
.isRecommendation(entity.getEntityType().equals(EntityType.RECOMMENDATION))
|
.isRecommendation(entity.getEntityType().equals(EntityType.RECOMMENDATION))
|
||||||
.isFalsePositive(entity.getEntityType().equals(EntityType.FALSE_POSITIVE) || entity.getEntityType().equals(EntityType.FALSE_RECOMMENDATION))
|
.isFalsePositive(entity.getEntityType().equals(EntityType.FALSE_POSITIVE) || entity.getEntityType().equals(EntityType.FALSE_RECOMMENDATION))
|
||||||
.section(entity.getDeepestFullyContainingNode().toString())
|
.section(entity.getDeepestFullyContainingNode().toString())
|
||||||
.sectionNumber(entity.getDeepestFullyContainingNode().getTocId().get(0))
|
.sectionNumber(sectionNumber)
|
||||||
.matchedRule(entity.getMatchedRule())
|
.matchedRule(entity.getMatchedRule())
|
||||||
.isDictionaryEntry(entity.isDictionaryEntry())
|
.isDictionaryEntry(entity.isDictionaryEntry())
|
||||||
.textAfter(entity.getTextAfter().toString())
|
.textAfter(entity.getTextAfter().toString())
|
||||||
|
|||||||
@ -15,9 +15,9 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Table;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SectionText;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SectionText;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@ -52,9 +52,9 @@ public class SectionGridCreatorService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textBlock instanceof TextBlock) {
|
if (textBlock instanceof ClassificationTextBlock) {
|
||||||
|
|
||||||
TextBlock tb = (TextBlock) textBlock;
|
ClassificationTextBlock tb = (ClassificationTextBlock) textBlock;
|
||||||
classifiedDoc.getSectionGrid()
|
classifiedDoc.getSectionGrid()
|
||||||
.getRectanglesPerPage()
|
.getRectanglesPerPage()
|
||||||
.computeIfAbsent(page, (x) -> new ArrayList<>())
|
.computeIfAbsent(page, (x) -> new ArrayList<>())
|
||||||
@ -65,10 +65,10 @@ public class SectionGridCreatorService {
|
|||||||
paragraph.getPageBlocks().size(),
|
paragraph.getPageBlocks().size(),
|
||||||
null));
|
null));
|
||||||
|
|
||||||
} else if (textBlock instanceof Table) {
|
} else if (textBlock instanceof TablePageBlock) {
|
||||||
|
|
||||||
List<CellRectangle> cellRectangles = new ArrayList<>();
|
List<CellRectangle> cellRectangles = new ArrayList<>();
|
||||||
for (List<Cell> row : ((Table) textBlock).getRows()) {
|
for (List<Cell> row : ((TablePageBlock) textBlock).getRows()) {
|
||||||
for (Cell cell : row) {
|
for (Cell cell : row) {
|
||||||
if (cell != null) {
|
if (cell != null) {
|
||||||
cellRectangles.add(new CellRectangle(new Point((float) cell.getX(), (float) cell.getY()), (float) cell.getWidth(), (float) cell.getHeight()));
|
cellRectangles.add(new CellRectangle(new Point((float) cell.getX(), (float) cell.getY()), (float) cell.getWidth(), (float) cell.getHeight()));
|
||||||
|
|||||||
@ -19,9 +19,9 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Table;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SectionText;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.SectionText;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.UnclassifiedText;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.UnclassifiedText;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.CellValue;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.CellValue;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
|
||||||
@ -42,8 +42,8 @@ public class SectionTextBuilderService {
|
|||||||
AtomicInteger sectionNumber = new AtomicInteger(1);
|
AtomicInteger sectionNumber = new AtomicInteger(1);
|
||||||
for (Section section : classifiedDoc.getSections()) {
|
for (Section section : classifiedDoc.getSections()) {
|
||||||
|
|
||||||
List<Table> tables = section.getTables();
|
List<TablePageBlock> tables = section.getTables();
|
||||||
for (Table table : tables) {
|
for (TablePageBlock table : tables) {
|
||||||
sectionTexts.addAll(processTablePerRow(table, sectionNumber));
|
sectionTexts.addAll(processTablePerRow(table, sectionNumber));
|
||||||
sectionNumber.incrementAndGet();
|
sectionNumber.incrementAndGet();
|
||||||
}
|
}
|
||||||
@ -70,14 +70,14 @@ public class SectionTextBuilderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<SectionText> processTablePerRow(Table table, AtomicInteger sectionNumber) {
|
private List<SectionText> processTablePerRow(TablePageBlock table, AtomicInteger sectionNumber) {
|
||||||
|
|
||||||
List<SectionText> sectionTexts = new ArrayList<>();
|
List<SectionText> sectionTexts = new ArrayList<>();
|
||||||
boolean hasHeader = hasTableHeader(table);
|
boolean hasHeader = hasTableHeader(table);
|
||||||
|
|
||||||
for (List<Cell> row : table.getRows()) {
|
for (List<Cell> row : table.getRows()) {
|
||||||
|
|
||||||
List<TextBlock> textBlocks = new ArrayList<>();
|
List<ClassificationTextBlock> textBlocks = new ArrayList<>();
|
||||||
List<SectionArea> areas = new ArrayList<>();
|
List<SectionArea> areas = new ArrayList<>();
|
||||||
Map<String, CellValue> tabularData = new HashMap<>();
|
Map<String, CellValue> tabularData = new HashMap<>();
|
||||||
List<Integer> startOffsets = new ArrayList<>();
|
List<Integer> startOffsets = new ArrayList<>();
|
||||||
@ -125,13 +125,13 @@ public class SectionTextBuilderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getRowText(List<TextBlock> rowTextBlocks) {
|
public String getRowText(List<ClassificationTextBlock> rowTextBlocks) {
|
||||||
|
|
||||||
return SearchableText.buildString(rowTextBlocks.stream().map(textBlock -> textBlock.getSequences()).flatMap(List::stream).collect(Collectors.toList()));
|
return SearchableText.buildString(rowTextBlocks.stream().map(textBlock -> textBlock.getSequences()).flatMap(List::stream).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean hasTableHeader(Table table) {
|
private boolean hasTableHeader(TablePageBlock table) {
|
||||||
|
|
||||||
return table.getRows().stream().anyMatch(row -> row.stream().anyMatch(cell -> !cell.isHeaderCell() && !cell.getHeaderCells().isEmpty()));
|
return table.getRows().stream().anyMatch(row -> row.stream().anyMatch(cell -> !cell.isHeaderCell() && !cell.getHeaderCells().isEmpty()));
|
||||||
}
|
}
|
||||||
@ -170,13 +170,13 @@ public class SectionTextBuilderService {
|
|||||||
|
|
||||||
|
|
||||||
private SectionText processText(SearchableText searchableText,
|
private SectionText processText(SearchableText searchableText,
|
||||||
List<TextBlock> paragraphTextBlocks,
|
List<ClassificationTextBlock> paragraphTextBlocks,
|
||||||
String headline,
|
String headline,
|
||||||
AtomicInteger sectionNumber,
|
AtomicInteger sectionNumber,
|
||||||
List<ClassifiedImage> images) {
|
List<ClassifiedImage> images) {
|
||||||
|
|
||||||
SectionText sectionText = new SectionText();
|
SectionText sectionText = new SectionText();
|
||||||
for (TextBlock paragraphTextBlock : paragraphTextBlocks) {
|
for (ClassificationTextBlock paragraphTextBlock : paragraphTextBlocks) {
|
||||||
SectionArea sectionArea = new SectionArea(new Point(paragraphTextBlock.getMinX(), paragraphTextBlock.getMinY()),
|
SectionArea sectionArea = new SectionArea(new Point(paragraphTextBlock.getMinX(), paragraphTextBlock.getMinY()),
|
||||||
paragraphTextBlock.getWidth(),
|
paragraphTextBlock.getWidth(),
|
||||||
paragraphTextBlock.getHeight(),
|
paragraphTextBlock.getHeight(),
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.se
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentDataMapper;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentDataMapper;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentGraphMapper;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentGraphMapper;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory.DocumentGraphFactory;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory.DocumentGraphFactory;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntitiesAdapter;
|
import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntitiesAdapter;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.Dictionary;
|
||||||
@ -83,7 +83,6 @@ public class AnalyzeService {
|
|||||||
ImageServiceResponseAdapter imageServiceResponseAdapter;
|
ImageServiceResponseAdapter imageServiceResponseAdapter;
|
||||||
ImportedRedactionService importedRedactionService;
|
ImportedRedactionService importedRedactionService;
|
||||||
SectionFinder sectionFinder;
|
SectionFinder sectionFinder;
|
||||||
DocumentGraphFactory documentGraphFactory;
|
|
||||||
|
|
||||||
FunctionTimerValues redactmanagerAnalyzePagewiseValues;
|
FunctionTimerValues redactmanagerAnalyzePagewiseValues;
|
||||||
|
|
||||||
@ -112,7 +111,7 @@ public class AnalyzeService {
|
|||||||
throw new RedactionException(e);
|
throw new RedactionException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentGraph documentGraph = documentGraphFactory.buildDocumentGraph(classifiedDoc);
|
DocumentGraph documentGraph = DocumentGraphFactory.buildDocumentGraph(classifiedDoc);
|
||||||
|
|
||||||
List<SectionText> sectionTexts = sectionTextBuilderService.buildSectionText(classifiedDoc);
|
List<SectionText> sectionTexts = sectionTextBuilderService.buildSectionText(classifiedDoc);
|
||||||
|
|
||||||
@ -206,8 +205,10 @@ public class AnalyzeService {
|
|||||||
KieContainer kieContainer = droolsExecutionService.updateRules(analyzeRequest.getDossierTemplateId());
|
KieContainer kieContainer = droolsExecutionService.updateRules(analyzeRequest.getDossierTemplateId());
|
||||||
long rulesVersion = droolsExecutionService.getRulesVersion(analyzeRequest.getDossierTemplateId());
|
long rulesVersion = droolsExecutionService.getRulesVersion(analyzeRequest.getDossierTemplateId());
|
||||||
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId());
|
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId());
|
||||||
|
log.debug("Starting Dictionary Search");
|
||||||
|
long dictSearchStart = System.currentTimeMillis();
|
||||||
entityRedactionService.addDictionaryEntities(dictionary, documentGraph);
|
entityRedactionService.addDictionaryEntities(dictionary, documentGraph);
|
||||||
|
log.debug("Finished Dictionary Search in {} ms", System.currentTimeMillis() - dictSearchStart);
|
||||||
Set<FileAttribute> addedFileAttributes = entityRedactionService.addRuleEntitiesToGraphAndReturnAddedFileAttributes(dictionary,
|
Set<FileAttribute> addedFileAttributes = entityRedactionService.addRuleEntitiesToGraphAndReturnAddedFileAttributes(dictionary,
|
||||||
documentGraph,
|
documentGraph,
|
||||||
kieContainer,
|
kieContainer,
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations
|
|||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.RedRectangle2D;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.RedRectangle2D;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.redaction.service.entityredaction;
|
package com.iqser.red.service.redaction.v1.server.redaction.service.entityredaction;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -10,12 +11,14 @@ import org.springframework.stereotype.Service;
|
|||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
|
||||||
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionEntity;
|
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionEntity;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
||||||
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.SearchImplementation;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.service.DroolsExecutionService;
|
import com.iqser.red.service.redaction.v1.server.redaction.service.DroolsExecutionService;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
@ -39,12 +42,15 @@ public class EntityRedactionService {
|
|||||||
AnalyzeRequest analyzeRequest,
|
AnalyzeRequest analyzeRequest,
|
||||||
List<EntityRecognitionEntity> nerEntities) {
|
List<EntityRecognitionEntity> nerEntities) {
|
||||||
|
|
||||||
|
log.debug("Starting Drools Execution");
|
||||||
|
long droolsStart = System.currentTimeMillis();
|
||||||
List<FileAttribute> allFileAttributes = droolsExecutionService.executeRules(kieContainer,
|
List<FileAttribute> allFileAttributes = droolsExecutionService.executeRules(kieContainer,
|
||||||
documentGraph,
|
documentGraph,
|
||||||
dictionary,
|
dictionary,
|
||||||
analyzeRequest.getFileAttributes(),
|
analyzeRequest.getFileAttributes(),
|
||||||
analyzeRequest.getManualRedactions(),
|
analyzeRequest.getManualRedactions(),
|
||||||
nerEntities);
|
nerEntities);
|
||||||
|
log.debug("Finished Drools Execution in {} ms", System.currentTimeMillis() - droolsStart);
|
||||||
return allFileAttributes.stream().filter(fileAttribute -> !analyzeRequest.getFileAttributes().contains(fileAttribute)).collect(Collectors.toUnmodifiableSet());
|
return allFileAttributes.stream().filter(fileAttribute -> !analyzeRequest.getFileAttributes().contains(fileAttribute)).collect(Collectors.toUnmodifiableSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,17 +73,30 @@ public class EntityRedactionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addDictionaryEntities(Dictionary dictionary, DocumentGraph documentGraph) {
|
public void addDictionaryEntities(Dictionary dictionary, DocumentGraph document) {
|
||||||
|
|
||||||
dictionary.getDictionaryModels().forEach(dictionaryModel -> addDictionaryModelEntities(dictionaryModel, documentGraph));
|
List<RedactionEntity> foundEntities = new LinkedList<>();
|
||||||
|
for (var model : dictionary.getDictionaryModels()) {
|
||||||
|
findEntitiesWithSearchImplementation(document, model.getEntriesSearch(), EntityType.ENTITY, foundEntities, model.getType());
|
||||||
|
findEntitiesWithSearchImplementation(document, model.getFalsePositiveSearch(), EntityType.FALSE_POSITIVE, foundEntities, model.getType());
|
||||||
|
findEntitiesWithSearchImplementation(document, model.getFalseRecommendationsSearch(), EntityType.FALSE_RECOMMENDATION, foundEntities, model.getType());
|
||||||
|
}
|
||||||
|
foundEntities.forEach(entity -> entityCreationService.addEntityToGraph(entity, document));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void addDictionaryModelEntities(DictionaryModel dictionaryModel, DocumentGraph documentGraph) {
|
private void findEntitiesWithSearchImplementation(DocumentGraph documentGraph,
|
||||||
|
SearchImplementation searchImplementation,
|
||||||
|
EntityType entityType,
|
||||||
|
List<RedactionEntity> foundEntities,
|
||||||
|
String type) {
|
||||||
|
|
||||||
entityCreationService.bySearchImplementation(dictionaryModel.getEntriesSearch(), dictionaryModel.getType(), EntityType.ENTITY, documentGraph);
|
TextBlock textBlock = documentGraph.getTextBlock();
|
||||||
entityCreationService.bySearchImplementation(dictionaryModel.getFalsePositiveSearch(), dictionaryModel.getType(), EntityType.FALSE_POSITIVE, documentGraph);
|
searchImplementation.getBoundaries(textBlock, textBlock.getBoundary())
|
||||||
entityCreationService.bySearchImplementation(dictionaryModel.getFalseRecommendationsSearch(), dictionaryModel.getType(), EntityType.FALSE_RECOMMENDATION, documentGraph);
|
.stream()
|
||||||
|
.filter(boundary -> entityCreationService.isValidEntityBoundary(textBlock, boundary))
|
||||||
|
.map(bounds -> RedactionEntity.initialEntityNode(bounds, type, entityType))
|
||||||
|
.forEach(foundEntities::add);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
package com.iqser.red.service.redaction.v1.server.redaction.utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drools fucked up and introduced a bug with newer versions of java, this is needed or else:
|
||||||
|
* java.lang.IncompatibleClassChangeError: Method 'java.util.List java.util.List.of(java.lang.Object, java.lang.Object)' must be InterfaceMethodref constant
|
||||||
|
* Will be thrown on LHS of rules
|
||||||
|
*/
|
||||||
|
public class Liszt {
|
||||||
|
|
||||||
|
public static <E> List<E> of(E e1, E e2) {
|
||||||
|
|
||||||
|
return List.of(e1, e2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -12,8 +12,8 @@ import org.apache.pdfbox.pdmodel.PDPage;
|
|||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.AtomicTextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.AtomicTextBlock;
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.mo
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Page;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Section;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Table;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.text.ClassificationTextBlock;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
@ -46,12 +46,12 @@ public class PdfVisualisationService {
|
|||||||
if (textBlock.getPage() != page) {
|
if (textBlock.getPage() != page) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (textBlock instanceof TextBlock) {
|
if (textBlock instanceof ClassificationTextBlock) {
|
||||||
textBlock.setClassification((i + 1) + "/" + paragraph.getPageBlocks().size());
|
textBlock.setClassification((i + 1) + "/" + paragraph.getPageBlocks().size());
|
||||||
visualizeTextBlock((TextBlock) textBlock, contentStream);
|
visualizeTextBlock((ClassificationTextBlock) textBlock, contentStream);
|
||||||
} else if (textBlock instanceof Table) {
|
} else if (textBlock instanceof TablePageBlock) {
|
||||||
textBlock.setClassification((i + 1) + "/" + paragraph.getPageBlocks().size());
|
textBlock.setClassification((i + 1) + "/" + paragraph.getPageBlocks().size());
|
||||||
visualizeTable((Table) textBlock, contentStream);
|
visualizeTable((TablePageBlock) textBlock, contentStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -75,10 +75,10 @@ public class PdfVisualisationService {
|
|||||||
if (textBlock == null) {
|
if (textBlock == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (textBlock instanceof TextBlock) {
|
if (textBlock instanceof ClassificationTextBlock) {
|
||||||
visualizeTextBlock((TextBlock) textBlock, contentStream);
|
visualizeTextBlock((ClassificationTextBlock) textBlock, contentStream);
|
||||||
} else if (textBlock instanceof Table) {
|
} else if (textBlock instanceof TablePageBlock) {
|
||||||
visualizeTable((Table) textBlock, contentStream);
|
visualizeTable((TablePageBlock) textBlock, contentStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ public class PdfVisualisationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void visualizeTextBlock(TextBlock textBlock, PDPageContentStream contentStream) throws IOException {
|
private void visualizeTextBlock(ClassificationTextBlock textBlock, PDPageContentStream contentStream) throws IOException {
|
||||||
|
|
||||||
contentStream.setStrokingColor(Color.RED);
|
contentStream.setStrokingColor(Color.RED);
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ public class PdfVisualisationService {
|
|||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private void drawPositions(PDPageContentStream contentStream, TextBlock textBlock) {
|
private void drawPositions(PDPageContentStream contentStream, ClassificationTextBlock textBlock) {
|
||||||
|
|
||||||
contentStream.setNonStrokingColor(Color.BLUE);
|
contentStream.setNonStrokingColor(Color.BLUE);
|
||||||
contentStream.setFont(PDType1Font.TIMES_ROMAN, 2f);
|
contentStream.setFont(PDType1Font.TIMES_ROMAN, 2f);
|
||||||
@ -145,7 +145,7 @@ public class PdfVisualisationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void visualizeTable(Table table, PDPageContentStream contentStream) throws IOException {
|
private void visualizeTable(TablePageBlock table, PDPageContentStream contentStream) throws IOException {
|
||||||
|
|
||||||
for (List<Cell> row : table.getRows()) {
|
for (List<Cell> row : table.getRows()) {
|
||||||
for (Cell cell : row) {
|
for (Cell cell : row) {
|
||||||
@ -156,7 +156,7 @@ public class PdfVisualisationService {
|
|||||||
contentStream.stroke();
|
contentStream.stroke();
|
||||||
|
|
||||||
contentStream.setStrokingColor(Color.GREEN);
|
contentStream.setStrokingColor(Color.GREEN);
|
||||||
for (TextBlock textBlock : cell.getTextBlocks()) {
|
for (ClassificationTextBlock textBlock : cell.getTextBlocks()) {
|
||||||
contentStream.addRect(textBlock.getPdfMinX(),
|
contentStream.addRect(textBlock.getPdfMinX(),
|
||||||
textBlock.getPdfMinY(),
|
textBlock.getPdfMinY(),
|
||||||
textBlock.getPdfMaxX() - textBlock.getPdfMinX(),
|
textBlock.getPdfMaxX() - textBlock.getPdfMinX(),
|
||||||
|
|||||||
@ -162,7 +162,8 @@ public abstract class AbstractRedactionIntegrationTest {
|
|||||||
when(dictionaryClient.getDictionaryForType(REDACTION_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(REDACTION_INDICATOR, false));
|
when(dictionaryClient.getDictionaryForType(REDACTION_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(REDACTION_INDICATOR, false));
|
||||||
when(dictionaryClient.getDictionaryForType(HINT_ONLY_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(HINT_ONLY_INDICATOR, false));
|
when(dictionaryClient.getDictionaryForType(HINT_ONLY_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(HINT_ONLY_INDICATOR, false));
|
||||||
when(dictionaryClient.getDictionaryForType(MUST_REDACT_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(MUST_REDACT_INDICATOR, false));
|
when(dictionaryClient.getDictionaryForType(MUST_REDACT_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(MUST_REDACT_INDICATOR, false));
|
||||||
when(dictionaryClient.getDictionaryForType(PUBLISHED_INFORMATION_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(PUBLISHED_INFORMATION_INDICATOR, false));
|
when(dictionaryClient.getDictionaryForType(PUBLISHED_INFORMATION_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(PUBLISHED_INFORMATION_INDICATOR,
|
||||||
|
false));
|
||||||
when(dictionaryClient.getDictionaryForType(TEST_METHOD_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(TEST_METHOD_INDICATOR, false));
|
when(dictionaryClient.getDictionaryForType(TEST_METHOD_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(TEST_METHOD_INDICATOR, false));
|
||||||
when(dictionaryClient.getDictionaryForType(PII_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(DICTIONARY_PII, false));
|
when(dictionaryClient.getDictionaryForType(PII_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(DICTIONARY_PII, false));
|
||||||
when(dictionaryClient.getDictionaryForType(PURITY_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(PURITY_INDICATOR, false));
|
when(dictionaryClient.getDictionaryForType(PURITY_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(PURITY_INDICATOR, false));
|
||||||
@ -172,8 +173,10 @@ public abstract class AbstractRedactionIntegrationTest {
|
|||||||
when(dictionaryClient.getDictionaryForType(SIGNATURE_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(SIGNATURE_INDICATOR, false));
|
when(dictionaryClient.getDictionaryForType(SIGNATURE_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(SIGNATURE_INDICATOR, false));
|
||||||
when(dictionaryClient.getDictionaryForType(FORMULA_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(FORMULA_INDICATOR, false));
|
when(dictionaryClient.getDictionaryForType(FORMULA_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(FORMULA_INDICATOR, false));
|
||||||
when(dictionaryClient.getDictionaryForType(ROTATE_SIMPLE_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(ROTATE_SIMPLE_INDICATOR, false));
|
when(dictionaryClient.getDictionaryForType(ROTATE_SIMPLE_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(ROTATE_SIMPLE_INDICATOR, false));
|
||||||
when(dictionaryClient.getDictionaryForType(DOSSIER_REDACTIONS_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(DOSSIER_REDACTIONS_INDICATOR,true));
|
when(dictionaryClient.getDictionaryForType(DOSSIER_REDACTIONS_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(DOSSIER_REDACTIONS_INDICATOR,
|
||||||
when(dictionaryClient.getDictionaryForType(IMPORTED_REDACTION_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(IMPORTED_REDACTION_INDICATOR,true));
|
true));
|
||||||
|
when(dictionaryClient.getDictionaryForType(IMPORTED_REDACTION_TYPE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(IMPORTED_REDACTION_INDICATOR,
|
||||||
|
true));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +240,7 @@ public abstract class AbstractRedactionIntegrationTest {
|
|||||||
|
|
||||||
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
|
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
throw new IllegalArgumentException("could not load classpath resource: drools/rules.drl");
|
throw new IllegalArgumentException("could not load classpath resource: drools/prod_rules_new.drl");
|
||||||
}
|
}
|
||||||
List<String> stringList = Files.readAllLines(new File(resource.getPath()).toPath());
|
List<String> stringList = Files.readAllLines(new File(resource.getPath()).toPath());
|
||||||
return String.join("\n", stringList);
|
return String.join("\n", stringList);
|
||||||
@ -426,22 +429,30 @@ public abstract class AbstractRedactionIntegrationTest {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
protected AnalyzeRequest uploadFileToStorage(String file) {
|
protected AnalyzeRequest uploadFileToStorage(String file) {
|
||||||
|
|
||||||
return prepareStorage(file, "files/cv_service_empty_response.json");
|
return prepareStorage(file, "files/cv_service_empty_response.json", "files/empty_image_response.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
protected AnalyzeRequest prepareStorage(String file, String cvServiceResponseFile) {
|
protected AnalyzeRequest prepareStorage(String file, String cvServiceResponseFile) {
|
||||||
|
|
||||||
ClassPathResource pdfFileResource = new ClassPathResource(file);
|
return prepareStorage(file, cvServiceResponseFile, "files/empty_image_response.json");
|
||||||
ClassPathResource cvServiceResponseFileResource = new ClassPathResource(cvServiceResponseFile);
|
|
||||||
|
|
||||||
return prepareStorage(pdfFileResource.getInputStream(), cvServiceResponseFileResource.getInputStream());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
protected AnalyzeRequest prepareStorage(InputStream fileStream, InputStream cvServiceResponseFileStream) {
|
protected AnalyzeRequest prepareStorage(String file, String cvServiceResponseFile, String imageServiceResponseFile) {
|
||||||
|
|
||||||
|
ClassPathResource pdfFileResource = new ClassPathResource(file);
|
||||||
|
ClassPathResource cvServiceResponseFileResource = new ClassPathResource(cvServiceResponseFile);
|
||||||
|
ClassPathResource imageServiceResponseFileResource = new ClassPathResource(imageServiceResponseFile);
|
||||||
|
|
||||||
|
return prepareStorage(pdfFileResource.getInputStream(), cvServiceResponseFileResource.getInputStream(), imageServiceResponseFileResource.getInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
protected AnalyzeRequest prepareStorage(InputStream fileStream, InputStream cvServiceResponseFileStream, InputStream imageServiceResponseStream) {
|
||||||
|
|
||||||
AnalyzeRequest request = AnalyzeRequest.builder()
|
AnalyzeRequest request = AnalyzeRequest.builder()
|
||||||
.dossierTemplateId(TEST_DOSSIER_TEMPLATE_ID)
|
.dossierTemplateId(TEST_DOSSIER_TEMPLATE_ID)
|
||||||
@ -450,10 +461,13 @@ public abstract class AbstractRedactionIntegrationTest {
|
|||||||
.lastProcessed(OffsetDateTime.now())
|
.lastProcessed(OffsetDateTime.now())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
storageService.storeObject(TenantContext.getTenantId(), RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.ORIGIN), fileStream);
|
||||||
storageService.storeObject(TenantContext.getTenantId(),
|
storageService.storeObject(TenantContext.getTenantId(),
|
||||||
RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.TABLES),
|
RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.TABLES),
|
||||||
cvServiceResponseFileStream);
|
cvServiceResponseFileStream);
|
||||||
storageService.storeObject(TenantContext.getTenantId(), RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.ORIGIN), fileStream);
|
storageService.storeObject(TenantContext.getTenantId(),
|
||||||
|
RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.IMAGE_INFO),
|
||||||
|
imageServiceResponseStream);
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
|
|
||||||
|
|||||||
@ -1,546 +0,0 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.kie.api.KieServices;
|
|
||||||
import org.kie.api.builder.KieBuilder;
|
|
||||||
import org.kie.api.builder.KieFileSystem;
|
|
||||||
import org.kie.api.builder.KieModule;
|
|
||||||
import org.kie.api.builder.KieRepository;
|
|
||||||
import org.kie.api.builder.ReleaseId;
|
|
||||||
import org.kie.api.runtime.KieContainer;
|
|
||||||
import org.kie.internal.io.ResourceFactory;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
|
||||||
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|
||||||
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors;
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntry;
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.client.DictionaryClient;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.client.RulesClient;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.utils.ResourceLoader;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
|
|
||||||
import com.iqser.red.storage.commons.StorageAutoConfiguration;
|
|
||||||
import com.iqser.red.storage.commons.service.StorageService;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
@ExtendWith(SpringExtension.class)
|
|
||||||
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|
||||||
@Import(AbstractTestWithDictionaries.TestConfiguration.class)
|
|
||||||
public class AbstractTestWithDictionaries {
|
|
||||||
|
|
||||||
protected static final String RULES = loadFromClassPath("drools/testRules.drl");
|
|
||||||
protected static final String RULES_PATH = "drools/rules.drl";
|
|
||||||
protected static final String ENTITY_RULES_PATH = "drools/entity_rules.drl";
|
|
||||||
protected static final String MERGE_ENTITY_RULES_PATH = "drools/testRules.drl";
|
|
||||||
private static final String VERTEBRATE = "vertebrate";
|
|
||||||
private static final String ADDRESS = "CBI_address";
|
|
||||||
private static final String AUTHOR = "CBI_author";
|
|
||||||
private static final String SPONSOR = "CBI_sponsor";
|
|
||||||
private static final String NO_REDACTION_INDICATOR = "no_redaction_indicator";
|
|
||||||
private static final String REDACTION_INDICATOR = "redaction_indicator";
|
|
||||||
private static final String HINT_ONLY = "hint_only";
|
|
||||||
private static final String MUST_REDACT = "must_redact";
|
|
||||||
private static final String PUBLISHED_INFORMATION = "published_information";
|
|
||||||
private static final String TEST_METHOD = "test_method";
|
|
||||||
private static final String PURITY = "purity";
|
|
||||||
private static final String IMAGE = "image";
|
|
||||||
private static final String LOGO = "logo";
|
|
||||||
private static final String SIGNATURE = "signature";
|
|
||||||
private static final String FORMULA = "formula";
|
|
||||||
private static final String OCR = "ocr";
|
|
||||||
private static final String DOSSIER_REDACTIONS = "dossier_redactions";
|
|
||||||
private static final String IMPORTED_REDACTION = "imported_redaction";
|
|
||||||
private static final String PII = "PII";
|
|
||||||
private static final String ROTATE_SIMPLE = "RotateSimple";
|
|
||||||
|
|
||||||
protected final static String TEST_DOSSIER_TEMPLATE_ID = "123";
|
|
||||||
protected final static String TEST_DOSSIER_ID = "123";
|
|
||||||
protected final static String TEST_FILE_ID = "123";
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected StorageService storageService;
|
|
||||||
|
|
||||||
@MockBean
|
|
||||||
protected DictionaryClient dictionaryClient;
|
|
||||||
|
|
||||||
@MockBean
|
|
||||||
protected RulesClient rulesClient;
|
|
||||||
|
|
||||||
private final Map<String, List<String>> dictionary = new HashMap<>();
|
|
||||||
private final Map<String, List<String>> dossierDictionary = new HashMap<>();
|
|
||||||
private final Map<String, List<String>> falsePositive = new HashMap<>();
|
|
||||||
private final Map<String, List<String>> falseRecommendation = new HashMap<>();
|
|
||||||
private final Map<String, String> typeColorMap = new HashMap<>();
|
|
||||||
private final Map<String, Boolean> hintTypeMap = new HashMap<>();
|
|
||||||
private final Map<String, Boolean> caseInSensitiveMap = new HashMap<>();
|
|
||||||
private final Map<String, Boolean> recommendationTypeMap = new HashMap<>();
|
|
||||||
private final Map<String, Integer> rankTypeMap = new HashMap<>();
|
|
||||||
private final Colors colors = new Colors();
|
|
||||||
private final Map<String, Long> reanalysisVersions = new HashMap<>();
|
|
||||||
private final Set<String> deleted = new HashSet<>();
|
|
||||||
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void stubClients() {
|
|
||||||
|
|
||||||
TenantContext.setTenantId("redaction");
|
|
||||||
|
|
||||||
when(rulesClient.getVersion(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(0L);
|
|
||||||
when(rulesClient.getRules(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(JSONPrimitive.of(RULES));
|
|
||||||
|
|
||||||
loadDictionaryForTest();
|
|
||||||
loadTypeForTest();
|
|
||||||
loadNerForTest();
|
|
||||||
when(dictionaryClient.getVersion(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(0L);
|
|
||||||
when(dictionaryClient.getAllTypesForDossierTemplate(TEST_DOSSIER_TEMPLATE_ID, false)).thenReturn(getTypeResponse());
|
|
||||||
|
|
||||||
when(dictionaryClient.getVersion(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(0L);
|
|
||||||
when(dictionaryClient.getAllTypesForDossier(TEST_DOSSIER_ID, false)).thenReturn(List.of(Type.builder()
|
|
||||||
.id(DOSSIER_REDACTIONS + ":" + TEST_DOSSIER_TEMPLATE_ID)
|
|
||||||
.type(DOSSIER_REDACTIONS)
|
|
||||||
.dossierTemplateId(TEST_DOSSIER_ID)
|
|
||||||
.hexColor("#ffe187")
|
|
||||||
.isHint(hintTypeMap.get(DOSSIER_REDACTIONS))
|
|
||||||
.isCaseInsensitive(caseInSensitiveMap.get(DOSSIER_REDACTIONS))
|
|
||||||
.isRecommendation(recommendationTypeMap.get(DOSSIER_REDACTIONS))
|
|
||||||
.rank(rankTypeMap.get(DOSSIER_REDACTIONS))
|
|
||||||
.build()));
|
|
||||||
|
|
||||||
mockDictionaryCalls(null);
|
|
||||||
mockDictionaryCalls(0L);
|
|
||||||
|
|
||||||
when(dictionaryClient.getColors(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(colors);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void mockDictionaryCalls(Long version) {
|
|
||||||
|
|
||||||
when(dictionaryClient.getDictionaryForType(VERTEBRATE + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(VERTEBRATE,
|
|
||||||
false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(ADDRESS + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(ADDRESS, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(AUTHOR + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(AUTHOR, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(SPONSOR + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(SPONSOR, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(NO_REDACTION_INDICATOR + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(
|
|
||||||
NO_REDACTION_INDICATOR,
|
|
||||||
false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(REDACTION_INDICATOR + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(
|
|
||||||
REDACTION_INDICATOR,
|
|
||||||
false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(HINT_ONLY + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(HINT_ONLY, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(MUST_REDACT + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(MUST_REDACT,
|
|
||||||
false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(PUBLISHED_INFORMATION + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(
|
|
||||||
PUBLISHED_INFORMATION,
|
|
||||||
false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(TEST_METHOD + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(TEST_METHOD,
|
|
||||||
false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(PII + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(PII, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(PURITY + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(PURITY, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(IMAGE + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(IMAGE, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(OCR + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(OCR, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(LOGO + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(LOGO, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(SIGNATURE + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(SIGNATURE, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(FORMULA + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(FORMULA, false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(ROTATE_SIMPLE + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(ROTATE_SIMPLE,
|
|
||||||
false));
|
|
||||||
when(dictionaryClient.getDictionaryForType(DOSSIER_REDACTIONS + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(
|
|
||||||
DOSSIER_REDACTIONS,
|
|
||||||
true));
|
|
||||||
when(dictionaryClient.getDictionaryForType(IMPORTED_REDACTION + ":" + TEST_DOSSIER_TEMPLATE_ID, version)).then((Answer<Type>) invocation -> getDictionaryResponse(
|
|
||||||
IMPORTED_REDACTION,
|
|
||||||
true));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static String loadFromClassPath(String path) {
|
|
||||||
|
|
||||||
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
|
|
||||||
if (resource == null) {
|
|
||||||
throw new IllegalArgumentException("could not load classpath resource: drools/rules.drl");
|
|
||||||
}
|
|
||||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
String str;
|
|
||||||
while ((str = br.readLine()) != null) {
|
|
||||||
sb.append(str).append("\n");
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalArgumentException("could not load classpath resource: " + path, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String cleanDictionaryEntry(String entry) {
|
|
||||||
|
|
||||||
return TextNormalizationUtilities.removeHyphenLineBreaks(entry).replaceAll("\\n", " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private List<DictionaryEntry> toDictionaryEntry(List<String> entries) {
|
|
||||||
|
|
||||||
if (entries == null) {
|
|
||||||
entries = Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DictionaryEntry> dictionaryEntries = new ArrayList<>();
|
|
||||||
entries.forEach(entry -> {
|
|
||||||
dictionaryEntries.add(DictionaryEntry.builder().value(entry).version(reanalysisVersions.getOrDefault(entry, 0L)).deleted(deleted.contains(entry)).build());
|
|
||||||
});
|
|
||||||
return dictionaryEntries;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Type getDictionaryResponse(String type, boolean isDossierDictionary) {
|
|
||||||
|
|
||||||
return Type.builder()
|
|
||||||
.id(type + ":" + TEST_DOSSIER_TEMPLATE_ID)
|
|
||||||
.hexColor(typeColorMap.get(type))
|
|
||||||
.entries(isDossierDictionary ? toDictionaryEntry(dossierDictionary.get(type)) : toDictionaryEntry(dictionary.get(type)))
|
|
||||||
.falsePositiveEntries(falsePositive.containsKey(type) ? toDictionaryEntry(falsePositive.get(type)) : new ArrayList<>())
|
|
||||||
.falseRecommendationEntries(falseRecommendation.containsKey(type) ? toDictionaryEntry(falseRecommendation.get(type)) : new ArrayList<>())
|
|
||||||
.isHint(hintTypeMap.get(type))
|
|
||||||
.isCaseInsensitive(caseInSensitiveMap.get(type))
|
|
||||||
.isRecommendation(recommendationTypeMap.get(type))
|
|
||||||
.rank(rankTypeMap.get(type))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void loadTypeForTest() {
|
|
||||||
|
|
||||||
typeColorMap.put(VERTEBRATE, "#ff85f7");
|
|
||||||
typeColorMap.put(ADDRESS, "#ffe187");
|
|
||||||
typeColorMap.put(AUTHOR, "#ffe187");
|
|
||||||
typeColorMap.put(SPONSOR, "#85ebff");
|
|
||||||
typeColorMap.put(NO_REDACTION_INDICATOR, "#be85ff");
|
|
||||||
typeColorMap.put(REDACTION_INDICATOR, "#caff85");
|
|
||||||
typeColorMap.put(HINT_ONLY, "#abc0c4");
|
|
||||||
typeColorMap.put(MUST_REDACT, "#fab4c0");
|
|
||||||
typeColorMap.put(PUBLISHED_INFORMATION, "#85ebff");
|
|
||||||
typeColorMap.put(TEST_METHOD, "#91fae8");
|
|
||||||
typeColorMap.put(PII, "#66ccff");
|
|
||||||
typeColorMap.put(PURITY, "#ffe187");
|
|
||||||
typeColorMap.put(IMAGE, "#fcc5fb");
|
|
||||||
typeColorMap.put(OCR, "#fcc5fb");
|
|
||||||
typeColorMap.put(LOGO, "#ffe187");
|
|
||||||
typeColorMap.put(FORMULA, "#ffe187");
|
|
||||||
typeColorMap.put(SIGNATURE, "#ffe187");
|
|
||||||
typeColorMap.put(IMPORTED_REDACTION, "#fcfbe6");
|
|
||||||
typeColorMap.put(ROTATE_SIMPLE, "#66ccff");
|
|
||||||
|
|
||||||
hintTypeMap.put(VERTEBRATE, true);
|
|
||||||
hintTypeMap.put(ADDRESS, false);
|
|
||||||
hintTypeMap.put(AUTHOR, false);
|
|
||||||
hintTypeMap.put(SPONSOR, false);
|
|
||||||
hintTypeMap.put(NO_REDACTION_INDICATOR, true);
|
|
||||||
hintTypeMap.put(REDACTION_INDICATOR, true);
|
|
||||||
hintTypeMap.put(HINT_ONLY, true);
|
|
||||||
hintTypeMap.put(MUST_REDACT, true);
|
|
||||||
hintTypeMap.put(PUBLISHED_INFORMATION, true);
|
|
||||||
hintTypeMap.put(TEST_METHOD, true);
|
|
||||||
hintTypeMap.put(PII, false);
|
|
||||||
hintTypeMap.put(PURITY, false);
|
|
||||||
hintTypeMap.put(IMAGE, true);
|
|
||||||
hintTypeMap.put(OCR, true);
|
|
||||||
hintTypeMap.put(FORMULA, false);
|
|
||||||
hintTypeMap.put(LOGO, false);
|
|
||||||
hintTypeMap.put(SIGNATURE, false);
|
|
||||||
hintTypeMap.put(DOSSIER_REDACTIONS, false);
|
|
||||||
hintTypeMap.put(IMPORTED_REDACTION, false);
|
|
||||||
hintTypeMap.put(ROTATE_SIMPLE, false);
|
|
||||||
|
|
||||||
caseInSensitiveMap.put(VERTEBRATE, true);
|
|
||||||
caseInSensitiveMap.put(ADDRESS, false);
|
|
||||||
caseInSensitiveMap.put(AUTHOR, false);
|
|
||||||
caseInSensitiveMap.put(SPONSOR, false);
|
|
||||||
caseInSensitiveMap.put(NO_REDACTION_INDICATOR, true);
|
|
||||||
caseInSensitiveMap.put(REDACTION_INDICATOR, true);
|
|
||||||
caseInSensitiveMap.put(HINT_ONLY, true);
|
|
||||||
caseInSensitiveMap.put(MUST_REDACT, true);
|
|
||||||
caseInSensitiveMap.put(PUBLISHED_INFORMATION, true);
|
|
||||||
caseInSensitiveMap.put(TEST_METHOD, false);
|
|
||||||
caseInSensitiveMap.put(PII, false);
|
|
||||||
caseInSensitiveMap.put(PURITY, false);
|
|
||||||
caseInSensitiveMap.put(IMAGE, true);
|
|
||||||
caseInSensitiveMap.put(OCR, true);
|
|
||||||
caseInSensitiveMap.put(SIGNATURE, true);
|
|
||||||
caseInSensitiveMap.put(LOGO, true);
|
|
||||||
caseInSensitiveMap.put(FORMULA, true);
|
|
||||||
caseInSensitiveMap.put(DOSSIER_REDACTIONS, false);
|
|
||||||
caseInSensitiveMap.put(IMPORTED_REDACTION, false);
|
|
||||||
caseInSensitiveMap.put(ROTATE_SIMPLE, true);
|
|
||||||
|
|
||||||
recommendationTypeMap.put(VERTEBRATE, false);
|
|
||||||
recommendationTypeMap.put(ADDRESS, false);
|
|
||||||
recommendationTypeMap.put(AUTHOR, false);
|
|
||||||
recommendationTypeMap.put(SPONSOR, false);
|
|
||||||
recommendationTypeMap.put(NO_REDACTION_INDICATOR, false);
|
|
||||||
recommendationTypeMap.put(REDACTION_INDICATOR, false);
|
|
||||||
recommendationTypeMap.put(HINT_ONLY, false);
|
|
||||||
recommendationTypeMap.put(MUST_REDACT, false);
|
|
||||||
recommendationTypeMap.put(PUBLISHED_INFORMATION, false);
|
|
||||||
recommendationTypeMap.put(TEST_METHOD, false);
|
|
||||||
recommendationTypeMap.put(PII, false);
|
|
||||||
recommendationTypeMap.put(PURITY, false);
|
|
||||||
recommendationTypeMap.put(IMAGE, false);
|
|
||||||
recommendationTypeMap.put(OCR, false);
|
|
||||||
recommendationTypeMap.put(FORMULA, false);
|
|
||||||
recommendationTypeMap.put(SIGNATURE, false);
|
|
||||||
recommendationTypeMap.put(LOGO, false);
|
|
||||||
recommendationTypeMap.put(DOSSIER_REDACTIONS, false);
|
|
||||||
recommendationTypeMap.put(IMPORTED_REDACTION, false);
|
|
||||||
recommendationTypeMap.put(ROTATE_SIMPLE, false);
|
|
||||||
|
|
||||||
rankTypeMap.put(PURITY, 155);
|
|
||||||
rankTypeMap.put(PII, 150);
|
|
||||||
rankTypeMap.put(ADDRESS, 140);
|
|
||||||
rankTypeMap.put(AUTHOR, 130);
|
|
||||||
rankTypeMap.put(SPONSOR, 120);
|
|
||||||
rankTypeMap.put(VERTEBRATE, 110);
|
|
||||||
rankTypeMap.put(MUST_REDACT, 100);
|
|
||||||
rankTypeMap.put(REDACTION_INDICATOR, 90);
|
|
||||||
rankTypeMap.put(NO_REDACTION_INDICATOR, 80);
|
|
||||||
rankTypeMap.put(PUBLISHED_INFORMATION, 70);
|
|
||||||
rankTypeMap.put(TEST_METHOD, 60);
|
|
||||||
rankTypeMap.put(HINT_ONLY, 50);
|
|
||||||
rankTypeMap.put(IMAGE, 30);
|
|
||||||
rankTypeMap.put(OCR, 29);
|
|
||||||
rankTypeMap.put(LOGO, 28);
|
|
||||||
rankTypeMap.put(SIGNATURE, 27);
|
|
||||||
rankTypeMap.put(FORMULA, 26);
|
|
||||||
rankTypeMap.put(DOSSIER_REDACTIONS, 200);
|
|
||||||
rankTypeMap.put(IMPORTED_REDACTION, 200);
|
|
||||||
rankTypeMap.put(ROTATE_SIMPLE, 150);
|
|
||||||
|
|
||||||
colors.setSkippedColor("#cccccc");
|
|
||||||
colors.setRequestAddColor("#04b093");
|
|
||||||
colors.setRequestRemoveColor("#04b093");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private void loadNerForTest() {
|
|
||||||
|
|
||||||
ClassPathResource responseJson = new ClassPathResource("files/ner_response.json");
|
|
||||||
storageService.storeObject(TenantContext.getTenantId(),
|
|
||||||
RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.NER_ENTITIES),
|
|
||||||
responseJson.getInputStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private List<Type> getTypeResponse() {
|
|
||||||
|
|
||||||
return typeColorMap.entrySet()
|
|
||||||
.stream()
|
|
||||||
.map(typeColor -> Type.builder()
|
|
||||||
.id(typeColor.getKey() + ":" + TEST_DOSSIER_TEMPLATE_ID)
|
|
||||||
.type(typeColor.getKey())
|
|
||||||
.dossierTemplateId(TEST_DOSSIER_TEMPLATE_ID)
|
|
||||||
.hexColor(typeColor.getValue())
|
|
||||||
.isHint(hintTypeMap.get(typeColor.getKey()))
|
|
||||||
.isCaseInsensitive(caseInSensitiveMap.get(typeColor.getKey()))
|
|
||||||
.isRecommendation(recommendationTypeMap.get(typeColor.getKey()))
|
|
||||||
.rank(rankTypeMap.get(typeColor.getKey()))
|
|
||||||
.build())
|
|
||||||
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected List<File> getPathsRecursively(File path) {
|
|
||||||
|
|
||||||
List<File> result = new ArrayList<>();
|
|
||||||
if (path == null || path.listFiles() == null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
for (File f : path.listFiles()) {
|
|
||||||
if (f.isFile()) {
|
|
||||||
result.add(f);
|
|
||||||
} else {
|
|
||||||
result.addAll(getPathsRecursively(f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void loadOnlyDictionaryForSimpleFile() {
|
|
||||||
|
|
||||||
dictionary.clear();
|
|
||||||
dictionary.computeIfAbsent(ROTATE_SIMPLE, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/RotateTestFileSimple.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void loadDictionaryForTest() {
|
|
||||||
|
|
||||||
dictionary.computeIfAbsent(AUTHOR, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/CBI_author.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(SPONSOR, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/CBI_sponsor.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(VERTEBRATE, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/vertebrate.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(ADDRESS, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/CBI_address.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(NO_REDACTION_INDICATOR, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/no_redaction_indicator.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(REDACTION_INDICATOR, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/redaction_indicator.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(HINT_ONLY, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/hint_only.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(MUST_REDACT, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/must_redact.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(PUBLISHED_INFORMATION, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/published_information.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(TEST_METHOD, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/test_method.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(PII, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/PII.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(PURITY, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/purity.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(IMAGE, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/empty.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(OCR, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/empty.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(LOGO, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/empty.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(SIGNATURE, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/empty.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dictionary.computeIfAbsent(FORMULA, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/empty.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dossierDictionary.computeIfAbsent(DOSSIER_REDACTIONS, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/dossier_redactions.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
dossierDictionary.put(IMPORTED_REDACTION, new ArrayList<>());
|
|
||||||
|
|
||||||
falsePositive.computeIfAbsent(PII, v -> new ArrayList<>())
|
|
||||||
.addAll(ResourceLoader.load("dictionaries/PII_false_positive.txt").stream().map(this::cleanDictionaryEntry).collect(Collectors.toSet()));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
protected AnalyzeRequest prepareStorage(String file) {
|
|
||||||
|
|
||||||
return prepareStorage(file, "files/cv_service_empty_response.json", "files/empty_image_response.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
protected AnalyzeRequest prepareStorage(String file, String cvServiceResponseFile, String imageInfoFile) {
|
|
||||||
|
|
||||||
ClassPathResource pdfFileResource = new ClassPathResource(file);
|
|
||||||
ClassPathResource cvServiceResponseFileResource = new ClassPathResource(cvServiceResponseFile);
|
|
||||||
ClassPathResource imageInfoFileResource = new ClassPathResource(imageInfoFile);
|
|
||||||
|
|
||||||
return prepareStorage(pdfFileResource.getInputStream(), cvServiceResponseFileResource.getInputStream(), imageInfoFileResource.getInputStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
protected AnalyzeRequest prepareStorage(InputStream fileStream, InputStream cvServiceResponseFileStream, InputStream imageInfoStream) {
|
|
||||||
|
|
||||||
AnalyzeRequest request = AnalyzeRequest.builder()
|
|
||||||
.dossierTemplateId(TEST_DOSSIER_TEMPLATE_ID)
|
|
||||||
.dossierId(TEST_DOSSIER_ID)
|
|
||||||
.fileId(TEST_FILE_ID)
|
|
||||||
.lastProcessed(OffsetDateTime.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
storageService.storeObject(TenantContext.getTenantId(),
|
|
||||||
RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.IMAGE_INFO),
|
|
||||||
imageInfoStream);
|
|
||||||
storageService.storeObject(TenantContext.getTenantId(),
|
|
||||||
RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.TABLES),
|
|
||||||
cvServiceResponseFileStream);
|
|
||||||
storageService.storeObject(TenantContext.getTenantId(), RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.ORIGIN), fileStream);
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void cleanupStorage() {
|
|
||||||
|
|
||||||
if (this.storageService instanceof FileSystemBackedStorageService) {
|
|
||||||
((FileSystemBackedStorageService) this.storageService).clearStorage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class, StorageAutoConfiguration.class})
|
|
||||||
public static class TestConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public KieContainer kieContainer() {
|
|
||||||
|
|
||||||
KieServices kieServices = KieServices.Factory.get();
|
|
||||||
|
|
||||||
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
|
|
||||||
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH, "UTF-8"));
|
|
||||||
|
|
||||||
KieRepository kieRepository = kieServices.getRepository();
|
|
||||||
|
|
||||||
kieRepository.addKieModule(new KieModule() {
|
|
||||||
public ReleaseId getReleaseId() {
|
|
||||||
|
|
||||||
return kieRepository.getDefaultReleaseId();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem).buildAll();
|
|
||||||
|
|
||||||
return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Primary
|
|
||||||
public StorageService inmemoryStorage() {
|
|
||||||
|
|
||||||
return new FileSystemBackedStorageService();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -2,50 +2,59 @@ package com.iqser.red.service.redaction.v1.server;
|
|||||||
|
|
||||||
import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.boundaryIsSurroundedBySeparators;
|
import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.boundaryIsSurroundedBySeparators;
|
||||||
import static com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw.drawRectangle2DList;
|
import static com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw.drawRectangle2DList;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.kie.api.KieServices;
|
||||||
|
import org.kie.api.builder.KieBuilder;
|
||||||
|
import org.kie.api.builder.KieFileSystem;
|
||||||
|
import org.kie.api.builder.KieModule;
|
||||||
import org.kie.api.runtime.KieContainer;
|
import org.kie.api.runtime.KieContainer;
|
||||||
import org.kie.api.runtime.KieSession;
|
import org.kie.internal.io.ResourceFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.PdfSegmentationService;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory.DocumentGraphFactory;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
import com.iqser.red.service.redaction.v1.server.document.graph.BuildDocumentGraphIntegrationTest;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SectionNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.SemanticNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
||||||
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.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.dictionary.SearchImplementation;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.service.DictionaryService;
|
import com.iqser.red.service.redaction.v1.server.redaction.service.DictionaryService;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.service.RedactionLogCreatorService;
|
import com.iqser.red.service.redaction.v1.server.redaction.service.DroolsExecutionService;
|
||||||
import com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw;
|
import com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
public class DocumentGraphIntegrationTest extends AbstractTestWithDictionaries {
|
@Import(DocumentGraphPerformanceIntegrationTest.TestConfiguration.class)
|
||||||
|
public class DocumentGraphPerformanceIntegrationTest extends BuildDocumentGraphIntegrationTest {
|
||||||
|
|
||||||
@Autowired
|
private static final String RULES = "drools/rules.drl";
|
||||||
private DocumentGraphFactory documentGraphFactory;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PdfSegmentationService segmentationService;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DictionaryService dictionaryService;
|
private DictionaryService dictionaryService;
|
||||||
@ -54,24 +63,71 @@ public class DocumentGraphIntegrationTest extends AbstractTestWithDictionaries {
|
|||||||
private EntityCreationService entityCreationService;
|
private EntityCreationService entityCreationService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedactionLogCreatorService redactionLogCreatorService;
|
private DroolsExecutionService droolsExecutionService;
|
||||||
|
|
||||||
@Qualifier("kieContainer")
|
@Qualifier("kieContainer")
|
||||||
@Autowired
|
@Autowired
|
||||||
private KieContainer kieContainer;
|
private KieContainer kieContainer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(BuildDocumentGraphIntegrationTest.TestConfiguration.class)
|
||||||
|
public static class TestConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public KieContainer kieContainer() {
|
||||||
|
|
||||||
|
KieServices kieServices = KieServices.Factory.get();
|
||||||
|
|
||||||
|
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
|
||||||
|
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES));
|
||||||
|
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
|
||||||
|
kieBuilder.buildAll();
|
||||||
|
KieModule kieModule = kieBuilder.getKieModule();
|
||||||
|
|
||||||
|
return kieServices.newKieContainer(kieModule.getReleaseId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void stubClients() {
|
||||||
|
|
||||||
|
TenantContext.setTenantId("redaction");
|
||||||
|
|
||||||
|
when(rulesClient.getVersion(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(0L);
|
||||||
|
when(rulesClient.getRules(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(JSONPrimitive.of(RULES));
|
||||||
|
|
||||||
|
loadDictionaryForTest();
|
||||||
|
loadTypeForTest();
|
||||||
|
loadNerForTest();
|
||||||
|
when(dictionaryClient.getVersion(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(0L);
|
||||||
|
when(dictionaryClient.getAllTypesForDossierTemplate(TEST_DOSSIER_TEMPLATE_ID, false)).thenReturn(getTypeResponse());
|
||||||
|
|
||||||
|
when(dictionaryClient.getVersion(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(0L);
|
||||||
|
when(dictionaryClient.getAllTypesForDossier(TEST_DOSSIER_ID, false)).thenReturn(List.of(Type.builder()
|
||||||
|
.id(DOSSIER_REDACTIONS_INDICATOR + ":" + TEST_DOSSIER_TEMPLATE_ID)
|
||||||
|
.type(DOSSIER_REDACTIONS_INDICATOR)
|
||||||
|
.dossierTemplateId(TEST_DOSSIER_ID)
|
||||||
|
.hexColor("#ffe187")
|
||||||
|
.isHint(hintTypeMap.get(DOSSIER_REDACTIONS_INDICATOR))
|
||||||
|
.isCaseInsensitive(caseInSensitiveMap.get(DOSSIER_REDACTIONS_INDICATOR))
|
||||||
|
.isRecommendation(recommendationTypeMap.get(DOSSIER_REDACTIONS_INDICATOR))
|
||||||
|
.rank(rankTypeMap.get(DOSSIER_REDACTIONS_INDICATOR))
|
||||||
|
.build()));
|
||||||
|
|
||||||
|
mockDictionaryCalls(null);
|
||||||
|
|
||||||
|
when(dictionaryClient.getColors(TEST_DOSSIER_TEMPLATE_ID)).thenReturn(colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void testDroolsOnDocumentGraph() {
|
public void testDroolsOnDocumentGraph() {
|
||||||
|
|
||||||
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
|
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
|
||||||
|
DocumentGraph document = buildGraph(filename);
|
||||||
prepareStorage(filename + ".pdf");
|
|
||||||
ClassPathResource fileResource = new ClassPathResource(filename + ".pdf");
|
|
||||||
|
|
||||||
var classifiedDoc = segmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, fileResource.getInputStream(), null);
|
|
||||||
DocumentGraph document = documentGraphFactory.buildDocumentGraph(classifiedDoc);
|
|
||||||
|
|
||||||
dictionaryService.updateDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
|
dictionaryService.updateDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
|
||||||
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
|
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
|
||||||
@ -88,28 +144,20 @@ public class DocumentGraphIntegrationTest extends AbstractTestWithDictionaries {
|
|||||||
long graphInsertionStart = System.currentTimeMillis();
|
long graphInsertionStart = System.currentTimeMillis();
|
||||||
foundEntities.forEach(entity -> entity.setDossierDictionaryEntry(true));
|
foundEntities.forEach(entity -> entity.setDossierDictionaryEntry(true));
|
||||||
foundEntities.forEach(entity -> entity.setDictionaryEntry(true));
|
foundEntities.forEach(entity -> entity.setDictionaryEntry(true));
|
||||||
foundEntities.forEach(entity -> entityCreationService.addEntityToGraph(entity, document.getTableOfContents()));
|
foundEntities.forEach(entity -> entityCreationService.addEntityToGraph(entity, document));
|
||||||
System.out.printf("Inserting entities into the graph took %d ms\n", System.currentTimeMillis() - graphInsertionStart);
|
System.out.printf("Inserting entities into the graph took %d ms\n", System.currentTimeMillis() - graphInsertionStart);
|
||||||
|
|
||||||
KieSession kieSession = kieContainer.newKieSession();
|
long droolsStart = System.currentTimeMillis();
|
||||||
kieSession.setGlobal("document", document);
|
List<FileAttribute> fileAttributes = droolsExecutionService.executeRules(kieContainer,
|
||||||
kieSession.setGlobal("entityCreationService", entityCreationService);
|
document,
|
||||||
kieSession.setGlobal("dictionary", dictionary);
|
dictionary,
|
||||||
|
Collections.emptyList(),
|
||||||
|
new ManualRedactions(),
|
||||||
|
Collections.emptyList());
|
||||||
|
System.out.printf("Firing rules took %d ms\n", System.currentTimeMillis() - droolsStart);
|
||||||
|
|
||||||
long serializationStart = System.currentTimeMillis();
|
|
||||||
document.getEntities().forEach(kieSession::insert);
|
|
||||||
document.getTableOfContents().streamAllEntriesInOrder().forEach(entry -> kieSession.insert(entry.getNode()));
|
|
||||||
document.getPages().forEach(kieSession::insert);
|
|
||||||
System.out.printf("Object serialization and kieSession insertion took %d ms\n", System.currentTimeMillis() - serializationStart);
|
|
||||||
|
|
||||||
long dictionaryAddsStart = System.currentTimeMillis();
|
|
||||||
kieSession.insert(FileAttribute.builder().label("Vertebrate Study").value("Yes").build());
|
|
||||||
kieSession.getAgenda().getAgendaGroup("LOCAL_DICTIONARY_ADDS").setFocus();
|
|
||||||
kieSession.fireAllRules();
|
|
||||||
System.out.printf("Firing rules took %d ms\n", System.currentTimeMillis() - dictionaryAddsStart);
|
|
||||||
System.out.printf("Total time %d ms\n", System.currentTimeMillis() - dictionarySearchStart);
|
System.out.printf("Total time %d ms\n", System.currentTimeMillis() - dictionarySearchStart);
|
||||||
List<RedactionLogEntry> redactionLogEntries = redactionLogCreatorService.createRedactionLog(document, TEST_DOSSIER_TEMPLATE_ID);
|
drawAllEntities(filename, document);
|
||||||
drawAllEntities(filename, fileResource, document);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -119,12 +167,7 @@ public class DocumentGraphIntegrationTest extends AbstractTestWithDictionaries {
|
|||||||
|
|
||||||
int n = 10000;
|
int n = 10000;
|
||||||
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
|
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
|
||||||
|
DocumentGraph document = buildGraph(filename);
|
||||||
prepareStorage(filename + ".pdf");
|
|
||||||
ClassPathResource fileResource = new ClassPathResource(filename + ".pdf");
|
|
||||||
|
|
||||||
var classifiedDoc = segmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, fileResource.getInputStream(), null);
|
|
||||||
DocumentGraph document = documentGraphFactory.buildDocumentGraph(classifiedDoc);
|
|
||||||
var start = System.currentTimeMillis();
|
var start = System.currentTimeMillis();
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
TextBlock textBlock = document.getTextBlock();
|
TextBlock textBlock = document.getTextBlock();
|
||||||
@ -155,17 +198,12 @@ public class DocumentGraphIntegrationTest extends AbstractTestWithDictionaries {
|
|||||||
public void testDictionarySearchOnDocumentGraph() {
|
public void testDictionarySearchOnDocumentGraph() {
|
||||||
|
|
||||||
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
|
String filename = "files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06";
|
||||||
|
DocumentGraph document = buildGraph(filename);
|
||||||
prepareStorage(filename + ".pdf");
|
|
||||||
ClassPathResource fileResource = new ClassPathResource(filename + ".pdf");
|
|
||||||
|
|
||||||
var classifiedDoc = segmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, fileResource.getInputStream(), null);
|
|
||||||
DocumentGraph document = documentGraphFactory.buildDocumentGraph(classifiedDoc);
|
|
||||||
|
|
||||||
dictionaryService.updateDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
|
dictionaryService.updateDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
|
||||||
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
|
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(TEST_DOSSIER_TEMPLATE_ID, TEST_DOSSIER_ID);
|
||||||
|
|
||||||
int numberOfRuns = 25;
|
int numberOfRuns = 1;
|
||||||
float totalSearchTime = 0;
|
float totalSearchTime = 0;
|
||||||
float totalGraphTime = 0;
|
float totalGraphTime = 0;
|
||||||
float totalInsertTime = 0;
|
float totalInsertTime = 0;
|
||||||
@ -174,7 +212,7 @@ public class DocumentGraphIntegrationTest extends AbstractTestWithDictionaries {
|
|||||||
foundEntities = new LinkedList<>();
|
foundEntities = new LinkedList<>();
|
||||||
|
|
||||||
var graphStart = System.currentTimeMillis();
|
var graphStart = System.currentTimeMillis();
|
||||||
document = documentGraphFactory.buildDocumentGraph(classifiedDoc);
|
document = buildGraph(filename);
|
||||||
var graphTime = System.currentTimeMillis() - graphStart;
|
var graphTime = System.currentTimeMillis() - graphStart;
|
||||||
totalGraphTime += graphTime;
|
totalGraphTime += graphTime;
|
||||||
|
|
||||||
@ -189,7 +227,7 @@ public class DocumentGraphIntegrationTest extends AbstractTestWithDictionaries {
|
|||||||
|
|
||||||
var insertStart = System.currentTimeMillis();
|
var insertStart = System.currentTimeMillis();
|
||||||
DocumentGraph finalDocument = document;
|
DocumentGraph finalDocument = document;
|
||||||
foundEntities.forEach(entity -> entityCreationService.addEntityToGraph(entity, finalDocument.getTableOfContents()));
|
foundEntities.forEach(entity -> entityCreationService.addEntityToGraph(entity, finalDocument));
|
||||||
var insertTime = System.currentTimeMillis() - insertStart;
|
var insertTime = System.currentTimeMillis() - insertStart;
|
||||||
totalInsertTime += insertTime;
|
totalInsertTime += insertTime;
|
||||||
|
|
||||||
@ -202,13 +240,14 @@ public class DocumentGraphIntegrationTest extends AbstractTestWithDictionaries {
|
|||||||
System.out.printf("Found %d entities and saved %d\n", foundEntities.size(), document.getEntities().size());
|
System.out.printf("Found %d entities and saved %d\n", foundEntities.size(), document.getEntities().size());
|
||||||
assert document.getEntities().size() == foundEntities.size();
|
assert document.getEntities().size() == foundEntities.size();
|
||||||
|
|
||||||
drawAllEntities(filename, fileResource, document);
|
drawAllEntities(filename, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void drawAllEntities(String filename, ClassPathResource fileResource, DocumentGraph document) throws IOException {
|
private static void drawAllEntities(String filename, DocumentGraph document) throws IOException {
|
||||||
|
|
||||||
var tmpFileName = "/tmp/" + filename.split("/")[2] + "_ENTITY_BBOX.pdf";
|
var tmpFileName = "/tmp/" + filename.split("/")[2] + "_ENTITY_BBOX.pdf";
|
||||||
|
var fileResource = new ClassPathResource(filename + ".pdf");
|
||||||
try (var fileStream = fileResource.getInputStream()) {
|
try (var fileStream = fileResource.getInputStream()) {
|
||||||
PDDocument pdDocument = PDDocument.load(fileStream);
|
PDDocument pdDocument = PDDocument.load(fileStream);
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ import org.springframework.context.annotation.Import;
|
|||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.*;
|
|
||||||
import com.amazonaws.services.s3.AmazonS3;
|
import com.amazonaws.services.s3.AmazonS3;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
|
||||||
@ -53,6 +53,8 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
|
|||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntry;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntry;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
|
||||||
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.ChangeType;
|
||||||
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
|
||||||
import com.iqser.red.service.redaction.v1.model.StructureAnalyzeRequest;
|
import com.iqser.red.service.redaction.v1.model.StructureAnalyzeRequest;
|
||||||
import com.iqser.red.service.redaction.v1.server.annotate.AnnotationService;
|
import com.iqser.red.service.redaction.v1.server.annotate.AnnotationService;
|
||||||
import com.iqser.red.service.redaction.v1.server.client.DictionaryClient;
|
import com.iqser.red.service.redaction.v1.server.client.DictionaryClient;
|
||||||
@ -202,7 +204,7 @@ public class HeadlinesGoldStandardIntegrationTest {
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class/*, StorageAutoConfiguration.class*/})
|
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class/*, StorageAutoConfiguration.class*/})
|
||||||
@ComponentScan(excludeFilters={@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE, value=StorageAutoConfiguration.class)})
|
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = StorageAutoConfiguration.class)})
|
||||||
public static class RedactionIntegrationTestConfiguration {
|
public static class RedactionIntegrationTestConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -365,7 +367,9 @@ public class HeadlinesGoldStandardIntegrationTest {
|
|||||||
.lastProcessed(OffsetDateTime.now())
|
.lastProcessed(OffsetDateTime.now())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
storageService.storeObject(TenantContext.getTenantId(), RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.TABLES), cvServiceResponseFileStream);
|
storageService.storeObject(TenantContext.getTenantId(),
|
||||||
|
RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.TABLES),
|
||||||
|
cvServiceResponseFileStream);
|
||||||
storageService.storeObject(TenantContext.getTenantId(), RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.ORIGIN), fileStream);
|
storageService.storeObject(TenantContext.getTenantId(), RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.ORIGIN), fileStream);
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
@ -376,7 +380,7 @@ public class HeadlinesGoldStandardIntegrationTest {
|
|||||||
|
|
||||||
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
|
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
throw new IllegalArgumentException("could not load classpath resource: drools/rules.drl");
|
throw new IllegalArgumentException("could not load classpath resource: drools/prod_rules_new.drl");
|
||||||
}
|
}
|
||||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@ -395,7 +399,9 @@ public class HeadlinesGoldStandardIntegrationTest {
|
|||||||
private void loadNerForTest() {
|
private void loadNerForTest() {
|
||||||
|
|
||||||
ClassPathResource responseJson = new ClassPathResource("files/ner_response.json");
|
ClassPathResource responseJson = new ClassPathResource("files/ner_response.json");
|
||||||
storageService.storeObject(TenantContext.getTenantId(), RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.NER_ENTITIES), responseJson.getInputStream());
|
storageService.storeObject(TenantContext.getTenantId(),
|
||||||
|
RedactionStorageService.StorageIdUtils.getStorageId(TEST_DOSSIER_ID, TEST_FILE_ID, FileType.NER_ENTITIES),
|
||||||
|
responseJson.getInputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -227,15 +227,16 @@ public class RedactionIntegrationTest extends AbstractRedactionIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
public void titleExtraction() throws IOException {
|
public void titleExtraction() throws IOException {
|
||||||
|
|
||||||
AnalyzeRequest request = uploadFileToStorage("files/new/APN3_Clean_6.1 (6.4.3.01-02)_Apple_211029.pdf");
|
AnalyzeRequest request = uploadFileToStorage("files/Metolachlor/S-Metolachlor_RAR_02_Volume_2_2018-09-06.pdf");
|
||||||
|
|
||||||
|
System.out.println("Start Full integration test");
|
||||||
analyzeService.analyzeDocumentStructure(new StructureAnalyzeRequest(request.getDossierId(), request.getFileId()));
|
analyzeService.analyzeDocumentStructure(new StructureAnalyzeRequest(request.getDossierId(), request.getFileId()));
|
||||||
|
System.out.println("Finished structure analysis");
|
||||||
AnalyzeResult result = analyzeService.analyze(request);
|
AnalyzeResult result = analyzeService.analyze(request);
|
||||||
|
System.out.println("Finished analysis");
|
||||||
|
|
||||||
var redactionLog = redactionStorageService.getRedactionLog(TEST_DOSSIER_ID, TEST_FILE_ID);
|
var redactionLog = redactionStorageService.getRedactionLog(TEST_DOSSIER_ID, TEST_FILE_ID);
|
||||||
|
|
||||||
var text = redactionStorageService.getText(TEST_DOSSIER_ID, TEST_FILE_ID);
|
|
||||||
|
|
||||||
AnnotateResponse annotateResponse = annotationService.annotate(AnnotateRequest.builder().dossierId(TEST_DOSSIER_ID).fileId(TEST_FILE_ID).build());
|
AnnotateResponse annotateResponse = annotationService.annotate(AnnotateRequest.builder().dossierId(TEST_DOSSIER_ID).fileId(TEST_FILE_ID).build());
|
||||||
|
|
||||||
String outputFileName = OsUtils.getTemporaryDirectory() + "/Annotated.pdf";
|
String outputFileName = OsUtils.getTemporaryDirectory() + "/Annotated.pdf";
|
||||||
|
|||||||
@ -95,7 +95,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Import(RulesTest.RulesTestConfiguration.class)
|
@Import(RulesTest.RulesTestConfiguration.class)
|
||||||
public class RulesTest {
|
public class RulesTest {
|
||||||
|
|
||||||
private static final String RULES_PATH = "drools/rules.drl";
|
private static final String RULES_PATH = "drools/prod_rules_new.drl";
|
||||||
private static final String RULES = loadFromClassPath(RULES_PATH);
|
private static final String RULES = loadFromClassPath(RULES_PATH);
|
||||||
private static final String VERTEBRATE = "vertebrate";
|
private static final String VERTEBRATE = "vertebrate";
|
||||||
private static final String ADDRESS = "CBI_address";
|
private static final String ADDRESS = "CBI_address";
|
||||||
@ -797,7 +797,7 @@ public class RulesTest {
|
|||||||
|
|
||||||
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
|
URL resource = ResourceLoader.class.getClassLoader().getResource(path);
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
throw new IllegalArgumentException("could not load classpath resource: drools/rules.drl");
|
throw new IllegalArgumentException("could not load classpath resource: drools/prod_rules_new.drl");
|
||||||
}
|
}
|
||||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@ -814,7 +814,7 @@ public class RulesTest {
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
|
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
|
||||||
@ComponentScan(excludeFilters={@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE, value=StorageAutoConfiguration.class)})
|
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = StorageAutoConfiguration.class)})
|
||||||
public static class RulesTestConfiguration {
|
public static class RulesTestConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@ -0,0 +1,78 @@
|
|||||||
|
package com.iqser.red.service.redaction.v1.server.document.graph;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.FilterType;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import com.iqser.red.service.redaction.v1.server.AbstractRedactionIntegrationTest;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.Application;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.FileSystemBackedStorageService;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.adapter.ImageServiceResponseAdapter;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.PdfSegmentationService;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory.DocumentGraphFactory;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
|
import com.iqser.red.storage.commons.StorageAutoConfiguration;
|
||||||
|
import com.iqser.red.storage.commons.service.StorageService;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
@Import(BuildDocumentGraphIntegrationTest.TestConfiguration.class)
|
||||||
|
public class BuildDocumentGraphIntegrationTest extends AbstractRedactionIntegrationTest {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
|
||||||
|
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = StorageAutoConfiguration.class)})
|
||||||
|
public static class TestConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
public StorageService inmemoryStorage() {
|
||||||
|
|
||||||
|
return new FileSystemBackedStorageService();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PdfSegmentationService segmentationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ImageServiceResponseAdapter imageServiceResponseAdapter;
|
||||||
|
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
protected DocumentGraph buildGraph(String filename) {
|
||||||
|
|
||||||
|
if (filename.equals("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06")) {
|
||||||
|
prepareStorage(filename + ".pdf", "files/cv_service_empty_response.json", filename + ".IMAGE_INFO.json");
|
||||||
|
} else {
|
||||||
|
uploadFileToStorage(filename + ".pdf");
|
||||||
|
}
|
||||||
|
ClassPathResource fileResource = new ClassPathResource(filename + ".pdf");
|
||||||
|
|
||||||
|
try (InputStream inputStream = fileResource.getInputStream()) {
|
||||||
|
Map<Integer, List<ClassifiedImage>> pdfImages = imageServiceResponseAdapter.convertImages(TEST_DOSSIER_ID, TEST_FILE_ID);
|
||||||
|
Document classifiedDoc = segmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, inputStream, pdfImages);
|
||||||
|
return DocumentGraphFactory.buildDocumentGraph(classifiedDoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,62 +0,0 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.document.graph;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.AbstractTestWithDictionaries;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.adapter.ImageServiceResponseAdapter;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.PdfSegmentationService;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.factory.DocumentGraphFactory;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
public class BuildDocumentGraphTest extends AbstractTestWithDictionaries {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private DocumentGraphFactory documentGraphFactory;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PdfSegmentationService segmentationService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ImageServiceResponseAdapter imageServiceResponseAdapter;
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void buildMetolachlor() {
|
|
||||||
|
|
||||||
DocumentGraph documentGraph = buildGraph("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06");
|
|
||||||
assertEquals(221, documentGraph.getPages().size());
|
|
||||||
assertEquals(220, documentGraph.getPages().stream().filter(page -> page.getHeader().hasText()).count());
|
|
||||||
assertEquals(0, documentGraph.getPages().stream().filter(page -> page.getFooter().hasText()).count());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
protected DocumentGraph buildGraph(String filename) {
|
|
||||||
|
|
||||||
if (filename.equals("files/Metolachlor/S-Metolachlor_RAR_01_Volume_1_2018-09-06")) {
|
|
||||||
prepareStorage(filename + ".pdf", "files/cv_service_empty_response.json", filename + ".IMAGE_INFO.json");
|
|
||||||
} else {
|
|
||||||
prepareStorage(filename + ".pdf");
|
|
||||||
}
|
|
||||||
ClassPathResource fileResource = new ClassPathResource(filename + ".pdf");
|
|
||||||
|
|
||||||
try (InputStream inputStream = fileResource.getInputStream()) {
|
|
||||||
Map<Integer, List<ClassifiedImage>> pdfImages = imageServiceResponseAdapter.convertImages(TEST_DOSSIER_ID, TEST_FILE_ID);
|
|
||||||
Document classifiedDoc = segmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, inputStream, pdfImages);
|
|
||||||
return documentGraphFactory.buildDocumentGraph(classifiedDoc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -10,9 +10,9 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.TableOfContents;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeadlineNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.HeadlineNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
||||||
@ -24,7 +24,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.te
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
||||||
|
|
||||||
public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
public class DocumentGraphEntityInsertionIntegrationTest extends BuildDocumentGraphIntegrationTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private EntityCreationService entityCreationService;
|
private EntityCreationService entityCreationService;
|
||||||
@ -51,7 +51,7 @@ public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
|||||||
|
|
||||||
Boundary boundary = new Boundary(start, start + searchTerm.length());
|
Boundary boundary = new Boundary(start, start + searchTerm.length());
|
||||||
RedactionEntity redactionEntity = RedactionEntity.initialEntityNode(boundary, "123", EntityType.ENTITY);
|
RedactionEntity redactionEntity = RedactionEntity.initialEntityNode(boundary, "123", EntityType.ENTITY);
|
||||||
entityCreationService.addEntityToGraph(redactionEntity, documentGraph.getTableOfContents());
|
entityCreationService.addEntityToGraph(redactionEntity, documentGraph);
|
||||||
return redactionEntity;
|
return redactionEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
|||||||
assertEquals(searchTerm, redactionEntity.getValue());
|
assertEquals(searchTerm, redactionEntity.getValue());
|
||||||
assertEquals("Rule 5: Do not redact genitive CBI_authors (Entries based on Dict) ",
|
assertEquals("Rule 5: Do not redact genitive CBI_authors (Entries based on Dict) ",
|
||||||
redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
||||||
assertEquals(2, redactionEntity.getIntersectingNodes().size());
|
assertEquals(3, redactionEntity.getIntersectingNodes().size());
|
||||||
assertEquals(5, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
assertEquals(5, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
||||||
assertInstanceOf(ParagraphNode.class, redactionEntity.getDeepestFullyContainingNode());
|
assertInstanceOf(ParagraphNode.class, redactionEntity.getDeepestFullyContainingNode());
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
|||||||
assertEquals(" Purity Hint", redactionEntity.getTextAfter());
|
assertEquals(" Purity Hint", redactionEntity.getTextAfter());
|
||||||
assertEquals(searchTerm, redactionEntity.getValue());
|
assertEquals(searchTerm, redactionEntity.getValue());
|
||||||
assertEquals("Rule 39: Purity Hint ", redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
assertEquals("Rule 39: Purity Hint ", redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
||||||
assertEquals(2, redactionEntity.getIntersectingNodes().size());
|
assertEquals(3, redactionEntity.getIntersectingNodes().size());
|
||||||
assertEquals(6, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
assertEquals(6, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
||||||
assertInstanceOf(HeadlineNode.class, redactionEntity.getDeepestFullyContainingNode());
|
assertInstanceOf(HeadlineNode.class, redactionEntity.getDeepestFullyContainingNode());
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
|||||||
assertEquals("", redactionEntity.getTextAfter());
|
assertEquals("", redactionEntity.getTextAfter());
|
||||||
assertEquals(searchTerm, redactionEntity.getValue());
|
assertEquals(searchTerm, redactionEntity.getValue());
|
||||||
assertEquals("Rule 6-11 (Authors Table) ", redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
assertEquals("Rule 6-11 (Authors Table) ", redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
||||||
assertEquals(3, redactionEntity.getIntersectingNodes().size());
|
assertEquals(5, redactionEntity.getIntersectingNodes().size());
|
||||||
assertEquals(15, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
assertEquals(15, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
||||||
assertInstanceOf(TableCellNode.class, redactionEntity.getDeepestFullyContainingNode());
|
assertInstanceOf(TableCellNode.class, redactionEntity.getDeepestFullyContainingNode());
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
|||||||
assertEquals("1.1.4 Evaluations carried out under other regulatory contexts ",
|
assertEquals("1.1.4 Evaluations carried out under other regulatory contexts ",
|
||||||
redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
||||||
assertEquals(searchTerm, redactionEntity.getValue());
|
assertEquals(searchTerm, redactionEntity.getValue());
|
||||||
assertEquals(2, redactionEntity.getIntersectingNodes().size());
|
assertEquals(3, redactionEntity.getIntersectingNodes().size());
|
||||||
assertEquals(5, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
assertEquals(5, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
||||||
assertTrue(redactionEntity.getPages().stream().allMatch(pageNode -> pageNode.getNumber() == 10));
|
assertTrue(redactionEntity.getPages().stream().allMatch(pageNode -> pageNode.getNumber() == 10));
|
||||||
assertInstanceOf(ParagraphNode.class, redactionEntity.getDeepestFullyContainingNode());
|
assertInstanceOf(ParagraphNode.class, redactionEntity.getDeepestFullyContainingNode());
|
||||||
@ -210,14 +210,14 @@ public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
|||||||
|
|
||||||
Boundary boundary = new Boundary(start, start + searchTerm.length());
|
Boundary boundary = new Boundary(start, start + searchTerm.length());
|
||||||
RedactionEntity redactionEntity = RedactionEntity.initialEntityNode(boundary, "123", EntityType.ENTITY);
|
RedactionEntity redactionEntity = RedactionEntity.initialEntityNode(boundary, "123", EntityType.ENTITY);
|
||||||
entityCreationService.addEntityToGraph(redactionEntity, documentGraph.getTableOfContents());
|
entityCreationService.addEntityToGraph(redactionEntity, documentGraph);
|
||||||
|
|
||||||
assertEquals("2.6.1 Summary of ", redactionEntity.getTextBefore());
|
assertEquals("2.6.1 Summary of ", redactionEntity.getTextBefore());
|
||||||
assertEquals(" and excretion in", redactionEntity.getTextAfter());
|
assertEquals(" and excretion in", redactionEntity.getTextAfter());
|
||||||
assertEquals("2.6.1 Summary of absorption, distribution, metabolism and excretion in mammals ",
|
assertEquals("2.6.1 Summary of absorption, distribution, metabolism and excretion in mammals ",
|
||||||
redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
||||||
assertEquals(searchTerm, redactionEntity.getValue());
|
assertEquals(searchTerm, redactionEntity.getValue());
|
||||||
assertEquals(2, redactionEntity.getIntersectingNodes().size());
|
assertEquals(3, redactionEntity.getIntersectingNodes().size());
|
||||||
assertEquals(4, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
assertEquals(4, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
||||||
assertTrue(redactionEntity.getPages().stream().allMatch(pageNode -> pageNode.getNumber() == 33));
|
assertTrue(redactionEntity.getPages().stream().allMatch(pageNode -> pageNode.getNumber() == 33));
|
||||||
assertInstanceOf(HeadlineNode.class, redactionEntity.getDeepestFullyContainingNode());
|
assertInstanceOf(HeadlineNode.class, redactionEntity.getDeepestFullyContainingNode());
|
||||||
@ -236,8 +236,8 @@ public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
|||||||
assertEquals("2-[(2-(1-hydroxy-ethyl)-6methyl-phenyl-amino]propan-1-ol (", redactionEntity.getTextBefore());
|
assertEquals("2-[(2-(1-hydroxy-ethyl)-6methyl-phenyl-amino]propan-1-ol (", redactionEntity.getTextBefore());
|
||||||
assertEquals(" of metabolite of", redactionEntity.getTextAfter());
|
assertEquals(" of metabolite of", redactionEntity.getTextAfter());
|
||||||
assertEquals(searchTerm, redactionEntity.getValue());
|
assertEquals(searchTerm, redactionEntity.getValue());
|
||||||
assertEquals(3, redactionEntity.getIntersectingNodes().size());
|
assertEquals(4, redactionEntity.getIntersectingNodes().size());
|
||||||
assertEquals("2.7.2 Summary of metabolism, distribution and expression of residues in plants, poultry, lactating ruminants, pigs and fish ",
|
assertEquals("Table 2.7-1: List of substances and metabolites and related structural formula ",
|
||||||
redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
redactionEntity.getDeepestFullyContainingNode().getHeadline().buildTextBlock().getSearchText());
|
||||||
assertTrue(redactionEntity.getPages().stream().allMatch(pageNode -> pageNode.getNumber() == 54));
|
assertTrue(redactionEntity.getPages().stream().allMatch(pageNode -> pageNode.getNumber() == 54));
|
||||||
assertEquals(26, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
assertEquals(26, redactionEntity.getDeepestFullyContainingNode().getNumberOnPage());
|
||||||
@ -248,11 +248,12 @@ public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// this might fail, if an entity with the same name exists twice in the deepest containing node
|
||||||
private static void assertSameOffsetInAllIntersectingNodes(String searchTerm, RedactionEntity redactionEntity) {
|
private static void assertSameOffsetInAllIntersectingNodes(String searchTerm, RedactionEntity redactionEntity) {
|
||||||
|
|
||||||
List<Integer> paragraphStart = redactionEntity.getIntersectingNodes().stream()//
|
List<Integer> paragraphStart = redactionEntity.getIntersectingNodes().stream()//
|
||||||
.map(SemanticNode::buildTextBlock)//
|
.map(SemanticNode::buildTextBlock)//
|
||||||
.map(textBlock -> textBlock.indexOf(searchTerm))//
|
.map(textBlock -> textBlock.indexOf(searchTerm, redactionEntity.getDeepestFullyContainingNode().getBoundary().start()))//
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
paragraphStart.forEach(nodeStart -> assertEquals(redactionEntity.getBoundary().start(), nodeStart));
|
paragraphStart.forEach(nodeStart -> assertEquals(redactionEntity.getBoundary().start(), nodeStart));
|
||||||
@ -267,7 +268,7 @@ public class DocumentGraphEntityInsertionTest extends BuildDocumentGraphTest {
|
|||||||
|
|
||||||
Boundary boundary = new Boundary(start, start + searchTerm.length());
|
Boundary boundary = new Boundary(start, start + searchTerm.length());
|
||||||
RedactionEntity redactionEntity = RedactionEntity.initialEntityNode(boundary, "123", EntityType.ENTITY);
|
RedactionEntity redactionEntity = RedactionEntity.initialEntityNode(boundary, "123", EntityType.ENTITY);
|
||||||
entityCreationService.addEntityToGraph(redactionEntity, documentGraph.getTableOfContents());
|
entityCreationService.addEntityToGraph(redactionEntity, documentGraph);
|
||||||
PageNode pageNode = documentGraph.getPages().stream().filter(page -> page.getNumber() == pageNumber).findFirst().orElseThrow();
|
PageNode pageNode = documentGraph.getPages().stream().filter(page -> page.getNumber() == pageNumber).findFirst().orElseThrow();
|
||||||
|
|
||||||
assertEquals(redactionEntity.getValue(), searchTerm);
|
assertEquals(redactionEntity.getValue(), searchTerm);
|
||||||
@ -1,5 +1,8 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.document.graph;
|
package com.iqser.red.service.redaction.v1.server.document.graph;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.wildfly.common.Assert.assertTrue;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicPositionBlockData;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.AtomicPositionBlockData;
|
||||||
@ -9,12 +12,15 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.Pag
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.TableOfContentsData;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.TableOfContentsData;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentDataMapper;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentDataMapper;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentGraphMapper;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.DocumentGraphMapper;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.PropertiesMapper;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.NodeType;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
|
||||||
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
|
import com.iqser.red.service.redaction.v1.server.multitenancy.TenantContext;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
public class DocumentGraphMappingTest extends BuildDocumentGraphTest {
|
public class DocumentGraphMappingIntegrationTest extends BuildDocumentGraphIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@ -45,8 +51,37 @@ public class DocumentGraphMappingTest extends BuildDocumentGraphTest {
|
|||||||
.build();
|
.build();
|
||||||
DocumentGraph newDocumentGraph = DocumentGraphMapper.toDocumentGraph(documentData2);
|
DocumentGraph newDocumentGraph = DocumentGraphMapper.toDocumentGraph(documentData2);
|
||||||
|
|
||||||
assert document.toString().equals(newDocumentGraph.toString());
|
assertTrue(allTablesHavePositiveNumberOfRowsAndColumns(document));
|
||||||
assert document.getTableOfContents().toString().equals(newDocumentGraph.getTableOfContents().toString());
|
assertTrue(allTablesHavePositiveNumberOfRowsAndColumns(documentData));
|
||||||
|
assertTrue(allTablesHavePositiveNumberOfRowsAndColumns(documentData2));
|
||||||
|
assertTrue(allTablesHavePositiveNumberOfRowsAndColumns(newDocumentGraph));
|
||||||
|
|
||||||
|
assertEquals(document.toString(), newDocumentGraph.toString());
|
||||||
|
assertEquals(document.getTableOfContents().toString(), newDocumentGraph.getTableOfContents().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean allTablesHavePositiveNumberOfRowsAndColumns(DocumentGraph documentGraph) {
|
||||||
|
|
||||||
|
return documentGraph.streamAllSubNodes()
|
||||||
|
.filter(semanticNode -> semanticNode instanceof TableNode)
|
||||||
|
.map(semanticNode -> (TableNode) semanticNode)
|
||||||
|
.allMatch(tableNode -> tableNode.getNumberOfCols() > 0 && tableNode.getNumberOfRows() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean allTablesHavePositiveNumberOfRowsAndColumns(DocumentData documentData) {
|
||||||
|
|
||||||
|
return documentData.getTableOfContents()
|
||||||
|
.streamAllEntries()
|
||||||
|
.filter(entryData -> entryData.getType().equals(NodeType.TABLE))
|
||||||
|
.map(TableOfContentsData.EntryData::getProperties)
|
||||||
|
.map(properties -> {
|
||||||
|
var builder = TableNode.builder();
|
||||||
|
PropertiesMapper.parseTableProperties(properties, builder);
|
||||||
|
return builder.build();
|
||||||
|
})
|
||||||
|
.allMatch(tableNode -> tableNode.getNumberOfCols() > 0 && tableNode.getNumberOfRows() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
package com.iqser.red.service.redaction.v1.server.document.graph;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.PageNode;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableCellNode;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.TableNode;
|
||||||
|
|
||||||
|
public class DocumentGraphTableNodeIntegrationTest extends BuildDocumentGraphIntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllTableCellAccessesCorrect() {
|
||||||
|
|
||||||
|
DocumentGraph documentGraph = buildGraph("files/Metolachlor/S-Metolachlor_RAR_02_Volume_2_2018-09-06");
|
||||||
|
PageNode pageFive = documentGraph.getPages().stream().filter(pageNode -> pageNode.getNumber() == 5).findFirst().get();
|
||||||
|
TableNode tableNode = pageFive.getMainBody()
|
||||||
|
.stream()
|
||||||
|
.filter(semanticNode -> semanticNode instanceof TableNode)
|
||||||
|
.map(semanticNode -> (TableNode) semanticNode)
|
||||||
|
.findFirst()
|
||||||
|
.get();
|
||||||
|
|
||||||
|
for (int row = 0; row < tableNode.getNumberOfRows(); row++) {
|
||||||
|
for (int col = 0; col < tableNode.getNumberOfCols(); col++) {
|
||||||
|
TableCellNode tableCellNode = tableNode.getCell(row, col);
|
||||||
|
assertEquals(col, tableCellNode.getCol());
|
||||||
|
assertEquals(row, tableCellNode.getRow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -9,13 +9,13 @@ import org.junit.jupiter.api.Disabled;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentGraph;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.TextBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw;
|
import com.iqser.red.service.redaction.v1.server.visualization.service.PdfDraw;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
public class DocumentGraphVisualizationTest extends BuildDocumentGraphTest {
|
public class DocumentGraphVisualizationIntegrationTest extends BuildDocumentGraphIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@ -31,6 +31,20 @@ public class DocumentGraphVisualizationTest extends BuildDocumentGraphTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
@Disabled
|
||||||
|
public void visualizeMetolachlor2() {
|
||||||
|
|
||||||
|
String filename = "files/Metolachlor/S-Metolachlor_RAR_02_Volume_2_2018-09-06";
|
||||||
|
|
||||||
|
DocumentGraph documentGraph = buildGraph(filename);
|
||||||
|
TextBlock textBlock = documentGraph.buildTextBlock();
|
||||||
|
|
||||||
|
visualizeSemanticNodes(filename, documentGraph, textBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Disabled
|
@Disabled
|
||||||
@ -45,6 +59,20 @@ public class DocumentGraphVisualizationTest extends BuildDocumentGraphTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
@Disabled
|
||||||
|
public void visualizeCraftedDocument() {
|
||||||
|
|
||||||
|
String filename = "files/new/crafted document";
|
||||||
|
|
||||||
|
DocumentGraph documentGraph = buildGraph(filename);
|
||||||
|
TextBlock textBlock = documentGraph.buildTextBlock();
|
||||||
|
|
||||||
|
visualizeSemanticNodes(filename, documentGraph, textBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void visualizeSemanticNodes(String filename, DocumentGraph documentGraph, TextBlock textBlock) throws IOException {
|
private static void visualizeSemanticNodes(String filename, DocumentGraph documentGraph, TextBlock textBlock) throws IOException {
|
||||||
|
|
||||||
var tmpFileName = "/tmp/" + filename.split("/")[2] + "_SEMANTIC_NODES_BBOX.pdf";
|
var tmpFileName = "/tmp/" + filename.split("/")[2] + "_SEMANTIC_NODES_BBOX.pdf";
|
||||||
@ -0,0 +1,264 @@
|
|||||||
|
package com.iqser.red.service.redaction.v1.server.document.graph;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.wildfly.common.Assert.assertFalse;
|
||||||
|
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.kie.api.KieServices;
|
||||||
|
import org.kie.api.builder.KieBuilder;
|
||||||
|
import org.kie.api.builder.KieFileSystem;
|
||||||
|
import org.kie.api.builder.KieModule;
|
||||||
|
import org.kie.api.runtime.KieContainer;
|
||||||
|
import org.kie.api.runtime.KieSession;
|
||||||
|
import org.kie.internal.io.ResourceFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationStatus;
|
||||||
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle;
|
||||||
|
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.ManualResizeRedaction;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionPosition;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.DocumentGraph;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ParagraphNode;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.ManualRedactionApplicationService;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
||||||
|
|
||||||
|
@Import(ManualResizeRedactionIntegrationTest.TestConfiguration.class)
|
||||||
|
public class ManualResizeRedactionIntegrationTest extends BuildDocumentGraphIntegrationTest {
|
||||||
|
|
||||||
|
private static final String RULES = "drools/manual_redaction_rules.drl";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EntityCreationService entityCreationService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ManualRedactionApplicationService manualRedactionApplicationService;
|
||||||
|
|
||||||
|
@Qualifier("kieContainer")
|
||||||
|
@Autowired
|
||||||
|
private KieContainer kieContainer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(BuildDocumentGraphIntegrationTest.TestConfiguration.class)
|
||||||
|
public static class TestConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public KieContainer kieContainer() {
|
||||||
|
|
||||||
|
KieServices kieServices = KieServices.Factory.get();
|
||||||
|
|
||||||
|
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
|
||||||
|
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES));
|
||||||
|
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
|
||||||
|
kieBuilder.buildAll();
|
||||||
|
KieModule kieModule = kieBuilder.getKieModule();
|
||||||
|
|
||||||
|
return kieServices.newKieContainer(kieModule.getReleaseId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void manualResizeRedactionTest() {
|
||||||
|
|
||||||
|
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
|
||||||
|
Set<RedactionEntity> entities = entityCreationService.byKeyword("David Ksenia", "CBI_author", EntityType.ENTITY, documentGraph);
|
||||||
|
Set<RedactionEntity> biggerEntities = entityCreationService.byKeyword("David Ksenia Max Mustermann", "CBI_author", EntityType.ENTITY, documentGraph);
|
||||||
|
|
||||||
|
RedactionEntity entity = entities.stream().filter(e -> e.getPages().stream().anyMatch(p -> p.getNumber() == 1)).findFirst().get();
|
||||||
|
RedactionEntity biggerEntity = biggerEntities.stream().filter(e -> e.getPages().stream().anyMatch(p -> p.getNumber() == 1)).findFirst().get();
|
||||||
|
|
||||||
|
String initialId = entity.getRedactionPositionsPerPage().get(0).getId();
|
||||||
|
ManualResizeRedaction manualResizeRedaction = ManualResizeRedaction.builder()
|
||||||
|
.annotationId(initialId)
|
||||||
|
.value(biggerEntity.getValue())
|
||||||
|
.positions(toAnnotationRectangles(biggerEntity.getRedactionPositionsPerPage().get(0)))
|
||||||
|
.status(AnnotationStatus.APPROVED)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
KieSession kieSession = kieContainer.newKieSession();
|
||||||
|
kieSession.setGlobal("manualRedactionApplicationService", manualRedactionApplicationService);
|
||||||
|
kieSession.insert(entity);
|
||||||
|
kieSession.insert(manualResizeRedaction);
|
||||||
|
kieSession.fireAllRules();
|
||||||
|
kieSession.dispose();
|
||||||
|
|
||||||
|
assertEquals(biggerEntity.getBoundary(), entity.getBoundary());
|
||||||
|
assertEquals(biggerEntity.getDeepestFullyContainingNode(), entity.getDeepestFullyContainingNode());
|
||||||
|
assertEquals(biggerEntity.getIntersectingNodes(), entity.getIntersectingNodes());
|
||||||
|
assertEquals(biggerEntity.getPages(), entity.getPages());
|
||||||
|
assertEquals(biggerEntity.getValue(), entity.getValue());
|
||||||
|
assertEquals(initialId, entity.getRedactionPositionsPerPage().get(0).getId());
|
||||||
|
assertRectanglesAlmostEqual(biggerEntity.getRedactionPositionsPerPage().get(0).getRectanglePerLine(), entity.getRedactionPositionsPerPage().get(0).getRectanglePerLine());
|
||||||
|
assertTrue(entity.isResized());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void manualForceRedactionTest() {
|
||||||
|
|
||||||
|
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
|
||||||
|
Set<RedactionEntity> entities = entityCreationService.byKeyword("David Ksenia", "CBI_author", EntityType.ENTITY, documentGraph);
|
||||||
|
|
||||||
|
RedactionEntity entity = entities.stream().filter(e -> e.getPages().stream().anyMatch(p -> p.getNumber() == 1)).findFirst().get();
|
||||||
|
|
||||||
|
String initialId = entity.getRedactionPositionsPerPage().get(0).getId();
|
||||||
|
ManualForceRedaction manualForceRedaction = ManualForceRedaction.builder()
|
||||||
|
.annotationId(initialId)
|
||||||
|
.status(AnnotationStatus.APPROVED)
|
||||||
|
.requestDate(OffsetDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
KieSession kieSession = kieContainer.newKieSession();
|
||||||
|
kieSession.setGlobal("manualRedactionApplicationService", manualRedactionApplicationService);
|
||||||
|
kieSession.insert(entity);
|
||||||
|
kieSession.insert(manualForceRedaction);
|
||||||
|
kieSession.fireAllRules();
|
||||||
|
kieSession.dispose();
|
||||||
|
|
||||||
|
assertEquals(ParagraphNode.class, entity.getDeepestFullyContainingNode().getClass());
|
||||||
|
assertFalse(entity.getIntersectingNodes().isEmpty());
|
||||||
|
assertEquals(1, entity.getPages().size());
|
||||||
|
assertEquals("David Ksenia", entity.getValue());
|
||||||
|
assertEquals(initialId, entity.getRedactionPositionsPerPage().get(0).getId());
|
||||||
|
assertFalse(entity.isRemoved());
|
||||||
|
assertTrue(entity.isSkipRemoveEntitiesContainedInLarger());
|
||||||
|
assertTrue(entity.isRedaction());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void manualIDRemovalTest() {
|
||||||
|
|
||||||
|
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
|
||||||
|
Set<RedactionEntity> entities = entityCreationService.byKeyword("David Ksenia", "CBI_author", EntityType.ENTITY, documentGraph);
|
||||||
|
|
||||||
|
RedactionEntity entity = entities.stream().filter(e -> e.getPages().stream().anyMatch(p -> p.getNumber() == 1)).findFirst().get();
|
||||||
|
|
||||||
|
String initialId = entity.getRedactionPositionsPerPage().get(0).getId();
|
||||||
|
IdRemoval idRemoval = IdRemoval.builder().annotationId(initialId).status(AnnotationStatus.APPROVED).requestDate(OffsetDateTime.now()).build();
|
||||||
|
|
||||||
|
KieSession kieSession = kieContainer.newKieSession();
|
||||||
|
kieSession.setGlobal("manualRedactionApplicationService", manualRedactionApplicationService);
|
||||||
|
kieSession.insert(entity);
|
||||||
|
kieSession.insert(idRemoval);
|
||||||
|
kieSession.fireAllRules();
|
||||||
|
kieSession.dispose();
|
||||||
|
|
||||||
|
assertNull(entity.getDeepestFullyContainingNode());
|
||||||
|
assertTrue(entity.getIntersectingNodes().isEmpty());
|
||||||
|
assertTrue(entity.getPages().isEmpty());
|
||||||
|
assertEquals("David Ksenia", entity.getValue());
|
||||||
|
assertEquals(initialId, entity.getRedactionPositionsPerPage().get(0).getId());
|
||||||
|
assertTrue(entity.isRemoved());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void manualIDRemovalButAlsoForceRedactionTest() {
|
||||||
|
|
||||||
|
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
|
||||||
|
Set<RedactionEntity> entities = entityCreationService.byKeyword("David Ksenia", "CBI_author", EntityType.ENTITY, documentGraph);
|
||||||
|
|
||||||
|
RedactionEntity entity = entities.stream().filter(e -> e.getPages().stream().anyMatch(p -> p.getNumber() == 1)).findFirst().get();
|
||||||
|
|
||||||
|
String initialId = entity.getRedactionPositionsPerPage().get(0).getId();
|
||||||
|
IdRemoval idRemoval = IdRemoval.builder().annotationId(initialId).status(AnnotationStatus.APPROVED).requestDate(OffsetDateTime.now()).build();
|
||||||
|
ManualForceRedaction manualForceRedaction = ManualForceRedaction.builder()
|
||||||
|
.annotationId(initialId)
|
||||||
|
.status(AnnotationStatus.APPROVED)
|
||||||
|
.requestDate(OffsetDateTime.now())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
KieSession kieSession = kieContainer.newKieSession();
|
||||||
|
kieSession.setGlobal("manualRedactionApplicationService", manualRedactionApplicationService);
|
||||||
|
kieSession.insert(entity);
|
||||||
|
kieSession.insert(idRemoval);
|
||||||
|
kieSession.insert(manualForceRedaction);
|
||||||
|
kieSession.fireAllRules();
|
||||||
|
kieSession.dispose();
|
||||||
|
|
||||||
|
assertEquals(ParagraphNode.class, entity.getDeepestFullyContainingNode().getClass());
|
||||||
|
assertFalse(entity.getIntersectingNodes().isEmpty());
|
||||||
|
assertEquals(1, entity.getPages().size());
|
||||||
|
assertEquals("David Ksenia", entity.getValue());
|
||||||
|
assertEquals(initialId, entity.getRedactionPositionsPerPage().get(0).getId());
|
||||||
|
assertFalse(entity.isRemoved());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void manualIDRemovalNotApprovedTest() {
|
||||||
|
|
||||||
|
DocumentGraph documentGraph = buildGraph("files/new/crafted document");
|
||||||
|
Set<RedactionEntity> entities = entityCreationService.byKeyword("David Ksenia", "CBI_author", EntityType.ENTITY, documentGraph);
|
||||||
|
|
||||||
|
RedactionEntity entity = entities.stream().filter(e -> e.getPages().stream().anyMatch(p -> p.getNumber() == 1)).findFirst().get();
|
||||||
|
|
||||||
|
String initialId = entity.getRedactionPositionsPerPage().get(0).getId();
|
||||||
|
IdRemoval idRemoval = IdRemoval.builder().annotationId(initialId).status(AnnotationStatus.REQUESTED).build();
|
||||||
|
|
||||||
|
KieSession kieSession = kieContainer.newKieSession();
|
||||||
|
kieSession.setGlobal("manualRedactionApplicationService", manualRedactionApplicationService);
|
||||||
|
kieSession.insert(entity);
|
||||||
|
kieSession.insert(idRemoval);
|
||||||
|
kieSession.fireAllRules();
|
||||||
|
kieSession.dispose();
|
||||||
|
|
||||||
|
assertEquals(ParagraphNode.class, entity.getDeepestFullyContainingNode().getClass());
|
||||||
|
assertFalse(entity.getIntersectingNodes().isEmpty());
|
||||||
|
assertEquals(1, entity.getPages().size());
|
||||||
|
assertEquals("David Ksenia", entity.getValue());
|
||||||
|
assertEquals(initialId, entity.getRedactionPositionsPerPage().get(0).getId());
|
||||||
|
assertFalse(entity.isRemoved());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void assertRectanglesAlmostEqual(Collection<Rectangle2D> rects1, Collection<Rectangle2D> rects2) {
|
||||||
|
|
||||||
|
if (rects1.stream().allMatch(rect1 -> rects2.stream().anyMatch(rect2 -> rectanglesAlmostEqual(rect1, rect2)))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// use this for nice formatting of error message
|
||||||
|
assertEquals(rects1, rects2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean rectanglesAlmostEqual(Rectangle2D r1, Rectangle2D r2) {
|
||||||
|
|
||||||
|
double tolerance = 1e-1;
|
||||||
|
return Math.abs(r1.getX() - r2.getX()) < tolerance &&//
|
||||||
|
Math.abs(r1.getY() - r2.getY()) < tolerance &&//
|
||||||
|
Math.abs(r1.getWidth() - r2.getWidth()) < tolerance &&//
|
||||||
|
Math.abs(r1.getHeight() - r2.getHeight()) < tolerance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static List<Rectangle> toAnnotationRectangles(RedactionPosition redactionPositions) {
|
||||||
|
|
||||||
|
return redactionPositions.getRectanglePerLine().stream().map(rectangle2D -> toAnnotationRectangle(rectangle2D, redactionPositions.getPageNode().getNumber())).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Rectangle toAnnotationRectangle(Rectangle2D rectangle2D, Integer number) {
|
||||||
|
|
||||||
|
return new Rectangle((float) rectangle2D.getMaxX(), (float) rectangle2D.getMaxY(), (float) rectangle2D.getWidth(), (float) rectangle2D.getHeight(), number);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package com.iqser.red.service.redaction.v1.server.document.graph;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
public class ManualResizeRedactionTest extends BuildDocumentGraphTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void manualResizeRedactionTest() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -103,7 +103,7 @@ public class LiveDataIntegrationTest {
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
|
@EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
|
||||||
@ComponentScan(excludeFilters={@ComponentScan.Filter(type= FilterType.ASSIGNABLE_TYPE, value=StorageAutoConfiguration.class)})
|
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = StorageAutoConfiguration.class)})
|
||||||
public static class RedactionIntegrationTestConfiguration {
|
public static class RedactionIntegrationTestConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ -124,7 +124,7 @@ public class LiveDataIntegrationTest {
|
|||||||
when(dictionaryClient.getVersion(anyString())).thenReturn(1L);
|
when(dictionaryClient.getVersion(anyString())).thenReturn(1L);
|
||||||
when(dictionaryClient.getVersionForDossier(anyString())).thenReturn(1L);
|
when(dictionaryClient.getVersionForDossier(anyString())).thenReturn(1L);
|
||||||
|
|
||||||
var rules = IOUtils.toString(new ClassPathResource(BASE_DIR + EFSA_SANITISATION_GFL_V1 + "rules.drl").getInputStream());
|
var rules = IOUtils.toString(new ClassPathResource(BASE_DIR + EFSA_SANITISATION_GFL_V1 + "prod_rules_new.drl").getInputStream());
|
||||||
when(rulesClient.getRules(any())).thenReturn(JSONPrimitive.of(rules));
|
when(rulesClient.getRules(any())).thenReturn(JSONPrimitive.of(rules));
|
||||||
|
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|||||||
@ -40,7 +40,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.ad
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.Document;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.image.ClassifiedImage;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Cell;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.Table;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.model.table.TablePageBlock;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.BlockificationService;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.BlockificationService;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.PdfSegmentationService;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.PdfSegmentationService;
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.RulingCleaningService;
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.classification.service.RulingCleaningService;
|
||||||
@ -147,7 +147,7 @@ public class PdfSegmentationServiceTest {
|
|||||||
|
|
||||||
Document document = pdfSegmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, pdfFileResource.getInputStream(), null);
|
Document document = pdfSegmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, pdfFileResource.getInputStream(), null);
|
||||||
assertThat(document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList())).isNotEmpty();
|
assertThat(document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList())).isNotEmpty();
|
||||||
Table table = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(0);
|
TablePageBlock table = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(0);
|
||||||
assertThat(table.getColCount()).isEqualTo(6);
|
assertThat(table.getColCount()).isEqualTo(6);
|
||||||
assertThat(table.getRowCount()).isEqualTo(13);
|
assertThat(table.getRowCount()).isEqualTo(13);
|
||||||
assertThat(table.getRows().stream().mapToInt(List::size).sum()).isEqualTo(6 * 13);
|
assertThat(table.getRows().stream().mapToInt(List::size).sum()).isEqualTo(6 * 13);
|
||||||
@ -162,10 +162,10 @@ public class PdfSegmentationServiceTest {
|
|||||||
|
|
||||||
Document document = pdfSegmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, pdfFileResource.getInputStream(), null);
|
Document document = pdfSegmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, pdfFileResource.getInputStream(), null);
|
||||||
assertThat(document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList())).isNotEmpty();
|
assertThat(document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList())).isNotEmpty();
|
||||||
Table firstTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(0);
|
TablePageBlock firstTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(0);
|
||||||
assertThat(firstTable.getColCount()).isEqualTo(8);
|
assertThat(firstTable.getColCount()).isEqualTo(8);
|
||||||
assertThat(firstTable.getRowCount()).isEqualTo(1);
|
assertThat(firstTable.getRowCount()).isEqualTo(1);
|
||||||
Table secondTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(1);
|
TablePageBlock secondTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(1);
|
||||||
assertThat(secondTable.getColCount()).isEqualTo(8);
|
assertThat(secondTable.getColCount()).isEqualTo(8);
|
||||||
assertThat(secondTable.getRowCount()).isEqualTo(2);
|
assertThat(secondTable.getRowCount()).isEqualTo(2);
|
||||||
List<List<Cell>> firstTableHeaderCells = firstTable.getRows().get(0).stream().map(Collections::singletonList).collect(Collectors.toList());
|
List<List<Cell>> firstTableHeaderCells = firstTable.getRows().get(0).stream().map(Collections::singletonList).collect(Collectors.toList());
|
||||||
@ -181,10 +181,10 @@ public class PdfSegmentationServiceTest {
|
|||||||
|
|
||||||
Document document = pdfSegmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, pdfFileResource.getInputStream(), null);
|
Document document = pdfSegmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, pdfFileResource.getInputStream(), null);
|
||||||
assertThat(document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList())).isNotEmpty();
|
assertThat(document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList())).isNotEmpty();
|
||||||
Table firstTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(0);
|
TablePageBlock firstTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(0);
|
||||||
assertThat(firstTable.getColCount()).isEqualTo(9);
|
assertThat(firstTable.getColCount()).isEqualTo(9);
|
||||||
assertThat(firstTable.getRowCount()).isEqualTo(5);
|
assertThat(firstTable.getRowCount()).isEqualTo(5);
|
||||||
Table secondTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(1);
|
TablePageBlock secondTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(1);
|
||||||
assertThat(secondTable.getColCount()).isEqualTo(9);
|
assertThat(secondTable.getColCount()).isEqualTo(9);
|
||||||
assertThat(secondTable.getRowCount()).isEqualTo(6);
|
assertThat(secondTable.getRowCount()).isEqualTo(6);
|
||||||
List<List<Cell>> firstTableHeaderCells = firstTable.getRows().get(firstTable.getRowCount() - 1).stream().map(Cell::getHeaderCells).collect(Collectors.toList());
|
List<List<Cell>> firstTableHeaderCells = firstTable.getRows().get(firstTable.getRowCount() - 1).stream().map(Cell::getHeaderCells).collect(Collectors.toList());
|
||||||
@ -200,10 +200,10 @@ public class PdfSegmentationServiceTest {
|
|||||||
|
|
||||||
Document document = pdfSegmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, pdfFileResource.getInputStream(), null);
|
Document document = pdfSegmentationService.parseDocument(TEST_DOSSIER_ID, TEST_FILE_ID, pdfFileResource.getInputStream(), null);
|
||||||
assertThat(document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList())).isNotEmpty();
|
assertThat(document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList())).isNotEmpty();
|
||||||
Table firstTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(0);
|
TablePageBlock firstTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(0);
|
||||||
assertThat(firstTable.getColCount()).isEqualTo(8);
|
assertThat(firstTable.getColCount()).isEqualTo(8);
|
||||||
assertThat(firstTable.getRowCount()).isEqualTo(1);
|
assertThat(firstTable.getRowCount()).isEqualTo(1);
|
||||||
Table secondTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(1);
|
TablePageBlock secondTable = document.getSections().stream().flatMap(paragraph -> paragraph.getTables().stream()).collect(Collectors.toList()).get(1);
|
||||||
assertThat(secondTable.getColCount()).isEqualTo(8);
|
assertThat(secondTable.getColCount()).isEqualTo(8);
|
||||||
assertThat(secondTable.getRowCount()).isEqualTo(6);
|
assertThat(secondTable.getRowCount()).isEqualTo(6);
|
||||||
List<List<Cell>> firstTableHeaderCells = firstTable.getRows().get(0).stream().map(Collections::singletonList).collect(Collectors.toList());
|
List<List<Cell>> firstTableHeaderCells = firstTable.getRows().get(0).stream().map(Collections::singletonList).collect(Collectors.toList());
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
import java.util.List
|
||||||
|
import com.iqser.red.service.redaction.v1.server.DroolsListBugTest
|
||||||
|
|
||||||
|
rule "bugtest"
|
||||||
|
when
|
||||||
|
then
|
||||||
|
DroolsListBugTest.callInFunction();
|
||||||
|
end
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
package drools
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtils.anyMatch;
|
||||||
|
import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtils.exactMatch;
|
||||||
|
import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.data.mapper.PropertiesMapper.parseImageType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.redaction.utils.Liszt;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.*
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.*
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.*
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.*
|
||||||
|
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
|
||||||
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
|
||||||
|
import java.util.Set
|
||||||
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.services.EntityCreationService;
|
||||||
|
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.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.layoutparsing.document.services.ManualRedactionApplicationService;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionEntity
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.RedactionEntity
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.Boundary
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
import java.util.Collection
|
||||||
|
import java.util.stream.Stream
|
||||||
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtils;
|
||||||
|
|
||||||
|
global DocumentGraph document
|
||||||
|
global ManualRedactionApplicationService manualRedactionApplicationService
|
||||||
|
global Dictionary dictionary
|
||||||
|
|
||||||
|
// --------------------------------------- manual redaction rules -------------------------------------------------------------------
|
||||||
|
|
||||||
|
rule "Apply manual resize redaction"
|
||||||
|
salience 128
|
||||||
|
when
|
||||||
|
$resizeRedaction: ManualResizeRedaction($id: annotationId)
|
||||||
|
$entityToBeResized: RedactionEntity(matchesAnnotationId($id))
|
||||||
|
then
|
||||||
|
manualRedactionApplicationService.resizeEntityAndReinsert($entityToBeResized, $resizeRedaction);
|
||||||
|
retract($resizeRedaction);
|
||||||
|
update($entityToBeResized);
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Apply id removals that are valid and not in forced redactions to Entity"
|
||||||
|
salience 128
|
||||||
|
when
|
||||||
|
IdRemoval(status == AnnotationStatus.APPROVED, !removeFromDictionary, requestDate != null, $id: annotationId)
|
||||||
|
not ManualForceRedaction($id == annotationId, status == AnnotationStatus.APPROVED, requestDate != null)
|
||||||
|
$entityToBeRemoved: RedactionEntity(matchesAnnotationId($id))
|
||||||
|
then
|
||||||
|
$entityToBeRemoved.removeFromGraph();
|
||||||
|
retract($entityToBeRemoved);
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Apply id removals that are valid and not in forced redactions to Image"
|
||||||
|
salience 128
|
||||||
|
when
|
||||||
|
IdRemoval(status == AnnotationStatus.APPROVED, !removeFromDictionary, requestDate != null, $id: annotationId)
|
||||||
|
not ManualForceRedaction($id == annotationId, status == AnnotationStatus.APPROVED, requestDate != null)
|
||||||
|
$imageEntityToBeRemoved: ImageNode($id == id)
|
||||||
|
then
|
||||||
|
$imageEntityToBeRemoved.setIgnored(true);
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Apply force redaction"
|
||||||
|
salience 128
|
||||||
|
when
|
||||||
|
ManualForceRedaction($id: annotationId, status == AnnotationStatus.APPROVED, requestDate != null, $legalBasis: legalBasis)
|
||||||
|
$entityToForce: RedactionEntity(matchesAnnotationId($id))
|
||||||
|
then
|
||||||
|
$entityToForce.setLegalBasis($legalBasis);
|
||||||
|
$entityToForce.setRedaction(true);
|
||||||
|
$entityToForce.setSkipRemoveEntitiesContainedInLarger(true);
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Apply image recategorization"
|
||||||
|
salience 128
|
||||||
|
when
|
||||||
|
ManualImageRecategorization($id: annotationId, status == AnnotationStatus.APPROVED, $imageType: type)
|
||||||
|
$image: ImageNode($id == id)
|
||||||
|
then
|
||||||
|
$image.setImageType(parseImageType($imageType));
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
@ -15,6 +15,7 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.no
|
|||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.*
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.entity.*
|
||||||
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.*
|
import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.textblock.*
|
||||||
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
|
||||||
|
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
|
||||||
import java.util.Set
|
import java.util.Set
|
||||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine
|
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine
|
||||||
@ -180,6 +181,15 @@ rule "add NER Entities of type CBI_author or CBI_address"
|
|||||||
|
|
||||||
// --------------------------------------- CBI rules -------------------------------------------------------------------
|
// --------------------------------------- CBI rules -------------------------------------------------------------------
|
||||||
|
|
||||||
|
rule "0: Expand CBI_author and PII matches with salutation prefix"
|
||||||
|
when
|
||||||
|
$entityToExpand: RedactionEntity((type == "CBI_author" || type == "PII"), anyMatch(textBefore, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*"))
|
||||||
|
then
|
||||||
|
RedactionEntity entity = entityCreationService.byPrefixExpansionRegex($entityToExpand, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*");
|
||||||
|
setFields(entity, 0, null, null, Engine.RULE);
|
||||||
|
insert(entity);
|
||||||
|
end
|
||||||
|
|
||||||
rule "1: Redact CBI Authors (Non vertebrate study)"
|
rule "1: Redact CBI Authors (Non vertebrate study)"
|
||||||
no-loop true
|
no-loop true
|
||||||
when
|
when
|
||||||
@ -438,3 +448,32 @@ rule "18: Redact Phone and Fax by RegEx"
|
|||||||
setFields(entities, 18, "Found by Phone and Fax regex", null, Engine.RULE);
|
setFields(entities, 18, "Found by Phone and Fax regex", null, Engine.RULE);
|
||||||
entities.forEach(entity -> insert(entity));
|
entities.forEach(entity -> insert(entity));
|
||||||
end
|
end
|
||||||
|
|
||||||
|
// --------------------------------------- Image rules -------------------------------------------------------------------
|
||||||
|
|
||||||
|
rule "19: Redact signatures"
|
||||||
|
when
|
||||||
|
$signature: ImageNode(imageType == ImageType.SIGNATURE)
|
||||||
|
then
|
||||||
|
$signature.setRedaction(true);
|
||||||
|
$signature.setRedactionReason("Signature Found");
|
||||||
|
$signature.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)");
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "20: Redact formulas"
|
||||||
|
when
|
||||||
|
$signature: ImageNode(imageType == ImageType.FORMULA)
|
||||||
|
then
|
||||||
|
$signature.setRedaction(true);
|
||||||
|
$signature.setRedactionReason("Formula Found");
|
||||||
|
$signature.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)");
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "20: Redact logos"
|
||||||
|
when
|
||||||
|
$signature: ImageNode(imageType == ImageType.LOGO)
|
||||||
|
then
|
||||||
|
$signature.setRedaction(true);
|
||||||
|
$signature.setRedactionReason("Logo Found");
|
||||||
|
$signature.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)");
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user