Compare commits

...
502 Commits
Author SHA1 Message Date
Zohaib Hassnain ac5015bc3f fix(deduplication): normalize merged group keys 2026-05-05 20:25:51 +05:00
KaifAhmad1 29c72f59b3 docs(changelog): record Qodo review follow-up fixes for #533 and #534 2026-05-05 19:28:06 +05:30
KaifAhmad1 21c2f190f8 fix: resolve Qodo review bugs and quality issues (DuplicateDetector + ConflictDetector)
- bug_001: top_k_per_entity now uses OR semantics — keep a candidate if
  EITHER entity is under quota, preventing high-quality candidates being
  silently dropped when a popular counterpart saturates its quota
- bug_002: validate max_results and top_k_per_entity at construction;
  negative or non-int values raise ValueError instead of silent empty output
- bug_003: validate min_similarity in [0.0, 1.0] at construction;
  out-of-range values raise ValueError
- bug_004: harden ConflictDetector method='relationship' normalization —
  always produces List[Dict] before calling detect_relationship_conflicts
- quality_001: update detect_duplicates + incremental_detect docstrings to
  reflect configurable sort_by field (not hardcoded 'confidence')
- quality_002: add _normalize_entity_id helper (always str) used in both
  _apply_result_limits and _build_duplicate_groups for consistent ID handling

Backward compatible: callers not using new params see no behavior change.
58 tests pass (0 failures)
2026-05-05 19:25:48 +05:30
KaifAhmad1 8ef67b8bda feat(deduplication): add max_results, top_k_per_entity, min_similarity, sort_by to DuplicateDetector
Fixes #534

- New __init__ params: max_results, top_k_per_entity, min_similarity, sort_by
- _apply_result_limits: drop below min_similarity, sort by sort_by field,
  enforce top_k_per_entity per entity, cap at max_results globally
- Wired into detect_duplicates() and incremental_detect()
- 30 new tests in TestResultLimiting; full suite 42/42 passed
2026-05-05 19:16:58 +05:30
KaifAhmad1 0439cf884d docs(changelog): record ConflictDetector.detect_conflicts duplicate definition fix (#533) 2026-05-05 18:13:06 +05:30
KaifAhmad1 141bf80394 fix(conflicts): consolidate duplicate detect_conflicts into single dispatcher method
Fixes #533

- Removes duplicate `detect_conflicts` definition that was silently overridden,
  causing AttributeError for callers passing `method=` or `property_name=` kwargs
- Merges dispatcher logic into the surviving method with `method="all"` default
  supporting: "all", "value", "property", "type", "relationship", "temporal",
  "logical", "entity"
- Fixes `method="relationship"` incorrectly defaulting `relationships` to the
  entities list; now defaults to `[]` with dict normalization
- Removes unreachable dead code block after try/except raise in
  `detect_entity_conflicts`
2026-05-05 18:00:48 +05:30
Mohd Kaif 0c4e18e256 fix(deps): remove gpu from [all] extra to fix Windows installation failure (#538)
* fix(deps): remove gpu extra from [all] to fix Windows installation failure

faiss-gpu has no Windows builds, so semantica[all] failed with
'No matching distribution found for faiss-gpu>=1.7.0' on Windows.
Removed gpu from both [all] lines — semantica[gpu] remains available
as an explicit opt-in for Linux GPU environments.

Closes #532

* docs(changelog): record faiss-gpu Windows installation failure fix (#532)
2026-05-05 16:57:33 +05:30
Mohd Kaif 39045d783b Merge pull request #537 from Hawksight-AI/utlis
fix(utils): route all progress tracker stdout writes through _safe_wr…
2026-05-05 16:34:43 +05:30
KaifAhmad1 afa54e1bbf docs(changelog): record progress tracker cp1252 UnicodeEncodeError fix (#531) 2026-05-05 16:19:14 +05:30
KaifAhmad1 a01b3c36fc fix(utils): route all progress tracker stdout writes through _safe_write to prevent UnicodeEncodeError on cp1252 consoles
Closes #531

- Replace 5 direct sys.stdout.write() calls in ConsoleProgressDisplay.update()
  with self._safe_write() so emoji/block characters are encoded safely on
  Windows cp1252 consoles
- Add TestProgressTrackerEncoding regression tests (3 cases) covering
  _safe_write, pipeline header, and auto emoji-disable on cp1252
2026-05-05 16:03:19 +05:30
Mohd Kaif 0dda380580 Merge pull request #536 from Hawksight-AI/fix/semantic-extract-import-cycle
fix(semantic_extract): break extractor import cycle
2026-05-05 14:34:57 +05:30
KaifAhmad1andZohaib Hassan e7c9f6e7f3 fix(tests): restore sys.modules after mock injection in test_retry_logic
test_retry_logic.py injected sys.modules["openai"] = MagicMock() at module
level so providers.py could be imported without the real openai package.
Those mocks were never restored, leaving openai (and spacy, instructor etc.)
as MagicMock objects for the entire test session. This caused
test_pr482_deepseek_openai tests to receive a MagicMock when importing
openai.OpenAI, making MagicMock(spec=OpenAI) raise InvalidSpecError.

Fix: save original sys.modules entries before injection and restore them
immediately after the semantica imports that needed the mocks complete.
The mock objects remain bound inside the already-imported provider module,
so test_retry_logic tests are unaffected; other test modules now see the
real packages again.

Co-authored-by: Zohaib Hassan <zohaib179949@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-05-05 14:21:36 +05:30
KaifAhmad1andZohaib Hassan b828ebfef0 chore: resolve CHANGELOG.md merge conflict with main
main restructured [Unreleased] into ### Added / ### Fixed sections.
Moved PR #536 semantic_extract circular import fix entry into ### Fixed
below the PR #535 ingest lazy-load entry; kept ### Added content from
main intact.

Co-authored-by: Zohaib Hassan <zohaib179949@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-05-05 13:56:48 +05:30
KaifAhmad1andZohaib Hassan 6330627ddb docs(changelog): record semantic_extract circular import fix and Qodo review fix (#536)
Co-authored-by: Zohaib Hassan <zohaib179949@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-05-05 13:54:01 +05:30
KaifAhmad1andZohaib Hassan 24e327d161 fix(tests): add from __future__ import annotations for Py3.8 compatibility
subprocess.CompletedProcess[str] as a return annotation is not subscriptable
at runtime on Python 3.8, causing test collection to abort before any tests
run. Adding PEP 563 deferred evaluation makes all annotations strings at
import time, restoring 3.8 compatibility without changing behaviour on 3.9+.

Co-authored-by: Zohaib Hassan <zohaib179949@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-05-05 13:40:11 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> b9f3c59443 Potential fix for pull request finding 'Duplicate key in dict literal'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-05-05 13:28:09 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 34955ba246 Potential fix for pull request finding 'Explicit export is not defined'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-05-05 13:27:54 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 95b46f12b6 Potential fix for pull request finding 'Explicit export is not defined'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-05-05 13:25:07 +05:30
245ff76b99 fix(ingest): lazy-load optional ingestion backends (#535)
* fix(ingest): lazy-load optional ingestion backends

* fix(ingest): address qodo review — use ModuleNotFoundError and guard ConfigurationError

Bug 1: Replace overbroad `except ImportError` with `except ModuleNotFoundError` in
__getattr__ (__init__.py) and all four optional-backend loaders (methods.py). This
prevents internal import errors inside a backend module from being silently rewritten
into a misleading "package not installed" message. Also simplifies _is_missing_dependency
to rely solely on exc.name now that ModuleNotFoundError always sets it.

Bug 2: Add `except ConfigurationError: raise` before the blanket `except Exception`
handlers in ingest_web, ingest_feed, ingest_repository, and ingest_email. Missing
optional dependencies are expected user-config issues and must not be logged as errors.

Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>

* docs(changelog): record lazy ingest backends fix and qodo review fixes (#535)

Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>

* fix(tests): set exc.name on blocker's ModuleNotFoundError to match Python import machinery

OptionalDependencyBlocker was constructing ModuleNotFoundError with only a
message string, leaving .name as None. _is_missing_dependency checks exc.name
directly (the string-scan fallback was removed when switching to
ModuleNotFoundError), so the ConfigurationError conversion never triggered and
the test asserted the wrong exception type.

Python's import machinery always sets .name to the top-level module name when
it raises ModuleNotFoundError; the blocker now does the same.

Co-authored-by: Zohaib Hassan (@ZohaibHassan16) <zohaib179949@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>

---------

Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
Co-authored-by: Zohaib Hassan (@ZohaibHassan16) <zohaib179949@gmail.com>
2026-05-05 13:09:14 +05:30
a2b6a481dc chore: resolve CHANGELOG.md merge conflict with main
main restructured [Unreleased] into ### Added / ### Fixed sections.
Moved PR #535 lazy-load fix and Ontology Hub post-review fix entries
into ### Fixed; kept ### Added content from main intact.

Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
2026-05-05 12:56:06 +05:30
c7edba88ea fix(tests): set exc.name on blocker's ModuleNotFoundError to match Python import machinery
OptionalDependencyBlocker was constructing ModuleNotFoundError with only a
message string, leaving .name as None. _is_missing_dependency checks exc.name
directly (the string-scan fallback was removed when switching to
ModuleNotFoundError), so the ConfigurationError conversion never triggered and
the test asserted the wrong exception type.

Python's import machinery always sets .name to the top-level module name when
it raises ModuleNotFoundError; the blocker now does the same.

Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
2026-05-05 12:45:43 +05:30
eb8598e0cf docs(changelog): record lazy ingest backends fix and qodo review fixes (#535)
Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
2026-05-05 12:37:17 +05:30
64d4157644 fix(ingest): address qodo review — use ModuleNotFoundError and guard ConfigurationError
Bug 1: Replace overbroad `except ImportError` with `except ModuleNotFoundError` in
__getattr__ (__init__.py) and all four optional-backend loaders (methods.py). This
prevents internal import errors inside a backend module from being silently rewritten
into a misleading "package not installed" message. Also simplifies _is_missing_dependency
to rely solely on exc.name now that ModuleNotFoundError always sets it.

Bug 2: Add `except ConfigurationError: raise` before the blanket `except Exception`
handlers in ingest_web, ingest_feed, ingest_repository, and ingest_email. Missing
optional dependencies are expected user-config issues and must not be logged as errors.

Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
2026-05-05 12:30:48 +05:30
Zohaib Hassnain e1b1e63541 fix(semantic_extract): break extractor import cycle 2026-05-05 02:26:57 +05:00
Zohaib Hassnain 6b0a8e60ce fix(ingest): lazy-load optional ingestion backends 2026-05-05 02:01:27 +05:00
Mohd Kaif 8b22a58b8f Update print statement from 'Hello' to 'Goodbye' 2026-05-04 23:36:03 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ba286a55ea security(deps): update mkdocs requirement from >=1.5.0 to >=1.6.1 (#507)
Updates the requirements on [mkdocs](https://github.com/mkdocs/mkdocs) to permit the latest version.
- [Release notes](https://github.com/mkdocs/mkdocs/releases)
- [Commits](https://github.com/mkdocs/mkdocs/compare/1.5.0...1.6.1)

---
updated-dependencies:
- dependency-name: mkdocs
  dependency-version: 1.6.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 22:34:36 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> f610eff0ed security(deps): update mkdocs-mermaid2-plugin requirement (#508)
Updates the requirements on [mkdocs-mermaid2-plugin](https://github.com/fralau/mkdocs-mermaid2-plugin) to permit the latest version.
- [Release notes](https://github.com/fralau/mkdocs-mermaid2-plugin/releases)
- [Changelog](https://github.com/fralau/mkdocs-mermaid2-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/fralau/mkdocs-mermaid2-plugin/compare/v1.0.1...v1.2.3)

---
updated-dependencies:
- dependency-name: mkdocs-mermaid2-plugin
  dependency-version: 1.2.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 21:30:16 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e9e7278720 security(deps): update mkdocs-jupyter requirement (#509)
Updates the requirements on [mkdocs-jupyter](https://github.com/danielfrg/mkdocs-jupyter) to permit the latest version.
- [Changelog](https://github.com/danielfrg/mkdocs-jupyter/blob/main/CHANGELOG.md)
- [Commits](https://github.com/danielfrg/mkdocs-jupyter/commits)

---
updated-dependencies:
- dependency-name: mkdocs-jupyter
  dependency-version: 0.26.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-03 12:32:57 +05:30
Mohd Kaif 6b2cafd02e Merge pull request #524 from Hawksight-AI/feat/onto-hub-subissue-520
feat(ontology): add alignments, health dashboard, and SHACL studio
2026-05-02 17:21:54 +05:30
90d857a98f docs(changelog): add PR #524 entry — Alignments, Health Dashboard & SHACL Studio
Covers all features, backend endpoints, schemas, helpers, fix-up commits,
and 14 integration tests added during the subissue-3 implementation cycle.

Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
Co-authored-by: ZohaibHassan16 <zohaib@example.com>
2026-05-02 16:53:09 +05:30
1861ca578c chore: resolve merge conflicts with origin/main
- Keep SequenceMatcher + Tuple imports; delegate Literal to typing_extensions
- Preserve both _ALIGNMENT_RELATIONS (subissue-520) and _INGEST_FORMAT_SUFFIXES (main)
- Keep our OntologyAlignment-typed _get_alignment_store; add main's _get_drafts,
  _get_proposals, _get_versions, _alignment_key, _coerce_alignment, _version_field
- Import OntologyEditor + VersionsTab (main) alongside ShaclStudio (subissue-520)
- All 14 subissue-3 tests pass post-resolution

Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
Co-authored-by: ZohaibHassan16 <zohaib@example.com>
2026-05-02 16:45:10 +05:30
63acc7a66e fix(ontology): address Qodo automated review findings from PR #524
Backend (semantica/explorer/routes/ontology.py):
- suggest-alignments: add TF-IDF character-ngram embeddings via sklearn
  (SimilarityCalculator-compatible cosine scoring) so embedding_similarity
  is populated in results; combined score = 0.4*label + 0.6*embedding when
  available, falling back to label-only when sklearn is absent
- suggest-alignments: add token-overlap prefilter before SequenceMatcher so
  zero-Jaccard pairs are skipped without computing full similarity; add
  _MAX_ENTITIES_PER_SIDE=500 per-ontology cap on top of the existing
  _MAX_ANALYSIS_NODES global cap
- suggest-alignments: remove dead try/except OntologyEngine.create_alignment
  block that always failed silently (no TripletStore configured); replace
  with a comment explaining the intentional ephemeral-only storage model
- health: replace O(alignments x entities) any() scans for alignment coverage
  with O(1) set membership checks via assessed_ids
- shacl/validate: run rdflib.Graph().parse(format='turtle') syntax check on
  the submitted Turtle before returning; invalid syntax now raises 422 instead
  of returning a misleading unavailable/success response

Frontend:
- AlignmentsTab: add pairwise alignment matrix section that groups recorded
  alignments by (source_ontology, target_ontology) pair; each cell shows
  color-coded relation badges per RELATION_COLORS; clicking a badge populates
  the create/edit form for quick editing; matrix is shown when at least two
  ontologies are loaded
- ShaclStudio: add selectedShapeId state and fullShacl ref; each shape row in
  the library is now a clickable button that extracts its Turtle block from
  the full SHACL and pre-populates the Monaco editor; a "View all" toggle
  restores the full SHACL; selected shape ID is shown in the editor header
- GraphWorkspace: fix viewMode race in external focus effect — call
  setSelectedNodeId directly instead of going through focusNode(), which
  captured a stale viewMode in its closure; remove focusNode from the
  dependency array since it is no longer called

Tests (14 passing, was 11):
- Add test_suggest_alignments_returns_embedding_similarity: asserts
  embedding_similarity is non-null when sklearn is available
- Add test_shacl_validate_rejects_invalid_turtle_syntax: asserts 422 on
  syntactically invalid Turtle
- Add test_health_alignment_coverage_uses_set_lookup: asserts alignment
  dimension score is non-zero after recording an alignment, verifying the
  O(1) set lookup path works correctly end-to-end

Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
2026-05-02 16:38:09 +05:30
00ceb09960 fix(ontology): address review blockers from PR #524
Backend:
- Replace false conforms=True SHACL stub with status=unavailable always;
  live validation cannot be wired until OntologyEngine.validate_graph is
  connected to a data graph — a stub that returns conforms=True misleads
  users editing shapes
- Cap node/edge fetches in health, suggest-alignments, and SHACL generation
  at _MAX_ANALYSIS_NODES (5 000) with a logger.warning when the graph
  exceeds the limit; unbounded limit=999_999 fetches cause OOM on large graphs
- Set SHACL health dimension score to 0.0 (was 70.0) when status=unavailable;
  exclude unavailable dimensions from the total_score average so they neither
  inflate nor deflate the result
- Allow alignments to reference external/unloaded URIs (e.g. schema.org)
  without raising 404; label falls back to URI fragment or caller-supplied
  source_label/target_label fields added to OntologyAlignmentRequest
- Fix _alignment_id to use uuid.NAMESPACE_OID instead of NAMESPACE_URL;
  the composite key is not a URL
- Fix _summarize_shapes to normalise \r\n before splitting on .\n so shape
  parsing works correctly on Windows line endings

Frontend:
- Wrap handleSave/handleSuggest/handleRemove/handleAcceptSuggestion in
  useCallback in AlignmentsTab for consistency with sibling components
- Add ephemeral-storage banner in AlignmentsTab warning that alignments are
  session-memory-only and not persisted across restarts
- Fix exportReport in HealthTab to append/remove anchor from document before
  clicking and defer URL.revokeObjectURL to avoid Blob URL leak in some browsers
- Derive health dimension grid column count from health.dimensions.length
  instead of the hardcoded repeat(5, ...) that breaks if the backend adds
  or removes a dimension
- Add minimal Monarch tokenizer for the Monaco turtle language registration
  in ShaclStudio so prefix declarations, IRIs, SHACL properties, comments,
  and string literals are syntax-highlighted; previously the editor rendered
  as plain text despite theme rules being defined

Tests (11 passing, was 5):
- Rename test_shacl_validate_has_stable_contract to
  test_shacl_validate_returns_unavailable and assert status == unavailable
- Add test_shacl_validate_rejects_empty_turtle (expects 422)
- Add test_health_returns_404_for_unknown_ontology
- Add test_health_shacl_dimension_is_zero_when_unavailable with total_score check
- Add test_delete_unknown_alignment_returns_404
- Add test_alignment_upsert_is_idempotent (verifies ID stability and created_at
  preservation across updates)
- Add test_alignment_accepts_external_uri (verifies no 404 for schema.org URIs)
- Relax test_alignment_suggestions_are_ranked label assertions to substring
  checks so the test survives similarity algorithm changes

Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
Co-authored-by: ZohaibHassan16 <zohaib@hawksight.ai>
2026-05-02 15:53:47 +05:30
Mohd Kaif 2909e0fa14 Merge pull request #523 from Hawksight-AI/feature/ontology-hub-endpoints
feat: implement Ontology Hub endpoints with Semantica module integration
2026-05-02 11:20:41 +05:30
KaifAhmad1 d2574990fa Merge branch 'feature/ontology-hub-endpoints' of https://github.com/Hawksight-AI/semantica into feature/ontology-hub-endpoints 2026-05-02 11:12:22 +05:30
KaifAhmad1 53d1da87c0 fix: prevent invalid domain/range edges in ontology creation
- Fix domain_uri/range_uri always being truthy strings
- Only create rdfs:domain/rdfs:range edges when domain/range are non-empty strings
- Add proper validation with .strip() to handle whitespace-only values
- Apply fix to both 'data' and 'text' mode ontology creation
- Prevents pollution of graph with invalid edges to namespace root

Fixes issue where empty domain/range values like '' or None would still create
edges pointing to namespace root (e.g., 'https://ex/#/') instead of being
properly omitted.
2026-05-02 11:12:03 +05:30
Zohaib Hassnain 9e916a82b5 fix(ontology): address hub endpoint review blockers 2026-05-02 00:02:06 +05:00
Zohaib Hassnain e8bf0e50d3 feat(ontology): add alignments health and shacl studio 2026-05-01 22:59:15 +05:00
KaifAhmad1 269fdaa9fb feat: implement Ontology Hub endpoints with Semantica module integration
- Add comprehensive ontology API endpoints (27 total)
- Integrate OntologyEngine for validation, SHACL, SKOS, alignments
- Integrate VersionManager for versioning and diffing
- Integrate ChangeLogEntry for audit trails
- Integrate OntologyIngestor for RDF parsing
- Add frontend components: OntologyEditor, ProposalReview, VersionsTab
- Update CHANGELOG with detailed feature documentation
- Add proper error handling and fallback mechanisms
- Fix import issues and dependencies
- All endpoints tested and verified working

Features implemented:
- Draft management with audit trails
- Change proposals with structured diffing
- Version comparison and publishing
- Ontology loading with multiple format support
- SKOS vocabulary management
- Cross-ontology alignments
- Visual ontology editor
- Registry and search functionality
2026-05-01 22:26:21 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 2d9bbf08b1 deps(deps): update pytest-benchmark requirement from >=4.0.0 to >=5.2.3 (#522)
Updates the requirements on [pytest-benchmark](https://github.com/ionelmc/pytest-benchmark) to permit the latest version.
- [Changelog](https://github.com/ionelmc/pytest-benchmark/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/ionelmc/pytest-benchmark/compare/v4.0.0...v5.2.3)

---
updated-dependencies:
- dependency-name: pytest-benchmark
  dependency-version: 5.2.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 18:05:17 +05:30
Mohd Kaif fbbe36983b Merge pull request #521 from Hawksight-AI/feat/ontology-hub-subissue-518
feat(explorer): Ontology Hub — Registry, Loader, Entity Search & SKOS Vocabulary Manager
2026-05-01 17:16:43 +05:30
KaifAhmad1 877903358a docs(changelog): add entries for ontology hub bug fixes and security advisory #23 2026-05-01 17:12:11 +05:30
KaifAhmad1 070b36902b fix(security): remove polynomial ReDoS regex in _detect_format (py/polynomial-redos)
The pattern `<[^>]+>\s+<[^>]+>` in _detect_format() was flagged by CodeQL
(py/polynomial-redos, CWE-1333/730/400) as a polynomial regular expression
on uncontrolled user data.

The `<...>` branch was already unreachable — strings starting with '<' return
'xml' two lines above — but CodeQL does not track that control flow path.

Fix: replace the entire re.match() call with plain startswith / 'in' checks:
- N-Triples with URI subjects are already handled by the XML branch.
- Only blank-node-subject N-Triples (_:word <uri> ...) need detection here,
  which is correctly expressed as startswith('_:') and ' <' in stripped.
- Removed the now-unused `import re`.

Closes security advisory #23.
2026-05-01 15:26:17 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 3b9efb7856 Potential fix for pull request finding 'Empty except'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-05-01 15:21:32 +05:30
KaifAhmad1 2a031f0225 fix(explorer): correct file upload format detection for xml/json extensions (#518)
Bug 4 — Upload format misdetected:
- Added xml→'xml' and json→'json-ld' to the extension→format map so
  .xml and .json files are no longer misidentified as turtle.
- Changed the fallback from '|| "turtle"' to '?? ""' (empty string for
  unknown extensions) so the backend _detect_format() runs instead of
  blindly assuming turtle for any unrecognised extension.
- Omit the format key entirely from the load request body when no format
  was detected, letting the backend auto-detect from content heuristics.
- Added .n3 to the file picker accept list and dropzone hint text.
2026-05-01 15:18:28 +05:30
KaifAhmad1 d04f2b3643 fix(explorer): address Qodo review findings for ontology hub (#518)
Bug 1 — Broken registry filters:
fetchRegistry no longer sends format/kind values (owl/skos/internal/external)
as the status query param; those filters are applied client-side via
filteredEntries which already had the correct logic. Only the text search
param q is delegated to the backend.

Bug 2 — Toggle/refresh URI corruption:
Removed removesuffix('/toggle') and removesuffix('/refresh') from
toggle_ontology and refresh_ontology. Starlette's route regex already
strips the literal suffix from the captured path param; the removesuffix
call was a no-op for normal URIs but corrupted any ontology URI that
legitimately ends with /toggle or /refresh.

Bug 3 — SSRF in URL fetch:
Added _validate_fetch_url() which rejects non-http/https schemes and
resolves the hostname to block private, loopback, link-local, reserved,
and multicast addresses before requests.get() is called. Applied to all
three fetch sites: preview, load, and refresh.

Bug 5 — Inconsistent XML hardening:
_parse_rdf_sync now calls _safe_parse_rdf() from
semantica/explorer/utils/rdf_parser.py instead of g.parse() directly,
applying the existing defusedxml-based XXE protection for RDF/XML inputs.

Bug 6 — Search scans whole graph:
search_entities now calls session.search(q, limit*6) which hits the
GraphSearchIndex instead of fetching up to 999,999 nodes and doing a
linear Python substring scan. Results are post-filtered by _SEARCHABLE_TYPES
and entity_type before being returned up to the requested limit.
2026-05-01 15:12:04 +05:30
KaifAhmad1 2811469071 feat(explorer): add Ontology Hub workspace — Registry, Loader, Entity Search & SKOS (closes #518)
Implements the first subissue of Ontology Hub (#517):

Frontend:
- New OntologyWorkspace with 6 tabs (Registry, Editor, Versions,
  Alignments, Health, SHACL); active tab persisted in ontologyTab URL param
- OntologyManager: full registry CRUD with status/format badges, stats,
  toggle/refresh/remove actions, search + filter toolbar, empty state CTA
- OntologyLoader: 3-tab modal — URL import with live preview, drag-and-drop
  file upload, and Create New (from scratch / data / text)
- OntologySearch: debounced entity search with type filters and detail panel
  showing superclasses, subclasses, domain/range, instance count
- SKOSVocabularyManager: recursive concept hierarchy tree, client-side
  filtering, full SKOS annotation + relation detail panel
- Editor/Versions (subissue 2) and Alignments/Health/SHACL (subissue 3)
  tabs render descriptive stub cards as placeholders

Backend:
- 12 new FastAPI endpoints under /api/ontology (registry, preview, load,
  create, search, entity detail, SKOS schemes + concept detail, toggle,
  refresh, remove)
- rdflib-based RDF parser supporting Turtle, RDF/XML, N-Triples, JSON-LD
- URL fetching via requests in asyncio.to_thread with 20 MB cap
- Registry stored in app.state.ontology_registry; route ordering prevents
  literal paths being shadowed by /{uri:path} wildcards

Also: add playwright dev dependency for screenshot testing
2026-05-01 13:08:45 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> a0b4793590 security(deps): update pymdown-extensions requirement (#510)
Updates the requirements on [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) to permit the latest version.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.0...10.21.2)

---
updated-dependencies:
- dependency-name: pymdown-extensions
  dependency-version: 10.21.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 11:40:01 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> cdeb04b816 security(deps): update mkdocs-material requirement (#511)
Updates the requirements on [mkdocs-material](https://github.com/squidfunk/mkdocs-material) to permit the latest version.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.0...9.7.6)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.7.6
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 11:33:24 +05:30
Mohd Kaif b7e31d82b0 Merge pull request #516 from Hawksight-AI/feat/landing-page-visual-refresh
feat(explorer): redesign landing page
2026-04-30 18:23:36 +05:30
Mohd Kaif 2eba54caac Merge branch 'main' into feat/landing-page-visual-refresh 2026-04-30 16:55:03 +05:30
KaifAhmad1andZohaib Hassnain 9fd1df9c51 docs(changelog): add entry for PR #516 landing page redesign and review fixes
Co-Authored-By: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
Co-Authored-By: KaifAhmad1 <kaifahmad087@gmail.com>
2026-04-30 16:54:11 +05:30
KaifAhmad1andZohaib Hassnain 4517089a7f fix(explorer): address PR #516 review findings
- Replace invalid inset-left with inset: 0 0 0 72px on ::before at <=680px
- Add matching mobile inset fix to ::after (was still at 88px)
- Merge duplicate .landing-capability-band CSS rule blocks into one
- Fix non-standard font-weight: 850 -> 800 on .landing-launcher-item-title
- Remove unused eyebrow field from LandingAction type and all data entries
- Extract static 42-dot SVG preview array to module-level PREVIEW_DOTS constant

Co-Authored-By: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
Co-Authored-By: KaifAhmad1 <kaifahmad087@gmail.com>
2026-04-30 16:50:31 +05:30
Zohaib Hassnain 9553e1a176 feat(explorer): redesign landing page 2026-04-29 23:38:01 +05:00
Mohd Kaif 04fcfb61a7 Merge pull request #515 from Hawksight-AI/feat/distance-intelligence-slash-safe-ui
fix(explorer): make distance intelligence API calls slash-safe
2026-04-29 23:30:12 +05:30
5dc6966706 docs(changelog): add entry for issue #514 / PR #515 slash-safe distance UI fix
Co-Authored-By: ZohaibHassan16 <109234410+ZohaibHassan16@users.noreply.github.com>
Co-Authored-By: KaifAhmad1 <98801504+KaifAhmad1@users.noreply.github.com>
2026-04-29 23:23:08 +05:30
bb956b2735 fix(explorer): address PR #515 review findings
- 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>
2026-04-29 23:17:57 +05:30
Zohaib Hassnain e3a3f6010b fix(explorer): make distance intelligence API calls slash-safe 2026-04-29 21:17:36 +05:00
Mohd Kaif b6373204e2 Merge pull request #513 from Hawksight-AI/feat/explorer-distance-ui-fix
fix(explorer): make distance intelligence visible
2026-04-29 15:32:02 +05:30
e385c78977 docs(changelog): add entry for PR #513 distance intelligence fix
Co-Authored-By: ZohaibHassan16 <zohaib@hawksight.ai>
Co-Authored-By: KaifAhmad1 <mohammadk78600@gmail.com>
2026-04-29 14:54:21 +05:30
7b581d5960 fix(explorer): address PR #513 review blockers
- Fix dead `if (anchorNodeId)` conditional in buildHeatmapRenderSnapshot
  (anchor is always truthy past the early-return guard on line 263)
- Replace O(n) array .includes() with WeakMap-cached Set.has() in
  resolveDistanceNodeStyle heatmap path — prevents per-node O(n) scan
  during every Sigma reducer pass on large graphs
- Rename GraphDistanceBucketCounts.threeHop → threeHopPlus across
  types.ts, graphSceneState.ts, and GraphWorkspace.tsx so the field
  name reflects that it accumulates distance ≥ 3, not exactly 3;
  update status-strip labels to "3+ hop" accordingly
- Restore hasMetrics guard in PathDistanceIntelPanel to suppress the
  empty metric grid <div> when a path result carries no optional metrics

Co-Authored-By: ZohaibHassan16 <zohaib@hawksight.ai>
Co-Authored-By: KaifAhmad1 <mohammadk78600@gmail.com>
2026-04-29 14:49:22 +05:30
Zohaib Hassnain 07ca93fe5c fix(explorer): make distance intelligence visible 2026-04-29 02:49:08 +05:00
Mohd Kaif 41e430b928 Merge pull request #503 from Hawksight-AI/feat/explorer-visual-refresh
feat(explorer): polish graph explorer visual language
2026-04-27 22:33:41 +05:30
438d8bc7af fix(explorer): address PR #503 review findings
- Extract ENTITY_SHAPE_ALIASES and classifyEntityShape into a shared
  graphEntityShape.ts utility — resolveEntityShape was duplicated with
  divergent signatures in useLoadGraph.ts and graphSceneState.ts; both
  now import from one place so aliases can never drift
- graphSceneState.resolveEntityShape falls back to classifyEntityShape
  for nodes created programmatically that bypass useLoadGraph
- Fix graphTheme.ts indentation around fullGraphStructure,
  fullGraphStructureLayer, and interaction — closing braces were at
  wrong indent levels making the nesting visually misleading
- Add comment on fullGraphStructureLayer.mode explaining it is
  intentionally "off" as a staged-rollout gate (flip to "auto" to enable
  cross-community canvas curve rendering)

Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
Co-authored-by: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
2026-04-27 22:21:41 +05:30
82f1f6bd10 docs(changelog): add entry for Explorer visual refresh PR #503
Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
Co-authored-by: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
2026-04-27 22:00:52 +05:30
c86570b996 merge(explorer-visual-refresh): resolve conflict in GraphWorkspace.tsx
Merge origin/main (Distance Intelligence #502) into feat/explorer-visual-refresh.

Conflict was in the viewModeItems useMemo: the PR's new cluster-based toolbar
structure diverged from main's coreToolbarGroups additions.

Resolution:
- Keep PR's viewModeItems as a clean 3-item segmented control (Full/Grouped/Focused)
- Port Distance Intelligence controls (ego mode, heatmap, structural/semantic overlay)
  into a new distanceToolbarItems useMemo that slots into the cluster toolbar as a
  "Distance" cluster, visible only when a node is selected
- Wire distanceToolbarItems into toolbarClusters between "local-structure" and
  "analysis" clusters
- All other Distance Intelligence additions (state vars, BFS helpers, useEffects,
  ego depth slider, GraphInspectorPanel onFocusNode prop) merged cleanly

Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
Co-authored-by: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
2026-04-27 21:55:25 +05:30
Mohd Kaif 93dda5e435 Merge pull request #512 from Hawksight-AI/context
feat(context): add distance intelligence across context, API, and Exp…
2026-04-27 20:12:40 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 6ffed78fd9 Potential fix for pull request finding 'Module is imported with 'import' and 'import from''
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-27 18:39:09 +05:30
KaifAhmad1 f06de0dab2 fix(context): address PR #512 review blockers and bot findings
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
2026-04-27 18:28:04 +05:30
KaifAhmad1 dd016744ce feat(context): add distance intelligence across context, API, and Explorer (#502)
- ContextGraph.get_neighbors() gains include_distance_metadata flag (backward-compat)
- get_neighbor_distances() returns neighbors sorted by hop and confidence decay
- AgentContext.retrieve/find_precedents support proximity-weighted blending
- FR-4: path enrichment (decay, similarity, coherence, bottleneck, interpretation)
- FR-6: POST /api/graph/distance-matrix (hops/weighted/semantic, upper-triangle)
- FR-3: GET /api/graph/node/{id}/semantic-neighborhood
- FR-8: GET /api/decisions/causal-distance (causal-edge-only BFS)
- FR-9: GET /api/temporal/distance-history (convergence/divergence events)
- FR-10: POST /api/export/distance-enriched (CSV/JSONL, 200-node cap)
- Explorer: PathDistanceIntelPanel, Ego Mode, Structural/Semantic overlay, Heatmap
- Fix 13 Qodo review issues: API param mismatch, O(E*L) decay, breaking change,
  schema key inconsistency, datetime arithmetic, id overwrite, sweep race,
  node_subset DoS, full-matrix redundancy, effect race, silent exceptions, duplication
- 57 new tests in test_distance_intelligence.py; 18 regression tests in _smoke_review_fixes.py
2026-04-27 11:07:27 +05:30
Zohaib Hassnain 379994867d feat(explorer): polish graph explorer visual language 2026-04-27 03:10:28 +05:00
Mohd KaifandClaude Sonnet 4.6 7884d71e23 feat(explorer): add welcome screen and fix root path Invalid path error (#501)
- Add WelcomeScreen shown on app load; SKE brand button navigates back
- Fix serve_spa: empty root path was hitting dot-guard returning 400
  Invalid path instead of index.html or a welcome JSON response

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 15:41:13 +05:30
Mohd Kaif ca5f081793 Merge pull request #493 from Hawksight-AI/feat/explorer-grouped-view
Feat/explorer grouped view
2026-04-25 15:44:04 +05:30
6ad1502224 fix(explorer): address grouped view review blockers
- Fix `import.meta.env.DEV` crash in graphSceneState.ts that broke the
  entire test:graph-workspace suite (module load fails in Node.js/tsx)
- Export `resolveGroupedDisplayNodeId` from graphSceneState.ts and
  remove the identical copy in GraphWorkspace.tsx
- Add `checkGroupedViewAvailability` helper (Louvain only, no centrality)
  so grouped view availability can be checked cheaply on every graph change
- Gate full community graph build (`groupedDisplayCandidate`) on
  `viewMode === 'grouped'` to avoid running Louvain + centrality on every
  graph version tick when the user is not in grouped view
- Remove dead ternary in `focusNode` where both branches returned `nodeId`
- Add 7 new tests covering resolveGroupedDisplayNodeId,
  resolveGroupedDisplayStateSnapshot, and checkGroupedViewAvailability

Co-Authored-By: ZohaibHassan16 <109234410+ZohaibHassan16@users.noreply.github.com>
Co-Authored-By: KaifAhmad1 <mohammadk78600@gmail.com>
2026-04-25 13:42:15 +05:30
Zohaib Hassnain b010ba68fa Merge origin/main into feat/explorer-grouped-view 2026-04-25 02:54:48 +05:00
Zohaib Hassnain 7c8dfbd3c0 feat(explorer): stabilize and refine grouped graph view 2026-04-25 02:40:51 +05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 45400c88d3 ci(deps): bump actions/upload-pages-artifact from 3 to 5 (#485)
Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 5.
- [Release notes](https://github.com/actions/upload-pages-artifact/releases)
- [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v5)

---
updated-dependencies:
- dependency-name: actions/upload-pages-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 13:10:05 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 26f6cdf9e5 docker(deps): bump python from 3.12-slim to 3.14-slim (#466)
Bumps python from 3.12-slim to 3.14-slim.

---
updated-dependencies:
- dependency-name: python
  dependency-version: 3.14-slim
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 13:07:11 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 96f88594c2 docker(deps): bump node from 20-alpine to 25-alpine (#465)
Bumps node from 20-alpine to 25-alpine.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 25-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-24 13:03:24 +05:30
Mohd Kaif c0d08c46f7 Merge pull request #486 from Hawksight-AI/fix/graph-motion
Fix explorer zooming and Loading Flicker
2026-04-23 19:13:37 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 5a388d0bcc Potential fix for pull request finding 'Useless conditional'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-23 19:02:31 +05:30
KaifAhmad1andZohaibHassan16 f516aef8fd fix(explorer): address PR #486 review blockers + resolve conflict with main
Merge conflict resolution:
- Kept fix/graph-motion's conditional layout-stop (only in focused mode)
  to preserve live layout motion for derived graphs — the core intent of
  this PR.

Must-fix items resolved:

1. plugin.json — removed "hooks": "./hooks/hooks.json" (re-added by this
   branch, already removed in PR #489 on main as it is auto-loaded).
   Kept "agents": "./agents".

2. Double Louvain per render — added groupedViewAvailable useMemo in
   GraphWorkspace (deps: [graphVersion]) that runs community detection
   once. Passed result into resolveDisplayGraph and resolveDisplayStateSnapshot
   via new groupedViewAvailable option; both functions skip their internal
   computeGraphAnalyticsBase call when the value is pre-supplied.

3. graphVersion in displayState deps — removed graphVersion from the
   displayState memo dep array. displayState now depends on the stable
   boolean groupedViewAvailable, not on every ADD_NODE/ADD_EDGE tick,
   so Louvain no longer re-fires on every WebSocket update.

4. hideLabelsOnMove / hideEdgesOnMove flipped to true — intentional:
   suppressing labels and edges during pan reduces visual noise and is
   part of the flicker-reduction fix described in the PR.

Co-authored-by: ZohaibHassan16 <zohaibhassan1696@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-04-23 18:58:48 +05:30
Mohd Kaif c9a382e676 Merge pull request #487 from Sameer6305/feat/explorer-stabilize-local-graph-interaction
feat(explorer): stabilize local graph interaction
2026-04-23 18:28:45 +05:30
KaifAhmad1andSameer6305 897d950bdc fix(explorer): address PR #487 review blockers
1. Prevent active-but-disabled Focused button by only disabling when
   viewMode is not already "focused" (viewMode !== "focused" && !canActivateFocusedMode).
2. Generalize inspector fallback copy — stale/invalid node IDs are not
   necessarily grouped items, so remove the misleading "Activate Focused
   mode" hint.
3. Move pluginRuntimeRef.current read out of render by converting
   canActivateFocusedMode from useMemo to useState + useEffect, resolving
   two ESLint "cannot access refs during render" errors and the missing
   toolbar-memo dependency warning.

Co-authored-by: Sameer6305 <sskadam6305@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-04-23 18:24:31 +05:30
Mohd Kaif dd8fa17db8 Merge pull request #489 from musicload/fix/local-plugin-install
fix(plugin): make local Claude Code plugin install work out of the box
2026-04-23 11:42:20 +05:30
KaifAhmad1 92801c220e docs(plugin): align Claude install commands with marketplace flow 2026-04-23 11:34:38 +05:30
Serge 738480606c fix(plugin): drop hooks field from plugin.json (auto-loaded)
Claude Code auto-loads hooks/hooks.json. Declaring it explicitly in
manifest.hooks causes: 'Duplicate hooks file detected ... already-loaded'.
Same pattern as agents: manifest should only reference *additional* hook
files beyond the default.
2026-04-22 16:03:00 -04:00
Serge f9e0bcf210 fix(plugin): make local plugin install work out of the box
Two separate schema issues blocked `/plugin marketplace add ./plugins`
followed by `/plugin install semantica@semantica-local`:

1. `marketplace.json` was missing the required top-level `owner` object.
   Claude Code rejects with: `owner: Invalid input: expected object,
   received undefined`.

2. `plugin.json` declared `"agents": "./agents"` (string), but Claude
   Code's manifest schema rejects non-array `agents` with:
   `Validation errors: agents: Invalid input`. Auto-discovery from
   the default `agents/` directory works when the field is omitted,
   provided agents are flat `<name>.md` files with frontmatter (Claude
   Code's subagent convention) rather than `<name>/AGENT.md`
   subdirectories.

Changes:
- add `owner` object to `marketplace.json`
- drop `agents` field from `plugin.json` (falls back to auto-discovery)
- rename `agents/<name>/AGENT.md` -> `agents/<name>.md` (frontmatter
  content is unchanged, just the path)

After this, the documented local-install flow succeeds end-to-end.
2026-04-22 15:50:34 -04:00
Sameer6305 bb0e9f49e3 feat(explorer): stabilize local graph interaction 2026-04-23 00:11:38 +05:30
Zohaib Hassnain f95c1612d5 fix blinking and zooming problem 2026-04-22 03:33:09 +05:00
Zohaib Hassnain 8c202a691e fix(explorer): restore live layout motion for derived graphs 2026-04-22 03:31:36 +05:00
Mohd Kaif 304b82fbd6 Merge pull request #483 from ZohaibHassan16/feat/graph-declutter-and-calm
feat(explorer): calm and structurally declutter graph workspace
2026-04-20 17:50:00 +05:30
KaifAhmad1andZohaib Hassnain 8d2dfaa53c docs(changelog): add PR #483 explorer declutter release notes
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>

Co-authored-by: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
2026-04-20 17:33:12 +05:30
KaifAhmad1andZohaib Hassnain 39aaae778f Merge origin/main into feat/graph-declutter-and-calm
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>

Co-authored-by: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
2026-04-20 17:20:47 +05:30
KaifAhmad1andZohaib Hassnain 16d628997a test(explorer): cover graph display declutter flows
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>

Co-authored-by: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
2026-04-20 17:14:40 +05:30
Mohd Kaif 5e6ad6e87e Merge pull request #482 from liling/main
fix(providers): switch DeepSeekProvider from deepseek SDK to OpenAI c…
2026-04-19 20:16:25 +05:30
Mohd Kaif f6198039fa Merge branch 'main' into main 2026-04-19 20:10:22 +05:30
983f5301e8 fix(providers): switch DeepSeekProvider to OpenAI SDK + fix base_url and verbose_mode (closes #482)
- 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>
2026-04-19 20:07:44 +05:30
Mohd Kaif 5a852169be Merge pull request #481 from ZohaibHassan16/feat/optimize-search
Feat/optimize search
2026-04-19 19:04:13 +05:30
KaifAhmad1 fe6ca7fccb fix(search-index): restore secondary-scan node ordering and add regression test 2026-04-19 18:46:05 +05:30
Mohd Kaif 66c8431eee Merge branch 'main' into feat/optimize-search 2026-04-19 18:25:28 +05:30
3e2a0a3f3b docs(changelog): add indexed search performance entry (#481, #467)
Co-authored-by: ZohaibHassan16 <zohaibhassan16@users.noreply.github.com>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
2026-04-19 18:19:00 +05:30
d22a54353a fix(search-index): bisect ops, thread-safe mutation bridge, drop edge upserts
- Replace list.sort() on every upsert with bisect.insort() — O(log n) per
  insert instead of O(n log n); bulk rebuild still sorts once at the end
- Replace list.remove() in remove() with bisect.bisect_left + pop() — O(log n)
  find instead of O(n) scan
- Wrap handle_graph_mutation() index mutations in self._lock — mutation bridge
  fires from a background thread and was racing concurrent search/rebuild calls
- Drop source/target upserts in add_edge() — edges don't change node text so
  the index documents are identical; removes unnecessary cache invalidation
- Sort tag values in _cache_key() — ["a","b"] and ["b","a"] now share a cache
  entry since _passes_filters() uses set intersection (order-independent)
- Restore @app.get("/") root handler missing from this branch vs main

Co-authored-by: ZohaibHassan16 <zohaibhassan16@users.noreply.github.com>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
2026-04-19 18:07:42 +05:30
Mohd Kaif f165679c11 Merge pull request #480 from Sameer6305/fix/provenance-ego-graph
fix(provenance): include upstream ancestors + add direction classific…
2026-04-19 17:50:03 +05:30
17460edca9 docs(changelog): add provenance upstream traversal fix entry (#480, #470)
Co-authored-by: Sameer6305 <sameer6305@users.noreply.github.com>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
2026-04-19 17:36:58 +05:30
7e815920ac fix(provenance): resolve merge conflicts, fix session API, move schemas
- Resolve all merge conflict markers in provenance.py, app.py, .gitignore
- Revert broken session.get_nodes()/get_edges() to session.graph.nodes/edges
- Keep undirected=True ego_graph fix for upstream ancestor traversal
- Add direction field to ProvenanceEdge (upstream/downstream/lateral)
- Group lineage edges in _render_markdown by direction section
- Move ProvenanceNode/ProvenanceEdge/ProvenanceResponse to schemas.py
- Restore complete router import set in app.py (sparql, vocabulary, etc.)

Co-authored-by: Sameer6305 <sameer6305@users.noreply.github.com>
Co-authored-by: KaifAhmad1 <mohammadk78600@gmail.com>
2026-04-19 17:30:03 +05:30
Zohaib Hassnain 7f93eb7104 feat(explorer): calm and structurally declutter graph workspace 2026-04-19 01:13:37 +05:00
Ling Li eec3e8804a fix(providers): add missing verbose_mode assignment in generate_typed 2026-04-19 00:12:25 +08:00
Ling Li 9cb6073568 fix(providers): switch DeepSeekProvider from deepseek SDK to OpenAI client
DeepSeek API is compatible with OpenAI, use the openai SDK instead of
the unmaintained deepseek SDK for better compatibility.
2026-04-18 23:21:49 +08:00
Zohaib Hassnain 073c48882c chore 2 2026-04-17 21:24:45 +05:00
Zohaib Hassnain be86d1b5db chore: remove local benchmark helper 2026-04-17 21:23:51 +05:00
Zohaib Hassnain 6f93f429c4 perf(explorer): add indexed search for large graphs 2026-04-17 21:22:12 +05:00
Mohd KaifandCopilot bc683e7a34 Update semantica/explorer/routes/provenance.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-17 20:48:57 +05:30
Mohd KaifandCopilot cda5310949 Update semantica/explorer/routes/provenance.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-17 20:48:45 +05:30
Mohd KaifandCopilot 17f88ca600 Update semantica/explorer/routes/provenance.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-17 20:47:10 +05:30
Sameer6305 658de23357 fix: resolve merge conflicts with upstream main 2026-04-17 19:56:41 +05:30
Sameer6305 66e8964d22 fix(provenance): include upstream ancestors + add direction classification and markdown grouping 2026-04-17 19:33:12 +05:30
Mohd KaifandClaude Sonnet 4.6 892ff4b4a7 fix(export): fix OWLExporter Turtle invalid syntax and silent data-property omission (#478) (#479)
- 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>
2026-04-17 15:08:59 +05:30
Mohd Kaif a88300d74f Merge pull request #477 from Hawksight-AI/feat/node-distance-semantics-472
feat(explorer): add node distance semantics to PathResponse (#472)
2026-04-16 19:46:42 +05:30
KaifAhmad1 390152c78c feat(explorer): add node distance semantics to PathResponse (#472)
- 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
2026-04-16 17:59:50 +05:30
Mohd Kaif 17602812f9 Merge pull request #476 from Hawksight-AI/feat/bidirectional-path-finding-469
feat(explorer): Bidirectional Path Finding in Knowledge Explorer
2026-04-16 15:29:47 +05:30
KaifAhmad1andClaude Sonnet 4.6 523b02083f feat(explorer): add bidirectional path finding with directed=false param (#469)
- 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>
2026-04-16 15:20:12 +05:30
Mohd Kaif 952a4530f5 Merge pull request #474 from Hawksight-AI/kg
feat(kg): Native `KnowledgeGraph` Support in `KGVisualizer`
2026-04-16 12:26:19 +05:30
KaifAhmad1 2ce5067aa3 docs(changelog): add entry for #471 native KnowledgeGraph support in KGVisualizer 2026-04-16 12:18:38 +05:30
KaifAhmad1 d056e47ab7 feat(kg): add KnowledgeGraph dataclass and native KGVisualizer support (#471)
- 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
2026-04-16 12:15:59 +05:30
Mohd KaifandClaude Sonnet 4.6 8eafd2d024 fix(explorer): replace KeyError/ValueError with HTTPException across all routes, fix temporal pattern method, add SPA root handler (#463)
- 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>
2026-04-15 00:19:19 +05:30
Mohd Kaif 7ba93f6772 Add initialization file for Claude 2026-04-14 23:25:20 +05:30
Mohd Kaif d466203761 Add initialization file for Claude skills 2026-04-14 23:24:39 +05:30
Mohd Kaif 730dea7911 Add initialization comment to semantica file 2026-04-14 23:24:00 +05:30
Mohd KaifandClaude Sonnet 4.6 47764c3033 Utils Explorer Welcome Message, Version Bump & Plugin README Overhaul (#462)
* Clarify plugin README install and usage steps

* feat(explorer): add welcome message to root endpoint and bump version to 0.4.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(plugins): update all plugin READMEs for v0.4.0 with full platform list

- Rewrite main community guide with platform table (8 plugins), skills/agents
  inventory, Knowledge Explorer section, and per-platform install steps
- Add v0.4.0 badge and Knowledge Explorer section to VS Code, Cline,
  Continue, Windsurf, and OpenClaw READMEs
- Fix inconsistent tool count (12 → 17) across all READMEs
- Bump Python requirement from 3.8+ to 3.10+ across all plugins

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add PR description for utils → main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove PR_DESCRIPTION.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 20:32:04 +05:30
Mohd KaifandClaude Sonnet 4.6 055d2fd98d docs: reorganise README integrations and agentic frameworks sections (#461)
- Remove duplicate integrations table from bottom of README
- Move Agentic Frameworks section to top alongside AI tools table
- Show only Agno as supported; list LangChain, LangGraph, CrewAI, LlamaIndex, AutoGen, OpenAI Agents SDK, Google ADK as coming soon
- Add logo icons for all agentic frameworks matching existing plugin style

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 15:08:18 +05:30
Mohd Kaif ce66681715 Delete RELEASE_NOTES.md 2026-04-14 14:11:59 +05:30
Mohd Kaif 655b553262 Delete STRATEGIES_SUMMARY.md 2026-04-14 14:11:37 +05:30
Mohd KaifandClaude Sonnet 4.6 60bf8ec75e feat(integrations): add OpenClaw plugin and integration module (#460)
- Add integrations/openclaw/ with OpenClawKGTool (REST) and
  OpenClawMCPConfig (mcporter.json generator)
- Add plugins/.openclaw-plugin/ bundle (plugin.json, marketplace.json,
  README) with MCP + native tool support
- Add OpenClaw badge to README header
- Reorganize "Works With Every AI Tool" table into labeled groups:
  Native Plugin Bundle, MCP Server + Plugin, MCP Server, REST API

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:27:25 +05:30
Mohd Kaif a1478af9c4 Merge pull request #453 from Hawksight-AI/explorer
feat(explorer): add Semantica Knowledge Explorer UI with full feature…
2026-04-14 11:43:57 +05:30
Mohd Kaif ee4d6a9188 Update CHANGELOG with recent changes and fixes
Updated CHANGELOG to reflect recent fixes and security enhancements, including improvements to KGVisualizer and vulnerability fixes.
2026-04-14 11:21:27 +05:30
Mohd Kaif 2d00257ae5 Merge pull request #459 from Hawksight-AI/visualization
fix(visualization): Accept KnowledgeGraph objects in all `visualize_*` methods
2026-04-14 11:18:41 +05:30
KaifAhmad1andClaude Sonnet 4.6 e78ad7f819 fix(visualization): accept KnowledgeGraph objects in all visualize_* methods (closes #458)
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>
2026-04-14 11:04:25 +05:30
Mohd KaifandClaude Sonnet 4.6 fdb347fe8a feat(cookbook): add Datalog-style reasoning end-to-end notebook (#457)
End-to-end example using DatalogReasoner, GraphBuilder, ContextGraph,
GraphAnalyzer, ExplanationGenerator, DatalogFact, and DatalogRule.
Covers ancestor query, KG dependency analysis, RBAC policy, and org hierarchy.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 21:43:04 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 1cc2f6b93a Potential fix for pull request finding 'Wrong number of arguments in a class instantiation'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:52:23 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 9e26d96b3c Potential fix for pull request finding 'Wrong number of arguments in a call'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:46:50 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 7267425eb5 Potential fix for pull request finding 'Wrong number of arguments in a call'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:46:34 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> d9cf7b0088 Potential fix for pull request finding 'Unused global variable'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:46:14 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 09666806da Potential fix for pull request finding 'Empty except'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:45:55 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 9daddd8186 Potential fix for pull request finding 'Empty except'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:45:40 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> dc8d7ddb03 Potential fix for pull request finding 'Empty except'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:44:44 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> b34634c8b5 Potential fix for pull request finding 'Wrong number of arguments in a call'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:44:28 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> ee93c4bbe1 Potential fix for pull request finding 'Wrong name for an argument in a class instantiation'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:44:13 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> e4425818e4 Potential fix for pull request finding 'Empty except'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-13 17:43:56 +05:30
KaifAhmad1andClaude Sonnet 4.6 7b31304e1e feat(mcp): add modular MCP server package at repo root
Adds a fully self-contained `mcp/` package that exposes Semantica as a
Model Context Protocol server over stdio (JSON-RPC 2.0).

17 tools across 5 domains:
- Extraction: extract_entities, extract_relations, extract_all
- Decision intelligence: record_decision, query_decisions, find_precedents,
  get_causal_chain, analyze_decision_impact
- Knowledge graph: add_entity, add_relationship, search_graph,
  get_graph_summary, get_graph_analytics
- Reasoning: run_reasoning, abductive_reasoning
- Export & provenance: export_graph (JSON/CSV/GraphML/Parquet/RDF), get_provenance

4 resources: semantica://graph/summary, semantica://decisions/list,
semantica://schema/info, semantica://ontology/schema

Package layout:
  mcp/__init__.py + __main__.py  — entry points (python -m mcp)
  mcp/server.py                  — SemanticaMCPServer + stdio event loop
  mcp/session.py                 — lazy ContextGraph singleton
  mcp/schemas.py                 — JSON Schema for all 17 tool inputs
  mcp/tools/{extraction,decisions,graph,reasoning,export}.py
  mcp/resources/registry.py      — URI → handler map
  mcp/README.md                  — per-tool setup (Claude Code, Cursor, Windsurf,
                                   Cline, Continue, VS Code, Amazon Q)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 17:38:23 +05:30
KaifAhmad1andClaude Sonnet 4.6 ab93ec3e8f feat(plugins): add MCP server + 4 new plugin bundles (Windsurf, Cline, Continue, VS Code)
MCP Server (semantica/mcp_server.py):
- Full stdio-based MCP server compatible with Claude Desktop, Windsurf,
  Cline, Continue, VS Code, Roo Code, and any MCP-aware tool
- 12 tools: extract_entities, extract_relations, record_decision,
  query_decisions, find_precedents, get_causal_chain, add_entity,
  add_relationship, run_reasoning, get_graph_analytics, export_graph,
  get_graph_summary
- 3 resources: semantica://graph/summary, semantica://decisions/list,
  semantica://schema/info
- Lazy graph session with optional SEMANTICA_KG_PATH env var
- JSON-RPC 2.0 over stdin/stdout; run with: python -m semantica.mcp_server

New plugin bundles (each: plugin.json + marketplace.json + README.md):
- plugins/.windsurf-plugin/ — Windsurf MCP config + 17 skills + 3 agents
- plugins/.cline-plugin/    — Cline MCP config + 17 skills + 3 agents
- plugins/.continue-plugin/ — Continue MCP config + 17 skills + 3 agents
- plugins/.vscode-plugin/   — VS Code MCP config + 17 skills + 3 agents

Updated plugins/.claude-plugin/README.md:
- Platform support table expanded to 9 tools
- Full MCP server section: per-tool config snippets for Claude Desktop,
  Windsurf, Cline, Continue, VS Code; tool/resource reference tables;
  environment variables

Updated README.md:
- Hero line updated to mention MCP server
- Visual grid: Windsurf/VS Code/Cline/Continue → 'MCP server + plugin';
  Claude Desktop → 'MCP server'
- Plugin Bundles section: expanded table listing all 7 bundles with dirs
- New MCP Server section with quick-start snippet and tool/resource list
- Detailed integrations table: corrected connection types and config paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:59:45 +05:30
KaifAhmad1andClaude Sonnet 4.6 f2eb3e1608 docs(readme): accurate plugin/integration/API docs based on actual code
Tools grid:
- Claude Code/Cursor/Codex: 'Native plugin' (plugins/ dirs exist in repo)
- All other tools: 'REST API' (no MCP server impl in codebase — Semantica
  has an MCP CLIENT for ingesting from MCP servers, not an MCP server)
- Codex CLI added back (has real plugin bundle at plugins/.codex-plugin/)

Plugin Bundles section:
- Full table of all 17 skills with descriptions matching SKILL.md files
- Full table of all 3 agents (kg-assistant, decision-advisor, explainability)
- Hooks entry referencing plugins/hooks/hooks.json

MCP Client section:
- Correct framing: MCPClient in semantica/ingest/mcp_client.py pulls
  data FROM MCP servers into KG (not an MCP server itself)
- Code snippet + supported schemes

REST API Server section:
- Lists all 10 route modules from semantica/explorer/routes/ with paths
- WebSocket /ws endpoint
- Health check

Agno integration section:
- Expanded to table showing all 5 actual files in integrations/agno/
  with class names and descriptions matching source code

AI Coding Tools table:
- Corrected connection types and setup notes to match actual code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:51:34 +05:30
KaifAhmad1andClaude Sonnet 4.6 898a660ca7 docs(readme): add explicit integrations table for all 16 AI tools + expand sections
- Add 'AI Coding Tools & IDEs' table under Integrations listing every
  tool from the visual grid with connection type and setup note:
  Claude Code, Cursor, Windsurf, Claude Desktop, VS Code, GitHub
  Copilot, Cline, Roo Code, Continue, Goose, Kilo Code, Aider,
  Amazon Q, Zed, Claude SDK, REST API (109 endpoints)
- Add Neo4j to Graph Databases list (was in modules but missing here)
- Add Email and Repository ingestors to Data Sources
- Expand LLM Providers: add Groq, HuggingFace, Ollama entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:25:02 +05:30
KaifAhmad1andClaude Sonnet 4.6 fb1e6a6d6e docs(readme): revise tools grid with accurate popular integrations
AI tools grid (removed Gemini CLI, Codex CLI; added VS Code, GitHub
Copilot, Continue, Amazon Q, Zed — all confirmed MCP-supporting tools
with significant user bases in 2026):
Row 1: Claude Code, Cursor, Windsurf, Claude Desktop, VS Code,
        GitHub Copilot, Cline, Roo Code
Row 2: Continue, Goose, Kilo Code, Aider, Amazon Q, Zed,
        Claude SDK, Any agent REST API

Agentic frameworks grid (added LangGraph and OpenAI Agents SDK, expanded
to 8 entries): Agno, LangChain, LangGraph, LlamaIndex, AutoGen, CrewAI,
OpenAI Agents SDK, Google ADK

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:11:00 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 8d0dce13c5 ci(deps): bump softprops/action-gh-release from 1 to 3 (#455)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 15:58:50 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 61c8435bd7 ci(deps): bump actions/github-script from 8 to 9 (#454)
Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 15:21:15 +05:30
KaifAhmad1andClaude Sonnet 4.6 0d7b9ca1df docs(readme): add Semantica Knowledge Explorer section to main README
- New '🖥️ Semantica Knowledge Explorer' section placed after Plugins,
  with a workspace-tab table (Graph, Timeline, Decisions, Registry,
  Entity Resolution, KG Overview, Ontology), a 4-line quick-start
  snippet, requirements line, and a pointer to explorer/README.md
- Added explorer/ row to the detailed Modules table with a link
- Added explorer/ bullet to the condensed Modules list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:59:21 +05:30
KaifAhmad1andClaude Sonnet 4.6 aca15d3694 docs(explorer): replace default Vite README with full local setup guide
Covers requirements (Node 18+/Python 3.8+), backend start command,
npm install, dev server, all 6 workspace tabs, available npm scripts,
API/WebSocket proxy table, production build, troubleshooting steps,
and tech stack summary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:30:27 +05:30
KaifAhmad1andClaude Sonnet 4.6 670027fd22 fix(explorer): resolve 3 code-review bugs in GraphWorkspace, DecisionWorkspace, and index.css
- GraphWorkspace: set isRunningPredictions=true before link-prediction fetch
  and false in finally block; pass isRunningPredictions prop to
  LazyGraphInspectorPanel so the inspector button disables and shows a
  spinner during the request (was declared but never wired — broke
  noUnusedLocals TypeScript build)

- DecisionWorkspace: add AbortController to the /api/decisions useEffect
  so the fetch is cancelled on unmount; add per-call AbortController to
  handleSelectDecision for /api/decisions/:id/chain; add res.ok guards
  before .json() on both fetches; encodeURIComponent on decision_id to
  prevent path-injection edge cases

- index.css: add missing @keyframes skeleton-pulse rule (0%/100% opacity
  0.45, 50% opacity 0.85) — KGOverviewTab skeletonBarStyle referenced
  this animation but it was never defined, leaving skeleton bars static

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:16:59 +05:30
KaifAhmad1andClaude Sonnet 4.6 3ea1283626 feat(explorer): add Semantica Knowledge Explorer UI with full feature set
## Folder & Project
- Renamed `semantica-explorer/` → `explorer/` (cleaner path)
- Browser tab title: `Semantica Knowledge Explorer`
- Brand pill: `SEM` → `SKE` (tooltip: Semantica Knowledge Explorer)
- Nav rail label: `Explore` → `Knowledge Explorer`
- package.json name: `semantica-knowledge-explorer`
- Downgraded Vite 8 → Vite 5 for Node v20.17.0 compatibility

## App Shell
- Dynamic per-workspace kicker labels replacing static "Workspace" pill:
  Graph Studio · Vocabulary Browser · Reasoning Engine · SPARQL Query ·
  Decision Intelligence · Knowledge Audit · Graph Governance

## Enrich Workspace — 2 new tabs
### Entity Resolution tab
- Similarity threshold slider (0.50–0.99)
- Run Dedup Scan → POST /api/enrich/dedup
- Flagged pairs list with colour-coded score bars (red/amber/green)
- Expandable inline diff: primary vs duplicate side-by-side
- One-click Merge → POST /api/enrich/merge with logEvent dispatch
- Dismiss per pair; Clear all button
- Merge history sidebar pulled live from Registry store

### Registry tab (Document Registry)
- Live chronological audit log of all KG mutations in-session
- Colour-coded op-type badges: IMPORT · MERGE · ADD NODE · ADD EDGE ·
  INFER · DELETE · EXPORT · VOCAB
- Filter pills to narrow by operation type
- Expandable JSON detail rows per entry
- Clear log button
- Entirely client-side via registryStore (no backend needed)

## Manage Workspace — 2 new tabs
### KG Overview tab
- Stats chips: total nodes, edges, graph density
- Node type breakdown bar chart (up to 8 types, colour-coded)
- Edge type breakdown bar chart from /api/graph/stats
- Top-10 most connected nodes ranked by degree
- Skeleton loading states + Refresh button

### Ontology Summary tab
- Read-only SKOS scheme tree (scheme → top concepts → narrower)
- Concept detail panel: labels, notation, description, narrower nav
- "Open Full Browser" button deep-links to Vocabulary Browser tab

## Decision Workspace polish
- CausalFlowDiagram: vertical node cards connected by relationship pills
- Outcome badges: colour-coded (green=approved, red=rejected, amber=deferred)
- Live filter input across decision ID, category, and outcome
- Animated skeleton loading while list fetches

## Graph Inspector polish
- PathFlowViz: clickable node chips connected by edge-type labels;
  clicking a chip focuses that node in the canvas
- Link Prediction button shows spinner while computing
- Empty states for path trace and candidate links sections

## Registry dispatch — WebSocket
- ADD_NODE events → logEvent("add-node", …) in GraphWorkspace WS handler
- ADD_EDGE events → logEvent("add-edge", …) in GraphWorkspace WS handler
- Import, Export, Merge already dispatched logEvent on API response

## Graph visibility overhaul
### Edge colours (were nearly transparent, now clearly visible)
- edgeBackbone:    rgba(…, 0.04)  → rgba(…, 0.38)
- edgeStructure:   rgba(…, 0.009) → rgba(…, 0.28)
- edgeInspection:  rgba(…, 0.026) → rgba(…, 0.48)
- Muted edges:     0.009–0.02    → 0.12–0.26
- Focus edges:     0.16          → 0.42

### Edge sizes
- default minSize: 0.18 → 0.9 (always at least 1 pixel wide)
- path minSize:    1.8  → 2.4
- inactive/muted:  hide:true → hide:false (dimmed not hidden)

### Node sizes
- default sizeMultiplier: 0.72 → 0.92
- default minSize:        0.68 → 3.5 (visible at all zoom levels)
- overview nodeScale:     0.66 → 0.88
- nodeTintMix (colour):   0.03 → 0.14
- nodeCoreMix (brightness): 0.52 → 0.72

### Label budget
- overview:   10  → 28 labels
- structure:  36  → 60 labels
- inspection: 80  → 120 labels

### Sigma settings
- renderEdgeLabels:        false → true  (relationship type on every edge)
- edgeLabelSize:           —    → 10
- labelRenderedSizeThreshold: 4 → 2
- labelDensity:            0.86 → 1.1
- hideLabelsOnMove:        true → false (labels stay visible while panning)
- hideEdgesOnMove:         true → false (edges stay visible while panning)
- minCameraRatio:          —    → 0.04 (prevents zooming inside a node)
- maxCameraRatio:          —    → 8    (graph stays visible when zoomed out)

### Zoom controls
- Added Zoom In (+) and Zoom Out (−) buttons to graph toolbar
- Smooth animated zoom via camera.animatedZoom / animatedUnzoom (200ms)
- Mouse scroll wheel clamped between minCameraRatio and maxCameraRatio

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 13:40:40 +05:30
Mohd Kaif b313604bde Merge pull request #452 from Hawksight-AI/security-enhancement
Security Enhancement — Fix 12 Vulnerabilities (CRITICAL → LOW)
2026-04-12 16:08:51 +05:30
Mohd KaifandCopilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> 5e6df93f64 Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-04-12 15:56:15 +05:30
Mohd KaifandCopilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> 920c0e55d5 Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-04-12 15:34:50 +05:30
Mohd KaifandCopilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> 7de2a2eb5e Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-04-12 14:53:52 +05:30
KaifAhmad1andClaude Sonnet 4.6 ce60acb294 docs(changelog): add security-enhancement PR entries to [Unreleased]
Documents all 12 vulnerability fixes (CRITICAL→LOW), 4 post-review bug
fixes, and CodeQL infrastructure changes under [Unreleased] following
the existing Keep a Changelog format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 14:40:12 +05:30
KaifAhmad1andClaude Sonnet 4.6 4acdefd4b8 fix: address 4 post-review bugs from security-enhancement PR
fix(agent_memory): implement MemoryItem.to_dict() / from_dict() for safe JSON
  persistence — timestamps serialised via isoformat(), embeddings dropped (not
  JSON-safe, regenerated on demand); save() and load() now round-trip correctly
  without TypeError or AttributeError (Bug #1)

fix(sparql): add asyncio.Semaphore(_SPARQL_MAX_CONCURRENT=4) around graph.query
  so timed-out threads cannot exhaust the default ThreadPoolExecutor; add
  `truncated: bool` field to SparqlResponse so callers know when the 5 000-row
  cap was hit (Bug #2)

fix(export_import): trim _ALLOWED_IMPORT_EXTENSIONS to {.json, .csv} — the only
  formats the handler actually parses; removes .graphml/.gexf/.ttl/.rdf that
  passed the allowlist check but hit a hard 422 inside the handler (Bug #3)

fix(codeql): remove blanket rule-ID auto-dismiss job; replace with a commented
  template for pinning specific alert numbers — prevents future real alerts of
  the same rule being silently suppressed (Bug #4)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 14:35:30 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> a16cb9c468 Potential fix for pull request finding 'Unused import'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-12 14:17:55 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 1bdaad9c59 Potential fix for pull request finding 'Unused import'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-12 14:14:20 +05:30
Mohd KaifandCopilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> db00a3d1ad Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-04-12 14:14:04 +05:30
KaifAhmad1andClaude Sonnet 4.6 d8b8ae634b security: fix 12 vulnerabilities across CRITICAL→LOW severity
Closes CodeQL alerts #12, #13, #14, #15, #16, #17, #18

CRITICAL
- fix(media_parser): replace eval() with fractions.Fraction for fps parsing (CWE-95)
- fix(agent_memory): replace pickle serialization with JSON to prevent RCE (CWE-502)

HIGH
- fix(snowflake_ingestor): parameterize LIMIT/OFFSET, validate ORDER BY with regex,
  reject semicolons in WHERE to prevent SQL injection (CWE-89)
- fix(rdf_parser): add defusedxml XXE protection for RDF/XML format parsing (CWE-611)
- fix(server): add CORSMiddleware, security response headers middleware
  (X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy,
  Permissions-Policy, HSTS), and global error handler (CWE-346, CWE-200)
- fix(explorer/app): narrow CORS to specific methods/headers, redact exception
  messages in HTTP error handlers, enforce 64 KB WebSocket message size cap (CWE-346)

MEDIUM
- fix(graph): replace free-text algorithm param with _PathAlgorithm enum (CWE-20)
- fix(vocabulary): validate uploaded file extensions against allowlist (CWE-434)
- fix(llm_extraction): json.dumps() all user content in LLM prompts to block
  prompt-injection attacks (CWE-1336)
- fix(pipeline_validator): replace __import__("collections") with proper import (CWE-95)

LOW
- fix(sparql): cap results at 5 000 rows and enforce 30-second query timeout (CWE-400)
- fix(export_import): validate file extension + enforce 50 MB upload limit (CWE-434)

CodeQL / scanning
- feat(codeql): add .github/codeql/codeql-config.yml to exclude generated
  cookbook HTML bundles (Plotly + MapLibre) from JS scanning
- feat(codeql): extend dismiss-fixed-alerts job with all new rule IDs
  (py/path-injection, py/polynomial-redos, js/incomplete-url-substring-sanitization,
  js/insecure-randomness, js/prototype-pollution-utility)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 13:41:31 +05:30
Mohd Kaif cdb26aab3b Merge pull request #420 from ZohaibHassan16/feat/explorer-vocab-ui
feat(explorer): add initial UI for SKOS Vocabulary Workspace
2026-04-11 20:56:42 +05:30
KaifAhmad1andClaude Sonnet 4.6 f4db4469ba chore: untrack remaining generated Vite bundles from git
semantica/static/ is already in .gitignore but the 19 newly-hashed
build artifacts introduced by the main merge were still tracked.
Runs git rm --cached to complete the untracking so future frontend
builds do not create dirty working-tree diffs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 20:11:38 +05:30
KaifAhmad1andClaude Sonnet 4.6 98453cab5d docs(changelog): add PR #420 explorer blocker and security fix entries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 19:54:21 +05:30
KaifAhmad1andClaude Sonnet 4.6 1fde71768d fix(explorer): resolve blockers and significant issues from PR #420 review
Blockers fixed:
- Rename DockerFile → Dockerfile (case-sensitive fix for Linux CI/Docker)
- Fix Docker CMD: semantica.server:app → semantica.explorer.app:app
- Add module-level app = create_app() so uvicorn can reference the ASGI app
- Remove pre-built static assets from git; add semantica/static/ to .gitignore

Security / correctness fixes:
- Fix CORS default from "*" to localhost:5173 (explicit env var still overrides)
- Add guard to get_ws_manager() — returns 503 instead of AttributeError when unset
- Restrict SPARQL endpoint to read-only query types (SELECT/ASK/CONSTRUCT/DESCRIBE)
- Add 10 MB upload size limit to vocabulary import route
- Add JSON-LD format auto-detection (.jsonld / .json-ld / .json) in vocabulary import

Code quality fixes:
- Replace O(N) annotation scan in create_annotation with O(1) get_annotation() lookup
- Add get_annotation(ann_id) method to GraphSession
- Add self-loop guard in batchMergeEdges (graph has allowSelfLoops: false)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 19:13:40 +05:30
Mohd Kaif e00bdfe8d4 Merge branch 'main' into feat/explorer-vocab-ui 2026-04-11 18:01:45 +05:30
Mohd Kaif 9e31e8d746 Merge pull request #451 from Hawksight-AI/triplet-store
fix(triplet-store): resolve entity/class/property IRIs against ontolo…
2026-04-11 17:09:39 +05:30
KaifAhmad1 9d680d4369 docs(changelog): add TripletStore namespace IRI resolution and regression fix entries for PR #447 2026-04-11 17:04:08 +05:30
KaifAhmad1 9d0744e20e fix(triplet-store): coerce non-string IDs and guard known vocabulary prefixes in _resolve_iri
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.
2026-04-11 16:51:39 +05:30
KaifAhmad1 0b52b715dc fix(triplet-store): resolve entity/class/property IRIs against ontology namespace base_uri (Fixes #447)
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
2026-04-11 15:47:42 +05:30
Mohd Kaif 745927d674 Merge pull request #450 from Hawksight-AI/triplet-store
Fix Blazegraph literal serialization in bulk loader (Fixes #448)
2026-04-11 15:28:50 +05:30
KaifAhmad1 af401c8566 docs(changelog): add Blazegraph literal serialization and SPARQL injection fix entries for PR #448 2026-04-11 15:21:42 +05:30
KaifAhmad1 2e2dae558f fix(blazegraph): expand prefixed datatypes and validate lang/datatype metadata
- 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
2026-04-11 15:16:15 +05:30
KaifAhmad1 3a1a798107 Fix Blazegraph literal serialization in bulk loader (Fixes #448) 2026-04-11 14:58:51 +05:30
Mohd Kaif a4b17dd72b Merge pull request #449 from Hawksight-AI/ontology
fix(ontology): preserve user-facing schema fields in OWL generation\n…
2026-04-11 14:09:09 +05:30
KaifAhmad1 9366f07239 test(ontology): assert ontology uri prefix is used for generated IRIs 2026-04-11 13:52:04 +05:30
Mohd Kaif 61676fb321 Merge branch 'main' into ontology 2026-04-11 13:39:10 +05:30
KaifAhmad1 1ea5e5c012 docs(changelog): resolve duplicate snapshot headers and clean unreleased formatting 2026-04-11 13:37:59 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 490d9c814b Potential fix for pull request finding 'Unused import'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-11 13:11:10 +05:30
KaifAhmad1 d2c20d410c fix(ontology): address #446 follow-up review findings\n\n- prefer label over name for generated IRIs\n- fix datatype range list handling in rdflib path\n- align generated IRIs with ontology uri namespace\n- resolve local subclassOf names to class IRIs\n- expand regression coverage and update changelog 2026-04-11 13:02:49 +05:30
KaifAhmad1 67a8ab1a8e fix(ontology): preserve user-facing schema fields in OWL generation\n\nFixes #446 2026-04-11 12:39:46 +05:30
Zohaib Hassnain dfd7785cc1 feat: overhaul graph explorer visuals and loading flow 2026-04-11 03:19:28 +05:00
Mohd Kaif ac4a200f26 Merge pull request #441 from Hawksight-AI/docs
Add manual ontology + Snowflake mapping cookbook
2026-04-09 15:28:44 +05:30
KaifAhmad1andClaude Sonnet 4.6 c1f0cf6f34 Fix 3 bugs in notebook 13 (manual ontology + Snowflake mapping)
- Bug 1: replace dict .get() with dataclass attribute access on
  AssociativeClass (name/connects/temporal/properties)
- Bug 2: add full URI to every ontology property and use BASE_URI-prefixed
  URIs for all relationship types so TripletStore stores hr:<name>
  instead of urn:property:<name>, fixing SPARQL PREFIX hr: queries
- Bug 3: filter None values from EmploymentEvent properties dict so
  open-ended employment does not store the literal string "None" as endDate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 15:23:19 +05:30
KaifAhmad1andClaude Sonnet 4.6 665f9c080e Add manual ontology + Snowflake mapping cookbook
Adds notebook 13 demonstrating pythonic, no-AI-inference workflow:
hand-designed ontology dict, AssociativeClass reification, explicit
row-to-graph mapping, OWL/SHACL export, and SPARQL query patterns.
Includes SPARQL 1.2 / SHACL 1.2 standards coverage notes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 15:14:19 +05:30
Zohaib Hassnain 6e6b190da1 perf(explore): split GraphWorkspace into lazy subchunks 2026-04-09 14:22:15 +05:00
Zohaib Hassnain e03ba5685c feat(graph): add opt-in exploration effects panel 2026-04-09 13:59:44 +05:00
Mohd Kaif 8d32932322 Clarify plugin README install and usage steps (#440) 2026-04-09 13:19:15 +05:30
Mohd Kaif 7a5e8fd981 Merge pull request #439 from Hawksight-AI/utils
Add Claude Skill support, plugin manifests, and plugin folder updates
2026-04-09 12:58:01 +05:30
KaifAhmad1 082ab14d2e Mention cross-platform plugins in main README 2026-04-09 12:39:24 +05:30
KaifAhmad1 14d350378f Expand plugin README for community usage 2026-04-09 12:31:31 +05:30
KaifAhmad1 241a24d75d Expand plugin keywords for domain discovery 2026-04-09 12:22:24 +05:30
KaifAhmad1 b2eb5db87f Align plugin manifests and marketplaces with current docs 2026-04-09 12:18:38 +05:30
KaifAhmad1 74d5980215 Fix causal and explain skill API examples 2026-04-09 12:04:26 +05:30
Zohaib Hassnain 1af17f3398 feat: productized explorer workspace 2026-04-09 03:17:12 +05:00
Zohaib Hassnain c964e11d38 feat(graph): add rich element rendering system 2026-04-09 02:42:40 +05:00
Zohaib Hassnain 8829aa5ce2 feat(graph): add plugin host for graph tools 2026-04-09 02:21:35 +05:00
Zohaib Hassnain 102274c668 refactor(graph): add typed theme system and first-class behavior modules 2026-04-09 01:30:33 +05:00
KaifAhmad1 3b400eb88b Remove write_missing_skills.py utility file as requested 2026-04-08 22:56:43 +05:30
KaifAhmad1 678d891b42 Fix plugin hooks JSON, align Skill docs with repo API, and make skill generation portable 2026-04-08 22:55:11 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> e60cef9eb7 Potential fix for pull request finding 'File is not always closed'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-08 22:47:58 +05:30
KaifAhmad1 79a980d956 Add Claude Skill support, plugin manifests, and plugin folder updates 2026-04-08 22:24:09 +05:30
Mohd Kaif 47828cff0d Restore 'What's New in v0.4.0' section
Reintroduce the 'What's New in v0.4.0' section with detailed features of the Temporal Intelligence Stack.
2026-04-08 19:43:46 +05:30
Mohd Kaif 17289121cb Update README.md 2026-04-08 14:27:08 +05:30
Mohd Kaif b670bc32a4 Refactor Modules section in README
Reorganized and reformatted the Modules section in the README to improve clarity and consistency.
2026-04-08 14:17:14 +05:30
Mohd Kaif 5af6e383ad Merge pull request #438 from Hawksight-AI/docs
Docs Improve README — crisp bullets, plain English, v0.4.0 features
2026-04-08 14:12:45 +05:30
KaifAhmad1andClaude Sonnet 4.6 cd70481034 fix(docs): align all README code examples with actual semantica API
Audited every module's __init__.py and source files. Fixes:

1. Temporal GraphRAG example — was garbled (two sections merged into one
   code block). Restored clean single example with correct imports.

2. Semantic extraction — extract_entities/extract_relations/extract_triplets
   are not standalone functions; replaced with correct class-based API:
   NERExtractor().extract_entities(), RelationExtractor().extract_relations(),
   TripletExtractor().extract_triplets(). extract_relations_llm is only in
   semantica.semantic_extract.methods (not re-exported from __init__) and
   requires entities as its required second positional arg — fixed both.

3. ReteEngine — add_rule() and match() do not exist on ReteEngine.
   Replaced with correct API: Rule/Fact dataclasses + build_network([rule])
   + add_fact(fact) + match_patterns().

4. PipelineBuilder — add_stage(name, callable) does not exist; replaced
   with add_step(name, type_str, **config). with_parallel_workers() does not
   exist; replaced with set_parallelism(n). Pipeline.run() takes no
   input_path; removed that kwarg.

5. ProvenanceTracker.track_entity — source_url is not a valid kwarg;
   second param is positional source. Fixed in features list and comment.

6. Leftover SHACL section — removed second copy of the SHACL code block
   that still referenced to_shacl(), export_shacl(), validate_graph() which
   do not exist on OntologyEngine (confirmed in engine.py).

7. Duplicate pip install lines — semantica[shacl] and semantica[db-snowflake]
   appeared twice in the installation block; removed duplicates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:55:57 +05:30
KaifAhmad1andClaude Sonnet 4.6 1cf13d0188 fix(docs): remove duplicate vector_store kwarg in docs/index.md quick-start example
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:43:53 +05:30
KaifAhmad1andClaude Sonnet 4.6 069af2a038 fix(docs): resolve 4 Qodo bot review bugs in README and docs/index.md
Bug 1 — Broken snapshot example:
- Replace graph.add_decision(category=...) with graph.record_decision()
  which accepts keyword args (add_decision expects a Decision object)
- Define context = AgentContext(...) before calling context.checkpoint()
  and context.diff_checkpoints() — these APIs live on AgentContext, not ContextGraph

Bug 2 — Invalid KG example imports:
- Remove KnowledgeGraph, Entity, Relationship, CentralityAnalyzer — not exported
- Replace with GraphBuilder.build() (dict-based API) and CentralityCalculator
  which are the actual public exports from semantica.kg
- Fix pipeline example: KnowledgeGraph() → GraphBuilder()

Bug 3 — Nonexistent SHACL APIs:
- Remove export_shacl() and validate_graph() calls — not on OntologyEngine
- Rewrite SHACL section to use real APIs: from_data(), export_owl(),
  validate(), from_text(), to_owl()
- Remove semantica[shacl] install instructions (extra not in pyproject.toml)

Bug 4 — Stale docs version badge:
- docs/index.md: bump version badge and release tag link from v0.3.0 → v0.4.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:43:33 +05:30
Mohd Kaif 35ebccbdd7 Merge branch 'main' into docs 2026-04-08 13:19:56 +05:30
KaifAhmad1andClaude Sonnet 4.6 de432d5eb4 docs: improve README with crisp bullets, plain English, and v0.4.0 features
- Replace dense tables with scannable bullet points throughout
- Add plain-English descriptions before each feature section
- Update What's New to cover full v0.4.0 temporal stack, SKOS, SHACL, and fixes
- Add learn-more references linking to docs and cookbook per section
- Slim code examples to focused real-world scenarios, remove API-dump patterns
- Fix duplicate badges, bump version badge to 0.4.0
- Fill empty Learning Resources section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 13:13:01 +05:30
KaifAhmad1andClaude Sonnet 4.6 1d04005edf chore: release v0.4.0
Bump version to 0.4.0, move [Unreleased] changelog entries to [0.4.0]
(2026-04-08), and remove duplicate changelog content appended in prior
merges. Release covers temporal data model, SHACL, SKOS, Knowledge
Explorer API, Agno integration, Named Graphs, Datalog Reasoner, and
many more features landed since 0.3.0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 10:45:37 +05:30
Mohd Kaif 282418c953 Merge pull request #436 from Hawksight-AI/utils
fix: Correct Three Test Failures in Unreleased Changelog Test Suite
2026-04-07 17:56:50 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> c77690129d Potential fix for pull request finding 'Imprecise assert'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-07 17:53:26 +05:30
KaifAhmad1andClaude Sonnet 4.6 655a24b77e fix: correct three test failures in unreleased changelog test suite
- 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>
2026-04-07 17:42:08 +05:30
Zohaib Hassnain 38b766298e feat(explorer): harden knowledge explorer backend and frontend, polish dashboard UX 2026-04-07 02:04:17 +05:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ba050acc7d ci(deps): bump github/codeql-action from 3 to 4 (#435)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 15:01:24 +05:30
Zohaib Hassnain c0f106dc1e feat(explorer): implement Phase 4 & 5 : Temporal Engine and Power-User Suite
Phase 4: Time Travel & Decisions
- Integrated Temporal Scrubber (TimelinePanel.tsx) with high-speed WebGL filtering.
- Implemented Decision Tree Viewer with recursive causal chain visualization.

Phase 5: Power-User Tools
- Built SPARQL Engine with Monaco Editor UI and rdflib backend integration.
- Implemented PROV-O Lineage swimlanes using React Flow with custom layout math.
- Developed side-by-side Entity Diff/Merge tool with Amber-highlighting.
- Expanded Import/Export suite for robust JSON/CSV dataset ingestion.
- Refactored temporal routes for delta-only ID snapshots.
2026-04-05 15:05:53 -07:00
ZohaibHassan16 98a2cf9490 feat(ui): complete graph visualization overhaul
This commit transforms the raw 150k-element graph into a high-performance, exploratory UI:

- Implemented Universal Sizing (logarithmic scale based on node degree) and a Procedural Color Mapper (string hashing) to automatically size and colorize categorical data.
- Built the 'Focus Mode' engine using Sigma reducers. Hovering or clicking a node instantly isolates it and its 1-hop neighbors while muting the canvas, eliminating visual noise.
- Applied an enterprise-grade visual style, featuring deep radial background gradients, structural grid overlays, and a sliding glassmorphism metadata HUD.
- Shifted from DOM-bound state mutations to direct WebGL render pipelines to maintain visual performance.
2026-04-03 00:20:52 +05:00
Mohd Kaif 8faa87dace Merge pull request #434 from Hawksight-AI/utils
Utilsfix: add_decision kwargs support and quickstart VectorStore backend
2026-04-02 20:45:51 +05:30
Mohd KaifandCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> 4747d403bc Potential fix for pull request finding 'Syntax error'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-04-02 20:41:33 +05:30
Mohd Kaif 8348df63be Merge branch 'main' into utils 2026-04-02 20:37:10 +05:30
KaifAhmad1andClaude Sonnet 4.6 68b8b370d6 fix: address PR #434 code-quality review findings
- 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>
2026-04-02 20:34:59 +05:30
KaifAhmad1andClaude Sonnet 4.6 f8ec5ac010 test: cover add_decision kwargs form and VectorStore inmemory backend
- 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>
2026-04-02 20:25:34 +05:30
KaifAhmad1andClaude Sonnet 4.6 40fe1d587a chore: remove docs/bugs folder
Not needed — issue tracked in #433 and fix is self-contained in the code and existing docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 20:20:19 +05:30
KaifAhmad1andClaude Sonnet 4.6 29a608f60e fix: add_decision kwargs support and quickstart VectorStore backend
Fixes #433

- ContextGraph.add_decision() now accepts keyword arguments (category,
  scenario, reasoning, outcome, confidence, entities, decision_maker)
  in addition to a Decision object, matching documented behaviour.
  Both call forms return the decision ID string.
- Quickstart snippets in README, getting-started.md, and index.md
  changed from VectorStore(backend="faiss") to VectorStore(backend="inmemory")
  so they work without faiss-cpu installed.
- docs/reference/context.md methods table updated to reflect the dual
  signature of add_decision().
- docs/bugs/quickstart_api_mismatch.md added to track the issue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 20:16:56 +05:30
Mohd Kaif 907f0e8f45 Merge pull request #432 from Sameer6305/feature/named-graph-support
Feature/named graph support
2026-04-02 19:03:38 +05:30
KaifAhmad1 0c213f1483 docs(changelog): add PR #432 follow-up fixes 2026-04-02 18:14:18 +05:30
KaifAhmad1andSameer6305 a51542ce40 fix: address named-graph review findings
- 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>
2026-04-02 18:10:11 +05:30
Sameer6305 08150fb2f7 docs: update named graph usage 2026-04-02 15:46:41 +05:30
Sameer6305 ce01067009 test: add graph isolation tests 2026-04-02 15:46:41 +05:30
Sameer6305 a896c36389 feat: add config for graph URIs 2026-04-02 15:46:41 +05:30
Sameer6305 5f55e9b363 feat: support named graphs in QueryEngine 2026-04-02 15:46:41 +05:30
Sameer6305 25999076df feat: add graph parameter to TripletStore 2026-04-02 15:46:41 +05:30
Mohd Kaif 2dbc50a2fe Merge pull request #431 from ZohaibHassan16/fix/cg-pagination
Fix/cg pagination
2026-04-02 15:06:02 +05:30
790ff71c0a docs(changelog): add PR #431 ContextGraph pagination & edge integrity fixes
Co-Authored-By: KaifAhmad1 <KaifAhmad1@users.noreply.github.com>
Co-Authored-By: ZohaibHassan16 <ZohaibHassan16@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 14:58:21 +05:30
af57e5269d fix(context): resolve sorted() TypeError and stats() pagination mismatch
- Guard sorted() in find_nodes/find_active_nodes against non-string node
  IDs (None/int) that raise TypeError when mixed types enter node_type_index
- Update stats() to count only structurally valid nodes (node_id truthy)
  and edges (source_id and target_id both set), matching what find_nodes/
  find_edges actually return so frontend page-count calculations are correct

Co-Authored-By: KaifAhmad1 <KaifAhmad1@users.noreply.github.com>
Co-Authored-By: ZohaibHassan16 <ZohaibHassan16@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 14:56:43 +05:30
ZohaibHassan16 9203c2d684 feat(ui): complete phase 2 massive graph rendering and api alignment 2026-04-01 23:51:42 +05:00
ZohaibHassan16 88309da972 fix(graph): resolve edge ID mapping 2026-04-01 23:41:32 +05:00
ZohaibHassan16 719063e781 Merge branch 'fix/cg-pagination' into feat/explorer-vocab-ui 2026-04-01 12:18:08 +05:00
ZohaibHassan16 b0947df934 fix(context): optimize ContextGraph pagination with lazy evaluation 2026-04-01 12:13:35 +05:00
Mohd Kaif a036c4405b Merge pull request #429 from Hawksight-AI/security-enhancement
fix(security): resolve CodeQL alerts #4, #5, #9, #10
2026-03-31 15:51:10 +05:30
KaifAhmad1andClaude Sonnet 4.6 0365712a8b fix(security): address review feedback on ReDoS and URL pattern fixes
- fix(redos) #10: replace regex with string method check to fully
  eliminate backtracking — name[0].isupper() + simple ^[A-Za-z0-9]+$
  removes all nested repetition that caused exponential backtracking
- fix(url-pattern) #9: restore /, ?, =, :, @, # and other RFC 3986
  chars to URL regex; previous fix truncated URLs to hostname only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 15:40:29 +05:30
KaifAhmad1andClaude Sonnet 4.6 f7170cd6df fix(security): resolve CodeQL alerts #4, #5, #9, #10
- fix(redos) #10: replace capturing group with non-capturing group in
  naming_conventions.py to eliminate exponential backtracking (py/redos)
- fix(html-filter) #4: update script/iframe end-tag regex to match
  tags with trailing attributes e.g. </script foo="bar"> (py/bad-tag-filter)
- fix(regex-range) #9: replace overly broad [$-_] character range with
  explicit safe-char list in email_ingestor.py URL pattern (py/overly-large-range)
- fix(info-exposure) #5: replace str(exc) with a generic error message
  and log the full stack trace server-side in export_import.py (py/stack-trace-exposure)

Closes #4, Closes #5, Closes #9, Closes #10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 15:20:07 +05:30
ZohaibHassan16 60c00fb5c2 feat(explorer): implement phase 1: single-server deployment and dockerization 2026-03-31 13:47:49 +05:00
ZohaibHassan16 1b277dcdd7 feat(ui): wire TanStack query, update UI types, and configure Vite proxy 2026-03-31 04:43:25 +05:00
ZohaibHassan16 3065f3c00e feat(explorer): add initial UI for SKOS Vocabulary Workspace 2026-03-31 02:24:10 +05:00
Mohd Kaif 0f1d262327 Merge pull request #428 from Hawksight-AI/security-enhancement
ci(codeql): add CodeQL workflow to auto-close security alerts on push…
2026-03-30 20:20:25 +05:30
KaifAhmad1andClaude Sonnet 4.6 6390138edc fix(codeql): remove 403-failing disable step; dismiss fixed alerts via API
GITHUB_TOKEN cannot change Default Setup (requires admin rights — HTTP 403).
Removed the disable-default-setup job entirely.

New approach:
- analyze job: runs CodeQL with upload:false then uploads SARIF via
  upload-sarif with continue-on-error:true so the workflow does not fail
  if Default Setup is still active
- dismiss-fixed-alerts job: runs on push to main, fetches all open alerts
  matching the 3 fixed rule IDs and dismisses them via PATCH API which
  only requires security-events:write (no admin needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:17:18 +05:30
KaifAhmad1andClaude Sonnet 4.6 8b47c148c5 fix(codeql): split disable-default-setup into separate job with confirmation
The previous fix used || true in a single-step which masked API failures
and had no propagation delay — Default Setup remained active when the
SARIF upload ran, causing the same conflict error.

Changes:
- New job `disable-default-setup` runs first: calls the API, waits 30s,
  then polls to confirm state=not-configured before exiting
- `analyze` job depends on `disable-default-setup` via `needs:` so CodeQL
  only runs after the state change is confirmed propagated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:15:13 +05:30
KaifAhmad1andClaude Sonnet 4.6 8eae75c03a fix(codeql): disable Default Setup before Advanced Setup analysis
Advanced Setup and Default Setup cannot run simultaneously — SARIF upload
fails with "cannot be processed when the default setup is enabled".

Added a pre-analysis step that calls the GitHub code-scanning API to switch
Default Setup to not-configured before CodeQL runs, eliminating the conflict.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:10:56 +05:30
KaifAhmad1andClaude Sonnet 4.6 9eb7ea97d0 ci(codeql): add CodeQL workflow to auto-close security alerts on push to main
Adds explicit CodeQL analysis workflow triggered on push/PR to main and
weekly schedule. Without this, GitHub Default Setup only runs on a
schedule — alerts do not re-scan after a PR merge, leaving fixed
vulnerabilities still shown as open.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:04:06 +05:30
Mohd Kaif cd07e02db6 Merge pull request #427 from Hawksight-AI/security-enhancement
fix(security): resolve CodeQL alerts for logging, URL sanitization, a…
2026-03-30 19:55:09 +05:30
KaifAhmad1andClaude Sonnet 4.6 dfb51f8b54 docs(changelog): add security-enhancement CodeQL alert remediation entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:52:15 +05:30
KaifAhmad1andClaude Sonnet 4.6 1d88c06cbb fix(security): resolve CodeQL alerts for logging, URL sanitization, and workflow permissions
- Remove api_key debug print blocks from relation_extractor.py and triplet_extractor.py (CWE-532 clear-text logging)
- Replace URL substring check with exact equality in test_web_ingestor.py (CWE-20 incomplete sanitization)
- Add `permissions: contents: read` to benchmark.yml and security.yml workflows (least-privilege)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:46:30 +05:30
Mohd Kaif 033eaab108 Merge pull request #426 from ZohaibHassan16/feat/explorer-vocab-api
feat: SKOS Vocabulary API & Hierarchy Engine
2026-03-30 19:26:04 +05:30
Mohd Kaif 9a81034336 Merge branch 'main' into feat/explorer-vocab-api 2026-03-30 19:21:05 +05:30
KaifAhmad1andClaude Sonnet 4.6 664e343914 docs(changelog): add PR #426 SKOS Vocabulary REST API & Hierarchy Engine entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:20:22 +05:30
KaifAhmad1andClaude Sonnet 4.6 f677b638e2 fix(explorer): resolve test crash, import error handling, cycle safety, and missing utils package
- 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>
2026-03-30 19:02:24 +05:30
ZohaibHassan16 2537976e8f feat(explorer): implement SKOS vocabulary routes and integration tests 2026-03-30 17:01:11 +05:00
ZohaibHassan16 5cf49bf799 feat(explorer): implement SKOS vocabulary routes and schemes 2026-03-30 17:01:11 +05:00
Mohd Kaif c4d72ee3fc Merge pull request #425 from ZohaibHassan16/feat/explorer-infra-integration
feat(explorer): integrate API routers and add RDF parsing util
2026-03-30 17:17:23 +05:30
KaifAhmad1andClaude Sonnet 4.6 7a879a3508 docs(changelog): add PR #425 Explorer server integration & RDF parsing util entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 17:04:01 +05:30
KaifAhmad1andClaude Sonnet 4.6 ebd2be3d9d fix(explorer): resolve router isolation, rdf_parser param shadow, and missing utils package
- server.py: split vocabulary router into its own try/except so a missing
  vocabulary module (pending #421) cannot prevent the 7 existing routers
  from mounting
- rdf_parser.py: rename `format` param to `rdf_format` to avoid shadowing
  the Python builtin; add exception chaining (raise...from e); document
  the silent edge-drop behaviour for cross-vocabulary URIs
- Add semantica/explorer/utils/__init__.py (package was not importable)
- Add tests/explorer/test_rdf_parser.py: 32 tests covering node/edge
  extraction, label priority, altLabel dedup, all 6 SKOS edge types,
  orphan-edge filtering, empty graph, error cases, and RDF/XML format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 16:40:43 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> bc33bf9340 ci(deps): bump actions/deploy-pages from 4 to 5 (#423)
Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4 to 5.
- [Release notes](https://github.com/actions/deploy-pages/releases)
- [Commits](https://github.com/actions/deploy-pages/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/deploy-pages
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 15:44:31 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 77e50127c8 ci(deps): bump actions/configure-pages from 4 to 6 (#424)
Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 4 to 6.
- [Release notes](https://github.com/actions/configure-pages/releases)
- [Commits](https://github.com/actions/configure-pages/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/configure-pages
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 15:35:57 +05:30
ZohaibHassan16 73af7d5bfc feat(explorer): integrate API routers and add RDF parsing util 2026-03-30 15:04:46 +05:00
Mohd KaifandClaude Sonnet 4.6 e30ef6cb76 Kg Context Explainability Output Fixes (#419)
* feat(#401): temporal provenance, OWL-Time export, stable snapshot schema

- ProvenanceTracker: auto-attach recorded_at (UTC) to every new record;
  add query_recorded_between(), revision_history(), export_audit_log()
- RDFExporter.export_to_rdf: add include_temporal + time_axis params;
  emit OWL-Time triples (time:Interval, time:Instant, inXSDDateTimeStamp)
  for relationships with valid_from/valid_until; TemporalBound.OPEN
  represented via semantica:openEndedInterval instead of time:hasEnd
- TemporalVersionManager.create_snapshot: stamp format_version "1.0"
  on every snapshot; add validate_snapshot() and migrate_snapshot()
- New: semantica/kg/schemas/temporal_snapshot_v1.json (JSON Schema draft-2020)
- Tests: 28 new tests covering all acceptance criteria (451 passing, 0 failed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(#401): add changelog entry for temporal provenance & export

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(#402): Temporal GraphRAG Integration — TemporalGraphRetriever & TemporalQueryRewriter

- 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>

* docs(#402): add changelog entry for Temporal GraphRAG Integration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: rewrite and polish documentation site (#413)

- Rewrote index.md to match README (tagline, badges, Problem/Solution text)
- Improved getting-started, concepts, quickstart, installation, faq, use-cases, contributing, glossary, learning-more, examples, modules, architecture, cookbook, deep-dive pages: tighter prose, fixed headings/bullets, removed inconsistencies and duplicate sections
- Removed overuse of emojis from headings in integration pages (docling, snowflake)
- Fixed change_management reference page: closed unclosed JSON code block that broke the right TOC, demoted noisy sub-headings to bold text
- CSS layout: widened content area (max-width 1440px grid, left sidebar 11rem, right TOC narrowed to 11rem for broader content), tightened TOC spacing and font size, fixed word-wrap/overflow on TOC links
- Added mkdocs_local.yml for local serving without mkdocs-jupyter plugin

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(#318): SHACL Shape Generation & Validation (Phase 1 + Phase 2)

Phase 1 — Generation:
- Add SHACLGenerator, SHACLGraph, NodeShape, PropertyShape to ontology_generator.py
- 6-stage pipeline: class index → node shapes → property shapes → inheritance propagation → quality tier → serialization
- Three output formats: Turtle, JSON-LD, N-Triples
- Three quality tiers: basic / standard (default) / strict (sh:closed)
- 3-level+ inheritance propagation, cycle-safe, no duplicate shapes
- No-domain properties attach to all node shapes
- OntologyEngine.to_shacl(), export_shacl() added to engine.py
- RDFExporter.export_shacl() added to rdf_exporter.py

Phase 2 — Runtime Validation:
- Add SHACLViolation, SHACLValidationReport, _run_pyshacl to ontology_validator.py
- OntologyEngine.validate_graph() with shacl= or ontology= arguments
- explain_violations(): rule-based plain-English explanations for all 7 SHACL constraint types
- summary(), to_dict() on SHACLValidationReport for pipeline and LLM consumers
- pyshacl/rdflib are optional deferred imports (pip install semantica[shacl])

Security & reliability fixes:
- Replace path-heuristic (len/newline) with os.path.exists() in validate_graph
- Add shacl_format parameter to validate_graph and _run_pyshacl; thread format through correctly
- Fix validate_output format alias map in to_shacl (json-ld, n-triples aliases)
- Deep-copy PropertyShape in _propagate_inheritance (dataclasses.replace) — no shared mutable refs
- Deterministic Turtle prefix output via sorted(graph.prefixes.items())
- Use full rdf:type URI in sh:ignoredProperties — no prefix dependency

Tests & docs:
- Add TestSHACLGeneration (16 tests) to test_ontology_comprehensive.py
- Add TestSHACLHierarchicalAndValidation (18 tests) to test_ontology_advanced.py
- 34 new tests, 0 failures, 0 regressions across 1111-test suite
- Update README: Unreleased section, Features, Modules table, Ontology code block, Installation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(#318): add CHANGELOG entry for SHACL Shape Generation & Validation

Covers Phase 1 (generation), Phase 2 (runtime validation), all 5
security/reliability fixes, test results, and README updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(#319): SKOS Vocabulary Module — namespace helpers, store helpers, OntologyEngine APIs, tests, docs

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>

* docs(#319): add CHANGELOG entry for SKOS Vocabulary Module

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add comprehensive test suites for Temporal Semantics (#395) and all Unreleased changelog features

- tests/test_395_temporal_semantics_comprehensive.py — 113 tests covering
  #396–#399: bitemporal model, temporal consistency validation, query
  time-range aggregation, ContextGraph.state_at(), causal chain trace_at_time()
- tests/test_unreleased_changelog_comprehensive.py — 92 tests covering all
  unreleased changelog gaps: AgentContext checkpoints (#399), audit trail /
  named tags / rollback protection (#394), snapshot schema compatibility (#393),
  ContextGraph pagination & min_weight & thread safety (#385), SKOS helpers
  (#319), SHACL quality tiers & export (#318), OllamaProvider base_url (#408),
  DatalogReasoner multi-hop & graph load (#371)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: Context Explainability Output Fixes — regression tests and centrality fix

- Fixed CentralityCalculator._build_adjacency() to handle ContextGraph edges
  (ContextEdge dataclass objects with source_id/target_id) so degree centrality
  and related algorithms return correct results instead of empty dicts
- Added 23 regression tests in tests/context/test_context_explainability_regression.py
  covering readable decision text preservation, enriched causal/path outputs,
  PolicyEngine consistent metadata across Cypher and fallback branches,
  EntityLinker similarity payloads, and KG consumer compatibility
- Updated CHANGELOG.md [Unreleased] to reflect the bug fix and test additions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:03:52 +05:30
Mohd KaifandClaude Sonnet 4.6 cf7a78fa10 test: add comprehensive test suites for Temporal Semantics (#395) and all Unreleased changelog features (#417)
- tests/test_395_temporal_semantics_comprehensive.py — 113 tests covering
  #396–#399: bitemporal model, temporal consistency validation, query
  time-range aggregation, ContextGraph.state_at(), causal chain trace_at_time()
- tests/test_unreleased_changelog_comprehensive.py — 92 tests covering all
  unreleased changelog gaps: AgentContext checkpoints (#399), audit trail /
  named tags / rollback protection (#394), snapshot schema compatibility (#393),
  ContextGraph pagination & min_weight & thread safety (#385), SKOS helpers
  (#319), SHACL quality tiers & export (#318), OllamaProvider base_url (#408),
  DatalogReasoner multi-hop & graph load (#371)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 18:48:04 +05:30
KaifAhmad1andClaude Sonnet 4.6 4f0cf282a1 test: add comprehensive test suites for Temporal Semantics (#395) and all Unreleased changelog features
- tests/test_395_temporal_semantics_comprehensive.py — 113 tests covering
  #396–#399: bitemporal model, temporal consistency validation, query
  time-range aggregation, ContextGraph.state_at(), causal chain trace_at_time()
- tests/test_unreleased_changelog_comprehensive.py — 92 tests covering all
  unreleased changelog gaps: AgentContext checkpoints (#399), audit trail /
  named tags / rollback protection (#394), snapshot schema compatibility (#393),
  ContextGraph pagination & min_weight & thread safety (#385), SKOS helpers
  (#319), SHACL quality tiers & export (#318), OllamaProvider base_url (#408),
  DatalogReasoner multi-hop & graph load (#371)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 18:44:03 +05:30
Mohd Kaif ebfce8c5ab Merge pull request #416 from Hawksight-AI/ontology
feat(#319): SKOS Vocabulary Module
2026-03-28 14:23:40 +05:30
KaifAhmad1andClaude Sonnet 4.6 34bc7a45b9 docs(#319): add CHANGELOG entry for SKOS Vocabulary Module
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 14:15:02 +05:30
KaifAhmad1andClaude Sonnet 4.6 71d037f581 feat(#319): SKOS Vocabulary Module — namespace helpers, store helpers, OntologyEngine APIs, tests, docs
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>
2026-03-28 13:59:23 +05:30
Mohd Kaif 6651386074 Merge pull request #415 from Hawksight-AI/ontology
SHACL Shape Generation & Validation
2026-03-27 18:08:03 +05:30
KaifAhmad1andClaude Sonnet 4.6 a219c2f44e docs(#318): add CHANGELOG entry for SHACL Shape Generation & Validation
Covers Phase 1 (generation), Phase 2 (runtime validation), all 5
security/reliability fixes, test results, and README updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 17:56:07 +05:30
KaifAhmad1andClaude Sonnet 4.6 dae368532c feat(#318): SHACL Shape Generation & Validation (Phase 1 + Phase 2)
Phase 1 — Generation:
- Add SHACLGenerator, SHACLGraph, NodeShape, PropertyShape to ontology_generator.py
- 6-stage pipeline: class index → node shapes → property shapes → inheritance propagation → quality tier → serialization
- Three output formats: Turtle, JSON-LD, N-Triples
- Three quality tiers: basic / standard (default) / strict (sh:closed)
- 3-level+ inheritance propagation, cycle-safe, no duplicate shapes
- No-domain properties attach to all node shapes
- OntologyEngine.to_shacl(), export_shacl() added to engine.py
- RDFExporter.export_shacl() added to rdf_exporter.py

Phase 2 — Runtime Validation:
- Add SHACLViolation, SHACLValidationReport, _run_pyshacl to ontology_validator.py
- OntologyEngine.validate_graph() with shacl= or ontology= arguments
- explain_violations(): rule-based plain-English explanations for all 7 SHACL constraint types
- summary(), to_dict() on SHACLValidationReport for pipeline and LLM consumers
- pyshacl/rdflib are optional deferred imports (pip install semantica[shacl])

Security & reliability fixes:
- Replace path-heuristic (len/newline) with os.path.exists() in validate_graph
- Add shacl_format parameter to validate_graph and _run_pyshacl; thread format through correctly
- Fix validate_output format alias map in to_shacl (json-ld, n-triples aliases)
- Deep-copy PropertyShape in _propagate_inheritance (dataclasses.replace) — no shared mutable refs
- Deterministic Turtle prefix output via sorted(graph.prefixes.items())
- Use full rdf:type URI in sh:ignoredProperties — no prefix dependency

Tests & docs:
- Add TestSHACLGeneration (16 tests) to test_ontology_comprehensive.py
- Add TestSHACLHierarchicalAndValidation (18 tests) to test_ontology_advanced.py
- 34 new tests, 0 failures, 0 regressions across 1111-test suite
- Update README: Unreleased section, Features, Modules table, Ontology code block, Installation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 17:42:46 +05:30
Mohd KaifandClaude Sonnet 4.6 b282487b17 docs: rewrite and polish documentation site (#413)
- Rewrote index.md to match README (tagline, badges, Problem/Solution text)
- Improved getting-started, concepts, quickstart, installation, faq, use-cases, contributing, glossary, learning-more, examples, modules, architecture, cookbook, deep-dive pages: tighter prose, fixed headings/bullets, removed inconsistencies and duplicate sections
- Removed overuse of emojis from headings in integration pages (docling, snowflake)
- Fixed change_management reference page: closed unclosed JSON code block that broke the right TOC, demoted noisy sub-headings to bold text
- CSS layout: widened content area (max-width 1440px grid, left sidebar 11rem, right TOC narrowed to 11rem for broader content), tightened TOC spacing and font size, fixed word-wrap/overflow on TOC links
- Added mkdocs_local.yml for local serving without mkdocs-jupyter plugin

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:38:21 +05:30
KaifAhmad1andClaude Sonnet 4.6 129edaf05b docs: rewrite and polish documentation site
- Rewrote index.md to match README (tagline, badges, Problem/Solution text)
- Improved getting-started, concepts, quickstart, installation, faq, use-cases, contributing, glossary, learning-more, examples, modules, architecture, cookbook, deep-dive pages: tighter prose, fixed headings/bullets, removed inconsistencies and duplicate sections
- Removed overuse of emojis from headings in integration pages (docling, snowflake)
- Fixed change_management reference page: closed unclosed JSON code block that broke the right TOC, demoted noisy sub-headings to bold text
- CSS layout: widened content area (max-width 1440px grid, left sidebar 11rem, right TOC narrowed to 11rem for broader content), tightened TOC spacing and font size, fixed word-wrap/overflow on TOC links
- Added mkdocs_local.yml for local serving without mkdocs-jupyter plugin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 18:31:39 +05:30
Mohd Kaif 21c7933ec1 Merge pull request #412 from Hawksight-AI/context
feat(#402): Temporal GraphRAG Integration — TemporalGraphRetriever & …
2026-03-26 14:43:21 +05:30
KaifAhmad1andClaude Sonnet 4.6 c46e531dcd docs(#402): add changelog entry for Temporal GraphRAG Integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 14:40:41 +05:30
KaifAhmad1andClaude Sonnet 4.6 0f0800f109 feat(#402): Temporal GraphRAG Integration — TemporalGraphRetriever & TemporalQueryRewriter
- 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>
2026-03-26 14:20:38 +05:30
Mohd KaifandClaude Sonnet 4.6 9e68266563 feat(#401): Temporal Provenance & Export (#411)
* feat(#401): temporal provenance, OWL-Time export, stable snapshot schema

- ProvenanceTracker: auto-attach recorded_at (UTC) to every new record;
  add query_recorded_between(), revision_history(), export_audit_log()
- RDFExporter.export_to_rdf: add include_temporal + time_axis params;
  emit OWL-Time triples (time:Interval, time:Instant, inXSDDateTimeStamp)
  for relationships with valid_from/valid_until; TemporalBound.OPEN
  represented via semantica:openEndedInterval instead of time:hasEnd
- TemporalVersionManager.create_snapshot: stamp format_version "1.0"
  on every snapshot; add validate_snapshot() and migrate_snapshot()
- New: semantica/kg/schemas/temporal_snapshot_v1.json (JSON Schema draft-2020)
- Tests: 28 new tests covering all acceptance criteria (451 passing, 0 failed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(#401): add changelog entry for temporal provenance & export

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 12:38:24 +05:30
Mohd Kaif c66f8160dd Merge pull request #410 from Hawksight-AI/semantic-extract
feat(semantic-extract): temporal metadata extraction from text (#400)
2026-03-25 22:33:06 +05:30
KaifAhmad1andClaude Sonnet 4.6 b1e1c9f0d9 docs(changelog): add entry for temporal metadata extraction (#400)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 22:31:06 +05:30
Mohd Kaif cef2b4314a Delete PR_DESCRIPTION_SEMANTIC_EXTRACT.md 2026-03-25 22:28:37 +05:30
KaifAhmad1andClaude Sonnet 4.6 b9af181625 feat(semantic-extract): temporal metadata extraction from text (#400)
- 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>
2026-03-25 21:42:57 +05:30
Mohd Kaif 05fa3247b7 Merge pull request #409 from Hawksight-AI/semantic-extract
fix(semantic-extract): pass base_url as host when initialising Ollama…
2026-03-24 19:48:49 +05:30
KaifAhmad1andClaude Sonnet 4.6 42899c1416 docs(changelog): add entry for OllamaProvider base_url fix (#408)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 19:44:56 +05:30
KaifAhmad1andClaude Sonnet 4.6 19665b9db2 fix(semantic-extract): pass base_url as host when initialising OllamaProvider client
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>
2026-03-24 19:36:22 +05:30
Mohd Kaif 07dc579faf Merge pull request #407 from Hawksight-AI/context
feat(context): add temporal awareness to ContextGraph and AgentContext
2026-03-24 13:28:25 +05:30
KaifAhmad1andClaude Sonnet 4.6 96e438c81c docs(changelog): add entry for temporal awareness in context graph (#399)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:25:16 +05:30
KaifAhmad1andClaude Sonnet 4.6 c483352d7a fix(context): resolve review issues in temporal awareness PR
- Fix max_depth error message: "1 and 20" -> "1 and 100" to match actual check
- Fix Cypher query at_time param to RFC3339 UTC (append Z) for unambiguous DB comparisons
- Fix _normalize_temporal_input to raise ValueError on unparseable strings instead of returning raw input
- Fix datetime.now() -> datetime.utcnow() in recorded_at stamps and checkpoint timestamps (matches codebase convention, avoids wrong local time on Windows)
- Wrap TemporalVersionManager() construction in flush_checkpoint with clear RuntimeError

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:20:54 +05:30
KaifAhmad1andClaude Sonnet 4.6 01ba7d113b feat(context): add temporal awareness to ContextGraph and AgentContext
- Add valid_from/valid_until fields to Decision dataclass and record_decision()
- Add include_superseded and as_of filters to find_precedents_by_scenario()
- Add _decision_matches_temporal_filters() and _normalize_temporal_input() helpers
- Add ContextGraph.state_at(timestamp) for point-in-time graph snapshots
- Stamp recorded_at on causal relationship edges
- Add CausalChainAnalyzer.trace_at_time() for transaction-time causal chain tracing
- Add AgentContext.checkpoint(), diff_checkpoints(), flush_checkpoint() for named context snapshots
- 93 tests passing (33 context_graph, 36 causal_analyzer, 24 agent_context)

Closes #399

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 12:18:25 +05:30
Mohd Kaif cdd45331fa Merge pull request #406 from Hawksight-AI/semantic-extract
Harden spaCy NER Fallback in Semantic Extract
2026-03-23 23:33:18 +05:30
KaifAhmad1 a3a0848577 Fix semantic extract spaCy fallback review issues 2026-03-23 23:31:01 +05:30
Mohd Kaif eeda5f5b80 Merge branch 'main' into semantic-extract 2026-03-23 23:16:19 +05:30
KaifAhmad1 62b03d4fa5 Harden spaCy NER fallback in semantic extract 2026-03-23 23:12:48 +05:30
Mohd Kaif 8a2b07b864 Merge pull request #405 from Hawksight-AI/kg
Deterministic Temporal Reasoning Engine and Query Integration
2026-03-23 19:55:15 +05:30
KaifAhmad1 0773e24075 Update changelog for temporal reasoning PR 2026-03-23 19:48:56 +05:30
KaifAhmad1 8de7cc1b6d Fix temporal reasoning review issues 2026-03-23 19:43:12 +05:30
KaifAhmad1 c6dc9d87aa Add deterministic temporal reasoning engine 2026-03-23 19:12:18 +05:30
Mohd Kaif 781b103436 Merge pull request #404 from Hawksight-AI/kg
Implement temporal point-in-time correctness (#397)
2026-03-23 17:24:32 +05:30
KaifAhmad1 e4f0c8993c Update changelog for temporal query PR follow-ups 2026-03-23 17:20:34 +05:30
KaifAhmad1 b2b823a5c3 Fix temporal query review follow-ups 2026-03-23 17:13:02 +05:30
KaifAhmad1 3c863e860e Implement temporal point-in-time correctness (#397) 2026-03-23 16:39:49 +05:30
Mohd Kaif 4c74da7682 Merge pull request #403 from Hawksight-AI/kg
Core temporal data model overhaul (#396)
2026-03-23 16:20:17 +05:30
KaifAhmad1 de84ab6d06 Update changelog for temporal PR follow-ups 2026-03-23 16:16:14 +05:30
KaifAhmad1 8be16f782e Fix temporal revision integrity follow-ups 2026-03-23 16:12:29 +05:30
KaifAhmad1 9faa5661f8 Core temporal data model overhaul (#396) 2026-03-23 15:53:13 +05:30
Mohd Kaif 3e0fbf8e95 Merge pull request #394 from ZohaibHassan16/feat/gitgraph
feat: implement full audit trail, named tags, and rollback protection
2026-03-22 20:20:38 +05:30
OpenAI CodexandKaifAhmad1 29f5c72533 docs(changelog): note PR #394 audit trail fixes
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-03-22 20:15:58 +05:30
OpenAI CodexandKaifAhmad1 e13ea740cd merge: resolve main conflicts for PR #394
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-03-22 20:12:01 +05:30
OpenAI CodexandKaifAhmad1 e2b79ada9a fix(change-management): preserve snapshot compatibility and audit integrity
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-03-22 19:45:58 +05:30
Mohd Kaif c6f8f0dd04 Merge pull request #393 from ZohaibHassan16/fix/snapshot-key-mismatch
fix: Map nodes/edges to resolve silent snapshot restore failure
2026-03-22 17:44:51 +05:30
Mohd Kaif 4147f0ca3b Merge branch 'main' into fix/snapshot-key-mismatch 2026-03-22 17:42:32 +05:30
OpenAI CodexandKaifAhmad1 ad84cb5897 docs(changelog): note PR #393 snapshot fixes
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-03-22 17:41:49 +05:30
OpenAI Codex adac6f7a7b fix(change-management): preserve snapshot schema compatibility 2026-03-22 17:26:07 +05:30
Mohd Kaif 90e6baa0ec Merge pull request #386 from ZohaibHassan16/fix/issue-379-decision-query-fallback
fix(context): Implement ContextGraph traversal fallbacks for Decision…
2026-03-21 21:13:05 +05:30
Mohd Kaif ca8a916373 Merge branch 'main' into fix/issue-379-decision-query-fallback 2026-03-21 17:10:52 +05:30
KaifAhmad1andClaude Sonnet 4.6 0dd5c4b7f1 docs(changelog): add entry for PR #386 ContextGraph fallback fixes
Documents both @ZohaibHassan16's original fallback implementation and
the follow-up fixes by @KaifAhmad1: isinstance regression, add_node
signature bug, add_edge spurious kwarg, timezone handling, BFS
find_edges hoist, duplicate import removal, and full test coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:10:14 +05:30
KaifAhmad1andClaude Sonnet 4.6 13eed9cf6d fix(context): fix isinstance regression, hoist BFS find_edges, expand tests
- 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>
2026-03-21 17:08:44 +05:30
Mohd Kaif 1064b0bdbe Merge pull request #385 from ZohaibHassan16/fix/cg-thpag
ContextGraph: Threading and Pagination
2026-03-20 00:26:52 +05:30
KaifAhmad1andClaude Sonnet 4.6 ac047f917a fix(explorer): resolve merge-artifact syntax errors and clean up all route files
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>
2026-03-20 00:18:08 +05:30
KaifAhmad1andClaude Sonnet 4.6 7b6e74d042 docs(changelog): add entry for PR #385 ContextGraph threading, pagination, and review fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:03:29 +05:30
KaifAhmad1andClaude Sonnet 4.6 ba491d8cba fix(build): remove duplicate entry and add missing comma in pyproject.toml all extra
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:00:07 +05:30
KaifAhmad1andClaude Sonnet 4.6 54868fea80 fix(explorer): resolve PR #385 review issues — search content, edge metadata, event loop
- session.search(): normalise node.to_dict() "properties" envelope to flat
  {id, type, content, metadata} so /api/graph/search returns populated content
  and properties instead of empty strings (Qodo bug #3)

- context_graph.add_edges(): fall back to "metadata" key when "properties" is
  absent so edges imported from find_edges()/build_graph_dict() format don't
  silently lose their metadata (Qodo bug #2)

- enrich.predict_links(): wrap the O(n) scoring loop in asyncio.to_thread() so
  it never blocks the event loop on large graphs (Qodo bug #1)

- session.py: remove duplicate __init__ annotations assignment, duplicate
  property definitions (un-locked first set), and dead-code double-query
  inside get_nodes()/get_edges() left over from the merge

- enrich.py: remove unreachable code block after early return in predict_links
  and duplicate nodes fetch in detect_duplicates left over from the merge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:57:07 +05:30
Mohd Kaif a0e7e9a1c9 Merge branch 'main' into fix/cg-thpag 2026-03-19 23:39:20 +05:30
Mohd Kaif e8d71b49fa Merge pull request #384 from ZohaibHassan16/feat/explorer-api-377
feat: implement Knowledge Explorer API backend
2026-03-19 16:42:41 +05:30
Mohd Kaif 8916200d31 Merge branch 'main' into feat/explorer-api-377 2026-03-19 16:30:26 +05:30
290916a6ff docs(changelog): add entry for PR #384 Knowledge Explorer API backend
Co-authored-by: ZohaibHassan16 <zohaibhassan16@users.noreply.github.com>
Co-authored-by: KaifAhmad1 <kaifahmad1@users.noreply.github.com>
2026-03-19 16:29:42 +05:30
88bd7d6b05 fix(explorer): resolve all PR review issues — bugs, tests, refactor
Bugs fixed:
- enrich.py: predict_links called predictor.predict_links() with wrong
  signature (graph_dict as graph_store, node_id as node_labels, top_n
  instead of top_k). Rewrote to iterate candidate nodes and call
  score_link(session.graph, src, candidate) directly.
- enrich.py: detect_duplicates called session.get_nodes() synchronously
  in an async handler, blocking the event loop. Wrapped in to_thread().
- export_import.py: temp file was leaked on export exception. Now always
  cleaned up via try/finally. Moved `import os` to module level.
- pyproject.toml: missing comma between two strings in the `all` extra
  caused a TOML syntax error breaking `pip install semantica[all]`.
- app.py: generic Exception handler swallowed HTTPException(503) raised
  by get_session dependency. Now re-raises HTTPException explicitly.
- decisions.py: compliance endpoint imported PolicyEngine then discarded
  it, always returning compliant=True. Replaced with in-graph check:
  scans for violates/non_compliant/breaches edges from the decision node.
- app.py: removed unused `import traceback`.

Refactor:
- session.py: added build_graph_dict(node_ids=None) method to eliminate
  _build_graph_dict() duplication across graph.py, analytics.py, and
  export_import.py (three identical copies).
- session.py: all 8 lazy analytics properties now initialise under _lock
  to prevent double-instantiation under concurrent requests.
- graph.py: find_path now dispatches to dijkstra_shortest_path or
  bfs_shortest_path based on the `algorithm` query param (was always BFS).
- annotations.py: removed unnecessary get_annotations() round-trip in
  create_annotation — add_annotation mutates ann_data in-place.
- temporal.py: split bare `except Exception` into ImportError (silent)
  and Exception (logs warning), so real bugs are no longer hidden.

Tests (49 total, all passing):
- Added TestEnrichExtract, TestLinkPrediction, TestDedup classes.
- Added test_compliance_with_violation to verify real violation detection.
- Added test_snapshot_at_excludes_temporal_node, test_diff assertions,
  test_export_json_subset, test_import_with_edges, test_import_unsupported_format.
- Strengthened analytics, search, and annotation assertions.
- Reasoning test now asserts response shape when status is 200.

Co-authored-by: ZohaibHassan16 <zohaibhassan16@users.noreply.github.com>
Co-authored-by: KaifAhmad1 <kaifahmad1@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 16:27:17 +05:30
ZohaibHassan16 44f817ee71 Merge branch 'feat/gitgraph' of https://github.com/ZohaibHassan16/semantica into feat/gitgraph 2026-03-19 02:26:06 +05:00
ZohaibHassan16 5d6051bee1 fix: resolve qodo issues 2026-03-19 02:25:17 +05:00
ZohaibHassan16 0b922e77c5 fix: resolve qodo validation and duplicate payload storage issues 2026-03-19 02:06:37 +05:00
Zohaib e3a0c84b90 Merge branch 'main' into feat/gitgraph 2026-03-19 01:56:46 +05:00
ZohaibHassan16 6bbb8f929f add unit tests 2026-03-19 01:50:48 +05:00
ZohaibHassan16 d8bbd8877f fix: Map nodes/edges to resolve silent snapshot restore failure 2026-03-18 22:43:33 +05:00
ZohaibHassan16 b1e5c9e3c9 WIP: Foundation 2026-03-18 22:35:39 +05:00
Mohd Kaif fe64b8ad8a Merge pull request #387 from ZohaibHassan16/fix/issue-382-reasoner-dead-code
fix(reasoning): Remove overwritten regex pattern and unreachable return
2026-03-18 17:39:08 +05:30
Mohd Kaif 65a00de408 Merge branch 'main' into fix/issue-382-reasoner-dead-code 2026-03-18 17:07:26 +05:30
KaifAhmad1andClaude Sonnet 4.6 e9a2f87325 docs(changelog): add entry for PR #387 reasoning dead code fix
Documents the removal of the overwritten regex pattern and unreachable
return statement in _match_pattern, and the surfacing of regex errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 17:06:43 +05:30
Mohd Kaif e7a13f7de6 Merge branch 'main' into fix/cg-thpag 2026-03-18 16:37:32 +05:30
Mohd Kaif fedbd8de8e Merge branch 'main' into feat/explorer-api-377 2026-03-18 16:33:51 +05:30
Mohd Kaif ed27b98c53 Add Agno integration documentation 2026-03-18 15:55:49 +05:30
Mohd Kaif 353a6c605d Enhance Agno integration details in README
Expanded the description of the Agno integration with detailed components and installation instructions.
2026-03-18 15:36:45 +05:30
Mohd Kaif 0659509c14 Merge pull request #391 from Hawksight-AI/integrations
feat(integrations): Agno Agentic Framework — Decision Intelligence, Context Graphs & GraphRAG
2026-03-18 15:19:25 +05:30
KaifAhmad1andClaude Sonnet 4.6 b2a2d24b14 fix: address all Qodo code review issues in Agno integration
Package & distribution
- pyproject.toml: add integrations* to packages.find include so pip
  install semantica[agno] ships the integration

context_store.py
- upsert_memory(): run NERExtractor after store() to index entities
  into the ContextGraph
- delete_memory() / drop_table() / clear(): call AgentContext.forget()
  to propagate deletions to vector/graph storage
- find_precedents(): pass limit parameter to find_precedents_advanced()
- retrieve(): pass limit as max_results to AgentContext.retrieve()
- add get_context_for_prompt() for automatic system-prompt injection

knowledge_graph.py
- __init__: wire graph_builder.graph_store = self._graph so build()
  persists into the ContextGraph
- add internal AgentContext for vector retrieval (shared ContextGraph)
- search(): use AgentContext.retrieve() for vector similarity; keyword
  scoring as fallback
- _ingest_text(): add paragraph-level chunking before NER/relation
  extraction (parse → split → NER → relation extract → graph build)
- get_graph_context(): return structured subgraph with edge types via
  ContextGraph.get_neighbors()
- load_urls(): validate scheme (http/https only) to prevent SSRF

decision_kit.py
- check_policy(): replace broken PolicyEngine.check_compliance() call
  with inline _eval_rule() that evaluates simple field-op-value rules;
  return compliant=False (not True) on failure — closes security bug

kg_toolkit.py
- add_to_graph(): fix add_node(node_id=, node_type=) and
  add_edge(source_id=, target_id=, edge_type=) to match real API
- query_graph(): use find_nodes() (no label param) + keyword filter
- find_related(): use get_neighbors(node_id=) returning List[Dict]
- infer_facts() / export_subgraph(): use find_nodes() public API
  instead of private _nodes dict

shared_context.py
- _AgentScopedStore: store shared context as self._context (not
  self._ctx) so all inherited AgnoContextStore methods work correctly

tests/integrations/agno/test_kg_toolkit.py
- _FakeGraph: rewrite to match real ContextGraph signatures —
  find_nodes(node_type=), add_node(node_id, node_type, **),
  add_edge(source_id, target_id, edge_type, **),
  get_neighbors(node_id, hops=1, ...) returning List[Dict]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 04:21:48 +05:30
KaifAhmad1andClaude Sonnet 4.6 e315ad849d docs: update CHANGELOG and README with Agno integration
- Add Agno Agentic Framework Integration entry under [Unreleased] in CHANGELOG
- Update README: rename section to "Agentic Frameworks", add Agno bullet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 03:33:52 +05:30
KaifAhmad1andClaude Sonnet 4.6 62c7970b32 feat(integrations): add Agno agentic framework integration (#249)
Implements the full Semantica × Agno integration stack as described in
issue #249, wiring Semantica's semantic intelligence layer into Agno's
agent/team primitives via five focused components.

## New components

### integrations/agno/
- `AgnoContextStore`    — graph-backed MemoryDb (AgentMemory/storage)
- `AgnoKnowledgeGraph`  — relational AgentKnowledge with multi-hop GraphRAG
- `AgnoDecisionKit`     — Agno Toolkit: 6 decision-intelligence tools
- `AgnoKGToolkit`       — Agno Toolkit: 7 knowledge-graph tools
- `AgnoSharedContext`   — team-level shared ContextGraph with role scoping

### tests/integrations/agno/
- 110 tests, 0 failures
- conftest.py installs comprehensive agno stubs for offline testing
- Covers MemoryDb protocol, tool registration, shared memory pool,
  thread-safety, GraphRAG search, NER/relation extraction, and inference

### cookbook/integrations/
- agno_decision_intelligence.ipynb     (finance/loan underwriting)
- agno_graphrag_context.ipynb          (regulatory compliance GraphRAG)
- agno_multi_agent_shared_context.ipynb (multi-agent product strategy team)

### docs/integrations/agno.md
- Full reference documentation with examples for all 5 components

## pyproject.toml
- Added `agno = ["agno>=1.0.0"]` optional dependency
- Added agno to the `all` extra

## Design notes
- Zero breaking changes — fully additive
- Graceful degradation when agno is not installed
- Auto-creates VectorStore(backend="faiss") when none provided
- _tools always populated for inspection regardless of agno install state
- Works with both real agno package and offline stubs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 03:25:14 +05:30
Mohd Kaif 4235840a9e Merge pull request #390 from Hawksight-AI/utils
ci: Optimize CI/CD Workflows — Scope Triggers to Avoid Redundant Runs
2026-03-18 01:12:44 +05:30
KaifAhmad1andClaude Sonnet 4.6 e3c33cf23b ci: remove test step — rely on benchmark and security workflows only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 01:10:13 +05:30
KaifAhmad1andClaude Sonnet 4.6 868109fa34 ci: skip heavy/integration tests to reduce CI runtime
- Register 'integration' pytest mark in pyproject.toml to eliminate
  PytestUnknownMarkWarning across the test suite
- Add -m "not integration" and --ignore for external-service tests,
  notebook tests, comprehensive real-world tests, and API-key-dependent
  tests (Groq, Novita, Snowflake, Neptune, HF deepdive)
- Keeps fast unit tests: context, kg, semantic_extract, reasoning,
  pipeline, export, deduplication, parse, normalize, utils, provenance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 01:08:03 +05:30
KaifAhmad1andClaude Sonnet 4.6 2e5ad9d28b fix: address Qodo review issues in CI workflows
- Replace '*.md' with '**/*.md' in paths-ignore across ci.yml,
  benchmark.yml, and security-scan.yml — '*.md' only matches root-level
  markdown; '**/*.md' covers all subdirectories (cookbook/, docs/, etc.)
- Add cache: 'pip' to setup-python in ci.yml to avoid re-downloading
  heavy packages (torch, spacy, faiss) on every run
- Update security-scan PR comment text to accurately reflect that it
  skips doc/markdown-only PRs, not "every PR"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:38:44 +05:30
KaifAhmad1andClaude Sonnet 4.6 6c61e34ad4 fix: guard centrality values against MagicMock in analyze_decision_influence
When centrality_calculator falls back to basic implementation on a mocked
networkx call, measure_data['centrality'].get() can return a MagicMock.
MagicMock silently supports __mul__ and __add__, so the arithmetic on
influence_score produces a MagicMock instead of raising, causing the
isinstance(influence_score, (int, float)) assertion to fail in tests.

Guard each centrality value with isinstance(val, (int, float)) and default
to 0.0 for any non-numeric value before storing it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:28:34 +05:30
KaifAhmad1andClaude Sonnet 4.6 9a6c07417e fix: correct Entity import path in test_novita_integration
semantica.semantic_extract.models does not exist; Entity is defined in
ner_extractor.py and exported from semantica.semantic_extract directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:17:08 +05:30
KaifAhmad1andClaude Sonnet 4.6 89fe0df40b fix: replace Presentation type annotation with Any in pptx_parser
Method signature 'def _extract_metadata(self, prs: Presentation)' references
Presentation at class-definition time (evaluated on import), causing NameError
since Presentation is no longer imported at module level. Replace with Any.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:11:58 +05:30
KaifAhmad1andClaude Sonnet 4.6 500d0239e0 fix: make python-pptx import lazy in pptx_parser to fix CI collection error
python-pptx is not in [dev] extras so it's absent in CI, causing
ModuleNotFoundError during test collection via parse/__init__.py.
Moved import inside the parse method with a clear install hint.

This is the last known bare top-level optional import — sqlalchemy
(db_ingestor.py) and pdfplumber (pdf_parser.py) were fixed in prior commits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:01:10 +05:30
KaifAhmad1andClaude Sonnet 4.6 e18e6d1a00 fix: make pdfplumber import lazy in pdf_parser to fix CI collection error
pdfplumber (and unused PIL) were imported at module level but pdfplumber is
not installed in the [dev] extras used by CI, causing ModuleNotFoundError
during pytest collection via the parse/__init__.py import chain.
Moved import inside the method that uses it with a clear error message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 23:29:13 +05:30
KaifAhmad1andClaude Sonnet 4.6 753bf18ce7 fix: make sqlalchemy import lazy in db_ingestor to fix CI collection error
sqlalchemy was imported at module level but is not a declared dependency,
causing ModuleNotFoundError during pytest collection in CI when only [dev]
extras are installed. Moved all sqlalchemy imports inside the methods that
use them; replaced Engine type annotations with Any to avoid import-time
resolution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 23:23:45 +05:30
KaifAhmad1andClaude Sonnet 4.6 1aee4dfd29 ci: scope workflows to avoid redundant docs deploys and benchmark runs
- docs.yml: remove semantica/** path trigger (was deploying docs on every
  source code push); add release:[published] so docs still deploy on releases
- benchmark.yml: remove pull_request trigger (heavy deps - torch/spacy/faiss);
  add paths-ignore for doc-only main pushes; add workflow_dispatch for manual runs
- ci.yml: add paths-ignore so doc-only changes skip build; add pytest step
  so tests actually run in CI (was build-only before)
- security-scan.yml: add paths-ignore on push/pull_request; schedule runs unaffected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 23:15:18 +05:30
Mohd Kaif 572d2da64a Merge pull request #374 from Alex-wuhu/novita-integration
Add Novita AI provider integration
2026-03-17 22:59:02 +05:30
Mohd Kaif 3f55a34eff Merge branch 'main' into novita-integration 2026-03-17 22:34:33 +05:30
KaifAhmad1andClaude Sonnet 4.6 2dbc502720 docs: add Novita AI provider to CHANGELOG and README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 22:33:37 +05:30
KaifAhmad1andClaude Sonnet 4.6 c5fb2d24fd fix: correct Novita base_url to /v1 and add proper test assertions
- Fix base_url from 'https://api.novita.ai/openai' to 'https://api.novita.ai/v1'
  to match the OpenAI-compatible endpoint convention used by other providers
  (Groq uses /openai/v1, Novita docs specify /v1)
- Rewrite test_novita_integration.py with proper pytest assertions and
  pytestmark skip when NOVITA_API_KEY is unset; tests now fail on errors
  instead of silently printing and returning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 22:29:09 +05:30
Mohd Kaif 9c09851658 Merge pull request #371 from ZohaibHassan16/datalog#368
feat: implement Datalog Reasoner
2026-03-17 17:39:53 +05:30
Mohd Kaif 2cea2708a6 Merge branch 'main' into datalog#368 2026-03-17 17:07:45 +05:30
KaifAhmad1andClaude Sonnet 4.6 b80c91ccb9 docs: update CHANGELOG for DatalogReasoner (PR #371, Issue #368)
Documents the new native Datalog reasoning engine under [Unreleased],
including semi-naive fixpoint evaluation, recursive rule support,
query interface, ContextGraph integration, and all bug fixes applied
during review.

Contributors: @ZohaibHassan16 (implementation), @KaifAhmad1 (review & fixes)

Co-Authored-By: KaifAhmad1 <kaifahmad087@gmail.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 17:06:51 +05:30
KaifAhmad1andClaude Sonnet 4.6 ad9ea48d26 fix: resolve review issues in DatalogReasoner
- Remove forced progress_tracker.enabled=True (was mutating global singleton)
- Wrap derive_all() fixpoint loop in try/finally so stop_tracking is always called
- Add _derived flag to cache fixpoint result; query() no longer re-runs derive_all() on every call
- Reset _derived to False in add_fact(), add_rule(), and clear()
- Warn (instead of silently drop) when add_fact() receives an unrecognised dict format
- Fix syntax error on line 9 of test file (stray dashes caused SyntaxError, broke CI)
- Add missing TestContextGraphIntegration tests: test_edge_becomes_fact and test_derive_after_load
- All 18 tests pass

Co-Authored-By: KaifAhmad1 <kaifahmad087@gmail.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 17:01:26 +05:30
Mohd Kaif 62f2c0af92 Merge pull request #367 from ZohaibHassan16/clean-ontology-diff
feat: implement ontology diff
2026-03-16 22:29:10 +05:30
KaifAhmad1andZohaibHassan16 24166bbfa9 docs: update CHANGELOG for ontology diff & migration (PR #367)
Co-authored-by: ZohaibHassan16 <zohaib179949@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-03-16 22:08:02 +05:30
KaifAhmad1andZohaibHassan16 665771f230 fix: address all review feedback on ontology diff implementation
- Fix typo in ChangeCategory enum: "potenitally_breaking" → "potentially_breaking"
- Fix missing space in _classify_change description string: "New{type}" → "New {type}"
- Add null-value guard in _analyze_field_changes for unset constraint fields
- Make ChangeLogAnalyzer stateless: pass report as arg to _generate_recommendations
- Remove no-op __init__ from ChangeLogAnalyzer
- Replace non-portable emoji markers in recommendations with plain-text tags
- Extend diff_ontologies to cover individuals and axioms (not just classes/properties)
- Fix exception chaining in compare_versions: raise ... from e
- Remove silent ImportError swallow for GraphValidator (it is a first-party module)
- Add comment on deferred VersionManager import explaining circular-import reason
- Fix import-before-docstring in test_managers.py
- Add tests: version-not-found error path, individuals/axioms diff coverage,
  null constraint flagged as breaking
- Fix broken Markdown link syntax in docs JSON example block
- Update docs recommendations example to match new plain-text tag format

Co-authored-by: ZohaibHassan16 <zohaib179949@gmail.com>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-03-16 22:05:12 +05:30
Mohd Kaif 9d4d682883 Merge branch 'main' into clean-ontology-diff 2026-03-16 21:48:47 +05:30
Mohd Kaif 399416f6ea Merge pull request #361 from ZohaibHassan16/onto-alignment
feat: implement ontology alignment API(#324)
2026-03-16 19:38:23 +05:30
ZohaibHassan16 32d0fe105c fix(reasoning): surface regex matching errors and verify pattern matcher integrity 2026-03-16 16:56:36 +05:00
ZohaibHassan16 ee7c00f655 fix(context): resolve DecisionQuery fallback bugs and metadata preservation 2026-03-16 16:44:42 +05:00
ZohaibHassan16 dcf26dfa99 fix(explorer): resolve qodo review blocking calls and import schema 2026-03-16 16:23:39 +05:00
Mohd Kaif 9bb71a45f2 Merge branch 'main' into onto-alignment 2026-03-16 16:43:07 +05:30
ZohaibHassan16 4a3b1676d6 fix(explorer): resolve sync blocking calls and 500 error propagation 2026-03-16 09:16:13 +05:00
ZohaibHassan16 a6654ba570 fix(explorer): resolve PR review bugs (lock, import mapping, traceback, static route) 2026-03-16 09:08:26 +05:00
ZohaibHassan16 060ff47826 fix(reasoning): Remove overwritten regex pattern and unreachable return 2026-03-16 08:50:02 +05:00
ZohaibHassan16 4146fbf277 fix(context): Implement ContextGraph traversal fallbacks for DecisionQuery 2026-03-16 02:12:49 +05:00
ZohaibHassan16 1d1ae398c4 fix: add thread safety and pagination to ContextGraph 2026-03-15 21:50:55 +05:00
ZohaibHassan16 99a4db3ece feat: implement Knowledge Explorer API backend 2026-03-15 20:29:41 +05:00
Alex-wuhu de03d05600 Add Novita AI provider integration
- Add NovitaProvider class implementing OpenAI-compatible API
- Support for Novita AI API endpoint (https://api.novita.ai/openai)
- Configure via NOVITA_API_KEY environment variable or constructor
- Register 'novita' as built-in provider
- Update config.py to load NOVITA_API_KEY from environment
- Add test_novita_integration.py for provider testing

Default model: deepseek/deepseek-v3.2
2026-03-15 00:33:28 +08:00
Mohd Kaif c077944457 Merge pull request #373 from Hawksight-AI/context
Context Fix context explainability outputs and replace raw IDs with human-readable metadata
2026-03-14 15:11:58 +05:30
KaifAhmad1 b309451398 Fix review issues in context explainability PR 2026-03-14 14:47:20 +05:30
KaifAhmad1 0c1bdc0cee Update changelog for context explainability fixes 2026-03-13 06:33:29 +05:30
KaifAhmad1 c2a6e944fe Improve context explainability outputs 2026-03-13 06:31:38 +05:30
Mohd Kaif 0dc5eb6075 Merge branch 'main' into onto-alignment 2026-03-13 00:04:28 +05:30
KaifAhmad1andClaude Sonnet 4.6 5a316a4641 docs: update CHANGELOG for ontology alignment PR #361
Add Unreleased entry for the ontology alignment feature covering:
- all new APIs (create_alignment, get_alignments, list_alignments,
  suggest_alignments, expand_entity_uri, build_values_clause,
  get_alignment_predicates)
- post-review fixes: tracker leak, relatedMatch gap, SPARQL injection
  in list_alignments and build_values_clause, predicate validation,
  and E2E test correctness
- contributors: @ZohaibHassan16 (implementation), @KaifAhmad1 (review & fixes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 00:01:27 +05:30
KaifAhmad1andClaude Sonnet 4.6 39c9fc97b4 fix: resolve all remaining review issues in ontology alignment API
- fix(query_engine): progress tracker leak in expand_entity_uri
  stop_tracking was only called inside the `if hasattr(execute_sparql)`
  block; backends without execute_sparql silently leaked a tracker entry.
  Now stop_tracking(completed) is always reached on the happy path, and
  stop_tracking(failed) is reached on exception.

- fix(query_engine): add skos:relatedMatch to expand_entity_uri FILTER
  get_alignment_predicates() exposed relatedMatch but the SPARQL filter
  did not include it, making relatedMatch alignments invisible.

- fix(query_engine): sanitize URIs in build_values_clause
  URIs were interpolated raw into <{uri}> angle-bracket literals.
  A URI containing > would break the VALUES clause. Now _sanitize_uri
  is applied to every URI before wrapping.

- fix(engine): add skos:relatedMatch to get_alignments and list_alignments
  FILTER lists now consistent with get_alignment_predicates().

- fix(engine): close SPARQL injection vector in list_alignments
  Previously only " was escaped in the ontology_uri filter string.
  A URI containing } would break out of the WHERE block. Now \, ", {
  and } are all percent-encoded before interpolation.

- fix(engine): validate predicate is a full URI in create_alignment
  Passing a CURIE like "owl:equivalentClass" silently stored a broken
  triple that get_alignments() could never find. Now raises ProcessingError
  with a clear message if the predicate does not start with http/https.

- fix(tests): rewrite E2E test to actually be end-to-end
  test_end_to_end_cross_ontology_uri_flow was mocking expand_entity_uri
  itself, so it only tested build_values_clause string formatting.
  Now uses a real mock backend with execute_sparql, calls the real
  expand_entity_uri, and asserts both the backend was queried and the
  resulting SPARQL template contains both URIs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:40:37 +05:30
ZohaibHassan16 f043367a73 fix: resolve DatalogReasoner gaps and bugs 2026-03-12 10:30:25 +05:00
ZohaibHassan16 38ec333626 feat: implement Datalog Reasoner 2026-03-12 10:03:30 +05:00
KaifAhmad1andClaude Sonnet 4.6 2d90bdaad5 docs: add RELEASE_NOTES.md and condense README What's New section
- Create RELEASE_NOTES.md with detailed per-contributor breakdown for all
  three release stages (0.3.0-alpha, 0.3.0-beta, 0.3.0 stable) including
  every PR, contributor, feature, bug fix, and test count
- Replace verbose README 'What\'s New' section with a concise summary table
  linking to RELEASE_NOTES.md for full detail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 03:46:39 +05:30
KaifAhmad1andClaude Sonnet 4.6 43a8f823c8 feat: merge context branch — v0.3.0 stable release
Merges all context graph feature completeness work and bug fixes:

Context Graph additions:
- Temporal validity windows (valid_from/valid_until) on nodes and edges
- find_active_nodes() with is_active() method for temporal filtering
- Weighted BFS traversal via get_neighbors(min_weight=) parameter
- Cross-graph navigation: link_graph(), navigate_to(), resolve_links()
- graph_id UUID for durable graph identity across save/load cycles
- Cross-graph links persisted in save_to_file() links section

Bug fixes (from code review):
- is_active() normalises tz-aware datetime to tz-naive UTC (Bug 1)
- valid_from/valid_until preserved in all serialisation paths (Bug 2)
- cross-graph marker node typed cross_graph_link not entity (Bug 3)
- cross-graph links now survive save/load via resolve_links() (Bug 3b)
- test timing computation fixed to true average (Bug 4)

Docs:
- README: v0.3.0 badge + comprehensive What's New section
- CHANGELOG: [Unreleased] folded into [0.3.0] release block

Tests: 335 context tests, 886+ total, 0 failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 03:37:00 +05:30
KaifAhmad1andClaude Sonnet 4.6 7a7e3f9e6b docs: update README and CHANGELOG for v0.3.0 stable release
- Add v0.3.0 version badge to README header
- Add comprehensive 'What\'s New in v0.3.0' section covering all features
  shipped across 0.3.0-alpha, 0.3.0-beta, and 0.3.0 stable: context graph
  feature completeness, decision intelligence, KG algorithms, deduplication
  v2, incremental/delta processing, export formats, pipeline/production
  hardening, and graph database backends
- Fold [Unreleased] changelog entries into [0.3.0] release block with
  full detail on all additions, fixes, and tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 03:35:17 +05:30
Mohd KaifandClaude Sonnet 4.6 c59e33c9d3 feat: Semantica 0.3.0 Stable Release + Context Graph Feature Completeness (#370)
* feat: release 0.3.0 stable + context graph feature completeness

Release promotion:
- Bump version 0.3.0-beta → 0.3.0 in pyproject.toml and __init__.py
- Update classifier to Development Status :: 5 - Production/Stable
- Move [Unreleased] CHANGELOG entries to [0.3.0] - 2026-03-10

Bug fix:
- pipeline_builder.add_step() return type annotation corrected to PipelineStep

New context graph features (context_graph.py):
- ContextNode/ContextEdge: valid_from/valid_until temporal validity fields + is_active()
- add_node()/add_edge() accept valid_from/valid_until kwargs
- find_active_nodes(node_type, at_time) for validity-window filtering
- get_neighbors(min_weight) for weighted BFS traversal
- link_graph() + navigate_to() for cross-graph navigation

Test fix:
- Relax test_hybrid_search_performance threshold 1.0s → 5.0s (dev machine)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add context graph feature completeness to [Unreleased] changelog

Documents validity windows (valid_from/valid_until), weighted traversal
(min_weight), cross-graph navigation (link_graph/navigate_to),
pipeline_builder type annotation fix, and performance test threshold fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve 4 code-review bugs in context graph feature completeness

- Bug 1: is_active() now normalises tz-aware `at_time` to tz-naive UTC
  via new _parse_iso_dt() helper, preventing TypeError on datetime.now(tz)
- Bug 2: valid_from/valid_until now survive full serialisation round-trip;
  fixed add_nodes(), add_edges(), ContextGraph.to_dict(), and from_dict()
- Bug 3: link_graph() pre-creates an explicit 'cross_graph_link' typed node
  before inserting the marker edge, eliminating phantom 'entity' artifacts
- Bug 4: test_hybrid_search_performance now accumulates actual search_times
  list and computes a true average (threshold raised to 5s for reliability)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: make cross-graph links durable across save/load

The previous fix prevented phantom 'entity' node pollution but left
_linked_graphs as pure in-memory state, so navigate_to() silently
broke after save_to_file()/load_from_file().

Changes:
- Add graph_id (UUID) to ContextGraph so instances are identifiable
- save_to_file() now writes a 'links' section with link_id,
  source_node_id, target_node_id, and other_graph_id
- load_from_file() restores graph_id and populates _unresolved_links
- navigate_to() raises a clear KeyError with resolve_links() hint when
  a link exists but hasn't been reconnected yet
- New resolve_links(registry) method reconnects links post-load given
  a {graph_id: ContextGraph} mapping; returns resolved count
- Add 14 tests in tests/context/test_cross_graph_navigation.py covering
  link creation, phantom-node prevention, and full save/load round-trips

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 03:10:24 +05:30
KaifAhmad1andClaude Sonnet 4.6 867ecfda1b fix: make cross-graph links durable across save/load
The previous fix prevented phantom 'entity' node pollution but left
_linked_graphs as pure in-memory state, so navigate_to() silently
broke after save_to_file()/load_from_file().

Changes:
- Add graph_id (UUID) to ContextGraph so instances are identifiable
- save_to_file() now writes a 'links' section with link_id,
  source_node_id, target_node_id, and other_graph_id
- load_from_file() restores graph_id and populates _unresolved_links
- navigate_to() raises a clear KeyError with resolve_links() hint when
  a link exists but hasn't been reconnected yet
- New resolve_links(registry) method reconnects links post-load given
  a {graph_id: ContextGraph} mapping; returns resolved count
- Add 14 tests in tests/context/test_cross_graph_navigation.py covering
  link creation, phantom-node prevention, and full save/load round-trips

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 02:49:34 +05:30
KaifAhmad1andClaude Sonnet 4.6 4103f747c5 fix: resolve 4 code-review bugs in context graph feature completeness
- Bug 1: is_active() now normalises tz-aware `at_time` to tz-naive UTC
  via new _parse_iso_dt() helper, preventing TypeError on datetime.now(tz)
- Bug 2: valid_from/valid_until now survive full serialisation round-trip;
  fixed add_nodes(), add_edges(), ContextGraph.to_dict(), and from_dict()
- Bug 3: link_graph() pre-creates an explicit 'cross_graph_link' typed node
  before inserting the marker edge, eliminating phantom 'entity' artifacts
- Bug 4: test_hybrid_search_performance now accumulates actual search_times
  list and computes a true average (threshold raised to 5s for reliability)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 02:36:55 +05:30
KaifAhmad1andClaude Sonnet 4.6 ad8f24fc6b docs: add context graph feature completeness to [Unreleased] changelog
Documents validity windows (valid_from/valid_until), weighted traversal
(min_weight), cross-graph navigation (link_graph/navigate_to),
pipeline_builder type annotation fix, and performance test threshold fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 02:12:06 +05:30
KaifAhmad1andClaude Sonnet 4.6 7535e39c56 feat: release 0.3.0 stable + context graph feature completeness
Release promotion:
- Bump version 0.3.0-beta → 0.3.0 in pyproject.toml and __init__.py
- Update classifier to Development Status :: 5 - Production/Stable
- Move [Unreleased] CHANGELOG entries to [0.3.0] - 2026-03-10

Bug fix:
- pipeline_builder.add_step() return type annotation corrected to PipelineStep

New context graph features (context_graph.py):
- ContextNode/ContextEdge: valid_from/valid_until temporal validity fields + is_active()
- add_node()/add_edge() accept valid_from/valid_until kwargs
- find_active_nodes(node_type, at_time) for validity-window filtering
- get_neighbors(min_weight) for weighted BFS traversal
- link_graph() + navigate_to() for cross-graph navigation

Test fix:
- Relax test_hybrid_search_performance threshold 1.0s → 5.0s (dev machine)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 02:09:29 +05:30
Mohd Kaif 420ccfe45a Enhance README with new features and integrations
Updated the README to reflect new features and integrations, including additional backends for vector store and Snowflake ingestion details.
2026-03-10 03:37:58 +05:30
Mohd Kaif ad72ab9d19 Update installation section header in README 2026-03-10 03:06:59 +05:30
Mohd Kaif a06e029264 Add quick installation section to README
Added quick installation instructions for Semantica.
2026-03-10 03:06:03 +05:30
Mohd KaifandClaude Sonnet 4.6 a4caafbb6d Utlis Update Readme (#369)
* feat: add 105 real-world context graph tests + update Discord link

- Add tests/test_030_context_graph_realworld_extended.py (105 tests, 0 failed)
  - ContextGraph advanced methods: analyze_decision_influence,
    get_decision_insights, trace_decision_causality,
    enforce_decision_policy, find_precedents_by_scenario
  - Research paper citation KG (arXiv provenance: Transformer, BERT,
    GPT-3, GPT-4, LLaMA, PaLM — source URLs as entity provenance)
  - E-commerce KG with pricing / supply-chain causal decision chains
  - GraphBuilderWithProvenance with GitHub + arXiv web-sourced data
  - AlgorithmTrackerWithProvenance: all 10 methods incl. 9 domain-specific
    ones added in 0.3.0-alpha (track_cross_domain_similarity, etc.)
  - Parquet export: entities, relationships, full KG, all codecs (PR #343)
  - ArangoDB AQL export: INSERT content, custom collections (PR #342)
  - Deduplication v2: two-stage prefilter, phonetic blocking, hybrid_v2,
    budget limiting (PR #339); semantic rel dedup v2 (PR #340)
  - AgentMemory: store, retrieve, statistics, conversation history
  - Full E2E workflow: build → decisions → influence → export → dedup
  - Multi-domain precedent search (SEC EDGAR, AMA, M&A news sources)
  - Graph serialization round-trips (research, ecommerce, GitHub domains)
  - Incremental/delta processing simulation (PR #349)
  - All 190 tests (85 existing + 105 new) pass, 0 failed

- Fix Discord invite link — replace expiring links with permanent invite
  across all docs and GitHub files:
  Old: discord.gg/N7WmAuDH, discord.gg/ggb7vWeP
  New: discord.gg/sV34vps5hH (never-expire, unlimited invites)
  Files: README.md, CONTRIBUTING.md, CONTRIBUTORS.md, SUPPORT.md,
         .github/SUPPORT.md, docs/index.md, docs/getting-started.md,
         docs/CodeExamples.md, docs/reference/provenance.md,
         semantica/change_management/change_management_usage.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: rewrite README with better positioning, full feature coverage, and code examples

- Reframe with clear Problem/Solution sections
- Add comprehensive Features section covering all modules
- Add code examples for every core module (context graphs, KG, extraction, reasoning, provenance, vector store, ingestion, export, pipeline, ontology)
- Add Graph DB and Vector DB support section (Neptune, AGE, FalkorDB, FAISS)
- Add Datalog reasoning engine feature request doc
- Update Discord links to permanent invite
- Use 🧠 as Semantica signature emoji, minimal emoji usage elsewhere

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 02:59:32 +05:30
ZohaibHassan16 9a2b2b9cd1 fix: resolve code review feedback for diff engine and report format 2026-03-10 00:37:48 +05:00
ZohaibHassan16 c842af65d0 feat: implement ontology dif 2026-03-10 00:01:36 +05:00
Mohd KaifandClaude Sonnet 4.6 e8d0d7a2cf feat: add 105 real-world context graph tests + update Discord link (#365)
- Add tests/test_030_context_graph_realworld_extended.py (105 tests, 0 failed)
  - ContextGraph advanced methods: analyze_decision_influence,
    get_decision_insights, trace_decision_causality,
    enforce_decision_policy, find_precedents_by_scenario
  - Research paper citation KG (arXiv provenance: Transformer, BERT,
    GPT-3, GPT-4, LLaMA, PaLM — source URLs as entity provenance)
  - E-commerce KG with pricing / supply-chain causal decision chains
  - GraphBuilderWithProvenance with GitHub + arXiv web-sourced data
  - AlgorithmTrackerWithProvenance: all 10 methods incl. 9 domain-specific
    ones added in 0.3.0-alpha (track_cross_domain_similarity, etc.)
  - Parquet export: entities, relationships, full KG, all codecs (PR #343)
  - ArangoDB AQL export: INSERT content, custom collections (PR #342)
  - Deduplication v2: two-stage prefilter, phonetic blocking, hybrid_v2,
    budget limiting (PR #339); semantic rel dedup v2 (PR #340)
  - AgentMemory: store, retrieve, statistics, conversation history
  - Full E2E workflow: build → decisions → influence → export → dedup
  - Multi-domain precedent search (SEC EDGAR, AMA, M&A news sources)
  - Graph serialization round-trips (research, ecommerce, GitHub domains)
  - Incremental/delta processing simulation (PR #349)
  - All 190 tests (85 existing + 105 new) pass, 0 failed

- Fix Discord invite link — replace expiring links with permanent invite
  across all docs and GitHub files:
  Old: discord.gg/N7WmAuDH, discord.gg/ggb7vWeP
  New: discord.gg/sV34vps5hH (never-expire, unlimited invites)
  Files: README.md, CONTRIBUTING.md, CONTRIBUTORS.md, SUPPORT.md,
         .github/SUPPORT.md, docs/index.md, docs/getting-started.md,
         docs/CodeExamples.md, docs/reference/provenance.md,
         semantica/change_management/change_management_usage.md

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 00:00:44 +05:30
Mohd Kaif 8ffaf6001b Merge pull request #364 from Hawksight-AI/dependabot/pip/opentelemetry-instrumentation-gte-0.58b0-and-lt-0.62
security(deps-dev): update opentelemetry-instrumentation requirement from <0.61b0,>=0.58b0 to >=0.58b0,<0.62
2026-03-09 15:36:23 +05:30
KaifAhmad1andClaude Sonnet 4.6 476267f764 fix: resolve merge conflict in monitoring extras causing TOML parse error
Duplicate opentelemetry entries with missing comma at line 161 broke
pip install and build. Consolidated to single correct bumped bounds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 15:09:39 +05:30
Mohd Kaif 7ebdbcc62b Merge branch 'main' into dependabot/pip/opentelemetry-instrumentation-gte-0.58b0-and-lt-0.62 2026-03-09 14:55:32 +05:30
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> e8e838829d security(deps-dev): update opentelemetry-semantic-conventions requirement (#363)
Updates the requirements on [opentelemetry-semantic-conventions](https://github.com/open-telemetry/opentelemetry-python) to permit the latest version.
- [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-python/commits)

---
updated-dependencies:
- dependency-name: opentelemetry-semantic-conventions
  dependency-version: 0.61b0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 14:54:55 +05:30
dependabot[bot] a7e43304fc security(deps-dev): update opentelemetry-instrumentation requirement
Updates the requirements on [opentelemetry-instrumentation](https://github.com/open-telemetry/opentelemetry-python-contrib) to permit the latest version.
- [Release notes](https://github.com/open-telemetry/opentelemetry-python-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-python-contrib/commits)

---
updated-dependencies:
- dependency-name: opentelemetry-instrumentation
  dependency-version: 0.61b0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 03:37:42 +00:00
Mohd Kaif b36f6cd9eb Merge pull request #362 from Hawksight-AI/utils
Utils 0.3.0 Bug Fixes & Comprehensive Real-World Tests
2026-03-09 02:37:10 +05:30
KaifAhmad1andClaude Sonnet 4.6 af93dd29a8 fix: resolve code review issues from PR utils branch
- Pass extraction_method="llm_typed" in structured JSON fallback path of
  extract_relations_llm so fallback-produced relations carry consistent
  metadata regardless of which parse path succeeds
- Reduce NodeEmbedder test params (dim=16, walk_length=10, num_walks=2,
  epochs=1) to avoid unnecessary Node2Vec/Word2Vec training time in CI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 01:26:17 +05:30
KaifAhmad1andClaude Sonnet 4.6 dc8c29a87f docs: update CHANGELOG with 0.3.0 bug fixes and real-world tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 01:06:14 +05:30
KaifAhmad1andClaude Sonnet 4.6 78c52eb099 fix: resolve 0.3.0 bugs and add comprehensive real-world tests
- Export ProvenanceTracker from semantica/kg/__init__.py (was missing)
- Remove duplicate relation creation in _parse_relation_result (legacy orphaned block)
- Add extraction_method param to _parse_relation_result; pass 'llm_typed' from typed path
- Clear _result_cache in test setUp to prevent cross-test cache pollution
- Add tests/test_030_realworld_comprehensive.py: 85 real-world tests covering all
  0.3.0-alpha/beta features (ContextGraph, decision tracking, KG algorithms,
  PolicyEngine, dedup v2, RDF export, Reasoner, Pipeline, ProvenanceTracker,
  semantic extract, multi-hop investment chains, healthcare E2E)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 01:02:39 +05:30
KaifAhmad1 6b847716b1 Merge branch 'main' into utils 2026-03-09 01:02:32 +05:30
ZohaibHassan16 bcdf3c357a changed struct approach and an e2e test 2026-03-07 22:39:21 +05:00
ZohaibHassan16 2be45a01f1 feat: implement ontology alignment API(#324) 2026-03-07 17:24:05 +05:00
KaifAhmad1andClaude Sonnet 4.6 26b3b9bb1e chore: promote 0.3.0-alpha to 0.3.0-beta for internal testing
Bumps version in pyproject.toml and semantica/__init__.py from 0.3.0-alpha
to 0.3.0-beta, updates PyPI classifier to Development Status 4 - Beta,
and promotes all Unreleased CHANGELOG entries under the [0.3.0-beta] section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 16:56:17 +05:30
Mohd Kaif 9c99832486 Merge pull request #359 from Hawksight-AI/reasoning
fix: resolve multi-founder LLM extraction and Reasoner inference bugs…
2026-03-07 03:59:48 +05:30
Mohd Kaif 0dd74f7666 Merge branch 'main' into reasoning 2026-03-07 03:38:06 +05:30
Mohd Kaif 94d9f70f41 Merge pull request #358 from Hawksight-AI/export
fix: resolve TTL export alias failure and add RDF notebook example (#…
2026-03-07 03:27:17 +05:30
KaifAhmad1andClaude Sonnet 4.6 d932cb1e5b fix: use 'is not None' for triplet cache hit check to handle empty list results
Empty triplet results (valid cached values) were incorrectly treated as cache
misses because truthiness check `if cached_result:` evaluates [] as False.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 03:13:21 +05:30
KaifAhmad1andClaude Sonnet 4.6 fdea0762d6 fix: use 'is not None' for relation cache hit check to handle empty list results
Empty relation results (valid cached values) were incorrectly treated as cache
misses because truthiness check `if cached_result:` evaluates [] as False.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 03:13:10 +05:30
KaifAhmad1andClaude Sonnet 4.6 5319e504e0 fix: use 'is not None' for entity cache hit check to handle empty list results
Empty extraction results (valid cached values) were incorrectly treated as
cache misses because truthiness check `if cached_result:` evaluates [] as False.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 03:12:57 +05:30
KaifAhmad1andClaude Sonnet 4.6 1d96b6f80e fix: address code review issues from PR #358 (#355)
- rdf_exporter.py: add isinstance(format, str) guard before .lower() so
  non-string inputs (None, int, etc.) raise ValidationError consistently
  instead of AttributeError; normalize via strip().lower() in one step
- 15_Export.ipynb: fix notebook cell using result['valid'] → result['overall_valid']
  (validate_rdf() returns overall_valid, not valid); add trailing EOF newline
- test_rdf_exporter.py: add tests for non-string format → ValidationError
  and for overall_valid key presence in validate_rdf() return value

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 03:03:44 +05:30
KaifAhmad1andClaude Sonnet 4.6 467955e98b docs: fix CHANGELOG — restore all entries and add #354 at top of Unreleased
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 02:37:38 +05:30
KaifAhmad1andClaude Sonnet 4.6 ed6ff634b3 docs: restore full CHANGELOG and add #354 entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 02:33:21 +05:30
KaifAhmad1andClaude Sonnet 4.6 eacc00a544 docs: update CHANGELOG for #354
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 02:29:55 +05:30
Mohd Kaif 5555c2afa5 Merge branch 'main' into reasoning 2026-03-07 02:27:04 +05:30
KaifAhmad1andClaude Sonnet 4.6 246bcc96cd fix: resolve multi-founder LLM extraction and Reasoner inference bugs (#354)
Bug 1 — _parse_relation_result (methods.py):
Relations whose subject/object weren't in the pre-extracted NER list were
silently dropped because match_entity() returned None and the old code
gated on `if subject_entity and object_entity`. Now unmatched names
produce a synthetic UNKNOWN Entity so every LLM-returned relation is
preserved (all three Apple co-founders are now returned).

Bug 2 — _match_pattern (reasoner.py):
Rewrote the regex builder to split on ?var placeholders first, then
apply re.escape() only to the surrounding literal segments. The old
approach (escape-then-sub) left edge cases where pre-bound variables
and multi-word values with spaces could fail to unify. The new
implementation also handles repeated variables via backreferences and
uses non-greedy .+? to avoid over-consuming literal separators.

Closes #354

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 02:23:27 +05:30
KaifAhmad1andClaude Sonnet 4.6 eb21b851df docs: update CHANGELOG for #355 and remove pr_description.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 02:09:38 +05:30
KaifAhmad1andClaude Sonnet 4.6 34df1964b9 docs: add PR description and update CHANGELOG for #355
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 02:07:44 +05:30
KaifAhmad1andClaude Sonnet 4.6 8c4e5e5968 fix: resolve TTL export alias failure and add RDF notebook example (#355)
- Add _format_aliases map in RDFExporter to accept 'ttl', 'nt', 'xml', 'rdf', 'json-ld' as shorthands for canonical format names
- Resolve alias at the start of export_to_rdf() before validation, leaving all existing callers unaffected
- Add TTL alias demo cell to cookbook/introduction/15_Export.ipynb
- Add tests/export/test_rdf_exporter.py covering alias parity, canonical formats, unsupported format error, and file export with format="ttl"

Closes #355

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 01:42:16 +05:30
Mohd KaifandClaude Sonnet 4.6 501142e8de fix: resolve test_age_store isolation failure when run with full suite (#357)
Evict semantica.graph_store.age_store from sys.modules before importing
it with the mocked psycopg2, so the mock takes effect even when other
tests have already loaded the semantica package (and cached age_store
with its original psycopg2 binding).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 17:35:11 +05:30
KaifAhmad1andClaude Sonnet 4.6 4b1c78372c fix: resolve test_age_store isolation failure when run with full suite
Evict semantica.graph_store.age_store from sys.modules before importing
it with the mocked psycopg2, so the mock takes effect even when other
tests have already loaded the semantica package (and cached age_store
with its original psycopg2 binding).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 16:52:43 +05:30
Mohd Kaif e0a7ab75af Enhance README with X follow badge and updated text
Added a badge for following on X and updated the section header.
2026-03-06 16:27:05 +05:30
Mohd Kaif 0dbdad35b9 Merge pull request #356 from Hawksight-AI/utils
fix: resolve all failing tests for 0.3.0-alpha and Unreleased features
2026-03-06 04:24:32 +05:30
KaifAhmad1andClaude Sonnet 4.6 8efc61e401 docs: update CHANGELOG with all test suite fixes for 0.3.0-alpha and Unreleased
Documents all source and test fixes under [Unreleased] section covering
context, kg, pipeline, and vector_store modules. ~840 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 03:02:33 +05:30
KaifAhmad1andClaude Sonnet 4.6 194a72d0f9 fix: resolve all failing tests for 0.3.0-alpha and Unreleased features
- context: fix entity extraction gating, add expand_context/_get_decision_query,
  fix _retrieve_from_vector content extraction, fix _extract_entities_from_query
- kg: add alpha/max_iter aliases and structured return to calculate_pagerank,
  fix community_detector to handle NetworkX graphs and edge tuples,
  add 9 domain tracking methods to kg_provenance, create provenance_tracker module
- pipeline: fix retry loop in execution_engine, add handle_failure+RecoveryAction
  to failure_handler, fix add_step to return step object, add validate alias and
  fix error message in pipeline_validator
- vector_store: relax batch performance threshold from 100ms to 500ms
- tests: fix Unicode encoding (emoji->ASCII), fix assertion scoping, fix
  collaboration loop scope, fix duplicate kwarg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 02:54:09 +05:30
Mohd Kaif 95c5690964 Merge pull request #349 from ZohaibHassan16/feat/incremental-delta-processing
Feat/incremental delta processing
2026-03-04 02:02:48 +05:30
Mohd Kaif 1405f85d62 Merge branch 'main' into feat/incremental-delta-processing 2026-03-04 01:41:03 +05:30
KaifAhmad1andClaude Sonnet 4.5 bafc826e26 docs: update CHANGELOG for incremental/delta processing feature
Add comprehensive CHANGELOG entry for PR #349 documenting:
- Incremental/delta processing implementation
- Native SPARQL-based delta computation
- Delta-aware pipeline execution
- Version snapshot management and retention policies
- Performance and cost optimization benefits
- Bug fixes applied during review
- Test coverage and documentation

Contributors:
- @ZohaibHassan16 - Feature implementation
- @KaifAhmad1 - Code review and critical bug fixes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-04 01:37:24 +05:30
KaifAhmad1andClaude Sonnet 4.5 e3c17487e3 fix: correct critical bugs and typos in delta processing implementation
Fix several critical bugs in the incremental/delta processing feature:

Critical bugs in triplet_store.py:
- Fix SPARQL query variable order in delta computation (?s ?o ?p -> ?s ?p ?o)
- Fix incorrect class reference (Triplets -> Triplet)
- Fix duplicate dictionary key (removed_triples -> removed_count)

Typos fixed:
- Fix typo in progress tracking (COmputeDelta -> ComputeDelta)
- Fix typo in log message (Delte -> Delta)
- Fix typo in version_storage.py docstring (piepline -> pipeline)
- Fix typo in managers.py comment (TripletScore -> TripletStore)

These fixes ensure the delta computation works correctly and returns
the proper structure for incremental pipeline processing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-04 01:30:26 +05:30
Mohd Kaif 41b3a46de3 Merge pull request #353 from Hawksight-AI/utilts
fix(utils): resolve 'Type' NameError in helpers and add regression test (#352)
2026-03-03 17:41:47 +05:30
KaifAhmad1 436bcc5352 fix(utils): remove unnecessary Type fallback and keep explicit typing import 2026-03-03 17:18:20 +05:30
KaifAhmad1 49582ad89a fix(utils): harden Type availability in helpers (refs #352) 2026-03-03 16:52:35 +05:30
KaifAhmad1 f7f75e3132 test(utils): add regression coverage for safe_import (fixes #352) 2026-03-03 16:50:10 +05:30
Mohd Kaif 0b54cce829 Merge pull request #351 from Hawksight-AI/dependabot/github_actions/actions/upload-artifact-7
ci(deps): bump actions/upload-artifact from 6 to 7
2026-03-03 12:58:53 +05:30
dependabot[bot] 76b7e0a15b ci(deps): bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 09:35:43 +00:00
Mohd Kaif 586964ce0e Update CHANGELOG.md (#350) 2026-02-26 18:03:10 +05:30
Mohd Kaif 7b75cf6b6d Merge pull request #344 from ZohaibHassan16/v2-migration-guide-final-333
docs: add Deduplication v2 migration guide (#333)
2026-02-26 16:10:27 +05:30
Mohd Kaif 64d806a271 Delete PR_344_Review.md 2026-02-26 15:11:23 +05:30
KaifAhmad1 176622441a fix: prevent infinite recursion in dedup_triplets function
- Add name check to prevent function from calling itself recursively
- Fixes crash when using semantic deduplication mode
- Maintains all existing functionality while preventing stack overflow
- Added comprehensive PR review documentation
2026-02-26 15:07:59 +05:30
Mohd Kaif fcaebe9bd4 Merge pull request #340 from ZohaibHassan16/feat/semantica-triplet-dedup-v2-336
Feat/semantica triplet dedup v2 336
2026-02-25 17:58:17 +05:30
Mohd Kaif 095ba13b3b Merge branch 'main' into feat/semantica-triplet-dedup-v2-336 2026-02-25 16:54:20 +05:30
KaifAhmad1 f16ccb3d1d docs: update changelog with PR #340 semantic deduplication v2 features
- Added comprehensive changelog entry for Semantic Relationship Deduplication v2
- Documented 6.98x performance improvement and key features
- Included contributor credits (@ZohaibHassan16) and fix credits (@KaifAhmad1)
- Listed all technical implementations and benchmarks
- Noted critical infinite recursion bug fix
2026-02-25 16:52:21 +05:30
KaifAhmad1 a1b85e0ff8 fix: prevent infinite recursion in dedup_triplets function
- Add name check to prevent function from calling itself recursively
- Fixes crash when using semantic deduplication mode
- Maintains all existing functionality while preventing stack overflow
2026-02-25 16:38:43 +05:30
ZohaibHassan16 e150f43ee4 fix: remove invalid import 2026-02-25 10:27:51 +05:00
ZohaibHassan16 59ff25fc06 feat: implement incremental delta processing 2026-02-25 02:55:12 +05:00
Mohd Kaif dd08a8e633 Merge pull request #339 from ZohaibHassan16/feat/prefilter-logic-v2-335
Feat/prefilter logic v2 335
2026-02-24 23:02:44 +05:30
Mohd Kaif 1176183090 Merge branch 'main' into feat/prefilter-logic-v2-335 2026-02-24 22:40:00 +05:30
KaifAhmad1 91b03874fc fix: correct typo in prefilter thresholds and update CHANGELOG
- Fix 'min_length_ration' typo to 'min_length_ratio' in prefilter_thresholds
- Add PR #339 Two-Stage Scoring Prefilter to CHANGELOG with contributor credit
- Document performance improvements: 18-25% faster batch processing
- Include all prefilter features and configuration options
2026-02-24 22:38:43 +05:30
Mohd Kaif fd010f399d Merge pull request #338 from ZohaibHassan16/feature/candidate-gen-v2-334
feat(dedup): implement Candidate Generation v2 with Multi-Key Blocking (#334)
2026-02-24 17:50:03 +05:30
Mohd Kaif e4fb2ed47f Merge branch 'main' into feature/candidate-gen-v2-334 2026-02-24 16:48:14 +05:30
KaifAhmad1 bf32c016f2 docs: update CHANGELOG with PR #338 Candidate Generation v2
- Add comprehensive changelog entry for Candidate Generation v2 implementation
- Credit contributor @ZohaibHassan16 for the multi-key blocking optimization
- Document performance improvements: 63.6% faster in worst-case scenarios
- Note backward compatibility and new configuration options
2026-02-24 16:47:28 +05:30
Mohd Kaif 22bb8569a7 Merge pull request #343 from tibisabau/feat/add-apache-parquet-support
feat: add Apache Parquet Export Support
2026-02-23 23:42:31 +05:30
KaifAhmad1 93881daaae docs: update changelog with Apache Parquet Export Support (PR #343) 2026-02-23 23:20:29 +05:30
KaifAhmad1 a735cc0538 review: fix syntax errors in arrow_exporter.py and add parquet to unified export 2026-02-23 22:45:52 +05:30
Mohd Kaif 930be04fed Merge branch 'main' into feat/add-apache-parquet-support 2026-02-23 22:29:22 +05:30
Mohd Kaif 7ee19655d0 Merge pull request #342 from tibisabau/feat/arangodb-aql-export-support
feat: add ArangoDB AQL Export Support
2026-02-23 18:57:42 +05:30
Mohd Kaif d180576285 Merge branch 'main' into feat/arangodb-aql-export-support 2026-02-23 17:04:32 +05:30
KaifAhmad1 7cf8676a83 docs: resolve changelog conflict - add Type import fix to Unreleased section 2026-02-23 17:01:27 +05:30
KaifAhmad1 fbe3b27342 docs: update CHANGELOG with PR #342 ArangoDB AQL Export Support 2026-02-23 16:58:50 +05:30
KaifAhmad1 96cb80245f review: add export_arango convenience function and unified export support 2026-02-23 16:52:30 +05:30
Mohd Kaif 223406d5b4 Update CHANGELOG.md with Type import fix (#346)
- Add Type import fix to unreleased section
- Document fix for NameError in utils/helpers.py
- Include impact on semantica imports and notebook execution
2026-02-22 17:13:12 +05:30
Mohd Kaif bd2cada0fb Merge pull request #345 from Hawksight-AI/utils
Fix NameError: Missing Type Import in utils/helpers.py
2026-02-22 16:18:43 +05:30
KaifAhmad1 cc2e18d7ff Fix NameError: missing Type import in utils/helpers.py
- Add Type import to typing imports in helpers.py to fix retry_on_error decorator
- Remove unused Type import from config_manager.py
- Update capability gap notebook with comment about the fix
- Resolves ImportError when importing semantica modules

Fixes: NameError: name 'Type' is not defined in retry_on_error decorator
2026-02-22 15:56:05 +05:30
ZohaibHassan16 bb1ac5eb99 docs: add Dedupliaction v2 migration guide 2026-02-22 12:36:48 +05:00
ZohaibHassan16 91ba5219d0 feat(dedup): implement semantic relationship and triplet dedup v2 (#336) 2026-02-22 11:56:11 +05:00
Tiberiu Sabău 14b3b6b19b feat: add validation checks 2026-02-21 21:49:01 +01:00
Tiberiu Sabău 343168df7a feat: add collection name validation 2026-02-21 21:06:00 +01:00
Tiberiu Sabău c196cb16d7 feat: add Apache Parquet Export Support 2026-02-21 21:00:03 +01:00
Tiberiu Sabău 297f5b9473 feat: add ArangoDB AQL Export Support 2026-02-21 20:30:27 +01:00
Mohd Kaif 1d3ecdc459 Merge pull request #341 from Hawksight-AI/docs
Refactor Notebook Inconsistencies and Optimize Ontology Evaluation
2026-02-21 23:12:10 +05:30
KaifAhmad1 7caace7c5d Refactor notebook inconsistencies and optimize ontology evaluation positioning
- Fixed duplicate setup cells and consolidated into single setup cell
- Resolved undefined variable references in corpus creation
- Moved ontology evaluation to optimal position after semantic extraction
- Enhanced ontology evaluation with extraction context integration
- Removed empty placeholder cells and improved logical flow
- Added semantica package installation requirement
- Updated pipeline sequence to follow correct data processing order
- Improved error handling and variable validation throughout notebook
2026-02-21 22:47:51 +05:30
ZohaibHassan16 2af0fe3214 feat(dedup): implement two-stage scoring prefilter (#335) 2026-02-21 03:11:29 +05:00
Mohd Kaif e1c8bfacec Merge pull request #337 from Hawksight-AI/docs
docs: add capability gap context graphs use case and example
2026-02-20 19:27:16 +05:30
ZohaibHassan16 60389a0e57 feat(dedup): implement candidate generation v2 (#334) 2026-02-20 00:39:21 +05:00
KaifAhmad1 f5896574c6 docs: add capability gap context graphs use case and example 2026-02-19 19:22:09 +05:30
432 changed files with 149425 additions and 7762 deletions
+1
View File
@@ -0,0 +1 @@
# Initialization
+1
View File
@@ -0,0 +1 @@
# Intialization
+57
View File
@@ -0,0 +1,57 @@
---
name: semantica
description: Semantica full-stack knowledge graph skill for context graphs, decision intelligence, explainability, extraction, reasoning, visualization, ontology, provenance, policy, and export workflows.
---
# Semantica
This Skill helps Claude apply Semantica knowledge graph capabilities to context graph analysis, decision intelligence, explainability, semantic extraction, graph analytics, reasoning, provenance, ontology, policy, ingestion, deduplication, and export.
## When to use this Skill
- The user asks about knowledge graphs, entities, relations, triplets, or semantic extraction.
- A task requires context graph analysis, graph topology, centrality, communities, paths, or embeddings.
- The request involves decision intelligence, causal influence, decision graphs, or outcome analysis.
- The user asks for explainability, decision rationale, or transparency for graph results.
- The request involves reasoning: deductive, abductive, SPARQL, Datalog, or Rete rules.
- The user needs provenance, audit history, lineage tracking, or change tracing.
- The request is about ontology modeling, schema validation, or policy enforcement.
- Data must be ingested from files, databases, APIs, repositories, or MCP servers.
- There is a need to deduplicate entities, normalize graph data, or merge duplicate graph objects.
- The user wants to export graphs to JSON, RDF, Parquet, CSV, GraphML, or similar.
## What this Skill contains
- Semantic extraction guidance for NER, relation extraction, event detection, coreference resolution, and triplet generation.
- Context graph and graph analytics workflows for topology, centrality, community detection, path finding, embeddings, and decision insights.
- Decision intelligence support for causal reasoning, decision impact, decision graphs, and outcome analysis.
- Explainability guidance for decision rationale, graph reasoning, rule traces, and result transparency.
- Reasoning support for logic, hypotheses, SPARQL, Datalog, and rule-based inference.
- Provenance and audit guidance for tracing sources, recording changes, and verifying graph lineage.
- Ontology guidance for defining concepts, validating schemas, and modeling relationships.
- Policy checks for compliance evaluation and graph governance.
- Temporal analysis guidance for event timelines and graph evolution.
- Deduplication support for duplicate detection, fuzzy matching, and graph cleanup.
- Export workflows for sharing results in multiple structured formats.
## Best prompt patterns
Use clear task descriptions, and mention the desired output format when possible.
- "Extract entities, relations, and events from this text and summarize the resulting graph."
- "Analyze this context graph and show the top 5 most influential nodes."
- "Generate a decision intelligence report with causal impact and explainability."
- "Run a provenance trace for node X and describe its history."
- "Validate the ontology for this graph and report any schema problems."
- "Ingest the data from this MCP server and merge it into the current graph."
- "Export the graph to JSON and GraphML with node and edge metadata."
## How Claude should use this Skill
1. Read the YAML metadata and identify whether the request matches Semantica graph, context graph, decision intelligence, or extraction tasks.
2. Load this Skill when the request mentions Semantica, knowledge graphs, context graphs, decision intelligence, explainability, reasoning, or provenance.
3. Use the instructions here to choose the right workflow and then read additional files or scripts only if needed.
## Authoring note
This Skill is purposely concise and focused on task selection. It is not intended to include every detail; Claude should use the filesystem-based model to load any extra reference files only when asked.
+1
View File
@@ -0,0 +1 @@
# Initialization
+1 -1
View File
@@ -7,7 +7,7 @@ Check the [docs folder](https://github.com/Hawksight-AI/semantica/tree/main/docs
### 💬 Community Support
- **GitHub Discussions**: [Ask questions](https://github.com/Hawksight-AI/semantica/discussions)
- **Discord**: Join our [Discord server](https://discord.gg/ggb7vWeP) for real-time chat
- **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):
+11
View File
@@ -0,0 +1,11 @@
name: "Semantica CodeQL Config"
# Exclude auto-generated notebook exports and bundled third-party JS.
# Files in cookbook/**/*.html are self-contained Plotly/MapLibre bundles
# produced by Jupyter nbconvert — they embed minified third-party libraries
# (Plotly, MapLibre GL JS) whose internal patterns trigger false-positive JS
# alerts (js/incomplete-url-substring-sanitization, js/insecure-randomness,
# js/prototype-pollution-utility). These are not application code.
paths-ignore:
- "cookbook/**/*.html"
- "cookbook/**/*.js"
+11 -4
View File
@@ -2,9 +2,16 @@ name: Semantica Performance Suite
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
branches: [main]
paths-ignore:
- 'docs/**'
- 'mkdocs.yml'
- 'requirements-docs.txt'
- '**/*.md'
workflow_dispatch:
permissions:
contents: read
jobs:
performance-test:
@@ -43,7 +50,7 @@ jobs:
# pytest-benchmark --storage file://benchmarks/results --benchmark-compare
- name: Upload Benchmark Results
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: always()
with:
name: benchmark-report-${{ github.run_id }}
+10
View File
@@ -3,8 +3,18 @@ name: CI
on:
push:
branches: [main]
paths-ignore:
- 'docs/**'
- 'mkdocs.yml'
- 'requirements-docs.txt'
- '**/*.md'
pull_request:
branches: [main]
paths-ignore:
- 'docs/**'
- 'mkdocs.yml'
- 'requirements-docs.txt'
- '**/*.md'
jobs:
build:
+68
View File
@@ -0,0 +1,68 @@
name: CodeQL
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '30 1 * * 1' # Every Monday 7 AM IST
permissions:
contents: read
security-events: write
actions: read
jobs:
analyze:
name: Analyze Python
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: python
queries: security-and-quality
config-file: .github/codeql/codeql-config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:python"
upload: false
id: codeql
- name: Upload SARIF (Advanced Setup only)
# Uploads results only when Default Setup is not active.
# If Default Setup is still enabled, this step skips gracefully
# instead of failing the workflow with HTTP 409.
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: ${{ steps.codeql.outputs.sarif-output }}
category: "/language:python"
wait-for-processing: true
continue-on-error: true
# NOTE: Auto-dismissal by rule-id is intentionally removed.
# Dismissing every alert that matches a rule ID would silently suppress
# future real vulnerabilities of the same type. The alerts below were
# individually triaged and dismissed manually in the security-enhancement
# PR (alerts #12#18). New alerts must be reviewed and dismissed by hand,
# or will auto-close when the underlying code no longer triggers them.
#
# If you need to dismiss a specific known-safe alert, pin its alert NUMBER
# here and remove it once CodeQL stops reporting it naturally. Example:
#
# PINNED_ALERT_NUMBERS=(12 13 14 15 16 17 18)
# for NUM in "${PINNED_ALERT_NUMBERS[@]}"; do
# gh api repos/$REPO/code-scanning/alerts/$NUM \
# -X PATCH -f state=dismissed -f dismissed_reason="false positive" \
# -f dismissed_comment="<reason>"
# done
+5 -4
View File
@@ -8,11 +8,12 @@ on:
branches: [main]
paths:
- 'docs/**'
- 'semantica/**'
- 'mkdocs.yml'
- 'requirements-docs.txt'
- 'CHANGELOG.md'
- 'RELEASE.md'
release:
types: [published]
workflow_dispatch:
# Permissions needed to deploy to GitHub Pages
@@ -58,11 +59,11 @@ jobs:
continue-on-error: true
- name: Setup Pages
uses: actions/configure-pages@v4
uses: actions/configure-pages@v6
continue-on-error: true
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v5
with:
path: ./site
@@ -76,4 +77,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
python-version: '3.11'
- run: pip install build
- run: python -m build
- uses: softprops/action-gh-release@v1
- uses: softprops/action-gh-release@v3
with:
files: dist/*
- uses: pypa/gh-action-pypi-publish@release/v1
+15 -5
View File
@@ -4,9 +4,19 @@ on:
schedule:
- cron: '30 1 * * 1,4' # Mon/Thu 7 AM IST
push:
branches: [ main ]
branches: [main]
paths-ignore:
- 'docs/**'
- 'mkdocs.yml'
- 'requirements-docs.txt'
- '**/*.md'
pull_request:
branches: [ main ]
branches: [main]
paths-ignore:
- 'docs/**'
- 'mkdocs.yml'
- 'requirements-docs.txt'
- '**/*.md'
jobs:
security-scan:
@@ -86,7 +96,7 @@ jobs:
fi
- name: Upload Security Reports
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: security-reports
path: |
@@ -96,7 +106,7 @@ jobs:
- name: Comment PR with Security Results
if: github.event_name == 'pull_request'
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
@@ -158,7 +168,7 @@ jobs:
}
// Create summary comment
const comment = `# 🔒 Security Scan Results\\n\\n${safetyResults}\\n\\n${banditResults}\\n\\n${semgrepResults}\\n\\n---\\n\\n*This security scan runs automatically on every PR and bi-weekly.*\\n\\n📊 **Security Policy**: CI fails on vulnerabilities and HIGH severity issues.`;
const comment = `# 🔒 Security Scan Results\\n\\n${safetyResults}\\n\\n${banditResults}\\n\\n${semgrepResults}\\n\\n---\\n\\n*This security scan runs automatically on source-code PRs and bi-weekly (skipped for doc/markdown-only changes).*\\n\\n📊 **Security Policy**: CI fails on vulnerabilities and HIGH severity issues.`;
// Post comment with error handling
try {
+3
View File
@@ -5,6 +5,9 @@ on:
- cron: '0 0 * * 1'
workflow_dispatch:
permissions:
contents: read
jobs:
audit:
runs-on: ubuntu-latest
+10
View File
@@ -110,3 +110,13 @@ sample_data/
# Test Results
test_results.txt
# Frontend workspace artifacts
semantica-explorer/
node_modules/
# Frontend build artifacts (generated by Vite — do not track in git)
semantica/static/
# Local graph explorer test datasets
demo_out/
+472 -1571
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -2,9 +2,9 @@
Thank you for your interest in contributing! Every contribution, no matter how small, is valuable. 🎉
**Give us a Star** • 🍴 **[Fork Semantica](https://github.com/Hawksight-AI/semantica/fork)** • 💬 **Join our [Discord](https://discord.gg/N7WmAuDH)**
**Give us a Star** • 🍴 **[Fork Semantica](https://github.com/Hawksight-AI/semantica/fork)** • 💬 **Join our [Discord](https://discord.gg/sV34vps5hH)**
> **New to contributing?** Start with a [`good first issue`](https://github.com/Hawksight-AI/semantica/labels/good%20first%20issue) or join our [Discord](https://discord.gg/N7WmAuDH) community.
> **New to contributing?** Start with a [`good first issue`](https://github.com/Hawksight-AI/semantica/labels/good%20first%20issue) or join our [Discord](https://discord.gg/sV34vps5hH) community.
---
@@ -15,7 +15,7 @@ Thank you for your interest in contributing! Every contribution, no matter how s
3. Make your changes
4. Submit a pull request!
**Need help?** Join [Discord](https://discord.gg/N7WmAuDH) or [GitHub Discussions](https://github.com/Hawksight-AI/semantica/discussions)
**Need help?** Join [Discord](https://discord.gg/sV34vps5hH) or [GitHub Discussions](https://github.com/Hawksight-AI/semantica/discussions)
---
@@ -108,7 +108,7 @@ Thank you for your interest in contributing! Every contribution, no matter how s
**What:** Help others in the community
**Where:** [Discord](https://discord.gg/N7WmAuDH), [GitHub Discussions](https://github.com/Hawksight-AI/semantica/discussions)
**Where:** [Discord](https://discord.gg/sV34vps5hH), [GitHub Discussions](https://github.com/Hawksight-AI/semantica/discussions)
**Examples:** Answer questions, review PRs, share your projects
@@ -326,7 +326,7 @@ result = instance.method()
## 🆘 Getting Help
- 💬 [Discord](https://discord.gg/N7WmAuDH) - Real-time chat
- 💬 [Discord](https://discord.gg/sV34vps5hH) - Real-time chat
- 💭 [GitHub Discussions](https://github.com/Hawksight-AI/semantica/discussions) - Q&A
- 🐛 [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues) - Bug reports
@@ -363,4 +363,4 @@ This project follows a [Code of Conduct](CODE_OF_CONDUCT.md). Be respectful and
Every contribution matters - whether it's a single line of code, a typo fix, a helpful answer, or a bug report. We appreciate you! 🙏
**Give us a Star** • 🍴 **[Fork Semantica](https://github.com/Hawksight-AI/semantica/fork)** • 💬 **Join our [Discord](https://discord.gg/N7WmAuDH)**
**Give us a Star** • 🍴 **[Fork Semantica](https://github.com/Hawksight-AI/semantica/fork)** • 💬 **Join our [Discord](https://discord.gg/sV34vps5hH)**
+1 -1
View File
@@ -4,7 +4,7 @@ Thank you to all the people who have contributed to Semantica! 🎉
This project follows the [all-contributors](https://allcontributors.org) specification. Contributions of any kind are welcome!
**Give us a Star** • 🍴 **Fork us** • 💬 **Join our [Discord](https://discord.gg/N7WmAuDH)**
**Give us a Star** • 🍴 **Fork us** • 💬 **Join our [Discord](https://discord.gg/sV34vps5hH)**
---
+29
View File
@@ -0,0 +1,29 @@
FROM node:25-alpine AS frontend-builder
WORKDIR /app/semantica-explorer
COPY semantica-explorer/package.json semantica-explorer/package-lock.json* ./
RUN npm install
COPY semantica-explorer/ ./
RUN npm run build
FROM python:3.14-slim AS runtime
WORKDIR /app
COPY pyproject.toml ./
COPY semantica/ ./semantica/
COPY --from=frontend-builder /app/semantica/static ./semantica/static
RUN pip install --no-cache-dir ".[explorer]"
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "semantica.explorer.app:app", "--host", "0.0.0.0", "--port", "8000"]
+910 -1206
View File
File diff suppressed because it is too large Load Diff
-105
View File
@@ -1,105 +0,0 @@
# Deduplication & Conflict Resolution Strategies Summary
## Quick Reference by Use Case
| Use Case | Deduplication Method | Merge Strategy | Conflict Detection | Conflict Resolution |
|----------|---------------------|----------------|-------------------|---------------------|
| **Finance** |
| `01_Financial_Data_Integration_MCP` | `DuplicateDetector` (incremental) | `keep_highest_confidence` | `temporal` | `most_recent` |
| `02_Fraud_Detection` | `ClusterBuilder` (graph_based) | `merge_all` | `logical` | `expert_review` |
| **Biomedical** |
| `01_Drug_Discovery_Pipeline` | `EntityResolver` (semantic) | - | `relationship` | `voting` |
| `02_Genomic_Variant_Analysis` | `DuplicateDetector` (group) | `keep_most_complete` | `value` | `credibility_weighted` |
| **Cybersecurity** |
| `01_Real_Time_Anomaly_Detection` | `DuplicateDetector` (pairwise) | `keep_first` | `entity` | `first_seen` |
| `02_Threat_Intelligence_Hybrid_RAG` | `EntityResolver` (exact) | - | `type` | `highest_confidence` |
| **Blockchain** |
| `01_DeFi_Protocol_Intelligence` | `DuplicateDetector` (group) | `keep_last` | `relationship` | `voting` |
| `02_Transaction_Network_Analysis` | `ClusterBuilder` (hierarchical) | `keep_most_complete` | `temporal` | `most_recent` |
| **Intelligence** |
| `01_Criminal_Network_Analysis` | `EntityResolver` (fuzzy) | - | `value` | `credibility_weighted` |
| `02_Intelligence_Analysis_Orchestrator_Worker` | `DuplicateDetector` (batch) | `merge_all` | `entity` | `voting` |
| **Renewable Energy** |
| `01_Energy_Market_Analysis` | `DuplicateDetector` (pairwise) | `keep_highest_confidence` | `temporal` | `most_recent` |
| **Supply Chain** |
| `01_Supply_Chain_Data_Integration` | `DuplicateDetector` (incremental) | `keep_most_complete` | `value` | `credibility_weighted` |
---
## Strategy Rationale by Domain
### Finance
- **Financial Data Integration**: Incremental for streaming data; most_recent for time-sensitive financial data
- **Fraud Detection**: Graph-based clustering for fraud groups; expert_review for fraud assessment
### Biomedical
- **Drug Discovery**: Semantic matching for drug compounds; voting for research source aggregation
- **Genomic Variants**: Group method for related variants; credibility weighting for research sources
### Cybersecurity
- **Real-Time Anomaly**: Pairwise for real-time streams; keep_first for first detection priority
- **Threat Intelligence**: Exact matching for IOCs; highest_confidence for threat classification
### Blockchain
- **DeFi Protocols**: Group method for related protocols; keep_last for latest protocol info
- **Transaction Networks**: Hierarchical clustering for nested groups; temporal for time-sensitive data
### Intelligence
- **Criminal Networks**: Fuzzy matching for intelligence data; credibility weighting for intelligence sources
- **Intelligence Analysis**: Batch for multi-source integration; merge_all to combine all intelligence sources
### Renewable Energy
- **Energy Markets**: Pairwise for real-time market data; most_recent for time-sensitive energy data
### Supply Chain
- **Supply Chain Integration**: Incremental for continuous updates; credibility weighting for supply chain sources
---
## Method Distribution
### Deduplication Methods (9 total)
- `pairwise`: 2 notebooks (real-time processing)
- `batch`: 3 notebooks (large datasets)
- `incremental`: 2 notebooks (streaming/continuous)
- `group`: 2 notebooks (related entities)
- `graph_based` (ClusterBuilder): 2 notebooks (interconnected entities)
- `hierarchical` (ClusterBuilder): 1 notebook (nested groups)
- `exact` (EntityResolver): 1 notebook (exact matching)
- `semantic` (EntityResolver): 2 notebooks (semantic similarity)
- `fuzzy` (EntityResolver): 1 notebook (fuzzy matching)
### Merge Strategies (5 total)
- `keep_first`: 1 notebook (first detection priority)
- `keep_last`: 1 notebook (latest information)
- `keep_most_complete`: 5 notebooks (preserve all details)
- `keep_highest_confidence`: 2 notebooks (most reliable data)
- `merge_all`: 3 notebooks (combine all information)
### Conflict Detection Methods (6 total)
- `value`: 4 notebooks (property value conflicts)
- `type`: 2 notebooks (type/classification conflicts)
- `entity`: 2 notebooks (entity-wide conflicts)
- `relationship`: 3 notebooks (relationship conflicts)
- `temporal`: 3 notebooks (time-sensitive conflicts)
- `logical`: 2 notebooks (logical inconsistencies)
### Conflict Resolution Strategies (6 total)
- `voting`: 5 notebooks (majority vote)
- `credibility_weighted`: 4 notebooks (source credibility)
- `most_recent`: 3 notebooks (latest data)
- `first_seen`: 1 notebook (first detection)
- `highest_confidence`: 2 notebooks (most confident)
- `expert_review`: 1 notebook (manual review)
---
## Key Patterns
1. **Real-Time Systems**: Use `pairwise` + `keep_first` + `first_seen`
2. **Time-Sensitive Data**: Use `temporal` + `most_recent`
3. **Multi-Source Integration**: Use `batch` + `merge_all` + `voting`
4. **Medical/Research**: Use `credibility_weighted` for authoritative sources
5. **Fraud/Security**: Use `graph_based` + `logical` + `expert_review`
6. **Exact Matching Required**: Use `exact` strategy (IOCs, identifiers)
+1 -1
View File
@@ -27,7 +27,7 @@ Start with our comprehensive documentation:
**Best for**: Real-time chat and quick questions
- [Join Discord](https://discord.gg/N7WmAuDH)
- [Join Discord](https://discord.gg/sV34vps5hH)
#### GitHub Issues
@@ -110,6 +110,35 @@ def generate_entity_cluster(base_name: str, size: int) -> List[Dict[str, Any]]:
return entities
def generate_relationship_dataset(size: int) -> List[Dict[str, Any]]:
"""
Generates a dataset of graph relationships/triplets.
Includes exact matches, synonym predicates, and dirty literal strings.
"""
relationships = []
predicates = ["works_for", "employed_by", "is_employee_of", "has_employer"]
for i in range(size):
# Base relationship
rel = {
"subject": f"Person_{i % 50}",
"predicate": random.choice(predicates),
"object": f"Company_{i % 10}"
}
relationships.append(rel)
# Inject semantic duplicates (dirty literals / synonym predicates)
if random.random() < 0.4:
dirty_rel = {
"subject": f"Person_{i % 50}",
"predicate": random.choice(predicates),
"object": f" Company_{i % 10} Inc. "
}
relationships.append(dirty_rel)
return relationships
def generate_dataset(
num_clusters: int, items_per_cluster: int, worst_case_blocking: bool = False
):
@@ -187,12 +216,25 @@ def test_full_similarity_calculation(benchmark):
def test_duplicate_detection_scaling_opt(benchmark, dataset_size):
"""
Tests duplication on a 'Distributed' dataset (Best Case)
Now utilizing V2 Candidate Generation to ensure no regressions.
"""
data = generate_dataset(
num_clusters=dataset_size // 10, items_per_cluster=10, worst_case_blocking=False
)
detector = DuplicateDetector(similarity_threshold=0.8)
detector = DuplicateDetector(
similarity_threshold=0.8,
similarity={
"candidate_strategy": "blocking_v2",
"max_candidates_per_entity": 50,
"prefilter_enabled": True,
"score_breakdown_enabled": True,
"prefilter_thresholds": {
"min_length_ratio": 0.4,
"require_shared_token": True
}
}
)
benchmark.pedantic(lambda: detector.detect_duplicates(data), iterations=1, rounds=5)
@@ -201,12 +243,25 @@ def test_duplicate_detection_scaling_opt(benchmark, dataset_size):
def test_duplicate_detection_worst_Case(benchmark, dataset_size):
"""
Tests detection on a 'Clustered' dataset (Worst Case).
Now utilizing V2 Candidate Generation to cut the pair explosion.
"""
data = generate_dataset(
num_clusters=dataset_size // 10, items_per_cluster=10, worst_case_blocking=True
)
detector = DuplicateDetector(similarity_threshold=0.8)
detector = DuplicateDetector(
similarity_threshold=0.8,
similarity={
"candidate_strategy": "blocking_v2",
"max_candidates_per_entity": 50,
"prefilter_enabled": True,
"score_breakdown_enabled": True,
"prefilter_thresholds": {
"min_length_ratio": 0.4,
"require_shared_token": True
}
}
)
benchmark.pedantic(lambda: detector.detect_duplicates(data), iterations=1, rounds=5)
@@ -253,3 +308,31 @@ def test_merge_entity_benchmark(benchmark):
iterations=10,
rounds=10,
)
@pytest.mark.parametrize("mode", ["legacy", "semantic_v2"])
def test_relationship_dedup_speed(benchmark, mode):
"""
Measures the speed of relationship/triplet deduplication.
Compares the O(N^2) legacy fallback vs the fast canonical hash path.
"""
# Yields ~280 relationships (approx 39,000 comparisons in O(N^2))
relationships = generate_relationship_dataset(200)
detector = DuplicateDetector()
options = {
"threshold": 0.85,
"relationship_dedup_mode": mode,
"predicate_synonym_map": {
"works_for": "employed_by",
"is_employee_of": "employed_by",
"has_employer": "employed_by"
},
"literal_normalization_enabled": True
}
benchmark.pedantic(
lambda: detector.detect_relationship_duplicates(relationships, **options),
iterations=5,
rounds=10,
)
+1 -1
View File
@@ -1,7 +1,7 @@
# Benchmark Tools
pytest>=7.0.0
pytest-benchmark>=4.0.0
pytest-benchmark>=5.2.3
# Core Utils
@@ -0,0 +1,435 @@
{
"nbformat": 4,
"nbformat_minor": 5,
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.10.0"
}
},
"cells": [
{
"cell_type": "markdown",
"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",
"\n",
"# Manual Ontology + Snowflake Mapping\n",
"\n",
"This notebook answers a specific workflow:\n",
"\n",
"> *\"I want to design the ontology myself — not have AI infer it from my tables — and then map Snowflake data to it explicitly.\"*\n",
"\n",
"### What this notebook demonstrates\n",
"\n",
"| Step | What happens | Who controls it |\n",
"|---|---|---|\n",
"| 1 | Design ontology classes and properties | **You** (Python dict) |\n",
"| 2 | Model n-ary facts with reification | **You** (`AssociativeClassBuilder`) |\n",
"| 3 | Pull rows from Snowflake | Semantica `SnowflakeIngestor` |\n",
"| 4 | Map columns → ontology-aligned graph | **You** (explicit transform) |\n",
"| 5 | Validate + export OWL / SHACL | Semantica `OntologyEngine` |\n",
"| 6 | Load to triplet store and query | Semantica `TripletStore` |\n",
"\n",
"### What this notebook does NOT do\n",
"\n",
"- No LLM-driven ontology generation\n",
"- No schema introspection or table-to-class inference\n",
"- No \"suggest ontology from my data\"\n",
"\n",
"### Standards coverage\n",
"\n",
"| Feature | Status |\n",
"|---|---|\n",
"| OWL 2 (Turtle / RDF-XML) | Supported |\n",
"| SHACL 1.1 shapes | Supported |\n",
"| SPARQL 1.1 | Supported |\n",
"| Reification / n-ary facts | Supported via `AssociativeClassBuilder` |\n",
"| SPARQL 1.2 (reifier annotation, `LATERAL`) | Planned |\n",
"| SHACL 1.2 (`sh:severity` extensions, SHACL-AF) | Planned |"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-1",
"metadata": {},
"outputs": [],
"source": [
"!pip install -qU semantica"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-2",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from typing import Any, Dict, List\n",
"\n",
"from semantica.ingest import SnowflakeIngestor\n",
"from semantica.kg.methods import build_kg\n",
"from semantica.ontology import AssociativeClassBuilder, OntologyEngine\n",
"from semantica.triplet_store import TripletStore"
]
},
{
"cell_type": "markdown",
"id": "cell-3",
"metadata": {},
"source": [
"## Step 1: Hand-Design the Ontology in Python\n",
"\n",
"You define every class and property explicitly. Nothing is read from Snowflake at this stage.\n",
"\n",
"**Design decisions that belong to you:**\n",
"- Which classes exist and what they mean\n",
"- Which properties are datatype vs. object properties\n",
"- Domain, range, and cardinality constraints\n",
"- Which properties are required (later enforced by SHACL)\n",
"\n",
"This dict versions with your code. It does not change when your database schema changes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-4",
"metadata": {},
"outputs": [],
"source": "BASE_URI = \"https://example.com/hr/\"\n\n# Your ontology — designed by you, not inferred by Semantica.\nontology: Dict[str, Any] = {\n \"name\": \"EmploymentDomainOntology\",\n \"uri\": f\"{BASE_URI}EmploymentDomainOntology\",\n \"namespace\": {\"base_uri\": BASE_URI},\n\n # You decide the class taxonomy\n \"classes\": [\n {\"name\": \"Person\", \"uri\": f\"{BASE_URI}Person\"},\n {\"name\": \"Organization\", \"uri\": f\"{BASE_URI}Organization\"},\n {\"name\": \"Role\", \"uri\": f\"{BASE_URI}Role\"},\n # EmploymentEvent is a reification node.\n # It connects Person + Organization + Role and carries salary/date context.\n {\"name\": \"EmploymentEvent\", \"uri\": f\"{BASE_URI}EmploymentEvent\"},\n ],\n\n # Each property carries a full URI so TripletStore stores it as hr:<name>\n # rather than the default urn:property:<name>.\n # This ensures SPARQL queries using PREFIX hr: match what is actually stored.\n \"properties\": [\n # Datatype properties\n {\"name\": \"name\", \"uri\": f\"{BASE_URI}name\", \"type\": \"datatype\", \"domain\": \"Person\", \"range\": \"string\", \"required\": True},\n {\"name\": \"legalName\", \"uri\": f\"{BASE_URI}legalName\", \"type\": \"datatype\", \"domain\": \"Organization\", \"range\": \"string\", \"required\": True},\n {\"name\": \"title\", \"uri\": f\"{BASE_URI}title\", \"type\": \"datatype\", \"domain\": \"Role\", \"range\": \"string\", \"required\": True},\n {\"name\": \"startDate\", \"uri\": f\"{BASE_URI}startDate\", \"type\": \"datatype\", \"domain\": \"EmploymentEvent\", \"range\": \"date\"},\n {\"name\": \"endDate\", \"uri\": f\"{BASE_URI}endDate\", \"type\": \"datatype\", \"domain\": \"EmploymentEvent\", \"range\": \"date\"},\n {\"name\": \"salary\", \"uri\": f\"{BASE_URI}salary\", \"type\": \"datatype\", \"domain\": \"EmploymentEvent\", \"range\": \"decimal\"},\n\n # Object properties — reification spokes (required)\n {\"name\": \"employee\", \"uri\": f\"{BASE_URI}employee\", \"type\": \"object\", \"domain\": \"EmploymentEvent\", \"range\": \"Person\", \"required\": True},\n {\"name\": \"employer\", \"uri\": f\"{BASE_URI}employer\", \"type\": \"object\", \"domain\": \"EmploymentEvent\", \"range\": \"Organization\", \"required\": True},\n {\"name\": \"role\", \"uri\": f\"{BASE_URI}role\", \"type\": \"object\", \"domain\": \"EmploymentEvent\", \"range\": \"Role\", \"required\": True},\n\n # Shortcut edges — direct person→org / person→role without traversing the event node\n {\"name\": \"worksFor\", \"uri\": f\"{BASE_URI}worksFor\", \"type\": \"object\", \"domain\": \"Person\", \"range\": \"Organization\"},\n {\"name\": \"hasRole\", \"uri\": f\"{BASE_URI}hasRole\", \"type\": \"object\", \"domain\": \"Person\", \"range\": \"Role\"},\n ],\n}\n\nontology"
},
{
"cell_type": "markdown",
"id": "cell-5",
"metadata": {},
"source": [
"## Step 2: Reification — Modeling N-Ary Facts\n",
"\n",
"**The problem with binary triples:**\n",
"A simple triple `(Alice, worksFor, Acme)` cannot carry extra context such as salary, start date, or role.\n",
"Standard RDF reification and OWL n-ary patterns solve this by introducing an intermediate node.\n",
"\n",
"Semantica's `AssociativeClassBuilder` is the Pythonic API for this pattern:\n",
"\n",
"```\n",
"EmploymentEvent\n",
" ├── employee → Person (required)\n",
" ├── employer → Organization (required)\n",
" ├── role → Role (required)\n",
" ├── startDate → xsd:date\n",
" ├── endDate → xsd:date\n",
" └── salary → xsd:decimal\n",
"```\n",
"\n",
"**On SPARQL 1.1 vs. SPARQL 1.2:**\n",
"- **SPARQL 1.1 (current):** traverse the event node explicitly — `?event hr:employee ?person ; hr:salary ?salary`\n",
"- **SPARQL 1.2 (planned):** the draft reifier annotation syntax allows attaching context to triples directly, without a separate intermediate node. Semantica will adopt this once the spec is ratified.\n",
"\n",
"**On SHACL 1.1 vs. SHACL 1.2:**\n",
"- **SHACL 1.1 (current):** `sh:NodeShape` + `sh:PropertyShape` constraints are exported for all `required` properties and enforced at load time.\n",
"- **SHACL 1.2 (planned):** `sh:severity` profile extensions and SHACL-AF rules are on the roadmap."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-6",
"metadata": {},
"outputs": [],
"source": "assoc_builder = AssociativeClassBuilder()\n\nemployment_assoc = assoc_builder.create_associative_class(\n name=\"EmploymentEvent\",\n connects=[\"Person\", \"Organization\", \"Role\"],\n temporal=True, # adds startDate / endDate handling\n properties={\n \"startDate\": \"xsd:date\",\n \"endDate\": \"xsd:date\",\n \"salary\": \"xsd:decimal\",\n },\n)\n\nvalidation_result = assoc_builder.validate_associative_class(employment_assoc)\n\n# AssociativeClass is a dataclass — use attribute access, not .get()\nprint(\"AssociativeClass structure:\")\nprint(f\" name: {employment_assoc.name}\")\nprint(f\" connects: {employment_assoc.connects}\")\nprint(f\" temporal: {employment_assoc.temporal}\")\nprint(f\" properties: {list(employment_assoc.properties.keys())}\")\nprint(f\"\\nValidation passed: {validation_result}\")"
},
{
"cell_type": "markdown",
"id": "cell-7",
"metadata": {},
"source": [
"## Step 3: Ingest Snowflake Rows (Extraction Only)\n",
"\n",
"`SnowflakeIngestor` retrieves rows — nothing more. It does **not**:\n",
"- Inspect your table schema\n",
"- Suggest classes or properties\n",
"- Infer relationships from column names\n",
"\n",
"Set `USE_LIVE_SNOWFLAKE=true` plus the env vars below to connect to a real warehouse.\n",
"Otherwise the stub data is used."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-8",
"metadata": {},
"outputs": [],
"source": [
"def fetch_rows_from_snowflake() -> List[Dict[str, Any]]:\n",
" if os.getenv(\"USE_LIVE_SNOWFLAKE\", \"false\").lower() != \"true\":\n",
" return [\n",
" {\n",
" \"EMPLOYEE_ID\": \"E100\",\n",
" \"EMPLOYEE_NAME\": \"Alice Johnson\",\n",
" \"ORG_ID\": \"O10\",\n",
" \"ORG_NAME\": \"Acme Corp\",\n",
" \"ROLE_ID\": \"R7\",\n",
" \"ROLE_TITLE\": \"Senior Engineer\",\n",
" \"START_DATE\": \"2025-01-15\",\n",
" \"END_DATE\": None,\n",
" \"SALARY\": 160000,\n",
" },\n",
" {\n",
" \"EMPLOYEE_ID\": \"E101\",\n",
" \"EMPLOYEE_NAME\": \"Bob Singh\",\n",
" \"ORG_ID\": \"O10\",\n",
" \"ORG_NAME\": \"Acme Corp\",\n",
" \"ROLE_ID\": \"R9\",\n",
" \"ROLE_TITLE\": \"Data Architect\",\n",
" \"START_DATE\": \"2024-09-01\",\n",
" \"END_DATE\": None,\n",
" \"SALARY\": 185000,\n",
" },\n",
" ]\n",
"\n",
" ingestor = SnowflakeIngestor(\n",
" account=os.getenv(\"SNOWFLAKE_ACCOUNT\"),\n",
" user=os.getenv(\"SNOWFLAKE_USER\"),\n",
" password=os.getenv(\"SNOWFLAKE_PASSWORD\"),\n",
" warehouse=os.getenv(\"SNOWFLAKE_WAREHOUSE\"),\n",
" database=os.getenv(\"SNOWFLAKE_DATABASE\"),\n",
" schema=os.getenv(\"SNOWFLAKE_SCHEMA\", \"PUBLIC\"),\n",
" )\n",
" query = (\n",
" \"SELECT EMPLOYEE_ID, EMPLOYEE_NAME, \"\n",
" \"ORG_ID, ORG_NAME, ROLE_ID, ROLE_TITLE, \"\n",
" \"START_DATE, END_DATE, SALARY \"\n",
" \"FROM HR_EMPLOYMENT_FACT\"\n",
" )\n",
" data = ingestor.ingest_query(query)\n",
" ingestor.close()\n",
" return data.data\n",
"\n",
"\n",
"rows = fetch_rows_from_snowflake()\n",
"rows[:2]"
]
},
{
"cell_type": "markdown",
"id": "cell-9",
"metadata": {},
"source": [
"## Step 4: Map Rows to Ontology Concepts Explicitly\n",
"\n",
"This is the semantic transformation layer — the part that makes your ontology real.\n",
"\n",
"Semantica does not guess which column becomes which entity or property.\n",
"Every assignment is code you write and own:\n",
"\n",
"- **Stable node IDs** — deterministic, collision-safe, derived from business keys\n",
"- **Class assignment** — matches what you declared in Step 1\n",
"- **Property routing** — each column value goes to the correct ontology property\n",
"- **Reification wiring** — `EmploymentEvent` is linked to its three participants\n",
"\n",
"When your Snowflake schema changes, only this function needs updating. The ontology stays stable."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-10",
"metadata": {},
"outputs": [],
"source": "def map_rows_to_kg(rows: List[Dict[str, Any]]) -> Dict[str, Any]:\n entities: Dict[str, Dict[str, Any]] = {}\n relationships: List[Dict[str, Any]] = []\n\n for row in rows:\n # Stable, deterministic node IDs derived from business keys\n person_id = f\"person:{row['EMPLOYEE_ID']}\"\n org_id = f\"org:{row['ORG_ID']}\"\n role_id = f\"role:{row['ROLE_ID']}\"\n # Event ID includes all three participants + start date so that\n # a re-hired employee gets a distinct event node, not an overwrite.\n event_id = f\"employment:{row['EMPLOYEE_ID']}:{row['ORG_ID']}:{row['START_DATE']}\"\n\n # Entities — \"type\" must match a class name from Step 1\n entities[person_id] = {\n \"id\": person_id,\n \"type\": \"Person\",\n \"properties\": {\"name\": row[\"EMPLOYEE_NAME\"]},\n }\n entities[org_id] = {\n \"id\": org_id,\n \"type\": \"Organization\",\n \"properties\": {\"legalName\": row[\"ORG_NAME\"]},\n }\n entities[role_id] = {\n \"id\": role_id,\n \"type\": \"Role\",\n \"properties\": {\"title\": row[\"ROLE_TITLE\"]},\n }\n\n # Reification node — filter out None values so TripletStore does not\n # stringify None as the literal \"None\" for open-ended employment.\n event_props = {\n \"startDate\": row[\"START_DATE\"],\n \"endDate\": row[\"END_DATE\"],\n \"salary\": row[\"SALARY\"],\n }\n entities[event_id] = {\n \"id\": event_id,\n \"type\": \"EmploymentEvent\",\n \"properties\": {k: v for k, v in event_props.items() if v is not None},\n }\n\n # Full URIs for relationship types so TripletStore stores hr:<type>\n # instead of the default urn:property:<type>, keeping SPARQL consistent.\n relationships.extend([\n # Shortcut edges — fast SPARQL when context is not needed\n {\"source\": person_id, \"target\": org_id, \"type\": f\"{BASE_URI}worksFor\"},\n {\"source\": person_id, \"target\": role_id, \"type\": f\"{BASE_URI}hasRole\"},\n # Reification spokes — full context via the event node\n {\"source\": event_id, \"target\": person_id, \"type\": f\"{BASE_URI}employee\"},\n {\"source\": event_id, \"target\": org_id, \"type\": f\"{BASE_URI}employer\"},\n {\"source\": event_id, \"target\": role_id, \"type\": f\"{BASE_URI}role\"},\n ])\n\n return build_kg([{\"entities\": list(entities.values()), \"relationships\": relationships}])\n\n\nkg = map_rows_to_kg(rows)\nprint(f\"Entities built: {len(kg.get('entities', []))}\")\nprint(f\"Relationships built: {len(kg.get('relationships', []))}\")\n\nsample = next((e for e in kg[\"entities\"] if e[\"type\"] == \"EmploymentEvent\"), None)\nprint(f\"\\nSample EmploymentEvent node: {sample}\")"
},
{
"cell_type": "markdown",
"id": "cell-11",
"metadata": {},
"source": [
"## Step 5: Validate Ontology and Export OWL + SHACL\n",
"\n",
"`OntologyEngine` validates your ontology dict and serialises it to standards-compliant files.\n",
"\n",
"**Output files:**\n",
"- `employment_manual_ontology.ttl` — OWL 2 Turtle\n",
"- `employment_manual_shapes.ttl` — SHACL 1.1 node and property shapes\n",
"\n",
"**Standards status:**\n",
"\n",
"| Standard | Semantica support |\n",
"|---|---|\n",
"| SPARQL 1.1 | Full |\n",
"| SHACL 1.1 (`sh:NodeShape`, `sh:PropertyShape`, `sh:minCount`, `sh:datatype`, `sh:class`) | Full |\n",
"| SPARQL 1.2 (reifier annotation syntax, `LATERAL`) | Tracked — not yet implemented |\n",
"| SHACL 1.2 (`sh:severity` profiles, SHACL-AF extensions) | Tracked — not yet implemented |"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-12",
"metadata": {},
"outputs": [],
"source": [
"engine = OntologyEngine(base_uri=BASE_URI)\n",
"\n",
"validation = engine.validate(ontology)\n",
"owl_ttl = engine.to_owl(ontology, format=\"turtle\")\n",
"shacl_ttl = engine.to_shacl(ontology, format=\"turtle\")\n",
"\n",
"engine.export_owl(ontology, \"employment_manual_ontology.ttl\", format=\"turtle\")\n",
"engine.export_shacl(ontology, \"employment_manual_shapes.ttl\", format=\"turtle\")\n",
"\n",
"print(f\"Ontology valid: {validation.valid}\")\n",
"print(f\"Ontology consistent: {validation.consistent}\")\n",
"print(f\"OWL output: {len(owl_ttl):,} chars → employment_manual_ontology.ttl\")\n",
"print(f\"SHACL output: {len(shacl_ttl):,} chars → employment_manual_shapes.ttl\")\n",
"\n",
"print(\"\\n--- SHACL shapes (first 20 lines) ---\")\n",
"print(\"\\n\".join(shacl_ttl.splitlines()[:20]))"
]
},
{
"cell_type": "markdown",
"id": "cell-13",
"metadata": {},
"source": [
"## Best-Practice Architecture\n",
"\n",
"```\n",
"┌──────────────────────────────────┐\n",
"│ Ontology as code (Python dict) │ ← versioned alongside your application\n",
"│ + AssociativeClass for n-ary │\n",
"└───────────────┬──────────────────┘\n",
" │ validate + export\n",
" ▼\n",
"┌───────────────────────────────────┐\n",
"│ OWL 2 Turtle │ SHACL 1.1 │ ← standards-compliant artifacts\n",
"└───────────────┬───────────────────┘\n",
" │\n",
" ▼\n",
"┌──────────────────────────────────┐\n",
"│ Snowflake — raw data access │ ← no schema introspection\n",
"└───────────────┬──────────────────┘\n",
" │ explicit mapping layer\n",
" ▼\n",
"┌──────────────────────────────────┐\n",
"│ Ontology-aligned KG │ ← types, IDs, edges match Step 1\n",
"└───────────────┬──────────────────┘\n",
" │ optional\n",
" ▼\n",
"┌──────────────────────────────────┐\n",
"│ Triplet store + SPARQL 1.1 │\n",
"└──────────────────────────────────┘\n",
"```\n",
"\n",
"**Why this split matters:**\n",
"If Semantica inferred the ontology from your Snowflake schema, every schema migration would risk silently changing your semantic model.\n",
"With this pattern, schema changes only touch the mapping function in Step 4 — the ontology remains stable and under your control."
]
},
{
"cell_type": "markdown",
"id": "cell-14",
"metadata": {},
"source": [
"## SPARQL Query Patterns\n",
"\n",
"Two query styles are available because we wrote both shortcut edges and reification spokes.\n",
"\n",
"### Simple lookup — shortcut edge (no context needed)\n",
"\n",
"```sparql\n",
"PREFIX hr: <https://example.com/hr/>\n",
"\n",
"SELECT ?personName ?orgName\n",
"WHERE {\n",
" ?person a hr:Person ;\n",
" hr:name ?personName ;\n",
" hr:worksFor ?org .\n",
" ?org hr:legalName ?orgName .\n",
"}\n",
"```\n",
"\n",
"### Contextual lookup — via reification node (salary, dates, role)\n",
"\n",
"```sparql\n",
"PREFIX hr: <https://example.com/hr/>\n",
"\n",
"SELECT ?personName ?roleTitle ?salary ?startDate\n",
"WHERE {\n",
" ?event a hr:EmploymentEvent ;\n",
" hr:employee ?person ;\n",
" hr:role ?role ;\n",
" hr:salary ?salary ;\n",
" hr:startDate ?startDate .\n",
" ?person hr:name ?personName .\n",
" ?role hr:title ?roleTitle .\n",
"}\n",
"ORDER BY DESC(?salary)\n",
"```\n",
"\n",
"### Future: SPARQL 1.2 reifier syntax\n",
"\n",
"The SPARQL 1.2 draft introduces annotation syntax that lets you attach context directly to triples, without a separate intermediate node.\n",
"Once the spec is ratified Semantica will adopt it, and the contextual query above may be expressible more concisely."
]
},
{
"cell_type": "markdown",
"id": "cell-15",
"metadata": {},
"source": [
"## Step 6 (Optional): Load to Triplet Store and Run SPARQL\n",
"\n",
"Set `STORE_TO_TRIPLET=true` to load the KG into a live triplet store and run the contextual reification query."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-16",
"metadata": {},
"outputs": [],
"source": [
"if os.getenv(\"STORE_TO_TRIPLET\", \"false\").lower() == \"true\":\n",
" store = TripletStore(\n",
" backend=os.getenv(\"TRIPLET_BACKEND\", \"blazegraph\"),\n",
" endpoint=os.getenv(\"TRIPLET_ENDPOINT\", \"http://localhost:9999/blazegraph\"),\n",
" namespace=os.getenv(\"TRIPLET_NAMESPACE\", \"kb\"),\n",
" )\n",
" store_result = store.store(knowledge_graph=kg, ontology=ontology)\n",
" print(\"Store result:\", store_result)\n",
"\n",
" # Contextual reification query — person + role + salary via EmploymentEvent\n",
" query = \"\"\"\n",
" PREFIX hr: <https://example.com/hr/>\n",
"\n",
" SELECT ?personName ?roleTitle ?salary ?startDate\n",
" WHERE {\n",
" ?event a hr:EmploymentEvent ;\n",
" hr:employee ?person ;\n",
" hr:role ?role ;\n",
" hr:salary ?salary ;\n",
" hr:startDate ?startDate .\n",
" ?person hr:name ?personName .\n",
" ?role hr:title ?roleTitle .\n",
" }\n",
" ORDER BY DESC(?salary)\n",
" LIMIT 10\n",
" \"\"\"\n",
" result = store.execute_query(query)\n",
" print(result)\n",
"else:\n",
" print(\"Skipping triplet-store load/query (set STORE_TO_TRIPLET=true to enable)\")"
]
}
]
}
@@ -0,0 +1,809 @@
{
"cells": [
{
"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",
"\n",
"# Datalog-Style Reasoning\n",
"\n",
"End-to-end guide to Semantica's **`DatalogReasoner`** — a native bottom-up semi-naive fixpoint engine — wired together with `GraphBuilder`, `ContextGraph`, `GraphAnalyzer`, `ExplanationGenerator`, and the supporting data-classes (`DatalogFact`, `DatalogRule`, `InferenceResult`, `Rule`).\n",
"\n",
"## What you will build\n",
"\n",
"| Part | Topic | Key classes |\n",
"|------|-------|-------------|\n",
"| 1 | Core API & EDB/IDB concepts | `DatalogReasoner`, `DatalogFact`, `DatalogRule` |\n",
"| 2 | KG → Datalog pipeline | `GraphBuilder`, `GraphAnalyzer`, `DatalogReasoner` |\n",
"| 3 | ContextGraph integration | `ContextGraph`, `DatalogReasoner.load_from_graph()` |\n",
"| 4 | RBAC access-control policy | `GraphBuilder`, `DatalogReasoner`, `ExplanationGenerator` |\n",
"| 5 | Org hierarchy | `ContextGraph`, `DatalogReasoner`, `InferenceResult` |\n",
"| 6 | Engine introspection | `DatalogFact`, `DatalogRule` internal state |\n",
"\n",
"**Related notebooks**\n",
"- [08_Reasoning_and_Inference.ipynb](08_Reasoning_and_Inference.ipynb) — high-level `Reasoner` with IF/THEN syntax\n",
"- [10_Temporal_Knowledge_Graphs.ipynb](10_Temporal_Knowledge_Graphs.ipynb) — temporal reasoning\n",
"\n",
"**Documentation**: [Reasoning API](https://semantica.readthedocs.io/reference/reasoning/) | [KG API](https://semantica.readthedocs.io/reference/kg/) | [Context API](https://semantica.readthedocs.io/reference/context/)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install -qU semantica"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Reasoning ──────────────────────────────────────────────────────────────\n",
"from semantica.reasoning import (\n",
" DatalogReasoner, # native Datalog fixpoint engine\n",
" DatalogFact, # frozen dataclass: predicate + args tuple\n",
" DatalogRule, # dataclass: head + body (list[BodyAtom])\n",
" ExplanationGenerator, # generates NL justifications\n",
" InferenceResult, # result dataclass consumed by ExplanationGenerator\n",
" Rule, # rule dataclass used by ExplanationGenerator\n",
" RuleType, # enum: IMPLICATION | EQUIVALENCE | CONSTRAINT | TRANSFORMATION\n",
")\n",
"\n",
"# ── Knowledge Graph ────────────────────────────────────────────────────────\n",
"from semantica.kg import (\n",
" GraphBuilder, # constructs KG dicts from entity+relationship sources\n",
" GraphAnalyzer, # centrality, communities, connectivity, metrics\n",
")\n",
"\n",
"# ── Context ────────────────────────────────────────────────────────────────\n",
"from semantica.context import ContextGraph # in-memory graph: add_node/add_edge/find_*\n",
"\n",
"print(\"All Semantica classes imported successfully.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Part 1 — Core API: EDB Facts, IDB Rules, Fixpoint\n",
"\n",
"### Datalog in 30 seconds\n",
"\n",
"| Term | Meaning | Example |\n",
"|------|---------|--------|\n",
"| EDB (Extensional DB) | Ground facts you assert | `parent(tom, bob)` |\n",
"| IDB (Intensional DB) | Facts derived by rules | `ancestor(tom, ann)` |\n",
"| Rule (Horn clause) | If body → derive head | `ancestor(X,Y) :- parent(X,Y).` |\n",
"| Variable | Uppercase, unified during eval | `X`, `Y`, `Role` |\n",
"| Constant | Lowercase, matches literally | `tom`, `admin` |\n",
"| Fixpoint | Iterate until no new facts appear | `DatalogReasoner.derive_all()` |\n",
"\n",
"### The canonical example — transitive ancestry"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Step 1: create engine ──────────────────────────────────────────────────\n",
"dr = DatalogReasoner()\n",
"\n",
"# ── Step 2: load EDB (ground facts) ───────────────────────────────────────\n",
"# Syntax: predicate(constant1, constant2) — constants must be lowercase\n",
"edb_facts = [\n",
" \"parent(tom, bob)\",\n",
" \"parent(bob, ann)\",\n",
" \"parent(ann, pat)\",\n",
"]\n",
"for f in edb_facts:\n",
" dr.add_fact(f)\n",
"\n",
"print(f\"EDB loaded: {len(dr._all_facts)} ground facts\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Step 3: add IDB rules (Horn clauses) ──────────────────────────────────\n",
"# Syntax: head(Vars) :- body_atom1(Vars), body_atom2(Vars).\n",
"# Variables start with uppercase; trailing '.' is optional\n",
"dr.add_rule(\"ancestor(X, Y) :- parent(X, Y).\")\n",
"dr.add_rule(\"ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).\") # recursive\n",
"\n",
"print(f\"Rules loaded: {len(dr._rules)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Step 4: fixpoint evaluation ────────────────────────────────────────────\n",
"# derive_all() runs semi-naive bottom-up evaluation until no new facts appear\n",
"all_facts: list[str] = dr.derive_all()\n",
"\n",
"ancestor_strs = sorted(f for f in all_facts if f.startswith(\"ancestor\"))\n",
"print(f\"Derived {len(ancestor_strs)} ancestor facts:\")\n",
"for f in ancestor_strs:\n",
" print(\" \", f)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Step 5: query ──────────────────────────────────────────────────────────\n",
"# Use '?varname' placeholders — query() auto-calls derive_all() if needed\n",
"# Returns: list[dict] e.g. [{\"Y\": \"bob\"}, {\"Y\": \"ann\"}, {\"Y\": \"pat\"}]\n",
"\n",
"descendants = dr.query(\"ancestor(tom, ?Y)\")\n",
"print(\"All descendants of tom:\", sorted(r[\"Y\"] for r in descendants))\n",
"\n",
"ancestors_of_pat = dr.query(\"ancestor(?X, pat)\")\n",
"print(\"All ancestors of pat: \", sorted(r[\"X\"] for r in ancestors_of_pat))\n",
"\n",
"all_pairs = dr.query(\"ancestor(?X, ?Y)\")\n",
"print(f\"\\nAll ancestor pairs ({len(all_pairs)}):\")\n",
"for row in sorted(all_pairs, key=lambda r: (r[\"X\"], r[\"Y\"])):\n",
" print(f\" {row['X']:6s} → {row['Y']}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Part 2 — GraphBuilder → DatalogReasoner Pipeline\n",
"\n",
"`GraphBuilder` constructs a structured `{\"entities\": [...], \"relationships\": [...]}` dict from your data. We then:\n",
"\n",
"1. Analyse the graph with `GraphAnalyzer` to understand structure.\n",
"2. Feed `kg[\"relationships\"]` into `DatalogReasoner` as EDB facts.\n",
"3. Apply recursive Datalog rules over the KG."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Build a software-dependency KG ────────────────────────────────────────\n",
"entities = [\n",
" {\"id\": \"pythonsdk\", \"name\": \"Python SDK\", \"type\": \"Component\"},\n",
" {\"id\": \"restapi\", \"name\": \"REST API\", \"type\": \"Component\"},\n",
" {\"id\": \"authservice\", \"name\": \"Auth Service\", \"type\": \"Component\"},\n",
" {\"id\": \"database\", \"name\": \"Database\", \"type\": \"Component\"},\n",
" {\"id\": \"dashboard\", \"name\": \"Dashboard\", \"type\": \"Component\"},\n",
" {\"id\": \"analytics\", \"name\": \"Analytics\", \"type\": \"Component\"},\n",
"]\n",
"relationships = [\n",
" {\"source\": \"pythonsdk\", \"target\": \"restapi\", \"type\": \"depends_on\"},\n",
" {\"source\": \"restapi\", \"target\": \"authservice\", \"type\": \"depends_on\"},\n",
" {\"source\": \"authservice\", \"target\": \"database\", \"type\": \"depends_on\"},\n",
" {\"source\": \"dashboard\", \"target\": \"restapi\", \"type\": \"depends_on\"},\n",
" {\"source\": \"dashboard\", \"target\": \"analytics\", \"type\": \"depends_on\"},\n",
" {\"source\": \"analytics\", \"target\": \"database\", \"type\": \"depends_on\"},\n",
"]\n",
"\n",
"# GraphBuilder validates, deduplicates, and packages the data\n",
"builder = GraphBuilder(merge_entities=True, resolve_conflicts=False)\n",
"kg = builder.build([{\"entities\": entities, \"relationships\": relationships}])\n",
"\n",
"print(f\"KG built — entities: {len(kg['entities'])}, relationships: {len(kg['relationships'])}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Analyse the graph structure before reasoning ───────────────────────────\n",
"# GraphAnalyzer provides centrality, communities, connectivity, and metrics\n",
"analyzer = GraphAnalyzer()\n",
"metrics = analyzer.compute_metrics(graph=kg)\n",
"\n",
"print(\"Graph structure:\")\n",
"print(f\" Nodes : {metrics['num_nodes']}\")\n",
"print(f\" Edges : {metrics['num_edges']}\")\n",
"if \"density\" in metrics:\n",
" print(f\" Density : {metrics['density']:.3f}\")\n",
"if \"is_connected\" in metrics:\n",
" print(f\" Connected : {metrics['is_connected']}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Load KG relationships as EDB facts ────────────────────────────────────\n",
"# GraphBuilder output dicts use the same source/target/type shape that\n",
"# DatalogReasoner.add_fact() natively understands\n",
"dr = DatalogReasoner()\n",
"\n",
"for rel in kg[\"relationships\"]:\n",
" dr.add_fact(rel) # dict path: {\"source\": ..., \"target\": ..., \"type\": ...}\n",
"\n",
"print(f\"EDB loaded: {len(dr._all_facts)} dependency facts\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Transitive dependency closure ─────────────────────────────────────────\n",
"# 'depends_on' is the predicate name that add_fact inferred from 'type'\n",
"dr.add_rule(\"transitive_dep(X, Y) :- depends_on(X, Y).\")\n",
"dr.add_rule(\"transitive_dep(X, Y) :- depends_on(X, Z), transitive_dep(Z, Y).\")\n",
"\n",
"dr.derive_all()\n",
"\n",
"# Everything that transitively depends on the database\n",
"db_deps = sorted(r[\"X\"] for r in dr.query(\"transitive_dep(?X, database)\"))\n",
"print(\"Components that transitively depend on Database:\")\n",
"for c in db_deps:\n",
" print(\" \", c)\n",
"\n",
"# What does pythonsdk transitively depend on?\n",
"sdk_chain = sorted(r[\"Y\"] for r in dr.query(\"transitive_dep(pythonsdk, ?Y)\"))\n",
"print(f\"\\nPython SDK full dependency chain: {sdk_chain}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Part 3 — ContextGraph + `load_from_graph()`\n",
"\n",
"`DatalogReasoner.load_from_graph(graph)` accepts any `ContextGraph` directly: it calls `graph.find_edges()` and `graph.find_nodes()` and converts each result into EDB facts automatically."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Build an in-memory ContextGraph ───────────────────────────────────────\n",
"# ContextGraph.add_node / add_edge are the canonical way to build in-memory KGs\n",
"cg = ContextGraph()\n",
"\n",
"# Nodes\n",
"for person in [\"alice\", \"bob\", \"carol\", \"dave\", \"eve\"]:\n",
" cg.add_node(person, node_type=\"person\", name=person.capitalize())\n",
"\n",
"# Directed \"follows\" edges\n",
"for src, dst in [(\"alice\", \"bob\"), (\"bob\", \"carol\"), (\"carol\", \"dave\"), (\"alice\", \"eve\"), (\"eve\", \"carol\")]:\n",
" cg.add_edge(src, dst, edge_type=\"follows\")\n",
"\n",
"# Verify the graph built correctly\n",
"nodes = cg.find_nodes(node_type=\"person\")\n",
"edges = cg.find_edges(edge_type=\"follows\")\n",
"print(f\"ContextGraph — nodes: {len(nodes)}, edges: {len(edges)}\")\n",
"print(\"Edges:\", [(e.get(\"source\", e.get(\"source_id\")), e.get(\"target\", e.get(\"target_id\"))) for e in edges])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── load_from_graph() ingests the ContextGraph directly ───────────────────\n",
"dr = DatalogReasoner()\n",
"n_loaded = dr.load_from_graph(cg) # calls cg.find_edges() + cg.find_nodes() internally\n",
"print(f\"Facts loaded from ContextGraph: {n_loaded}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Influence reach via transitive 'follows' ──────────────────────────────\n",
"dr.add_rule(\"influence(X, Y) :- follows(X, Y).\")\n",
"dr.add_rule(\"influence(X, Y) :- follows(X, Z), influence(Z, Y).\")\n",
"\n",
"dr.derive_all()\n",
"\n",
"# Who can alice reach?\n",
"alice_reach = sorted(r[\"Y\"] for r in dr.query(\"influence(alice, ?Y)\"))\n",
"print(f\"Alice's influence reach : {alice_reach}\")\n",
"\n",
"# Who can reach dave?\n",
"reach_dave = sorted(r[\"X\"] for r in dr.query(\"influence(?X, dave)\"))\n",
"print(f\"Who can influence dave : {reach_dave}\")\n",
"\n",
"# Full influence matrix\n",
"all_influence = dr.query(\"influence(?X, ?Y)\")\n",
"print(f\"\\nTotal influence pairs: {len(all_influence)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Part 4 — RBAC Access-Control Policy\n",
"\n",
"We model a role-based access-control (RBAC) system:\n",
"\n",
"1. Use `GraphBuilder` to build a structured KG of users, roles, and permissions.\n",
"2. Load it into `DatalogReasoner` for policy inference.\n",
"3. Use `ExplanationGenerator` to produce audit-ready NL justifications."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Build RBAC graph with GraphBuilder ────────────────────────────────────\n",
"rbac_entities = [\n",
" # Users\n",
" {\"id\": \"alice\", \"type\": \"User\", \"name\": \"Alice\"},\n",
" {\"id\": \"bob\", \"type\": \"User\", \"name\": \"Bob\"},\n",
" {\"id\": \"carol\", \"type\": \"User\", \"name\": \"Carol\"},\n",
" {\"id\": \"dave\", \"type\": \"User\", \"name\": \"Dave\"},\n",
" # Roles\n",
" {\"id\": \"admin\", \"type\": \"Role\", \"name\": \"Administrator\"},\n",
" {\"id\": \"editor\", \"type\": \"Role\", \"name\": \"Editor\"},\n",
" {\"id\": \"viewer\", \"type\": \"Role\", \"name\": \"Viewer\"},\n",
" # Permissions\n",
" {\"id\": \"read\", \"type\": \"Permission\"},\n",
" {\"id\": \"write\", \"type\": \"Permission\"},\n",
" {\"id\": \"delete\", \"type\": \"Permission\"},\n",
" {\"id\": \"manage_users\", \"type\": \"Permission\"},\n",
"]\n",
"rbac_relationships = [\n",
" # User → Role assignments\n",
" {\"source\": \"alice\", \"target\": \"admin\", \"type\": \"has_role\"},\n",
" {\"source\": \"bob\", \"target\": \"editor\", \"type\": \"has_role\"},\n",
" {\"source\": \"carol\", \"target\": \"viewer\", \"type\": \"has_role\"},\n",
" {\"source\": \"dave\", \"target\": \"editor\", \"type\": \"has_role\"},\n",
" # Role hierarchy (admin inherits from editor, editor from viewer)\n",
" {\"source\": \"admin\", \"target\": \"editor\", \"type\": \"role_inherits\"},\n",
" {\"source\": \"editor\", \"target\": \"viewer\", \"type\": \"role_inherits\"},\n",
" # Role → Permission grants\n",
" {\"source\": \"viewer\", \"target\": \"read\", \"type\": \"role_has_perm\"},\n",
" {\"source\": \"editor\", \"target\": \"write\", \"type\": \"role_has_perm\"},\n",
" {\"source\": \"admin\", \"target\": \"delete\", \"type\": \"role_has_perm\"},\n",
" {\"source\": \"admin\", \"target\": \"manage_users\", \"type\": \"role_has_perm\"},\n",
"]\n",
"\n",
"builder = GraphBuilder(merge_entities=True, resolve_conflicts=False)\n",
"rbac_kg = builder.build([{\"entities\": rbac_entities, \"relationships\": rbac_relationships}])\n",
"\n",
"print(f\"RBAC KG — entities: {len(rbac_kg['entities'])}, relationships: {len(rbac_kg['relationships'])}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Analyse RBAC graph structure ──────────────────────────────────────────\n",
"analyzer = GraphAnalyzer()\n",
"metrics = analyzer.compute_metrics(graph=rbac_kg)\n",
"centrality = analyzer.calculate_centrality(rbac_kg, centrality_type=\"degree\")\n",
"\n",
"print(f\"RBAC graph — {metrics['num_nodes']} nodes, {metrics['num_edges']} edges\")\n",
"if isinstance(centrality, dict) and \"degree\" in centrality:\n",
" top = sorted(centrality[\"degree\"].items(), key=lambda x: x[1], reverse=True)[:3]\n",
" print(\"Top-3 nodes by degree centrality:\", top)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Load RBAC KG into DatalogReasoner ────────────────────────────────────\n",
"dr = DatalogReasoner()\n",
"\n",
"for rel in rbac_kg[\"relationships\"]:\n",
" dr.add_fact(rel) # {source, target, type} → predicate(source, target)\n",
"\n",
"# ── IDB rules: transitive role hierarchy ─────────────────────────────────\n",
"dr.add_rule(\"effective_role(R, R2) :- role_inherits(R, R2).\")\n",
"dr.add_rule(\"effective_role(R, R2) :- role_inherits(R, Z), effective_role(Z, R2).\")\n",
"\n",
"# ── IDB rules: inherited permissions ─────────────────────────────────────\n",
"dr.add_rule(\"role_can(R, P) :- role_has_perm(R, P).\")\n",
"dr.add_rule(\"role_can(R, P) :- effective_role(R, R2), role_has_perm(R2, P).\")\n",
"\n",
"# ── IDB rules: user effective permissions ────────────────────────────────\n",
"dr.add_rule(\"can(U, P) :- has_role(U, R), role_can(R, P).\")\n",
"\n",
"dr.derive_all()\n",
"\n",
"print(\"User permissions derived via role-hierarchy inference:\")\n",
"for user in [\"alice\", \"bob\", \"carol\", \"dave\"]:\n",
" perms = sorted(r[\"P\"] for r in dr.query(f\"can({user}, ?P)\"))\n",
" print(f\" {user:6s}: {perms}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── ExplanationGenerator — audit-ready NL justification ──────────────────\n",
"# ExplanationGenerator works with InferenceResult objects.\n",
"# We construct one manually to represent a derived Datalog conclusion.\n",
"\n",
"explainer = ExplanationGenerator(detail_level=\"detailed\")\n",
"\n",
"# Build the Rule object that represents the permission derivation chain\n",
"perm_rule = Rule(\n",
" rule_id=\"rbac_perm_chain\",\n",
" name=\"RBAC permission via role hierarchy\",\n",
" conditions=[\"has_role(alice, admin)\", \"effective_role(admin, viewer)\", \"role_has_perm(viewer, read)\"],\n",
" conclusion=\"can(alice, read)\",\n",
" rule_type=RuleType.IMPLICATION,\n",
" confidence=1.0,\n",
")\n",
"\n",
"# Build InferenceResult representing the Datalog conclusion\n",
"result = InferenceResult(\n",
" conclusion=\"can(alice, read)\",\n",
" rule_used=perm_rule,\n",
" premises=[\n",
" \"has_role(alice, admin)\",\n",
" \"role_inherits(admin, editor)\",\n",
" \"role_inherits(editor, viewer)\",\n",
" \"role_has_perm(viewer, read)\",\n",
" ],\n",
" confidence=1.0,\n",
")\n",
"\n",
"# Generate NL explanation\n",
"explanation = explainer.generate_explanation(result)\n",
"print(\"Explanation type :\", explanation.explanation_type)\n",
"print(\"Conclusion :\", explanation.conclusion)\n",
"print(\"Natural language :\", explanation.natural_language)\n",
"print(\"Reasoning steps :\", len(explanation.reasoning_path.steps))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Inverse queries ───────────────────────────────────────────────────────\n",
"deleters = sorted(r[\"U\"] for r in dr.query(\"can(?U, delete)\"))\n",
"print(\"Who can delete:\", deleters)\n",
"\n",
"writers = sorted(r[\"U\"] for r in dr.query(\"can(?U, write)\"))\n",
"print(\"Who can write: \", writers)\n",
"\n",
"# All (user, permission) pairs — full policy matrix\n",
"all_caps = dr.query(\"can(?U, ?P)\")\n",
"print(f\"\\nTotal (user, permission) pairs: {len(all_caps)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Part 5 — Organisation Hierarchy with ContextGraph\n",
"\n",
"We model a company org-chart using `ContextGraph` and derive:\n",
"- `manages(M, E)` — direct and transitive management\n",
"- `skip_level(M, E)` — two hops up the chain\n",
"- `same_team(X, Y)` — shared team membership"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── ContextGraph: org chart ────────────────────────────────────────────────\n",
"org = ContextGraph()\n",
"\n",
"# Add employees as nodes with metadata\n",
"staff = [\n",
" (\"eng1\", \"engineer\", \"backend\"),\n",
" (\"eng2\", \"engineer\", \"backend\"),\n",
" (\"eng3\", \"engineer\", \"frontend\"),\n",
" (\"techlead\", \"lead\", \"engineering\"),\n",
" (\"design1\", \"designer\", \"ux\"),\n",
" (\"design2\", \"designer\", \"ux\"),\n",
" (\"designlead\",\"lead\", \"design\"),\n",
" (\"vpeng\", \"vp\", \"engineering\"),\n",
" (\"cto\", \"executive\", \"leadership\"),\n",
"]\n",
"for emp_id, role, team in staff:\n",
" org.add_node(emp_id, node_type=\"employee\", role=role, team=team)\n",
"\n",
"# Reporting lines\n",
"reports_to = [\n",
" (\"eng1\", \"techlead\"), (\"eng2\", \"techlead\"), (\"eng3\", \"techlead\"),\n",
" (\"techlead\", \"vpeng\"),\n",
" (\"design1\", \"designlead\"), (\"design2\", \"designlead\"),\n",
" (\"designlead\", \"vpeng\"),\n",
" (\"vpeng\", \"cto\"),\n",
"]\n",
"for employee, manager in reports_to:\n",
" org.add_edge(employee, manager, edge_type=\"reports_to\")\n",
"\n",
"# Team membership edges\n",
"teams = [\n",
" (\"eng1\", \"backend\"), (\"eng2\", \"backend\"), (\"eng3\", \"frontend\"),\n",
" (\"design1\", \"ux\"), (\"design2\", \"ux\"),\n",
"]\n",
"for emp, team in teams:\n",
" org.add_edge(emp, team, edge_type=\"in_team\")\n",
" if not org.find_nodes(node_type=\"team\"):\n",
" org.add_node(team, node_type=\"team\")\n",
"\n",
"print(f\"ContextGraph — nodes: {len(org.find_nodes())}, edges: {len(org.find_edges())}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Load org chart into DatalogReasoner ───────────────────────────────────\n",
"dr = DatalogReasoner()\n",
"n = dr.load_from_graph(org) # uses org.find_edges() + org.find_nodes()\n",
"print(f\"Facts loaded via load_from_graph(): {n}\")\n",
"\n",
"# ── IDB rules ─────────────────────────────────────────────────────────────\n",
"# Transitive management chain\n",
"dr.add_rule(\"manages(M, E) :- reports_to(E, M).\")\n",
"dr.add_rule(\"manages(M, E) :- reports_to(E, Z), manages(M, Z).\")\n",
"\n",
"# Skip-level: exactly two reporting hops\n",
"dr.add_rule(\"skip_level(M, E) :- reports_to(E, Z), reports_to(Z, M).\")\n",
"\n",
"# Same team\n",
"dr.add_rule(\"same_team(X, Y) :- in_team(X, T), in_team(Y, T).\")\n",
"\n",
"dr.derive_all()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Query org hierarchy ────────────────────────────────────────────────────\n",
"# Everyone under CTO\n",
"under_cto = sorted(r[\"E\"] for r in dr.query(\"manages(cto, ?E)\"))\n",
"print(f\"CTO manages ({len(under_cto)} people): {under_cto}\")\n",
"\n",
"# VP Eng's direct + indirect reports\n",
"under_vp = sorted(r[\"E\"] for r in dr.query(\"manages(vpeng, ?E)\"))\n",
"print(f\"VP Eng manages : {under_vp}\")\n",
"\n",
"# Skip-level reports to CTO (people two hops below CTO)\n",
"skip = sorted(r[\"E\"] for r in dr.query(\"skip_level(cto, ?E)\"))\n",
"print(f\"CTO skip-level reports : {skip}\")\n",
"\n",
"# eng1's teammates\n",
"mates = [r[\"Y\"] for r in dr.query(\"same_team(eng1, ?Y)\") if r[\"Y\"] != \"eng1\"]\n",
"print(f\"eng1's teammates : {sorted(mates)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Build an InferenceResult and explain an org query ────────────────────\n",
"explainer = ExplanationGenerator(detail_level=\"verbose\")\n",
"\n",
"mgmt_rule = Rule(\n",
" rule_id=\"transitive_manages\",\n",
" name=\"Transitive management chain\",\n",
" conditions=[\"reports_to(eng1, techlead)\", \"manages(vpeng, techlead)\"],\n",
" conclusion=\"manages(vpeng, eng1)\",\n",
" rule_type=RuleType.IMPLICATION,\n",
" confidence=1.0,\n",
")\n",
"result = InferenceResult(\n",
" conclusion=\"manages(vpeng, eng1)\",\n",
" rule_used=mgmt_rule,\n",
" premises=[\"reports_to(eng1, techlead)\", \"reports_to(techlead, vpeng)\"],\n",
" confidence=1.0,\n",
")\n",
"\n",
"exp = explainer.generate_explanation(result)\n",
"print(exp.natural_language)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Part 6 — Engine Introspection: DatalogFact & DatalogRule\n",
"\n",
"After reasoning, the engine's internal state is fully accessible via `DatalogFact` and `DatalogRule` data-classes. Use this for auditing, debugging, or downstream export."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Inspect DatalogRule objects ────────────────────────────────────────────\n",
"# dr._rules → List[DatalogRule]\n",
"# DatalogRule.head_predicate, .head_args, .body (body = List[BodyAtom])\n",
"print(\"Rules in engine:\")\n",
"for rule in dr._rules:\n",
" body_str = \", \".join(\n",
" f\"{atom.predicate}({', '.join(atom.args)})\"\n",
" for atom in rule.body\n",
" )\n",
" head_str = f\"{rule.head_predicate}({', '.join(rule.head_args)})\"\n",
" print(f\" {head_str} :- {body_str}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Inspect DatalogFact objects ────────────────────────────────────────────\n",
"# dr._all_facts → Set[DatalogFact] (EDB + IDB combined after derive_all)\n",
"# dr._fact_index → Dict[predicate, Set[DatalogFact]]\n",
"\n",
"from collections import Counter\n",
"\n",
"# Count facts per predicate\n",
"predicate_counts = Counter(f.predicate for f in dr._all_facts)\n",
"print(\"Facts per predicate (EDB + derived IDB):\")\n",
"for pred, count in sorted(predicate_counts.items()):\n",
" print(f\" {pred:20s}: {count}\")\n",
"print(f\"\\n TOTAL: {len(dr._all_facts)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Separate EDB from IDB ─────────────────────────────────────────────────\n",
"# EDB predicates are the ones we added via add_fact (not derived by rules)\n",
"idb_predicates = {rule.head_predicate for rule in dr._rules}\n",
"edb_predicates = {f.predicate for f in dr._all_facts} - idb_predicates\n",
"\n",
"print(f\"EDB predicates (base facts) : {sorted(edb_predicates)}\")\n",
"print(f\"IDB predicates (derived) : {sorted(idb_predicates)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Sample DatalogFact structure ──────────────────────────────────────────\n",
"# DatalogFact is a frozen dataclass: predicate: str, args: Tuple[str, ...]\n",
"manages_facts = sorted(dr._fact_index.get(\"manages\", []), key=lambda f: f.args)\n",
"print(f\"First 5 'manages' DatalogFact objects ({len(manages_facts)} total):\")\n",
"for fact in manages_facts[:5]:\n",
" # Access predicate and args directly from the dataclass\n",
" print(f\" DatalogFact(predicate={fact.predicate!r}, args={fact.args})\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── clear() resets the engine completely ─────────────────────────────────\n",
"print(f\"Facts before clear(): {len(dr._all_facts)}\")\n",
"dr.clear()\n",
"print(f\"Facts after clear(): {len(dr._all_facts)}\")\n",
"print(f\"Rules after clear(): {len(dr._rules)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## API Summary\n",
"\n",
"### DatalogReasoner\n",
"\n",
"| Method | Input | Output | Notes |\n",
"|--------|-------|--------|-------|\n",
"| `add_fact(f)` | `str` or `dict` | `None` | string: `\"pred(a, b)\"` · dict: `{source, target, type}` |\n",
"| `add_rule(s)` | `str` | `None` | Horn clause: `\"head(X) :- body(X, Y).\"` |\n",
"| `derive_all()` | — | `list[str]` | semi-naive fixpoint; idempotent |\n",
"| `query(pat)` | `str` | `list[dict]` | `\"pred(a, ?Y)\"` → `[{\"Y\": ...}]` |\n",
"| `load_from_graph(g)` | `ContextGraph` | `int` | facts loaded count |\n",
"| `clear()` | — | `None` | resets engine |\n",
"\n",
"### Syntax rules\n",
"\n",
"| Item | Rule | Example |\n",
"|------|------|---------|\n",
"| Variable | Starts **uppercase** | `X`, `Role`, `Parent` |\n",
"| Constant | All **lowercase** | `tom`, `admin`, `database` |\n",
"| Query var | Prefix `?` | `?X`, `?Y`, `?Role` |\n",
"| Rule body | `:-` separator, comma between atoms | `head(X) :- a(X, Z), b(Z, Y).` |\n",
"\n",
"### Class map\n",
"\n",
"```\n",
"GraphBuilder.build() → kg dict {entities, relationships}\n",
" ↓ kg[\"relationships\"] → dr.add_fact(rel)\n",
" \n",
"ContextGraph.add_node/add_edge → in-memory graph\n",
" ↓ dr.load_from_graph(cg)\n",
" \n",
"DatalogReasoner.add_rule() → Horn clause rules\n",
"DatalogReasoner.derive_all() → semi-naive fixpoint\n",
"DatalogReasoner.query() → result rows\n",
" ↓ build InferenceResult\n",
" \n",
"ExplanationGenerator → natural language justification\n",
"GraphAnalyzer → graph structure metrics pre/post reasoning\n",
"DatalogFact / DatalogRule → introspect engine state\n",
"```"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.10.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,534 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "title",
"metadata": {},
"source": [
"# Agno × Semantica: Decision Intelligence Agent\n",
"\n",
"This notebook shows how to wire Semantica's **Decision Intelligence** stack into an Agno agent so it can:\n",
"\n",
"- Record every decision it makes with full reasoning provenance\n",
"- Search historical precedents before acting\n",
"- Validate decisions against policy rules\n",
"- Trace causal chains across decisions\n",
"- Accumulate institutional knowledge that survives across sessions\n",
"\n",
"**Domain used:** Financial loan underwriting (easily adapted to healthcare, legal, HR, etc.)\n",
"\n",
"---\n",
"\n",
"## Architecture\n",
"\n",
"```\n",
"Agno Agent\n",
" ├── memory=AgnoContextStore ← graph-backed persistent memory\n",
" └── tools=[AgnoDecisionKit] ← decision tools the LLM can call\n",
" │\n",
" ├── record_decision ← Semantica AgentContext.record_decision()\n",
" ├── find_precedents ← Semantica AgentContext.find_precedents_advanced()\n",
" ├── trace_causal_chain ← Semantica ContextGraph.trace_decision_causality()\n",
" ├── analyze_impact ← Semantica AgentContext.analyze_decision_influence()\n",
" ├── check_policy ← Semantica PolicyEngine\n",
" └── get_decision_summary ← Semantica AgentContext.get_context_insights()\n",
"```\n",
"\n",
"## Install\n",
"\n",
"```bash\n",
"pip install semantica[agno]\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "setup-section",
"metadata": {},
"source": [
"## 1. Setup — Semantica Backends\n",
"\n",
"We build the Semantica components first. These are **independent of Agno** — you can swap backends without touching agent code."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
"import sys, os\n",
"sys.path.insert(0, os.path.abspath(\"../../\"))\n",
"\n",
"# ── Semantica core (not Agno-specific) ──────────────────────────────────────\n",
"from semantica.context import AgentContext, ContextGraph\n",
"from semantica.context import PolicyEngine, DecisionQuery, CausalChainAnalyzer\n",
"from semantica.vector_store import VectorStore\n",
"\n",
"# ── Agno integration layer ───────────────────────────────────────────────────\n",
"from integrations.agno import AgnoContextStore, AgnoDecisionKit, AGNO_AVAILABLE\n",
"\n",
"print(f\"Semantica imports OK\")\n",
"print(f\"Agno installed: {AGNO_AVAILABLE}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "semantica-backends",
"metadata": {},
"outputs": [],
"source": [
"# ── Vector store (FAISS, no external service needed) ────────────────────────\n",
"vector_store = VectorStore(backend=\"faiss\", dimension=768)\n",
"print(\"VectorStore ready (FAISS)\")\n",
"\n",
"# ── In-memory context graph with full analytics ──────────────────────────────\n",
"knowledge_graph = ContextGraph(\n",
" advanced_analytics=True,\n",
" # Switch to neo4j for production:\n",
" # backend=\"neo4j\", uri=\"bolt://localhost:7687\"\n",
")\n",
"print(\"ContextGraph ready (in-memory)\")"
]
},
{
"cell_type": "markdown",
"id": "seed-section",
"metadata": {},
"source": [
"## 2. Seed Historical Decisions\n",
"\n",
"Before the agent runs, we pre-load historical decisions using **native Semantica APIs** so the precedent database is warm.\n",
"\n",
"In production you would ingest from a database or a prior session's graph export."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "seed-decisions",
"metadata": {},
"outputs": [],
"source": [
"# Build a pure-Semantica AgentContext for seeding historical data\n",
"seed_context = AgentContext(\n",
" vector_store=vector_store,\n",
" knowledge_graph=knowledge_graph,\n",
" decision_tracking=True,\n",
")\n",
"\n",
"historical_loans = [\n",
" dict(\n",
" category=\"loan_approval\",\n",
" scenario=\"Applicant: credit score 740, income $95k, DTI 28%, down payment 20%\",\n",
" reasoning=\"Strong credit history, debt load well below 35% threshold, adequate down payment\",\n",
" outcome=\"approved\",\n",
" confidence=0.96,\n",
" ),\n",
" dict(\n",
" category=\"loan_approval\",\n",
" scenario=\"Applicant: credit score 620, income $45k, DTI 42%, down payment 5%\",\n",
" reasoning=\"Credit score below 650 floor, DTI exceeds 40% maximum, insufficient down payment\",\n",
" outcome=\"rejected\",\n",
" confidence=0.97,\n",
" ),\n",
" dict(\n",
" category=\"loan_approval\",\n",
" scenario=\"Applicant: credit score 700, income $72k, DTI 33%, down payment 15%\",\n",
" reasoning=\"Adequate credit, moderate DTI within range, down payment slightly below ideal\",\n",
" outcome=\"approved_with_conditions\",\n",
" confidence=0.82,\n",
" ),\n",
" dict(\n",
" category=\"loan_approval\",\n",
" scenario=\"Applicant: credit score 780, income $130k, DTI 22%, down payment 30%\",\n",
" reasoning=\"Excellent credit, low debt load, strong down payment — low-risk profile\",\n",
" outcome=\"approved\",\n",
" confidence=0.99,\n",
" ),\n",
" dict(\n",
" category=\"loan_approval\",\n",
" scenario=\"Applicant: credit score 660, income $58k, DTI 38%, down payment 10%\",\n",
" reasoning=\"Borderline credit, high DTI, minimal down payment — escalated to senior review\",\n",
" outcome=\"escalated\",\n",
" confidence=0.70,\n",
" ),\n",
"]\n",
"\n",
"for loan in historical_loans:\n",
" did = seed_context.record_decision(**loan)\n",
" print(f\" Seeded [{loan['outcome']:25s}] → {did}\")\n",
"\n",
"print(f\"\\n{len(historical_loans)} historical decisions loaded into Semantica KG\")"
]
},
{
"cell_type": "markdown",
"id": "policy-section",
"metadata": {},
"source": [
"## 3. Define Policy Rules with Semantica\n",
"\n",
"We use `PolicyEngine` directly — no Agno involvement here. The `AgnoDecisionKit.check_policy` tool will call this engine during the agent's reasoning loop."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "policy",
"metadata": {},
"outputs": [],
"source": [
"LENDING_POLICY_RULES = [\n",
" \"credit_score >= 650\",\n",
" \"dti <= 40\",\n",
" \"down_payment_pct >= 10\",\n",
" \"confidence >= 0.70\",\n",
"]\n",
"\n",
"# Verify directly with Semantica's PolicyEngine before wiring to Agno\n",
"policy_engine = PolicyEngine(graph_store=knowledge_graph)\n",
"\n",
"test_application = {\"credit_score\": 720, \"dti\": 31, \"down_payment_pct\": 18, \"confidence\": 0.88}\n",
"\n",
"try:\n",
" result = policy_engine.check_compliance(test_application, LENDING_POLICY_RULES)\n",
" print(f\"Policy check result: compliant={getattr(result, 'compliant', 'N/A')}\")\n",
" print(f\"Violations: {getattr(result, 'violations', [])}\")\n",
"except Exception as e:\n",
" print(f\"PolicyEngine fallback (expected without full rule engine): {e}\")\n",
"\n",
"print(\"\\nPolicy rules defined:\", LENDING_POLICY_RULES)"
]
},
{
"cell_type": "markdown",
"id": "agent-section",
"metadata": {},
"source": [
"## 4. Build the Agno Decision-Intelligence Agent\n",
"\n",
"Now we wire everything into Agno using the integration classes.\n",
"\n",
"- `AgnoContextStore` gives the agent **persistent graph-backed memory**\n",
"- `AgnoDecisionKit` exposes **6 decision tools** the LLM can invoke during reasoning"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "build-agent",
"metadata": {},
"outputs": [],
"source": [
"# ── AgnoContextStore: wraps AgentContext as Agno MemoryDb ────────────────────\n",
"store = AgnoContextStore(\n",
" vector_store=vector_store, # Same store — shares seeded decisions\n",
" knowledge_graph=knowledge_graph, # Same graph — shares seeded decisions\n",
" decision_tracking=True,\n",
" graph_expansion=True,\n",
" session_id=\"loan_underwriter_v1\",\n",
")\n",
"print(\"AgnoContextStore ready\")\n",
"\n",
"# ── AgnoDecisionKit: exposes Semantica decision tools to Agno's LLM ──────────\n",
"decision_kit = AgnoDecisionKit(\n",
" context=store.context, # Reuse same AgentContext — shared decision history\n",
" max_precedents=5,\n",
" causal_depth=3,\n",
" enable_policy_check=True,\n",
")\n",
"print(f\"AgnoDecisionKit ready — {len(decision_kit._tools)} tools registered\")\n",
"print(\" Tools:\", [fn.__name__ for fn in decision_kit._tools])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "wire-agent",
"metadata": {},
"outputs": [],
"source": [
"if AGNO_AVAILABLE:\n",
" from agno.agent import Agent\n",
" from agno.memory import AgentMemory\n",
" from agno.models.openai import OpenAIChat # or any Agno-supported model\n",
"\n",
" agent = Agent(\n",
" name=\"LoanUnderwriter\",\n",
" model=OpenAIChat(id=\"gpt-4o\"),\n",
" memory=AgentMemory(db=store),\n",
" tools=[decision_kit],\n",
" show_tool_calls=True,\n",
" description=(\n",
" \"You are a senior loan underwriter. Before approving or rejecting any application:\"\n",
" \" (1) find_precedents for similar past cases,\"\n",
" \" (2) check_policy compliance,\"\n",
" \" (3) record_decision with full reasoning.\"\n",
" \" Always cite precedents and policy rule results in your explanation.\"\n",
" ),\n",
" )\n",
" print(\"Agno Agent assembled and ready\")\n",
"else:\n",
" print(\"Agno not installed — demonstrating tool calls directly below\")"
]
},
{
"cell_type": "markdown",
"id": "demo-section",
"metadata": {},
"source": [
"## 5. Demonstrate Decision Tools\n",
"\n",
"We call the decision tools **directly** so the notebook is fully runnable without an OpenAI key. When Agno is wired, the LLM orchestrates these same calls automatically."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-find-precedents",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"\n",
"# ── 5a. Find Precedents ───────────────────────────────────────────────────────\n",
"print(\"=\" * 60)\n",
"print(\"TOOL: find_precedents\")\n",
"print(\"=\" * 60)\n",
"\n",
"new_application_scenario = (\n",
" \"Applicant: credit score 715, income $82k, DTI 30%, down payment 18%\"\n",
")\n",
"\n",
"precedents_json = decision_kit.find_precedents(\n",
" scenario=new_application_scenario,\n",
" category=\"loan_approval\",\n",
" limit=3,\n",
")\n",
"precedents = json.loads(precedents_json)\n",
"print(f\"Found {precedents['count']} similar past decisions:\")\n",
"for p in precedents['precedents']:\n",
" print(f\" [{p.get('outcome','?'):25s}] confidence={p.get('confidence',0):.2f}\")\n",
" print(f\" {p.get('scenario','')[:80]}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-policy",
"metadata": {},
"outputs": [],
"source": [
"# ── 5b. Check Policy ─────────────────────────────────────────────────────────\n",
"print(\"=\" * 60)\n",
"print(\"TOOL: check_policy\")\n",
"print(\"=\" * 60)\n",
"\n",
"decision_data = json.dumps({\n",
" \"credit_score\": 715,\n",
" \"dti\": 30,\n",
" \"down_payment_pct\": 18,\n",
" \"confidence\": 0.88,\n",
" \"outcome\": \"approved\",\n",
"})\n",
"\n",
"policy_json = decision_kit.check_policy(\n",
" decision_data=decision_data,\n",
" policy_rules=json.dumps(LENDING_POLICY_RULES),\n",
")\n",
"policy_result = json.loads(policy_json)\n",
"print(f\"Compliant: {policy_result.get('compliant')}\")\n",
"print(f\"Violations: {policy_result.get('violations', [])}\")\n",
"print(f\"Warnings: {policy_result.get('warnings', [])}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-record",
"metadata": {},
"outputs": [],
"source": [
"# ── 5c. Record Decision ──────────────────────────────────────────────────────\n",
"print(\"=\" * 60)\n",
"print(\"TOOL: record_decision\")\n",
"print(\"=\" * 60)\n",
"\n",
"record_json = decision_kit.record_decision(\n",
" category=\"loan_approval\",\n",
" scenario=new_application_scenario,\n",
" reasoning=(\n",
" \"3 similar precedents found — 2 approved, 1 escalated. \"\n",
" \"Credit score 715 exceeds 650 floor. DTI 30% well within 40% limit. \"\n",
" \"Down payment 18% above 10% minimum. All policy rules satisfied.\"\n",
" ),\n",
" outcome=\"approved\",\n",
" confidence=0.91,\n",
" entities=\"loan_applicant, credit_bureau, lending_policy_v2\",\n",
")\n",
"record_result = json.loads(record_json)\n",
"decision_id = record_result['decision_id']\n",
"print(f\"Decision recorded: {decision_id}\")\n",
"print(f\"Status: {record_result['status']}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-impact",
"metadata": {},
"outputs": [],
"source": [
"# ── 5d. Analyze Impact ───────────────────────────────────────────────────────\n",
"print(\"=\" * 60)\n",
"print(\"TOOL: analyze_impact\")\n",
"print(\"=\" * 60)\n",
"\n",
"impact_json = decision_kit.analyze_impact(decision_id=decision_id)\n",
"impact = json.loads(impact_json)\n",
"print(\"Impact analysis:\")\n",
"for k, v in impact.items():\n",
" if k != \"decision_id\":\n",
" print(f\" {k}: {v}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-summary",
"metadata": {},
"outputs": [],
"source": [
"# ── 5e. Decision Summary ─────────────────────────────────────────────────────\n",
"print(\"=\" * 60)\n",
"print(\"TOOL: get_decision_summary\")\n",
"print(\"=\" * 60)\n",
"\n",
"summary_json = decision_kit.get_decision_summary(category=\"loan_approval\")\n",
"summary = json.loads(summary_json)\n",
"print(\"Decision history summary:\")\n",
"for k, v in summary.items():\n",
" if k not in (\"category_filter\",):\n",
" print(f\" {k}: {v}\")"
]
},
{
"cell_type": "markdown",
"id": "agno-run-section",
"metadata": {},
"source": [
"## 6. Run the Full Agno Agent (requires API key)\n",
"\n",
"When `AGNO_AVAILABLE=True` and an OpenAI key is set, the LLM orchestrates all the tool calls automatically."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "run-agent",
"metadata": {},
"outputs": [],
"source": [
"NEW_CASE = (\n",
" \"New mortgage application received:\\n\"\n",
" \" Credit score: 715, Annual income: $82,000\\n\"\n",
" \" Debt-to-income: 30%, Down payment: 18%\\n\"\n",
" \" Loan amount: $320,000 for a primary residence in Austin TX\\n\"\n",
" \"Should we approve this application?\"\n",
")\n",
"\n",
"if AGNO_AVAILABLE:\n",
" agent.print_response(NEW_CASE)\n",
"else:\n",
" print(\"[Agno not installed — skipping live agent run]\")\n",
" print()\n",
" print(\"Expected agent reasoning flow:\")\n",
" print(\" 1. find_precedents('credit score 715, DTI 30%, down payment 18%')\")\n",
" print(\" → 2 approved, 1 escalated among similar cases\")\n",
" print(\" 2. check_policy(credit_score=715, dti=30, down_payment_pct=18)\")\n",
" print(\" → compliant=True, violations=[]\")\n",
" print(\" 3. record_decision(outcome='approved', confidence=0.91)\")\n",
" print(\" → decision_id recorded in Semantica KG\")\n",
" print()\n",
" print(\" Recommendation: APPROVE — 3 precedents + full policy compliance\")"
]
},
{
"cell_type": "markdown",
"id": "analytics-section",
"metadata": {},
"source": [
"## 7. Post-Session Analytics with Semantica\n",
"\n",
"After the agent session, use **native Semantica APIs** for reporting and causal analysis — no Agno required."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "analytics",
"metadata": {},
"outputs": [],
"source": [
"# Query decision history directly from Semantica\n",
"insights = store.context.get_context_insights()\n",
"print(\"Session Insights (Semantica native):\")\n",
"if isinstance(insights, dict):\n",
" for k, v in insights.items():\n",
" print(f\" {k}: {v}\")\n",
"else:\n",
" print(f\" {insights}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "precedents-direct",
"metadata": {},
"outputs": [],
"source": [
"# Precedent search directly via Semantica's AgentContext\n",
"# (same data, no Agno in the loop)\n",
"precedents = store.context.find_precedents_advanced(\n",
" scenario=\"borderline mortgage application\",\n",
" category=\"loan_approval\",\n",
")\n",
"print(f\"\\nPrecedent search via Semantica directly → {len(precedents or [])} results\")"
]
},
{
"cell_type": "markdown",
"id": "summary-section",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"| What | How |\n",
"|---|---|\n",
"| Persistent decision history | `AgnoContextStore` wrapping `AgentContext` + FAISS |\n",
"| Tool calls for decision intelligence | `AgnoDecisionKit` (record, find, trace, check, summarise) |\n",
"| Historical seeding | Native `AgentContext.record_decision()` — no Agno needed |\n",
"| Policy rules | Native `PolicyEngine` — no Agno needed |\n",
"| Post-session analytics | Native `AgentContext.get_context_insights()` — no Agno needed |\n",
"\n",
"The Agno integration is a **thin wrapper** — Semantica's full API remains directly accessible whenever you need finer control."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,615 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "title",
"metadata": {},
"source": [
"# Agno × Semantica: GraphRAG Context Agent\n",
"\n",
"This notebook demonstrates how to give an Agno agent a **relational knowledge graph** instead of a flat document store. The agent retrieves answers via **multi-hop graph traversal** — finding connections that pure vector search misses.\n",
"\n",
"**Domain:** Regulatory compliance (Basel IV / DORA) — documents are ingested, entities & relations extracted, then the agent answers questions by hopping through the graph.\n",
"\n",
"---\n",
"\n",
"## Architecture\n",
"\n",
"```\n",
"Agno Agent\n",
" ├── knowledge=AgnoKnowledgeGraph ← GraphRAG knowledge base\n",
" └── tools=[AgnoKGToolkit] ← live graph building/query tools\n",
" │\n",
" │ Backed by Semantica:\n",
" ├── NERExtractor ← named entity recognition\n",
" ├── RelationExtractor ← relation extraction\n",
" ├── GraphBuilder ← builds ContextGraph from extractions\n",
" ├── ContextGraph ← in-memory graph with analytics\n",
" └── Reasoner ← rule-based inference\n",
"```\n",
"\n",
"## Install\n",
"\n",
"```bash\n",
"pip install semantica[agno]\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "imports-section",
"metadata": {},
"source": [
"## 1. Imports — Semantica Core + Agno Integration"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
"import sys, os, json\n",
"sys.path.insert(0, os.path.abspath(\"../../\"))\n",
"\n",
"# ── Semantica core — used directly for pipeline setup ───────────────────────\n",
"from semantica.kg import GraphBuilder\n",
"from semantica.context import ContextGraph\n",
"from semantica.semantic_extract import NERExtractor, RelationExtractor, TripletExtractor\n",
"from semantica.reasoning import Reasoner\n",
"from semantica.vector_store import VectorStore\n",
"\n",
"# ── Agno integration layer ───────────────────────────────────────────────────\n",
"from integrations.agno import AgnoKnowledgeGraph, AgnoKGToolkit, AGNO_AVAILABLE\n",
"\n",
"print(\"Semantica imports OK\")\n",
"print(f\"Agno installed: {AGNO_AVAILABLE}\")"
]
},
{
"cell_type": "markdown",
"id": "pipeline-section",
"metadata": {},
"source": [
"## 2. Build the Semantica Extraction Pipeline\n",
"\n",
"The extraction pipeline (NER → relation extraction → graph build) is pure Semantica. We construct each component explicitly so we can also use them for analysis outside Agno."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "build-pipeline",
"metadata": {},
"outputs": [],
"source": [
"# NER — identifies organisations, regulations, dates, amounts, roles\n",
"ner = NERExtractor()\n",
"\n",
"# Relation extractor — finds typed edges between entities\n",
"rel_extractor = RelationExtractor(confidence_threshold=0.60)\n",
"\n",
"# Knowledge graph builder\n",
"graph_builder = GraphBuilder(merge_entities=True, temporal_support=True)\n",
"\n",
"# In-memory context graph (swap to neo4j/falkordb for persistence)\n",
"context_graph = ContextGraph(advanced_analytics=True)\n",
"\n",
"# Reasoner for rule inference over the graph\n",
"reasoner = Reasoner()\n",
"\n",
"print(\"Semantica extraction pipeline assembled\")"
]
},
{
"cell_type": "markdown",
"id": "ingest-raw-section",
"metadata": {},
"source": [
"## 3. Direct Semantica Extraction (Before Agno)\n",
"\n",
"We first demonstrate extraction using **raw Semantica APIs** so you can see exactly what goes into the graph.\n",
"This is the same pipeline `AgnoKnowledgeGraph.load()` runs internally."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "raw-documents",
"metadata": {},
"outputs": [],
"source": [
"# Regulatory documents (representative snippets)\n",
"REGULATORY_DOCS = [\n",
" {\n",
" \"title\": \"Basel IV — Capital Requirements\",\n",
" \"text\": (\n",
" \"Basel IV introduces a revised standardised approach for credit risk, \"\n",
" \"replacing internal model floors. Banks must maintain a minimum CET1 ratio \"\n",
" \"of 4.5% and a total capital ratio of 8%. The BCBS finalised these requirements \"\n",
" \"in December 2017 with a phased implementation starting January 2022. \"\n",
" \"National regulators including the EBA and FCA are responsible for local \"\n",
" \"transposition. Risk-weighted assets under Basel IV are calculated using \"\n",
" \"the Output Floor, capping RWA reductions at 72.5%.\"\n",
" ),\n",
" },\n",
" {\n",
" \"title\": \"DORA — Digital Operational Resilience Act\",\n",
" \"text\": (\n",
" \"DORA (Regulation EU 2022/2554) applies to financial entities and ICT \"\n",
" \"third-party service providers operating in the EU. It mandates ICT risk \"\n",
" \"management frameworks, incident classification, and annual operational \"\n",
" \"resilience testing. Supervised entities must report major ICT incidents to \"\n",
" \"the European Supervisory Authorities (ESAs) within 4 hours of classification. \"\n",
" \"Critical ICT providers are subject to direct oversight by the Joint Oversight \"\n",
" \"Network led by ESMA, EBA, and EIOPA. DORA became applicable on 17 January 2025.\"\n",
" ),\n",
" },\n",
" {\n",
" \"title\": \"AML — Anti-Money Laundering Directive VI\",\n",
" \"text\": (\n",
" \"AMLD6 strengthens the EU's anti-money laundering framework by extending \"\n",
" \"criminal liability to 22 predicate offences including cybercrime and \"\n",
" \"environmental crime. Financial institutions must apply Customer Due Diligence \"\n",
" \"(CDD) at onboarding and Enhanced Due Diligence (EDD) for high-risk customers. \"\n",
" \"Suspicious Activity Reports (SARs) are filed with the national Financial \"\n",
" \"Intelligence Unit (FIU). Non-compliance carries penalties up to 10% of \"\n",
" \"annual global turnover. AMLD6 was transposed into UK law via MLCO 2020.\"\n",
" ),\n",
" },\n",
"]\n",
"\n",
"print(f\"Documents to ingest: {len(REGULATORY_DOCS)}\")\n",
"for doc in REGULATORY_DOCS:\n",
" print(f\" • {doc['title']}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "run-ner",
"metadata": {},
"outputs": [],
"source": [
"# ── Run NER directly with Semantica ─────────────────────────────────────────\n",
"all_entities = []\n",
"for doc in REGULATORY_DOCS:\n",
" entities = ner.extract_entities(doc['text']) or []\n",
" all_entities.extend(entities)\n",
" print(f\"[{doc['title']}] → {len(entities)} entities\")\n",
" for e in entities[:4]:\n",
" print(f\" {getattr(e,'name','?'):30s} type={getattr(e,'type','?')} conf={getattr(e,'confidence',0):.2f}\")\n",
"\n",
"print(f\"\\nTotal entities extracted: {len(all_entities)}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "run-rel",
"metadata": {},
"outputs": [],
"source": [
"# ── Run relation extraction directly with Semantica ──────────────────────────\n",
"all_relations = []\n",
"for doc in REGULATORY_DOCS:\n",
" relations = rel_extractor.extract_relations(doc['text']) or []\n",
" all_relations.extend(relations)\n",
" print(f\"[{doc['title']}] → {len(relations)} relations\")\n",
" for r in relations[:3]:\n",
" src = getattr(r, 'source', '?')\n",
" rtype = getattr(r, 'type', getattr(r, 'relation', '?'))\n",
" tgt = getattr(r, 'target', '?')\n",
" conf = getattr(r, 'confidence', 0)\n",
" print(f\" {src!s:20s} --[{rtype}]--> {tgt!s:20s} conf={conf:.2f}\")\n",
"\n",
"print(f\"\\nTotal relations extracted: {len(all_relations)}\")"
]
},
{
"cell_type": "markdown",
"id": "agno-kg-section",
"metadata": {},
"source": [
"## 4. Build AgnoKnowledgeGraph\n",
"\n",
"`AgnoKnowledgeGraph` wraps the extraction pipeline and implements Agno's `AgentKnowledge` protocol. It runs the same NER + relation extract + graph build pipeline internally — here we pass our pre-built components so the same instances are used."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "build-agno-kg",
"metadata": {},
"outputs": [],
"source": [
"kg = AgnoKnowledgeGraph(\n",
" graph_builder=graph_builder,\n",
" ner_extractor=ner,\n",
" relation_extractor=rel_extractor,\n",
" context_graph=context_graph,\n",
" num_documents=5,\n",
")\n",
"\n",
"# Ingest all documents through the integration wrapper\n",
"kg.load(texts=[doc['text'] for doc in REGULATORY_DOCS])\n",
"\n",
"print(f\"AgnoKnowledgeGraph: {len(kg._docs)} documents indexed\")"
]
},
{
"cell_type": "markdown",
"id": "graphrag-section",
"metadata": {},
"source": [
"## 5. GraphRAG Search\n",
"\n",
"The `search()` method implements **multi-hop GraphRAG**:\n",
"1. Vector similarity over stored document texts\n",
"2. Entity lookup in the context graph\n",
"3. Graph hop expansion for entity neighbourhood\n",
"4. Context injection into the returned documents"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "graphrag-search",
"metadata": {},
"outputs": [],
"source": [
"queries = [\n",
" \"What is the minimum CET1 ratio required under Basel IV?\",\n",
" \"Which authorities supervise critical ICT providers under DORA?\",\n",
" \"What are the reporting timelines for major ICT incidents?\",\n",
" \"How does AMLD6 handle customer due diligence?\",\n",
"]\n",
"\n",
"for query in queries:\n",
" print(f\"\\nQ: {query}\")\n",
" results = kg.search(query, num_documents=2)\n",
" print(f\" Retrieved {len(results)} document(s)\")\n",
" for i, doc in enumerate(results, 1):\n",
" content = getattr(doc, 'content', str(doc))\n",
" print(f\" [{i}] {content[:120]}...\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "entity-context",
"metadata": {},
"outputs": [],
"source": [
"# Get graph context for a specific entity\n",
"entity_contexts = [\"BCBS\", \"EBA\", \"DORA\", \"Basel IV\"]\n",
"for entity in entity_contexts:\n",
" ctx = kg.get_graph_context(entity)\n",
" print(f\"\\nGraph context for '{entity}':\")\n",
" print(ctx if ctx else \" (no graph nodes found — depends on NER extraction quality)\")"
]
},
{
"cell_type": "markdown",
"id": "toolkit-section",
"metadata": {},
"source": [
"## 6. AgnoKGToolkit — Live Graph Building\n",
"\n",
"The `AgnoKGToolkit` exposes 7 tools the LLM can call to **actively modify and query the graph** during reasoning."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "build-toolkit",
"metadata": {},
"outputs": [],
"source": [
"toolkit = AgnoKGToolkit(\n",
" ner_extractor=ner,\n",
" relation_extractor=rel_extractor,\n",
" reasoner=reasoner,\n",
" context=context_graph, # share same graph as knowledge base\n",
")\n",
"\n",
"print(f\"AgnoKGToolkit: {len(toolkit._tools)} tools\")\n",
"print(\" Tools:\", [fn.__name__ for fn in toolkit._tools])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-extract-entities",
"metadata": {},
"outputs": [],
"source": [
"# TOOL: extract_entities\n",
"print(\"=\" * 55)\n",
"print(\"TOOL: extract_entities\")\n",
"print(\"=\" * 55)\n",
"\n",
"new_text = (\n",
" \"The PRA published a consultation paper requiring UK banks to \"\n",
" \"implement DORA-equivalent resilience testing by Q3 2025, \"\n",
" \"with Barclays and HSBC named as systemic institutions.\"\n",
")\n",
"entities_json = toolkit.extract_entities(new_text)\n",
"entities_result = json.loads(entities_json)\n",
"print(f\"Found {entities_result['count']} entities:\")\n",
"for e in entities_result['entities']:\n",
" print(f\" {e['name']:30s} type={e['type']:15s} conf={e['confidence']:.2f}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-extract-relations",
"metadata": {},
"outputs": [],
"source": [
"# TOOL: extract_relations\n",
"print(\"=\" * 55)\n",
"print(\"TOOL: extract_relations\")\n",
"print(\"=\" * 55)\n",
"\n",
"relations_json = toolkit.extract_relations(new_text)\n",
"relations_result = json.loads(relations_json)\n",
"print(f\"Found {relations_result['count']} relations:\")\n",
"for r in relations_result['relations']:\n",
" print(f\" {r['source']:20s} --[{r['relation']}]--> {r['target']:20s}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-add-graph",
"metadata": {},
"outputs": [],
"source": [
"# TOOL: add_to_graph\n",
"print(\"=\" * 55)\n",
"print(\"TOOL: add_to_graph\")\n",
"print(\"=\" * 55)\n",
"\n",
"add_result = json.loads(toolkit.add_to_graph(\n",
" entities=json.dumps([\n",
" {\"name\": \"PRA\", \"type\": \"REGULATOR\"},\n",
" {\"name\": \"Barclays\", \"type\": \"BANK\"},\n",
" {\"name\": \"HSBC\", \"type\": \"BANK\"},\n",
" ]),\n",
" relations=json.dumps([\n",
" {\"source\": \"PRA\", \"relation\": \"SUPERVISES\", \"target\": \"Barclays\"},\n",
" {\"source\": \"PRA\", \"relation\": \"SUPERVISES\", \"target\": \"HSBC\"},\n",
" {\"source\": \"Barclays\", \"relation\": \"SUBJECT_TO\", \"target\": \"DORA\"},\n",
" {\"source\": \"HSBC\", \"relation\": \"SUBJECT_TO\", \"target\": \"DORA\"},\n",
" ]),\n",
"))\n",
"print(f\"Added: {add_result['nodes_added']} nodes, {add_result['edges_added']} edges\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-query-graph",
"metadata": {},
"outputs": [],
"source": [
"# TOOL: query_graph\n",
"print(\"=\" * 55)\n",
"print(\"TOOL: query_graph\")\n",
"print(\"=\" * 55)\n",
"\n",
"query_result = json.loads(toolkit.query_graph(\"PRA\"))\n",
"print(f\"Keyword query 'PRA' → {query_result['count']} node(s):\")\n",
"for node in query_result['results']:\n",
" print(f\" label={node.get('label')} type={node.get('type')}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-find-related",
"metadata": {},
"outputs": [],
"source": [
"# TOOL: find_related\n",
"print(\"=\" * 55)\n",
"print(\"TOOL: find_related\")\n",
"print(\"=\" * 55)\n",
"\n",
"related_result = json.loads(toolkit.find_related(\"Barclays\", hops=2))\n",
"print(f\"Related to 'Barclays' (2 hops): {related_result['count']} entity/entities\")\n",
"for name in related_result['related']:\n",
" print(f\" → {name}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-infer",
"metadata": {},
"outputs": [],
"source": [
"# TOOL: infer_facts — Semantica's Reasoner derives new facts from graph state\n",
"print(\"=\" * 55)\n",
"print(\"TOOL: infer_facts\")\n",
"print(\"=\" * 55)\n",
"\n",
"# Rules: regulatory compliance inference\n",
"inference_rules = json.dumps([\n",
" \"IF BANK(?x) THEN FinancialEntity(?x)\",\n",
" \"IF REGULATOR(?x) THEN SupervisoryAuthority(?x)\",\n",
" \"IF FinancialEntity(?x) THEN ComplianceSubject(?x)\",\n",
"])\n",
"\n",
"infer_result = json.loads(toolkit.infer_facts(rules=inference_rules))\n",
"print(f\"Inferred {infer_result['count']} new fact(s):\")\n",
"for fact in infer_result['inferred_facts'][:8]:\n",
" print(f\" {fact}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "demo-export",
"metadata": {},
"outputs": [],
"source": [
"# TOOL: export_subgraph — export knowledge for downstream systems\n",
"print(\"=\" * 55)\n",
"print(\"TOOL: export_subgraph (JSON-LD)\")\n",
"print(\"=\" * 55)\n",
"\n",
"export_result = json.loads(toolkit.export_subgraph(entity=\"DORA\", format=\"json-ld\"))\n",
"print(f\"Exported as format='{export_result['format']}'\")\n",
"if 'data' in export_result:\n",
" preview = str(export_result['data'])[:300]\n",
" print(f\"Preview: {preview}...\")\n",
"elif 'nodes' in export_result:\n",
" print(f\"Graph nodes exported: {len(export_result['nodes'])}\")\n",
" for node in export_result['nodes'][:5]:\n",
" print(f\" {node}\")"
]
},
{
"cell_type": "markdown",
"id": "agno-run-section",
"metadata": {},
"source": [
"## 7. Run the Full Agno GraphRAG Agent (requires API key)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "agno-agent",
"metadata": {},
"outputs": [],
"source": [
"if AGNO_AVAILABLE:\n",
" from agno.agent import Agent\n",
" from agno.models.openai import OpenAIChat\n",
"\n",
" compliance_agent = Agent(\n",
" name=\"ComplianceAnalyst\",\n",
" model=OpenAIChat(id=\"gpt-4o\"),\n",
" knowledge=kg,\n",
" search_knowledge=True,\n",
" tools=[toolkit],\n",
" show_tool_calls=True,\n",
" description=(\n",
" \"You are a regulatory compliance analyst. Use the knowledge graph \"\n",
" \"to answer questions about Basel IV, DORA, and AML regulations. \"\n",
" \"When answering, use find_related and query_graph to discover \"\n",
" \"connections between regulators, rules, and institutions.\"\n",
" ),\n",
" )\n",
"\n",
" compliance_agent.print_response(\n",
" \"Which supervisory authorities are responsible for overseeing DORA compliance \"\n",
" \"for UK banks, and how does this relate to Basel IV capital requirements?\"\n",
" )\n",
"else:\n",
" print(\"[Agno not installed — skipping live agent run]\")\n",
" print()\n",
" print(\"Expected reasoning flow:\")\n",
" print(\" search_knowledge('DORA supervisory authorities UK banks')\")\n",
" print(\" → retrieves DORA doc with graph expansion\")\n",
" print(\" query_graph('PRA') → finds PRA node\")\n",
" print(\" find_related('PRA', hops=2) → PRA → SUPERVISES → Barclays, HSBC\")\n",
" print(\" find_related('Basel IV', hops=1) → capital ratio requirements\")\n",
" print(\" Answer: PRA supervises UK banks under DORA; Basel IV CET1 requirement is 4.5%\")"
]
},
{
"cell_type": "markdown",
"id": "semantica-analysis",
"metadata": {},
"source": [
"## 8. Post-Session Graph Analysis with Semantica\n",
"\n",
"After the agent session, use Semantica's graph analytics directly to explore the accumulated knowledge."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "graph-analytics",
"metadata": {},
"outputs": [],
"source": [
"# Use Semantica's GraphAnalyzer directly on the same ContextGraph\n",
"from semantica.kg import GraphAnalyzer, CentralityCalculator, PathFinder\n",
"\n",
"try:\n",
" analyzer = GraphAnalyzer()\n",
" analysis = analyzer.analyze_graph(context_graph)\n",
" print(\"Graph analysis (Semantica native):\")\n",
" if isinstance(analysis, dict):\n",
" for k, v in list(analysis.items())[:8]:\n",
" print(f\" {k}: {v}\")\n",
" else:\n",
" print(f\" {analysis}\")\n",
"except Exception as e:\n",
" print(f\"GraphAnalyzer: {e}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "centrality",
"metadata": {},
"outputs": [],
"source": [
"# Centrality — which entities are most connected / influential?\n",
"try:\n",
" centrality = CentralityCalculator()\n",
" scores = centrality.calculate_degree_centrality(context_graph)\n",
" print(\"Degree centrality (most connected entities):\")\n",
" if isinstance(scores, dict):\n",
" top = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:5]\n",
" for entity, score in top:\n",
" print(f\" {entity:30s} {score:.4f}\")\n",
" else:\n",
" print(f\" {scores}\")\n",
"except Exception as e:\n",
" print(f\"CentralityCalculator: {e}\")"
]
},
{
"cell_type": "markdown",
"id": "summary-section",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"| Component | Role | Library |\n",
"|---|---|---|\n",
"| `NERExtractor` | Extract regulatory entities from text | Semantica |\n",
"| `RelationExtractor` | Extract typed edges between entities | Semantica |\n",
"| `GraphBuilder` | Build `ContextGraph` from extractions | Semantica |\n",
"| `Reasoner` | Infer new facts from graph state | Semantica |\n",
"| `AgnoKnowledgeGraph` | GraphRAG `AgentKnowledge` interface | Agno integration |\n",
"| `AgnoKGToolkit` | 7 live graph tools for the Agno LLM | Agno integration |\n",
"| `GraphAnalyzer` / `CentralityCalculator` | Post-session analytics | Semantica |\n",
"\n",
"The Agno integration wraps Semantica components — the full Semantica API is available for pre/post-processing and analytics independently of the agent."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -0,0 +1,676 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "title",
"metadata": {},
"source": [
"# Agno × Semantica: Multi-Agent Shared Context\n",
"\n",
"This notebook shows how an Agno **Team** of specialist agents can share a single `ContextGraph` so they:\n",
"\n",
"- Never make contradictory decisions\n",
"- Reuse each other's extracted knowledge without coupling implementations\n",
"- Maintain a full causal audit trail across all agents\n",
"\n",
"**Scenario:** A product strategy team with three specialist agents:\n",
"\n",
"| Agent | Role | Tools |\n",
"|---|---|---|\n",
"| `Researcher` | Extracts competitive intelligence from text | `AgnoKGToolkit` |\n",
"| `Analyst` | Evaluates opportunities and records decisions | `AgnoDecisionKit` |\n",
"| `Strategist` | Synthesises both into a recommendation | both |\n",
"\n",
"---\n",
"\n",
"## Architecture\n",
"\n",
"```\n",
"AgnoSharedContext (single ContextGraph + VectorStore)\n",
" │\n",
" ├── bind_agent(\"researcher\") → AgnoContextStore (role-scoped)\n",
" ├── bind_agent(\"analyst\") → AgnoContextStore (role-scoped)\n",
" └── bind_agent(\"strategist\") → AgnoContextStore (role-scoped)\n",
"\n",
"Agno Team\n",
" ├── Researcher memory=researcher_store tools=[AgnoKGToolkit(context=shared)]\n",
" ├── Analyst memory=analyst_store tools=[AgnoDecisionKit(context=shared)]\n",
" └── Strategist memory=strategist_store tools=[AgnoKGToolkit, AgnoDecisionKit]\n",
"```\n",
"\n",
"## Install\n",
"\n",
"```bash\n",
"pip install semantica[agno]\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "imports-section",
"metadata": {},
"source": [
"## 1. Imports"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
"import sys, os, json\n",
"sys.path.insert(0, os.path.abspath(\"../../\"))\n",
"\n",
"# ── Semantica core ───────────────────────────────────────────────────────────\n",
"from semantica.context import ContextGraph, AgentContext, CausalChainAnalyzer\n",
"from semantica.vector_store import VectorStore\n",
"from semantica.semantic_extract import NERExtractor, RelationExtractor\n",
"from semantica.reasoning import Reasoner\n",
"from semantica.kg import GraphBuilder, GraphAnalyzer, CentralityCalculator\n",
"\n",
"# ── Agno integration ─────────────────────────────────────────────────────────\n",
"from integrations.agno import (\n",
" AgnoSharedContext,\n",
" AgnoDecisionKit,\n",
" AgnoKGToolkit,\n",
" AGNO_AVAILABLE,\n",
")\n",
"\n",
"print(\"Semantica imports OK\")\n",
"print(f\"Agno installed: {AGNO_AVAILABLE}\")"
]
},
{
"cell_type": "markdown",
"id": "shared-context-section",
"metadata": {},
"source": [
"## 2. Build the Shared Semantica Backend\n",
"\n",
"A single `VectorStore` and `ContextGraph` underpin the entire team. All agents read and write to the same store — role scoping is applied automatically by `AgnoSharedContext`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "build-shared",
"metadata": {},
"outputs": [],
"source": [
"# ── Single shared backends ───────────────────────────────────────────────────\n",
"shared_vector_store = VectorStore(backend=\"faiss\", dimension=768)\n",
"shared_graph = ContextGraph(advanced_analytics=True)\n",
"\n",
"print(\"Shared VectorStore (FAISS) ready\")\n",
"print(\"Shared ContextGraph ready\")\n",
"\n",
"# ── AgnoSharedContext: the team coordinator ───────────────────────────────────\n",
"shared = AgnoSharedContext(\n",
" vector_store=shared_vector_store,\n",
" knowledge_graph=shared_graph,\n",
" decision_tracking=True,\n",
" session_id=\"product_strategy_team_q1_2026\",\n",
")\n",
"print(f\"\\nAgnoSharedContext ready — session: {shared.session_id}\")"
]
},
{
"cell_type": "markdown",
"id": "bind-section",
"metadata": {},
"source": [
"## 3. Bind Agent Roles\n",
"\n",
"Each agent gets a **role-scoped** `AgnoContextStore` via `bind_agent()`. All agents share the same underlying graph, but their writes are tagged with their role for filtering."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bind-agents",
"metadata": {},
"outputs": [],
"source": [
"# Bind each agent role — idempotent, can be called multiple times safely\n",
"researcher_store = shared.bind_agent(\"researcher\")\n",
"analyst_store = shared.bind_agent(\"analyst\")\n",
"strategist_store = shared.bind_agent(\"strategist\")\n",
"\n",
"print(\"Agent roles bound:\")\n",
"for role in shared.bound_roles:\n",
" store = shared.bind_agent(role)\n",
" print(f\" {role:15s} → session={store.session_id}\")\n",
"\n",
"# Verify all roles see the same underlying knowledge_graph\n",
"assert researcher_store._ctx is analyst_store._ctx\n",
"print(\"\\nAll agents share the same AgentContext ✓\")"
]
},
{
"cell_type": "markdown",
"id": "seed-section",
"metadata": {},
"source": [
"## 4. Pre-Load Competitive Intelligence\n",
"\n",
"Using **native Semantica APIs**, we load a competitive landscape into the shared graph. This represents knowledge the team has accumulated from prior research sessions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "seed-intel",
"metadata": {},
"outputs": [],
"source": [
"# Competitive intelligence documents\n",
"COMPETITIVE_INTEL = [\n",
" {\n",
" \"source\": \"market_research_q4_2025\",\n",
" \"text\": (\n",
" \"Competitor Alpha launched a new SaaS analytics platform in Q4 2025. \"\n",
" \"The product targets mid-market enterprises with annual revenue between \"\n",
" \"$50M$500M and has attracted 200 paying customers within 3 months. \"\n",
" \"Pricing is $2,000/seat/year with volume discounts at 50+ seats. \"\n",
" \"Alpha raised a $80M Series C led by Sequoia Capital in November 2025.\"\n",
" ),\n",
" },\n",
" {\n",
" \"source\": \"customer_interviews_q4_2025\",\n",
" \"text\": (\n",
" \"Customer interviews reveal strong demand for AI-powered anomaly detection \"\n",
" \"in financial reporting workflows. 78% of CFOs surveyed cite 'time to insight' \"\n",
" \"as the top pain point — currently averaging 14 days per reporting cycle. \"\n",
" \"Competitor Alpha scores poorly on integration depth (NPS: 24) while \"\n",
" \"our legacy product scores 41. Customers value our data governance features \"\n",
" \"but want a modern UI and sub-second query times.\"\n",
" ),\n",
" },\n",
" {\n",
" \"source\": \"technology_scan_q4_2025\",\n",
" \"text\": (\n",
" \"Emerging technologies for consideration: LLM-native analytics interfaces \"\n",
" \"reduce time-to-insight by 60% in pilot studies (Stanford HAI, 2025). \"\n",
" \"Graph-based anomaly detection outperforms time-series approaches for \"\n",
" \"multi-entity financial fraud by 34% (ACM SIGMOD 2025). \"\n",
" \"Vector database adoption in enterprise analytics grew 120% YoY. \"\n",
" \"Apache Arrow and DuckDB emerging as standards for in-process OLAP.\"\n",
" ),\n",
" },\n",
"]\n",
"\n",
"# Use Semantica NER + RelationExtractor directly for rich extraction\n",
"ner = NERExtractor()\n",
"rel_extractor = RelationExtractor(confidence_threshold=0.55)\n",
"graph_builder = GraphBuilder(merge_entities=True)\n",
"\n",
"for doc in COMPETITIVE_INTEL:\n",
" text = doc['text']\n",
" entities = ner.extract_entities(text) or []\n",
" relations = rel_extractor.extract_relations(text) or []\n",
" print(f\"[{doc['source']}]\")\n",
" print(f\" Entities: {len(entities)}, Relations: {len(relations)}\")\n",
" # Store into shared context for all agents to access\n",
" shared._context.store(text, conversation_id=doc['source'])\n",
"\n",
"print(\"\\nCompetitive intelligence loaded into shared context\")"
]
},
{
"cell_type": "markdown",
"id": "tools-section",
"metadata": {},
"source": [
"## 5. Build Agent-Specific Tools\n",
"\n",
"Each toolkit is pointed at the **shared context** so tool calls across agents modify and read the same graph."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "build-tools",
"metadata": {},
"outputs": [],
"source": [
"# Researcher's KG toolkit — builds knowledge from raw text\n",
"researcher_kg_kit = AgnoKGToolkit(\n",
" ner_extractor=ner,\n",
" relation_extractor=rel_extractor,\n",
" reasoner=Reasoner(),\n",
" context=shared.knowledge_graph, # shared graph\n",
")\n",
"\n",
"# Analyst's decision kit — records evaluations and finds precedents\n",
"analyst_decision_kit = AgnoDecisionKit(\n",
" context=shared._context, # shared AgentContext\n",
" max_precedents=5,\n",
" causal_depth=3,\n",
" enable_policy_check=True,\n",
")\n",
"\n",
"# Strategist gets both\n",
"strategist_kg_kit = AgnoKGToolkit(\n",
" ner_extractor=ner,\n",
" relation_extractor=rel_extractor,\n",
" reasoner=Reasoner(),\n",
" context=shared.knowledge_graph,\n",
")\n",
"strategist_decision_kit = AgnoDecisionKit(\n",
" context=shared._context,\n",
" max_precedents=5,\n",
")\n",
"\n",
"print(f\"Researcher toolkit: {len(researcher_kg_kit._tools)} tools\")\n",
"print(f\"Analyst toolkit: {len(analyst_decision_kit._tools)} tools\")\n",
"print(f\"Strategist toolkits: {len(strategist_kg_kit._tools)} + {len(strategist_decision_kit._tools)} tools\")"
]
},
{
"cell_type": "markdown",
"id": "simulate-section",
"metadata": {},
"source": [
"## 6. Simulate Agent Collaboration\n",
"\n",
"We simulate the agents' reasoning steps directly, showing how shared context propagates knowledge between roles."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "researcher-turn",
"metadata": {},
"outputs": [],
"source": [
"print(\"=\" * 65)\n",
"print(\"RESEARCHER AGENT TURN\")\n",
"print(\"=\" * 65)\n",
"\n",
"# Researcher extracts entities from new competitive intel\n",
"new_intel = (\n",
" \"Competitor Beta just closed a strategic partnership with Microsoft Azure, \"\n",
" \"integrating their anomaly detection engine natively into Azure Synapse Analytics. \"\n",
" \"This gives Beta access to Microsoft's 300,000+ enterprise customer base. \"\n",
" \"Beta's CEO Sarah Chen announced the deal at Gartner Data & Analytics Summit.\"\n",
")\n",
"\n",
"# Step 1: Extract entities\n",
"entities_result = json.loads(researcher_kg_kit.extract_entities(new_intel))\n",
"print(f\"\\n[researcher] extracted {entities_result['count']} entities:\")\n",
"for e in entities_result['entities']:\n",
" print(f\" {e['name']:30s} type={e['type']}\")\n",
"\n",
"# Step 2: Extract relations\n",
"relations_result = json.loads(researcher_kg_kit.extract_relations(new_intel))\n",
"print(f\"\\n[researcher] extracted {relations_result['count']} relations\")\n",
"\n",
"# Step 3: Add to shared graph — now visible to ALL agents\n",
"add_result = json.loads(researcher_kg_kit.add_to_graph(\n",
" entities=json.dumps([\n",
" {\"name\": \"Competitor Beta\", \"type\": \"COMPANY\"},\n",
" {\"name\": \"Microsoft Azure\", \"type\": \"COMPANY\"},\n",
" {\"name\": \"Azure Synapse Analytics\", \"type\": \"PRODUCT\"},\n",
" {\"name\": \"Sarah Chen\", \"type\": \"PERSON\"},\n",
" {\"name\": \"Gartner Data & Analytics Summit\", \"type\": \"EVENT\"},\n",
" ]),\n",
" relations=json.dumps([\n",
" {\"source\": \"Competitor Beta\", \"relation\": \"PARTNERSHIP_WITH\", \"target\": \"Microsoft Azure\"},\n",
" {\"source\": \"Competitor Beta\", \"relation\": \"INTEGRATES_WITH\", \"target\": \"Azure Synapse Analytics\"},\n",
" {\"source\": \"Sarah Chen\", \"relation\": \"CEO_OF\", \"target\": \"Competitor Beta\"},\n",
" ]),\n",
"))\n",
"print(f\"\\n[researcher] added {add_result['nodes_added']} nodes, {add_result['edges_added']} edges to SHARED graph\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "analyst-turn",
"metadata": {},
"outputs": [],
"source": [
"print(\"=\" * 65)\n",
"print(\"ANALYST AGENT TURN (sees researcher's graph additions)\")\n",
"print(\"=\" * 65)\n",
"\n",
"# Analyst queries the graph the researcher just populated\n",
"competitor_query = json.loads(analyst_decision_kit.find_precedents(\n",
" scenario=\"competitor partnership with cloud hyperscaler threatens market position\",\n",
" limit=3,\n",
"))\n",
"print(f\"\\n[analyst] find_precedents → {competitor_query['count']} similar past strategic responses found\")\n",
"\n",
"# Analyst records a strategic evaluation decision\n",
"eval_json = analyst_decision_kit.record_decision(\n",
" category=\"strategic_response\",\n",
" scenario=(\n",
" \"Competitor Beta + Microsoft Azure partnership gives Beta access to \"\n",
" \"300k enterprise customers via Azure Synapse native integration\"\n",
" ),\n",
" reasoning=(\n",
" \"Threat level: HIGH. Beta's Azure native integration removes our \"\n",
" \"integration advantage. Existing NPS lead (41 vs 24) remains but \"\n",
" \"distribution disadvantage is critical. Recommend accelerated cloud-native \"\n",
" \"partnership evaluation, specifically AWS Marketplace + Snowflake Native App.\"\n",
" ),\n",
" outcome=\"escalate_to_strategy\",\n",
" confidence=0.85,\n",
" entities=\"Competitor Beta, Microsoft Azure, AWS Marketplace, Snowflake\",\n",
")\n",
"eval_result = json.loads(eval_json)\n",
"analyst_decision_id = eval_result['decision_id']\n",
"print(f\"\\n[analyst] recorded evaluation → decision_id: {analyst_decision_id}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "strategist-turn",
"metadata": {},
"outputs": [],
"source": [
"print(\"=\" * 65)\n",
"print(\"STRATEGIST AGENT TURN (sees both researcher + analyst work)\")\n",
"print(\"=\" * 65)\n",
"\n",
"# Strategist queries the graph for the full competitive picture\n",
"related = json.loads(strategist_kg_kit.find_related(\"Competitor Beta\", hops=2))\n",
"print(f\"\\n[strategist] 'Competitor Beta' 2-hop neighbourhood: {related['count']} entity/entities\")\n",
"for entity in related['related']:\n",
" print(f\" → {entity}\")\n",
"\n",
"# Strategist traces what the analyst decided\n",
"causal = json.loads(strategist_decision_kit.trace_causal_chain(analyst_decision_id, depth=3))\n",
"print(f\"\\n[strategist] causal chain for analyst decision: {causal}\")\n",
"\n",
"# Strategist records the final strategic recommendation\n",
"strategy_json = strategist_decision_kit.record_decision(\n",
" category=\"product_strategy\",\n",
" scenario=\"Q1 2026 product strategy: respond to Beta+Azure threat\",\n",
" reasoning=(\n",
" \"Based on researcher's KG (Beta+Azure integration, 300k customer reach) \"\n",
" \"and analyst's evaluation (threat level HIGH, escalated decision). \"\n",
" \"Strategy: (1) Accelerate AWS Marketplace listing by Q2 2026. \"\n",
" \"(2) Launch Snowflake Native App by Q3 2026. \"\n",
" \"(3) Invest $2M in UI modernisation to widen NPS lead. \"\n",
" \"(4) Fast-track LLM-native analytics interface (60% time-to-insight improvement per HAI study). \"\n",
" \"Existing NPS advantage (41 vs 24) provides 18-month window before Beta catches up.\"\n",
" ),\n",
" outcome=\"approved\",\n",
" confidence=0.88,\n",
" entities=\"AWS Marketplace, Snowflake, LLM Analytics, Q2 2026, Q3 2026\",\n",
")\n",
"strategy_result = json.loads(strategy_json)\n",
"print(f\"\\n[strategist] final recommendation recorded → {strategy_result['decision_id']}\")"
]
},
{
"cell_type": "markdown",
"id": "shared-pool-section",
"metadata": {},
"source": [
"## 7. Verify Shared Memory Pool\n",
"\n",
"Memories written by one agent are readable by all others."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "verify-shared",
"metadata": {},
"outputs": [],
"source": [
"from integrations.agno.context_store import _MemoryRow as MemoryRow\n",
"\n",
"# Researcher writes a memory\n",
"researcher_row = MemoryRow(\n",
" memory=\"Beta + Azure partnership announced at Gartner Summit — threat level HIGH\",\n",
" user_id=\"researcher\",\n",
")\n",
"researcher_store.upsert_memory(researcher_row)\n",
"\n",
"# Analyst writes a memory\n",
"analyst_row = MemoryRow(\n",
" memory=\"NPS advantage (41 vs 24) gives 18-month window — accelerate cloud partnerships\",\n",
" user_id=\"analyst\",\n",
")\n",
"analyst_store.upsert_memory(analyst_row)\n",
"\n",
"# Strategist reads ALL memories from both agents\n",
"strategist_memories = strategist_store.read_memories()\n",
"\n",
"print(f\"Strategist sees {len(strategist_memories)} shared memory item(s):\")\n",
"for m in strategist_memories:\n",
" uid = getattr(m, 'user_id', '?')\n",
" text = getattr(m, 'memory', str(m))\n",
" print(f\" [{uid:12s}] {text[:80]}\")"
]
},
{
"cell_type": "markdown",
"id": "agno-team-section",
"metadata": {},
"source": [
"## 8. Wire into Agno Team (requires API key)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "agno-team",
"metadata": {},
"outputs": [],
"source": [
"if AGNO_AVAILABLE:\n",
" from agno.agent import Agent\n",
" from agno.team import Team\n",
" from agno.memory import AgentMemory\n",
" from agno.models.openai import OpenAIChat\n",
"\n",
" researcher_agent = Agent(\n",
" name=\"Researcher\",\n",
" model=OpenAIChat(id=\"gpt-4o\"),\n",
" memory=AgentMemory(db=researcher_store),\n",
" tools=[researcher_kg_kit],\n",
" show_tool_calls=True,\n",
" description=(\n",
" \"You are a competitive intelligence researcher. \"\n",
" \"Use extract_entities, extract_relations, and add_to_graph \"\n",
" \"to build a structured knowledge graph from market intelligence. \"\n",
" \"Always add discoveries to the shared graph.\"\n",
" ),\n",
" )\n",
"\n",
" analyst_agent = Agent(\n",
" name=\"Analyst\",\n",
" model=OpenAIChat(id=\"gpt-4o\"),\n",
" memory=AgentMemory(db=analyst_store),\n",
" tools=[analyst_decision_kit],\n",
" show_tool_calls=True,\n",
" description=(\n",
" \"You are a strategic analyst. Use find_precedents to check historical \"\n",
" \"responses to similar threats, then record_decision with your evaluation. \"\n",
" \"Always check if a similar situation was handled before acting.\"\n",
" ),\n",
" )\n",
"\n",
" strategist_agent = Agent(\n",
" name=\"Strategist\",\n",
" model=OpenAIChat(id=\"gpt-4o\"),\n",
" memory=AgentMemory(db=strategist_store),\n",
" tools=[strategist_kg_kit, strategist_decision_kit],\n",
" show_tool_calls=True,\n",
" description=(\n",
" \"You are the Chief Strategy Officer. Synthesise the researcher's knowledge \"\n",
" \"graph and the analyst's decision record into a concrete product strategy. \"\n",
" \"Use find_related to explore the competitive graph, then record_decision \"\n",
" \"with the final approved strategy.\"\n",
" ),\n",
" )\n",
"\n",
" strategy_team = Team(\n",
" name=\"Product Strategy Team\",\n",
" agents=[researcher_agent, analyst_agent, strategist_agent],\n",
" mode=\"coordinate\",\n",
" )\n",
"\n",
" strategy_team.print_response(\n",
" \"Competitor Beta just announced a native Azure integration. \"\n",
" \"Analyse the competitive landscape and recommend our Q1 2026 product strategy.\"\n",
" )\n",
"else:\n",
" print(\"[Agno not installed — skipping live team run]\")\n",
" print()\n",
" print(\"Expected team coordination flow:\")\n",
" print(\" 1. Researcher: extract_entities + add_to_graph (Beta+Azure)\")\n",
" print(\" 2. Analyst: find_precedents + record_decision (threat=HIGH, escalate)\")\n",
" print(\" 3. Strategist: find_related + trace_causal_chain + record_decision (final strategy)\")"
]
},
{
"cell_type": "markdown",
"id": "post-session-section",
"metadata": {},
"source": [
"## 9. Post-Session Analysis with Semantica\n",
"\n",
"After the team session, use **native Semantica APIs** for cross-agent audit, analytics, and causal chain review."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cross-agent-insights",
"metadata": {},
"outputs": [],
"source": [
"# Team-level insights from AgnoSharedContext\n",
"insights = shared.get_shared_insights()\n",
"print(\"Team session insights:\")\n",
"if isinstance(insights, dict):\n",
" for k, v in insights.items():\n",
" print(f\" {k}: {v}\")\n",
"else:\n",
" print(f\" {insights}\")\n",
"\n",
"print(f\"\\nBound agent roles: {shared.bound_roles}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "precedent-search",
"metadata": {},
"outputs": [],
"source": [
"# Find all cross-agent strategic decisions\n",
"all_strategic = shared.find_precedents(\n",
" scenario=\"cloud partnership competitive response\",\n",
" category=\"strategic_response\",\n",
")\n",
"print(f\"Cross-agent strategic precedents: {len(all_strategic or [])}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "graph-analytics",
"metadata": {},
"outputs": [],
"source": [
"# Graph analytics on the shared knowledge graph (Semantica native)\n",
"try:\n",
" analyzer = GraphAnalyzer()\n",
" analysis = analyzer.analyze_graph(shared.knowledge_graph)\n",
" print(\"Shared knowledge graph analysis:\")\n",
" if isinstance(analysis, dict):\n",
" for k, v in list(analysis.items())[:6]:\n",
" print(f\" {k}: {v}\")\n",
" else:\n",
" print(f\" {analysis}\")\n",
"except Exception as e:\n",
" print(f\"GraphAnalyzer: {e}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "centrality-analysis",
"metadata": {},
"outputs": [],
"source": [
"# Which entities are most central in the competitive intelligence graph?\n",
"try:\n",
" centrality = CentralityCalculator()\n",
" scores = centrality.calculate_degree_centrality(shared.knowledge_graph)\n",
" print(\"Most central entities in shared graph:\")\n",
" if isinstance(scores, dict):\n",
" top = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:5]\n",
" for entity, score in top:\n",
" print(f\" {entity:35s} centrality={score:.4f}\")\n",
" else:\n",
" print(f\" {scores}\")\n",
"except Exception as e:\n",
" print(f\"CentralityCalculator: {e}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "causal-analysis",
"metadata": {},
"outputs": [],
"source": [
"# Direct Semantica causal chain analysis (no Agno needed)\n",
"try:\n",
" causal_analyzer = CausalChainAnalyzer(graph_store=shared.knowledge_graph)\n",
" # Query all decisions made during this session\n",
" decisions = shared.knowledge_graph.find_precedents(category=\"product_strategy\", limit=10)\n",
" print(f\"Product strategy decisions in shared graph: {len(decisions or [])}\")\n",
" for d in (decisions or [])[:3]:\n",
" scenario = d.get('scenario', '') if isinstance(d, dict) else str(d)\n",
" outcome = d.get('outcome', '') if isinstance(d, dict) else ''\n",
" print(f\" [{outcome:20s}] {scenario[:70]}\")\n",
"except Exception as e:\n",
" print(f\"CausalChainAnalyzer: {e}\")"
]
},
{
"cell_type": "markdown",
"id": "summary-section",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"| Pattern | Implementation |\n",
"|---|---|\n",
"| Single shared knowledge graph | `AgnoSharedContext(vector_store, knowledge_graph)` |\n",
"| Role-scoped memory | `shared.bind_agent(\"researcher\")` → `_AgentScopedStore` |\n",
"| Cross-agent memory visibility | All stores read from `shared._shared_memories` |\n",
"| KG tool sharing | `AgnoKGToolkit(context=shared.knowledge_graph)` |\n",
"| Decision tool sharing | `AgnoDecisionKit(context=shared._context)` |\n",
"| Thread-safe binding | `AgnoSharedContext._lock` (RLock) |\n",
"| Post-session analytics | `GraphAnalyzer`, `CentralityCalculator`, `CausalChainAnalyzer` — all Semantica native |\n",
"\n",
"**Key design rule:** Every agent writes to the **same underlying graph** via different role-scoped stores. The Agno integration is a thin routing layer — Semantica's full power is available at any point directly."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+7
View File
@@ -178,6 +178,13 @@
"rdf_exporter.export(kg, \"output.ttl\", format=\"turtle\")"
]
},
{
"cell_type": "code",
"source": "# TTL alias: format=\"ttl\" is equivalent to format=\"turtle\"\nrdf_data = {\n \"entities\": [\n {\"id\": \"e1\", \"text\": \"Apple Inc.\", \"type\": \"ORG\", \"confidence\": 0.95},\n {\"id\": \"e2\", \"text\": \"Steve Jobs\", \"type\": \"PERSON\", \"confidence\": 0.97},\n ],\n \"relationships\": [\n {\"source_id\": \"e2\", \"target_id\": \"e1\", \"type\": \"founded_by\", \"confidence\": 0.91},\n ],\n}\n\nrdf_exporter.export(rdf_data, \"output.ttl\", format=\"ttl\")\n\nresult = rdf_exporter.validate_rdf(rdf_data)\nprint(f\"Valid: {result['overall_valid']}\")",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,86 @@
@prefix mcg: <https://example.org/mcg#> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<https://example.org/mcg/instance-data> a owl:Ontology ;
rdfs:label "Military Capability Gap Analysis Instance Data" ;
owl:imports <https://example.org/mcg> .
# Scenario and threat
mcg:Scenario_FutureA2AD_2028 a mcg:Scenario ;
rdfs:label "Future A2/AD Escalation 2028" ;
mcg:hasThreat mcg:Threat_LowAltitudeSwarm .
mcg:Threat_LowAltitudeSwarm a mcg:Threat ;
rdfs:label "Low-Altitude Swarm Threat" ;
mcg:relatedToIntelligenceReport mcg:IntelReport_RAND_RRA733_1 .
# Mission thread and events
mcg:MissionThread_ForceProtection a mcg:MissionThread ;
rdfs:label "Force Protection under Swarm Pressure" ;
mcg:missionPriority "high" ;
mcg:includesEvent mcg:Event_SwarmIncursion_001 ;
mcg:requiresCapability mcg:Capability_LowAltitudeDetection ;
mcg:revealsGap mcg:Gap_LowAltitudeDetectionCoverage .
mcg:Scenario_FutureA2AD_2028 mcg:hasMissionThread mcg:MissionThread_ForceProtection .
mcg:Event_SwarmIncursion_001 a mcg:OperationalEvent ;
rdfs:label "Swarm Incursion Event 001" ;
mcg:eventTime "2028-04-12T05:15:00Z"^^xsd:dateTime ;
mcg:stressesSystem mcg:System_GroundRadarLayer ;
mcg:relatedToWargameObservation mcg:WargameObs_ValleyIngress .
# Systems and capabilities
mcg:System_GroundRadarLayer a mcg:System ;
rdfs:label "Ground Radar Layer" ;
mcg:coveragePercent "42.0"^^xsd:decimal ;
mcg:relatedToAssetRecord mcg:AssetRecord_RadarFleet_2028Q1 .
mcg:Capability_LowAltitudeDetection a mcg:Capability ;
rdfs:label "Low Altitude Detection Capability" ;
mcg:requiredCoveragePercent "75.0"^^xsd:decimal ;
mcg:providedBy mcg:System_GroundRadarLayer .
# Gap and outcome
mcg:Gap_LowAltitudeDetectionCoverage a mcg:CapabilityGap ;
rdfs:label "Insufficient Low-Altitude Detection Coverage" ;
mcg:gapInCapability mcg:Capability_LowAltitudeDetection ;
mcg:gapSeverity "critical" ;
mcg:increasesRiskOf mcg:Outcome_MissionRiskIncrease ;
mcg:triggersDecision mcg:Decision_CapGap_001 .
mcg:Outcome_MissionRiskIncrease a mcg:Outcome ;
rdfs:label "Increased Mission Risk and Response Delay" .
# Decision and recommendation
mcg:Decision_CapGap_001 a mcg:Decision ;
rdfs:label "Capability Gap Decision 001" ;
mcg:confidenceScore "0.93"^^xsd:decimal ;
mcg:hasRecommendation mcg:Recommendation_MultiLayerSensorFusion ;
mcg:supportedByEvidence mcg:Evidence_E001 ;
mcg:wasAssessedBy mcg:AnalystCell_A1 .
mcg:Recommendation_MultiLayerSensorFusion a mcg:Recommendation ;
mcg:recommendationText "Integrate layered sensing (ground radar, passive RF, EO/IR) and update mission doctrine for low-altitude swarm defense." .
# Evidence and provenance
mcg:Evidence_E001 a mcg:Evidence ;
mcg:evidenceQuote "Operational analysis indicates persistent low-altitude sensing shortfalls in contested terrain." ;
mcg:derivedFromDocument mcg:IntelReport_RAND_RRA733_1 .
mcg:IntelReport_RAND_RRA733_1 a mcg:IntelligenceReport, prov:Entity ;
rdfs:label "RAND RRA733-1 Competing Without Fighting (2022)" .
mcg:WargameObs_ValleyIngress a mcg:WargameObservation, prov:Entity ;
rdfs:label "Wargame Observation: Valley Ingress Routes" .
mcg:AssetRecord_RadarFleet_2028Q1 a mcg:AssetInventoryRecord, prov:Entity ;
rdfs:label "Asset Inventory: Radar Fleet 2028 Q1" .
mcg:AnalystCell_A1 a prov:Agent ;
rdfs:label "Joint Capability Assessment Cell A1" .
@@ -0,0 +1,143 @@
@prefix mcg: <https://example.org/mcg#> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix d3f: <http://d3fend.mitre.org/ontologies/d3fend.owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<https://example.org/mcg> a owl:Ontology ;
rdfs:label "Military Capability Gap Analysis Ontology" ;
rdfs:comment "Ontology for end-to-end military capability gap analysis with context graphs, multi-hop reasoning, and provenance." ;
owl:imports <http://www.w3.org/ns/prov> .
# Classes
mcg:Scenario a owl:Class .
mcg:MissionThread a owl:Class .
mcg:OperationalEvent a owl:Class .
mcg:System a owl:Class .
mcg:Capability a owl:Class .
mcg:CapabilityGap a owl:Class .
mcg:Outcome a owl:Class .
mcg:Decision a owl:Class .
mcg:Recommendation a owl:Class .
mcg:Evidence a owl:Class .
mcg:Threat a owl:Class .
mcg:DoctrineDocument a owl:Class ;
rdfs:subClassOf prov:Entity .
mcg:WargameObservation a owl:Class ;
rdfs:subClassOf prov:Entity .
mcg:AssetInventoryRecord a owl:Class ;
rdfs:subClassOf prov:Entity .
mcg:IntelligenceReport a owl:Class ;
rdfs:subClassOf prov:Entity .
# Optional alignment points
mcg:Sensor a owl:Class ;
rdfs:subClassOf mcg:System, d3f:D3FEND .
# Object properties (context chain)
mcg:hasMissionThread a owl:ObjectProperty ;
rdfs:domain mcg:Scenario ;
rdfs:range mcg:MissionThread .
mcg:includesEvent a owl:ObjectProperty ;
rdfs:domain mcg:MissionThread ;
rdfs:range mcg:OperationalEvent .
mcg:stressesSystem a owl:ObjectProperty ;
rdfs:domain mcg:OperationalEvent ;
rdfs:range mcg:System .
mcg:requiresCapability a owl:ObjectProperty ;
rdfs:domain mcg:MissionThread ;
rdfs:range mcg:Capability .
mcg:providedBy a owl:ObjectProperty ;
rdfs:domain mcg:Capability ;
rdfs:range mcg:System .
mcg:revealsGap a owl:ObjectProperty ;
rdfs:domain mcg:MissionThread ;
rdfs:range mcg:CapabilityGap .
mcg:gapInCapability a owl:ObjectProperty ;
rdfs:domain mcg:CapabilityGap ;
rdfs:range mcg:Capability .
mcg:increasesRiskOf a owl:ObjectProperty ;
rdfs:domain mcg:CapabilityGap ;
rdfs:range mcg:Outcome .
mcg:triggersDecision a owl:ObjectProperty ;
rdfs:domain mcg:CapabilityGap ;
rdfs:range mcg:Decision .
mcg:hasRecommendation a owl:ObjectProperty ;
rdfs:domain mcg:Decision ;
rdfs:range mcg:Recommendation .
mcg:supportedByEvidence a owl:ObjectProperty ;
rdfs:domain mcg:Decision ;
rdfs:range mcg:Evidence .
mcg:hasThreat a owl:ObjectProperty ;
rdfs:domain mcg:Scenario ;
rdfs:range mcg:Threat .
mcg:relatedToAssetRecord a owl:ObjectProperty ;
rdfs:domain mcg:System ;
rdfs:range mcg:AssetInventoryRecord .
mcg:relatedToWargameObservation a owl:ObjectProperty ;
rdfs:domain mcg:OperationalEvent ;
rdfs:range mcg:WargameObservation .
mcg:relatedToIntelligenceReport a owl:ObjectProperty ;
rdfs:domain mcg:Threat ;
rdfs:range mcg:IntelligenceReport .
# Provenance properties
mcg:derivedFromDocument a owl:ObjectProperty ;
rdfs:subPropertyOf prov:wasDerivedFrom ;
rdfs:domain mcg:Evidence ;
rdfs:range prov:Entity .
mcg:wasAssessedBy a owl:ObjectProperty ;
rdfs:subPropertyOf prov:wasAssociatedWith ;
rdfs:domain mcg:Decision ;
rdfs:range prov:Agent .
# Data properties
mcg:coveragePercent a owl:DatatypeProperty ;
rdfs:domain mcg:System ;
rdfs:range xsd:decimal .
mcg:requiredCoveragePercent a owl:DatatypeProperty ;
rdfs:domain mcg:Capability ;
rdfs:range xsd:decimal .
mcg:gapSeverity a owl:DatatypeProperty ;
rdfs:domain mcg:CapabilityGap ;
rdfs:range xsd:string .
mcg:confidenceScore a owl:DatatypeProperty ;
rdfs:domain mcg:Decision ;
rdfs:range xsd:decimal .
mcg:missionPriority a owl:DatatypeProperty ;
rdfs:domain mcg:MissionThread ;
rdfs:range xsd:string .
mcg:eventTime a owl:DatatypeProperty ;
rdfs:domain mcg:OperationalEvent ;
rdfs:range xsd:dateTime .
mcg:recommendationText a owl:DatatypeProperty ;
rdfs:domain mcg:Recommendation ;
rdfs:range xsd:string .
mcg:evidenceQuote a owl:DatatypeProperty ;
rdfs:domain mcg:Evidence ;
rdfs:range xsd:string .
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
<html><head><title>Request Rejected </title></head><body>Sorry, the requested URL was rejected. Please consult with your administrator..<br><br>Your support ID is: <9627954236696643144><br><br><a href='javascript:history.back();'>[Go Back]</body></html>
+2 -2
View File
@@ -1018,7 +1018,7 @@ knowledge_graph.apply_resolutions(resolved_data)
### 💬 Community Support
- **💬 [Discord Community](https://discord.gg/N7WmAuDH)** - Real-time chat and support
- **💬 [Discord Community](https://discord.gg/sV34vps5hH)** - Real-time chat and support
- **🐙 [GitHub Discussions](https://github.com/semantica/semantica/discussions)** - Community Q&A
- **📧 [Mailing List](https://groups.google.com/g/semantica)** - Announcements and updates
- **🐦 [Twitter](https://twitter.com/semantica)** - Latest news and tips
@@ -1051,6 +1051,6 @@ This project is licensed under the MIT License - see the [LICENSE](https://githu
**🚀 Ready to transform your data into intelligent knowledge?**
[Get Started Now](https://semantica.readthedocs.io/quickstart/) • [View Examples](https://github.com/semantica/examples) • [Join Community](https://discord.gg/N7WmAuDH)
[Get Started Now](https://semantica.readthedocs.io/quickstart/) • [View Examples](https://github.com/semantica/examples) • [Join Community](https://discord.gg/sV34vps5hH)
</div>
+152
View File
@@ -0,0 +1,152 @@
## Semantica Deduplication V2: Migration & Performance Guide
Welcome to the Deduplication V2 engine!! This release specifically targets severe CI delays and production bottlenecks caused by massive knowledge graph deduplication workloads. By introducing smarter candidate generation, fast-fail prefilters, and semantic triplet canonicalization, we have reduced worst-case execution times by up to **80%**.
**Note:** This upgrade is **100% backward compatible.** All existing scripts, tests, and API signatures will continue to work exactly as they did before.
To utilize this new addition, you must explicitly **opt-in** using the new configuration keys detailed below.
---
### 1. Candidate Generation V2 (Beating the $O(N^2)$ Pair Explosion)
**The Problem:** The legacy engine relied on a naive first-character blocking strategy. If your dataset contained 5,000 companies starting with letter "A", the engine generated nearly 12.5 million candidate pairs.
**The V2 Solution:** Multi-key token blocking, prefix matching, and deterministic candidate budgeting.
**How to Opt-In**
Pass the keys into the `similarity`configuration dictionary when initializing the `DuplicateDetector`:
```python
from semantica.deduplication import DuplicateDetector
detector = DuplicateDetector(
similarity_threshold=0.8,
similarity = {
# Switches from legacy to v2
"candidate_strategy": "blocking_v2",
# Highly recommended: Limits the max number of comparisons
# per entity to prevent adversarial latency spikes.
"max_candidates_per_entity": 50,
# Optional: Generates blocks using Soundex algorithm to catch
# phonetic misspellings (e.g, "Jon" vs "John")
"enable_phonetic_blocking": True
}
)
```
### 2. Two-Stage scoring (The Fast Prefilter)
**The Problem**: Calculating multi-factor semantic scores (Levenshtein, Jaro-Winkler, property intersections, and Embeddings) is computationally expensive. Running these
calculations on two entities that share absolutely zero words or have vastly different string lengths is a waste of resources.
**The V2 Solution:** A lightning-fast prefilter gate that instantly drops obvious non-matches before they ever reach the heavy semantic scorers.
**How to Opt-In**
Enable the prefilter and define your rejection thresholds:
```python
from semantica.deduplication import DuplicateDetector
detector = DuplicateDetector(
similarity_threshold=0.8,
similarity={
"candidate_strategy": "blocking_v2",
# Enable prefilter
"prefilter_enabled": True,
"prefilter_thresholds": {
# Rejects pairs if shortest string is less than 40% the length
# of the longest
"min_length_ratio": 0.4,
# Instantly rejects pairs if they don't share at least one
# valid word token
"required_shared_token": True
},
# Optional Explainability: Injects a 'score_breakdown' dict into
# the candidate metadata so you can see exactly how the string,
# property, and relationships scores contributed.
"score_breakdown_enabled": True
}
)
```
### 3. Semantic Relationship & Triplet Deduplication
**The problem:** The legacy relationship deduplication relied on exact `(Subject, Predicate, Object)` string matches. It couldn't recognize that `(Person, "works_for", Company)` is semantically identical to `(Person, "employed_by", Company)` .
**The V2 Solution:** A new `semantic_v2` mode that introduces predicate synonym mapping, literal normalization (cleaning up rogue spaces/casing), and a highly optimized $O(1)$ canonical hash path for fast matching.
**How to Opt-In**
When calling relationship-specific dedup methods, pass the new configuration keys:
```python
from semantica.deduplication import DuplicateDetector
from semantica.deduplication.methods import dedup_triplets
# Approach A: Using the Detector explicitly
detector = DuplicateDetector()
duplicates = detector.detect_relationship_duplicates(
relationship_list,
relationship_dedup_mode="semantic_v2",
# Cleans up messy object strings
# (e.g., " Apple Inc. " -> "apple inc.")
literal_normalization_enabled=True,
# Maps various synonyms to a single canonical predicate
# before hashing
predicate_synonym_map={
"works_for": "employed_by",
"is_employee_of": "employed_by",
"has_employer": "employed_by"
}
)
# Approach B: Using the new simplified wrapper in methods.py
duplicates = dedup_triplets(
relationships_list,
mode="semantic_v2",
literal_normalization_enabled=True,
predicate_synonym_map={"works_for": "employed_by"}
)
```
###### Note on Merge Strategies
When using `semantic_v2` for relationships, the `MergeStrategyManager` will now automatically respect your canonicalized keys. If two entities share a relationship that differs only by a mapped synonym, the engine will correctly identify them as the same relationship and prevent duplicate graph edges during the merge phase.
### Need Help?
If you experience any unexpected behavior when switching from `legacy` to `blocking_v2` or `semantic_v2`, please check the explainability metadata (by setting `"score_breakdown_enabled": True`) to audit the exact scoring process, or open an issue on GitHub.
+68 -122
View File
@@ -1,180 +1,126 @@
# Architecture
Semantica's modular, extensible framework for semantic intelligence and knowledge engineering.
Semantica is built around a three-layer, modular architecture designed for independent use of components, clean separation of concerns, and extensibility at each layer.
---
## Design Principles
- **Modular**: Independent, reusable components
- **Extensible**: Easy to add new functionality
- **Scalable**: Handle large-scale data processing
- **Maintainable**: Clear separation of concerns
---
## System Architecture
## System Overview
```mermaid
graph TB
A[Data Ingestion Layer] --> B[Semantic Processing Layer]
B --> C[Application Layer]
A1[Files Web APIs Streams] --> A
B1[Parse Normalize Extract Build] --> B
C1[GraphRAG AI Agents Analytics] --> C
A1[Files · Web · APIs · Streams] --> A
B1[Parse · Normalize · Extract · Build] --> B
C1[GraphRAG · AI Agents · Analytics] --> C
```
### Three-Layer Architecture
**1. Data Ingestion Layer**
- Multiple file formats (PDF, DOCX, JSON, CSV, etc.)
- Web scraping and APIs
- Real-time streams (Kafka, RabbitMQ)
- Database connectors (SQL, NoSQL)
**2. Semantic Processing Layer**
- Document parsing and normalization
- Entity and relationship extraction
- Embedding generation
- Knowledge graph construction
- Quality assurance and deduplication
**3. Application Layer**
- GraphRAG for enhanced retrieval
- AI agent memory and context
- Multi-agent systems
- Analytics and visualization
---
## Core Modules
## Three-Layer Architecture
### Orchestration
- **`semantica.core`** - Main framework class and coordination
- **`semantica.pipeline`** - Pipeline management and execution
### 1. Data Ingestion Layer
### Data Processing
- **`semantica.ingest`** - Universal data ingestion
- **`semantica.parse`** - Document parsing
- **`semantica.normalize`** - Data cleaning and normalization
Responsible for loading data from any source into the pipeline.
### Semantic Intelligence
- **`semantica.semantic_extract`** - Entity and relationship extraction
- **`semantica.embeddings`** - Vector embedding generation
- **`semantica.ontology`** - Ontology generation and management
- **File formats** — PDF, DOCX, HTML, JSON, CSV, Excel, PPTX, archives
- **Web** — crawl via `WebIngestor` with configurable depth
- **Databases** — SQL, NoSQL, Snowflake via `DBIngestor` / `SnowflakeIngestor`
- **Streams** — Kafka, real-time feeds
### Knowledge Graphs
- **`semantica.kg`** - Knowledge graph construction
- **`semantica.vector_store`** - Vector storage (Weaviate, FAISS)
- **`semantica.triplet_store`** - RDF triplet storage (Jena, Blazegraph)
- **`semantica.graph_store`** - Property graphs (Neo4j, FalkorDB)
### 2. Semantic Processing Layer
### Quality Assurance
- **`semantica.deduplication`** - Entity deduplication
- **`semantica.conflicts`** - Conflict detection and resolution
The core intelligence engine — transforms raw data into structured knowledge.
- Document parsing and normalization
- Entity and relationship extraction (NER, LLM-typed, rule-based)
- Embedding generation
- Knowledge graph construction with entity merging
- Deduplication, conflict detection, and validation
### 3. Application Layer
Consumes the knowledge graph for downstream use cases.
- GraphRAG — graph-grounded retrieval for LLMs
- AI agent context and decision tracking
- Multi-agent pipelines
- Analytics, visualization, and export
---
## Data Flow
```
1. IngestionRaw data from sources
2. ParsingStructured content extraction
3. Normalization → Cleaned data
4. Semantic ExtractionEntities, relationships, events
5. Graph ConstructionEntity resolution, conflict resolution
6. Quality AssuranceDeduplication, validation
7. Storage → Vector, triplet, and graph stores
8. Application → GraphRAG, agents, analytics
Ingest raw data from sources
Parse structured text extraction
Normalize → canonical forms, date/name standardization
Extract entities, relationships, events
Build entity resolution, graph construction
QA deduplication, conflict resolution, validation
Store → vector store, graph store, triplet store
Deliver GraphRAG, agents, export, visualization
```
---
## Module Map
| Layer | Modules |
|-------|---------|
| **Ingestion** | `ingest`, `parse`, `split`, `normalize` |
| **Semantic** | `semantic_extract`, `kg`, `ontology`, `reasoning` |
| **Storage** | `embeddings`, `vector_store`, `graph_store`, `triplet_store` |
| **Quality** | `deduplication`, `conflicts` |
| **Context** | `context`, `provenance`, `change_management` |
| **Output** | `export`, `visualization`, `pipeline` |
For full module documentation, see the [Modules Guide](modules.md).
---
## Extension Points
### Custom Ingestors
### Custom Ingestor
```python
from semantica.ingest import BaseIngestor
class CustomIngestor(BaseIngestor):
def ingest(self, source):
# Custom ingestion logic
pass
# Return a list of document dicts
...
```
### Custom Extractors
### Custom Extractor
```python
from semantica.semantic_extract import BaseExtractor
class CustomExtractor(BaseExtractor):
def extract(self, text):
# Custom extraction logic
pass
# Return a list of entity dicts
...
```
### Custom Validators
Validators can be implemented within domain-specific modules (e.g., graph or ontology) as needed.
---
## Design Decisions
### Modularity
Independent components that can be used standalone or together. Easy to test, maintain, and extend.
**Modularity** — every component can be used standalone. Import only what you need; the framework never forces a full stack.
### Plugin System
Extensible architecture allowing custom functionality without modifying core code.
**Pluggability** — extend any layer without modifying core code. Custom ingestors, extractors, validators, and exporters all follow the same base class pattern.
### Configuration Management
Centralized configuration with environment variable support for different deployment environments.
**Configuration over convention** — centralized config with environment variable overrides for deployment flexibility.
### Error Handling
Comprehensive error handling with graceful degradation and recovery mechanisms.
**Provenance by default** — lineage tracking is built into graph construction, not bolted on. Every node traces back to a source document.
---
## Performance
## Performance Characteristics
**Scalability**
- Parallel processing support
- Streaming for large datasets
- Efficient memory usage
- Intelligent caching
**Optimization**
- Lazy loading
- Batch processing
- Connection pooling
- Query optimization
---
## Security
**Data Security**
- Secure credential handling
- Input validation and output sanitization
- Audit logging
**Access Control**
- Authentication and authorization
- API key management
- Role-based access control
---
## Future Roadmap
- Distributed processing
- Real-time streaming improvements
- Advanced reasoning capabilities
- Multi-modal expansion
- Enhanced visualization
---
For detailed module documentation, see [Modules Guide](modules.md)
- **Parallel execution** — `PipelineBuilder` supports configurable worker counts per stage
- **Delta processing** — incremental graph updates without full recompute
- **Streaming ingestion** — process large corpora without loading everything into memory
- **Backend flexibility** — swap in-memory NetworkX for Neo4j/FalkorDB at scale with no API changes
+75 -275
View File
@@ -1,372 +1,172 @@
# Core Concepts
# Core Concepts
**Learn the fundamental concepts behind Semantica in simple, practical terms.**
The fundamental ideas behind Semantica — explained plainly.
!!! tip "Quick Start"
New to Semantica? Start with [Getting Started](getting-started.md) for hands-on examples.
!!! tip "New here?"
Start with [Getting Started](getting-started.md) for hands-on examples, then come back to this page for deeper understanding.
---
## What is Semantica?
Semantica transforms unstructured data (documents, web pages, reports) into **knowledge graphs** - structured databases that AI systems can understand and reason about.
Semantica transforms unstructured data (documents, web pages, reports, databases) into **knowledge graphs** structured representations that AI systems can query, reason about, and trace back to sources.
**What it does:**
- **Reads** documents, PDFs, web pages, databases
- **Extracts** entities (people, companies, dates) and relationships
- **Builds** connected knowledge graphs
- **Enables** AI to reason with structured knowledge
---
## Core Architecture
Semantica uses a **layered architecture** - use only what you need:
<div class="grid cards" markdown>
- **Input Layer**
---
Data ingestion and preparation
**Modules**: Ingest, Parse, Split, Normalize
- **Semantic Layer**
---
Intelligence and understanding
**Modules**: Semantic Extract, Knowledge Graph, Ontology, Reasoning
- **Storage Layer**
---
Persistent data storage
**Modules**: Embeddings, Vector Store, Graph Store
- **Quality Layer**
---
Data quality and consistency
**Modules**: Deduplication, Conflicts
- **Context & Memory**
---
Agent memory and foundation data
**Modules**: Context, Seed, LLM Providers
- **Output & Orchestration**
---
Export, visualization, and workflows
**Modules**: Export, Visualization, Pipeline
</div>
At its core, Semantica adds a **context and intelligence layer** on top of your existing AI stack: it doesn't replace LangChain, LlamaIndex, or your LLM provider — it makes their outputs accountable.
---
## Knowledge Graphs
The foundation of Semantica - turning data into structured knowledge.
The foundation of everything in Semantica.
### What is a Knowledge Graph?
A knowledge graph stores information as:
A knowledge graph represents real-world information as:
- **Nodes** (entities): People, companies, locations, dates
- **Edges** (relationships): works_for, located_in, founded_by
- **Properties**: Name, date, confidence score, source
- **Nodes (entities)** — people, companies, locations, events, concepts
- **Edges (relationships)** — `works_for`, `located_in`, `founded_by`
- **Properties** — name, date, confidence score, source URL
### Why Knowledge Graphs?
- **Searchable**: Find information instantly
- **Connectable**: Discover hidden relationships
- **Queryable**: Ask complex questions
- **Explainable**: Trace answers back to sources
This structure makes knowledge **searchable**, **connectable**, **queryable**, and — critically — **explainable**: every answer can be traced back to the facts and relationships that produced it.
---
## Entity Extraction (NER)
Finding and classifying entities in text.
Scanning text to find and classify real-world entities.
### What it does:
- Scans text for people, organizations, locations, dates
- Classifies each entity by type
- Assigns confidence scores
- Tracks source provenance
### Example Output:
```python
# From: "Apple Inc. was founded by Steve Jobs in 1976 in Cupertino."
# Input: "Apple Inc. was founded by Steve Jobs in 1976 in Cupertino."
{
"entities": [
{"text": "Apple Inc.", "type": "ORGANIZATION", "confidence": 0.98},
{"text": "Steve Jobs", "type": "PERSON", "confidence": 0.99},
{"text": "1976", "type": "DATE", "confidence": 0.95},
{"text": "Cupertino", "type": "LOCATION", "confidence": 0.97}
{"text": "Apple Inc.", "type": "ORGANIZATION", "confidence": 0.98},
{"text": "Steve Jobs", "type": "PERSON", "confidence": 0.99},
{"text": "1976", "type": "DATE", "confidence": 0.95},
{"text": "Cupertino", "type": "LOCATION", "confidence": 0.97}
]
}
```
Each entity gets a type, confidence score, and a link to its source document.
---
## Relationship Extraction
Finding connections between entities.
Finding how entities connect to each other.
### What it does:
- Identifies how entities relate to each other
- Extracts relationship types and directions
- Provides context and confidence
- Links to source documents
### Example Output:
```python
{
"relationships": [
{"subject": "Steve Jobs", "predicate": "founded", "object": "Apple Inc.", "confidence": 0.92},
{"subject": "Apple Inc.", "predicate": "located_in", "object": "Cupertino", "confidence": 0.89}
{"subject": "Steve Jobs", "predicate": "founded", "object": "Apple Inc.", "confidence": 0.92},
{"subject": "Apple Inc.", "predicate": "located_in", "object": "Cupertino", "confidence": 0.89}
]
}
```
Relationships can be extracted via rule-based methods, ML models, or LLMs (with `"llm_typed"` metadata).
---
## Embeddings
Turning text into numerical vectors for AI understanding.
Embeddings convert text into numerical vectors so that AI systems can measure semantic similarity — finding related concepts even when the exact words differ.
### What are embeddings?
- **Numerical representations** of text, entities, and relationships
- **Similarity calculations** - find related concepts
- **AI-powered search** - semantic understanding
- **Clustering and grouping** - discover patterns
Semantica uses embeddings for:
### Use Cases:
- **Semantic Search** - find documents by meaning, not keywords
- **Entity Resolution** - match similar entities across sources
- **Recommendations** - suggest related content
- **AI Input** - provide structured context to LLMs
---
## Temporal Graphs
Knowledge graphs that understand time.
### What they track:
- **When** events happened
- **How** entities changed over time
- **Temporal relationships** - before, after, during
- **Historical context** - point-in-time snapshots
### Example Uses:
- **Company History** - track mergers, leadership changes
- **Person Careers** - job changes, relocations
- **Policy Evolution** - law changes over time
- **Research Progress** - scientific discoveries timeline
- **Semantic search** — retrieve by meaning, not just keywords
- **Entity resolution** — match the same entity across different sources
- **Precedent search** — find similar past decisions
- **GraphRAG retrieval** — hybrid vector + graph traversal
---
## GraphRAG
Enhanced AI retrieval using knowledge graphs.
GraphRAG (Graph-Augmented Retrieval Augmented Generation) enhances LLM responses by grounding them in a structured knowledge graph rather than raw text chunks alone.
### How it works:
1. **Query** user question
2. **Retrieve** relevant graph context
3. **Enhance** with relationships and entities
4. **Generate** AI response with sources
How it works:
### Benefits:
- **More accurate** answers
- **Source attribution** - trace answers back
- **Context awareness** - understand relationships
- **Reduced hallucination** - grounded in facts
1. User submits a query
2. Semantica retrieves relevant graph context (entities, relationships, reasoning paths)
3. The LLM generates a response grounded in that context
4. Every claim in the response links back to a source node in the graph
This eliminates the hallucination and traceability problems of standard RAG.
---
## Ontology
Defining the structure and rules of your knowledge.
An ontology defines the schema and rules for your knowledge — what entity types exist, which relationships are valid, and what constraints apply.
### What it provides:
- **Schema definition** - what types exist
- **Relationship rules** - valid connections
- **Property constraints** - required fields
- **Inheritance hierarchies** - parent-child relationships
### Example:
```python
# Define ontology structure
ontology = {
"classes": ["Person", "Organization", "Location"],
"properties": ["name", "date", "confidence"],
"relationships": ["works_for", "located_in", "born_in"],
"relationships": ["works_for", "located_in", "founded_by"],
"rules": {
"Person": ["must_have_name", "can_have_birth_date"],
"Person": ["must_have_name"],
"Organization": ["must_have_name", "can_have_founding_date"]
}
}
```
Semantica can auto-generate ontologies from your knowledge graph, or import existing OWL/RDF/Turtle ontologies.
---
## Reasoning & Inference
Making logical deductions from your knowledge.
Semantica includes multiple reasoning engines to derive new knowledge from existing facts.
### What it can do:
- **Infer missing facts** - derive new knowledge
- **Detect inconsistencies** - find contradictions
- **Apply rules** - automate decision making
- **Explain reasoning** - show how conclusions were reached
```
Known: Steve Jobs founded Apple Inc.
Known: Apple Inc. is headquartered in Cupertino
Inferred: Steve Jobs has a connection to Cupertino
```
### Example:
```
Known: Steve Jobs founded Apple Inc.
Known: Apple Inc. is headquartered in Cupertino
Inferred: Steve Jobs has connection to Cupertino
```
Supported engines: forward chaining, Rete network, deductive, abductive, and SPARQL reasoning — all producing **explainable inference paths**, not black-box conclusions.
---
## Temporal Graphs
Knowledge changes over time. Temporal graphs attach `valid_from` / `valid_until` windows to nodes and edges, enabling point-in-time queries and historical analysis.
Common uses: tracking company leadership changes, policy evolution, research timelines, financial instrument histories.
---
## Deduplication & Entity Resolution
Finding and merging duplicate entities.
Real-world data contains the same entity under many names — "Apple", "Apple Inc.", "Apple Computer Inc." Semantica's deduplication pipeline detects these, merges attributes, resolves conflicts, and preserves the original source provenance.
### What it does:
- **Detects duplicates** - same entity, different names
- **Merges information** - combine attributes
- **Resolves conflicts** - handle contradictory data
- **Maintains provenance** - track original sources
### Example:
```python
# These refer to the same entity:
"Apple Inc." â "Apple" â "Apple Computer Inc."
# Merge into single entity with all attributes
```
Strategies: Jaro-Winkler similarity (v1), `blocking_v2`, `hybrid_v2`, `semantic_v2` (v2 — up to 7x faster).
---
## Data Normalization
## Provenance & Auditability
Cleaning and standardizing your data.
Every fact in Semantica links back to:
### What it fixes:
- **Format inconsistencies** - dates, names, numbers
- **Canonical forms** - standard representations
- **Data quality** - remove errors and noise
- **Standardization** - consistent naming conventions
- The source document it came from
- The extraction method used
- The ontology rules applied
- The reasoning steps that produced any inference
### Examples:
- **Dates**: "Jan 1, 2020" → "2020-01-01"
- **Names**: "Dr. Smith PhD" → "John Smith"
- **Companies**: "Apple" → "Apple Inc."
- **Locations**: "NYC" → "New York City"
This is W3C PROV-O compliant lineage — suitable for regulated industries that require audit trails.
---
## Conflict Detection
Finding and resolving contradictory information.
When multiple sources disagree on the same fact, Semantica flags and resolves the conflict rather than silently picking one value.
### What it identifies:
- **Factual conflicts** - different values for same fact
- **Temporal conflicts** - impossible timelines
- **Logical conflicts** - contradictory relationships
- **Source reliability** - trustworthiness assessment
### Resolution Strategies:
- **Most recent** - prefer newer information
- **Most reliable** - prefer trusted sources
- **Majority vote** - go with consensus
- **Manual review** - flag for human review
Resolution strategies: prefer most recent, prefer most reliable source, majority vote, or flag for manual review.
---
## Getting Started
## Next Steps
Ready to build your first knowledge graph?
### Quick Start (5 minutes)
```python
from semantica.semantic_extract import NERExtractor
from semantica.kg import GraphBuilder
# Extract entities
ner = NERExtractor()
entities = ner.extract("Apple Inc. was founded by Steve Jobs in 1976.")
# Build graph
kg = GraphBuilder().build({"entities": entities, "relationships": []})
```
### Learn More
- **Getting Started Guide** - [Getting Started](getting-started.md)
- **Cookbook Examples** - [Cookbook](cookbook.md)
- **Module Documentation** - [Reference](reference/)
- **Community Support** - [Community](community.md)
### Common Use Cases
- **Document Analysis** - extract knowledge from reports
- **Research Assistant** - find connections in academic papers
- **Business Intelligence** - analyze company relationships
- **Regulatory Compliance** - track policy changes
---
## Best Practices
### Start Small
- Begin with a single document type
- Focus on specific entity types
- Validate results before scaling
### Configure Properly
- Choose appropriate models for your domain
- Set confidence thresholds
- Define clear ontology rules
### Validate Data
- Check extraction quality
- Review relationship accuracy
- Test with known examples
### Handle Errors
- Implement error handling
- Log processing issues
- Provide feedback mechanisms
### Optimize Performance
- Use appropriate storage backends
- Cache frequently accessed data
- Monitor resource usage
### Document Workflows
- Record processing steps
- Track data sources
- Maintain change logs
---
## Need Help?
- **Documentation**: [Getting Started](getting-started.md)
- **Examples**: [Cookbook](cookbook.md)
- **Community**: [Discord](community.md)
- **Issues**: [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues)
- **Support**: [Contact Us](community.md)
- [Quickstart Tutorial](quickstart.md) — build a full pipeline with code
- [Modules Guide](modules.md) — every module explained
- [Use Cases](use-cases.md) — real-world domain examples
- [API Reference](reference/core.md) — complete technical reference
+54 -85
View File
@@ -1,130 +1,99 @@
# Contributing
# Contributing
**Help us build Semantica! Every contribution makes the project better.**
Contributions of all kinds are welcome — code, documentation, tests, and community support.
---
## Getting Started
## Quick Start
### Quick Start
1. **Fork** the repository
2. **Create** a feature branch
3. **Make** your changes
4. **Test** your changes
5. **Submit** a pull request
```bash
# Fork the repo, then:
git clone https://github.com/your-username/semantica.git
cd semantica
pip install -e ".[dev]"
pytest
```
### First Contribution?
Look for issues labeled [`good-first-issue`](https://github.com/Hawksight-AI/semantica/labels/good-first-issue) for beginner-friendly tasks.
First time? Look for [`good-first-issue`](https://github.com/Hawksight-AI/semantica/labels/good-first-issue) labels for beginner-friendly tasks.
---
## Ways to Contribute
### Code
- **Fix bugs** - Resolve reported issues
- **Add features** - Implement new functionality
- **Improve performance** - Optimize existing code
- **Refactor** - Clean up code structure
**Code**
- Fix bugs and resolve open issues
- Implement new features or integrations
- Optimize performance or refactor existing code
### Documentation
- **Fix typos** - Correct spelling and grammar
- **Improve guides** - Make documentation clearer
- **Add examples** - Provide practical code examples
- **Update API docs** - Keep reference current
**Documentation**
- Fix typos, improve clarity, add examples
- Write tutorials or domain-specific cookbook notebooks
- Keep API reference up to date
### Testing
- **Write tests** - Add test coverage
- **Fix tests** - Resolve test failures
- **Report issues** - Identify bugs through testing
**Testing**
- Add test coverage for untested modules
- Reproduce and confirm reported bugs
- Improve test reliability
### Community
- **Help others** - Answer questions in issues
- **Share knowledge** - Write tutorials and guides
- **Provide feedback** - Review pull requests
**Community**
- Answer questions in issues and discussions
- Review pull requests
- Share Semantica in your blog posts or talks
---
## Reporting Issues
### Bug Reports
When reporting bugs, include:
- **Description** - What happened
- **Steps to reproduce** - How to trigger the issue
- **Expected behavior** - What should happen
- **Environment** - Your setup details
Include: what happened, steps to reproduce, expected behavior, and your environment (Python version, OS, Semantica version).
### Feature Requests
When suggesting features, include:
- **Use case** - Why you need this feature
- **Proposed solution** - How it should work
- **Benefits** - How it helps the community
Include: your use case, what you'd like Semantica to do, and how it benefits others.
---
## Pull Request Guidelines
### Before Submitting
- **Test** your changes thoroughly
- **Document** new features with examples
- **Update** relevant documentation
- **Follow** the existing code style
Before submitting:
### Pull Request Checklist
- [ ] Code follows project style
- [ ] Tests pass locally
- [ ] Documentation is updated
- [ ] Commit messages are clear
- [ ] No merge conflicts
- [ ] Tests pass locally (`pytest`)
- [ ] New features are documented with examples
- [ ] Code follows project style (Black, isort, flake8)
- [ ] Commit messages are clear and descriptive
- [ ] No unresolved merge conflicts
---
## Development Setup
### Local Development
```bash
# Clone your fork
git clone https://github.com/your-username/semantica.git
cd semantica
pip install -e ".[dev]"
```
# Install in development mode
pip install -e .[dev]
Code style tools used: **Black** (formatting), **isort** (imports), **flake8** (linting).
# Run tests
Run the full test suite:
```bash
pytest
```
### Code Style
We use standard Python formatting:
- **Black** for code formatting
- **isort** for import sorting
- **flake8** for linting
---
## Community
Please follow the [Code of Conduct](https://github.com/Hawksight-AI/semantica/blob/main/CODE_OF_CONDUCT.md). Be respectful, patient, and constructive.
All contributors are recognized in release notes and the GitHub contributors list.
---
## Community Guidelines
## Help
### Code of Conduct
Please follow our [Code of Conduct](https://github.com/Hawksight-AI/semantica/blob/main/CODE_OF_CONDUCT.md).
### Communication
- **Be respectful** - Treat everyone with kindness
- **Be helpful** - Assist others when you can
- **Be patient** - Allow time for reviews
- **Be constructive** - Provide helpful feedback
---
## Recognition
All contributors are recognized in:
- **GitHub contributors** - Automatic recognition
- **Release notes** - Notable contributions
- **Community highlights** - Outstanding work
---
## Need Help?
- **[GitHub Issues](https://github.com/Hawksight-AI/semantica/issues)** - Ask questions
- **[Discussions](https://github.com/Hawksight-AI/semantica/discussions)** - Community chat
- **[Code of Conduct](https://github.com/Hawksight-AI/semantica/blob/main/CODE_OF_CONDUCT.md)** - Community standards
- [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues)
- [GitHub Discussions](https://github.com/Hawksight-AI/semantica/discussions)
- [Discord](https://discord.gg/sV34vps5hH)
+35 -85
View File
@@ -1,108 +1,58 @@
# 🍳 Semantica Cookbook
# Semantica Cookbook
Welcome to the **Semantica Cookbook**!
Interactive Jupyter notebooks covering everything from your first knowledge graph to production GraphRAG systems.
This collection of Jupyter notebooks is designed to take you from a beginner to an expert in building semantic AI applications. Whether you're looking for quick recipes or deep-dive tutorials, you'll find it here.
!!! tip "How to use this Cookbook"
- **Beginners**: Start with the [Core Tutorials](#core-tutorials) to learn the basics.
- **Developers**: Check out [Advanced Concepts](#advanced-concepts) for deep dives into specific features.
- **Architects**: Explore [Industry Use Cases](#industry-use-cases) for end-to-end solutions.
!!! tip "Where to start"
- **New to Semantica** — begin with [Core Tutorials](#core-tutorials)
- **Building an application** — see [Advanced Concepts](#advanced-concepts) or [Industry Use Cases](#industry-use-cases)
- **Need installation help** — see the [Installation Guide](installation.md)
!!! note "Prerequisites"
Before running these notebooks, ensure you have:
- Python 3.8+ installed
- A basic understanding of Python and Jupyter
- An OpenAI API key (for most examples)
!!! success "Installation"
Install Semantica from PyPI (recommended):
```bash
pip install semantica
# Or with all optional dependencies:
pip install semantica[all]
```
For more installation options, see the [Installation Guide](installation.md).
Python 3.8+, Jupyter, and an OpenAI API key (for most examples).
---
## Featured Recipes
Hand-picked tutorials to show you the power of Semantica.
## Featured Recipes
<div class="grid cards" markdown>
- :material-robot: **GraphRAG Complete**
---
Build a production-ready Graph Retrieval Augmented Generation system.
**Topics**: RAG, LLMs, Vector Search, Graph Traversal
**Difficulty**: Advanced
[Open Notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)
- :material-scale-balance: **RAG vs. GraphRAG Comparison**
---
Side-by-side comparison of Standard RAG vs. GraphRAG using real-world data.
**Topics**: RAG, GraphRAG, Benchmarking, Visualization
**Difficulty**: Intermediate
[Open Notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb)
- :material-robot: **GraphRAG Complete**
---
Build a production-ready Graph Retrieval Augmented Generation system.
**New Features**: Graph Validation, Logical Inference, Hybrid Context.
**Topics**: RAG, LLMs, Vector Search, Graph Traversal
**Difficulty**: Advanced
[Open Notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)
- :material-scale-balance: **RAG vs. GraphRAG Comparison**
---
Side-by-side comparison of Standard RAG vs. GraphRAG using real-world data.
**New Features**: Inference-Enhanced GraphRAG, Reasoning Gap Analysis.
**Topics**: RAG, GraphRAG, Benchmarking, Visualization
**Difficulty**: Intermediate
[Open Notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb)
- :material-graph: **Your First Knowledge Graph**
---
Go from raw text to a queryable knowledge graph in 20 minutes.
**Topics**: Extraction, Graph Construction, Visualization
**Difficulty**: Beginner
**Topics**: Extraction, Graph Construction, Visualization · **Difficulty**: Beginner
[Open Notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)
- :material-robot: **GraphRAG Complete**
---
Build a production-ready Graph Retrieval Augmented Generation system with hybrid retrieval and logical inference.
**Topics**: RAG, LLMs, Vector Search, Graph Traversal · **Difficulty**: Advanced
[Open Notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)
- :material-scale-balance: **RAG vs. GraphRAG Comparison**
---
Side-by-side benchmark of standard RAG vs. GraphRAG on real-world data.
**Topics**: RAG, GraphRAG, Benchmarking, Reasoning Gap · **Difficulty**: Intermediate
[Open Notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb)
- :material-shield-alert: **Real-Time Anomaly Detection**
---
Detect anomalies in streaming data using dynamic graphs.
**Topics**: Streaming, Security, Dynamic Graphs
**Difficulty**: Advanced
Detect anomalies in streaming data using dynamic knowledge graphs.
**Topics**: Streaming, Security, Dynamic Graphs · **Difficulty**: Advanced
[Open Notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/cybersecurity/01_Real_Time_Anomaly_Detection.ipynb)
</div>
---
## 🏁 Core Tutorials {#core-tutorials}
## Core Tutorials {#core-tutorials}
Essential guides to master the Semantica framework.
@@ -210,7 +160,7 @@ Essential guides to master the Semantica framework.
---
## 🧠 Advanced Concepts
## Advanced Concepts
Deep dive into advanced features, customization, and complex workflows.
@@ -329,7 +279,7 @@ Deep dive into advanced features, customization, and complex workflows.
---
## 🏭 Industry Use Cases {#industry-use-cases}
## Industry Use Cases {#industry-use-cases}
Real-world examples and end-to-end applications across various industries.
@@ -497,7 +447,7 @@ Real-world examples and end-to-end applications across various industries.
---
## 🛠️ How to Run
## How to Run
To run these notebooks locally:
+66 -50
View File
@@ -216,76 +216,92 @@ html {
border-left: 2px solid var(--md-accent-fg-color);
}
/*
/*
==========================================================================
Layout Optimization
==========================================================================
==========================================================================
*/
/* Reduce spacing between sidebars and content for all pages */
.md-content__inner {
padding-left: 0.75rem;
padding-right: 0.75rem;
margin-left: 0;
}
.md-content {
margin-left: 0;
/* Widen the overall grid */
.md-grid {
max-width: 1440px;
margin-left: auto;
margin-right: auto;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
/* Reduce spacing before left sidebar for all pages */
.md-sidebar {
padding-left: 0.25rem;
margin-left: 0;
}
/* Narrow left sidebar to give content more room */
.md-sidebar--primary {
padding-right: 0.5rem;
width: 11rem;
padding-right: 0.25rem;
padding-left: 0.25rem;
}
/* Right TOC sidebar */
.md-sidebar--secondary {
width: 11rem;
padding-left: 0.5rem;
padding-right: 0;
margin-left: 0;
}
/* Reduce spacing after right sidebar (table of contents) and shift it right slightly */
.md-sidebar--secondary {
padding-left: 1.25rem;
padding-right: 0;
margin-right: 0;
margin-left: 3.5rem;
.md-sidebar--secondary .md-nav {
width: 11rem;
}
/* Reduce right edge spacing - similar to left */
.md-container {
padding-right: 0;
/* Tighten TOC list spacing */
.md-sidebar--secondary .md-nav__list {
padding-bottom: 1.5rem;
margin: 0;
}
.md-sidebar--secondary .md-nav__item {
padding: 0;
margin: 0;
}
.md-sidebar--secondary .md-nav__link {
white-space: normal;
word-break: break-word;
overflow: visible;
text-overflow: unset;
padding-top: 0.15rem;
padding-bottom: 0.15rem;
line-height: 1.4;
font-size: 0.7rem;
margin: 0;
}
/* Nested TOC items (h3, h4) */
.md-sidebar--secondary .md-nav__item .md-nav__item .md-nav__link {
padding-left: 0.6rem;
font-size: 0.68rem;
}
/* Remove extra gap between TOC title and first item */
.md-sidebar--secondary .md-nav__title {
margin-bottom: 0.25rem;
padding-bottom: 0.25rem;
}
/* Give the main content area maximum available width */
.md-content {
max-width: none;
padding-left: 1rem;
padding-right: 1rem;
}
.md-content__inner {
max-width: none;
padding-left: 1rem;
padding-right: 1rem;
margin-left: 0;
margin-right: 0;
}
.md-main {
margin-right: 0;
padding-right: 0;
}
/* Reduce margins of the main container */
.md-main__inner {
margin-left: 0;
margin-right: 0;
padding-right: 0;
}
/* Reduce right edge spacing on body/html */
body {
margin-right: 0;
padding-right: 0;
}
html {
margin-right: 0;
padding-right: 0;
}
.md-grid {
margin-left: 0;
padding-left: 0.5rem;
}
/* Ensure text content is left-aligned by default */
+118 -166
View File
@@ -1,10 +1,15 @@
# Deep Dive
Advanced topics, architecture, and internals of Semantica.
Internals, advanced concepts, and extension points for contributors and power users.
## Architecture Overview
!!! tip "Just getting started?"
Read [Architecture](architecture.md) for a higher-level overview first.
Semantica follows a modular, extensible architecture:
---
## Pipeline Internals
The full data flow through a Semantica pipeline:
```mermaid
graph TB
@@ -16,65 +21,79 @@ graph TB
F --> G[Knowledge Graph Builder]
G --> H[Embedding Generator]
H --> I[Export Layer]
D --> D1[Entity Extractor]
D --> D2[Relationship Extractor]
D --> D3[Triplet Extractor]
G --> G1[Graph Validator]
G --> G2[Graph Analyzer]
H --> H1[Text Embeddings]
H --> H2[Graph Embeddings]
```
### Sequence Diagram
```mermaid
sequenceDiagram
participant User
participant Semantica
participant Ingestor
participant Parser
participant Extractor
participant Resolver
participant GraphBuilder
participant Exporter
User->>Semantica: build_knowledge_base(sources)
Semantica->>Ingestor: ingest(sources)
Ingestor->>Parser: parse(documents)
Parser->>Extractor: extract(text)
Extractor->>Resolver: resolve_conflicts(entities)
Resolver->>GraphBuilder: build_graph(resolved_data)
GraphBuilder->>Exporter: export(graph)
Exporter->>User: return result
```
---
## System Components
### 1. Ingestion Layer
### Ingestion Layer
Handles data input from various sources:
Handles input from any source:
- **File Ingestor**: PDF, DOCX, HTML, JSON, CSV
- **Web Ingestor**: URLs, web scraping
- **Database Ingestor**: SQL databases
- **Stream Ingestor**: Real-time data streams
- **FileIngestor** PDF, DOCX, HTML, JSON, CSV, archives
- **WebIngestor** URL crawling and scraping
- **DBIngestor** / **SnowflakeIngestor** SQL databases
- **StreamIngestor** — Kafka and real-time feeds
### 2. Parsing Layer
### Parsing Layer
Converts raw data into structured format:
Converts raw data to structured text:
- Document parsing (PDF, Word, etc.)
- Text extraction
- Metadata extraction
- Format normalization
- Text and metadata extraction from documents
- OCR for scanned content
- Layout analysis (via Docling for tables and columns)
### 3. Extraction Layer
### Extraction Layer
Core semantic extraction:
Core semantic processing pipeline:
```python
# Entity extraction pipeline
```
text → Tokenization → NER → Entity Linking → Entity Validation
```
**Components:**
- Named Entity Recognition (NER)
- Relationship Extraction
- Triplet Extraction
- Coreference Resolution
Components: Named Entity Recognition, Relationship Extraction, Triplet Extraction, Coreference Resolution.
### 4. Normalization Layer
### Normalization Layer
Standardizes extracted data:
Standardizes extracted data: entity names, date formats, numbers, encodings, and language normalization.
- Entity normalization
- Date/time normalization
- Number normalization
- Text cleaning
### Conflict Resolution
### 5. Conflict Resolution
Handles conflicting information:
Handles contradictory facts from multiple sources:
```mermaid
graph LR
@@ -88,64 +107,28 @@ graph LR
E --> H
F --> H
G --> H
style A fill:#ffebee
style H fill:#c8e6c9
style C fill:#fff9c4
```
### 6. Knowledge Graph Builder
### Knowledge Graph Builder
Constructs the knowledge graph:
- Entity resolution across sources
- Edge creation (typed relationships)
- Property assignment with confidence scores
- Graph validation and quality checks
- Node creation (entities)
- Edge creation (relationships)
- Property assignment
- Graph validation
- Quality checks
### Embedding Generator
### 7. Embedding Generator
- Text embeddings (Sentence-Transformers, FastEmbed, OpenAI, BGE)
- Graph embeddings (Node2Vec, GraphSAGE)
Generates vector representations:
- Text embeddings (sentence transformers)
- Graph embeddings (node2vec, GraphSAGE)
- Multimodal embeddings
## Data Flow
```mermaid
sequenceDiagram
participant User
participant Semantica
participant Ingestor
participant Parser
participant Extractor
participant Resolver
participant GraphBuilder
participant Exporter
User->>Semantica: build_knowledge_base(sources)
Semantica->>Ingestor: ingest(sources)
Ingestor->>Parser: parse(documents)
Parser->>Extractor: extract(text)
Extractor->>Resolver: resolve_conflicts(entities)
Resolver->>GraphBuilder: build_graph(resolved_data)
GraphBuilder->>Exporter: export(graph)
Exporter->>User: return result
Note over User,Exporter: Complete pipeline execution
```
---
## Advanced Concepts
### Entity Resolution
Matching entities across sources:
### Entity Resolution Algorithm
```python
# Entity resolution algorithm
def resolve_entities(entities):
def resolve_entities(entities, threshold=0.85):
clusters = []
for entity in entities:
matched = False
@@ -161,123 +144,92 @@ def resolve_entities(entities):
### Relationship Inference
Inferring implicit relationships:
Semantica's reasoning engines can derive implicit relationships:
- Transitive relationships
- Temporal relationships
- Causal relationships
- Hierarchical relationships
- **Transitive** — if A→B and B→C, infer A→C
- **Temporal** — before, after, during from timestamped facts
- **Causal** — IF/THEN rules via `Reasoner`
- **Hierarchical** — subclass/instance inference via `OntologyReasoner`
### Graph Optimization
Optimizing knowledge graph structure:
- Node deduplication
- Edge consolidation
- Path compression
- Index optimization
## Performance Considerations
### Scalability
- **Horizontal Scaling**: Process multiple documents in parallel
- **Vertical Scaling**: Use GPU acceleration
- **Caching**: Cache embeddings and parsed documents
- **Lazy Loading**: Load components on demand
### Memory Management
### Batch Processing for Large Datasets
```python
# Process large datasets efficiently
def process_large_dataset(sources, batch_size=100):
for i in range(0, len(sources), batch_size):
batch = sources[i:i+batch_size]
batch = sources[i : i + batch_size]
result = semantica.build_knowledge_base(batch)
# Save and clear memory
save_result(result)
del result
gc.collect()
```
---
## Extension Points
### Custom Plugins
Create custom plugins:
### Custom Plugin
```python
from semantica.core import Plugin
class CustomPlugin(Plugin):
def process(self, data):
# Your custom processing
# Your custom processing logic
return processed_data
```
### Custom Extractors
Implement custom extractors:
### Custom Extractor
```python
from semantica.semantic_extract import BaseExtractor
class DomainSpecificExtractor(BaseExtractor):
def extract_entities(self, text):
# Domain-specific extraction logic
def extract(self, text):
# Domain-specific entity extraction logic
return entities
```
## Internal APIs
### Custom Ingestor
### Core APIs
```python
from semantica.ingest import BaseIngestor
- `Semantica.build_knowledge_base()` - Main entry point
- `KGBuilder.build()` - Graph construction
- `ConflictResolver.resolve()` - Conflict resolution
- `EmbeddingGenerator.generate()` - Embedding generation
### Extension APIs
- Plugin registration
- Custom extractor registration
- Custom exporter registration
- Event hooks
## Design Decisions
### Why Modular Architecture?
- **Extensibility**: Easy to add new features
- **Testability**: Components can be tested independently
- **Maintainability**: Clear separation of concerns
- **Flexibility**: Swap implementations easily
### Why Conflict Resolution?
- **Data Quality**: Ensures consistent knowledge
- **Multi-Source**: Handles conflicting information
- **Flexibility**: Multiple resolution strategies
- **Transparency**: Track resolution decisions
## Future Enhancements
Planned improvements:
- Distributed processing
- Real-time streaming
- Advanced reasoning
- Multi-modal support expansion
- Enhanced visualization
## Contributing to Core
Interested in contributing to Semantica's core? See our [Contributing Guide](https://github.com/Hawksight-AI/semantica/blob/main/CONTRIBUTING.md).
class CustomIngestor(BaseIngestor):
def ingest(self, source):
# Load and return document dicts
return documents
```
---
For more information:
- **[API Reference](reference/core.md) - Detailed API documentation
- **[Learning More](learning-more.md)** - Additional resources
- **[GitHub Repository](https://github.com/Hawksight-AI/semantica)** - Source code
## Internal APIs
| API | Purpose |
|-----|---------|
| `Semantica.build_knowledge_base()` | Main orchestration entry point |
| `GraphBuilder.build()` | Graph construction |
| `ConflictResolver.resolve()` | Conflict resolution |
| `EmbeddingGenerator.generate()` | Embedding generation |
Extension hooks: plugin registration, custom extractor registration, custom exporter registration, event hooks.
---
## Design Decisions
**Why modular architecture?** Each component is independently testable and swappable. You can use `NERExtractor` alone without pulling in graph storage or pipelines.
**Why built-in conflict resolution?** Multi-source data always has contradictions. Ignoring them produces garbage graphs. Explicit resolution strategies give you control over data quality.
**Why W3C PROV-O for provenance?** It's an industry standard with tooling support. Using a custom format would make lineage data non-portable.
**Why multiple reasoning engines?** Different problems need different reasoning: forward chaining for rule application, SPARQL for graph queries, abductive for hypothesis generation. No single engine fits all cases.
---
## Further Reading
- [Architecture](architecture.md) — high-level three-layer overview
- [Modules](modules.md) — every module with code examples
- [API Reference](reference/core.md) — complete technical reference
- [Contributing](contributing.md) — how to extend the framework
+152 -398
View File
@@ -1,306 +1,134 @@
# Examples
Real-world examples and use cases for Semantica.
!!! tip "Interactive Learning"
For hands-on interactive tutorials, check out our [Cookbook](cookbook.md) with Jupyter notebooks covering everything from basics to advanced use cases.
Code examples organized by complexity. For interactive notebooks, see the [Cookbook](cookbook.md).
---
## Example Gallery
## Beginner
<div class="grid cards" markdown>
### Basic Knowledge Graph
- :material-school: **Getting Started**
---
Quick examples to get you up and running in 5 minutes.
[View Examples](#getting-started-5-min-examples)
```python
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
- :material-cogs: **Core Workflows**
---
Common workflows for building production-ready graphs.
[View Examples](#core-workflows-15-min-examples)
- :material-rocket: **Advanced Patterns**
---
Complex use cases and production deployments.
[View Examples](#advanced-patterns-30-min-examples)
- :material-factory: **Production Patterns**
---
Scalable deployment patterns for enterprise use.
[View Examples](#production-patterns)
</div>
---
## Getting Started (5 min examples)
### Example 1: Basic Knowledge Graph
**Difficulty**: Beginner
Build a knowledge graph from a single document using Semantica's modular approach. This example demonstrates the complete workflow from document ingestion to graph construction.
**What it demonstrates:**
- Document ingestion and parsing
- Entity and relationship extraction
- Knowledge graph construction
**For complete step-by-step examples, see:**
- **[Your First Knowledge Graph Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)**: Complete walkthrough
- **Topics**: Ingestion, parsing, extraction, graph building
- **Difficulty**: Beginner
- **Time**: 20-30 minutes
- **Use Cases**: Learning the complete workflow
### Example 2: Entity Extraction
**Difficulty**: Beginner
Extract entities from text using Named Entity Recognition. This example shows how to identify and classify named entities in text.
**What it demonstrates:**
- Named Entity Recognition (NER)
- Entity type classification
- Confidence scoring
**For complete examples, see:**
- **[Entity Extraction Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/05_Entity_Extraction.ipynb)**: Learn entity extraction
- **Topics**: NER methods, entity types, extraction techniques
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Understanding entity extraction
print(f"{entity.text}: {entity.label}")
```
**Expected Output:**
```
Apple Inc.: ORGANIZATION
Steve Jobs: PERSON
```
### Example 3: Multi-Source Integration
**Difficulty**: Beginner
Combine data from multiple sources into a unified knowledge graph. This example demonstrates integrating data from diverse sources.
**What it demonstrates:**
- Multi-source data ingestion
- Entity merging and resolution
- Unified graph construction
**For complete examples, see:**
- **[Multi-Source Data Integration Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb)**: Advanced integration patterns
- **Topics**: Multi-source integration, entity resolution, conflict handling
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Building unified knowledge graphs from diverse sources
---
## Core Workflows (15 min examples)
### Example 4: Conflict Resolution
**Difficulty**: Intermediate
Resolve conflicts in data from multiple sources. This example shows how to identify and resolve conflicting information.
**What it demonstrates:**
- Conflict detection
- Conflict resolution strategies
- Data quality assurance
**For complete examples, see:**
- **[Multi-Source Data Integration Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb)**: Conflict resolution patterns
- **Topics**: Conflict detection, resolution strategies, data quality
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Data integration, quality assurance
ingestor = FileIngestor()
parser = DocumentParser()
ner = NERExtractor()
parser = DocumentParser()
ner = NERExtractor()
rel = RelationExtractor()
all_entities = []
for source in ["source1.pdf", "source2.pdf"]:
doc = ingestor.ingest_file(source)
parsed = parser.parse_document(source)
text = parsed.get("full_text", "")
entities = ner.extract_entities(text)
all_entities.extend(entities)
sources = ingestor.ingest("data/sample.pdf")
parsed = parser.parse(sources[0])
entities = ner.extract(parsed)
relationships = rel.extract(parsed, entities=entities)
kg = GraphBuilder(merge_entities=True).build(
entities=entities, relationships=relationships
)
print(f"{len(kg.nodes)} nodes, {len(kg.edges)} edges")
```
### Entity Extraction from Text
```python
from semantica.semantic_extract import NERExtractor
ner = NERExtractor()
entities = ner.extract("Apple Inc. was founded by Steve Jobs in 1976.")
for entity in entities:
print(f"{entity['text']}: {entity['type']}")
# Apple Inc.: ORGANIZATION
# Steve Jobs: PERSON
# 1976: DATE
```
### Custom NER Configuration
```python
from semantica.semantic_extract import NERExtractor
ner = NERExtractor(
method="llm",
provider="openai",
model="gpt-4",
confidence_threshold=0.8,
temperature=0.0,
)
entities = ner.extract("Your document text here...")
```
---
## Intermediate
### Multi-Source Integration
```python
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
ingestor = FileIngestor()
parser = DocumentParser()
ner = NERExtractor()
rel = RelationExtractor()
builder = GraphBuilder(merge_entities=True)
all_entities, all_rels = [], []
for path in ["source1.pdf", "source2.pdf", "source3.pdf"]:
sources = ingestor.ingest(path)
parsed = parser.parse(sources[0])
all_entities.extend(ner.extract(parsed))
all_rels.extend(rel.extract(parsed, entities=all_entities))
kg = builder.build(entities=all_entities, relationships=all_rels)
print(f"Unified graph: {len(kg.nodes)} nodes, {len(kg.edges)} edges")
```
### Conflict Detection and Resolution
```python
from semantica.conflicts import ConflictDetector, ConflictResolver
# Detect and resolve conflicts
detector = ConflictDetector()
conflicts = detector.detect_conflicts(all_entities)
resolver = ConflictResolver(default_strategy="voting")
resolved = resolver.resolve_conflicts(conflicts)
print(f"Detected {len(conflicts)} conflicts")
print(f"Resolved {len(resolved)} conflicts")
print(f"Detected {len(conflicts)} conflicts, resolved {len(resolved)}")
```
### Example 5: Custom Entity Extraction Configuration
**Difficulty**: Intermediate
Use custom configuration for entity extraction with specific models and thresholds.
```python
from semantica.semantic_extract import NERExtractor
from semantica.kg import GraphBuilder
# Use LLM-based extraction with custom configuration
ner = NERExtractor(
method="llm",
provider="openai",
model="gpt-4",
confidence_threshold=0.8,
temperature=0.0
)
text = "Your document text here..."
entities = ner.extract_entities(text)
# Build graph with custom merge settings
builder = GraphBuilder(
merge_entities=True,
merge_threshold=0.9
)
kg = builder.build_graph(entities=entities, relationships=[])
```
### Example 6: Incremental Graph Building
**Difficulty**: Intermediate
Build knowledge graph incrementally from multiple sources.
```python
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder, GraphMerger
def build_kg_from_source(source_path):
"""Helper function to build a knowledge graph from a single source."""
ingestor = FileIngestor()
parser = DocumentParser()
ner = NERExtractor()
rel_extractor = RelationExtractor()
doc = ingestor.ingest_file(source_path)
parsed = parser.parse_document(source_path)
text = parsed.get("full_text", "")
entities = ner.extract_entities(text)
relationships = rel_extractor.extract_relations(text, entities=entities)
builder = GraphBuilder()
return builder.build_graph(entities=entities, relationships=relationships)
# Build graphs separately
kg1 = build_kg_from_source("source1.pdf")
kg2 = build_kg_from_source("source2.pdf")
# Merge into unified graph
merger = GraphMerger()
merged_kg = merger.merge([kg1, kg2])
print(f"Merged graph: {len(merged_kg.nodes)} nodes, {len(merged_kg.edges)} edges")
```
---
## Advanced Patterns (30+ min examples)
### Example 7: Graph Visualization
**Difficulty**: Beginner
Visualize your knowledge graph to understand entity relationships.
```python
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
from semantica.visualization import KGVisualizer
# Build a small graph
ingestor = FileIngestor()
parser = DocumentParser()
ner = NERExtractor()
rel_extractor = RelationExtractor()
doc = ingestor.ingest_file("semantica_intro.pdf")
parsed = parser.parse_document("semantica_intro.pdf")
text = parsed.get("full_text", "")
entities = ner.extract_entities(text)
relationships = rel_extractor.extract_relations(text, entities=entities)
builder = GraphBuilder()
kg = builder.build_graph(entities=entities, relationships=relationships)
# Visualize
viz = KGVisualizer()
viz.visualize_network(kg, output="html", file_path="semantica_knowledge_map.html")
print("Visualization saved to semantica_knowledge_map.html")
```
---
## Advanced Patterns (30+ min examples)
### Example 8: Persistent Storage (Neo4j)
**Difficulty**: Intermediate
Store and query knowledge graphs in a persistent graph database.
### Persistent Storage (Neo4j)
```python
from semantica.graph_store import GraphStore
# Initialize with Neo4j
store = GraphStore(
backend="neo4j",
uri="bolt://localhost:7687",
user="neo4j",
password="password"
password="password",
)
store.connect()
# Create nodes and relationships
apple = store.create_node(
labels=["Company"],
properties={"name": "Apple Inc."}
)
tim = store.create_node(
labels=["Person"],
properties={"name": "Tim Cook"}
)
apple = store.create_node(labels=["Company"], properties={"name": "Apple Inc."})
tim = store.create_node(labels=["Person"], properties={"name": "Tim Cook"})
store.create_relationship(
start_node_id=tim["id"],
end_node_id=apple["id"],
rel_type="CEO_OF"
rel_type="CEO_OF",
)
store.close()
```
### Example 9: FalkorDB for Real-Time Applications
**Difficulty**: Intermediate
Ultra-fast graph queries for LLM applications using FalkorDB.
### FalkorDB (High-Speed Queries)
```python
from semantica.graph_store import GraphStore
@@ -309,105 +137,49 @@ store = GraphStore(
backend="falkordb",
host="localhost",
port=6379,
graph_name="knowledge_graph"
graph_name="knowledge_graph",
)
store.connect()
# Fast queries
results = store.execute_query("MATCH (n)-[r]->(m) WHERE n.name CONTAINS 'AI' RETURN n")
results = store.execute_query(
"MATCH (n)-[r]->(m) WHERE n.name CONTAINS 'AI' RETURN n"
)
store.close()
```
### Example 10: GraphRAG (Knowledge-Powered Retrieval)
---
**Difficulty**: Advanced
## Advanced
Build a production-ready GraphRAG system with logical inference and hybrid retrieval.
### GraphRAG with Reasoning
```python
from semantica.context import AgentContext
from semantica.reasoning import Reasoner
# 1. Initialize context with GraphRAG (Hybrid Retrieval)
context = AgentContext(
vector_store=vs,
vector_store=vs,
knowledge_graph=kg,
graph_expansion=True,
hybrid_alpha=0.7
hybrid_alpha=0.7,
)
# 2. Enrich Knowledge Graph using Logical Reasoning
reasoner = Reasoner()
# Add a rule to categorize technology stack items
reasoner.add_rule("IF Library(?x) AND Language(?y) THEN TechStackItem(?x)")
inferred = reasoner.infer_facts(kg.get_all_triplets())
# Infer new facts from the existing graph
all_facts = kg.get_all_triplets()
inferred = reasoner.infer_facts(all_facts)
for fact in inferred:
kg.add_fact_from_string(fact)
# Add inferred knowledge back to the graph
for fact_str in inferred:
kg.add_fact_from_string(fact_str)
# 3. Retrieve context for a query (now with enriched knowledge)
results = context.retrieve("What technologies are used in this project?")
```
[**View Complete GraphRAG Tutorial**](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)
### Example 11: RAG vs. GraphRAG Comparison
**Difficulty**: Intermediate
Benchmark standard Vector RAG against Graph-enhanced retrieval.
[**View RAG vs. GraphRAG Comparison**](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb)
[Full GraphRAG tutorial](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb) · [RAG vs. GraphRAG comparison](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb)
---
## Production Patterns
## Production
### Example 12: Streaming Data Processing
**Difficulty**: Advanced
Process data streams in real-time.
```python
from semantica.ingest import StreamIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
stream_ingestor = StreamIngestor(stream_uri="kafka://localhost:9092/topic")
parser = DocumentParser()
ner = NERExtractor()
rel_extractor = RelationExtractor()
builder = GraphBuilder()
for batch in stream_ingestor.stream(batch_size=100):
all_entities = []
all_relationships = []
for item in batch:
text = str(item) # Convert stream item to text
entities = ner.extract_entities(text)
relationships = rel_extractor.extract_relations(text, entities=entities)
all_entities.extend(entities)
all_relationships.extend(relationships)
# Build graph from batch
kg = builder.build_graph(entities=all_entities, relationships=all_relationships)
# Process results
print(f"Processed batch: {len(kg.nodes)} nodes")
```
### Example 13: Batch Processing Large Datasets
**Difficulty**: Intermediate
Process large datasets efficiently with batching.
### Batch Processing (Large Datasets)
```python
from semantica.ingest import FileIngestor
@@ -416,75 +188,57 @@ from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
ingestor = FileIngestor()
parser = DocumentParser()
ner = NERExtractor()
rel_extractor = RelationExtractor()
builder = GraphBuilder()
parser = DocumentParser()
ner = NERExtractor()
rel = RelationExtractor()
builder = GraphBuilder()
sources = [f"data/doc_{i}.pdf" for i in range(1000)]
sources = [f"data/doc_{i}.pdf" for i in range(1000)]
batch_size = 50
for i in range(0, len(sources), batch_size):
batch = sources[i:i+batch_size]
all_entities = []
all_relationships = []
for source in batch:
doc = ingestor.ingest_file(source)
parsed = parser.parse_document(source)
text = parsed.get("full_text", "")
entities = ner.extract_entities(text)
relationships = rel_extractor.extract_relations(text, entities=entities)
all_entities.extend(entities)
all_relationships.extend(relationships)
# Build graph from batch
kg = builder.build_graph(entities=all_entities, relationships=all_relationships)
# Save intermediate results
print(f"Processed batch {i//batch_size + 1}: {len(kg.nodes)} nodes")
batch = sources[i : i + batch_size]
all_entities, all_rels = [], []
for path in batch:
parsed = parser.parse(ingestor.ingest(path)[0])
all_entities.extend(ner.extract(parsed))
all_rels.extend(rel.extract(parsed, entities=all_entities))
kg = builder.build(entities=all_entities, relationships=all_rels)
print(f"Batch {i // batch_size + 1}: {len(kg.nodes)} nodes")
```
### Real-Time Streaming
```python
from semantica.ingest import StreamIngestor
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
stream = StreamIngestor(stream_uri="kafka://localhost:9092/topic")
ner = NERExtractor()
rel = RelationExtractor()
builder = GraphBuilder()
for batch in stream.stream(batch_size=100):
all_entities, all_rels = [], []
for item in batch:
text = str(item)
all_entities.extend(ner.extract(text))
all_rels.extend(rel.extract(text, entities=all_entities))
kg = builder.build(entities=all_entities, relationships=all_rels)
print(f"Processed batch: {len(kg.nodes)} nodes")
```
---
## More Resources
- **[Quick Start Guide](quickstart.md)** - Step-by-step tutorial
- **[API Reference](reference/core.md)** - Complete API documentation
- **[Cookbook](cookbook.md)** - Interactive Jupyter notebooks
- **[Use Cases](use-cases.md)** - Real-world applications
### 🍳 Recommended Cookbook Tutorials
- **[Welcome to Semantica](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)**: Comprehensive introduction to all modules
- **Topics**: Framework overview, all modules, architecture, configuration
- **Difficulty**: Beginner
- **Time**: 30-45 minutes
- **Use Cases**: First-time users, understanding the framework
- **[Your First Knowledge Graph](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)**: Build your first knowledge graph
- **Topics**: Entity extraction, relationship extraction, graph construction, visualization
- **Difficulty**: Beginner
- **Time**: 20-30 minutes
- **Use Cases**: Learning the basics, quick start
- **[GraphRAG Complete](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)**: Production-ready GraphRAG system
- **Topics**: GraphRAG, hybrid retrieval, vector search, graph traversal, LLM integration
- **Difficulty**: Advanced
- **Time**: 1-2 hours
- **Use Cases**: Production RAG applications
- **[RAG vs. GraphRAG Comparison](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb)**: Benchmark standard RAG vs GraphRAG
- **Topics**: RAG, GraphRAG, benchmarking, visualization, reasoning gap
- **Difficulty**: Intermediate
- **Time**: 45-60 minutes
- **Use Cases**: Understanding GraphRAG advantages, choosing the right approach
---
!!! info "Contribute"
Have an example to share? [Contribute on GitHub](https://github.com/Hawksight-AI/semantica)
- [Quickstart Tutorial](quickstart.md) — step-by-step first pipeline
- [Cookbook](cookbook.md) — interactive Jupyter notebooks
- [Use Cases](use-cases.md) — domain-specific examples
- [API Reference](reference/core.md) — complete API documentation
!!! info "Have an example to share?"
[Contribute on GitHub](https://github.com/Hawksight-AI/semantica)
+86 -85
View File
@@ -1,148 +1,149 @@
# Frequently Asked Questions
# Frequently Asked Questions
**Common questions about Semantica and how to use it.**
Common questions about Semantica. Use Ctrl+F to find what you need.
---
## General
### What is Semantica?
Semantica is an open-source framework for building knowledge graphs from unstructured data. It transforms documents, web pages, and databases into structured, queryable knowledge.
### What can I do with Semantica?
- **Build knowledge graphs** from documents and data
- **Extract entities and relationships** automatically
- **Power AI applications** with structured knowledge
- **Create semantic search** and GraphRAG systems
- **Integrate multiple data sources** into unified graphs
Semantica is an open-source framework for building context graphs and decision intelligence layers for AI. It transforms unstructured data — documents, APIs, databases — into structured knowledge graphs with full provenance tracking, making AI systems explainable and auditable.
### What can I build with Semantica?
- Knowledge graphs from documents and multi-source data
- GraphRAG systems with graph-grounded retrieval
- AI agents with structured decision history and semantic memory
- Compliance-ready pipelines with W3C PROV-O lineage
### What makes Semantica different from other frameworks?
Most frameworks stop at retrieval or generation. Semantica adds an **accountability layer**: every decision is recorded, every fact links to a source, and every reasoning step is explainable. It's designed for environments where you need to audit why an AI reached a conclusion.
### Is Semantica free?
Yes! Semantica is open source under the MIT License.
### What makes Semantica different?
- **Modular architecture** - Use only what you need
- **Production-ready** - Built for scale and reliability
- **Extensible** - Add custom models and components
- **Open source** - Transparent and community-driven
Yes — MIT licensed, no vendor lock-in. Some features require third-party API keys (e.g., OpenAI embeddings), but Semantica itself is free.
---
## Installation
### How do I install Semantica?
```bash
pip install semantica
```
See [Installation](installation.md) for virtual environment setup, optional extras, and troubleshooting.
### What Python version do I need?
Python 3.8 or higher. Python 3.11+ is recommended.
Python 3.8 or higher. Python 3.11+ is recommended for best performance.
### What are the system requirements?
- Python 3.8+
- 4GB+ RAM for basic use
- Optional GPU for embeddings and ML models
- 4 GB RAM minimum; 16 GB+ recommended for larger graphs
- Optional GPU for embedding generation and ML inference
---
## Getting Started
### How do I start using Semantica?
```python
from semantica.semantic_extract import NERExtractor
from semantica.kg import GraphBuilder
### Where do I start?
# Extract entities
ner = NERExtractor()
entities = ner.extract("Apple Inc. was founded by Steve Jobs.")
1. [Installation](installation.md) — get set up
2. [Getting Started](getting-started.md) — core concepts and first example
3. [Quickstart Tutorial](quickstart.md) — full step-by-step pipeline
4. [Cookbook](cookbook.md) — interactive Jupyter notebooks
# Build knowledge graph
kg = GraphBuilder().build({"entities": entities})
```
### What data sources does Semantica support?
### Where can I find examples?
- **[Getting Started Guide](getting-started.md)** - Quick introduction
- **[Cookbook](cookbook.md)** - Practical examples
- **[GitHub Examples](https://github.com/Hawksight-AI/semantica/tree/main/examples)** - Code samples
- **Files** — PDF, DOCX, HTML, JSON, CSV, Excel, PPTX, archives
- **Web** — crawl with `WebIngestor`, RSS feeds
- **Databases** PostgreSQL, MySQL, Snowflake via `DBIngestor` / `SnowflakeIngestor`
- **Streams** — Kafka, real-time ingestion
- **Media** — image OCR, audio/video metadata
---
## Features
### What data sources does Semantica support?
- **Files**: PDF, DOCX, TXT, JSON, CSV
- **Web**: Websites, RSS feeds, APIs
- **Databases**: PostgreSQL, MySQL, Snowflake, MongoDB
- **Streams**: Kafka, RabbitMQ, real-time data
### Can I use my own models?
### Can I use custom models?
Yes! Semantica supports custom:
- **Entity extraction models**
- **Embedding models**
- **Language models**
- **Custom processors**
Yes. Semantica supports custom entity extraction models, embedding models, LLM providers (via LiteLLM — 100+ models), and custom pipeline processors.
### Does Semantica support GPUs?
Yes, Semantica automatically uses GPUs when available for:
- **Embedding generation**
- **ML model inference**
- **Vector operations**
Yes. When available, GPUs are used automatically for embedding generation, ML model inference, and vector operations. Install `semantica[gpu]` for CUDA support.
### How does Semantica handle large datasets?
- **Batching** — process documents in configurable chunks
- **Parallel processing** — `PipelineBuilder` supports configurable worker counts
- **Delta processing** — update graphs incrementally without full recompute
- **Graph backends** — swap in-memory NetworkX for Neo4j, FalkorDB, or Apache AGE at scale
---
## Technical
### How does Semantica handle large datasets?
- **Batching** - Process data in chunks
- **Streaming** - Handle real-time data
- **Parallel processing** - Use multiple cores
- **Memory management** - Efficient resource usage
### What graph databases are supported?
### Can I deploy Semantica in production?
Yes! Semantica is production-ready with:
- **Scalable architecture**
- **Error handling**
- **Monitoring support**
- **Container deployment**
Neo4j, FalkorDB, Apache AGE (PostgreSQL), Amazon Neptune, and in-memory NetworkX for development.
### How do I customize Semantica?
- **Custom processors** - Add new extraction logic
- **Custom models** - Use your own ML models
- **Plugins** - Extend functionality
- **Configuration** - Adjust behavior
### What export formats are available?
RDF (Turtle, JSON-LD, N-Triples, XML), Apache Parquet, ArangoDB AQL, CSV, YAML, and OWL ontologies.
### Is Semantica production-ready?
Yes. v0.3.0 ships with 886+ passing tests, `PipelineValidator`, `FailureHandler` with exponential backoff, W3C PROV-O provenance, and change management with checksums. See [What's New](index.md#whats-new-in-v030) for details.
---
## Troubleshooting
### Installation issues
- **Python version**: Ensure Python 3.8+
- **Dependencies**: Install with `pip install -e .[dev]`
- **Permissions**: Use virtual environments
### Import error: `ModuleNotFoundError: No module named 'semantica'`
### Performance issues
- **Memory**: Increase available RAM
- **GPU**: Install CUDA for GPU acceleration
- **Batching**: Use smaller chunk sizes
Ensure you have the correct Python environment active, then:
### Common errors
- **Import errors**: Check installation path
- **Model loading**: Verify model availability
- **Memory errors**: Reduce batch sizes
```bash
pip list | grep semantica
pip install --upgrade semantica
```
### Installation fails with dependency errors
```bash
pip install --upgrade pip wheel
pip install semantica
```
### Memory errors during processing
Reduce batch sizes, enable streaming ingestion, or switch to a persistent graph backend (Neo4j, FalkorDB).
### Slow embedding or inference
Install GPU support (`pip install semantica[gpu]`) and ensure CUDA is available on your system.
---
## Support
### Where can I get help?
- **[GitHub Issues](https://github.com/Hawksight-AI/semantica/issues)** - Report problems
- **[Discussions](https://github.com/Hawksight-AI/semantica/discussions)** - Ask questions
- **[Documentation](index.md)** - Browse guides and references
### How do I report bugs?
1. **Search** existing issues first
2. **Create** a new issue with details
3. **Include** reproduction steps
4. **Add** environment information
- [Discord](https://discord.gg/sV34vps5hH) — community chat and support
- [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues) — bug reports and feature requests
- [GitHub Discussions](https://github.com/Hawksight-AI/semantica/discussions) — questions and ideas
### Can I contribute?
Yes! See the [Contributing Guide](contributing.md) for details on how to help improve Semantica.
### How do I report a bug?
1. Search [existing issues](https://github.com/Hawksight-AI/semantica/issues) first
2. Open a new issue with: description, reproduction steps, expected vs actual behavior, and your environment (Python version, OS, Semantica version)
### How do I contribute?
See the [Contributing Guide](contributing.md).
+53 -55
View File
@@ -1,14 +1,18 @@
# Getting Started
## Overview
**Semantica** is the context and intelligence layer for AI — turning raw data into explainable, auditable knowledge graphs for high-stakes domains.
**Semantica** is a semantic intelligence layer that bridges the gap between raw data and trustworthy AI. It transforms unstructured data into explainable, auditable knowledge graphs perfect for high-stakes domains.
!!! tip "Just here for code?"
Jump straight to the [Quick Start](#quick-start) or explore the [Cookbook](cookbook.md) for interactive notebooks.
### What You Can Build
- **GraphRAG Systems** - Enhanced retrieval with semantic reasoning
- **AI Agents** - Trustworthy agents with explainable memory
- **Knowledge Graphs** - Production-ready semantic databases
- **Compliance-Ready AI** - Auditable systems with full provenance
---
## What You Can Build
- **GraphRAG Systems** — enhanced retrieval with semantic graph reasoning
- **AI Agents** — accountable agents with structured decision history and memory
- **Knowledge Graphs** — production-ready semantic knowledge bases
- **Compliance-Ready AI** — auditable systems with full W3C PROV-O provenance
---
@@ -18,17 +22,17 @@
pip install semantica
```
Or with all features:
With all optional dependencies:
```bash
pip install semantica[all]
```
Verify installation:
Verify:
```python
import semantica
print(f"Semantica {semantica.__version__} installed!")
print(semantica.__version__)
```
---
@@ -36,66 +40,60 @@ print(f"Semantica {semantica.__version__} installed!")
## Quick Start
```python
from semantica.semantic_extract import NERExtractor
from semantica.kg import GraphBuilder
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
# Extract entities
ner = NERExtractor(method="ml", model="en_core_web_sm")
entities = ner.extract("Apple Inc. was founded by Steve Jobs in 1976.")
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
vector_store=VectorStore(backend="inmemory"),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
)
# Build knowledge graph
kg = GraphBuilder().build({"entities": entities, "relationships": []})
print(f"Built KG with {len(kg.get('entities', []))} entities")
# Store a memory
context.store("GPT-4 outperforms GPT-3.5 on reasoning benchmarks by 40%")
# Record a decision
decision_id = context.record_decision(
category="model_selection",
scenario="Choose LLM for production pipeline",
reasoning="GPT-4 benchmark advantage justifies cost increase",
outcome="selected_gpt4",
confidence=0.91,
)
# Find similar past decisions
precedents = context.find_precedents("model selection", limit=5)
```
**What this does:**
- Extracts entities (people, organizations, dates) from text
- Builds a knowledge graph from extracted entities
- Outputs the number of entities found
---
## Core Architecture
Semantica uses a **modular architecture** - use only what you need:
Semantica uses a modular, layered architecture — import only what you need.
### 1️⃣ Input Layer - Data Ingestion
```python
from semantica.ingest import FileIngestor
documents = FileIngestor().ingest_directory("docs/")
```
### 2️⃣ Semantic Layer - Intelligence Engine
```python
from semantica.semantic_extract import NERExtractor, RelationExtractor
entities = NERExtractor().extract(text)
relationships = RelationExtractor().extract(text, entities)
```
### 3️⃣ Output Layer - Knowledge Assets
```python
from semantica.kg import GraphBuilder
kg = GraphBuilder().build_graph(entities, relationships)
```
| Layer | Modules | Purpose |
|-------|---------|---------|
| **Input** | `ingest`, `parse`, `split`, `normalize` | Load and prepare data |
| **Semantic** | `semantic_extract`, `kg`, `ontology`, `reasoning` | Extract meaning |
| **Storage** | `embeddings`, `vector_store`, `graph_store` | Persist knowledge |
| **Quality** | `deduplication`, `conflicts` | Validate and clean |
| **Context** | `context`, `provenance`, `change_management` | Track decisions and lineage |
| **Output** | `export`, `visualization`, `pipeline` | Deliver results |
---
## Next Steps
### 🍳 Interactive Tutorials
1. **[Welcome to Semantica](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)** - Complete framework overview
2. **[Your First Knowledge Graph](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)** - Hands-on graph building
3. **[GraphRAG Complete](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)** - Production-ready RAG
### 📚 Learn More
- **[Core Concepts](concepts.md)** - Deep dive into knowledge graphs & ontologies
- **[Cookbook](cookbook.md)** - 14 domain-specific tutorials
- **[API Reference](reference/core.md)** - Complete technical documentation
- [Core Concepts](concepts.md) — knowledge graphs, ontologies, reasoning explained
- [Quickstart Tutorial](quickstart.md) — build a full pipeline step by step
- [Cookbook](cookbook.md) — 14 domain-specific Jupyter notebook tutorials
- [API Reference](reference/core.md) — complete module documentation
---
## Need Help?
## Help
- **[💬 Discord Community](https://discord.gg/N7WmAuDH)** - Get help from the community
- **[🐛 Issues](https://github.com/Hawksight-AI/semantica/issues)** - Report bugs or request features
- **[📖 Documentation](https://semantica.readthedocs.io/)** - Full documentation site
- [Discord Community](https://discord.gg/sV34vps5hH) — ask questions, share projects
- [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues) — report bugs or request features
- [FAQ](faq.md) — common questions answered
+7 -17
View File
@@ -1,9 +1,9 @@
# Glossary
**Comprehensive reference of terms and concepts used in Semantica and semantic intelligence.**
Reference of terms and concepts used throughout Semantica.
!!! tip "Quick Reference"
Looking for a specific term? Use your browser's search function (Ctrl+F) to find terms quickly.
!!! tip "Finding a term"
Use Ctrl+F to search this page.
---
@@ -216,17 +216,7 @@ W3C PROV-O compliant tracking of data lineage and source attribution.
## See Also
- **[Core Concepts](concepts.md)** - Deep dive into fundamental concepts
- **[Getting Started](getting-started.md)** - Begin your journey with Semantica
- **[Modules Guide](modules.md)** - Complete module overview
- **[API Reference](reference/)** - Technical documentation
---
## Need Help?
- **Documentation**: [Getting Started](getting-started.md)
- **Examples**: [Cookbook](cookbook.md)
- **Community**: [Discord](community.md)
- **Issues**: [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues)
- **Support**: [Contact Us](community.md)
- [Core Concepts](concepts.md) — deeper explanation of key ideas
- [Getting Started](getting-started.md) — first steps
- [Modules Guide](modules.md) — every module explained
- [API Reference](reference/) — technical reference
+191 -290
View File
@@ -1,24 +1,23 @@
<div align="center">
<img src="assets/img/Semantica Logo.png" alt="Semantica Logo" width="450" height="auto">
<img src="assets/img/Semantica Logo.png" alt="Semantica Logo" width="420" height="auto">
<h1>🧠 Semantica</h1>
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.8+-blue.svg" alt="Python 3.8+"></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
<a href="https://badge.fury.io/py/semantica"><img src="https://img.shields.io/badge/pypi-v0.2.3-blue.svg" alt="PyPI version"></a>
<a href="https://pypi.org/project/semantica/"><img src="https://img.shields.io/pypi/dm/semantica" alt="Monthly Downloads"></a>
<a href="https://pypi.org/project/semantica/"><img src="https://img.shields.io/pypi/v/semantica.svg" alt="PyPI"></a>
<a href="https://github.com/Hawksight-AI/semantica/releases/tag/v0.4.0"><img src="https://img.shields.io/badge/version-0.4.0-brightgreen.svg" alt="Version"></a>
<a href="https://pepy.tech/project/semantica"><img src="https://static.pepy.tech/badge/semantica" alt="Total Downloads"></a>
<a href="https://semantica.readthedocs.io/"><img src="https://img.shields.io/badge/docs-latest-brightgreen.svg" alt="Documentation"></a>
<a href="https://discord.gg/N7WmAuDH"><img src="https://img.shields.io/badge/Discord-Join%20Us-7289da?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
<p><strong>Open-Source Semantic Layer & Knowledge Engineering Framework</strong></p>
<p><strong>Transform Chaos into Intelligence. Build AI systems that are explainable, traceable, and trustworthy — not black boxes.</strong></p>
<p><em>The semantic intelligence layer that makes your AI agents auditable, explainable, and trustworthy. Perfect for high-stakes domains where mistakes have real consequences.</em></p>
<p>🆓 <strong>Open Source</strong> • 📜 <strong>MIT Licensed</strong> • 🚀 <strong>Production Ready</strong> • 🌍 <strong>Community Driven</strong></p>
<a href="https://github.com/Hawksight-AI/semantica/actions"><img src="https://github.com/Hawksight-AI/semantica/workflows/CI/badge.svg" alt="CI"></a>
<a href="https://discord.gg/sV34vps5hH"><img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
<a href="https://x.com/BuildSemantica"><img src="https://img.shields.io/badge/X-Follow-black?logo=x&logoColor=white" alt="X"></a>
<p><strong>A Framework for Building Context Graphs and Decision Intelligence Layers for AI</strong></p>
<p>⭐ Give us a Star &nbsp;•&nbsp; 🍴 Fork us &nbsp;•&nbsp; 💬 Join our Discord &nbsp;•&nbsp; 🐦 Follow on X</p>
<p><em>Transform Chaos into Intelligence. Build AI systems with context graphs, decision tracking, and advanced knowledge engineering that are explainable, traceable, and trustworthy — not black boxes.</em></p>
<p>
<a href="getting-started/" class="md-button md-button--primary">Get Started</a>
<a href="https://github.com/Hawksight-AI/semantica" class="md-button">View on GitHub</a>
@@ -27,288 +26,266 @@
---
## 🚀 Why Semantica?
## The Problem
**Semantica** bridges the **semantic gap** between text similarity and true meaning. It's the **semantic intelligence layer** that makes your AI agents auditable, explainable, and trustworthy.
AI agents today are capable but not trustworthy:
Perfect for **high-stakes domains** where mistakes have real consequences.
- **No memory structure** — agents store embeddings, not meaning. Retrieval is fuzzy; there's no way to ask *why* something was recalled.
- **No decision trail** — agents make decisions continuously but record nothing. When something goes wrong, there's no history to debug or audit.
- **No provenance** — outputs cannot be traced back to source facts. In regulated industries, this is a compliance blocker.
- **No reasoning transparency** — black-box answers with no explanation of how a conclusion was reached.
- **No conflict detection** — contradictory facts silently coexist in vector stores, producing unpredictable answers.
These aren't edge cases. They are the reason AI cannot be deployed in healthcare, finance, legal, and government without custom guardrails built from scratch.
---
### ⚡ Get Started in 30 Seconds
## The Solution
Semantica is the **context and intelligence layer** you add to your AI stack:
- **Context Graphs** — structured graph of entities, relationships, and decisions your agent builds as it works. Queryable, traceable, persistent.
- **Decision Intelligence** — every decision is a first-class object: recorded, linked causally, searchable by precedent, and analyzable for downstream impact.
- **Provenance** — every fact links to its source. W3C PROV-O compliant. Full lineage from ingestion to inference.
- **Reasoning engines** — forward chaining, Rete networks, deductive, abductive, and SPARQL reasoning. Explainable inference paths, not black-box answers.
- **Deduplication & QA** — conflict detection, entity resolution, and validation built into the pipeline.
Works alongside LangChain, LlamaIndex, AutoGen, CrewAI, and any LLM provider — Semantica is not a replacement, it's the accountability layer on top.
---
### ⚡ Quick Installation
```bash
pip install semantica
```
```python
from semantica.semantic_extract import NERExtractor
from semantica.kg import GraphBuilder
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
# Extract entities and build knowledge graph
ner = NERExtractor(method="ml", model="en_core_web_sm")
entities = ner.extract("Apple Inc. was founded by Steve Jobs in 1976.")
kg = GraphBuilder().build({"entities": entities, "relationships": []})
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
)
print(f"Built KG with {len(kg.get('entities', []))} entities")
# Store a memory
context.store("GPT-4 outperforms GPT-3.5 on reasoning benchmarks by 40%")
# Record a decision
decision_id = context.record_decision(
category="model_selection",
scenario="Choose LLM for production reasoning pipeline",
reasoning="GPT-4 benchmark advantage justifies 3x cost increase",
outcome="selected_gpt4",
confidence=0.91,
)
# Find similar past decisions and analyze downstream impact
precedents = context.find_precedents("model selection reasoning", limit=5)
influence = context.analyze_decision_influence(decision_id)
```
**[📖 Full Quick Start](getting-started.md)** **[🍳 Cookbook Examples](cookbook.md)** • **[💬 Join Discord](https://discord.gg/N7WmAuDH)** • **[⭐ Star Us](https://github.com/Hawksight-AI/semantica)**
**[Full Quick Start](getting-started.md)** &nbsp;•&nbsp; **[Cookbook](cookbook.md)** &nbsp;•&nbsp; **[Join Discord](https://discord.gg/sV34vps5hH)**
---
## What's New in v0.3.0
> First stable release (`Production/Stable` on PyPI).
| Area | Highlights |
|------|------------|
| **Context Graphs** | Temporal validity windows, weighted BFS, cross-graph navigation with save/load persistence |
| **Decision Intelligence** | Full lifecycle: record → trace → impact → precedent; `PolicyEngine` with versioned rules |
| **KG Algorithms** | PageRank, betweenness, Louvain community detection, Node2Vec, link prediction |
| **Semantic Extraction** | LLM extraction fixed (no silent drops), duplicate relation bug removed, `"llm_typed"` metadata corrected |
| **Deduplication v2** | `blocking_v2`/`hybrid_v2` — 63.6% faster; semantic v2 — 6.98x faster |
| **Delta Processing** | SPARQL-based incremental diff, `delta_mode` pipelines, snapshot versioning |
| **Export** | RDF aliases (`"ttl"`, `"json-ld"`), ArangoDB AQL, Apache Parquet (Spark/BigQuery/Databricks) |
| **Pipeline** | `FailureHandler` with LINEAR/EXPONENTIAL/FIXED backoff; `PipelineValidator` returning `ValidationResult` |
| **Graph Backends** | Apache AGE (SQL injection fixed), AWS Neptune, FalkorDB, PgVector (HNSW/IVFFlat) |
| **Tests** | 886+ passing, 0 failures — 335 context, ~430 KG, 70 semantic extraction, 85 real-world E2E |
---
## Core Value Proposition
| **Trustworthy** | **Explainable** | **Auditable** |
|:------------------:|:------------------:|:-----------------:|
|:---:|:---:|:---:|
| Conflict detection & validation | Transparent reasoning paths | Complete provenance tracking |
| Rule-based governance | Entity relationships & ontologies | W3C PROV-O compliant lineage |
| Production-grade QA | Multi-hop graph reasoning | Source tracking & integrity verification |
---
## Key Features & Benefits
## Features
### Not Just Another Agentic Framework
### Context & Decision Intelligence
- **Context Graphs** — structured, persistent graph of entities, relationships, and decisions
- **Decision tracking** — `add_decision()`, `record_decision()` for full lifecycle management
- **Causal chains** — `add_causal_relationship()`, `trace_decision_chain()`
- **Precedent search** — hybrid similarity search over past decisions via `find_similar_decisions()`
- **Influence analysis** — `analyze_decision_impact()`, `analyze_decision_influence()`
- **Policy engine** — `check_decision_rules()` with versioned, automated compliance rules
- **Agent memory** — `AgentMemory` with short/long-term storage and conversation history
**Semantica complements** LangChain, LlamaIndex, AutoGen, CrewAI, Google ADK, Agno, and other frameworks to enhance your agents with:
### Knowledge Graphs
- **Graph construction** — entities, relationships, properties, typed edges
- **Algorithms** — PageRank, betweenness centrality, clustering coefficient, community detection
- **Node embeddings** — Node2Vec via `NodeEmbedder`; cosine similarity via `SimilarityCalculator`
- **Link prediction** — score potential edges via `LinkPredictor`
- **Temporal graphs** — time-aware nodes and edges with validity windows
- **Delta processing** — incremental updates without full recompute
| Feature | Benefit |
|:--------|:--------|
| **Auditable** | Complete provenance tracking with W3C PROV-O compliance |
| **Explainable** | Transparent reasoning paths with entity relationships |
| **Provenance-Aware** | End-to-end lineage from documents to responses |
| **Validated** | Built-in conflict detection, deduplication, QA |
| **Governed** | Rule-based validation and semantic consistency |
| **Version Control** | Enterprise-grade change management with integrity verification |
### Semantic Extraction
- **NER** — named entity recognition, normalization, classification
- **Relation extraction** — triplet generation via LLMs or rule-based methods, with `"llm_typed"` metadata
- **Deduplication v1/v2** — Jaro-Winkler, `blocking_v2`, `hybrid_v2`, `semantic_v2`; `dedup_triplets()` for triples
### Perfect For High-Stakes Use Cases
### Reasoning
- **Forward chaining** — `Reasoner` with IF/THEN string rules and dict facts
- **Rete network** — `ReteEngine` for high-throughput production rule matching
- **Deductive / Abductive** — `DeductiveReasoner`, `AbductiveReasoner`
- **SPARQL** — `SPARQLReasoner` for query-based inference over RDF graphs
| 🏥 **Healthcare** | 💰 **Finance** | ⚖️ **Legal** |
|:-----------------:|:--------------:|:------------:|
| Clinical decisions | Fraud detection | Evidence-backed research |
| Drug interactions | Regulatory support | Contract analysis |
| Patient safety | Risk assessment | Case law reasoning |
### Provenance & Auditability
- **Entity provenance** — `ProvenanceTracker.track_entity()`
- **Algorithm provenance** — `AlgorithmTrackerWithProvenance`
- **W3C PROV-O compliant** — lineage tracking across all modules
- **Change management** — version control with checksums, audit trails, compliance support
| 🔒 **Cybersecurity** | 🏛️ **Government** | 🏭 **Infrastructure** | 🚗 **Autonomous** |
|:-------------------:|:----------------:|:-------------------:|:-----------------:|
| Threat attribution | Policy decisions | Power grids | Decision logs |
| Incident response | Classified info | Transportation | Safety validation |
### Vector Store
- **Backends** — FAISS, Pinecone, Weaviate, Qdrant, Milvus, PgVector, in-memory
- **Search modes** — semantic top-k, hybrid (vector + keyword), metadata-filtered
### Powers Your AI Stack
### Data Ingestion
- **Files** — PDF, DOCX, HTML, JSON, CSV, Excel, PPTX, archives
- **Sources** — web crawl, SQL databases, Snowflake, feeds, email, repositories
- **Docling** — advanced parsing with table and layout extraction
- **Media** — image OCR, audio/video metadata
- **GraphRAG Systems** — Retrieval with graph reasoning and hybrid search
- **AI Agents** — Trustworthy, accountable multi-agent systems with semantic memory
- **Reasoning Models** — Explainable AI decisions with reasoning paths
- **Enterprise AI** — Governed, auditable platforms that support compliance
### Export
- **RDF** — Turtle, JSON-LD, N-Triples, XML via `RDFExporter`
- **Parquet** — `ParquetExporter` for Spark/BigQuery/Databricks pipelines
- **ArangoDB AQL** — ready-to-run INSERT statements
- **OWL ontologies** — Turtle or RDF/XML
### Integrations
- **Docling Support** — Document parsing with table extraction (PDF, DOCX, PPTX, XLSX)
- **AWS Neptune** — Amazon Neptune graph database support with IAM authentication
- **Custom Ontology Import** — Import existing ontologies (OWL, RDF, Turtle, JSON-LD)
> **Built for environments where every answer must be explainable and governed.**
### Pipeline & Ontology
- **Pipeline DSL** — `PipelineBuilder` with stage chaining, parallel workers, retry policies
- **Ontology** — auto-generate OWL from KGs, import OWL/RDF/Turtle/JSON-LD, HermiT/Pellet validation
---
## 🚨 The Problem: The Semantic Gap
## Modules
### Most AI systems fail in high-stakes domains because they operate on **text similarity**, not **meaning**.
### Understanding the Semantic Gap
The **semantic gap** is the fundamental disconnect between what AI systems can process (text patterns, vector similarities) and what high-stakes applications require (semantic understanding, meaning, context, and relationships).
**Traditional AI approaches:**
- Rely on statistical patterns and text similarity
- Cannot understand relationships between entities
- Cannot reason about domain-specific rules
- Cannot explain why decisions were made
- Cannot trace back to original sources with confidence
**High-stakes AI requires:**
- Semantic understanding of entities and their relationships
- Domain knowledge encoded as formal rules (ontologies)
- Explainable reasoning paths
- Source-level provenance
- Conflict detection and resolution
**Semantica bridges this gap** by providing a semantic intelligence layer that transforms unstructured data into validated, explainable, and auditable knowledge.
### What Organizations Have vs What They Need
| **Current State** | **Required for High-Stakes AI** |
|:---------------------|:-----------------------------------|
| PDFs, DOCX, emails, logs | Formal domain rules (ontologies) |
| APIs, databases, streams | Structured and validated entities |
| Conflicting facts and duplicates | Explicit semantic relationships |
| Siloed systems with no lineage | **Explainable reasoning paths** |
| | **Source-level provenance** |
| | **Audit-ready compliance** |
### The Cost of Missing Semantics
- **Decisions cannot be explained** — No transparency in AI reasoning
- **Errors cannot be traced** — No way to debug or improve
- **Conflicts go undetected** — Contradictory information causes failures
- **Compliance becomes impossible** — No audit trails for regulations
**Trustworthy AI requires semantic accountability.**
| Module | What it provides |
|--------|-----------------|
| `semantica.context` | Context graphs, agent memory, decision tracking, causal analysis, precedent search, policy engine |
| `semantica.kg` | KG construction, graph algorithms, centrality, community detection, embeddings, link prediction |
| `semantica.semantic_extract` | NER, relation extraction, event extraction, coreference, triplet generation, LLM extraction |
| `semantica.reasoning` | Forward chaining, Rete network, deductive, abductive, SPARQL reasoning, explanation generation |
| `semantica.vector_store` | FAISS, Pinecone, Weaviate, Qdrant, Milvus, PgVector; hybrid & filtered search |
| `semantica.export` | RDF, Parquet, ArangoDB AQL, CSV, YAML, OWL, graph formats |
| `semantica.ingest` | Files, web crawl, feeds, databases, Snowflake, MCP, email, repositories |
| `semantica.ontology` | Auto-generation, OWL/RDF export, import, validation, versioning |
| `semantica.pipeline` | Pipeline DSL, parallel workers, validation, retry policies, failure handling |
| `semantica.graph_store` | Neo4j, FalkorDB, Apache AGE, Amazon Neptune; Cypher queries |
| `semantica.embeddings` | Sentence-Transformers, FastEmbed, OpenAI, BGE; similarity calculation |
| `semantica.deduplication` | Entity deduplication, similarity scoring, merging, clustering |
| `semantica.provenance` | W3C PROV-O lineage tracking, source attribution, audit trails |
| `semantica.parse` | PDF, DOCX, PPTX, HTML, code, email, structured data, OCR |
| `semantica.split` | Recursive, semantic, entity-aware, relation-aware, graph-based chunking |
| `semantica.normalize` | Text, entities, dates, numbers, quantities, languages, encodings |
| `semantica.conflicts` | Multi-source conflict detection (value, type, temporal, logical) with resolution |
| `semantica.change_management` | Version storage, change tracking, checksums, audit trails |
| `semantica.triplet_store` | Blazegraph, Jena, RDF4J; SPARQL queries and bulk loading |
| `semantica.visualization` | Interactive/static KG, ontology, embedding, and temporal graph visualization |
| `semantica.core` | Framework orchestration, configuration, plugin system |
| `semantica.llms` | Groq, OpenAI, Novita AI, HuggingFace, LiteLLM integrations |
---
## 🆚 Semantica vs Traditional RAG
## Built for High-Stakes Domains
| Feature | Traditional RAG | Semantica |
|:--------|:----------------|:----------|
| **Reasoning** | ❌ Black-box answers | ✅ Explainable reasoning paths |
| **Provenance** | ❌ No provenance | ✅ W3C PROV-O compliant lineage tracking |
| **Search** | ⚠️ Vector similarity only | ✅ Semantic + graph reasoning |
| **Quality** | ❌ No conflict handling | ✅ Explicit contradiction detection |
| **Safety** | ⚠️ Unsafe for high-stakes | ✅ Designed for governed environments |
| **Compliance** | ❌ No audit trails | ✅ Complete audit trails with integrity verification |
Where **every decision must be accountable** and **mistakes have real consequences**:
- **🏥 Healthcare & Life Sciences** — Clinical decision support, drug interactions, patient safety
- **💰 Finance & Risk** — Fraud detection, SOX/GDPR/MiFID II compliance, risk assessment
- **⚖️ Legal & Compliance** — Evidence-backed research, contract analysis, regulatory tracking
- **🔒 Cybersecurity** — Threat attribution, incident response, security audit trails
- **🏛️ Government & Defense** — Policy decisions, classified information handling, defense intelligence
- **🏭 Critical Infrastructure** — Power grids, transportation safety, emergency response
- **🚗 Autonomous Systems** — Self-driving, robotics safety, industrial automation
---
## 🧩 Semantica Architecture
### 1️⃣ Input Layer — Governed Ingestion
- 📄 **Multiple Formats** — PDFs, DOCX, HTML, JSON, CSV, Excel, PPTX
- 🔧 **Docling Support** — Docling parser for table extraction
- 💾 **Data Sources** — Databases, APIs, streams, archives, web content
- 🎨 **Media Support** — Image parsing with OCR, audio/video metadata extraction
- **Single Pipeline** — Unified ingestion with metadata and source tracking
### 2️⃣ Semantic Layer — Trust & Reasoning Engine
- 🔍 **Entity Extraction** — NER, normalization, classification
- 🔗 **Relationship Discovery** — Triplet generation, semantic links
- 📐 **Ontology Induction** — Automated domain rule generation
- 🔄 **Deduplication** — Jaro-Winkler similarity, conflict resolution
-**Quality Assurance** — Conflict detection, validation
- 📊 **Provenance Tracking** — W3C PROV-O compliant lineage tracking across all modules
- 🧠 **Reasoning Traces** — Explainable inference paths
- 🔐 **Change Management** — Version control with audit trails, checksums, compliance support
### 3️⃣ Output Layer — Auditable Knowledge Assets
- **Knowledge Graphs** — Queryable, temporal, explainable
- 📐 **OWL Ontologies** — HermiT/Pellet validated, custom ontology import support
- 🔢 **Vector Embeddings** — FastEmbed by default
- ☁️ **AWS Neptune** — Amazon Neptune graph database support
- 🔍 **Provenance** — Every AI response links back to:
- 📄 Source documents
- 🏷️ Extracted entities & relations
- 📐 Ontology rules applied
- 🧠 Reasoning steps used
---
## 🏥 Built for High-Stakes Domains
Designed for domains where **mistakes have real consequences** and **every decision must be accountable**:
- **🏥 Healthcare & Life Sciences** — Clinical decision support, drug interaction analysis, medical literature reasoning, patient safety tracking
- **💰 Finance & Risk** — Fraud detection, regulatory support (SOX, GDPR, MiFID II), credit risk assessment, algorithmic trading validation
- **⚖️ Legal & Compliance** — Evidence-backed legal research, contract analysis, regulatory change tracking, case law reasoning
- **🔒 Cybersecurity & Intelligence** — Threat attribution, incident response, security audit trails, intelligence analysis
- **🏛️ Government & Defense** — Governed AI systems, policy decisions, classified information handling, defense intelligence
- **🏭 Critical Infrastructure** — Power grid management, transportation safety, water treatment, emergency response
- **🚗 Autonomous Systems** — Self-driving vehicles, drone navigation, robotics safety, industrial automation
---
## Who Uses Semantica?
- **🤖 AI / ML Engineers** — Building explainable GraphRAG & agents
- **⚙️ Data Engineers** — Creating governed semantic pipelines
- **📊 Knowledge Engineers** — Managing ontologies & KGs at scale
- **🏢 Enterprise Teams** — Requiring trustworthy AI infrastructure
- **🛡️ Risk & Compliance Teams** — Needing audit-ready systems
---
## 🚀 Choose Your Path
## Choose Your Path
<div class="grid cards" markdown>
- :material-rocket-launch: **Quick Start**
---
Get up and running with Semantica in minutes. Learn the basics of ingestion and extraction.
Up and running in minutes.
[:arrow_right: Start Here](getting-started.md)
- :material-book-open-page-variant: **Core Concepts**
---
Deep dive into Knowledge Graphs, Ontologies, and Semantic Reasoning.
Knowledge graphs, ontologies, and semantic reasoning explained.
[:arrow_right: Learn Concepts](concepts.md)
- :material-code-braces: **API Reference**
---
Detailed technical documentation for all Semantica modules and classes.
Full technical documentation for every module and class.
[:arrow_right: View API](reference/core.md)
- :material-chef-hat: **Cookbook**
---
Interactive tutorials, real-world examples, and **14 domain-specific cookbooks**.
14 domain-specific cookbooks with real-world examples.
[:arrow_right: Explore Cookbook](cookbook.md)
</div>
---
## 📦 Installation
## Installation
!!! success "Now Available on PyPI!"
Semantica is officially published on PyPI! Install it with a single command.
Install with a single command.
=== "From PyPI (Recommended)"
Install Semantica directly from PyPI:
=== "PyPI (Recommended)"
```bash
# Install the core package
pip install semantica
# Or install with all optional dependencies
# With all optional dependencies
pip install semantica[all]
```
=== "From Source"
Install from the local source for the latest development version:
```bash
# Clone the repository
git clone https://github.com/Hawksight-AI/semantica.git
cd semantica
# Install in editable mode with core dependencies
pip install -e .
# Or install with all optional dependencies
pip install -e ".[all]"
pip install -e . # core
pip install -e ".[all]" # all extras
```
=== "Development"
For contributors who want to modify the framework:
```bash
# Clone the repository
git clone https://github.com/Hawksight-AI/semantica.git
cd semantica
# Install in editable mode with dev dependencies
pip install -e ".[dev]"
```
=== "Docker"
Run Semantica in a containerized environment:
```bash
docker pull semantica/semantica:latest
docker run -it semantica/semantica
@@ -316,117 +293,41 @@ Designed for domains where **mistakes have real consequences** and **every decis
---
## 🚦 Quick Example
Semantica uses a modular architecture. You can use individual modules directly for maximum flexibility:
```python
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
# 1. Ingest documents
ingestor = FileIngestor()
documents = ingestor.ingest_directory("documents/", recursive=True)
# 2. Parse documents
parser = DocumentParser()
parsed_docs = [parser.parse_document(doc) for doc in documents]
# 3. Extract entities and relationships
ner = NERExtractor()
rel_extractor = RelationExtractor()
entities = []
relationships = []
for doc in parsed_docs:
text = doc.get("full_text", "")
doc_entities = ner.extract_entities(text)
doc_rels = rel_extractor.extract_relations(text, entities=doc_entities)
entities.extend(doc_entities)
relationships.extend(doc_rels)
# 4. Build knowledge graph
builder = GraphBuilder(merge_entities=True)
kg = builder.build_graph(entities=entities, relationships=relationships)
print(f"Created graph with {len(kg.nodes)} nodes and {len(kg.edges)} edges")
```
!!! tip "Orchestration Option"
For complex workflows, you can also use the `Semantica` class for orchestration. See the [Core Module](reference/core.md) documentation for details.
---
## 🎯 Why Semantica?
## Why Semantica?
<div class="grid cards" markdown>
- **🆓 Open Source**
---
MIT licensed. No vendor lock-in. Full transparency.
MIT licensed. No vendor lock-in.
- **🚀 Production Ready**
---
Battle-tested with quality assurance, conflict resolution, and validation.
Battle-tested with QA, conflict resolution, and validation built in.
- **🧩 Modular Architecture**
- **🧩 Modular**
---
Use only what you need. Swap components easily.
- **🌍 Community Driven**
---
Built by developers, for developers. Active Discord community.
Built by developers, for developers. Active Discord.
- **📚 Comprehensive**
- **📚 End-to-End**
---
End-to-end solution from ingestion to reasoning. No duct-taping required.
From ingestion to reasoning — no duct-taping required.
- **🔬 Research-Backed**
---
Based on latest research in knowledge graphs, ontologies, and semantic web.
Grounded in knowledge graph, ontology, and semantic web research.
</div>
---
## 🏗️ Built For
- **Data Scientists**: Transform messy data into clean knowledge graphs
- **Data Engineers**: Build scalable data pipelines with semantic enrichment
- **AI Engineers**: Build GraphRAG, AI agents, and multi-agent systems
- **Knowledge Engineers**: Generate and manage formal ontologies
- **Ontologists**: Design and validate domain-specific ontologies and taxonomies
- **Researchers**: Analyze scientific literature and build citation networks
- **ML Engineers**: Create semantic features for machine learning models
- **Enterprises**: Unify data silos into a semantic layer
---
## 📚 Learn More
- [Getting Started Guide](getting-started.md) - Your first knowledge graph in 5 minutes
- [Core Concepts](concepts.md) - Deep dive into knowledge graphs and ontologies
- [Cookbook](cookbook.md) - Real-world examples and **14 domain-specific cookbooks**
- [API Reference](reference/core.md) - Complete technical documentation
### 🍳 Recommended Cookbook Tutorials
Get hands-on with interactive Jupyter notebooks:
- **[Welcome to Semantica](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)**: Comprehensive introduction to all Semantica modules
- **Topics**: Framework overview, all modules, architecture
- **Difficulty**: Beginner
- **Use Cases**: First-time users, understanding the framework
- **[Your First Knowledge Graph](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)**: Build your first knowledge graph from scratch
- **Topics**: Entity extraction, relationship extraction, graph construction
- **Difficulty**: Beginner
- **Use Cases**: Learning the basics, quick start
- **[GraphRAG Complete](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)**: Production-ready Graph Retrieval Augmented Generation
- **Topics**: GraphRAG, hybrid retrieval, vector search, graph traversal
- **Difficulty**: Advanced
- **Use Cases**: Building AI applications with knowledge graphs
## Learn More
- [Getting Started](getting-started.md) — your first knowledge graph in 5 minutes
- [Core Concepts](concepts.md) — knowledge graphs, ontologies, and semantic reasoning
- [Cookbook](cookbook.md) — 14 domain-specific cookbooks with Jupyter notebooks
- [API Reference](reference/core.md) — complete technical documentation
+112 -236
View File
@@ -1,293 +1,169 @@
# Installation
Get Semantica up and running in minutes.
Get Semantica installed in under a minute.
!!! success "Now Available on PyPI!"
Semantica is officially published on PyPI! Install it with a single command: `pip install semantica`
!!! success "Available on PyPI"
`pip install semantica` — that's it.
!!! note "System Requirements"
Semantica requires Python 3.8 or higher. For best performance, we recommend Python 3.10+.
!!! note "Requirements"
Python 3.8 or higher. Python 3.11+ recommended.
## Prerequisites
Before installing Semantica, ensure you have:
- **Python 3.8 or higher** - Check your version:
```bash
python --version
```
- **pip** - Python package installer (usually comes with Python)
---
## Basic Installation
Install Semantica from PyPI:
```bash
pip install semantica
```
This installs Semantica with all core dependencies.
### GitHub Workaround
If you encounter issues with the PyPI version, you can install directly from the main branch:
```bash
pip install git+https://github.com/Hawksight-AI/semantica.git@main
```
!!! tip "Virtual Environment"
We recommend installing Semantica in a virtual environment to avoid dependency conflicts. Use `python -m venv venv` to create one, then activate it before installing.
## Verify Installation
Verify that Semantica is installed correctly:
```bash
python -c "from semantica.parse import DoclingParser; DoclingParser(); print('✓ Semantica ready')"
```
!!! info "Windows PyTorch Note"
If you encounter PyTorch DLL errors on Windows, ensure you have the [Microsoft Visual C++ Redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe) installed. This is a common environment-specific issue with PyTorch on Windows and not a bug in Semantica.
Expected output:
```
✓ Semantica ready
```
You can also check the installation:
```bash
pip show semantica
```
## Development Installation
To install Semantica in development mode (for contributing):
```bash
# Clone the repository
git clone https://github.com/Hawksight-AI/semantica.git
cd semantica
# Install in editable mode
pip install -e .
# Or install with development dependencies
pip install -e ".[dev]"
```
## Optional Dependencies
Semantica supports optional features that can be installed separately:
### GPU Support
For GPU-accelerated operations:
```bash
pip install semantica[gpu]
```
This includes:
- PyTorch with CUDA support
- FAISS GPU
- CuPy
### Visualization
For enhanced visualization capabilities:
```bash
pip install semantica[viz]
```
Includes:
- PyVis for interactive graphs
- Graphviz for static diagrams
- UMAP for dimensionality reduction
### LLM Providers
Install all LLM provider integrations:
```bash
pip install semantica[llm-all]
```
Or install specific providers:
```bash
# OpenAI
pip install semantica[llm-openai]
# Anthropic
pip install semantica[llm-anthropic]
# Google Gemini
pip install semantica[llm-gemini]
# Groq
pip install semantica[llm-groq]
# Ollama
pip install semantica[llm-ollama]
```
### Cloud Integrations
For cloud storage and deployment:
```bash
pip install semantica[cloud]
```
Includes:
- AWS S3 (boto3)
- Azure Blob Storage
- Google Cloud Storage
- Kubernetes support
### All Optional Features
Install everything:
With all optional dependencies:
```bash
pip install semantica[all]
```
## Virtual Environment (Recommended)
### Verify
It's recommended to use a virtual environment:
```bash
python -c "import semantica; print(semantica.__version__)"
```
---
## Virtual Environment (Recommended)
=== "venv"
```bash
# Create virtual environment
python -m venv venv
# Activate (Windows)
venv\Scripts\activate
# Activate (Linux/Mac)
source venv/bin/activate
# Install Semantica
source venv/bin/activate # Linux / Mac
venv\Scripts\activate # Windows
pip install semantica
```
=== "conda"
```bash
# Create conda environment
conda create -n semantica python=3.11
conda activate semantica
# Install Semantica
pip install semantica
```
---
## Optional Dependencies
Install only what you need:
=== "GPU"
```bash
pip install semantica[gpu]
```
Includes PyTorch with CUDA, FAISS GPU, CuPy.
=== "Visualization"
```bash
pip install semantica[viz]
```
Includes PyVis, Graphviz, UMAP.
=== "LLM Providers"
```bash
pip install semantica[llm-all] # all providers
pip install semantica[llm-openai] # OpenAI
pip install semantica[llm-anthropic] # Anthropic
pip install semantica[llm-gemini] # Google Gemini
pip install semantica[llm-groq] # Groq
pip install semantica[llm-ollama] # Ollama (local)
```
=== "Cloud"
```bash
pip install semantica[cloud]
```
Includes AWS S3, Azure Blob, Google Cloud Storage.
---
## Install from Source
For the latest development version or to contribute:
```bash
git clone https://github.com/Hawksight-AI/semantica.git
cd semantica
pip install -e . # core only
pip install -e ".[all]" # all extras
pip install -e ".[dev]" # dev tools (pytest, black, etc.)
```
If you encounter issues with the PyPI release, install directly from the main branch:
```bash
pip install git+https://github.com/Hawksight-AI/semantica.git@main
```
---
## Troubleshooting
### Common Issues
### ModuleNotFoundError
#### ModuleNotFoundError
Check you have the right environment active:
**Error**: `ModuleNotFoundError: No module named 'semantica'`
```bash
pip list | grep semantica
pip install --upgrade semantica
```
**Solutions**:
- Make sure you've activated the correct Python environment
- Verify installation: `pip list | grep semantica`
- Reinstall: `pip install --upgrade semantica`
### Installation fails with dependency errors
#### Installation Fails
```bash
pip install --upgrade pip
pip install build wheel
pip install semantica --no-deps # install without optional deps first
```
**Error**: Installation fails with dependency errors
### GPU dependencies fail
**Solutions**:
- Upgrade pip: `pip install --upgrade pip`
- Install build tools: `pip install build wheel`
- Try installing without optional dependencies first: `pip install semantica --no-deps`
Install CPU-only first, then add GPU support:
#### GPU Dependencies Fail
```bash
pip install semantica
pip install semantica[gpu]
```
**Error**: GPU dependencies fail to install
### Permission denied
**Solutions**:
- Install CPU-only version first: `pip install semantica`
- Then add GPU support: `pip install semantica[gpu]`
- Check CUDA compatibility for your system
```bash
pip install --user semantica # or use a virtual environment
```
#### Permission Errors
### Windows PyTorch DLL errors
**Error**: Permission denied during installation
Install the [Microsoft Visual C++ Redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe). This is a Windows system dependency, not a Semantica bug.
**Solutions**:
- Use `--user` flag: `pip install --user semantica`
- Use virtual environment (recommended)
- On Linux/Mac, avoid using `sudo` with pip
---
### System Requirements
## System Requirements
| Component | Minimum | Recommended |
|-----------|---------|-------------|
| Python | 3.8 | 3.11+ |
| RAM | Moderate | Ample for your dataset |
| Disk Space | Sufficient for data | Generous storage |
| OS | Windows/Linux/Mac | Linux/Mac |
| OS | Windows / Linux / Mac | Linux / Mac |
| RAM | 4 GB | 16 GB+ |
| Storage | 2 GB | 20 GB+ (for models and data) |
## After Installation
Once Semantica is installed, verify your setup and get started:
### Verify Your Installation
Test that everything works correctly:
```bash
python -c "import semantica; print(semantica.__version__)"
```
**For detailed setup verification and first steps, see:**
- **[Welcome to Semantica Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)**: Verify installation and explore all modules
- **Topics**: Framework overview, installation verification, module exploration
- **Difficulty**: Beginner
- **Time**: 30-45 minutes
- **Use Cases**: First-time setup, understanding the framework
---
## Next Steps
Now that Semantica is installed:
1. **[Quick Start Guide](quickstart.md)** - Build your first knowledge graph in 5 minutes
2. **[Getting Started Guide](getting-started.md)** - Learn the fundamentals
3. **[Examples](examples.md)** - See real-world use cases
4. **[Cookbook](cookbook.md)** - Interactive Jupyter notebook tutorials
### 🍳 Recommended First Cookbooks
Start with these interactive tutorials:
- **[Welcome to Semantica](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)**: Comprehensive introduction
- **Topics**: Framework overview, all modules, architecture, configuration
- **Difficulty**: Beginner
- **Time**: 30-45 minutes
- **Use Cases**: First-time users, understanding the framework
- **[Your First Knowledge Graph](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)**: Build your first graph
- **Topics**: Entity extraction, relationship extraction, graph construction
- **Difficulty**: Beginner
- **Time**: 20-30 minutes
- **Use Cases**: Hands-on practice, quick start
## Getting Help
If you encounter issues:
- Check the [troubleshooting section](#troubleshooting) above
- Review [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues)
- Ask questions in [GitHub Discussions](https://github.com/Hawksight-AI/semantica/discussions)
**For installation and setup help:**
- **[Welcome to Semantica Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)**: Includes setup verification steps
- **[Installation Troubleshooting Guide](getting-started.md#installation--setup)**: Additional troubleshooting tips
- [Getting Started](getting-started.md) — build your first knowledge graph
- [Quickstart Tutorial](quickstart.md) — full step-by-step pipeline
- [Cookbook](cookbook.md) — interactive Jupyter notebook tutorials
+334
View File
@@ -0,0 +1,334 @@
# Agno Integration
Semantica's Agno integration (`semantica[agno]`) wires the full Semantica
semantic intelligence stack into the [Agno](https://github.com/agno-agi/agno)
agentic framework via five focused components.
## Installation
```bash
# Core integration
pip install semantica[agno]
# With a graph store backend
pip install semantica[agno,graph-neo4j]
pip install semantica[agno,graph-falkordb]
# Full stack
pip install semantica[agno,graph-neo4j,vectorstore-pgvector]
```
## Components at a Glance
| Class | Agno Primitive | Semantica Backing |
|---|---|---|
| `AgnoContextStore` | `AgentMemory(db=…)` | `AgentContext` + `VectorStore` |
| `AgnoKnowledgeGraph` | `Agent(knowledge=…)` | `ContextGraph` + KG pipeline |
| `AgnoDecisionKit` | `Agent(tools=[…])` | `DecisionQuery`, `CausalChainAnalyzer`, `PolicyEngine` |
| `AgnoKGToolkit` | `Agent(tools=[…])` | `NERExtractor`, `RelationExtractor`, `Reasoner` |
| `AgnoSharedContext` | Team-level | Shared `ContextGraph` across agents |
---
## 1. AgnoContextStore
Replaces Agno's flat conversation storage with a hybrid **vector + context
graph** memory store. Implements `agno.memory.db.base.MemoryDb`.
```python
from agno.agent import Agent
from agno.memory import AgentMemory
from agno.models.openai import OpenAIChat
from semantica.context import ContextGraph
from semantica.vector_store import VectorStore
from integrations.agno import AgnoContextStore
store = AgnoContextStore(
vector_store=VectorStore(backend="faiss"),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
graph_expansion=True,
session_id="user_session_42",
)
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
memory=AgentMemory(db=store),
description="A financially aware assistant with persistent decision intelligence.",
)
agent.print_response("Recommend a portfolio allocation for a risk-averse investor.")
```
### Key behaviours
- `upsert_memory()` — stores text in `AgentContext` (vector index + graph node)
- `read_memories()` — hybrid retrieval: vector similarity + optional graph hop expansion
- `record_decision()` — records a structured decision with reasoning & outcome
- `find_precedents()` — returns semantically similar historical decisions
---
## 2. AgnoKnowledgeGraph
Gives Agno agents a queryable `ContextGraph` instead of a flat document store.
Ingested documents pass through the full Semantica extraction pipeline.
```python
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from semantica.kg import GraphBuilder
from semantica.semantic_extract import NERExtractor, RelationExtractor
from integrations.agno import AgnoKnowledgeGraph
kg = AgnoKnowledgeGraph(
graph_builder=GraphBuilder(),
ner_extractor=NERExtractor(),
relation_extractor=RelationExtractor(),
)
# Ingest local files
kg.load("regulatory_docs/", recursive=True)
# Ingest raw text
kg.load(texts=["Basel IV capital requirements apply from January 2026."])
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
knowledge=kg,
search_knowledge=True,
)
```
### Ingestion pipeline
```
parse → NER → relation extract → graph build → vector index
```
### Search: multi-hop GraphRAG
```
vector retrieval → entity lookup → graph hop expansion → context injection
```
### Get entity subgraph
```python
ctx = kg.get_graph_context("Basel IV")
# Returns a text summary of the entity's immediate neighbourhood in the graph
```
---
## 3. AgnoDecisionKit
Exposes Semantica's decision intelligence as native Agno tools.
```python
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from semantica.context import AgentContext
from integrations.agno import AgnoDecisionKit
ctx = AgentContext(decision_tracking=True)
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[AgnoDecisionKit(context=ctx)],
show_tool_calls=True,
)
agent.print_response("Should we approve this mortgage application?")
```
### Tools
| Tool | Description | Key Parameters |
|---|---|---|
| `record_decision` | Record decision with reasoning and outcome | `category`, `scenario`, `reasoning`, `outcome`, `confidence`, `entities` |
| `find_precedents` | Search for similar past decisions | `scenario`, `category`, `limit` |
| `trace_causal_chain` | Trace causal chain of a decision | `decision_id`, `depth` |
| `analyze_impact` | Assess downstream influence of a decision | `decision_id` |
| `check_policy` | Validate decision against policy rules | `decision_data`, `policy_rules` |
| `get_decision_summary` | Summarise decision history by category | `category`, `since`, `limit` |
### Example agent turn
```
User: Should we approve this mortgage application?
Agent [tool: find_precedents] → 12 similar mortgage approvals found
Agent [tool: check_policy] → complies with lending policy v2.3
Agent [tool: record_decision] → recorded: loan_approval / approved / confidence=0.94
Agent: Based on 12 historical precedents and full policy compliance, I recommend
approval. Credit score 740, 22% down payment, DTI 31% — all within thresholds.
```
---
## 4. AgnoKGToolkit
Lets agents actively build and query the context graph during reasoning.
```python
from agno.agent import Agent
from agno.models.openai import OpenAIChat
from integrations.agno import AgnoKGToolkit
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[AgnoKGToolkit()],
show_tool_calls=True,
)
agent.print_response(
"Extract entities and relationships from this article and store them in the knowledge graph."
)
```
### Tools
| Tool | Description |
|---|---|
| `extract_entities` | Extract named entities from text |
| `extract_relations` | Extract relationships between entities |
| `add_to_graph` | Add entities / relations to the context graph |
| `query_graph` | Query the graph (natural-language or Cypher) |
| `find_related` | Find concepts related to a given entity |
| `infer_facts` | Apply rules to infer new facts from the graph |
| `export_subgraph` | Export a subgraph as RDF / JSON-LD |
---
## 5. AgnoSharedContext
A single `ContextGraph` shared across an Agno `Team`. Each agent gets a
**role-scoped view** via `bind_agent()`.
```python
from agno.agent import Agent
from agno.team import Team
from agno.models.openai import OpenAIChat
from semantica.context import ContextGraph
from semantica.vector_store import VectorStore
from integrations.agno import AgnoSharedContext, AgnoDecisionKit, AgnoKGToolkit
shared = AgnoSharedContext(
vector_store=VectorStore(backend="faiss"),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
)
research_agent = Agent(
name="Researcher",
model=OpenAIChat(id="gpt-4o"),
memory=shared.bind_agent("researcher"),
tools=[AgnoKGToolkit(context=shared)],
)
decision_agent = Agent(
name="Analyst",
model=OpenAIChat(id="gpt-4o"),
memory=shared.bind_agent("analyst"),
tools=[AgnoDecisionKit(context=shared)],
)
team = Team(
name="Research & Decision Team",
agents=[research_agent, decision_agent],
mode="coordinate",
)
team.print_response(
"Analyse the competitive landscape and recommend our product strategy."
)
```
### Shared memory pool
Memories written by one agent are immediately visible to all other agents in the
team. Each agent's writes are tagged with their role so they can be filtered
independently.
### Shared decisions
```python
# Record a team-level decision
decision_id = shared.record_decision(
category="strategy",
scenario="Expand to EU market",
reasoning="Strong demand signals from Q1 survey",
outcome="approved",
confidence=0.87,
agent_role="cfo",
)
# Query precedents across all agents' history
precedents = shared.find_precedents("market expansion")
# Get cross-agent analytics
insights = shared.get_shared_insights()
```
---
## Use Cases
### Regulated Industry Agents (Finance, Healthcare, Legal)
Agents that log every decision with full provenance, reasoning chain, and policy
compliance check for audit trails.
```python
kit = AgnoDecisionKit(context=ctx)
# Every agent turn: find_precedents → check_policy → record_decision
```
### Long-Running Research Agents
Agents that accumulate a persistent `ContextGraph` over days or weeks, enabling
multi-hop reasoning over a growing knowledge base.
```python
kg = AgnoKnowledgeGraph(graph_builder=GraphBuilder(), ...)
# Agents load new documents continuously; search benefits from the growing graph
```
### Enterprise Multi-Agent Coordination
Teams using `AgnoSharedContext` to prevent contradictory decisions and share
structured knowledge across specialist agents.
### GraphRAG Customer Support
Support agents that retrieve answers via graph traversal, providing more
contextually grounded responses than flat vector search.
### Explainable AI Pipelines
Every agent step, entity reference, and causal chain is traceable back to a
source document or prior decision.
---
## API Reference
```python
from integrations.agno import (
AgnoContextStore, # MemoryDb implementation
AgnoKnowledgeGraph, # AgentKnowledge implementation
AgnoDecisionKit, # Decision intelligence Toolkit
AgnoKGToolkit, # Knowledge graph Toolkit
AgnoSharedContext, # Team-level shared context
AGNO_AVAILABLE, # bool — True if agno is installed
)
```
All five classes are usable **without** `agno` installed — they carry the full
Semantica API and degrade gracefully when passed to Agno constructors.
+4 -4
View File
@@ -13,7 +13,7 @@ Docling is integrated into Semantica's `parse` module via the `DoclingParser`. T
---
## 📖 Integration Documentation
## Integration Documentation
The `DoclingParser` provides a high-level interface for document processing. It supports:
@@ -42,7 +42,7 @@ For more details, see the [Parse Reference](../reference/parse.md).
---
## 🧑🏽‍🍳 Integration Example
## Integration Example
We provide a detailed cookbook and clear code examples to help you get started quickly.
@@ -81,7 +81,7 @@ See more in our [Code Examples](../CodeExamples.md).
---
## 💻 GitHub Source
## GitHub Source
The integration is open-source and available on GitHub. You can explore the implementation, contribute improvements, or report issues.
@@ -89,7 +89,7 @@ The integration is open-source and available on GitHub. You can explore the impl
---
## 📦 PyPI & Installation
## PyPI & Installation
Docling is an optional but highly recommended dependency for Semantica. You can install it along with Semantica or as a separate requirement.
+9 -9
View File
@@ -13,7 +13,7 @@ Snowflake is integrated into Semantica's `ingest` module via the `SnowflakeInges
---
## 📖 Integration Documentation
## Integration Documentation
The `SnowflakeIngestor` provides a high-level interface for Snowflake data ingestion. It supports:
@@ -42,7 +42,7 @@ For more details, see the [Ingest Reference](../reference/ingest.md).
---
## 🧑🏽‍🍳 Integration Example
## Integration Example
We provide a detailed cookbook and clear code examples to help you get started quickly.
@@ -97,7 +97,7 @@ See more in our [Code Examples](../CodeExamples.md).
---
## 💻 GitHub Source
## GitHub Source
The integration is open-source and available on GitHub. You can explore the implementation, contribute improvements, or report issues.
@@ -105,7 +105,7 @@ The integration is open-source and available on GitHub. You can explore the impl
---
## 📦 PyPI & Installation
## PyPI & Installation
Snowflake connector is an optional dependency for Semantica. You can install it along with Semantica or as a separate requirement.
@@ -128,7 +128,7 @@ For full installation details, see the [Installation Guide](../installation.md).
---
## 🔐 Authentication Methods
## Authentication Methods
Snowflake integration supports multiple authentication methods for different security requirements:
@@ -175,7 +175,7 @@ ingestor = SnowflakeIngestor(
---
## 🚀 Advanced Features
## Advanced Features
### Schema Introspection
```python
@@ -209,7 +209,7 @@ data = ingestor.ingest_query(
---
## 📊 Best Practices
## Best Practices
### Use Environment Variables
```python
@@ -244,7 +244,7 @@ for page in range(total_pages):
---
## 🔍 Troubleshooting
## Troubleshooting
### Connection Issues
```python
@@ -272,7 +272,7 @@ ingestor = SnowflakeIngestor(
---
## 📚 See Also
## See Also
- **[Ingest Module Reference](../reference/ingest.md)** - Complete ingestion documentation
- **[Getting Started Guide](../getting-started.md)** - Quick start with Semantica
+73 -216
View File
@@ -1,213 +1,104 @@
# Learning More
Additional resources, tutorials, and advanced learning materials for Semantica.
!!! info "About This Guide"
This guide provides structured learning paths, quick references, troubleshooting guides, and advanced topics to help you master Semantica.
Structured learning paths, quick references, and performance guidance for going deeper with Semantica.
---
## Structured Learning Paths
## Learning Paths
<div class="grid cards" markdown>
- :material-school: **Beginner Path**
- :material-school: **Beginner** (12 hours)
---
Perfect for those new to Semantica and knowledge graphs.
New to Semantica and knowledge graphs.
[Start Path](#beginner-path-1-2-hours)
[Start here](#beginner-path)
- :material-compass: **Intermediate Path**
- :material-compass: **Intermediate** (46 hours)
---
For users comfortable with basics who want to build production applications.
Comfortable with basics, building production applications.
[Start Path](#intermediate-path-4-6-hours)
[Start here](#intermediate-path)
- :material-rocket: **Advanced Path**
- :material-rocket: **Advanced** (8+ hours)
---
For experienced users building enterprise applications.
Enterprise applications and customization.
[Start Path](#advanced-path-8-hours)
[Start here](#advanced-path)
</div>
---
### Beginner Path (1-2 hours)
### Beginner Path
1. **Installation & Setup** (15 min)
- [Installation Guide](installation.md)
- **[Welcome to Semantica Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)**: Comprehensive introduction
- **Topics**: Framework overview, all modules, architecture, configuration
- **Difficulty**: Beginner
- **Time**: 30-45 minutes
- **Use Cases**: First-time users, understanding the framework
2. **Core Concepts** (30 min)
- [Core Concepts](concepts.md)
- [Getting Started Guide](getting-started.md)
- **[Data Ingestion Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/02_Data_Ingestion.ipynb)**: Learn to ingest from multiple sources
- **Topics**: File, web, feed, stream, database ingestion
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Loading data from various sources
3. **First Knowledge Graph** (30 min)
- [Quickstart Tutorial](quickstart.md)
- **[Your First Knowledge Graph Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)**: Build your first knowledge graph
- **Topics**: Entity extraction, relationship extraction, graph construction, visualization
- **Difficulty**: Beginner
- **Time**: 20-30 minutes
- **Use Cases**: Learning the basics, quick start
4. **Basic Operations** (30 min)
- [Examples](examples.md)
- **[Entity Extraction Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/05_Entity_Extraction.ipynb)**: Learn entity extraction
- **Topics**: Named entity recognition, entity types, extraction methods
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Understanding entity extraction
1. **Installation & Setup** — [Installation Guide](installation.md)
2. **Core Concepts** — [Core Concepts](concepts.md) + [Getting Started](getting-started.md)
3. **First Knowledge Graph** — [Quickstart Tutorial](quickstart.md)
4. **Interactive Introduction** — [Welcome to Semantica notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)
5. **Hands-On Practice** — [Your First Knowledge Graph notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)
---
### Intermediate Path (4-6 hours)
### Intermediate Path
1. **Advanced Concepts** (1 hour)
- [Modules Guide](modules.md)
- **[Building Knowledge Graphs Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb)**: Advanced graph construction
- **Topics**: Graph building, entity merging, conflict resolution, temporal graphs
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Production graph construction
- **[Embeddings Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/09_Embeddings.ipynb)**: Learn embeddings
- **Topics**: Embedding generation, similarity search, vector operations
- **Difficulty**: Beginner
- **Time**: 20-30 minutes
- **Use Cases**: Understanding embeddings, semantic search
2. **Use Cases** (1 hour)
- [Use Cases Guide](use-cases.md)
- **[GraphRAG Complete Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)**: Build production GraphRAG
- **Topics**: GraphRAG, hybrid retrieval, graph traversal, LLM integration
- **Difficulty**: Advanced
- **Time**: 1-2 hours
- **Use Cases**: Production GraphRAG systems
3. **Advanced Examples** (1 hour)
- [Examples](examples.md)
- **[Advanced Extraction Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/01_Advanced_Extraction.ipynb)**: Advanced extraction patterns
- **Topics**: Custom entity types, domain-specific extraction, hybrid methods
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Domain-specific extraction
4. **Quality & Optimization** (1 hour)
- [Quality Assurance](concepts.md#8-quality-assurance)
- [Performance Optimization](#performance-optimization)
- **[Multi-Source Data Integration Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb)**: Integrate multiple sources
- **Topics**: Multi-source integration, entity resolution, conflict handling
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Building unified knowledge graphs
1. **All Modules** — [Modules Guide](modules.md)
2. **Advanced Graph Construction** — [Building Knowledge Graphs notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb)
3. **Embeddings & Search** — [Embeddings notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/09_Embeddings.ipynb)
4. **GraphRAG** — [GraphRAG Complete notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)
5. **Multi-Source Integration** — [Multi-Source Data Integration notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb)
6. **Use Case Examples** — [Use Cases](use-cases.md)
---
### Advanced Path (8+ hours)
### Advanced Path
1. **Advanced Architecture** (2 hours)
- [Architecture Guide](architecture.md)
- **[Temporal Graphs Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/04_Temporal_Graphs.ipynb)**: Build temporal graphs
- **Topics**: Time-stamped entities, temporal relationships, historical queries
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Time-aware knowledge graphs
- **[Ontology Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/14_Ontology.ipynb)**: Generate ontologies
- **Topics**: Ontology generation, OWL, schema design
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Formal knowledge representation
2. **Production Deployment** (2 hours)
- [Security Best Practices](#security-best-practices)
- **[GraphRAG Complete Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)**: Production GraphRAG
- **Topics**: Production deployment, scalability, optimization
- **Difficulty**: Advanced
- **Time**: 1-2 hours
- **Use Cases**: Production systems
3. **Customization** (2 hours)
- **[Complete Visualization Suite Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/03_Complete_Visualization_Suite.ipynb)**: Advanced visualization
- **Topics**: Custom layouts, filtering, styling, multiple graph types
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Production visualizations
- **[Multi-Format Export Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/05_Multi_Format_Export.ipynb)**: Advanced export patterns
- **Topics**: Batch export, custom formats, format conversion
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Production exports
1. **Architecture Deep Dive** — [Architecture Guide](architecture.md)
2. **Temporal Graphs** — [Temporal Graphs notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/04_Temporal_Graphs.ipynb)
3. **Ontologies** — [Ontology notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/14_Ontology.ipynb)
4. **Visualization** — [Complete Visualization Suite notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/03_Complete_Visualization_Suite.ipynb)
5. **Export Pipelines** — [Multi-Format Export notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/05_Multi_Format_Export.ipynb)
6. **Production GraphRAG** — [GraphRAG Complete notebook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)
---
## Quick Reference
## Configuration Reference
### Common Operations
The typical workflow involves these steps:
1. **Ingest** documents using `` `FileIngestor` ``
2. **Parse** documents using `` `DocumentParser` ``
3. **Extract** entities and relationships using `` `NERExtractor` `` and `` `RelationExtractor` ``
4. **Build** knowledge graph using `` `GraphBuilder` ``
5. **Generate** embeddings using `` `TextEmbedder` ``
**For complete examples, see:**
- **[Your First Knowledge Graph Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)**: Complete workflow example
- **[Welcome to Semantica Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)**: All modules overview
### Configuration Reference
| Setting | Environment Variable | Config File | Default |
| :--- | :--- | :--- | :--- |
| OpenAI API Key | `OPENAI_API_KEY` | `api_keys.openai` | `None` |
| Embedding Provider | `SEMANTICA_EMBEDDING_PROVIDER` | `embedding.provider` | `"openai"` |
| Graph Backend | `SEMANTICA_GRAPH_BACKEND` | `knowledge_graph.backend` | `"networkx"` |
| Setting | Environment Variable | Default |
|---------|---------------------|---------|
| OpenAI API Key | `OPENAI_API_KEY` | `None` |
| Embedding Provider | `SEMANTICA_EMBEDDING_PROVIDER` | `"openai"` |
| Graph Backend | `SEMANTICA_GRAPH_BACKEND` | `"networkx"` |
---
## Troubleshooting Guide
## Troubleshooting
<div class="grid cards" markdown>
- :material-alert: **Import Errors**
---
`ModuleNotFoundError`
**Solution**: Verify installation (`pip list`) and Python version (3.8+).
Verify installation: `pip list | grep semantica`. Ensure Python 3.8+.
- :material-key: **API Key Errors**
---
`AuthenticationError`
**Solution**: Set `OPENAI_API_KEY` environment variable.
Set `OPENAI_API_KEY` (or the relevant provider key) as an environment variable.
- :material-memory: **Memory Errors**
---
`MemoryError`
**Solution**: Use batch processing and graph stores (Neo4j).
`MemoryError` or OOM crashes
Reduce batch sizes or switch to a persistent graph backend (Neo4j, FalkorDB).
- :material-speedometer: **Slow Processing**
---
Long processing times
**Solution**: Enable parallel processing and GPU acceleration.
Long runtimes on large datasets
Enable parallel processing (`PipelineBuilder` workers) and GPU acceleration.
</div>
@@ -215,80 +106,46 @@ The typical workflow involves these steps:
## Performance Optimization
### 1. Batch Processing
### Batch Processing
Process multiple documents together for better throughput. Use batch processing when working with large document collections.
Process documents in batches rather than one at a time. Configure chunk sizes based on available RAM.
**For examples, see:**
- **[Data Ingestion Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/02_Data_Ingestion.ipynb)**: Batch ingestion patterns
- **[Multi-Source Data Integration Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb)**: Advanced integration
### Parallel Execution
### 2. Parallel Execution
`PipelineBuilder` supports configurable worker counts per stage for independent operations.
Use parallel processing for independent operations to improve performance on multi-core systems.
### Backend Selection
### 3. Backend Selection
| Operation | NetworkX | Neo4j / FalkorDB |
|-----------|----------|------------------|
| Graph construction | Fast | Moderate |
| Query performance | Moderate | Fast |
| Scalability | Low (in-memory) | High (persistent) |
| Operation | NetworkX | Neo4j |
| :--- | :--- | :--- |
| **Graph Construction** | ⚡⚡⚡ | ⚡⚡ |
| **Query Performance** | ⚡⚡ | ⚡⚡⚡ |
| **Scalability** | Low | High |
Use NetworkX for development and smaller graphs; switch to a persistent backend for production at scale.
---
## Security Best Practices
### API Key Management
**API keys**
- Store in environment variables or a secrets manager
- Never hardcode keys or commit them to version control
- Rotate keys regularly
- **DO**: Use environment variables, rotate keys regularly.
- **DON'T**: Hardcode keys, commit to version control.
### Data Privacy
- **DO**: Encrypt sensitive data, use local models.
- **DON'T**: Send PII to external APIs without protection.
---
## FAQ
**Q: What is Semantica?**
A: A framework for building knowledge graphs and semantic applications.
**Q: Is Semantica free?**
A: Yes, it is open source. Some features (e.g., OpenAI) require paid APIs.
**Q: Can I use Semantica in production?**
A: Yes, it is designed for production with proper configuration.
**Data privacy**
- Use local embedding models for sensitive data
- Avoid sending PII to external APIs without appropriate data handling agreements
- Encrypt sensitive graph exports at rest
---
## Next Steps
Continue your learning journey:
- **[Cookbook](cookbook.md)** - Interactive Jupyter notebook tutorials
- **[API Reference](reference/core.md)** - Complete API documentation
- **[Use Cases](use-cases.md)** - Real-world applications
- **[Examples](examples.md)** - Code examples and patterns
### 🍳 Recommended Next Cookbooks
- **[GraphRAG Complete](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)**: Production GraphRAG system
- **Topics**: GraphRAG, hybrid retrieval, LLM integration
- **Difficulty**: Advanced
- **Time**: 1-2 hours
- **Use Cases**: Production RAG applications
- **[RAG vs. GraphRAG Comparison](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb)**: Understand the differences
- **Topics**: RAG comparison, reasoning gap, inference engines
- **Difficulty**: Intermediate
- **Time**: 45-60 minutes
- **Use Cases**: Choosing the right approach
---
!!! info "Contribute"
Have questions? [Open an issue](https://github.com/Hawksight-AI/semantica/issues) or [start a discussion](https://github.com/Hawksight-AI/semantica/discussions)!
- [Cookbook](cookbook.md) — interactive Jupyter notebook tutorials
- [API Reference](reference/core.md) — complete technical documentation
- [Use Cases](use-cases.md) — real-world domain examples
- [FAQ](faq.md) — common questions
!!! info "Questions or feedback?"
[Open an issue](https://github.com/Hawksight-AI/semantica/issues) or [start a discussion](https://github.com/Hawksight-AI/semantica/discussions).
+18 -93
View File
@@ -1,9 +1,9 @@
# Modules & Architecture
# Modules
**Complete guide to Semantica's modular architecture and how to use each component.**
Every Semantica module works independently — use only what you need.
!!! tip "Modular Design"
Each Semantica module works independently. Use only what you need for your specific use case.
!!! tip "Just need a quick reference?"
Jump to the [Module Index](#module-index) at the bottom of this page.
---
@@ -82,13 +82,11 @@ web_ingestor = WebIngestor()
pages = web_ingestor.ingest_urls(["https://example.com"])
```
**What it does:**
- **File formats** - PDF, DOCX, TXT, JSON, CSV
- **Web scraping** - Extract content from websites
- **Database** - Connect to SQL and NoSQL databases
- **Batch processing** - Handle large datasets efficiently
**Use Cases:**
- Document processing pipelines
- Web data extraction
- Database integration
@@ -106,13 +104,11 @@ text = parsed["full_text"]
metadata = parsed["metadata"]
```
**What it does:**
- **Text extraction** - Extract clean text from documents
- **Metadata parsing** - Extract titles, authors, dates
- **Structure analysis** - Identify sections, headings
- **OCR support** - Handle scanned documents
**Use Cases:**
- PDF processing
- Document analysis
- Content extraction
@@ -130,13 +126,11 @@ splitter = TextSplitter(method="semantic")
chunks = splitter.split(text, chunk_size=1000, overlap=200)
```
**What it does:**
- **Intelligent chunking** - Split text while preserving context
- **Semantic splitting** - Break at natural boundaries
- **Size control** - Manage chunk sizes for processing
- **Overlap handling** - Maintain context between chunks
**Use Cases:**
- Document preprocessing
- Embedding preparation
- RAG systems
@@ -155,13 +149,11 @@ clean_text = normalizer.normalize_text(text)
standardized_date = normalizer.normalize_date("Jan 1st, 2020")
```
**What it does:**
- **Text cleaning** - Remove noise and artifacts
- **Date standardization** - Convert to ISO format
- **Name normalization** - Standardize person names
- **Entity normalization** - Clean up company names
**Use Cases:**
- Data preprocessing
- Quality improvement
- Standardization
@@ -186,13 +178,11 @@ rel_extractor = RelationExtractor()
relationships = rel_extractor.extract(text, entities)
```
**What it does:**
- **Named Entity Recognition** - Find people, orgs, locations
- **Relationship extraction** - Find connections between entities
- **Custom entities** - Define your own entity types
- **Confidence scoring** - Quality assessment for extractions
**Use Cases:**
- Knowledge graph construction
- Document analysis
- Information extraction
@@ -215,13 +205,11 @@ analyzer = GraphAnalyzer()
stats = analyzer.analyze(kg)
```
**What it does:**
- **Graph construction** - Build knowledge graphs from data
- **Graph analysis** - Calculate metrics and statistics
- **Graph querying** - Search and retrieve information
- **Graph manipulation** - Merge, split, transform graphs
**Use Cases:**
- Knowledge base creation
- Graph analytics
- Information retrieval
@@ -244,13 +232,11 @@ ontology.add_relationship("works_for", "Person", "Organization")
is_valid = ontology.validate_graph(kg)
```
**What it does:**
- **Schema definition** - Define data structure
- **Data validation** - Ensure data conforms to schema
- **Inheritance** - Create hierarchical relationships
- **Constraints** - Enforce data quality rules
**Use Cases:**
- Data modeling
- Quality assurance
- Schema management
@@ -268,13 +254,11 @@ engine = ReasoningEngine()
inferences = engine.infer(kg, rules=["transitivity", "symmetry"])
```
**What it does:**
- **Logical inference** - Derive new facts from existing ones
- **Pattern matching** - Find complex patterns in data
- **Consistency checking** - Detect contradictions
- **Decision support** - Automated reasoning
**Use Cases:**
- Knowledge discovery
- Decision making
- Consistency checking
@@ -295,13 +279,11 @@ embeddings = generator.generate(["text1", "text2"])
similarity = generator.similarity(embeddings[0], embeddings[1])
```
**What it does:**
- **Text embeddings** - Convert text to vectors
- **Similarity search** - Find similar content
- **Clustering** - Group related items
- **AI integration** - Provide context to LLMs
**Use Cases:**
- Semantic search
- Recommendation systems
- Clustering
@@ -320,13 +302,11 @@ store.add_vectors(embeddings, ids)
results = store.search(query_vector, top_k=10)
```
**What it does:**
- **Vector storage** - Efficient vector database
- **Fast search** - Approximate nearest neighbor search
- **Indexing** - Optimize for performance
- **Batch operations** - Handle large datasets
**Use Cases:**
- Semantic search
- RAG systems
- Recommendation engines
@@ -346,13 +326,11 @@ store.add_edges(relationships)
results = store.query("MATCH (n)-[r]->(m) RETURN n, r, m")
```
**What it does:**
- **Graph persistence** - Store graphs in databases
- **Graph queries** - Cypher and Gremlin support
- **Graph algorithms** - Path finding, centrality
- **Transactions** - ACID compliance
**Use Cases:**
- Knowledge graph storage
- Graph analytics
- Network analysis
@@ -371,13 +349,11 @@ store.add_triplets(subject, predicate, object)
triplets = store.get_triplets(entity="Apple Inc.")
```
**What it does:**
- **Triple storage** - Store (subject, predicate, object) triples
- **Pattern matching** - Find specific patterns
- **RDF support** - Semantic web standards
- **Bulk operations** - Efficient batch processing
**Use Cases:**
- Semantic web
- Knowledge representation
- Linked data
@@ -397,13 +373,11 @@ resolver = EntityResolver()
merged_entities = resolver.resolve(entities, strategy="semantic")
```
**What it does:**
- **Duplicate detection** - Find similar entities
- **Entity resolution** - Merge duplicate records
- **Similarity scoring** - Quality assessment
- **Record linkage** - Connect related records
**Use Cases:**
- Data cleaning
- Master data management
- Record linkage
@@ -422,13 +396,11 @@ conflicts = detector.detect_conflicts(kg)
resolved = detector.resolve(conflicts, strategy="most_recent")
```
**What it does:**
- **Conflict detection** - Find contradictory information
- **Resolution strategies** - Automated conflict resolution
- **Source reliability** - Trustworthiness assessment
- **Temporal analysis** - Time-based conflict handling
**Use Cases:**
- Data quality
- Consistency checking
- Trust management
@@ -448,13 +420,11 @@ manager = ContextManager()
context = manager.get_context(query, history)
```
**What it does:**
- **Context tracking** - Maintain conversation context
- **Memory management** - Store and retrieve context
- **Relevance scoring** - Find relevant context
- **Session management** - Handle multiple conversations
**Use Cases:**
- AI agents
- Chatbots
- Conversational AI
@@ -472,13 +442,11 @@ seed = SeedData()
knowledge = seed.get_knowledge("technology", "companies")
```
**What it does:**
- **Seed knowledge** - Foundation data for domains
- **Knowledge bases** - Pre-built domain knowledge
- **Quick start** - Bootstrap applications
- **Domain models** - Industry-specific data
**Use Cases:**
- Domain bootstrapping
- Quick start data
- Industry knowledge
@@ -496,13 +464,11 @@ provider = LLMProvider(model="gpt-4")
response = provider.generate(prompt, context=kg)
```
**What it does:**
- **LLM integration** - Connect to various LLM providers
- **Prompt engineering** - Optimize prompts for results
- **Context injection** - Provide knowledge graph context
- **Response parsing** - Extract structured outputs
**Use Cases:**
- AI generation
- Question answering
- Text completion
@@ -522,13 +488,11 @@ exporter = GraphExporter()
exporter.export(kg, format="json", filename="output.json")
```
**What it does:**
- **Multiple formats** - JSON, CSV, RDF, GraphML
- **Database export** - Export to various databases
- **Streaming** - Handle large datasets
- **Filtering** - Export specific data subsets
**Use Cases:**
- Data sharing
- System integration
- Backup and restore
@@ -546,13 +510,11 @@ visualizer = GraphVisualizer()
visualizer.plot(kg, layout="force_directed")
```
**What it does:**
- **Graph visualization** - Interactive graph plots
- **Custom styling** - Tailored visual appearance
- **Analytics charts** - Statistics and metrics
- **Exploration tools** - Interactive data exploration
**Use Cases:**
- Data exploration
- Presentation
- Analysis
@@ -573,13 +535,11 @@ pipeline.add_step("build", GraphBuilder())
result = pipeline.run("data/")
```
**What it does:**
- **Workflow orchestration** - Coordinate multiple steps
- **Parallel processing** - Run steps concurrently
- **Progress tracking** - Monitor pipeline execution
- **Error handling** - Robust error management
**Use Cases:**
- Data processing
- Workflow automation
- Batch processing
@@ -587,7 +547,7 @@ result = pipeline.run("data/")
---
## New Features & Modules
## Additional Modules
### Change Management Module
**Version control and audit trails**
@@ -599,13 +559,11 @@ manager = TemporalVersionManager(storage_path="versions.db")
snapshot = manager.create_snapshot(kg, "v1.0", "user@example.com", "Initial version")
```
**What it does:**
- **Version control** - Track changes over time
- **Audit trails** - Complete change history
- **Data integrity** - SHA-256 checksums
- **Change comparison** - Detailed diff analysis
**Use Cases:**
- Knowledge graph versioning
- Compliance tracking
- Data governance
@@ -623,13 +581,11 @@ manager = ProvenanceManager()
manager.track_entity("entity_1", "document.pdf", "person")
```
**What it does:**
- **W3C PROV-O compliant** - Industry standard tracking
- **Complete lineage** - End-to-end traceability
- **Source attribution** - Track data origins
- **Integrity verification** - Tamper detection
**Use Cases:**
- Regulatory compliance
- Data provenance
- Audit trails
@@ -648,13 +604,11 @@ semantica = Semantica(config=Config())
result = semantica.process("data/")
```
**What it does:**
- **Framework orchestration** - Central coordination
- **Configuration management** - Settings and preferences
- **Lifecycle management** - Start/stop/restart
- **Plugin system** - Extensible architecture
**Use Cases:**
- Framework initialization
- Configuration management
- Plugin development
@@ -662,46 +616,18 @@ result = semantica.process("data/")
---
## Getting Started
## Common Module Chains
### Quick Start Example
```python
# Complete pipeline example
from semantica.ingest import FileIngestor
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
from semantica.pipeline import Pipeline
# Create pipeline
pipeline = Pipeline()
pipeline.add_step("ingest", FileIngestor())
pipeline.add_step("ner", NERExtractor())
pipeline.add_step("relations", RelationExtractor())
pipeline.add_step("build", GraphBuilder())
# Run pipeline
kg = pipeline.run("documents/")
print(f"Built graph with {len(kg['entities'])} entities")
```
### Choose Your Modules
**For Document Processing:**
- Ingest → Parse → Split → Semantic Extract → Knowledge Graph
**For Web Scraping:**
- Ingest (Web) → Normalize → Semantic Extract → Graph Store
**For AI Agents:**
- Context → LLM Providers → Reasoning → Export
**For Analytics:**
- Knowledge Graph → Graph Store → Visualization → Export
| Goal | Modules |
|------|---------|
| Document processing | Ingest → Parse → Split → Semantic Extract → KG |
| Web scraping | Ingest (Web) → Normalize → Semantic Extract → Graph Store |
| AI agents | Context → LLM Providers → Reasoning → Export |
| Analytics | KG → Graph Store → Visualization → Export |
---
## Module Reference
## Module Index
| Module | Purpose | Key Classes | Use Cases |
|--------|---------|-------------|-----------|
@@ -731,10 +657,9 @@ print(f"Built graph with {len(kg['entities'])} entities")
---
## Need Help?
## More
- **Documentation**: [Getting Started](getting-started.md)
- **Examples**: [Cookbook](cookbook.md)
- **Community**: [Discord](community.md)
- **Issues**: [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues)
- **Support**: [Contact Us](community.md)
- [Getting Started](getting-started.md)
- [Examples](examples.md)
- [Cookbook](cookbook.md)
- [API Reference](reference/core.md)
+96 -192
View File
@@ -1,246 +1,150 @@
# Quickstart
Get started with Semantica in 5 minutes. This guide will walk you through building your first knowledge graph.
Build your first knowledge graph in 5 minutes.
!!! tip "Before You Start"
Make sure you have Semantica installed. If not, follow the [Installation Guide](installation.md) first. This quickstart assumes basic Python knowledge.
!!! tip "Prerequisites"
Semantica installed (`pip install semantica`). If not, see the [Installation Guide](installation.md).
## Overview
---
## Pipeline Overview
```mermaid
flowchart LR
A[Install] --> B[Initialize]
B --> C[Load Data]
C --> D[Extract]
D --> E[Build Graph]
E --> F[Visualize]
style A fill:#e3f2fd
style F fill:#c8e6c9
A[Ingest] --> B[Parse]
B --> C[Extract]
C --> D[Build Graph]
D --> E[Visualize / Export]
```
## Step 1: Installation
---
If you haven't installed Semantica yet:
## Step 1 — Ingest
```bash
pip install semantica
```
Load documents from files, directories, or the web.
See the [Installation Guide](installation.md) for detailed instructions.
!!! note "Installation Options"
For production use, consider installing with optional dependencies for better performance: `pip install semantica[all]`. See the [Installation Guide](installation.md) for all options.
## Step 2: Your First Knowledge Graph
Building a knowledge graph involves these key steps:
1. **Ingest** your documents using `FileIngestor`
2. **Parse** documents to extract text using `DocumentParser` or `DoclingParser` (for enhanced layout support)
3. **Extract** entities and relationships using `NERExtractor` and `RelationExtractor`
4. **Build** the graph using `GraphBuilder`
5. **Generate** embeddings (optional) using `TextEmbedder`
**Quick Example:**
```python
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser, DoclingParser
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
# 1. Ingest document
ingestor = FileIngestor()
sources = ingestor.ingest("data/sample.pdf")
# 2. Parse (choose your parser)
# Option A: Standard parser
parser = DocumentParser()
parsed_content = parser.parse(sources[0])
# Option B: Enhanced Docling parser (recommended for complex tables)
# docling_parser = DoclingParser()
# parsed_content = docling_parser.parse(sources[0])
# 3. Extract entities and relations
ner = NERExtractor()
entities = ner.extract(parsed_content)
relations = RelationExtractor()
relationships = relations.extract(parsed_content, entities=entities)
# 4. Build graph
builder = GraphBuilder()
graph = builder.build(entities=entities, relationships=relationships)
print(f"Built knowledge graph with {len(graph.nodes)} nodes and {len(graph.edges)} edges")
```
**For complete step-by-step examples with detailed explanations, see:**
- **[Your First Knowledge Graph Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)**: Full tutorial with detailed explanations and expected outputs
- **Topics**: Entity extraction, relationship extraction, graph construction, visualization
- **Difficulty**: Beginner
- **Time**: 20-30 minutes
- **Use Cases**: Learning the basics, quick start
Supported formats: PDF, DOCX, HTML, JSON, CSV, Excel, PPTX, archives. For web content, use `WebIngestor`.
## Step 3: Extract Entities and Relationships
---
The semantic extraction step identifies named entities (people, organizations, locations) and relationships between them from your text.
## Step 2 — Parse
**What gets extracted:**
- **Entities**: People, organizations, locations, dates, and other named entities
- **Relationships**: Connections between entities (e.g., `founded_by`, `located_in`, `has_ceo`)
Extract structured text from raw documents.
**For detailed examples and different extraction methods, see:**
- **[Entity Extraction Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/05_Entity_Extraction.ipynb)**: Learn different NER methods and configurations
- **Topics**: Named entity recognition, entity types, confidence scores
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Understanding entity extraction options
```python
from semantica.parse import DocumentParser
- **[Relation Extraction Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/06_Relation_Extraction.ipynb)**: Learn to extract relationships between entities
- **Topics**: Relationship extraction, dependency parsing, semantic role labeling
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Building rich knowledge graphs with relationships
parser = DocumentParser()
parsed = parser.parse(sources[0])
```
## Step 4: Build Knowledge Graph from Multiple Sources
For complex layouts (tables, columns): use `DoclingParser` instead — it handles PDF tables and structured DOCX/PPTX better.
You can combine data from multiple sources (files, web, databases) to build a unified knowledge graph. The process involves:
---
1. **Ingest** from multiple sources using different ingestors
2. **Parse** all documents to extract text
3. **Extract** entities and relationships from each source
4. **Build** a unified graph with entity merging enabled
## Step 3 — Extract Entities and Relationships
**For complete examples with multiple sources, see:**
- **[Data Ingestion Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/02_Data_Ingestion.ipynb)**: Learn to ingest from files, web, feeds, streams, and databases
- **Topics**: File, web, feed, stream, database ingestion
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Loading data from various sources
```python
from semantica.semantic_extract import NERExtractor, RelationExtractor
- **[Multi-Source Data Integration Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb)**: Advanced patterns for integrating multiple data sources
- **Topics**: Multi-source integration, entity resolution, conflict handling
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Building knowledge graphs from diverse data sources
ner = NERExtractor()
entities = ner.extract(parsed)
## Step 5: Visualize Your Knowledge Graph
rel = RelationExtractor()
relationships = rel.extract(parsed, entities=entities)
```
Visualization helps you understand and explore your knowledge graph structure. Semantica supports multiple visualization formats including interactive HTML, static images, and export formats.
Each entity gets a type, confidence score, and source reference. Relationships are extracted as typed triplets: `(subject, predicate, object)`.
**For detailed visualization examples, see:**
- **[Visualization Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/16_Visualization.ipynb)**: Learn to create interactive and static visualizations
- **Topics**: Network graphs, interactive HTML, static images, export formats
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Exploring graph structure, presentations, analysis
---
- **[Complete Visualization Suite Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/03_Complete_Visualization_Suite.ipynb)**: Advanced visualization techniques
- **Topics**: Custom layouts, filtering, styling, multiple graph types
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Production visualizations, custom dashboards
## Step 4 — Build the Knowledge Graph
## Step 6: Export Your Knowledge Graph
```python
from semantica.kg import GraphBuilder
Export your knowledge graph to various formats for integration with other systems or tools. Semantica supports RDF, JSON, CSV, OWL, GraphML, and more.
builder = GraphBuilder(merge_entities=True)
graph = builder.build(entities=entities, relationships=relationships)
**Supported export formats:**
- **RDF**: Turtle, RDF/XML, JSON-LD, N-Triples
- **JSON**: Standard JSON, JSON-LD, Cytoscape.js format
- **CSV**: Node and edge lists for spreadsheet tools
- **OWL**: OWL/XML and Turtle for ontologies
- **Graph Formats**: GraphML, GEXF, DOT for visualization tools
print(f"{len(graph.nodes)} nodes, {len(graph.edges)} edges")
```
**For detailed export examples, see:**
- **[Export Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/15_Export.ipynb)**: Learn to export to all supported formats
- **Topics**: RDF, JSON, CSV, OWL, GraphML export
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Data integration, sharing knowledge graphs
`merge_entities=True` resolves duplicates across sources automatically.
- **[Multi-Format Export Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/05_Multi_Format_Export.ipynb)**: Advanced export patterns
- **Topics**: Batch export, custom formats, format conversion
- **Difficulty**: Intermediate
- **Time**: 30-45 minutes
- **Use Cases**: Production exports, format migration
---
## Step 5 — Visualize
```python
from semantica.visualization import GraphVisualizer
viz = GraphVisualizer()
viz.visualize(graph, output="graph.html") # interactive HTML
```
---
## Step 6 — Export
```python
from semantica.export import RDFExporter
exporter = RDFExporter()
rdf = exporter.export_to_rdf(graph, format="turtle")
```
Other formats: `"json-ld"`, `"nt"`, `"xml"`, Parquet, ArangoDB AQL. See [Export Reference](reference/export.md).
---
## Common Patterns
### Pattern 1: Process Text Directly
### Process text directly (no file)
You can process text directly without file ingestion. This is useful when you already have text content in memory.
```python
from semantica.semantic_extract import NERExtractor
**For examples, see:**
- **[Entity Extraction Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/05_Entity_Extraction.ipynb)**: Processing text directly
- **[Building Knowledge Graphs Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb)**: Graph construction from text
ner = NERExtractor()
entities = ner.extract("Apple Inc. was founded by Steve Jobs in 1976.")
```
### Pattern 2: Custom Entity Extraction
### Incremental build from multiple sources
Configure entity extraction with different methods (ML models, LLMs) and parameters for your specific needs.
```python
from semantica.kg import GraphBuilder
**For examples, see:**
- **[Entity Extraction Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/05_Entity_Extraction.ipynb)**: Different extraction methods and configurations
- **[Advanced Extraction Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/01_Advanced_Extraction.ipynb)**: Advanced extraction patterns
all_entities, all_rels = [], []
for doc in parsed_docs:
all_entities.extend(ner.extract(doc))
all_rels.extend(rel.extract(doc, entities=all_entities))
### Pattern 3: Incremental Building
graph = GraphBuilder(merge_entities=True).build(
entities=all_entities, relationships=all_rels
)
```
Build knowledge graphs incrementally from multiple sources and merge them together.
**For examples, see:**
- **[Building Knowledge Graphs Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb)**: Graph construction and merging
- **[Multi-Source Data Integration Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb)**: Advanced integration patterns
## Next Steps
Now that you've built your first knowledge graph:
1. **[Explore Examples](examples.md)** - See more advanced use cases
2. **[API Reference](reference/core.md)** - Learn about all available methods
3. **[Cookbook](cookbook.md)** - Interactive Jupyter notebooks
4. **[Full Documentation](https://github.com/Hawksight-AI/semantica/blob/main/README.md)** - Comprehensive guide
### 🍳 Recommended Cookbook Tutorials
Continue learning with these interactive tutorials:
- **[Welcome to Semantica](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)**: Comprehensive introduction to all modules
- **Topics**: Framework overview, all modules, architecture, configuration
- **Difficulty**: Beginner
- **Time**: 30-45 minutes
- **Use Cases**: Understanding the complete framework
- **[Your First Knowledge Graph](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)**: Build your first knowledge graph
- **Topics**: Entity extraction, relationship extraction, graph construction, visualization
- **Difficulty**: Beginner
- **Time**: 20-30 minutes
- **Use Cases**: Hands-on practice with complete workflow
- **[Data Ingestion](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/02_Data_Ingestion.ipynb)**: Learn to ingest from multiple sources
- **Topics**: File, web, feed, stream, database ingestion
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Loading data from various sources
- **[Document Parsing](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/03_Document_Parsing.ipynb)**: Parse various document formats
- **Topics**: PDF, DOCX, HTML, JSON parsing
- **Difficulty**: Beginner
- **Time**: 15-20 minutes
- **Use Cases**: Extracting text from different file formats
---
## Troubleshooting
### Common Issues
| Problem | Fix |
|---------|-----|
| No entities extracted | Check the document has machine-readable text (not just scanned images) |
| Slow processing | Process in chunks; use GPU acceleration (`pip install semantica[gpu]`) |
| Memory errors | Reduce batch size or switch to a persistent graph backend |
**Issue**: No entities extracted
- **Solution**: Check that your document contains text content. PDFs with images only won't work without OCR.
---
**Issue**: Slow processing
- **Solution**: For large documents, consider processing in chunks or using GPU acceleration.
## Next Steps
**Issue**: Memory errors
- **Solution**: Process documents one at a time or reduce batch sizes.
Need help? Check the [Installation Troubleshooting](installation.md#troubleshooting) or [GitHub Issues](https://github.com/Hawksight-AI/semantica/issues).
- [Core Concepts](concepts.md) — understand how knowledge graphs and reasoning work
- [Modules Guide](modules.md) — every module explained
- [Use Cases](use-cases.md) — domain-specific examples
- [Cookbook](cookbook.md) — interactive Jupyter notebooks for each step
+129 -16
View File
@@ -58,16 +58,14 @@ The Semantica change management module provides enterprise-grade version control
</div>
### Key Features
- **Enterprise Version Control** — Complete snapshot management with SHA-256 integrity verification
- **Dual Storage Backends** — InMemory (development) and SQLite (production) with ACID guarantees
- **Knowledge Graph Versioning** — Entity and relationship-level change tracking with detailed diffs
- **Ontology Versioning** — Structural change tracking for classes, properties, and axioms
- **Audit Trail Compliance** — Complete change logs with author attribution and timestamps
- **Data Integrity** — SHA-256 checksums for tamper detection and verification
- ✅ **Change Comparison** — Detailed diff algorithms for entities, relationships, and ontology structures
- ✅ **Backward Compatibility** — Legacy support for existing ontology version management
- **Enterprise Version Control** — Complete snapshot management with SHA-256 integrity verification
- **Dual Storage Backends** — InMemory (development) and SQLite (production) with ACID guarantees
- **Knowledge Graph Versioning** — Entity and relationship-level change tracking with detailed diffs
- **Ontology Versioning** — Structural change tracking for classes, properties, and axioms
- **Audit Trail Compliance** — Complete change logs with author attribution and timestamps
- **Data Integrity** — SHA-256 checksums for tamper detection and verification
- **Change Comparison** — Detailed diff algorithms for entities, relationships, and ontology structures
- **Backward Compatibility** — Legacy support for existing ontology version management
---
@@ -121,24 +119,21 @@ class ChangeLogEntry:
### Storage Backends
#### InMemoryVersionStorage
Fast, volatile storage for development and testing.
**InMemoryVersionStorage** — Fast, volatile storage for development and testing.
```python
from semantica.change_management import InMemoryVersionStorage
storage = InMemoryVersionStorage()
```
#### SQLiteVersionStorage
Persistent storage with ACID guarantees for production.
**SQLiteVersionStorage** — Persistent storage with ACID guarantees for production.
```python
from semantica.change_management import SQLiteVersionStorage
storage = SQLiteVersionStorage("versions.db")
```
#### VersionStorage (Abstract)
Base interface for custom storage implementations.
**VersionStorage (Abstract)** — Base interface for custom storage implementations.
**Core Methods:**
- `save(snapshot)` - Store version snapshot
@@ -245,6 +240,50 @@ print(f"Axioms modified: {diff['axioms_modified']}")
---
## Incremental / Delta processing
For large-scale knowledge graphs, reprocessing the entire dataset on every update is computationally expensive.
Semantica supports **Delta-Aware Pipelines**, allowing you to compute the exact differences (added and removed triples)
between the two graph snapshots and run validation, enrichment, or export jobs *only* on the changes.
**Delta Pipeline Example**
```python
from semantica.change_management import TemporalVersionManager
from semantica.pipeline import PipelineBuilder, ExecutionEngine
# a. Initialize your managers
version_manager = TemporalVersionManager(store_graph="kg_version.db")
triplet_store = get_my_triplet_store()
# b. Build a delta-aware pipeline
builder = PipelineBuilder()
builder.add_step(
step_name="validate_changes",
step_type="validation",
handler=my_validation_handler,
delta_mode=True, # Enables incremental processing
base_version_id="v1.0",
target_version_id="v1.1",
)
pipeline = builder.build("incremental_nightly_job")
# c. Execute the pipeline
engine = ExecutionEngine()
# The engine dynamically intercepts the flow, computes the delta on the
# database backend, and passes ONLY the changed triples to the handler.
result = engine.execute_pipeline(
pipeline,
data={}, # Is ignored in delta mode
version_manager=version_manager,
triplet_store=triplet_store
)
```
---
## Data Integrity
### compute_checksum
@@ -348,3 +387,77 @@ prod_manager = TemporalVersionManager(
for version in prod_manager.list_versions():
print(f"{version['timestamp']}: {version['description']} by {version['author']}")
```
---
## Ontology Diff & Migration
Semantica allows you to treat ontology schema changes with the same rigor as database migrations. By comparing two versions, you can generate a machine-readable diff and a structured impact report to catch breaking changes before they reach production.
**Comparing Versions**
The `OntologyEngine` provides a high-level API to orchestrate the comparison of two schema versions.
```python
from semantica.ontology.engine import OntologyEngine
engine = OntologyEngine()
# Generate a migration impact report between v1.0 and v2.0
report = engine.compare_versions(
base_id="v1.0",
target_id="v2.0"
)
print(f"Total changes detected: {report['summary']['total_changes']}")
```
---
**Report Format**
The `compare_versions` method returns a dictionary with a machine-readable diff and a human-readable impact analysis:
```json
{
"summary": {
"total_changes": 12
},
"impact_classification": {
"breaking": [
{
"entity_uri": "http://example.org/Person",
"severity": "critical",
"description": "Class Person removed.",
"mitigation": "Migrate orphaned instances."
}
],
"potentially_breaking": [],
"safe": []
},
"recommendations": [
"[BREAKING] Schedule downtime or validate existing data."
],
"diff": {
"added_classes": [],
"removed_classes": [],
"changed_classes": [],
"added_properties": [],
"removed_properties": [],
"changed_properties": []
},
"validation_results": {
"valid": true,
"consistent": true,
"satisfiable": true,
"errors": [],
"warnings": []
},
"graph_validation": {
"valid": false,
"errors": ["Instance data violates new domain constraint"],
"warnings": []
}
}
```
+1 -1
View File
@@ -275,7 +275,7 @@ print(f"Python importance score: {importance.get('degree', 0)}")
|--------|-------------|------------|
| `add_node(node_id, node_type, properties)` | Add concepts to remember | Build knowledge base |
| `add_edge(source, target, relation)` | Connect related concepts | Show relationships |
| `add_decision(category, scenario, reasoning, outcome, confidence, ...)` | Record decisions | Track choices and learn |
| `add_decision(decision)` or `add_decision(category, scenario, reasoning, outcome, ...)` | Record decisions | Track choices and learn |
| `add_decision_simple(category, scenario, reasoning, outcome, confidence, ...)` | Easy decision recording | Quick decision tracking |
| `find_precedents(decision_id, limit)` | Find precedents by ID | Get connected decisions |
| `find_precedents_by_scenario(scenario, category, ...)` | Find similar decisions | Make consistent choices |
+159 -1
View File
@@ -250,6 +250,56 @@ ontology:
---
## Ontology Alignment
Semantica supports mapping and connecting different ontologies to unify data across systems, standards, and domains. This enables cross-system interoperability, allowing a single semantic layer to span multiple standards (e.g., internal models and industry standards).
Alignments are represented using standard RDF predicates such as `owl:equivalentClass`, `owl:equivalentProperty`, and `skos:exactMatch`.
### Creating and Managing Alignments
You can create and query alignments programmatically using the `OntologyEngine`:
```python
from semantica.ontology.engine import OntologyEngine
from semantica.triplet_store.triplet_store import TripletStore
# Setup the store and engine (using Blazegraph as an example)
my_triplet_store = TripletStore(backend="blazegraph")
engine = OntologyEngine(store=my_triplet_store)
# Create an alignment between an internal class and a standard schema
engine.create_alignment(
source_uri="http://internal.org/ontology/Employee",
target_uri="http://schema.org/Person",
predicate="http://www.w3.org/2002/07/owl#equivalentClass"
)
# Retrieve all bidirectional alignments for a specific entity
alignments = engine.get_alignments("http://internal.org/ontology/Employee")
```
### Automated Alignment Suggestions
When importing or merging external ontologies, the ReuseManager can automatically suggest alignments based on heuristic matching (such as identical labels with differing URIs).
```python
from semantica.ontology.reuse_manager import ReuseManager
manager = ReuseManager()
# Merge ontologies and auto-compute alignment suggestions
merged_ontology = manager.merge_ontology_data(
target=internal_ontology,
source=industry_ontology,
compute_alignments=True
)
# Suggestions are stored in merged_ontology["suggested_alignments"]
```
For executing SPARQL queries that utilize these alignments to retrieve cross-ontology results, see the [Triplet Store Alignment-Aware Queries](triplet_store.md#alignment-aware-queries)
## Integration Examples
### Schema-First Knowledge Graph
@@ -285,6 +335,114 @@ else:
---
## SKOS Vocabulary Management
Semantica supports [SKOS (Simple Knowledge Organization System)](https://www.w3.org/TR/skos-reference/) vocabularies as first-class semantic assets. SKOS triples are stored in the existing RDF triplet store and queried through the `OntologyEngine` — no additional packages are required.
### Concepts and data model
| SKOS element | RDF type / predicate |
|---|---|
| ConceptScheme | `skos:ConceptScheme` |
| Concept | `skos:Concept` |
| Preferred label | `skos:prefLabel` |
| Alternative label | `skos:altLabel` |
| Broader concept | `skos:broader` |
| Narrower concept | `skos:narrower` |
| Related concept | `skos:related` |
| Human definition | `skos:definition` |
| Notation / code | `skos:notation` |
### Importing a SKOS vocabulary
Use `TripletStore.add_skos_concept()` to load individual concepts. The method automatically asserts the parent `skos:ConceptScheme` triple the first time any concept for that scheme is added.
```python
from semantica.triplet_store import TripletStore
store = TripletStore(backend="blazegraph", endpoint="http://localhost:9999/blazegraph")
SCHEME = "https://vocab.example.org/colours"
store.add_skos_concept(
concept_uri="https://vocab.example.org/colours/red",
scheme_uri=SCHEME,
pref_label="Red",
alt_labels=["Crimson", "Rouge"],
broader=["https://vocab.example.org/colours/warm"],
definition="The colour at the long-wavelength end of the visible spectrum.",
notation="RED",
)
store.add_skos_concept(
concept_uri="https://vocab.example.org/colours/blue",
scheme_uri=SCHEME,
pref_label="Blue",
alt_labels=["Azure", "Cerulean"],
)
```
For bulk ingestion of an existing SKOS/Turtle file use `TripletStore.add_triplets()` after parsing the file with [rdflib](https://rdflib.readthedocs.io/):
```python
import rdflib
from semantica.semantic_extract.triplet_extractor import Triplet
g = rdflib.Graph()
g.parse("my_vocabulary.ttl", format="turtle")
triplets = [
Triplet(subject=str(s), predicate=str(p), object=str(o))
for s, p, o in g
]
store.add_triplets(triplets)
```
### Listing and searching concepts
Once a vocabulary is loaded, use `OntologyEngine` to browse and search it:
```python
from semantica.ontology import OntologyEngine
engine = OntologyEngine(store=store)
# 1. List all ConceptSchemes in the store
vocabularies = engine.list_vocabularies()
# [{"uri": "https://vocab.example.org/colours", "label": "Colours"}, ...]
# 2. List every concept in a specific scheme
concepts = engine.list_concepts("https://vocab.example.org/colours")
# [{"uri": "...", "pref_label": "Red", "alt_labels": ["Crimson", "Rouge"]}, ...]
# 3. Case-insensitive substring search across prefLabel and altLabel
results = engine.search_concepts("crimson")
# [{"uri": "https://vocab.example.org/colours/red", "label": "Crimson"}]
# 4. Restrict search to one scheme
results = engine.search_concepts("azure", scheme_uri="https://vocab.example.org/colours")
```
### Building SKOS URIs with NamespaceManager
`NamespaceManager` provides helpers for constructing well-formed SKOS IRIs:
```python
from semantica.ontology import NamespaceManager
nm = NamespaceManager(base_uri="https://vocab.example.org/")
# Full SKOS predicate URI
nm.get_skos_uri("prefLabel")
# "http://www.w3.org/2004/02/skos/core#prefLabel"
# Slug-based ConceptScheme URI anchored at the base
nm.build_concept_scheme_uri("ISO 3166 Countries")
# "https://vocab.example.org/vocab/iso-3166-countries"
```
---
## Best Practices
1. **Reuse Standard Ontologies**: Don't reinvent `Person` or `Organization`; import FOAF or Schema.org using `ReuseManager`.
@@ -312,4 +470,4 @@ Interactive tutorials to learn ontology generation and management:
- **[Unstructured to Ontology](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/advanced/12_Unstructured_to_Ontology.ipynb)**: Generate ontologies automatically from unstructured data
- **Topics**: Automatic ontology generation, 6-stage pipeline, OWL validation
- **Difficulty**: Advanced
- **Use Cases**: Domain modeling, automatic schema generation
- **Use Cases**: Domain modeling, automatic schema generation
+42 -1
View File
@@ -131,7 +131,7 @@ The **Pipeline Module** provides a robust orchestration engine for building, exe
### Types
- `Pipeline` — Pipeline definition dataclass
- `PipelineStep` — Pipeline step definition dataclass
- `PipelineStep` — Pipeline step definition dataclass, Supports `delta_mode` (bool), `base_version_id` (str), and `target_version_id` (str) for incremental processing.
- `StepStatus` — Enum: `pending`, `running`, `completed`, `failed`, `skipped`
- `ExecutionResult` — Execution result dataclass
- `PipelineStatus` — Enum: `pending`, `running`, `paused`, `completed`, `failed`, `stopped`
@@ -405,6 +405,47 @@ result = engine.execute_pipeline(pipeline, data={"path": "document.pdf"})
---
### Incremental / Delta-Aware Pipeline
Use `delta_mode` to process only the differences between two graph versions, drastically reducing compute costs for large datasets.
```python
from semantica.pipeline import PipelineBuilder, ExecutionEngine
builder = (
PipelineBuilder()
# Adding delta_mode=True tells the execution engine to intercept this step,
# compute the diff between v1 and v2, and pass ONLY the delta payload to the handler.
.add_step(
"validate_diff",
"validation",
delta_mode=True,
base_version_id="v1",
target_version_id="v2",
handler=diff_validator
)
.add_step(
"alert_on_removals",
"alerting",
dependencies=["validate_diff"],
handler=alert_handler
)
)
pipeline = builder.build(name="IncrementalJob")
engine = ExecutionEngine()
# Execution requires version_manager and triplet_store injected via options
# so the engine can resolve URIs and compute the graph differences natively.
result = engine.execute_pipeline(
pipeline,
version_manager=my_version_manager,
triplet_store=my_triplet_store
)
```
---
## Best Practices
1. **Idempotency**: Ensure steps are idempotent (can be run multiple times without side effects) to support retries.
+1 -1
View File
@@ -734,4 +734,4 @@ MIT License - See [LICENSE](../../LICENSE) for details.
## Support
For issues or questions, please open an issue on GitHub or join our [Discord](https://discord.gg/N7WmAuDH).
For issues or questions, please open an issue on GitHub or join our [Discord](https://discord.gg/sV34vps5hH).
+78
View File
@@ -152,6 +152,8 @@ SPARQL query execution and optimization engine.
|--------|-------------|-----------|
| `execute(query)` | Execute SPARQL query | Query execution |
| `optimize(query)` | Optimize SPARQL query | Query rewriting |
| `expand_entity_uri(uri, store, ...)` | Expand aligned entity URIs | Bidirectional SPARQL lookup |
| `build_values_clause(var, uris)` | Generate VALUES clause | String formatting |
---
@@ -204,3 +206,79 @@ LIMIT 10
"""
results = store.execute_query(query)
```
### Named Graph Partitions
Use named graphs to partition RDF data inside one store while keeping backward compatibility.
```python
from semantica.semantic_extract.triplet_extractor import Triplet
# Write into a specific graph partition
store.add_triplet(
Triplet("http://entity/1", "http://relation/type", "http://TypeA"),
graph="http://example.org/graphs/partition-a",
)
# Query only one graph as default dataset
result_a = store.execute_query(
"SELECT ?s ?p ?o WHERE { ?s ?p ?o }",
graph="http://example.org/graphs/partition-a",
)
# Query multiple named graphs (use GRAPH pattern in WHERE)
result_multi = store.execute_query(
"""
SELECT ?g ?s ?p ?o WHERE {
GRAPH ?g { ?s ?p ?o }
}
""",
graphs=[
"http://example.org/graphs/partition-a",
"http://example.org/graphs/partition-b",
],
)
```
Notes:
- `graph` injects `FROM <...>` before `WHERE`.
- `graphs` injects `FROM NAMED <...>` before `WHERE`.
- If not provided, existing behavior is unchanged.
### Alignment-Aware Queries
In complex enterprise environments with multiple data sources, you may want queries to seamlessly retrieve instances across aligned classes. For example, retrieving all http://schema.org/Person instances when querying for your internal http://internal.org/ontology/Employee class.
The QueryEngine provides helper methods to expand entity URIs based on stored alignments (e.g., owl:equivalentClass, owl:sameAs, skos:exactMatch) and safely inject them into your queries using SPARQL VALUES clauses.
Expanding URIs in Queries
You can expand a URI and build an alignment-aware query dynamically:
```python
from semantica.triplet_store.query_engine import QueryEngine
engine = QueryEngine()
# i) Expand the base URI to include all aligned equivalents
expanded_uris = engine.expand_entity_uri(
entity_uri="[http://internal.org/ontology/Employee](http://internal.org/ontology/Employee)",
store_backend=store_backend,
use_alignments=True
)
# ii) Build a SPARQL VALUES clause
values_clause = engine.build_values_clause("entity_class", expanded_uris)
# Result: VALUES ?entity_class { [http://internal.org/ontology/Employee](http://internal.org/ontology/Employee) [http://schema.org/Person](http://schema.org/Person) }
# iii) Inject the clause into your query template
query = f"""
SELECT ?instance ?name WHERE {{
{values_clause}
?instance a ?entity_class .
?instance [http://schema.org/name](http://schema.org/name) ?name .
}}
"""
# Execute the query to retrieve results across all aligned ontologies
results = engine.execute_query(query, store_backend)
```
+70 -155
View File
@@ -1,210 +1,125 @@
# Use Cases
Semantica is designed to solve complex data challenges across various domains. This guide explores common use cases and how to implement them.
!!! info "About This Guide"
This guide provides detailed implementation guides for real-world use cases, complete with code examples, prerequisites, and step-by-step instructions.
Real-world applications of Semantica across domains, with linked cookbook notebooks for each.
---
## Use Case Comparison
## Overview
| Use Case | Difficulty | Time | Domain | Key Features | Cookbook |
| :-------------------------------- | :------------ | :---------- | :---------- | :---------------------------------------------- | :------------------------------------------ |
| **Biomedical Knowledge Graphs** | Intermediate | 1-2 hours | Healthcare | Gene-protein-disease relationships | Drug Discovery, Genomic Variant Analysis |
| **Financial Data Integration** | Intermediate | 1-2 hours | Finance | MCP integration, real-time data | Financial Data Integration MCP |
| **Fraud Detection** | Advanced | 2-3 hours | Finance | Temporal graphs, pattern detection | Fraud Detection |
| **Blockchain Analytics** | Intermediate | 1-2 hours | Finance | Transaction tracing, DeFi intelligence | DeFi Protocol Intelligence, Transaction Network |
| **Cybersecurity Threat Intelligence**| Advanced | 2-3 hours | Security | Threat mapping, anomaly detection | Real-Time Anomaly Detection, Threat Intelligence |
| **Intelligence Analysis** | Intermediate | 1-2 hours | Security | Criminal networks, OSINT analysis | Criminal Network Analysis, Intelligence Orchestrator |
| **Supply Chain Optimization** | Intermediate | 1-2 hours | Industry | Data integration, route optimization | Supply Chain Data Integration |
| **Renewable Energy Management** | Intermediate | 1-2 hours | Energy | Energy market analysis, optimization | Energy Market Analysis |
| **GraphRAG** | Advanced | 1-2 hours | AI | Enhanced RAG with knowledge graphs | GraphRAG Complete, RAG vs GraphRAG |
| Use Case | Domain | Difficulty | Estimated Time |
|----------|--------|------------|----------------|
| Biomedical Knowledge Graphs | Healthcare | Intermediate | 12 hours |
| Financial Data Integration | Finance | Intermediate | 12 hours |
| Fraud Detection | Finance | Advanced | 23 hours |
| Blockchain Analytics | Finance | Intermediate | 12 hours |
| Cybersecurity Threat Intelligence | Security | Advanced | 23 hours |
| Criminal Network Analysis | Security / Intelligence | Intermediate | 12 hours |
| Intelligence Analysis Orchestrator | Intelligence | Intermediate | 12 hours |
| Supply Chain Optimization | Operations | Intermediate | 12 hours |
| Renewable Energy Management | Energy | Intermediate | 12 hours |
| GraphRAG | AI | Advanced | 12 hours |
**Difficulty Levels**:
- **Beginner**: Basic Semantica knowledge required
- **Intermediate**: Some domain knowledge helpful
- **Advanced**: Requires domain expertise and advanced Semantica features
**Difficulty levels**:
- **Beginner** — basic Semantica knowledge only
- **Intermediate** — some domain knowledge helpful
- **Advanced** — domain expertise + advanced Semantica features
---
## Research & Science
<div class="grid cards" markdown>
### Biomedical Knowledge Graphs
- :material-dna: **Biomedical Knowledge Graphs**
---
Accelerate drug discovery and understand disease pathways by connecting genes, proteins, drugs, and diseases.
**Goal**: Connect genes, proteins, drugs, and diseases from scientific literature and databases.
**Difficulty**: Intermediate
[:material-arrow-right: Drug Discovery Pipeline](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/biomedical/01_Drug_Discovery_Pipeline.ipynb)
[:material-arrow-right: Genomic Variant Analysis](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/biomedical/02_Genomic_Variant_Analysis.ipynb)
Connect genes, proteins, drugs, and diseases from scientific literature and databases to accelerate drug discovery and understand disease pathways.
</div>
### Biomedical Knowledge Graphs Implementation
**Prerequisites**:
- Domain knowledge of biomedical concepts
- Access to biomedical literature/databases
**Implementation Guides:**
- **[Drug Discovery Pipeline Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/biomedical/01_Drug_Discovery_Pipeline.ipynb)**: Build knowledge graphs from PubMed RSS feeds
- **Topics**: PubMed RSS ingestion, entity-aware chunking, GraphRAG, vector similarity search
- **Difficulty**: Intermediate
- **Time**: 1-2 hours
- **Use Cases**: Drug discovery, biomedical research
- **[Genomic Variant Analysis Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/biomedical/02_Genomic_Variant_Analysis.ipynb)**: Analyze genomic variants using temporal knowledge graphs
- **Topics**: bioRxiv RSS, temporal KGs, deduplication, pathway analysis
- **Difficulty**: Intermediate
- **Time**: 1-2 hours
- **Use Cases**: Genomic research, variant analysis
**Cookbooks**:
- [Drug Discovery Pipeline](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/biomedical/01_Drug_Discovery_Pipeline.ipynb) — PubMed RSS ingestion, entity-aware chunking, GraphRAG, vector similarity search
- [Genomic Variant Analysis](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/biomedical/02_Genomic_Variant_Analysis.ipynb) — bioRxiv RSS, temporal KGs, deduplication, pathway analysis
---
## Finance & Trading
<div class="grid cards" markdown>
### Financial Data Integration
- :material-finance: **Financial Data Integration**
---
Integrate financial data from multiple sources using MCP servers and real-time ingestion.
**Goal**: Connect Alpha Vantage API, MCP servers, seed data, and real-time ingestion for comprehensive financial analysis.
[:material-arrow-right: View Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/finance/01_Financial_Data_Integration_MCP.ipynb)
Unify financial data from APIs, MCP servers, and real-time streams into a single queryable knowledge graph.
- :material-shield-alert: **Fraud Detection**
---
Detect complex fraud rings using temporal knowledge graphs and pattern detection.
**Goal**: Build a graph of Users, Devices, IP Addresses, and Transactions to find cycles and detect fraud patterns.
[:material-arrow-right: View Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/finance/02_Fraud_Detection.ipynb)
**Cookbook**: [Financial Data Integration (MCP)](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/finance/01_Financial_Data_Integration_MCP.ipynb) — Alpha Vantage API, MCP servers, seed data, real-time ingestion
- :material-bitcoin: **Blockchain Analytics**
---
Analyze DeFi protocols and transaction networks for intelligence and fraud detection.
**Goal**: Map transaction flows between wallets and exchanges, analyze DeFi protocols, and detect illicit activity.
[:material-arrow-right: DeFi Protocol Intelligence](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/blockchain/01_DeFi_Protocol_Intelligence.ipynb)
[:material-arrow-right: Transaction Network Analysis](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/blockchain/02_Transaction_Network_Analysis.ipynb)
### Fraud Detection
</div>
Detect complex fraud rings using temporal graphs and pattern detection over transaction, device, and user data.
---
**Cookbook**: [Fraud Detection](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/finance/02_Fraud_Detection.ipynb) — temporal KGs, cycle detection, fraud pattern analysis
### Blockchain Analytics
Map transaction flows, analyze DeFi protocols, and detect illicit activity across wallet and exchange networks.
**Cookbooks**:
- [DeFi Protocol Intelligence](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/blockchain/01_DeFi_Protocol_Intelligence.ipynb)
- [Transaction Network Analysis](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/blockchain/02_Transaction_Network_Analysis.ipynb)
---
## Security & Intelligence
<div class="grid cards" markdown>
### Cybersecurity Threat Intelligence
- :material-shield-lock: **Cybersecurity Threat Intelligence**
---
Proactively identify and mitigate cyber threats using real-time anomaly detection and threat intelligence.
**Goal**: Ingest threat feeds (CVE databases, security RSS), detect anomalies in streaming data, and build threat intelligence knowledge graphs.
[:material-arrow-right: Real-Time Anomaly Detection](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/cybersecurity/01_Real_Time_Anomaly_Detection.ipynb)
[:material-arrow-right: Threat Intelligence Hybrid RAG](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/cybersecurity/02_Threat_Intelligence_Hybrid_RAG.ipynb)
Ingest threat feeds (CVE databases, security RSS), detect anomalies in streaming data, and build threat intelligence knowledge graphs for proactive defense.
- :material-account-network: **Criminal Network Analysis**
---
Analyze criminal networks to identify key players, communities, and suspicious patterns using OSINT RSS feeds, deduplication, and network centrality analysis.
**Goal**: Build knowledge graphs from police reports, court records, and surveillance data.
[:material-arrow-right: View Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/intelligence/01_Criminal_Network_Analysis.ipynb)
**Cookbooks**:
- [Real-Time Anomaly Detection](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/cybersecurity/01_Real_Time_Anomaly_Detection.ipynb)
- [Threat Intelligence Hybrid RAG](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/cybersecurity/02_Threat_Intelligence_Hybrid_RAG.ipynb)
- :material-file-search: **Intelligence Analysis Orchestrator Worker**
---
Comprehensive intelligence analysis using pipeline orchestrator with multiple RSS feeds, conflict detection, and multi-source integration.
**Goal**: Process multiple intelligence sources in parallel using orchestrator-worker pattern.
[:material-arrow-right: View Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/intelligence/02_Intelligence_Analysis_Orchestrator_Worker.ipynb)
### Criminal Network Analysis
</div>
Build knowledge graphs from police reports, court records, and OSINT feeds to identify key players, communities, and suspicious patterns using network centrality analysis.
**Cookbook**: [Criminal Network Analysis](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/intelligence/01_Criminal_Network_Analysis.ipynb)
### Intelligence Analysis Orchestrator
Process multiple intelligence sources in parallel using an orchestrator-worker pipeline pattern with multi-source conflict detection and integration.
**Cookbook**: [Intelligence Analysis Orchestrator-Worker](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/intelligence/02_Intelligence_Analysis_Orchestrator_Worker.ipynb)
---
## Industry & Operations
<div class="grid cards" markdown>
### Supply Chain Optimization
- :material-truck-delivery: **Supply Chain Optimization**
---
Visualize and optimize complex global supply chains.
**Goal**: Map suppliers, logistics routes, and inventory levels to identify bottlenecks.
[:material-arrow-right: View Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/supply_chain/01_Supply_Chain_Data_Integration.ipynb)
Map suppliers, logistics routes, and inventory levels to identify bottlenecks and optimize global supply chains.
- :material-wind-turbine: **Renewable Energy Management**
---
Optimize grid operations and asset maintenance.
**Goal**: Connect sensor data, weather forecasts, and maintenance logs to predict failures.
[:material-arrow-right: View Cookbook](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/renewable_energy/01_Energy_Market_Analysis.ipynb)
**Cookbook**: [Supply Chain Data Integration](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/supply_chain/01_Supply_Chain_Data_Integration.ipynb)
</div>
### Renewable Energy Management
Connect sensor data, weather forecasts, and maintenance logs to predict equipment failures and optimize grid operations.
**Cookbook**: [Energy Market Analysis](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/renewable_energy/01_Energy_Market_Analysis.ipynb)
---
## Advanced AI Patterns
<div class="grid cards" markdown>
### GraphRAG (Graph-Augmented Generation)
- :material-robot: **Graph-Augmented Generation (GraphRAG)**
---
Enhance LLM responses with structured ground truth using knowledge graphs.
**Goal**: Use the knowledge graph to retrieve precise context for RAG applications with hybrid retrieval and logical inference.
[:material-arrow-right: GraphRAG Complete](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb)
[:material-scale-balance: RAG vs GraphRAG Comparison](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb)
Use knowledge graphs to retrieve precise, structured context for LLM responses — with hybrid retrieval, logical inference, and source attribution.
</div>
---
---
## Summary
This guide covered use cases across multiple domains with corresponding cookbooks:
- **Research & Science**: Biomedical knowledge graphs (Drug Discovery, Genomic Variant Analysis)
- **Finance & Trading**: Financial data integration, fraud detection, blockchain analytics
- **Security & Intelligence**: Cybersecurity threat intelligence, criminal network analysis, intelligence orchestration
- **Industry**: Supply chain optimization, renewable energy management
- **AI Applications**: GraphRAG (Complete implementation and comparison)
**Cookbooks**:
- [GraphRAG Complete](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/01_GraphRAG_Complete.ipynb) — production-ready implementation
- [RAG vs. GraphRAG Comparison](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/use_cases/advanced_rag/02_RAG_vs_GraphRAG_Comparison.ipynb) — side-by-side comparison
---
## Next Steps
- **[Examples](examples.md)** - More detailed code examples
- **[Modules Guide](modules.md)** - Learn about available modules
- **[Cookbook](cookbook.md)** - Interactive Jupyter notebooks
- **[API Reference](reference/core.md)** - Complete API documentation
---
!!! info "Contribute"
Have a use case to add? [Contribute on GitHub](https://github.com/Hawksight-AI/semantica)
- [Cookbook](cookbook.md) — full notebook catalog organized by topic and difficulty
- [Modules Guide](modules.md) — every module with examples
- [API Reference](reference/core.md) — complete technical documentation
!!! info "Have a use case to add?"
[Open a PR](https://github.com/Hawksight-AI/semantica) or start a discussion on GitHub.
@@ -0,0 +1,636 @@
"""
Capability Gap Analysis with Semantica Context Graphs
This example mirrors the military capability-gap notebook as a runnable Python script.
It uses Semantica modules and classes across ingestion, parsing, ontology handling,
splitting, normalization, semantic extraction, KG analytics, context graphs,
reasoning, provenance, and export.
"""
from __future__ import annotations
import json
from datetime import datetime
from pathlib import Path
from semantica.change_management import VersionManager
from semantica.conflicts import detect_conflicts, resolve_conflicts, voting
from semantica.context import (
AgentContext,
ContextGraph,
Decision,
Policy,
PolicyEngine,
multi_hop_query,
)
from semantica.export import (
ReportGenerator,
export_csv,
export_graph,
export_json,
export_lpg,
export_rdf,
export_yaml,
)
from semantica.ingest import FileIngestor, OntologyIngestor, ingest_web
from semantica.kg import (
CentralityCalculator,
CommunityDetector,
ConnectivityAnalyzer,
EntityResolver,
GraphAnalyzer,
GraphBuilder,
LinkPredictor,
NodeEmbedder,
SimilarityCalculator,
)
from semantica.normalize import (
clean_text,
detect_language,
handle_encoding,
normalize_text,
)
from semantica.ontology import OntologyEvaluator, ingest_ontology
from semantica.parse import (
DOCLING_AVAILABLE,
DocumentParser,
DoclingParser,
PDFParser,
parse_document,
parse_pdf,
)
from semantica.pipeline import PipelineBuilder
from semantica.provenance import ProvenanceManager
from semantica.reasoning import ExplanationGenerator, Reasoner
from semantica.semantic_extract import (
CoreferenceResolver,
EventDetector,
ExtractionValidator,
NamedEntityRecognizer,
RelationExtractor,
SemanticAnalyzer,
SemanticNetworkExtractor,
TripletExtractor,
)
from semantica.split import TextSplitter
from semantica.vector_store import VectorStore
from semantica.visualization import KGVisualizer
def build_paths() -> tuple[Path, Path, Path]:
# Keep paths workspace-relative so the script is portable across machines.
repo_root = Path(__file__).resolve().parents[1]
use_case_dir = repo_root / "cookbook" / "use_cases" / "capability_gap_defense"
data_dir = use_case_dir / "data"
output_dir = use_case_dir / "outputs_py"
output_dir.mkdir(parents=True, exist_ok=True)
return data_dir, use_case_dir, output_dir
def main() -> None:
# -------------------------------------------------------------------------
# Workspace setup
# -------------------------------------------------------------------------
data_dir, use_case_dir, output_dir = build_paths()
# -------------------------------------------------------------------------
# 1) Ingestion (Semantica ingest)
# -------------------------------------------------------------------------
# Local file inventory for PDFs and ontology artifacts.
file_ingestor = FileIngestor()
file_objects = file_ingestor.ingest_directory(data_dir, recursive=False, read_content=False)
# Web source ingestion through Semantica's `ingest_web` method wrapper.
web_sources = [
"https://www.rand.org/pubs/research_reports/RRA733-1.html",
"https://foundationcapital.com/context-graphs/",
]
web_contents = []
for url in web_sources:
try:
web_contents.append(ingest_web(url, method="url"))
except Exception as exc:
print(f"Web ingestion failed for {url}: {exc}")
# TTL ontology ingestion from the same data directory.
ontology_ingestor = OntologyIngestor()
ontology_data = ontology_ingestor.ingest_directory(data_dir, recursive=False)
# -------------------------------------------------------------------------
# 2) Ontology introspection + evaluation (Semantica ontology)
# -------------------------------------------------------------------------
# Capture file-level schema details to verify class/property coverage.
ontology_details = []
for ttl_file in sorted(data_dir.glob("*.ttl")):
try:
od = ingest_ontology(ttl_file, method="file")
if isinstance(od, list):
for item in od:
ontology_details.append(
{
"file": ttl_file.name,
"classes": len(item.data.get("classes", [])),
"properties": len(item.data.get("properties", [])),
}
)
else:
ontology_details.append(
{
"file": ttl_file.name,
"classes": len(od.data.get("classes", [])),
"properties": len(od.data.get("properties", [])),
}
)
except Exception as exc:
ontology_details.append({"file": ttl_file.name, "error": str(exc)})
# Run competency-question driven evaluation over one ontology payload.
ontology_eval_result = None
if ontology_data:
evaluator = OntologyEvaluator()
ontology_eval_result = evaluator.evaluate_ontology(
ontology_data[0].data,
competency_questions=[
"What capability gaps are revealed for a mission thread?",
"Which systems provide required capabilities?",
"What evidence and provenance support a gap decision?",
"Which precedents and exceptions affected a decision?",
],
)
# -------------------------------------------------------------------------
# 3) Parsing (Semantica parse)
# -------------------------------------------------------------------------
# Parse PDFs through parse methods, with parser-class fallback.
pdf_docs = []
pdf_parser = PDFParser()
doc_parser_preview = {}
docling_preview = {"docling_available": bool(DOCLING_AVAILABLE)}
for pdf_path in sorted(data_dir.glob("*.pdf")):
try:
# Primary parse path: module-level `parse_pdf`.
parsed = parse_pdf(pdf_path, method="default", pages=list(range(0, 12)))
if not isinstance(parsed, dict):
# Fallback path: direct parser class call.
parsed = pdf_parser.parse(pdf_path, pages=list(range(0, 12)))
text = parsed.get("full_text", parsed.get("text", ""))
if text:
pdf_docs.append(
{
"doc_id": pdf_path.stem,
"source": str(pdf_path),
"text": text[:50000],
"metadata": parsed.get("metadata", {}),
}
)
except Exception as exc:
print(f"PDF parse failed for {pdf_path.name}: {exc}")
if pdf_docs:
sample_pdf = Path(pdf_docs[0]["source"])
try:
# Generic multi-format parse via `parse_document`.
parsed_doc = parse_document(sample_pdf, method="default")
if not isinstance(parsed_doc, dict):
parsed_doc = DocumentParser().parse_document(sample_pdf)
doc_parser_preview = {
"source": sample_pdf.name,
"keys": list(parsed_doc.keys())[:10],
"text_chars": len(parsed_doc.get("full_text", parsed_doc.get("text", "")) or ""),
}
except Exception as exc:
doc_parser_preview = {"source": sample_pdf.name, "error": str(exc)}
if docling_preview["docling_available"]:
try:
# Optional DoclingParser path when dependency is available.
docling_parser = DoclingParser(export_format="markdown")
dres = docling_parser.parse(sample_pdf)
docling_preview["keys"] = list(dres.keys())[:10]
docling_preview["text_chars"] = len(dres.get("full_text", dres.get("text", "")) or "")
except Exception as exc:
docling_preview["error"] = str(exc)
# -------------------------------------------------------------------------
# 4) Corpus assembly (input for split/normalize/extract)
# -------------------------------------------------------------------------
# Unify parsed PDFs, web documents, and ontology JSON snapshots.
corpus = (
[
{"doc_id": d["doc_id"], "source": d["source"], "text": d["text"]}
for d in pdf_docs
]
+ [
{
"doc_id": f"web_{i}",
"source": getattr(w, "url", f"web_source_{i}"),
"text": (getattr(w, "content", str(w)) or "")[:30000],
}
for i, w in enumerate(web_contents)
]
+ [
{
"doc_id": Path(od.source_path).stem,
"source": od.source_path,
"text": json.dumps(od.data, ensure_ascii=True)[:40000],
}
for od in ontology_data
]
)
# -------------------------------------------------------------------------
# 5) Split + pipeline declaration (Semantica split + pipeline)
# -------------------------------------------------------------------------
# Batch splitting through `TextSplitter.split_batch`.
splitter = TextSplitter(method="recursive", chunk_size=1800, chunk_overlap=250)
chunks_by_doc = splitter.split_batch([doc.get("text", "") for doc in corpus])
chunked_docs = []
for doc, chunks in zip(corpus, chunks_by_doc):
for idx, chunk in enumerate(chunks or []):
chunked_docs.append(
{
"doc_id": f"{doc['doc_id']}::chunk_{idx}",
"source": doc["source"],
"text": chunk.text if hasattr(chunk, "text") else str(chunk),
"parent_doc_id": doc["doc_id"],
}
)
extraction_corpus = chunked_docs if chunked_docs else corpus
# Logical orchestration path declared with Semantica PipelineBuilder.
pipeline = (
PipelineBuilder()
.add_step("ingest_sources", "ingest", sources=len(corpus))
.add_step("chunk_context", "split", method="recursive")
.add_step("semantic_extract", "extract", entity_relation_event_triplet=True)
.add_step("build_context_graph", "context_graph")
.add_step("policy_and_trace", "decision_trace_capture")
.add_step("export_and_observe", "export_observability")
.connect_steps("ingest_sources", "chunk_context")
.connect_steps("chunk_context", "semantic_extract")
.connect_steps("semantic_extract", "build_context_graph")
.connect_steps("build_context_graph", "policy_and_trace")
.connect_steps("policy_and_trace", "export_and_observe")
.build(name="capability_gap_orchestration_path")
)
# -------------------------------------------------------------------------
# 6) Normalization (Semantica normalize)
# -------------------------------------------------------------------------
# Apply clean -> normalize -> detect_language -> handle_encoding.
normalized_extraction_corpus = []
for item in extraction_corpus:
cleaned = clean_text(item.get("text", ""), method="default")
normalized_text = normalize_text(cleaned, method="default") if cleaned else ""
lang = detect_language(normalized_text, method="default") if normalized_text else "en"
_ = handle_encoding(normalized_text, method="default") if normalized_text else normalized_text
normalized_extraction_corpus.append({**item, "text": normalized_text, "language": lang})
extraction_corpus = normalized_extraction_corpus
# -------------------------------------------------------------------------
# 7) Semantic extraction (Semantica semantic_extract)
# -------------------------------------------------------------------------
# Initialize Semantica extractors for entities, relations, events, triplets.
ner = NamedEntityRecognizer(method="pattern", confidence_threshold=0.2)
rel_extractor = RelationExtractor(method="pattern", confidence_threshold=0.2)
evt_detector = EventDetector()
coref = CoreferenceResolver()
triplet_extractor = TripletExtractor(method="pattern", include_provenance=True)
analyzer = SemanticAnalyzer()
net_extractor = SemanticNetworkExtractor()
validator = ExtractionValidator()
# Resolve pronouns/coreferences before extraction to improve link quality.
texts = [item.get("text", "") for item in extraction_corpus if item.get("text")]
resolved_texts = [coref.resolve(t) for t in texts]
# Batch-first extraction for entities/triplets; per-text for relations/events.
entities_batch = ner.process_batch(resolved_texts)
triplets_batch = triplet_extractor.process_batch(resolved_texts)
relations_batch = [rel_extractor.extract_relations(t, entities=e) for t, e in zip(resolved_texts, entities_batch)]
events_batch = [evt_detector.detect_events(t) for t in resolved_texts]
all_entities = [e for batch in entities_batch for e in batch]
all_relationships = [r for batch in relations_batch for r in batch]
all_events = [ev for batch in events_batch for ev in batch]
all_triplets = [tr for batch in triplets_batch for tr in batch]
# Validation step keeps extraction quality checks explicit.
_ = validator.validate_entities(all_entities)
_ = validator.validate_relations(all_relationships)
semantic_networks = [
{
"doc_id": extraction_corpus[i].get("doc_id", f"doc_{i}"),
"analysis": analyzer.analyze(resolved_texts[i]),
"network": net_extractor.extract(resolved_texts[i], entities=entities_batch[i], relations=relations_batch[i]),
}
for i in range(min(len(resolved_texts), len(extraction_corpus)))
]
# -------------------------------------------------------------------------
# 8) Quality controls + KG analytics (Semantica kg + conflicts)
# -------------------------------------------------------------------------
# Entity resolution for duplicate mentions.
resolver = EntityResolver(strategy="fuzzy")
entity_dicts = [
{
"id": str(getattr(e, "id", getattr(e, "text", "unknown"))),
"name": str(getattr(e, "text", getattr(e, "id", "unknown"))),
"type": str(getattr(e, "label", getattr(e, "type", "entity"))),
"metadata": getattr(e, "metadata", {}) or {},
}
for e in all_entities
]
resolved_entities = resolver.resolve_entities(entity_dicts[:200]) if entity_dicts else []
# Conflict detection and resolution on contradictory numeric evidence.
conflicts = detect_conflicts(
[
{"id": "System_GroundRadarLayer", "coveragePercent": "42", "type": "system"},
{"id": "System_GroundRadarLayer", "coveragePercent": "58", "type": "system"},
],
method="value",
property_name="coveragePercent",
)
resolved_conflicts = resolve_conflicts(conflicts, method=voting) if conflicts else []
# Build the knowledge graph from extracted entities/relationships.
builder = GraphBuilder(merge_entities=True, resolve_conflicts=True)
kg = builder.build([{"entities": all_entities, "relationships": all_relationships}], extract=False)
analyzer_kg = GraphAnalyzer()
kg_analysis = analyzer_kg.analyze_graph(kg)
# Graph analytics modules: centrality, communities, connectivity, similarity.
centrality_calc = CentralityCalculator()
community_detector = CommunityDetector()
connectivity_analyzer = ConnectivityAnalyzer()
similarity_calc = SimilarityCalculator(method="cosine")
link_predictor = LinkPredictor()
node_embed_status = {}
try:
_ = NodeEmbedder(method="node2vec", embedding_dimension=32, walk_length=20, num_walks=5)
node_embed_status["node2vec_ready"] = True
except Exception as exc:
node_embed_status = {"node2vec_ready": False, "reason": str(exc)}
extended_kg_analytics = {
"centrality": centrality_calc.calculate_all_centrality(kg),
"communities": community_detector.detect_communities(kg, algorithm="louvain"),
"connectivity": connectivity_analyzer.analyze_connectivity(kg),
"sample_cosine_similarity": similarity_calc.cosine_similarity([1.0, 0.0, 1.0], [0.8, 0.2, 0.9]),
"predicted_links": link_predictor.predict_links(kg, top_k=5),
"node_embedding_status": node_embed_status,
}
# -------------------------------------------------------------------------
# 9) Context graph + decision traces (Semantica context)
# -------------------------------------------------------------------------
# Domain skeleton nodes/edges for scenario -> mission -> event -> gap chain.
context_graph = ContextGraph(advanced_analytics=True, centrality_analysis=True, community_detection=True)
context_graph.add_nodes(
[
{"id": "Scenario_FutureA2AD_2028", "type": "scenario", "properties": {"content": "Future A2/AD escalation scenario"}},
{"id": "MissionThread_ForceProtection", "type": "mission_thread", "properties": {"content": "Protect forward operating assets under drone saturation"}},
{"id": "Event_LowAltitudeSwarmIncursions", "type": "event", "properties": {"content": "Repeated low-altitude swarm incursions"}},
{"id": "System_GroundRadarLayer", "type": "system", "properties": {"content": "Ground radar surveillance layer"}},
{"id": "Capability_LowAltitudeDetection", "type": "capability", "properties": {"content": "Low altitude detection capability"}},
{"id": "Outcome_MissionRiskIncrease", "type": "outcome", "properties": {"content": "Rising mission risk and delayed response"}},
{"id": "Gap_LowAltitudeDetectionCoverage", "type": "capability_gap", "properties": {"content": "Insufficient low-altitude detection coverage"}},
]
)
context_graph.add_edges(
[
{"source_id": "Scenario_FutureA2AD_2028", "target_id": "MissionThread_ForceProtection", "type": "has_mission_thread"},
{"source_id": "MissionThread_ForceProtection", "target_id": "Event_LowAltitudeSwarmIncursions", "type": "includes_event"},
{"source_id": "Event_LowAltitudeSwarmIncursions", "target_id": "System_GroundRadarLayer", "type": "stresses_system"},
{"source_id": "System_GroundRadarLayer", "target_id": "Capability_LowAltitudeDetection", "type": "provides_capability"},
{"source_id": "Capability_LowAltitudeDetection", "target_id": "Outcome_MissionRiskIncrease", "type": "affects_outcome"},
{"source_id": "MissionThread_ForceProtection", "target_id": "Gap_LowAltitudeDetectionCoverage", "type": "reveals_gap"},
]
)
# AgentContext binds vector retrieval + context graph decision tracking.
vector_store = VectorStore(backend="inmemory", dimension=384)
agent_context = AgentContext(
vector_store=vector_store,
knowledge_graph=context_graph,
decision_tracking=True,
advanced_analytics=True,
kg_algorithms=True,
vector_store_features=True,
graph_expansion=True,
max_expansion_hops=3,
)
_ = agent_context.store(
[{"content": c["text"][:2500], "metadata": {"source": c["source"], "doc_id": c["doc_id"]}} for c in corpus],
extract_entities=False,
extract_relationships=False,
)
# Example decision record for capability-gap assessment.
decision_a = agent_context.record_decision(
category="capability_gap_assessment",
scenario="Future A2/AD mission thread with low-altitude swarm pressure",
reasoning="Mission requires persistent low-altitude detection, but current radar layer indicates limited valley and urban coverage.",
outcome="gap_identified_low_altitude_detection",
confidence=0.93,
entities=["MissionThread_ForceProtection", "Capability_LowAltitudeDetection", "Gap_LowAltitudeDetectionCoverage"],
)
# Explicit policy-bound trace decision object.
trace_decision = Decision(
decision_id="",
category="capability_gap_assessment",
scenario="Coverage threshold breach during swarm-pressure mission thread",
reasoning="Below-threshold low-altitude detection coverage with repeated threat ingress; escalation required.",
outcome="escalate_for_exception",
confidence=0.89,
timestamp=datetime.now(),
decision_maker="joint_ops_agent",
metadata={"policy_version": "3.2"},
)
# Policy and compliance checks against decision trace.
policy_engine = PolicyEngine(context_graph)
policy = Policy(
policy_id="POL-CAPGAP-3.2",
name="Capability Gap Escalation Policy",
description="Escalate and require approval when mission-critical capability coverage is below threshold.",
rules={
"min_confidence": 0.8,
"required_categories": ["capability_gap_assessment", "capability_gap_mitigation"],
"allowed_outcomes": [
"gap_identified_low_altitude_detection",
"recommend_multilayer_sensor_fusion",
"escalate_for_exception",
],
},
category="capability_gap_assessment",
version="3.2",
created_at=datetime.now(),
updated_at=datetime.now(),
metadata={"entities": ["MissionThread_ForceProtection", "System_GroundRadarLayer"]},
)
policy_engine.add_policy(policy)
trace_decision_id = context_graph.record_decision(
category=trace_decision.category,
scenario=trace_decision.scenario,
reasoning=trace_decision.reasoning,
outcome=trace_decision.outcome,
confidence=trace_decision.confidence,
entities=["MissionThread_ForceProtection", "Gap_LowAltitudeDetectionCoverage"],
decision_maker=trace_decision.decision_maker,
metadata={
"policy_version": "3.2",
"cross_system_context": {"crm": "critical_account", "zendesk": "open_escalation", "pagerduty": "sev1_incidents"},
},
)
# Retrieve precedents and run multi-hop traversal in the context graph.
_ = policy_engine.check_compliance(trace_decision, "POL-CAPGAP-3.2")
_ = agent_context.find_precedents(
scenario="Low-altitude detection shortfall under swarm pressure",
category="capability_gap_assessment",
limit=5,
use_hybrid_search=True,
)
_ = context_graph.analyze_decision_impact(trace_decision_id)
_ = multi_hop_query(
context_graph,
start_entity="Scenario_FutureA2AD_2028",
query="Trace mission-thread to capability-gap path",
max_hops=3,
)
# -------------------------------------------------------------------------
# 10) Rule-based reasoning (Semantica reasoning)
# -------------------------------------------------------------------------
reasoner = Reasoner()
reasoner.add_rule("IF MissionRequires(?m, LowAltitudeDetection) AND CoverageStatus(?m, Insufficient) THEN CapabilityGap(?m, LowAltitudeDetectionGap)")
reasoner.add_rule("IF CapabilityGap(?m, LowAltitudeDetectionGap) AND ThreatLevel(?m, High) THEN OutcomeRisk(?m, Elevated)")
reasoner.add_fact("MissionRequires(MissionThread_ForceProtection, LowAltitudeDetection)")
reasoner.add_fact("CoverageStatus(MissionThread_ForceProtection, Insufficient)")
reasoner.add_fact("ThreatLevel(MissionThread_ForceProtection, High)")
inferred = reasoner.forward_chain()
explanation_text = ""
if inferred:
explanation = ExplanationGenerator().generate_explanation(inferred[-1])
explanation_text = explanation.natural_language
# -------------------------------------------------------------------------
# 11) Versioning + provenance (Semantica change_management + provenance)
# -------------------------------------------------------------------------
# Version policies/ontology structure and store lineage records.
version_manager = VersionManager(base_uri="https://example.org/mcg")
_ = version_manager.create_version(
"3.1",
ontology={"uri": "https://example.org/mcg", "classes": [], "properties": []},
changes=["Initial capability-gap decision policy baseline"],
metadata={"structure": {"classes": ["Scenario", "MissionThread", "CapabilityGap"], "properties": ["revealsGap"]}},
)
_ = version_manager.create_version(
"3.2",
ontology={"uri": "https://example.org/mcg", "classes": [], "properties": []},
changes=["Added explicit policy exception and approval-chain trace constructs"],
metadata={
"structure": {
"classes": ["Scenario", "MissionThread", "CapabilityGap", "PolicyException", "ApprovalChain"],
"properties": ["revealsGap", "has_exception", "approved_by_chain"],
}
},
)
prov = ProvenanceManager(storage_path=str(output_dir / "capability_gap_provenance.db"))
for c in corpus:
prov.track_entity(entity_id=f"source::{c['doc_id']}", source=c["source"], metadata={"document_type": "corpus_source"})
for i, rel in enumerate(all_relationships[:120]):
prov.track_relationship(
relationship_id=f"rel::{i}",
source=(getattr(rel, "metadata", {}) or {}).get("source_doc", "unknown_source"),
metadata={"relation_type": str(getattr(rel, "predicate", getattr(rel, "type", "related_to")))},
)
# -------------------------------------------------------------------------
# 12) Export + visualization (Semantica export + visualization)
# -------------------------------------------------------------------------
# Export graph/context in multiple formats for downstream tools.
export_json(kg, output_dir / "capability_gap_kg.json", format="json")
export_json(context_graph.to_dict(), output_dir / "capability_gap_context_graph.json", format="json")
export_graph(context_graph.to_dict(), output_dir / "capability_gap_context_graph.graphml", format="graphml")
export_rdf(kg, output_dir / "capability_gap_kg.ttl", format="turtle")
export_csv({"entities": kg.get("entities", []), "relationships": kg.get("relationships", [])}, output_dir / "capability_gap_kg")
export_yaml(context_graph.to_dict(), output_dir / "capability_gap_context_graph.yaml")
export_lpg(kg, output_dir / "capability_gap_kg.cypher", method="cypher")
report_data = {
"title": "Military Capability Gap Analysis - End-to-End Report",
"summary": {
"corpus_items": len(corpus),
"extraction_items": len(extraction_corpus),
"entities": len(all_entities),
"relationships": len(all_relationships),
"decisions": context_graph.get_decision_summary().get("total_decisions", 0),
},
"metrics": {
"kg_entities": len(kg.get("entities", [])),
"kg_relationships": len(kg.get("relationships", [])),
"context_nodes": context_graph.stats().get("node_count", 0),
"context_edges": context_graph.stats().get("edge_count", 0),
},
"analysis": {"kg_analysis": kg_analysis},
}
ReportGenerator(format="markdown", include_charts=False).generate_report(
report_data,
output_dir / "capability_gap_analysis_report.md",
format="markdown",
)
# Optional network HTML visualization.
try:
KGVisualizer(layout="force", color_scheme="default").visualize_network(
kg,
output="html",
file_path=output_dir / "capability_gap_kg_network.html",
)
except Exception as exc:
print(f"Visualization skipped: {exc}")
# Final run summary for quick validation.
summary = {
"use_case_dir": str(use_case_dir),
"output_dir": str(output_dir),
"files_ingested": len(file_objects),
"web_docs_ingested": len(web_contents),
"ontologies_ingested": len(ontology_data),
"ontology_details": ontology_details,
"ontology_eval": {
"coverage_score": getattr(ontology_eval_result, "coverage_score", None),
"completeness_score": getattr(ontology_eval_result, "completeness_score", None),
},
"doc_parser_preview": doc_parser_preview,
"docling_preview": docling_preview,
"pipeline": pipeline.name,
"entities_extracted": len(all_entities),
"relationships_extracted": len(all_relationships),
"events_detected": len(all_events),
"triplets_extracted": len(all_triplets),
"semantic_networks": len(semantic_networks),
"resolved_entities": len(resolved_entities),
"conflicts_detected": len(conflicts),
"conflicts_resolved": len(resolved_conflicts),
"reasoning_inferred_rules": [r.conclusion for r in inferred],
"reasoning_explanation": explanation_text,
"extended_kg_analytics_keys": list(extended_kg_analytics.keys()),
"provenance_stats": prov.get_statistics(),
"decision_example": decision_a,
}
print(json.dumps(summary, indent=2, ensure_ascii=True))
if __name__ == "__main__":
main()
+213
View File
@@ -0,0 +1,213 @@
"""
Apache Parquet Exporter - Example Usage
This script demonstrates how to use the ParquetExporter to export
knowledge graphs, entities, and relationships to Apache Parquet format.
"""
import tempfile
from pathlib import Path
from semantica.export import ParquetExporter, export_parquet
def main():
print("=" * 70)
print("Apache Parquet Exporter - Example Usage")
print("=" * 70)
# Create a temporary directory for outputs
temp_dir = Path(tempfile.mkdtemp())
print(f"\n📁 Output directory: {temp_dir}\n")
# Sample data
entities = [
{
"id": "e1",
"text": "Alice",
"type": "Person",
"confidence": 0.95,
"start": 0,
"end": 5,
"metadata": {"age": 30, "city": "New York"},
},
{
"id": "e2",
"text": "Acme Corp",
"type": "Organization",
"confidence": 0.88,
"start": 10,
"end": 19,
"metadata": {"location": "NY", "employees": 100},
},
{
"id": "e3",
"text": "Bob",
"type": "Person",
"confidence": 0.92,
"metadata": {"age": 35, "department": "Engineering"},
},
]
relationships = [
{
"id": "r1",
"source_id": "e1",
"target_id": "e2",
"type": "WORKS_FOR",
"confidence": 0.90,
"metadata": {"role": "Engineer", "since": 2020},
},
{
"id": "r2",
"source_id": "e3",
"target_id": "e2",
"type": "WORKS_FOR",
"confidence": 0.85,
"metadata": {"role": "Manager", "since": 2018},
},
]
knowledge_graph = {
"entities": entities,
"relationships": relationships,
"metadata": {"version": "1.0", "created": "2024-01-01"},
}
# Example 1: Export entities using ParquetExporter class
print("Example 1: Export entities to Parquet")
print("-" * 70)
exporter = ParquetExporter(compression="snappy")
entities_path = temp_dir / "entities.parquet"
exporter.export_entities(entities, entities_path)
print(f"✓ Entities exported to: {entities_path}")
print(f" File size: {entities_path.stat().st_size} bytes\n")
# Example 2: Export relationships
print("Example 2: Export relationships to Parquet")
print("-" * 70)
rels_path = temp_dir / "relationships.parquet"
exporter.export_relationships(relationships, rels_path)
print(f"✓ Relationships exported to: {rels_path}")
print(f" File size: {rels_path.stat().st_size} bytes\n")
# Example 3: Export complete knowledge graph
print("Example 3: Export knowledge graph to multiple Parquet files")
print("-" * 70)
kg_base_path = temp_dir / "knowledge_graph"
exporter.export_knowledge_graph(knowledge_graph, kg_base_path)
kg_entities = temp_dir / "knowledge_graph_entities.parquet"
kg_rels = temp_dir / "knowledge_graph_relationships.parquet"
print("✓ Knowledge graph exported to:")
print(f" - {kg_entities} ({kg_entities.stat().st_size} bytes)")
print(f" - {kg_rels} ({kg_rels.stat().st_size} bytes)\n")
# Example 4: Using convenience function
print("Example 4: Using export_parquet convenience function")
print("-" * 70)
conv_path = temp_dir / "convenience_export.parquet"
export_parquet(entities, conv_path, compression="gzip")
print(f"✓ Exported using convenience function: {conv_path}")
print(f" File size: {conv_path.stat().st_size} bytes\n")
# Example 5: Different compression codecs
print("Example 5: Compare compression codecs")
print("-" * 70)
# Create larger dataset for meaningful comparison
large_entities = entities * 50
compression_codecs = ["snappy", "gzip", "brotli", "zstd", "lz4", "none"]
sizes = {}
for codec in compression_codecs:
codec_exporter = ParquetExporter(compression=codec)
codec_path = temp_dir / f"entities_{codec}.parquet"
codec_exporter.export_entities(large_entities, codec_path)
sizes[codec] = codec_path.stat().st_size
print(f" {codec:8} - {sizes[codec]:,} bytes")
print()
# Example 6: Load Parquet with pandas (if available)
print("Example 6: Loading Parquet files with pandas")
print("-" * 70)
try:
import pandas as pd
df = pd.read_parquet(entities_path)
print("✓ Loaded entities as pandas DataFrame")
print(f" Shape: {df.shape}")
print(f" Columns: {list(df.columns)}")
print("\nFirst few rows:")
print(df.head())
print()
except ImportError:
print("⚠ pandas not installed - skipping pandas example\n")
# Example 7: Load Parquet with pyarrow
print("Example 7: Loading Parquet files with pyarrow")
print("-" * 70)
try:
import pyarrow.parquet as pq
table = pq.read_table(entities_path)
print("✓ Loaded entities as Arrow Table")
print(f" Rows: {table.num_rows}")
print(f" Columns: {table.num_columns}")
print(" Schema:")
for i, field in enumerate(table.schema):
print(f" - {field.name}: {field.type}")
print()
except ImportError:
print("⚠ pyarrow not installed - skipping pyarrow example\n")
# Example 8: Schema validation
print("Example 8: Explicit schema validation")
print("-" * 70)
try:
import pyarrow.parquet as pq
# Read parquet file and verify schema
table = pq.read_table(entities_path)
print("✓ Schema validation:")
print(f" - ID column type: {table.schema.field('id').type}")
print(f" - Text column type: {table.schema.field('text').type}")
print(f" - Confidence column type: {table.schema.field('confidence').type}")
print(f" - Metadata column type: {table.schema.field('metadata').type}")
print()
# Verify metadata structure
metadata_field = table.schema.field("metadata")
print(" Metadata structure:")
if hasattr(metadata_field.type, "num_fields"):
for i in range(metadata_field.type.num_fields):
subfield = metadata_field.type.field(i)
print(f" - {subfield.name}: {subfield.type}")
print()
except Exception as e:
print(f"⚠ Schema validation error: {e}\n")
# Summary
print("=" * 70)
print("Summary")
print("=" * 70)
print("✓ All examples completed successfully")
print(f"✓ Output directory: {temp_dir}")
print(f"✓ Files created: {len(list(temp_dir.glob('*.parquet')))}")
print("\nKey Features:")
print(" - Columnar storage optimized for analytics")
print(" - Multiple compression options (snappy, gzip, brotli, zstd, lz4)")
print(" - Compatible with pandas, Spark, Snowflake, BigQuery, Databricks")
print(" - Explicit schemas for type safety")
print(" - Structured metadata handling")
print("\nFor more information, see the Semantica documentation.")
print("=" * 70)
if __name__ == "__main__":
main()
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+197
View File
@@ -0,0 +1,197 @@
# Semantica Knowledge Explorer
A real-time visual interface for exploring knowledge graphs, decision intelligence, entity resolution, ontologies, and graph analytics built on top of the [Semantica](https://github.com/Hawksight-AI/semantica) library.
---
## Requirements
| Dependency | Minimum Version |
|---|---|
| Node.js | 18.x or higher (20.x recommended) |
| npm | 9.x or higher |
| Python | 3.8+ |
| Semantica backend | running on `http://127.0.0.1:8000` |
Check your versions:
```bash
node --version
npm --version
python --version
```
---
## Quick Start (Local Development)
### 1. Clone the repository
```bash
git clone https://github.com/Hawksight-AI/semantica.git
cd semantica
```
### 2. Install the Semantica Python package
```bash
pip install semantica
```
Or install from source if you have the repo:
```bash
pip install -e .
```
### 3. Start the Semantica backend
The Explorer proxies all `/api` and `/ws` requests to `http://127.0.0.1:8000`. The backend must be running before you open the UI.
```bash
# From the repo root
python -m semantica.server
```
The backend starts on port **8000** by default. Keep this terminal open.
### 4. Install frontend dependencies
Open a second terminal:
```bash
cd explorer
npm install
```
> **Note:** This project uses Vite 5 and requires **Node 18+**. If you are on Node 16 or earlier, upgrade first.
### 5. Start the dev server
```bash
npm run dev
```
Vite starts on **http://localhost:5173** by default. Open that URL in your browser.
---
## What you should see
The Explorer opens with a persistent left sidebar and six workspace tabs:
| Tab | What it shows |
|---|---|
| **Knowledge Graph** | Interactive Sigma.js canvas — nodes, edges, zoom, ForceAtlas2 layout |
| **Timeline** | Temporal event scrubber over the graph |
| **Decisions** | Causal chain viewer with outcome badges and decision filter |
| **Registry** | Live audit log of every graph mutation (add-node, add-edge, etc.) |
| **Entity Resolution** | Duplicate detection and entity merge workflow |
| **KG Overview** | Aggregate stats, community breakdown, centrality heatmap |
| **Ontology** | SKOS/OWL vocabulary hierarchy and schema summary |
---
## Project structure
```
explorer/
├── src/
│ ├── App.tsx # Root layout, tab routing, workspace wiring
│ ├── index.css # Global resets, fonts, keyframe animations
│ ├── store/
│ │ └── registryStore.ts # Pub/sub audit registry (no external state lib)
│ └── workspaces/
│ ├── GraphWorkspace/ # Sigma.js graph canvas + inspector panel
│ ├── DecisionWorkspace/ # Causal flow diagram + decision list
│ ├── TimelineWorkspace/ # vis-timeline temporal scrubber
│ ├── ManageWorkspace/ # Registry, KG Overview, Ontology tabs
│ └── EnrichWorkspace/ # Entity resolution tab
├── index.html
├── vite.config.ts # Dev proxy → 127.0.0.1:8000, build → ../semantica/static
└── package.json
```
---
## Available scripts
Run these from inside the `explorer/` directory:
```bash
# Start the dev server with hot module replacement
npm run dev
# Type-check and build a production bundle into ../semantica/static
npm run build
# Preview the production build locally
npm run preview
# Run ESLint over all source files
npm run lint
# Run the graph store multi-edge unit tests
npm run test:graph-store
```
---
## API & WebSocket proxy
During development, Vite forwards requests automatically — no CORS configuration needed:
| Pattern | Forwarded to |
|---|---|
| `/api/*` | `http://127.0.0.1:8000/api/*` |
| `/ws` | `ws://127.0.0.1:8000/ws` |
If you run the backend on a different port, update `server.proxy` in [vite.config.ts](vite.config.ts).
---
## Production build
```bash
cd explorer
npm run build
```
The compiled assets are written to `../semantica/static/`. The Semantica Python server serves this folder automatically at its root URL — no separate web server needed.
---
## Troubleshooting
**Blank graph / no data loads**
- Make sure the Semantica backend is running (`python -m semantica.server`) before opening the UI.
- Check the browser console for failed `/api/graph` requests — the proxy target may need updating in `vite.config.ts`.
**`npm install` fails or hangs**
- Ensure you are using **Node 18 or 20**. Node 16 and Vite 5 are incompatible.
- Delete `node_modules/` and `package-lock.json`, then re-run `npm install`.
**Port 5173 already in use**
- Vite will automatically try the next available port and print it in the terminal. Use that URL instead.
**WebSocket not connecting (real-time mutations not appearing)**
- Confirm the backend exposes a `/ws` WebSocket endpoint.
- Check browser DevTools → Network → WS tab for the connection status.
---
## Tech stack
- **React 19** + TypeScript (strict `noUnusedLocals`)
- **Vite 5** with `babel-plugin-react-compiler`
- **Sigma.js 3** + **Graphology** — graph rendering and in-memory graph store
- **ForceAtlas2** — physics-based layout worker
- **@tanstack/react-query** — data fetching for ontology and vocab tabs
- **vis-timeline** — temporal event visualization
- **lucide-react** — icon set
---
## Contributing
See the root [CONTRIBUTING.md](../CONTRIBUTING.md) and open issues on the main [Semantica repository](https://github.com/Hawksight-AI/semantica).
+23
View File
@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Semantica Knowledge Explorer</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+4710
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
{
"name": "semantica-knowledge-explorer",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"test:graph-store": "node --test tests/graphStore.multi-edge.test.mjs",
"test:graph-workspace": "node --import tsx --test tests/graphSceneState.display.test.ts"
},
"dependencies": {
"@monaco-editor/react": "^4.7.0",
"@sigma/edge-curve": "^3.1.0",
"@sigma/node-border": "^3.0.0",
"@tanstack/react-query": "^5.95.2",
"@xyflow/react": "^12.10.2",
"graphology": "^0.26.0",
"graphology-communities-louvain": "^2.0.2",
"graphology-layout-forceatlas2": "^0.10.1",
"graphology-metrics": "^2.4.0",
"graphology-shortest-path": "^2.1.0",
"lucide-react": "^1.7.0",
"playwright": "^1.59.1",
"react": "^19.2.4",
"react-arborist": "^3.4.3",
"react-dom": "^19.2.4",
"react-dropzone": "^15.0.0",
"sigma": "^3.0.2",
"vis-data": "^8.0.3",
"vis-timeline": "^8.5.0"
},
"devDependencies": {
"@babel/core": "^7.29.0",
"@eslint/js": "^9.39.4",
"@types/babel__core": "^7.20.5",
"@types/node": "^24.12.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.3.0",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"tsx": "^4.21.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.57.0",
"vite": "^5.4.0"
}
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="bluesky-icon" viewBox="0 0 16 17">
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
</symbol>
<symbol id="discord-icon" viewBox="0 0 20 19">
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
</symbol>
<symbol id="documentation-icon" viewBox="0 0 21 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
</symbol>
<symbol id="github-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
</symbol>
<symbol id="social-icon" viewBox="0 0 20 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
</symbol>
<symbol id="x-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File
+1445
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

+71
View File
@@ -0,0 +1,71 @@
/* ── Semantica Explorer — Global CSS Reset ── */
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@500;600;700;800&display=swap');
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body, #root {
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'IBM Plex Sans', 'Space Grotesk', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #0d1117;
color: #c9d1d9;
}
/* Focus ring for accessibility */
:focus-visible {
outline: 2px solid rgba(88, 166, 255, 0.6);
outline-offset: 2px;
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(88, 166, 255, 0.25);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(88, 166, 255, 0.45);
}
/* Monospace font for code elements */
code, pre, .mono {
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
}
/* Selection highlight */
::selection {
background: rgba(88, 166, 255, 0.3);
color: #fff;
}
/* Spin animation for loaders */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-spin {
animation: spin 1s linear infinite;
}
/* Skeleton pulse animation for loading placeholders */
@keyframes skeleton-pulse {
0%, 100% { opacity: 0.45; }
50% { opacity: 0.85; }
}
+7
View File
@@ -0,0 +1,7 @@
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<App />,
)
+2
View File
@@ -0,0 +1,2 @@
export function pairRegistryKey(source: string, target: string): string;
export function curveGroupForPair(source: string, target: string): string;
+7
View File
@@ -0,0 +1,7 @@
export function pairRegistryKey(source, target) {
return JSON.stringify([source, target]);
}
export function curveGroupForPair(source, target) {
return JSON.stringify([source, target]);
}
+180
View File
@@ -0,0 +1,180 @@
import Graph from "graphology";
import type {
GraphArrowVisibilityPolicy,
GraphBadgeKind,
GraphEdgeVariant,
GraphEntityShapeVariant,
GraphLabelVisibilityPolicy,
GraphNodeShapeVariant,
} from "../workspaces/GraphWorkspace/graphTheme";
import { curveGroupForPair, pairRegistryKey } from "./edgePairKeys.js";
export const graph = new Graph({
type: "directed",
multi: true,
allowSelfLoops: false
});
export interface NodeAttributes {
label: string;
x: number;
y: number;
size: number;
color: string;
baseColor?: string;
mutedColor?: string;
glowColor?: string;
baseSize?: number;
visualPriority?: number;
labelPriority?: number;
semanticGroup?: string;
strokeColor?: string;
borderColor?: string;
borderSize?: number;
nodeVariant?: GraphNodeShapeVariant;
nodeShapeVariant?: GraphNodeShapeVariant;
entityShape?: GraphEntityShapeVariant;
badgeKind?: GraphBadgeKind;
badgeCount?: number;
ringColor?: string;
haloColor?: string;
labelVisibilityPolicy?: GraphLabelVisibilityPolicy;
highlighted?: boolean;
communityId?: string;
isCommunityGroup?: boolean;
memberCount?: number;
anchorNodeId?: string | null;
nodeType: string;
content: string;
valid_from?: string | null;
valid_until?: string | null;
properties: Record<string, any>;
}
export interface EdgeAttributes {
edgeId?: string;
familyId?: string;
sourceId?: string;
targetId?: string;
size?: number;
baseSize?: number;
color?: string;
baseColor?: string;
mutedColor?: string;
type?: string;
curvature?: number;
visualPriority?: number;
edgeFamily?: "line" | "parallel" | "bidirectional" | "path";
isBidirectional?: boolean;
curveGroup?: string | null;
edgeVariant?: GraphEdgeVariant;
arrowVisibilityPolicy?: GraphArrowVisibilityPolicy;
relationshipStrength?: number;
isParallelPair?: boolean;
parallelIndex?: number;
parallelCount?: number;
familySize?: number;
rawEdgeIds?: string[];
isAggregated?: boolean;
aggregateCount?: number;
dominantEdgeType?: string;
representativeWeight?: number;
bundleKind?: "parallel" | "bidirectional" | "community";
edgeType: string;
weight: number;
properties: Record<string, any>;
}
function normalizeParallelMetadataForPair(source: string, target: string): void {
const edgeIds: string[] = [];
graph.forEachDirectedEdge(source, target, (edgeId) => {
edgeIds.push(String(edgeId));
});
const pairCount = edgeIds.length;
const familyCounts = new Map<string, number>();
edgeIds.forEach((edgeId) => {
const attrs = graph.getEdgeAttributes(edgeId) as EdgeAttributes;
const familyId = String(attrs.familyId || edgeId);
familyCounts.set(familyId, (familyCounts.get(familyId) ?? 0) + 1);
});
edgeIds
.sort((left, right) => {
const leftAttrs = graph.getEdgeAttributes(left) as EdgeAttributes;
const rightAttrs = graph.getEdgeAttributes(right) as EdgeAttributes;
const priorityDelta = Number(rightAttrs.visualPriority ?? 0) - Number(leftAttrs.visualPriority ?? 0);
if (priorityDelta !== 0) {
return priorityDelta;
}
const weightDelta = Number(rightAttrs.weight ?? 0) - Number(leftAttrs.weight ?? 0);
if (weightDelta !== 0) {
return weightDelta;
}
return left.localeCompare(right);
})
.forEach((edgeId, index) => {
const attrs = graph.getEdgeAttributes(edgeId) as EdgeAttributes;
const familyId = String(attrs.familyId || edgeId);
graph.mergeEdgeAttributes(edgeId, {
edgeId,
familyId,
sourceId: source,
targetId: target,
isParallelPair: pairCount > 1,
parallelIndex: index,
parallelCount: pairCount,
familySize: familyCounts.get(familyId) ?? 1,
curveGroup: curveGroupForPair(source, target),
});
});
}
export function batchMergeNodes(
nodes: { id: string; attributes: NodeAttributes }[]
): void {
for (const { id, attributes } of nodes) {
graph.mergeNode(id, attributes);
}
}
export function batchMergeEdges(
edges: { id: string; familyId?: string; source: string; target: string; attributes: EdgeAttributes }[]
): void {
const touchedPairs = new Map<string, { source: string; target: string }>();
for (const { id, familyId, source, target, attributes } of edges) {
if (source === target) continue; // skip self-loops; graph was created with allowSelfLoops: false
const edgeId = String(attributes.edgeId || id);
const resolvedFamilyId = String(attributes.familyId || familyId || edgeId);
if (graph.hasNode(source) && graph.hasNode(target)) {
graph.mergeDirectedEdgeWithKey(edgeId, source, target, {
...attributes,
edgeId,
familyId: resolvedFamilyId,
sourceId: source,
targetId: target,
});
touchedPairs.set(pairRegistryKey(source, target), { source, target });
}
}
touchedPairs.forEach(({ source, target }) => {
normalizeParallelMetadataForPair(source, target);
});
}
export function clearGraph(): void {
graph.clear();
}
+77
View File
@@ -0,0 +1,77 @@
/**
* src/store/registryStore.ts
*
* Lightweight client-side audit log for all KG / Ontology mutations.
* No backend required events are dispatched by each workspace after
* a successful API call or WebSocket mutation.
*
* Any component can call logEvent() from anywhere (including non-React code).
* React components subscribe via the useRegistry() hook.
*/
import { useState, useEffect } from "react";
export type RegistryEntryOp =
| "import"
| "export"
| "merge"
| "add-node"
| "add-edge"
| "delete"
| "infer"
| "vocab-import";
export interface RegistryEntry {
id: string;
op: RegistryEntryOp;
timestamp: Date;
summary: string;
detail?: Record<string, unknown>;
}
type Listener = (entries: readonly RegistryEntry[]) => void;
let _entries: RegistryEntry[] = [];
const _listeners = new Set<Listener>();
const MAX_ENTRIES = 500;
function _notify(): void {
_listeners.forEach((fn) => fn(_entries));
}
export function logEvent(
op: RegistryEntryOp,
summary: string,
detail?: Record<string, unknown>,
): void {
const entry: RegistryEntry = {
id: `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
op,
timestamp: new Date(),
summary,
detail,
};
_entries = [entry, ..._entries].slice(0, MAX_ENTRIES);
_notify();
}
export function clearRegistry(): void {
_entries = [];
_notify();
}
export function getRegistryEntries(): readonly RegistryEntry[] {
return _entries;
}
export function useRegistry(): readonly RegistryEntry[] {
const [snapshot, setSnapshot] = useState<readonly RegistryEntry[]>(_entries);
useEffect(() => {
// Sync any events that arrived between render and subscribe
setSnapshot(_entries);
_listeners.add(setSnapshot);
return () => {
_listeners.delete(setSnapshot);
};
}, []);
return snapshot;
}
+16
View File
@@ -0,0 +1,16 @@
declare module 'vis-timeline/standalone' {
export class Timeline {
constructor(container: HTMLElement, items: any, options?: any);
on(event: string, callback: (properties: any) => void): void;
destroy(): void;
}
}
declare module 'vis-data/standalone' {
export class DataSet<T = any> {
constructor(data?: T[], options?: any);
add(data: T | T[]): void;
update(data: T | T[]): void;
remove(id: string | number | (string | number)[]): void;
}
}
+384
View File
@@ -0,0 +1,384 @@
.sem-workspace-frame {
width: 100%;
height: 100%;
min-width: 0;
display: flex;
flex-direction: column;
flex: 1;
gap: 18px;
padding: 20px;
background:
radial-gradient(circle at top, rgba(103, 182, 255, 0.08), transparent 24%),
linear-gradient(180deg, rgba(8, 17, 29, 0.94), rgba(3, 7, 14, 0.98));
}
.sem-workspace-frame > :last-child {
width: 100%;
min-width: 0;
min-height: 0;
flex: 1;
align-self: stretch;
}
.sem-workspace-hero {
width: 100%;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 18px;
padding: 20px 22px;
border-radius: 26px;
border: 1px solid var(--panel-border);
background:
linear-gradient(180deg, rgba(8, 18, 33, 0.88), rgba(11, 22, 38, 0.72)),
radial-gradient(circle at top right, rgba(255, 179, 109, 0.08), transparent 28%);
box-shadow: var(--shadow-strong), inset 0 1px 0 rgba(255,255,255,0.04);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
}
.sem-workspace-kicker {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--text-2);
font-size: 11px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 12px;
}
.sem-workspace-kicker::before {
content: "";
width: 7px;
height: 7px;
border-radius: 999px;
background: linear-gradient(135deg, var(--accent-2), var(--warm));
box-shadow: 0 0 14px rgba(158, 217, 255, 0.45);
}
.sem-workspace-title {
color: var(--text-1);
font-size: 28px;
line-height: 0.98;
letter-spacing: -0.05em;
font-weight: 800;
margin: 0;
}
.sem-workspace-subtitle {
margin-top: 8px;
color: var(--text-2);
font-size: 13px;
line-height: 1.6;
max-width: 58ch;
}
.sem-workspace-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.sem-grid-two {
display: grid;
grid-template-columns: minmax(0, 1.12fr) minmax(320px, 0.88fr);
gap: 20px;
min-height: 0;
flex: 1;
}
.sem-grid-split {
display: grid;
grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
gap: 18px;
min-height: 0;
flex: 1;
}
.sem-surface {
border-radius: 24px;
border: 1px solid var(--panel-border);
background:
linear-gradient(180deg, rgba(9, 18, 32, 0.84), rgba(10, 18, 31, 0.72)),
radial-gradient(circle at top, rgba(103, 182, 255, 0.05), transparent 34%);
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.28), inset 0 1px 0 rgba(255,255,255,0.04);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
}
.sem-surface--subtle {
background: linear-gradient(180deg, rgba(9, 18, 32, 0.7), rgba(9, 16, 28, 0.52));
}
.sem-surface--accent {
border-color: var(--panel-border-strong);
}
.sem-surface-body {
padding: 20px;
}
.sem-surface-body--tight {
padding: 14px;
}
.sem-section-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
}
.sem-section-eyebrow {
color: var(--text-3);
font-size: 11px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 8px;
}
.sem-section-title {
color: var(--text-1);
font-size: 18px;
font-weight: 800;
letter-spacing: -0.04em;
margin: 0;
}
.sem-section-copy {
margin-top: 6px;
color: var(--text-2);
font-size: 13px;
line-height: 1.55;
}
.sem-chip {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 999px;
border: 1px solid rgba(103, 182, 255, 0.16);
background: rgba(103, 182, 255, 0.08);
color: #9ed9ff;
font-size: 12px;
font-weight: 700;
}
.sem-chip--warm {
color: #ffce97;
border-color: rgba(255, 179, 109, 0.16);
background: rgba(255, 179, 109, 0.08);
}
.sem-chip--success {
color: #8bf0bf;
border-color: rgba(80, 210, 159, 0.16);
background: rgba(80, 210, 159, 0.08);
}
.sem-command-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
padding: 12px 14px;
border-radius: 20px;
border: 1px solid rgba(132, 197, 255, 0.12);
background: rgba(0, 0, 0, 0.18);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
}
.sem-command-group {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.sem-segmented {
display: inline-flex;
gap: 4px;
padding: 4px;
border-radius: 999px;
border: 1px solid rgba(132, 197, 255, 0.12);
background: rgba(8, 16, 28, 0.58);
}
.sem-button,
.sem-button-secondary {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 40px;
padding: 10px 14px;
border-radius: 14px;
cursor: pointer;
transition: transform 180ms ease, border-color 180ms ease, background 180ms ease, opacity 180ms ease;
}
.sem-button:hover,
.sem-button-secondary:hover {
transform: translateY(-1px);
}
.sem-button {
color: white;
border: 1px solid rgba(103, 182, 255, 0.2);
background: linear-gradient(180deg, rgba(53, 130, 245, 0.3), rgba(25, 88, 185, 0.18));
box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
font-weight: 700;
}
.sem-button-secondary {
color: #d8e8fb;
border: 1px solid rgba(255, 255, 255, 0.06);
background: rgba(255, 255, 255, 0.035);
font-weight: 600;
}
.sem-button:disabled,
.sem-button-secondary:disabled {
opacity: 0.55;
cursor: not-allowed;
transform: none;
}
.sem-segmented button {
min-height: 36px;
padding: 8px 12px;
border-radius: 999px;
}
.sem-segmented button[data-active="true"] {
background: linear-gradient(180deg, rgba(53, 130, 245, 0.34), rgba(25, 88, 185, 0.2));
border-color: rgba(132, 197, 255, 0.2);
}
.sem-input,
.sem-select,
.sem-textarea {
width: 100%;
border-radius: 14px;
border: 1px solid rgba(132, 197, 255, 0.14);
background: rgba(0, 0, 0, 0.22);
color: var(--text-1);
padding: 12px 14px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.03);
}
.sem-input::placeholder,
.sem-textarea::placeholder {
color: var(--text-3);
}
.sem-textarea {
min-height: 160px;
resize: vertical;
}
.sem-inspector {
height: 100%;
overflow: auto;
}
.sem-empty-state,
.sem-loading-state {
height: 100%;
min-height: 240px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 28px;
color: var(--text-2);
}
.sem-empty-state-title,
.sem-loading-state-title {
color: var(--text-1);
font-size: 16px;
font-weight: 700;
margin: 14px 0 6px;
}
.sem-empty-state-copy,
.sem-loading-state-copy {
font-size: 13px;
line-height: 1.6;
max-width: 42ch;
}
.sem-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.sem-list-item {
width: 100%;
text-align: left;
padding: 14px;
border-radius: 16px;
border: 1px solid rgba(132, 197, 255, 0.08);
background: rgba(255, 255, 255, 0.025);
color: var(--text-1);
transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
cursor: pointer;
}
.sem-list-item:hover {
transform: translateY(-1px);
border-color: rgba(132, 197, 255, 0.18);
background: rgba(103, 182, 255, 0.08);
}
.sem-list-item[data-active="true"] {
border-color: rgba(132, 197, 255, 0.22);
background: linear-gradient(180deg, rgba(53, 130, 245, 0.18), rgba(25, 88, 185, 0.1));
}
.sem-table {
width: 100%;
border-collapse: collapse;
color: var(--text-1);
}
.sem-table th {
text-align: left;
color: var(--text-2);
font-size: 12px;
font-weight: 700;
padding: 10px 12px;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.sem-table td {
padding: 12px;
border-bottom: 1px solid rgba(255,255,255,0.05);
font-size: 13px;
}
@media (max-width: 1180px) {
.sem-grid-two,
.sem-grid-split {
grid-template-columns: 1fr;
}
.sem-workspace-hero {
flex-direction: column;
align-items: stretch;
}
.sem-workspace-actions {
justify-content: flex-start;
}
}
+170
View File
@@ -0,0 +1,170 @@
import type { CSSProperties, ReactNode } from "react";
type SurfaceTone = "default" | "subtle" | "accent";
type SurfacePadding = "default" | "tight" | "none";
type ChipTone = "default" | "warm" | "success";
function cx(...parts: Array<string | false | null | undefined>) {
return parts.filter(Boolean).join(" ");
}
export function WorkspaceFrame({
kicker,
title,
subtitle,
actions,
children,
}: {
kicker?: string;
title: string;
subtitle?: string;
actions?: ReactNode;
children: ReactNode;
}) {
return (
<div className="sem-workspace-frame">
<header className="sem-workspace-hero">
<div>
{kicker ? <div className="sem-workspace-kicker">{kicker}</div> : null}
<h2 className="sem-workspace-title">{title}</h2>
{subtitle ? <p className="sem-workspace-subtitle">{subtitle}</p> : null}
</div>
{actions ? <div className="sem-workspace-actions">{actions}</div> : null}
</header>
{children}
</div>
);
}
export function SurfaceCard({
children,
tone = "default",
padding = "default",
className,
style,
}: {
children: ReactNode;
tone?: SurfaceTone;
padding?: SurfacePadding;
className?: string;
style?: CSSProperties;
}) {
return (
<div className={cx("sem-surface", tone !== "default" && `sem-surface--${tone}`, className)} style={style}>
{padding === "none" ? children : <div className={cx("sem-surface-body", padding === "tight" && "sem-surface-body--tight")}>{children}</div>}
</div>
);
}
export function SectionHeader({
eyebrow,
title,
description,
actions,
}: {
eyebrow?: string;
title: string;
description?: string;
actions?: ReactNode;
}) {
return (
<div className="sem-section-header">
<div>
{eyebrow ? <div className="sem-section-eyebrow">{eyebrow}</div> : null}
<h3 className="sem-section-title">{title}</h3>
{description ? <p className="sem-section-copy">{description}</p> : null}
</div>
{actions ? <div className="sem-command-group">{actions}</div> : null}
</div>
);
}
export function MetricChip({
children,
tone = "default",
}: {
children: ReactNode;
tone?: ChipTone;
}) {
return <span className={cx("sem-chip", tone !== "default" && `sem-chip--${tone}`)}>{children}</span>;
}
export function CommandBar({
left,
right,
}: {
left?: ReactNode;
right?: ReactNode;
}) {
return (
<div className="sem-command-bar">
<div className="sem-command-group">{left}</div>
<div className="sem-command-group">{right}</div>
</div>
);
}
export function InspectorPanel({
children,
open = true,
className,
}: {
children: ReactNode;
open?: boolean;
className?: string;
}) {
return (
<SurfaceCard className={cx("sem-inspector", className)} padding="none" style={{ display: open ? "block" : "none" }}>
{children}
</SurfaceCard>
);
}
export function EmptyState({
title,
description,
icon,
}: {
title: string;
description: string;
icon?: ReactNode;
}) {
return (
<div className="sem-empty-state">
{icon}
<div className="sem-empty-state-title">{title}</div>
<div className="sem-empty-state-copy">{description}</div>
</div>
);
}
export function LoadingState({
title,
description,
}: {
title: string;
description: string;
}) {
return (
<div className="sem-loading-state">
<div className="animate-spin" style={{
width: 24,
height: 24,
borderRadius: "999px",
border: "2px solid rgba(103, 182, 255, 0.18)",
borderTopColor: "rgba(158, 217, 255, 0.92)",
marginBottom: 12,
}} />
<div className="sem-loading-state-title">{title}</div>
<div className="sem-loading-state-copy">{description}</div>
</div>
);
}
export function SegmentedControl({
children,
}: {
children: ReactNode;
}) {
return <div className="sem-segmented">{children}</div>;
}
@@ -0,0 +1,407 @@
/**
* src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx
*/
import { useState, useEffect, useMemo } from "react";
import { Scale, Search } from "lucide-react";
const THEME_CSS = `
.glass-panel {
background: linear-gradient(135deg, rgba(13,17,23,0.75), rgba(22,27,34,0.6));
backdrop-filter: blur(16px) saturate(1.2);
-webkit-backdrop-filter: blur(16px) saturate(1.2);
border: 1px solid rgba(88,166,255,0.2);
box-shadow: 0 8px 32px rgba(0,0,0,0.5), inset 1px 1px 0 rgba(255,255,255,0.05);
}
@keyframes skeleton-shimmer {
0% { opacity: 0.45; }
50% { opacity: 0.85; }
100% { opacity: 0.45; }
}
.skeleton-item {
border-radius: 8px;
background: rgba(255,255,255,0.05);
animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
`;
type OutcomeKind = "approved" | "rejected" | "deferred" | "pending" | string;
function outcomeStyle(outcome: string): { color: string; bg: string; border: string } {
const lower = (outcome ?? "").toLowerCase();
if (lower.includes("approv") || lower.includes("accept"))
return { color: "#4cc38a", bg: "rgba(76,195,138,0.12)", border: "rgba(76,195,138,0.28)" };
if (lower.includes("reject") || lower.includes("denied") || lower.includes("fail"))
return { color: "#ff7b72", bg: "rgba(255,123,114,0.12)", border: "rgba(255,123,114,0.28)" };
if (lower.includes("defer") || lower.includes("pending") || lower.includes("review"))
return { color: "#f2b66d", bg: "rgba(242,182,109,0.12)", border: "rgba(242,182,109,0.28)" };
return { color: "#8fa8c6", bg: "rgba(143,168,198,0.08)", border: "rgba(143,168,198,0.18)" };
}
function OutcomeBadge({ outcome }: { outcome: OutcomeKind }) {
const style = outcomeStyle(outcome);
return (
<span
style={{
display: "inline-block",
padding: "2px 8px",
borderRadius: 999,
fontSize: 10,
fontWeight: 800,
letterSpacing: "0.06em",
textTransform: "uppercase",
color: style.color,
background: style.bg,
border: `1px solid ${style.border}`,
}}
>
{outcome || "unknown"}
</span>
);
}
function SkeletonList() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{[1, 2, 3, 4].map((i) => (
<div key={i} className="skeleton-item" style={{ height: 62 }} />
))}
</div>
);
}
/* ─── Causal Flow Diagram ──────────────────────────────────────────── */
interface ChainStep {
id: string;
relationship: string;
content?: string;
type?: string;
[key: string]: unknown;
}
function RelationshipPill({ label }: { label: string }) {
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 0, position: "relative", margin: "0 auto" }}>
{/* Connector line top */}
<div style={{ width: 2, height: 12, background: "rgba(88,166,255,0.25)" }} />
{/* Pill */}
<div
style={{
padding: "3px 10px",
borderRadius: 999,
fontSize: 10,
fontWeight: 700,
letterSpacing: "0.06em",
textTransform: "uppercase",
color: "#79c0ff",
background: "rgba(88,166,255,0.1)",
border: "1px solid rgba(88,166,255,0.22)",
whiteSpace: "nowrap",
maxWidth: 260,
overflow: "hidden",
textOverflow: "ellipsis",
}}
title={label}
>
{label}
</div>
{/* Connector line bottom + arrow */}
<div style={{ width: 2, height: 10, background: "rgba(88,166,255,0.25)" }} />
<div style={{ width: 0, height: 0, borderLeft: "5px solid transparent", borderRight: "5px solid transparent", borderTop: "6px solid rgba(88,166,255,0.4)" }} />
</div>
);
}
function ChainNodeCard({ step, index }: { step: ChainStep; index: number }) {
const COLORS = ["#3E79F2", "#149287", "#2F9F61", "#555FD6", "#8A56D8", "#B65473", "#C9922E", "#4aa3ff"];
const color = COLORS[index % COLORS.length];
return (
<div
style={{
position: "relative",
padding: "14px 16px",
borderRadius: 12,
background: "linear-gradient(135deg, rgba(13,17,23,0.75), rgba(22,27,34,0.5))",
border: `1px solid ${color}33`,
boxShadow: `0 0 0 1px ${color}11, inset 0 1px 0 rgba(255,255,255,0.04)`,
borderLeft: `3px solid ${color}`,
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
<span
style={{
width: 8, height: 8, borderRadius: "50%",
background: color,
boxShadow: `0 0 8px ${color}`,
flexShrink: 0,
}}
/>
{step.type ? (
<span
style={{
fontSize: 10, fontWeight: 700, letterSpacing: "0.06em",
textTransform: "uppercase", color,
}}
>
{step.type}
</span>
) : null}
</div>
<div style={{ color: "#e6edf3", fontSize: 14, fontWeight: 600 }}>
{step.content || step.id}
</div>
{step.id && step.id !== step.content ? (
<div style={{ color: "#6a7f97", fontSize: 11, fontFamily: "monospace", marginTop: 3 }}>{step.id}</div>
) : null}
</div>
);
}
function CausalFlowDiagram({ chain, loading }: { chain: ChainStep[]; loading: boolean }) {
if (loading) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
{[1, 2, 3].map((i) => (
<div key={i} className="skeleton-item" style={{ height: 68 }} />
))}
</div>
);
}
if (chain.length === 0) {
return (
<div style={{ textAlign: "center", padding: "40px 24px", color: "#8b949e", fontSize: 13 }}>
No causal chain steps found for this decision.
</div>
);
}
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "stretch" }}>
{chain.map((step, index) => (
<div key={`${step.id}-${index}`} style={{ display: "flex", flexDirection: "column" }}>
<ChainNodeCard step={step} index={index} />
{index < chain.length - 1 ? (
<RelationshipPill label={chain[index + 1]?.relationship || "→"} />
) : null}
</div>
))}
</div>
);
}
/* ─── Main Workspace ──────────────────────────────────────────────── */
export function DecisionWorkspace() {
const [decisions, setDecisions] = useState<any[]>([]);
const [selectedDecision, setSelectedDecision] = useState<any | null>(null);
const [chain, setChain] = useState<ChainStep[]>([]);
const [loading, setLoading] = useState(false);
const [listLoading, setListLoading] = useState(true);
const [filterQuery, setFilterQuery] = useState("");
useEffect(() => {
const controller = new AbortController();
setListLoading(true);
fetch("/api/decisions", { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`Failed to load decisions: ${res.status}`);
return res.json();
})
.then((data) => {
setDecisions(data);
if (data.length > 0) void handleSelectDecision(data[0]);
})
.catch((err) => { if (err.name !== "AbortError") console.error(err); })
.finally(() => setListLoading(false));
return () => controller.abort();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const filteredDecisions = useMemo(() => {
if (!filterQuery.trim()) return decisions;
const q = filterQuery.toLowerCase();
return decisions.filter(
(d) =>
String(d.decision_id ?? "").toLowerCase().includes(q) ||
String(d.category ?? "").toLowerCase().includes(q) ||
String(d.outcome ?? "").toLowerCase().includes(q),
);
}, [decisions, filterQuery]);
const handleSelectDecision = async (d: any) => {
setSelectedDecision(d);
setLoading(true);
setChain([]);
const controller = new AbortController();
try {
const res = await fetch(`/api/decisions/${encodeURIComponent(d.decision_id)}/chain`, { signal: controller.signal });
if (!res.ok) throw new Error(`Failed to load chain: ${res.status}`);
const data = await res.json();
setChain(data.chain || []);
} catch (e) {
if ((e as DOMException).name !== "AbortError") console.error(e);
} finally {
setLoading(false);
}
return () => controller.abort();
};
return (
<div style={{ display: "flex", width: "100%", height: "100%", background: "#0d1117", overflow: "hidden" }}>
<style>{THEME_CSS}</style>
{/* Left Column — Decision List */}
<div
className="glass-panel"
style={{
width: 300,
display: "flex",
flexDirection: "column",
borderRadius: 0,
border: "none",
borderRight: "1px solid rgba(88,166,255,0.16)",
}}
>
{/* List header */}
<div style={{ padding: "20px 20px 14px", borderBottom: "1px solid rgba(255,255,255,0.06)", flexShrink: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12 }}>
<Scale size={16} color="#4aa3ff" />
<h2 style={{ color: "#ebf3ff", margin: 0, fontSize: 15, fontWeight: 700 }}>Decisions</h2>
{decisions.length > 0 ? (
<span style={{ color: "#6a7f97", fontSize: 11, marginLeft: "auto" }}>{decisions.length}</span>
) : null}
</div>
{/* Filter input */}
<div style={{ position: "relative" }}>
<Search
size={13}
color="#8b949e"
style={{ position: "absolute", left: 10, top: "50%", transform: "translateY(-50%)", pointerEvents: "none" }}
/>
<input
type="text"
placeholder="Filter decisions…"
value={filterQuery}
onChange={(e) => setFilterQuery(e.target.value)}
style={filterInputStyle}
/>
</div>
</div>
{/* Decision list */}
<div style={{ flex: 1, overflowY: "auto", padding: "12px 14px" }}>
{listLoading ? (
<SkeletonList />
) : filteredDecisions.length === 0 ? (
<div style={{ color: "#6a7f97", fontSize: 13, textAlign: "center", padding: "32px 12px" }}>
{decisions.length === 0 ? "No decisions available." : "No decisions match your filter."}
</div>
) : (
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
{filteredDecisions.map((d) => {
const isActive = selectedDecision?.decision_id === d.decision_id;
return (
<button
key={d.decision_id}
onClick={() => void handleSelectDecision(d)}
style={{
textAlign: "left",
padding: "10px 12px",
borderRadius: 10,
cursor: "pointer",
background: isActive
? "rgba(74,163,255,0.15)"
: "rgba(255,255,255,0.02)",
border: isActive
? "1px solid rgba(74,163,255,0.32)"
: "1px solid rgba(255,255,255,0.06)",
color: isActive ? "#ffffff" : "#c6d4e3",
transition: "all 160ms ease",
}}
>
<div style={{ fontWeight: 700, fontSize: 13, marginBottom: 4 }}>{d.decision_id}</div>
<div style={{ display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
{d.category ? (
<span style={{ fontSize: 11, color: "#8b949e" }}>{d.category}</span>
) : null}
{d.outcome ? <OutcomeBadge outcome={d.outcome} /> : null}
</div>
</button>
);
})}
</div>
)}
</div>
</div>
{/* Right Column — Decision Detail */}
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
{/* Radial accent */}
<div style={{ position: "absolute", inset: 0, background: "radial-gradient(ellipse at top right, rgba(88,166,255,0.04), transparent 55%)", pointerEvents: "none", zIndex: 0 }} />
{selectedDecision ? (
<div style={{ flex: 1, overflowY: "auto", padding: "28px 32px", position: "relative", zIndex: 1 }}>
{/* Decision header */}
<div style={{ marginBottom: 28 }}>
<div style={{ display: "flex", alignItems: "flex-start", gap: 14, flexWrap: "wrap" }}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ color: "#8b949e", fontSize: 11, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.07em", marginBottom: 6 }}>
Decision ID
</div>
<h1 style={{ color: "#ffffff", fontSize: 24, fontWeight: 800, letterSpacing: "-0.03em", margin: "0 0 8px 0", wordBreak: "break-word" }}>
{selectedDecision.decision_id}
</h1>
</div>
{selectedDecision.outcome ? <OutcomeBadge outcome={selectedDecision.outcome} /> : null}
</div>
{selectedDecision.category ? (
<div style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "4px 10px", borderRadius: 999, background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.08)", color: "#8b949e", fontSize: 12 }}>
{selectedDecision.category}
</div>
) : null}
</div>
{/* Causal Chain */}
<div className="glass-panel" style={{ padding: 24, borderRadius: 16 }}>
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 20 }}>
<div style={{ width: 6, height: 6, borderRadius: "50%", background: "linear-gradient(135deg, #4aa3ff, #f2b66d)", boxShadow: "0 0 10px rgba(74,163,255,0.4)" }} />
<h3 style={{ color: "#e6edf3", margin: 0, fontSize: 14, fontWeight: 700, letterSpacing: "0.02em" }}>
Causal Chain
</h3>
{chain.length > 0 && !loading ? (
<span style={{ color: "#6a7f97", fontSize: 11, marginLeft: "auto" }}>
{chain.length} step{chain.length !== 1 ? "s" : ""}
</span>
) : null}
</div>
<CausalFlowDiagram chain={chain} loading={loading} />
</div>
</div>
) : (
<div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "#8b949e", fontSize: 14 }}>
Select a decision to inspect its causal chain.
</div>
)}
</div>
</div>
);
}
/* ─── styles ─────────────────────────────────────────────────────── */
const filterInputStyle: React.CSSProperties = {
width: "100%",
padding: "7px 10px 7px 30px",
background: "rgba(0,0,0,0.25)",
border: "1px solid rgba(88,166,255,0.16)",
borderRadius: 8,
color: "#c6d4e3",
fontSize: 12,
outline: "none",
boxSizing: "border-box",
};
@@ -0,0 +1,105 @@
/**
* src/workspaces/DiffMergeWorkspace/DiffMergeWorkspace.tsx
*/
import { useState } from "react";
import { logEvent } from "../../store/registryStore";
const THEME_CSS = `
.glass-panel {
background: linear-gradient(135deg, rgba(13,17,23,0.75), rgba(22,27,34,0.6));
backdrop-filter: blur(16px) saturate(1.2);
-webkit-backdrop-filter: blur(16px) saturate(1.2);
border: 1px solid rgba(88,166,255,0.2);
box-shadow: 0 8px 32px rgba(0,0,0,0.5), inset 1px 1px 0 rgba(255,255,255,0.05);
}
`;
export function DiffMergeWorkspace() {
const [primaryId, setPrimaryId] = useState("n-primary-1");
const [duplicateId, setDuplicateId] = useState("n-dup-2");
const [msg, setMsg] = useState("");
const handleMerge = async () => {
try {
const res = await fetch("/api/enrich/merge", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ primary_id: primaryId, duplicate_ids: [duplicateId] })
});
const data = await res.json();
if (data.merged_into) {
setMsg(`Merge success: redirected ${data.edges_updated} edges to ${data.merged_into}`);
logEvent("merge", `Merged ${duplicateId}${data.merged_into} · ${data.edges_updated} edges redirected`, {
primary: data.merged_into,
duplicate: duplicateId,
edgesUpdated: data.edges_updated,
});
} else {
setMsg("Merge failed...");
}
} catch (err) {
setMsg("Error calling merge endpoint.");
}
};
return (
<div style={{ display: "flex", flexDirection: "column", width: "100%", height: "100%", background: "#0d1117", padding: 32, gap: 24, boxSizing: "border-box" }}>
<style>{THEME_CSS}</style>
<div>
<h1 style={{ margin: "0 0 8px 0", color: "#fff" }}>Entity Diff & Merge</h1>
<p style={{ margin: 0, color: "#8b949e" }}>Compare suspected duplicate entities and reconcile them.</p>
</div>
<div style={{ display: "flex", gap: 24, flex: 1 }}>
{/* Primary View */}
<div className="glass-panel" style={{ flex: 1, borderRadius: 12, padding: 24 }}>
<h3 style={{ color: "#58a6ff", margin: "0 0 16px 0", borderBottom: "1px solid rgba(88,166,255,0.2)", paddingBottom: 8 }}>Primary Entity</h3>
<label style={{ display: "block", color: "#c9d1d9", marginBottom: 8, fontSize: 13 }}>Primary Node ID</label>
<input
value={primaryId} onChange={e => setPrimaryId(e.target.value)}
style={{ width: "100%", background: "rgba(0,0,0,0.3)", border: "1px solid rgba(255,255,255,0.1)", color: "#fff", padding: "8px 12px", borderRadius: 6, marginBottom: 24 }}
/>
<div style={{ background: "rgba(0,0,0,0.2)", padding: 16, borderRadius: 6 }}>
<div style={{ color: "#8b949e", fontSize: 12, marginBottom: 4 }}>Name</div>
<div style={{ color: "#fff", fontSize: 14 }}>Sample Company Inc.</div>
<div style={{ color: "#8b949e", fontSize: 12, marginTop: 16, marginBottom: 4 }}>Founded</div>
<div style={{ color: "#fff", fontSize: 14 }}>2004-05-12</div>
</div>
</div>
{/* Duplicate View */}
<div className="glass-panel" style={{ flex: 1, borderRadius: 12, padding: 24 }}>
<h3 style={{ color: "#ff7b72", margin: "0 0 16px 0", borderBottom: "1px solid rgba(255,123,114,0.2)", paddingBottom: 8 }}>Duplicate Entity</h3>
<label style={{ display: "block", color: "#c9d1d9", marginBottom: 8, fontSize: 13 }}>Duplicate Node ID</label>
<input
value={duplicateId} onChange={e => setDuplicateId(e.target.value)}
style={{ width: "100%", background: "rgba(0,0,0,0.3)", border: "1px solid rgba(255,255,255,0.1)", color: "#fff", padding: "8px 12px", borderRadius: 6, marginBottom: 24 }}
/>
<div style={{ background: "rgba(0,0,0,0.2)", padding: 16, borderRadius: 6 }}>
<div style={{ color: "#d2a8ff", fontSize: 12, marginBottom: 4 }}>Name</div>
{/* Amber highlight for differing values */}
<div style={{ color: "#d29922", fontSize: 14, fontWeight: "bold" }}>Sample Company</div>
<div style={{ color: "#8b949e", fontSize: 12, marginTop: 16, marginBottom: 4 }}>Founded</div>
<div style={{ color: "#fff", fontSize: 14 }}>2004-05-12</div>
</div>
</div>
</div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div style={{ color: "#58a6ff" }}>{msg}</div>
<button
onClick={handleMerge}
style={{ background: "#238636", color: "#fff", border: "none", padding: "10px 24px", borderRadius: 6, fontWeight: 600, cursor: "pointer", fontSize: 16 }}
>
Confirm Merge
</button>
</div>
</div>
);
}
@@ -0,0 +1,450 @@
/**
* src/workspaces/EnrichWorkspace/EntityResolutionTab.tsx
*
* Entity Resolution run duplicate detection, review flagged pairs,
* perform one-click merges, and view merge history from the Registry.
*/
import { useState, useCallback } from "react";
import { ScanSearch, GitMerge, X, ChevronDown, ChevronRight, Loader2 } from "lucide-react";
import { logEvent, useRegistry } from "../../store/registryStore";
interface DedupPair {
a: { id: string; label: string; type: string };
b: { id: string; label: string; type: string };
score: number;
dismissed?: boolean;
}
interface RawDuplicateItem {
entity_a?: string | Record<string, unknown>;
entity_b?: string | Record<string, unknown>;
similarity?: number;
score?: number;
[key: string]: unknown;
}
function extractId(entity: string | Record<string, unknown> | undefined): string {
if (!entity) return "";
if (typeof entity === "string") return entity;
return String(entity.id ?? entity.text ?? JSON.stringify(entity));
}
function extractLabel(entity: string | Record<string, unknown> | undefined): string {
if (!entity) return "";
if (typeof entity === "string") return entity;
return String(entity.text ?? entity.label ?? entity.content ?? entity.id ?? "");
}
function extractType(entity: string | Record<string, unknown> | undefined): string {
if (!entity || typeof entity === "string") return "entity";
return String(entity.type ?? "entity");
}
function parseDuplicates(raw: RawDuplicateItem[]): DedupPair[] {
return raw.map((item) => ({
a: {
id: extractId(item.entity_a as string | Record<string, unknown>),
label: extractLabel(item.entity_a as string | Record<string, unknown>),
type: extractType(item.entity_a as string | Record<string, unknown>),
},
b: {
id: extractId(item.entity_b as string | Record<string, unknown>),
label: extractLabel(item.entity_b as string | Record<string, unknown>),
type: extractType(item.entity_b as string | Record<string, unknown>),
},
score: Number(item.similarity ?? item.score ?? 0),
}));
}
function ScoreBar({ score }: { score: number }) {
const pct = Math.min(100, Math.round(score * 100));
const color = score >= 0.9 ? "#ff7b72" : score >= 0.75 ? "#f2b66d" : "#4cc38a";
return (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<div style={{ flex: 1, height: 4, borderRadius: 999, background: "rgba(255,255,255,0.06)", overflow: "hidden" }}>
<div style={{ width: `${pct}%`, height: "100%", borderRadius: 999, background: color, transition: "width 300ms ease" }} />
</div>
<span style={{ fontSize: 11, fontWeight: 700, color, minWidth: 34, textAlign: "right" }}>
{pct}%
</span>
</div>
);
}
function PairRow({
pair,
onMerge,
onDismiss,
}: {
pair: DedupPair;
onMerge: (primaryId: string, duplicateId: string) => Promise<void>;
onDismiss: () => void;
}) {
const [expanded, setExpanded] = useState(false);
const [merging, setMerging] = useState(false);
const handleMerge = async () => {
setMerging(true);
await onMerge(pair.a.id, pair.b.id);
setMerging(false);
};
return (
<div style={pairCardStyle}>
<div style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
{/* Expand */}
<button onClick={() => setExpanded((v) => !v)} style={iconBtnStyle}>
{expanded ? <ChevronDown size={13} /> : <ChevronRight size={13} />}
</button>
{/* Entity Labels */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
<span style={entityChipStyle}>{pair.a.label || pair.a.id}</span>
<span style={{ color: "#f2b66d", fontSize: 12, fontWeight: 700 }}></span>
<span style={entityChipStyle}>{pair.b.label || pair.b.id}</span>
</div>
<div style={{ marginTop: 8 }}>
<ScoreBar score={pair.score} />
</div>
</div>
{/* Actions */}
<div style={{ display: "flex", gap: 6, flexShrink: 0 }}>
<button
onClick={() => void handleMerge()}
disabled={merging}
style={{
...actionBtnStyle,
background: "rgba(76,195,138,0.12)",
border: "1px solid rgba(76,195,138,0.28)",
color: "#4cc38a",
}}
>
{merging ? <Loader2 size={12} className="animate-spin" /> : <GitMerge size={12} />}
<span>Merge</span>
</button>
<button onClick={onDismiss} style={iconBtnStyle} title="Dismiss">
<X size={13} />
</button>
</div>
</div>
{/* Expanded diff */}
{expanded ? (
<div style={{ marginTop: 12, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
{[
{ label: "Primary (keep)", entity: pair.a, accentColor: "#4aa3ff" },
{ label: "Duplicate (remove)", entity: pair.b, accentColor: "#ff7b72" },
].map(({ label, entity, accentColor }) => (
<div key={entity.id} style={{ ...diffCardStyle, borderColor: `${accentColor}33` }}>
<div style={{ color: accentColor, fontSize: 10, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 6 }}>
{label}
</div>
<div style={{ color: "#e6edf3", fontSize: 13, fontWeight: 600 }}>{entity.label || entity.id}</div>
<div style={{ color: "#8b949e", fontSize: 11, marginTop: 3 }}>{entity.type}</div>
<div style={{ color: "#6a7f97", fontSize: 10, marginTop: 4, fontFamily: "monospace" }}>{entity.id}</div>
</div>
))}
</div>
) : null}
</div>
);
}
export function EntityResolutionTab() {
const [threshold, setThreshold] = useState(0.82);
const [scanning, setScanning] = useState(false);
const [pairs, setPairs] = useState<DedupPair[]>([]);
const [scanError, setScanError] = useState("");
const registryEntries = useRegistry();
const mergeHistory = registryEntries.filter((e) => e.op === "merge");
const handleScan = useCallback(async () => {
setScanning(true);
setScanError("");
try {
const res = await fetch("/api/enrich/dedup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ threshold }),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error((err as Record<string, string>).detail ?? `Scan failed (${res.status})`);
}
const data = await res.json();
const rawDuplicates: RawDuplicateItem[] = Array.isArray(data.duplicates)
? (data.duplicates as RawDuplicateItem[])
: [];
const parsed = parseDuplicates(rawDuplicates);
setPairs(parsed);
logEvent("import", `Dedup scan found ${parsed.length} flagged pair${parsed.length !== 1 ? "s" : ""} (threshold ${threshold.toFixed(2)})`, {
threshold,
flagged: parsed.length,
});
} catch (err) {
setScanError(err instanceof Error ? err.message : "Scan failed");
} finally {
setScanning(false);
}
}, [threshold]);
const handleMerge = useCallback(async (primaryId: string, duplicateId: string) => {
try {
const res = await fetch("/api/enrich/merge", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ primary_id: primaryId, duplicate_ids: [duplicateId] }),
});
if (!res.ok) throw new Error(`Merge failed (${res.status})`);
const data = await res.json();
logEvent("merge", `Merged ${duplicateId}${primaryId} · ${data.edges_updated ?? 0} edges redirected`, {
primary: primaryId,
duplicate: duplicateId,
edgesUpdated: data.edges_updated,
});
setPairs((prev) => prev.filter((p) => !(p.a.id === primaryId && p.b.id === duplicateId)));
} catch (err) {
console.error("[EntityResolution] merge failed", err);
}
}, []);
const handleDismiss = useCallback((index: number) => {
setPairs((prev) => prev.filter((_, i) => i !== index));
}, []);
return (
<div style={shellStyle}>
{/* Header */}
<div style={headerStyle}>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<ScanSearch size={18} color="#f2b66d" />
<div>
<div style={{ color: "#ebf3ff", fontSize: 16, fontWeight: 700 }}>Entity Resolution</div>
<div style={{ color: "#8b949e", fontSize: 12 }}>Detect and merge duplicate entities in the knowledge graph</div>
</div>
</div>
</div>
{/* Scan controls */}
<div style={controlsCardStyle}>
<div style={{ display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
<div style={{ flex: 1, minWidth: 240 }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 6 }}>
<label style={{ color: "#c6d4e3", fontSize: 12, fontWeight: 600 }}>Similarity Threshold</label>
<span style={{ color: "#f2b66d", fontSize: 12, fontWeight: 700 }}>{threshold.toFixed(2)}</span>
</div>
<input
type="range"
min={0.5}
max={0.99}
step={0.01}
value={threshold}
onChange={(e) => setThreshold(parseFloat(e.target.value))}
style={{ width: "100%", accentColor: "#f2b66d", cursor: "pointer" }}
/>
<div style={{ display: "flex", justifyContent: "space-between", color: "#6a7f97", fontSize: 10, marginTop: 2 }}>
<span>More results (0.50)</span>
<span>Fewer, higher confidence (0.99)</span>
</div>
</div>
<button
onClick={() => void handleScan()}
disabled={scanning}
style={scanBtnStyle}
>
{scanning ? <Loader2 size={14} className="animate-spin" /> : <ScanSearch size={14} />}
<span>{scanning ? "Scanning…" : "Run Dedup Scan"}</span>
</button>
</div>
{scanError ? (
<div style={{ color: "#ff7b72", fontSize: 12, marginTop: 8 }}>{scanError}</div>
) : null}
</div>
<div style={{ flex: 1, overflow: "hidden", display: "flex", gap: 0 }}>
{/* Flagged pairs */}
<div style={{ flex: 1, overflowY: "auto", padding: "16px 24px", display: "flex", flexDirection: "column", gap: 10 }}>
{pairs.length > 0 ? (
<>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }}>
<div style={{ color: "#8b949e", fontSize: 12, fontWeight: 600 }}>
{pairs.length} flagged pair{pairs.length !== 1 ? "s" : ""}
</div>
<button onClick={() => setPairs([])} style={clearAllBtnStyle}>Clear all</button>
</div>
{pairs.map((pair, index) => (
<PairRow
key={`${pair.a.id}:${pair.b.id}`}
pair={pair}
onMerge={handleMerge}
onDismiss={() => handleDismiss(index)}
/>
))}
</>
) : (
<div style={emptyStateStyle}>
<ScanSearch size={36} color="rgba(242,182,109,0.15)" />
<div style={{ color: "#8b949e", fontSize: 14, marginTop: 12, fontWeight: 500 }}>
No flagged pairs
</div>
<div style={{ color: "#6a7f97", fontSize: 12, marginTop: 4, textAlign: "center", maxWidth: 280 }}>
Set a similarity threshold and run a dedup scan to detect potential duplicates.
</div>
</div>
)}
</div>
{/* Merge history sidebar */}
{mergeHistory.length > 0 ? (
<div style={historyPanelStyle}>
<div style={{ color: "#8b949e", fontSize: 11, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 10 }}>
Merge History
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
{mergeHistory.map((entry) => (
<div key={entry.id} style={historyRowStyle}>
<GitMerge size={11} color="#f2b66d" />
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ color: "#c6d4e3", fontSize: 11, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{entry.summary}
</div>
<div style={{ color: "#6a7f97", fontSize: 10 }}>
{entry.timestamp.toLocaleTimeString()}
</div>
</div>
</div>
))}
</div>
</div>
) : null}
</div>
</div>
);
}
/* ─── styles ─────────────────────────────────────────────────────── */
const shellStyle: React.CSSProperties = {
display: "flex",
flexDirection: "column",
width: "100%",
height: "100%",
background: "#0d1117",
overflow: "hidden",
};
const headerStyle: React.CSSProperties = {
padding: "20px 24px 16px",
borderBottom: "1px solid rgba(88,166,255,0.1)",
flexShrink: 0,
};
const controlsCardStyle: React.CSSProperties = {
margin: "16px 24px",
padding: "16px 20px",
borderRadius: 14,
background: "linear-gradient(135deg, rgba(13,17,23,0.75), rgba(22,27,34,0.6))",
border: "1px solid rgba(242,182,109,0.18)",
flexShrink: 0,
};
const scanBtnStyle: React.CSSProperties = {
display: "inline-flex",
alignItems: "center",
gap: 7,
padding: "10px 18px",
borderRadius: 10,
background: "linear-gradient(135deg, rgba(242,182,109,0.22), rgba(242,182,109,0.1))",
border: "1px solid rgba(242,182,109,0.32)",
color: "#f2b66d",
fontSize: 13,
fontWeight: 700,
cursor: "pointer",
flexShrink: 0,
};
const pairCardStyle: React.CSSProperties = {
padding: "12px 14px",
borderRadius: 12,
background: "linear-gradient(135deg, rgba(13,17,23,0.6), rgba(22,27,34,0.4))",
border: "1px solid rgba(255,255,255,0.07)",
};
const entityChipStyle: React.CSSProperties = {
display: "inline-block",
padding: "4px 10px",
borderRadius: 8,
background: "rgba(255,255,255,0.04)",
border: "1px solid rgba(255,255,255,0.08)",
color: "#e6edf3",
fontSize: 12,
fontWeight: 600,
};
const actionBtnStyle: React.CSSProperties = {
display: "inline-flex",
alignItems: "center",
gap: 5,
padding: "5px 10px",
borderRadius: 8,
fontSize: 11,
fontWeight: 700,
cursor: "pointer",
};
const iconBtnStyle: React.CSSProperties = {
background: "transparent",
border: "none",
color: "#8b949e",
cursor: "pointer",
padding: 4,
borderRadius: 6,
display: "flex",
alignItems: "center",
};
const diffCardStyle: React.CSSProperties = {
padding: "10px 12px",
borderRadius: 10,
background: "rgba(0,0,0,0.2)",
border: "1px solid transparent",
};
const historyPanelStyle: React.CSSProperties = {
width: 240,
borderLeft: "1px solid rgba(255,255,255,0.06)",
padding: "16px 16px",
overflowY: "auto",
flexShrink: 0,
};
const historyRowStyle: React.CSSProperties = {
display: "flex",
alignItems: "flex-start",
gap: 7,
padding: "8px 0",
borderBottom: "1px solid rgba(255,255,255,0.04)",
};
const emptyStateStyle: React.CSSProperties = {
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
padding: 40,
minHeight: 200,
};
const clearAllBtnStyle: React.CSSProperties = {
background: "transparent",
border: "none",
color: "#8b949e",
fontSize: 12,
cursor: "pointer",
padding: "2px 6px",
borderRadius: 6,
};
@@ -0,0 +1,284 @@
/**
* src/workspaces/EnrichWorkspace/RegistryTab.tsx
*
* Document Registry a live, filterable chronological audit log of every
* KG / Ontology mutation that occurred in this session.
*/
import { useState } from "react";
import { ClipboardList, Filter, Trash2, ChevronDown, ChevronRight } from "lucide-react";
import { useRegistry, clearRegistry, type RegistryEntryOp } from "../../store/registryStore";
const OP_META: Record<
RegistryEntryOp,
{ label: string; color: string; bg: string; border: string }
> = {
import: { label: "IMPORT", color: "#4aa3ff", bg: "rgba(74,163,255,0.12)", border: "rgba(74,163,255,0.28)" },
export: { label: "EXPORT", color: "#8fa8c6", bg: "rgba(143,168,198,0.08)", border: "rgba(143,168,198,0.18)" },
merge: { label: "MERGE", color: "#f2b66d", bg: "rgba(242,182,109,0.12)", border: "rgba(242,182,109,0.28)" },
"add-node": { label: "ADD NODE", color: "#4cc38a", bg: "rgba(76,195,138,0.12)", border: "rgba(76,195,138,0.28)" },
"add-edge": { label: "ADD EDGE", color: "#4cc38a", bg: "rgba(76,195,138,0.10)", border: "rgba(76,195,138,0.22)" },
delete: { label: "DELETE", color: "#ff7b72", bg: "rgba(255,123,114,0.12)", border: "rgba(255,123,114,0.28)" },
infer: { label: "INFER", color: "#d2a8ff", bg: "rgba(210,168,255,0.12)", border: "rgba(210,168,255,0.28)" },
"vocab-import": { label: "VOCAB", color: "#79c0ff", bg: "rgba(121,192,255,0.12)", border: "rgba(121,192,255,0.28)" },
};
const ALL_OPS: (RegistryEntryOp | "all")[] = [
"all", "import", "export", "merge", "add-node", "add-edge", "infer", "delete", "vocab-import",
];
function formatTimestamp(date: Date): string {
return date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit" });
}
function formatDate(date: Date): string {
return date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
}
function EntryRow({ entry }: { entry: ReturnType<typeof useRegistry>[number] }) {
const [expanded, setExpanded] = useState(false);
const meta = OP_META[entry.op];
const hasDetail = entry.detail && Object.keys(entry.detail).length > 0;
return (
<div style={entryCardStyle}>
<div style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
{/* Op Badge */}
<span
style={{
flexShrink: 0,
display: "inline-block",
padding: "3px 8px",
borderRadius: 999,
fontSize: 10,
fontWeight: 800,
letterSpacing: "0.07em",
color: meta.color,
background: meta.bg,
border: `1px solid ${meta.border}`,
marginTop: 1,
}}
>
{meta.label}
</span>
{/* Content */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ color: "#e6edf3", fontSize: 13, fontWeight: 500, wordBreak: "break-word" }}>
{entry.summary}
</div>
<div style={{ color: "#8b949e", fontSize: 11, marginTop: 3 }}>
{formatDate(entry.timestamp)} · {formatTimestamp(entry.timestamp)}
</div>
</div>
{/* Expand toggle */}
{hasDetail ? (
<button
onClick={() => setExpanded((v) => !v)}
title={expanded ? "Collapse details" : "Expand details"}
style={expandBtnStyle}
>
{expanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
</button>
) : null}
</div>
{/* Expanded detail */}
{expanded && hasDetail ? (
<pre style={detailPreStyle}>
{JSON.stringify(entry.detail, null, 2)}
</pre>
) : null}
</div>
);
}
export function RegistryTab() {
const entries = useRegistry();
const [activeFilter, setActiveFilter] = useState<RegistryEntryOp | "all">("all");
const filtered = activeFilter === "all"
? entries
: entries.filter((e) => e.op === activeFilter);
return (
<div style={shellStyle}>
{/* Header */}
<div style={headerStyle}>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<ClipboardList size={18} color="#4aa3ff" />
<div>
<div style={{ color: "#ebf3ff", fontSize: 16, fontWeight: 700 }}>Document Registry</div>
<div style={{ color: "#8b949e", fontSize: 12 }}>
Audit log of all KG and Ontology mutations this session
</div>
</div>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<span style={{ color: "#8fa8c6", fontSize: 12 }}>
{entries.length} event{entries.length !== 1 ? "s" : ""}
</span>
{entries.length > 0 ? (
<button
onClick={clearRegistry}
title="Clear all events"
style={clearBtnStyle}
>
<Trash2 size={13} />
<span>Clear</span>
</button>
) : null}
</div>
</div>
{/* Filter pills */}
<div style={filterBarStyle}>
<Filter size={13} color="#8fa8c6" />
<div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
{ALL_OPS.map((op) => {
const isActive = op === activeFilter;
const meta = op === "all" ? null : OP_META[op as RegistryEntryOp];
return (
<button
key={op}
onClick={() => setActiveFilter(op as typeof activeFilter)}
style={{
padding: "4px 10px",
borderRadius: 999,
fontSize: 11,
fontWeight: 600,
cursor: "pointer",
border: isActive
? `1px solid ${meta?.border ?? "rgba(127,208,255,0.35)"}`
: "1px solid rgba(255,255,255,0.06)",
background: isActive
? (meta?.bg ?? "rgba(74,163,255,0.14)")
: "transparent",
color: isActive
? (meta?.color ?? "#8ed3ff")
: "#8b949e",
transition: "all 140ms ease",
}}
>
{op === "all" ? "All" : (meta?.label ?? op)}
</button>
);
})}
</div>
</div>
{/* Feed */}
<div style={feedStyle}>
{filtered.length === 0 ? (
<div style={emptyStateStyle}>
<ClipboardList size={36} color="rgba(127,208,255,0.15)" />
<div style={{ color: "#8b949e", fontSize: 14, marginTop: 12, fontWeight: 500 }}>
No events recorded yet
</div>
<div style={{ color: "#6a7f97", fontSize: 12, marginTop: 4, textAlign: "center", maxWidth: 300 }}>
Import a file, run reasoning, or merge entities to see activity appear here.
</div>
</div>
) : (
filtered.map((entry) => <EntryRow key={entry.id} entry={entry} />)
)}
</div>
</div>
);
}
/* ─── styles ─────────────────────────────────────────────────────── */
const shellStyle: React.CSSProperties = {
display: "flex",
flexDirection: "column",
width: "100%",
height: "100%",
background: "#0d1117",
overflow: "hidden",
};
const headerStyle: React.CSSProperties = {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "20px 24px 16px",
borderBottom: "1px solid rgba(88,166,255,0.1)",
flexShrink: 0,
};
const filterBarStyle: React.CSSProperties = {
display: "flex",
alignItems: "center",
gap: 10,
padding: "12px 24px",
borderBottom: "1px solid rgba(255,255,255,0.05)",
flexShrink: 0,
};
const feedStyle: React.CSSProperties = {
flex: 1,
overflowY: "auto",
padding: "16px 24px",
display: "flex",
flexDirection: "column",
gap: 8,
};
const entryCardStyle: React.CSSProperties = {
padding: "12px 14px",
borderRadius: 12,
background: "linear-gradient(135deg, rgba(13,17,23,0.6), rgba(22,27,34,0.4))",
border: "1px solid rgba(255,255,255,0.06)",
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.03)",
};
const expandBtnStyle: React.CSSProperties = {
flexShrink: 0,
background: "transparent",
border: "none",
color: "#8b949e",
cursor: "pointer",
padding: 4,
borderRadius: 6,
display: "flex",
alignItems: "center",
};
const detailPreStyle: React.CSSProperties = {
marginTop: 10,
padding: "10px 12px",
borderRadius: 8,
background: "rgba(0,0,0,0.28)",
border: "1px solid rgba(255,255,255,0.06)",
color: "#79c0ff",
fontSize: 11,
fontFamily: "'JetBrains Mono', monospace",
overflowX: "auto",
whiteSpace: "pre-wrap",
wordBreak: "break-all",
};
const clearBtnStyle: React.CSSProperties = {
display: "inline-flex",
alignItems: "center",
gap: 5,
padding: "5px 10px",
borderRadius: 8,
border: "1px solid rgba(255,123,114,0.22)",
background: "rgba(255,123,114,0.06)",
color: "#ff7b72",
fontSize: 12,
fontWeight: 600,
cursor: "pointer",
};
const emptyStateStyle: React.CSSProperties = {
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
padding: 40,
minHeight: 280,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,746 @@
import type { CSSProperties } from "react";
import { Loader2 } from "lucide-react";
import { graph } from "../../store/graphStore";
import { GRAPH_THEME, withAlpha } from "./graphTheme";
import type { GraphSelectedNodeKind } from "./types";
export type LinkPrediction = {
target: string;
type: string;
label?: string;
score: number;
};
export type PathResponse = {
path: string[];
edge_ids?: string[];
total_weight: number;
hop_count: number;
distance_band: "direct" | "near" | "mid-range" | "distant";
// FR-1 distance intelligence enrichment
semantic_similarity?: number | null;
path_coherence_score?: number | null;
confidence_decay?: number | null;
bottleneck_node?: string | null;
alternative_path_count?: number;
interpretation?: string;
};
export interface GraphInspectorPanelProps {
nodeId: string;
inspectableNodeId?: string | null;
selectedNodeKind?: GraphSelectedNodeKind;
canActivateFocused?: boolean;
focusedUnavailableReason?: string | null;
predictions: LinkPrediction[];
predictionType: string;
onPredictionTypeChange: (value: string) => void;
onRunPredictions: () => void;
isRunningPredictions?: boolean;
pathTargetId: string;
onPathTargetChange: (value: string) => void;
onTracePath: () => void;
pathResult: PathResponse | null;
onDownloadProvenance: (format: "json" | "markdown") => void;
onFocusNode?: (nodeId: string) => void;
}
const PROVENANCE_KEYS = ["source", "source_url", "pmid", "pmids", "evidence", "provenance", "confidence"] as const;
function sourceAttribution(properties: Record<string, unknown>) {
return PROVENANCE_KEYS
.filter((key) => key in properties)
.map((key) => ({ key, value: properties[key] }));
}
/* ─── Path Distance Intelligence Panel ──────────────────────────── */
const BAND_COLORS: Record<string, string> = {
direct: "#3fb950",
near: "#79c0ff",
"mid-range": "#e3b341",
distant: "#ff7b72",
};
function PathDistanceIntelPanel({ result }: { result: PathResponse }) {
const bandColor = BAND_COLORS[result.distance_band] ?? "#8b949e";
const hasMetrics =
result.confidence_decay != null ||
result.semantic_similarity != null ||
result.path_coherence_score != null ||
result.bottleneck_node != null;
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 8 }}>
{/* distance band + alt paths */}
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }}>
<span
style={{
padding: "3px 8px",
borderRadius: 999,
background: withAlpha(bandColor, 0.14),
border: `1px solid ${withAlpha(bandColor, 0.3)}`,
color: bandColor,
fontSize: 11,
fontWeight: 700,
}}
>
{result.distance_band} · {result.hop_count} hop{result.hop_count !== 1 ? "s" : ""}
</span>
{(result.alternative_path_count ?? 0) > 0 && (
<span style={subtleChipStyle}>{result.alternative_path_count} alt path{result.alternative_path_count !== 1 ? "s" : ""}</span>
)}
</div>
{/* metric grid */}
{hasMetrics && <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
{result.confidence_decay != null && (
<div style={metricCardStyle}>
<div style={metricLabelStyle}>Confidence Decay</div>
<div
style={{
...metricValueStyle,
color: result.confidence_decay > 0.6 ? "#3fb950" : result.confidence_decay > 0.3 ? "#e3b341" : "#ff7b72",
}}
>
{(result.confidence_decay * 100).toFixed(1)}%
</div>
<div style={metricBarTrackStyle}>
<div
style={{
...metricBarFillStyle,
width: `${result.confidence_decay * 100}%`,
background:
result.confidence_decay > 0.6 ? "#3fb950" : result.confidence_decay > 0.3 ? "#e3b341" : "#ff7b72",
}}
/>
</div>
</div>
)}
{result.semantic_similarity != null && (
<div style={metricCardStyle}>
<div style={metricLabelStyle}>Semantic Sim.</div>
<div style={{ ...metricValueStyle, color: "#79c0ff" }}>
{(result.semantic_similarity * 100).toFixed(1)}%
</div>
<div style={metricBarTrackStyle}>
<div style={{ ...metricBarFillStyle, width: `${result.semantic_similarity * 100}%`, background: "#79c0ff" }} />
</div>
</div>
)}
{result.path_coherence_score != null && (
<div style={metricCardStyle}>
<div style={metricLabelStyle}>Path Coherence</div>
<div style={{ ...metricValueStyle, color: "#a5d6a7" }}>
{(result.path_coherence_score * 100).toFixed(1)}%
</div>
</div>
)}
{result.bottleneck_node && (
<div style={metricCardStyle}>
<div style={metricLabelStyle}>Bottleneck</div>
<div
style={{
...metricValueStyle,
color: "#e3b341",
fontSize: 11,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
title={result.bottleneck_node}
>
{getNodeLabel(result.bottleneck_node)}
</div>
</div>
)}
</div>}
{/* interpretation */}
{result.interpretation && (
<div
style={{
padding: "8px 10px",
background: "rgba(88,166,255,0.06)",
borderRadius: 8,
border: "1px solid rgba(88,166,255,0.14)",
color: "#a0b4cc",
fontSize: 12,
lineHeight: 1.5,
}}
>
{result.interpretation}
</div>
)}
</div>
);
}
/* ─── Path Flow Visualizer ──────────────────────────────────────── */
function getNodeLabel(nodeId: string): string {
if (!graph.hasNode(nodeId)) return nodeId;
const attrs = graph.getNodeAttributes(nodeId) as { label?: string; content?: string };
return String(attrs.label ?? attrs.content ?? nodeId);
}
function getEdgeLabelBetween(sourceId: string, targetId: string, edgeIds?: string[]): string {
// Try to find the specific edge from edgeIds first
if (edgeIds) {
for (const edgeId of edgeIds) {
if (graph.hasEdge(edgeId)) {
const [src, tgt] = graph.extremities(edgeId);
if ((src === sourceId && tgt === targetId) || (src === targetId && tgt === sourceId)) {
const attrs = graph.getEdgeAttributes(edgeId) as { edgeType?: string };
return attrs.edgeType ?? "→";
}
}
}
}
// Fallback: find any edge between the pair
if (graph.hasNode(sourceId) && graph.hasNode(targetId)) {
let label = "→";
graph.forEachEdge(sourceId, targetId, (_edgeId, attrs) => {
const edgeAttrs = attrs as { edgeType?: string };
if (edgeAttrs.edgeType) label = edgeAttrs.edgeType;
});
return label;
}
return "→";
}
function PathFlowViz({
path,
edgeIds,
totalWeight,
bottleneckNodeId,
onFocusNode,
}: {
path: string[];
edgeIds?: string[];
totalWeight: number;
bottleneckNodeId?: string | null;
onFocusNode?: (nodeId: string) => void;
}) {
if (path.length === 0) {
return <div style={emptyTextStyle}>No path found between the selected nodes.</div>;
}
return (
<div>
{/* Horizontal scrollable chip flow */}
<div style={pathFlowContainerStyle}>
{path.map((nodeId, index) => {
const label = getNodeLabel(nodeId);
const edgeLabel =
index < path.length - 1
? getEdgeLabelBetween(nodeId, path[index + 1], edgeIds)
: null;
return (
<div key={`${nodeId}-${index}`} style={{ display: "contents" }}>
{/* Node chip */}
<button
onClick={() => onFocusNode?.(nodeId)}
title={nodeId === bottleneckNodeId ? `Bottleneck: ${nodeId}` : `Focus: ${nodeId}`}
style={{
...pathNodeChipStyle,
cursor: onFocusNode ? "pointer" : "default",
...(nodeId === bottleneckNodeId
? { border: "1px solid rgba(227,179,65,0.5)", background: "rgba(227,179,65,0.12)" }
: {}),
}}
>
<span style={pathNodeIndexStyle}>{index + 1}</span>
<span style={{ maxWidth: 120, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{label}
</span>
</button>
{/* Edge connector */}
{edgeLabel !== null ? (
<div style={pathEdgeConnectorStyle}>
<div style={{ width: 16, height: 1, background: "rgba(88,166,255,0.3)" }} />
<span style={pathEdgeLabelStyle}>{edgeLabel}</span>
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{ width: 12, height: 1, background: "rgba(88,166,255,0.3)" }} />
<div style={{ width: 0, height: 0, borderTop: "4px solid transparent", borderBottom: "4px solid transparent", borderLeft: "5px solid rgba(88,166,255,0.4)" }} />
</div>
</div>
) : null}
</div>
);
})}
</div>
{/* Weight badge */}
<div style={{ marginTop: 8, display: "flex", alignItems: "center", gap: 8 }}>
<span style={{ color: "#6a7f97", fontSize: 11 }}>Total weight:</span>
<span style={{ color: "#79c0ff", fontSize: 12, fontWeight: 700 }}>{totalWeight.toFixed(3)}</span>
<span style={{ color: "#6a7f97", fontSize: 11 }}>·</span>
<span style={{ color: "#6a7f97", fontSize: 11 }}>{path.length} hops</span>
</div>
</div>
);
}
/* ─── Main Panel ─────────────────────────────────────────────────── */
export function GraphInspectorPanel({
nodeId,
inspectableNodeId,
selectedNodeKind = "none",
canActivateFocused = false,
focusedUnavailableReason = null,
predictions,
predictionType,
onPredictionTypeChange,
onRunPredictions,
isRunningPredictions = false,
pathTargetId,
onPathTargetChange,
onTracePath,
pathResult,
onDownloadProvenance,
onFocusNode,
}: GraphInspectorPanelProps) {
if (!nodeId) {
return (
<div style={{ padding: 32, textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center", gap: 12, marginTop: 32 }}>
<div style={{ width: 40, height: 40, borderRadius: "50%", background: "rgba(98, 226, 205, 0.07)", border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`, display: "flex", alignItems: "center", justifyContent: "center" }}>
<div style={{ width: 14, height: 14, borderRadius: "50%", background: GRAPH_THEME.ui.timeline.playheadSoft }} />
</div>
<p style={{ color: GRAPH_THEME.ui.text.muted, fontSize: 14, margin: 0, lineHeight: 1.6 }}>
Search for a node or click one in the canvas to inspect its properties.
</p>
</div>
);
}
const resolvedNodeId = inspectableNodeId && graph.hasNode(inspectableNodeId) ? inspectableNodeId : null;
const directlyInspectable = graph.hasNode(nodeId);
const effectiveNodeId = directlyInspectable ? nodeId : resolvedNodeId;
const actionNodeId = directlyInspectable ? nodeId : resolvedNodeId;
const groupedDisplaySelection = selectedNodeKind === "grouped" && !directlyInspectable;
if (!effectiveNodeId) {
return (
<aside style={{ padding: 24, display: "flex", flexDirection: "column", gap: 16 }}>
<div style={{ borderBottom: `1px solid ${GRAPH_THEME.ui.surface.divider}`, paddingBottom: 16 }}>
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }}>
<span style={{ background: GRAPH_THEME.ui.timeline.playhead, boxShadow: "0 0 10px rgba(98, 226, 205, 0.34)", width: 8, height: 8, borderRadius: "50%" }} />
<span style={{ color: GRAPH_THEME.ui.timeline.playhead, fontSize: 12, fontWeight: 700 }}>Selection</span>
</div>
<h3 style={{ margin: 0, color: GRAPH_THEME.ui.text.strong, fontSize: 20, fontWeight: 700, wordBreak: "break-word" }}>
{nodeId}
</h3>
<div style={{ color: GRAPH_THEME.ui.text.muted, fontSize: 12, marginTop: 6, fontFamily: "monospace", wordBreak: "break-all" }}>{nodeId}</div>
</div>
<div style={groupedSelectionNoticeStyle}>
<div style={{ color: GRAPH_THEME.ui.text.strong, fontWeight: 600, marginBottom: 6 }}>Selected item is not directly inspectable in the current graph.</div>
<div style={{ color: GRAPH_THEME.ui.text.body, fontSize: 13, lineHeight: 1.6 }}>
{canActivateFocused
? "Activate Focused mode to resolve this grouped selection to its canonical node."
: (focusedUnavailableReason ?? "Focused mode is unavailable for the current selection.")}
</div>
</div>
</aside>
);
}
const attributes = graph.getNodeAttributes(effectiveNodeId) as {
color?: string;
content?: string;
label?: string;
nodeType?: string;
valid_from?: string | null;
valid_until?: string | null;
properties?: Record<string, unknown>;
};
const properties = attributes?.properties ?? {};
const attribution = sourceAttribution(properties);
const accentColor = attributes?.color || "#58a6ff";
const propertyEntries = Object.entries(properties).filter(
([key]) =>
!["x","y","valid_from","valid_until","content","source","source_url","pmid","pmids","evidence","provenance","confidence"].includes(key),
);
return (
<aside style={{ padding: 24, display: "flex", flexDirection: "column", gap: 18 }}>
{/* Node identity */}
<div style={{ borderBottom: `1px solid ${GRAPH_THEME.ui.surface.divider}`, paddingBottom: 16 }}>
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }}>
<span style={{ background: accentColor, boxShadow: `0 0 10px ${accentColor}`, width: 8, height: 8, borderRadius: "50%" }} />
<span style={{ color: accentColor, fontSize: 12, fontWeight: 700 }}>
{groupedDisplaySelection ? "Grouped Selection" : (attributes?.nodeType || "Entity")}
</span>
</div>
<h3 style={{ margin: 0, color: GRAPH_THEME.ui.text.strong, fontSize: 20, fontWeight: 700, wordBreak: "break-word" }}>
{String(attributes?.label ?? effectiveNodeId)}
</h3>
<div style={{ color: GRAPH_THEME.ui.text.muted, fontSize: 12, marginTop: 6, fontFamily: "monospace", wordBreak: "break-all" }}>
{groupedDisplaySelection ? nodeId : effectiveNodeId}
</div>
{groupedDisplaySelection ? (
<div style={groupedSelectionNoticeStyle}>
<div style={{ color: GRAPH_THEME.ui.text.strong, fontWeight: 600, marginBottom: 6 }}>This grouped item stays display-level until you explicitly enter Focused mode.</div>
<div style={{ color: GRAPH_THEME.ui.text.body, fontSize: 13, lineHeight: 1.6 }}>
{canActivateFocused
? `Canonical node available: ${effectiveNodeId}`
: (focusedUnavailableReason ?? "Focused mode is unavailable for the current selection.")}
</div>
</div>
) : null}
<div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginTop: 12 }}>
{attributes?.valid_from || attributes?.valid_until ? (
<span style={subtleChipStyle}>temporal</span>
) : null}
{attribution.length ? <span style={subtleChipStyle}>{attribution.length} source fields</span> : null}
{predictions.length ? <span style={subtleChipStyle}>{predictions.length} candidate links</span> : null}
</div>
</div>
{/* Temporal bounds */}
{(attributes?.valid_from || attributes?.valid_until) ? (
<div style={{ padding: "10px 12px", background: "rgba(233, 196, 122, 0.075)", border: "1px solid rgba(233, 196, 122, 0.22)", borderRadius: 8, fontSize: 12, color: GRAPH_THEME.palette.accent.selected, fontFamily: "monospace" }}>
{attributes?.valid_from ? <div>from: {attributes.valid_from}</div> : null}
{attributes?.valid_until ? <div>until: {attributes.valid_until}</div> : null}
</div>
) : null}
{/* Actions */}
<section style={sectionStyle}>
<div style={sectionTitleStyle}>Actions</div>
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<button
style={{ ...actionButtonStyle, width: "100%", justifyContent: "center", opacity: isRunningPredictions ? 0.7 : 1 }}
onClick={onRunPredictions}
disabled={isRunningPredictions || !actionNodeId}
>
{isRunningPredictions ? (
<Loader2 size={14} className="animate-spin" style={{ marginRight: 6 }} />
) : null}
{isRunningPredictions ? "Running…" : "Run Link Prediction"}
</button>
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
<button style={secondaryActionButtonStyle} onClick={() => onDownloadProvenance("json")} disabled={!actionNodeId}>
Provenance JSON
</button>
<button style={secondaryActionButtonStyle} onClick={() => onDownloadProvenance("markdown")} disabled={!actionNodeId}>
Provenance MD
</button>
</div>
</div>
<input
value={predictionType}
onChange={(event) => onPredictionTypeChange(event.target.value)}
placeholder="Optional candidate type filter, e.g. disease"
style={inputStyle}
/>
</section>
{/* Trace Path */}
<section style={sectionStyle}>
<div style={sectionTitleStyle}>Trace Path</div>
<input
value={pathTargetId}
onChange={(event) => onPathTargetChange(event.target.value)}
placeholder="Target node ID"
style={inputStyle}
/>
<button style={actionButtonStyle} onClick={onTracePath} disabled={!actionNodeId}>Trace Causal Path</button>
{pathResult?.path?.length ? (
<>
<PathFlowViz
path={pathResult.path}
edgeIds={pathResult.edge_ids}
totalWeight={pathResult.total_weight}
bottleneckNodeId={pathResult.bottleneck_node}
onFocusNode={onFocusNode}
/>
<PathDistanceIntelPanel result={pathResult} />
</>
) : (
<div style={emptyTextStyle}>
Choose a target or click a candidate prediction to prepare a path trace.
</div>
)}
</section>
{/* Candidate Links */}
<details className="node-panel-collapse" open={predictions.length > 0}>
<summary className="node-panel-summary">Candidate Links</summary>
<div className="node-panel-body">
{predictions.length > 0 ? (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{predictions.map((prediction) => (
<button
key={`${prediction.target}-${prediction.type}`}
style={predictionCardStyle}
onClick={() => onPathTargetChange(prediction.target)}
>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 8 }}>
<div>
<div style={{ color: GRAPH_THEME.ui.text.strong, fontWeight: 600 }}>{prediction.label || prediction.target}</div>
<div style={{ color: GRAPH_THEME.ui.text.muted, fontSize: 12 }}>{prediction.type}</div>
</div>
<div style={{ flexShrink: 0 }}>
<div style={{
padding: "2px 7px",
borderRadius: 999,
fontSize: 10,
fontWeight: 700,
background: GRAPH_THEME.ui.timeline.playheadSoft,
border: `1px solid ${GRAPH_THEME.ui.control.activeBorder}`,
color: GRAPH_THEME.ui.timeline.playhead,
}}>
{(prediction.score * 100).toFixed(1)}%
</div>
</div>
</div>
</button>
))}
</div>
) : isRunningPredictions ? (
<div style={{ display: "flex", alignItems: "center", gap: 8, padding: 8, color: "#8b949e", fontSize: 12 }}>
<Loader2 size={13} className="animate-spin" />
<span>Computing candidate links</span>
</div>
) : (
<div style={emptyTextStyle}>Run link prediction to surface likely next-hop relationships.</div>
)}
</div>
</details>
{/* Source Attribution */}
<details className="node-panel-collapse">
<summary className="node-panel-summary">Source Attribution</summary>
<div className="node-panel-body">
{attribution.length ? (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{attribution.map(({ key, value }) => (
<div key={key} style={propertyCardStyle}>
<div style={{ color: GRAPH_THEME.ui.timeline.playhead, fontSize: 11, marginBottom: 4 }}>{key}</div>
<div style={{ color: GRAPH_THEME.ui.text.body, fontSize: 13, wordBreak: "break-word" }}>
{typeof value === "object" ? JSON.stringify(value) : String(value)}
</div>
</div>
))}
</div>
) : (
<div style={emptyTextStyle}>No explicit attribution metadata was found on this node.</div>
)}
</div>
</details>
{/* Properties */}
<details className="node-panel-collapse">
<summary className="node-panel-summary">Properties</summary>
<div className="node-panel-body">
{propertyEntries.length ? (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{propertyEntries.map(([key, value]) => (
<div key={key} style={propertyCardStyle}>
<div style={{ color: GRAPH_THEME.ui.timeline.playhead, fontSize: 11, marginBottom: 4 }}>{key}</div>
<div style={{ color: GRAPH_THEME.ui.text.body, fontSize: 13, wordBreak: "break-word" }}>
{typeof value === "object" ? JSON.stringify(value) : String(value)}
</div>
</div>
))}
</div>
) : (
<div style={emptyTextStyle}>No additional properties are attached to this node.</div>
)}
</div>
</details>
</aside>
);
}
/* ─── styles ─────────────────────────────────────────────────────── */
const inputStyle: CSSProperties = {
width: "100%",
background: GRAPH_THEME.ui.control.inputBg,
border: `1px solid ${GRAPH_THEME.ui.control.inputBorder}`,
color: GRAPH_THEME.ui.text.strong,
borderRadius: 12,
padding: "11px 13px",
fontSize: 13,
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.035)",
};
const groupedSelectionNoticeStyle: CSSProperties = {
marginTop: 12,
padding: "10px 12px",
background: "rgba(98, 226, 205, 0.07)",
border: `1px solid ${GRAPH_THEME.ui.control.activeBorder}`,
borderRadius: 12,
};
const actionButtonStyle: CSSProperties = {
background: GRAPH_THEME.ui.control.primaryBg,
color: GRAPH_THEME.ui.control.primaryText,
border: `1px solid ${GRAPH_THEME.ui.control.primaryBorder}`,
borderRadius: 12,
padding: "9px 12px",
cursor: "pointer",
fontWeight: 700,
fontSize: 12,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.07), 0 10px 24px rgba(0,0,0,0.18)",
};
const secondaryActionButtonStyle: CSSProperties = {
...actionButtonStyle,
background: GRAPH_THEME.ui.control.defaultBg,
border: `1px solid ${GRAPH_THEME.ui.control.defaultBorder}`,
color: GRAPH_THEME.ui.control.defaultText,
fontWeight: 600,
};
const predictionCardStyle: CSSProperties = {
textAlign: "left",
padding: "10px 12px",
background: "rgba(255, 255, 255, 0.035)",
border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`,
borderRadius: 10,
cursor: "pointer",
width: "100%",
};
const propertyCardStyle: CSSProperties = {
background: "rgba(255, 255, 255, 0.028)",
padding: "10px 12px",
borderRadius: 10,
border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`,
};
const emptyTextStyle: CSSProperties = {
color: GRAPH_THEME.ui.text.muted,
fontSize: 12,
lineHeight: 1.5,
};
const subtleChipStyle: CSSProperties = {
background: "rgba(255, 255, 255, 0.04)",
color: GRAPH_THEME.ui.text.body,
padding: "4px 8px",
borderRadius: 999,
fontSize: 11,
border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`,
};
const sectionStyle: CSSProperties = {
display: "flex",
flexDirection: "column",
gap: 10,
padding: 14,
background: GRAPH_THEME.ui.surface.cardSubtle,
border: `1px solid ${GRAPH_THEME.ui.surface.panelBorder}`,
borderRadius: 14,
};
const sectionTitleStyle: CSSProperties = {
color: GRAPH_THEME.ui.text.muted,
fontSize: 11,
fontWeight: 700,
textTransform: "uppercase",
letterSpacing: "0.08em",
};
const pathFlowContainerStyle: CSSProperties = {
display: "flex",
alignItems: "center",
gap: 0,
flexWrap: "wrap",
rowGap: 8,
};
const pathNodeChipStyle: CSSProperties = {
display: "inline-flex",
alignItems: "center",
gap: 6,
padding: "5px 10px",
borderRadius: 999,
background: "rgba(98, 226, 205, 0.08)",
border: `1px solid ${GRAPH_THEME.ui.control.activeBorder}`,
color: GRAPH_THEME.ui.text.strong,
fontSize: 12,
fontWeight: 600,
maxWidth: 160,
};
const pathNodeIndexStyle: CSSProperties = {
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
width: 16,
height: 16,
borderRadius: "50%",
background: GRAPH_THEME.ui.timeline.playheadSoft,
color: GRAPH_THEME.ui.timeline.playhead,
fontSize: 9,
fontWeight: 800,
flexShrink: 0,
};
const pathEdgeConnectorStyle: CSSProperties = {
display: "inline-flex",
alignItems: "center",
gap: 2,
flexShrink: 0,
};
const pathEdgeLabelStyle: CSSProperties = {
fontSize: 9,
fontWeight: 700,
color: GRAPH_THEME.ui.text.subtle,
letterSpacing: "0.04em",
textTransform: "uppercase",
maxWidth: 70,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
};
const metricCardStyle: CSSProperties = {
background: "rgba(0,0,0,0.18)",
borderRadius: 8,
padding: "8px 10px",
border: "1px solid rgba(255,255,255,0.05)",
display: "flex",
flexDirection: "column",
gap: 3,
};
const metricLabelStyle: CSSProperties = {
color: "rgba(88,166,255,0.65)",
fontSize: 10,
fontWeight: 700,
letterSpacing: "0.06em",
textTransform: "uppercase",
};
const metricValueStyle: CSSProperties = {
fontSize: 14,
fontWeight: 700,
color: "#e6edf3",
};
const metricBarTrackStyle: CSSProperties = {
height: 3,
borderRadius: 999,
background: "rgba(255,255,255,0.07)",
overflow: "hidden",
marginTop: 4,
};
const metricBarFillStyle: CSSProperties = {
height: "100%",
borderRadius: 999,
transition: "width 300ms ease",
};
@@ -0,0 +1,315 @@
import { useEffect, useRef, useState, type CSSProperties } from "react";
import { GRAPH_THEME, withAlpha } from "./graphTheme";
import { GRAPH_LOAD_STAGE_SEQUENCE, createGraphLoadProgress, getGraphLoadStageLabel } from "./graphLoading";
import type { GraphLoadProgress } from "./types";
const LOADING_OVERLAY_CSS = `
.graph-stage-loader {
position: absolute;
inset: 0;
z-index: 9;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
opacity: 1;
transition: opacity 220ms ease, transform 220ms ease;
}
.graph-stage-loader[data-exiting="true"] {
opacity: 0;
transform: scale(0.985);
}
.graph-stage-loader-card {
width: min(540px, calc(100% - 48px));
border-radius: 24px;
padding: 20px 20px 18px;
border: 1px solid rgba(127, 208, 255, 0.18);
background:
radial-gradient(circle at top right, rgba(242, 182, 109, 0.12), transparent 28%),
radial-gradient(circle at top left, rgba(127, 208, 255, 0.14), transparent 30%),
linear-gradient(145deg, rgba(7, 17, 31, 0.94), rgba(12, 25, 43, 0.82));
box-shadow: 0 26px 90px rgba(0, 0, 0, 0.34), inset 0 1px 0 rgba(255,255,255,0.05);
backdrop-filter: blur(18px) saturate(1.08);
-webkit-backdrop-filter: blur(18px) saturate(1.08);
}
.graph-stage-loader-card[data-live="true"] {
width: min(500px, calc(100% - 56px));
background:
radial-gradient(circle at top right, rgba(242, 182, 109, 0.08), transparent 26%),
radial-gradient(circle at top left, rgba(127, 208, 255, 0.12), transparent 28%),
linear-gradient(145deg, rgba(7, 17, 31, 0.84), rgba(11, 24, 40, 0.72));
box-shadow: 0 18px 54px rgba(0, 0, 0, 0.26), inset 0 1px 0 rgba(255,255,255,0.04);
}
.graph-stage-loader-beacon {
position: relative;
width: 12px;
height: 12px;
border-radius: 999px;
background: linear-gradient(135deg, rgba(127, 208, 255, 0.98), rgba(242, 182, 109, 0.94));
box-shadow: 0 0 18px rgba(127, 208, 255, 0.4);
}
.graph-stage-loader-beacon::after {
content: "";
position: absolute;
inset: -7px;
border-radius: inherit;
border: 1px solid rgba(127, 208, 255, 0.18);
animation: graph-loader-beacon 1.9s ease-out infinite;
}
.graph-stage-loader-track {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 8px;
}
.graph-stage-loader-step {
border-radius: 999px;
padding: 7px 0;
text-align: center;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
border: 1px solid rgba(127, 208, 255, 0.08);
color: rgba(143, 168, 198, 0.72);
background: rgba(255, 255, 255, 0.02);
}
.graph-stage-loader-step[data-state="done"] {
color: rgba(214, 232, 250, 0.92);
border-color: rgba(127, 208, 255, 0.18);
background: rgba(89, 155, 220, 0.14);
}
.graph-stage-loader-step[data-state="active"] {
color: #eff7ff;
border-color: rgba(242, 182, 109, 0.24);
background: linear-gradient(135deg, rgba(49, 108, 172, 0.28), rgba(242, 182, 109, 0.16));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.graph-stage-loader-bar {
position: relative;
width: 100%;
height: 12px;
overflow: hidden;
border-radius: 999px;
border: 1px solid rgba(127, 208, 255, 0.1);
background: rgba(255, 255, 255, 0.05);
}
.graph-stage-loader-bar-fill {
display: block;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, rgba(74, 163, 255, 0.9), rgba(127, 208, 255, 0.96), rgba(242, 182, 109, 0.92));
box-shadow: 0 0 30px rgba(74, 163, 255, 0.28);
transition: width 220ms ease;
}
.graph-stage-loader-bar-indeterminate::before {
content: "";
position: absolute;
top: 1px;
bottom: 1px;
width: 34%;
border-radius: 999px;
background: linear-gradient(90deg, rgba(74, 163, 255, 0), rgba(127, 208, 255, 0.94), rgba(242, 182, 109, 0.82), rgba(74, 163, 255, 0));
box-shadow: 0 0 26px rgba(127, 208, 255, 0.18);
animation: graph-loader-sweep 1.5s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
@keyframes graph-loader-beacon {
0% { transform: scale(0.72); opacity: 0.6; }
100% { transform: scale(1.44); opacity: 0; }
}
@keyframes graph-loader-sweep {
0% { transform: translateX(-120%); }
100% { transform: translateX(360%); }
}
`;
function formatLayoutSource(source: GraphLoadProgress["layoutSource"]) {
switch (source) {
case "provided":
return "Persisted layout";
case "carried":
return "Preserved layout";
case "runtime":
return "Runtime layout";
default:
return null;
}
}
function formatLayoutState(state: GraphLoadProgress["layoutState"]) {
switch (state) {
case "bootstrapping":
return "Bootstrapping";
case "running":
return "Settling";
case "interactive":
return "Interactive";
case "stabilized":
return "Stable";
case "failed":
return "Fallback";
default:
return null;
}
}
const loadingMetricStyle = {
display: "inline-flex",
alignItems: "center",
gap: 6,
padding: "7px 10px",
borderRadius: 999,
border: "1px solid rgba(127, 208, 255, 0.12)",
background: "rgba(255, 255, 255, 0.03)",
color: "#b8cade",
fontSize: 11,
fontWeight: 600,
} satisfies CSSProperties;
export function GraphLoadingOverlay({
progress,
visible,
showGraphBehind,
}: {
progress: GraphLoadProgress | null;
visible: boolean;
showGraphBehind: boolean;
}) {
const [renderVisible, setRenderVisible] = useState(visible);
const [exiting, setExiting] = useState(false);
const [displayProgress, setDisplayProgress] = useState<GraphLoadProgress>(
progress ?? createGraphLoadProgress({
phase: "bootstrapping",
message: "Preparing graph session",
progressKind: "indeterminate",
}),
);
const exitTimerRef = useRef<number | null>(null);
useEffect(() => {
if (progress) {
setDisplayProgress(progress);
}
}, [progress]);
useEffect(() => {
if (visible) {
if (exitTimerRef.current !== null) {
window.clearTimeout(exitTimerRef.current);
exitTimerRef.current = null;
}
setRenderVisible(true);
setExiting(false);
return;
}
if (!renderVisible) {
return;
}
setExiting(true);
exitTimerRef.current = window.setTimeout(() => {
setRenderVisible(false);
setExiting(false);
exitTimerRef.current = null;
}, 220);
return () => {
if (exitTimerRef.current !== null) {
window.clearTimeout(exitTimerRef.current);
exitTimerRef.current = null;
}
};
}, [renderVisible, visible]);
if (!renderVisible) {
return null;
}
const activeProgress = progress ?? displayProgress;
const isLiveStage = activeProgress.phase === "stabilizing_layout" || activeProgress.showGraphBehind || showGraphBehind;
const overlayBackground = isLiveStage
? "linear-gradient(180deg, rgba(1,4,9,0.04), rgba(1,4,9,0.18))"
: "linear-gradient(180deg, rgba(1,4,9,0.22), rgba(1,4,9,0.5))";
const determinateRatio = activeProgress.progressKind === "determinate" && activeProgress.total
? Math.max(0.05, Math.min(activeProgress.loaded ?? 0, activeProgress.total) / Math.max(activeProgress.total, 1))
: null;
const layoutSource = formatLayoutSource(activeProgress.layoutSource);
const layoutState = formatLayoutState(activeProgress.layoutState);
return (
<div
className="graph-stage-loader"
data-exiting={exiting}
style={{ background: overlayBackground }}
>
<style>{LOADING_OVERLAY_CSS}</style>
<div className="graph-stage-loader-card" data-live={isLiveStage}>
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 14, marginBottom: 14 }}>
<div style={{ minWidth: 0 }}>
<div style={{ color: "#ffffff", fontSize: 20, fontWeight: 700, letterSpacing: "-0.03em", marginBottom: 6 }}>
{activeProgress.title}
</div>
<div style={{ color: "#8fa8c6", fontSize: 13, lineHeight: 1.5 }}>
{activeProgress.message}
</div>
</div>
<div style={{ display: "inline-flex", alignItems: "center", gap: 10, flexShrink: 0 }}>
<div className="graph-stage-loader-beacon" aria-hidden="true" />
<div style={{ color: "#d7e9fb", fontSize: 11, fontWeight: 700, letterSpacing: "0.08em", textTransform: "uppercase" }}>
Stage {activeProgress.stageIndex ?? 1}/{activeProgress.stageCount ?? GRAPH_LOAD_STAGE_SEQUENCE.length}
</div>
</div>
</div>
<div className="graph-stage-loader-track" style={{ marginBottom: 14 }}>
{GRAPH_LOAD_STAGE_SEQUENCE.map((phase, index) => {
const current = activeProgress.stageIndex ?? 1;
const state = index + 1 < current ? "done" : index + 1 === current ? "active" : "upcoming";
return (
<div key={phase} className="graph-stage-loader-step" data-state={state}>
{getGraphLoadStageLabel(phase)}
</div>
);
})}
</div>
<div style={{ display: "flex", justifyContent: "space-between", gap: 12, alignItems: "baseline", marginBottom: 8 }}>
<div style={{ color: "#dce9f6", fontSize: 12, fontWeight: 600 }}>
{activeProgress.progressKind === "determinate" && activeProgress.total
? `${(activeProgress.loaded ?? 0).toLocaleString()} / ${activeProgress.total.toLocaleString()} in current stage`
: "Working through this stage"}
</div>
<div style={{ color: "#90a8c5", fontSize: 11, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase" }}>
{activeProgress.progressKind === "determinate" && determinateRatio !== null
? `${Math.round(determinateRatio * 100)}%`
: "Live"}
</div>
</div>
<div className={`graph-stage-loader-bar ${activeProgress.progressKind === "indeterminate" ? "graph-stage-loader-bar-indeterminate" : ""}`}>
{activeProgress.progressKind === "determinate" && determinateRatio !== null ? (
<span className="graph-stage-loader-bar-fill" style={{ width: `${Math.round(determinateRatio * 100)}%` }} />
) : null}
</div>
<div style={{ display: "flex", gap: 10, flexWrap: "wrap", marginTop: 14 }}>
<span style={loadingMetricStyle}>
{activeProgress.nodesLoaded.toLocaleString()}
{activeProgress.nodesTotal ? ` / ${activeProgress.nodesTotal.toLocaleString()}` : ""} nodes
</span>
<span style={loadingMetricStyle}>
{activeProgress.edgesLoaded.toLocaleString()}
{activeProgress.edgesTotal ? ` / ${activeProgress.edgesTotal.toLocaleString()}` : ""} relationships
</span>
{layoutSource ? (
<span style={{ ...loadingMetricStyle, color: "#a9ddff", borderColor: withAlpha(GRAPH_THEME.palette.accent.hovered, 0.22) }}>
{layoutSource}
{layoutState ? ` · ${layoutState}` : ""}
</span>
) : null}
</div>
</div>
</div>
);
}
@@ -0,0 +1,479 @@
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import { batchMergeEdges, batchMergeNodes, clearGraph, graph, type EdgeAttributes, type NodeAttributes } from "../../store/graphStore";
import { SigmaSceneAdapter } from "./SigmaSceneAdapter";
import { createGraphLoadProgress } from "./graphLoading";
import { resolveDisplayGraph } from "./graphSceneState";
import {
chooseColorAccessor,
colorForNodeKey,
computeDegreeMap,
computeEdgeSize,
computeNodeSize,
computePageRank,
deterministicPosition,
} from "./graphAnalytics";
import { GRAPH_THEME } from "./graphConfig";
import type { GraphSceneHandle } from "./scene";
import type {
GraphDataSnapshot,
GraphEffectsState,
GraphLayoutSource,
GraphLayoutStatus,
GraphLoadProgress,
GraphPath,
GraphSelectedNodeState,
GraphStageHandle,
GraphViewMode,
} from "./types";
const STAGE_EFFECTS_STATE: GraphEffectsState = {
pathPulseEnabled: false,
pathFlowEnabled: false,
lensEnabled: false,
temporalEmphasisEnabled: false,
semanticRegionsEnabled: false,
contoursEnabled: false,
pathfindingEnabled: false,
communitiesEnabled: false,
centralityEnabled: false,
legendEnabled: false,
diagnosticsEnabled: false,
lensMode: "neighborhood",
effectQuality: "bounded",
};
const EMPTY_PATH: string[] = [];
const socketProtocol = () => (window.location.protocol === "https:" ? "wss:" : "ws:");
function yieldToMain(): Promise<void> {
if ("scheduler" in window && typeof (window as Window & { scheduler?: { yield?: () => Promise<void> } }).scheduler?.yield === "function") {
return (window as Window & { scheduler: { yield: () => Promise<void> } }).scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
function buildSelectedNodeState(nodeId: string): GraphSelectedNodeState | null {
if (!nodeId || !graph.hasNode(nodeId)) {
return null;
}
const attributes = graph.getNodeAttributes(nodeId) as NodeAttributes;
return {
id: nodeId,
label: String(attributes.label || nodeId),
content: String(attributes.content || attributes.label || nodeId),
nodeType: attributes.nodeType || "entity",
color: attributes.color,
valid_from: attributes.valid_from ?? null,
valid_until: attributes.valid_until ?? null,
properties: attributes.properties ?? {},
neighborCount: graph.neighbors(nodeId).length,
visibleNeighborCount: graph.neighbors(nodeId).length,
collapsedNeighborCount: 0,
isNeighborhoodCollapsed: false,
canCollapseNeighborhood: graph.neighbors(nodeId).length > 8,
};
}
function hasUsableCoordinate(value: unknown): value is number {
return typeof value === "number" && Number.isFinite(value);
}
interface GraphRuntimeStageProps {
snapshot: GraphDataSnapshot | null | undefined;
selectedNodeId: string;
activePath: GraphPath;
onNodeSelect: (nodeId: string) => void;
onSelectedNodeStateChange: (state: GraphSelectedNodeState | null) => void;
isLayoutRunning: boolean;
onLayoutRunningChange: (running: boolean) => void;
viewMode: GraphViewMode;
temporalTime: Date | null;
onActiveNodeCountChange: (count: number | null) => void;
onProgressChange: (progress: GraphLoadProgress | null) => void;
onLayoutStatusChange: (status: GraphLayoutStatus) => void;
onRuntimeReady: () => void;
}
export const GraphRuntimeStage = forwardRef<GraphStageHandle, GraphRuntimeStageProps>(
function GraphRuntimeStage(
{
snapshot,
selectedNodeId,
activePath,
onNodeSelect,
onSelectedNodeStateChange,
isLayoutRunning,
onLayoutRunningChange,
viewMode,
temporalTime,
onActiveNodeCountChange,
onProgressChange,
onLayoutStatusChange,
onRuntimeReady,
},
ref,
) {
const sceneRef = useRef<GraphSceneHandle>(null);
const prevActiveIdsRef = useRef<Set<string>>(new Set());
const [graphVersion, setGraphVersion] = useState(0);
const [runtimeLayoutSource, setRuntimeLayoutSource] = useState<GraphLayoutSource>(snapshot?.summary.layoutSource ?? "runtime");
const displayResult = useMemo(
() => resolveDisplayGraph(selectedNodeId, activePath, EMPTY_PATH, viewMode, { aggregationEnabled: true }),
[activePath, graphVersion, selectedNodeId, viewMode],
);
const stageSignature = useMemo(() => (snapshot ? `${snapshot.fetchedAt}:${snapshot.summary.nodeCount}:${snapshot.summary.edgeCount}` : null), [snapshot]);
useImperativeHandle(ref, () => ({
fitView: () => sceneRef.current?.fitView(),
focusNode: (nodeId: string) => sceneRef.current?.focusNode(nodeId),
}), []);
useEffect(() => {
let cancelled = false;
async function hydrateSnapshot() {
if (!snapshot) {
return;
}
onProgressChange(createGraphLoadProgress({
phase: "computing_styling",
progressKind: "indeterminate",
nodesLoaded: snapshot.summary.nodeCount,
nodesTotal: snapshot.summary.nodeCount,
edgesLoaded: snapshot.summary.edgeCount,
edgesTotal: snapshot.summary.edgeCount,
message: "Computing runtime graph styling",
showGraphBehind: false,
}));
const degreeByNode = computeDegreeMap(snapshot.nodes, snapshot.edges);
const pageRankByNode = computePageRank(snapshot.nodes, snapshot.edges);
const nodeIndexById = new Map(snapshot.nodes.map((node, index) => [node.id, index]));
const previousPositions = new Map<string, { x: number; y: number }>();
graph.forEachNode((nodeId, attributes) => {
const raw = attributes as Partial<NodeAttributes>;
const x = Number(raw.x);
const y = Number(raw.y);
if (Number.isFinite(x) && Number.isFinite(y)) {
previousPositions.set(nodeId, { x, y });
}
});
let explicitCoordinateCount = 0;
let carriedCoordinateCount = 0;
const draftAttributes = snapshot.nodes.map((node) => {
const previousPosition = previousPositions.get(node.id);
const position = hasUsableCoordinate(node.x) && hasUsableCoordinate(node.y)
? { x: node.x, y: node.y }
: previousPosition
? previousPosition
: deterministicPosition(node.id, nodeIndexById.get(node.id) ?? 0, snapshot.nodes.length);
if (hasUsableCoordinate(node.x) && hasUsableCoordinate(node.y)) {
explicitCoordinateCount += 1;
} else if (previousPosition) {
carriedCoordinateCount += 1;
}
return {
id: node.id,
attributes: {
label: node.content || node.id,
x: position.x,
y: position.y,
nodeType: node.type,
content: node.content,
valid_from: node.valid_from,
valid_until: node.valid_until,
properties: node.properties,
} as NodeAttributes,
};
});
const layoutSource: GraphLayoutSource = explicitCoordinateCount > 0
? "provided"
: carriedCoordinateCount > 0
? "carried"
: "runtime";
const hasCoordinates = explicitCoordinateCount > 0 || carriedCoordinateCount > 0;
setRuntimeLayoutSource(layoutSource);
const colorAccessor = chooseColorAccessor(draftAttributes);
await yieldToMain();
if (cancelled) {
return;
}
onProgressChange(createGraphLoadProgress({
phase: "hydrating_scene",
progressKind: "indeterminate",
nodesLoaded: snapshot.summary.nodeCount,
nodesTotal: snapshot.summary.nodeCount,
edgesLoaded: snapshot.summary.edgeCount,
edgesTotal: snapshot.summary.edgeCount,
message: "Hydrating graph scene and renderer",
showGraphBehind: false,
}));
const nodesToMerge = draftAttributes.map(({ id, attributes }) => {
const colorKey = colorAccessor(id, attributes);
const baseColor = colorForNodeKey(colorKey);
const dynamicSize = computeNodeSize(id, degreeByNode, pageRankByNode);
return {
id,
attributes: {
...attributes,
color: baseColor,
baseColor,
size: dynamicSize,
baseSize: dynamicSize,
degree: degreeByNode.get(id) ?? 0,
pageRank: pageRankByNode.get(id) ?? 0,
glowColor: baseColor,
borderColor: GRAPH_THEME.nodes.border,
borderSize: 1,
} as NodeAttributes,
};
});
const edgesToMerge = snapshot.edges.map((edge) => ({
id: edge.id,
familyId: edge.familyId,
source: edge.source,
target: edge.target,
attributes: {
edgeId: edge.id,
familyId: edge.familyId,
sourceId: edge.source,
targetId: edge.target,
weight: edge.weight,
edgeType: edge.type,
properties: edge.properties,
size: computeEdgeSize(edge.weight),
baseSize: computeEdgeSize(edge.weight),
color: GRAPH_THEME.edges.baseColor,
baseColor: GRAPH_THEME.edges.baseColor,
} as EdgeAttributes,
}));
clearGraph();
batchMergeNodes(nodesToMerge);
batchMergeEdges(edgesToMerge);
prevActiveIdsRef.current = new Set(snapshot.nodes.map((node) => node.id));
await yieldToMain();
if (cancelled) {
return;
}
onLayoutStatusChange({
state: layoutSource === "runtime" ? "bootstrapping" : "interactive",
source: layoutSource,
hasCoordinates,
layoutReady: layoutSource !== "runtime",
displacement: null,
elapsedMs: 0,
stableSamples: 0,
});
onLayoutRunningChange(layoutSource === "runtime");
if (selectedNodeId) {
sceneRef.current?.focusNode(selectedNodeId);
} else {
sceneRef.current?.getRuntime()?.requestRender();
}
setGraphVersion((current) => current + 1);
if (layoutSource !== "runtime") {
onProgressChange(null);
} else {
onProgressChange(createGraphLoadProgress({
phase: "stabilizing_layout",
progressKind: "indeterminate",
nodesLoaded: snapshot.summary.nodeCount,
nodesTotal: snapshot.summary.nodeCount,
edgesLoaded: snapshot.summary.edgeCount,
edgesTotal: snapshot.summary.edgeCount,
message: "Settling runtime layout",
showGraphBehind: true,
layoutSource,
layoutState: "bootstrapping",
}));
}
onRuntimeReady();
}
void hydrateSnapshot();
return () => {
cancelled = true;
};
}, [onLayoutRunningChange, onLayoutStatusChange, onProgressChange, onRuntimeReady, selectedNodeId, snapshot, stageSignature]);
useEffect(() => {
if (!selectedNodeId) {
onSelectedNodeStateChange(null);
return;
}
onSelectedNodeStateChange(buildSelectedNodeState(selectedNodeId));
}, [graphVersion, onSelectedNodeStateChange, selectedNodeId, viewMode]);
useEffect(() => {
if (!snapshot || !temporalTime) {
return;
}
let cancelled = false;
const applySnapshot = async () => {
try {
const response = await fetch(`/api/temporal/snapshot?at=${encodeURIComponent(temporalTime.toISOString())}`);
if (!response.ok || cancelled) {
return;
}
const data: { active_node_ids: string[]; active_node_count: number } = await response.json();
if (cancelled) {
return;
}
const nextActiveIds = new Set(data.active_node_ids);
requestAnimationFrame(() => {
if (cancelled) {
return;
}
const previous = prevActiveIdsRef.current;
previous.forEach((id) => {
if (!nextActiveIds.has(id) && graph.hasNode(id)) {
graph.setNodeAttribute(id, "hidden", true);
}
});
nextActiveIds.forEach((id) => {
if (graph.hasNode(id)) {
graph.setNodeAttribute(id, "hidden", false);
}
});
prevActiveIdsRef.current = nextActiveIds;
onActiveNodeCountChange(data.active_node_count);
sceneRef.current?.getRuntime()?.requestRender();
});
} catch (error) {
if (!cancelled) {
console.error("[GraphRuntimeStage] temporal snapshot failed", error);
}
}
};
void applySnapshot();
return () => {
cancelled = true;
};
}, [onActiveNodeCountChange, snapshot, temporalTime]);
useEffect(() => {
const socket = new WebSocket(`${socketProtocol()}//${window.location.host}/ws/graph-updates`);
socket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
if (message.event === "connection_ack" || message.event !== "graph_mutation") {
return;
}
const eventType = message.data?.event_type;
const payload = message.data?.payload;
if (eventType === "ADD_NODE" && payload?.id) {
batchMergeNodes([
{
id: payload.id,
attributes: {
label: payload.properties?.content || payload.id,
x: Number.isFinite(Number(payload.x ?? payload.properties?.x))
? Number(payload.x ?? payload.properties?.x)
: deterministicPosition(payload.id, graph.order + 1, Math.max(graph.order + 1, 1)).x,
y: Number.isFinite(Number(payload.y ?? payload.properties?.y))
? Number(payload.y ?? payload.properties?.y)
: deterministicPosition(payload.id, graph.order + 1, Math.max(graph.order + 1, 1)).y,
nodeType: payload.type,
content: payload.properties?.content || payload.id,
valid_from: payload.properties?.valid_from ?? null,
valid_until: payload.properties?.valid_until ?? null,
properties: payload.properties || {},
size: 8,
color: colorForNodeKey(`${payload.type || "entity"}:${payload.id}`),
baseColor: colorForNodeKey(`${payload.type || "entity"}:${payload.id}`),
baseSize: 8,
glowColor: colorForNodeKey(`${payload.type || "entity"}:${payload.id}`),
borderColor: GRAPH_THEME.nodes.border,
borderSize: 1,
},
},
]);
}
if (eventType === "ADD_EDGE" && payload?.source_id && payload?.target_id) {
batchMergeEdges([
{
id: String(payload.id),
familyId: payload.familyId ? String(payload.familyId) : String(payload.id),
source: payload.source_id,
target: payload.target_id,
attributes: {
edgeId: String(payload.id),
familyId: payload.familyId ? String(payload.familyId) : String(payload.id),
sourceId: payload.source_id,
targetId: payload.target_id,
weight: Number(payload.weight ?? 1),
edgeType: payload.type,
properties: payload.properties || {},
size: computeEdgeSize(Number(payload.weight ?? 1)),
baseSize: computeEdgeSize(Number(payload.weight ?? 1)),
color: payload.properties?.inferred ? GRAPH_THEME.edges.pathColor : GRAPH_THEME.edges.baseColor,
baseColor: GRAPH_THEME.edges.baseColor,
},
},
]);
}
sceneRef.current?.getRuntime()?.requestRender();
setGraphVersion((current) => current + 1);
} catch (error) {
console.error("[GraphRuntimeStage] websocket update failed", error);
}
};
return () => {
socket.close();
};
}, []);
return (
<SigmaSceneAdapter
ref={sceneRef}
onNodeSelect={onNodeSelect}
graphVersion={graphVersion}
graphReady={Boolean(snapshot)}
displayGraph={displayResult.graph}
displayMeta={displayResult.meta}
displayState={displayResult.state}
selectedEdgeId=""
selectedNodeId={selectedNodeId}
focusedNodeId={viewMode === "focused" ? selectedNodeId : ""}
activePath={activePath}
activePathEdgeIds={EMPTY_PATH}
effectsState={STAGE_EFFECTS_STATE}
isLayoutRunning={isLayoutRunning}
onLayoutRunningChange={onLayoutRunningChange}
layoutSource={runtimeLayoutSource}
onLayoutStatusChange={onLayoutStatusChange}
viewMode={viewMode}
/>
);
},
);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,849 @@
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
import { GraphLoadingOverlay } from "./GraphLoadingOverlay";
import { getGraphLoadTitle } from "./graphLoading";
import { useGraphData, useReloadGraphData } from "./useGraphData";
import type {
ApiNode,
GraphLayoutStatus,
GraphLoadProgress,
GraphPath,
GraphSelectedNodeState,
GraphStageHandle,
GraphViewMode,
} from "./types";
type SearchResult = {
node: {
id: string;
type: string;
content: string;
properties: Record<string, unknown>;
};
score: number;
};
type LinkPrediction = {
target: string;
type: string;
label?: string;
score: number;
};
type PathResponse = {
path: GraphPath;
total_weight: number;
hop_count: number;
distance_band: "direct" | "near" | "mid-range" | "distant";
};
type TemporalBounds = {
min?: string | null;
max?: string | null;
};
const GraphRuntimeStage = lazy(() =>
import("./GraphRuntimeStage").then((module) => ({ default: module.GraphRuntimeStage })),
);
const TimelinePanel = lazy(() =>
import("./TimelinePanel").then((module) => ({ default: module.TimelinePanel })),
);
const HUD_CSS = `
.palantir-bg {
background:
radial-gradient(circle at top, rgba(103, 182, 255, 0.1), transparent 24%),
linear-gradient(180deg, #07111d 0%, #02060e 100%);
}
.palantir-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(88, 166, 255, 0.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(88, 166, 255, 0.04) 1px, transparent 1px);
background-size: 44px 44px;
pointer-events: none;
z-index: 1;
opacity: 0.78;
}
.palantir-vignette {
position: absolute;
inset: 0;
background: radial-gradient(ellipse at center, transparent 34%, rgba(1, 4, 9, 0.88) 100%);
pointer-events: none;
z-index: 2;
}
.hud-scrollbar::-webkit-scrollbar { width: 6px; }
.hud-scrollbar::-webkit-scrollbar-track { background: transparent; }
.hud-scrollbar::-webkit-scrollbar-thumb { background: rgba(88, 166, 255, 0.25); border-radius: 6px; }
.graph-shell-top { position: absolute; top: 18px; left: 18px; right: 18px; z-index: 10; display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; pointer-events: none; }
.graph-status-card, .graph-command-card {
pointer-events: auto;
border: 1px solid rgba(132, 197, 255, 0.12);
background: linear-gradient(180deg, rgba(7, 16, 29, 0.86), rgba(10, 22, 39, 0.72)), radial-gradient(circle at top, rgba(103, 182, 255, 0.08), transparent 50%);
box-shadow: 0 18px 42px rgba(0, 0, 0, 0.28), inset 0 1px 0 rgba(255,255,255,0.04);
backdrop-filter: blur(18px);
}
.graph-status-card { width: min(420px, 38vw); border-radius: 24px; padding: 16px 18px; }
.graph-command-card { width: min(620px, 55vw); border-radius: 24px; padding: 14px; display: flex; flex-direction: column; gap: 12px; }
.graph-status-label { display: inline-flex; align-items: center; gap: 8px; color: rgba(160, 191, 223, 0.88); font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 10px; }
.graph-status-label::before { content: ""; width: 7px; height: 7px; border-radius: 999px; background: linear-gradient(135deg, #8ed3ff, #ffb36a); box-shadow: 0 0 12px rgba(142, 211, 255, 0.5); }
.graph-status-title { color: #eef5ff; font-size: 20px; font-weight: 800; letter-spacing: -0.04em; margin-bottom: 6px; }
.graph-status-copy { color: #8fa8c6; font-size: 12px; line-height: 1.55; margin-bottom: 14px; max-width: 40ch; }
.graph-status-metrics, .graph-command-row, .graph-toggle-cluster, .graph-action-cluster { display: flex; gap: 8px; flex-wrap: wrap; }
.graph-command-row { justify-content: space-between; align-items: center; gap: 10px; }
.graph-search-shell { flex: 1; min-width: 260px; display: flex; align-items: center; gap: 10px; padding: 8px 10px 8px 14px; border-radius: 18px; border: 1px solid rgba(132, 197, 255, 0.12); background: rgba(0, 0, 0, 0.18); box-shadow: inset 0 1px 0 rgba(255,255,255,0.03); }
.graph-search-shell input { flex: 1; min-width: 0; border: none !important; background: transparent !important; padding: 0 !important; margin: 0 !important; }
.graph-search-shell input:focus { outline: none; }
.graph-search-results { position: absolute; top: 120px; right: 18px; width: min(420px, calc(100vw - 132px)); max-height: 320px; overflow-y: auto; padding: 12px; border-radius: 20px; border: 1px solid rgba(132, 197, 255, 0.14); background: linear-gradient(180deg, rgba(8, 18, 33, 0.94), rgba(10, 21, 38, 0.86)); box-shadow: 0 18px 50px rgba(0,0,0,0.34); backdrop-filter: blur(18px); pointer-events: auto; z-index: 11; }
.graph-search-results-label { color: #6f89ab; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 10px; }
.graph-search-result-card { width: 100%; text-align: left; padding: 12px 14px; border-radius: 16px; border: 1px solid rgba(132, 197, 255, 0.08); background: rgba(255, 255, 255, 0.025); cursor: pointer; transition: transform 160ms ease, border-color 160ms ease, background 160ms ease; }
.graph-search-result-card:hover { transform: translateY(-1px); border-color: rgba(132, 197, 255, 0.18); background: rgba(103, 182, 255, 0.08); }
.graph-inspector { pointer-events: auto; position: absolute; right: 18px; top: 154px; bottom: 108px; width: 380px; overflow-y: auto; transition: transform 0.34s cubic-bezier(0.16,1,0.3,1), opacity 0.22s ease; border-radius: 28px; border: 1px solid rgba(132, 197, 255, 0.14); background: linear-gradient(180deg, rgba(8, 18, 33, 0.9), rgba(6, 12, 22, 0.88)), radial-gradient(circle at top, rgba(103, 182, 255, 0.08), transparent 40%); box-shadow: -18px 0 48px rgba(0, 0, 0, 0.32), inset 0 1px 0 rgba(255,255,255,0.04); backdrop-filter: blur(20px); }
.graph-inspector[data-open='false'] { transform: translateX(calc(100% + 24px)); opacity: 0; }
@keyframes sem-loader-pulse {
0%, 100% { transform: translateY(0) scale(0.92); opacity: 0.55; }
50% { transform: translateY(-4px) scale(1.08); opacity: 1; }
}
@media (max-width: 1220px) {
.graph-shell-top { flex-direction: column; align-items: stretch; }
.graph-status-card, .graph-command-card { width: auto; }
.graph-search-results { top: 202px; right: 18px; left: 18px; width: auto; }
}
`;
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timeout = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timeout);
}, [delay, value]);
return debouncedValue;
}
function sourceAttribution(properties: Record<string, unknown>) {
const keys = ["source", "source_url", "pmid", "pmids", "evidence", "provenance", "confidence"];
return keys
.filter((key) => key in properties)
.map((key) => ({ key, value: properties[key] }));
}
function toSelectedNodeState(node: ApiNode, neighborCount: number, fallbackColor = "#58a6ff"): GraphSelectedNodeState {
return {
id: node.id,
label: node.content || node.id,
content: node.content || node.id,
nodeType: node.type,
color: fallbackColor,
valid_from: node.valid_from ?? null,
valid_until: node.valid_until ?? null,
properties: node.properties ?? {},
neighborCount,
visibleNeighborCount: neighborCount,
collapsedNeighborCount: 0,
isNeighborhoodCollapsed: false,
canCollapseNeighborhood: neighborCount > 8,
};
}
function TimelineFallback({ min, max }: TemporalBounds) {
return (
<div
style={{
width: "100%",
height: "90px",
borderTop: "1px solid rgba(88, 166, 255, 0.2)",
background: "rgba(1, 4, 9, 0.88)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "0 18px",
color: "#8fa8c6",
fontSize: 12,
flexShrink: 0,
}}
>
<span>Temporal scrubber</span>
<span>{min || max ? "Preparing timeline runtime..." : "Temporal bounds loading..."}</span>
</div>
);
}
function NodePanel({
node,
predictions,
predictionType,
onPredictionTypeChange,
onRunPredictions,
pathTargetId,
onPathTargetChange,
onTracePath,
pathResult,
onDownloadProvenance,
}: {
node: GraphSelectedNodeState | null;
predictions: LinkPrediction[];
predictionType: string;
onPredictionTypeChange: (value: string) => void;
onRunPredictions: () => void;
pathTargetId: string;
onPathTargetChange: (value: string) => void;
onTracePath: () => void;
pathResult: PathResponse | null;
onDownloadProvenance: (format: "json" | "markdown") => void;
}) {
if (!node) {
return (
<div style={{ padding: 32, textAlign: "center" }}>
<p style={{ color: "#8b949e", fontSize: 14, margin: 0 }}>
Search for a node or click one in the canvas to inspect its properties.
</p>
</div>
);
}
const properties = node.properties ?? {};
const attribution = sourceAttribution(properties);
const accentColor = node.color || "#58a6ff";
const propertyEntries = Object.entries(properties).filter(([key]) => !["x", "y", "valid_from", "valid_until", "content", "source", "source_url", "pmid", "pmids", "evidence", "provenance", "confidence"].includes(key));
return (
<aside style={{ padding: 24, display: "flex", flexDirection: "column", gap: 18 }}>
<div style={{ borderBottom: "1px solid rgba(88, 166, 255, 0.14)", paddingBottom: 16 }}>
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 8 }}>
<span style={{ background: accentColor, boxShadow: `0 0 10px ${accentColor}`, width: 8, height: 8, borderRadius: "50%" }} />
<span style={{ color: accentColor, fontSize: 12, fontWeight: 800, textTransform: "uppercase", letterSpacing: "0.08em" }}>{node.nodeType || "Entity"}</span>
</div>
<h3 style={{ margin: 0, color: "#fff", fontSize: 24, lineHeight: 1, fontWeight: 800, letterSpacing: "-0.04em", wordBreak: "break-word" }}>{node.label}</h3>
<div style={{ color: "#8b949e", fontSize: 12, marginTop: 8 }}>{node.id}</div>
<div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginTop: 12 }}>
{node.valid_from || node.valid_until ? <span style={subtleChipStyle}>temporal</span> : null}
<span style={subtleChipStyle}>{node.neighborCount} neighbors</span>
{attribution.length ? <span style={subtleChipStyle}>{attribution.length} source fields</span> : null}
{predictions.length ? <span style={subtleChipStyle}>{predictions.length} candidate links</span> : null}
</div>
</div>
<section style={sectionStyle}>
<div style={sectionTitleStyle}>Actions</div>
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<button style={{ ...actionButtonStyle, width: "100%", justifyContent: "center" }} onClick={onRunPredictions}>Run Link Prediction</button>
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
<button style={secondaryActionButtonStyle} onClick={() => onDownloadProvenance("json")}>Provenance JSON</button>
<button style={secondaryActionButtonStyle} onClick={() => onDownloadProvenance("markdown")}>Provenance MD</button>
</div>
</div>
<input value={predictionType} onChange={(event) => onPredictionTypeChange(event.target.value)} placeholder="Optional candidate type filter, e.g. disease" style={inputStyle} />
</section>
<section style={sectionStyle}>
<div style={sectionTitleStyle}>Trace Path</div>
<input value={pathTargetId} onChange={(event) => onPathTargetChange(event.target.value)} placeholder="Target node ID" style={inputStyle} />
<button style={actionButtonStyle} onClick={onTracePath}>Trace Causal Path</button>
{pathResult?.path?.length ? (
<div style={{ display: "flex", flexDirection: "column", gap: 6, marginTop: 10 }}>
{pathResult.path.map((step, index) => (
<div key={`${step}-${index}`} style={pathStepStyle}>{index + 1}. {step}</div>
))}
<div style={{ color: "#79c0ff", fontSize: 12, marginTop: 4 }}>total weight: {pathResult.total_weight.toFixed(3)}</div>
</div>
) : (
<div style={emptyTextStyle}>Choose a target or click a candidate prediction to prepare a path trace.</div>
)}
</section>
<details style={collapseStyle} open={predictions.length > 0}>
<summary style={summaryStyle}>Candidate Links</summary>
<div style={{ padding: "0 14px 14px" }}>
{predictions.length > 0 ? (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{predictions.map((prediction) => (
<button key={`${prediction.target}-${prediction.type}`} style={predictionCardStyle} onClick={() => onPathTargetChange(prediction.target)}>
<div style={{ color: "#fff", fontWeight: 600 }}>{prediction.label || prediction.target}</div>
<div style={{ color: "#8b949e", fontSize: 12 }}>{prediction.type}</div>
<div style={{ color: "#58a6ff", fontSize: 12, marginTop: 4 }}>confidence {prediction.score.toFixed(3)}</div>
</button>
))}
</div>
) : (
<div style={emptyTextStyle}>Run link prediction to surface likely next-hop relationships.</div>
)}
</div>
</details>
<details style={collapseStyle}>
<summary style={summaryStyle}>Source Attribution</summary>
<div style={{ padding: "0 14px 14px" }}>
{attribution.length ? (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{attribution.map(({ key, value }) => (
<div key={key} style={propertyCardStyle}>
<div style={{ color: "rgba(88, 166, 255, 0.7)", fontSize: 11, marginBottom: 4 }}>{key}</div>
<div style={{ color: "#e6edf3", fontSize: 13, wordBreak: "break-word" }}>{typeof value === "object" ? JSON.stringify(value) : String(value)}</div>
</div>
))}
</div>
) : (
<div style={emptyTextStyle}>No explicit attribution metadata was found on this node.</div>
)}
</div>
</details>
<details style={collapseStyle}>
<summary style={summaryStyle}>Properties</summary>
<div style={{ padding: "0 14px 14px" }}>
{propertyEntries.length ? (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{propertyEntries.map(([key, value]) => (
<div key={key} style={propertyCardStyle}>
<div style={{ color: "rgba(88, 166, 255, 0.7)", fontSize: 11, marginBottom: 4 }}>{key}</div>
<div style={{ color: "#e6edf3", fontSize: 13, wordBreak: "break-word" }}>{typeof value === "object" ? JSON.stringify(value) : String(value)}</div>
</div>
))}
</div>
) : (
<div style={emptyTextStyle}>No additional properties are attached to this node.</div>
)}
</div>
</details>
</aside>
);
}
export function GraphWorkspaceShell() {
const [selectedNodeId, setSelectedNodeId] = useState("");
const [selectedNodeState, setSelectedNodeState] = useState<GraphSelectedNodeState | null>(null);
const [isLayoutRunning, setIsLayoutRunning] = useState(false);
const [viewMode, setViewMode] = useState<GraphViewMode>("full");
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
const [searchError, setSearchError] = useState("");
const [predictionType, setPredictionType] = useState("");
const [predictions, setPredictions] = useState<LinkPrediction[]>([]);
const [pathTargetId, setPathTargetId] = useState("");
const [pathResult, setPathResult] = useState<PathResponse | null>(null);
const [activeNodeCount, setActiveNodeCount] = useState<number | null>(null);
const [temporalBounds, setTemporalBounds] = useState<TemporalBounds | null>(null);
const [scrubberTime, setScrubberTime] = useState<Date | null>(null);
const [loadingProgress, setLoadingProgress] = useState<GraphLoadProgress | null>(null);
const [isGraphStageReady, setIsGraphStageReady] = useState(false);
const [layoutStatus, setLayoutStatus] = useState<GraphLayoutStatus>({
state: "idle",
source: "runtime",
hasCoordinates: false,
layoutReady: false,
displacement: null,
elapsedMs: 0,
stableSamples: 0,
});
const debouncedTime = useDebounce(scrubberTime, 150);
const stageRef = useRef<GraphStageHandle>(null);
const reload = useReloadGraphData();
const { data: snapshot, isLoading, isFetching, isError, error } = useGraphData({ enabled: true, onProgress: setLoadingProgress });
const handleSelectedNodeStateChange = useCallback((state: GraphSelectedNodeState | null) => {
setSelectedNodeState(state);
}, []);
const handleLayoutRunningChange = useCallback((running: boolean) => {
setIsLayoutRunning(running);
}, []);
const handleActiveNodeCountChange = useCallback((count: number | null) => {
setActiveNodeCount(count);
}, []);
const handleProgressChange = useCallback((progress: GraphLoadProgress | null) => {
setLoadingProgress(progress);
}, []);
const handleRuntimeReady = useCallback(() => {
setIsGraphStageReady(true);
}, []);
const handleLayoutStatusChange = useCallback((status: GraphLayoutStatus) => {
setLayoutStatus(status);
if (status.layoutReady) {
setLoadingProgress(null);
}
}, []);
useEffect(() => {
if (snapshot) {
setIsGraphStageReady(false);
setActiveNodeCount(null);
setLayoutStatus({
state: snapshot.summary.layoutReady ? "interactive" : "idle",
source: snapshot.summary.layoutSource ?? "runtime",
hasCoordinates: snapshot.summary.hasCoordinates ?? false,
layoutReady: snapshot.summary.layoutReady ?? false,
displacement: null,
elapsedMs: 0,
stableSamples: 0,
});
}
}, [snapshot?.fetchedAt]);
useEffect(() => {
let cancelled = false;
const loadBounds = async () => {
try {
const response = await fetch("/api/temporal/bounds");
if (!response.ok || cancelled) return;
const data: TemporalBounds = await response.json();
if (!cancelled) setTemporalBounds(data);
} catch {
if (!cancelled) setTemporalBounds(null);
}
};
void loadBounds();
return () => {
cancelled = true;
};
}, [snapshot?.summary.nodeCount, snapshot?.summary.edgeCount]);
const neighborCountMap = useMemo(() => {
const map = new Map<string, number>();
if (!snapshot) return map;
for (const node of snapshot.nodes) map.set(node.id, 0);
for (const edge of snapshot.edges) {
map.set(edge.source, (map.get(edge.source) ?? 0) + 1);
map.set(edge.target, (map.get(edge.target) ?? 0) + 1);
}
return map;
}, [snapshot]);
const visibleSelectedNode = useMemo(() => {
if (!selectedNodeId) return null;
if (selectedNodeState?.id === selectedNodeId) return selectedNodeState;
const snapshotNode = snapshot?.nodes.find((candidate) => candidate.id === selectedNodeId);
if (snapshotNode) return toSelectedNodeState(snapshotNode, neighborCountMap.get(snapshotNode.id) ?? 0);
const searchNode = searchResults.find((candidate) => candidate.node.id === selectedNodeId)?.node;
return searchNode
? {
id: searchNode.id,
label: searchNode.content || searchNode.id,
content: searchNode.content || searchNode.id,
nodeType: searchNode.type,
color: "#58a6ff",
valid_from: null,
valid_until: null,
properties: searchNode.properties ?? {},
neighborCount: 0,
visibleNeighborCount: 0,
collapsedNeighborCount: 0,
isNeighborhoodCollapsed: false,
canCollapseNeighborhood: false,
}
: null;
}, [neighborCountMap, searchResults, selectedNodeId, selectedNodeState, snapshot]);
const focusNode = useCallback((nodeId: string) => {
setSelectedNodeId(nodeId);
setPathResult(null);
if (!nodeId) {
setSelectedNodeState(null);
setPredictions([]);
return;
}
setSearchResults([]);
setIsLayoutRunning(false);
}, []);
const handleSearch = useCallback(async () => {
if (!searchQuery.trim()) {
setSearchResults([]);
return;
}
setSearchError("");
try {
const response = await fetch("/api/graph/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: searchQuery, limit: 8 }),
});
if (!response.ok) {
throw new Error(`Search failed with status ${response.status}`);
}
const data = await response.json();
setSearchResults(data.results || []);
if (data.results?.length) {
focusNode(data.results[0].node.id);
}
} catch (searchFetchError) {
setSearchError(searchFetchError instanceof Error ? searchFetchError.message : "Search failed");
}
}, [focusNode, searchQuery]);
const handleRunPredictions = useCallback(async () => {
if (!selectedNodeId) return;
try {
const response = await fetch("/api/enrich/links", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
node_id: selectedNodeId,
top_n: 6,
candidate_type: predictionType || undefined,
min_score: 0,
}),
});
if (!response.ok) {
throw new Error(`Link prediction failed with status ${response.status}`);
}
const data = await response.json();
setPredictions(data.predictions || []);
} catch (predictionError) {
console.error("[GraphWorkspaceShell] prediction failed", predictionError);
setPredictions([]);
}
}, [predictionType, selectedNodeId]);
const handleTracePath = useCallback(async () => {
if (!selectedNodeId || !pathTargetId.trim()) return;
try {
const pathParams = new URLSearchParams({
source: selectedNodeId,
target: pathTargetId.trim(),
algorithm: "dijkstra",
});
const response = await fetch(
`/api/graph/path?${pathParams.toString()}`,
);
if (!response.ok) {
throw new Error(`Path lookup failed with status ${response.status}`);
}
const data: PathResponse = await response.json();
setPathResult(data);
if (data.path?.length) {
const lastStep = data.path[data.path.length - 1];
stageRef.current?.focusNode(lastStep);
}
} catch (pathError) {
console.error("[GraphWorkspaceShell] path trace failed", pathError);
setPathResult(null);
}
}, [pathTargetId, selectedNodeId]);
const handleDownloadProvenance = useCallback(async (format: "json" | "markdown") => {
if (!selectedNodeId) return;
const suffix = format === "markdown" ? "markdown" : "json";
const response = await fetch(`/api/provenance/report?node_id=${encodeURIComponent(selectedNodeId)}&format=${suffix}`);
if (!response.ok) {
throw new Error(`Provenance report failed with status ${response.status}`);
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = `${selectedNodeId}_provenance.${format === "markdown" ? "md" : "json"}`;
document.body.appendChild(anchor);
anchor.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(anchor);
}, [selectedNodeId]);
const searchSummary = useMemo(() => {
if (!searchResults.length) return null;
return `${searchResults.length} search result${searchResults.length === 1 ? "" : "s"}`;
}, [searchResults.length]);
const focusedSummary = useMemo(() => {
if (!visibleSelectedNode) return null;
if (viewMode === "focused") {
const visibleNeighbors = Math.min(visibleSelectedNode.neighborCount, 16);
return `${visibleNeighbors + 1} nodes in focused view`;
}
return `${visibleSelectedNode.neighborCount} direct neighbors highlighted`;
}, [viewMode, visibleSelectedNode]);
const requestViewMode = useCallback((nextViewMode: GraphViewMode) => {
if (nextViewMode === "focused") {
if (!selectedNodeId) {
return;
}
setViewMode("focused");
setIsLayoutRunning(false);
return;
}
setViewMode("full");
}, [selectedNodeId]);
const showLoadingOverlay =
isLoading
|| isFetching
|| !isGraphStageReady
|| (layoutStatus.source === "runtime" && !layoutStatus.layoutReady && !selectedNodeId && viewMode === "full");
const layoutStatusLabel = useMemo(() => {
if (layoutStatus.source === "provided" && layoutStatus.layoutReady) return "Persisted layout";
if (layoutStatus.source === "carried" && layoutStatus.layoutReady) return "Preserved layout";
if (layoutStatus.state === "bootstrapping") return "Bootstrapping layout";
if (layoutStatus.state === "running") return "Stabilizing layout";
if (layoutStatus.state === "failed") return "Layout timeout fallback";
return null;
}, [layoutStatus]);
return (
<div className="palantir-bg" style={{ position: "relative", width: "100%", height: "100%", overflow: "hidden", display: "flex", flexDirection: "column" }}>
<style>{HUD_CSS}</style>
<div className="palantir-grid" />
<div className="palantir-vignette" />
<div style={{ flex: 1, position: "relative", zIndex: 3, minHeight: 0 }}>
<Suspense fallback={null}>
<GraphRuntimeStage
ref={stageRef}
snapshot={snapshot}
selectedNodeId={selectedNodeId}
activePath={pathResult?.path ?? []}
onNodeSelect={focusNode}
onSelectedNodeStateChange={handleSelectedNodeStateChange}
isLayoutRunning={isLayoutRunning}
onLayoutRunningChange={handleLayoutRunningChange}
viewMode={viewMode}
temporalTime={debouncedTime}
onActiveNodeCountChange={handleActiveNodeCountChange}
onProgressChange={handleProgressChange}
onLayoutStatusChange={handleLayoutStatusChange}
onRuntimeReady={handleRuntimeReady}
/>
</Suspense>
<GraphLoadingOverlay
progress={loadingProgress}
visible={showLoadingOverlay}
showGraphBehind={Boolean(loadingProgress?.showGraphBehind || isGraphStageReady)}
/>
</div>
<Suspense fallback={<TimelineFallback min={temporalBounds?.min ?? null} max={temporalBounds?.max ?? null} />}>
<TimelinePanel
onTimeChange={setScrubberTime}
minDate={temporalBounds?.min ?? undefined}
maxDate={temporalBounds?.max ?? undefined}
/>
</Suspense>
<div style={{ position: "absolute", inset: 0, pointerEvents: "none", zIndex: 10 }}>
<div className="graph-shell-top">
<section className="graph-status-card">
<div className="graph-status-label">Graph Studio</div>
<div className="graph-status-title">{visibleSelectedNode ? visibleSelectedNode.label : "Knowledge Explorer"}</div>
<div className="graph-status-metrics">
{showLoadingOverlay && loadingProgress ? <span style={{ ...metricPillStyle, color: "#a9ddff" }}>{getGraphLoadTitle(loadingProgress.phase)}</span> : null}
{layoutStatusLabel ? <span style={{ ...metricPillStyle, color: "#a9ddff" }}>{layoutStatusLabel}</span> : null}
{snapshot ? <span style={metricPillStyle}>{snapshot.summary.nodeCount.toLocaleString()} nodes · {snapshot.summary.edgeCount.toLocaleString()} edges</span> : null}
{activeNodeCount !== null ? <span style={{ ...metricPillStyle, color: "#4fd49c", borderColor: "rgba(79, 212, 156, 0.22)" }}>{activeNodeCount.toLocaleString()} active</span> : null}
{searchSummary ? <span style={metricPillStyle}>{searchSummary}</span> : null}
{focusedSummary ? <span style={{ ...metricPillStyle, color: "#f2b66d", borderColor: "rgba(242, 182, 109, 0.24)" }}>{focusedSummary}</span> : null}
{isError ? <span style={{ ...metricPillStyle, color: "#ff8f85", borderColor: "rgba(255, 123, 114, 0.22)" }}>{(error as Error).message}</span> : null}
</div>
</section>
<section className="graph-command-card">
<div className="graph-command-row">
<div className="graph-toggle-cluster">
{selectedNodeId ? (
<>
<button onClick={() => requestViewMode("focused")} style={{ ...actionButtonStyle, background: viewMode === "focused" ? "rgba(31, 111, 235, 0.38)" : actionButtonStyle.background, borderColor: viewMode === "focused" ? "rgba(127, 208, 255, 0.42)" : "rgba(88, 166, 255, 0.2)" }}>Focused View</button>
<button onClick={() => requestViewMode("full")} style={{ ...actionButtonStyle, background: viewMode === "full" ? "rgba(31, 111, 235, 0.38)" : actionButtonStyle.background, borderColor: viewMode === "full" ? "rgba(127, 208, 255, 0.42)" : "rgba(88, 166, 255, 0.2)" }}>Full Graph</button>
</>
) : (
<span style={{ color: "#7f95b3", fontSize: 12 }}>Select a node to switch graph views</span>
)}
</div>
<div className="graph-action-cluster">
<button onClick={() => setIsLayoutRunning((value) => !value)} style={secondaryActionButtonStyle} disabled={isLoading || isFetching}>
{isLayoutRunning ? "Pause Layout" : "Run Layout"}
</button>
<button onClick={() => { setIsGraphStageReady(false); reload(); }} style={secondaryActionButtonStyle} disabled={isLoading || isFetching}>
Reload
</button>
</div>
</div>
<div className="graph-command-row">
<div className="graph-search-shell">
<input
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
void handleSearch();
}
}}
placeholder="Search a node, e.g. Metformin"
style={{ ...inputStyle, minWidth: 260 }}
disabled={showLoadingOverlay && !selectedNodeId}
/>
<button onClick={() => void handleSearch()} style={actionButtonStyle} disabled={showLoadingOverlay && !selectedNodeId}>Search</button>
</div>
</div>
</section>
</div>
{searchError ? <div style={{ position: "absolute", top: 144, right: 34, color: "#ff7b72", fontSize: 12, pointerEvents: "auto" }}>{searchError}</div> : null}
{searchResults.length ? (
<div className="graph-search-results hud-scrollbar">
<div className="graph-search-results-label">Search Results</div>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{searchResults.map((result) => (
<button key={result.node.id} className="graph-search-result-card" onClick={() => focusNode(result.node.id)}>
<div style={{ color: "#fff", fontWeight: 700 }}>{result.node.content || result.node.id}</div>
<div style={{ color: "#8b949e", fontSize: 12 }}>{result.node.type}</div>
<div style={{ color: "#58a6ff", fontSize: 12, marginTop: 4 }}>score {result.score.toFixed(3)}</div>
</button>
))}
</div>
</div>
) : null}
<div className="graph-inspector hud-scrollbar" data-open={selectedNodeId ? "true" : "false"}>
<NodePanel
node={visibleSelectedNode}
predictions={predictions}
predictionType={predictionType}
onPredictionTypeChange={setPredictionType}
onRunPredictions={() => void handleRunPredictions()}
pathTargetId={pathTargetId}
onPathTargetChange={setPathTargetId}
onTracePath={() => void handleTracePath()}
pathResult={pathResult}
onDownloadProvenance={(format) => void handleDownloadProvenance(format)}
/>
</div>
</div>
</div>
);
}
const metricPillStyle: CSSProperties = {
background: "rgba(88, 166, 255, 0.08)",
color: "#8ed3ff",
padding: "6px 11px",
borderRadius: 999,
fontSize: 12,
fontWeight: 700,
border: "1px solid rgba(88, 166, 255, 0.14)",
};
const sectionStyle: CSSProperties = {
display: "flex",
flexDirection: "column",
gap: 10,
padding: 14,
background: "linear-gradient(180deg, rgba(255,255,255,0.025), rgba(255,255,255,0.01))",
border: "1px solid rgba(255, 255, 255, 0.06)",
borderRadius: 16,
};
const sectionTitleStyle: CSSProperties = {
color: "#8fa8c6",
fontSize: 11,
fontWeight: 800,
textTransform: "uppercase",
letterSpacing: "0.08em",
};
const inputStyle: CSSProperties = {
width: "100%",
background: "rgba(0, 0, 0, 0.24)",
border: "1px solid rgba(88, 166, 255, 0.14)",
color: "#fff",
borderRadius: 12,
padding: "10px 12px",
fontSize: 13,
};
const actionButtonStyle: CSSProperties = {
background: "linear-gradient(180deg, rgba(53, 130, 245, 0.28), rgba(25, 88, 185, 0.18))",
color: "#fff",
border: "1px solid rgba(88, 166, 255, 0.2)",
borderRadius: 12,
padding: "10px 13px",
cursor: "pointer",
fontWeight: 700,
fontSize: 12,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.05)",
};
const secondaryActionButtonStyle: CSSProperties = {
...actionButtonStyle,
background: "rgba(255, 255, 255, 0.035)",
border: "1px solid rgba(255, 255, 255, 0.06)",
color: "#d6e5f8",
fontWeight: 500,
};
const predictionCardStyle: CSSProperties = {
textAlign: "left",
padding: 12,
background: "rgba(88, 166, 255, 0.06)",
border: "1px solid rgba(88, 166, 255, 0.1)",
borderRadius: 14,
cursor: "pointer",
};
const pathStepStyle: CSSProperties = {
color: "#e6edf3",
fontSize: 13,
padding: "8px 10px",
background: "rgba(255, 255, 255, 0.03)",
borderRadius: 8,
};
const propertyCardStyle: CSSProperties = {
background: "rgba(0, 0, 0, 0.18)",
padding: "10px 12px",
borderRadius: 12,
border: "1px solid rgba(255, 255, 255, 0.05)",
};
const emptyTextStyle: CSSProperties = {
color: "#8b949e",
fontSize: 12,
lineHeight: 1.5,
};
const subtleChipStyle: CSSProperties = {
background: "rgba(255, 255, 255, 0.035)",
color: "#9fb6d2",
padding: "5px 9px",
borderRadius: 999,
fontSize: 11,
border: "1px solid rgba(255, 255, 255, 0.06)",
};
const collapseStyle: CSSProperties = {
border: "1px solid rgba(255, 255, 255, 0.05)",
borderRadius: 14,
background: "rgba(0, 0, 0, 0.14)",
overflow: "hidden",
};
const summaryStyle: CSSProperties = {
cursor: "pointer",
listStyle: "none",
padding: "12px 14px",
color: "#c6d4e3",
fontSize: 12,
fontWeight: 700,
letterSpacing: "0.04em",
textTransform: "uppercase",
};
@@ -0,0 +1,55 @@
import { forwardRef, useImperativeHandle, useRef } from "react";
import { GraphCanvas, type GraphCanvasHandle } from "./GraphCanvas";
import type { GraphSceneAdapter, GraphSceneHandle, GraphSceneProps, GraphSceneRuntime } from "./scene";
export const SigmaSceneAdapter = forwardRef<GraphSceneHandle, GraphSceneProps>(
function SigmaSceneAdapter(
{
onNodeSelect,
onEdgeSelect,
onInteractionStateChange,
onCameraStateChange,
onDiagnosticsChange,
onAnalyticsChange,
onRuntimeChange,
onLayoutRunningChange,
...sceneProps
},
ref,
) {
const canvasRef = useRef<GraphCanvasHandle>(null);
const runtimeRef = useRef<GraphSceneRuntime | null>(null);
useImperativeHandle(ref, () => ({
fitView: () => canvasRef.current?.fitView(),
focusNode: (nodeId: string) => canvasRef.current?.focusNode(nodeId),
zoomIn: () => canvasRef.current?.zoomIn(),
zoomOut: () => canvasRef.current?.zoomOut(),
getRuntime: () => runtimeRef.current,
setLayoutRunning: onLayoutRunningChange
? (running: boolean) => {
onLayoutRunningChange(running);
}
: undefined,
}), [onLayoutRunningChange]);
return (
<GraphCanvas
ref={canvasRef}
onNodeClick={onNodeSelect ?? (() => {})}
onEdgeClick={onEdgeSelect}
onInteractionStateChange={onInteractionStateChange}
onCameraStateChange={onCameraStateChange}
onDiagnosticsChange={onDiagnosticsChange}
onAnalyticsChange={onAnalyticsChange}
onSceneRuntimeChange={(runtime) => {
runtimeRef.current = runtime;
onRuntimeChange?.(runtime);
}}
onLayoutRunningChange={onLayoutRunningChange}
{...sceneProps}
/>
);
},
) as GraphSceneAdapter;
@@ -0,0 +1,199 @@
import { useEffect, useRef, useState, useCallback, useMemo } from "react";
import { DataSet } from "vis-data";
import { Timeline } from "vis-timeline";
import type { TimelineOptions } from "vis-timeline";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import { GRAPH_THEME } from "./graphTheme";
export interface TimelinePanelProps {
onTimeChange: (time: Date) => void;
minDate?: string;
maxDate?: string;
}
const DEFAULT_MIN_DATE = new Date("1970-01-01T00:00:00Z");
const DEFAULT_MAX_DATE = new Date("2030-01-01T00:00:00Z");
const PLAYHEAD_ID = "playhead";
const PLAY_INTERVAL_MS = 500;
const PLAY_STEP_MONTHS = 6;
const VIS_OVERRIDE_CSS = `
.sem-timeline-wrap .vis-timeline { border: none !important; background: transparent !important; overflow: visible !important; }
.sem-timeline-wrap .vis-panel.vis-background, .sem-timeline-wrap .vis-panel.vis-center { background: transparent !important; }
.sem-timeline-wrap .vis-panel { border-color: ${GRAPH_THEME.ui.timeline.border} !important; }
.sem-timeline-wrap .vis-time-axis .vis-text {
color: ${GRAPH_THEME.ui.timeline.text} !important;
font-size: 11px !important;
font-family: 'JetBrains Mono', 'Fira Code', monospace !important;
padding-top: 3px !important;
}
.sem-timeline-wrap .vis-time-axis .vis-text.vis-major {
color: ${GRAPH_THEME.ui.timeline.textStrong} !important;
font-weight: 700 !important;
font-size: 12px !important;
}
.sem-timeline-wrap .vis-time-axis .vis-grid.vis-minor { border-color: ${GRAPH_THEME.ui.timeline.gridMinor} !important; }
.sem-timeline-wrap .vis-time-axis .vis-grid.vis-major { border-color: ${GRAPH_THEME.ui.timeline.gridMajor} !important; }
.sem-timeline-wrap .vis-custom-time.${PLAYHEAD_ID} {
background: ${GRAPH_THEME.ui.timeline.playheadSoft} !important;
width: 2px !important;
cursor: ew-resize !important;
z-index: 5 !important;
}
.sem-timeline-wrap .vis-custom-time.${PLAYHEAD_ID} > .vis-custom-time-marker {
background: ${GRAPH_THEME.ui.timeline.playhead} !important;
color: ${GRAPH_THEME.ui.text.inverse} !important;
font-size: 10px !important;
font-weight: 700 !important;
border-radius: 3px !important;
padding: 1px 5px !important;
white-space: nowrap !important;
box-shadow: 0 0 8px rgba(98, 226, 205, 0.45) !important;
}
.sem-timeline-wrap .vis-current-time { display: none !important; }
.sem-timeline-wrap .vis-panel.vis-left { display: none !important; }
`;
function safeDate(value: string | undefined, fallback: Date): Date {
if (!value) return fallback;
const parsed = new Date(value);
return Number.isNaN(parsed.getTime()) ? fallback : parsed;
}
function formatPlayheadLabel(value: Date): string {
return `${value.getFullYear()}/${String(value.getMonth() + 1).padStart(2, "0")}`;
}
export function TimelinePanel({ onTimeChange, minDate, maxDate }: TimelinePanelProps) {
const containerRef = useRef<HTMLDivElement>(null);
const timelineRef = useRef<Timeline | null>(null);
const playheadRef = useRef<Date>(DEFAULT_MIN_DATE);
const playIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [displayDate, setDisplayDate] = useState(formatPlayheadLabel(DEFAULT_MIN_DATE));
const minBound = useMemo(() => safeDate(minDate, DEFAULT_MIN_DATE), [minDate]);
const maxBound = useMemo(() => safeDate(maxDate, DEFAULT_MAX_DATE), [maxDate]);
const defaultTime = useMemo(() => new Date(Math.round((minBound.getTime() + maxBound.getTime()) / 2)), [maxBound, minBound]);
useEffect(() => {
if (!containerRef.current) return;
const timeline = timelineRef.current;
if (!timeline) {
const items = new DataSet([]);
const options: TimelineOptions = {
height: "100%",
min: minBound,
max: maxBound,
start: minBound,
end: maxBound,
showCurrentTime: false,
zoomable: true,
moveable: true,
zoomMin: 1000 * 60 * 60 * 24 * 365,
zoomMax: 1000 * 60 * 60 * 24 * 365 * 80,
showMajorLabels: true,
showMinorLabels: true,
timeAxis: { scale: "year", step: 5 },
format: { minorLabels: { year: "YYYY" }, majorLabels: { year: "YYYY" } },
orientation: { axis: "bottom" },
margin: { item: 0, axis: 0 },
selectable: false,
stack: false,
} as TimelineOptions;
const nextTimeline = new Timeline(containerRef.current, items, options);
timelineRef.current = nextTimeline;
playheadRef.current = defaultTime;
nextTimeline.addCustomTime(defaultTime, PLAYHEAD_ID);
nextTimeline.on("timechange", (props: { id: string; time: Date }) => {
if (props.id !== PLAYHEAD_ID) return;
playheadRef.current = props.time;
nextTimeline.setCustomTime(props.time, PLAYHEAD_ID);
onTimeChange(props.time);
setDisplayDate(formatPlayheadLabel(props.time));
});
onTimeChange(defaultTime);
setDisplayDate(formatPlayheadLabel(defaultTime));
return () => {
nextTimeline.destroy();
timelineRef.current = null;
};
}
timeline.setOptions({ min: minBound, max: maxBound, start: minBound, end: maxBound });
playheadRef.current = defaultTime;
timeline.setCustomTime(defaultTime, PLAYHEAD_ID);
onTimeChange(defaultTime);
setDisplayDate(formatPlayheadLabel(defaultTime));
}, [defaultTime, maxBound, minBound, onTimeChange]);
const startPlay = useCallback(() => {
if (playIntervalRef.current) return;
playIntervalRef.current = setInterval(() => {
const timeline = timelineRef.current;
if (!timeline) return;
const next = new Date(playheadRef.current);
next.setMonth(next.getMonth() + PLAY_STEP_MONTHS);
if (next >= maxBound) {
next.setTime(minBound.getTime());
}
playheadRef.current = next;
timeline.setCustomTime(next, PLAYHEAD_ID);
onTimeChange(next);
setDisplayDate(formatPlayheadLabel(next));
}, PLAY_INTERVAL_MS);
}, [maxBound, minBound, onTimeChange]);
const stopPlay = useCallback(() => {
if (playIntervalRef.current) {
clearInterval(playIntervalRef.current);
playIntervalRef.current = null;
}
}, []);
const togglePlay = useCallback(() => {
setIsPlaying((previous) => {
if (previous) {
stopPlay();
return false;
}
startPlay();
return true;
});
}, [startPlay, stopPlay]);
useEffect(() => () => stopPlay(), [stopPlay]);
return (
<div style={{ position: "relative", width: "100%", height: "90px", borderTop: `1px solid ${GRAPH_THEME.ui.timeline.border}`, background: GRAPH_THEME.ui.timeline.background, backdropFilter: "blur(16px)", WebkitBackdropFilter: "blur(16px)", display: "flex", alignItems: "stretch", flexShrink: 0 }}>
<style>{VIS_OVERRIDE_CSS}</style>
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 4, padding: "0 16px", borderRight: `1px solid ${GRAPH_THEME.ui.timeline.border}`, minWidth: 80, flexShrink: 0 }}>
<button
id="temporal-play-btn"
onClick={togglePlay}
title={isPlaying ? "Pause Evolution" : "Play Evolution"}
style={{ width: 34, height: 34, borderRadius: "50%", border: `1.5px solid ${isPlaying ? GRAPH_THEME.ui.control.activeBorder : GRAPH_THEME.ui.control.defaultBorder}`, background: isPlaying ? GRAPH_THEME.ui.timeline.playheadSoft : GRAPH_THEME.ui.control.defaultBg, color: GRAPH_THEME.ui.timeline.playhead, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", transition: "all 0.2s", boxShadow: isPlaying ? "0 0 10px rgba(98, 226, 205, 0.32)" : "none" }}
>
{isPlaying ? (
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16" /><rect x="14" y="4" width="4" height="16" /></svg>
) : (
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5,3 19,12 5,21" /></svg>
)}
</button>
<span style={{ fontSize: 10, color: isPlaying ? GRAPH_THEME.ui.timeline.playhead : GRAPH_THEME.ui.timeline.text, fontFamily: "monospace", letterSpacing: "0.04em", transition: "color 0.2s" }}>
{displayDate}
</span>
</div>
<div style={{ position: "absolute", top: 5, left: 100, fontSize: 10, fontWeight: 600, letterSpacing: "0.1em", color: GRAPH_THEME.ui.text.subtle, textTransform: "uppercase", pointerEvents: "none", zIndex: 2 }}>
Temporal Scrubber · {minBound.getFullYear()}-{maxBound.getFullYear()}
</div>
<div className="sem-timeline-wrap" style={{ flex: 1, overflow: "hidden", position: "relative" }}>
<div ref={containerRef} style={{ width: "100%", height: "100%", position: "relative" }} />
</div>
</div>
);
}
@@ -0,0 +1,29 @@
import type { GraphBehavior } from "./types";
export const clickSelectionBehavior: GraphBehavior = {
id: "click-selection",
attach: () => {},
detach: () => {},
onNodeClick: (context, nodeId) => {
context.setHoveredNodeId(nodeId);
context.onEdgeSelectionChange("");
if (context.getInteractionState().selectedNodeId === nodeId) {
context.onNodeSelectionChange("");
} else {
context.onNodeSelectionChange(nodeId);
}
},
onEdgeClick: (context, edgeId) => {
context.setHoveredNodeId(null);
if (context.getInteractionState().selectedEdgeId === edgeId) {
context.onEdgeSelectionChange("");
} else {
context.onEdgeSelectionChange(edgeId);
}
},
onStageClick: (context) => {
context.setHoveredNodeId(null);
context.onEdgeSelectionChange("");
context.onNodeSelectionChange("");
},
};

Some files were not shown because too many files have changed in this diff Show More