From 4a451f410d1d94c7fead07c1188faaf9908bd34f Mon Sep 17 00:00:00 2001 From: OctoBored <212877535+OctoBored@users.noreply.github.com> Date: Mon, 17 Aug 2026 08:16:29 +0000 Subject: [PATCH 01/11] docs: fix broken star history chart in README The star history chart was broken due to GitHub stargazer API restrictions, so it could no longer be rendered. Update the README to point to a working alternative that uses a different data source requiring no API token. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8b89fd5f..8e729b2d 100644 --- a/README.md +++ b/README.md @@ -1566,11 +1566,11 @@ On-premises deployment · Private cloud · Custom domain implementations · SLA- ## Star History - + - - - Star History Chart + + + Star History Chart From eb7427d12cfbdc4f8289cadc7491223cdf16c506 Mon Sep 17 00:00:00 2001 From: FABIOTESS Date: Fri, 21 Aug 2026 13:01:40 +0100 Subject: [PATCH 02/11] fix(export): carry metadata through every RDF serialization (#1154) convert_kg_to_rdf copies metadata into the RDF-ready dictionary at rdf_exporter.py:302 and no serializer has ever read it back out. Turtle, N-Triples, RDF/XML and RDFExporter's JSON-LD each write an entity's id, type, text and confidence and nothing else, so an entity keeps its confidence score and loses what produced it. JSONExporter's json-ld path keeps the same fields, which is how one knowledge graph exported two ways carried the user's data through one exporter and none through the other. Measured on e3405ebc with an entity carrying four metadata keys: 3 triples per format, 0 of them metadata. With this change: 7 triples per format, 4 of them metadata, and the same four in all four formats. The keys Semantica itself writes are mapped to declared terms in DEFAULT_METADATA_TERMS and declared in semantica-ns.ttl. A key the caller supplied is not: which namespace an arbitrary key belongs in is #1146, and that issue is open on the maintainer's modelling call, so the exporter warns and skips rather than inventing an IRI. Callers who already know the answer pass metadata_terms={key: iri}. Two keys cannot keep their own name. sem:source is already the ObjectProperty holding the subject of a reified relationship, so the Neo4j loader's "source" is written as sem:sourceSystem and its "uri" as sem:sourceUri, the one term whose value is a node rather than a literal. sem:builtAt and sem:snapshotAt have range xsd:string, not xsd:dateTime. GraphBuilder stamps with a timezone-naive datetime.now(), and #1114 is the demonstration of what typing such a value as xsd:dateTime costs: a timezone-qualified SPARQL filter over it silently drops the row. #1121 swept export and provenance and deliberately left kg/ alone. Graph-level metadata is written only when the caller names the graph with graph_uri, because this serializer has never minted a document node and #1147 is where that default belongs once it lands. The lexical form and datatype of a value are chosen once, in _typed_literal_parts, so the four serializers cannot come to disagree about them the way they disagreed about confidence in #1100. The JSON-LD path writes explicit @value/@type rather than JSON's native numbers, which would have made an integer xsd:double there and xsd:integer everywhere else. 21 tests, asserting on the parsed graph in all four formats. Output is unchanged when no metadata is present. Full-suite failure set is identical to the parent commit: 512 = 512. --- semantica/export/rdf_exporter.py | 311 +++++++++++++++++- .../ontology/vocabulary/semantica-ns.ttl | 84 +++++ tests/export/test_metadata_passthrough.py | 228 +++++++++++++ 3 files changed, 613 insertions(+), 10 deletions(-) create mode 100644 tests/export/test_metadata_passthrough.py diff --git a/semantica/export/rdf_exporter.py b/semantica/export/rdf_exporter.py index de3d0dd0..af5a21c3 100644 --- a/semantica/export/rdf_exporter.py +++ b/semantica/export/rdf_exporter.py @@ -69,6 +69,187 @@ def mint_relationship_iri(index: int, source: Any, target: Any) -> str: return f"{SEMANTICA_NS}rel_{index}_{digest}" +#: The metadata keys Semantica itself produces, and the terms they are written +#: as. GraphBuilder.build_graph writes the first five, create_snapshot writes +#: snapshot_time, and load_from_neo4j writes source / uri / database. These are +#: Semantica's own vocabulary, so they are minted in the declared namespace and +#: declared in semantica-ns.ttl. +#: +#: A key the caller supplied is a different matter. Which namespace an +#: arbitrary metadata key belongs in is issue #1146, and until that is settled +#: the exporter refuses to guess: it warns and skips, and a caller who already +#: knows the answer passes ``metadata_terms``. +#: +#: The map is key -> term rather than key -> namespace because two of the keys +#: cannot keep their own name. ``source`` on a graph loaded from Neo4j is the +#: system it came from, while sem:source is already the ObjectProperty holding +#: the subject of a reified relationship; reusing it would put a string where +#: an entity belongs. +DEFAULT_METADATA_TERMS: Dict[str, str] = { + "num_entities": f"{SEMANTICA_NS}numEntities", + "num_relationships": f"{SEMANTICA_NS}numRelationships", + "temporal_enabled": f"{SEMANTICA_NS}temporalEnabled", + "entity_resolution_applied": f"{SEMANTICA_NS}entityResolutionApplied", + "timestamp": f"{SEMANTICA_NS}builtAt", + "snapshot_time": f"{SEMANTICA_NS}snapshotAt", + "source": f"{SEMANTICA_NS}sourceSystem", + "uri": f"{SEMANTICA_NS}sourceUri", + "database": f"{SEMANTICA_NS}sourceDatabase", +} + +#: Terms whose value is a node rather than a string. Everything else stays a +#: literal: a metadata value that merely looks like a URL is not thereby a +#: reference to one. +IRI_VALUED_METADATA_TERMS: Set[str] = {f"{SEMANTICA_NS}sourceUri"} + +_XSD_NS = "http://www.w3.org/2001/XMLSchema#" + + +def _escape_literal(value: str) -> str: + """Escape a string for a Turtle or N-Triples quoted literal.""" + return ( + value.replace("\\", "\\\\") + .replace('"', '\\"') + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + ) + + +def _escape_xml(value: str) -> str: + return value.replace("&", "&").replace("<", "<").replace(">", ">") + + +def _split_iri(iri: str) -> Optional[tuple]: + """Split an IRI into (namespace, local name) for RDF/XML's QName syntax.""" + for sep in ("#", "/"): + index = iri.rfind(sep) + if index != -1 and index + 1 < len(iri): + local = iri[index + 1 :] + if local and not local[0].isdigit(): + return iri[: index + 1], local + return None + + +def _metadata_statements( + metadata: Any, + terms: Dict[str, str], + logger: Any, +) -> List[tuple]: + """Resolve a metadata mapping to a list of (term IRI, value) pairs. + + A key with no term is skipped and reported. Silence is the defect this + fixes, so an unmapped key must be louder than a mapped one, not quieter. + """ + if not isinstance(metadata, dict): + return [] + + statements: List[tuple] = [] + for key, value in metadata.items(): + term = terms.get(key) + if term is None: + logger.warning( + "Metadata key %r has no term and was not exported. Which " + "namespace a caller-supplied key belongs in is issue #1146; " + "pass metadata_terms={%r: ''} to export it now.", + key, + key, + ) + continue + if value is None: + continue + if isinstance(value, (dict, list, tuple, set)): + logger.warning( + "Metadata key %r holds a %s, which has no modelled RDF shape " + "yet, and was not exported.", + key, + type(value).__name__, + ) + continue + statements.append((term, value)) + return statements + + +def _resolve_metadata_terms(overrides: Optional[Dict[str, str]]) -> Dict[str, str]: + if not overrides: + return DEFAULT_METADATA_TERMS + return {**DEFAULT_METADATA_TERMS, **overrides} + + +def _typed_literal_parts(term: str, value: Any) -> tuple: + """Return (kind, lexical, datatype) for one metadata value. + + kind is "iri" or "literal". The lexical form and datatype are chosen once, + here, so that the four serializers cannot disagree about them the way they + disagreed about confidence in #1100. + """ + if term in IRI_VALUED_METADATA_TERMS and isinstance(value, str): + return "iri", value, None + if isinstance(value, bool): + return "literal", "true" if value else "false", f"{_XSD_NS}boolean" + if isinstance(value, int): + return "literal", str(value), f"{_XSD_NS}integer" + if isinstance(value, float): + return "literal", repr(value), f"{_XSD_NS}decimal" + return "literal", str(value), None + + +def _turtle_object(term: str, value: Any) -> str: + kind, lexical, datatype = _typed_literal_parts(term, value) + if kind == "iri": + return f"<{lexical}>" + if datatype is None: + return f'"{_escape_literal(lexical)}"' + return f'"{lexical}"^^<{datatype}>' + + +def _turtle_metadata_clauses(statements: List[tuple]) -> List[str]: + return [f"<{term}> {_turtle_object(term, value)}" for term, value in statements] + + +def _ntriples_metadata_lines(subject: str, statements: List[tuple]) -> List[str]: + return [ + f"<{subject}> <{term}> {_turtle_object(term, value)} ." + for term, value in statements + ] + + +def _rdfxml_metadata_lines(statements: List[tuple], indent: str) -> List[str]: + """RDF/XML needs a QName, so an unprefixed term declares its own prefix.""" + lines: List[str] = [] + for position, (term, value) in enumerate(statements): + split = _split_iri(term) + if split is None: + continue + namespace, local = split + kind, lexical, datatype = _typed_literal_parts(term, value) + prefix = f"md{position}" + opening = f'{indent}<{prefix}:{local} xmlns:{prefix}="{_escape_xml(namespace)}"' + if kind == "iri": + lines.append(f'{opening} rdf:resource="{_escape_xml(lexical)}"/>') + continue + if datatype is not None: + opening += f' rdf:datatype="{_escape_xml(datatype)}"' + lines.append(f"{opening}>{_escape_xml(lexical)}") + return lines + + +def _jsonld_metadata_entries(statements: List[tuple]) -> Dict[str, Any]: + """Absolute IRIs as keys, and explicit @value/@type rather than JSON's own + types: JSON's number is xsd:double, which would make the JSON-LD export + disagree with the other three about the datatype of an integer.""" + entries: Dict[str, Any] = {} + for term, value in statements: + kind, lexical, datatype = _typed_literal_parts(term, value) + if kind == "iri": + entries[term] = {"@id": lexical} + elif datatype is None: + entries[term] = lexical + else: + entries[term] = {"@value": lexical, "@type": datatype} + return entries + + class NamespaceManager: """ RDF namespace management engine. @@ -364,6 +545,8 @@ class RDFSerializer: """ include_temporal: bool = options.pop("include_temporal", False) time_axis: str = options.pop("time_axis", "valid") + metadata_terms = _resolve_metadata_terms(options.pop("metadata_terms", None)) + graph_uri: Optional[str] = options.pop("graph_uri", None) lines = [] @@ -397,9 +580,23 @@ class RDFSerializer: text = entity.get("text") or entity.get("label", "") confidence = entity.get("confidence", 1.0) - lines.append(f"<{entity_id}> a <{entity_type}> ;") - lines.append(f' semantica:text "{text}" ;') - lines.append(f" semantica:confidence {confidence} .") + clauses = [ + f"a <{entity_type}>", + f'semantica:text "{text}"', + f"semantica:confidence {confidence}", + ] + clauses.extend( + _turtle_metadata_clauses( + _metadata_statements( + entity.get("metadata"), metadata_terms, self.logger + ) + ) + ) + + lines.append(f"<{entity_id}> {clauses[0]} ;") + for clause in clauses[1:-1]: + lines.append(f" {clause} ;") + lines.append(f" {clauses[-1]} .") lines.append("") # Convert relationships to RDF triplets @@ -416,6 +613,30 @@ class RDFSerializer: if owl_lines: lines.extend(owl_lines) + # Graph-level metadata needs a subject, and this serializer has never + # minted a document node. Rather than invent one here, it is written + # only when the caller names the graph; issue #1147 is where the + # default subject comes from once that lands. + graph_clauses = ( + _turtle_metadata_clauses( + _metadata_statements( + rdf_data.get("metadata"), metadata_terms, self.logger + ) + ) + if graph_uri + else [] + ) + if graph_clauses: + lines.append("") + lines.append( + f"<{graph_uri}> {graph_clauses[0]} " + + (";" if len(graph_clauses) > 1 else ".") + ) + for clause in graph_clauses[1:-1]: + lines.append(f" {clause} ;") + if len(graph_clauses) > 1: + lines.append(f" {graph_clauses[-1]} .") + return "\n".join(lines) def _owl_time_triples_for_rel( @@ -510,6 +731,9 @@ class RDFSerializer: ... } >>> rdfxml = serializer.serialize_to_rdfxml(rdf_data) """ + metadata_terms = _resolve_metadata_terms(options.pop("metadata_terms", None)) + graph_uri: Optional[str] = options.pop("graph_uri", None) + lines = [''] lines.append('{confidence}" ) + lines.extend( + _rdfxml_metadata_lines( + _metadata_statements( + entity.get("metadata"), metadata_terms, self.logger + ), + " ", + ) + ) lines.append(" ") lines.append("") @@ -552,6 +784,22 @@ class RDFSerializer: lines.append(" ") lines.append("") + graph_lines = ( + _rdfxml_metadata_lines( + _metadata_statements( + rdf_data.get("metadata"), metadata_terms, self.logger + ), + " ", + ) + if graph_uri + else [] + ) + if graph_lines: + lines.append(f' ') + lines.extend(graph_lines) + lines.append(" ") + lines.append("") + lines.append("") return "\n".join(lines) @@ -582,6 +830,9 @@ class RDFSerializer: """ import json + metadata_terms = _resolve_metadata_terms(options.pop("metadata_terms", None)) + graph_uri: Optional[str] = options.pop("graph_uri", None) + # Initialize JSON-LD structure with context jsonld = { "@context": { @@ -608,14 +859,20 @@ class RDFSerializer: # and was dropped in full by a JSON-LD parser, silently. entity_id = entity.get("id") or mint_entity_iri(entity.get("text", "")) - jsonld["@graph"].append( - { - "@id": entity_id, - "@type": entity.get("type", "semantica:Entity"), - "semantica:text": entity.get("text") or entity.get("label", ""), - "semantica:confidence": entity.get("confidence", 1.0), - } + node = { + "@id": entity_id, + "@type": entity.get("type", "semantica:Entity"), + "semantica:text": entity.get("text") or entity.get("label", ""), + "semantica:confidence": entity.get("confidence", 1.0), + } + node.update( + _jsonld_metadata_entries( + _metadata_statements( + entity.get("metadata"), metadata_terms, self.logger + ) + ) ) + jsonld["@graph"].append(node) # Convert relationships to JSON-LD relationships = rdf_data.get("relationships", []) @@ -639,6 +896,18 @@ class RDFSerializer: } ) + graph_entries = ( + _jsonld_metadata_entries( + _metadata_statements( + rdf_data.get("metadata"), metadata_terms, self.logger + ) + ) + if graph_uri + else {} + ) + if graph_entries: + jsonld["@graph"].append({"@id": graph_uri, **graph_entries}) + return json.dumps(jsonld, indent=2, ensure_ascii=False) def serialize_to_ntriples(self, rdf_data: Dict[str, Any], **options) -> str: @@ -655,6 +924,9 @@ class RDFSerializer: Returns: String containing N-Triples serialization """ + metadata_terms = _resolve_metadata_terms(options.pop("metadata_terms", None)) + graph_uri: Optional[str] = options.pop("graph_uri", None) + lines = [] def expand_uri(uri: str) -> str: @@ -704,6 +976,15 @@ class RDFSerializer: f'{subject} {expand_uri("semantica:confidence")} "{confidence}"^^ .' ) + lines.extend( + _ntriples_metadata_lines( + subject.strip("<>"), + _metadata_statements( + entity.get("metadata"), metadata_terms, self.logger + ), + ) + ) + # Convert relationships relationships = rdf_data.get("relationships", []) for rel in relationships: @@ -716,6 +997,16 @@ class RDFSerializer: f"{expand_uri(source_id)} {expand_uri(rel_type)} {expand_uri(target_id)} ." ) + if graph_uri: + lines.extend( + _ntriples_metadata_lines( + graph_uri, + _metadata_statements( + rdf_data.get("metadata"), metadata_terms, self.logger + ), + ) + ) + return "\n".join(lines) diff --git a/semantica/ontology/vocabulary/semantica-ns.ttl b/semantica/ontology/vocabulary/semantica-ns.ttl index 36e206bc..8dba7fee 100644 --- a/semantica/ontology/vocabulary/semantica-ns.ttl +++ b/semantica/ontology/vocabulary/semantica-ns.ttl @@ -134,6 +134,90 @@ JSONExporter.export_to_jsonld in export/json_exporter.py.""" ; rdfs:range xsd:string ; rdfs:isDefinedBy . +# ── Metadata carried through from the graph builder ────────────────────────── +# +# The keys GraphBuilder and the Neo4j loader write into "metadata". Declared +# here because the RDF serializers emit them (#1154); a caller-supplied key is +# not declared here and is not emitted, because which namespace it belongs in +# is #1146. + +sem:numEntities a owl:DatatypeProperty ; + rdfs:label "number of entities" ; + rdfs:comment """Count of entities in the graph as built, from +GraphBuilder.build_graph. A count of what was built, not a constraint on what +the graph contains: an export filtered after the fact will disagree with it.""" ; + rdfs:range xsd:integer ; + rdfs:isDefinedBy . + +sem:numRelationships a owl:DatatypeProperty ; + rdfs:label "number of relationships" ; + rdfs:comment "Count of relationships in the graph as built." ; + rdfs:range xsd:integer ; + rdfs:isDefinedBy . + +sem:temporalEnabled a owl:DatatypeProperty ; + rdfs:label "temporal enabled" ; + rdfs:comment """True when the builder was configured to track valid time. +False does not mean the graph is untimed; it means no temporal bounds were +recorded for it.""" ; + rdfs:range xsd:boolean ; + rdfs:isDefinedBy . + +sem:entityResolutionApplied a owl:DatatypeProperty ; + rdfs:label "entity resolution applied" ; + rdfs:comment """True when a resolver ran over the extracted entities, so a +consumer knows whether two nodes with the same surface text were ever +considered for merging.""" ; + rdfs:range xsd:boolean ; + rdfs:isDefinedBy . + +sem:builtAt a owl:DatatypeProperty ; + rdfs:label "built at" ; + rdfs:comment """When the graph was built, as GraphBuilder recorded it. + +The range is xsd:string, deliberately, and not xsd:dateTime. GraphBuilder +stamps with a timezone-naive datetime.now(), and #1114 is the demonstration of +what typing such a value as xsd:dateTime costs: a timezone-qualified SPARQL +filter over it raises an indeterminate comparison and silently drops the row. +#1121 swept the export and provenance modules to an explicit UTC offset and +deliberately left kg/ alone, because the context and vector-store modules +compare against naive values already on disk. Until that sweep reaches +GraphBuilder this value is a string that looks like a timestamp, and saying so +is more useful than a type that invites arithmetic it cannot support.""" ; + rdfs:range xsd:string ; + rdfs:isDefinedBy . + +sem:snapshotAt a owl:DatatypeProperty ; + rdfs:label "snapshot at" ; + rdfs:comment """The point in time a snapshot represents, from +GraphBuilder.create_snapshot. A string for the same reason as sem:builtAt.""" ; + rdfs:range xsd:string ; + rdfs:isDefinedBy . + +sem:sourceSystem a owl:DatatypeProperty ; + rdfs:label "source system" ; + rdfs:comment """The system a graph was loaded from, currently the literal +"neo4j" written by GraphBuilder.load_from_neo4j. + +Named sourceSystem rather than source because sem:source is already the +ObjectProperty carrying the subject of a reified relationship. The metadata key +is still "source"; the exporter maps the key to this term.""" ; + rdfs:range xsd:string ; + rdfs:isDefinedBy . + +sem:sourceUri a owl:ObjectProperty ; + rdfs:label "source URI" ; + rdfs:comment """The address of the system a graph was loaded from. The one +metadata term whose value is a node rather than a literal, because it names a +thing rather than describing one.""" ; + rdfs:isDefinedBy . + +sem:sourceDatabase a owl:DatatypeProperty ; + rdfs:label "source database" ; + rdfs:comment "The database within the source system a graph was loaded from." ; + rdfs:range xsd:string ; + rdfs:isDefinedBy . + # ── Temporal term (OWL-Time export) ────────────────────────────────────────── sem:openEndedInterval a owl:DatatypeProperty ; diff --git a/tests/export/test_metadata_passthrough.py b/tests/export/test_metadata_passthrough.py new file mode 100644 index 00000000..15fbcb7d --- /dev/null +++ b/tests/export/test_metadata_passthrough.py @@ -0,0 +1,228 @@ +"""Metadata must survive serialization (issue #1154). + +``convert_kg_to_rdf`` copies ``metadata`` into the RDF-ready dictionary at +rdf_exporter.py:302, and no serializer has ever read it back out. Turtle, +N-Triples, RDF/XML and RDFExporter's JSON-LD all write the entity's id, type, +text and confidence, and none of them writes a single metadata statement, so an +entity keeps its confidence score and loses what produced it: the source +document, the page, the extractor, the reviewer. JSONExporter's json-ld path +keeps all of them, which is how the same knowledge graph exported two ways came +to carry ten triples of user data through one exporter and none through the +other. + +The keys Semantica itself produces (GraphBuilder writes num_entities, +num_relationships, temporal_enabled, timestamp and entity_resolution_applied; +the Neo4j loader writes source, uri and database) are Semantica's own +vocabulary, so they are minted in the declared namespace and declared in +semantica-ns.ttl. Keys the caller supplied are not: which namespace those +belong in is issue #1146, and until that is settled the exporter refuses to +guess rather than inventing an IRI, warns, and takes an explicit +``metadata_terms`` mapping from any caller who already knows the answer. +""" + +import json + +import pytest +from rdflib import Graph, Literal, URIRef +from rdflib.namespace import XSD + +from semantica.export.rdf_exporter import ( + DEFAULT_METADATA_TERMS, + RDFSerializer, + SEMANTICA_NS, + mint_entity_iri, +) + +ENTITY_IRI = "https://example.org/e1" + +# The provenance fields the issue names, plus one key Semantica itself writes. +GRAPH_WITH_METADATA = { + "entities": [ + { + "id": ENTITY_IRI, + "type": "https://example.org/Org", + "text": "Acme Corp", + "confidence": 0.91, + "metadata": {"num_entities": 1, "temporal_enabled": True}, + } + ], + "relationships": [], + "metadata": { + "num_entities": 1, + "num_relationships": 0, + "temporal_enabled": False, + "entity_resolution_applied": True, + }, +} + +NUM_ENTITIES = URIRef(f"{SEMANTICA_NS}numEntities") +TEMPORAL_ENABLED = URIRef(f"{SEMANTICA_NS}temporalEnabled") + + +def _parse(text: str, fmt: str) -> Graph: + """Assert on the parsed graph, never on the serialized text.""" + g = Graph() + g.parse(data=text, format=fmt) + return g + + +def _serialize(serializer: RDFSerializer, fmt: str, data, **options) -> Graph: + method, parse_as = { + "turtle": (serializer.serialize_to_turtle, "turtle"), + "ntriples": (serializer.serialize_to_ntriples, "nt"), + "rdfxml": (serializer.serialize_to_rdfxml, "xml"), + "jsonld": (serializer.serialize_to_jsonld, "json-ld"), + }[fmt] + return _parse(method(data, **options), parse_as) + + +FORMATS = ["turtle", "ntriples", "rdfxml", "jsonld"] + + +@pytest.mark.parametrize("fmt", FORMATS) +def test_entity_metadata_reaches_every_serialization(fmt): + """The headline defect: the statement is absent from all four formats.""" + g = _serialize(RDFSerializer(), fmt, GRAPH_WITH_METADATA) + assert (URIRef(ENTITY_IRI), NUM_ENTITIES, Literal(1)) in g + + +@pytest.mark.parametrize("fmt", FORMATS) +def test_entity_metadata_booleans_keep_their_datatype(fmt): + g = _serialize(RDFSerializer(), fmt, GRAPH_WITH_METADATA) + assert (URIRef(ENTITY_IRI), TEMPORAL_ENABLED, Literal(True)) in g + + +def test_every_format_writes_the_same_metadata_triples(): + """A value must not change datatype with the serializer, as #1100 found.""" + per_format = {} + for fmt in FORMATS: + g = _serialize(RDFSerializer(), fmt, GRAPH_WITH_METADATA) + per_format[fmt] = { + (p, o) for s, p, o in g if str(p).startswith(SEMANTICA_NS) and "numEntities" in str(p) + } + assert len(set(map(frozenset, per_format.values()))) == 1, per_format + + +def test_graph_metadata_needs_a_subject_the_caller_named(): + """Graph-level metadata hangs off graph_uri; #1147 owns the default.""" + doc = URIRef("https://example.org/graph/1") + g = _serialize( + RDFSerializer(), + "turtle", + GRAPH_WITH_METADATA, + graph_uri=str(doc), + ) + assert (doc, NUM_ENTITIES, Literal(1)) in g + assert (doc, URIRef(f"{SEMANTICA_NS}entityResolutionApplied"), Literal(True)) in g + + +def test_graph_metadata_is_not_invented_without_a_subject(): + g = _serialize(RDFSerializer(), "turtle", GRAPH_WITH_METADATA) + assert not list(g.subjects(NUM_ENTITIES, Literal(0))) + # the entity keeps its own metadata; only the graph-level block waits + assert (URIRef(ENTITY_IRI), NUM_ENTITIES, Literal(1)) in g + + +def test_an_unknown_key_is_refused_out_loud_not_dropped_in_silence(caplog): + """#1146 owns which namespace a caller's key belongs in. Until then: warn.""" + data = { + "entities": [ + {"id": ENTITY_IRI, "text": "Acme", "metadata": {"reviewed_by": "fabio"}} + ], + "relationships": [], + } + with caplog.at_level("WARNING"): + g = _serialize(RDFSerializer(), "turtle", data) + assert not any("reviewed_by" in str(p) for p in g.predicates()) + assert any("reviewed_by" in r.getMessage() for r in caplog.records) + assert any("1146" in r.getMessage() for r in caplog.records) + + +@pytest.mark.parametrize("fmt", FORMATS) +def test_a_caller_who_knows_the_answer_can_supply_the_term(fmt): + data = { + "entities": [ + {"id": ENTITY_IRI, "text": "Acme", "metadata": {"reviewed_by": "fabio"}} + ], + "relationships": [], + } + terms = {"reviewed_by": "http://purl.org/dc/terms/creator"} + g = _serialize(RDFSerializer(), fmt, data, metadata_terms=terms) + assert ( + URIRef(ENTITY_IRI), + URIRef("http://purl.org/dc/terms/creator"), + Literal("fabio"), + ) in g + + +def test_a_literal_with_a_quote_or_newline_still_parses(): + """Metadata is user text; #1098 is the same class of defect one field over.""" + data = { + "entities": [ + { + "id": ENTITY_IRI, + "text": "Acme", + "metadata": {"source": 'the "Q3" report\nsecond line'}, + } + ], + "relationships": [], + } + for fmt in FORMATS: + g = _serialize(RDFSerializer(), fmt, data) + assert ( + URIRef(ENTITY_IRI), + URIRef(f"{SEMANTICA_NS}sourceSystem"), + Literal('the "Q3" report\nsecond line'), + ) in g + + +def test_an_iri_valued_key_is_written_as_a_node_not_a_string(): + """The Neo4j loader's ``uri`` key. Note the term is sem:sourceUri, not + sem:uri: the key names a field, the term names a relation.""" + data = { + "entities": [ + {"id": ENTITY_IRI, "text": "Acme", "metadata": {"uri": "https://example.org/db"}} + ], + "relationships": [], + } + g = _serialize(RDFSerializer(), "turtle", data) + assert ( + URIRef(ENTITY_IRI), + URIRef(f"{SEMANTICA_NS}sourceUri"), + URIRef("https://example.org/db"), + ) in g + + +def test_output_is_unchanged_when_no_metadata_is_present(): + plain = { + "entities": [{"id": ENTITY_IRI, "type": "https://example.org/Org", "text": "Acme"}], + "relationships": [{"source_id": ENTITY_IRI, "target_id": "https://example.org/e2"}], + } + serializer = RDFSerializer() + assert serializer.serialize_to_turtle(plain) == serializer.serialize_to_turtle(plain) + g = _parse(serializer.serialize_to_turtle(plain), "turtle") + assert len(g) == 4 + + +def test_every_default_term_is_declared_in_the_shipped_vocabulary(): + """Drift guard: a term the exporter emits and the vocabulary omits is a bug.""" + from semantica.ontology.vocabulary import vocabulary_path + + vocab = Graph() + vocab.parse(vocabulary_path(), format="turtle") + declared = {str(s) for s in vocab.subjects()} + missing = sorted(set(DEFAULT_METADATA_TERMS.values()) - declared) + assert not missing, f"emitted but undeclared: {missing}" + + +def test_jsonld_metadata_survives_a_real_jsonld_processor(): + data = { + "entities": [ + {"id": ENTITY_IRI, "text": "Acme", "metadata": {"num_entities": 3}} + ], + "relationships": [], + } + raw = RDFSerializer().serialize_to_jsonld(data) + json.loads(raw) # must be valid JSON before it can be valid JSON-LD + g = _parse(raw, "json-ld") + assert (URIRef(ENTITY_IRI), NUM_ENTITIES, Literal(3)) in g From 1a220da477dc679db45dbcf7c18c9729249f3ba8 Mon Sep 17 00:00:00 2001 From: Fabio Rovai Date: Fri, 21 Aug 2026 14:43:02 +0100 Subject: [PATCH 03/11] fix(export): address the review findings on the metadata pass-through --- semantica/export/rdf_exporter.py | 103 +++++++++++++++-- tests/export/test_metadata_passthrough.py | 135 +++++++++++++++++++++- 2 files changed, 223 insertions(+), 15 deletions(-) diff --git a/semantica/export/rdf_exporter.py b/semantica/export/rdf_exporter.py index 6f07d619..ce64a3a2 100644 --- a/semantica/export/rdf_exporter.py +++ b/semantica/export/rdf_exporter.py @@ -187,16 +187,53 @@ def _escape_literal(value: str) -> str: def _escape_xml(value: str) -> str: - return value.replace("&", "&").replace("<", "<").replace(">", ">") + """Escape a string for either XML element text or an attribute value. + + The quotes matter. This helper feeds `rdf:about`, `rdf:resource` and + `xmlns:` attribute values, which are delimited by double quotes, so a value + carrying one would close the attribute early and produce a document that + does not parse. Escaping them in element text as well is harmless and + means one helper cannot be used in the wrong place. + """ + return ( + value.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + .replace("'", "'") + ) + + +def _is_ncname(value: str) -> bool: + """Whether a string can be an XML NCName, which is what RDF/XML requires. + + Checked over the ASCII range rather than the full XML production: the + grammar also admits combining characters and extenders, so this is + deliberately conservative. It refuses names it could have accepted, and it + never accepts one that would produce a document a parser rejects. The + earlier check tested only that the first character was not a digit, which + let through every other way a local name can fail to be a name. + """ + if not value: + return False + if not (value[0].isascii() and (value[0].isalpha() or value[0] == "_")): + return False + return all(c.isascii() and (c.isalnum() or c in "._-") for c in value[1:]) def _split_iri(iri: str) -> Optional[tuple]: - """Split an IRI into (namespace, local name) for RDF/XML's QName syntax.""" + """Split an IRI into (namespace, local name) for RDF/XML's QName syntax. + + Returns None when no split yields a usable local name. RDF/XML is the only + serialization here that cannot write an arbitrary predicate IRI, so this is + the one place a term can be unrepresentable, and the caller reports it + rather than dropping it quietly. + """ for sep in ("#", "/"): index = iri.rfind(sep) if index != -1 and index + 1 < len(iri): local = iri[index + 1 :] - if local and not local[0].isdigit(): + if _is_ncname(local): return iri[: index + 1], local return None @@ -260,7 +297,24 @@ def _typed_literal_parts(term: str, value: Any) -> tuple: if isinstance(value, int): return "literal", str(value), f"{_XSD_NS}integer" if isinstance(value, float): - return "literal", repr(value), f"{_XSD_NS}decimal" + # xsd:double, not xsd:decimal. `repr(1e-05)` is "1e-05" and + # `repr(float("nan"))` is "nan", and xsd:decimal admits neither the + # exponent form nor the special values, so typing a float as decimal + # produced lexicals a strict parser rejects. A Python float is an IEEE + # 754 double; xsd:double has legal lexicals for all of them, and it is + # also the honest claim, since nothing that arrived as a float was ever + # exact. `normalize_confidence` keeps xsd:decimal for confidence + # deliberately: that is a bounded score where exactness is meaningful + # and NaN is not a confidence at all. + if value != value: + lexical = "NaN" + elif value == float("inf"): + lexical = "INF" + elif value == float("-inf"): + lexical = "-INF" + else: + lexical = repr(value) + return "literal", lexical, f"{_XSD_NS}double" return "literal", str(value), None @@ -284,12 +338,28 @@ def _ntriples_metadata_lines(subject: str, statements: List[tuple]) -> List[str] ] -def _rdfxml_metadata_lines(statements: List[tuple], indent: str) -> List[str]: - """RDF/XML needs a QName, so an unprefixed term declares its own prefix.""" +def _rdfxml_metadata_lines( + statements: List[tuple], indent: str, logger: Any = None +) -> List[str]: + """RDF/XML needs a QName, so an unprefixed term declares its own prefix. + + A term with no QName form has no RDF/XML representation at all, and this is + the only serialization with that restriction. Skipping it quietly would + reintroduce, in one format, exactly the silent metadata loss this module + was changed to stop, so it is reported and the other three formats still + carry the statement in full. + """ lines: List[str] = [] for position, (term, value) in enumerate(statements): split = _split_iri(term) if split is None: + if logger is not None: + logger.warning( + "Term %r has no QName form, so it cannot be written in " + "RDF/XML and was omitted from that serialization only. " + "Turtle, N-Triples and JSON-LD carry it in full.", + term, + ) continue namespace, local = split kind, lexical, datatype = _typed_literal_parts(term, value) @@ -881,8 +951,15 @@ class RDFSerializer: confidence = normalize_confidence(entity.get("confidence", 1.0)) # RDF/XML syntax: rdf:Description with rdf:about - lines.append(f' ') - lines.append(f' ') + # Attribute values are delimited by quotes, and both of these + # are caller input. Element text is left alone deliberately: that + # is #1098, and it is being fixed on its own path. + lines.append( + f' ' + ) + lines.append( + f' ' + ) lines.append(f" {text}") if confidence is None: self.logger.warning( @@ -900,6 +977,7 @@ class RDFSerializer: entity.get("metadata"), metadata_terms, self.logger ), " ", + self.logger, ) ) lines.append(" ") @@ -913,8 +991,12 @@ class RDFSerializer: rel_type = rel.get("type", "semantica:related_to") # Relationship as property on source entity - lines.append(f' ') - lines.append(f' <{rel_type} rdf:resource="{target_id}"/>') + lines.append( + f' ' + ) + lines.append( + f' <{rel_type} rdf:resource="{_escape_xml(str(target_id))}"/>' + ) lines.append(" ") lines.append("") @@ -924,6 +1006,7 @@ class RDFSerializer: rdf_data.get("metadata"), metadata_terms, self.logger ), " ", + self.logger, ) if graph_uri else [] diff --git a/tests/export/test_metadata_passthrough.py b/tests/export/test_metadata_passthrough.py index 15fbcb7d..cdb73227 100644 --- a/tests/export/test_metadata_passthrough.py +++ b/tests/export/test_metadata_passthrough.py @@ -98,7 +98,9 @@ def test_every_format_writes_the_same_metadata_triples(): for fmt in FORMATS: g = _serialize(RDFSerializer(), fmt, GRAPH_WITH_METADATA) per_format[fmt] = { - (p, o) for s, p, o in g if str(p).startswith(SEMANTICA_NS) and "numEntities" in str(p) + (p, o) + for s, p, o in g + if str(p).startswith(SEMANTICA_NS) and "numEntities" in str(p) } assert len(set(map(frozenset, per_format.values()))) == 1, per_format @@ -181,7 +183,11 @@ def test_an_iri_valued_key_is_written_as_a_node_not_a_string(): sem:uri: the key names a field, the term names a relation.""" data = { "entities": [ - {"id": ENTITY_IRI, "text": "Acme", "metadata": {"uri": "https://example.org/db"}} + { + "id": ENTITY_IRI, + "text": "Acme", + "metadata": {"uri": "https://example.org/db"}, + } ], "relationships": [], } @@ -195,11 +201,17 @@ def test_an_iri_valued_key_is_written_as_a_node_not_a_string(): def test_output_is_unchanged_when_no_metadata_is_present(): plain = { - "entities": [{"id": ENTITY_IRI, "type": "https://example.org/Org", "text": "Acme"}], - "relationships": [{"source_id": ENTITY_IRI, "target_id": "https://example.org/e2"}], + "entities": [ + {"id": ENTITY_IRI, "type": "https://example.org/Org", "text": "Acme"} + ], + "relationships": [ + {"source_id": ENTITY_IRI, "target_id": "https://example.org/e2"} + ], } serializer = RDFSerializer() - assert serializer.serialize_to_turtle(plain) == serializer.serialize_to_turtle(plain) + assert serializer.serialize_to_turtle(plain) == serializer.serialize_to_turtle( + plain + ) g = _parse(serializer.serialize_to_turtle(plain), "turtle") assert len(g) == 4 @@ -226,3 +238,116 @@ def test_jsonld_metadata_survives_a_real_jsonld_processor(): json.loads(raw) # must be valid JSON before it can be valid JSON-LD g = _parse(raw, "json-ld") assert (URIRef(ENTITY_IRI), NUM_ENTITIES, Literal(3)) in g + + +# --- Findings from the Qodo review of PR #1165 ----------------------------- + + +@pytest.mark.parametrize("fmt", FORMATS) +@pytest.mark.parametrize( + "value", [1e-05, 1e300, 0.1, -0.0, float("nan"), float("inf"), float("-inf")] +) +def test_a_float_metadata_value_is_a_double_and_keeps_a_legal_lexical(fmt, value): + """`repr()` of a float is not an xsd:decimal lexical. + + `repr(1e-05)` is "1e-05" and `repr(float("nan"))` is "nan", neither of which + xsd:decimal admits, so typing a float as decimal produced RDF a strict + parser rejects. A Python float is an IEEE 754 double, xsd:double has legal + lexicals for the exponent form and for the three special values, and saying + double is also the honest claim: nothing here was ever exact. + """ + data = { + "entities": [ + {"id": ENTITY_IRI, "text": "Acme", "metadata": {"num_entities": value}} + ], + "relationships": [], + } + g = _serialize(RDFSerializer(), fmt, data) + objects = list(g.objects(URIRef(ENTITY_IRI), NUM_ENTITIES)) + assert len(objects) == 1, f"{fmt}: {objects}" + (written,) = objects + assert written.datatype == XSD.double, written.datatype + parsed = written.toPython() + if value != value: # NaN + assert parsed != parsed + else: + assert parsed == value + + +def test_every_format_agrees_on_a_float_metadata_value(): + per_format = {} + for fmt in FORMATS: + g = _serialize( + RDFSerializer(), + fmt, + { + "entities": [ + {"id": ENTITY_IRI, "text": "A", "metadata": {"num_entities": 1e-05}} + ], + "relationships": [], + }, + ) + per_format[fmt] = {(p, o) for s, p, o in g if p == NUM_ENTITIES} + assert len(set(map(frozenset, per_format.values()))) == 1, per_format + + +def test_a_term_rdfxml_cannot_name_is_refused_out_loud(caplog): + """RDF/XML needs a QName, and the PR's whole point is no silent drops. + + A term whose local part is not an XML NCName has no RDF/XML form at all. + Skipping it quietly reintroduces, in one format, exactly the loss this + change exists to stop. + """ + unnameable = "http://example.org/ns/123" + data = { + "entities": [ + {"id": ENTITY_IRI, "text": "Acme", "metadata": {"reviewed_by": "fabio"}} + ], + "relationships": [], + } + with caplog.at_level("WARNING"): + xml = RDFSerializer().serialize_to_rdfxml( + data, metadata_terms={"reviewed_by": unnameable} + ) + _parse(xml, "xml") # must still be well-formed + messages = " ".join(r.getMessage() for r in caplog.records) + assert unnameable in messages + assert "RDF/XML" in messages + + +@pytest.mark.parametrize("fmt", ["turtle", "ntriples", "jsonld"]) +def test_the_other_formats_still_carry_a_term_rdfxml_cannot_name(fmt): + """Only RDF/XML has the QName restriction; the rest write the full IRI.""" + unnameable = "http://example.org/ns/123" + data = { + "entities": [ + {"id": ENTITY_IRI, "text": "Acme", "metadata": {"reviewed_by": "fabio"}} + ], + "relationships": [], + } + g = _serialize( + RDFSerializer(), fmt, data, metadata_terms={"reviewed_by": unnameable} + ) + assert (URIRef(ENTITY_IRI), URIRef(unnameable), Literal("fabio")) in g + + +def test_a_quote_in_an_attribute_value_cannot_break_the_document(): + """`_escape_xml` feeds attribute values, which are delimited by quotes. + + Escaping only &, < and > leaves a caller-supplied value able to close the + attribute early and produce XML that does not parse. + """ + data = { + "entities": [ + { + "id": 'https://example.org/e"1', + "text": "Acme", + "metadata": {"uri": 'https://example.org/db"x'}, + } + ], + "relationships": [], + } + xml = RDFSerializer().serialize_to_rdfxml(data) + from xml.dom.minidom import parseString + + parseString(xml) # well-formedness is the assertion From e41993a6bd7b10389991dd222ff260e969033d13 Mon Sep 17 00:00:00 2001 From: Freakz2z Date: Sun, 23 Aug 2026 23:02:15 +0800 Subject: [PATCH 04/11] fix(triplet_store): honor RDF4J repository id --- docs/storage-backends.md | 4 ++-- semantica/triplet_store/rdf4j_store.py | 2 +- tests/triplet_store/test_rdf4j_store.py | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/storage-backends.md b/docs/storage-backends.md index bb05e115..dc211ef0 100644 --- a/docs/storage-backends.md +++ b/docs/storage-backends.md @@ -35,7 +35,7 @@ This page is intentionally conservative: it distinguishes between an adapter exi | FalkorDB | LPG | Yes | Yes | Partial | Partial | Redis-based; provenance depends on node/edge properties, and multi-graph isolation depends on the selected graph name. | | Amazon Neptune | LPG | Yes | Yes | Partial | Partial | Use the property-graph endpoint; AWS auth, VPC, and endpoint configuration can affect local tests. Provenance depends on node/edge properties. | | Apache AGE | LPG | Yes | Yes | Partial | Partial | Runs through PostgreSQL/AGE; Cypher compatibility and property handling can differ from standalone LPG engines. | -| RDF4J | RDF | Yes | Partial | Partial | Partial | Context separation relies on named graphs; triple-level provenance may require reification or graph-level metadata. `RDF4JStore(repository_id=...)` currently has no effect — the constructor always connects to the `"default"` repository regardless of the value passed; track a fix separately. | +| RDF4J | RDF | Yes | Partial | Partial | Partial | Context separation relies on named graphs; triple-level provenance may require reification or graph-level metadata. | | Apache Jena | RDF | Yes | Partial | Partial | Partial | Named graphs are needed for context separation; backend configuration and transaction behavior matter. | | Blazegraph | RDF | Yes | Partial | Partial | Partial | Use quads/named graphs for context; IRI stability and graph naming matter for provenance. | | Anzo | RDF | Yes | Partial | Partial | Partial | Anzo deployments are environment-specific; validate `dataset_uri`/graphmart naming, named-graph support, and provenance mapping. | @@ -107,7 +107,7 @@ from semantica.triplet_store import RDF4JStore store = RDF4JStore( endpoint='http://localhost:8080/rdf4j-server', - repository_id='semantica' # currently has no effect; connects to "default" (see Known limitations) + repository_id='semantica' ) ``` diff --git a/semantica/triplet_store/rdf4j_store.py b/semantica/triplet_store/rdf4j_store.py index 8dad6997..c03b64a8 100644 --- a/semantica/triplet_store/rdf4j_store.py +++ b/semantica/triplet_store/rdf4j_store.py @@ -67,7 +67,7 @@ class RDF4JStore: self.progress_tracker.enabled = True self.endpoint = endpoint.rstrip("/") - self.repository_id = config.get("repository_id", "default") + self.repository_id = repository_id or config.get("repository_id", "default") self.username = config.get("username") self.password = config.get("password") self.timeout = config.get("timeout", 30) diff --git a/tests/triplet_store/test_rdf4j_store.py b/tests/triplet_store/test_rdf4j_store.py index 4ef4f16b..3f630b93 100644 --- a/tests/triplet_store/test_rdf4j_store.py +++ b/tests/triplet_store/test_rdf4j_store.py @@ -22,6 +22,27 @@ def _make_connected_store(): CONSTRUCT_QUERY = "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }" +class TestRDF4JStoreInitialization(unittest.TestCase): + def test_explicit_repository_id_selects_repository(self): + response = MagicMock(status_code=200) + + with patch( + "semantica.triplet_store.rdf4j_store.requests.get", + return_value=response, + ) as mock_get: + store = RDF4JStore( + endpoint="http://localhost:8080/rdf4j-server/", + repository_id="semantica", + ) + + self.assertEqual(store.repository_id, "semantica") + mock_get.assert_called_once_with( + "http://localhost:8080/rdf4j-server/repositories/semantica", + timeout=30, + auth=None, + ) + + class TestRDF4JStoreIsConstructQuery(unittest.TestCase): def test_detects_uppercase(self): self.assertTrue(_make_connected_store()._is_construct_query( From de31b43663972037dde2d6aacc9c4a0aa3dd2585 Mon Sep 17 00:00:00 2001 From: Aldrin Joseph Date: Sun, 23 Aug 2026 14:11:35 +0530 Subject: [PATCH 05/11] fix(utils): write console progress only to an interactive stdout ProgressTracker attached ConsoleProgressDisplay unconditionally, so any script or CI job that piped or redirected stdout had one progress bar per stage written into its output, escape sequences included. A plain `python demo.py > out.txt` captured 173 bytes of progress-bar noise around 10 bytes of the program's own output. Console progress is now attached only when stdout is an interactive terminal, when running under Jupyter, or when SEMANTICA_FORCE_PROGRESS is set. FileProgressDisplay is untouched, so progress logging still works in pipelines, and SEMANTICA_DISABLE_PROGRESS keeps its existing meaning and still takes precedence. Both progress environment variables are now documented in the README and the utils reference; SEMANTICA_DISABLE_PROGRESS previously existed only in the reference page. Deviations from the issue: the issue suggested disabling the tracker on non-TTY stdout. This gates the display instead, because disabling the tracker would short-circuit before FileProgressDisplay and take file progress logging down with it, and the ~20 modules that set `progress_tracker.enabled = True` in __init__ would need the property setter taught about TTY state to avoid undoing it. Gating the display leaves both alone. Design note: the claim comment on the issue proposed an `enabled: Optional[bool] = None` constructor opt-in; during implementation the opt-in became SEMANTICA_FORCE_PROGRESS, which needs no signature change and follows the NO_COLOR/FORCE_COLOR convention. Known limitation: TTY detection runs once at tracker construction (the tracker is a process-wide singleton), so a process that redirects stdout after first use needs the env vars to change behaviour. Fixes #1185 --- README.md | 2 + docs/reference/utils.md | 10 +++ semantica/utils/progress_tracker.py | 39 +++++++-- tests/test_progress_tracker_regressions.py | 96 ++++++++++++++++++++++ 4 files changed, 142 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a2646bde..64d2cd7e 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,8 @@ semantica doctor # Config file pass ~/.semantica/config.yaml ``` +**Running in a script or CI?** Progress bars are written only when stdout is an interactive terminal (or a Jupyter notebook), so piping and redirecting stay clean by default. Override with `SEMANTICA_DISABLE_PROGRESS=1` to silence progress everywhere, or `SEMANTICA_FORCE_PROGRESS=1` to keep it when stdout is redirected. `SEMANTICA_DISABLE_PROGRESS` takes precedence. +
If Semantica solves a real problem for you, a star helps others find it. diff --git a/docs/reference/utils.md b/docs/reference/utils.md index 83f9cc97..f7e97789 100644 --- a/docs/reference/utils.md +++ b/docs/reference/utils.md @@ -77,7 +77,17 @@ Most users won't call utils directly: it's the **shared foundation** for all mod export SEMANTICA_LOG_LEVEL=DEBUG export SEMANTICA_LOG_FORMAT=json # "json" | "text" export SEMANTICA_DISABLE_PROGRESS=true + export SEMANTICA_FORCE_PROGRESS=true ``` + + + **Progress bars follow your terminal.** Console progress is written only when + stdout is an interactive terminal (or a Jupyter notebook), so piping or + redirecting output no longer fills logs with progress bars and escape + sequences. Set `SEMANTICA_DISABLE_PROGRESS` to silence progress even in a + terminal, or `SEMANTICA_FORCE_PROGRESS` to keep it when stdout is redirected. + `SEMANTICA_DISABLE_PROGRESS` wins if both are set. + diff --git a/semantica/utils/progress_tracker.py b/semantica/utils/progress_tracker.py index febfb4b9..f27768d4 100644 --- a/semantica/utils/progress_tracker.py +++ b/semantica/utils/progress_tracker.py @@ -64,6 +64,29 @@ def _progress_disabled_from_env() -> bool: "on", ) + +def _progress_forced_from_env() -> bool: + """Return whether console progress is forced on despite a non-interactive stdout.""" + return os.getenv("SEMANTICA_FORCE_PROGRESS", "").strip().lower() in ( + "1", + "true", + "yes", + "on", + ) + + +def _stdout_is_tty() -> bool: + """Return whether stdout is an interactive terminal. + + Replacement streams do not always implement ``isatty`` and closed streams can + raise, so both cases are treated as non-interactive. + """ + try: + return bool(sys.stdout is not None and sys.stdout.isatty()) + except (AttributeError, ValueError): + return False + + # Try to import IPython for Jupyter support try: from IPython import get_ipython @@ -1040,18 +1063,24 @@ class ProgressTracker: # Create displays self.displays: List[ProgressDisplay] = [] + # Console output only suits an interactive stdout. When output is piped or + # redirected (scripts, CI logs) the progress bars and their escape + # sequences would otherwise drown the program's own output. + console_ok = _stdout_is_tty() or self.is_jupyter or _progress_forced_from_env() + # Always try Jupyter first if available, fallback to console if IPYTHON_AVAILABLE: # Try to detect Jupyter - if available, use it if self.is_jupyter and not self.disable_jupyter_progress: self.displays.append(JupyterProgressDisplay(use_emoji=use_emoji)) # Also add console as fallback for immediate feedback - self.displays.append( - ConsoleProgressDisplay( - use_emoji=use_emoji, update_interval=update_interval + if console_ok: + self.displays.append( + ConsoleProgressDisplay( + use_emoji=use_emoji, update_interval=update_interval + ) ) - ) - else: + elif console_ok: self.displays.append( ConsoleProgressDisplay( use_emoji=use_emoji, update_interval=update_interval diff --git a/tests/test_progress_tracker_regressions.py b/tests/test_progress_tracker_regressions.py index ac4c09da..b42a885b 100644 --- a/tests/test_progress_tracker_regressions.py +++ b/tests/test_progress_tracker_regressions.py @@ -12,6 +12,7 @@ import semantica.utils.progress_tracker as progress_module @pytest.fixture(autouse=True) def reset_progress_singletons(monkeypatch): monkeypatch.delenv("SEMANTICA_DISABLE_PROGRESS", raising=False) + monkeypatch.delenv("SEMANTICA_FORCE_PROGRESS", raising=False) progress_module.ProgressTracker._instance = None progress_module._global_tracker = None yield @@ -19,6 +20,40 @@ def reset_progress_singletons(monkeypatch): progress_module._global_tracker = None +class _FakeStdout: + """Minimal stdout stand-in with controllable TTY reporting.""" + + encoding = "utf-8" + + def __init__(self, tty): + self._tty = tty + self.written = [] + + def isatty(self): + return self._tty + + def write(self, text): + self.written.append(text) + return len(text) + + def flush(self): + pass + + +def _use_stdout(monkeypatch, tty): + """Point sys.stdout at a fake with the given TTY behaviour, outside Jupyter.""" + stream = _FakeStdout(tty=tty) + monkeypatch.setattr(sys, "stdout", stream) + monkeypatch.setattr( + progress_module.ProgressTracker, "_detect_jupyter", lambda *_: False + ) + return stream + + +def _displays_of(tracker, display_cls): + return [d for d in tracker.displays if isinstance(d, display_cls)] + + def _install_tracker_as_singleton(tracker: progress_module.ProgressTracker) -> None: progress_module.ProgressTracker._instance = tracker progress_module._global_tracker = tracker @@ -100,6 +135,67 @@ def test_disable_progress_env_prevents_reenable(monkeypatch): assert tracker.start_tracking(module="core", submodule="test") == "" +def test_console_display_omitted_when_stdout_is_not_a_tty(monkeypatch): + _use_stdout(monkeypatch, tty=False) + + tracker = progress_module.ProgressTracker(use_emoji=False, update_interval=0) + + assert _displays_of(tracker, progress_module.ConsoleProgressDisplay) == [] + + +def test_console_display_present_when_stdout_is_a_tty(monkeypatch): + _use_stdout(monkeypatch, tty=True) + + tracker = progress_module.ProgressTracker(use_emoji=False, update_interval=0) + + assert _displays_of(tracker, progress_module.ConsoleProgressDisplay) + + +def test_file_display_survives_non_tty_stdout(monkeypatch): + _use_stdout(monkeypatch, tty=False) + + tracker = progress_module.ProgressTracker(use_emoji=False, update_interval=0) + + assert _displays_of(tracker, progress_module.FileProgressDisplay) + + +def test_force_progress_env_restores_console_display_on_non_tty(monkeypatch): + monkeypatch.setenv("SEMANTICA_FORCE_PROGRESS", "1") + _use_stdout(monkeypatch, tty=False) + + tracker = progress_module.ProgressTracker(use_emoji=False, update_interval=0) + + assert _displays_of(tracker, progress_module.ConsoleProgressDisplay) + + +def test_disable_progress_env_beats_force_progress_env(monkeypatch): + monkeypatch.setenv("SEMANTICA_DISABLE_PROGRESS", "1") + monkeypatch.setenv("SEMANTICA_FORCE_PROGRESS", "1") + stream = _use_stdout(monkeypatch, tty=False) + + tracker = progress_module.ProgressTracker(use_emoji=False, update_interval=0) + _install_tracker_as_singleton(tracker) + + assert tracker.enabled is False + assert tracker.start_tracking(module="core", submodule="test") == "" + assert stream.written == [] + + +def test_non_tty_stdout_stays_silent_after_module_reenables_tracker(monkeypatch): + stream = _use_stdout(monkeypatch, tty=False) + tracker = progress_module.ProgressTracker(use_emoji=False, update_interval=0) + _install_tracker_as_singleton(tracker) + + # Mirrors the ~20 modules that do `self.progress_tracker.enabled = True`. + tracker.enabled = True + tracking_id = tracker.start_tracking( + module="core", submodule="Semantica", message="Building" + ) + tracker.update_progress(tracking_id, processed=1, total=1, message="Processing") + + assert stream.written == [] + + def test_build_knowledge_base_subprocess_does_not_deadlock(): root = Path(__file__).resolve().parents[1] runtime_dir = root / "test_data" / "runtime" / f"build-regression-{os.getpid()}" From 4c997b501799f2c71e7040187a5c87e972e1fd4e Mon Sep 17 00:00:00 2001 From: Freakz2z Date: Mon, 24 Aug 2026 09:01:22 +0800 Subject: [PATCH 06/11] fix(triplet_store): encode RDF4J repository paths --- docs/reference/triplet_store.md | 2 +- semantica/triplet_store/rdf4j_store.py | 11 ++++--- tests/triplet_store/test_rdf4j_store.py | 44 +++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/docs/reference/triplet_store.md b/docs/reference/triplet_store.md index ad7a0645..ee5c24e6 100644 --- a/docs/reference/triplet_store.md +++ b/docs/reference/triplet_store.md @@ -182,7 +182,7 @@ for row in result.bindings: store = TripletStore( backend="rdf4j", endpoint="http://localhost:8080/rdf4j-server", - repository_id="semantica", # passed through **config + repository_id="semantica", # selects the remote repository ) ``` diff --git a/semantica/triplet_store/rdf4j_store.py b/semantica/triplet_store/rdf4j_store.py index c03b64a8..d788ab7b 100644 --- a/semantica/triplet_store/rdf4j_store.py +++ b/semantica/triplet_store/rdf4j_store.py @@ -28,7 +28,7 @@ License: MIT import re from typing import Any, Dict, List, Optional -from urllib.parse import urlparse +from urllib.parse import quote, urlparse import requests from rdflib import Graph, Literal @@ -68,6 +68,7 @@ class RDF4JStore: self.endpoint = endpoint.rstrip("/") self.repository_id = repository_id or config.get("repository_id", "default") + self._encoded_repository_id = quote(self.repository_id, safe="") self.username = config.get("username") self.password = config.get("password") self.timeout = config.get("timeout", 30) @@ -79,7 +80,7 @@ class RDF4JStore: """Connect to RDF4J server.""" try: # Test connection - test_url = f"{self.endpoint}/repositories/{self.repository_id}" + test_url = f"{self.endpoint}/repositories/{self._encoded_repository_id}" response = requests.get( test_url, timeout=self.timeout, @@ -100,11 +101,11 @@ class RDF4JStore: def _get_sparql_endpoint(self) -> str: """Get SPARQL query endpoint.""" - return f"{self.endpoint}/repositories/{self.repository_id}" + return f"{self.endpoint}/repositories/{self._encoded_repository_id}" def _get_update_endpoint(self) -> str: """Get SPARQL Update endpoint.""" - return f"{self.endpoint}/repositories/{self.repository_id}/statements" + return f"{self.endpoint}/repositories/{self._encoded_repository_id}/statements" def _is_construct_query(self, query: str) -> bool: """ @@ -163,7 +164,7 @@ class RDF4JStore: """ # RDF4J transaction support transaction_url = ( - f"{self.endpoint}/repositories/{self.repository_id}/transactions" + f"{self.endpoint}/repositories/{self._encoded_repository_id}/transactions" ) try: diff --git a/tests/triplet_store/test_rdf4j_store.py b/tests/triplet_store/test_rdf4j_store.py index 3f630b93..03a9b90b 100644 --- a/tests/triplet_store/test_rdf4j_store.py +++ b/tests/triplet_store/test_rdf4j_store.py @@ -23,6 +23,7 @@ CONSTRUCT_QUERY = "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }" class TestRDF4JStoreInitialization(unittest.TestCase): + def test_explicit_repository_id_selects_repository(self): response = MagicMock(status_code=200) @@ -42,6 +43,49 @@ class TestRDF4JStoreInitialization(unittest.TestCase): auth=None, ) + def test_repository_id_is_encoded_as_a_single_url_path_segment(self): + response = MagicMock(status_code=200) + + with patch( + "semantica.triplet_store.rdf4j_store.requests.get", + return_value=response, + ) as mock_get: + store = RDF4JStore( + endpoint="http://localhost:8080/rdf4j-server", + repository_id="team/repo ?#", + ) + + self.assertEqual(store.repository_id, "team/repo ?#") + mock_get.assert_called_once_with( + "http://localhost:8080/rdf4j-server/repositories/team%2Frepo%20%3F%23", + timeout=30, + auth=None, + ) + self.assertEqual( + store._get_sparql_endpoint(), + "http://localhost:8080/rdf4j-server/repositories/team%2Frepo%20%3F%23", + ) + self.assertEqual( + store._get_update_endpoint(), + "http://localhost:8080/rdf4j-server/repositories/" + "team%2Frepo%20%3F%23/statements", + ) + + transaction_response = MagicMock() + transaction_response.headers = {"Location": "/transactions/tx-1"} + with patch( + "semantica.triplet_store.rdf4j_store.requests.post", + return_value=transaction_response, + ) as mock_post: + self.assertEqual(store.begin_transaction(), "tx-1") + + mock_post.assert_called_once_with( + "http://localhost:8080/rdf4j-server/repositories/" + "team%2Frepo%20%3F%23/transactions", + timeout=30, + auth=None, + ) + class TestRDF4JStoreIsConstructQuery(unittest.TestCase): def test_detects_uppercase(self): From 595f08ee303885e076c6d5e008d8a3d51a35a02a Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 24 Aug 2026 12:49:31 +0530 Subject: [PATCH 07/11] docs: escape & as & in Star History HTML attributes Matches the README's existing convention for query params inside HTML attribute URLs (e.g. the Trendshift badge), per review feedback from Zohaib Hassan and Qodo on this PR. Co-authored-by: OctoBored <212877535+OctoBored@users.noreply.github.com> --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e729b2d..dea7a87d 100644 --- a/README.md +++ b/README.md @@ -1566,11 +1566,11 @@ On-premises deployment · Private cloud · Custom domain implementations · SLA- ## Star History - + - - - Star History Chart + + + Star History Chart From 220fb10e5c160761587b8398a80713d285420a8a Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 24 Aug 2026 13:38:07 +0530 Subject: [PATCH 08/11] fix(export): escape IRI-valued metadata to close a Turtle/N-Triples injection gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _turtle_object() wrote an IRI-valued metadata value (currently only sem:sourceUri, from the "uri" metadata key) straight into `<{value}>` with no escaping. Turtle/N-Triples IRIREFs exclude control characters, space, and <>"{}|^`\ unescaped, so a value shaped like ` .

` closed the reference early and let the rest of the string be parsed as an attacker-chosen extra triple: metadata={"uri": "https://x> . ` delimiter-breaking payload from the report, and a control-character (newline/tab) variant covering the other half of the excluded set. --- semantica/export/rdf_exporter.py | 20 +++++++++- tests/export/test_metadata_passthrough.py | 48 +++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/semantica/export/rdf_exporter.py b/semantica/export/rdf_exporter.py index bcd89140..dffd4951 100644 --- a/semantica/export/rdf_exporter.py +++ b/semantica/export/rdf_exporter.py @@ -193,6 +193,24 @@ def _escape_literal(value: str) -> str: ) +#: Turtle/N-Triples IRIREF grammar excludes these unescaped between `<` and +#: `>`: control characters, space, and <>"{}|^`\. An IRI-valued metadata +#: value (currently only sem:sourceUri, from the caller-controlled "uri" +#: metadata key) is written as `<{value}>` with no other quoting, so a value +#: containing one of these characters — a ">" followed by a full triple, for +#: instance — closes the IRIREF early and lets the rest of the string be +#: parsed as further RDF statements. This is the same shape of defect the +#: entity/relationship IRIs were hardened against; that hardening resolves +#: prefixes as well, which a metadata value never needs, so this stays a +#: narrower, dedicated guard rather than reusing _as_turtle_iri. +_IRI_REF_UNSAFE_RE = re.compile(r'[\x00-\x20<>"{}|^`\\]') + + +def _safe_iri_ref(value: str) -> str: + """Percent-encode the characters an IRIREF may not contain unescaped.""" + return _IRI_REF_UNSAFE_RE.sub(lambda m: quote(m.group(0), safe=""), value) + + def _escape_xml(value: str) -> str: """Escape a string for either XML element text or an attribute value. @@ -328,7 +346,7 @@ def _typed_literal_parts(term: str, value: Any) -> tuple: def _turtle_object(term: str, value: Any) -> str: kind, lexical, datatype = _typed_literal_parts(term, value) if kind == "iri": - return f"<{lexical}>" + return f"<{_safe_iri_ref(lexical)}>" if datatype is None: return f'"{_escape_literal(lexical)}"' return f'"{lexical}"^^<{datatype}>' diff --git a/tests/export/test_metadata_passthrough.py b/tests/export/test_metadata_passthrough.py index cdb73227..ff4c3ddc 100644 --- a/tests/export/test_metadata_passthrough.py +++ b/tests/export/test_metadata_passthrough.py @@ -351,3 +351,51 @@ def test_a_quote_in_an_attribute_value_cannot_break_the_document(): from xml.dom.minidom import parseString parseString(xml) # well-formedness is the assertion + + +# --- Finding from review of PR #1165 ---------------------------------------- + + +@pytest.mark.parametrize("fmt", ["turtle", "ntriples"]) +def test_an_iri_valued_metadata_value_cannot_inject_a_second_triple(fmt): + """`sem:sourceUri` (the "uri" key) is the one metadata term written as a + node, ``<{value}>``, with no other quoting. Turtle/N-Triples IRIREFs + exclude '>' (among other characters) unescaped, so a value shaped like + `` .

`` closed the reference early and let the + rest of the string be parsed as an unrelated, attacker-chosen triple. + """ + payload = ( + "https://evil.example/x> . " + " ' — cover the control-character half of + the grammar, not only the delimiter characters. + """ + payload = "https://evil.example/x\ninjected line\ttabbed" + data = { + "entities": [ + {"id": ENTITY_IRI, "text": "Acme", "metadata": {"uri": payload}} + ], + "relationships": [], + } + g = _serialize(RDFSerializer(), fmt, data) + assert len(g) == 4 From 7a6f1d041751beb4bb97378982343db2918b8635 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 24 Aug 2026 16:07:22 +0530 Subject: [PATCH 09/11] docs: add citation section and fix stale org references Add a Cite Us section to the README with BibTeX citation info, and align it with docs/citation.md (author/organization: Semantica, 2026). Update LICENSE and docs/project-license.md copyright holder to Semantica, and replace the stale Hawksight-AI GitHub org slug with semantica-agi across READMEs, plugin manifests, cookbook notebooks, and GitHub templates. --- .github/DISCUSSION_TEMPLATE/ideas.md | 2 +- .github/DISCUSSION_TEMPLATE/qa.md | 4 ++-- .github/FUNDING.yml | 2 +- .github/ISSUE_TEMPLATE/config.yml | 4 ++-- .github/SUPPORT.md | 18 +++++++++--------- CHANGELOG.md | 2 +- CODE_OF_CONDUCT.md | 2 +- CONTRIBUTORS.md | 6 +++--- LICENSE | 2 +- README.md | 17 +++++++++++++++++ .../advanced/01_Advanced_Extraction.ipynb | 2 +- .../03_Complete_Visualization_Suite.ipynb | 2 +- .../advanced/05_Multi_Format_Export.ipynb | 2 +- .../advanced/08_Reasoning_and_Inference.ipynb | 2 +- .../09_Semantic_Layer_Construction.ipynb | 2 +- .../10_Temporal_Knowledge_Graphs.ipynb | 2 +- .../12_Unstructured_to_Ontology.ipynb | 2 +- ...13_Manual_Ontology_Snowflake_Mapping.ipynb | 2 +- .../advanced/14_Datalog_Style_Reasoning.ipynb | 2 +- .../Advanced_Vector_Store_and_Search.ipynb | 4 ++-- .../01_Welcome_to_Semantica.ipynb | 2 +- cookbook/introduction/02_Data_Ingestion.ipynb | 2 +- .../introduction/03_Document_Parsing.ipynb | 2 +- .../introduction/04_Data_Normalization.ipynb | 2 +- .../introduction/05_Entity_Extraction.ipynb | 4 ++-- .../introduction/06_Relation_Extraction.ipynb | 4 ++-- .../07_Building_Knowledge_Graphs.ipynb | 2 +- .../08_Your_First_Knowledge_Graph.ipynb | 2 +- .../introduction/10_Graph_Analytics.ipynb | 2 +- .../11_Chunking_and_Splitting.ipynb | 4 ++-- .../12_Embedding_Generation.ipynb | 2 +- cookbook/introduction/13_Vector_Store.ipynb | 4 ++-- cookbook/introduction/14_Ontology.ipynb | 2 +- cookbook/introduction/15_Export.ipynb | 2 +- cookbook/introduction/16_Visualization.ipynb | 2 +- cookbook/introduction/18_Deduplication.ipynb | 2 +- cookbook/introduction/19_Context_Module.ipynb | 2 +- docs/citation.md | 19 +++++++++---------- docs/cookbook.md | 2 +- docs/governance.md | 4 ++-- docs/project-license.md | 2 +- plugins/.claude-plugin/README.md | 2 +- plugins/.claude-plugin/marketplace.json | 4 ++-- plugins/.claude-plugin/plugin.json | 4 ++-- plugins/.cline-plugin/plugin.json | 4 ++-- plugins/.codex-plugin/plugin.json | 4 ++-- plugins/.continue-plugin/plugin.json | 4 ++-- plugins/.cursor-plugin/plugin.json | 4 ++-- plugins/.openclaw-plugin/plugin.json | 4 ++-- plugins/.vscode-plugin/plugin.json | 4 ++-- plugins/.windsurf-plugin/plugin.json | 4 ++-- .../change_management_usage.md | 2 +- tests/ingest/test_notebook_02.py | 2 +- 53 files changed, 104 insertions(+), 88 deletions(-) diff --git a/.github/DISCUSSION_TEMPLATE/ideas.md b/.github/DISCUSSION_TEMPLATE/ideas.md index 6d28cd2a..c6e4e3fa 100644 --- a/.github/DISCUSSION_TEMPLATE/ideas.md +++ b/.github/DISCUSSION_TEMPLATE/ideas.md @@ -69,5 +69,5 @@ If you have ideas on how this could be implemented, please share. --- -**Note**: For feature requests that are ready to be implemented, consider creating a [Feature Request issue](https://github.com/Hawksight-AI/semantica/issues/new?template=feature_request.md) instead. +**Note**: For feature requests that are ready to be implemented, consider creating a [Feature Request issue](https://github.com/semantica-agi/semantica/issues/new?template=feature_request.md) instead. diff --git a/.github/DISCUSSION_TEMPLATE/qa.md b/.github/DISCUSSION_TEMPLATE/qa.md index fc019e53..76fc1959 100644 --- a/.github/DISCUSSION_TEMPLATE/qa.md +++ b/.github/DISCUSSION_TEMPLATE/qa.md @@ -46,8 +46,8 @@ If applicable, paste any error messages or describe unexpected behavior: ## Checklist -- [ ] I have searched existing [discussions](https://github.com/Hawksight-AI/semantica/discussions) and [issues](https://github.com/Hawksight-AI/semantica/issues) -- [ ] I have checked the [documentation](https://github.com/Hawksight-AI/semantica/tree/main/docs) and [FAQ](https://github.com/Hawksight-AI/semantica/blob/main/docs/faq.md) +- [ ] I have searched existing [discussions](https://github.com/semantica-agi/semantica/discussions) and [issues](https://github.com/semantica-agi/semantica/issues) +- [ ] I have checked the [documentation](https://github.com/semantica-agi/semantica/tree/main/docs) and [FAQ](https://github.com/semantica-agi/semantica/blob/main/docs/faq.md) - [ ] I have provided a minimal code example (if applicable) - [ ] I have included error messages (if applicable) - [ ] I have provided environment details diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 5699f569..35fd4d5c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,3 @@ # Funding options for Semantica -github: Hawksight-AI +github: semantica-agi diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2b702ee7..3ff95742 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: true contact_links: - name: 📚 Documentation - url: https://github.com/Hawksight-AI/semantica/tree/main/docs + url: https://github.com/semantica-agi/semantica/tree/main/docs about: Browse the documentation - name: 💬 Discussions - url: https://github.com/Hawksight-AI/semantica/discussions + url: https://github.com/semantica-agi/semantica/discussions about: Ask questions and discuss with the community diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index 148ceab1..4d484c62 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -3,31 +3,31 @@ ## Getting Help ### 📚 Documentation -Check the [docs folder](https://github.com/Hawksight-AI/semantica/tree/main/docs) and [README](https://github.com/Hawksight-AI/semantica/blob/main/README.md) for guides and examples. +Check the [docs folder](https://github.com/semantica-agi/semantica/tree/main/docs) and [README](https://github.com/semantica-agi/semantica/blob/main/README.md) for guides and examples. ### 💬 Community Support -- **GitHub Discussions**: [Ask questions](https://github.com/Hawksight-AI/semantica/discussions) +- **GitHub Discussions**: [Ask questions](https://github.com/semantica-agi/semantica/discussions) - **Discord**: Join our [Discord server](https://discord.gg/sV34vps5hH) for real-time chat ### 💭 Discussions -Join the conversation on [GitHub Discussions](https://github.com/Hawksight-AI/semantica/discussions): +Join the conversation on [GitHub Discussions](https://github.com/semantica-agi/semantica/discussions): - **Q&A**: Ask questions and get help from the community - **Ideas**: Share feature requests and suggestions - **Show and Tell**: Showcase your projects and use cases - **General**: General discussions about Semantica ### 🐛 Bug Reports -Found a bug? [Create an issue](https://github.com/Hawksight-AI/semantica/issues/new/choose) +Found a bug? [Create an issue](https://github.com/semantica-agi/semantica/issues/new/choose) ### 📖 Resources -- [Quick Start Guide](https://github.com/Hawksight-AI/semantica/blob/main/docs/quickstart.md) -- [FAQ](https://github.com/Hawksight-AI/semantica/blob/main/docs/faq.md) -- [Cookbook Examples](https://github.com/Hawksight-AI/semantica/tree/main/cookbook) +- [Quick Start Guide](https://github.com/semantica-agi/semantica/blob/main/docs/quickstart.md) +- [FAQ](https://github.com/semantica-agi/semantica/blob/main/docs/faq.md) +- [Cookbook Examples](https://github.com/semantica-agi/semantica/tree/main/cookbook) ## Commercial Support For enterprise support, custom development, or consulting services: -- Contact us through [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues) +- Contact us through [GitHub Issues](https://github.com/semantica-agi/semantica/issues) - Include "Commercial Support" in the title ## Sponsorship @@ -35,7 +35,7 @@ For enterprise support, custom development, or consulting services: ### Sponsor this project Support Semantica development: -- [GitHub Sponsors](https://github.com/sponsors/Hawksight-AI) +- [GitHub Sponsors](https://github.com/sponsors/semantica-agi) Your sponsorship helps us: - Maintain and improve the framework diff --git a/CHANGELOG.md b/CHANGELOG.md index 5057334c..5c1ec4d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1531,4 +1531,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- -For detailed release notes, see [GitHub Releases](https://github.com/Hawksight-AI/semantica/releases). +For detailed release notes, see [GitHub Releases](https://github.com/semantica-agi/semantica/releases). diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0197824a..7b3304eb 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -58,7 +58,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement through -[GitHub Issues](https://github.com/Hawksight-AI/semantica/issues) with "[CoC]" prefix. +[GitHub Issues](https://github.com/semantica-agi/semantica/issues) with "[CoC]" prefix. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 336ece42..b5ec8b37 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -44,7 +44,7 @@ We recognize all types of contributions: All contributors are recognized in: - This contributors list -- [GitHub contributors page](https://github.com/Hawksight-AI/semantica/graphs/contributors) +- [GitHub contributors page](https://github.com/semantica-agi/semantica/graphs/contributors) - Release notes for significant contributions - Community appreciation @@ -54,7 +54,7 @@ All contributors are recognized in: ### Automatic Recognition -If you've made a commit, you'll automatically appear in [GitHub's contributors graph](https://github.com/Hawksight-AI/semantica/graphs/contributors). +If you've made a commit, you'll automatically appear in [GitHub's contributors graph](https://github.com/semantica-agi/semantica/graphs/contributors). ### Using All-Contributors Bot @@ -111,4 +111,4 @@ Every contribution, no matter how small, helps make Semantica better. Thank you **Want to contribute?** -⭐ Give us a Star • 🍴 [Fork us](https://github.com/Hawksight-AI/semantica/fork) • Check out our [Contributing Guide](CONTRIBUTING.md) to get started! +⭐ Give us a Star • 🍴 [Fork us](https://github.com/semantica-agi/semantica/fork) • Check out our [Contributing Guide](CONTRIBUTING.md) to get started! diff --git a/LICENSE b/LICENSE index d0dbcb9a..c66f5086 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Hawksight AI +Copyright (c) 2026 Semantica Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a2646bde..1a0cca5b 100644 --- a/README.md +++ b/README.md @@ -1594,6 +1594,23 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for full guidelines. --- +## Cite Us + +If you use Semantica in your research or production systems, please cite it as: + +```bibtex +@software{semantica2026, + title = {Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems}, + author = {Semantica}, + year = {2026}, + url = {https://github.com/semantica-agi/semantica} +} +``` + +All citation formats (APA, MLA, Chicago, IEEE) live on the [Citation](https://docs.getsemantica.ai/citation) page — every format attributes authorship to **Semantica**, not individual contributors. + +--- +

MIT License · Built by [Semantica](https://github.com/semantica-agi) diff --git a/cookbook/advanced/01_Advanced_Extraction.ipynb b/cookbook/advanced/01_Advanced_Extraction.ipynb index e4989da6..49113115 100644 --- a/cookbook/advanced/01_Advanced_Extraction.ipynb +++ b/cookbook/advanced/01_Advanced_Extraction.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/01_Advanced_Extraction.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/01_Advanced_Extraction.ipynb)\n", "\n", "# Advanced Extraction\n", "\n", diff --git a/cookbook/advanced/03_Complete_Visualization_Suite.ipynb b/cookbook/advanced/03_Complete_Visualization_Suite.ipynb index d081b2df..655e723a 100644 --- a/cookbook/advanced/03_Complete_Visualization_Suite.ipynb +++ b/cookbook/advanced/03_Complete_Visualization_Suite.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/03_Complete_Visualization_Suite.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/03_Complete_Visualization_Suite.ipynb)\n", "\n", "# Complete Visualization Suite\n", "\n", diff --git a/cookbook/advanced/05_Multi_Format_Export.ipynb b/cookbook/advanced/05_Multi_Format_Export.ipynb index 197ce788..306409c7 100644 --- a/cookbook/advanced/05_Multi_Format_Export.ipynb +++ b/cookbook/advanced/05_Multi_Format_Export.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/05_Multi_Format_Export.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/05_Multi_Format_Export.ipynb)\n", "\n", "# Advanced Multi-Format Export\n", "\n", diff --git a/cookbook/advanced/08_Reasoning_and_Inference.ipynb b/cookbook/advanced/08_Reasoning_and_Inference.ipynb index 2f86fe1c..5854f4bf 100644 --- a/cookbook/advanced/08_Reasoning_and_Inference.ipynb +++ b/cookbook/advanced/08_Reasoning_and_Inference.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/08_Reasoning_and_Inference.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/08_Reasoning_and_Inference.ipynb)\n", "\n", "# Reasoning and Inference\n", "\n", diff --git a/cookbook/advanced/09_Semantic_Layer_Construction.ipynb b/cookbook/advanced/09_Semantic_Layer_Construction.ipynb index d8c090c9..de1ecd78 100644 --- a/cookbook/advanced/09_Semantic_Layer_Construction.ipynb +++ b/cookbook/advanced/09_Semantic_Layer_Construction.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/09_Semantic_Layer_Construction.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/09_Semantic_Layer_Construction.ipynb)\n", "\n", "# Semantic Layer Construction\n", "\n", diff --git a/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb b/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb index 21597361..03843a60 100644 --- a/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb +++ b/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb)\n", "\n", "# Deep Dive: Temporal Knowledge Graphs\n", "\n", diff --git a/cookbook/advanced/12_Unstructured_to_Ontology.ipynb b/cookbook/advanced/12_Unstructured_to_Ontology.ipynb index f683c7e0..6eddba5b 100644 --- a/cookbook/advanced/12_Unstructured_to_Ontology.ipynb +++ b/cookbook/advanced/12_Unstructured_to_Ontology.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/12_Unstructured_to_Ontology.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/12_Unstructured_to_Ontology.ipynb)\n", "\n", "# Unstructured Text to Ontology\n", "\n", diff --git a/cookbook/advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb b/cookbook/advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb index 392cdc28..0647f3b9 100644 --- a/cookbook/advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb +++ b/cookbook/advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb @@ -18,7 +18,7 @@ "id": "cell-0", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb)\n", "\n", "# Manual Ontology + Snowflake Mapping\n", "\n", diff --git a/cookbook/advanced/14_Datalog_Style_Reasoning.ipynb b/cookbook/advanced/14_Datalog_Style_Reasoning.ipynb index 5382c03e..2898f9d6 100644 --- a/cookbook/advanced/14_Datalog_Style_Reasoning.ipynb +++ b/cookbook/advanced/14_Datalog_Style_Reasoning.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/14_Datalog_Style_Reasoning.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/14_Datalog_Style_Reasoning.ipynb)\n", "\n", "# Datalog-Style Reasoning\n", "\n", diff --git a/cookbook/advanced/Advanced_Vector_Store_and_Search.ipynb b/cookbook/advanced/Advanced_Vector_Store_and_Search.ipynb index 1823a434..175736fe 100644 --- a/cookbook/advanced/Advanced_Vector_Store_and_Search.ipynb +++ b/cookbook/advanced/Advanced_Vector_Store_and_Search.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/advanced/Advanced_Vector_Store_and_Search.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/Advanced_Vector_Store_and_Search.ipynb)\n", "\n", "# Advanced Vector Store - Made Easy\n", "\n", @@ -352,7 +352,7 @@ "- Build a multi-user application\n", "- Explore the [introduction notebook](../introduction/13_Vector_Store.ipynb) for more basics\n", "\n", - "**Need Help?** Check our [documentation](https://semantica.readthedocs.io) or ask on [GitHub](https://github.com/Hawksight-AI/semantica)." + "**Need Help?** Check our [documentation](https://semantica.readthedocs.io) or ask on [GitHub](https://github.com/semantica-agi/semantica)." ] } ], diff --git a/cookbook/introduction/01_Welcome_to_Semantica.ipynb b/cookbook/introduction/01_Welcome_to_Semantica.ipynb index 05417881..21677088 100644 --- a/cookbook/introduction/01_Welcome_to_Semantica.ipynb +++ b/cookbook/introduction/01_Welcome_to_Semantica.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)\n", "\n", "Semantica is a **semantic intelligence and knowledge engineering framework**. It helps you:\n", "\n", diff --git a/cookbook/introduction/02_Data_Ingestion.ipynb b/cookbook/introduction/02_Data_Ingestion.ipynb index f343a5a2..a8d9e5d6 100644 --- a/cookbook/introduction/02_Data_Ingestion.ipynb +++ b/cookbook/introduction/02_Data_Ingestion.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/02_Data_Ingestion.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/02_Data_Ingestion.ipynb)\n", "\n", "# Data Ingestion - Comprehensive Guide\n", "\n", diff --git a/cookbook/introduction/03_Document_Parsing.ipynb b/cookbook/introduction/03_Document_Parsing.ipynb index 639c076b..d7ed84c3 100644 --- a/cookbook/introduction/03_Document_Parsing.ipynb +++ b/cookbook/introduction/03_Document_Parsing.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/04_Document_Parsing.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/04_Document_Parsing.ipynb)\n", "\n", "# Document Parsing\n", "\n", diff --git a/cookbook/introduction/04_Data_Normalization.ipynb b/cookbook/introduction/04_Data_Normalization.ipynb index a4a668ee..f725bd9e 100644 --- a/cookbook/introduction/04_Data_Normalization.ipynb +++ b/cookbook/introduction/04_Data_Normalization.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/05_Data_Normalization.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/05_Data_Normalization.ipynb)\n", "\n", "# Data Normalization\n", "\n", diff --git a/cookbook/introduction/05_Entity_Extraction.ipynb b/cookbook/introduction/05_Entity_Extraction.ipynb index 4b78e19c..78cabe22 100644 --- a/cookbook/introduction/05_Entity_Extraction.ipynb +++ b/cookbook/introduction/05_Entity_Extraction.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/05_Entity_Extraction.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/05_Entity_Extraction.ipynb)\n", "\n", "# Entity Extraction - Comprehensive Guide\n", "\n", @@ -622,7 +622,7 @@ "\n", "---\n", "\n", - "**Questions or Issues?** Check out our [GitHub repository](https://github.com/Hawksight-AI/semantica) or [documentation](https://semantica.readthedocs.io)." + "**Questions or Issues?** Check out our [GitHub repository](https://github.com/semantica-agi/semantica) or [documentation](https://semantica.readthedocs.io)." ] } ], diff --git a/cookbook/introduction/06_Relation_Extraction.ipynb b/cookbook/introduction/06_Relation_Extraction.ipynb index e11566c6..8015f86f 100644 --- a/cookbook/introduction/06_Relation_Extraction.ipynb +++ b/cookbook/introduction/06_Relation_Extraction.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/06_Relation_Extraction.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/06_Relation_Extraction.ipynb)\n", "\n", "# Relation Extraction - Comprehensive Guide\n", "\n", @@ -599,7 +599,7 @@ "\n", "---\n", "\n", - "**Questions or Issues?** Check out our [GitHub repository](https://github.com/Hawksight-AI/semantica) or [documentation](https://semantica.readthedocs.io)." + "**Questions or Issues?** Check out our [GitHub repository](https://github.com/semantica-agi/semantica) or [documentation](https://semantica.readthedocs.io)." ] } ], diff --git a/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb b/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb index bd3c7ba2..4586c879 100644 --- a/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb +++ b/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Building_Knowledge_Graphs.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/08_Building_Knowledge_Graphs.ipynb)\n", "\n", "# Building Knowledge Graphs\n", "\n", diff --git a/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb b/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb index fb6e1c2f..7f65a910 100644 --- a/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb +++ b/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/09_Your_First_Knowledge_Graph.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/09_Your_First_Knowledge_Graph.ipynb)\n", "\n", "# 🚀 Your First Knowledge Graph\n", "\n", diff --git a/cookbook/introduction/10_Graph_Analytics.ipynb b/cookbook/introduction/10_Graph_Analytics.ipynb index f15ee443..327d4cea 100644 --- a/cookbook/introduction/10_Graph_Analytics.ipynb +++ b/cookbook/introduction/10_Graph_Analytics.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/11_Graph_Analytics.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/11_Graph_Analytics.ipynb)\n", "\n", "# Graph Analytics\n", "\n", diff --git a/cookbook/introduction/11_Chunking_and_Splitting.ipynb b/cookbook/introduction/11_Chunking_and_Splitting.ipynb index 9bb5cb3f..27101493 100644 --- a/cookbook/introduction/11_Chunking_and_Splitting.ipynb +++ b/cookbook/introduction/11_Chunking_and_Splitting.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/11_Chunking_and_Splitting.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/11_Chunking_and_Splitting.ipynb)\n", "\n", "# Chunking and Splitting - Comprehensive Guide\n", "\n", @@ -817,7 +817,7 @@ "\n", "---\n", "\n", - "**Questions or Issues?** Check out our [GitHub repository](https://github.com/Hawksight-AI/semantica) or [documentation](https://semantica.readthedocs.io)." + "**Questions or Issues?** Check out our [GitHub repository](https://github.com/semantica-agi/semantica) or [documentation](https://semantica.readthedocs.io)." ] } ], diff --git a/cookbook/introduction/12_Embedding_Generation.ipynb b/cookbook/introduction/12_Embedding_Generation.ipynb index b1ad1081..a17f81c2 100644 --- a/cookbook/introduction/12_Embedding_Generation.ipynb +++ b/cookbook/introduction/12_Embedding_Generation.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/13_Embedding_Generation.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/13_Embedding_Generation.ipynb)\n", "\n", "# Embedding Generation\n", "\n", diff --git a/cookbook/introduction/13_Vector_Store.ipynb b/cookbook/introduction/13_Vector_Store.ipynb index f2424104..32baeab3 100644 --- a/cookbook/introduction/13_Vector_Store.ipynb +++ b/cookbook/introduction/13_Vector_Store.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/13_Vector_Store.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/13_Vector_Store.ipynb)\n", "\n", "# Vector Store - Comprehensive Guide\n", "\n", @@ -492,7 +492,7 @@ "\n", "---\n", "\n", - "**Questions or Issues?** Check out our [GitHub repository](https://github.com/Hawksight-AI/semantica) or [documentation](https://semantica.readthedocs.io)." + "**Questions or Issues?** Check out our [GitHub repository](https://github.com/semantica-agi/semantica) or [documentation](https://semantica.readthedocs.io)." ] } ], diff --git a/cookbook/introduction/14_Ontology.ipynb b/cookbook/introduction/14_Ontology.ipynb index 3c112404..64bbee06 100644 --- a/cookbook/introduction/14_Ontology.ipynb +++ b/cookbook/introduction/14_Ontology.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/14_Ontology.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/14_Ontology.ipynb)\n", "\n", "# Ontology Generation \n", "\n", diff --git a/cookbook/introduction/15_Export.ipynb b/cookbook/introduction/15_Export.ipynb index 3d6a8c10..224c63ea 100644 --- a/cookbook/introduction/15_Export.ipynb +++ b/cookbook/introduction/15_Export.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/15_Export.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/15_Export.ipynb)\n", "\n", "# Export Module - Comprehensive Guide\n", "\n", diff --git a/cookbook/introduction/16_Visualization.ipynb b/cookbook/introduction/16_Visualization.ipynb index 05c55b61..754beef6 100644 --- a/cookbook/introduction/16_Visualization.ipynb +++ b/cookbook/introduction/16_Visualization.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/17_Visualization.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/17_Visualization.ipynb)\n", "\n", "# Visualization\n", "\n", diff --git a/cookbook/introduction/18_Deduplication.ipynb b/cookbook/introduction/18_Deduplication.ipynb index 087817a7..53e03683 100644 --- a/cookbook/introduction/18_Deduplication.ipynb +++ b/cookbook/introduction/18_Deduplication.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/18_Deduplication.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/18_Deduplication.ipynb)\n", "\n", "# Deduplication in Semantica\n", "\n", diff --git a/cookbook/introduction/19_Context_Module.ipynb b/cookbook/introduction/19_Context_Module.ipynb index 2bbe81ce..d2ec4cf4 100644 --- a/cookbook/introduction/19_Context_Module.ipynb +++ b/cookbook/introduction/19_Context_Module.ipynb @@ -5,7 +5,7 @@ "id": "c21e9c8d", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Hawksight-AI/semantica/blob/main/cookbook/introduction/19_Context_Module.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/19_Context_Module.ipynb)\n", "\n", "# Context Module — Practical Guide\n", "\n", diff --git a/docs/citation.md b/docs/citation.md index 45ac350b..b798677a 100644 --- a/docs/citation.md +++ b/docs/citation.md @@ -13,26 +13,25 @@ icon: "quote-left" ```bibtex @software{semantica2026, - title = {Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems}, - author = {Semantica}, - year = {2026}, - url = {https://github.com/semantica-agi/semantica}, - version = {0.6.6}, - doi = {10.5281/zenodo.XXXXXXX} + title = {Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems}, + author = {Semantica}, + year = {2026}, + url = {https://github.com/semantica-agi/semantica}, + doi = {10.5281/zenodo.XXXXXXX} } ``` - Semantica. (2026). *Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems* (Version 0.6.6) \[Computer software\]. https://github.com/semantica-agi/semantica + Semantica. (2026). *Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems* \[Computer software\]. https://github.com/semantica-agi/semantica - Semantica. *Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems*. Version 0.6.6, GitHub, 2026, https://github.com/semantica-agi/semantica. + Semantica. *Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems*. GitHub, 2026, https://github.com/semantica-agi/semantica. - Semantica. *Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems*. Version 0.6.6. GitHub, 2026. https://github.com/semantica-agi/semantica. + Semantica. *Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems*. GitHub, 2026. https://github.com/semantica-agi/semantica. - Semantica, "Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems," Version 0.6.6, GitHub, 2026. \[Online\]. Available: https://github.com/semantica-agi/semantica + Semantica, "Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems," GitHub, 2026. \[Online\]. Available: https://github.com/semantica-agi/semantica diff --git a/docs/cookbook.md b/docs/cookbook.md index 443aae7c..d7a780bb 100644 --- a/docs/cookbook.md +++ b/docs/cookbook.md @@ -80,6 +80,6 @@ Deep dive into advanced features, customization, and complex workflows. You can also run the cookbook using Docker: ```bash - docker run -p 8888:8888 hawksight/semantica-cookbook + docker run -p 8888:8888 semantica/semantica-cookbook ``` diff --git a/docs/governance.md b/docs/governance.md index 332cf5a2..e1df0508 100644 --- a/docs/governance.md +++ b/docs/governance.md @@ -4,12 +4,12 @@ description: "Project governance model: roles, decision process, release cadence icon: "scale-balanced" --- -> Semantica is maintained by Hawksight AI with community contributions under an open governance model. +> Semantica is maintained by the Semantica team with community contributions under an open governance model. ## Roles -- **Maintainers** — Hawksight AI team: review and merge PRs, manage releases and code quality, set project direction and community standards. +- **Maintainers** — Semantica team: review and merge PRs, manage releases and code quality, set project direction and community standards. - **Contributors** — Submit code, documentation, and bug reports. Help with issues and reviews. Recognized in [CONTRIBUTORS.md](https://github.com/semantica-agi/semantica/blob/main/CONTRIBUTORS.md). - **Community Members** — Use Semantica, provide feedback, share use cases, and participate in GitHub Discussions and Discord. diff --git a/docs/project-license.md b/docs/project-license.md index b1fb31ef..220f0f44 100644 --- a/docs/project-license.md +++ b/docs/project-license.md @@ -12,7 +12,7 @@ icon: "file-contract" ``` MIT License -Copyright (c) 2026 Hawksight AI +Copyright (c) 2026 Semantica Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/plugins/.claude-plugin/README.md b/plugins/.claude-plugin/README.md index 6fa56eef..c685a65b 100644 --- a/plugins/.claude-plugin/README.md +++ b/plugins/.claude-plugin/README.md @@ -53,7 +53,7 @@ plugins/ ## Prerequisites ```bash -git clone https://github.com/Hawksight-AI/semantica.git +git clone https://github.com/semantica-agi/semantica.git cd semantica pip install semantica # Python 3.10+ ``` diff --git a/plugins/.claude-plugin/marketplace.json b/plugins/.claude-plugin/marketplace.json index cbe8b924..0afe6ddb 100644 --- a/plugins/.claude-plugin/marketplace.json +++ b/plugins/.claude-plugin/marketplace.json @@ -1,8 +1,8 @@ { "name": "semantica-local", "owner": { - "name": "Hawksight AI", - "url": "https://github.com/Hawksight-AI/semantica" + "name": "Semantica", + "url": "https://github.com/semantica-agi/semantica" }, "plugins": [ { diff --git a/plugins/.claude-plugin/plugin.json b/plugins/.claude-plugin/plugin.json index fd5d35e7..8328e3a9 100644 --- a/plugins/.claude-plugin/plugin.json +++ b/plugins/.claude-plugin/plugin.json @@ -5,8 +5,8 @@ "author": { "name": "Semantica Contributors" }, - "homepage": "https://github.com/Hawksight-AI/semantica", - "repository": "https://github.com/Hawksight-AI/semantica", + "homepage": "https://github.com/semantica-agi/semantica", + "repository": "https://github.com/semantica-agi/semantica", "license": "MIT", "keywords": [ "semantica", diff --git a/plugins/.cline-plugin/plugin.json b/plugins/.cline-plugin/plugin.json index 81c004c4..d4f09886 100644 --- a/plugins/.cline-plugin/plugin.json +++ b/plugins/.cline-plugin/plugin.json @@ -6,8 +6,8 @@ "author": { "name": "Semantica Contributors" }, - "homepage": "https://github.com/Hawksight-AI/semantica", - "repository": "https://github.com/Hawksight-AI/semantica", + "homepage": "https://github.com/semantica-agi/semantica", + "repository": "https://github.com/semantica-agi/semantica", "license": "MIT", "keywords": [ "semantica", diff --git a/plugins/.codex-plugin/plugin.json b/plugins/.codex-plugin/plugin.json index c12d66c2..eac91fc6 100644 --- a/plugins/.codex-plugin/plugin.json +++ b/plugins/.codex-plugin/plugin.json @@ -5,8 +5,8 @@ "author": { "name": "Semantica Contributors" }, - "homepage": "https://github.com/Hawksight-AI/semantica", - "repository": "https://github.com/Hawksight-AI/semantica", + "homepage": "https://github.com/semantica-agi/semantica", + "repository": "https://github.com/semantica-agi/semantica", "license": "MIT", "keywords": [ "semantica", diff --git a/plugins/.continue-plugin/plugin.json b/plugins/.continue-plugin/plugin.json index da93dc38..57d45e82 100644 --- a/plugins/.continue-plugin/plugin.json +++ b/plugins/.continue-plugin/plugin.json @@ -6,8 +6,8 @@ "author": { "name": "Semantica Contributors" }, - "homepage": "https://github.com/Hawksight-AI/semantica", - "repository": "https://github.com/Hawksight-AI/semantica", + "homepage": "https://github.com/semantica-agi/semantica", + "repository": "https://github.com/semantica-agi/semantica", "license": "MIT", "keywords": [ "semantica", diff --git a/plugins/.cursor-plugin/plugin.json b/plugins/.cursor-plugin/plugin.json index 3b73366a..0a221866 100644 --- a/plugins/.cursor-plugin/plugin.json +++ b/plugins/.cursor-plugin/plugin.json @@ -6,8 +6,8 @@ "author": { "name": "Semantica Contributors" }, - "homepage": "https://github.com/Hawksight-AI/semantica", - "repository": "https://github.com/Hawksight-AI/semantica", + "homepage": "https://github.com/semantica-agi/semantica", + "repository": "https://github.com/semantica-agi/semantica", "license": "MIT", "keywords": [ "semantica", diff --git a/plugins/.openclaw-plugin/plugin.json b/plugins/.openclaw-plugin/plugin.json index 0489b609..582145f5 100644 --- a/plugins/.openclaw-plugin/plugin.json +++ b/plugins/.openclaw-plugin/plugin.json @@ -6,8 +6,8 @@ "author": { "name": "Semantica Contributors" }, - "homepage": "https://github.com/Hawksight-AI/semantica", - "repository": "https://github.com/Hawksight-AI/semantica", + "homepage": "https://github.com/semantica-agi/semantica", + "repository": "https://github.com/semantica-agi/semantica", "license": "MIT", "keywords": [ "semantica", diff --git a/plugins/.vscode-plugin/plugin.json b/plugins/.vscode-plugin/plugin.json index a39c097e..771df46d 100644 --- a/plugins/.vscode-plugin/plugin.json +++ b/plugins/.vscode-plugin/plugin.json @@ -6,8 +6,8 @@ "author": { "name": "Semantica Contributors" }, - "homepage": "https://github.com/Hawksight-AI/semantica", - "repository": "https://github.com/Hawksight-AI/semantica", + "homepage": "https://github.com/semantica-agi/semantica", + "repository": "https://github.com/semantica-agi/semantica", "license": "MIT", "keywords": [ "semantica", diff --git a/plugins/.windsurf-plugin/plugin.json b/plugins/.windsurf-plugin/plugin.json index cbf45713..abb4831d 100644 --- a/plugins/.windsurf-plugin/plugin.json +++ b/plugins/.windsurf-plugin/plugin.json @@ -6,8 +6,8 @@ "author": { "name": "Semantica Contributors" }, - "homepage": "https://github.com/Hawksight-AI/semantica", - "repository": "https://github.com/Hawksight-AI/semantica", + "homepage": "https://github.com/semantica-agi/semantica", + "repository": "https://github.com/semantica-agi/semantica", "license": "MIT", "keywords": [ "semantica", diff --git a/semantica/change_management/change_management_usage.md b/semantica/change_management/change_management_usage.md index 0b5c161d..d7dde3ae 100644 --- a/semantica/change_management/change_management_usage.md +++ b/semantica/change_management/change_management_usage.md @@ -1039,6 +1039,6 @@ manager = TemporalVersionManager(storage_path="large_data.db") ## Support For questions or issues: -- GitHub Issues: https://github.com/Hawksight-AI/semantica/issues +- GitHub Issues: https://github.com/semantica-agi/semantica/issues - Documentation: https://semantica.readthedocs.io - Community: https://discord.gg/sV34vps5hH diff --git a/tests/ingest/test_notebook_02.py b/tests/ingest/test_notebook_02.py index 0cbb6a52..f99a8b37 100644 --- a/tests/ingest/test_notebook_02.py +++ b/tests/ingest/test_notebook_02.py @@ -157,7 +157,7 @@ class TestNotebook02DataIngestion: repo_ingestor = RepoIngestor() with patch.object(repo_ingestor, 'ingest_repository') as mock_ingest: mock_ingest.return_value = {'name': 'semantica'} - repo_data = repo_ingestor.ingest_repository("https://github.com/Hawksight-AI/semantica.git") + repo_data = repo_ingestor.ingest_repository("https://github.com/semantica-agi/semantica.git") assert repo_data['name'] == 'semantica' def test_07_email_ingestion(self): From 3c00ffb01955650848ae4d8e0fa1b8595e9d0d25 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 24 Aug 2026 16:13:47 +0530 Subject: [PATCH 10/11] fix(cookbook): correct mismatched Open in Colab badge links Seven introduction notebooks linked to a different notebook's filename in their Colab badge (off-by-one numbering), sending readers to the wrong notebook or a 404. Point each badge back at its own file. --- .../introduction/03_Document_Parsing.ipynb | 37 +------------------ .../introduction/04_Data_Normalization.ipynb | 2 +- .../07_Building_Knowledge_Graphs.ipynb | 2 +- .../08_Your_First_Knowledge_Graph.ipynb | 2 +- .../introduction/10_Graph_Analytics.ipynb | 2 +- .../12_Embedding_Generation.ipynb | 2 +- cookbook/introduction/16_Visualization.ipynb | 2 +- 7 files changed, 8 insertions(+), 41 deletions(-) diff --git a/cookbook/introduction/03_Document_Parsing.ipynb b/cookbook/introduction/03_Document_Parsing.ipynb index d7ed84c3..4705884f 100644 --- a/cookbook/introduction/03_Document_Parsing.ipynb +++ b/cookbook/introduction/03_Document_Parsing.ipynb @@ -3,40 +3,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/04_Document_Parsing.ipynb)\n", - "\n", - "# Document Parsing\n", - "\n", - "## Overview\n", - "\n", - "This notebook demonstrates how to parse various document formats using Semantica's parsing modules. You'll learn to extract text, metadata, and structured data from PDFs, DOCX, CSV, JSON, XML, and HTML files.\n", - "\n", - "**Documentation**: [API Reference](https://semantica.readthedocs.io/reference/parse/)\n", - "\n", - "### Learning Objectives\n", - "\n", - "- Use `DocumentParser` for general document parsing\n", - "- Use format-specific parsers: `PDFParser`, `DOCXParser`, `CSVParser`, `JSONParser`, `XMLParser`, `HTMLParser`\n", - "- Extract text content and metadata from documents\n", - "- Parse structured data formats\n", - "\n", - "## Installation\n", - "\n", - "Install Semantica from PyPI:\n", - "\n", - "```bash\n", - "pip install semantica\n", - "# Or with all optional dependencies:\n", - "pip install semantica[all]\n", - "```\n", - "\n", - "---\n", - "\n", - "## Step 1: Document Parser\n", - "\n", - "Parse various document formats using the general DocumentParser.\n" - ] + "source": "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/03_Document_Parsing.ipynb)\n\n# Document Parsing\n\n## Overview\n\nThis notebook demonstrates how to parse various document formats using Semantica's parsing modules. You'll learn to extract text, metadata, and structured data from PDFs, DOCX, CSV, JSON, XML, and HTML files.\n\n**Documentation**: [API Reference](https://semantica.readthedocs.io/reference/parse/)\n\n### Learning Objectives\n\n- Use `DocumentParser` for general document parsing\n- Use format-specific parsers: `PDFParser`, `DOCXParser`, `CSVParser`, `JSONParser`, `XMLParser`, `HTMLParser`\n- Extract text content and metadata from documents\n- Parse structured data formats\n\n## Installation\n\nInstall Semantica from PyPI:\n\n```bash\npip install semantica\n# Or with all optional dependencies:\npip install semantica[all]\n```\n\n---\n\n## Step 1: Document Parser\n\nParse various document formats using the general DocumentParser." }, { "cell_type": "code", @@ -271,4 +238,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/cookbook/introduction/04_Data_Normalization.ipynb b/cookbook/introduction/04_Data_Normalization.ipynb index f725bd9e..6cdd07db 100644 --- a/cookbook/introduction/04_Data_Normalization.ipynb +++ b/cookbook/introduction/04_Data_Normalization.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/05_Data_Normalization.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/04_Data_Normalization.ipynb)\n", "\n", "# Data Normalization\n", "\n", diff --git a/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb b/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb index 4586c879..5c27420a 100644 --- a/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb +++ b/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/08_Building_Knowledge_Graphs.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb)\n", "\n", "# Building Knowledge Graphs\n", "\n", diff --git a/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb b/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb index 7f65a910..f159efb6 100644 --- a/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb +++ b/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/09_Your_First_Knowledge_Graph.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)\n", "\n", "# 🚀 Your First Knowledge Graph\n", "\n", diff --git a/cookbook/introduction/10_Graph_Analytics.ipynb b/cookbook/introduction/10_Graph_Analytics.ipynb index 327d4cea..e0307e04 100644 --- a/cookbook/introduction/10_Graph_Analytics.ipynb +++ b/cookbook/introduction/10_Graph_Analytics.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/11_Graph_Analytics.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/10_Graph_Analytics.ipynb)\n", "\n", "# Graph Analytics\n", "\n", diff --git a/cookbook/introduction/12_Embedding_Generation.ipynb b/cookbook/introduction/12_Embedding_Generation.ipynb index a17f81c2..4b334fcc 100644 --- a/cookbook/introduction/12_Embedding_Generation.ipynb +++ b/cookbook/introduction/12_Embedding_Generation.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/13_Embedding_Generation.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/12_Embedding_Generation.ipynb)\n", "\n", "# Embedding Generation\n", "\n", diff --git a/cookbook/introduction/16_Visualization.ipynb b/cookbook/introduction/16_Visualization.ipynb index 754beef6..31483278 100644 --- a/cookbook/introduction/16_Visualization.ipynb +++ b/cookbook/introduction/16_Visualization.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/17_Visualization.ipynb)\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/16_Visualization.ipynb)\n", "\n", "# Visualization\n", "\n", From 943be0c10f3accf42810e3b56f3a50051bcee1a7 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 24 Aug 2026 16:14:22 +0530 Subject: [PATCH 11/11] fix(cookbook): restore original notebook JSON formatting The previous commit's fix to 03_Document_Parsing.ipynb collapsed the cell's source array into a single string and dropped the trailing newline. Restore the original array-of-lines formatting so the diff is limited to the corrected badge URL. --- .../introduction/03_Document_Parsing.ipynb | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/cookbook/introduction/03_Document_Parsing.ipynb b/cookbook/introduction/03_Document_Parsing.ipynb index 4705884f..171f111a 100644 --- a/cookbook/introduction/03_Document_Parsing.ipynb +++ b/cookbook/introduction/03_Document_Parsing.ipynb @@ -3,7 +3,40 @@ { "cell_type": "markdown", "metadata": {}, - "source": "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/03_Document_Parsing.ipynb)\n\n# Document Parsing\n\n## Overview\n\nThis notebook demonstrates how to parse various document formats using Semantica's parsing modules. You'll learn to extract text, metadata, and structured data from PDFs, DOCX, CSV, JSON, XML, and HTML files.\n\n**Documentation**: [API Reference](https://semantica.readthedocs.io/reference/parse/)\n\n### Learning Objectives\n\n- Use `DocumentParser` for general document parsing\n- Use format-specific parsers: `PDFParser`, `DOCXParser`, `CSVParser`, `JSONParser`, `XMLParser`, `HTMLParser`\n- Extract text content and metadata from documents\n- Parse structured data formats\n\n## Installation\n\nInstall Semantica from PyPI:\n\n```bash\npip install semantica\n# Or with all optional dependencies:\npip install semantica[all]\n```\n\n---\n\n## Step 1: Document Parser\n\nParse various document formats using the general DocumentParser." + "source": [ + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/03_Document_Parsing.ipynb)\n", + "\n", + "# Document Parsing\n", + "\n", + "## Overview\n", + "\n", + "This notebook demonstrates how to parse various document formats using Semantica's parsing modules. You'll learn to extract text, metadata, and structured data from PDFs, DOCX, CSV, JSON, XML, and HTML files.\n", + "\n", + "**Documentation**: [API Reference](https://semantica.readthedocs.io/reference/parse/)\n", + "\n", + "### Learning Objectives\n", + "\n", + "- Use `DocumentParser` for general document parsing\n", + "- Use format-specific parsers: `PDFParser`, `DOCXParser`, `CSVParser`, `JSONParser`, `XMLParser`, `HTMLParser`\n", + "- Extract text content and metadata from documents\n", + "- Parse structured data formats\n", + "\n", + "## Installation\n", + "\n", + "Install Semantica from PyPI:\n", + "\n", + "```bash\n", + "pip install semantica\n", + "# Or with all optional dependencies:\n", + "pip install semantica[all]\n", + "```\n", + "\n", + "---\n", + "\n", + "## Step 1: Document Parser\n", + "\n", + "Parse various document formats using the general DocumentParser.\n" + ] }, { "cell_type": "code", @@ -238,4 +271,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +}