- Align _coerce_embedding_vector inner dict-probe key list with
_extract_node_embeddings outer key list (add 'embeddings', reorder to
generic-first) so nested embedding dicts resolve consistently.
- Add TODO comment on _extract_node_embeddings to cache per-session
graph revision and avoid O(N) re-scan on every semantic request.
- Add deprecation docstrings to legacy path-segment routes
(/node/{id}/path and /node/{id}/semantic-neighborhood) documenting
the known slash-in-ID limitation and pointing to the query-param
alternatives.
- Extract _FakeSimilarity to module level so it is shared without
duplication across test classes.
- Rewrite test_legacy_semantic_neighborhood_still_works_for_simple_ids
as a fully isolated TestClient session instead of mutating the
shared module-scoped 'client' fixture, preventing cross-test
state pollution.
- Extract _make_slash_node_session helper to reduce boilerplate in the
slash-safe route tests.
Co-Authored-By: ZohaibHassan16 <109234410+ZohaibHassan16@users.noreply.github.com>
Co-Authored-By: KaifAhmad1 <98801504+KaifAhmad1@users.noreply.github.com>
Merge blockers (ZohaibHassan16):
- fix: distance-matrix raises HTTP 503 when metric=semantic but no
similarity backend is available, instead of silently returning hop
distances labeled as semantic
- fix: distance-enriched export now requires node_subset (HTTP 422 if
omitted), preventing unbounded all-pairs O(n^2) export over full graph
- fix: DistanceExportRequest default include corrected from ["hops",
"distance_band"] to ["source_id", "target_id", "hop_count",
"distance_band"] so default exports are unambiguous and use the correct
column name
- fix: confidence decay edge weight index now reads graph_dict.get("edges")
or graph_dict.get("relationships") to handle both graph dict shapes,
fixing always-1.0 decay when session returns relationships key
Bot findings (github-code-quality / chatgpt-codex):
- fix: remove unused Iterable import from distance_exporter.py
- fix: remove unused Response import from graph.py
- fix: move logger init before optional KG import; replace empty
except ImportError: pass with logger.debug in distance_exporter.py
- fix: replace two bare except Exception: pass in temporal.distance_history
with logger.warning including source, target, metric, and timestamp context
- fix: remove mixed import style in test_qual003 — use only module import
and reference CausalChainAnalyzer through it
- Replace deepseek.Client with openai.OpenAI(base_url="https://api.deepseek.com/v1")
in DeepSeekProvider._init_client(); the deepseek PyPI package has no Client class
- Add self.base_url = "https://api.deepseek.com/v1" to DeepSeekProvider.__init__()
(missing from original PR; caused AttributeError on every instantiation)
- Fix verbose_mode NameError in BaseProvider.generate_typed() instructor path
- Update pyproject.toml: llm-deepseek extra now declares openai>=1.0.0
- Update _init_client warning message to reference openai library
- Add 19 tests in tests/semantic_extract/test_pr482_deepseek_openai.py
- Update CHANGELOG.md
Co-authored-by: liling <liling@users.noreply.github.com>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
- Add _ttl_block() helper to accumulate all predicate-object pairs before
writing, producing a single valid Turtle subject block terminated by one
period — eliminates the bug where rdfs:subClassOf / domain / range were
appended after a closed '.' block
- Add missing data_properties loop to _export_owl_turtle so
owl:DatatypeProperty declarations are no longer silently dropped
- Add _escape_ttl_str() to escape quotes, backslashes, newlines, carriage
returns, and tabs inside Turtle string literals (rdfs:label, rdfs:comment,
owl:versionInfo)
- Unify optional-field null checks to consistent x = prop.get(); if x: pattern
- Add 43 tests in tests/export/test_owl_exporter.py covering syntax validity,
data properties, string escaping, null handling, and header output
- Update CHANGELOG.md with [Unreleased] entry
Closes#478
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extend PathResponse with hop_count (len(path)-1) and distance_band
("direct"|"near"|"mid-range"|"distant") as first-class API fields
- Add classify_path_distance() to semantica/utils/helpers.py as the
single source of truth for hop-count thresholds; both the route and
the visualizer import from it, eliminating duplicate threshold logic
- Populate hop_count and distance_band in find_path route via
classify_path_distance(); remove local _classify_distance() copy
- Add highlight_path: list[str] param to KGVisualizer.visualize_network;
path edges rendered as a distance-aware orange trace (opacity and
stroke width scale from direct→distant: 1.0/4px to 0.35/1.5px)
- Fix bidirectional edge lookup: only forward pairs (A→B) along the
path are added to path_edge_set; reverse back-edges in directed
graphs are no longer incorrectly highlighted
- Add logger.warning when highlight_path contains node IDs absent from
the layout position map, surfacing silent no-op mismatches
- Extend frontend PathResponse type in GraphInspectorPanel.tsx and
GraphWorkspaceShell.tsx with hop_count: number and distance_band
literal union to match the updated API contract
- Add 10 new tests: 2 API-level and 8 unit tests covering all four
band boundaries (0, 1, 2, 3, 4, 6, 7, 20 hops); 104 explorer tests
pass, 0 failures introduced
- Update CHANGELOG.md
- PathFinder.bfs_shortest_path() and dijkstra_shortest_path() gain a
directed: bool = True parameter. When False, a temporary undirected
view (graph.to_undirected()) is used for traversal only; the original
directed edges are preserved and returned in the response.
- _make_undirected_view() helper added to PathFinder; falls back safely
for non-NetworkX graph types.
- GET /api/graph/node/{id}/path exposes ?directed=false query param.
- PathResponse gains a directed: bool field echoing the mode used.
- Route now returns 404 on empty path (previously returned 200 with
path: []).
- 21 new tests: 12 unit (TestBidirectionalPathFinding) + 9 API-level
(TestBidirectionalPathRoute). All 120 tests pass.
- CHANGELOG updated under [Unreleased].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add semantica/kg/knowledge_graph.py with KnowledgeGraph dataclass
(entities, relationships, metadata) plus __len__ and __bool__ helpers
- Export KnowledgeGraph from semantica/kg/__init__.py
- Add KGVisualizer._convert_knowledge_graph() for explicit, non-mutating
conversion from KnowledgeGraph to internal dict format
- Route isinstance(graph, KnowledgeGraph) through _convert_knowledge_graph
inside _normalize_graph so all five visualize_* entry points accept
KnowledgeGraph directly without any manual conversion
- Add TestFormalKnowledgeGraphType (15 tests)
Closes#471
- routes/graph.py: raise HTTPException(404) for missing nodes; wrap path_finder call in try/except → HTTPException(404)
- routes/decisions.py: raise HTTPException(404) on chain, compliance, precedents sub-routes
- routes/annotations.py: raise HTTPException(404) on create (missing node) and delete (missing annotation)
- routes/enrich.py: raise HTTPException(404/503/422) for missing nodes, unavailable services, and extraction errors
- routes/temporal.py: fix TemporalPatternDetector method name detect_patterns → detect_temporal_patterns
- explorer/app.py: root / now serves built index.html when available, falls back to minimal HTML shell; add HTMLResponse import
- tests/explorer/test_explorer_api.py: test_extract accepts 503 (spacy/transformers not installed is a valid service state)
All 45 explorer API integration tests pass.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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>