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;