DM-285: adjust code to new EntityLog format.

This commit is contained in:
Kilian Schüttler 2023-09-26 13:17:17 +02:00
parent ce580b6285
commit 66c4a38aff
15 changed files with 783 additions and 140 deletions

View File

@ -16,7 +16,7 @@ val layoutParserVersion = "0.70.0"
val jacksonVersion = "2.15.2"
val droolsVersion = "8.44.0.Final"
val pdfBoxVersion = "3.0.0"
val persistenceServiceVersion = "2.182.0"
val persistenceServiceVersion = "2.187.0"
configurations {
all {

View File

@ -10,6 +10,8 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.ManualChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import com.iqser.red.service.redaction.v1.server.model.document.nodes.Document;
import com.iqser.red.service.redaction.v1.server.model.document.nodes.SemanticNode;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
@ -34,11 +36,11 @@ public class Entity {
boolean imported;
SemanticNode containingNode;
String section;
float[] color;
List<Position> positions;
int sectionNumber;
String textBefore;
String textAfter;
@ -65,7 +67,7 @@ public class Entity {
Set<String> importedRedactionIntersections;
public static Entity fromEntityLogEntry(EntityLogEntry e) {
public static Entity fromEntityLogEntry(EntityLogEntry e, Document document) {
return Entity.builder()
.id(e.getId())
@ -79,8 +81,7 @@ public class Entity {
.imported(e.isImported())
.section(e.getSection())
.color(e.getColor())
.positions(e.getPositions())
.sectionNumber(e.getSectionNumber())
.positions(e.getPositions()).containingNode(document.getDocumentTree().getEntryById(e.getContainingNodeId()).getNode())
.textBefore(e.getTextBefore())
.textAfter(e.getTextAfter())
.startOffset(e.getStartOffset())

View File

@ -249,7 +249,7 @@ public interface SemanticNode {
* Ignores Entity with ignored == true or removed == true.
*
* @param types an array of strings representing the types of entities to check for
* @return true, if this SemanticNode has at least one Entity of any of the provided types
* @return true, if this SemanticNode has at least one Entity of the provided types
*/
default boolean hasEntitiesOfAnyType(String... types) {
@ -455,7 +455,7 @@ public interface SemanticNode {
* @param y the lower left corner Y value
* @param w width
* @param h height
* @param pageNumber the pagenumber of the rectangle
* @param pageNumber the pageNumber of the rectangle
* @return true if intersects, false otherwise
*/
default boolean intersectsRectangle(int x, int y, int w, int h, int pageNumber) {
@ -607,7 +607,7 @@ public interface SemanticNode {
private Map<Page, Rectangle2D> getBBoxFromLeafTextBlock(Map<Page, Rectangle2D> bBoxPerPage) {
Map<Page, List<AtomicTextBlock>> atomicTextBlockPerPage = getTextBlock().getAtomicTextBlocks().stream().collect(Collectors.groupingBy(AtomicTextBlock::getPage));
atomicTextBlockPerPage.forEach((page, atbs) -> bBoxPerPage.put(page, RectangleTransformations.atomicTextBlockBBox(atbs)));
atomicTextBlockPerPage.forEach((page, atomicTextBlocks) -> bBoxPerPage.put(page, RectangleTransformations.atomicTextBlockBBox(atomicTextBlocks)));
return bBoxPerPage;
}

View File

@ -122,7 +122,7 @@ public class AnalyzeService {
dictionary.getVersion(),
kieWrapperEntityRules.rulesVersion());
return finalizeAnalysis(analyzeRequest, startTime, kieWrapperComponentRules, new EntityLogChanges(entityLog, false),
return finalizeAnalysis(analyzeRequest, startTime, kieWrapperComponentRules, new EntityLogChanges(entityLog, false), document,
redactionLog,
document.getNumberOfPages(),
dictionary.getVersion(),
@ -165,7 +165,7 @@ public class AnalyzeService {
entityLogCreatorService.updateVersionsAndReturnChanges(previousEntityLog,
dictionaryIncrement.getDictionaryVersion(),
analyzeRequest.getDossierTemplateId(),
false),
false), document,
previousRedactionLog,
document.getNumberOfPages(),
dictionaryIncrement.getDictionaryVersion(),
@ -206,7 +206,7 @@ public class AnalyzeService {
return finalizeAnalysis(analyzeRequest,
startTime,
kieContainerCreationService.getLatestKieContainer(analyzeRequest.getDossierTemplateId(), RuleFileType.COMPONENT),
entityLogChanges,
entityLogChanges, document,
redactionLog,
document.getNumberOfPages(),
dictionaryIncrement.getDictionaryVersion(),
@ -240,7 +240,7 @@ public class AnalyzeService {
}
private AnalyzeResult finalizeAnalysis(AnalyzeRequest analyzeRequest, long startTime, KieWrapper kieWrapperComponentRules, EntityLogChanges entityLogChanges,
private AnalyzeResult finalizeAnalysis(AnalyzeRequest analyzeRequest, long startTime, KieWrapper kieWrapperComponentRules, EntityLogChanges entityLogChanges, Document document,
RedactionLog redactionLog,
int numberOfPages,
DictionaryVersion dictionaryVersion,
@ -254,7 +254,7 @@ public class AnalyzeService {
log.info("Created entity log for file {} in dossier {}", analyzeRequest.getFileId(), analyzeRequest.getDossierId());
if (entityLogChanges.isHasChanges() || !isReanalysis) {
computeComponentsWhenRulesArePresent(analyzeRequest, kieWrapperComponentRules, addedFileAttributes, entityLogChanges, dictionaryVersion);
computeComponentsWhenRulesArePresent(analyzeRequest, kieWrapperComponentRules, document, addedFileAttributes, entityLogChanges, dictionaryVersion);
}
log.info("Stored analysis logs for file {} in dossier {}", analyzeRequest.getFileId(), analyzeRequest.getDossierId());
@ -284,7 +284,7 @@ public class AnalyzeService {
private void computeComponentsWhenRulesArePresent(AnalyzeRequest analyzeRequest,
KieWrapper kieWrapperComponentRules,
KieWrapper kieWrapperComponentRules, Document document,
Set<FileAttribute> addedFileAttributes,
EntityLogChanges entityLogChanges,
DictionaryVersion dictionaryVersion) {
@ -294,7 +294,7 @@ public class AnalyzeService {
}
List<Component> components = componentDroolsExecutionService.executeRules(kieWrapperComponentRules.container(),
entityLogChanges.getEntityLog(),
entityLogChanges.getEntityLog(), document,
addedFileAttributes.stream().toList());
log.info("Finished component rule execution for file {} in dossier {}", analyzeRequest.getFileId(), analyzeRequest.getDossierId());

View File

@ -6,8 +6,9 @@ import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.EntityReference;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntityReference;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import com.iqser.red.service.redaction.v1.server.model.component.Component;
import com.iqser.red.service.redaction.v1.server.model.component.Entity;
@ -18,36 +19,35 @@ public class ComponentLogCreatorService {
public ComponentLog buildComponentLog(int analysisNumber, List<Component> components, long componentRulesVersion) {
List<com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.Component> componentLogComponents = components.stream()
List<ComponentLogEntry> componentLogComponents = components.stream()
.collect(Collectors.groupingBy(Component::getName, Collectors.mapping(this::buildComponentLogEntry, Collectors.toList())))
.entrySet()
.stream()
.map(entry -> new com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.Component(entry.getKey(), entry.getValue()))
.stream().map(entry -> new ComponentLogEntry(entry.getKey(), entry.getValue()))
.toList();
return new ComponentLog(analysisNumber, componentRulesVersion, componentLogComponents);
}
private ComponentValue buildComponentLogEntry(Component component) {
private ComponentLogEntryValue buildComponentLogEntry(Component component) {
return ComponentValue.builder()
return ComponentLogEntryValue.builder()
.value(component.getValue()).originalValue(component.getValue())
.componentRuleId(component.getMatchedRule().toString())
.valueDescription(component.getValueDescription())
.entityReferences(toComponentEntityReferences(component.getReferences().stream().sorted(EntityComparators.start()).toList()))
.componentLogEntityReferences(toComponentEntityReferences(component.getReferences().stream().sorted(EntityComparators.start()).toList()))
.build();
}
private List<EntityReference> toComponentEntityReferences(List<Entity> references) {
private List<ComponentLogEntityReference> toComponentEntityReferences(List<Entity> references) {
return references.stream().map(this::toComponentEntityReference).toList();
}
private EntityReference toComponentEntityReference(Entity entity) {
private ComponentLogEntityReference toComponentEntityReference(Entity entity) {
return EntityReference.builder().id(entity.getId())
return ComponentLogEntityReference.builder().id(entity.getId())
.page(entity.getPositions().stream().findFirst().map(Position::getPageNumber).orElse(0)).entityRuleId(entity.getMatchedRule())
.type(entity.getType())
.build();

View File

@ -86,12 +86,15 @@ public class EntityLogCreatorService {
public EntityLogChanges updatePreviousEntityLog(AnalyzeRequest analyzeRequest,
Document document,
List<ManualEntity> notFoundManualRedactionEntries,
EntityLog previousEntityLog, Set<Integer> sectionsToReanalyseIds, DictionaryVersion dictionaryVersion) {
EntityLog previousEntityLog,
Set<Integer> sectionsToReanalyseIds,
DictionaryVersion dictionaryVersion) {
List<EntityLogEntry> newEntityLogEntries = createEntityLogEntries(document, analyzeRequest.getDossierTemplateId(), notFoundManualRedactionEntries);
List<EntityLogEntry> previousEntries = previousEntityLog.getEntityLogEntry()
.stream()
.filter(entry -> sectionsToReanalyseIds.contains(entry.getSectionNumber()) && !entry.getType().equals(ImportedRedactionService.IMPORTED_REDACTION_TYPE))
.filter(entry -> (entry.getContainingNodeId().isEmpty() || sectionsToReanalyseIds.contains(entry.getContainingNodeId().get(0))) && !entry.getType()
.equals(ImportedRedactionService.IMPORTED_REDACTION_TYPE))
.toList();
previousEntityLog.getEntityLogEntry().removeAll(previousEntries);
boolean hasChanges = entityChangeLogService.computeChanges(previousEntries, newEntityLogEntries, analyzeRequest.getAnalysisNumber());
@ -140,8 +143,7 @@ public class EntityLogCreatorService {
private List<EntityLogEntry> createEntityLogEntriesFromActiveEntities(Document document, String dossierTemplateId, List<ManualEntity> notFoundManualRedactionEntries) {
List<EntityLogEntry> entries = new ArrayList<>();
document.getEntities()
.stream().filter(EntityLogCreatorService::isEntityOrRecommendationType).filter(IEntity::active)
document.getEntities().stream().filter(EntityLogCreatorService::isEntityOrRecommendationType).filter(IEntity::active)
.forEach(entityNode -> entries.addAll(toEntityLogEntries(entityNode, dossierTemplateId)));
document.streamAllImages().filter(IEntity::active).forEach(imageNode -> entries.add(createEntityLogEntry(imageNode, dossierTemplateId)));
notFoundManualRedactionEntries.stream().filter(IEntity::active).forEach(entityIdentifier -> entries.add(createEntityLogEntry(entityIdentifier, dossierTemplateId)));
@ -182,33 +184,30 @@ public class EntityLogCreatorService {
}
private EntityLogEntry createEntityLogEntry(TextEntity entity, String dossierTemplateId) {
public EntityLogEntry createEntityLogEntry(Image image, String dossierTemplateId) {
Set<String> referenceIds = new HashSet<>();
entity.references().stream().filter(TextEntity::active).forEach(ref -> ref.getPositionsOnPagePerPage().forEach(pos -> referenceIds.add(pos.getId())));
int sectionNumber = entity.getDeepestFullyContainingNode().getTreeId().isEmpty() ? 0 : entity.getDeepestFullyContainingNode().getTreeId().get(0);
boolean isHint = isHint(entity.getType(), dossierTemplateId);
String imageType = image.getImageType().equals(ImageType.OTHER) ? "image" : image.getImageType().toString().toLowerCase(Locale.ENGLISH);
boolean isHint = dictionaryService.isHint(imageType, dossierTemplateId);
return EntityLogEntry.builder()
.color(getColor(entity.getType(), dossierTemplateId, entity.applied()))
.reason(entity.buildReasonWithManualChangeDescriptions())
.legalBasis(entity.legalBasis())
.value(entity.getManualOverwrite().getValue().orElse(entity.getMatchedRule().isWriteValueWithLineBreaks() ? entity.getValueWithLineBreaks() : entity.getValue()))
.type(entity.getType())
.section(entity.getManualOverwrite().getSection().orElse(entity.getDeepestFullyContainingNode().toString()))
.sectionNumber(sectionNumber)
.matchedRule(entity.getMatchedRule().getRuleIdentifier().toString())
.dictionaryEntry(entity.isDictionaryEntry())
.textAfter(entity.getTextAfter())
.textBefore(entity.getTextBefore())
.startOffset(entity.getTextRange().start())
.endOffset(entity.getTextRange().end())
.dossierDictionaryEntry(entity.isDossierDictionaryEntry())
.engines(entity.getEngines() != null ? entity.getEngines() : Collections.emptySet())
.reference(referenceIds)
.manualChanges(manualChangeFactory.toManualChangeList(entity.getManualOverwrite().getManualChangeLog(), isHint))
.state(buildEntryState(entity))
.entryType(buildEntryType(entity, isHint))
.id(image.getId())
.value(image.value())
.color(getColor(imageType, dossierTemplateId, image.applied()))
.value(image.value())
.type(imageType)
.reason(image.buildReasonWithManualChangeDescriptions())
.legalBasis(image.legalBasis())
.matchedRule(image.getMatchedRule().getRuleIdentifier().toString())
.dictionaryEntry(false)
.positions(List.of(new Position(image.getPosition(), image.getPage().getNumber())))
.containingNodeId(image.getTreeId())
.closestHeadline(image.getHeadline().getTextBlock().getSearchText())
.section(image.getManualOverwrite().getSection().orElse(image.getParent().toString()))
.imageHasTransparency(image.isTransparent())
.manualChanges(manualChangeFactory.toManualChangeList(image.getManualOverwrite().getManualChangeLog(), isHint))
.state(buildEntryState(image))
.entryType(buildEntryType(image, isHint))
.build();
}
@ -226,8 +225,9 @@ public class EntityLogCreatorService {
.state(buildEntryState(manualEntity))
.entryType(buildEntryType(manualEntity, isHint))
.section(manualEntity.getManualOverwrite().getSection().orElse(manualEntity.getSection()))
.sectionNumber(0)
.matchedRule("ManualRedaction")
.containingNodeId(Collections.emptyList())
.closestHeadline("")
.matchedRule(manualEntity.getMatchedRule().getRuleIdentifier().toString())
.dictionaryEntry(manualEntity.isDictionaryEntry())
.dossierDictionaryEntry(manualEntity.isDossierDictionaryEntry())
.textAfter("")
@ -242,29 +242,34 @@ public class EntityLogCreatorService {
}
public EntityLogEntry createEntityLogEntry(Image image, String dossierTemplateId) {
private EntityLogEntry createEntityLogEntry(TextEntity entity, String dossierTemplateId) {
String imageType = image.getImageType().equals(ImageType.OTHER) ? "image" : image.getImageType().toString().toLowerCase(Locale.ENGLISH);
boolean isHint = dictionaryService.isHint(imageType, dossierTemplateId);
Set<String> referenceIds = new HashSet<>();
entity.references().stream().filter(TextEntity::active).forEach(ref -> ref.getPositionsOnPagePerPage().forEach(pos -> referenceIds.add(pos.getId())));
int sectionNumber = entity.getDeepestFullyContainingNode().getTreeId().isEmpty() ? 0 : entity.getDeepestFullyContainingNode().getTreeId().get(0);
boolean isHint = isHint(entity.getType(), dossierTemplateId);
return EntityLogEntry.builder()
.id(image.getId())
.value(image.value())
.color(getColor(imageType, dossierTemplateId, image.applied()))
.value(image.value())
.type(imageType)
.reason(image.buildReasonWithManualChangeDescriptions())
.legalBasis(image.legalBasis())
.matchedRule(image.getMatchedRule().getRuleIdentifier().toString())
.dictionaryEntry(false)
.positions(List.of(new Position(image.getPosition(), image.getPage().getNumber())))
.sectionNumber(image.getTreeId().get(0))
.section(image.getManualOverwrite().getSection().orElse(image.getParent().toString()))
.imageHasTransparency(image.isTransparent())
.manualChanges(manualChangeFactory.toManualChangeList(image.getManualOverwrite().getManualChangeLog(), isHint))
.state(buildEntryState(image))
.entryType(buildEntryType(image, isHint))
.color(getColor(entity.getType(), dossierTemplateId, entity.applied()))
.reason(entity.buildReasonWithManualChangeDescriptions())
.legalBasis(entity.legalBasis())
.value(entity.getManualOverwrite().getValue().orElse(entity.getMatchedRule().isWriteValueWithLineBreaks() ? entity.getValueWithLineBreaks() : entity.getValue()))
.type(entity.getType())
.section(entity.getManualOverwrite().getSection().orElse(entity.getDeepestFullyContainingNode().toString()))
.containingNodeId(entity.getDeepestFullyContainingNode().getTreeId())
.closestHeadline(entity.getDeepestFullyContainingNode().getHeadline().getTextBlock().getSearchText())
.matchedRule(entity.getMatchedRule().getRuleIdentifier().toString())
.dictionaryEntry(entity.isDictionaryEntry())
.textAfter(entity.getTextAfter())
.textBefore(entity.getTextBefore())
.startOffset(entity.getTextRange().start())
.endOffset(entity.getTextRange().end())
.dossierDictionaryEntry(entity.isDossierDictionaryEntry())
.engines(entity.getEngines() != null ? entity.getEngines() : Collections.emptySet())
.reference(referenceIds)
.manualChanges(manualChangeFactory.toManualChangeList(entity.getManualOverwrite().getManualChangeLog(), isHint))
.state(buildEntryState(entity))
.entryType(buildEntryType(entity, isHint))
.build();
}

View File

@ -9,7 +9,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.OptionalInt;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -17,6 +17,9 @@ import org.kie.api.runtime.KieSession;
import com.iqser.red.service.redaction.v1.server.model.component.Component;
import com.iqser.red.service.redaction.v1.server.model.component.Entity;
import com.iqser.red.service.redaction.v1.server.model.document.nodes.SemanticNode;
import com.iqser.red.service.redaction.v1.server.model.document.nodes.Table;
import com.iqser.red.service.redaction.v1.server.model.document.nodes.TableCell;
import com.iqser.red.service.redaction.v1.server.model.drools.RuleIdentifier;
import com.iqser.red.service.redaction.v1.server.utils.DateConverter;
@ -32,6 +35,14 @@ public class ComponentCreationService {
Set<Entity> referencedEntities = new HashSet<>();
/**
* Finds the first value from the collection of entities and creates a component from it. If no value is found, the fallback value is used instead.
*
* @param ruleIdentifier the identifier for the rule
* @param name the name of the operation
* @param entities the collection of entities to search for the first value
* @param fallback the value to be returned if no value is found in the collection
*/
public void firstOrElse(String ruleIdentifier, String name, Collection<Entity> entities, String fallback) {
String valueDescription = String.format("First found value or else '%s'", fallback);
@ -40,24 +51,45 @@ public class ComponentCreationService {
}
/**
* Creates a new component with the given parameters and inserts it into the kieSession.
*
* @param ruleIdentifier The rule identifier for the component.
* @param name The name of the component.
* @param value The value of the component.
* @param valueDescription The description of the value.
* @param references A collection of Entity objects that the component references.
*/
public void create(String ruleIdentifier, String name, String value, String valueDescription, Collection<Entity> references) {
referencedEntities.addAll(references);
kieSession.insert(Component.builder()
.matchedRule(RuleIdentifier.fromString(ruleIdentifier)).name(name)
.value(value).valueDescription(valueDescription)
kieSession.insert(Component.builder().matchedRule(RuleIdentifier.fromString(ruleIdentifier)).name(name).value(value).valueDescription(valueDescription)
.references(new LinkedList<>(references))
.build());
}
/**
* Joins entity values, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
*/
public void joining(String ruleIdentifier, String name, Collection<Entity> entities) {
joining(ruleIdentifier, name, entities, ", ");
}
/**
* Joins entity values, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
*/
public void joining(String ruleIdentifier, String name, Collection<Entity> entities, String delimiter) {
String valueDescription = String.format("Joining all values with '%s'", delimiter);
@ -66,12 +98,26 @@ public class ComponentCreationService {
}
/**
* Joins entity values from the first section entities appear in, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
*/
public void joiningFromFirstSectionOnly(String ruleIdentifier, String name, Collection<Entity> entities) {
joiningFromFirstSectionOnly(ruleIdentifier, name, entities, ", ");
}
/**
* Joins unique entity values from the first section entities appear in, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
*/
public void joiningFromFirstSectionOnly(String ruleIdentifier, String name, Collection<Entity> entities, String delimiter) {
List<Entity> entitiesFromFirstSection = findEntitiesFromFirstSection(entities);
@ -81,21 +127,35 @@ public class ComponentCreationService {
private static List<Entity> findEntitiesFromFirstSection(Collection<Entity> entities) {
var entitiesBySection = entities.stream().collect(Collectors.groupingBy(Entity::getSectionNumber));
OptionalInt firstSection = entitiesBySection.keySet().stream().mapToInt(Integer::intValue).min();
var entitiesBySection = entities.stream().collect(Collectors.groupingBy(entity -> entity.getContainingNode().getHighestParent()));
Optional<SemanticNode> firstSection = entitiesBySection.keySet().stream().min(SemanticNodeComparators.first());
if (firstSection.isEmpty()) {
return Collections.emptyList();
}
return entitiesBySection.get(firstSection.getAsInt());
return entitiesBySection.get(firstSection.get());
}
/**
* Joins unique entity values from the first section entities appear in, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
*/
public void joiningUniqueFromFirstSectionOnly(String ruleIdentifier, String name, Collection<Entity> entities) {
joiningUniqueFromFirstSectionOnly(ruleIdentifier, name, entities, ", ");
}
/**
* Joins entity values from the first section entities appear in, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
*/
public void joiningUniqueFromFirstSectionOnly(String ruleIdentifier, String name, Collection<Entity> entities, String delimiter) {
List<Entity> entitiesFromFirstSection = findEntitiesFromFirstSection(entities);
@ -103,29 +163,14 @@ public class ComponentCreationService {
}
private static List<Entity> findEntitiesFromLongestSection(Collection<Entity> entities) {
var entitiesBySection = entities.stream().collect(Collectors.groupingBy(Entity::getSectionNumber));
OptionalInt longestSection = entitiesBySection.entrySet()
.stream()
.sorted(Comparator.comparingInt(ComponentCreationService::getTotalLengthOfEntities).reversed())
.mapToInt(Map.Entry::getKey)
.findFirst();
if (longestSection.isEmpty()) {
return Collections.emptyList();
}
return entitiesBySection.get(longestSection.getAsInt());
}
private static int getTotalLengthOfEntities(Map.Entry<Integer, List<Entity>> entry) {
return entry.getValue().stream().mapToInt(Entity::getLength).sum();
}
/**
* Joins all unique values from a collection of entities into a single string using a specified delimiter and creates a component from the result.
*
* @param ruleIdentifier the identifier of the rule
* @param name the name of the joining operation
* @param entities the collection of entities
* @param delimiter the delimiter to use for joining the values
*/
public void joiningUnique(String ruleIdentifier, String name, Collection<Entity> entities, String delimiter) {
String valueDescription = String.format("Joining all values with '%s'", delimiter);
@ -134,6 +179,26 @@ public class ComponentCreationService {
}
/**
* Joins entity values with delimiter ', ' from the section with the longest combined entity values only, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
*/
public void joiningFromLongestSectionOnly(String ruleIdentifier, String name, Collection<Entity> entities) {
joiningFromLongestSectionOnly(ruleIdentifier, name, entities, ", ");
}
/**
* Joins entity values from the section with the longest combined entity values only, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
*/
public void joiningFromLongestSectionOnly(String ruleIdentifier, String name, Collection<Entity> entities, String delimiter) {
List<Entity> entitiesFromLongestSection = findEntitiesFromLongestSection(entities);
@ -141,6 +206,49 @@ public class ComponentCreationService {
}
private static List<Entity> findEntitiesFromLongestSection(Collection<Entity> entities) {
var entitiesBySection = entities.stream().collect(Collectors.groupingBy(entity -> entity.getContainingNode().getHighestParent()));
Optional<SemanticNode> longestSection = entitiesBySection.entrySet()
.stream()
.sorted(Comparator.comparingInt(ComponentCreationService::getTotalLengthOfEntities).reversed()).map(Map.Entry::getKey)
.findFirst();
if (longestSection.isEmpty()) {
return Collections.emptyList();
}
return entitiesBySection.get(longestSection.get());
}
private static int getTotalLengthOfEntities(Map.Entry<SemanticNode, List<Entity>> entry) {
return entry.getValue().stream().mapToInt(Entity::getLength).sum();
}
/**
* Joins unique entity values with delimiter ', ' from the section with the longest combined entity values only, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
*/
public void joiningUniqueFromLongestSectionOnly(String ruleIdentifier, String name, Collection<Entity> entities) {
joiningUniqueFromLongestSectionOnly(ruleIdentifier, name, entities, ", ");
}
/**
* Joins unique entity values from the section with the longest combined entity values only, and creates a component from the result.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities The collection of entities to process.
* @param delimiter The string delimiter to join the entities.
*/
public void joiningUniqueFromLongestSectionOnly(String ruleIdentifier, String name, Collection<Entity> entities, String delimiter) {
List<Entity> entitiesFromLongestSection = findEntitiesFromLongestSection(entities);
@ -148,40 +256,88 @@ public class ComponentCreationService {
}
/**
* Joins the names of the entities in the provided collection, separated by a comma,
* and creates a component from the result.
*
* @param ruleIdentifier The unique identifier of the rule being applied.
* @param name The name to which the joined result will be set.
* @param entities The collection of entities whose names will be joined.
*/
public void joiningUnique(String ruleIdentifier, String name, Collection<Entity> entities) {
joiningUnique(ruleIdentifier, name, entities, ", ");
}
/**
* Computes the number of unique values in the collection of entities and creates a component with the result.
*
* @param ruleIdentifier the identifier of the rule
* @param name the name of the record
* @param entities the collection of entities to compute unique values from
*/
public void uniqueValueCount(String ruleIdentifier, String name, Collection<Entity> entities) {
long count = entities.stream().map(Entity::getValue).distinct().count();
create(ruleIdentifier, name, String.valueOf(count), "Number of unique values in the entity references", entities);
}
/**
* Creates a component for each sentence in the collection of entities.
*
* @param ruleIdentifier The identifier of the rule.
* @param name The name of the entity.
* @param entities A collection of Entity objects.
*/
public void asSentences(String ruleIdentifier, String name, Collection<Entity> entities) {
if (entities.isEmpty()) {
return;
}
for (Entity entity : entities) {
BreakIterator iterator = BreakIterator.getSentenceInstance(Locale.ENGLISH);
iterator.setText(entity.getValue());
int start = iterator.first();
for (int end = iterator.next(); end != BreakIterator.DONE; start = end, end = iterator.next()) {
create(ruleIdentifier, name, entity.getValue().substring(start, end).replaceAll("\\n", "").trim(), "Split into sentences", entity);
create(ruleIdentifier,
name,
entity.getValue().substring(start, end).replaceAll("\\n", "").trim(),
String.format("Values of type '%s' as sentences", entity.getType()),
entity);
}
}
}
/**
* Creates a new component with the given rule identifier, name, value, value description, and reference.
* The component is built using the provided parameters and inserted into the knowledge session.
* The reference is added to the referencedEntities list.
*
* @param ruleIdentifier The identifier of the rule for the component.
* @param name The name of the component.
* @param value The value of the component.
* @param valueDescription The description of the value.
* @param reference The reference entity for the component.
*/
public void create(String ruleIdentifier, String name, String value, String valueDescription, Entity reference) {
referencedEntities.add(reference);
List<Entity> referenceList = new LinkedList<>();
referenceList.add(reference);
kieSession.insert(Component.builder().matchedRule(RuleIdentifier.fromString(ruleIdentifier)).name(name).value(value).valueDescription(valueDescription)
.references(referenceList)
.build());
kieSession.insert(Component.builder().matchedRule(RuleIdentifier.fromString(ruleIdentifier)).name(name).value(value)
.valueDescription(valueDescription).references(referenceList).build());
}
/**
* Creates components for unmapped entities.
*
* @param ruleIdentifier The identifier of the rule being applied.
* @param entities The collection of entities to create components for.
*/
public void createComponentsForUnMappedEntities(String ruleIdentifier, Collection<Entity> entities) {
entities.stream()
@ -190,30 +346,109 @@ public class ComponentCreationService {
}
/**
* Converts entity values to the 'dd/MM/yyyy' format and joins them with ', '. If the value could not be parsed as a date, it will be created as is.
*
* @param ruleIdentifier the identifier of the rule
* @param name the name of the entity
* @param entities the collection of entities
*/
public void convertDates(String ruleIdentifier, String name, Collection<Entity> entities) {
convertDates(ruleIdentifier, name, entities, "dd/MM/yyyy");
}
/**
* Converts entity values to provided format and joins them with ', '. If the value could not be parsed as a date, it will be created as is.
*
* @param ruleIdentifier the identifier of the rule
* @param name the name of the entity
* @param entities the collection of entities
* @param resultFormat the desired format for the converted dates
*/
public void convertDates(String ruleIdentifier, String name, Collection<Entity> entities, String resultFormat) {
String valueDescription = "Convert values of type to dd/MM/yyyy joined with ', '";
String valueDescription = String.format("Convert values of type to %s joined with ', '", resultFormat);
String date = entities.stream().map(Entity::getValue).map(value -> DateConverter.convertDate(value, resultFormat)).collect(Collectors.joining(", "));
create(ruleIdentifier, name, date, valueDescription, entities);
}
/**
* Joins values from entities that are in the same table row. If entities are not in a table cell they are added as a single component.
*
* @param ruleIdentifier the identifier of the rule
* @param name the name of the entity
* @param entities the collection of entities
*/
public void joiningFromSameTableRow(String ruleIdentifier, String name, Collection<Entity> entities) {
String types = entities.stream().map(Entity::getType).distinct().collect(Collectors.joining());
String valueDescription = String.format("Combine values of %s that are in same table row", types);
Map<Optional<Table>, List<Entity>> entitiesPerTable = entities.stream().collect(Collectors.groupingBy(this::getFirstTable));
entitiesPerTable.forEach((optionalTable, groupedEntities) -> {
if (optionalTable.isEmpty()) {
groupedEntities.forEach(entity -> create(ruleIdentifier, name, entity.getValue(), valueDescription, entity));
}
groupedEntities.stream()
.filter(entity -> !(entity.getContainingNode() instanceof TableCell))
.forEach(entity -> create(ruleIdentifier, name, entity.getValue(), valueDescription, entity));
groupedEntities.stream()
.filter(entity -> entity.getContainingNode() instanceof TableCell)
.collect(Collectors.groupingBy(entity -> ((TableCell) entity.getContainingNode()).getRow()))
.forEach((row, entitiesInSameRow) -> create(ruleIdentifier,
name,
entities.stream().map(Entity::getValue).collect(Collectors.joining(", ")),
valueDescription,
entitiesInSameRow));
});
}
private Optional<Table> getFirstTable(Entity entity) {
SemanticNode node = entity.getContainingNode();
while (!(node instanceof Table)) {
if (!node.hasParent()) {
return Optional.empty();
}
node = node.getParent();
}
return Optional.of((Table) node);
}
/**
* Creates a new component with the given rule identifier, name, value, and value description.
* If the component is part of a table, it also takes a list of entities that belong to the same table row.
*
* @param ruleIdentifier the identifier of the rule
* @param name the name of the entity
* @param value the value of the component
* @param valueDescription the description of the value
*/
public void create(String ruleIdentifier, String name, String value, String valueDescription) {
create(ruleIdentifier, name, value, valueDescription, Collections.emptyList());
}
/**
* Creates a new component with the given rule identifier, name, and value.
*
* @param ruleIdentifier the identifier of the rule
* @param name the name of the entity
* @param value the value of the component
*/
public void create(String ruleIdentifier, String name, String value) {
kieSession.insert(Component.builder().matchedRule(RuleIdentifier.fromString(ruleIdentifier)).name(name)
.value(value).valueDescription("")
.value(value)
.valueDescription("")
.references(Collections.emptyList())
.build());
}

View File

@ -47,7 +47,10 @@ public class SectionFinderService {
Set<Integer> sectionsToReanalyse = new HashSet<>();
for (EntityLogEntry entry : entityLog.getEntityLogEntry()) {
if (relevantManuallyModifiedAnnotationIds.contains(entry.getId())) {
sectionsToReanalyse.add(entry.getSectionNumber());
if (entry.getContainingNodeId().isEmpty()) {
continue; // Empty list means either Entity has not been found or it is between main sections. Thus, this might lead to wrong reanalysis.
}
sectionsToReanalyse.add(entry.getContainingNodeId().get(0));
}
}

View File

@ -0,0 +1,46 @@
package com.iqser.red.service.redaction.v1.server.service.document;
import java.util.Comparator;
import java.util.List;
import com.iqser.red.service.redaction.v1.server.model.document.nodes.SemanticNode;
public abstract class SemanticNodeComparators implements Comparator<SemanticNode> {
public static SemanticNodeComparators first() {
return new FirstSemanticNode();
}
public static class FirstSemanticNode extends SemanticNodeComparators {
@Override
public int compare(SemanticNode semanticNode, SemanticNode otherSemanticNode) {
List<Integer> treeId = semanticNode.getTreeId();
List<Integer> otherTreeId = otherSemanticNode.getTreeId();
int prefixLength = Math.min(treeId.size(), otherTreeId.size());
// Compare ids one by one
for (int i = 0; i < prefixLength; i++) {
int id1 = treeId.get(i);
int id2 = otherTreeId.get(i);
// If ids are different, return the comparison result
if (id1 != id2) {
return Integer.compare(id1, id2);
}
}
// If all prefix ids are equal, shorter treeId is first
if (treeId.size() != otherTreeId.size()) {
return Integer.compare(treeId.size(), otherTreeId.size());
}
// Lists are equal
return 0;
}
}
}

View File

@ -7,8 +7,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.redaction.v1.server.utils.exception.DroolsTimeoutException;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.QueryResults;
@ -16,11 +14,14 @@ import org.kie.api.runtime.rule.QueryResultsRow;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.redaction.v1.server.RedactionServiceSettings;
import com.iqser.red.service.redaction.v1.server.model.component.Component;
import com.iqser.red.service.redaction.v1.server.model.component.Entity;
import com.iqser.red.service.redaction.v1.server.model.document.nodes.Document;
import com.iqser.red.service.redaction.v1.server.service.document.ComponentCreationService;
import com.iqser.red.service.redaction.v1.server.utils.exception.DroolsTimeoutException;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
@ -36,13 +37,13 @@ public class ComponentDroolsExecutionService {
RedactionServiceSettings settings;
public List<Component> executeRules(KieContainer kieContainer, EntityLog entityLog, List<FileAttribute> fileAttributes) {
public List<Component> executeRules(KieContainer kieContainer, EntityLog entityLog, Document document, List<FileAttribute> fileAttributes) {
KieSession kieSession = kieContainer.newKieSession();
ComponentCreationService componentCreationService = new ComponentCreationService(kieSession);
kieSession.setGlobal("componentCreationService", componentCreationService);
entityLog.getEntityLogEntry().stream().map(Entity::fromEntityLogEntry).forEach(kieSession::insert);
entityLog.getEntityLogEntry().stream().map(entry -> Entity.fromEntityLogEntry(entry, document)).forEach(kieSession::insert);
fileAttributes.stream().filter(f -> f.getValue() != null).forEach(kieSession::insert);
CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {

View File

@ -113,7 +113,7 @@ public class RedactionAcceptanceTest extends AbstractRedactionIntegrationTest {
EntityLog entityLog = redactionStorageService.getEntityLog(TEST_DOSSIER_ID, TEST_FILE_ID);
var publishedInformationEntry1 = findEntityByTypeAndValue(entityLog, "published_information", "Oxford University Press").findFirst().orElseThrow();
var asyaLyon1 = findEntityByTypeAndValueAndSectionNumber(entityLog, "CBI_author", "Asya Lyon", publishedInformationEntry1.getSectionNumber()).findFirst().orElseThrow();
var asyaLyon1 = findEntityByTypeAndValueAndSectionNumber(entityLog, "CBI_author", "Asya Lyon", publishedInformationEntry1.getContainingNodeId()).findFirst().orElseThrow();
assertEquals(EntryState.SKIPPED, asyaLyon1.getState());
@ -126,7 +126,7 @@ public class RedactionAcceptanceTest extends AbstractRedactionIntegrationTest {
entityLog = redactionStorageService.getEntityLog(TEST_DOSSIER_ID, TEST_FILE_ID);
var publishedInformationEntry2 = findEntityByTypeAndValue(entityLog, "published_information", "Oxford University Press").findFirst().orElseThrow();
var asyaLyon2 = findEntityByTypeAndValueAndSectionNumber(entityLog, "CBI_author", "Asya Lyon", publishedInformationEntry2.getSectionNumber()).findFirst().orElseThrow();
var asyaLyon2 = findEntityByTypeAndValueAndSectionNumber(entityLog, "CBI_author", "Asya Lyon", publishedInformationEntry2.getContainingNodeId()).findFirst().orElseThrow();
assertEquals(EntryState.APPLIED, asyaLyon2.getState());
@ -141,13 +141,12 @@ public class RedactionAcceptanceTest extends AbstractRedactionIntegrationTest {
}
private Stream<EntityLogEntry> findEntityByTypeAndValueAndSectionNumber(EntityLog redactionLog, String type, String value, int sectionNumber) {
private Stream<EntityLogEntry> findEntityByTypeAndValueAndSectionNumber(EntityLog redactionLog, String type, String value, List<Integer> sectionNumber) {
return redactionLog.getEntityLogEntry()
.stream()
.filter(entry -> entry.getType().equals(type))
.filter(entry -> entry.getValue().equals(value))
.filter(entry -> entry.getSectionNumber() == sectionNumber);
.filter(entry -> entry.getValue().equals(value)).filter(entry -> entry.getContainingNodeId().get(0).equals(sectionNumber.get(0)));
}

View File

@ -589,11 +589,6 @@ public class RedactionIntegrationTest extends AbstractRedactionIntegrationTest {
}
public void testRedactionLogAndEntityLogEquality() {
}
@Test
public void testRemovePublishedInformations() throws IOException {

View File

@ -2,7 +2,6 @@ package com.iqser.red.service.redaction.v1.server;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import java.io.BufferedReader;
@ -81,7 +80,6 @@ import com.iqser.red.service.redaction.v1.server.client.RulesClient;
import com.iqser.red.service.redaction.v1.server.controller.RedactionController;
import com.iqser.red.service.redaction.v1.server.service.AnalyzeService;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import com.iqser.red.service.redaction.v1.server.utils.ExceptionProvider;
import com.iqser.red.service.redaction.v1.server.utils.LayoutParsingRequestProvider;
import com.iqser.red.service.redaction.v1.server.utils.ResourceLoader;
import com.iqser.red.service.redaction.v1.server.utils.TextNormalizationUtilities;
@ -473,7 +471,7 @@ public class RulesTest {
assertThat(savedRedactionLogEntry.get().isFalsePositive()).isEqualTo(redactionLogEntry.getEntryType().equals(EntryType.FALSE_POSITIVE));
assertThat(savedRedactionLogEntry.get().getSection()).isEqualTo(redactionLogEntry.getSection());
assertThat(savedRedactionLogEntry.get().getColor()).isEqualTo(redactionLogEntry.getColor());
assertThat(savedRedactionLogEntry.get().getSectionNumber()).isEqualTo(redactionLogEntry.getSectionNumber());
assertThat(savedRedactionLogEntry.get().getSectionNumber()).isEqualTo(redactionLogEntry.getContainingNodeId().get(0));
assertThat(savedRedactionLogEntry.get().getTextBefore()).isEqualTo(redactionLogEntry.getTextBefore());
assertThat(savedRedactionLogEntry.get().getTextAfter()).isEqualTo(redactionLogEntry.getTextAfter());
assertThat(savedRedactionLogEntry.get().getStartOffset()).isEqualTo(redactionLogEntry.getStartOffset());

View File

@ -0,0 +1,67 @@
package com.iqser.red.service.redaction.v1.server.service.document;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import com.iqser.red.service.redaction.v1.server.model.document.nodes.Section;
import com.iqser.red.service.redaction.v1.server.model.document.nodes.SemanticNode;
class SemanticNodeComparatorsTest {
@Test
public void testFirstSemanticNode() {
var node = new Section(List.of(0, 1), null, null, null);
var otherNode = new Section(List.of(0, 2), null, null, null);
List<SemanticNode> list = new ArrayList<>();
list.add(otherNode);
list.add(node);
list.sort(SemanticNodeComparators.first());
assertEquals(node, list.get(0));
}
@Test
public void testFirstSemanticNode2() {
var node = new Section(Collections.emptyList(), null, null, null);
var otherNode = new Section(List.of(0, 2), null, null, null);
List<SemanticNode> list = new ArrayList<>();
list.add(otherNode);
list.add(node);
list.sort(SemanticNodeComparators.first());
assertEquals(node, list.get(0));
}
@Test
public void testFirstSemanticNode3() {
var node = new Section(List.of(1, 5, 8), null, null, null);
var otherNode = new Section(List.of(0, 2), null, null, null);
List<SemanticNode> list = new ArrayList<>();
list.add(otherNode);
list.add(node);
list.sort(SemanticNodeComparators.first());
assertEquals(otherNode, list.get(0));
}
@Test
public void testFirstSemanticNode4() {
var node = new Section(List.of(1, 5, 8), null, null, null);
var otherNode = new Section(List.of(1, 5, 9), null, null, null);
List<SemanticNode> list = new ArrayList<>();
list.add(otherNode);
list.add(node);
list.sort(SemanticNodeComparators.first());
assertEquals(node, list.get(0));
}
}

View File

@ -49,7 +49,7 @@ declare GuidelineMapping
rule "StudyTitle.0.0: Study Title"
when
$titleCandidates: List() from collect (Entity(type == "study_title"))
$titleCandidates: List() from collect (Entity(type == "title"))
then
componentCreationService.firstOrElse("StudyTitle.0.0", "Study_Title", $titleCandidates, "No study title found!");
end
@ -180,38 +180,331 @@ rule "TestGuideline.2.0: Test Guideline 2"
end
rule "DefaultComponents.6.0: Experimental Starting Date"
rule "StartDate.0.0: Experimental Starting Date"
when
$startDates: List(!isEmpty()) from collect (Entity(type == "experimental_start_date"))
then
componentCreationService.convertDates("DefaultComponents.6.0", "Experimental_Starting_Date", $startDates);
componentCreationService.convertDates("StartDate.0.0", "Experimental_Starting_Date", $startDates);
end
rule "DefaultComponents.7.0: Experimental Completion Date"
rule "CompletionDate.0.0: Experimental Completion Date"
when
$endDates: List(!isEmpty()) from collect (Entity(type == "experimental_end_date"))
then
componentCreationService.convertDates("DefaultComponents.7.0", "Experimental_Completion_Date", $endDates);
componentCreationService.convertDates("CompletionDate.0.0", "Experimental_Completion_Date", $endDates);
end
rule "DefaultComponents.8.0: Certificate of analysis batch identification"
rule "AnalysisCertificate.0.0: Certificate of analysis batch identification"
when
$batchNumbers: List(!isEmpty()) from collect (Entity(type == "batch_number"))
then
componentCreationService.joiningUnique("DefaultComponents.8.0", "Batch_Number", $batchNumbers);
componentCreationService.joiningUnique("AnalysisCertificate.0.0", "Batch_Number", $batchNumbers);
end
rule "StudyConclusion.0.0: Study conclusion in first found section"
when
FileAttribute(label == "oecd_number", value == "425" || value == "430")
$oecdNumber: String() from List.of("402", "403", "404", "405", "425", "429", "436", "471")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$studyConclusions: List() from collect(Entity(type == "study_conclusion"))
then
componentCreationService.joiningUniqueFromFirstSectionOnly("Study_Conclusion.0.0", "Study_Conclusion", $studyConclusions);
end
rule "GuidelineDeviation.0.0: Guideline deviation as sentences"
when
$oecdNumber: String() from List.of("402", "403", "404", "405", "425", "429", "436", "471")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$guidelineDeviations: List() from collect (Entity(type == "guideline_deviation"))
then
componentCreationService.asSentences("GuidelineDeviation.0.0", "Deviation_from_the_Guideline", $guidelineDeviations);
end
rule "Species.0.0: First found species"
when
$oecdNumber: String() from List.of("402", "403", "404", "405", "425", "429", "436", "471")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$species: List() from collect (Entity(type == "species"))
then
componentCreationService.firstOrElse("Species.0.0", "Species", $species, "");
end
rule "Strain.0.0: First found strain"
when
$oecdNumber: String() from List.of("402", "403", "404", "405", "425", "429", "436", "471")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$strain: List() from collect (Entity(type == "strain"))
then
componentCreationService.firstOrElse("Strain.0.0", "Strain", $strain, "");
end
rule "Conclusion.0.0: Unique values of Conclusion LD50"
when
$oecdNumber: String() from List.of("402", "403", "425", "436")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$conclusions: List() from collect (Entity(type == "ld50_value"))
then
componentCreationService.joiningUnique("Conclusion.0.0", "Conclusion_LD50_mg_per_kg", $conclusions, "");
end
rule "Conclusion0.1.0: Greater than found"
when
$oecdNumber: String() from List.of("402", "403", "425", "436")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$conclusions: List(!isEmpty()) from collect (Entity(type == "ld50_greater"))
then
componentCreationService.create("Conclusion.1.0", "Conclusion_LD50_Greater_than", "Greater Than", "\"Greater than\" value found", $conclusions);
end
rule "Conclusion.1.1: Greater than not found"
when
$oecdNumber: String() from List.of("402", "403", "425", "436")
FileAttribute(label == "oecd_number", value == $oecdNumber)
not Entity(type == "ld50_greater")
then
componentCreationService.create("Conclusion.1.1", "Conclusion_LD50_Greater_than", "", "No \"Greater than\" value found");
end
rule "Conclusion.2.0: Minimum confidence as unique values"
when
$oecdNumber: String() from List.of("402", "403", "425", "436")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$conclusions: List() from collect (Entity(type == "confidence_minimal"))
then
componentCreationService.joiningUnique("Conclusion.2.0", "Conclusion_Minimum_Confidence", $conclusions);
end
rule "Conclusion.3.0: Maximum confidence as unique values"
when
$oecdNumber: String() from List.of("402", "403", "425", "436")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$conclusions: List() from collect (Entity(type == "confidence_maximal"))
then
componentCreationService.joiningUnique("Conclusion.3.0", "Conclusion_Maximum_Confidence", $conclusions);
end
rule "Necropsy.0.0: Necropsy findings from longest section"
when
FileAttribute(label == "oecd_number", value == "402")
$necropsies: List() from collect (Entity(type == "necropsy_findings"))
then
componentCreationService.joiningFromLongestSectionOnly("Necropsy.0.0", "Necropsy_Findings", $necropsies);
end
rule "Necropsy.1.0: Doses mg per kg of Bodyweight as one block"
when
FileAttribute(label == "oecd_number", value == "402")
$dosages: List() from collect (Entity(type == "doses_(mg_kg_bw)"))
then
componentCreationService.joining("Necropsy.1.0", "Doses_mg_per_kg_bw", $dosages, " ");
end
rule "Necropsy.2.0: Necropsy findings as one block"
when
$oecdNumber: String() from List.of("403", "436")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$necropsies: List() from collect (Entity(type == "necropsy_findings"))
then
componentCreationService.joining("Necropsy.2.0", "Necropsy_Findings", $necropsies, " ");
end
rule "Necropsy.3.0: Conducted with 4 hours of exposure as one block"
when
$oecdNumber: String() from List.of("403", "436")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$exposures: List() from collect (Entity(type == "4h_exposure"))
then
componentCreationService.joining("Necropsy.3.0", "Conducted_with_4_Hours_of_Exposure", $exposures, " ");
end
rule "StudyDesign.0.0: Study design as one block"
when
$oecdNumber: String() from List.of("404", "405", "429", "406", "428", "438", "439", "474", "487")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$studyDesigns: List() from collect (Entity(type == "study_design"))
then
componentCreationService.joining("StudyDesign.0.0", "Study_Design", $studyDesigns, " ");
end
rule "Results.0.0: Results and conclusions as joined values"
when
$oecdNumber: String() from List.of("406", "428", "438", "439", "474", "487")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$results: List() from collect (Entity(type == "results_and_conclusion"))
then
componentCreationService.joining("Results.0.0", "Results_and_Conclusions", $results, " ");
end
rule "WeightBehavior.0.0: Weight change behavior as sentences"
when
FileAttribute(label == "oecd_number", value == "402")
$weightChanges: List() from collect (Entity(type == "weight_behavior_changes"))
then
componentCreationService.asSentences("WeightBehavior.0.0", "Weight_Behavior_Changes", $weightChanges);
end
rule "MortalityStatement.0.0: Mortality statements as one block"
when
FileAttribute(label == "oecd_number", value == "402")
$mortalityStatements: List() from collect (Entity(type == "mortality_statement"))
then
componentCreationService.joining("MortalityStatement.0.0", "Mortality_Statement", $mortalityStatements, " ");
end
rule "ClinicalObservations.0.0: Clinical observations as sentences"
when
FileAttribute(label == "oecd_number", value == "403")
$observations: List() from collect (Entity(type == "clinical_observations"))
then
componentCreationService.asSentences("MortalityStatement.0.0", "Clinical_Observations", $observations);
end
rule "BodyWeight.0.0: Bodyweight changes as sentences"
when
FileAttribute(label == "oecd_number", value == "403")
$weightChanges: List() from collect (Entity(type == "bodyweight_changes"))
then
componentCreationService.asSentences("BodyWeight.0.0", "Body_Weight_Changes", $weightChanges);
end
rule "Detailing.0.0: Detailing of reported changes as one block"
when
$oecdNumber: String() from List.of("404", "405")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$detailings: List() from collect (Entity(type == "detailing"))
then
componentCreationService.joining("Detailing.0.0", "Detailing_of_Reported_Changes", $detailings, " ");
end
rule "Sex.0.0: Male sex found"
when
$oecdNumber: String() from List.of("405", "429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$males: List(!isEmpty) from collect (Entity(type == "sex", (value == "male" || value == "males")))
then
componentCreationService.create("Sex.0.0", "Sex", "male", "male sex found", $males);
end
rule "Sex.1.0: Female sex found"
when
$oecdNumber: String() from List.of("405", "429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$females: List(!isEmpty) from collect (Entity(type == "sex", (value == "female" || value == "females")))
then
componentCreationService.create("Sex.0.0", "Sex", "female", "female sex found", $females);
end
rule "NumberOfAnimals.0.0: Number of animals found"
when
$oecdNumber: String() from List.of("405", "429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$numberOfAnimals: Entity(type == "number_of_animals")
then
componentCreationService.create("NumberOfAnimals.0.0", "Number_of_Animals", $numberOfAnimals.getValue(), "Number of animals found directly", $numberOfAnimals);
end
rule "NumberOfAnimals.1.0: Count unique occurences of animals"
when
$oecdNumber: String() from List.of("405", "429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
not Entity(type == "number_of_animals")
$animals: List() from collect (Entity(type == "animal_number"))
then
componentCreationService.uniqueValueCount("NumberOfAnimals.1.0", "Number_of_Animals", $animals);
end
rule "ClinicalSigns.0.0: Clinical signs as sentences"
when
$oecdNumber: String() from List.of("425")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$clinicalSigns: List() from collect (Entity(type == "clinical_signs"))
then
componentCreationService.asSentences("ClinicalSigns.0.0", "Clinical_Signs", $clinicalSigns);
end
rule "DoseMortality.0.0: Dose mortality as sentences"
when
$oecdNumber: String() from List.of("425")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$doseMortalities: List() from collect (Entity(type == "dose_mortality" || type == "dose_mortality_dose"))
then
componentCreationService.joiningFromSameTableRow("DoseMortality.0.0", "Dose_Mortality", $doseMortalities);
end
rule "Mortality.0.0: Mortality as one block"
when
$oecdNumber: String() from List.of("425")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$mortalities: List() from collect (Entity(type == "mortality"))
then
componentCreationService.joining("Mortality.0.0", "Mortality", $mortalities, " ");
end
rule "Dosages.0.0: First found value of Dosages"
when
$oecdNumber: String() from List.of("425")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$mortalities: List() from collect (Entity(type == "mortality"))
then
componentCreationService.firstOrElse("Dosages.0.0", "Dosages", $mortalities, "");
end
rule "PrelimResults.0.0: Preliminary test results as sentences"
when
$oecdNumber: String() from List.of("429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$results: List() from collect (Entity(type == "preliminary_test_results"))
then
componentCreationService.asSentences("PrelimResults.0.0", "Preliminary_Test_Results", $results);
end
rule "TestResults.0.0: Test results as one block"
when
$oecdNumber: String() from List.of("429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$results: List() from collect (Entity(type == "test_results"))
then
componentCreationService.joining("TestResults.0.0", "Test_Results", $results, " ");
end
rule "PositiveControl.0.0: Was the definitive study conducted with positive control"
when
$oecdNumber: String() from List.of("429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$results: List() from collect (Entity(type == "positive_control"))
then
componentCreationService.joining("PositiveControl.0.0", "Was_the_definitive_study_conducted_with_positive_control", $results, " ");
end
rule "MainResults.0.0: Was the definitive study conducted with positive control"
when
$oecdNumber: String() from List.of("429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$results: List() from collect (Entity(type == "results_(main_study)"))
then
componentCreationService.joining("MainResults.0.0", "Results_Main_Study", $results, " ");
end
rule "UsedApproach.0.0: Was the definitive study conducted with positive control"
when
$oecdNumber: String() from List.of("429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
$results: List(!isEmpty()) from collect (Entity(type == "approach_used"))
then
componentCreationService.create("UsedApproach.0.0", "What_was_the_approach_used", "Group", "'Group' when approach used is present, else 'Individual'", $results);
end
rule "UsedApproach.1.0: Was the definitive study conducted with positive control"
when
$oecdNumber: String() from List.of("429")
FileAttribute(label == "oecd_number", value == $oecdNumber)
not Entity(type == "approach_used")
then
componentCreationService.create("UsedApproach.1.0", "What_was_the_approach_used", "Individual", "'Group' when approach used is present, else 'Individual'");
end
/*
rule "DefaultComponents.999.0: Create components for all unmapped entities."
salience -999
when
@ -220,7 +513,7 @@ rule "DefaultComponents.999.0: Create components for all unmapped entities."
componentCreationService.createComponentsForUnMappedEntities("DefaultComponents.999.0", $allEntities);
end
*/
//------------------------------------ Component merging rules ------------------------------------