medterm4ds 0.0.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.
Files changed (96) hide show
  1. medterm4ds/__init__.py +333 -0
  2. medterm4ds/apps/__init__.py +1 -0
  3. medterm4ds/apps/_asyncutil.py +24 -0
  4. medterm4ds/apps/api.py +413 -0
  5. medterm4ds/apps/cli.py +1668 -0
  6. medterm4ds/apps/fhir_api.py +3745 -0
  7. medterm4ds/apps/mcp.py +959 -0
  8. medterm4ds/client.py +733 -0
  9. medterm4ds/core/__init__.py +55 -0
  10. medterm4ds/core/config.py +101 -0
  11. medterm4ds/core/display.py +143 -0
  12. medterm4ds/core/http.py +46 -0
  13. medterm4ds/core/models.py +557 -0
  14. medterm4ds/core/normalize.py +75 -0
  15. medterm4ds/core/provision.py +401 -0
  16. medterm4ds/core/schemas.py +222 -0
  17. medterm4ds/core/sql.py +47 -0
  18. medterm4ds/domains/__init__.py +47 -0
  19. medterm4ds/domains/evidence.py +488 -0
  20. medterm4ds/domains/terminology.py +420 -0
  21. medterm4ds/ds.py +221 -0
  22. medterm4ds/engines/__init__.py +21 -0
  23. medterm4ds/engines/api/__init__.py +5 -0
  24. medterm4ds/engines/api/engine.py +420 -0
  25. medterm4ds/engines/base.py +186 -0
  26. medterm4ds/engines/duckdb/__init__.py +7 -0
  27. medterm4ds/engines/duckdb/_engine_base.py +748 -0
  28. medterm4ds/engines/duckdb/_mixins/_DiscoveryOps.py +253 -0
  29. medterm4ds/engines/duckdb/_mixins/_EngineState.py +334 -0
  30. medterm4ds/engines/duckdb/_mixins/_HierarchyOps.py +114 -0
  31. medterm4ds/engines/duckdb/_mixins/_IndicationsOps.py +50 -0
  32. medterm4ds/engines/duckdb/_mixins/_LookupOps.py +255 -0
  33. medterm4ds/engines/duckdb/_mixins/_MappingOps.py +301 -0
  34. medterm4ds/engines/duckdb/_mixins/_OptimizeOps.py +206 -0
  35. medterm4ds/engines/duckdb/_mixins/_PatientFriendlyOps.py +191 -0
  36. medterm4ds/engines/duckdb/_mixins/_ResolutionOps.py +126 -0
  37. medterm4ds/engines/duckdb/_mixins/__init__.py +6 -0
  38. medterm4ds/engines/duckdb/engine.py +59 -0
  39. medterm4ds/engines/duckdb/hierarchy.py +357 -0
  40. medterm4ds/engines/duckdb/indications.py +390 -0
  41. medterm4ds/engines/duckdb/mappings.py +1278 -0
  42. medterm4ds/engines/duckdb/patient_friendly.py +1329 -0
  43. medterm4ds/engines/duckdb/prepared.py +1572 -0
  44. medterm4ds/engines/duckdb/resolution.py +518 -0
  45. medterm4ds/engines/fhir/__init__.py +225 -0
  46. medterm4ds/engines/fhir/closure.py +291 -0
  47. medterm4ds/engines/fhir/equivalence.py +155 -0
  48. medterm4ds/engines/fhir/responses.py +574 -0
  49. medterm4ds/engines/fhir/semantic.py +213 -0
  50. medterm4ds/engines/fhir/xml.py +153 -0
  51. medterm4ds/outputs/__init__.py +55 -0
  52. medterm4ds/outputs/checkpoint.py +257 -0
  53. medterm4ds/outputs/fhir.py +183 -0
  54. medterm4ds/outputs/records.py +124 -0
  55. medterm4ds/outputs/render.py +174 -0
  56. medterm4ds/services/__init__.py +133 -0
  57. medterm4ds/services/bulk.py +125 -0
  58. medterm4ds/services/conceptmap.py +111 -0
  59. medterm4ds/services/crosswalk.py +112 -0
  60. medterm4ds/services/crosswalk_prepared.py +452 -0
  61. medterm4ds/services/crosswalk_walk.py +553 -0
  62. medterm4ds/services/data_setup.py +711 -0
  63. medterm4ds/services/discovery.py +82 -0
  64. medterm4ds/services/extraction.py +931 -0
  65. medterm4ds/services/hierarchy.py +192 -0
  66. medterm4ds/services/indications.py +30 -0
  67. medterm4ds/services/inventory.py +165 -0
  68. medterm4ds/services/loinc_parts.py +391 -0
  69. medterm4ds/services/lookup.py +71 -0
  70. medterm4ds/services/lookup_builder.py +150 -0
  71. medterm4ds/services/mapping.py +48 -0
  72. medterm4ds/services/optimize.py +32 -0
  73. medterm4ds/services/patient_friendly.py +48 -0
  74. medterm4ds/services/patient_friendly_prepared.py +1454 -0
  75. medterm4ds/services/prepared_primitives.py +124 -0
  76. medterm4ds/services/resolution.py +95 -0
  77. medterm4ds/services/rxnorm_tty_walk.py +320 -0
  78. medterm4ds/services/schema_reporting.py +74 -0
  79. medterm4ds/services/search.py +1094 -0
  80. medterm4ds/services/selection.py +273 -0
  81. medterm4ds/services/walk.py +399 -0
  82. medterm4ds/setup.py +104 -0
  83. medterm4ds/sources/__init__.py +98 -0
  84. medterm4ds/sources/base.py +193 -0
  85. medterm4ds/sources/cpt_hcpcs.py +126 -0
  86. medterm4ds/sources/cvx.py +50 -0
  87. medterm4ds/sources/generic.py +29 -0
  88. medterm4ds/sources/icd.py +43 -0
  89. medterm4ds/sources/loinc.py +144 -0
  90. medterm4ds/sources/rxnorm.py +236 -0
  91. medterm4ds/sources/snomed.py +187 -0
  92. medterm4ds-0.0.1.dist-info/METADATA +1000 -0
  93. medterm4ds-0.0.1.dist-info/RECORD +96 -0
  94. medterm4ds-0.0.1.dist-info/WHEEL +4 -0
  95. medterm4ds-0.0.1.dist-info/entry_points.txt +3 -0
  96. medterm4ds-0.0.1.dist-info/licenses/LICENSE +674 -0
medterm4ds/__init__.py ADDED
@@ -0,0 +1,333 @@
1
+ """Medical Terminology for Data Science.
2
+
3
+ # Recommended API (start here)
4
+
5
+ import medterm4ds as mt
6
+
7
+ # Connect to a UMLS DuckDB file (built via mt.build_umls_duckdb or the
8
+ # medterm4ds-build package).
9
+ terms = mt.connect("/path/to/umls.duckdb")
10
+
11
+ # Lookup, hierarchy, mapping, patient-friendly names.
12
+ info = terms.lookup("SNOMEDCT_US", "44054006")
13
+ parents = terms.parents([("SNOMEDCT_US", "44054006")])
14
+ friendly = terms.patient_friendly("SNOMEDCT_US", "44054006")
15
+
16
+ # Text-to-code search (BM25 + SapBERT).
17
+ results = mt.search("diabetes", mode="hybrid")
18
+
19
+ # Text extraction (GLiNER + medspaCy + search).
20
+ concepts = mt.extract("Patient has T2DM. No CKD.", format="codes")
21
+
22
+ For a remote HTTP backend instead of a local DuckDB:
23
+
24
+ terms = mt.connect_remote("http://localhost:8000")
25
+
26
+ # Surfaces
27
+
28
+ Same engine, four deliverables:
29
+
30
+ - **Python library** (this module)
31
+ - **CLI**: `pip install medterm4ds[api]` then `medterm4ds --help`
32
+ - **MCP server**: `pip install medterm4ds[mcp]` then `medterm4ds-mcp`
33
+ - **FHIR R4 server**: `pip install medterm4ds[fhir]` then `python -m medterm4ds.apps.fhir_api`
34
+
35
+ # Module layout
36
+
37
+ - `medterm4ds` — recommended API (this module)
38
+ - `medterm4ds.services.*` — service-layer functions (lookup, hierarchy, mapping,
39
+ search, extraction, etc.). Accept an `engine=` arg.
40
+ - `medterm4ds.domains.*` — domain-specific helpers (diagnosis_codes, lab_codes,
41
+ drugs_for_indication). Wrap services with source-list presets.
42
+ - `medterm4ds.engines.*` — engine implementations (LocalDuckDBEngine,
43
+ LocalLiteEngine, RemoteApiEngine).
44
+ - `medterm4ds.apps.*` — CLI, FastAPI, MCP, FHIR server entry points.
45
+ - `medterm4ds.ds` — dataframe helpers for pandas/polars workflows.
46
+ """
47
+
48
+ __version__ = "0.0.1"
49
+
50
+ # ============================================================================
51
+ # Primary API — what most users need
52
+ # ============================================================================
53
+
54
+ from .client import Terminology, connect, connect_remote
55
+
56
+ # Intelligent text-to-code search (optional — requires BM25/SapBERT indexes)
57
+ # Log ImportError at WARNING so real bugs (typos, broken transitive imports)
58
+ # surface instead of silently producing AttributeError at the user's call site.
59
+ # Optional-deps users who expectedly lack BM25/SapBERT see one warning per
60
+ # missing surface, which they can silence via logging config if desired.
61
+ import logging as _logging
62
+
63
+ _logger = _logging.getLogger(__name__)
64
+
65
+ try:
66
+ from .services.search import search as _search_func
67
+
68
+ def search(query, *, mode="lexical", sources=None, count=20):
69
+ """Text-to-code search (lexical/semantic/hybrid).
70
+
71
+ Requires BM25 indexes at MEDTERM4DS_SEARCH_INDEX_DIR and/or SapBERT
72
+ model at MEDTERM4DS_EMBEDDING_MODEL_DIR.
73
+ """
74
+ return _search_func(query, mode=mode, sources=sources, count=count)
75
+ except ImportError as _exc:
76
+ _logger.warning("medterm4ds.search unavailable (install medterm4ds[search]?): %s", _exc)
77
+
78
+ # Text extraction (optional — requires medterm4ds[extraction])
79
+ try:
80
+ from .services.extraction import extract as _extract_func
81
+ from .services.extraction import find_terms as _find_terms_func
82
+ from .services.extraction import resolve_spans as _resolve_spans_func
83
+
84
+ def extract(text, *, format="codes", **kwargs):
85
+ """Extract medical concepts from free text.
86
+
87
+ Requires medterm4ds[extraction] (medspaCy + transformers).
88
+ """
89
+ return _extract_func(text, format=format, **kwargs)
90
+
91
+ def find_terms(text, **kwargs):
92
+ """Extract medical terms from text (NLP only, no code resolution)."""
93
+ return _find_terms_func(text, **kwargs)
94
+
95
+ def resolve_spans(spans, **kwargs):
96
+ """Resolve filtered spans to coded concepts via search."""
97
+ return _resolve_spans_func(spans, **kwargs)
98
+ except ImportError as _exc:
99
+ _logger.warning("medterm4ds.extract unavailable (install medterm4ds[extraction]?): %s", _exc)
100
+
101
+ # FHIR server (optional — requires medterm4ds[fhir])
102
+ try:
103
+ from .apps.fhir_api import create_fhir_app
104
+ except ImportError as _exc:
105
+ _logger.warning("medterm4ds.create_fhir_app unavailable (install medterm4ds[fhir]?): %s", _exc)
106
+
107
+ # Cache management — inspect and clean the ~/.medterm4ds/ cache
108
+ from .core.provision import cache_clear, cache_info, cache_versions
109
+
110
+ # ============================================================================
111
+ # Types — what most call sites need
112
+ # ============================================================================
113
+
114
+ from .core.models import (
115
+ CodeInfo,
116
+ CodeMapping,
117
+ CodeRef,
118
+ CodeRelation,
119
+ CodeResolution,
120
+ ConceptMapRow,
121
+ FriendlyNameResult,
122
+ NameSearchResult,
123
+ OptimizeResult,
124
+ OptimizeRule,
125
+ Provenance,
126
+ ProvenanceStep,
127
+ SourceStats,
128
+ )
129
+
130
+ # ============================================================================
131
+ # Advanced — engine implementations, service functions, domain helpers
132
+ #
133
+ # These are re-exported at the top level for legacy callers and for users who
134
+ # need direct access without going through the Terminology facade. Most new
135
+ # code should import from the submodule directly (e.g.
136
+ # `from medterm4ds.services.hierarchy import get_descendants`) for clarity.
137
+ # ============================================================================
138
+
139
+ from .core.config import (
140
+ LOCAL_DUCKDB_MEMORY_PROFILES,
141
+ LOCAL_LITE_MEMORY_PROFILES,
142
+ LocalDuckDBConfig,
143
+ LocalLiteConfig,
144
+ local_duckdb_config,
145
+ local_lite_config,
146
+ )
147
+ from .core.schemas import (
148
+ OUTPUT_SCHEMA_VERSION,
149
+ OutputField,
150
+ OutputSchema,
151
+ get_output_schema,
152
+ list_output_schemas,
153
+ )
154
+ from .domains import (
155
+ cross_reference,
156
+ diagnosis_codes,
157
+ discover,
158
+ drugs_by_class,
159
+ drugs_for_indication,
160
+ fda_label_by_rxcui,
161
+ guideline_fulltext,
162
+ guideline_recommendations,
163
+ guideline_search,
164
+ guidelines_for_code,
165
+ hcpcs_drugs,
166
+ indication_search,
167
+ lab_codes,
168
+ lab_value_codes,
169
+ procedure_codes,
170
+ search_drug,
171
+ vaccine_codes,
172
+ )
173
+ from .ds import (
174
+ code_ttys_dataframe,
175
+ conceptmap_dataframe,
176
+ hierarchy_dataframe,
177
+ lookup_dataframe,
178
+ map_dataframe,
179
+ mapping_conceptmap_dataframe,
180
+ patient_friendly_dataframe,
181
+ sample_codes_dataframe,
182
+ search_names_dataframe,
183
+ source_stats_dataframe,
184
+ )
185
+ from .engines.api import RemoteApiEngine
186
+ from .engines.duckdb import LocalDuckDBEngine, LocalLiteEngine
187
+ from .services.bulk import (
188
+ iter_batches,
189
+ iter_hierarchy_bulk,
190
+ iter_lookup_bulk,
191
+ iter_mapping_bulk,
192
+ iter_patient_friendly_bulk,
193
+ )
194
+ from .services.conceptmap import (
195
+ get_concept_map,
196
+ get_mapping_concept_map,
197
+ iter_concept_map,
198
+ iter_mapping_concept_map,
199
+ )
200
+ from .services.data_setup import (
201
+ DEFAULT_UMLS_RELEASE_TYPE,
202
+ DEFAULT_UMLS_VERIFY_SOURCES,
203
+ annotate_umls_duckdb,
204
+ build_umls_duckdb,
205
+ download_umls_release,
206
+ prepare_umls_duckdb,
207
+ verify_umls_duckdb,
208
+ )
209
+ from .services.discovery import get_code_ttys, get_source_stats, sample_source_codes, search_names
210
+ from .services.hierarchy import (
211
+ get_ancestors,
212
+ get_children,
213
+ get_code_relations,
214
+ get_descendants,
215
+ get_descendants_bfs,
216
+ get_parents,
217
+ is_descendant,
218
+ )
219
+ from .services.lookup import get_code_info, get_code_infos
220
+ from .services.mapping import get_code_mappings
221
+ from .services.optimize import optimize_codes
222
+ from .services.patient_friendly import get_patient_friendly_names
223
+ from .services.resolution import resolve_codes
224
+
225
+ __all__ = [
226
+ "__version__",
227
+ # Primary API
228
+ "Terminology",
229
+ "connect",
230
+ "connect_remote",
231
+ "search",
232
+ "extract",
233
+ "find_terms",
234
+ "resolve_spans",
235
+ "create_fhir_app",
236
+ "cache_clear",
237
+ "cache_info",
238
+ "cache_versions",
239
+ # Types
240
+ "CodeRef",
241
+ "CodeInfo",
242
+ "CodeMapping",
243
+ "CodeRelation",
244
+ "CodeResolution",
245
+ "ConceptMapRow",
246
+ "FriendlyNameResult",
247
+ "NameSearchResult",
248
+ "OptimizeResult",
249
+ "OptimizeRule",
250
+ "Provenance",
251
+ "ProvenanceStep",
252
+ "SourceStats",
253
+ # Advanced — engines
254
+ "LocalDuckDBEngine",
255
+ "LocalLiteEngine",
256
+ "RemoteApiEngine",
257
+ "LocalDuckDBConfig",
258
+ "LocalLiteConfig",
259
+ "LOCAL_DUCKDB_MEMORY_PROFILES",
260
+ "LOCAL_LITE_MEMORY_PROFILES",
261
+ "local_duckdb_config",
262
+ "local_lite_config",
263
+ # Advanced — service functions
264
+ "get_ancestors",
265
+ "get_children",
266
+ "get_code_relations",
267
+ "get_descendants",
268
+ "get_descendants_bfs",
269
+ "get_parents",
270
+ "is_descendant",
271
+ "get_code_info",
272
+ "get_code_infos",
273
+ "get_code_mappings",
274
+ "get_code_ttys",
275
+ "get_source_stats",
276
+ "get_patient_friendly_names",
277
+ "optimize_codes",
278
+ "resolve_codes",
279
+ "sample_source_codes",
280
+ "search_names",
281
+ "get_concept_map",
282
+ "get_mapping_concept_map",
283
+ "iter_concept_map",
284
+ "iter_mapping_concept_map",
285
+ "iter_batches",
286
+ "iter_hierarchy_bulk",
287
+ "iter_lookup_bulk",
288
+ "iter_mapping_bulk",
289
+ "iter_patient_friendly_bulk",
290
+ # Advanced — domain helpers
291
+ "cross_reference",
292
+ "diagnosis_codes",
293
+ "discover",
294
+ "drugs_by_class",
295
+ "drugs_for_indication",
296
+ "fda_label_by_rxcui",
297
+ "guideline_fulltext",
298
+ "guideline_recommendations",
299
+ "guideline_search",
300
+ "guidelines_for_code",
301
+ "hcpcs_drugs",
302
+ "indication_search",
303
+ "lab_codes",
304
+ "lab_value_codes",
305
+ "procedure_codes",
306
+ "search_drug",
307
+ "vaccine_codes",
308
+ # Advanced — dataframe helpers
309
+ "code_ttys_dataframe",
310
+ "conceptmap_dataframe",
311
+ "hierarchy_dataframe",
312
+ "lookup_dataframe",
313
+ "map_dataframe",
314
+ "mapping_conceptmap_dataframe",
315
+ "patient_friendly_dataframe",
316
+ "sample_codes_dataframe",
317
+ "search_names_dataframe",
318
+ "source_stats_dataframe",
319
+ # Advanced — data setup
320
+ "DEFAULT_UMLS_RELEASE_TYPE",
321
+ "DEFAULT_UMLS_VERIFY_SOURCES",
322
+ "annotate_umls_duckdb",
323
+ "build_umls_duckdb",
324
+ "download_umls_release",
325
+ "prepare_umls_duckdb",
326
+ "verify_umls_duckdb",
327
+ # Advanced — schemas
328
+ "OUTPUT_SCHEMA_VERSION",
329
+ "OutputField",
330
+ "OutputSchema",
331
+ "get_output_schema",
332
+ "list_output_schemas",
333
+ ]
@@ -0,0 +1 @@
1
+ """Application entry points."""
@@ -0,0 +1,24 @@
1
+ """Shared async helpers for the medterm4ds app layer.
2
+
3
+ Both `mcp.py` and `fhir_api.py` need to offload synchronous DuckDB-touching
4
+ work to a single-worker ThreadPoolExecutor (DuckDB Python connections are
5
+ not thread-safe under concurrent use). This module centralizes that helper
6
+ so the two apps don't drift apart.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ from functools import partial
14
+
15
+
16
+ async def run_db(executor: ThreadPoolExecutor, func, *args, **kwargs):
17
+ """Offload a sync call to the given single-worker executor.
18
+
19
+ `executor` is per-app (lives on McpRuntime.db_executor or
20
+ app.state.db_executor) so multiple servers in one process get
21
+ independent workers and clean teardown.
22
+ """
23
+ loop = asyncio.get_running_loop()
24
+ return await loop.run_in_executor(executor, partial(func, *args, **kwargs))