secondbrain-py 0.2.1__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.
- brain/__init__.py +0 -0
- brain/__main__.py +18 -0
- brain/_capture_command.py +445 -0
- brain/_compose.py +52 -0
- brain/activity.py +206 -0
- brain/ask.py +631 -0
- brain/audio.py +591 -0
- brain/backfill/__init__.py +12 -0
- brain/backfill/search_extras.py +141 -0
- brain/backfill/source_rows.py +101 -0
- brain/bin/__init__.py +1 -0
- brain/bin/_launcher.py +107 -0
- brain/bin/down.py +8 -0
- brain/bin/launchd.py +268 -0
- brain/bin/monitor.py +570 -0
- brain/bin/rebuild.py +8 -0
- brain/bin/status.py +8 -0
- brain/bin/up.py +8 -0
- brain/brief.py +272 -0
- brain/capture.py +49 -0
- brain/chat.py +293 -0
- brain/cli.py +9760 -0
- brain/cli_claude.py +81 -0
- brain/cli_connect.py +285 -0
- brain/cli_demo.py +266 -0
- brain/config.py +1949 -0
- brain/connect.py +925 -0
- brain/db.py +540 -0
- brain/demo/__init__.py +452 -0
- brain/demo/corpus/manifest.json +403 -0
- brain/demo/embedder.py +74 -0
- brain/durations.py +84 -0
- brain/edit_session.py +156 -0
- brain/editor.py +67 -0
- brain/elicit/__init__.py +16 -0
- brain/elicit/detectors.py +250 -0
- brain/elicit/drafter.py +70 -0
- brain/elicit/queue.py +220 -0
- brain/elicit/schema.py +48 -0
- brain/elicit/session.py +445 -0
- brain/embedding_targets.py +54 -0
- brain/embeddings.py +424 -0
- brain/enrichment.py +808 -0
- brain/errors.py +357 -0
- brain/eval/__init__.py +129 -0
- brain/eval/answer_eval.py +281 -0
- brain/eval/baseline.py +265 -0
- brain/eval/concept_extraction.py +378 -0
- brain/eval/corpus.py +152 -0
- brain/eval/errors.py +19 -0
- brain/eval/graph_baseline.py +226 -0
- brain/eval/graph_retrieval.py +202 -0
- brain/eval/graph_runner.py +319 -0
- brain/eval/metrics.py +101 -0
- brain/eval/runner.py +223 -0
- brain/format.py +783 -0
- brain/gaps.py +390 -0
- brain/graph_rag/__init__.py +94 -0
- brain/graph_rag/_retrieval_common.py +113 -0
- brain/graph_rag/aggregates.py +303 -0
- brain/graph_rag/aliases/__init__.py +583 -0
- brain/graph_rag/backends/__init__.py +10 -0
- brain/graph_rag/backends/_age_helpers.py +473 -0
- brain/graph_rag/backends/age.py +782 -0
- brain/graph_rag/backends/base.py +272 -0
- brain/graph_rag/build.py +344 -0
- brain/graph_rag/communities.py +644 -0
- brain/graph_rag/communities_summary.py +437 -0
- brain/graph_rag/concepts.py +202 -0
- brain/graph_rag/cooccur.py +193 -0
- brain/graph_rag/cross_type.py +312 -0
- brain/graph_rag/extract.py +885 -0
- brain/graph_rag/fuse.py +371 -0
- brain/graph_rag/global_.py +412 -0
- brain/graph_rag/grouping.py +372 -0
- brain/graph_rag/person_resolver.py +167 -0
- brain/graph_rag/reconcile.py +792 -0
- brain/graph_rag/relational.py +353 -0
- brain/graph_rag/retrieve.py +526 -0
- brain/graph_rag/router.py +288 -0
- brain/graph_rag/schema.py +320 -0
- brain/graph_rag/sync.py +237 -0
- brain/graph_rag/tenancy.py +43 -0
- brain/graph_rag/themes.py +501 -0
- brain/graph_rag/weighting.py +202 -0
- brain/ingest/__init__.py +1926 -0
- brain/ingest/chunker.py +249 -0
- brain/ingest/docx.py +40 -0
- brain/ingest/gmail.py +621 -0
- brain/ingest/markdown.py +37 -0
- brain/ingest/pdf.py +61 -0
- brain/ingest/stdin.py +22 -0
- brain/ingest/sub_tokens.py +91 -0
- brain/ingest/text.py +16 -0
- brain/interactions.py +205 -0
- brain/maintenance.py +355 -0
- brain/mcp_server.py +3405 -0
- brain/migrations/001_init.sql +43 -0
- brain/migrations/002_qwen3_embedding.sql +17 -0
- brain/migrations/003_vault_model.sql +41 -0
- brain/migrations/004_relax_content_hash_uniqueness.sql +18 -0
- brain/migrations/005_derived_links.sql +67 -0
- brain/migrations/006_dedup_file_by_source_path.sql +25 -0
- brain/migrations/007_email_thread_and_draft.sql +15 -0
- brain/migrations/008_gmail_thread_unique.sql +11 -0
- brain/migrations/009_chunks_weighted_tsv.sql +28 -0
- brain/migrations/010_interactions.sql +30 -0
- brain/migrations/011_documents_summary.sql +23 -0
- brain/migrations/012_graphrag.sql +171 -0
- brain/migrations/013_graphrag_communities.sql +125 -0
- brain/migrations/014_graphrag_community_summary_hash.sql +33 -0
- brain/migrations/015_interactions_graph_targets.sql +89 -0
- brain/migrations/016_index_hygiene.sql +61 -0
- brain/migrations/017_elicit.sql +30 -0
- brain/migrations/018_review_gap_signal_kinds.sql +40 -0
- brain/migrations/019_search_queries.sql +35 -0
- brain/migrations/020_link_suggestions.sql +40 -0
- brain/migrations/021_timeline_doc_date.sql +34 -0
- brain/migrations/022_link_suggestions_undirected.sql +84 -0
- brain/migrations/023_search_queries_fts_count.sql +28 -0
- brain/quartz_overrides/__init__.py +8 -0
- brain/quartz_overrides/quartz/bootstrap-cli.mjs +65 -0
- brain/quartz_overrides/quartz/build.ts +568 -0
- brain/quartz_overrides/quartz/cli/args.js +152 -0
- brain/quartz_overrides/quartz/cli/build_partial_handler.js +544 -0
- brain/quartz_overrides/quartz/cli/handlers.js +636 -0
- brain/quartz_overrides/quartz/components/CommandPalette.tsx +172 -0
- brain/quartz_overrides/quartz/components/Explorer.tsx +198 -0
- brain/quartz_overrides/quartz/components/Footer.tsx +27 -0
- brain/quartz_overrides/quartz/components/Graph.tsx +468 -0
- brain/quartz_overrides/quartz/components/PageTitle.tsx +72 -0
- brain/quartz_overrides/quartz/components/RelatedDocs.tsx +38 -0
- brain/quartz_overrides/quartz/components/Search.tsx +161 -0
- brain/quartz_overrides/quartz/components/SummaryLede.tsx +72 -0
- brain/quartz_overrides/quartz/components/index.ts +92 -0
- brain/quartz_overrides/quartz/components/pages/TagContent.tsx +272 -0
- brain/quartz_overrides/quartz/components/scripts/commandPalette.inline.ts +665 -0
- brain/quartz_overrides/quartz/components/scripts/explorer.inline.ts +768 -0
- brain/quartz_overrides/quartz/components/scripts/graph.inline.ts +2302 -0
- brain/quartz_overrides/quartz/components/scripts/relatedDocs.inline.ts +163 -0
- brain/quartz_overrides/quartz/components/scripts/search.inline.ts +1011 -0
- brain/quartz_overrides/quartz/plugins/emitters/contentIndex.ts +546 -0
- brain/quartz_overrides/quartz/plugins/transformers/codeCopy.ts +94 -0
- brain/quartz_overrides/quartz/plugins/transformers/derivedFenceMark.ts +302 -0
- brain/quartz_overrides/quartz/plugins/transformers/emailThread.ts +148 -0
- brain/quartz_overrides/quartz/plugins/transformers/emptyDoorFilter.ts +213 -0
- brain/quartz_overrides/quartz/plugins/transformers/index.ts +114 -0
- brain/quartz_overrides/quartz/plugins/transformers/linkKindMark.ts +205 -0
- brain/quartz_overrides/quartz/plugins/transformers/linkSourceTag.ts +104 -0
- brain/quartz_overrides/quartz/plugins/transformers/relativeDate.ts +100 -0
- brain/quartz_overrides/quartz/plugins/transformers/reloadSignal.ts +131 -0
- brain/quartz_overrides/quartz/processors/parse.ts +371 -0
- brain/quartz_overrides/quartz/processors/parser_cache.ts +78 -0
- brain/quartz_overrides/quartz/static/brain-logo-dark.png +0 -0
- brain/quartz_overrides/quartz/static/brain-logo-light.png +0 -0
- brain/quartz_overrides/quartz/static/codeCopy.js +196 -0
- brain/quartz_overrides/quartz/static/emailThread.js +334 -0
- brain/quartz_overrides/quartz/static/favicon.ico +0 -0
- brain/quartz_overrides/quartz/static/icon.png +0 -0
- brain/quartz_overrides/quartz/static/linkSourceTag.js +104 -0
- brain/quartz_overrides/quartz/static/relativeDate.js +142 -0
- brain/quartz_overrides/quartz/static/reload.js +168 -0
- brain/quartz_overrides/quartz/styles/brain/_article.scss +252 -0
- brain/quartz_overrides/quartz/styles/brain/_atmosphere.scss +113 -0
- brain/quartz_overrides/quartz/styles/brain/_callouts.scss +180 -0
- brain/quartz_overrides/quartz/styles/brain/_cmdk.scss +7 -0
- brain/quartz_overrides/quartz/styles/brain/_code.scss +208 -0
- brain/quartz_overrides/quartz/styles/brain/_command_palette.scss +369 -0
- brain/quartz_overrides/quartz/styles/brain/_email_thread.scss +228 -0
- brain/quartz_overrides/quartz/styles/brain/_explorer.scss +142 -0
- brain/quartz_overrides/quartz/styles/brain/_home.scss +182 -0
- brain/quartz_overrides/quartz/styles/brain/_links.scss +322 -0
- brain/quartz_overrides/quartz/styles/brain/_marginalia.scss +117 -0
- brain/quartz_overrides/quartz/styles/brain/_motion.scss +175 -0
- brain/quartz_overrides/quartz/styles/brain/_people_hub.scss +100 -0
- brain/quartz_overrides/quartz/styles/brain/_related_docs.scss +137 -0
- brain/quartz_overrides/quartz/styles/brain/_search.scss +252 -0
- brain/quartz_overrides/quartz/styles/brain/_sidebar.scss +468 -0
- brain/quartz_overrides/quartz/styles/brain/_summary_lede.scss +56 -0
- brain/quartz_overrides/quartz/styles/brain/_surface.scss +43 -0
- brain/quartz_overrides/quartz/styles/brain/_tag_content.scss +118 -0
- brain/quartz_overrides/quartz/styles/brain/_tokens.scss +197 -0
- brain/quartz_overrides/quartz/styles/brain/_typography.scss +92 -0
- brain/quartz_overrides/quartz/styles/custom.scss +89 -0
- brain/quartz_overrides/quartz/styles/graph.scss +505 -0
- brain/quartz_overrides/quartz/util/ctx.ts +92 -0
- brain/quartz_overrides/quartz/util/fastpath_manifest.ts +608 -0
- brain/quartz_overrides/quartz/util/path.ts +358 -0
- brain/quartz_overrides/quartz/util/sourceIcons.ts +55 -0
- brain/quartz_overrides/quartz.config.ts +270 -0
- brain/quartz_overrides/quartz.layout.ts +314 -0
- brain/queries.py +1188 -0
- brain/rank_fusion.py +8 -0
- brain/resurface.py +210 -0
- brain/review/__init__.py +26 -0
- brain/review/emit.py +27 -0
- brain/review/queries.py +436 -0
- brain/review/render.py +196 -0
- brain/review/scans.py +355 -0
- brain/review/weekly.py +413 -0
- brain/search.py +704 -0
- brain/set_similarity.py +15 -0
- brain/setup.py +1205 -0
- brain/tags.py +56 -0
- brain/templates/Caddyfile.j2 +9 -0
- brain/templates/__init__.py +1 -0
- brain/templates/bin/__init__.py +1 -0
- brain/templates/bin/_brain-brief-fg.sh +25 -0
- brain/templates/bin/_brain-build-fg.sh +53 -0
- brain/templates/bin/_brain-watcher-fg.sh +65 -0
- brain/templates/bin/brain-down.sh +89 -0
- brain/templates/bin/brain-status.sh +83 -0
- brain/templates/bin/brain-up.sh +221 -0
- brain/templates/docker/age/Dockerfile +79 -0
- brain/templates/docker-compose.stock.yml.j2 +26 -0
- brain/templates/docker-compose.yml.j2 +34 -0
- brain/templates/env.example +190 -0
- brain/templates/launchd/__init__.py +1 -0
- brain/templates/launchd/com.brain.brief.plist.j2 +45 -0
- brain/templates/launchd/com.brain.build.plist.j2 +46 -0
- brain/templates/launchd/com.brain.watcher.plist.j2 +46 -0
- brain/templates/skill/SKILL.md +63 -0
- brain/templates/skill/__init__.py +1 -0
- brain/timeline.py +834 -0
- brain/todo.py +124 -0
- brain/uninstall.py +185 -0
- brain/vault/__init__.py +115 -0
- brain/vault/_atomic.py +25 -0
- brain/vault/daily_index.py +228 -0
- brain/vault/derived_links/__init__.py +50 -0
- brain/vault/derived_links/directory.py +683 -0
- brain/vault/derived_links/fence.py +408 -0
- brain/vault/derived_links/gws.py +64 -0
- brain/vault/derived_links/participants.py +143 -0
- brain/vault/derived_links/pass_runner.py +362 -0
- brain/vault/derived_links/rules.py +137 -0
- brain/vault/export.py +683 -0
- brain/vault/frontmatter.py +165 -0
- brain/vault/graph.py +620 -0
- brain/vault/graph_format.py +388 -0
- brain/vault/link_rewrite.py +235 -0
- brain/vault/links.py +260 -0
- brain/vault/note_builder.py +211 -0
- brain/vault/paths.py +55 -0
- brain/vault/quartz_overlay.py +236 -0
- brain/vault/rename.py +591 -0
- brain/vault/resolver.py +304 -0
- brain/vault/slug.py +127 -0
- brain/vault/sync.py +1513 -0
- brain/vault/sync_summaries.py +264 -0
- brain/vault/templates.py +145 -0
- brain/vault/watch.py +1052 -0
- brain/wiki/__init__.py +6 -0
- brain/wiki/_github_slugger.py +76 -0
- brain/wiki/_person_name.py +314 -0
- brain/wiki/build_homepage.py +541 -0
- brain/wiki/build_partial.py +273 -0
- brain/wiki/build_people.py +934 -0
- brain/wiki/build_related.py +758 -0
- brain/wiki/build_swap.py +585 -0
- brain/wiki/build_watcher.py +975 -0
- brain/wiki/edit_classifier.py +215 -0
- brain/wiki/errors.py +10 -0
- brain/wiki/fastpath_manifest.py +475 -0
- brain/wiki/fastpath_state.py +174 -0
- brain/wiki/install.py +296 -0
- brain/wiki/slug.py +111 -0
- secondbrain_py-0.2.1.dist-info/METADATA +195 -0
- secondbrain_py-0.2.1.dist-info/RECORD +273 -0
- secondbrain_py-0.2.1.dist-info/WHEEL +5 -0
- secondbrain_py-0.2.1.dist-info/entry_points.txt +11 -0
- secondbrain_py-0.2.1.dist-info/licenses/LICENSE +21 -0
- secondbrain_py-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Derive-time edge weighting + generic-entity suppression (wave G1, GraphRAG).
|
|
2
|
+
|
|
3
|
+
Pure logic, no DB. Two derive-time concerns the spec assigns to this module
|
|
4
|
+
(spec §8 ``weighting.py — normalized lift + generic suppression``):
|
|
5
|
+
|
|
6
|
+
1. **Normalized lift** — the single normative edge-weight metric, in ``(0, 1]``
|
|
7
|
+
(spec §4 D4: PMI rejected because it can go negative). The design names the
|
|
8
|
+
metric but not its formula; this module makes the canonical, spec-faithful
|
|
9
|
+
choice and documents the derivation:
|
|
10
|
+
|
|
11
|
+
lift(A, B) = P(A, B) / (P(A) · P(B)) # ∈ [0, ∞)
|
|
12
|
+
|
|
13
|
+
Given fixed marginals, the co-document count cannot exceed the rarer
|
|
14
|
+
entity's document count, so the maximum attainable lift is
|
|
15
|
+
``1 / max(P(A), P(B))``. Dividing lift by that maximum normalizes it into
|
|
16
|
+
``(0, 1]`` and — because the corpus size ``N`` cancels — collapses to a
|
|
17
|
+
clean document-count ratio:
|
|
18
|
+
|
|
19
|
+
normalized_lift(A, B) = co_df(A, B) / min(df(A), df(B)) # ∈ (0, 1]
|
|
20
|
+
|
|
21
|
+
where ``df(X)`` is X's document frequency and ``co_df(A, B)`` the count of
|
|
22
|
+
documents in which A and B co-occur. The value is ``1.0`` exactly when one
|
|
23
|
+
entity always appears with the other (its document set is a subset of the
|
|
24
|
+
other's), and strictly positive whenever they ever co-occur. This satisfies
|
|
25
|
+
the migration-012 ``CHECK (weight > 0 AND weight <= 1)`` by construction.
|
|
26
|
+
|
|
27
|
+
2. **Generic-entity suppression** — entities that appear in almost every
|
|
28
|
+
document carry no thematic signal, so edges touching them are *dropped* at
|
|
29
|
+
derive time (spec §6b "exclude … generic"; §4 D4 "generic-suppressed at
|
|
30
|
+
derive time"). An entity is **generic** when its document frequency exceeds
|
|
31
|
+
the absolute cap ``round(GENERIC_DF × tenant_corpus_N)`` (spec §6b step 2;
|
|
32
|
+
``BRAIN_GRAPH_GENERIC_DF`` default ``0.30``, spec §10). The spec frames this
|
|
33
|
+
as exclusion (drop), not down-weighting, so :func:`edge_weight` returns
|
|
34
|
+
``None`` for a suppressed edge — the row is simply not materialized.
|
|
35
|
+
|
|
36
|
+
**Versioning.** :data:`WEIGHTING_VERSION` is the derive-time algorithm version
|
|
37
|
+
that feeds ``graph_index_state.suppress_ver`` (migration 012; spec §7 step 1).
|
|
38
|
+
A change to the weighting/suppression *semantics* bumps the constant; a change
|
|
39
|
+
to the suppression *config* (the ``GENERIC_DF`` ratio) changes the suppression
|
|
40
|
+
outcome too, so :func:`suppress_ver` folds both into the watermark string,
|
|
41
|
+
forcing a re-derive when either moves.
|
|
42
|
+
"""
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
from ..errors import WeightingError
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"DEFAULT_GENERIC_DF",
|
|
49
|
+
"WEIGHTING_VERSION",
|
|
50
|
+
"edge_weight",
|
|
51
|
+
"generic_df_cap",
|
|
52
|
+
"is_generic_entity",
|
|
53
|
+
"is_suppressed_edge",
|
|
54
|
+
"normalized_lift",
|
|
55
|
+
"suppress_ver",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# Derive-time weighting/suppression algorithm version. Feeds
|
|
59
|
+
# ``graph_index_state.suppress_ver`` (spec §7). Bump when the lift formula or
|
|
60
|
+
# the suppression rule changes so reconcile re-derives affected documents.
|
|
61
|
+
WEIGHTING_VERSION = "nlift-v1"
|
|
62
|
+
|
|
63
|
+
# Default generic-entity document-frequency ratio: an entity appearing in more
|
|
64
|
+
# than this fraction of the tenant corpus is treated as generic and its edges
|
|
65
|
+
# are suppressed. Mirrors ``BRAIN_GRAPH_GENERIC_DF`` (spec §10, default 0.30).
|
|
66
|
+
DEFAULT_GENERIC_DF = 0.30
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def normalized_lift(co_doc_count: int, src_doc_count: int, dst_doc_count: int) -> float:
|
|
70
|
+
"""Normalized lift in ``(0, 1]`` = ``co_doc_count / min(src_df, dst_df)``.
|
|
71
|
+
|
|
72
|
+
See the module docstring for the derivation. The result is provably in
|
|
73
|
+
``(0, 1]`` once the guards below pass, so the migration-012 weight ``CHECK``
|
|
74
|
+
cannot be violated.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
co_doc_count: Documents in which the two entities co-occur (``>= 1``).
|
|
78
|
+
src_doc_count: Document frequency of one endpoint (``>= 1``).
|
|
79
|
+
dst_doc_count: Document frequency of the other endpoint (``>= 1``).
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The normalized-lift edge weight.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
WeightingError: if ``co_doc_count < 1`` (no edge), either marginal is
|
|
86
|
+
``< 1``, or ``co_doc_count`` exceeds ``min(src_df, dst_df)`` (a pair
|
|
87
|
+
cannot co-occur in more documents than its rarer entity appears in).
|
|
88
|
+
"""
|
|
89
|
+
if co_doc_count < 1:
|
|
90
|
+
raise WeightingError(
|
|
91
|
+
f"co_doc_count must be >= 1 for an edge to exist (got {co_doc_count})"
|
|
92
|
+
)
|
|
93
|
+
if src_doc_count < 1 or dst_doc_count < 1:
|
|
94
|
+
raise WeightingError(
|
|
95
|
+
"endpoint document frequencies must be >= 1 "
|
|
96
|
+
f"(got src={src_doc_count}, dst={dst_doc_count})"
|
|
97
|
+
)
|
|
98
|
+
min_df = min(src_doc_count, dst_doc_count)
|
|
99
|
+
if co_doc_count > min_df:
|
|
100
|
+
raise WeightingError(
|
|
101
|
+
f"co_doc_count ({co_doc_count}) cannot exceed the rarer endpoint's "
|
|
102
|
+
f"document frequency ({min_df})"
|
|
103
|
+
)
|
|
104
|
+
return co_doc_count / min_df
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def generic_df_cap(corpus_doc_count: int, generic_df_ratio: float = DEFAULT_GENERIC_DF) -> int:
|
|
108
|
+
"""Absolute generic-entity document-frequency cap (spec §6b step 2).
|
|
109
|
+
|
|
110
|
+
``round(corpus_doc_count × generic_df_ratio)``. An entity whose document
|
|
111
|
+
frequency exceeds this cap is generic (see :func:`is_generic_entity`).
|
|
112
|
+
``round`` uses Python's banker's rounding (round-half-to-even), matching the
|
|
113
|
+
spec's plain "round".
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
corpus_doc_count: Number of documents in the tenant corpus (``>= 0``).
|
|
117
|
+
generic_df_ratio: Generic fraction in ``(0, 1]``
|
|
118
|
+
(default :data:`DEFAULT_GENERIC_DF`).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
The absolute document-frequency cap.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
WeightingError: if ``corpus_doc_count < 0`` or ``generic_df_ratio`` is
|
|
125
|
+
outside ``(0, 1]``.
|
|
126
|
+
"""
|
|
127
|
+
if corpus_doc_count < 0:
|
|
128
|
+
raise WeightingError(
|
|
129
|
+
f"corpus_doc_count must be >= 0 (got {corpus_doc_count})"
|
|
130
|
+
)
|
|
131
|
+
if not 0.0 < generic_df_ratio <= 1.0:
|
|
132
|
+
raise WeightingError(
|
|
133
|
+
f"generic_df_ratio must be in (0, 1] (got {generic_df_ratio})"
|
|
134
|
+
)
|
|
135
|
+
return round(corpus_doc_count * generic_df_ratio)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def is_generic_entity(entity_doc_count: int, cap: int) -> bool:
|
|
139
|
+
"""True when an entity's document frequency exceeds the generic ``cap``.
|
|
140
|
+
|
|
141
|
+
Strictly greater-than: an entity sitting exactly at the cap is kept (spec
|
|
142
|
+
§6b — only entities that co-occur with *almost everything* are excluded).
|
|
143
|
+
"""
|
|
144
|
+
return entity_doc_count > cap
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def is_suppressed_edge(src_doc_count: int, dst_doc_count: int, cap: int) -> bool:
|
|
148
|
+
"""True when *either* endpoint is generic, so the edge is suppressed."""
|
|
149
|
+
return is_generic_entity(src_doc_count, cap) or is_generic_entity(dst_doc_count, cap)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def edge_weight(
|
|
153
|
+
co_doc_count: int,
|
|
154
|
+
src_doc_count: int,
|
|
155
|
+
dst_doc_count: int,
|
|
156
|
+
*,
|
|
157
|
+
cap: int,
|
|
158
|
+
) -> float | None:
|
|
159
|
+
"""Derive-time edge weight, or ``None`` when the edge is suppressed.
|
|
160
|
+
|
|
161
|
+
Returns ``None`` when either endpoint is generic (document frequency above
|
|
162
|
+
``cap``); otherwise the :func:`normalized_lift` weight. The ``None`` signal
|
|
163
|
+
means "do not materialize this edge" (spec §6b excludes generic entities).
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
co_doc_count: Documents in which the two entities co-occur.
|
|
167
|
+
src_doc_count: Document frequency of one endpoint.
|
|
168
|
+
dst_doc_count: Document frequency of the other endpoint.
|
|
169
|
+
cap: Absolute generic cap (see :func:`generic_df_cap`).
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
WeightingError: propagated from :func:`normalized_lift` for a
|
|
173
|
+
non-suppressed edge with impossible counts.
|
|
174
|
+
"""
|
|
175
|
+
if is_suppressed_edge(src_doc_count, dst_doc_count, cap):
|
|
176
|
+
return None
|
|
177
|
+
return normalized_lift(co_doc_count, src_doc_count, dst_doc_count)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def suppress_ver(generic_df_ratio: float = DEFAULT_GENERIC_DF) -> str:
|
|
181
|
+
"""Composite derive-time version for ``graph_index_state.suppress_ver``.
|
|
182
|
+
|
|
183
|
+
Folds the algorithm version (:data:`WEIGHTING_VERSION`) together with the
|
|
184
|
+
config that changes the suppression outcome (the ``GENERIC_DF`` ratio), so
|
|
185
|
+
a ratio change forces reconcile to re-derive (spec §7 watermark).
|
|
186
|
+
|
|
187
|
+
The ratio is rendered with ``repr`` (``!r``), i.e. Python's shortest
|
|
188
|
+
round-trippable float string, for two reasons:
|
|
189
|
+
|
|
190
|
+
* **Collision-free** — ``repr`` is injective over floats (two *distinct*
|
|
191
|
+
floats always render to distinct strings), so two different
|
|
192
|
+
``GENERIC_DF`` ratios can never fold to the same ``suppress_ver`` and
|
|
193
|
+
silently skip a re-derive. The old ``:g`` format rounded to 6 significant
|
|
194
|
+
figures, so e.g. ``0.1234561`` and ``0.1234562`` both rendered
|
|
195
|
+
``0.123456`` — a watermark collision that would skip a legitimate
|
|
196
|
+
suppression change.
|
|
197
|
+
* **Stable across spellings** — equal floats render identically
|
|
198
|
+
(``0.30`` and ``0.3`` are the same float ⇒ both ``0.3``), so an
|
|
199
|
+
equivalent re-spelling of the config does not spuriously force a
|
|
200
|
+
re-derive.
|
|
201
|
+
"""
|
|
202
|
+
return f"{WEIGHTING_VERSION}:gdf={generic_df_ratio!r}"
|