altar 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 (161) hide show
  1. altar/__init__.py +10 -0
  2. altar/access.py +30 -0
  3. altar/agent/__init__.py +13 -0
  4. altar/agent/browser.py +88 -0
  5. altar/agent/checkpointer.py +18 -0
  6. altar/agent/knowledge.py +107 -0
  7. altar/agent/prioritization/__init__.py +0 -0
  8. altar/agent/prioritization/graph.py +685 -0
  9. altar/agent/prioritization/state.py +71 -0
  10. altar/agent/reference/__init__.py +58 -0
  11. altar/agent/reference/logic.py +320 -0
  12. altar/agent/reference/seed.py +310 -0
  13. altar/agent/reference/sqlite.py +623 -0
  14. altar/agent/reference/stores.py +463 -0
  15. altar/agent/stores.py +541 -0
  16. altar/agent/streaming.py +436 -0
  17. altar/agent/tools/__init__.py +0 -0
  18. altar/agent/tools/inputs.py +12 -0
  19. altar/agent/tools/literature.py +178 -0
  20. altar/agent/tools/variant_analysis/__init__.py +2 -0
  21. altar/agent/tools/variant_analysis/ask_user.py +81 -0
  22. altar/agent/tools/variant_analysis/browser_control.py +1147 -0
  23. altar/agent/tools/variant_analysis/candidate_genes.py +128 -0
  24. altar/agent/tools/variant_analysis/capture_browser.py +189 -0
  25. altar/agent/tools/variant_analysis/count_variants.py +113 -0
  26. altar/agent/tools/variant_analysis/examine_variant.py +86 -0
  27. altar/agent/tools/variant_analysis/family_callset.py +326 -0
  28. altar/agent/tools/variant_analysis/filter_schema.py +140 -0
  29. altar/agent/tools/variant_analysis/filter_variants.py +235 -0
  30. altar/agent/tools/variant_analysis/gene_expression.py +265 -0
  31. altar/agent/tools/variant_analysis/read_knowledge_base.py +209 -0
  32. altar/agent/tools/variant_analysis/relevant_models.py +214 -0
  33. altar/agent/tools/variant_analysis/save_to_sublist.py +155 -0
  34. altar/agent/tools/variant_analysis/schemas.py +183 -0
  35. altar/agent/tools/variant_analysis/update_knowledge_base.py +385 -0
  36. altar/agent/utils/__init__.py +0 -0
  37. altar/agent/utils/display.py +10 -0
  38. altar/agent/utils/embeddings.py +39 -0
  39. altar/agent/utils/llms.py +116 -0
  40. altar/apis/__init__.py +10 -0
  41. altar/apis/biorxiv/__init__.py +0 -0
  42. altar/apis/biorxiv/api.py +128 -0
  43. altar/apis/cellxgene/__init__.py +0 -0
  44. altar/apis/cellxgene/api.py +120 -0
  45. altar/apis/europepmc/__init__.py +0 -0
  46. altar/apis/europepmc/api.py +153 -0
  47. altar/apis/gtex/__init__.py +0 -0
  48. altar/apis/gtex/api.py +115 -0
  49. altar/apis/hgnc/__init__.py +0 -0
  50. altar/apis/hgnc/api.py +116 -0
  51. altar/apis/rest_api.py +71 -0
  52. altar/apis/utils.py +94 -0
  53. altar/execution.py +120 -0
  54. altar/models.py +195 -0
  55. altar/plugins/__init__.py +46 -0
  56. altar/plugins/auth.py +97 -0
  57. altar/plugins/capabilities.py +32 -0
  58. altar/plugins/data_lake/README.md +61 -0
  59. altar/plugins/data_lake/__init__.py +166 -0
  60. altar/plugins/data_lake/allelic_records.py +289 -0
  61. altar/plugins/data_lake/annotation_source.py +206 -0
  62. altar/plugins/data_lake/bigquery.py +2495 -0
  63. altar/plugins/data_lake/bigquery_allelic_records.py +248 -0
  64. altar/plugins/data_lake/bigquery_detail.py +430 -0
  65. altar/plugins/data_lake/bigquery_lineages.py +273 -0
  66. altar/plugins/data_lake/bigquery_sql.py +697 -0
  67. altar/plugins/data_lake/bigquery_staged_annotations.py +185 -0
  68. altar/plugins/data_lake/detail_store.py +200 -0
  69. altar/plugins/data_lake/display_rider.py +57 -0
  70. altar/plugins/data_lake/lineage_storage.py +91 -0
  71. altar/plugins/data_lake/materialize.py +179 -0
  72. altar/plugins/data_lake/reuse.py +181 -0
  73. altar/plugins/data_lake/schema.py +149 -0
  74. altar/plugins/data_lake/score_store.py +385 -0
  75. altar/plugins/data_lake/sql_capability.py +89 -0
  76. altar/plugins/data_lake/sqlite.py +493 -0
  77. altar/plugins/data_lake/sqlite_detail.py +273 -0
  78. altar/plugins/data_lake/sqlite_lineages.py +180 -0
  79. altar/plugins/data_lake/types.py +81 -0
  80. altar/plugins/execution/README.md +64 -0
  81. altar/plugins/execution/__init__.py +231 -0
  82. altar/plugins/execution/backend.py +656 -0
  83. altar/plugins/execution/k8s.py +797 -0
  84. altar/plugins/execution/local.py +348 -0
  85. altar/plugins/execution/modal.py +436 -0
  86. altar/plugins/execution/reconcile.py +512 -0
  87. altar/plugins/groups.py +38 -0
  88. altar/plugins/job_queue.py +62 -0
  89. altar/plugins/manifest.py +1086 -0
  90. altar/plugins/model/README.md +51 -0
  91. altar/plugins/model/__init__.py +120 -0
  92. altar/plugins/model/configuration.py +424 -0
  93. altar/plugins/model/engine.py +904 -0
  94. altar/plugins/model/plugin.py +971 -0
  95. altar/plugins/model/preparation.py +581 -0
  96. altar/plugins/model/result_codec.py +390 -0
  97. altar/plugins/model/scoring_paths.py +115 -0
  98. altar/plugins/model/workflow.py +462 -0
  99. altar/plugins/predicate.py +252 -0
  100. altar/plugins/registry.py +433 -0
  101. altar/plugins/storage.py +228 -0
  102. altar/plugins/variant_gene_link.py +476 -0
  103. altar/plugins/variant_gene_link_store.py +812 -0
  104. altar/predicates.py +42 -0
  105. altar/py.typed +0 -0
  106. altar/registry.py +36 -0
  107. altar/results.py +56 -0
  108. altar/scoring.py +57 -0
  109. altar/sources.py +138 -0
  110. altar/testing/__init__.py +84 -0
  111. altar/testing/allelic_records.py +128 -0
  112. altar/testing/annotation_source.py +134 -0
  113. altar/testing/auth.py +48 -0
  114. altar/testing/bigquery.py +211 -0
  115. altar/testing/container_fixtures.py +48 -0
  116. altar/testing/contract_model.py +147 -0
  117. altar/testing/detail_store.py +188 -0
  118. altar/testing/execution.py +324 -0
  119. altar/testing/job_queue.py +97 -0
  120. altar/testing/materialize_fixture.py +202 -0
  121. altar/testing/model_plugin.py +196 -0
  122. altar/testing/plugin_discovery.py +51 -0
  123. altar/testing/score_store.py +609 -0
  124. altar/testing/scoring.py +168 -0
  125. altar/testing/sql_annotation_source.py +106 -0
  126. altar/testing/storage.py +91 -0
  127. altar/testing/variant_gene_link.py +172 -0
  128. altar/testing/variant_gene_link_store.py +142 -0
  129. altar/variants/__init__.py +40 -0
  130. altar/variants/annotation/__init__.py +0 -0
  131. altar/variants/annotation/annotate.py +266 -0
  132. altar/variants/annotation/data.py +83 -0
  133. altar/variants/annotation/family.py +403 -0
  134. altar/variants/annotation/maf.py +42 -0
  135. altar/variants/annotation/regions.py +297 -0
  136. altar/variants/core/__init__.py +0 -0
  137. altar/variants/core/io.py +408 -0
  138. altar/variants/core/logging.py +135 -0
  139. altar/variants/data/gene_df.tsv +78725 -0
  140. altar/variants/export/__init__.py +1 -0
  141. altar/variants/export/augmented_vcf.py +558 -0
  142. altar/variants/preprocessing/__init__.py +0 -0
  143. altar/variants/preprocessing/callset_union.py +338 -0
  144. altar/variants/preprocessing/pipeline.py +241 -0
  145. altar/variants/preprocessing/region_filter.py +257 -0
  146. altar/variants/preprocessing/selected_vcf_ingest.py +118 -0
  147. altar/variants/preprocessing/streaming.py +737 -0
  148. altar/variants/preprocessing/validate.py +201 -0
  149. altar/variants/preprocessing/vcf.py +69 -0
  150. altar/variants/scripts/__init__.py +0 -0
  151. altar/variants/scripts/construct_ccre_dnatree.py +67 -0
  152. altar/variants/scripts/construct_region_annotations.py +267 -0
  153. altar/variants/scripts/construct_variants_df.py +112 -0
  154. altar/variants/scripts/download_ccres.sh +9 -0
  155. altar/variants/scripts/download_ensembl_gff.sh +16 -0
  156. altar/variants/scripts/download_variants.sh +10 -0
  157. altar-0.1.0.dist-info/METADATA +113 -0
  158. altar-0.1.0.dist-info/RECORD +161 -0
  159. altar-0.1.0.dist-info/WHEEL +4 -0
  160. altar-0.1.0.dist-info/entry_points.txt +22 -0
  161. altar-0.1.0.dist-info/licenses/LICENSE +21 -0
altar/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Infrastructure-neutral contracts and reference adapters for genomic analysis.
2
+
3
+ The supported API is intentionally exposed through the documented façade modules such as
4
+ :mod:`altar.models`, :mod:`altar.execution`, and :mod:`altar.sources`. The package root only exposes the
5
+ distribution version; it does not flatten those concept-specific namespaces.
6
+ """
7
+
8
+ __version__: str = "0.1.0"
9
+
10
+ __all__ = ["__version__"]
altar/access.py ADDED
@@ -0,0 +1,30 @@
1
+ """Provisional authentication-provider contract.
2
+
3
+ Authentication extensions should implement :class:`AuthProvider` through this module. Agent credentials
4
+ and service-specific clients are not part of this API.
5
+
6
+ Every name in this module is provisional and listed in ``__provisional__``: it remains importable, but it is
7
+ excluded from the stable-API commitment. Altar ships only the development-only ``DevAuthProvider`` and no
8
+ component that consumes the contract. The module becomes stable when a public production adapter, an
9
+ in-repository consumer, documentation, and conformance coverage of that adapter exist.
10
+ """
11
+
12
+ from altar.plugins.auth import AuthIdentity, AuthProvider, DevAuthProvider, InvalidTokenError, get_auth_registry
13
+
14
+
15
+ __all__ = [
16
+ "AuthIdentity",
17
+ "AuthProvider",
18
+ "DevAuthProvider",
19
+ "InvalidTokenError",
20
+ "get_auth_registry",
21
+ ]
22
+
23
+ # Exported but outside the stable-API commitment; see docs/public-api.md ("Provisional names").
24
+ __provisional__ = (
25
+ "AuthIdentity",
26
+ "AuthProvider",
27
+ "DevAuthProvider",
28
+ "InvalidTokenError",
29
+ "get_auth_registry",
30
+ )
@@ -0,0 +1,13 @@
1
+ """Experimental LangGraph agent and tools for triaging scored variants.
2
+
3
+ This optional namespace is not part of the stable façade. Its contracts and imports may change without a
4
+ compatibility period and require the `[agent]` extra plus provider credentials for real runs.
5
+
6
+ The agent reasons over a scoring job's variants — filtering and ranking them, pulling candidate
7
+ genes, cell-type expression, and literature, and (where a host supplies a knowledge provider)
8
+ disease evidence — and streams its work back to a chat UI. It depends on its host only through small seams:
9
+ data stores (`altar.agent.stores`), a knowledge provider (`altar.agent.knowledge`), an LLM
10
+ API-key provider (`altar.agent.utils.llms`), and a genome-browser relay (`altar.agent.browser`).
11
+ Each seam ships a built-in default, so the agent runs standalone and a host swaps in richer
12
+ implementations by injecting or installing its own.
13
+ """
altar/agent/browser.py ADDED
@@ -0,0 +1,88 @@
1
+ """The genome-browser relay seam the browser-control tools depend on.
2
+
3
+ The agent's genome-browser tools drive a Loom genome browser running in the user's UI over
4
+ a live connection. A host that offers this connects the two ends and exposes a *relay*: a
5
+ lookup, keyed by an opaque per-browser `session_id`, from that session to a connected Loom
6
+ client, plus a readiness signal for it. One `session_id` identifies one browser instance
7
+ (one UI panel), so a host that shows several browsers at once keeps them from colliding.
8
+
9
+ The relay is process-wide infrastructure — a single registry of live connections per
10
+ process — so it is installed once via `set_browser_relay` rather than threaded through
11
+ every tool. Hosts that don't serve a live browser leave the built-in default in place: it
12
+ reports every session as disconnected, so the browser tools stay dormant and return a "no
13
+ browser is connected" message instead of failing.
14
+ """
15
+
16
+ from __future__ import annotations
17
+ import asyncio
18
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
19
+
20
+
21
+ if TYPE_CHECKING:
22
+ from loom_client import LoomClient
23
+
24
+
25
+ @runtime_checkable
26
+ class BrowserRelay(Protocol):
27
+ """Lookup from a browser `session_id` to its live connection and readiness signal.
28
+
29
+ `session_id` is an opaque token identifying one browser instance. The relay is
30
+ `runtime_checkable`, so a host can assert its own implementation conforms with an
31
+ `issubclass` check.
32
+ """
33
+
34
+ def is_browser_connected(self, session_id: str) -> bool:
35
+ """Return whether a browser is currently connected for this session."""
36
+ ...
37
+
38
+ def get_loom_client(self, session_id: str) -> LoomClient:
39
+ """Return the connected Loom client for this session.
40
+
41
+ Raises `ConnectionError` if no browser is connected.
42
+ """
43
+ ...
44
+
45
+ def get_browser_ready_event(self, session_id: str) -> asyncio.Event:
46
+ """Return (creating if needed) the readiness event for this session.
47
+
48
+ The event is set once the browser has mounted and can handle commands.
49
+ """
50
+ ...
51
+
52
+
53
+ class DefaultBrowserRelay:
54
+ """The built-in default: no browser is ever connected.
55
+
56
+ With this relay installed the browser tools stay dormant — `is_browser_connected` is
57
+ always `False`, `get_loom_client` raises, and each `get_browser_ready_event` returns a
58
+ fresh event that is never set — so the tools report that no browser is connected instead
59
+ of acting on one.
60
+ """
61
+
62
+ def is_browser_connected(self, session_id: str) -> bool:
63
+ return False
64
+
65
+ def get_loom_client(self, session_id: str) -> LoomClient:
66
+ msg = "No genome browser is connected for this session."
67
+ raise ConnectionError(msg)
68
+
69
+ def get_browser_ready_event(self, session_id: str) -> asyncio.Event:
70
+ return asyncio.Event()
71
+
72
+
73
+ _browser_relay: BrowserRelay = DefaultBrowserRelay()
74
+
75
+
76
+ def set_browser_relay(relay: BrowserRelay) -> None:
77
+ """Install the relay the browser tools use to reach connected browsers.
78
+
79
+ Call once at startup. A host that serves a live genome browser installs a relay backed by
80
+ its connection registry; otherwise the no-browser default applies.
81
+ """
82
+ global _browser_relay
83
+ _browser_relay = relay
84
+
85
+
86
+ def get_browser_relay() -> BrowserRelay:
87
+ """Return the installed browser relay (the no-browser default until overridden)."""
88
+ return _browser_relay
@@ -0,0 +1,18 @@
1
+ """Shared LangGraph checkpointer singleton for the application."""
2
+
3
+ from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
4
+
5
+
6
+ _checkpointer: AsyncPostgresSaver | None = None
7
+
8
+
9
+ def get_checkpointer() -> AsyncPostgresSaver:
10
+ if _checkpointer is None:
11
+ msg = "Checkpointer not initialized — call set_checkpointer() during app startup"
12
+ raise RuntimeError(msg)
13
+ return _checkpointer
14
+
15
+
16
+ def set_checkpointer(cp: AsyncPostgresSaver) -> None:
17
+ global _checkpointer
18
+ _checkpointer = cp
@@ -0,0 +1,107 @@
1
+ """The agent's host-knowledge seam.
2
+
3
+ Altar does not build, store, or traverse a knowledge graph (see the repository's architectural
4
+ invariants). Some hosts have one, and the variant-prioritization agent can use context derived
5
+ from it: HPO id → name resolution, HGNC symbol → Ensembl id, ontology term names, and per-variant
6
+ evidence for the `ExamineVariant` tool. Every such touchpoint routes through this one protocol, so
7
+ the host owns graph construction, ontology traversal, and disease-evidence synthesis, and the agent
8
+ only consumes the resulting typed values. Two implementations satisfy it:
9
+
10
+ * a host that has a knowledge graph supplies an implementation backed by it — the agent gains
11
+ whatever disease context, ontology names, and gene-id resolution that host provides;
12
+ * `DefaultKnowledgeProvider` below is the built-in fallback — a fully functional agent for the
13
+ core prioritization loop that simply lacks those host-derived signals.
14
+
15
+ The provider is injected the same way the data-store seams are: closed over in
16
+ `create_prioritization_graph` and handed to the tools that need it (`ExamineVariant`,
17
+ `GetGeneExpression`, `GetRelevantModels`) and to the system prompt (HPO names). Nothing in Altar
18
+ imports a graph implementation.
19
+
20
+ The four surfaces and what the built-in default returns when no host provider is wired:
21
+
22
+ | method | resolves | default (no provider) |
23
+ | -------------------- | ------------------------------------------ | --------------------- |
24
+ | `hpo_id_to_name` | HPO id → human-readable name | `{}` |
25
+ | `resolve_ensembl_id` | HGNC gene symbol → Ensembl gene id | `None` |
26
+ | `ontology_term_name` | ontology term id (e.g. a CL id) → name | `None` |
27
+ | `examine_variant` | per-variant `VariantEvidence` | stub evidence |
28
+ """
29
+
30
+ from __future__ import annotations
31
+ from typing import Protocol, runtime_checkable
32
+
33
+ from altar.agent.tools.variant_analysis.schemas import VariantEvidence
34
+
35
+
36
+ @runtime_checkable
37
+ class AgentKnowledgeProvider(Protocol):
38
+ """The host-knowledge surface the prioritization agent depends on.
39
+
40
+ A host with a knowledge graph implements these four methods against it; the built-in
41
+ `DefaultKnowledgeProvider` answers empty when no provider is wired.
42
+ """
43
+
44
+ def hpo_id_to_name(self) -> dict[str, str]:
45
+ """Return the full HPO id → human-readable name map (`{}` when unavailable)."""
46
+ ...
47
+
48
+ async def resolve_ensembl_id(self, symbol: str) -> str | None:
49
+ """Resolve an HGNC gene symbol to its Ensembl gene id (`None` when unavailable)."""
50
+ ...
51
+
52
+ def ontology_term_name(self, term_id: str) -> str | None:
53
+ """Resolve an ontology term id (e.g. a CL cell-type id) to its name (`None` when unavailable)."""
54
+ ...
55
+
56
+ async def examine_variant(
57
+ self,
58
+ job_id: str,
59
+ variant_id: str,
60
+ phenotypes: list[str],
61
+ ) -> VariantEvidence:
62
+ """Return host-derived evidence for one variant (`chr:pos:ref:alt`).
63
+
64
+ How the evidence is assembled is entirely the host's concern; Altar only defines the
65
+ returned `VariantEvidence` shape (nearby genes, model scores, motif effects, cCRE
66
+ overlaps, disease links, phenotype paths, and a markdown `agent_summary`). The built-in
67
+ default returns a minimal `VariantEvidence` carrying only the variant id and a
68
+ "no knowledge provider" note.
69
+ """
70
+ ...
71
+
72
+
73
+ class DefaultKnowledgeProvider:
74
+ """The built-in `AgentKnowledgeProvider` with no host knowledge — the default a host gets
75
+ when it wires none.
76
+
77
+ The agent stays fully functional for the core prioritization loop (filter/count/candidate
78
+ genes/literature/sublists/KB); it just cannot resolve HPO or ontology names, cannot map gene
79
+ symbols to Ensembl ids (so `GetGeneExpression` finds nothing to query), and `ExamineVariant`
80
+ returns a stub rather than host evidence. A host with its own knowledge source replaces this
81
+ with a conforming provider.
82
+ """
83
+
84
+ def hpo_id_to_name(self) -> dict[str, str]:
85
+ return {}
86
+
87
+ async def resolve_ensembl_id(self, symbol: str) -> str | None:
88
+ return None
89
+
90
+ def ontology_term_name(self, term_id: str) -> str | None:
91
+ return None
92
+
93
+ async def examine_variant(
94
+ self,
95
+ job_id: str,
96
+ variant_id: str,
97
+ phenotypes: list[str],
98
+ ) -> VariantEvidence:
99
+ return VariantEvidence(
100
+ variant_id=variant_id,
101
+ agent_summary=(
102
+ f"## Variant: {variant_id}\n"
103
+ "No host knowledge provider is configured, so no disease or phenotype evidence is "
104
+ "available for this variant. Use FilterJobVariants and the variant's model scores "
105
+ "for prioritization instead."
106
+ ),
107
+ )
File without changes