import os from rdflib import Graph, Literal, RDF, URIRef, Namespace from rdflib.namespace import SKOS, XSD from pyshacl import validate class SemanticValidator: def __init__(self): # Definiamo i namespace self.EX = Namespace("http://activa.ai/ontology/") self.shapes_file = os.path.join(os.path.dirname(__file__), "shapes/schema_constraints.ttl") # Carica le shapes se il file esiste, altrimenti usa grafo vuoto if os.path.exists(self.shapes_file): self.shacl_graph = Graph() self.shacl_graph.parse(self.shapes_file, format="turtle") print("🛡️ SHACL Constraints caricati.") else: print("⚠️ File SHACL non trovato. Validazione disabilitata.") self.shacl_graph = None def _json_to_rdf(self, triples): """Converte le triple JSON (Pydantic) in un grafo RDFLib in memoria.""" g = Graph() g.bind("skos", SKOS) g.bind("ex", self.EX) for t in triples: # Creiamo URI sanitizzati subj_uri = URIRef(self.EX[t.subject.replace(" ", "_")]) obj_uri = URIRef(self.EX[t.object.replace(" ", "_")]) # Aggiungiamo il tipo Concept g.add((subj_uri, RDF.type, SKOS.Concept)) g.add((subj_uri, SKOS.prefLabel, Literal(t.subject, lang="it"))) g.add((obj_uri, RDF.type, SKOS.Concept)) g.add((obj_uri, SKOS.prefLabel, Literal(t.object, lang="it"))) # Mappiamo il predicato (se è standard o custom) if t.predicate == "skos:related" or t.predicate == "related": pred = SKOS.related elif t.predicate == "skos:broader" or t.predicate == "broader": pred = SKOS.broader else: # Fallback su namespace custom per predicati non standard (es. situato_in) pred = self.EX[t.predicate] g.add((subj_uri, pred, obj_uri)) return g def validate_batch(self, triples): """ Esegue la validazione SHACL sulle triple. Ritorna (is_valid, report_text, rdf_graph) """ if not self.shacl_graph: return True, "No Constraints", None data_graph = self._json_to_rdf(triples) print("🔍 Esecuzione Validazione SHACL...") conforms, report_graph, report_text = validate( data_graph, shacl_graph=self.shacl_graph, inference='rdfs', serialize_report_graph=True ) return conforms, report_text, data_graph