diff --git a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningService.java b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningService.java index 52d93b02c68..8202e486401 100644 --- a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningService.java +++ b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningService.java @@ -69,5 +69,6 @@ void rejectSpanSuggestion(String aSessionOwner, User aDataOwner, AnnotationLayer SpanSuggestion aSuggestion); void skipSpanSuggestion(String aSessionOwner, User aDataOwner, AnnotationLayer aLayer, - SpanSuggestion aSuggestion); + SpanSuggestion aSuggestion) + throws AnnotationException; } diff --git a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningServiceImpl.java b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningServiceImpl.java index 2ca011c1855..22e43fa316d 100644 --- a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningServiceImpl.java +++ b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningServiceImpl.java @@ -261,6 +261,7 @@ public void rejectSpanSuggestion(String aSessionOwner, User aDataOwner, Annotati @Transactional public void skipSpanSuggestion(String aSessionOwner, User aDataOwner, AnnotationLayer aLayer, SpanSuggestion aSuggestion) + throws AnnotationException { var document = documentService.getSourceDocument(aLayer.getProject(), aSuggestion.getDocumentName()); diff --git a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.java b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.java index 9eb0ee8141b..f6d3b07d865 100644 --- a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.java +++ b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.java @@ -94,6 +94,9 @@ import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler; import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.event.AjaxRecommendationAcceptedEvent; +import de.tudarmstadt.ukp.inception.recommendation.api.event.AjaxRecommendationRejectedEvent; +import de.tudarmstadt.ukp.inception.recommendation.api.event.PredictionsSwitchedEvent; import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecord; import de.tudarmstadt.ukp.inception.recommendation.api.model.Offset; @@ -102,9 +105,6 @@ import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup; import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionGroup; import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionGroup.Delta; -import de.tudarmstadt.ukp.inception.recommendation.event.AjaxRecommendationAcceptedEvent; -import de.tudarmstadt.ukp.inception.recommendation.event.AjaxRecommendationRejectedEvent; -import de.tudarmstadt.ukp.inception.recommendation.event.PredictionsSwitchedEvent; import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState; import de.tudarmstadt.ukp.inception.rendering.editorstate.FeatureState; import de.tudarmstadt.ukp.inception.rendering.pipeline.RenderAnnotationsEvent; @@ -701,12 +701,15 @@ private void actionSkip(AjaxRequestTarget aTarget) throws AnnotationException var sessionOwner = userService.getCurrentUsername(); - alStateModel.getObject().getSuggestion().ifPresent(suggestion -> { - requestClearningSelectionAndJumpingToSuggestion(); - activeLearningService.skipSpanSuggestion(sessionOwner, getModelObject().getUser(), - alStateModel.getObject().getLayer(), suggestion); - moveToNextSuggestion(aTarget); - }); + var maybeSuggestion = alStateModel.getObject().getSuggestion(); + if (!maybeSuggestion.isPresent()) { + return; + } + + requestClearningSelectionAndJumpingToSuggestion(); + activeLearningService.skipSpanSuggestion(sessionOwner, getModelObject().getUser(), + alStateModel.getObject().getLayer(), maybeSuggestion.get()); + moveToNextSuggestion(aTarget); } private void actionReject(AjaxRequestTarget aTarget) throws AnnotationException diff --git a/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataAnnotationSelectionPanel.html b/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataAnnotationSelectionPanel.html index da4e6bca4d6..e3ae368c3ca 100644 --- a/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataAnnotationSelectionPanel.html +++ b/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataAnnotationSelectionPanel.html @@ -55,6 +55,17 @@ title="Delete"> + + diff --git a/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataAnnotationSelectionPanel.java b/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataAnnotationSelectionPanel.java index 34b097407b0..4b00cde49e9 100644 --- a/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataAnnotationSelectionPanel.java +++ b/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataAnnotationSelectionPanel.java @@ -17,6 +17,9 @@ */ package de.tudarmstadt.ukp.inception.ui.core.docanno.sidebar; +import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.EXTENSION_ID; +import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation.MAIN_EDITOR; +import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.ACCEPTED; import static de.tudarmstadt.ukp.inception.support.lambda.HtmlElementEvents.CLICK; import static de.tudarmstadt.ukp.inception.support.lambda.LambdaBehavior.enabledWhen; import static de.tudarmstadt.ukp.inception.support.lambda.LambdaBehavior.visibleWhen; @@ -24,6 +27,7 @@ import static java.util.Collections.emptyList; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; +import static org.apache.wicket.event.Broadcast.BREADTH; import java.io.IOException; import java.io.Serializable; @@ -32,10 +36,13 @@ import java.util.List; import java.util.Objects; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.uima.UIMAException; import org.apache.uima.cas.CAS; +import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; +import org.apache.wicket.Session; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.feedback.IFeedback; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -59,11 +66,14 @@ import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.AnnotationPage; import de.tudarmstadt.ukp.inception.annotation.events.FeatureValueUpdatedEvent; import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler; +import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.event.AjaxRecommendationAcceptedEvent; import de.tudarmstadt.ukp.inception.recommendation.api.model.MetadataSuggestion; import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState; import de.tudarmstadt.ukp.inception.rendering.vmodel.VID; @@ -89,6 +99,7 @@ public class DocumentMetadataAnnotationSelectionPanel private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final String CID_LABEL = "label"; + private static final String CID_SCORE = "score"; private static final String CID_LAYERS = "layers"; private static final String CID_ANNOTATIONS = "annotations"; private static final String CID_LAYER = "layer"; @@ -96,11 +107,15 @@ public class DocumentMetadataAnnotationSelectionPanel private static final String CID_ANNOTATIONS_CONTAINER = "annotationsContainer"; private static final String CID_ANNOTATION_DETAILS = "annotationDetails"; private static final String CID_DELETE = "delete"; + private static final String CID_ACCEPT = "accept"; + private static final String CID_REJECT = "reject"; private @SpringBean LayerSupportRegistry layerSupportRegistry; private @SpringBean FeatureSupportRegistry fsRegistry; private @SpringBean AnnotationSchemaService annotationService; private @SpringBean RecommendationService recommendationService; + private @SpringBean LearningRecordService learningRecordService; + private @SpringBean UserDao userService; private final AnnotationPage annotationPage; private final CasProvider jcasProvider; @@ -163,6 +178,44 @@ public DocumentMetadataAnnotationSelectionPanel(String aId, CasProvider aCasProv add(noLayersWarning); } + private void actionAccept(AjaxRequestTarget aTarget, AnnotationListItem aItem) + { + try { + var page = (AnnotationPage) aTarget.getPage(); + var dataOwner = state.getObject().getUser().getUsername(); + var sessionOwner = userService.getCurrentUser(); + var suggestion = (MetadataSuggestion) recommendationService + .getPredictions(sessionOwner, state.getObject().getProject()) + .getPredictionByVID(state.getObject().getDocument(), aItem.vid).get(); + var layer = annotationService.getLayer(suggestion.getLayerId()); + var feature = annotationService.getFeature(suggestion.getFeature(), layer); + var adapter = (DocumentMetadataLayerAdapter) annotationService.getAdapter(layer); + + var aCas = jcasProvider.get(); + + var annotation = new DocumentMetadataRecommendationSupport(recommendationService) + .acceptSuggestion(sessionOwner.getUsername(), state.getObject().getDocument(), + dataOwner, aCas, adapter, feature, suggestion, MAIN_EDITOR, ACCEPTED); + + page.writeEditorCas(aCas); + + // Set selection to the accepted annotation and select it and load it into the detail + // editor + // state.getObject().getSelection().set(adapter.select(VID.of(annotation), annotation)); + + // Send a UI event that the suggestion has been accepted + page.send(page, BREADTH, + new AjaxRecommendationAcceptedEvent(aTarget, state.getObject(), aItem.vid)); + } + catch (Exception e) { + handleException(this, aTarget, e); + } + } + + private void actionReject(AjaxRequestTarget a$, AnnotationListItem aAnnotationListItem) + { + } + private void actionCreate(AjaxRequestTarget aTarget) throws AnnotationException, IOException { try { @@ -297,8 +350,12 @@ protected void populateItem(ListItem aItem) Model.of(vid), jcasProvider, annotationPage, actionHandler, state); aItem.add(detailPanel); - container.add(new LambdaAjaxEventBehavior(CLICK, - $ -> actionSelect($, container, detailPanel))); + var isSuggestion = EXTENSION_ID.equals(aItem.getModelObject().vid.getExtensionId()); + + if (!isSuggestion) { + container.add(new LambdaAjaxEventBehavior(CLICK, + $ -> actionSelect($, container, detailPanel))); + } detailPanel.add(visibleWhen(() -> isExpanded(aItem, container))); @@ -309,24 +366,42 @@ protected void populateItem(ListItem aItem) } var close = new WebMarkupContainer("close"); - close.add(visibleWhen(() -> isExpanded(aItem, container))); + close.add(visibleWhen(() -> isExpanded(aItem, container) && !isSuggestion)); close.setOutputMarkupId(true); container.add(close); var open = new WebMarkupContainer("open"); - open.add(visibleWhen(() -> !isExpanded(aItem, container))); + open.add(visibleWhen(() -> !isExpanded(aItem, container) && !isSuggestion)); open.setOutputMarkupId(true); container.add(open); - container.add(new Label(CID_LABEL).add(visibleWhen( - () -> !aItem.getModelObject().singleton && !isExpanded(aItem, container)))); - container.setOutputMarkupId(true); + container.add(new Label(CID_LABEL, + StringUtils.defaultIfEmpty(aItem.getModelObject().label, "[No label]")) + .add(visibleWhen(() -> !aItem.getModelObject().singleton + && !isExpanded(aItem, container)))); - aItem.add(new LambdaAjaxLink(CID_DELETE, $ -> actionDelete($, detailPanel)) - .add(visibleWhen(() -> !aItem.getModelObject().singleton)) + aItem.queue(new LambdaAjaxLink(CID_DELETE, $ -> actionDelete($, detailPanel)) + .add(AttributeModifier.replace("title", aItem.getModelObject().vid)) + .add(visibleWhen(() -> !aItem.getModelObject().singleton && !isSuggestion)) .add(enabledWhen(() -> annotationPage.isEditable() && !aItem.getModelObject().layer.isReadonly()))); + aItem.queue(new Label(CID_SCORE, String.format(Session.get().getLocale(), "%.2f", + aItem.getModelObject().score)).add(visibleWhen( + () -> isSuggestion && aItem.getModelObject().score != 0.0d))); + + aItem.queue( + new LambdaAjaxLink(CID_ACCEPT, $ -> actionAccept($, aItem.getModelObject())) + .add(AttributeModifier.replace("title", aItem.getModelObject().vid)) + .add(visibleWhen(() -> isSuggestion && annotationPage.isEditable() + && !aItem.getModelObject().layer.isReadonly()))); + + aItem.queue( + new LambdaAjaxLink(CID_REJECT, $ -> actionReject($, aItem.getModelObject())) + .add(AttributeModifier.replace("title", aItem.getModelObject().vid)) + .add(visibleWhen(() -> isSuggestion && annotationPage.isEditable() + && !aItem.getModelObject().layer.isReadonly()))); + aItem.setOutputMarkupId(true); } @@ -393,7 +468,7 @@ private List listAnnotations(AnnotationLayer aLayer) for (var fs : cas.select(adapter.getAnnotationType(cas))) { var renderedFeatures = renderer.renderLabelFeatureValues(adapter, fs, features); var labelText = TypeUtil.getUiLabelText(renderedFeatures); - items.add(new AnnotationListItem(VID.of(fs), labelText, aLayer, singleton)); + items.add(new AnnotationListItem(VID.of(fs), labelText, aLayer, singleton, 0.0d)); } // --- Populate with predictions --- @@ -410,7 +485,7 @@ private List listAnnotations(AnnotationLayer aLayer) var annotation = featureSupport.renderFeatureValue(feature, suggestion.getLabel()); items.add(new AnnotationListItem(suggestion.getVID(), annotation, aLayer, - singleton)); + singleton, suggestion.getScore())); } } } @@ -486,14 +561,16 @@ private class AnnotationListItem final String label; final AnnotationLayer layer; final boolean singleton; + final double score; public AnnotationListItem(VID aVid, String aLabel, AnnotationLayer aLayer, - boolean aSingleton) + boolean aSingleton, double aScore) { vid = aVid; label = aLabel; layer = aLayer; singleton = aSingleton; + score = aScore; } } } diff --git a/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataRecommendationSupport.java b/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataRecommendationSupport.java new file mode 100644 index 00000000000..2ea4507b722 --- /dev/null +++ b/inception/inception-layer-docmetadata/src/main/java/de/tudarmstadt/ukp/inception/ui/core/docanno/sidebar/DocumentMetadataRecommendationSupport.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.ui.core.docanno.sidebar; + +import org.apache.commons.lang3.NotImplementedException; +import org.apache.uima.cas.AnnotationBaseFS; +import org.apache.uima.cas.CAS; +import org.apache.uima.jcas.tcas.Annotation; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.inception.recommendation.api.LayerRecommendationSupport; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction; +import de.tudarmstadt.ukp.inception.recommendation.api.model.MetadataSuggestion; +import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; +import de.tudarmstadt.ukp.inception.ui.core.docanno.layer.DocumentMetadataLayerAdapter; +import de.tudarmstadt.ukp.inception.ui.core.docanno.layer.DocumentMetadataLayerTraits; + +public class DocumentMetadataRecommendationSupport + implements LayerRecommendationSupport + +{ + private final RecommendationService recommendationService; + + public DocumentMetadataRecommendationSupport(RecommendationService aRecommendationService) + { + recommendationService = aRecommendationService; + } + + @Override + public AnnotationBaseFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, + String aDataOwner, CAS aCas, DocumentMetadataLayerAdapter aAdapter, + AnnotationFeature aFeature, MetadataSuggestion aSuggestion, + LearningRecordChangeLocation aLocation, LearningRecordUserAction aAction) + throws AnnotationException + { + var aValue = aSuggestion.getLabel(); + + var candidates = aCas. select(aAdapter.getAnnotationTypeName()) // + .asList(); + + var candidateWithEmptyLabel = candidates.stream() // + .filter(c -> aAdapter.getFeatureValue(aFeature, c) == null) // + .findFirst(); + + AnnotationBaseFS annotation; + if (candidateWithEmptyLabel.isPresent()) { + // If there is an annotation where the predicted feature is unset, use it ... + annotation = candidateWithEmptyLabel.get(); + } + else if (candidates.isEmpty() || !aAdapter.getTraits(DocumentMetadataLayerTraits.class) + .map(DocumentMetadataLayerTraits::isSingleton).orElse(false)) { + // ... if not or if stacking is allowed, then we create a new annotation - this also + // takes care of attaching to an annotation if necessary + var newAnnotation = aAdapter.add(aDocument, aDataOwner, aCas); + annotation = newAnnotation; + } + else { + // ... if yes and stacking is not allowed, then we update the feature on the existing + // annotation + annotation = candidates.get(0); + } + + recommendationService.commmitAcceptedLabel(aSessionOwner, aDocument, aDataOwner, aCas, + aAdapter, aFeature, aSuggestion, aValue, annotation, aLocation, + LearningRecordUserAction.ACCEPTED); + + return annotation; + } + + @Override + public void rejectSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, + MetadataSuggestion aSuggestion, LearningRecordChangeLocation aAction) + throws AnnotationException + { + throw new NotImplementedException("Not yet implemented"); + } + + @Override + public void skipSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, + MetadataSuggestion aSuggestion, LearningRecordChangeLocation aAction) + throws AnnotationException + { + throw new NotImplementedException("Not yet implemented"); + } +} diff --git a/inception/inception-recommendation-api/pom.xml b/inception/inception-recommendation-api/pom.xml index 0358b3ee5a7..e7df5b98c83 100644 --- a/inception/inception-recommendation-api/pom.xml +++ b/inception/inception-recommendation-api/pom.xml @@ -75,6 +75,11 @@ org.apache.uima uimafit-core + + + org.springframework + spring-context + org.apache.commons diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/LayerRecommendationSupport.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/LayerRecommendationSupport.java new file mode 100644 index 00000000000..b3bb67c17b7 --- /dev/null +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/LayerRecommendationSupport.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.recommendation.api; + +import org.apache.uima.cas.AnnotationBaseFS; +import org.apache.uima.cas.CAS; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction; +import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; +import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter; + +public interface LayerRecommendationSupport +{ + AnnotationBaseFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, + String aDataOwner, CAS aCas, T aAdapter, AnnotationFeature aFeature, S aSuggestion, + LearningRecordChangeLocation aLocation, LearningRecordUserAction aAction) + throws AnnotationException; + + void rejectSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, + S suggestion, LearningRecordChangeLocation aAction) + throws AnnotationException; + + void skipSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, + S suggestion, LearningRecordChangeLocation aAction) + throws AnnotationException; +} diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/RecommendationService.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/RecommendationService.java index fed3f67fafe..b3013335c4d 100644 --- a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/RecommendationService.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/RecommendationService.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Optional; +import org.apache.uima.cas.AnnotationBaseFS; import org.apache.uima.cas.CAS; import org.apache.uima.cas.text.AnnotationFS; @@ -48,6 +49,7 @@ import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommenderContext; import de.tudarmstadt.ukp.inception.scheduling.TaskMonitor; import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; +import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter; import de.tudarmstadt.ukp.inception.support.logging.LogMessageGroup; /** @@ -154,6 +156,10 @@ Optional getEvaluatedRecommender(User aSessionOwner, */ void putContext(User aSessionOwner, Recommender aRecommender, RecommenderContext aContext); + /** + * @deprecated Obtain {@link LayerRecommendationSupport} for span layer and use that instead. + */ + @Deprecated AnnotationFS correctSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, CAS aCas, SpanAdapter aAdapter, AnnotationFeature aFeature, SpanSuggestion aOriginalSuggestion, SpanSuggestion aCorrectedSuggestion, @@ -181,7 +187,9 @@ AnnotationFS correctSuggestion(String aSessionOwner, SourceDocument aDocument, * @return the created/updated annotation. * @throws AnnotationException * if there was an annotation-level problem + * @deprecated Obtain {@link LayerRecommendationSupport} for span layer and use that instead. */ + @Deprecated AnnotationFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, CAS aCas, SpanAdapter aAdapter, AnnotationFeature aFeature, SpanSuggestion aSuggestion, LearningRecordChangeLocation aLocation) @@ -210,18 +218,32 @@ AnnotationFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, St * @return the created/updated annotation. * @throws AnnotationException * if there was an annotation-level problem + * @deprecated Obtain {@link LayerRecommendationSupport} for relation layer and use that + * instead. */ + @Deprecated AnnotationFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, CAS aCas, RelationAdapter aAdapter, AnnotationFeature aFeature, RelationSuggestion aSuggestion, LearningRecordChangeLocation aLocation, LearningRecordUserAction aAction) throws AnnotationException; + /** + * @deprecated Obtain {@link LayerRecommendationSupport} for relation layer and use that + * instead. + */ + @Deprecated void rejectSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, AnnotationSuggestion suggestion, LearningRecordChangeLocation aAction); + /** + * @deprecated Obtain {@link LayerRecommendationSupport} for relation layer and use that + * instead. + */ + @Deprecated void skipSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, - AnnotationSuggestion suggestion, LearningRecordChangeLocation aAction); + AnnotationSuggestion suggestion, LearningRecordChangeLocation aAction) + throws AnnotationException; /** * Compute predictions. @@ -296,4 +318,10 @@ void setPredictForAllDocuments(String aSessionOwner, Project aProject, long countEnabledRecommenders(); Progress getProgressTowardsNextEvaluation(User aSessionOwner, Project aProject); + + void commmitAcceptedLabel(String aSessionOwner, SourceDocument aDocument, String aDataOwner, + CAS aCas, TypeAdapter aAdapter, AnnotationFeature aFeature, + AnnotationSuggestion aSuggestion, String aValue, AnnotationBaseFS aAnnotation, + LearningRecordChangeLocation aLocation, LearningRecordUserAction aAction) + throws AnnotationException; } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/AjaxRecommendationAcceptedEvent.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/AjaxRecommendationAcceptedEvent.java similarity index 97% rename from inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/AjaxRecommendationAcceptedEvent.java rename to inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/AjaxRecommendationAcceptedEvent.java index cbd36373e1c..1818f991147 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/AjaxRecommendationAcceptedEvent.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/AjaxRecommendationAcceptedEvent.java @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.tudarmstadt.ukp.inception.recommendation.event; +package de.tudarmstadt.ukp.inception.recommendation.api.event; import org.apache.wicket.ajax.AjaxRequestTarget; diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/AjaxRecommendationRejectedEvent.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/AjaxRecommendationRejectedEvent.java similarity index 96% rename from inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/AjaxRecommendationRejectedEvent.java rename to inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/AjaxRecommendationRejectedEvent.java index 286fe239175..546d2da2e09 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/AjaxRecommendationRejectedEvent.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/AjaxRecommendationRejectedEvent.java @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.tudarmstadt.ukp.inception.recommendation.event; +package de.tudarmstadt.ukp.inception.recommendation.api.event; import org.apache.wicket.ajax.AjaxRequestTarget; diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/PredictionsSwitchedEvent.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/PredictionsSwitchedEvent.java similarity index 92% rename from inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/PredictionsSwitchedEvent.java rename to inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/PredictionsSwitchedEvent.java index 47ebff97645..daa999622ba 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/PredictionsSwitchedEvent.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/PredictionsSwitchedEvent.java @@ -15,12 +15,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.tudarmstadt.ukp.inception.recommendation.event; +package de.tudarmstadt.ukp.inception.recommendation.api.event; import org.springframework.context.ApplicationEvent; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; -import de.tudarmstadt.ukp.inception.recommendation.api.event.TransientAnnotationStateChangedEvent; import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState; import de.tudarmstadt.ukp.inception.support.wicket.event.HybridApplicationUIEvent; diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/RecommendationAcceptedEvent.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/RecommendationAcceptedEvent.java similarity index 90% rename from inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/RecommendationAcceptedEvent.java rename to inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/RecommendationAcceptedEvent.java index 14b6f4cd35a..6cfd8fc2005 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/RecommendationAcceptedEvent.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/RecommendationAcceptedEvent.java @@ -15,9 +15,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.tudarmstadt.ukp.inception.recommendation.event; +package de.tudarmstadt.ukp.inception.recommendation.api.event; -import org.apache.uima.cas.text.AnnotationFS; +import org.apache.uima.cas.AnnotationBaseFS; import org.springframework.context.ApplicationEvent; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; @@ -31,12 +31,12 @@ public class RecommendationAcceptedEvent private final SourceDocument document; private final String user; - private final AnnotationFS fs; + private final AnnotationBaseFS fs; private final AnnotationFeature feature; private final Object recommendedValue; public RecommendationAcceptedEvent(Object aSource, SourceDocument aDocument, String aUser, - AnnotationFS aFS, AnnotationFeature aFeature, Object aRecommendedValue) + AnnotationBaseFS aFS, AnnotationFeature aFeature, Object aRecommendedValue) { super(aSource); @@ -57,7 +57,7 @@ public String getUser() return user; } - public AnnotationFS getFS() + public AnnotationBaseFS getFS() { return fs; } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/RecommendationRejectedEvent.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/RecommendationRejectedEvent.java similarity index 95% rename from inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/RecommendationRejectedEvent.java rename to inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/RecommendationRejectedEvent.java index f97fc064a0e..c6a415c39b7 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/event/RecommendationRejectedEvent.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/event/RecommendationRejectedEvent.java @@ -15,13 +15,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.tudarmstadt.ukp.inception.recommendation.event; +package de.tudarmstadt.ukp.inception.recommendation.api.event; import org.springframework.context.ApplicationEvent; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; -import de.tudarmstadt.ukp.inception.recommendation.api.event.TransientAnnotationStateChangedEvent; public class RecommendationRejectedEvent extends ApplicationEvent diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java index d435c0ffd57..ba04a854406 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java @@ -60,14 +60,14 @@ import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler; import de.tudarmstadt.ukp.inception.recommendation.actionbar.RecommenderActionBarPanel; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.event.AjaxRecommendationAcceptedEvent; +import de.tudarmstadt.ukp.inception.recommendation.api.event.AjaxRecommendationRejectedEvent; +import de.tudarmstadt.ukp.inception.recommendation.api.event.PredictionsSwitchedEvent; import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.Predictions; import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.SpanSuggestion; import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderServiceAutoConfiguration; -import de.tudarmstadt.ukp.inception.recommendation.event.AjaxRecommendationAcceptedEvent; -import de.tudarmstadt.ukp.inception.recommendation.event.AjaxRecommendationRejectedEvent; -import de.tudarmstadt.ukp.inception.recommendation.event.PredictionsSwitchedEvent; import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState; import de.tudarmstadt.ukp.inception.rendering.selection.SelectionChangedEvent; import de.tudarmstadt.ukp.inception.rendering.vmodel.VID; diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/log/RecommendationAcceptedEventAdapter.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/log/RecommendationAcceptedEventAdapter.java index dc6f714306f..d6c63d6a612 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/log/RecommendationAcceptedEventAdapter.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/log/RecommendationAcceptedEventAdapter.java @@ -22,8 +22,8 @@ import de.tudarmstadt.ukp.inception.log.adapter.EventLoggingAdapter; import de.tudarmstadt.ukp.inception.log.model.AnnotationDetails; import de.tudarmstadt.ukp.inception.log.model.FeatureChangeDetails; +import de.tudarmstadt.ukp.inception.recommendation.api.event.RecommendationAcceptedEvent; import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderServiceAutoConfiguration; -import de.tudarmstadt.ukp.inception.recommendation.event.RecommendationAcceptedEvent; import de.tudarmstadt.ukp.inception.support.json.JSONUtil; /** diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/log/RecommendationRejectedEventAdapter.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/log/RecommendationRejectedEventAdapter.java index 5eab8b90339..e652db9512c 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/log/RecommendationRejectedEventAdapter.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/log/RecommendationRejectedEventAdapter.java @@ -22,8 +22,8 @@ import de.tudarmstadt.ukp.inception.log.adapter.EventLoggingAdapter; import de.tudarmstadt.ukp.inception.log.model.AnnotationDetails; import de.tudarmstadt.ukp.inception.log.model.FeatureChangeDetails; +import de.tudarmstadt.ukp.inception.recommendation.api.event.RecommendationRejectedEvent; import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderServiceAutoConfiguration; -import de.tudarmstadt.ukp.inception.recommendation.event.RecommendationRejectedEvent; import de.tudarmstadt.ukp.inception.support.json.JSONUtil; /** diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java index 7eb2ea40e2b..d0630d05108 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java @@ -21,16 +21,11 @@ import static de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasAccessMode.SHARED_READ_ONLY_ACCESS; import static de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasUpgradeMode.AUTO_CAS_UPGRADE; import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_OVERLAP; -import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_SKIPPED; import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_TRANSIENT_ACCEPTED; import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_TRANSIENT_CORRECTED; -import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_TRANSIENT_REJECTED; import static de.tudarmstadt.ukp.inception.recommendation.api.model.AutoAcceptMode.ON_FIRST_ACCESS; -import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation.AL_SIDEBAR; import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation.AUTO_ACCEPT; import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.ACCEPTED; -import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.CORRECTED; -import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.REJECTED; import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.SKIPPED; import static de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup.groupsOfType; import static de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionLayerFamily.RELATION; @@ -48,7 +43,6 @@ import static java.util.stream.Collectors.toMap; import static org.apache.uima.cas.text.AnnotationPredicates.colocated; import static org.apache.uima.fit.util.CasUtil.select; -import static org.apache.uima.fit.util.CasUtil.selectAt; import java.io.IOException; import java.lang.invoke.MethodHandles; @@ -80,13 +74,13 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.uima.UIMAException; +import org.apache.uima.cas.AnnotationBaseFS; import org.apache.uima.cas.CAS; import org.apache.uima.cas.Feature; import org.apache.uima.cas.Type; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.cas.text.AnnotationPredicates; import org.apache.uima.fit.util.CasUtil; -import org.apache.uima.jcas.tcas.Annotation; import org.apache.uima.resource.ResourceInitializationException; import org.apache.wicket.MetaDataKey; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -134,6 +128,7 @@ import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; import de.tudarmstadt.ukp.inception.recommendation.api.RecommenderFactoryRegistry; import de.tudarmstadt.ukp.inception.recommendation.api.RecommenderTypeSystemUtils; +import de.tudarmstadt.ukp.inception.recommendation.api.event.RecommendationAcceptedEvent; import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.AutoAcceptMode; import de.tudarmstadt.ukp.inception.recommendation.api.model.EvaluatedRecommender; @@ -156,8 +151,6 @@ import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommendationException; import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommenderContext; import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderServiceAutoConfiguration; -import de.tudarmstadt.ukp.inception.recommendation.event.RecommendationAcceptedEvent; -import de.tudarmstadt.ukp.inception.recommendation.event.RecommendationRejectedEvent; import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderDeletedEvent; import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderTaskNotificationEvent; import de.tudarmstadt.ukp.inception.recommendation.event.RecommenderUpdatedEvent; @@ -640,6 +633,7 @@ private void autoAccept(AjaxRequestTarget aTarget, User aUser, SourceDocument aD return; } + var srs = getSpanRecommendationSupport(); var count = 0; for (var prediction : predictions.getPredictionsByDocument(aDocument.getName())) { if (prediction.getAutoAcceptMode() != aAutoAcceptMode) { @@ -661,7 +655,7 @@ private void autoAccept(AjaxRequestTarget aTarget, User aUser, SourceDocument aD try { if (prediction instanceof SpanSuggestion) { var spanPrediction = (SpanSuggestion) prediction; - acceptOrCorrectSuggestion(null, aDocument, aUser.getUsername(), cas, + srs.acceptOrCorrectSuggestion(null, aDocument, aUser.getUsername(), cas, (SpanAdapter) adapter, feature, spanPrediction, AUTO_ACCEPT, ACCEPTED); count++; } @@ -692,6 +686,16 @@ private void autoAccept(AjaxRequestTarget aTarget, User aUser, SourceDocument aD } } + private SpanRecommendationSupportImpl getSpanRecommendationSupport() + { + return new SpanRecommendationSupportImpl(this, this, applicationEventPublisher); + } + + private RelationRecommendationSupportImpl getRelationRecommendationSupport() + { + return new RelationRecommendationSupportImpl(this, this, applicationEventPublisher); + } + /* * There can be multiple annotation changes in a single user request. Thus, we do not trigger a * training on every action but rather mark the project/user as dirty and trigger the training @@ -1049,6 +1053,7 @@ public void putContext(User aUser, Recommender aRecommender, RecommenderContext } } + @Deprecated @Override @Transactional public AnnotationFS correctSuggestion(String aSessionOwner, SourceDocument aDocument, @@ -1057,15 +1062,12 @@ public AnnotationFS correctSuggestion(String aSessionOwner, SourceDocument aDocu LearningRecordChangeLocation aLocation) throws AnnotationException { - // If the action was a correction (i.e. suggestion label != annotation value) then generate - // a rejection for the original value - we do not want the original value to re-appear - logRecord(aSessionOwner, aDocument, aDataOwner, aOriginalSuggestion, aFeature, REJECTED, - AL_SIDEBAR); - - return acceptOrCorrectSuggestion(aSessionOwner, aDocument, aDataOwner, aCas, aAdapter, - aFeature, aCorrectedSuggestion, aLocation, CORRECTED); + return getSpanRecommendationSupport().correctSuggestion(aSessionOwner, aDocument, + aDataOwner, aCas, aAdapter, aFeature, aOriginalSuggestion, aCorrectedSuggestion, + aLocation); } + @Deprecated @Override @Transactional public AnnotationFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, @@ -1073,54 +1075,14 @@ public AnnotationFS acceptSuggestion(String aSessionOwner, SourceDocument aDocum SpanSuggestion aSuggestion, LearningRecordChangeLocation aLocation) throws AnnotationException { - return acceptOrCorrectSuggestion(aSessionOwner, aDocument, aDataOwner, aCas, aAdapter, - aFeature, aSuggestion, aLocation, ACCEPTED); + return getSpanRecommendationSupport().acceptOrCorrectSuggestion(aSessionOwner, aDocument, + aDataOwner, aCas, aAdapter, aFeature, aSuggestion, aLocation, ACCEPTED); } - private AnnotationFS acceptOrCorrectSuggestion(String aSessionOwner, SourceDocument aDocument, - String aDataOwner, CAS aCas, SpanAdapter aAdapter, AnnotationFeature aFeature, - SpanSuggestion aSuggestion, LearningRecordChangeLocation aLocation, - LearningRecordUserAction aAction) - throws AnnotationException - { - var aBegin = aSuggestion.getBegin(); - var aEnd = aSuggestion.getEnd(); - var aValue = aSuggestion.getLabel(); - - var candidates = aCas. select(aAdapter.getAnnotationTypeName()) // - .at(aBegin, aEnd) // - .asList(); - - var candidateWithEmptyLabel = candidates.stream() // - .filter(c -> aAdapter.getFeatureValue(aFeature, c) == null) // - .findFirst(); - - AnnotationFS annotation; - if (candidateWithEmptyLabel.isPresent()) { - // If there is an annotation where the predicted feature is unset, use it ... - annotation = candidateWithEmptyLabel.get(); - } - else if (candidates.isEmpty() || aAdapter.getLayer().isAllowStacking()) { - // ... if not or if stacking is allowed, then we create a new annotation - this also - // takes care of attaching to an annotation if necessary - var newAnnotation = aAdapter.add(aDocument, aDataOwner, aCas, aBegin, aEnd); - annotation = newAnnotation; - } - else { - // ... if yes and stacking is not allowed, then we update the feature on the existing - // annotation - annotation = candidates.get(0); - } - - commmitAcceptedLabel(aSessionOwner, aDocument, aDataOwner, aCas, aAdapter, aFeature, - aSuggestion, aValue, annotation, aLocation, aAction); - - return annotation; - } - - private void commmitAcceptedLabel(String aSessionOwner, SourceDocument aDocument, + @Override + public void commmitAcceptedLabel(String aSessionOwner, SourceDocument aDocument, String aDataOwner, CAS aCas, TypeAdapter aAdapter, AnnotationFeature aFeature, - AnnotationSuggestion aSuggestion, String aValue, AnnotationFS annotation, + AnnotationSuggestion aSuggestion, String aValue, AnnotationBaseFS annotation, LearningRecordChangeLocation aLocation, LearningRecordUserAction aAction) throws AnnotationException { @@ -1144,6 +1106,7 @@ private void commmitAcceptedLabel(String aSessionOwner, SourceDocument aDocument } } + @Deprecated @Override @Transactional public AnnotationFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, @@ -1152,71 +1115,8 @@ public AnnotationFS acceptSuggestion(String aSessionOwner, SourceDocument aDocum LearningRecordUserAction aAction) throws AnnotationException { - var sourceBegin = aSuggestion.getPosition().getSourceBegin(); - var sourceEnd = aSuggestion.getPosition().getSourceEnd(); - var targetBegin = aSuggestion.getPosition().getTargetBegin(); - var targetEnd = aSuggestion.getPosition().getTargetEnd(); - - // Check if there is already a relation for the given source and target - var type = CasUtil.getType(aCas, aAdapter.getAnnotationTypeName()); - var attachType = CasUtil.getType(aCas, aAdapter.getAttachTypeName()); - - var sourceFeature = type.getFeatureByBaseName(FEAT_REL_SOURCE); - var targetFeature = type.getFeatureByBaseName(FEAT_REL_TARGET); - - // The begin and end feature of a relation in the CAS are of the dependent/target - // annotation. See also RelationAdapter::createRelationAnnotation. - // We use that fact to search for existing relations for this relation suggestion - var candidates = new ArrayList(); - for (AnnotationFS relationCandidate : selectAt(aCas, type, targetBegin, targetEnd)) { - AnnotationFS source = (AnnotationFS) relationCandidate.getFeatureValue(sourceFeature); - AnnotationFS target = (AnnotationFS) relationCandidate.getFeatureValue(targetFeature); - - if (source == null || target == null) { - continue; - } - - if (source.getBegin() == sourceBegin && source.getEnd() == sourceEnd - && target.getBegin() == targetBegin && target.getEnd() == targetEnd) { - candidates.add(relationCandidate); - } - } - - AnnotationFS annotation = null; - if (candidates.size() == 1) { - // One candidate, we just return it - annotation = candidates.get(0); - } - else if (candidates.size() == 2) { - LOG.warn("Found multiple candidates for upserting relation from suggestion"); - annotation = candidates.get(0); - } - - // We did not find a relation for this suggestion, so we create a new one - if (annotation == null) { - // FIXME: We get the first match for the (begin, end) span. With stacking, there can - // be more than one and we need to get the right one then which does not need to be - // the first. We wait for #2135 to fix this. When stacking is enabled, then also - // consider creating a new relation instead of upserting an existing one. - - var source = selectAt(aCas, attachType, sourceBegin, sourceEnd).stream().findFirst() - .orElse(null); - var target = selectAt(aCas, attachType, targetBegin, targetEnd).stream().findFirst() - .orElse(null); - - if (source == null || target == null) { - String msg = "Cannot find source or target annotation for upserting relation"; - LOG.error(msg); - throw new IllegalStateException(msg); - } - - annotation = aAdapter.add(aDocument, aDataOwner, source, target, aCas); - } - - commmitAcceptedLabel(aSessionOwner, aDocument, aDataOwner, aCas, aAdapter, aFeature, - aSuggestion, aSuggestion.getLabel(), annotation, aLocation, aAction); - - return annotation; + return getRelationRecommendationSupport().acceptSuggestion(aSessionOwner, aDocument, + aDataOwner, aCas, aAdapter, aFeature, aSuggestion, aLocation, aAction); } private static class CommittedDocument @@ -2401,62 +2301,36 @@ public Progress getProgressTowardsNextEvaluation(User aUser, Project aProject) } } + @Deprecated @Override @Transactional public void rejectSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, AnnotationSuggestion suggestion, LearningRecordChangeLocation aAction) { - // Hide the suggestion. This is faster than having to recalculate the visibility status for - // the entire document or even for the part visible on screen. - suggestion.hide(FLAG_TRANSIENT_REJECTED); - if (suggestion instanceof SpanSuggestion spanSuggestion) { - var recommender = getRecommender(suggestion.getVID().getId()); - var feature = recommender.getFeature(); - // Log the action to the learning record - logRecord(aSessionOwner, aDocument, aDataOwner, spanSuggestion, feature, REJECTED, - aAction); - - // Send an application event that the suggestion has been rejected - applicationEventPublisher.publishEvent(new RecommendationRejectedEvent(this, aDocument, - aDataOwner, spanSuggestion.getBegin(), spanSuggestion.getEnd(), - spanSuggestion.getCoveredText(), feature, spanSuggestion.getLabel())); - + getSpanRecommendationSupport().rejectSuggestion(aSessionOwner, aDocument, aDataOwner, + spanSuggestion, aAction); } else if (suggestion instanceof RelationSuggestion relationSuggestion) { - // TODO: Log rejection - // TODO: Publish rejection event + getRelationRecommendationSupport().rejectSuggestion(aSessionOwner, aDocument, + aDataOwner, relationSuggestion, aAction); } } + @Deprecated @Override @Transactional public void skipSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, AnnotationSuggestion suggestion, LearningRecordChangeLocation aAction) + throws AnnotationException { - // Hide the suggestion. This is faster than having to recalculate the visibility status for - // the entire document or even for the part visible on screen. - suggestion.hide(FLAG_SKIPPED); - if (suggestion instanceof SpanSuggestion spanSuggestion) { - var recommender = getRecommender(spanSuggestion.getVID().getId()); - var feature = recommender.getFeature(); - - // Log the action to the learning record - logRecord(aSessionOwner, aDocument, aDataOwner, spanSuggestion, feature, SKIPPED, - aAction); - - // // Send an application event that the suggestion has been rejected - // applicationEventPublisher.publishEvent(new RecommendationSkippedEvent(this, - // aDocument, - // aDataOwner, spanSuggestion.getBegin(), spanSuggestion.getEnd(), - // spanSuggestion.getCoveredText(), feature, spanSuggestion.getLabel())); - + getSpanRecommendationSupport().skipSuggestion(aSessionOwner, aDocument, aDataOwner, + spanSuggestion, aAction); } - else if (suggestion instanceof RelationSuggestion) { - RelationSuggestion relationSuggestion = (RelationSuggestion) suggestion; - // TODO: Log rejection - // TODO: Publish rejection event + else if (suggestion instanceof RelationSuggestion relationSuggestion) { + getRelationRecommendationSupport().skipSuggestion(aSessionOwner, aDocument, aDataOwner, + relationSuggestion, aAction); } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RelationRecommendationSupportImpl.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RelationRecommendationSupportImpl.java new file mode 100644 index 00000000000..e39e330c821 --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RelationRecommendationSupportImpl.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.recommendation.service; + +import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_SKIPPED; +import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_TRANSIENT_REJECTED; +import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.FEAT_REL_SOURCE; +import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.FEAT_REL_TARGET; +import static org.apache.uima.fit.util.CasUtil.selectAt; + +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; + +import org.apache.uima.cas.CAS; +import org.apache.uima.cas.text.AnnotationFS; +import org.apache.uima.fit.util.CasUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationAdapter; +import de.tudarmstadt.ukp.inception.recommendation.api.LayerRecommendationSupport; +import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction; +import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationSuggestion; +import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; + +public class RelationRecommendationSupportImpl + implements LayerRecommendationSupport +{ + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final RecommendationService recommendationService; + private final LearningRecordService learningRecordService; + private final ApplicationEventPublisher applicationEventPublisher; + + public RelationRecommendationSupportImpl(RecommendationService aRecommendationService, + LearningRecordService aLearningRecordService, + ApplicationEventPublisher aApplicationEventPublisher) + { + recommendationService = aRecommendationService; + learningRecordService = aLearningRecordService; + applicationEventPublisher = aApplicationEventPublisher; + } + + /** + * Uses the given annotation suggestion to create a new annotation or to update a feature in an + * existing annotation. + * + * @param aDocument + * the source document to which the annotations belong + * @param aDataOwner + * the annotator user to whom the annotations belong + * @param aCas + * the CAS containing the annotations + * @param aAdapter + * an adapter for the layer to upsert + * @param aFeature + * the feature on the layer that should be upserted + * @param aSuggestion + * the suggestion + * @param aLocation + * the location from where the change was triggered + * @param aAction + * TODO + * @return the created/updated annotation. + * @throws AnnotationException + * if there was an annotation-level problem + */ + @Override + public AnnotationFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, + String aDataOwner, CAS aCas, RelationAdapter aAdapter, AnnotationFeature aFeature, + RelationSuggestion aSuggestion, LearningRecordChangeLocation aLocation, + LearningRecordUserAction aAction) + throws AnnotationException + { + var sourceBegin = aSuggestion.getPosition().getSourceBegin(); + var sourceEnd = aSuggestion.getPosition().getSourceEnd(); + var targetBegin = aSuggestion.getPosition().getTargetBegin(); + var targetEnd = aSuggestion.getPosition().getTargetEnd(); + + // Check if there is already a relation for the given source and target + var type = CasUtil.getType(aCas, aAdapter.getAnnotationTypeName()); + var attachType = CasUtil.getType(aCas, aAdapter.getAttachTypeName()); + + var sourceFeature = type.getFeatureByBaseName(FEAT_REL_SOURCE); + var targetFeature = type.getFeatureByBaseName(FEAT_REL_TARGET); + + // The begin and end feature of a relation in the CAS are of the dependent/target + // annotation. See also RelationAdapter::createRelationAnnotation. + // We use that fact to search for existing relations for this relation suggestion + var candidates = new ArrayList(); + for (AnnotationFS relationCandidate : selectAt(aCas, type, targetBegin, targetEnd)) { + AnnotationFS source = (AnnotationFS) relationCandidate.getFeatureValue(sourceFeature); + AnnotationFS target = (AnnotationFS) relationCandidate.getFeatureValue(targetFeature); + + if (source == null || target == null) { + continue; + } + + if (source.getBegin() == sourceBegin && source.getEnd() == sourceEnd + && target.getBegin() == targetBegin && target.getEnd() == targetEnd) { + candidates.add(relationCandidate); + } + } + + AnnotationFS annotation = null; + if (candidates.size() == 1) { + // One candidate, we just return it + annotation = candidates.get(0); + } + else if (candidates.size() == 2) { + LOG.warn("Found multiple candidates for upserting relation from suggestion"); + annotation = candidates.get(0); + } + + // We did not find a relation for this suggestion, so we create a new one + if (annotation == null) { + // FIXME: We get the first match for the (begin, end) span. With stacking, there can + // be more than one and we need to get the right one then which does not need to be + // the first. We wait for #2135 to fix this. When stacking is enabled, then also + // consider creating a new relation instead of upserting an existing one. + + var source = selectAt(aCas, attachType, sourceBegin, sourceEnd).stream().findFirst() + .orElse(null); + var target = selectAt(aCas, attachType, targetBegin, targetEnd).stream().findFirst() + .orElse(null); + + if (source == null || target == null) { + String msg = "Cannot find source or target annotation for upserting relation"; + LOG.error(msg); + throw new IllegalStateException(msg); + } + + annotation = aAdapter.add(aDocument, aDataOwner, source, target, aCas); + } + + recommendationService.commmitAcceptedLabel(aSessionOwner, aDocument, aDataOwner, aCas, + aAdapter, aFeature, aSuggestion, aSuggestion.getLabel(), annotation, aLocation, + aAction); + + return annotation; + } + + @Override + public void rejectSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, + RelationSuggestion aSuggestion, LearningRecordChangeLocation aAction) + { + // Hide the suggestion. This is faster than having to recalculate the visibility status + // for + // the entire document or even for the part visible on screen. + aSuggestion.hide(FLAG_TRANSIENT_REJECTED); + + // TODO: See span recommendation support... + + } + + @Override + public void skipSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, + RelationSuggestion aSuggestion, LearningRecordChangeLocation aAction) + throws AnnotationException + { + // Hide the suggestion. This is faster than having to recalculate the visibility status + // for + // the entire document or even for the part visible on screen. + aSuggestion.hide(FLAG_SKIPPED); + + // TODO: Log rejection + // TODO: Publish rejection event + } +} diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/SpanRecommendationSupportImpl.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/SpanRecommendationSupportImpl.java new file mode 100644 index 00000000000..e88c8990b5f --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/SpanRecommendationSupportImpl.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.recommendation.service; + +import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_SKIPPED; +import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_TRANSIENT_REJECTED; +import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation.AL_SIDEBAR; +import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.CORRECTED; +import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.REJECTED; +import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.SKIPPED; + +import org.apache.uima.cas.AnnotationBaseFS; +import org.apache.uima.cas.CAS; +import org.apache.uima.cas.text.AnnotationFS; +import org.apache.uima.jcas.tcas.Annotation; +import org.springframework.context.ApplicationEventPublisher; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanAdapter; +import de.tudarmstadt.ukp.inception.recommendation.api.LayerRecommendationSupport; +import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.event.RecommendationRejectedEvent; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction; +import de.tudarmstadt.ukp.inception.recommendation.api.model.SpanSuggestion; +import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; + +public class SpanRecommendationSupportImpl + implements LayerRecommendationSupport + +{ + private final RecommendationService recommendationService; + private final LearningRecordService learningRecordService; + private final ApplicationEventPublisher applicationEventPublisher; + + public SpanRecommendationSupportImpl(RecommendationService aRecommendationService, + LearningRecordService aLearningRecordService, + ApplicationEventPublisher aApplicationEventPublisher) + { + recommendationService = aRecommendationService; + learningRecordService = aLearningRecordService; + applicationEventPublisher = aApplicationEventPublisher; + } + + public AnnotationFS correctSuggestion(String aSessionOwner, SourceDocument aDocument, + String aDataOwner, CAS aCas, SpanAdapter aAdapter, AnnotationFeature aFeature, + SpanSuggestion aOriginalSuggestion, SpanSuggestion aCorrectedSuggestion, + LearningRecordChangeLocation aLocation) + throws AnnotationException + { + // If the action was a correction (i.e. suggestion label != annotation value) then generate + // a rejection for the original value - we do not want the original value to re-appear + learningRecordService.logRecord(aSessionOwner, aDocument, aDataOwner, aOriginalSuggestion, + aFeature, REJECTED, AL_SIDEBAR); + + return acceptOrCorrectSuggestion(aSessionOwner, aDocument, aDataOwner, aCas, aAdapter, + aFeature, aCorrectedSuggestion, aLocation, CORRECTED); + } + + @Override + public AnnotationBaseFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, + String aDataOwner, CAS aCas, SpanAdapter aAdapter, AnnotationFeature aFeature, + SpanSuggestion aSuggestion, LearningRecordChangeLocation aLocation, + LearningRecordUserAction aAction) + throws AnnotationException + { + return acceptOrCorrectSuggestion(aSessionOwner, aDocument, aDataOwner, aCas, aAdapter, + aFeature, aSuggestion, aLocation, aAction); + } + + public AnnotationFS acceptOrCorrectSuggestion(String aSessionOwner, SourceDocument aDocument, + String aDataOwner, CAS aCas, SpanAdapter aAdapter, AnnotationFeature aFeature, + SpanSuggestion aSuggestion, LearningRecordChangeLocation aLocation, + LearningRecordUserAction aAction) + throws AnnotationException + { + var aBegin = aSuggestion.getBegin(); + var aEnd = aSuggestion.getEnd(); + var aValue = aSuggestion.getLabel(); + + var candidates = aCas. select(aAdapter.getAnnotationTypeName()) // + .at(aBegin, aEnd) // + .asList(); + + var candidateWithEmptyLabel = candidates.stream() // + .filter(c -> aAdapter.getFeatureValue(aFeature, c) == null) // + .findFirst(); + + AnnotationFS annotation; + if (candidateWithEmptyLabel.isPresent()) { + // If there is an annotation where the predicted feature is unset, use it ... + annotation = candidateWithEmptyLabel.get(); + } + else if (candidates.isEmpty() || aAdapter.getLayer().isAllowStacking()) { + // ... if not or if stacking is allowed, then we create a new annotation - this also + // takes care of attaching to an annotation if necessary + var newAnnotation = aAdapter.add(aDocument, aDataOwner, aCas, aBegin, aEnd); + annotation = newAnnotation; + } + else { + // ... if yes and stacking is not allowed, then we update the feature on the existing + // annotation + annotation = candidates.get(0); + } + + recommendationService.commmitAcceptedLabel(aSessionOwner, aDocument, aDataOwner, aCas, + aAdapter, aFeature, aSuggestion, aValue, annotation, aLocation, aAction); + + return annotation; + } + + @Override + public void rejectSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, + SpanSuggestion spanSuggestion, LearningRecordChangeLocation aAction) + { + + // Hide the suggestion. This is faster than having to recalculate the visibility status + // for the entire document or even for the part visible on screen. + spanSuggestion.hide(FLAG_TRANSIENT_REJECTED); + + var recommender = recommendationService.getRecommender(spanSuggestion.getVID().getId()); + var feature = recommender.getFeature(); + // Log the action to the learning record + learningRecordService.logRecord(aSessionOwner, aDocument, aDataOwner, spanSuggestion, + feature, REJECTED, aAction); + + // Send an application event that the suggestion has been rejected + applicationEventPublisher.publishEvent(new RecommendationRejectedEvent(this, aDocument, + aDataOwner, spanSuggestion.getBegin(), spanSuggestion.getEnd(), + spanSuggestion.getCoveredText(), feature, spanSuggestion.getLabel())); + + } + + @Override + public void skipSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, + SpanSuggestion aSuggestion, LearningRecordChangeLocation aAction) + throws AnnotationException + { + // Hide the suggestion. This is faster than having to recalculate the visibility status + // for the entire document or even for the part visible on screen. + aSuggestion.hide(FLAG_SKIPPED); + + var recommender = recommendationService.getRecommender(aSuggestion.getVID().getId()); + var feature = recommender.getFeature(); + + // Log the action to the learning record + learningRecordService.logRecord(aSessionOwner, aDocument, aDataOwner, aSuggestion, feature, + SKIPPED, aAction); + + // // Send an application event that the suggestion has been rejected + // applicationEventPublisher.publishEvent(new RecommendationSkippedEvent(this, + // aDocument, + // aDataOwner, spanSuggestion.getBegin(), spanSuggestion.getEnd(), + // spanSuggestion.getCoveredText(), feature, spanSuggestion.getLabel())); + } +} diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommenderInfoPanel.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommenderInfoPanel.java index 7b6ae2366fe..95800371906 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommenderInfoPanel.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/sidebar/RecommenderInfoPanel.java @@ -55,6 +55,7 @@ import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; import de.tudarmstadt.ukp.inception.recommendation.api.evaluation.EvaluationResult; +import de.tudarmstadt.ukp.inception.recommendation.api.event.PredictionsSwitchedEvent; import de.tudarmstadt.ukp.inception.recommendation.api.model.EvaluatedRecommender; import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation; import de.tudarmstadt.ukp.inception.recommendation.api.model.Predictions; @@ -67,7 +68,6 @@ import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommendationEngine; import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommendationEngineFactory; import de.tudarmstadt.ukp.inception.recommendation.api.recommender.RecommenderContext; -import de.tudarmstadt.ukp.inception.recommendation.event.PredictionsSwitchedEvent; import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException;