diff --git a/extensions/wikidata/module/langs/translation-en.json b/extensions/wikidata/module/langs/translation-en.json index 4d5da5540..f86c253b2 100644 --- a/extensions/wikidata/module/langs/translation-en.json +++ b/extensions/wikidata/module/langs/translation-en.json @@ -77,6 +77,14 @@ "property-restricted-to-mainsnak-found-in-reference": { "title": "Statement-only property used as reference.", "body": "You are using in a reference a property that was designed to be used as a statement only." + }, + "missing-mandatory-qualifiers": { + "title": "Missing mandatory qualifiers.", + "body": "Your statements are missing qualifiers, it would be good to add them." + }, + "disallowed-qualifiers": { + "title": "Disallowed qualifiers.", + "body": "Some of your qualifiers are incompatible with the statements that they qualify. Please check your schema." } } } diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/ConstraintFetcher.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/ConstraintFetcher.java index 86d84db06..b9059487e 100644 --- a/extensions/wikidata/src/org/openrefine/wikidata/qa/ConstraintFetcher.java +++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/ConstraintFetcher.java @@ -2,6 +2,7 @@ package org.openrefine.wikidata.qa; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -11,6 +12,7 @@ import org.wikidata.wdtk.datamodel.interfaces.EntityDocument; import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue; import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue; import org.wikidata.wdtk.datamodel.interfaces.PropertyDocument; +import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue; import org.wikidata.wdtk.datamodel.interfaces.Snak; import org.wikidata.wdtk.datamodel.interfaces.SnakGroup; import org.wikidata.wdtk.datamodel.interfaces.Statement; @@ -42,8 +44,8 @@ public class ConstraintFetcher { // The following constraints still need to be implemented: - public static String ALLOWED_QUALIFIERS_CONSRAINT_QID = "Q21510851"; - public static String ALLOWED_QUALIFIERS_PID = "P2306"; + public static String ALLOWED_QUALIFIERS_CONSTRAINT_QID = "Q21510851"; + public static String ALLOWED_QUALIFIERS_CONSTRAINT_PID = "P2306"; public static String MANDATORY_QUALIFIERS_CONSTRAINT_QID = "Q21510856"; public static String MANDATORY_QUALIFIERS_CONSTRAINT_PID = "P2306"; @@ -108,6 +110,32 @@ public class ConstraintFetcher { return getSingleConstraint(pid, USED_ONLY_AS_REFERENCE_CONSTRAINT_QID) != null; } + /** + * Get the list of allowed qualifiers (as property ids) for this property (null if any) + */ + public Set allowedQualifiers(PropertyIdValue pid) { + List specs = getSingleConstraint(pid.getId(), ALLOWED_QUALIFIERS_CONSTRAINT_QID); + + if (specs != null) { + List properties = findValues(specs, ALLOWED_QUALIFIERS_CONSTRAINT_PID); + return properties.stream().map(e -> (PropertyIdValue) e).collect(Collectors.toSet()); + } + return null; + } + + /** + * Get the list of mandatory qualifiers (as property ids) for this property (null if any) + */ + public Set mandatoryQualifiers(PropertyIdValue pid) { + List specs = getSingleConstraint(pid.getId(), MANDATORY_QUALIFIERS_CONSTRAINT_QID); + + if (specs != null) { + List properties = findValues(specs, MANDATORY_QUALIFIERS_CONSTRAINT_PID); + return properties.stream().map(e -> (PropertyIdValue) e).collect(Collectors.toSet()); + } + return null; + } + /** * Returns a single constraint for a particular type and a property, or null * if there is no such constraint diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/EditInspector.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/EditInspector.java index 90d1fd17b..24a44a2d4 100644 --- a/extensions/wikidata/src/org/openrefine/wikidata/qa/EditInspector.java +++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/EditInspector.java @@ -8,6 +8,7 @@ import org.openrefine.wikidata.qa.scrutinizers.EditScrutinizer; import org.openrefine.wikidata.qa.scrutinizers.FormatConstraintScrutinizer; import org.openrefine.wikidata.qa.scrutinizers.InverseConstraintScrutinizer; import org.openrefine.wikidata.qa.scrutinizers.NewItemScrutinizer; +import org.openrefine.wikidata.qa.scrutinizers.QualifierCompatibilityScrutinizer; import org.openrefine.wikidata.qa.scrutinizers.RestrictedPositionScrutinizer; import org.openrefine.wikidata.qa.scrutinizers.SelfReferentialScrutinizer; import org.openrefine.wikidata.qa.scrutinizers.UnsourcedScrutinizer; @@ -33,6 +34,7 @@ public class EditInspector { register(new SelfReferentialScrutinizer()); register(new UnsourcedScrutinizer()); register(new RestrictedPositionScrutinizer()); + register(new QualifierCompatibilityScrutinizer()); } /** diff --git a/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/QualifierCompatibilityScrutinizer.java b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/QualifierCompatibilityScrutinizer.java new file mode 100644 index 000000000..9c609b59f --- /dev/null +++ b/extensions/wikidata/src/org/openrefine/wikidata/qa/scrutinizers/QualifierCompatibilityScrutinizer.java @@ -0,0 +1,76 @@ +package org.openrefine.wikidata.qa.scrutinizers; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.openrefine.wikidata.qa.ConstraintFetcher; +import org.wikidata.wdtk.datamodel.interfaces.EntityIdValue; +import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue; +import org.wikidata.wdtk.datamodel.interfaces.Statement; + +/** + * A scrutinizer that checks the compatibility of the qualifiers + * and the property of a statement, and looks for mandatory qualifiers. + * @author antonin + * + */ +public class QualifierCompatibilityScrutinizer extends StatementScrutinizer { + + private ConstraintFetcher _fetcher; + private Map> _allowedQualifiers; + private Map> _mandatoryQualifiers; + + public QualifierCompatibilityScrutinizer() { + _fetcher = new ConstraintFetcher(); + _allowedQualifiers = new HashMap<>(); + _mandatoryQualifiers = new HashMap<>(); + } + + protected boolean qualifierIsAllowed(PropertyIdValue statementProperty, PropertyIdValue qualifierProperty) { + Set allowed = null; + if (_allowedQualifiers.containsKey(statementProperty)) { + allowed = _allowedQualifiers.get(statementProperty); + } else { + allowed = _fetcher.allowedQualifiers(statementProperty); + _allowedQualifiers.put(statementProperty, allowed); + } + return allowed == null || allowed.contains(qualifierProperty); + } + + protected Set mandatoryQualifiers(PropertyIdValue statementProperty) { + Set mandatory = null; + if (_mandatoryQualifiers.containsKey(statementProperty)) { + mandatory = _mandatoryQualifiers.get(statementProperty); + } else { + mandatory = _fetcher.mandatoryQualifiers(statementProperty); + if (mandatory == null) { + mandatory = new HashSet<>(); + } + _mandatoryQualifiers.put(statementProperty, mandatory); + } + return mandatory; + } + + @Override + public void scrutinize(Statement statement, EntityIdValue entityId, boolean added) { + PropertyIdValue statementProperty = statement.getClaim().getMainSnak().getPropertyId(); + Set qualifiers = statement.getClaim().getQualifiers(). + stream().map(e -> e.getProperty()).collect(Collectors.toSet()); + + Set missingQualifiers = mandatoryQualifiers(statementProperty) + .stream().filter(p -> !qualifiers.contains(p)).collect(Collectors.toSet()); + Set disallowedQualifiers = qualifiers + .stream().filter(p -> !qualifierIsAllowed(statementProperty, p)).collect(Collectors.toSet()); + + if( !missingQualifiers.isEmpty()) { + warning("missing-mandatory-qualifiers"); + } + if (!disallowedQualifiers.isEmpty()) { + warning("disallowed-qualifiers"); + } + } + +} diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/ConstraintFetcherTests.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/ConstraintFetcherTests.java index 70c855109..f46b4497f 100644 --- a/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/ConstraintFetcherTests.java +++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/qa/ConstraintFetcherTests.java @@ -2,14 +2,24 @@ package org.openrefine.wikidata.qa; import org.testng.Assert; import org.testng.annotations.Test; +import org.wikidata.wdtk.datamodel.helpers.Datamodel; +import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue; + import java.util.regex.Pattern; public class ConstraintFetcherTests { private ConstraintFetcher fetcher; + private PropertyIdValue headOfGovernment; + private PropertyIdValue startTime; + private PropertyIdValue endTime; + public ConstraintFetcherTests() { fetcher = new ConstraintFetcher(); + headOfGovernment = Datamodel.makeWikidataPropertyIdValue("P6"); + startTime = Datamodel.makeWikidataPropertyIdValue("P580"); + endTime = Datamodel.makeWikidataPropertyIdValue("P582"); } @Test @@ -45,4 +55,19 @@ public class ConstraintFetcherTests { Assert.assertTrue(fetcher.isForValuesOnly("P6")); Assert.assertFalse(fetcher.isForValuesOnly("P854")); } + + @Test + public void testAllowedQualifiers() { + Assert.assertTrue(fetcher.allowedQualifiers(headOfGovernment).contains(startTime)); + Assert.assertTrue(fetcher.allowedQualifiers(headOfGovernment).contains(endTime)); + Assert.assertFalse(fetcher.allowedQualifiers(headOfGovernment).contains(headOfGovernment)); + Assert.assertNull(fetcher.allowedQualifiers(startTime)); + } + + @Test + public void testMandatoryQualifiers() { + Assert.assertTrue(fetcher.mandatoryQualifiers(headOfGovernment).contains(startTime)); + Assert.assertFalse(fetcher.mandatoryQualifiers(headOfGovernment).contains(endTime)); + Assert.assertNull(fetcher.allowedQualifiers(startTime)); + } }