From 288e0d3c51bc5ffd988ae7455bf13deac59eae9a Mon Sep 17 00:00:00 2001 From: Corina Olariu Date: Mon, 4 Nov 2024 14:24:18 +0100 Subject: [PATCH] RED-10186 Unlinked annotation with manual changes still linked and removed in specific corner case --- .../server/model/document/entity/IEntity.java | 17 ++- .../model/document/entity/MatchedRule.java | 7 +- .../service/EntityLogCreatorService.java | 3 - .../v1/server/RedactionIntegrationTest.java | 125 +++++++++++++++++- .../document/entity/TextEntityTest.java | 4 +- .../test/resources/files/new/test_file.pdf | Bin 0 -> 10163 bytes 6 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 redaction-service-v1/redaction-service-server-v1/src/test/resources/files/new/test_file.pdf diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/model/document/entity/IEntity.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/model/document/entity/IEntity.java index 3cc3cb3c..9929cc3c 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/model/document/entity/IEntity.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/model/document/entity/IEntity.java @@ -95,7 +95,9 @@ public interface IEntity { */ // Don't use default accessor pattern (e.g. isApplied()), as it might lead to errors in drools due to property-specific optimization of the drools planner. default boolean applied() { - + if (this.getMatchedRule().isHigherPriorityThanManual()) { + return getMatchedRule().isApplied(); + } return getManualOverwrite().getApplied() .orElse(getMatchedRule().isApplied()); } @@ -118,6 +120,9 @@ public interface IEntity { * @return True if ignored, false otherwise. */ default boolean ignored() { + if (this.getMatchedRule().isHigherPriorityThanManual()) { + return getMatchedRule().isIgnored(); + } return getManualOverwrite().getIgnored() .orElse(getMatchedRule().isIgnored()); @@ -130,8 +135,10 @@ public interface IEntity { * @return True if removed, false otherwise. */ default boolean removed() { - - return getManualOverwrite().getRemoved() + if (this.getMatchedRule().isHigherPriorityThanManual()) { + return getMatchedRule().isRemoved(); + } + return getManualOverwrite().getRemoved() .orElse(getMatchedRule().isRemoved()); } @@ -142,7 +149,9 @@ public interface IEntity { * @return True if resized, false otherwise. */ default boolean resized() { - + if (this.getMatchedRule().isHigherPriorityThanManual()) { + return getMatchedRule().isRemoved(); + } return getManualOverwrite().getResized() .orElse(false); } diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/model/document/entity/MatchedRule.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/model/document/entity/MatchedRule.java index a72a9adf..02f485c4 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/model/document/entity/MatchedRule.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/model/document/entity/MatchedRule.java @@ -28,8 +28,9 @@ public final class MatchedRule implements Comparable { public static final RuleType FINAL_TYPE = RuleType.fromString("FINAL"); public static final RuleType ELIMINATION_RULE_TYPE = RuleType.fromString("X"); public static final RuleType IMPORTED_TYPE = RuleType.fromString("IMP"); + public static final RuleType MANUAL_TYPE = RuleType.fromString("MAN"); public static final RuleType DICTIONARY_TYPE = RuleType.fromString("DICT"); - private static final List RULE_TYPE_PRIORITIES = List.of(FINAL_TYPE, ELIMINATION_RULE_TYPE, IMPORTED_TYPE, DICTIONARY_TYPE); + private static final List RULE_TYPE_PRIORITIES = List.of(FINAL_TYPE, ELIMINATION_RULE_TYPE, IMPORTED_TYPE, MANUAL_TYPE, DICTIONARY_TYPE); RuleIdentifier ruleIdentifier; @Builder.Default @@ -56,6 +57,10 @@ public final class MatchedRule implements Comparable { return MatchedRule.builder().ruleIdentifier(RuleIdentifier.empty()).build(); } + public boolean isHigherPriorityThanManual() { + return (-1 < RULE_TYPE_PRIORITIES.indexOf(this.ruleIdentifier.type())) && + (RULE_TYPE_PRIORITIES.indexOf(this.ruleIdentifier.type()) < RULE_TYPE_PRIORITIES.indexOf(MANUAL_TYPE)); + } /** * Returns a modified instance of {@link MatchedRule} based on its applied status. diff --git a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/service/EntityLogCreatorService.java b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/service/EntityLogCreatorService.java index 421f4e3d..380a1cf7 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/service/EntityLogCreatorService.java +++ b/redaction-service-v1/redaction-service-server-v1/src/main/java/com/iqser/red/service/redaction/v1/server/service/EntityLogCreatorService.java @@ -390,9 +390,6 @@ public class EntityLogCreatorService { Set engines = currentEngines != null ? new HashSet<>(currentEngines) : new HashSet<>(); - if (manualChangeOverwrite != null && !manualChangeOverwrite.getManualChangeLog().isEmpty()) { - engines.add(Engine.MANUAL); - } return engines; } diff --git a/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/RedactionIntegrationTest.java b/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/RedactionIntegrationTest.java index 8dc9ad8a..b1eba29c 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/RedactionIntegrationTest.java +++ b/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/RedactionIntegrationTest.java @@ -3,6 +3,7 @@ package com.iqser.red.service.redaction.v1.server; import static com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService.StorageIdUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -45,11 +46,13 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeResu 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.ChangeType; +import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Engine; import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog; import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry; import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryState; 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.ManualRedactionType; +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ForceRedactionRequest; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.IdRemoval; @@ -61,6 +64,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Point; import com.iqser.red.service.redaction.v1.server.annotate.AnnotateRequest; @@ -2164,7 +2168,6 @@ public class RedactionIntegrationTest extends RulesIntegrationTest { String assessment = "assessment"; dossierDictionary.get(DICTIONARY_AUTHOR).add(assessment); reanlysisVersions.put(assessment, 1L); - when(dictionaryClient.getVersionForDossier(TEST_DOSSIER_ID)).thenReturn(1L); analyzeService.reanalyze(request); @@ -2191,6 +2194,126 @@ public class RedactionIntegrationTest extends RulesIntegrationTest { } + @Test + @SneakyThrows + @Order(3) + public void testLocalRemovalOfDictEntry() { + + String EFSA_SANITISATION_RULES = loadFromClassPath("drools/efsa_sanitisation.drl"); + when(rulesClient.getRules(TEST_DOSSIER_TEMPLATE_ID, RuleFileType.ENTITY)).thenReturn(JSONPrimitive.of(EFSA_SANITISATION_RULES)); + + String pdfFile = "files/new/test_file.pdf"; + + String manualDictAddId = UUID.randomUUID().toString(); + String manualAddId = UUID.randomUUID().toString(); + String valueToAdd = "Crandu Seku Laku Meku"; + +// positions=[Rectangle(topLeft=Point(x=56.8, y=527.264), width=120.96, height=15.408, page=1)], + List fullPositions = List.of(Rectangle.builder().topLeftX(56.8f).topLeftY(527.264f).width(120.96f).height(15.408f).page(1).build()); + ManualRedactionEntry manualDictRedactionEntry = new ManualRedactionEntry(); + manualDictRedactionEntry.setAnnotationId(manualDictAddId); + manualDictRedactionEntry.setFileId("fileId"); + manualDictRedactionEntry.setUser("test"); + manualDictRedactionEntry.setType("CBI_author"); + manualDictRedactionEntry.setRectangle(false); + manualDictRedactionEntry.setAddToDictionary(true); + manualDictRedactionEntry.setDictionaryEntryType(DictionaryEntryType.ENTRY); + manualDictRedactionEntry.setRequestDate(OffsetDateTime.now()); + manualDictRedactionEntry.setValue(valueToAdd); + manualDictRedactionEntry.setReason("Dictionary Request"); + manualDictRedactionEntry.setPositions(fullPositions); + + var idUsedForRemoval = "4d92d86e7d70ab9bb5c0d554bfa3c7f0"; + var idRemoval = getIdRemoval(idUsedForRemoval); + + AnalyzeRequest request = uploadFileToStorage(pdfFile); + Set entriesToAdd = Set.of(manualDictRedactionEntry); + Set entriesToRemove = Set.of(idRemoval); + request.setManualRedactions(ManualRedactions.builder().entriesToAdd(entriesToAdd).idsToRemove(entriesToRemove).build()); + analyzeDocumentStructure(LayoutParsingType.REDACT_MANAGER, request); + request.setAnalysisNumber(0); + mockDictionaryCalls(1L); + + dossierDictionary.get(DICTIONARY_AUTHOR).add(valueToAdd); + when(dictionaryClient.getVersionForDossier(TEST_DOSSIER_ID)).thenReturn(2L); + reanlysisVersions.put(valueToAdd, 0L); + + analyzeService.analyze(request); + + var entityLog = redactionStorageService.getEntityLog(TEST_DOSSIER_ID, TEST_FILE_ID); + assertEquals(entityLog.getEntityLogEntry().size(), 4); + + EntityLogEntry entityLogEntry1 = entityLog.getEntityLogEntry() + .stream() + .filter(entityLogEntry -> entityLogEntry.getId().equals(idUsedForRemoval)) + .findFirst() + .get(); + assertEquals(entityLogEntry1.getState(), EntryState.IGNORED); + assertEquals(entityLogEntry1.getMatchedRule(), "CBI.0.3"); + assertFalse(entityLogEntry1.getEngines().contains(Engine.MANUAL)); +//-----------force local + var processedDate = OffsetDateTime.now(); + manualDictRedactionEntry.setProcessedDate(processedDate); + idRemoval.setProcessedDate(processedDate); + + //[Rectangle(topLeft=Point(x=56.8, y=528.9), width=120.96001, height=12.642, page=1)] + List localfullPositions = List.of(Rectangle.builder().topLeftX(56.8f).topLeftY(528.9f).width(120.96f).height(12.642f).page(1).build()); + ManualRedactionEntry manualRedactionEntry = new ManualRedactionEntry(); + manualRedactionEntry.setAnnotationId(manualAddId); + manualRedactionEntry.setFileId("fileId"); + manualRedactionEntry.setUser("test"); + manualRedactionEntry.setType("CBI_author"); + manualRedactionEntry.setRectangle(false); + manualRedactionEntry.setAddToDictionary(false); + manualRedactionEntry.setDictionaryEntryType(DictionaryEntryType.ENTRY); + manualRedactionEntry.setRequestDate(OffsetDateTime.now()); + manualRedactionEntry.setValue(valueToAdd); + manualRedactionEntry.setReason("Author found, removed by manual override"); + manualRedactionEntry.setSection("Header: This is my test"); + manualRedactionEntry.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + manualRedactionEntry.setTextBefore("Lorem My Ipsum "); + manualRedactionEntry.setTextAfter("Crandu Seku Laku"); + manualRedactionEntry.setPositions(localfullPositions); + + ManualForceRedaction forceRequest = new ManualForceRedaction(); + forceRequest.setAnnotationId(manualAddId); + forceRequest.setLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002"); + forceRequest.setUser("test"); + forceRequest.setRequestDate(OffsetDateTime.now()); + + Set forceRedactions = Set.of(forceRequest); + request.setManualRedactions(ManualRedactions.builder() + .entriesToAdd(Set.of(manualDictRedactionEntry, manualRedactionEntry)) + .idsToRemove(entriesToRemove) + .forceRedactions(forceRedactions) + .build()); + request.setAnalysisNumber(2); + + analyzeService.reanalyze(request); + + entityLog = redactionStorageService.getEntityLog(TEST_DOSSIER_ID, TEST_FILE_ID); + + assertEquals(entityLog.getEntityLogEntry().size(), 5); + + entityLogEntry1 = entityLog.getEntityLogEntry() + .stream() + .filter(entityLogEntry -> entityLogEntry.getId().equals(idUsedForRemoval)) + .findFirst() + .get(); + var entityLogEntry2 = entityLog.getEntityLogEntry() + .stream() + .filter(entityLogEntry -> entityLogEntry.getId().equals(manualAddId)) + .findFirst() + .get(); + assertEquals(entityLogEntry1.getState(), EntryState.REMOVED); + assertEquals(entityLogEntry1.getMatchedRule(), "X.11.1"); + assertFalse(entityLogEntry1.getEngines().contains(Engine.MANUAL)); + assertEquals(entityLogEntry2.getState(), EntryState.APPLIED); + assertEquals(entityLogEntry2.getMatchedRule(), "MAN.5.0"); + dossierDictionary.get(DICTIONARY_AUTHOR).remove(valueToAdd); + } + + @Test @SneakyThrows public void testDocumentDataFallback() { diff --git a/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/document/entity/TextEntityTest.java b/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/document/entity/TextEntityTest.java index 75a77785..32fb8fbd 100644 --- a/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/document/entity/TextEntityTest.java +++ b/redaction-service-v1/redaction-service-server-v1/src/test/java/com/iqser/red/service/redaction/v1/server/document/entity/TextEntityTest.java @@ -30,8 +30,8 @@ public class TextEntityTest { entity.apply("MAN.3.0", ""); entity.apply("MAN.3.3", ""); entity.skip("CBI.13.2", ""); - assertThat(entity.getMatchedRule().getRuleIdentifier().toString()).isEqualTo("CBI.13.2"); - assertThat(entity.getMatchedRuleUnit()).isEqualTo(13); + assertThat(entity.getMatchedRule().getRuleIdentifier().toString()).isEqualTo("MAN.3.0"); + assertThat(entity.getMatchedRuleUnit()).isEqualTo(3); } diff --git a/redaction-service-v1/redaction-service-server-v1/src/test/resources/files/new/test_file.pdf b/redaction-service-v1/redaction-service-server-v1/src/test/resources/files/new/test_file.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8daa5ae316b2ae2ac750b6aa966d0404c033fc26 GIT binary patch literal 10163 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%0Al)M+~PBmu|6e1JV)o&7? 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 L#*F>LfB^hI^RGNc literal 0 HcmV?d00001