#!/usr/bin/env python3 """Export QuickUMLS-detected CUIs and DuckDB-validated AUIs for query texts. Run with the QuickUMLS venv (required for entity detection):: /path/to/.venv-quickumls/bin/python3 scripts/export_query_entity_detections.py Single query:: .../python3 scripts/export_query_entity_detections.py \\ --query "Type II Diabetes Mellitus Uncontrolled" Full test split (writes ``results/query_entity_detections_test.json``):: .../python3 scripts/export_query_entity_detections.py --split test Build only the CUI→AUI cache (project venv is fine):: python3 scripts/export_query_entity_detections.py --build-aui-map-only """ from __future__ import annotations import argparse import sys from pathlib import Path WORKSPACE = Path(__file__).resolve().parent.parent if str(WORKSPACE) not in sys.path: sys.path.insert(0, str(WORKSPACE)) from config.paths import ( CUI_TO_VALID_AUI_PARQUET, QUICKUMLS_DATA_DIR, RESULTS_DIR, TEST_JSONL, TRAIN_JSONL, VAL_JSONL, ensure_dirs, ) from src.data.query_entity_export import ( build_cui_to_valid_aui_parquet, ensure_cui_to_valid_aui_lookup, iter_entity_detection_entries, write_entity_detections_json, ) from src.data.query_entity_linker import QueryEntityLinker SPLIT_PATHS = { "train": TRAIN_JSONL, "val": VAL_JSONL, "test": TEST_JSONL, } def _read_jsonl(path: Path) -> list[dict]: import polars as pl return pl.read_ndjson(path).to_dicts() def parse_args() -> argparse.Namespace: p = argparse.ArgumentParser( description="Export QuickUMLS CUIs and DuckDB-validated AUIs for queries." ) p.add_argument( "--query", type=str, default=None, help="Single query text to process.", ) p.add_argument( "--split", choices=sorted(SPLIT_PATHS), default=None, help="Process a train/val/test JSONL split.", ) p.add_argument( "--jsonl", type=Path, default=None, help="Custom JSONL input (uses query_text field).", ) p.add_argument( "--output", type=Path, default=None, help="Output JSON path (default: results/query_entity_detections_.json).", ) p.add_argument( "--limit", type=int, default=None, help="Process only the first N rows.", ) p.add_argument( "--rebuild-aui-map", action="store_true", help="Rebuild cui_to_valid_aui.parquet from id_maps + DuckDB.", ) p.add_argument( "--build-aui-map-only", action="store_true", help="Only build/load the CUI→AUI parquet cache and exit.", ) p.add_argument( "--pretty", action="store_true", help="Pretty-print output JSON (uses more disk; default is compact).", ) p.add_argument( "--quickumls-dir", type=Path, default=QUICKUMLS_DATA_DIR, help="QuickUMLS data directory.", ) return p.parse_args() def main() -> None: args = parse_args() ensure_dirs() if args.build_aui_map_only: n_cuis = build_cui_to_valid_aui_parquet() print( f"Built CUI→valid AUI parquet: {n_cuis} CUIs -> {CUI_TO_VALID_AUI_PARQUET}" ) return if args.query: rows = [{"query_text": args.query}] default_name = "single" elif args.jsonl is not None: rows = _read_jsonl(args.jsonl) default_name = args.jsonl.stem elif args.split is not None: rows = _read_jsonl(SPLIT_PATHS[args.split]) default_name = args.split else: rows = _read_jsonl(TEST_JSONL) default_name = "test" if args.limit is not None: rows = rows[: args.limit] if not args.quickumls_dir.exists(): raise FileNotFoundError( f"QuickUMLS data not found at {args.quickumls_dir}. " "Set --quickumls-dir or ICD_QUICKUMLS_DATA_DIR." ) print("Loading CUI→valid AUI lookup (Polars over parquet)...") lookup = ensure_cui_to_valid_aui_lookup(rebuild=args.rebuild_aui_map) print(f" {lookup.count()} CUIs with a valid clinical AUI in mrconso_clinical.parquet") print(f"Loading QuickUMLS linker from {args.quickumls_dir} ...") linker = QueryEntityLinker( quickumls_path=args.quickumls_dir, quickumls_only=True, skip_graph_cui_filter=True, ) output = args.output or ( RESULTS_DIR / f"query_entity_detections_{default_name}.json" ) print(f"Processing {len(rows)} queries -> {output}") try: entries = iter_entity_detection_entries( rows, linker=linker, lookup=lookup, ) n_entries, with_entities, total_entities = write_entity_detections_json( entries, output, pretty=args.pretty, ) finally: lookup.close() print(f"Wrote {output}") print( f" queries with entities: {with_entities}/{n_entries} " f"(total entities kept: {total_entities})" ) if __name__ == "__main__": main()