helios-cortex 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. helios_cortex/__init__.py +18 -0
  2. helios_cortex/_optional.py +17 -0
  3. helios_cortex/_pending/__init__.py +8 -0
  4. helios_cortex/_pending/browse.py +181 -0
  5. helios_cortex/_pending/crawler/__init__.py +6 -0
  6. helios_cortex/_pending/crawler/presets/index.yaml +13 -0
  7. helios_cortex/_pending/crawler/presets/retail-returns.yaml +535 -0
  8. helios_cortex/_pending/crawler/settings.py +566 -0
  9. helios_cortex/_pending/crawler/sources.py +144 -0
  10. helios_cortex/_pending/crawler_settings.py +94 -0
  11. helios_cortex/_pending/evidence.py +416 -0
  12. helios_cortex/artifact_storage.py +289 -0
  13. helios_cortex/artifacts.py +376 -0
  14. helios_cortex/audit_sink.py +86 -0
  15. helios_cortex/caller.py +46 -0
  16. helios_cortex/catalog/__init__.py +21 -0
  17. helios_cortex/catalog/base.py +91 -0
  18. helios_cortex/compiler/__init__.py +9 -0
  19. helios_cortex/compiler/compiler.py +402 -0
  20. helios_cortex/conformance.py +364 -0
  21. helios_cortex/data_authorization.py +222 -0
  22. helios_cortex/domain.py +118 -0
  23. helios_cortex/engines/__init__.py +16 -0
  24. helios_cortex/engines/base.py +108 -0
  25. helios_cortex/engines/dialect.py +62 -0
  26. helios_cortex/engines/duckdb_engine.py +55 -0
  27. helios_cortex/engines/sqlalchemy_engine.py +121 -0
  28. helios_cortex/engines/trino_engine.py +132 -0
  29. helios_cortex/index/__init__.py +46 -0
  30. helios_cortex/index/__main__.py +35 -0
  31. helios_cortex/index/ids.py +63 -0
  32. helios_cortex/index/mappings.py +128 -0
  33. helios_cortex/index/ontology_versions.py +104 -0
  34. helios_cortex/index/records.py +300 -0
  35. helios_cortex/index/runs.py +102 -0
  36. helios_cortex/index/schema_impala.sql +201 -0
  37. helios_cortex/index/store.py +123 -0
  38. helios_cortex/index/tables.py +142 -0
  39. helios_cortex/llm/__init__.py +17 -0
  40. helios_cortex/llm/client.py +535 -0
  41. helios_cortex/ontology/__init__.py +35 -0
  42. helios_cortex/ontology/graph.py +209 -0
  43. helios_cortex/ontology/mapping.py +439 -0
  44. helios_cortex/ontology/parser.py +336 -0
  45. helios_cortex/ossie/__init__.py +2 -0
  46. helios_cortex/ossie/format.py +43 -0
  47. helios_cortex/ossie/model.py +263 -0
  48. helios_cortex/ossie/ossie-schema.json +367 -0
  49. helios_cortex/plugins.py +128 -0
  50. helios_cortex/py.typed +0 -0
  51. helios_cortex/registry/__init__.py +214 -0
  52. helios_cortex/registry/__main__.py +27 -0
  53. helios_cortex/registry/migrations/env.py +18 -0
  54. helios_cortex/registry/migrations/script.py.mako +20 -0
  55. helios_cortex/registry/migrations/versions/0001_registry.py +73 -0
  56. helios_cortex/registry/sql.py +312 -0
  57. helios_cortex/runs.py +517 -0
  58. helios_cortex/settings.py +355 -0
  59. helios_cortex/visibility.py +46 -0
  60. helios_cortex-0.1.0.dist-info/METADATA +38 -0
  61. helios_cortex-0.1.0.dist-info/RECORD +63 -0
  62. helios_cortex-0.1.0.dist-info/WHEEL +4 -0
  63. helios_cortex-0.1.0.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,18 @@
1
+ """helios core library: shared clients and logic for jobs and applications."""
2
+
3
+ from helios_cortex.caller import Caller, IdentityError, IdentityMode
4
+
5
+ __all__ = ["Caller", "IdentityError", "IdentityMode"]
6
+
7
+
8
+ def __getattr__(name: str) -> str:
9
+ # __version__ is read from the installed metadata on first use, so importing
10
+ # helios_cortex opens no files.
11
+ if name == "__version__":
12
+ from importlib.metadata import PackageNotFoundError, version
13
+
14
+ try:
15
+ return version("helios-cortex")
16
+ except PackageNotFoundError: # running from a source path without an install
17
+ return "0.0.0+unknown"
18
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,17 @@
1
+ """Optional dependencies: the base install is the compiler, semantic model, DuckDB and
2
+ SQLite; anything else comes with an extra (``pip install 'helios-cortex[llm]'``).
3
+
4
+ A module that needs an extra raises ``missing_extra(...)`` from the ImportError, so
5
+ the message says what to install instead of naming a package the user never chose.
6
+ Base modules import those modules only where they are used.
7
+ """
8
+ from __future__ import annotations
9
+
10
+
11
+ def missing_extra(feature: str, extra: str, error: ImportError) -> ModuleNotFoundError:
12
+ """The error for ``feature`` when the package ``error`` failed to import is missing."""
13
+ return ModuleNotFoundError(
14
+ f"{feature} needs the optional dependency {error.name or 'it imports'}: "
15
+ f"pip install 'helios-cortex[{extra}]'",
16
+ name=error.name,
17
+ )
@@ -0,0 +1,8 @@
1
+ """Feature code that core used to reach into, held here until its package exists.
2
+
3
+ - health: helios-serve (Phase 6)
4
+ - crawler, crawler_settings, evidence, browse: helios-crawl (Phase 10)
5
+
6
+ Nothing outside this directory in helios_cortex may import it; import-linter
7
+ enforces that (burndown 1.2). Each module leaves when its package is created.
8
+ """
@@ -0,0 +1,181 @@
1
+ """Browsing what the crawler found, from an ontology class down to passages
2
+ (CR-9): the entities of a class, then one entity's documents and the passages
3
+ that mention it.
4
+
5
+ Reads each source's latest successful crawl. A finished run never changes, so
6
+ its tables are kept in memory; runs that are no longer the latest are dropped.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import posixpath
12
+ from typing import Any
13
+
14
+ from helios_cortex._pending import evidence
15
+ from helios_cortex.index.records import AssetRecord, EntityRecord, MentionRecord, RelationshipRecord, SegmentRecord
16
+ from helios_cortex.index.store import IndexStore
17
+
18
+ ASSETS = "helios_index.assets"
19
+ SEGMENTS = "helios_index.segments"
20
+ MENTIONS = "helios_index.mentions"
21
+ ENTITY_LINKS = "helios_index.entity_links"
22
+ RELATIONSHIPS = "helios_index.relationships"
23
+ MAX_DOCUMENTS = 30
24
+
25
+ _tables: dict[tuple[str, str], list[Any]] = {}
26
+
27
+
28
+ def _rows(store: IndexStore, crawl_run_id: str, table: str) -> list[Any]:
29
+ key = (crawl_run_id, table)
30
+ if key not in _tables:
31
+ _tables[key] = store.read(table, {"crawl_run_id": crawl_run_id})
32
+ return _tables[key]
33
+
34
+
35
+ def _latest(store: IndexStore) -> list[Any]:
36
+ latest = evidence.latest_runs(store)
37
+ keep = {run.crawl_run_id for run in latest}
38
+ for key in [k for k in _tables if k[0] not in keep]:
39
+ del _tables[key]
40
+ return latest
41
+
42
+
43
+ def _entity_view(entity: EntityRecord, source: str) -> dict[str, Any]:
44
+ return {
45
+ "entity_id": entity.entity_id,
46
+ "class": entity.ontology_class,
47
+ "name": entity.canonical_name,
48
+ "keys": list(entity.external_ids),
49
+ "source": source,
50
+ "crawl_run_id": entity.crawl_run_id,
51
+ }
52
+
53
+
54
+ def class_entities(
55
+ store: IndexStore, classes: list[str], query: str = "", limit: int = 50
56
+ ) -> dict[str, Any]:
57
+ """Entities of the given ontology classes, most-documented first."""
58
+ wanted = {c.casefold() for c in classes}
59
+ needle = query.strip().casefold()
60
+ found: list[dict[str, Any]] = []
61
+ for run in _latest(store):
62
+ documents: dict[str, set[str]] = {}
63
+ for rel in _rows(store, run.crawl_run_id, RELATIONSHIPS):
64
+ if isinstance(rel, RelationshipRecord) and (rel.source_kind, rel.target_kind) == ("asset", "entity"):
65
+ documents.setdefault(rel.target_id, set()).add(rel.source_id)
66
+ for entity in _rows(store, run.crawl_run_id, evidence.ENTITIES):
67
+ if not isinstance(entity, EntityRecord) or entity.ontology_class.casefold() not in wanted:
68
+ continue
69
+ haystack = " ".join([entity.canonical_name, *entity.external_ids]).casefold()
70
+ if needle and needle not in haystack:
71
+ continue
72
+ found.append(
73
+ {**_entity_view(entity, run.source), "documents": len(documents.get(entity.entity_id, ()))}
74
+ )
75
+ found.sort(key=lambda e: (-e["documents"], e["name"]))
76
+ return {"total": len(found), "entities": found[: max(1, min(limit, 500))]}
77
+
78
+
79
+ def _document_name(asset: AssetRecord) -> str:
80
+ locator = asset.source_locator or {}
81
+ path = str(locator.get("key") or locator.get("uri") or locator.get("path") or "")
82
+ return posixpath.basename(path) or asset.asset_id
83
+
84
+
85
+ def entity_documents(store: IndexStore, crawl_run_id: str, entity_id: str) -> dict[str, Any] | None:
86
+ """One entity with its documents, the passages that mention it (with the
87
+ character ranges of each mention), related entities and claims."""
88
+ run = next((r for r in _latest(store) if r.crawl_run_id == crawl_run_id), None)
89
+ if run is None:
90
+ return None
91
+ entities = {
92
+ e.entity_id: e for e in _rows(store, crawl_run_id, evidence.ENTITIES) if isinstance(e, EntityRecord)
93
+ }
94
+ entity = entities.get(entity_id)
95
+ if entity is None:
96
+ return None
97
+
98
+ relation: dict[str, str] = {} # asset_id -> About or Mentions (About wins)
99
+ related: list[dict[str, Any]] = []
100
+ for rel in _rows(store, crawl_run_id, RELATIONSHIPS):
101
+ if not isinstance(rel, RelationshipRecord):
102
+ continue
103
+ if rel.source_kind == "asset" and rel.target_id == entity_id:
104
+ if relation.get(rel.source_id) != "About":
105
+ relation[rel.source_id] = rel.relationship_type
106
+ elif rel.source_kind == "entity" and entity_id in (rel.source_id, rel.target_id):
107
+ other = entities.get(rel.target_id if rel.source_id == entity_id else rel.source_id)
108
+ if other is not None:
109
+ related.append(
110
+ {
111
+ "relationship": rel.relationship_type,
112
+ "direction": "out" if rel.source_id == entity_id else "in",
113
+ "entity_id": other.entity_id,
114
+ "class": other.ontology_class,
115
+ "name": other.canonical_name,
116
+ }
117
+ )
118
+
119
+ links = {
120
+ link.mention_id: link
121
+ for link in store.read(ENTITY_LINKS, {"crawl_run_id": crawl_run_id, "entity_id": entity_id})
122
+ }
123
+ by_segment: dict[str, list[dict[str, Any]]] = {}
124
+ for mention in _rows(store, crawl_run_id, MENTIONS):
125
+ link = links.get(mention.mention_id) if isinstance(mention, MentionRecord) else None
126
+ if link is None:
127
+ continue
128
+ by_segment.setdefault(mention.segment_id, []).append(
129
+ {
130
+ "surface_form": mention.surface_form,
131
+ "start": mention.start_offset,
132
+ "end": mention.end_offset,
133
+ "resolved_by": link.resolved_by,
134
+ "link_type": link.link_type,
135
+ }
136
+ )
137
+ passages: dict[str, list[dict[str, Any]]] = {}
138
+ for segment in _rows(store, crawl_run_id, SEGMENTS):
139
+ if isinstance(segment, SegmentRecord) and segment.segment_id in by_segment:
140
+ passages.setdefault(segment.asset_id, []).append(
141
+ {
142
+ "segment_id": segment.segment_id,
143
+ "segment_type": segment.segment_type,
144
+ "ordinal": segment.ordinal,
145
+ "locator": segment.locator,
146
+ "text": segment.text,
147
+ "mentions": sorted(
148
+ by_segment[segment.segment_id], key=lambda m: (m["start"] is None, m["start"] or 0)
149
+ ),
150
+ }
151
+ )
152
+
153
+ documents = []
154
+ for asset in _rows(store, crawl_run_id, ASSETS):
155
+ if not isinstance(asset, AssetRecord):
156
+ continue
157
+ if asset.asset_id not in relation and asset.asset_id not in passages:
158
+ continue
159
+ documents.append(
160
+ {
161
+ "asset_id": asset.asset_id,
162
+ "name": _document_name(asset),
163
+ "class": asset.ontology_class,
164
+ "mime_type": asset.mime_type,
165
+ "timestamp": asset.semantic_timestamp,
166
+ "relationship": relation.get(asset.asset_id, "Mentions"),
167
+ "passages": sorted(passages.get(asset.asset_id, []), key=lambda p: p["ordinal"]),
168
+ }
169
+ )
170
+ documents.sort(
171
+ key=lambda d: (d["relationship"] != "About", -len(d["passages"]), d["timestamp"] or "", d["name"])
172
+ )
173
+ claims = evidence.entity_claims(store, entity_id)
174
+ return {
175
+ "entity": _entity_view(entity, run.source),
176
+ "documents_total": len(documents),
177
+ "documents": documents[:MAX_DOCUMENTS],
178
+ "related": sorted(related, key=lambda r: (r["relationship"], r["name"]))[:50],
179
+ "claims_total": claims["claims_total"],
180
+ "claims": claims["claims"],
181
+ }
@@ -0,0 +1,6 @@
1
+ """Shared crawler definitions: settings (CR-0e). The crawler itself runs as a
2
+ Workbench Job in the Helios project (apps/helios/crawler)."""
3
+
4
+ from helios_cortex._pending.crawler.settings import EMPTY_SETTINGS, CrawlerSettings, load_preset, ontology_problems, presets
5
+
6
+ __all__ = ["EMPTY_SETTINGS", "CrawlerSettings", "load_preset", "ontology_problems", "presets"]
@@ -0,0 +1,13 @@
1
+ # Shipped crawler settings presets. Each is <name>.yaml in this directory: a
2
+ # complete settings document a user can load, edit and save as a version.
3
+ presets:
4
+ retail-returns:
5
+ title: Retail returns
6
+ description: >-
7
+ Store returns over a TPC-DS warehouse: emails, chat threads and PDF return
8
+ reports. Identifier patterns for RMA, case and ticket numbers, the PDF
9
+ report's labels, and cue phrases for damage, return reason and refunds.
10
+
11
+ # Before settings schema 2 one set of rules was built into the crawler. A stored
12
+ # schema 1 document is read as this preset with the document's sections on top.
13
+ schema_1_defaults: retail-returns