Spaces:
Running
Running
| """ | |
| RDF Knowledge Graph Builder. | |
| Constructs RDFLib graphs from system specifications and extracted claims, | |
| binding them to formal legal ontologies. | |
| """ | |
| from typing import Tuple, Dict, Any, List | |
| import re | |
| import rdflib | |
| from rdflib import Graph, URIRef, Literal, RDF, RDFS, XSD | |
| from src.core.config import REGU, EU_ACT, NIST, ISO, PROV, SH, SCHEMAS_DIR | |
| from src.core.models import ( | |
| SystemSpecification, | |
| ExtractedClaim, | |
| EntityCategory, | |
| AssertionStatus, | |
| ) | |
| class NormativeGraphBuilder: | |
| def __init__(self): | |
| self.category_predicate_map = { | |
| EntityCategory.RISK_MANAGEMENT: (REGU.hasRiskManagementSystem, REGU.RiskManagementSystem), | |
| EntityCategory.DATA_GOVERNANCE: (REGU.hasDataGovernance, REGU.DataGovernanceProcess), | |
| EntityCategory.BIAS_MITIGATION: (REGU.hasBiasMitigation, REGU.BiasMitigationControl), | |
| EntityCategory.TECHNICAL_DOCUMENTATION: (REGU.hasTechnicalDocumentation, REGU.TechnicalDocumentation), | |
| EntityCategory.RECORD_KEEPING: (REGU.hasLoggingCapability, REGU.AutomatedLogging), | |
| EntityCategory.TRANSPARENCY: (REGU.hasTransparencySpecification, REGU.TransparencySpecification), | |
| EntityCategory.HUMAN_OVERSIGHT: (REGU.hasHumanOversight, REGU.HumanOversightMechanism), | |
| EntityCategory.ACCURACY_ROBUSTNESS: (REGU.hasRobustnessControl, REGU.RobustnessControl), | |
| EntityCategory.CYBERSECURITY: (REGU.hasCybersecurityControl, REGU.CybersecurityControl), | |
| EntityCategory.FAIL_SAFE: (REGU.hasEmergencyStop, REGU.StopMechanism), | |
| EntityCategory.WATERMARKING_CONTROL: (REGU.hasWatermarkingControl, REGU.WatermarkingControl), | |
| } | |
| def _bind_namespaces(self, g: Graph) -> None: | |
| g.bind("regu", REGU) | |
| g.bind("eu", EU_ACT) | |
| g.bind("nist", NIST) | |
| g.bind("iso", ISO) | |
| g.bind("prov", PROV) | |
| g.bind("sh", SH) | |
| def load_base_ontologies(self) -> Graph: | |
| """Loads base OWL/RDFS legal definitions into a Graph.""" | |
| g = Graph() | |
| self._bind_namespaces(g) | |
| eu_path = SCHEMAS_DIR / "eu_ai_act.ttl" | |
| if eu_path.exists(): | |
| g.parse(str(eu_path), format="turtle") | |
| return g | |
| def build_system_graph(self, spec: SystemSpecification) -> Graph: | |
| """ | |
| Translates a parsed SystemSpecification with extracted claims | |
| into an RDF instance graph ready for SHACL verification. | |
| """ | |
| g = Graph() | |
| self._bind_namespaces(g) | |
| sys_uri = REGU[f"system_{spec.metadata.system_id.replace('-', '_')}"] | |
| # System Node & Regulatory Typing | |
| risk_class_lower = spec.metadata.eu_risk_classification.lower() | |
| is_prohibited = "prohibited" in risk_class_lower or bool(re.search(r"\barticle\s*5\b", risk_class_lower)) | |
| if is_prohibited: | |
| g.add((sys_uri, RDF.type, REGU.ProhibitedAISystem)) | |
| g.add((sys_uri, REGU.prohibitionStatus, REGU.ProhibitedPracticeDetected)) | |
| g.add((sys_uri, REGU.hasProhibitedPracticeType, REGU.ProhibitedPracticeDetected)) | |
| elif "generative" in risk_class_lower or "genai" in risk_class_lower or "synthetic" in risk_class_lower or "article 50" in risk_class_lower: | |
| g.add((sys_uri, RDF.type, REGU.GenerativeAISystem)) | |
| elif "general purpose" in risk_class_lower or "gpai" in risk_class_lower: | |
| if "systemic" in risk_class_lower or "article 51" in risk_class_lower: | |
| g.add((sys_uri, RDF.type, REGU.GPAISystemicRiskModel)) | |
| else: | |
| g.add((sys_uri, RDF.type, REGU.GPAIModel)) | |
| elif "high-risk" in risk_class_lower or "annex iii" in risk_class_lower or "annex i" in risk_class_lower: | |
| g.add((sys_uri, RDF.type, REGU.HighRiskAISystem)) | |
| else: | |
| g.add((sys_uri, RDF.type, REGU.AISystem)) | |
| g.add((sys_uri, RDFS.label, Literal(spec.metadata.name))) | |
| g.add((sys_uri, REGU.domain, Literal(spec.metadata.domain))) | |
| g.add((sys_uri, REGU.version, Literal(spec.metadata.version))) | |
| g.add((sys_uri, REGU.intendedPurpose, Literal(spec.metadata.intended_purpose))) | |
| g.add((sys_uri, REGU.developer, Literal(spec.metadata.developer_name))) | |
| # Process Claims into Instance Nodes | |
| for idx, claim in enumerate(spec.extracted_claims): | |
| if claim.category in self.category_predicate_map: | |
| pred, target_cls = self.category_predicate_map[claim.category] | |
| claim_node_uri = REGU[f"claim_{spec.metadata.system_id}_{idx}"] | |
| g.add((claim_node_uri, RDF.type, target_cls)) | |
| g.add((sys_uri, pred, claim_node_uri)) | |
| # Implementation Status Mapping | |
| if claim.assertion_status == AssertionStatus.IMPLEMENTED: | |
| g.add((claim_node_uri, REGU.implementationStatus, REGU.Implemented)) | |
| elif claim.assertion_status == AssertionStatus.PLANNED: | |
| g.add((claim_node_uri, REGU.implementationStatus, REGU.Planned)) | |
| else: | |
| g.add((claim_node_uri, REGU.implementationStatus, REGU.Absent)) | |
| # Grounding Metadata | |
| g.add((claim_node_uri, REGU.evidenceSource, Literal(claim.evidence_quote))) | |
| g.add((claim_node_uri, REGU.confidenceScore, Literal(claim.confidence, datatype=XSD.float))) | |
| g.add((claim_node_uri, REGU.normativeArticle, Literal(claim.normative_article))) | |
| return g | |
| def serialize(self, g: Graph, format: str = "turtle") -> str: | |
| """Serializes the RDF graph to a string.""" | |
| return g.serialize(format=format) | |