rememberstack 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.
- rememberstack/__init__.py +9 -0
- rememberstack/adapters/__init__.py +42 -0
- rememberstack/adapters/codex_writer.py +221 -0
- rememberstack/adapters/markitdown_converter.py +42 -0
- rememberstack/adapters/openrouter.py +136 -0
- rememberstack/adapters/selfhost/__init__.py +54 -0
- rememberstack/adapters/selfhost/forget.py +66 -0
- rememberstack/adapters/selfhost/git.py +374 -0
- rememberstack/adapters/selfhost/lance.py +328 -0
- rememberstack/adapters/selfhost/minio.py +279 -0
- rememberstack/adapters/selfhost/mounts.py +249 -0
- rememberstack/adapters/selfhost/object_store.py +130 -0
- rememberstack/adapters/selfhost/projection.py +80 -0
- rememberstack/adapters/selfhost/queue.py +137 -0
- rememberstack/adapters/selfhost/telemetry.py +45 -0
- rememberstack/adapters/selfhost/watcher.py +70 -0
- rememberstack/adapters/testing/__init__.py +15 -0
- rememberstack/adapters/testing/cost_meter.py +13 -0
- rememberstack/adapters/testing/model_provider.py +83 -0
- rememberstack/adapters/testing/queue.py +43 -0
- rememberstack/adapters/testing/telemetry.py +22 -0
- rememberstack/client.py +19 -0
- rememberstack/core/__init__.py +127 -0
- rememberstack/core/blockizer.py +189 -0
- rememberstack/core/chunker.py +216 -0
- rememberstack/core/consumption_skill.py +275 -0
- rememberstack/core/conversion.py +76 -0
- rememberstack/core/core_manifest.py +598 -0
- rememberstack/core/extension_packs.py +124 -0
- rememberstack/core/forget.py +17 -0
- rememberstack/core/knowledge_authored.py +276 -0
- rememberstack/core/knowledge_compile.py +215 -0
- rememberstack/core/knowledge_fact_sheet.py +210 -0
- rememberstack/core/knowledge_hashing.py +68 -0
- rememberstack/core/knowledge_planner.py +64 -0
- rememberstack/core/knowledge_writer.py +175 -0
- rememberstack/core/ranking.py +200 -0
- rememberstack/core/recipe_linter.py +149 -0
- rememberstack/core/section_snap.py +209 -0
- rememberstack/core/storage_routing.py +27 -0
- rememberstack/eval/__init__.py +53 -0
- rememberstack/eval/consumption.py +141 -0
- rememberstack/eval/contradiction.py +184 -0
- rememberstack/eval/harness.py +136 -0
- rememberstack/eval/lifecycle.py +400 -0
- rememberstack/eval/operational_scale.py +49 -0
- rememberstack/eval/resolution.py +255 -0
- rememberstack/eval/retrieval_spikes.py +50 -0
- rememberstack/eval/skeleton.py +231 -0
- rememberstack/llm/__init__.py +1 -0
- rememberstack/model/__init__.py +589 -0
- rememberstack/model/adjudication.py +100 -0
- rememberstack/model/auth.py +27 -0
- rememberstack/model/blocks.py +30 -0
- rememberstack/model/chunks.py +190 -0
- rememberstack/model/claims.py +162 -0
- rememberstack/model/client.py +98 -0
- rememberstack/model/clustering.py +54 -0
- rememberstack/model/component_version.py +124 -0
- rememberstack/model/consumption.py +88 -0
- rememberstack/model/conversion.py +31 -0
- rememberstack/model/deployment.py +53 -0
- rememberstack/model/documents.py +168 -0
- rememberstack/model/envelope.py +513 -0
- rememberstack/model/evaluation.py +72 -0
- rememberstack/model/forget.py +143 -0
- rememberstack/model/git.py +13 -0
- rememberstack/model/knowledge.py +840 -0
- rememberstack/model/knowledge_authored.py +325 -0
- rememberstack/model/knowledge_planner.py +431 -0
- rememberstack/model/lifecycle.py +42 -0
- rememberstack/model/model_provider.py +78 -0
- rememberstack/model/mounts.py +24 -0
- rememberstack/model/object_store.py +21 -0
- rememberstack/model/operational_scale.py +59 -0
- rememberstack/model/operations.py +153 -0
- rememberstack/model/processing.py +228 -0
- rememberstack/model/queue.py +73 -0
- rememberstack/model/recipes.py +83 -0
- rememberstack/model/relations.py +79 -0
- rememberstack/model/resolution.py +83 -0
- rememberstack/model/retrieval_spikes.py +62 -0
- rememberstack/model/sections.py +120 -0
- rememberstack/model/telemetry.py +30 -0
- rememberstack/ports/__init__.py +29 -0
- rememberstack/ports/auth.py +16 -0
- rememberstack/ports/connector.py +23 -0
- rememberstack/ports/cost_meter.py +17 -0
- rememberstack/ports/forget.py +20 -0
- rememberstack/ports/git.py +20 -0
- rememberstack/ports/model_provider.py +28 -0
- rememberstack/ports/mounts.py +16 -0
- rememberstack/ports/object_store.py +27 -0
- rememberstack/ports/p1_index.py +92 -0
- rememberstack/ports/purge.py +93 -0
- rememberstack/ports/queue.py +23 -0
- rememberstack/ports/telemetry.py +21 -0
- rememberstack/profiles/__init__.py +22 -0
- rememberstack/profiles/selfhost.py +324 -0
- rememberstack/profiles/selfhost_forget.py +158 -0
- rememberstack/profiles/selfhost_operations.py +95 -0
- rememberstack/py.typed +1 -0
- rememberstack/spine/__init__.py +93 -0
- rememberstack/spine/admission.py +26 -0
- rememberstack/spine/backfill.py +168 -0
- rememberstack/spine/catalog_contract.py +742 -0
- rememberstack/spine/chunk_catalog.py +237 -0
- rememberstack/spine/claim_catalog.py +298 -0
- rememberstack/spine/clustering.py +740 -0
- rememberstack/spine/component_versions.py +208 -0
- rememberstack/spine/consumption.py +81 -0
- rememberstack/spine/deployment_bootstrap.py +445 -0
- rememberstack/spine/document_catalog.py +621 -0
- rememberstack/spine/entity_registry.py +205 -0
- rememberstack/spine/extension_packs.py +220 -0
- rememberstack/spine/fact_catalog.py +571 -0
- rememberstack/spine/forget.py +1753 -0
- rememberstack/spine/knowledge.py +5467 -0
- rememberstack/spine/lifecycle.py +1071 -0
- rememberstack/spine/migrations/__init__.py +1 -0
- rememberstack/spine/migrations/_helpers.py +153 -0
- rememberstack/spine/migrations/env.py +58 -0
- rememberstack/spine/migrations/script.py.mako +27 -0
- rememberstack/spine/migrations/versions/__init__.py +1 -0
- rememberstack/spine/migrations/versions/p0_02_0001_extensions_enums.py +189 -0
- rememberstack/spine/migrations/versions/p0_02_0002_infrastructure_registries.py +321 -0
- rememberstack/spine/migrations/versions/p0_02_0003_entities_evaluation_e0_e1.py +631 -0
- rememberstack/spine/migrations/versions/p0_02_0004_claims_facts_evidence.py +411 -0
- rememberstack/spine/migrations/versions/p0_02_0005_projection_knowledge_retrieval.py +391 -0
- rememberstack/spine/migrations/versions/p0_02_0006_partitions_views.py +158 -0
- rememberstack/spine/migrations/versions/p2_06_0007_invalidated_outcome.py +26 -0
- rememberstack/spine/migrations/versions/p3_01_0008_document_version_target.py +58 -0
- rememberstack/spine/migrations/versions/p3_05_0009_reconcile_stage.py +27 -0
- rememberstack/spine/migrations/versions/p3_07_0010_lifecycle_eval_suite.py +25 -0
- rememberstack/spine/migrations/versions/p4_01_0011_survivor_view_rewrite.py +57 -0
- rememberstack/spine/migrations/versions/p6_02_0012_knowledge_compile_recovery.py +58 -0
- rememberstack/spine/migrations/versions/p6_04_0013_knowledge_writer_ledger.py +46 -0
- rememberstack/spine/migrations/versions/p6_05_0014_knowledge_planner_runtime.py +217 -0
- rememberstack/spine/migrations/versions/p6_06_0015_authored_dispatch_runtime.py +38 -0
- rememberstack/spine/migrations/versions/p7_02_0016_operational_eval_suite.py +19 -0
- rememberstack/spine/migrations/versions/p7_05_0017_hard_forget.py +55 -0
- rememberstack/spine/observation_adjudication.py +778 -0
- rememberstack/spine/operations.py +298 -0
- rememberstack/spine/projection.py +662 -0
- rememberstack/spine/recipes.py +276 -0
- rememberstack/spine/resolver.py +763 -0
- rememberstack/spine/review.py +650 -0
- rememberstack/spine/settings.py +22 -0
- rememberstack/spine/supersession.py +510 -0
- rememberstack/spine/sync.py +128 -0
- rememberstack/spine/work_ledger.py +816 -0
- rememberstack/surfaces/__init__.py +110 -0
- rememberstack/surfaces/cli.py +447 -0
- rememberstack/surfaces/consumption_skill.py +87 -0
- rememberstack/surfaces/graph_queries.py +698 -0
- rememberstack/surfaces/http_api.py +377 -0
- rememberstack/surfaces/mcp.py +67 -0
- rememberstack/surfaces/query_engine.py +1591 -0
- rememberstack/surfaces/recipe_executor.py +185 -0
- rememberstack/surfaces/recipe_surface.py +219 -0
- rememberstack/surfaces/remote_mcp.py +133 -0
- rememberstack/surfaces/sdk.py +324 -0
- rememberstack/workers/__init__.py +155 -0
- rememberstack/workers/base.py +312 -0
- rememberstack/workers/e0.py +577 -0
- rememberstack/workers/e1.py +425 -0
- rememberstack/workers/e2.py +525 -0
- rememberstack/workers/e3.py +434 -0
- rememberstack/workers/forget.py +299 -0
- rememberstack/workers/knowledge_authored.py +146 -0
- rememberstack/workers/knowledge_driver.py +735 -0
- rememberstack/workers/knowledge_fact_sheet.py +123 -0
- rememberstack/workers/knowledge_planner.py +325 -0
- rememberstack/workers/knowledge_writer.py +393 -0
- rememberstack/workers/operations.py +42 -0
- rememberstack/workers/p1.py +234 -0
- rememberstack/workers/p2.py +513 -0
- rememberstack/workers/p2_analytics.py +276 -0
- rememberstack/workers/p3.py +673 -0
- rememberstack/workers/reconcile.py +485 -0
- rememberstack/workers/sync.py +168 -0
- rememberstack-0.1.0.dist-info/METADATA +213 -0
- rememberstack-0.1.0.dist-info/RECORD +186 -0
- rememberstack-0.1.0.dist-info/WHEEL +4 -0
- rememberstack-0.1.0.dist-info/entry_points.txt +2 -0
- rememberstack-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Graph analytics on the freshly built snapshot (p2 §7, D11 → D72).
|
|
2
|
+
|
|
3
|
+
PageRank (salience prior), k-core (hub-ness), weakly-connected components
|
|
4
|
+
(ER-health signal), and **Louvain community detection** all run natively in
|
|
5
|
+
the engine on the snapshot the rebuild just validated — Louvain included,
|
|
6
|
+
which D72 corrected after verifying it live on the deployed build (the
|
|
7
|
+
vendored survey behind D11 said it was absent, so the original plan carried
|
|
8
|
+
an external igraph pass; the simpler mechanism removes it).
|
|
9
|
+
|
|
10
|
+
Results are written **back to Postgres** and never reprojected into the
|
|
11
|
+
graph's node tables: analytics are graph-DERIVED, so loading them into the
|
|
12
|
+
projection would be circular (D6). Community *labels* — the K1 navigation
|
|
13
|
+
aid — are an optional batched micro-LLM call over each community's top
|
|
14
|
+
members by PageRank; without a provider the communities are unlabeled,
|
|
15
|
+
which costs navigation polish and nothing load-bearing.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import cast
|
|
19
|
+
from typing import Final
|
|
20
|
+
from uuid import NAMESPACE_URL
|
|
21
|
+
from uuid import UUID
|
|
22
|
+
from uuid import uuid5
|
|
23
|
+
|
|
24
|
+
import ladybug
|
|
25
|
+
from pydantic import BaseModel
|
|
26
|
+
from pydantic import ConfigDict
|
|
27
|
+
from pydantic import Field
|
|
28
|
+
from pydantic_settings import BaseSettings
|
|
29
|
+
from pydantic_settings import SettingsConfigDict
|
|
30
|
+
|
|
31
|
+
from rememberstack.model import ModelRequest
|
|
32
|
+
from rememberstack.ports.model_provider import ModelProviderPort
|
|
33
|
+
from rememberstack.spine.projection import ProjectionCatalog
|
|
34
|
+
|
|
35
|
+
COMMUNITY_DETECTOR_VERSION: Final = "p2-communities-2026.07:louvain-native"
|
|
36
|
+
"""The analytics pass's component version (D12); the algorithm is part of
|
|
37
|
+
the identity because a detector swap changes every assignment."""
|
|
38
|
+
|
|
39
|
+
_PROJECTION: Final = "analytics_graph"
|
|
40
|
+
_LABEL_MEMBERS: Final = 8
|
|
41
|
+
"""How many top-PageRank members the labeling prompt sees per community."""
|
|
42
|
+
|
|
43
|
+
_LABEL_PROMPT: Final = (
|
|
44
|
+
"Each numbered line lists the most central members of one cluster in a"
|
|
45
|
+
" knowledge graph. Give EVERY cluster a short topic label (2-5 words) an"
|
|
46
|
+
" agent could navigate by, returning the cluster's number with its"
|
|
47
|
+
" label.\n\n{clusters}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CommunityLabelItem(BaseModel):
|
|
52
|
+
"""One cluster's label, keyed by its position in the batched prompt."""
|
|
53
|
+
|
|
54
|
+
model_config = ConfigDict(frozen=True, extra="ignore")
|
|
55
|
+
|
|
56
|
+
index: int = Field(default=-1)
|
|
57
|
+
label: str = Field(default="")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class CommunityLabels(BaseModel):
|
|
61
|
+
"""The batched labeling call's structured output (p2 §7)."""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(frozen=True, extra="ignore")
|
|
64
|
+
|
|
65
|
+
labels: tuple[CommunityLabelItem, ...] = ()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AnalyticsSettings(BaseSettings):
|
|
69
|
+
"""The analytics pass's knobs (D70 port config; D22 numbers)."""
|
|
70
|
+
|
|
71
|
+
model_config = SettingsConfigDict(env_prefix="REMEMBERSTACK_P2_ANALYTICS_")
|
|
72
|
+
|
|
73
|
+
label_model: str = Field(default="openai/gpt-5.6-luna")
|
|
74
|
+
min_community_size_to_label: int = Field(default=3, ge=1)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class GraphAnalyticsWorker:
|
|
78
|
+
"""Compute snapshot analytics natively; write assignments to Postgres."""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
*,
|
|
83
|
+
catalog: ProjectionCatalog,
|
|
84
|
+
model_provider: ModelProviderPort | None = None,
|
|
85
|
+
settings: AnalyticsSettings | None = None,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Bind to the spine and (optionally) the labeling model seat."""
|
|
88
|
+
self._catalog = catalog
|
|
89
|
+
self._model_provider = model_provider
|
|
90
|
+
self._settings = settings or AnalyticsSettings()
|
|
91
|
+
|
|
92
|
+
def compute(
|
|
93
|
+
self, *, snapshot_id: UUID, connection: ladybug.Connection
|
|
94
|
+
) -> tuple[tuple[dict[str, object], ...], tuple[dict[str, object], ...]]:
|
|
95
|
+
"""Run every algorithm on the snapshot; return rows, write nothing.
|
|
96
|
+
|
|
97
|
+
Computing and writing are deliberately separate (Codex review): the
|
|
98
|
+
algorithms need the writer's live graph, but their rows must not
|
|
99
|
+
outlive a snapshot that later fails validation or upload — so the
|
|
100
|
+
caller persists them only after the snapshot publishes.
|
|
101
|
+
"""
|
|
102
|
+
connection.execute("INSTALL algo")
|
|
103
|
+
connection.execute("LOAD algo")
|
|
104
|
+
connection.execute(
|
|
105
|
+
# analytics measure CURRENT connectivity: the snapshot
|
|
106
|
+
# deliberately retains invalidated and expired edges for
|
|
107
|
+
# transaction-time as-of (D69), but a withdrawn fact must not
|
|
108
|
+
# inflate centrality or fuse two communities (Codex review).
|
|
109
|
+
# The engine's filtered projection applies this per edge.
|
|
110
|
+
f"CALL PROJECT_GRAPH('{_PROJECTION}', ['Entity'],"
|
|
111
|
+
" {'RELATES': 'r.invalidated_at IS NULL AND (r.valid_until IS NULL"
|
|
112
|
+
" OR r.valid_until > current_timestamp())'})"
|
|
113
|
+
)
|
|
114
|
+
pagerank = _algorithm_scores(connection, algorithm="PAGE_RANK")
|
|
115
|
+
k_core = _algorithm_scores(connection, algorithm="K_CORE_DECOMPOSITION")
|
|
116
|
+
components = _algorithm_scores(
|
|
117
|
+
connection, algorithm="WEAKLY_CONNECTED_COMPONENTS"
|
|
118
|
+
)
|
|
119
|
+
louvain = _algorithm_scores(connection, algorithm="LOUVAIN")
|
|
120
|
+
degrees = _degrees(connection)
|
|
121
|
+
names = _names(connection)
|
|
122
|
+
|
|
123
|
+
members: dict[object, list[UUID]] = {}
|
|
124
|
+
for entity_id, group in louvain.items():
|
|
125
|
+
members.setdefault(group, []).append(entity_id)
|
|
126
|
+
communities: list[dict[str, object]] = []
|
|
127
|
+
community_ids: dict[object, UUID] = {}
|
|
128
|
+
labels = self._labels(members=members, pagerank=pagerank, names=names)
|
|
129
|
+
for group, group_members in sorted(
|
|
130
|
+
members.items(), key=lambda item: str(item[0])
|
|
131
|
+
):
|
|
132
|
+
# the id derives from the MEMBER SET, never the engine's group
|
|
133
|
+
# label: Louvain's numbering is order-dependent, so a re-run
|
|
134
|
+
# could otherwise reuse an id for a different community
|
|
135
|
+
# (Codex review). Same members ⇒ same id, always.
|
|
136
|
+
fingerprint = ",".join(sorted(str(member) for member in group_members))
|
|
137
|
+
community_id = uuid5(
|
|
138
|
+
NAMESPACE_URL, f"rememberstack:community:{snapshot_id}:{fingerprint}"
|
|
139
|
+
)
|
|
140
|
+
community_ids[group] = community_id
|
|
141
|
+
communities.append(
|
|
142
|
+
{
|
|
143
|
+
"community_id": community_id,
|
|
144
|
+
"label": labels.get(group),
|
|
145
|
+
"size": len(group_members),
|
|
146
|
+
"algorithm": "louvain",
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
metrics: tuple[dict[str, object], ...] = tuple(
|
|
150
|
+
{
|
|
151
|
+
"entity_id": entity_id,
|
|
152
|
+
"community_id": community_ids.get(louvain.get(entity_id)),
|
|
153
|
+
"pagerank": float(cast("float", pagerank.get(entity_id, 0.0))),
|
|
154
|
+
"degree": int(cast("int", degrees.get(entity_id, 0))),
|
|
155
|
+
"k_core": int(cast("int", k_core.get(entity_id, 0))),
|
|
156
|
+
"component_id": uuid5(
|
|
157
|
+
NAMESPACE_URL,
|
|
158
|
+
f"rememberstack:component:{snapshot_id}:{components.get(entity_id)}",
|
|
159
|
+
),
|
|
160
|
+
}
|
|
161
|
+
for entity_id in names
|
|
162
|
+
)
|
|
163
|
+
return tuple(communities), metrics
|
|
164
|
+
|
|
165
|
+
def persist(
|
|
166
|
+
self,
|
|
167
|
+
*,
|
|
168
|
+
deployment_id: UUID,
|
|
169
|
+
snapshot_id: UUID,
|
|
170
|
+
communities: tuple[dict[str, object], ...],
|
|
171
|
+
metrics: tuple[dict[str, object], ...],
|
|
172
|
+
) -> dict[str, int]:
|
|
173
|
+
"""Write a PUBLISHED snapshot's analytics back to Postgres (D6)."""
|
|
174
|
+
self._catalog.record_graph_analytics(
|
|
175
|
+
deployment_id=deployment_id,
|
|
176
|
+
snapshot_id=snapshot_id,
|
|
177
|
+
communities=communities,
|
|
178
|
+
metrics=metrics,
|
|
179
|
+
detector_version=COMMUNITY_DETECTOR_VERSION,
|
|
180
|
+
label_model=(
|
|
181
|
+
self._settings.label_model if self._model_provider is not None else None
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
return {"communities": len(communities), "entities": len(metrics)}
|
|
185
|
+
|
|
186
|
+
def _labels(
|
|
187
|
+
self,
|
|
188
|
+
*,
|
|
189
|
+
members: dict[object, list[UUID]],
|
|
190
|
+
pagerank: dict[UUID, object],
|
|
191
|
+
names: dict[UUID, str],
|
|
192
|
+
) -> dict[object, str]:
|
|
193
|
+
"""Short navigation labels for every eligible community — ONE call.
|
|
194
|
+
|
|
195
|
+
Batched by contract (p2 §7): community count must not multiply
|
|
196
|
+
rebuild latency, and thousands of small clusters must not become
|
|
197
|
+
thousands of sequential calls (Codex review). Skipped entirely
|
|
198
|
+
without a model seat; a failed call yields no labels rather than a
|
|
199
|
+
failed rebuild — labels are navigation aids, nothing load-bearing
|
|
200
|
+
reads them.
|
|
201
|
+
"""
|
|
202
|
+
if self._model_provider is None:
|
|
203
|
+
return {}
|
|
204
|
+
eligible = {
|
|
205
|
+
group: group_members
|
|
206
|
+
for group, group_members in members.items()
|
|
207
|
+
if len(group_members) >= self._settings.min_community_size_to_label
|
|
208
|
+
}
|
|
209
|
+
if not eligible:
|
|
210
|
+
return {}
|
|
211
|
+
ordered = sorted(eligible, key=str)
|
|
212
|
+
blocks: list[str] = []
|
|
213
|
+
for index, group in enumerate(ordered):
|
|
214
|
+
central = sorted(
|
|
215
|
+
eligible[group],
|
|
216
|
+
key=lambda entity: float(cast("float", pagerank.get(entity, 0.0))),
|
|
217
|
+
reverse=True,
|
|
218
|
+
)[:_LABEL_MEMBERS]
|
|
219
|
+
listing = ", ".join(names.get(entity, "") for entity in central)
|
|
220
|
+
blocks.append(f"{index}. {listing}")
|
|
221
|
+
try:
|
|
222
|
+
response = self._model_provider.generate(
|
|
223
|
+
request=ModelRequest(
|
|
224
|
+
model=self._settings.label_model,
|
|
225
|
+
prompt=_LABEL_PROMPT.format(clusters="\n".join(blocks)),
|
|
226
|
+
),
|
|
227
|
+
response_type=CommunityLabels,
|
|
228
|
+
)
|
|
229
|
+
except Exception: # noqa: BLE001 — a label never fails the analytics
|
|
230
|
+
return {}
|
|
231
|
+
return {
|
|
232
|
+
ordered[item.index]: item.label
|
|
233
|
+
for item in response.output.labels
|
|
234
|
+
if 0 <= item.index < len(ordered) and item.label
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _algorithm_scores(
|
|
239
|
+
connection: ladybug.Connection, *, algorithm: str
|
|
240
|
+
) -> dict[UUID, object]:
|
|
241
|
+
"""One algorithm's per-entity output, keyed by entity id."""
|
|
242
|
+
result = connection.execute(f"CALL {algorithm}('{_PROJECTION}') RETURN *")
|
|
243
|
+
assert isinstance(result, ladybug.QueryResult)
|
|
244
|
+
scores: dict[UUID, object] = {}
|
|
245
|
+
while result.has_next():
|
|
246
|
+
row = cast("list[object]", result.get_next())
|
|
247
|
+
node = cast("dict[str, object]", row[0])
|
|
248
|
+
scores[cast("UUID", node["id"])] = row[1]
|
|
249
|
+
return scores
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _degrees(connection: ladybug.Connection) -> dict[UUID, int]:
|
|
253
|
+
"""Relation degree per entity (undirected: the blast-radius input)."""
|
|
254
|
+
result = connection.execute(
|
|
255
|
+
"MATCH (e:Entity) OPTIONAL MATCH (e)-[r:RELATES]-()"
|
|
256
|
+
" WHERE r.invalidated_at IS NULL"
|
|
257
|
+
" AND (r.valid_until IS NULL OR r.valid_until > current_timestamp())"
|
|
258
|
+
" RETURN e.id, count(r)"
|
|
259
|
+
)
|
|
260
|
+
assert isinstance(result, ladybug.QueryResult)
|
|
261
|
+
degrees: dict[UUID, int] = {}
|
|
262
|
+
while result.has_next():
|
|
263
|
+
row = cast("list[object]", result.get_next())
|
|
264
|
+
degrees[cast("UUID", row[0])] = cast("int", row[1])
|
|
265
|
+
return degrees
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _names(connection: ladybug.Connection) -> dict[UUID, str]:
|
|
269
|
+
"""Every emitted entity's canonical name (the labeling input)."""
|
|
270
|
+
result = connection.execute("MATCH (e:Entity) RETURN e.id, e.name")
|
|
271
|
+
assert isinstance(result, ladybug.QueryResult)
|
|
272
|
+
names: dict[UUID, str] = {}
|
|
273
|
+
while result.has_next():
|
|
274
|
+
row = cast("list[object]", result.get_next())
|
|
275
|
+
names[cast("UUID", row[0])] = cast("str", row[1])
|
|
276
|
+
return names
|