diff --git a/redaction-service-v1/redaction-service-api-v1/pom.xml b/redaction-service-v1/redaction-service-api-v1/pom.xml index ede60e67..e91526ed 100644 --- a/redaction-service-v1/redaction-service-api-v1/pom.xml +++ b/redaction-service-v1/redaction-service-api-v1/pom.xml @@ -12,7 +12,7 @@ redaction-service-api-v1 - 2.75.0 + 2.79.0 diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/entity/RedactionEntity.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/entity/RedactionEntity.java index 66268838..a6c6db6d 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/entity/RedactionEntity.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/entity/RedactionEntity.java @@ -49,7 +49,7 @@ public class RedactionEntity { Set engines; Set references; @Builder.Default - Deque matchedRules = new LinkedList<>(); + Deque matchedRules = new LinkedList<>(); String redactionReason; String legalBasis; @@ -120,16 +120,26 @@ public class RedactionEntity { } - public void addMatchedRule(int ruleNumber) { + public void addMatchedRule(String ruleIdentifier) { - matchedRules.add(ruleNumber); + matchedRules.add(ruleIdentifier); } - public int getMatchedRule() { + public int getMatchedRuleUnit() { + + String[] values = getMatchedRule().split("\\."); + if (values.length < 2) { + return -1; + } + return Integer.parseInt(values[1]); + } + + + public String getMatchedRule() { if (matchedRules.isEmpty()) { - return 0; + return ""; } return matchedRules.getLast(); } diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/Image.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/Image.java index 24385379..826c2e25 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/Image.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/Image.java @@ -42,7 +42,7 @@ public class Image implements GenericSemanticNode { @Builder.Default String legalBasis = ""; @Builder.Default - int matchedRule = -1; + String matchedRule = ""; @EqualsAndHashCode.Exclude Page page; diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/SemanticNode.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/SemanticNode.java index 92825150..988fc37f 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/SemanticNode.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/SemanticNode.java @@ -184,11 +184,12 @@ public interface SemanticNode { /** * Checks whether this SemanticNode has any Entity of the provided type. + * Ignores Entity with ignored == true or removed == true. * * @param type string representing the type of entity to check for * @return true, if this SemanticNode has at least one Entity of the provided type */ - default boolean hasActiveEntitiesOfType(String type) { + default boolean hasEntitiesOfType(String type) { return getEntities().stream().filter(RedactionEntity::isActive).anyMatch(redactionEntity -> redactionEntity.getType().equals(type)); } @@ -196,11 +197,12 @@ public interface SemanticNode { /** * Returns a List of Entities in this SemanticNode which are of the provided type such as "CBI_author". + * Ignores Entity with ignored == true or removed == true. * * @param type string representing the type of entities to return * @return List of RedactionEntities of any the type */ - default List getActiveEntitiesOfType(String type) { + default List getEntitiesOfType(String type) { return getEntities().stream().filter(RedactionEntity::isActive).filter(redactionEntity -> redactionEntity.getType().equals(type)).toList(); } @@ -208,11 +210,12 @@ public interface SemanticNode { /** * Returns a List of Entities in this SemanticNode which have any of the provided types such as "CBI_author". + * Ignores Entity with ignored == true or removed == true. * * @param types A list of strings representing the types of entities to return * @return List of RedactionEntities of any provided type */ - default List getActiveEntitiesOfType(List types) { + default List getEntitiesOfType(List types) { return getEntities().stream().filter(RedactionEntity::isActive).filter(redactionEntity -> redactionEntity.isAnyType(types)).toList(); } diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/Table.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/Table.java index e2a3c6b7..4a7c2cbf 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/Table.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/graph/nodes/Table.java @@ -103,11 +103,12 @@ public class Table implements SemanticNode { /** * Streams all entities in this table, that appear in a row, which contains at least one entity with any of the provided types. + * Ignores Entity with ignored == true or removed == true. * * @param types type strings to check whether a row contains an entity like them * @return Stream of all entities in this table, that appear in a row, which contains at least one entity with any of the provided types. */ - public Stream streamEntitiesWhereRowContainsActiveEntitiesOfType(List types) { + public Stream streamEntitiesWhereRowContainsEntitiesOfType(List types) { List rowsWithEntityOfType = getEntities().stream() .filter(RedactionEntity::isActive) @@ -124,11 +125,12 @@ public class Table implements SemanticNode { /** * Streams all entities in this table, that appear in a row, which contains no entity of any of the provided types. + * Ignores Entity with ignored == true or removed == true. * * @param types type strings to check whether a row contains an entity like them * @return Stream of all entities in this table, that appear in a row, which contains at least one entity with any of the provided types. */ - public Stream streamEntitiesWhereRowContainsNoActiveEntitiesOfType(List types) { + public Stream streamEntitiesWhereRowContainsNoEntitiesOfType(List types) { return IntStream.range(0, numberOfRows) .boxed() @@ -272,19 +274,20 @@ public class Table implements SemanticNode { /** * Finds all entities of the provided type, which appear in the same row that the provided entity appears in. + * Ignores Entity with ignored == true or removed == true. * * @param type the type of entities to search for * @param redactionEntity the entity, which appears in the row to search * @return List of all entities of the provided type, which appear in the same row that the provided entity appears in. */ - public List getActiveEntitiesOfTypeInSameRow(String type, RedactionEntity redactionEntity) { + public List getEntitiesOfTypeInSameRow(String type, RedactionEntity redactionEntity) { return redactionEntity.getIntersectingNodes() .stream() .filter(node -> node instanceof TableCell) .map(node -> (TableCell) node) .flatMap(tableCellNode -> streamRow(tableCellNode.getRow())) - .map(cell -> cell.getActiveEntitiesOfType(type)) + .map(cell -> cell.getEntitiesOfType(type)) .flatMap(Collection::stream) .toList(); } diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/services/EntityCreationService.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/services/EntityCreationService.java index d381ae10..fe931572 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/services/EntityCreationService.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/layoutparsing/document/services/EntityCreationService.java @@ -1,22 +1,5 @@ package com.iqser.red.service.redaction.v1.server.layoutparsing.document.services; -import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility.getExpandedEndByRegex; -import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility.getExpandedStartByRegex; -import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.boundaryIsSurroundedBySeparators; - -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -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.Boundary; import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.DocumentTree; @@ -31,9 +14,17 @@ import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.Re import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntities; 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.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility.getExpandedEndByRegex; +import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility.getExpandedStartByRegex; +import static com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils.boundaryIsSurroundedBySeparators; @Slf4j @Service @@ -252,7 +243,7 @@ public class EntityCreationService { 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::getMatchedRuleUnit)).orElse(entitiesToMerge.get(0)); mergedEntity.setRedactionReason(entityWithHigherRuleNumber.getRedactionReason()); mergedEntity.setLegalBasis(entityWithHigherRuleNumber.getLegalBasis()); mergedEntity.setDictionaryEntry(entitiesToMerge.stream().anyMatch(RedactionEntity::isDictionaryEntry)); diff --git a/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/all_rules.drl b/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/all_rules.drl new file mode 100644 index 00000000..973c03c3 --- /dev/null +++ b/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/all_rules.drl @@ -0,0 +1,1268 @@ +package drools + +import static java.lang.String.format; +import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility.anyMatch; +import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility.exactMatch; + +import java.util.List; +import java.util.LinkedList; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.Collection; +import java.util.stream.Stream; +import java.util.Optional; + +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.layoutparsing.document.graph.entity.EntityType; +import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.ImageType; +import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute; +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.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.Boundary; +import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntitiesAdapter; +import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntities; +import com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility; + +global Document document +global EntityCreationService entityCreationService +global ManualRedactionApplicationService manualRedactionApplicationService +global NerEntitiesAdapter nerEntitiesAdapter +global Dictionary dictionary + +//------------------------------------ queries ------------------------------------ + +query "getFileAttributes" + $fileAttribute: FileAttribute() + end + +//------------------------------------ Syngenta specific rules ------------------------------------ + +// Rule unit: SYN.0 +rule "SYN.0.0: Redact if CTL/* or BL/* was found (Non Vertebrate Study)" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(containsString("CTL/") || containsString("BL/")) + then + Stream.concat( + entityCreationService.byString("CTL", "must_redact", EntityType.ENTITY, $section), + entityCreationService.byString("BL", "must_redact", EntityType.ENTITY, $section) + ).forEach(entity -> { + entity.setRedactionReason("hint_only"); + entity.addMatchedRule("SYN.0.0"); + entity.addEngine(Engine.RULE); + insert(entity); + }); + end + + +//------------------------------------ CBI rules ------------------------------------ + +// Rule unit: CBI.3 +rule "CBI.3.0: Redacted because Section contains Vertebrate" + when + $section: Section(!hasTables(), hasEntitiesOfType("vertebrate"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) + then + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("Vertebrate found"); + entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + entity.setRedaction(true); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.3.0"); + entity.addReferences($section.getEntitiesOfType("vertebrate")); + }); + end + +rule "CBI.3.1: Redacted because Table Row contains Vertebrate" + when + $table: Table(hasEntitiesOfType("vertebrate"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) + then + $table.streamEntitiesWhereRowContainsEntitiesOfType(List.of("vertebrate")) + .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("Vertebrate found"); + entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + entity.setRedaction(true); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.3.1"); + entity.addReferences($table.getEntitiesOfTypeInSameRow("vertebrate", entity)); + }); + end + +rule "CBI.3.2: Don't redact because Section doesn't contain Vertebrate" + when + $section: Section(!hasTables(), !hasEntitiesOfType("vertebrate"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) + then + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("No vertebrate found"); + entity.setRedaction(false); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.3.2"); + }); + end + +rule "CBI.3.3: Dont redact because Table Row doesn't contain Vertebrate" + when + $table: Table(hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address")) + then + $table.streamEntitiesWhereRowContainsNoEntitiesOfType(List.of("vertebrate")) + .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("No vertebrate found"); + entity.setRedaction(false); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.3.3"); + }); + end + + +// Rule unit: CBI.4 +rule "CBI.4.0: Dont redact Names and Addresses if no_redaction_indicator is found in Section" + when + $section: Section(!hasTables(), + hasEntitiesOfType("vertebrate"), + hasEntitiesOfType("no_redaction_indicator"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) + then + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("Vertebrate but a no redaction indicator found"); + entity.setRedaction(false); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.4.0"); + entity.addReferences($section.getEntitiesOfType("no_redaction_indicator")); + }); + end + +rule "CBI.4.1: Dont redact Names and Addresses if no_redaction_indicator is found in Table Row" + when + $table: Table(hasEntitiesOfType("no_redaction_indicator"), + hasEntitiesOfType("vertebrate"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) + then + $table.streamEntitiesWhereRowContainsEntitiesOfType(List.of("vertebrate", "no-redaction_indicator")) + .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("Vertebrate but a no redaction indicator found"); + entity.setRedaction(false); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.4.1"); + entity.addReferences($table.getEntitiesOfTypeInSameRow("vertebrate", entity)); + entity.addReferences($table.getEntitiesOfTypeInSameRow("no_redaction_indicator", entity)); + }); + end + + +// Rule unit: CBI.5 +rule "CBI.5.0: Redact Names and Addresses if no_redaction_indicator but also redaction_indicator is found in section" + when + $section: Section(!hasTables(), + hasEntitiesOfType("redaction_indicator"), + hasEntitiesOfType("no_redaction_indicator"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) + then + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("no_redaction_indicator but also redaction_indicator found"); + entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + entity.setRedaction(true); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.5.0"); + entity.addReferences($section.getEntitiesOfType("no_redaction_indicator")); + entity.addReferences($section.getEntitiesOfType("redaction_indicator")); + }); + end + +rule "CBI.5.1: Redact Names and Addresses if no_redaction_indicator but also redaction_indicator is found in Table Row" + when + $table: Table(hasEntitiesOfType("no_redaction_indicator"), + hasEntitiesOfType("redaction_indicator"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) + then + $table.streamEntitiesWhereRowContainsEntitiesOfType(List.of("redaction_indicator", "no-redaction_indicator")) + .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("no_redaction_indicator but also redaction_indicator found"); + entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + entity.setRedaction(true); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.5.1"); + entity.addReferences($table.getEntitiesOfTypeInSameRow("vertebrate", entity)); + entity.addReferences($table.getEntitiesOfTypeInSameRow("no_redaction_indicator", entity)); + }); + end + + +// Rule unit: CBI.8 +rule "CBI.8.0: Redacted because Section contains must_redact entity" + when + $section: Section(!hasTables(), hasEntitiesOfType("must_redact"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) + then + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("must_redact entity found"); + entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + entity.setRedaction(true); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.8.0"); + entity.addReferences($section.getEntitiesOfType("must_redact")); + }); + end + +rule "CBI.8.1: Redacted because Table Row contains must_redact entity" + when + $table: Table(hasEntitiesOfType("must_redact"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) + then + $table.streamEntitiesWhereRowContainsEntitiesOfType(List.of("must_redact")) + .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) + .forEach(entity -> { + entity.setRedactionReason("must_redact entity found"); + entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + entity.setRedaction(true); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.8.1"); + entity.addReferences($table.getEntitiesOfTypeInSameRow("must_redact", entity)); + }); + end + + +// Rule unit: CBI.9 +rule "CBI.9.0: Redact all Cell's with Header Author(s) as CBI_author (non vertebrate study)" + agenda-group "LOCAL_DICTIONARY_ADDS" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $table: Table(hasHeader("Author(s)")) + then + $table.streamTableCellsWithHeader("Author(s)") + .map(tableCell -> entityCreationService.bySemanticNode(tableCell, "CBI_author", EntityType.ENTITY)) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(redactionEntity -> { + redactionEntity.setRedaction(true); + redactionEntity.addMatchedRule("CBI.9.0"); + redactionEntity.addEngine(Engine.RULE); + redactionEntity.setRedactionReason("Author(s) found"); + redactionEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + insert(redactionEntity); + }); + end + +rule "CBI.9.1: Redact all Cell's with Header Author as CBI_author (non vertebrate study)" + agenda-group "LOCAL_DICTIONARY_ADDS" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $table: Table(hasHeader("Author")) + then + $table.streamTableCellsWithHeader("Author") + .map(tableCell -> entityCreationService.bySemanticNode(tableCell, "CBI_author", EntityType.ENTITY)) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(redactionEntity -> { + redactionEntity.setRedaction(true); + redactionEntity.addMatchedRule("CBI.9.1"); + redactionEntity.addEngine(Engine.RULE); + redactionEntity.setRedactionReason("Author found"); + redactionEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + insert(redactionEntity); + }); + end + + +// Rule unit: CBI.11 +rule "CBI.11.0: Recommend all CBI_author entities in Table with Vertebrate Study Y/N Header" + agenda-group "LOCAL_DICTIONARY_ADDS" + salience -1 + when + $table: Table(hasHeader("Author(s)") && hasHeader("Vertebrate Study Y/N")) + then + $table.getEntitiesOfType("CBI_author").forEach(entity -> dictionary.addMultipleAuthorsAsRecommendation(entity)); + end + + +// Rule unit: CBI.12 +rule "CBI.12.0: Add all Cell's with Header Author(s) as CBI_author" + salience 1 + when + $table: Table(hasHeader("Author(s)") || hasHeader("Author")) + then + Stream.concat( + $table.streamTableCellsWithHeader("Author(s)"), + $table.streamTableCellsWithHeader("Author") + ) + .map(tableCell -> entityCreationService.bySemanticNode(tableCell, "CBI_author", EntityType.ENTITY)) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(redactionEntity -> { + redactionEntity.addMatchedRule("CBI.12.0"); + redactionEntity.setRedactionReason("Author(s) header found"); + redactionEntity.addEngine(Engine.RULE); + insert(redactionEntity); + }); + end + +rule "CBI.12.1: Dont redact CBI_author, if its row contains a cell with header \"Vertebrate study Y/N\" and value No" + when + $table: Table(hasRowWithHeaderAndValue("Vertebrate study Y/N", "N") || hasRowWithHeaderAndValue("Vertebrate study Y/N", "No")) + then + $table.streamEntitiesWhereRowHasHeaderAndAnyValue("Vertebrate study Y/N", List.of("N", "No")) + .filter(redactionEntity -> redactionEntity.isAnyType(List.of("CBI_author", "CBI_address"))) + .forEach(authorEntity -> { + authorEntity.setRedaction(false); + authorEntity.setRedactionReason("Not redacted because it's row does not belong to a vertebrate study"); + authorEntity.addMatchedRule("CBI.12.1"); + }); + end + +rule "CBI.12.2: Redact CBI_author, if its row contains a cell with header \"Vertebrate study Y/N\" and value Yes" + when + $table: Table(hasRowWithHeaderAndValue("Vertebrate study Y/N", "Y") || hasRowWithHeaderAndValue("Vertebrate study Y/N", "Yes")) + then + $table.streamEntitiesWhereRowHasHeaderAndAnyValue("Vertebrate study Y/N", List.of("Y", "Yes")) + .filter(redactionEntity -> redactionEntity.isAnyType(List.of("CBI_author", "CBI_address"))) + .forEach(authorEntity -> { + authorEntity.setRedaction(true); + authorEntity.setRedactionReason("Redacted because it's row belongs to a vertebrate study"); + authorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + authorEntity.addMatchedRule("CBI.12.2"); + }); + end + + +// Rule unit: CBI.14 +rule "CBI.14.0: Redact CBI_sponsor entities if preceded by \"batches produced at\"" + when + $sponsorEntity: RedactionEntity(type == "CBI_sponsor", textBefore.contains("batches produced at")) + then + $sponsorEntity.setRedaction(true); + $sponsorEntity.setRedactionReason("Redacted because it represents a sponsor company"); + $sponsorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + $sponsorEntity.addMatchedRule("CBI.14.0"); + end + + +// Rule unit: CBI.15 +rule "CBI.15.0: Redact row if row contains \"determination of residues\" and livestock keyword" + when + $keyword: String() from List.of("livestock", + "live stock", + "tissue", + "tissues", + "liver", + "muscle", + "bovine", + "ruminant", + "ruminants") + $residueKeyword: String() from List.of("determination of residues", "determination of total residues") + $section: Section(!hasTables(), + containsStringIgnoreCase($residueKeyword), + containsStringIgnoreCase($keyword)) + then + entityCreationService.byString($keyword, "must_redact", EntityType.ENTITY, $section) + .forEach(keywordEntity -> insert(keywordEntity)); + + $section.getEntitiesOfType(List.of($keyword, $residueKeyword)) + .forEach(redactionEntity -> { + redactionEntity.setRedaction(true); + redactionEntity.addMatchedRule("CBI.15.0"); + redactionEntity.setRedactionReason("Determination of residues and keyword \"" + $keyword + "\" was found."); + redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + }); + end + +rule "CBI.15.1: Redact CBI_author and CBI_address if row contains \"determination of residues\" and livestock keyword" + when + $keyword: String() from List.of("livestock", + "live stock", + "tissue", + "tissues", + "liver", + "muscle", + "bovine", + "ruminant", + "ruminants") + $residueKeyword: String() from List.of("determination of residues", "determination of total residues") + $table: Table(containsStringIgnoreCase($residueKeyword), containsStringIgnoreCase($keyword)) + then + entityCreationService.byString($keyword, "must_redact", EntityType.ENTITY, $table) + .forEach(keywordEntity -> insert(keywordEntity)); + + $table.streamEntitiesWhereRowContainsStringsIgnoreCase(List.of($keyword, $residueKeyword)) + .filter(redactionEntity -> redactionEntity.isAnyType(List.of("CBI_author", "CBI_address"))) + .forEach(redactionEntity -> { + redactionEntity.setRedaction(true); + redactionEntity.addMatchedRule("CBI.15.1"); + redactionEntity.setRedactionReason("Determination of residues and keyword \"" + $keyword + "\" was found."); + redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + }); + end + + +// Rule unit: CBI.16 +rule "CBI.16.0: Add CBI_author with \"et al.\" Regex (non vertebrate study)" + agenda-group "LOCAL_DICTIONARY_ADDS" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(containsString("et al.")) + then + entityCreationService.byRegex("\\b([A-ZÄÖÜ][^\\s\\.,]+( [A-ZÄÖÜ]{1,2}\\.?)?( ?[A-ZÄÖÜ]\\.?)?) et al\\.?", "CBI_author", EntityType.ENTITY, 1, $section) + .forEach(entity -> { + entity.setRedaction(true); + entity.setRedactionReason("Author found by \"et al\" regex"); + entity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + entity.addMatchedRule("CBI.16.0"); + entity.addEngine(Engine.RULE); + insert(entity); + dictionary.addLocalDictionaryEntry("CBI_author", entity.getValue(), false); + }); + end + +rule "CBI.16.1: Add CBI_author with \"et al.\" Regex (vertebrate study)" + agenda-group "LOCAL_DICTIONARY_ADDS" + when + FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(containsString("et al.")) + then + entityCreationService.byRegex("\\b([A-ZÄÖÜ][^\\s\\.,]+( [A-ZÄÖÜ]{1,2}\\.?)?( ?[A-ZÄÖÜ]\\.?)?) et al\\.?", "CBI_author", EntityType.ENTITY, 1, $section) + .forEach(entity -> { + entity.setRedaction(true); + entity.setRedactionReason("Author found by \"et al\" regex"); + entity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + entity.addMatchedRule("CBI.16.1"); + entity.addEngine(Engine.RULE); + insert(entity); + dictionary.addLocalDictionaryEntry("CBI_author", entity.getValue(), false); + }); + end + + +// Rule unit: CBI.17 +rule "CBI.17.0: Add recommendation for Addresses in Test Organism sections, without colon" + when + $section: Section(!hasTables(), containsString("Species") && containsString("Source") && !containsString("Species:") && !containsString("Source:")) + then + entityCreationService.lineAfterString("Source", "CBI_address", EntityType.RECOMMENDATION, $section) + .forEach(entity -> { + entity.setRedactionReason("Line after \"Source\" in Test Organism Section"); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.17.0"); + insert(entity); + }); + end + +rule "CBI.17.1: Add recommendation for Addresses in Test Organism sections, with colon" + when + $section: Section(!hasTables(), containsString("Species:"), containsString("Source:")) + then + entityCreationService.lineAfterString("Source:", "CBI_address", EntityType.RECOMMENDATION, $section) + .forEach(entity -> { + entity.setRedactionReason("Line after \"Source:\" in Test Animals Section"); + entity.addEngine(Engine.RULE); + entity.addMatchedRule("CBI.17.1"); + insert(entity); + }); + end + + +// Rule unit: CBI.18 +rule "CBI.18.0: Expand CBI_author entities with firstname initials" + no-loop true + when + $entityToExpand: RedactionEntity(type == "CBI_author", + value.matches("[^\\s]+"), + textAfter.startsWith(" "), + anyMatch(textAfter, "(,? [A-Z]\\.?( ?[A-Z]\\.?)?( ?[A-Z]\\.?)?\\b\\.?)") + ) + then + RedactionEntity expandedEntity = entityCreationService.bySuffixExpansionRegex($entityToExpand, "(,? [A-Z]\\.?( ?[A-Z]\\.?)?( ?[A-Z]\\.?)?\\b\\.?)"); + expandedEntity.addMatchedRule("CBI.18.0"); + $entityToExpand.removeFromGraph(); + retract($entityToExpand); + insert(expandedEntity); + end + + +// Rule unit: CBI.19 +rule "CBI.19.0: Expand CBI_author entities with salutation prefix" + when + $entityToExpand: RedactionEntity(type == "CBI_author", anyMatch(textBefore, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*")) + then + RedactionEntity expandedEntity = entityCreationService.byPrefixExpansionRegex($entityToExpand, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*"); + expandedEntity.addMatchedRule("CBI.19.0"); + insert(expandedEntity); + end + + +// Rule unit: CBI.20 +rule "CBI.20.0: Redact between \"PERFORMING LABORATORY\" and \"LABORATORY PROJECT ID:\" (non vertebrate study)" + agenda-group "LOCAL_DICTIONARY_ADDS" + when + not FileAttribute(label == "Vertebrate Study", value == "Yes") + $section: Section(!hasTables(), containsString("PERFORMING LABORATORY:"), containsString("LABORATORY PROJECT ID:")) + then + entityCreationService.betweenStrings("PERFORMING LABORATORY:", "LABORATORY PROJECT ID:", "CBI_address", EntityType.ENTITY, $section) + .forEach(laboratoryEntity -> { + laboratoryEntity.setRedaction(false); + laboratoryEntity.addMatchedRule("CBI.20.0"); + laboratoryEntity.addEngine(Engine.RULE); + laboratoryEntity.setRedactionReason("PERFORMING LABORATORY was found for non vertebrate study"); + dictionary.addLocalDictionaryEntry(laboratoryEntity); + insert(laboratoryEntity); + }); + end + +rule "CBI.20.1: Redact between \"PERFORMING LABORATORY\" and \"LABORATORY PROJECT ID:\" (vertebrate study)" + agenda-group "LOCAL_DICTIONARY_ADDS" + when + FileAttribute(label == "Vertebrate Study", value == "Yes") + $section: Section(!hasTables(), containsString("PERFORMING LABORATORY:"), containsString("LABORATORY PROJECT ID:")) + then + entityCreationService.betweenStrings("PERFORMING LABORATORY:", "LABORATORY PROJECT ID:", "CBI_address", EntityType.ENTITY, $section) + .forEach(laboratoryEntity -> { + laboratoryEntity.setRedaction(true); + laboratoryEntity.addMatchedRule("CBI.20.1"); + laboratoryEntity.addEngine(Engine.RULE); + laboratoryEntity.setRedactionReason("PERFORMING LABORATORY was found"); + laboratoryEntity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + dictionary.addLocalDictionaryEntry(laboratoryEntity); + insert(laboratoryEntity); + }); + end + + +//------------------------------------ PII rules ------------------------------------ + +// Rule unit: PII.0 +rule "PII.0.0: Redact all PII (non vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $pii: RedactionEntity(type == "PII", dictionaryEntry) + then + $pii.setRedaction(true); + $pii.setRedactionReason("Personal Information found"); + $pii.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + $pii.addMatchedRule("PII.0.0"); + end + +rule "PII.0.1: Redact all PII (vertebrate study)" + when + FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $pii: RedactionEntity(type == "PII", dictionaryEntry) + then + $pii.setRedaction(true); + $pii.setRedactionReason("Personal Information found"); + $pii.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + $pii.addMatchedRule("PII.0.1"); + end + + +// Rule unit: PII.1 +rule "PII.1.0: Redact Emails by RegEx (Non vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(containsString("@")) + then + entityCreationService.byRegex("\\b([A-Za-z0-9._%+\\-]+@[A-Za-z0-9.\\-]+\\.[A-Za-z\\-]{1,23}[A-Za-z])\\b", "PII", EntityType.ENTITY, 1, $section) + .forEach(emailEntity -> { + emailEntity.setRedaction(true); + emailEntity.addEngine(Engine.RULE); + emailEntity.setRedactionReason("Found by Email Regex"); + emailEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + emailEntity.addMatchedRule("PII.1.0"); + insert(emailEntity); + }); + end + +rule "PII.1.1: Redact Emails by RegEx (vertebrate study)" + when + FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(containsString("@")) + then + entityCreationService.byRegex("\\b([A-Za-z0-9._%+\\-]+@[A-Za-z0-9.\\-]+\\.[A-Za-z\\-]{1,23}[A-Za-z])\\b", "PII", EntityType.ENTITY, 1, $section) + .forEach(emailEntity -> { + emailEntity.setRedaction(true); + emailEntity.addEngine(Engine.RULE); + emailEntity.setRedactionReason("Found by Email Regex"); + emailEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + emailEntity.addMatchedRule("PII.1.1"); + insert(emailEntity); + }); + end + + +// Rule unit: PII.4 +rule "PII.4.0: Redact line after contact information keywords (non vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $contactKeyword: String() from List.of("Contact point:", + "Contact:", + "Alternative contact:", + "European contact:", + "No:", + "Contact:", + "Tel.:", + "Tel:", + "Telephone number:", + "Telephone No:", + "Telephone:", + "Phone No.", + "Phone:", + "Fax number:", + "Fax:", + "E-mail:", + "Email:", + "e-mail:", + "E-mail address:") + $section: Section(containsString($contactKeyword)) + then + entityCreationService.lineAfterString($contactKeyword, "PII", EntityType.ENTITY, $section) + .forEach(contactEntity -> { + contactEntity.setRedaction(true); + contactEntity.addMatchedRule("PII.4.0"); + contactEntity.addEngine(Engine.RULE); + contactEntity.setRedactionReason("Found after \"" + $contactKeyword + "\" contact keyword"); + contactEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); + insert(contactEntity); + }); + end + +rule "PII.4.1: Redact line after contact information keywords (non vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $contactKeyword: String() from List.of("Contact point:", + "Contact:", + "Alternative contact:", + "European contact:", + "No:", + "Contact:", + "Tel.:", + "Tel:", + "Telephone number:", + "Telephone No:", + "Telephone:", + "Phone No.", + "Phone:", + "Fax number:", + "Fax:", + "E-mail:", + "Email:", + "e-mail:", + "E-mail address:") + $section: Section(containsString($contactKeyword)) + then + entityCreationService.lineAfterString($contactKeyword, "PII", EntityType.ENTITY, $section) + .forEach(contactEntity -> { + contactEntity.setRedaction(true); + contactEntity.addMatchedRule("PII.4.1"); + contactEntity.addEngine(Engine.RULE); + contactEntity.setRedactionReason("Found after \"" + $contactKeyword + "\" contact keyword"); + contactEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); + insert(contactEntity); + }); + end + + +// Rule unit: PII.6 +rule "PII.6.0: redact line between contact keywords (non vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section((containsString("No:") && containsString("Fax")) || (containsString("Contact:") && containsString("Tel"))) + then + Stream.concat( + entityCreationService.betweenStrings("No:", "Fax", "PII", EntityType.ENTITY, $section), + entityCreationService.betweenStrings("Contact:", "Tel", "PII", EntityType.ENTITY, $section) + ) + .forEach(contactEntity -> { + contactEntity.setRedaction(true); + contactEntity.addMatchedRule("PII.6.0"); + contactEntity.addEngine(Engine.RULE); + contactEntity.setRedactionReason("Found between contact keywords"); + contactEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + insert(contactEntity); + }); + end + +rule "PII.6.1: redact line between contact keywords" + when + FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section((containsString("No:") && containsString("Fax")) || (containsString("Contact:") && containsString("Tel"))) + then + Stream.concat( + entityCreationService.betweenStrings("No:", "Fax", "PII", EntityType.ENTITY, $section), + entityCreationService.betweenStrings("Contact:", "Tel", "PII", EntityType.ENTITY, $section) + ) + .forEach(contactEntity -> { + contactEntity.setRedaction(true); + contactEntity.addMatchedRule("PII.6.1"); + contactEntity.addEngine(Engine.RULE); + contactEntity.setRedactionReason("Found between contact keywords"); + contactEntity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + insert(contactEntity); + }); + end + + +// Rule unit: PII.7 +rule "PII.7.0: Redact contact information if applicant is found (non vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(getHeadline().containsString("applicant") || + getHeadline().containsString("Primary contact") || + getHeadline().containsString("Alternative contact") || + containsString("Applicant") || + containsString("Telephone number:")) + then + Stream.concat(entityCreationService.lineAfterStrings(List.of("Contact point:", "Contact:", "Alternative contact:", "European contact:", "No:", "Contact:", "Tel.:", "Tel:", "Telephone number:", + "Telephone No:", "Telephone:", "Phone No.", "Phone:", "Fax number:", "Fax:", "E-mail:", "Email:", "e-mail:", "E-mail address:"), "PII", EntityType.ENTITY, $section), + Stream.concat( + entityCreationService.betweenStrings("No:", "Fax", "PII", EntityType.ENTITY, $section), + entityCreationService.betweenStrings("Contact:", "Tel", "PII", EntityType.ENTITY, $section) + )) + .forEach(entity -> { + entity.setRedaction(true); + entity.setRedactionReason("Applicant information was found"); + entity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + entity.addMatchedRule("PII.7.0"); + entity.addEngine(Engine.RULE); + insert(entity); + }); + end + +rule "PII.7.1: Redact contact information if applicant is found (non vertebrate study)" + when + FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(getHeadline().containsString("applicant") || + getHeadline().containsString("Primary contact") || + getHeadline().containsString("Alternative contact") || + containsString("Applicant") || + containsString("Telephone number:")) + then + Stream.concat(entityCreationService.lineAfterStrings(List.of("Contact point:", "Contact:", "Alternative contact:", "European contact:", "No:", "Contact:", "Tel.:", "Tel:", "Telephone number:", + "Telephone No:", "Telephone:", "Phone No.", "Phone:", "Fax number:", "Fax:", "E-mail:", "Email:", "e-mail:", "E-mail address:"), "PII", EntityType.ENTITY, $section), + Stream.concat( + entityCreationService.betweenStrings("No:", "Fax", "PII", EntityType.ENTITY, $section), + entityCreationService.betweenStrings("Contact:", "Tel", "PII", EntityType.ENTITY, $section) + )) + .forEach(entity -> { + entity.setRedaction(true); + entity.setRedactionReason("Applicant information was found"); + entity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + entity.addMatchedRule("PII.7.1"); + entity.addEngine(Engine.RULE); + insert(entity); + }); + end + + +// Rule unit: PII.8 +rule "PII.8.0: Redact contact information if producer is found" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(containsStringIgnoreCase("producer of the plant protection") || + containsStringIgnoreCase("producer of the active substance") || + containsStringIgnoreCase("manufacturer of the active substance") || + containsStringIgnoreCase("manufacturer:") || + containsStringIgnoreCase("Producer or producers of the active substance")) + then + Stream.concat(entityCreationService.lineAfterStrings(List.of("Contact point:", "Contact:", "Alternative contact:", "European contact:", "No:", "Contact:", "Tel.:", "Tel:", "Telephone number:", + "Telephone No:", "Telephone:", "Phone No.", "Phone:", "Fax number:", "Fax:", "E-mail:", "Email:", "e-mail:", "E-mail address:"), "PII", EntityType.ENTITY, $section), + Stream.concat( + entityCreationService.betweenStrings("No:", "Fax", "PII", EntityType.ENTITY, $section), + entityCreationService.betweenStrings("Contact:", "Tel", "PII", EntityType.ENTITY, $section) + )) + .forEach(entity -> { + entity.setRedaction(true); + entity.setRedactionReason("Producer was found"); + entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); + entity.addMatchedRule("PII.8.0"); + entity.addEngine(Engine.RULE); + insert(entity); + }); + end + +rule "PII.8.1: Redact contact information if producer is found" + when + FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(containsStringIgnoreCase("producer of the plant protection") || + containsStringIgnoreCase("producer of the active substance") || + containsStringIgnoreCase("manufacturer of the active substance") || + containsStringIgnoreCase("manufacturer:") || + containsStringIgnoreCase("Producer or producers of the active substance")) + then + Stream.concat(entityCreationService.lineAfterStrings(List.of("Contact point:", "Contact:", "Alternative contact:", "European contact:", "No:", "Contact:", "Tel.:", "Tel:", "Telephone number:", + "Telephone No:", "Telephone:", "Phone No.", "Phone:", "Fax number:", "Fax:", "E-mail:", "Email:", "e-mail:", "E-mail address:"), "PII", EntityType.ENTITY, $section), + Stream.concat( + entityCreationService.betweenStrings("No:", "Fax", "PII", EntityType.ENTITY, $section), + entityCreationService.betweenStrings("Contact:", "Tel", "PII", EntityType.ENTITY, $section) + )) + .forEach(entity -> { + entity.setRedaction(true); + entity.setRedactionReason("Producer was found"); + entity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + entity.addMatchedRule("PII.8.1"); + entity.addEngine(Engine.RULE); + insert(entity); + }); + end + + +// Rule unit: PII.9 +rule "PII.9.0: Redact between \"AUTHOR(S)\" and \"COMPLETION DATE\" (non vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(!hasTables(), containsString("AUTHOR(S):"), containsString("COMPLETION DATE:"), !containsString("STUDY COMPLETION DATE:")) + then + entityCreationService.betweenStrings("AUTHOR(S):", "COMPLETION DATE:", "PII", EntityType.ENTITY, $section) + .forEach(authorEntity -> { + authorEntity.setRedaction(true); + authorEntity.addMatchedRule("PII.9.0"); + authorEntity.addEngine(Engine.RULE); + authorEntity.setRedactionReason("AUTHOR(S) was found"); + authorEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + insert(authorEntity); + }); + end + +rule "PII.9.1: Redact between \"AUTHOR(S)\" and \"STUDY COMPLETION DATE\" (non vertebrate study)" + when + FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(!hasTables(), containsString("AUTHOR(S):"), containsString("COMPLETION DATE:"), !containsString("STUDY COMPLETION DATE:")) + then + entityCreationService.betweenStrings("AUTHOR(S):", "COMPLETION DATE:", "PII", EntityType.ENTITY, $section) + .forEach(authorEntity -> { + authorEntity.setRedaction(true); + authorEntity.addMatchedRule("PII.9.1"); + authorEntity.addEngine(Engine.RULE); + authorEntity.setRedactionReason("AUTHOR(S) was found"); + authorEntity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + insert(authorEntity); + }); + end + +rule "PII.9.2: Redact between \"AUTHOR(S)\" and \"COMPLETION DATE\" (non vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(!hasTables(), containsString("AUTHOR(S):"), containsString("STUDY COMPLETION DATE:")) + then + entityCreationService.betweenStrings("AUTHOR(S):", "STUDY COMPLETION DATE:", "PII", EntityType.ENTITY, $section) + .forEach(authorEntity -> { + authorEntity.setRedaction(true); + authorEntity.addMatchedRule("PII.9.2"); + authorEntity.addEngine(Engine.RULE); + authorEntity.setRedactionReason("AUTHOR(S) was found"); + authorEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + insert(authorEntity); + }); + end + +rule "PII.9.3: Redact between \"AUTHOR(S)\" and \"STUDY COMPLETION DATE\" (vertebrate study)" + when + FileAttribute(label == "Vertebrate Study", value.toLowerCase() == "yes") + $section: Section(!hasTables(), containsString("AUTHOR(S):"), containsString("STUDY COMPLETION DATE:")) + then + entityCreationService.betweenStrings("AUTHOR(S):", "STUDY COMPLETION DATE:", "PII", EntityType.ENTITY, $section) + .forEach(authorEntity -> { + authorEntity.setRedaction(true); + authorEntity.addMatchedRule("PII.9.3"); + authorEntity.addEngine(Engine.RULE); + authorEntity.setRedactionReason("AUTHOR(S) was found"); + authorEntity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + insert(authorEntity); + }); + end + + +// Rule unit: PII.11 +rule "PII.11.0: Redact On behalf of Sequani Ltd.:" + when + $section: Section(!hasTables(), containsString("On behalf of Sequani Ltd.: Name Title")) + then + entityCreationService.betweenStrings("On behalf of Sequani Ltd.: Name Title", "On behalf of", "PII", EntityType.ENTITY, $section) + .forEach(authorEntity -> { + authorEntity.setRedaction(true); + authorEntity.addMatchedRule("PII.11.0"); + authorEntity.setRedactionReason("On behalf of Sequani Ltd.: Name Title was found"); + authorEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + insert(authorEntity); + }); + end + + +// Rule unit: PII.12 +rule "PII.12.0: Expand PII entities with salutation prefix" + when + $entityToExpand: RedactionEntity(type == "PII", anyMatch(textBefore, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*")) + then + RedactionEntity expandedEntity = entityCreationService.byPrefixExpansionRegex($entityToExpand, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*"); + expandedEntity.addMatchedRule("PII.12.0"); + insert(expandedEntity); + end + + +//------------------------------------ Other rules ------------------------------------ + +// Rule unit: ETC.1 +rule "ETC.1.0: Redact Purity" + when + $section: Section(containsStringIgnoreCase("purity")) + then + entityCreationService.byRegex("\\bPurity:\\s*(?\\s*\\d{1,2}(?:\\.\\d{1,2})?\\s*%)", "purity", EntityType.ENTITY, 1, $section) + .forEach(entity -> { + entity.addMatchedRule("ETC.1.0"); + entity.addEngine(Engine.RULE); + entity.setRedaction(true); + entity.setRedactionReason("Purity found"); + entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2a)"); + }); + end + + +// Rule unit: ETC.2 +rule "ETC.2.0: Redact signatures (non vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value == "Yes") + $signature: Image(imageType == ImageType.SIGNATURE) + then + $signature.setRedaction(true); + $signature.setMatchedRule("ETC.2.0"); + $signature.setRedactionReason("Signature Found"); + $signature.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + end + +rule "ETC.2.0: Redact signatures (vertebrate study)" + when + FileAttribute(label == "Vertebrate Study", value == "Yes") + $signature: Image(imageType == ImageType.SIGNATURE) + then + $signature.setRedaction(true); + $signature.setMatchedRule("ETC.2.0"); + $signature.setRedactionReason("Signature Found"); + $signature.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + end + + +// Rule unit: ETC.3 +rule "ETC.3.0: Redact logos (vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value == "Yes") + $logo: Image(imageType == ImageType.LOGO) + then + $logo.setRedaction(true); + $logo.setMatchedRule("ETC.3.0"); + $logo.setRedactionReason("Logo Found"); + $logo.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + end + +rule "ETC.3.1: Redact logos (non vertebrate study)" + when + FileAttribute(label == "Vertebrate Study", value == "Yes") + $logo: Image(imageType == ImageType.LOGO) + then + $logo.setRedaction(true); + $logo.setMatchedRule("ETC.3.1"); + $logo.setRedactionReason("Logo Found"); + $logo.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + end + + +// Rule unit: ETC.4 +rule "ETC.4.0: Redact dossier dictionary entries" + when + $dossierRedaction: RedactionEntity(type == "dossier_redaction") + then + $dossierRedaction.setRedaction(true); + $dossierRedaction.addMatchedRule("ETC.4.0"); + $dossierRedaction.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + $dossierRedaction.setRedactionReason("Specification of impurity found"); + end + + +// Rule unit: ETC.5 +rule "ETC.5.0: Ignore dossier_redaction entries if confidentiality is not 'confidential'" + when + not FileAttribute(label == "Confidentiality", value == "confidential") + $dossierRedaction: RedactionEntity(type == "dossier_redaction") + then + $dossierRedaction.removeFromGraph(); + retract($dossierRedaction); + end + + +// Rule unit: ETC.6 +rule "ETC.6.0: Redact CAS Number" + when + $table: Table(hasHeader("Sample #")) + then + $table.streamTableCellsWithHeader("Sample #") + .map(tableCell -> entityCreationService.bySemanticNode(tableCell, "PII", EntityType.ENTITY)) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(redactionEntity -> { + redactionEntity.setRedaction(true); + redactionEntity.addMatchedRule("ETC.6.0"); + redactionEntity.setRedactionReason("Sample # found in Header"); + redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); + insert(redactionEntity); + }); + end + + +// Rule unit: ETC.7 +rule "ETC.7.0: Guidelines FileAttributes" + when + $section: Section(!hasTables(), (containsString("DATA REQUIREMENT(S):") || containsString("TEST GUIDELINE(S):")) && (containsString("OECD") || containsString("EPA") || containsString("OPPTS"))) + then + RedactionSearchUtility.findBoundariesByRegex("OECD (No\\.? )?\\d{3}( \\(\\d{4}\\))?", $section.getTextBlock()).stream() + .map(boundary -> $section.getTextBlock().subSequence(boundary).toString()) + .map(value -> FileAttribute.builder().label("OECD Number").value(value).build()) + .forEach(fileAttribute -> insert(fileAttribute)); + end + + +// Rule unit: ETC.8 +rule "ETC.8.0: Redact formulas (vertebrate study)" + when + not FileAttribute(label == "Vertebrate Study", value == "Yes") + $logo: Image(imageType == ImageType.FORMULA) + then + $logo.setRedaction(true); + $logo.setMatchedRule("ETC.8.0"); + $logo.setRedactionReason("Logo Found"); + $logo.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + end + +rule "ETC.8.1: Redact formulas (non vertebrate study)" + when + FileAttribute(label == "Vertebrate Study", value == "Yes") + $logo: Image(imageType == ImageType.FORMULA) + then + $logo.setRedaction(true); + $logo.setMatchedRule("ETC.8.1"); + $logo.setRedactionReason("Logo Found"); + $logo.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); + end + + +//------------------------------------ AI rules ------------------------------------ + +// Rule unit: AI.0 +rule "AI.0.0: add all NER Entities of type CBI_author" + salience 999 + when + nerEntities: NerEntities(hasEntitiesOfType("CBI_author")) + then + nerEntities.streamEntitiesOfType("CBI_author") + .map(nerEntity -> entityCreationService.byNerEntity(nerEntity, EntityType.RECOMMENDATION, document)) + .forEach(entity -> insert(entity)); + end + + +// Rule unit: AI.1 +rule "AI.1.0: combine and add NER Entities as CBI_address" + salience 999 + when + nerEntities: NerEntities(hasEntitiesOfType("ORG") || hasEntitiesOfType("STREET") || hasEntitiesOfType("CITY")) + then + nerEntitiesAdapter.combineNerEntitiesToCbiAddressDefaults(nerEntities) + .map(boundary -> entityCreationService.byBoundary(boundary, "CBI_address", EntityType.RECOMMENDATION, document)) + .forEach(entity -> { + entity.addEngine(Engine.NER); + insert(entity); + }); + end + + +//------------------------------------ Manual redaction rules ------------------------------------ + +// Rule unit: MAN.0 +rule "MAN.0.0: 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 unit: MAN.1 +rule "MAN.1.0: 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.setIgnored(true); + end + +rule "MAN.1.1: 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: Image($id == id) + then + $imageEntityToBeRemoved.setIgnored(true); + end + + +// Rule unit: MAN.2 +rule "MAN.2.0: 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 unit: MAN.3 +rule "MAN.3.0: Apply image recategorization" + salience 128 + when + ManualImageRecategorization($id: annotationId, status == AnnotationStatus.APPROVED, $imageType: type) + $image: Image($id == id) + then + $image.setImageType(ImageType.fromString($imageType)); + end + + +//------------------------------------ Entity merging rules ------------------------------------ + +// Rule unit: X.0 +rule "X.0.0: remove Entity contained by Entity of same type" + salience 65 + when + $larger: RedactionEntity($type: type, $entityType: entityType) + $contained: RedactionEntity(containedBy($larger), type == $type, entityType == $entityType, this != $larger, !resized, !skipRemoveEntitiesContainedInLarger) + then + $contained.removeFromGraph(); + retract($contained); + end + + +// Rule unit: X.1 +rule "X.1.0: merge intersecting Entities of same type" + salience 64 + when + $first: RedactionEntity($type: type, $entityType: entityType, !resized, !skipRemoveEntitiesContainedInLarger) + $second: RedactionEntity(intersects($first), type == $type, entityType == $entityType, this != $first, !resized, !skipRemoveEntitiesContainedInLarger) + then + $first.removeFromGraph(); + $second.removeFromGraph(); + RedactionEntity mergedEntity = entityCreationService.byEntities(List.of($first, $second), $type, $entityType, document); + retract($first); + retract($second); + insert(mergedEntity); + end + + +// Rule unit: X.2 +rule "X.2.0: remove Entity of type ENTITY when contained by FALSE_POSITIVE" + salience 64 + when + $falsePositive: RedactionEntity($type: type, entityType == EntityType.FALSE_POSITIVE) + $entity: RedactionEntity(containedBy($falsePositive), type == $type, entityType == EntityType.ENTITY, !resized, !skipRemoveEntitiesContainedInLarger) + then + $entity.removeFromGraph(); + retract($entity) + end + + +// Rule unit: X.3 +rule "X.3.0: remove Entity of type RECOMMENDATION when contained by FALSE_RECOMMENDATION" + salience 64 + when + $falseRecommendation: RedactionEntity($type: type, entityType == EntityType.FALSE_RECOMMENDATION) + $recommendation: RedactionEntity(containedBy($falseRecommendation), type == $type, entityType == EntityType.RECOMMENDATION, !resized, !skipRemoveEntitiesContainedInLarger) + then + $recommendation.removeFromGraph(); + retract($recommendation); + end + + +// Rule unit: X.4 +rule "X.4.0: remove Entity of type RECOMMENDATION when intersected by ENTITY with same type" + salience 256 + when + $entity: RedactionEntity($type: type, entityType == EntityType.ENTITY) + $recommendation: RedactionEntity(intersects($entity), type == $type, entityType == EntityType.RECOMMENDATION, !resized, !skipRemoveEntitiesContainedInLarger) + then + $entity.addEngines($recommendation.getEngines()); + $recommendation.removeFromGraph(); + retract($recommendation); + end + + +// Rule unit: X.5 +rule "X.5.0: remove Entity of type RECOMMENDATION when contained by ENTITY" + salience 256 + when + $entity: RedactionEntity(entityType == EntityType.ENTITY) + $recommendation: RedactionEntity(containedBy($entity), entityType == EntityType.RECOMMENDATION, !resized, !skipRemoveEntitiesContainedInLarger) + then + $recommendation.removeFromGraph(); + retract($recommendation); + end + + +// Rule unit: X.6 +rule "X.6.0: remove Entity of lower rank, when intersected by entity of type ENTITY" + salience 32 + when + $higherRank: RedactionEntity($type: type, entityType == EntityType.ENTITY) + $lowerRank: RedactionEntity(intersects($higherRank), type != $type, dictionary.getDictionaryRank(type) < dictionary.getDictionaryRank($type), !resized, !skipRemoveEntitiesContainedInLarger) + then + $lowerRank.removeFromGraph(); + retract($lowerRank); + end + + +//------------------------------------ File attributes rules ------------------------------------ + +// Rule unit: FA.1 +rule "FA.1.0: remove duplicate FileAttributes" + salience 64 + when + $fileAttribute: FileAttribute($label: label, $value: value) + $duplicate: FileAttribute(this != $fileAttribute, label == $label, value == $value) + then + retract($duplicate); + end + + +//------------------------------------ Local dictionary search rules ------------------------------------ + +// Rule unit: LDS.0 +rule "LDS.0.0: run local dictionary search" + agenda-group "LOCAL_DICTIONARY_ADDS" + salience -999 + when + DictionaryModel(!localEntries.isEmpty(), $type: type, $searchImplementation: localSearch) from dictionary.getDictionaryModels() + then + entityCreationService.bySearchImplementation($searchImplementation, $type, EntityType.RECOMMENDATION, document) + .forEach(entity -> { + entity.addEngine(Engine.RULE); + insert(entity); + }); + end diff --git a/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules.drl b/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules.drl index 2668eb8f..973c03c3 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules.drl +++ b/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules.drl @@ -62,7 +62,7 @@ rule "SYN.0.0: Redact if CTL/* or BL/* was found (Non Vertebrate Study)" entityCreationService.byString("BL", "must_redact", EntityType.ENTITY, $section) ).forEach(entity -> { entity.setRedactionReason("hint_only"); - entity.addMatchedRule(0); + entity.addMatchedRule("SYN.0.0"); entity.addEngine(Engine.RULE); insert(entity); }); @@ -74,59 +74,59 @@ rule "SYN.0.0: Redact if CTL/* or BL/* was found (Non Vertebrate Study)" // Rule unit: CBI.3 rule "CBI.3.0: Redacted because Section contains Vertebrate" when - $section: Section(!hasTables(), hasActiveEntitiesOfType("vertebrate"), (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $section: Section(!hasTables(), hasEntitiesOfType("vertebrate"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(entity -> { entity.setRedactionReason("Vertebrate found"); entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); entity.setRedaction(true); entity.addEngine(Engine.RULE); - entity.addMatchedRule(3); - entity.addReferences($section.getActiveEntitiesOfType("vertebrate")); + entity.addMatchedRule("CBI.3.0"); + entity.addReferences($section.getEntitiesOfType("vertebrate")); }); end rule "CBI.3.1: Redacted because Table Row contains Vertebrate" when - $table: Table(hasActiveEntitiesOfType("vertebrate"), (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $table: Table(hasEntitiesOfType("vertebrate"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $table.streamEntitiesWhereRowContainsActiveEntitiesOfType(List.of("vertebrate")) + $table.streamEntitiesWhereRowContainsEntitiesOfType(List.of("vertebrate")) .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) .forEach(entity -> { entity.setRedactionReason("Vertebrate found"); entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); entity.setRedaction(true); entity.addEngine(Engine.RULE); - entity.addMatchedRule(3); - entity.addReferences($table.getActiveEntitiesOfTypeInSameRow("vertebrate", entity)); + entity.addMatchedRule("CBI.3.1"); + entity.addReferences($table.getEntitiesOfTypeInSameRow("vertebrate", entity)); }); end rule "CBI.3.2: Don't redact because Section doesn't contain Vertebrate" when - $section: Section(!hasTables(), !hasActiveEntitiesOfType("vertebrate"), (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $section: Section(!hasTables(), !hasEntitiesOfType("vertebrate"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(entity -> { entity.setRedactionReason("No vertebrate found"); entity.setRedaction(false); entity.addEngine(Engine.RULE); - entity.addMatchedRule(3); + entity.addMatchedRule("CBI.3.2"); }); end rule "CBI.3.3: Dont redact because Table Row doesn't contain Vertebrate" when - $table: Table(hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address")) + $table: Table(hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address")) then - $table.streamEntitiesWhereRowContainsNoActiveEntitiesOfType(List.of("vertebrate")) + $table.streamEntitiesWhereRowContainsNoEntitiesOfType(List.of("vertebrate")) .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) .forEach(entity -> { entity.setRedactionReason("No vertebrate found"); entity.setRedaction(false); entity.addEngine(Engine.RULE); - entity.addMatchedRule(3); + entity.addMatchedRule("CBI.3.3"); }); end @@ -135,35 +135,35 @@ rule "CBI.3.3: Dont redact because Table Row doesn't contain Vertebrate" rule "CBI.4.0: Dont redact Names and Addresses if no_redaction_indicator is found in Section" when $section: Section(!hasTables(), - hasActiveEntitiesOfType("vertebrate"), - hasActiveEntitiesOfType("no_redaction_indicator"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + hasEntitiesOfType("vertebrate"), + hasEntitiesOfType("no_redaction_indicator"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(entity -> { entity.setRedactionReason("Vertebrate but a no redaction indicator found"); entity.setRedaction(false); entity.addEngine(Engine.RULE); - entity.addMatchedRule(4); - entity.addReferences($section.getActiveEntitiesOfType("no_redaction_indicator")); + entity.addMatchedRule("CBI.4.0"); + entity.addReferences($section.getEntitiesOfType("no_redaction_indicator")); }); end rule "CBI.4.1: Dont redact Names and Addresses if no_redaction_indicator is found in Table Row" when - $table: Table(hasActiveEntitiesOfType("no_redaction_indicator"), - hasActiveEntitiesOfType("vertebrate"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $table: Table(hasEntitiesOfType("no_redaction_indicator"), + hasEntitiesOfType("vertebrate"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $table.streamEntitiesWhereRowContainsActiveEntitiesOfType(List.of("vertebrate", "no-redaction_indicator")) + $table.streamEntitiesWhereRowContainsEntitiesOfType(List.of("vertebrate", "no-redaction_indicator")) .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) .forEach(entity -> { entity.setRedactionReason("Vertebrate but a no redaction indicator found"); entity.setRedaction(false); entity.addEngine(Engine.RULE); - entity.addMatchedRule(4); - entity.addReferences($table.getActiveEntitiesOfTypeInSameRow("vertebrate", entity)); - entity.addReferences($table.getActiveEntitiesOfTypeInSameRow("no_redaction_indicator", entity)); + entity.addMatchedRule("CBI.4.1"); + entity.addReferences($table.getEntitiesOfTypeInSameRow("vertebrate", entity)); + entity.addReferences($table.getEntitiesOfTypeInSameRow("no_redaction_indicator", entity)); }); end @@ -172,38 +172,38 @@ rule "CBI.4.1: Dont redact Names and Addresses if no_redaction_indicator is foun rule "CBI.5.0: Redact Names and Addresses if no_redaction_indicator but also redaction_indicator is found in section" when $section: Section(!hasTables(), - hasActiveEntitiesOfType("redaction_indicator"), - hasActiveEntitiesOfType("no_redaction_indicator"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + hasEntitiesOfType("redaction_indicator"), + hasEntitiesOfType("no_redaction_indicator"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(entity -> { entity.setRedactionReason("no_redaction_indicator but also redaction_indicator found"); entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); entity.setRedaction(true); entity.addEngine(Engine.RULE); - entity.addMatchedRule(5); - entity.addReferences($section.getActiveEntitiesOfType("no_redaction_indicator")); - entity.addReferences($section.getActiveEntitiesOfType("redaction_indicator")); + entity.addMatchedRule("CBI.5.0"); + entity.addReferences($section.getEntitiesOfType("no_redaction_indicator")); + entity.addReferences($section.getEntitiesOfType("redaction_indicator")); }); end rule "CBI.5.1: Redact Names and Addresses if no_redaction_indicator but also redaction_indicator is found in Table Row" when - $table: Table(hasActiveEntitiesOfType("no_redaction_indicator"), - hasActiveEntitiesOfType("redaction_indicator"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $table: Table(hasEntitiesOfType("no_redaction_indicator"), + hasEntitiesOfType("redaction_indicator"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $table.streamEntitiesWhereRowContainsActiveEntitiesOfType(List.of("redaction_indicator", "no-redaction_indicator")) + $table.streamEntitiesWhereRowContainsEntitiesOfType(List.of("redaction_indicator", "no-redaction_indicator")) .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) .forEach(entity -> { entity.setRedactionReason("no_redaction_indicator but also redaction_indicator found"); entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); entity.setRedaction(true); entity.addEngine(Engine.RULE); - entity.addMatchedRule(5); - entity.addReferences($table.getActiveEntitiesOfTypeInSameRow("vertebrate", entity)); - entity.addReferences($table.getActiveEntitiesOfTypeInSameRow("no_redaction_indicator", entity)); + entity.addMatchedRule("CBI.5.1"); + entity.addReferences($table.getEntitiesOfTypeInSameRow("vertebrate", entity)); + entity.addReferences($table.getEntitiesOfTypeInSameRow("no_redaction_indicator", entity)); }); end @@ -211,32 +211,32 @@ rule "CBI.5.1: Redact Names and Addresses if no_redaction_indicator but also red // Rule unit: CBI.8 rule "CBI.8.0: Redacted because Section contains must_redact entity" when - $section: Section(!hasTables(), hasActiveEntitiesOfType("must_redact"), (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $section: Section(!hasTables(), hasEntitiesOfType("must_redact"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(entity -> { entity.setRedactionReason("must_redact entity found"); entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); entity.setRedaction(true); entity.addEngine(Engine.RULE); - entity.addMatchedRule(15); - entity.addReferences($section.getActiveEntitiesOfType("must_redact")); + entity.addMatchedRule("CBI.8.0"); + entity.addReferences($section.getEntitiesOfType("must_redact")); }); end rule "CBI.8.1: Redacted because Table Row contains must_redact entity" when - $table: Table(hasActiveEntitiesOfType("must_redact"), (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $table: Table(hasEntitiesOfType("must_redact"), (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $table.streamEntitiesWhereRowContainsActiveEntitiesOfType(List.of("must_redact")) + $table.streamEntitiesWhereRowContainsEntitiesOfType(List.of("must_redact")) .filter(entity -> entity.getType().equals("CBI_author") || entity.getType().equals("CBI_address")) .forEach(entity -> { entity.setRedactionReason("must_redact entity found"); entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); entity.setRedaction(true); entity.addEngine(Engine.RULE); - entity.addMatchedRule(15); - entity.addReferences($table.getActiveEntitiesOfTypeInSameRow("must_redact", entity)); + entity.addMatchedRule("CBI.8.1"); + entity.addReferences($table.getEntitiesOfTypeInSameRow("must_redact", entity)); }); end @@ -254,7 +254,7 @@ rule "CBI.9.0: Redact all Cell's with Header Author(s) as CBI_author (non verteb .map(Optional::get) .forEach(redactionEntity -> { redactionEntity.setRedaction(true); - redactionEntity.addMatchedRule(9); + redactionEntity.addMatchedRule("CBI.9.0"); redactionEntity.addEngine(Engine.RULE); redactionEntity.setRedactionReason("Author(s) found"); redactionEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); @@ -274,7 +274,7 @@ rule "CBI.9.1: Redact all Cell's with Header Author as CBI_author (non vertebrat .map(Optional::get) .forEach(redactionEntity -> { redactionEntity.setRedaction(true); - redactionEntity.addMatchedRule(9); + redactionEntity.addMatchedRule("CBI.9.1"); redactionEntity.addEngine(Engine.RULE); redactionEntity.setRedactionReason("Author found"); redactionEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); @@ -290,7 +290,7 @@ rule "CBI.11.0: Recommend all CBI_author entities in Table with Vertebrate Study when $table: Table(hasHeader("Author(s)") && hasHeader("Vertebrate Study Y/N")) then - $table.getActiveEntitiesOfType("CBI_author").forEach(entity -> dictionary.addMultipleAuthorsAsRecommendation(entity)); + $table.getEntitiesOfType("CBI_author").forEach(entity -> dictionary.addMultipleAuthorsAsRecommendation(entity)); end @@ -308,7 +308,7 @@ rule "CBI.12.0: Add all Cell's with Header Author(s) as CBI_author" .filter(Optional::isPresent) .map(Optional::get) .forEach(redactionEntity -> { - redactionEntity.addMatchedRule(12); + redactionEntity.addMatchedRule("CBI.12.0"); redactionEntity.setRedactionReason("Author(s) header found"); redactionEntity.addEngine(Engine.RULE); insert(redactionEntity); @@ -324,7 +324,7 @@ rule "CBI.12.1: Dont redact CBI_author, if its row contains a cell with header \ .forEach(authorEntity -> { authorEntity.setRedaction(false); authorEntity.setRedactionReason("Not redacted because it's row does not belong to a vertebrate study"); - authorEntity.addMatchedRule(12); + authorEntity.addMatchedRule("CBI.12.1"); }); end @@ -338,7 +338,7 @@ rule "CBI.12.2: Redact CBI_author, if its row contains a cell with header \"Vert authorEntity.setRedaction(true); authorEntity.setRedactionReason("Redacted because it's row belongs to a vertebrate study"); authorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - authorEntity.addMatchedRule(12); + authorEntity.addMatchedRule("CBI.12.2"); }); end @@ -351,7 +351,7 @@ rule "CBI.14.0: Redact CBI_sponsor entities if preceded by \"batches produced at $sponsorEntity.setRedaction(true); $sponsorEntity.setRedactionReason("Redacted because it represents a sponsor company"); $sponsorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - $sponsorEntity.addMatchedRule(14); + $sponsorEntity.addMatchedRule("CBI.14.0"); end @@ -375,10 +375,10 @@ rule "CBI.15.0: Redact row if row contains \"determination of residues\" and liv entityCreationService.byString($keyword, "must_redact", EntityType.ENTITY, $section) .forEach(keywordEntity -> insert(keywordEntity)); - $section.getActiveEntitiesOfType(List.of($keyword, $residueKeyword)) + $section.getEntitiesOfType(List.of($keyword, $residueKeyword)) .forEach(redactionEntity -> { redactionEntity.setRedaction(true); - redactionEntity.addMatchedRule(15); + redactionEntity.addMatchedRule("CBI.15.0"); redactionEntity.setRedactionReason("Determination of residues and keyword \"" + $keyword + "\" was found."); redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); }); @@ -405,7 +405,7 @@ rule "CBI.15.1: Redact CBI_author and CBI_address if row contains \"determinatio .filter(redactionEntity -> redactionEntity.isAnyType(List.of("CBI_author", "CBI_address"))) .forEach(redactionEntity -> { redactionEntity.setRedaction(true); - redactionEntity.addMatchedRule(15); + redactionEntity.addMatchedRule("CBI.15.1"); redactionEntity.setRedactionReason("Determination of residues and keyword \"" + $keyword + "\" was found."); redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); }); @@ -424,7 +424,7 @@ rule "CBI.16.0: Add CBI_author with \"et al.\" Regex (non vertebrate study)" entity.setRedaction(true); entity.setRedactionReason("Author found by \"et al\" regex"); entity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); - entity.addMatchedRule(18); + entity.addMatchedRule("CBI.16.0"); entity.addEngine(Engine.RULE); insert(entity); dictionary.addLocalDictionaryEntry("CBI_author", entity.getValue(), false); @@ -442,7 +442,7 @@ rule "CBI.16.1: Add CBI_author with \"et al.\" Regex (vertebrate study)" entity.setRedaction(true); entity.setRedactionReason("Author found by \"et al\" regex"); entity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); - entity.addMatchedRule(19); + entity.addMatchedRule("CBI.16.1"); entity.addEngine(Engine.RULE); insert(entity); dictionary.addLocalDictionaryEntry("CBI_author", entity.getValue(), false); @@ -459,7 +459,7 @@ rule "CBI.17.0: Add recommendation for Addresses in Test Organism sections, with .forEach(entity -> { entity.setRedactionReason("Line after \"Source\" in Test Organism Section"); entity.addEngine(Engine.RULE); - entity.addMatchedRule(20); + entity.addMatchedRule("CBI.17.0"); insert(entity); }); end @@ -472,7 +472,7 @@ rule "CBI.17.1: Add recommendation for Addresses in Test Organism sections, with .forEach(entity -> { entity.setRedactionReason("Line after \"Source:\" in Test Animals Section"); entity.addEngine(Engine.RULE); - entity.addMatchedRule(20); + entity.addMatchedRule("CBI.17.1"); insert(entity); }); end @@ -489,7 +489,7 @@ rule "CBI.18.0: Expand CBI_author entities with firstname initials" ) then RedactionEntity expandedEntity = entityCreationService.bySuffixExpansionRegex($entityToExpand, "(,? [A-Z]\\.?( ?[A-Z]\\.?)?( ?[A-Z]\\.?)?\\b\\.?)"); - expandedEntity.addMatchedRule(0); + expandedEntity.addMatchedRule("CBI.18.0"); $entityToExpand.removeFromGraph(); retract($entityToExpand); insert(expandedEntity); @@ -502,7 +502,7 @@ rule "CBI.19.0: Expand CBI_author entities with salutation prefix" $entityToExpand: RedactionEntity(type == "CBI_author", anyMatch(textBefore, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*")) then RedactionEntity expandedEntity = entityCreationService.byPrefixExpansionRegex($entityToExpand, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*"); - expandedEntity.addMatchedRule(0); + expandedEntity.addMatchedRule("CBI.19.0"); insert(expandedEntity); end @@ -517,7 +517,7 @@ rule "CBI.20.0: Redact between \"PERFORMING LABORATORY\" and \"LABORATORY PROJEC entityCreationService.betweenStrings("PERFORMING LABORATORY:", "LABORATORY PROJECT ID:", "CBI_address", EntityType.ENTITY, $section) .forEach(laboratoryEntity -> { laboratoryEntity.setRedaction(false); - laboratoryEntity.addMatchedRule(20); + laboratoryEntity.addMatchedRule("CBI.20.0"); laboratoryEntity.addEngine(Engine.RULE); laboratoryEntity.setRedactionReason("PERFORMING LABORATORY was found for non vertebrate study"); dictionary.addLocalDictionaryEntry(laboratoryEntity); @@ -534,7 +534,7 @@ rule "CBI.20.1: Redact between \"PERFORMING LABORATORY\" and \"LABORATORY PROJEC entityCreationService.betweenStrings("PERFORMING LABORATORY:", "LABORATORY PROJECT ID:", "CBI_address", EntityType.ENTITY, $section) .forEach(laboratoryEntity -> { laboratoryEntity.setRedaction(true); - laboratoryEntity.addMatchedRule(20); + laboratoryEntity.addMatchedRule("CBI.20.1"); laboratoryEntity.addEngine(Engine.RULE); laboratoryEntity.setRedactionReason("PERFORMING LABORATORY was found"); laboratoryEntity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); @@ -555,7 +555,7 @@ rule "PII.0.0: Redact all PII (non vertebrate study)" $pii.setRedaction(true); $pii.setRedactionReason("Personal Information found"); $pii.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); - $pii.addMatchedRule(0); + $pii.addMatchedRule("PII.0.0"); end rule "PII.0.1: Redact all PII (vertebrate study)" @@ -566,7 +566,7 @@ rule "PII.0.1: Redact all PII (vertebrate study)" $pii.setRedaction(true); $pii.setRedactionReason("Personal Information found"); $pii.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); - $pii.addMatchedRule(0); + $pii.addMatchedRule("PII.0.1"); end @@ -582,7 +582,7 @@ rule "PII.1.0: Redact Emails by RegEx (Non vertebrate study)" emailEntity.addEngine(Engine.RULE); emailEntity.setRedactionReason("Found by Email Regex"); emailEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); - emailEntity.addMatchedRule(1); + emailEntity.addMatchedRule("PII.1.0"); insert(emailEntity); }); end @@ -598,7 +598,7 @@ rule "PII.1.1: Redact Emails by RegEx (vertebrate study)" emailEntity.addEngine(Engine.RULE); emailEntity.setRedactionReason("Found by Email Regex"); emailEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); - emailEntity.addMatchedRule(1); + emailEntity.addMatchedRule("PII.1.1"); insert(emailEntity); }); end @@ -632,7 +632,7 @@ rule "PII.4.0: Redact line after contact information keywords (non vertebrate st entityCreationService.lineAfterString($contactKeyword, "PII", EntityType.ENTITY, $section) .forEach(contactEntity -> { contactEntity.setRedaction(true); - contactEntity.addMatchedRule(4); + contactEntity.addMatchedRule("PII.4.0"); contactEntity.addEngine(Engine.RULE); contactEntity.setRedactionReason("Found after \"" + $contactKeyword + "\" contact keyword"); contactEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); @@ -667,7 +667,7 @@ rule "PII.4.1: Redact line after contact information keywords (non vertebrate st entityCreationService.lineAfterString($contactKeyword, "PII", EntityType.ENTITY, $section) .forEach(contactEntity -> { contactEntity.setRedaction(true); - contactEntity.addMatchedRule(4); + contactEntity.addMatchedRule("PII.4.1"); contactEntity.addEngine(Engine.RULE); contactEntity.setRedactionReason("Found after \"" + $contactKeyword + "\" contact keyword"); contactEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); @@ -688,7 +688,7 @@ rule "PII.6.0: redact line between contact keywords (non vertebrate study)" ) .forEach(contactEntity -> { contactEntity.setRedaction(true); - contactEntity.addMatchedRule(6); + contactEntity.addMatchedRule("PII.6.0"); contactEntity.addEngine(Engine.RULE); contactEntity.setRedactionReason("Found between contact keywords"); contactEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); @@ -707,7 +707,7 @@ rule "PII.6.1: redact line between contact keywords" ) .forEach(contactEntity -> { contactEntity.setRedaction(true); - contactEntity.addMatchedRule(6); + contactEntity.addMatchedRule("PII.6.1"); contactEntity.addEngine(Engine.RULE); contactEntity.setRedactionReason("Found between contact keywords"); contactEntity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); @@ -736,7 +736,7 @@ rule "PII.7.0: Redact contact information if applicant is found (non vertebrate entity.setRedaction(true); entity.setRedactionReason("Applicant information was found"); entity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); - entity.addMatchedRule(7); + entity.addMatchedRule("PII.7.0"); entity.addEngine(Engine.RULE); insert(entity); }); @@ -761,7 +761,7 @@ rule "PII.7.1: Redact contact information if applicant is found (non vertebrate entity.setRedaction(true); entity.setRedactionReason("Applicant information was found"); entity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); - entity.addMatchedRule(7); + entity.addMatchedRule("PII.7.1"); entity.addEngine(Engine.RULE); insert(entity); }); @@ -788,7 +788,7 @@ rule "PII.8.0: Redact contact information if producer is found" entity.setRedaction(true); entity.setRedactionReason("Producer was found"); entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); - entity.addMatchedRule(8); + entity.addMatchedRule("PII.8.0"); entity.addEngine(Engine.RULE); insert(entity); }); @@ -813,7 +813,7 @@ rule "PII.8.1: Redact contact information if producer is found" entity.setRedaction(true); entity.setRedactionReason("Producer was found"); entity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); - entity.addMatchedRule(8); + entity.addMatchedRule("PII.8.1"); entity.addEngine(Engine.RULE); insert(entity); }); @@ -829,7 +829,7 @@ rule "PII.9.0: Redact between \"AUTHOR(S)\" and \"COMPLETION DATE\" (non vertebr entityCreationService.betweenStrings("AUTHOR(S):", "COMPLETION DATE:", "PII", EntityType.ENTITY, $section) .forEach(authorEntity -> { authorEntity.setRedaction(true); - authorEntity.addMatchedRule(9); + authorEntity.addMatchedRule("PII.9.0"); authorEntity.addEngine(Engine.RULE); authorEntity.setRedactionReason("AUTHOR(S) was found"); authorEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); @@ -845,7 +845,7 @@ rule "PII.9.1: Redact between \"AUTHOR(S)\" and \"STUDY COMPLETION DATE\" (non v entityCreationService.betweenStrings("AUTHOR(S):", "COMPLETION DATE:", "PII", EntityType.ENTITY, $section) .forEach(authorEntity -> { authorEntity.setRedaction(true); - authorEntity.addMatchedRule(9); + authorEntity.addMatchedRule("PII.9.1"); authorEntity.addEngine(Engine.RULE); authorEntity.setRedactionReason("AUTHOR(S) was found"); authorEntity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); @@ -861,7 +861,7 @@ rule "PII.9.2: Redact between \"AUTHOR(S)\" and \"COMPLETION DATE\" (non vertebr entityCreationService.betweenStrings("AUTHOR(S):", "STUDY COMPLETION DATE:", "PII", EntityType.ENTITY, $section) .forEach(authorEntity -> { authorEntity.setRedaction(true); - authorEntity.addMatchedRule(9); + authorEntity.addMatchedRule("PII.9.2"); authorEntity.addEngine(Engine.RULE); authorEntity.setRedactionReason("AUTHOR(S) was found"); authorEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); @@ -877,7 +877,7 @@ rule "PII.9.3: Redact between \"AUTHOR(S)\" and \"STUDY COMPLETION DATE\" (verte entityCreationService.betweenStrings("AUTHOR(S):", "STUDY COMPLETION DATE:", "PII", EntityType.ENTITY, $section) .forEach(authorEntity -> { authorEntity.setRedaction(true); - authorEntity.addMatchedRule(9); + authorEntity.addMatchedRule("PII.9.3"); authorEntity.addEngine(Engine.RULE); authorEntity.setRedactionReason("AUTHOR(S) was found"); authorEntity.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); @@ -886,13 +886,29 @@ rule "PII.9.3: Redact between \"AUTHOR(S)\" and \"STUDY COMPLETION DATE\" (verte end +// Rule unit: PII.11 +rule "PII.11.0: Redact On behalf of Sequani Ltd.:" + when + $section: Section(!hasTables(), containsString("On behalf of Sequani Ltd.: Name Title")) + then + entityCreationService.betweenStrings("On behalf of Sequani Ltd.: Name Title", "On behalf of", "PII", EntityType.ENTITY, $section) + .forEach(authorEntity -> { + authorEntity.setRedaction(true); + authorEntity.addMatchedRule("PII.11.0"); + authorEntity.setRedactionReason("On behalf of Sequani Ltd.: Name Title was found"); + authorEntity.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + insert(authorEntity); + }); + end + + // Rule unit: PII.12 rule "PII.12.0: Expand PII entities with salutation prefix" when $entityToExpand: RedactionEntity(type == "PII", anyMatch(textBefore, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*")) then RedactionEntity expandedEntity = entityCreationService.byPrefixExpansionRegex($entityToExpand, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*"); - expandedEntity.addMatchedRule(12); + expandedEntity.addMatchedRule("PII.12.0"); insert(expandedEntity); end @@ -906,7 +922,7 @@ rule "ETC.1.0: Redact Purity" then entityCreationService.byRegex("\\bPurity:\\s*(?\\s*\\d{1,2}(?:\\.\\d{1,2})?\\s*%)", "purity", EntityType.ENTITY, 1, $section) .forEach(entity -> { - entity.addMatchedRule(1); + entity.addMatchedRule("ETC.1.0"); entity.addEngine(Engine.RULE); entity.setRedaction(true); entity.setRedactionReason("Purity found"); @@ -922,7 +938,7 @@ rule "ETC.2.0: Redact signatures (non vertebrate study)" $signature: Image(imageType == ImageType.SIGNATURE) then $signature.setRedaction(true); - $signature.setMatchedRule(2); + $signature.setMatchedRule("ETC.2.0"); $signature.setRedactionReason("Signature Found"); $signature.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); end @@ -933,7 +949,7 @@ rule "ETC.2.0: Redact signatures (vertebrate study)" $signature: Image(imageType == ImageType.SIGNATURE) then $signature.setRedaction(true); - $signature.setMatchedRule(2); + $signature.setMatchedRule("ETC.2.0"); $signature.setRedactionReason("Signature Found"); $signature.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); end @@ -946,7 +962,7 @@ rule "ETC.3.0: Redact logos (vertebrate study)" $logo: Image(imageType == ImageType.LOGO) then $logo.setRedaction(true); - $logo.setMatchedRule(3); + $logo.setMatchedRule("ETC.3.0"); $logo.setRedactionReason("Logo Found"); $logo.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); end @@ -957,7 +973,7 @@ rule "ETC.3.1: Redact logos (non vertebrate study)" $logo: Image(imageType == ImageType.LOGO) then $logo.setRedaction(true); - $logo.setMatchedRule(3); + $logo.setMatchedRule("ETC.3.1"); $logo.setRedactionReason("Logo Found"); $logo.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); end @@ -969,7 +985,7 @@ rule "ETC.4.0: Redact dossier dictionary entries" $dossierRedaction: RedactionEntity(type == "dossier_redaction") then $dossierRedaction.setRedaction(true); - $dossierRedaction.addMatchedRule(4); + $dossierRedaction.addMatchedRule("ETC.4.0"); $dossierRedaction.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); $dossierRedaction.setRedactionReason("Specification of impurity found"); end @@ -997,7 +1013,7 @@ rule "ETC.6.0: Redact CAS Number" .map(Optional::get) .forEach(redactionEntity -> { redactionEntity.setRedaction(true); - redactionEntity.addMatchedRule(101); + redactionEntity.addMatchedRule("ETC.6.0"); redactionEntity.setRedactionReason("Sample # found in Header"); redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); insert(redactionEntity); @@ -1024,7 +1040,7 @@ rule "ETC.8.0: Redact formulas (vertebrate study)" $logo: Image(imageType == ImageType.FORMULA) then $logo.setRedaction(true); - $logo.setMatchedRule(3); + $logo.setMatchedRule("ETC.8.0"); $logo.setRedactionReason("Logo Found"); $logo.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); end @@ -1035,7 +1051,7 @@ rule "ETC.8.1: Redact formulas (non vertebrate study)" $logo: Image(imageType == ImageType.FORMULA) then $logo.setRedaction(true); - $logo.setMatchedRule(3); + $logo.setMatchedRule("ETC.8.1"); $logo.setRedactionReason("Logo Found"); $logo.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); end diff --git a/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules_backup.drl b/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules_backup.drl deleted file mode 100644 index 9baf5a54..00000000 --- a/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules_backup.drl +++ /dev/null @@ -1,697 +0,0 @@ -package drools - -import static java.lang.String.format; -import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility.anyMatch; -import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility.exactMatch; -import static com.iqser.red.service.redaction.v1.server.layoutparsing.document.utils.RedactionSearchUtility.exactMatch; - -import java.util.List; -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.layoutparsing.document.graph.entity.EntityType; -import com.iqser.red.service.redaction.v1.server.layoutparsing.document.graph.nodes.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.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.Boundary; -import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntitiesAdapter; -import com.iqser.red.service.redaction.v1.server.redaction.adapter.NerEntities; -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.RedactionSearchUtility; - -global Document document -global EntityCreationService entityCreationService -global ManualRedactionApplicationService manualRedactionApplicationService -global NerEntitiesAdapter nerEntitiesAdapter -global Dictionary dictionary - -// --------------------------------------- queries ------------------------------------------------------------------- - -query "getFileAttributes" - $fileAttribute: FileAttribute() - end - -// --------------------------------------- CBI rules ------------------------------------------------------------------- - -rule "0: Expand CBI_author entities with firstname initials" - no-loop true - when - $entityToExpand: RedactionEntity(type == "CBI_author", - value.matches("[^\\s]+"), - textAfter.startsWith(" "), - anyMatch(textAfter, "(,? [A-Z]\\.?( ?[A-Z]\\.?)?( ?[A-Z]\\.?)?\\b\\.?)") - ) - then - RedactionEntity expandedEntity = entityCreationService.bySuffixExpansionRegex($entityToExpand, "(,? [A-Z]\\.?( ?[A-Z]\\.?)?( ?[A-Z]\\.?)?\\b\\.?)"); - expandedEntity.addMatchedRule(0); - $entityToExpand.removeFromGraph(); - retract($entityToExpand); - insert(expandedEntity); - end - -rule "0: Expand CBI_author and PII entities with salutation prefix" - when - $entityToExpand: RedactionEntity((type == "CBI_author" || type == "PII"), anyMatch(textBefore, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*")) - then - RedactionEntity expandedEntity = entityCreationService.byPrefixExpansionRegex($entityToExpand, "\\b(Mrs?|Ms|Miss|Sir|Madame?|Mme)\\s?\\.?\\s*"); - expandedEntity.addMatchedRule(0); - insert(expandedEntity); - end - -rule "1: Redacted because Section contains Vertebrate" - when - $section: Section(hasActiveEntitiesOfType("vertebrate"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) - then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) - .forEach(redactionEntity -> { - redactionEntity.setRedaction(true); - redactionEntity.addMatchedRule(1); - redactionEntity.setRedactionReason("Vertebrate Found in this section"); - redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - }); - end - -rule "2: Not Redacted because Section contains no Vertebrate" - when - $section: Section(!hasActiveEntitiesOfType("vertebrate"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) - then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) - .forEach(redactionEntity -> { - redactionEntity.setRedaction(false); - redactionEntity.addMatchedRule(2); - redactionEntity.setRedactionReason("No Vertebrate Found in this section"); - }); - end - -rule "3: Do not redact Names and Addresses if no redaction Indicator is contained" - when - $section: Section(hasActiveEntitiesOfType("vertebrate"), - hasActiveEntitiesOfType("no_redaction_indicator"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) - then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) - .forEach(redactionEntity -> { - redactionEntity.setRedaction(false); - redactionEntity.addMatchedRule(3); - redactionEntity.setRedactionReason("Vertebrate and a no-redaction-indicator found in this section"); - }); - end - -rule "4: Redact Names and Addresses if no_redaction_indicator and redaction_indicator is contained" - when - $section: Section(hasActiveEntitiesOfType("vertebrate"), - hasActiveEntitiesOfType("no_redaction_indicator"), - hasActiveEntitiesOfType("redaction_indicator"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) - then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) - .forEach(redactionEntity -> { - redactionEntity.setRedaction(true); - redactionEntity.addMatchedRule(4); - redactionEntity.setRedactionReason("Vertebrate and a no-redaction-indicator, but also redaction-indicator, found in this section"); - redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - }); - end - -rule "5: Do not redact Names and Addresses if published information found" - - when - $section: Section(hasActiveEntitiesOfType("vertebrate"), - hasActiveEntitiesOfType("published_information"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) - then - List publishedInformationEntities = $section.getActiveEntitiesOfType("published_information"); - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) - .forEach(redactionEntity -> { - redactionEntity.setRedaction(false); - redactionEntity.setRedactionReason("Vertebrate but also Published Information found in this section"); - redactionEntity.addReferences(publishedInformationEntities); - }); - end - -rule "6.0: Add all Cell's with Header Author(s) as CBI_author" - when - $table: Table(hasHeader("Author(s)")) - then - $table.streamTableCellsWithHeader("Author(s)") - .map(tableCell -> entityCreationService.bySemanticNode(tableCell, "CBI_author", EntityType.ENTITY)) - .forEach(redactionEntity -> { - redactionEntity.addMatchedRule(6); - redactionEntity.setRedactionReason("Author(s) header found"); - insert(redactionEntity); - }); - end - -rule "6.1: Dont redact CBI_author, if its row contains a cell with header \"Vertebrate study Y/N\" and value No" - when - $table: Table(hasRowWithHeaderAndValue("Vertebrate study Y/N", "N") || hasRowWithHeaderAndValue("Vertebrate study Y/N", "No")) - then - $table.streamEntitiesWhereRowHasHeaderAndAnyValue("Vertebrate study Y/N", List.of("N", "No")) - .filter(redactionEntity -> redactionEntity.isAnyType(List.of("CBI_author", "CBI_address"))) - .forEach(authorEntity -> { - authorEntity.setRedaction(false); - authorEntity.setRedactionReason("Not redacted because it's row does not belong to a vertebrate study"); - authorEntity.setLegalBasis(""); - authorEntity.addMatchedRule(6); - }); - end - -rule "7: Redact CBI_author, if its row contains a cell with header \"Vertebrate study Y/N\" and value Yes" - when - $table: Table(hasRowWithHeaderAndValue("Vertebrate study Y/N", "Y") || hasRowWithHeaderAndValue("Vertebrate study Y/N", "Yes")) - then - $table.streamEntitiesWhereRowHasHeaderAndAnyValue("Vertebrate study Y/N", List.of("Y", "Yes")) - .filter(redactionEntity -> redactionEntity.isAnyType(List.of("CBI_author", "CBI_address"))) - .forEach(authorEntity -> { - authorEntity.setRedaction(true); - authorEntity.setRedactionReason("Redacted because it's row belongs to a vertebrate study"); - authorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - authorEntity.addMatchedRule(7); - }); - end - -rule "8: Redact if must_redact entity is found" - when - $section: Section(hasActiveEntitiesOfType("must_redact"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) - then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) - .forEach(redactionEntity -> { - redactionEntity.setRedaction(true); - redactionEntity.setRedactionReason("must_redact entry was found."); - redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - redactionEntity.addMatchedRule(8); - }); - end - -rule "9: Redact CBI_sponsor entities if preceded by \" batches produced at\"" - when - $sponsorEntity: RedactionEntity(type == "CBI_sponsor", textBefore.contains("batches produced at")) - then - $sponsorEntity.setRedaction(true); - $sponsorEntity.setRedactionReason("Redacted because it represents a sponsor company"); - $sponsorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - $sponsorEntity.addMatchedRule(9); - end - -rule "10: Redact row if row contains \"determination of residues\" and livestock keyword" - when - $keyword: String() from List.of("livestock", - "live stock", - "tissue", - "tissues", - "liver", - "muscle", - "bovine", - "ruminant", - "ruminants") - $residueKeyword: String() from List.of("determination of residues", "determination of total residues") - $table: Table(containsStringIgnoreCase($residueKeyword) - && containsStringIgnoreCase($keyword)) - then - entityCreationService.byString($keyword, "must_redact", EntityType.ENTITY, $table) - .forEach(keywordEntity -> insert(keywordEntity)); - - $table.streamEntitiesWhereRowContainsStringsIgnoreCase(List.of($keyword, $residueKeyword)) - .filter(redactionEntity -> redactionEntity.isAnyType(List.of("CBI_author", "CBI_address"))) - .forEach(redactionEntity -> { - redactionEntity.setRedaction(true); - redactionEntity.setRedactionReason("Determination of residues and keyword \"" + $keyword + "\" was found."); - redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - redactionEntity.addMatchedRule(10); - }); - end - -rule "11: Redact if CTL/* or BL/* was found" - when - $section: Section(excludesTables, (containsString("CTL/") || containsString("BL/"))) - then - entityCreationService.byString("CTL/", "must_redact", EntityType.ENTITY, $section) - .forEach(mustRedactEntity -> insert(mustRedactEntity)); - entityCreationService.byString("BL/", "must_redact", EntityType.ENTITY, $section) - .forEach(mustRedactEntity -> insert(mustRedactEntity)); - - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) - .forEach(redactionEntity -> { - redactionEntity.setRedaction(true); - redactionEntity.setRedactionReason("Laboratory for vertebrate studies found"); - redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - redactionEntity.addMatchedRule(11); - }); - end - -rule "12: Add CBI_author with \"et al.\" Regex" - agenda-group "LOCAL_DICTIONARY_ADDS" - when - $section: Section(containsString("et al.")) - then - entityCreationService.byRegex("\\b([A-ZÄÖÜ][^\\s\\.,]+( [A-ZÄÖÜ]{1,2}\\.?)?( ?[A-ZÄÖÜ]\\.?)?) et al\\.?", "CBI_author", EntityType.ENTITY, $section) - .forEach(entity -> { - entity.setRedaction(true); - entity.setRedactionReason("Author found by \"et al\" regex"); - entity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - entity.addMatchedRule(12); - insert(entity); - dictionary.addLocalDictionaryEntry("CBI_author", entity.getValue(), false); - }); - end - -rule "13: Add recommendation for Addresses in Test Organism sections" - when - $section: Section(excludesTables, containsString("Species") && containsString("Source") && !containsString("Species:") && !containsString("Source:")) - then - entityCreationService.lineAfterString("Source", "CBI_address", EntityType.RECOMMENDATION, $section) - .forEach(redactionEntity -> { - redactionEntity.setRedactionReason("Line after \"Source\" in Test Organism Section"); - redactionEntity.addMatchedRule(13); - insert(redactionEntity); - }); - end - -rule "14: Add recommendation for Addresses in Test Animals sections" - - when - $section: Section(excludesTables, containsString("Species:"), containsString("Source:")) - then - entityCreationService.lineAfterString("Source:", "CBI_address", EntityType.RECOMMENDATION, $section) - .forEach(redactionEntity -> { - redactionEntity.setRedactionReason("Line after \"Source:\" in Test Animals Section"); - redactionEntity.addMatchedRule(14); - insert(redactionEntity); - }); - end - -// --------------------------------------- PII rules ------------------------------------------------------------------- - -rule "15: Redact all PII" - when - $pii: RedactionEntity(type == "PII", redaction == false) - then - $pii.setRedaction(true); - $pii.setRedactionReason("PII found"); - $pii.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); - $pii.addMatchedRule(15); - end - -rule "16: Redact Emails by RegEx (Non vertebrate study)" - when - $section: Section(containsString("@")) - then - entityCreationService.byRegex("\\b([A-Za-z0-9._%+\\-]+@[A-Za-z0-9.\\-]+\\.[A-Za-z\\-]{1,23}[A-Za-z])\\b", "PII", EntityType.ENTITY, $section) - .forEach(emailEntity -> { - emailEntity.setRedaction(true); - emailEntity.setRedactionReason("Found by Email Regex"); - emailEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); - emailEntity.addMatchedRule(16); - insert(emailEntity); - }); - end - -rule "17: Redact line after contact information keywords" - agenda-group "LOCAL_DICTIONARY_ADDS" - when - $contactKeyword: String() from List.of("Contact point:", - "Contact:", - "Alternative contact:", - "European contact:", - "No:", - "Contact:", - "Tel.:", - "Tel:", - "Telephone number:", - "Telephone No:", - "Telephone:", - "Phone No.", - "Phone:", - "Fax number:", - "Fax:", - "E-mail:", - "Email:", - "e-mail:", - "E-mail address:") - $section: Section(containsString($contactKeyword)) - then - entityCreationService.lineAfterString($contactKeyword, "PII", EntityType.ENTITY, $section) - .forEach(contactEntity -> { - contactEntity.setRedaction(true); - contactEntity.addMatchedRule(17); - contactEntity.setRedactionReason("Found after \"" + $contactKeyword + "\" contact keyword"); - contactEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); - insert(contactEntity); - dictionary.addLocalDictionaryEntry("PII", contactEntity.getValue(), false); - }); - end - - -rule "18: redact line between contact keywords" - agenda-group "LOCAL_DICTIONARY_ADDS" - when - $section: Section((containsString("No:") && containsString("Fax")) || (containsString("Contact:") && containsString("Tel"))) - then - Stream.concat( - entityCreationService.betweenStrings("No:", "Fax", "PII", EntityType.ENTITY, $section), - entityCreationService.betweenStrings("Contact:", "Tel", "PII", EntityType.ENTITY, $section) - ) - .forEach(contactEntity -> { - contactEntity.setRedaction(true); - contactEntity.addMatchedRule(18); - contactEntity.setRedactionReason("Found between contact keywords"); - contactEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); - insert(contactEntity); - dictionary.addLocalDictionaryEntry("PII", contactEntity.getValue(), false); - }); - end - -rule "19: Redact AUTHOR(S)" - when - FileAttribute(placeholder == "{fileattributes.vertebrateStudy}", value == "true") - $section: Section(excludesTables, containsString("AUTHOR(S):"), containsString("COMPLETION DATE:")) - then - entityCreationService.betweenStrings("AUTHOR(S):", "COMPLETION DATE:", "PII", EntityType.ENTITY, $section) - .forEach(authorEntity -> { - authorEntity.setRedaction(true); - authorEntity.addMatchedRule(19); - authorEntity.setRedactionReason("AUTHOR(S) was found"); - authorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); - insert(authorEntity); - }); - end - -rule "20: Redact PERFORMING LABORATORY" - when - $section: Section(excludesTables, containsString("PERFORMING LABORATORY:")) - then - entityCreationService.betweenStrings("PERFORMING LABORATORY:", "COMPLETION DATE:", "PII", EntityType.ENTITY, $section) - .forEach(authorEntity -> { - authorEntity.setRedaction(true); - authorEntity.addMatchedRule(20); - authorEntity.setRedactionReason("PERFORMING LABORATORY was found"); - authorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); - insert(authorEntity); - }); - end - -rule "21: Redact On behalf of Sequani Ltd.:" - when - $section: Section(excludesTables, containsString("On behalf of Sequani Ltd.: Name Title")) - then - entityCreationService.betweenStrings("On behalf of Sequani Ltd.: Name Title", "On behalf of", "PII", EntityType.ENTITY, $section) - .forEach(authorEntity -> { - authorEntity.setRedaction(true); - authorEntity.addMatchedRule(21); - authorEntity.setRedactionReason("On behalf of Sequani Ltd.: Name Title was found"); - authorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); - insert(authorEntity); - }); - end - -rule "22: Redact On behalf of Syngenta Ltd.:" - when - $section: Section(excludesTables, containsString("On behalf of Syngenta Ltd.: Name Title")) - then - entityCreationService.betweenStrings("On behalf of Syngenta Ltd.: Name Title", "Study dates", "PII", EntityType.ENTITY, $section) - .forEach(authorEntity -> { - authorEntity.setRedaction(true); - authorEntity.addMatchedRule(21); - authorEntity.setRedactionReason("On behalf of Syngenta Ltd.: Name Title was found"); - authorEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2e)"); - insert(authorEntity); - }); - end - -rule "26: Redact signatures" - when - $signature: Image(imageType == ImageType.SIGNATURE) - then - $signature.setRedaction(true); - $signature.setMatchedRule(26); - $signature.setRedactionReason("Signature Found"); - $signature.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - end - -rule "27: Redact formulas" - when - $formula: Image(imageType == ImageType.FORMULA) - then - $formula.setRedaction(true); - $formula.setMatchedRule(27); - $formula.setRedactionReason("Formula Found"); - $formula.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - end - -rule "28: Redact logos" - when - $logo: Image(imageType == ImageType.LOGO) - then - $logo.setRedaction(true); - $logo.setMatchedRule(28); - $logo.setRedactionReason("Logo Found"); - $logo.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - end - -rule "29: Redact Dossier Redactions" - when - $dossierRedaction: RedactionEntity(type == "dossier_redactions") - then - $dossierRedaction.setRedaction(true); - $dossierRedaction.addMatchedRule(29); - $dossierRedaction.setRedactionReason("Dossier Redaction found"); - $dossierRedaction.setLegalBasis("Article 39(1)(2) of Regulation (EC) No 178/2002"); - end - -rule "30: Remove Dossier redactions if file is confidential" - when - FileAttribute(label == "Confidentiality", value == "confidential") - $dossierRedaction: RedactionEntity(type == "dossier_redactions") - then - $dossierRedaction.removeFromGraph(); - retract($dossierRedaction) - end - -rule "101: Redact CAS Number" - when - $table: Table(hasHeader("Sample #")) - then - $table.streamTableCellsWithHeader("Sample #") - .map(tableCell -> entityCreationService.bySemanticNode(tableCell, "PII", EntityType.ENTITY)) - .forEach(redactionEntity -> { - redactionEntity.setRedaction(true); - redactionEntity.addMatchedRule(101); - redactionEntity.setRedactionReason("Sample # found in Header"); - redactionEntity.setLegalBasis("Reg (EC) No 1107/2009 Art. 63 (2g)"); - insert(redactionEntity); - }); - end - -rule "102: Guidelines FileAttributes" - when - $section: Section(excludesTables, (containsString("DATA REQUIREMENT(S):") || containsString("TEST GUIDELINE(S):")) && (containsString("OECD") || containsString("EPA") || containsString("OPPTS"))) - then - RedactionSearchUtility.findBoundariesByRegex("OECD (No\\.? )?\\d{3}( \\(\\d{4}\\))?", $section.getTextBlock()).stream() - .map(boundary -> $section.getTextBlock().subSequence(boundary).toString()) - .map(value -> FileAttribute.builder().label("OECD Number").value(value).build()) - .forEach(fileAttribute -> insert(fileAttribute)); - end - -// --------------------------------------- NER Entities rules ------------------------------------------------------------------- - -rule "add NER Entities of type CBI_author" - salience 999 - when - nerEntities: NerEntities(hasEntitiesOfType("CBI_author")) - then - nerEntities.streamEntitiesOfType("CBI_author") - .map(nerEntity -> entityCreationService.byNerEntity(nerEntity, EntityType.RECOMMENDATION, document)) - .forEach(entity -> insert(entity)); - end - -rule "combine and add NER Entities as CBI_address" - salience 999 - when - nerEntities: NerEntities(hasEntitiesOfType("ORG") || hasEntitiesOfType("STREET") || hasEntitiesOfType("CITY")) - then - nerEntitiesAdapter.combineNerEntitiesToCbiAddressDefaults(nerEntities) - .map(boundary -> entityCreationService.byBoundary(boundary, "CBI_address", EntityType.RECOMMENDATION, document)) - .forEach(entity -> { - entity.addEngine(Engine.NER); - insert(entity); - }); - end - -// --------------------------------------- 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) - $entityToBeRemoved: Image($id == id) - then - $entityToBeRemoved.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: Image($id == id) - then - $image.setImageType(ImageType.fromString($imageType)); - end - -// --------------------------------------- merging rules ------------------------------------------------------------------- - -rule "remove Entity contained by Entity of same type" - salience 65 - when - $larger: RedactionEntity($type: type, $entityType: entityType) - $contained: RedactionEntity(containedBy($larger), type == $type, entityType == $entityType, this != $larger, !resized, !skipRemoveEntitiesContainedInLarger) - then - $contained.removeFromGraph(); - retract($contained); - end - -rule "merge intersecting Entities of same type" - salience 64 - when - $first: RedactionEntity($type: type, $entityType: entityType, !resized, !skipRemoveEntitiesContainedInLarger) - $second: RedactionEntity(intersects($first), type == $type, entityType == $entityType, this != $first, !resized, !skipRemoveEntitiesContainedInLarger) - then - $first.removeFromGraph(); - $second.removeFromGraph(); - RedactionEntity mergedEntity = entityCreationService.byEntities(List.of($first, $second), $type, $entityType, document); - retract($first); - retract($second); - insert(mergedEntity); - end - -rule "remove Entity of type ENTITY when contained by FALSE_POSITIVE" - salience 64 - when - $falsePositive: RedactionEntity($type: type, entityType == EntityType.FALSE_POSITIVE) - $entity: RedactionEntity(containedBy($falsePositive), type == $type, entityType == EntityType.ENTITY, !resized, !skipRemoveEntitiesContainedInLarger) - then - $entity.removeFromGraph(); - retract($entity) - end - -rule "remove Entity of type RECOMMENDATION when contained by FALSE_RECOMMENDATION" - salience 64 - when - $falseRecommendation: RedactionEntity($type: type, entityType == EntityType.FALSE_RECOMMENDATION) - $recommendation: RedactionEntity(containedBy($falseRecommendation), type == $type, entityType == EntityType.RECOMMENDATION, !resized, !skipRemoveEntitiesContainedInLarger) - then - $recommendation.removeFromGraph(); - retract($recommendation); - end - -rule "remove Entity of type RECOMMENDATION when intersected by ENTITY with same type" - salience 256 - when - $entity: RedactionEntity($type: type, entityType == EntityType.ENTITY) - $recommendation: RedactionEntity(intersects($entity), type == $type, entityType == EntityType.RECOMMENDATION, !resized, !skipRemoveEntitiesContainedInLarger) - then - $entity.addEngines($recommendation.getEngines()); - $recommendation.removeFromGraph(); - retract($recommendation); - end - -rule "remove Entity of type RECOMMENDATION when contained by ENTITY" - salience 256 - when - $entity: RedactionEntity(entityType == EntityType.ENTITY) - $recommendation: RedactionEntity(containedBy($entity), entityType == EntityType.RECOMMENDATION, !resized, !skipRemoveEntitiesContainedInLarger) - then - $recommendation.removeFromGraph(); - retract($recommendation); - end - -rule "remove Entity of lower rank, when equal boundaries and entityType" - salience 32 - when - $higherRank: RedactionEntity($type: type, $entityType: entityType, $boundary: boundary) - $lowerRank: RedactionEntity($boundary == boundary, type != $type, entityType == $entityType, dictionary.getDictionaryRank(type) < dictionary.getDictionaryRank($type), !redaction) - then - $lowerRank.removeFromGraph(); - retract($lowerRank); - end - -// --------------------------------------- FileAttribute Rules ------------------------------------------------------------------- - -rule "remove duplicate FileAttributes" - salience 64 - when - $fileAttribute: FileAttribute($label: label, $value: value) - $duplicate: FileAttribute(this != $fileAttribute, label == $label, value == $value) - then - retract($duplicate); - end - -// --------------------------------------- local dictionary search ------------------------------------------------------------------- - -rule "run local dictionary search" - agenda-group "LOCAL_DICTIONARY_ADDS" - salience -999 - when - DictionaryModel(!localEntries.isEmpty(), $type: type, $searchImplementation: localSearch) from dictionary.getDictionaryModels() - then - entityCreationService.bySearchImplementation($searchImplementation, $type, EntityType.RECOMMENDATION, document) - .forEach(entity -> { - entity.addEngine(Engine.RULE); - insert(entity); - }); - end diff --git a/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules_v2.drl b/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules_v2.drl index 1a43d6cd..8be4a838 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules_v2.drl +++ b/redaction-service-v1/redaction-service-server-v1/src/test/resources/drools/rules_v2.drl @@ -68,7 +68,7 @@ rule "Always redact CBI_author" when $cbiAuthor: RedactionEntity(type == "CBI_author", entityType == EntityType.ENTITY) then - $cbiAuthor.addMatchedRule(0); + $cbiAuthor.addMatchedRule("0"); $cbiAuthor.setRedaction(true); $cbiAuthor.setRedactionReason("Author found"); $cbiAuthor.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); @@ -81,7 +81,7 @@ rule "Always redact PII" when $cbiAuthor: RedactionEntity(type == "PII", entityType == EntityType.ENTITY) then - $cbiAuthor.addMatchedRule(1); + $cbiAuthor.addMatchedRule("1"); $cbiAuthor.setRedaction(true); $cbiAuthor.setRedactionReason("PII found"); $cbiAuthor.setLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002"); diff --git a/redaction-service-v1/redaction-service-server-v1/src/test/resources/performance/dictionaries/EFSA_sanitisation_GFL_v1/rules.drl b/redaction-service-v1/redaction-service-server-v1/src/test/resources/performance/dictionaries/EFSA_sanitisation_GFL_v1/rules.drl index aa00f81c..f97c680f 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/test/resources/performance/dictionaries/EFSA_sanitisation_GFL_v1/rules.drl +++ b/redaction-service-v1/redaction-service-server-v1/src/test/resources/performance/dictionaries/EFSA_sanitisation_GFL_v1/rules.drl @@ -78,10 +78,10 @@ rule "0: Expand CBI_author and PII entities with salutation prefix" rule "1: Redacted because Section contains Vertebrate" when - $section: Section(hasActiveEntitiesOfType("vertebrate"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $section: Section(hasEntitiesOfType("vertebrate"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(redactionEntity -> { redactionEntity.setRedaction(true); redactionEntity.addMatchedRule(1); @@ -92,10 +92,10 @@ rule "1: Redacted because Section contains Vertebrate" rule "2: Not Redacted because Section contains no Vertebrate" when - $section: Section(!hasActiveEntitiesOfType("vertebrate"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $section: Section(!hasEntitiesOfType("vertebrate"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(redactionEntity -> { redactionEntity.setRedaction(false); redactionEntity.addMatchedRule(2); @@ -105,11 +105,11 @@ rule "2: Not Redacted because Section contains no Vertebrate" rule "3: Do not redact Names and Addresses if no redaction Indicator is contained" when - $section: Section(hasActiveEntitiesOfType("vertebrate"), - hasActiveEntitiesOfType("no_redaction_indicator"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $section: Section(hasEntitiesOfType("vertebrate"), + hasEntitiesOfType("no_redaction_indicator"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(redactionEntity -> { redactionEntity.setRedaction(false); redactionEntity.addMatchedRule(3); @@ -119,12 +119,12 @@ rule "3: Do not redact Names and Addresses if no redaction Indicator is containe rule "4: Redact Names and Addresses if no_redaction_indicator and redaction_indicator is contained" when - $section: Section(hasActiveEntitiesOfType("vertebrate"), - hasActiveEntitiesOfType("no_redaction_indicator"), - hasActiveEntitiesOfType("redaction_indicator"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $section: Section(hasEntitiesOfType("vertebrate"), + hasEntitiesOfType("no_redaction_indicator"), + hasEntitiesOfType("redaction_indicator"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(redactionEntity -> { redactionEntity.setRedaction(true); redactionEntity.addMatchedRule(4); @@ -136,12 +136,12 @@ rule "4: Redact Names and Addresses if no_redaction_indicator and redaction_indi rule "5: Do not redact Names and Addresses if published information found" when - $section: Section(hasActiveEntitiesOfType("vertebrate"), - hasActiveEntitiesOfType("published_information"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $section: Section(hasEntitiesOfType("vertebrate"), + hasEntitiesOfType("published_information"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - List publishedInformationEntities = $section.getActiveEntitiesOfType("published_information"); - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + List publishedInformationEntities = $section.getEntitiesOfType("published_information"); + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(redactionEntity -> { redactionEntity.setRedaction(false); redactionEntity.setRedactionReason("Vertebrate but also Published Information found in this section"); @@ -192,10 +192,10 @@ rule "7: Redact CBI_author, if its row contains a cell with header \"Vertebrate rule "8: Redact if must_redact entity is found" when - $section: Section(hasActiveEntitiesOfType("must_redact"), - (hasActiveEntitiesOfType("CBI_author") || hasActiveEntitiesOfType("CBI_address"))) + $section: Section(hasEntitiesOfType("must_redact"), + (hasEntitiesOfType("CBI_author") || hasEntitiesOfType("CBI_address"))) then - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(redactionEntity -> { redactionEntity.setRedaction(true); redactionEntity.setRedactionReason("must_redact entry was found."); @@ -251,7 +251,7 @@ rule "11: Redact if CTL/* or BL/* was found" entityCreationService.byString("BL/", "must_redact", EntityType.ENTITY, $section) .forEach(mustRedactEntity -> insert(mustRedactEntity)); - $section.getActiveEntitiesOfType(List.of("CBI_author", "CBI_address")) + $section.getEntitiesOfType(List.of("CBI_author", "CBI_address")) .forEach(redactionEntity -> { redactionEntity.setRedaction(true); redactionEntity.setRedactionReason("Laboratory for vertebrate studies found");