I am trying to write a JAPE grammar to link an Anatomy annotation (tagged with a gazetteer list) with a Numeric value (tagged with an earlier phase) and a set of Units (tagged with a gazetteer list). I want to block certain patterns using pure negation on specific other annotations.
Here is my JAPE grammar:
Phase: Test
Input: Anatomy Numeric Units Lookup
Options: control=Appelt negationGrouping=false
Rule: test_1
(
( {Anatomy.minorType=="sinus_of_valsalva"} ):context
//Block matching here, allow up to 2 annotations to appear between context and value that are not a Lookup annotation of type 'body_surface_area' OR a Units annotation of type 'cm'.
( {!Lookup.minorType=="body_surface_area",
!Units.minorType=="cm"} )[0,2]
( {Numeric} ):value
( {Units.minorType=="cm"} ):unit
):test
-->
:test
{
gate.AnnotationSet matchedVar =(gate.AnnotationSet) bindings.get("value");
gate.AnnotationSet matchedcontext=(gate.AnnotationSet) bindings.get("context");
gate.AnnotationSet matchedunit =(gate.AnnotationSet) bindings.get("unit");
gate.AnnotationSet matchedAnns =(gate.AnnotationSet) bindings.get("test");
gate.FeatureMap newFeatures = Factory.newFeatureMap();
newFeatures.put("vartype","test");
newFeatures.put("varValue", stringFor(doc, matchedVar));
newFeatures.put("context", stringFor(doc, matchedcontext));
if(matchedunit != null) {
newFeatures.put("unit", stringFor(doc, matchedunit));
}else {
newFeatures.put("unit", null);
}
outputAS.add(matchedAnns.firstNode(),matchedAnns.lastNode(),"test", newFeatures);
}
I can't seem to get this to work with this very simple examples:
Text: SoV 75 cm
expected annotation --> {vartype=test, context=SoV, value=75, unit=cm}, works
Text: SoV other unimportant text 75 cm
expected annotation --> {vartype=test, context=SoV, value=75, unit=cm}, works
Text: SoV body surface area 75 cm
expected annotation --> blocked no match, works
Text: SoV is 75 cm
expected annotation --> no match, why doesn't this match??
My understanding is that pure negation {!Annotation X} is equivalent to matching any input annotation with the negation criteria, see here:
For reference the rule you have now which has only {!Lookup} constraints probably doesn't do what you think it does. Any {...} element that has only negative constraints is roughly equivalent to: ({A, !Lookup.....} | {B, !Lookup.....} | ....)
I suspect I am missing something fundamental here, can anyone enlighten me?
Full reproducible example all in one JAPE, without the need for any gazetteers:
Phase: AnatomyPhase
Input: Token
Options: control=Appelt negationGrouping=false
Rule: anatomy
(
{Token.string == "SoV"}
):anatomy
-->
:anatomy.Anatomy = {majorType = "aorta", minorType="sinus_of_valsalva", language="en"}
Phase: UnitsPhase
Input: Token
Options: control=Appelt negationGrouping=false
Rule: units
(
{Token.string == "cm"}
):units
-->
:units.Units = {majorType = "length", minorType="cm", language="en"}
Rule: units_2
(
{Token.string == "is"}
):units
-->
:units.Units = {majorType = "assertion", minorType="positive_assertion", language="en"}
Phase: LookupPhase
Input: Token
Options: control=Appelt negationGrouping=false
Rule: lookup
Priority: 100
(
{Token.string == "body"}{Token.string == "surface"}{Token.string == "area"}
):lookup
-->
:lookup.Lookup = {majorType = "body_surface_area", minorType="body_surface_area", language="en"}
Phase: TagNumeric
Input: Token SpaceToken Split Lookup
Options: control=Appelt negationGrouping=false
Rule: double_tagger
Priority: 100
(
{Token.kind == "number", Token notWithin Lookup}
({SpaceToken, !Split})[0,1]
{Token.string ==~ "[.,]"}
{Token.kind == "number", Token notWithin Lookup}
):double_tagger
-->
:double_tagger.Numeric = {type = "double"}
Rule: int_tagger
Priority: 99
(
{Token.kind == "number", Token notWithin Lookup}
):int_tagger
-->
:int_tagger.Numeric = {type = "integer"}
Phase: Test
Input: Lookup Numeric Units Anatomy
Options: control=Appelt negationGrouping=false
Rule: test_1
(
( {Anatomy.minorType=="sinus_of_valsalva"} ):context
( {!Lookup.minorType=="body_surface_area",
!Units.minorType=="cm"} )[0,2]
( {Numeric} ):value
( {Units.minorType=="cm"} ):unit
):test_var
-->
:test_var
{
gate.AnnotationSet matchedVar =(gate.AnnotationSet) bindings.get("value");
gate.AnnotationSet matchedcontext=(gate.AnnotationSet) bindings.get("context");
gate.AnnotationSet matchedunit =(gate.AnnotationSet) bindings.get("unit");
gate.AnnotationSet matchedAnns =(gate.AnnotationSet) bindings.get("test_var");
gate.FeatureMap newFeatures = Factory.newFeatureMap();
newFeatures.put("varType","test_var");
newFeatures.put("varValue", stringFor(doc, matchedVar));
newFeatures.put("context", stringFor(doc, matchedcontext));
if(matchedunit != null) {
newFeatures.put("unit", stringFor(doc, matchedunit));
}else {
newFeatures.put("unit", null);
}
outputAS.add(matchedAnns.firstNode(),matchedAnns.lastNode(),"test", newFeatures);
}