KGVisualizer.visualize_network() (and sibling methods) only accepted a raw
dict. Passing a KnowledgeGraph object — the natural output of
GraphBuilder.build() — silently returned without rendering.
Added _normalize_graph() which duck-types the input: dicts pass through
unchanged; any object exposing .entities / .relationships attributes is
converted to the canonical dict form; anything else raises a clear
ProcessingError naming the offending type.
_normalize_graph() is called as the first statement in visualize_network(),
visualize_communities(), visualize_centrality(), visualize_entity_types(),
and visualize_relationship_matrix().
Also adds 21 tests in tests/visualization/test_kg_visualizer_normalize_graph.py
covering the helper directly, the end-to-end regression for #458, and
a guard that every public method routes through _normalize_graph.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug 1 — non-string IDs crash store():
_resolve_iri() called .startswith() directly on local, causing AttributeError
when upstream graph builders emit integer entity/relationship IDs. Fixed by
coercing local to str() at entry; None/empty returns a safe urn: sentinel.
Bug 2 — prefixed W3C terms mis-resolved under base_uri:
Values like 'owl:Thing' and 'xsd:date' were not recognised as absolute IRIs
and with base_uri set were rewritten to e.g. https://example.com/owl:Thing,
corrupting standard OWL/XSD IRIs in stored triples. Fixed by adding a known-
prefix expansion table (xsd/rdf/rdfs/owl/skos/semantica) that is checked before
base_uri is applied, matching the same prefix map already used in blazegraph_store.
Added 5 regression tests covering both bugs: integer IDs with/without base_uri,
owl:Thing domain/range, xsd:date range, and rdfs:/skos: parent class expansion.
store() was minting urn:entity:, urn:class:, and urn:property: URIs for every
bare local name, even when the ontology carried a namespace.base_uri. This made
instance data and ontology class data irreconcilable in SPARQL joins.
- Extract base_uri from ontology.namespace.base_uri (or ontology.uri as fallback)
- Introduce _resolve_iri(local, kind) closure that appends the local name to
base_uri when present, keeping urn: fallback only when no base URI is known
- Apply _resolve_iri consistently for entity URIs, entity types, relationship
predicates, ontology class URIs, parent class URIs, property URIs, and
property domain/range URIs
- Explicit entity.uri values are never overridden
- Added 9 regression tests in TestTripletStoreOntologyNamespace covering all
IRI expansion paths, urn: fallback, explicit URI passthrough, top-level uri
key fallback, and trailing-slash safety
- Added _resolve_datatype_iri() to expand known prefixes (xsd/rdf/rdfs/owl/skos)
to full IRIs instead of blindly wrapping in <...>, fixing invalid SPARQL like
<xsd:integer>
- Validated language tags against RFC 5646 regex to prevent SPARQL injection
via metadata["lang"] values containing whitespace or punctuation
- Validated datatype IRIs for whitespace/special characters before interpolation
- Extended test suite from 7 to 15 cases covering prefix expansion, injection
rejection, and all accepted input forms
- Remove orphaned unclosed parenthesis (syntax error) in
test_unreleased_changelog_comprehensive.py (OllamaProvider block)
- Fix test_invalid_json_returns_error to assert compliant=False and
non-empty violations instead of missing "error" key — aligns with
check_policy() return schema
- Fix test_as_of_filters_future_decisions to extract scenario via
p["decision"]["scenario"] (correct nesting) and pass
similarity_threshold=0.0 so word-overlap doesn't filter out Bob's
decision below the 0.5 default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- add_decision: pass valid_from/valid_until through kwargs path so
temporal bounds are not silently dropped into metadata (Codex P1)
- add_decision: raise ValueError when Decision object and kwargs are
both provided, instead of silently ignoring the kwargs (Codex P2)
- fix guard condition to exclude decision_maker (non-None default)
to avoid false-positive ValueError on plain add_decision(obj) calls
- test_395: remove unused `import time`; strengthen as_of test with
concrete assertions on scenarios list (github-code-quality)
- test_unreleased: remove unused `import time`; drop unused `snap =`
assignment; drop unused `provider =` assignment (github-code-quality)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- test_add_decision_kwargs_form: verifies add_decision() accepts kwargs
directly (category, scenario, reasoning, outcome, confidence) without
requiring a Decision object
- test_add_decision_kwargs_and_object_both_return_id: verifies both call
forms return a non-empty string ID
- test_agent_context_inmemory_store_and_retrieve: verifies AgentContext
with VectorStore(backend="inmemory") stores memories without faiss-cpu
Closes#433
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- honor enable_named_graphs flag when forwarding support
- prevent duplicate FROM/FROM NAMED clauses for same graph
- add default_graph_uri compatibility alias
- harden graph URI sanitization in prune DROP GRAPH path
- add regression tests for all fixes
Co-authored-by: Sameer6305 <sskadam6305@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
- test_vocabulary.py: remove sys.modules['spacy'] = MagicMock() — caused
ValueError in pytest collection when transformers called
importlib.util.find_spec('spacy') on a MagicMock without __spec__;
add setup_function() reset_mock() to prevent cross-test state pollution;
expand from 3 to 16 tests covering narrower edges, topConceptOf,
hasTopConcept, flat scheme, empty scheme, missing param, cycle safety,
.rdf/.owl format path, invalid file 422, and metadata envelope fallback
- vocabulary.py: /import returned HTTP 200 with {"status":"error"} on parse
failure — now raises HTTPException(422) so clients get a proper error code;
replace bare except with ValueError-specific catch, move add_nodes/add_edges
outside the try block
- vocabulary.py: get_hierarchy tree assembly had no cycle detection — cyclic
broader/narrower edges in real-world SKOS data would cause infinite recursion
during Pydantic serialization; replaced inline loop with recursive
_attach_children() that carries a visited set
- semantica/explorer/utils/: branch was based on main and missing rdf_parser.py
and __init__.py (introduced in #425); copied from ebd2be3 so vocabulary.py
import resolves correctly
- tests/explorer/test_rdf_parser.py: carried forward from #425 (32 tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the existing ontology and triplet-store stack with first-class
SKOS support without adding any new top-level packages.
### semantica/ontology/namespace_manager.py
- `get_skos_uri(local_name)` — build full skos:core# URI from local name
- `build_concept_scheme_uri(name)` — slug a human name into a stable
ConceptScheme URI anchored at the configured base URI
### semantica/triplet_store/triplet_store.py
- `add_skos_concept(concept_uri, scheme_uri, pref_label, ...)` — asserts
ConceptScheme + Concept triples, prefLabel, altLabel, broader, narrower,
related, definition, notation via existing `add_triplets()` API
- `get_skos_concepts(scheme_uri=None)` — SPARQL SELECT via `execute_query()`,
collapses multi-valued bindings into concept dicts
### semantica/ontology/engine.py
- `list_vocabularies()` — list all skos:ConceptScheme instances
- `list_concepts(scheme_uri)` — list concepts in a scheme with alt labels
- `search_concepts(query, scheme_uri=None)` — case-insensitive substring
search over prefLabel + altLabel; sanitises user input against SPARQL injection
### tests
- `TestSKOSOntologyEngine` (14 tests) in test_ontology_comprehensive.py
- `TestSKOSTripletStore` (6 tests) in test_triplet_store.py
- All 1162 existing + new tests pass, 0 failures
### docs/reference/ontology.md
- New "SKOS Vocabulary Management" section: data-model table, import
examples (add_skos_concept + rdflib bulk), list/search API, NamespaceManager helpers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add TemporalGraphRetriever to context_retriever.py (no new file per project convention)
- Drop-in wrapper for ContextRetriever; filters related_entities/related_relationships
via reconstruct_at_time(); at_time=None is a true passthrough
- Returns new RetrievedContext objects (no in-place mutation)
- Graceful ImportError if temporal modules unavailable
- Add at_time + header_template to ContextRetriever._generate_reasoned_response()
and query_with_reasoning()
- Temporal header prepended to LLM context block only when at_time is set
- Naive datetimes normalised to UTC before formatting
- Header built with str.replace (not .format) to prevent format-string injection
- Add TemporalQueryRewriter + TemporalQueryResult to semantica/kg/
- Regex-only (default) and LLM-assisted extraction modes
- Resolves temporal phrases via TemporalNormalizer (deterministic, zero LLM)
- Word-boundary guards on intent keywords; year fallback for noun-phrase dates
- Never calls reconstruct_at_time — extraction only
- Export TemporalGraphRetriever from semantica.context
- Export TemporalQueryRewriter, TemporalQueryResult from semantica.kg
- Add 99 tests across two new test files
- tests/context/test_temporal_retriever.py (56 tests)
- tests/kg/test_temporal_query_rewriter.py (43 tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `extract_temporal_bounds: bool = False` to `extract_relations_llm()`.
When True the LLM prompt is extended with a calibrated confidence scale
and four few-shot examples; each returned Relation gains valid_from,
valid_until, temporal_confidence, and temporal_source_text in metadata.
Low confidence (<0.5) with non-null dates logs a WARNING. Default False
preserves 100% backward compatibility.
- Add `RelationWithTemporalOut` / `RelationsWithTemporalResponse` Pydantic
schemas so the four temporal fields are captured from structured LLM
output (separate from RelationOut which uses extra="ignore").
- New `semantica/kg/temporal_normalizer.py` — `TemporalNormalizer` class
(zero LLM calls, pure regex + dateutil arithmetic):
* normalize(value) → (start, end) UTC datetimes or None
* Resolution order: ISO 8601 → partial dates (year/month/Q) →
ambiguity detection → domain phrase map → relative phrases
* normalize_phrase(phrase) → metadata dict or None
* Default phrase map covers 13 domains: General, Policy, Healthcare,
Drug Discovery, Cybersecurity, Supply Chain, Finance, Energy
* TemporalAmbiguityWarning for DD/MM/YYYY-style ambiguous inputs
* Custom phrase_map at construction (merged over defaults)
- Add `TemporalAmbiguityWarning(UserWarning)` to exceptions.py.
- Export `TemporalNormalizer` from `semantica/kg/__init__.py`.
- Propagate `extract_temporal_bounds` through `_extract_relations_chunked`
and add flag to cache key to prevent cross-mode cache pollution.
- 53 new tests in tests/semantic_extract/test_temporal_extraction.py;
zero real LLM calls, suite runs in ~3.5s. 873 existing tests unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes#408
Previously `_init_client` assigned the raw `ollama` module to
`self.client`, so the `base_url` parameter was silently ignored and
every request hit the default localhost:11434. Now an `ollama.Client`
instance is created with `host=self.base_url`, so remote Ollama servers
are reachable.
Three regression tests added to prevent recurrence:
- default base_url is forwarded as host
- custom base_url (e.g. http://192.168.1.3:11434) is forwarded as host
- self.client is never the raw module
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace isinstance(graph_store, ContextGraph) with type() is ContextGraph
in all 12 guards across decision_query.py and decision_recorder.py.
Fixes 2 regressions where Mock(spec=ContextGraph) triggered fallback
paths, causing TypeError on iteration of mock return values.
- Hoist find_edges() calls out of the BFS while-loop in trace_decision_path
so edges are fetched once per call instead of once per visited node,
eliminating O(nodes * total_edges) repeated full-graph fetches.
- Expand test_decision_query_fallback.py: keep the original integration
test and add 13 targeted unit tests covering all 7 DecisionQuery and
4 DecisionRecorder ContextGraph fallback methods, including tz-aware/naive
datetime mixing and Mock guard validation.
Result: 353 passed, 0 failed (was 338 passed, 2 failed on this branch)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.py:
- Fix unclosed '(' in generic_error_handler (two implementations were merged,
leaving the return JSONResponse( call with no closing paren)
- Remove duplicate 'from fastapi import FastAPI, Request' import
- Remove unused 'import traceback'
- Remove duplicate static file mount (was mounted twice: once conditionally,
once unconditionally creating the dir — FastAPI raises on duplicate mounts)
decisions.py:
- Remove stub 'return ComplianceResponse(compliant=True)' with unclosed '('
that was left in front of the real edge-scan implementation
temporal.py:
- Remove blocking get_nodes/get_edges calls (without asyncio.to_thread) that
were left as dead code above the correct async versions
- Fix empty 'except Exception:' clause before 'except ImportError:' that
caused a SyntaxError
tests/explorer/test_explorer_api.py:
- Remove all merge-artifact duplicate class definitions (TestAnalytics x2,
TestReasoning x2, TestAnnotations x2) — Python silently used the second
definition, hiding the first; collapsed into single canonical classes
- Fix test_snapshot_at referencing undefined 'body' (no request was made);
merged its assertions into test_snapshot_now
- Fix test_compliance asserting isinstance(body, list) on a dict response;
the displaced precedents-check code is now in test_precedents where it
belongs
- Fix test_compliance_with_violation using wrong session reference
- Remove duplicate node-lookup and duplicate assertions throughout
- Add test_search_content_populated: asserts search results carry non-empty
content (regression guard for the to_dict envelope fix)
- Add test_import_edge_metadata_preserved: asserts edge metadata survives the
import round-trip (regression guard for the properties/metadata fallback fix)
All 51 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>