SHACL Rule Inference with AI Atlas Nexus¶
Demonstrates how to attach SHACL shapes to a base_dir and use the apply_rules=True flag on library queries to receive derived attributes alongside standard results.
Using the algorithmic hiring use case:
- Shape discovery —
.ttlfiles placed inbase_dir/shapes/are auto-loaded - Rule inference —
apply_rules=Truerunssh:SPARQLRuleCONSTRUCT queries via pyoxigraph and attaches results asinstance.derived_attrs - Constraint validation —
engine.validate()checkssh:propertyconstraints
1. Write a SHACL shape file¶
The shape targets nexus:Risk instances in the hiring taxonomy and defines:
- A constraint: every risk must declare
isDefinedByTaxonomy(sh:minCount 1) - A SPARQL rule: risks in
hiring-usecase-demogetnexus:requiresGovernance trueinferred
In [ ]:
Copied!
from pathlib import Path
from ai_atlas_nexus import AIAtlasNexus
SAMPLE_DATA_DIR = Path("sample_data")
shapes_dir = SAMPLE_DATA_DIR / "shapes"
shapes_dir.mkdir(exist_ok=True)
ttl_lines = ['@prefix sh: <http://www.w3.org/ns/shacl#> .', '@prefix nexus: <https://w3id.org/ai-atlas-nexus/> .', '', 'nexus:HiringRiskShape a sh:NodeShape ;', ' sh:targetClass nexus:Risk ;', ' sh:property [ sh:path nexus:isDefinedByTaxonomy ; sh:minCount 1 ] ;', ' sh:rule [', ' a sh:SPARQLRule ;', ' sh:construct """', 'PREFIX nexus: <https://w3id.org/ai-atlas-nexus/>', 'CONSTRUCT { ?this nexus:requiresGovernance true }', 'WHERE {', ' ?this a nexus:Risk ;', ' nexus:isDefinedByTaxonomy "hiring-usecase-demo" .', '}', '"""', ' ] .']
(shapes_dir / "hiring_governance.ttl").write_text("\n".join(ttl_lines))
print(f"Shape written: {shapes_dir / 'hiring_governance.ttl'}")
from pathlib import Path
from ai_atlas_nexus import AIAtlasNexus
SAMPLE_DATA_DIR = Path("sample_data")
shapes_dir = SAMPLE_DATA_DIR / "shapes"
shapes_dir.mkdir(exist_ok=True)
ttl_lines = ['@prefix sh: .', '@prefix nexus: .', '', 'nexus:HiringRiskShape a sh:NodeShape ;', ' sh:targetClass nexus:Risk ;', ' sh:property [ sh:path nexus:isDefinedByTaxonomy ; sh:minCount 1 ] ;', ' sh:rule [', ' a sh:SPARQLRule ;', ' sh:construct """', 'PREFIX nexus: ', 'CONSTRUCT { ?this nexus:requiresGovernance true }', 'WHERE {', ' ?this a nexus:Risk ;', ' nexus:isDefinedByTaxonomy "hiring-usecase-demo" .', '}', '"""', ' ] .']
(shapes_dir / "hiring_governance.ttl").write_text("\n".join(ttl_lines))
print(f"Shape written: {shapes_dir / 'hiring_governance.ttl'}")
Shape written: sample_data/shapes/hiring_governance.ttl
2. Load the library — shapes are auto-discovered¶
In [ ]:
Copied!
nexus = AIAtlasNexus(base_dir=str(SAMPLE_DATA_DIR))
engine = nexus.get_shacl_engine()
print(f"SHACL engine loaded : {engine is not None}")
print(f"Has shapes : {engine.has_shapes() if engine else False}")
nexus = AIAtlasNexus(base_dir=str(SAMPLE_DATA_DIR))
engine = nexus.get_shacl_engine()
print(f"SHACL engine loaded : {engine is not None}")
print(f"Has shapes : {engine.has_shapes() if engine else False}")
SHACL engine loaded : True Has shapes : True
3. Query with apply_rules=True¶
When shapes are loaded, passing apply_rules=True runs SHACL inference on the result set.
Objects that match a rule get a derived_attrs dict attached via object.__setattr__.
The default (apply_rules=False) is always a no-op.
In [ ]:
Copied!
risks = nexus.get_all_risks(taxonomy="hiring-usecase-demo", apply_rules=True)
print(f"{len(risks)} risks returned\n")
for risk in risks:
derived = getattr(risk, "derived_attrs", {})
tag = f" derived_attrs={derived}" if derived else " (no derived attrs)"
print(f" {risk.id}{tag}")
risks = nexus.get_all_risks(taxonomy="hiring-usecase-demo", apply_rules=True)
print(f"{len(risks)} risks returned\n")
for risk in risks:
derived = getattr(risk, "derived_attrs", {})
tag = f" derived_attrs={derived}" if derived else " (no derived attrs)"
print(f" {risk.id}{tag}")
5 risks returned
hiring-risk-lack-of-transparency derived_attrs={'requiresGovernance': True}
hiring-risk-over-under-reliance derived_attrs={'requiresGovernance': True}
hiring-risk-discriminatory-actions derived_attrs={'requiresGovernance': True}
hiring-risk-unexplainable-output derived_attrs={'requiresGovernance': True}
hiring-risk-unrepresentative-data derived_attrs={'requiresGovernance': True}
4. Validate data against shape constraints¶
engine.validate() serialises the pyoxigraph store to N-Quads and passes it to pyshacl.
The hiring data provides isDefinedByTaxonomy on every risk, so the constraint is satisfied.
In [ ]:
Copied!
from ai_atlas_nexus.blocks.graph_explorer import PyoxigraphExplorer
ox = PyoxigraphExplorer(nexus._ontology)
report = engine.validate(ox._store)
print(f"Conforms: {report.conforms}")
print()
print(report.results_text[:600])
from ai_atlas_nexus.blocks.graph_explorer import PyoxigraphExplorer
ox = PyoxigraphExplorer(nexus._ontology)
report = engine.validate(ox._store)
print(f"Conforms: {report.conforms}")
print()
print(report.results_text[:600])
Conforms: True Validation Report Conforms: True