biomedical-knowledge-lookup 2.2.0__tar.gz → 2.3.0__tar.gz

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 (110) hide show
  1. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/PKG-INFO +1 -1
  2. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/pyproject.toml +1 -1
  3. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/__init__.py +1 -1
  4. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/__main__.py +51 -0
  5. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/ols_adapter.py +76 -0
  6. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/expand.py +12 -1
  7. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/runners.py +6 -0
  8. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/state.py +3 -0
  9. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/core/term_expansion.py +384 -31
  10. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/LICENSE +0 -0
  11. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/README.md +0 -0
  12. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/__init__.py +0 -0
  13. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/biolinker_adapter.py +0 -0
  14. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/bioontology_adapter.py +0 -0
  15. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/bioportal_adapter.py +0 -0
  16. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/chembl_adapter.py +0 -0
  17. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/clinvar_adapter.py +0 -0
  18. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/cosmic_adapter.py +0 -0
  19. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/dbpedia_adapter.py +0 -0
  20. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/disgenet_adapter.py +0 -0
  21. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/drugbank_adapter.py +0 -0
  22. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/ebiols_adapter.py +0 -0
  23. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/ensembl_adapter.py +0 -0
  24. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/europepmc_adapter.py +0 -0
  25. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/eutils_adapter.py +0 -0
  26. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/geneontology_adapter.py +0 -0
  27. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/hgnc_adapter.py +0 -0
  28. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/hpo_adapter.py +0 -0
  29. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/interpro_adapter.py +0 -0
  30. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/kegg_adapter.py +0 -0
  31. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/mondo_adapter.py +0 -0
  32. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/obofoundry_adapter.py +0 -0
  33. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/omim_adapter.py +0 -0
  34. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/opentargets_adapter.py +0 -0
  35. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/oxo_adapter.py +0 -0
  36. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/pdb_adapter.py +0 -0
  37. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/pfam_adapter.py +0 -0
  38. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/pubchem_adapter.py +0 -0
  39. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/quickgo_adapter.py +0 -0
  40. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/reactome_adapter.py +0 -0
  41. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/string_adapter.py +0 -0
  42. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/tyto_adapter.py +0 -0
  43. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/umls_adapter.py +0 -0
  44. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/unichem_adapter.py +0 -0
  45. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/uniprot_adapter.py +0 -0
  46. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/wikidata_adapter.py +0 -0
  47. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/adapters/zooma_adapter.py +0 -0
  48. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/__init__.py +0 -0
  49. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/config.py +0 -0
  50. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/graph.py +0 -0
  51. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/__init__.py +0 -0
  52. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/_limits.py +0 -0
  53. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/aggregate.py +0 -0
  54. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/approval.py +0 -0
  55. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/detail_gather.py +0 -0
  56. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/enrichment.py +0 -0
  57. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/export.py +0 -0
  58. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/filter.py +0 -0
  59. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/lookup.py +0 -0
  60. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/preprocess.py +0 -0
  61. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/prune.py +0 -0
  62. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/quality_gate.py +0 -0
  63. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/refine.py +0 -0
  64. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/nodes/review.py +0 -0
  65. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/agents/routing.py +0 -0
  66. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/base.py +0 -0
  67. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/benchmarking/__init__.py +0 -0
  68. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/benchmarking/benchmarking.py +0 -0
  69. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/benchmarks/__init__.py +0 -0
  70. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/benchmarks/run_benchmark.py +0 -0
  71. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/cache/__init__.py +0 -0
  72. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/cache/cache.py +0 -0
  73. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/core/__init__.py +0 -0
  74. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/core/central_lookup.py +0 -0
  75. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/core/expansion_store.py +0 -0
  76. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/core/factory.py +0 -0
  77. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/core/multi_source_annotator.py +0 -0
  78. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/core/source_routing.py +0 -0
  79. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/curie_utils/__init__.py +0 -0
  80. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/curie_utils/normalization.py +0 -0
  81. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/curie_utils/reference.py +0 -0
  82. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/curie_utils/validation.py +0 -0
  83. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/export/__init__.py +0 -0
  84. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/export/exports.py +0 -0
  85. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/helpers/__init__.py +0 -0
  86. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/helpers/helpers.py +0 -0
  87. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/mcp_server/__init__.py +0 -0
  88. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/mcp_server/__main__.py +0 -0
  89. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/mcp_server/isolation.py +0 -0
  90. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/mcp_server/schemas.py +0 -0
  91. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/mcp_server/server.py +0 -0
  92. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/mcp_server/sources.py +0 -0
  93. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/models/__init__.py +0 -0
  94. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/models/biomedical_knowledge_models.py +0 -0
  95. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/models/extensions.py +0 -0
  96. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/models/models.py +0 -0
  97. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/models/validation_models.py +0 -0
  98. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/py.typed +0 -0
  99. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/services/__init__.py +0 -0
  100. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/services/ontology_concept_loader.py +0 -0
  101. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/services/rdf_converter.py +0 -0
  102. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/umls/__init__.py +0 -0
  103. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/umls/batch.py +0 -0
  104. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/umls/cache.py +0 -0
  105. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/umls/embeddings.py +0 -0
  106. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/umls/llm.py +0 -0
  107. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/umls/rdf.py +0 -0
  108. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/utils/redaction.py +0 -0
  109. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/utils/retry_utils.py +0 -0
  110. {biomedical_knowledge_lookup-2.2.0 → biomedical_knowledge_lookup-2.3.0}/src/knowledge_lookup/utils/timing_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: biomedical-knowledge-lookup
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Unified biological concept lookup across 36 biomedical knowledge sources including BioPortal, OLS, UMLS, ChEMBL, DisGeNET, and more
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -16,7 +16,7 @@ urls = { Documentation = "https://jonasheinickeBio.github.io/biomedical-knowledg
16
16
  # Optional feature groups. The core install pulls none of these; each dependency
17
17
  # guards its own import so `import knowledge_lookup` works without them and the
18
18
  # matching adapter/feature is simply disabled when absent.
19
- version = "2.2.0"
19
+ version = "2.3.0"
20
20
 
21
21
 
22
22
  [project.optional-dependencies]
@@ -114,7 +114,7 @@ __all__ = [
114
114
  # poetry-dynamic-versioning rewrites the next line with the real version at build time
115
115
  # ([tool.poetry-dynamic-versioning.substitution] in pyproject.toml). Keep it the only line
116
116
  # that starts with `__version__ = "`, or the substitution would hit more than one line.
117
- __version__ = "2.2.0"
117
+ __version__ = "2.3.0"
118
118
  __author__ = "AID-PAIS Knowledge Graph Team"
119
119
  __description__ = "Unified biological concept lookup across multiple knowledge sources"
120
120
 
@@ -72,6 +72,26 @@ def search(
72
72
  partial: bool = typer.Option(
73
73
  False, "--partial", "-p", help="Enable partial/fuzzy matching (UMLS adapter)"
74
74
  ),
75
+ expand: bool = typer.Option(
76
+ False,
77
+ "--expand",
78
+ "-e",
79
+ help="Iteratively expand the query with synonyms/long-forms discovered from results "
80
+ "and search each new term (uses search_concepts_expanded)",
81
+ ),
82
+ relationships: bool = typer.Option(
83
+ False,
84
+ "--relationships",
85
+ help="With --expand, also traverse relationship edges (STRING interactions, KEGG "
86
+ "pathways, DisGeNET/Open Targets associations) and search their named targets "
87
+ "(implies --expand)",
88
+ ),
89
+ expand_hierarchy: bool = typer.Option(
90
+ False,
91
+ "--expand-hierarchy",
92
+ help="With --expand, traverse only taxonomic class<->member edges (OLS narrower/"
93
+ "broader, UMLS parent/child) so a class term surfaces its members (implies --expand)",
94
+ ),
75
95
  ):
76
96
  """
77
97
  Search for biological concepts across knowledge sources.
@@ -119,6 +139,31 @@ def search(
119
139
  result = LookupResult(query=query)
120
140
  result.add_concepts(umls_concepts, KnowledgeSource.UMLS)
121
141
  return result
142
+ if expand or relationships or expand_hierarchy:
143
+ if expand_hierarchy:
144
+ from knowledge_lookup.core.term_expansion import (
145
+ hierarchy_relationship_sources,
146
+ )
147
+
148
+ return await lkp.search_concepts_expanded(
149
+ query=query,
150
+ sources=source_enums,
151
+ max_results=limit,
152
+ relationships=True,
153
+ relationship_sources=hierarchy_relationship_sources(lkp),
154
+ )
155
+ if relationships:
156
+ return await lkp.search_concepts_expanded(
157
+ query=query,
158
+ sources=source_enums,
159
+ max_results=limit,
160
+ relationships=True,
161
+ )
162
+ return await lkp.search_concepts_expanded(
163
+ query=query,
164
+ sources=source_enums,
165
+ max_results=limit,
166
+ )
122
167
  return await lkp.search_concepts(
123
168
  query=query,
124
169
  sources=source_enums,
@@ -249,6 +294,11 @@ def workflow(
249
294
  concept_types: list[str] | None = typer.Option(
250
295
  None, "--type", "-t", help="Filter by concept types"
251
296
  ),
297
+ relationships: bool = typer.Option(
298
+ False,
299
+ "--relationships",
300
+ help="Expand the search with relationship edges (interactions, pathways, class members)",
301
+ ),
252
302
  ):
253
303
  """
254
304
  Run the intelligent agent workflow with review and approval.
@@ -284,6 +334,7 @@ def workflow(
284
334
  export_path=export_path,
285
335
  max_iterations=max_iterations,
286
336
  auto_approve_threshold=auto_approve,
337
+ include_relationships=relationships,
287
338
  )
288
339
 
289
340
  result = asyncio.run(_run())
@@ -65,6 +65,26 @@ class OLSAdapter(KnowledgeSourceAdapter):
65
65
  # Get the first match
66
66
  term_data = data["_embedded"]["terms"][0]
67
67
  concept = self._convert_ols_concept_to_unified(term_data)
68
+ if concept is not None:
69
+ links = term_data.get("_links", {}) or {}
70
+ child_labels = [
71
+ label
72
+ for _iri, label in await self._fetch_linked_terms(
73
+ (links.get("children") or {}).get("href")
74
+ )
75
+ if label
76
+ ]
77
+ parent_labels = [
78
+ label
79
+ for _iri, label in await self._fetch_linked_terms(
80
+ (links.get("parents") or {}).get("href")
81
+ )
82
+ if label
83
+ ]
84
+ if child_labels:
85
+ concept.children = child_labels
86
+ if parent_labels:
87
+ concept.parents = parent_labels
68
88
  return concept
69
89
 
70
90
  return None
@@ -73,6 +93,62 @@ class OLSAdapter(KnowledgeSourceAdapter):
73
93
  logger.error(f"Failed to get OLS concept details for '{concept_id}': {e}")
74
94
  return None
75
95
 
96
+ async def _fetch_linked_terms(
97
+ self, href: str | None, limit: int = 20
98
+ ) -> list[tuple[str, str]]:
99
+ """Follow an OLS ``_links`` hierarchy href (children/parents/...) and
100
+ return ``(iri, label)`` pairs. Any failure degrades to ``[]``."""
101
+ if not href:
102
+ return []
103
+ try:
104
+ base = href.split("?", 1)[0]
105
+ data = await self._make_request(base, {"size": max(1, min(limit, 200)), "page": 0})
106
+ except Exception as e:
107
+ logger.debug(f"OLS linked-terms request failed for {href}: {e}")
108
+ return []
109
+ terms = (data.get("_embedded") or {}).get("terms") or []
110
+ return [(t.get("iri", ""), t.get("label", "")) for t in terms if t.get("iri")]
111
+
112
+ async def get_relationships(self, concept_id: str, limit: int = 10) -> list[dict[str, Any]]:
113
+ """Return direct hierarchy neighbours of an OLS term as relationship
114
+ edges.
115
+
116
+ Resolves the term's ``_links.children``/``_links.parents`` (the API
117
+ supplies ready-to-follow hrefs, so no endpoint guessing) and returns
118
+ them in the adapter relationship shape. Narrower terms become
119
+ ``has_narrower`` (a class->member edge for class terms), broader terms
120
+ ``has_broader``. Non-IRI ids and any failure return ``[]``.
121
+ """
122
+ if not concept_id or "http" not in concept_id:
123
+ return []
124
+ try:
125
+ data = await self._make_request(f"{self.base_url}/terms", {"iri": concept_id})
126
+ terms = (data.get("_embedded") or {}).get("terms") or []
127
+ if not terms:
128
+ return []
129
+ links = terms[0].get("_links", {}) or {}
130
+ except Exception as e:
131
+ logger.error(f"OLS get_relationships failed for '{concept_id}': {e}")
132
+ return []
133
+
134
+ rels: list[dict[str, Any]] = []
135
+ for key, label in (("children", "has_narrower"), ("parents", "has_broader")):
136
+ href = (links.get(key) or {}).get("href")
137
+ for iri, name in await self._fetch_linked_terms(href, limit=limit):
138
+ if not name:
139
+ continue
140
+ rels.append(
141
+ {
142
+ "relation_label": label,
143
+ "related_id": iri,
144
+ "related_name": name,
145
+ "source": "OLS",
146
+ }
147
+ )
148
+ if len(rels) >= limit:
149
+ return rels
150
+ return rels
151
+
76
152
  def _convert_ols_result_to_concept(self, result: dict[str, Any]) -> UnifiedConcept | None:
77
153
  """Convert OLS search result to unified concept."""
78
154
  try:
@@ -34,7 +34,11 @@ from __future__ import annotations
34
34
  import asyncio
35
35
 
36
36
  from ...core.central_lookup import CentralKnowledgeLookup
37
- from ...core.term_expansion import AbbreviationSource, expand_and_search
37
+ from ...core.term_expansion import (
38
+ AbbreviationSource,
39
+ default_relationship_sources,
40
+ expand_and_search,
41
+ )
38
42
  from ...models import KnowledgeSource, LookupConfig
39
43
  from ..state import LookupWorkflowState, make_step
40
44
  from . import _limits
@@ -66,6 +70,12 @@ async def expand_node(state: LookupWorkflowState) -> dict:
66
70
  None if source_filter is None or KnowledgeSource.UMLS in source_filter else []
67
71
  )
68
72
  lookup = CentralKnowledgeLookup(config=config, auto_initialize=True)
73
+ # Relationship-edge expansion (interactions, pathways, associations, class
74
+ # members) is opt-in via the workflow state; off by default so the bounded
75
+ # quality pass stays cheap unless a caller asks for a relationship network.
76
+ relationship_sources = (
77
+ default_relationship_sources(lookup) if state.get("include_relationships") else None
78
+ )
69
79
  try:
70
80
  _, trace = await asyncio.wait_for(
71
81
  expand_and_search(
@@ -75,6 +85,7 @@ async def expand_node(state: LookupWorkflowState) -> dict:
75
85
  max_rounds=_MAX_ROUNDS,
76
86
  max_terms_per_round=_MAX_TERMS_PER_ROUND,
77
87
  abbreviation_sources=abbreviation_sources,
88
+ relationship_sources=relationship_sources,
78
89
  ),
79
90
  timeout=_limits.EXPAND_TIMEOUT,
80
91
  )
@@ -41,6 +41,7 @@ def _initial_state(
41
41
  export_path: str | None,
42
42
  max_iterations: int,
43
43
  auto_approve_threshold: float,
44
+ include_relationships: bool = False,
44
45
  ) -> LookupWorkflowState:
45
46
  return {
46
47
  "query": query,
@@ -68,6 +69,7 @@ def _initial_state(
68
69
  "normalized_query": "",
69
70
  "original_query": query,
70
71
  "expanded_search_terms": [],
72
+ "include_relationships": include_relationships,
71
73
  "is_german": False,
72
74
  "quality_score": None,
73
75
  "quality_details": None,
@@ -125,6 +127,7 @@ async def run_workflow(
125
127
  export_path: str | None = None,
126
128
  max_iterations: int = 3,
127
129
  auto_approve_threshold: float = 0.8,
130
+ include_relationships: bool = False,
128
131
  checkpointer: BaseCheckpointSaver | None = None,
129
132
  ) -> dict:
130
133
  """Run the full lookup-review-approval-export workflow.
@@ -138,6 +141,8 @@ async def run_workflow(
138
141
  export_path: Directory for export files.
139
142
  max_iterations: Maximum lookup passes (initial search + refinements).
140
143
  auto_approve_threshold: Score above which auto-approval triggers.
144
+ include_relationships: Enable relationship-edge expansion (interactions,
145
+ pathways, class members) during the search step.
141
146
  checkpointer: Checkpointer holding paused runs. Defaults to a
142
147
  process-wide in-memory saver; pass the same one to
143
148
  :func:`resume_workflow`.
@@ -162,6 +167,7 @@ async def run_workflow(
162
167
  export_path=export_path,
163
168
  max_iterations=max_iterations,
164
169
  auto_approve_threshold=auto_approve_threshold,
170
+ include_relationships=include_relationships,
165
171
  )
166
172
 
167
173
  # Run the graph to completion (or interrupt)
@@ -44,6 +44,9 @@ class LookupWorkflowState(TypedDict):
44
44
  normalized_query: str # Cleaned/normalized version of the query
45
45
  original_query: str # Original query as provided
46
46
  expanded_search_terms: list[str] # Flat list of ALL search variants (searched in parallel)
47
+ include_relationships: (
48
+ bool # Opt-in relationship-edge expansion (interactions, pathways, class members)
49
+ )
47
50
  is_german: bool # Whether the query appears to be German
48
51
  quality_score: float | None # Quality gate score (0-1)
49
52
  quality_details: dict | None # Quality breakdown