diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..b60c33f --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# ruff format, line length 100 +65ca0f267e1804971ddb71ea2f85a02cad9f8294 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5961063..85d169c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,10 +6,14 @@ on: jobs: label-gate: uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/label-gate.yml@main + ruff: + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/ruff.yml@ruff-ty-workflows + ty: + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/ty.yml@ruff-ty-workflows build-test-sqlite: - uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/build-test.yml@main + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/build-test-sqlite.yml@ruff-ty-workflows build-test-postgres: - uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/build-test-postgres-v2.yml@main + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/build-test-postgres-v3.yml@ruff-ty-workflows with: postgres-db: omop_graph_test setup-commands: | diff --git a/README.md b/README.md index d191d25..57559fd 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,8 @@ This classification drives traversal filtering and scoring. from omop_graph.graph.paths import find_shortest_paths from omop_graph.extensions.omop_alchemy import PredicateKind -ingredient = kg.concept_id_by_code("RxNorm", "6809") # Metformin -drug = kg.concept_id_by_code("RxNorm", "860975") # Metformin 500 MG Oral Tablet +ingredient = kg.concept_id_by_code("RxNorm", "6809") # Metformin +drug = kg.concept_id_by_code("RxNorm", "860975") # Metformin 500 MG Oral Tablet paths, trace = find_shortest_paths( kg, diff --git a/docs/graph/kg.md b/docs/graph/kg.md index dfd0d82..f6f5cb3 100644 --- a/docs/graph/kg.md +++ b/docs/graph/kg.md @@ -66,8 +66,10 @@ from omop_graph.graph.kg import KnowledgeGraph, KnowledgeGraphEmbeddingConfigura from omop_emb.config import MetricType resolver = Resolver.from_active_config() -resolved_vector_store = resolver.resolve_vector_store("vector_store") # a [vector_stores.*] entry name -resolved_model = resolver.resolve_model("embedding-model") # a [models.*] entry name +resolved_vector_store = resolver.resolve_vector_store( + "vector_store" +) # a [vector_stores.*] entry name +resolved_model = resolver.resolve_model("embedding-model") # a [models.*] entry name backend = open_vector_store_writer(resolved_vector_store) emb_config = KnowledgeGraphEmbeddingConfiguration( diff --git a/docs/mkdocs_macros.py b/docs/mkdocs_macros.py index 5b09a58..6ff0eaa 100644 --- a/docs/mkdocs_macros.py +++ b/docs/mkdocs_macros.py @@ -1,28 +1,27 @@ -import pandas as pd import pathlib as pl -from typing import Optional, List, Mapping +from collections.abc import Mapping + +import pandas as pd + def define_env(env): @env.macro def to_grouped_table( - file_path: str, - group_cols: List[int] = [0], - displayed_cols: Optional[List[int]] = None, - sort_cols: Optional[List[int]] = None, - col_mappings: Optional[Mapping[str, str]] = None + file_path: str, + group_cols: list[int] | None = None, + displayed_cols: list[int] | None = None, + sort_cols: list[int] | None = None, + col_mappings: Mapping[str, str] | None = None, ): if group_cols is None: group_cols = [0] absolute_path = (pl.Path(env.project_dir) / file_path).resolve() - + if not absolute_path.exists(): return f"**Error:** Could not find file at `{absolute_path}`" - - suffix_loader_map = { - ".csv": pd.read_csv, - ".xlsx": pd.read_excel - } + + suffix_loader_map = {".csv": pd.read_csv, ".xlsx": pd.read_excel} loader_fn = suffix_loader_map[absolute_path.suffix] df = loader_fn(absolute_path, usecols=displayed_cols) @@ -35,36 +34,41 @@ def to_grouped_table( df = df.rename(columns=col_mappings) df = df.dropna(subset=df.columns[group_cols]) - + span_counts_map = {} for i, col_idx in enumerate(group_cols): - relevant_cols = group_cols[:i+1] - group_changes = (df.iloc[:, relevant_cols] != df.iloc[:, relevant_cols].shift()).any(axis=1) + relevant_cols = group_cols[: i + 1] + group_changes = (df.iloc[:, relevant_cols] != df.iloc[:, relevant_cols].shift()).any( + axis=1 + ) span_counts_map[col_idx] = group_changes.cumsum().value_counts(sort=False).tolist() table_style = "width: 100%; border-collapse: collapse; margin-bottom: 1rem;" cell_style = "border: 1px solid var(--md-typeset-table-color, #e0e0e0); padding: 12px; text-align: left;" - header_style = cell_style + " background-color: var(--md-default-bg-color, #f5f5f5); font-weight: bold;" - + header_style = ( + cell_style + + " background-color: var(--md-default-bg-color, #f5f5f5); font-weight: bold;" + ) + html = f'' html += "" - - for col in df.columns: + + for col in df.columns: html += f'' html += "" - + html += "" - + span_idx_map = {c: 0 for c in group_cols} rows_to_skip_map = {c: 0 for c in group_cols} - + for i, row in df.iterrows(): highest_change_level = -1 for level, col_idx in enumerate(group_cols): if rows_to_skip_map[col_idx] == 0: highest_change_level = level break - + if highest_change_level == 0 and i > 0: html += '' elif highest_change_level > 0 and i > 0: @@ -77,16 +81,19 @@ def to_grouped_table( if j in group_cols: if rows_to_skip_map[j] == 0: span = span_counts_map[j][span_idx_map[j]] - group_style = cell_style + " vertical-align: middle; font-weight: bold; background-color: var(--md-code-bg-color, #fafafa);" + group_style = ( + cell_style + + " vertical-align: middle; font-weight: bold; background-color: var(--md-code-bg-color, #fafafa);" + ) html += f'' rows_to_skip_map[j] = span - 1 span_idx_map[j] += 1 else: rows_to_skip_map[j] -= 1 - continue + continue else: html += f'' html += "" - + html += "
{col}
{display_val}{display_val}
" - return html \ No newline at end of file + return html diff --git a/docs/reasoning/grounding.md b/docs/reasoning/grounding.md index 591586d..65f851d 100644 --- a/docs/reasoning/grounding.md +++ b/docs/reasoning/grounding.md @@ -66,7 +66,7 @@ from omop_alchemy.cdm.query import ConceptFilter from omop_graph.extensions.omop_alchemy import PredicateKind constraints = GroundingConstraints( - parent_ids=(441484,), # 'Clinical Finding' — only accept descendants of this ancestor + parent_ids=(441484,), # 'Clinical Finding' — only accept descendants of this ancestor search_constraint=ConceptFilter( domains=("Condition",), vocabularies=("SNOMED",), @@ -80,7 +80,7 @@ results = ground_term( resolver_pipeline=pipeline, kg=kg, query="chest pain", - query_embedding=None, # pass a precomputed vector, or None to skip embedding-based scoring + query_embedding=None, # pass a precomputed vector, or None to skip embedding-based scoring constraints=constraints, ) ``` diff --git a/docs/reasoning/resolvers.md b/docs/reasoning/resolvers.md index 285c1eb..23df2d0 100644 --- a/docs/reasoning/resolvers.md +++ b/docs/reasoning/resolvers.md @@ -28,9 +28,9 @@ constraints = GroundingConstraints( search_constraint=ConceptFilter( domains=parsed_domains, vocabularies=parsed_vocabs, - require_standard=True # We prioritize Standard Concepts + require_standard=True, # We prioritize Standard Concepts ), - max_depth=6 # How deep to traverse the hierarchy + max_depth=6, # How deep to traverse the hierarchy ) # The resolver pipeline stops once it finds a high-confidence match diff --git a/notebooks/01_quickstart.ipynb b/notebooks/01_quickstart.ipynb deleted file mode 100644 index 6e6f6c4..0000000 --- a/notebooks/01_quickstart.ipynb +++ /dev/null @@ -1,716 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "edd10d93", - "metadata": {}, - "source": [ - "\n", - "This notebook demonstrates the minimal workflow:\n", - "\n", - "1. Create a KnowledgeGraph\n", - "2. Explore a concept neighbourhood\n", - "3. Find and rank paths\n", - "4. Explain why a path was selected" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "baff3b36", - "metadata": {}, - "outputs": [], - "source": [ - "from sqlalchemy.orm import sessionmaker\n", - "from dotenv import load_dotenv\n", - "\n", - "from omop_graph.graph.scoring import rank_paths, explain_path\n", - "from omop_graph.graph.paths import find_shortest_paths\n", - "from omop_graph.graph.kg import KnowledgeGraph\n", - "from omop_graph.graph.edges import PredicateKind\n", - "from omop_graph.render import (\n", - " render_trace,\n", - " render_path,\n", - " bind_default_renderers,\n", - ")\n", - "from orm_loader.helpers import configure_logging\n", - "from omop_alchemy import get_engine_name\n", - "import sqlalchemy as sa\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "9d271c31", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "configure_logging()\n", - "load_dotenv()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "11c7ac1a", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2026-01-12 10:14:33,396 | INFO | sql_loader.omop_alchemy.config | Database engine configured\n" - ] - } - ], - "source": [ - "engine_string = get_engine_name()\n", - "engine = sa.create_engine(engine_string, future=True, echo=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "7533b331", - "metadata": {}, - "outputs": [], - "source": [ - "Session = sessionmaker(bind=engine)\n", - "\n", - "session = Session()\n", - "kg = KnowledgeGraph(session)\n", - "bind_default_renderers(kg)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "9620337e", - "metadata": {}, - "outputs": [], - "source": [ - "ingredient = kg.concept_id_by_code(\"RxNorm\", \"6809\") # Metformin\n", - "drug = kg.concept_id_by_code(\"RxNorm\", \"860975\") # Metformin 500 MG Oral Tablet" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "ffb4566d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ConceptView(id=40163924, RxNorm:860975, name='24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet')" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "kg.concept_view(drug)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "9c280eb3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ConceptView(id=1503297, RxNorm:6809, name='metformin')" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "kg.concept_view(ingredient)" - ] - }, - { - "cell_type": "markdown", - "id": "a2c4ed32", - "metadata": {}, - "source": [ - "## Note on domain scope:\n", - "\n", - "By default, traversal is restricted to relationships within the same OMOP domain (e.g. Drug -> Drug, Condition -> Condition). This avoids misleading \u201cshortcut\u201d paths through terminology metadata. \n", - "\n", - "Cross-domain reasoning (e.g. Drug -> Condition) requires explicit biomedical relationships and is intentionally out of scope for the default - it also frequently defaults to structural or metadata relationsips such as SNOMED 'has module' which becomes rapidly non-specific if all terms resolve in 1 or 2 steps to an extreme high level parent.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "c29ea71b", - "metadata": {}, - "outputs": [], - "source": [ - "paths, trace = find_shortest_paths(\n", - " kg,\n", - " source=drug,\n", - " target=ingredient,\n", - " predicate_kinds={\n", - " PredicateKind.ONTOLOGICAL,\n", - " PredicateKind.MAPPING,\n", - " },\n", - " max_depth=6,\n", - " traced=True,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "4d319d70", - "metadata": {}, - "outputs": [], - "source": [ - "ranked = rank_paths(kg, paths)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "9e592d22", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "
\n", - " \n", - "
\n", - "
\n", - " [depth 0]\n", - "
\n", - " \n", - "
\n", - "
\n", - " 24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet \u2705\n", - "
\n", - "
\n", - " RxNorm:860975\n", - " \u00b7 Drug\n", - " \u00b7 Quant Clinical Drug\n", - "
\n", - "
\n", - " \n", - " \n", - "
\n", - " \u2514\u2500 Standard to Non-standard map (OMOP)\n", - "
\n", - " \n", - "
\n", - " \u2192 24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet\n", - "
\n", - " \n", - "
\n", - " \u2192 24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet\n", - "
\n", - " \n", - "
\n", - " \u2192 24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet\n", - "
\n", - " \n", - "
\n", - " \u2192 24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet\n", - "
\n", - " \n", - "
\n", - " \u2192 24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet\n", - "
\n", - " \n", - "
\n", - " \u2026 920 more\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 RxNorm to SPL (NLM)\n", - "
\n", - " \n", - "
\n", - " \u2192 METFORMIN ER 500 MG - metformin er 500 mg tablet\n", - "
\n", - " \n", - "
\n", - " \u2192 METFORMIN HYDROCHLORIDE - metformin hydrochloride tablet, extended release\n", - "
\n", - " \n", - "
\n", - " \u2192 METFORMIN HYDROCHLORIDE - metformin hydrochloride tablet, extended release\n", - "
\n", - " \n", - "
\n", - " \u2192 METFORMIN HYDROCHLORIDE - metformin hydrochloride tablet, extended release\n", - "
\n", - " \n", - "
\n", - " \u2192 METFORMIN HYDROCHLORIDE - metformin hydrochloride tablet, extended release\n", - "
\n", - " \n", - "
\n", - " \u2026 237 more\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Concept replaces\n", - "
\n", - " \n", - "
\n", - " \u2192 Metformin 500 MG 24 Hour Extended Release Tablet\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Has dose form (RxNorm)\n", - "
\n", - " \n", - "
\n", - " \u2192 Extended Release Oral Tablet\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 RxNorm to SNOMED equivalent (RxNorm)\n", - "
\n", - " \n", - "
\n", - " \u2192 Metformin hydrochloride 500 mg prolonged-release oral tablet\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Is a (RxNorm)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin Oral Product\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin Pill\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Non-standard to Standard map (OMOP)\n", - "
\n", - " \n", - "
\n", - " \u2192 24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Has tradename (RxNorm)\n", - "
\n", - " \n", - "
\n", - " \u2192 24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet [Glucophage]\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Quantified form of (RxNorm)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin hydrochloride 500 MG Extended Release Oral Tablet\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 RxNorm to VA Product equivalent (NDF-RT)\n", - "
\n", - " \n", - "
\n", - " \u2192 METFORMIN HCL 500MG TAB,SA\n", - "
\n", - " \n", - "
\n", - " \u2192 METFORMIN HCL 500MG 24HR TAB,SA\n", - "
\n", - "
\n", - "
\n", - "
\n", - " [depth 0]\n", - "
\n", - " \n", - "
\n", - "
\n", - " metformin \u2705\n", - "
\n", - "
\n", - " RxNorm:6809\n", - " \u00b7 Drug\n", - " \u00b7 Ingredient\n", - "
\n", - "
\n", - " \n", - " \n", - "
\n", - " \u2514\u2500 Brand name of (OMOP)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2026 521 more\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Has ingredient (RxNorm)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2026 113 more\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Non-standard to Standard map (OMOP)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2026 91 more\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Drug Source to RxNorm equivalent (OMOP)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2026 10 more\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 ATC to RxNorm/Extension primary lateral (OMOP)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2026 16 more\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 NDF-RT to RxNorm equivalent (RxNorm)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 SNOMED to RxNorm equivalent (RxNorm)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Standard to Non-standard map (OMOP)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 Form of (RxNorm)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - " \n", - "
\n", - " \u2514\u2500 ATC to RxNorm/Extension secondary upwards (OMOP)\n", - "
\n", - " \n", - "
\n", - " \u2192 metformin\n", - "
\n", - "
[terminated: shortest_paths_found]
\n", - "
\n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import HTML, display\n", - "\n", - "display(HTML(render_trace(kg, trace)))\n" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a23ad680", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(ranked)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "9411377f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet --[RxNorm is a]--> metformin Oral Product
metformin Oral Product --[RxNorm has ing]--> metformin" - ], - "text/plain": [ - "GraphPath(steps=(PathStep(subject=40163924, predicate='RxNorm is a', object=36223144), PathStep(subject=36223144, predicate='RxNorm has ing', object=1503297)))" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ranked[0]" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "8ef85864", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PathExplanation(path=GraphPath(steps=(PathStep(subject=40163924, predicate='RxNorm is a', object=36223144), PathStep(subject=36223144, predicate='RxNorm has ing', object=1503297))), profile=PathProfile(hops=2, invalid_concepts=0, non_standard_concepts=0, vocab_switches=0, ontological_edges=2, mapping_edges=0, metadata_edges=0), steps=(PathExplanationStep(step=PathStep(subject=40163924, predicate='RxNorm is a', object=36223144), traversal_depth=0, predicate_kind=, reason='ontological relationship (preferred structure)'), PathExplanationStep(step=PathStep(subject=36223144, predicate='RxNorm has ing', object=1503297), traversal_depth=None, predicate_kind=, reason='ontological relationship (preferred structure)')))" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "explain_path(kg, ranked[0], trace)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "c263ab6a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet --[RxNorm is a]--> metformin Oral Product
metformin Oral Product --[RxNorm has ing]--> metformin" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "24 HR metformin hydrochloride 500 MG Extended Release Oral Tablet --[RxNorm is a]--> metformin Pill
metformin Pill --[RxNorm has ing]--> metformin" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "for p in ranked:\n", - " display(HTML(render_path(kg, p)))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f8bb4bf4", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "kg-core (3.12.10)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/notebooks/02_reasoner.ipynb b/notebooks/02_reasoner.ipynb deleted file mode 100644 index 21b5248..0000000 --- a/notebooks/02_reasoner.ipynb +++ /dev/null @@ -1,363 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "12d9d4e5", - "metadata": {}, - "source": [ - "## Constraint-aware semantic resolution over OMOP vocabularies\n", - "\n", - "* Pydantic classes (via linkml) produce structured LLM queries\n", - "* the LLM proposes candidates\n", - "* LinkML defines valid target spaces\n", - "* the KG enforces ontological correctness\n", - "* scoring / ranking resolves ambiguity\n", - "\n", - "### Reasoners\n", - "\n", - "1. Hierarchy reasoner: Strict checks for candidate ⊑ allowed_parent\n", - "2. Domain reasoner: Broader checks simply confirming if this concept is in the correct OMOP domain?\n", - "3. Vocabulary preferences: List of ordered vocab targets \n", - "4. Standardness and validity\n", - "5. Specificity / depth: Out of concepts matching selected restrictions, which is at the most appropriate level of specificity?" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "7a6fd293", - "metadata": {}, - "outputs": [], - "source": [ - "from sqlalchemy.orm import sessionmaker\n", - "from dotenv import load_dotenv\n", - "\n", - "from omop_graph.graph.kg import KnowledgeGraph\n", - "from omop_graph.render import (\n", - " bind_default_renderers,\n", - ")\n", - "from orm_loader.helpers import configure_logging\n", - "from omop_alchemy import get_engine_name\n", - "import sqlalchemy as sa" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "ce23b2dc", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2026-01-12 10:15:20,755 | INFO | sql_loader.omop_alchemy.config | Database engine configured\n" - ] - } - ], - "source": [ - "configure_logging()\n", - "load_dotenv()\n", - "\n", - "engine_string = get_engine_name()\n", - "engine = sa.create_engine(engine_string, future=True, echo=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "934b60ab", - "metadata": {}, - "outputs": [], - "source": [ - "Session = sessionmaker(bind=engine)\n", - "\n", - "session = Session()\n", - "kg = KnowledgeGraph(session)\n", - "bind_default_renderers(kg)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "a7ebd037", - "metadata": {}, - "outputs": [], - "source": [ - "# synonyms = pd.DataFrame(\n", - "# session.query(\n", - "# Concept_Synonym.concept_synonym_name, Concept_Synonym.concept_id, Concept_Synonym.language_concept_id\n", - "# )\n", - "# )" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "ab83927f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(LabelMatch(input_label='heart attack', matched_label='Heart attack', concept_id=35819523, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='heart attack', matched_label='Heart attack', concept_id=36210764, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='heart attack', matched_label='Heart attack', concept_id=45466858, match_kind=, is_standard=False, is_active=True))" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "kg.label_lookup(\"Heart attack\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "4592f774", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=4134723, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=3309601, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary Artery Thrombosis', concept_id=45921297, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=3161942, match_kind=, is_standard=False, is_active=False),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=40623902, match_kind=, is_standard=False, is_active=False)]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sorted(\n", - " kg.label_lookup(\"Coronary artery thrombosis\", fuzzy=False)\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "2d4a8b6f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=4134723, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left anterior descending coronary artery thrombosis', concept_id=4153091, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left main coronary artery thrombosis', concept_id=4209308, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Right main coronary artery thrombosis', concept_id=4304192, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Right main coronary artery thrombosis', concept_id=3169041, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left main coronary artery thrombosis', concept_id=3270140, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=3309601, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left anterior descending coronary artery thrombosis', concept_id=3403562, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary Artery Thrombosis Method Ident', concept_id=37528657, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Right Main Coronary Artery Thrombosis', concept_id=45908931, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left Main Coronary Artery Thrombosis', concept_id=45915508, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary Artery Thrombosis', concept_id=45921297, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left Anterior Descending Coronary Artery Thrombosis', concept_id=45955194, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=3161942, match_kind=, is_standard=False, is_active=False),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=40623902, match_kind=, is_standard=False, is_active=False)]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sorted(\n", - " kg.label_lookup(\"Coronary artery thrombosis\", fuzzy=True)\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "7c964c57", - "metadata": {}, - "outputs": [], - "source": [ - "from omop_graph.reasoning.term_grounding import GroundingConstraints, ground_term\n", - "from omop_graph.reasoning.resolvers import ResolverPipeline, ExactLabelResolver, PartialLabelResolver, ExactSynonymResolver\n", - "from omop_graph.reasoning.term_grounding import _passes_constraints\n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "69726c8e", - "metadata": {}, - "outputs": [], - "source": [ - "constraints = GroundingConstraints(\n", - " parent_ids=(4027255,),#(37153816,), # malignant neoplasm\n", - " allowed_domains=(\"Condition\",), # redundant with parent_ids but perhaps we support combinations across domains eventually for more abstract grounding?\n", - " allowed_vocabularies=(\"SNOMED\", \"ICD10CM\"),\n", - " require_standard=True,\n", - " max_depth=4,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "ec17c091", - "metadata": {}, - "outputs": [], - "source": [ - "resolver_pipeline = ResolverPipeline(\n", - " resolvers=(ExactLabelResolver(),ExactSynonymResolver(),PartialLabelResolver()),\n", - " stop_after_confidence=PartialLabelResolver.confidence\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "b2400dda", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[GroundingCandidate(concept_id=4329847, label='Myocardial infarction', best_path_profile=PathProfile(hops=2, invalid_concepts=0, non_standard_concepts=0, vocab_switches=0, ontological_edges=2, mapping_edges=0, metadata_edges=0), reasons=(), paths=(GraphPath(steps=(PathStep(subject=4329847, predicate='Is a', object=4185932), PathStep(subject=4185932, predicate='Is a', object=4027255))),))]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ground_term(kg, \"heart attack\", constraints=constraints, resolver_pipeline=resolver_pipeline)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "2bc5a61a", - "metadata": {}, - "outputs": [], - "source": [ - "e = ExactLabelResolver()\n", - "p = PartialLabelResolver()\n", - "s = ExactSynonymResolver()" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "45a48125", - "metadata": {}, - "outputs": [], - "source": [ - "hits = e.resolve(kg, \"heart attack\")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "8b45b6eb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Concept ID 35819523 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36210764 passes: False, reasons: [\"domain Meas Value not in ('Condition',)\"]\n", - "Concept ID 45466858 passes: False, reasons: ['vocabulary Read not allowed']\n" - ] - } - ], - "source": [ - "for h in hits:\n", - " ok, reasons = _passes_constraints(kg, h.concept_id, constraints)\n", - " print(f\"Concept ID {h.concept_id} passes: {ok}, reasons: {reasons}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "1dd00c08", - "metadata": {}, - "outputs": [], - "source": [ - "hits = s.resolve(kg, \"heart attack\")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "85322723", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Concept ID 45616764 passes: False, reasons: ['vocabulary MeSH not allowed']\n", - "Concept ID 40398435 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 4329847 passes: True, reasons: []\n", - "Concept ID 40623902 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 4347016 passes: False, reasons: [\"domain Drug not in ('Condition',)\"]\n", - "Concept ID 40345176 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 40323863 passes: False, reasons: ['concept is non-standard']\n" - ] - } - ], - "source": [ - "for h in hits:\n", - " ok, reasons = _passes_constraints(kg, h.concept_id, constraints)\n", - " print(f\"Concept ID {h.concept_id} passes: {ok}, reasons: {reasons}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "b4a7152a", - "metadata": {}, - "outputs": [], - "source": [ - "# 4329847 is the only concept that passes constraints and matches the selected grounding returned above" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c2c96ab4", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "kg-core (3.12.10)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/03_phenotype_helper.ipynb b/notebooks/03_phenotype_helper.ipynb deleted file mode 100644 index 67f913b..0000000 --- a/notebooks/03_phenotype_helper.ipynb +++ /dev/null @@ -1,2319 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "1e074aa3", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import sqlalchemy.orm as so\n", - "import sqlalchemy as sa\n", - "\n", - "from dataclasses import asdict\n", - "from dotenv import load_dotenv\n", - "from orm_loader.helpers import configure_logging\n", - "from omop_alchemy import get_engine_name\n", - "from omop_alchemy.cdm.model.vocabulary import Concept\n", - "\n", - "from omop_graph.graph.kg import KnowledgeGraph\n", - "from omop_graph.render import (\n", - " bind_default_renderers,\n", - ")\n", - "from omop_graph.reasoning.phenotypes import (\n", - " find_common_parents,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "339e4d2f", - "metadata": {}, - "source": [ - "At the moment, this demo is input to a manual process of hierarchy resolution, because there are certainly arguments for allowing impurity (e.g. SNOMED cancer conditions do not meet the original ICDO3-specific filter if there is no corresponding ICDO3 code), as well as for supporting less than full coverage (e.g. valid but unusual/atypical classifications that introduce a lot of noise) - TBD the ideal level of final automation here, but irrespectively it is is still relatively useful for initial orientation steps..." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "33e36012", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2026-01-12 10:18:45,944 | INFO | sql_loader.omop_alchemy.config | Database engine configured\n" - ] - } - ], - "source": [ - "configure_logging()\n", - "load_dotenv()\n", - "\n", - "engine_string = get_engine_name()\n", - "engine = sa.create_engine(engine_string, future=True, echo=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "cb52ef35", - "metadata": {}, - "outputs": [], - "source": [ - "Session = so.sessionmaker(bind=engine)\n", - "\n", - "session = Session()\n", - "kg = KnowledgeGraph(session)\n", - "bind_default_renderers(kg)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "7e8f851d", - "metadata": {}, - "outputs": [], - "source": [ - "seeds = [4173456, 4174251, 4174241, 4172043, 4173614, 4175026] # ecog 0 thru 5\n", - "parent_stats = find_common_parents(seeds=seeds, kg=kg)\n", - "candidate_df = pd.DataFrame([{'concept_id': c} | asdict(v) for c, v in parent_stats.items()])" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "850d6df0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
04308794{4174241, 4172043, 4174251, 4173614, 4173456, ...{4174241, 4172043, 4174251, 4173614, 4173456, ...601.01.0000001
1432453{3655685, 4294662, 4032527, 4032528, 4032529, ...{4174241, 4172043, 4174251, 4173614, 4173456, ...69861.00.0060482
24089214{46284803, 4153350, 4120591, 3661842, 3661843,...{4174241, 4172043, 4174251, 4173614, 4173456, ...641951.00.0014285
34041284{4259853, 4161577, 4161578, 4161579, 4128815, ...{4174241, 4172043, 4174251, 4173614, 4173456, ...671351.00.0008405
44094294{4259853, 4161577, 4161578, 4161579, 4227116, ...{4174241, 4172043, 4174251, 4173614, 4173456, ...6144501.00.0004154
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "0 4308794 {4174241, 4172043, 4174251, 4173614, 4173456, ... \n", - "1 432453 {3655685, 4294662, 4032527, 4032528, 4032529, ... \n", - "2 4089214 {46284803, 4153350, 4120591, 3661842, 3661843,... \n", - "3 4041284 {4259853, 4161577, 4161578, 4161579, 4128815, ... \n", - "4 4094294 {4259853, 4161577, 4161578, 4161579, 4227116, ... \n", - "\n", - " found coverage pollution \\\n", - "0 {4174241, 4172043, 4174251, 4173614, 4173456, ... 6 0 \n", - "1 {4174241, 4172043, 4174251, 4173614, 4173456, ... 6 986 \n", - "2 {4174241, 4172043, 4174251, 4173614, 4173456, ... 6 4195 \n", - "3 {4174241, 4172043, 4174251, 4173614, 4173456, ... 6 7135 \n", - "4 {4174241, 4172043, 4174251, 4173614, 4173456, ... 6 14450 \n", - "\n", - " completeness purity max_depth \n", - "0 1.0 1.000000 1 \n", - "1 1.0 0.006048 2 \n", - "2 1.0 0.001428 5 \n", - "3 1.0 0.000840 5 \n", - "4 1.0 0.000415 4 " - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "candidate_df" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "1266d2fd", - "metadata": {}, - "outputs": [], - "source": [ - "seeds = [4230556, 4085075, 434489, 4173456, 4174251, 4174241, 4172043, 4173614, 4175026] # alive, dead, at risk\n", - "parent_stats = find_common_parents(seeds=seeds, kg=kg)\n", - "candidate_df = pd.DataFrame([{'concept_id': c} | asdict(v) for c, v in parent_stats.items()])" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f6d165e9", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
0432453{3655685, 4294662, 46270485, 35610650, 4202524...{4174241, 4172043, 4174251, 4173614, 4173456, ...95381.0000000.0164532
14308794{4174241, 4172043, 4174251, 4173614, 4173456, ...{4174241, 4172043, 4174251, 4173614, 4173456, ...600.6666671.0000001
24089214{46284803, 4153350, 4120591, 3661842, 3661843,...{4174241, 4172043, 4174251, 4173614, 4173456, ...641950.6666670.0014285
34041284{4259853, 4161577, 4161578, 4161579, 4128815, ...{4174241, 4172043, 4174251, 4173614, 4173456, ...671350.6666670.0008405
44094294{4259853, 4161577, 4161578, 4161579, 4227116, ...{4174241, 4172043, 4174251, 4173614, 4173456, ...6144500.6666670.0004154
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "0 432453 {3655685, 4294662, 46270485, 35610650, 4202524... \n", - "1 4308794 {4174241, 4172043, 4174251, 4173614, 4173456, ... \n", - "2 4089214 {46284803, 4153350, 4120591, 3661842, 3661843,... \n", - "3 4041284 {4259853, 4161577, 4161578, 4161579, 4128815, ... \n", - "4 4094294 {4259853, 4161577, 4161578, 4161579, 4227116, ... \n", - "\n", - " found coverage pollution \\\n", - "0 {4174241, 4172043, 4174251, 4173614, 4173456, ... 9 538 \n", - "1 {4174241, 4172043, 4174251, 4173614, 4173456, ... 6 0 \n", - "2 {4174241, 4172043, 4174251, 4173614, 4173456, ... 6 4195 \n", - "3 {4174241, 4172043, 4174251, 4173614, 4173456, ... 6 7135 \n", - "4 {4174241, 4172043, 4174251, 4173614, 4173456, ... 6 14450 \n", - "\n", - " completeness purity max_depth \n", - "0 1.000000 0.016453 2 \n", - "1 0.666667 1.000000 1 \n", - "2 0.666667 0.001428 5 \n", - "3 0.666667 0.000840 5 \n", - "4 0.666667 0.000415 4 " - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "candidate_df" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3447f842", - "metadata": {}, - "outputs": [], - "source": [ - "head_neck_conditions = [Concept.concept_code.ilike(f'%C{k:02d}%') for k in range(0, 15)]\n", - "head_neck_topog = [Concept.concept_code==f'C{k:02d}' for k in range(0, 15)]" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "159dafbd", - "metadata": {}, - "outputs": [], - "source": [ - "topog_concepts = pd.DataFrame(\n", - " session.query(\n", - " *Concept.__table__.columns\n", - " )\n", - " .filter(Concept.vocabulary_id=='ICDO3')\n", - " .filter(sa.or_(*head_neck_topog))\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "64c9da45", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "cac82637", - "metadata": {}, - "outputs": [], - "source": [ - "hn_concepts = pd.DataFrame(\n", - " session.query(\n", - " *Concept.__table__.columns\n", - " )\n", - " .filter(Concept.vocabulary_id=='ICDO3')\n", - " .filter(Concept.concept_class_id=='ICDO Condition')\n", - " .filter(sa.or_(*head_neck_conditions))\n", - " .filter(Concept.concept_code.like('%/3-%'))\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "87a0af78", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C00: Lip (734 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
159135858{36528135, 36519943, 36536330, 4145173, 365609...{36562945, 36532227, 36546565, 36517893, 36519...7187750.9782020.4809117
1364116808{36528135, 36519943, 36536330, 36560921, 41779...{36562945, 36532227, 36546565, 36517893, 36519...7186940.9782020.5084996
13537161350{36519943, 36528135, 36536330, 36560921, 36544...{36562945, 36532227, 36546565, 36517893, 36519...7185200.9782020.5799686
1014054605{602113, 4313090, 602115, 36532227, 602114, 36...{36562945, 36532227, 36546565, 36517893, 36519...7185090.9782020.5851675
544180779{602113, 4313090, 602115, 36532227, 602114, 36...{36562945, 36532227, 36546565, 36517893, 36519...7182400.9782020.7494784
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "159 135858 {36528135, 36519943, 36536330, 4145173, 365609... \n", - "136 4116808 {36528135, 36519943, 36536330, 36560921, 41779... \n", - "135 37161350 {36519943, 36528135, 36536330, 36560921, 36544... \n", - "101 4054605 {602113, 4313090, 602115, 36532227, 602114, 36... \n", - "54 4180779 {602113, 4313090, 602115, 36532227, 602114, 36... \n", - "\n", - " found coverage pollution \\\n", - "159 {36562945, 36532227, 36546565, 36517893, 36519... 718 775 \n", - "136 {36562945, 36532227, 36546565, 36517893, 36519... 718 694 \n", - "135 {36562945, 36532227, 36546565, 36517893, 36519... 718 520 \n", - "101 {36562945, 36532227, 36546565, 36517893, 36519... 718 509 \n", - "54 {36562945, 36532227, 36546565, 36517893, 36519... 718 240 \n", - "\n", - " completeness purity max_depth \n", - "159 0.978202 0.480911 7 \n", - "136 0.978202 0.508499 6 \n", - "135 0.978202 0.579968 6 \n", - "101 0.978202 0.585167 5 \n", - "54 0.978202 0.749478 4 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C01: Base of tongue (144 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
694178962{36560896, 36528130, 36552707, 36524036, 36536...{36566017, 36562435, 36561419, 36555283, 36536...14210330.9861110.1208514
33437498{36560896, 36528130, 36552707, 36536324, 36524...{36566017, 36562435, 36561419, 36555283, 36536...1429850.9861110.1259983
874054607{36566017, 4248065, 36562435, 4158473, 3656141...{36566017, 36562435, 36561419, 36555283, 36536...142400.9861110.7802204
344181332{36566017, 4248065, 36562435, 4158473, 3656141...{36566017, 36562435, 36561419, 36555283, 36536...142130.9861110.9161293
0256633{36566017, 36562435, 36561419, 36555283, 36536...{36566017, 36562435, 36561419, 36555283, 36536...14240.9861110.9726032
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "69 4178962 {36560896, 36528130, 36552707, 36524036, 36536... \n", - "33 437498 {36560896, 36528130, 36552707, 36536324, 36524... \n", - "87 4054607 {36566017, 4248065, 36562435, 4158473, 3656141... \n", - "34 4181332 {36566017, 4248065, 36562435, 4158473, 3656141... \n", - "0 256633 {36566017, 36562435, 36561419, 36555283, 36536... \n", - "\n", - " found coverage pollution \\\n", - "69 {36566017, 36562435, 36561419, 36555283, 36536... 142 1033 \n", - "33 {36566017, 36562435, 36561419, 36555283, 36536... 142 985 \n", - "87 {36566017, 36562435, 36561419, 36555283, 36536... 142 40 \n", - "34 {36566017, 36562435, 36561419, 36555283, 36536... 142 13 \n", - "0 {36566017, 36562435, 36561419, 36555283, 36536... 142 4 \n", - "\n", - " completeness purity max_depth \n", - "69 0.986111 0.120851 4 \n", - "33 0.986111 0.125998 3 \n", - "87 0.986111 0.780220 4 \n", - "34 0.986111 0.916129 3 \n", - "0 0.986111 0.972603 2 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C02: Other and unspecified parts of tongue (972 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
204432941{36560896, 36528130, 36552707, 36536324, 36519...{36560896, 36528130, 36552707, 36536324, 36524...9625550.9897120.6341468
1764103982{36560896, 36528130, 36552707, 36536324, 36519...{36560896, 36528130, 36552707, 36536324, 36524...9624680.9897120.6727277
17740481371{36560896, 36528130, 36552707, 36536324, 36519...{36560896, 36528130, 36552707, 36536324, 36524...9624140.9897120.6991287
1394130375{36560896, 36528130, 36552707, 36536324, 36519...{36560896, 36528130, 36552707, 36536324, 36524...9624050.9897120.7037316
854178962{36560896, 36528130, 36552707, 36536324, 36524...{36560896, 36528130, 36552707, 36536324, 36524...9622130.9897120.8187235
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "204 432941 {36560896, 36528130, 36552707, 36536324, 36519... \n", - "176 4103982 {36560896, 36528130, 36552707, 36536324, 36519... \n", - "177 40481371 {36560896, 36528130, 36552707, 36536324, 36519... \n", - "139 4130375 {36560896, 36528130, 36552707, 36536324, 36519... \n", - "85 4178962 {36560896, 36528130, 36552707, 36536324, 36524... \n", - "\n", - " found coverage pollution \\\n", - "204 {36560896, 36528130, 36552707, 36536324, 36524... 962 555 \n", - "176 {36560896, 36528130, 36552707, 36536324, 36524... 962 468 \n", - "177 {36560896, 36528130, 36552707, 36536324, 36524... 962 414 \n", - "139 {36560896, 36528130, 36552707, 36536324, 36524... 962 405 \n", - "85 {36560896, 36528130, 36552707, 36536324, 36524... 962 213 \n", - "\n", - " completeness purity max_depth \n", - "204 0.989712 0.634146 8 \n", - "176 0.989712 0.672727 7 \n", - "177 0.989712 0.699128 7 \n", - "139 0.989712 0.703731 6 \n", - "85 0.989712 0.818723 5 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C03: Gum (456 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
844246124{36546563, 36540420, 36548612, 602118, 602126,...{36540420, 36548612, 36558862, 36544539, 44503...4501650.9868420.7317073
15437163090{36548612, 36540420, 36548614, 602118, 602126,...{36548612, 36540420, 36558862, 36544539, 44503...4501300.9868420.7758625
1294054612{36548612, 36540420, 36548614, 602118, 3655886...{36548612, 36540420, 36558862, 36544539, 44503...4501120.9868420.8007124
824178963{36548612, 36540420, 602118, 602126, 36558862,...{36548612, 36540420, 36558862, 36544539, 44503...450260.9868420.9453783
6140950{36540420, 36548612, 602118, 36558862, 440335,...{36540420, 36548612, 36558862, 36544539, 44503...450100.9868420.9782612
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "84 4246124 {36546563, 36540420, 36548612, 602118, 602126,... \n", - "154 37163090 {36548612, 36540420, 36548614, 602118, 602126,... \n", - "129 4054612 {36548612, 36540420, 36548614, 602118, 3655886... \n", - "82 4178963 {36548612, 36540420, 602118, 602126, 36558862,... \n", - "6 140950 {36540420, 36548612, 602118, 36558862, 440335,... \n", - "\n", - " found coverage pollution \\\n", - "84 {36540420, 36548612, 36558862, 36544539, 44503... 450 165 \n", - "154 {36548612, 36540420, 36558862, 36544539, 44503... 450 130 \n", - "129 {36548612, 36540420, 36558862, 36544539, 44503... 450 112 \n", - "82 {36548612, 36540420, 36558862, 36544539, 44503... 450 26 \n", - "6 {36540420, 36548612, 36558862, 36544539, 44503... 450 10 \n", - "\n", - " completeness purity max_depth \n", - "84 0.986842 0.731707 3 \n", - "154 0.986842 0.775862 5 \n", - "129 0.986842 0.800712 4 \n", - "82 0.986842 0.945378 3 \n", - "6 0.986842 0.978261 2 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C04: Floor of mouth (608 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
744247357{36552704, 36560896, 36528130, 36552707, 36536...{36556800, 36521986, 44498948, 36536325, 36556...60137950.9884870.1367153
75439404{36552704, 36560896, 36528130, 36552707, 36536...{36556800, 36521986, 44498948, 36536325, 36556...60136260.9884870.1421813
1214130381{36556800, 36521986, 44498948, 36536325, 36556...{36556800, 36521986, 44498948, 36536325, 36556...6013210.9884870.6518444
764177101{36556800, 36521986, 44498948, 36536325, 36556...{36556800, 36521986, 44498948, 36536325, 36556...6012010.9884870.7493773
0135750{36556800, 36521986, 44498948, 36536325, 36556...{36556800, 36521986, 44498948, 36536325, 36556...6011790.9884870.7705132
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "74 4247357 {36552704, 36560896, 36528130, 36552707, 36536... \n", - "75 439404 {36552704, 36560896, 36528130, 36552707, 36536... \n", - "121 4130381 {36556800, 36521986, 44498948, 36536325, 36556... \n", - "76 4177101 {36556800, 36521986, 44498948, 36536325, 36556... \n", - "0 135750 {36556800, 36521986, 44498948, 36536325, 36556... \n", - "\n", - " found coverage pollution \\\n", - "74 {36556800, 36521986, 44498948, 36536325, 36556... 601 3795 \n", - "75 {36556800, 36521986, 44498948, 36536325, 36556... 601 3626 \n", - "121 {36556800, 36521986, 44498948, 36536325, 36556... 601 321 \n", - "76 {36556800, 36521986, 44498948, 36536325, 36556... 601 201 \n", - "0 {36556800, 36521986, 44498948, 36536325, 36556... 601 179 \n", - "\n", - " completeness purity max_depth \n", - "74 0.988487 0.136715 3 \n", - "75 0.988487 0.142181 3 \n", - "121 0.988487 0.651844 4 \n", - "76 0.988487 0.749377 3 \n", - "0 0.988487 0.770513 2 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C05: Palate (762 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
1664091365{602112, 36562944, 42512387, 45768712, 3653018...{42512387, 36530184, 44501004, 36550670, 36524...7514480.9855640.6263557
1504028251{602112, 36562944, 42512387, 45768712, 3653018...{42512387, 36530184, 44501004, 36550670, 36524...7514240.9855640.6391496
15137162644{602112, 36562944, 42512387, 36530184, 602123,...{42512387, 36530184, 44501004, 36550670, 36524...7511800.9855640.8066606
1264131604{602112, 36562944, 42512387, 36530184, 602123,...{42512387, 36530184, 44501004, 36550670, 36524...7511660.9855640.8189755
854178964{602112, 42512387, 36530184, 602123, 44501004,...{42512387, 36530184, 44501004, 36550670, 36524...751400.9855640.9494314
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "166 4091365 {602112, 36562944, 42512387, 45768712, 3653018... \n", - "150 4028251 {602112, 36562944, 42512387, 45768712, 3653018... \n", - "151 37162644 {602112, 36562944, 42512387, 36530184, 602123,... \n", - "126 4131604 {602112, 36562944, 42512387, 36530184, 602123,... \n", - "85 4178964 {602112, 42512387, 36530184, 602123, 44501004,... \n", - "\n", - " found coverage pollution \\\n", - "166 {42512387, 36530184, 44501004, 36550670, 36524... 751 448 \n", - "150 {42512387, 36530184, 44501004, 36550670, 36524... 751 424 \n", - "151 {42512387, 36530184, 44501004, 36550670, 36524... 751 180 \n", - "126 {42512387, 36530184, 44501004, 36550670, 36524... 751 166 \n", - "85 {42512387, 36530184, 44501004, 36550670, 36524... 751 40 \n", - "\n", - " completeness purity max_depth \n", - "166 0.985564 0.626355 7 \n", - "150 0.985564 0.639149 6 \n", - "151 0.985564 0.806660 6 \n", - "126 0.985564 0.818975 5 \n", - "85 0.985564 0.949431 4 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C06: Other and unspecified parts of mouth (761 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
1774307122{36536324, 44498948, 36536325, 44498950, 36536...{36552704, 36538370, 44500996, 36540422, 36560...75484750.9908020.0816998
1504042502{36536324, 44498948, 36536325, 44498950, 36536...{36552704, 36538370, 44500996, 36540422, 36560...75479780.9908020.0863497
9343530620{36536324, 44498948, 36536325, 44498950, 36536...{36552704, 36538370, 44500996, 36540422, 36560...75463990.9908020.1054106
1924184252{44498945, 4096016, 4194323, 36536341, 4449896...{36552704, 36538370, 44500996, 36540422, 36560...75460230.9908020.1112597
64130986{36536324, 44498948, 36536325, 44498950, 36536...{36552704, 36538370, 44500996, 36540422, 36560...75453760.9908020.1230025
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "177 4307122 {36536324, 44498948, 36536325, 44498950, 36536... \n", - "150 4042502 {36536324, 44498948, 36536325, 44498950, 36536... \n", - "93 43530620 {36536324, 44498948, 36536325, 44498950, 36536... \n", - "192 4184252 {44498945, 4096016, 4194323, 36536341, 4449896... \n", - "6 4130986 {36536324, 44498948, 36536325, 44498950, 36536... \n", - "\n", - " found coverage pollution \\\n", - "177 {36552704, 36538370, 44500996, 36540422, 36560... 754 8475 \n", - "150 {36552704, 36538370, 44500996, 36540422, 36560... 754 7978 \n", - "93 {36552704, 36538370, 44500996, 36540422, 36560... 754 6399 \n", - "192 {36552704, 36538370, 44500996, 36540422, 36560... 754 6023 \n", - "6 {36552704, 36538370, 44500996, 36540422, 36560... 754 5376 \n", - "\n", - " completeness purity max_depth \n", - "177 0.990802 0.081699 8 \n", - "150 0.990802 0.086349 7 \n", - "93 0.990802 0.105410 6 \n", - "192 0.990802 0.111259 7 \n", - "6 0.990802 0.123002 5 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C07: Parotid glanid (191 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
1084322566{44498950, 42512393, 36521996, 4096013, 365445...{42512896, 42511876, 44498950, 36544007, 44500...1859280.9685860.1662177
904122227{44498950, 42512393, 36521996, 36544530, 44498...{42512896, 42511876, 44498950, 36544007, 44500...1859020.9685860.1701936
914054610{44498950, 42512393, 36521996, 36544530, 44498...{42512896, 42511876, 44498950, 36544007, 44500...1858620.9685860.1766955
924202556{42512896, 4025347, 42511876, 44498950, 365440...{42512896, 42511876, 44498950, 36544007, 44500...185580.9685860.7613175
684130984{42512896, 42511876, 44498950, 36544007, 44500...{42512896, 42511876, 44498950, 36544007, 44500...185480.9685860.7939914
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "108 4322566 {44498950, 42512393, 36521996, 4096013, 365445... \n", - "90 4122227 {44498950, 42512393, 36521996, 36544530, 44498... \n", - "91 4054610 {44498950, 42512393, 36521996, 36544530, 44498... \n", - "92 4202556 {42512896, 4025347, 42511876, 44498950, 365440... \n", - "68 4130984 {42512896, 42511876, 44498950, 36544007, 44500... \n", - "\n", - " found coverage pollution \\\n", - "108 {42512896, 42511876, 44498950, 36544007, 44500... 185 928 \n", - "90 {42512896, 42511876, 44498950, 36544007, 44500... 185 902 \n", - "91 {42512896, 42511876, 44498950, 36544007, 44500... 185 862 \n", - "92 {42512896, 42511876, 44498950, 36544007, 44500... 185 58 \n", - "68 {42512896, 42511876, 44498950, 36544007, 44500... 185 48 \n", - "\n", - " completeness purity max_depth \n", - "108 0.968586 0.166217 7 \n", - "90 0.968586 0.170193 6 \n", - "91 0.968586 0.176695 5 \n", - "92 0.968586 0.761317 5 \n", - "68 0.968586 0.793991 4 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C08: Other and unspecified major salivary glands (699 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
19625572{44498950, 42512393, 36521996, 4096013, 409601...{36544530, 36562967, 36562968, 36528152, 36567...6885260.9842630.5667227
1774114483{44498950, 42512393, 36521996, 4096013, 409601...{36544530, 36562967, 36562968, 36528152, 36567...6884410.9842630.6093896
1784322566{44498950, 42512393, 36521996, 4096013, 365445...{36544530, 36562967, 36562968, 36528152, 36567...6884250.9842630.6181496
1554122227{44498950, 42512393, 36521996, 36544530, 44498...{36544530, 36562967, 36562968, 36528152, 36567...6883990.9842630.6329355
1304054610{44498950, 42512393, 36521996, 36544530, 44498...{36544530, 36562967, 36562968, 36528152, 36567...6883590.9842630.6571164
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "196 25572 {44498950, 42512393, 36521996, 4096013, 409601... \n", - "177 4114483 {44498950, 42512393, 36521996, 4096013, 409601... \n", - "178 4322566 {44498950, 42512393, 36521996, 4096013, 365445... \n", - "155 4122227 {44498950, 42512393, 36521996, 36544530, 44498... \n", - "130 4054610 {44498950, 42512393, 36521996, 36544530, 44498... \n", - "\n", - " found coverage pollution \\\n", - "196 {36544530, 36562967, 36562968, 36528152, 36567... 688 526 \n", - "177 {36544530, 36562967, 36562968, 36528152, 36567... 688 441 \n", - "178 {36544530, 36562967, 36562968, 36528152, 36567... 688 425 \n", - "155 {36544530, 36562967, 36562968, 36528152, 36567... 688 399 \n", - "130 {36544530, 36562967, 36562968, 36528152, 36567... 688 359 \n", - "\n", - " completeness purity max_depth \n", - "196 0.984263 0.566722 7 \n", - "177 0.984263 0.609389 6 \n", - "178 0.984263 0.618149 6 \n", - "155 0.984263 0.632935 5 \n", - "130 0.984263 0.657116 4 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C09: Tonsil (673 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
2044177243{44498945, 36560900, 36519941, 36528141, 36560...{36519941, 36562956, 36524045, 36530193, 36532...66633890.9895990.1642427
2734054510{36519941, 36560915, 36536342, 36560929, 41779...{36519941, 36562956, 36524045, 36530193, 36532...66614630.9895990.3128237
24146270500{36519941, 36560915, 36536342, 36560929, 36519...{36519941, 36562956, 36524045, 36530193, 36532...66614060.9895990.3214297
2054054617{36519941, 36560915, 36536342, 36560929, 36519...{36519941, 36562956, 36524045, 36530193, 36532...66613510.9895990.3301936
1174181338{36519941, 36560915, 36536342, 36560929, 36519...{36519941, 36562956, 36524045, 36530193, 36532...66611030.9895990.3764846
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "204 4177243 {44498945, 36560900, 36519941, 36528141, 36560... \n", - "273 4054510 {36519941, 36560915, 36536342, 36560929, 41779... \n", - "241 46270500 {36519941, 36560915, 36536342, 36560929, 36519... \n", - "205 4054617 {36519941, 36560915, 36536342, 36560929, 36519... \n", - "117 4181338 {36519941, 36560915, 36536342, 36560929, 36519... \n", - "\n", - " found coverage pollution \\\n", - "204 {36519941, 36562956, 36524045, 36530193, 36532... 666 3389 \n", - "273 {36519941, 36562956, 36524045, 36530193, 36532... 666 1463 \n", - "241 {36519941, 36562956, 36524045, 36530193, 36532... 666 1406 \n", - "205 {36519941, 36562956, 36524045, 36530193, 36532... 666 1351 \n", - "117 {36519941, 36562956, 36524045, 36530193, 36532... 666 1103 \n", - "\n", - " completeness purity max_depth \n", - "204 0.989599 0.164242 7 \n", - "273 0.989599 0.312823 7 \n", - "241 0.989599 0.321429 7 \n", - "205 0.989599 0.330193 6 \n", - "117 0.989599 0.376484 6 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C10: Oropharynx (1174 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
1234274025{4194320, 4194323, 4194328, 4194355, 4194365, ...{36556803, 36554756, 36562951, 36558856, 36540...11451652280.9752980.00688212
1244102111{36522894, 36403062, 36403063, 42598455, 42598...{36556803, 36554756, 36562951, 36558856, 36540...1145687640.9752980.01637811
2234266186{36403062, 36403063, 42598455, 42598456, 42598...{36556803, 36554756, 36562951, 36558856, 36540...1145684950.9752980.0164429
1438112{36403062, 36403063, 42598456, 42598461, 36403...{36556803, 36554756, 36562951, 36558856, 36540...1145681270.9752980.01652910
122443392{36524322, 42598456, 36559370, 36403071, 42598...{36556803, 36554756, 36562951, 36558856, 36540...1145550710.9752980.0203689
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "123 4274025 {4194320, 4194323, 4194328, 4194355, 4194365, ... \n", - "124 4102111 {36522894, 36403062, 36403063, 42598455, 42598... \n", - "223 4266186 {36403062, 36403063, 42598455, 42598456, 42598... \n", - "1 438112 {36403062, 36403063, 42598456, 42598461, 36403... \n", - "122 443392 {36524322, 42598456, 36559370, 36403071, 42598... \n", - "\n", - " found coverage pollution \\\n", - "123 {36556803, 36554756, 36562951, 36558856, 36540... 1145 165228 \n", - "124 {36556803, 36554756, 36562951, 36558856, 36540... 1145 68764 \n", - "223 {36556803, 36554756, 36562951, 36558856, 36540... 1145 68495 \n", - "1 {36556803, 36554756, 36562951, 36558856, 36540... 1145 68127 \n", - "122 {36556803, 36554756, 36562951, 36558856, 36540... 1145 55071 \n", - "\n", - " completeness purity max_depth \n", - "123 0.975298 0.006882 12 \n", - "124 0.975298 0.016378 11 \n", - "223 0.975298 0.016442 9 \n", - "1 0.975298 0.016529 10 \n", - "122 0.975298 0.020368 9 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C11: Nasopharynx (976 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
187435836{44503040, 36526083, 36560900, 36524041, 60212...{44503040, 36526083, 36560900, 36524041, 36542...9612450.9846310.7968498
16544783160{44503040, 36526083, 36560900, 36524041, 60212...{44503040, 36526083, 36560900, 36524041, 36542...9612110.9846310.8199667
1644181960{44503040, 36526083, 36560900, 36524041, 60212...{44503040, 36526083, 36560900, 36524041, 36542...9611950.9846310.8313157
1364054514{44503040, 36526083, 36560900, 36524041, 60212...{44503040, 36526083, 36560900, 36524041, 36542...9611850.9846310.8385696
964095312{44503040, 36526083, 36560900, 36524041, 60212...{44503040, 36526083, 36560900, 36524041, 36542...961700.9846310.9321055
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "187 435836 {44503040, 36526083, 36560900, 36524041, 60212... \n", - "165 44783160 {44503040, 36526083, 36560900, 36524041, 60212... \n", - "164 4181960 {44503040, 36526083, 36560900, 36524041, 60212... \n", - "136 4054514 {44503040, 36526083, 36560900, 36524041, 60212... \n", - "96 4095312 {44503040, 36526083, 36560900, 36524041, 60212... \n", - "\n", - " found coverage pollution \\\n", - "187 {44503040, 36526083, 36560900, 36524041, 36542... 961 245 \n", - "165 {44503040, 36526083, 36560900, 36524041, 36542... 961 211 \n", - "164 {44503040, 36526083, 36560900, 36524041, 36542... 961 195 \n", - "136 {44503040, 36526083, 36560900, 36524041, 36542... 961 185 \n", - "96 {44503040, 36526083, 36560900, 36524041, 36542... 961 70 \n", - "\n", - " completeness purity max_depth \n", - "187 0.984631 0.796849 8 \n", - "165 0.984631 0.819966 7 \n", - "164 0.984631 0.831315 7 \n", - "136 0.984631 0.838569 6 \n", - "96 0.984631 0.932105 5 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C12: Pyriform sinus (134 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
464181342{44498945, 36534274, 36558851, 36517896, 36556...{44498945, 36567557, 36567567, 36539926, 36534...1337100.9925370.1577705
31439746{44498945, 36534274, 36558851, 36517896, 36556...{44498945, 36567557, 36567567, 36539926, 36534...1336760.9925370.1644004
384129252{44498945, 36560901, 36567557, 36567567, 43156...{44498945, 36567557, 36567567, 36539926, 36534...133310.9925370.8109764
324180789{44498945, 36567557, 36567567, 4315668, 365399...{44498945, 36567557, 36567567, 36539926, 36534...13350.9925370.9637684
1435190{44498945, 36567557, 36567567, 36539926, 36534...{44498945, 36567557, 36567567, 36539926, 36534...13310.9925370.9925372
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "46 4181342 {44498945, 36534274, 36558851, 36517896, 36556... \n", - "31 439746 {44498945, 36534274, 36558851, 36517896, 36556... \n", - "38 4129252 {44498945, 36560901, 36567557, 36567567, 43156... \n", - "32 4180789 {44498945, 36567557, 36567567, 4315668, 365399... \n", - "1 435190 {44498945, 36567557, 36567567, 36539926, 36534... \n", - "\n", - " found coverage pollution \\\n", - "46 {44498945, 36567557, 36567567, 36539926, 36534... 133 710 \n", - "31 {44498945, 36567557, 36567567, 36539926, 36534... 133 676 \n", - "38 {44498945, 36567557, 36567567, 36539926, 36534... 133 31 \n", - "32 {44498945, 36567557, 36567567, 36539926, 36534... 133 5 \n", - "1 {44498945, 36567557, 36567567, 36539926, 36534... 133 1 \n", - "\n", - " completeness purity max_depth \n", - "46 0.992537 0.157770 5 \n", - "31 0.992537 0.164400 4 \n", - "38 0.992537 0.810976 4 \n", - "32 0.992537 0.963768 4 \n", - "1 0.992537 0.992537 2 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C13: Hypopharynx (669 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
2494129247{44498945, 36534274, 36558851, 36560901, 36517...{36534274, 36558851, 36517896, 36556809, 36548...6513780.9730940.6326538
21244783159{44498945, 36534274, 36558851, 36560901, 36517...{36534274, 36558851, 36517896, 36556809, 36548...6513660.9730940.6401188
1544129250{44498945, 36534274, 36558851, 36560901, 36517...{36534274, 36558851, 36517896, 36556809, 36548...6513590.9730940.6445546
994181342{44498945, 36534274, 36558851, 36517896, 36556...{36534274, 36558851, 36517896, 36556809, 36548...6511920.9730940.7722425
4439746{44498945, 36534274, 36558851, 36517896, 36556...{36534274, 36558851, 36517896, 36556809, 36548...6511580.9730940.8046974
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "249 4129247 {44498945, 36534274, 36558851, 36560901, 36517... \n", - "212 44783159 {44498945, 36534274, 36558851, 36560901, 36517... \n", - "154 4129250 {44498945, 36534274, 36558851, 36560901, 36517... \n", - "99 4181342 {44498945, 36534274, 36558851, 36517896, 36556... \n", - "4 439746 {44498945, 36534274, 36558851, 36517896, 36556... \n", - "\n", - " found coverage pollution \\\n", - "249 {36534274, 36558851, 36517896, 36556809, 36548... 651 378 \n", - "212 {36534274, 36558851, 36517896, 36556809, 36548... 651 366 \n", - "154 {36534274, 36558851, 36517896, 36556809, 36548... 651 359 \n", - "99 {36534274, 36558851, 36517896, 36556809, 36548... 651 192 \n", - "4 {36534274, 36558851, 36517896, 36556809, 36548... 651 158 \n", - "\n", - " completeness purity max_depth \n", - "249 0.973094 0.632653 8 \n", - "212 0.973094 0.640118 8 \n", - "154 0.973094 0.644554 6 \n", - "99 0.973094 0.772242 5 \n", - "4 0.973094 0.804697 4 " - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "C14: Other and ill-defined sites in lip, oral cavity and pharynx (384 candidates)\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
concept_iddescendantsfoundcoveragepollutioncompletenesspuritymax_depth
1324115106{42598582, 42598955, 42599092, 42599192, 43266...{36564997, 36526091, 36528139, 36556815, 36522...379225280.9869790.0165457
174111017{4194462, 42598582, 4194827, 132258, 42599689,...{36564997, 36526091, 36528139, 36556815, 36522...379224190.9869790.0166247
924028253{42598582, 42599844, 132565, 132573, 42599972,...{36564997, 36526091, 36528139, 36556815, 36522...379221010.9869790.0168596
18196931{44498945, 36536321, 44498948, 36536324, 44498...{36564997, 36526091, 36528139, 36556815, 36522...379187880.9869790.0197745
1284184252{44498945, 4096016, 4194323, 36536341, 4449896...{36564997, 36526091, 36528139, 36556815, 36522...37960490.9869790.0589619
\n", - "
" - ], - "text/plain": [ - " concept_id descendants \\\n", - "132 4115106 {42598582, 42598955, 42599092, 42599192, 43266... \n", - "17 4111017 {4194462, 42598582, 4194827, 132258, 42599689,... \n", - "92 4028253 {42598582, 42599844, 132565, 132573, 42599972,... \n", - "18 196931 {44498945, 36536321, 44498948, 36536324, 44498... \n", - "128 4184252 {44498945, 4096016, 4194323, 36536341, 4449896... \n", - "\n", - " found coverage pollution \\\n", - "132 {36564997, 36526091, 36528139, 36556815, 36522... 379 22528 \n", - "17 {36564997, 36526091, 36528139, 36556815, 36522... 379 22419 \n", - "92 {36564997, 36526091, 36528139, 36556815, 36522... 379 22101 \n", - "18 {36564997, 36526091, 36528139, 36556815, 36522... 379 18788 \n", - "128 {36564997, 36526091, 36528139, 36556815, 36522... 379 6049 \n", - "\n", - " completeness purity max_depth \n", - "132 0.986979 0.016545 7 \n", - "17 0.986979 0.016624 7 \n", - "92 0.986979 0.016859 6 \n", - "18 0.986979 0.019774 5 \n", - "128 0.986979 0.058961 9 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "all_parents = {}\n", - "all_candidates = {}\n", - "\n", - "for topography in topog_concepts.itertuples():\n", - " code = topography.concept_code\n", - " seeds = set(hn_concepts[hn_concepts.concept_code.str.contains(code)&hn_concepts.invalid_reason.isna()].concept_id)\n", - " print(f\"{code}: {topography.concept_name} ({len(seeds)} candidates)\")\n", - " all_parents[code] = find_common_parents(seeds=seeds, kg=kg)\n", - " all_candidates[code] = pd.DataFrame([{'concept_id': c} | asdict(v) for c, v in all_parents[code].items()])\n", - " best_result = all_candidates[code].sort_values(['coverage', 'purity']).tail(5)\n", - " display(best_result)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2ff820af", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "kg-core (3.12.10)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/04_mapping_support.ipynb b/notebooks/04_mapping_support.ipynb deleted file mode 100644 index 1543f24..0000000 --- a/notebooks/04_mapping_support.ipynb +++ /dev/null @@ -1,16 +0,0 @@ -{ - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.12.0" - } - }, - "cells": [] -} \ No newline at end of file diff --git a/notebooks/99_figures.ipynb b/notebooks/99_figures.ipynb deleted file mode 100644 index 85911f0..0000000 --- a/notebooks/99_figures.ipynb +++ /dev/null @@ -1,4930 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "12d9d4e5", - "metadata": {}, - "source": [ - "## Constraint-aware semantic resolution over OMOP vocabularies\n", - "\n", - "* Pydantic classes (via linkml) produce structured LLM queries\n", - "* the LLM proposes candidates\n", - "* LinkML defines valid target spaces\n", - "* the KG enforces ontological correctness\n", - "* scoring / ranking resolves ambiguity\n", - "\n", - "### Reasoners\n", - "\n", - "1. Hierarchy reasoner: Strict checks for candidate ⊑ allowed_parent\n", - "2. Domain reasoner: Broader checks simply confirming if this concept is in the correct OMOP domain?\n", - "3. Vocabulary preferences: List of ordered vocab targets \n", - "4. Standardness and validity\n", - "5. Specificity / depth: Out of concepts matching selected restrictions, which is at the most appropriate level of specificity?" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "7a6fd293", - "metadata": {}, - "outputs": [], - "source": [ - "from sqlalchemy.orm import sessionmaker\n", - "from dotenv import load_dotenv\n", - "\n", - "from omop_graph.graph.kg import KnowledgeGraph\n", - "from omop_graph.render import (\n", - " bind_default_renderers,\n", - ")\n", - "from orm_loader.helpers import configure_logging\n", - "from omop_alchemy import get_engine_name\n", - "import sqlalchemy as sa" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "ce23b2dc", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2026-02-06 16:14:11,909 | INFO | sql_loader.omop_alchemy.config | Default database engine configured\n" - ] - } - ], - "source": [ - "configure_logging()\n", - "load_dotenv()\n", - "\n", - "engine_string = get_engine_name()\n", - "engine = sa.create_engine(engine_string, future=True, echo=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "fff93617", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Engine(postgresql+psycopg2://hemonc_user:***@localhost:5555/hemonc_db)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "engine" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "934b60ab", - "metadata": {}, - "outputs": [], - "source": [ - "Session = sessionmaker(bind=engine)\n", - "\n", - "session = Session()\n", - "kg = KnowledgeGraph(session)\n", - "bind_default_renderers(kg)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "a7ebd037", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ConceptView(id=45921297, CIEL:143326, name='Coronary Artery Thrombosis')" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "kg.concept_view(45921297)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "ab83927f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(LabelMatch(input_label='heart attack', matched_label='Heart attack', concept_id=36210764, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='heart attack', matched_label='Heart attack', concept_id=45466858, match_kind=, is_standard=False, is_active=True))" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "kg.label_lookup(\"Heart attack\")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "4592f774", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=4134723, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=3309601, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary Artery Thrombosis', concept_id=45921297, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=3161942, match_kind=, is_standard=False, is_active=False),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=40623902, match_kind=, is_standard=False, is_active=False)]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sorted(kg.label_lookup(\"Coronary artery thrombosis\", fuzzy=False))" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "2d4a8b6f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[LabelMatch(input_label='coronary artery thrombosis', matched_label='Right main coronary artery thrombosis', concept_id=4304192, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=4134723, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left main coronary artery thrombosis', concept_id=4209308, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left anterior descending coronary artery thrombosis', concept_id=4153091, match_kind=, is_standard=True, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Right Main Coronary Artery Thrombosis', concept_id=45908931, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary Artery Thrombosis', concept_id=45921297, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left anterior descending coronary artery thrombosis', concept_id=3403562, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left Main Coronary Artery Thrombosis', concept_id=45915508, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left Anterior Descending Coronary Artery Thrombosis', concept_id=45955194, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Right main coronary artery thrombosis', concept_id=3169041, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=3309601, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Left main coronary artery thrombosis', concept_id=3270140, match_kind=, is_standard=False, is_active=True),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=40623902, match_kind=, is_standard=False, is_active=False),\n", - " LabelMatch(input_label='coronary artery thrombosis', matched_label='Coronary artery thrombosis', concept_id=3161942, match_kind=, is_standard=False, is_active=False)]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sorted(kg.label_lookup(\"Coronary artery thrombosis\", fuzzy=True))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "7c964c57", - "metadata": {}, - "outputs": [], - "source": [ - "from omop_graph.reasoning.term_grounding import GroundingConstraints, ground_term\n", - "from omop_graph.reasoning.resolvers import ResolverPipeline, ExactLabelResolver, PartialLabelResolver, ExactSynonymResolver\n", - "from omop_graph.reasoning.term_grounding import _passes_constraints\n" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "69726c8e", - "metadata": {}, - "outputs": [], - "source": [ - "constraints = GroundingConstraints(\n", - " parent_ids=(37153816,), # malignant neoplasm\n", - " allowed_domains=(\"Condition\",), # redundant with parent_ids but perhaps we support combinations across domains eventually for more abstract grounding?\n", - " allowed_vocabularies=(\"SNOMED\", \"ICDO3\"),\n", - " require_standard=True,\n", - " max_depth=4,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "ec17c091", - "metadata": {}, - "outputs": [], - "source": [ - "resolver_pipeline = ResolverPipeline(\n", - " resolvers=(ExactLabelResolver(),ExactSynonymResolver(),PartialLabelResolver()),\n", - " stop_after_confidence=PartialLabelResolver.confidence\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "b2400dda", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ground_term(kg, \"hodgkin lymphoma\", constraints=constraints, resolver_pipeline=resolver_pipeline)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "2bc5a61a", - "metadata": {}, - "outputs": [], - "source": [ - "e = ExactLabelResolver()\n", - "p = PartialLabelResolver()\n", - "s = ExactSynonymResolver()" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "45a48125", - "metadata": {}, - "outputs": [], - "source": [ - "hits = e.resolve(kg, \"hodgkin lymphoma\")" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "62bd9fe8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[CandidateHit(concept_id=37151896, resolver='exact_label'),\n", - " CandidateHit(concept_id=36311303, resolver='exact_label'),\n", - " CandidateHit(concept_id=37093686, resolver='exact_label'),\n", - " CandidateHit(concept_id=42485178, resolver='exact_label'),\n", - " CandidateHit(concept_id=45463156, resolver='exact_label'),\n", - " CandidateHit(concept_id=1567633, resolver='exact_label'),\n", - " CandidateHit(concept_id=45576344, resolver='exact_label'),\n", - " CandidateHit(concept_id=37615263, resolver='exact_label'),\n", - " CandidateHit(concept_id=1406737, resolver='exact_label'),\n", - " CandidateHit(concept_id=4031667, resolver='exact_label')]" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "hits" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "8b45b6eb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Concept ID 37151896 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36311303 passes: False, reasons: [\"domain Meas Value not in ('Condition',)\"]\n", - "Concept ID 37093686 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 42485178 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 45463156 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 1567633 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45576344 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 37615263 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 1406737 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 4031667 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n" - ] - } - ], - "source": [ - "for h in hits:\n", - " ok, reasons = _passes_constraints(kg, h.concept_id, constraints)\n", - " print(f\"Concept ID {h.concept_id} passes: {ok}, reasons: {reasons}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "1dd00c08", - "metadata": {}, - "outputs": [], - "source": [ - "hits = s.resolve(kg, \"hodgkin lymphoma\")" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "85322723", - "metadata": {}, - "outputs": [], - "source": [ - "for h in hits:\n", - " ok, reasons = _passes_constraints(kg, h.concept_id, constraints)\n", - " print(f\"Concept ID {h.concept_id} passes: {ok}, reasons: {reasons}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b4a7152a", - "metadata": {}, - "outputs": [], - "source": [ - "# 4329847 is the only concept that passes constraints and matches the selected grounding returned above" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "c2c96ab4", - "metadata": {}, - "outputs": [], - "source": [ - "hits = p.resolve(kg, \"hodgkin lymphoma\")" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "b509ebfd", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Concept ID 37093686 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37151896 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 37615263 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 45463156 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 4031667 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36311303 passes: False, reasons: [\"domain Meas Value not in ('Condition',)\"]\n", - "Concept ID 1567633 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45576344 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 1406737 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 42485178 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 1553022 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 45433102 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 1402163 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 777690 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 44499281 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4028861 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 37093693 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37615571 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 1567640 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45581232 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 1406752 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1406753 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 42485185 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 1402156 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 3430738 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 1397914 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 44499282 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4028701 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 3452776 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 3454287 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4179351 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4216428 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 45571548 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 3443626 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4012175 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 45936962 passes: False, reasons: ['vocabulary CIEL not allowed']\n", - "Concept ID 42542185 passes: False, reasons: ['vocabulary HemOnc not allowed']\n", - "Concept ID 36540835 passes: True, reasons: []\n", - "Concept ID 36539567 passes: True, reasons: []\n", - "Concept ID 36555113 passes: True, reasons: []\n", - "Concept ID 36544940 passes: True, reasons: []\n", - "Concept ID 36536539 passes: True, reasons: []\n", - "Concept ID 36402763 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36525284 passes: True, reasons: []\n", - "Concept ID 44503243 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36529144 passes: True, reasons: []\n", - "Concept ID 44505365 passes: True, reasons: []\n", - "Concept ID 36535471 passes: True, reasons: []\n", - "Concept ID 36538924 passes: True, reasons: []\n", - "Concept ID 36524874 passes: True, reasons: []\n", - "Concept ID 36554928 passes: True, reasons: []\n", - "Concept ID 36565324 passes: True, reasons: []\n", - "Concept ID 36539366 passes: True, reasons: []\n", - "Concept ID 36542532 passes: True, reasons: []\n", - "Concept ID 36563256 passes: True, reasons: []\n", - "Concept ID 36565037 passes: True, reasons: []\n", - "Concept ID 36544798 passes: True, reasons: []\n", - "Concept ID 36546669 passes: True, reasons: []\n", - "Concept ID 36563078 passes: True, reasons: []\n", - "Concept ID 36561341 passes: True, reasons: []\n", - "Concept ID 36538414 passes: True, reasons: []\n", - "Concept ID 36538420 passes: True, reasons: []\n", - "Concept ID 36538031 passes: True, reasons: []\n", - "Concept ID 36524234 passes: True, reasons: []\n", - "Concept ID 36553925 passes: True, reasons: []\n", - "Concept ID 36565906 passes: True, reasons: []\n", - "Concept ID 36536971 passes: True, reasons: []\n", - "Concept ID 36551165 passes: True, reasons: []\n", - "Concept ID 36532848 passes: True, reasons: []\n", - "Concept ID 36536897 passes: True, reasons: []\n", - "Concept ID 36567717 passes: True, reasons: []\n", - "Concept ID 1402157 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1402162 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 44499283 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 44499290 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 44502493 passes: True, reasons: []\n", - "Concept ID 36518352 passes: True, reasons: []\n", - "Concept ID 36527498 passes: True, reasons: []\n", - "Concept ID 36567190 passes: True, reasons: []\n", - "Concept ID 36565589 passes: True, reasons: []\n", - "Concept ID 36517582 passes: True, reasons: []\n", - "Concept ID 36402858 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36558535 passes: True, reasons: []\n", - "Concept ID 1402154 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1102860 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36548127 passes: True, reasons: []\n", - "Concept ID 44505366 passes: True, reasons: []\n", - "Concept ID 36540035 passes: True, reasons: []\n", - "Concept ID 36532344 passes: True, reasons: []\n", - "Concept ID 36537269 passes: True, reasons: []\n", - "Concept ID 36535531 passes: True, reasons: []\n", - "Concept ID 44499284 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36527613 passes: True, reasons: []\n", - "Concept ID 36538134 passes: True, reasons: []\n", - "Concept ID 36524397 passes: True, reasons: []\n", - "Concept ID 36527638 passes: True, reasons: []\n", - "Concept ID 36548454 passes: True, reasons: []\n", - "Concept ID 36518976 passes: True, reasons: []\n", - "Concept ID 36542492 passes: True, reasons: []\n", - "Concept ID 36543379 passes: True, reasons: []\n", - "Concept ID 36543347 passes: True, reasons: []\n", - "Concept ID 36537245 passes: True, reasons: []\n", - "Concept ID 36519189 passes: True, reasons: []\n", - "Concept ID 36531150 passes: True, reasons: []\n", - "Concept ID 36552513 passes: True, reasons: []\n", - "Concept ID 36565987 passes: True, reasons: []\n", - "Concept ID 36527088 passes: True, reasons: []\n", - "Concept ID 45600540 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 1402158 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 36540553 passes: True, reasons: []\n", - "Concept ID 36523613 passes: True, reasons: []\n", - "Concept ID 36559766 passes: True, reasons: []\n", - "Concept ID 36529259 passes: True, reasons: []\n", - "Concept ID 44499287 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 1402155 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 3350565 passes: False, reasons: ['vocabulary Nebraska Lexicon not allowed']\n", - "Concept ID 36526007 passes: True, reasons: []\n", - "Concept ID 44499286 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4322166 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4322613 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36565301 passes: True, reasons: []\n", - "Concept ID 36546937 passes: True, reasons: []\n", - "Concept ID 36528049 passes: True, reasons: []\n", - "Concept ID 36539641 passes: True, reasons: []\n", - "Concept ID 36518048 passes: True, reasons: []\n", - "Concept ID 36522741 passes: True, reasons: []\n", - "Concept ID 44502199 passes: True, reasons: []\n", - "Concept ID 36531135 passes: True, reasons: []\n", - "Concept ID 44499589 passes: True, reasons: []\n", - "Concept ID 36559154 passes: True, reasons: []\n", - "Concept ID 36540672 passes: True, reasons: []\n", - "Concept ID 36546468 passes: True, reasons: []\n", - "Concept ID 36524306 passes: True, reasons: []\n", - "Concept ID 36518889 passes: True, reasons: []\n", - "Concept ID 36556423 passes: True, reasons: []\n", - "Concept ID 36531625 passes: True, reasons: []\n", - "Concept ID 36550575 passes: True, reasons: []\n", - "Concept ID 36549283 passes: True, reasons: []\n", - "Concept ID 36546238 passes: True, reasons: []\n", - "Concept ID 36549164 passes: True, reasons: []\n", - "Concept ID 36549285 passes: True, reasons: []\n", - "Concept ID 36563745 passes: True, reasons: []\n", - "Concept ID 36554952 passes: True, reasons: []\n", - "Concept ID 44502590 passes: True, reasons: []\n", - "Concept ID 36562539 passes: True, reasons: []\n", - "Concept ID 36537854 passes: True, reasons: []\n", - "Concept ID 36551388 passes: True, reasons: []\n", - "Concept ID 36542254 passes: True, reasons: []\n", - "Concept ID 36554486 passes: True, reasons: []\n", - "Concept ID 36520979 passes: True, reasons: []\n", - "Concept ID 36567547 passes: True, reasons: []\n", - "Concept ID 36545929 passes: True, reasons: []\n", - "Concept ID 36525913 passes: True, reasons: []\n", - "Concept ID 36552608 passes: True, reasons: []\n", - "Concept ID 36523122 passes: True, reasons: []\n", - "Concept ID 36549595 passes: True, reasons: []\n", - "Concept ID 36547341 passes: True, reasons: []\n", - "Concept ID 36548303 passes: True, reasons: []\n", - "Concept ID 36518882 passes: True, reasons: []\n", - "Concept ID 36521026 passes: True, reasons: []\n", - "Concept ID 36517697 passes: True, reasons: []\n", - "Concept ID 36536069 passes: True, reasons: []\n", - "Concept ID 36524399 passes: True, reasons: []\n", - "Concept ID 44501432 passes: True, reasons: []\n", - "Concept ID 36538370 passes: True, reasons: []\n", - "Concept ID 36565821 passes: True, reasons: []\n", - "Concept ID 36544094 passes: True, reasons: []\n", - "Concept ID 36561602 passes: True, reasons: []\n", - "Concept ID 36526417 passes: True, reasons: []\n", - "Concept ID 36562143 passes: True, reasons: []\n", - "Concept ID 36554730 passes: True, reasons: []\n", - "Concept ID 36562406 passes: True, reasons: []\n", - "Concept ID 36530102 passes: True, reasons: []\n", - "Concept ID 36539747 passes: True, reasons: []\n", - "Concept ID 36565132 passes: True, reasons: []\n", - "Concept ID 36554774 passes: True, reasons: []\n", - "Concept ID 36544252 passes: True, reasons: []\n", - "Concept ID 36556119 passes: True, reasons: []\n", - "Concept ID 36561111 passes: True, reasons: []\n", - "Concept ID 36536945 passes: True, reasons: []\n", - "Concept ID 36551081 passes: True, reasons: []\n", - "Concept ID 36534154 passes: True, reasons: []\n", - "Concept ID 36562032 passes: True, reasons: []\n", - "Concept ID 36565356 passes: True, reasons: []\n", - "Concept ID 36533038 passes: True, reasons: []\n", - "Concept ID 36542922 passes: True, reasons: []\n", - "Concept ID 36524543 passes: True, reasons: []\n", - "Concept ID 36527529 passes: True, reasons: []\n", - "Concept ID 36549301 passes: True, reasons: []\n", - "Concept ID 36565762 passes: True, reasons: []\n", - "Concept ID 36523804 passes: True, reasons: []\n", - "Concept ID 36547847 passes: True, reasons: []\n", - "Concept ID 36562942 passes: True, reasons: []\n", - "Concept ID 36531475 passes: True, reasons: []\n", - "Concept ID 36563813 passes: True, reasons: []\n", - "Concept ID 36528915 passes: True, reasons: []\n", - "Concept ID 36536182 passes: True, reasons: []\n", - "Concept ID 36542599 passes: True, reasons: []\n", - "Concept ID 36550918 passes: True, reasons: []\n", - "Concept ID 36559992 passes: True, reasons: []\n", - "Concept ID 36555624 passes: True, reasons: []\n", - "Concept ID 36537962 passes: True, reasons: []\n", - "Concept ID 36544936 passes: True, reasons: []\n", - "Concept ID 36549807 passes: True, reasons: []\n", - "Concept ID 36520326 passes: True, reasons: []\n", - "Concept ID 1402160 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1402161 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 44499293 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36544451 passes: True, reasons: []\n", - "Concept ID 36533710 passes: True, reasons: []\n", - "Concept ID 36541420 passes: True, reasons: []\n", - "Concept ID 44499292 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4195472 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4166852 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36559070 passes: True, reasons: []\n", - "Concept ID 36528269 passes: True, reasons: []\n", - "Concept ID 36551094 passes: True, reasons: []\n", - "Concept ID 36556068 passes: True, reasons: []\n", - "Concept ID 36518129 passes: True, reasons: []\n", - "Concept ID 44501605 passes: True, reasons: []\n", - "Concept ID 36529276 passes: True, reasons: []\n", - "Concept ID 36561562 passes: True, reasons: []\n", - "Concept ID 36540107 passes: True, reasons: []\n", - "Concept ID 36560811 passes: True, reasons: []\n", - "Concept ID 36543685 passes: True, reasons: []\n", - "Concept ID 36532126 passes: True, reasons: []\n", - "Concept ID 36533375 passes: True, reasons: []\n", - "Concept ID 36518157 passes: True, reasons: []\n", - "Concept ID 36536388 passes: True, reasons: []\n", - "Concept ID 36558104 passes: True, reasons: []\n", - "Concept ID 36554896 passes: True, reasons: []\n", - "Concept ID 36545362 passes: True, reasons: []\n", - "Concept ID 36556137 passes: True, reasons: []\n", - "Concept ID 36535461 passes: True, reasons: []\n", - "Concept ID 36518018 passes: True, reasons: []\n", - "Concept ID 36550938 passes: True, reasons: []\n", - "Concept ID 36528243 passes: True, reasons: []\n", - "Concept ID 36548224 passes: True, reasons: []\n", - "Concept ID 36538102 passes: True, reasons: []\n", - "Concept ID 36527137 passes: True, reasons: []\n", - "Concept ID 36561812 passes: True, reasons: []\n", - "Concept ID 36557817 passes: True, reasons: []\n", - "Concept ID 36539796 passes: True, reasons: []\n", - "Concept ID 36538359 passes: True, reasons: []\n", - "Concept ID 36562138 passes: True, reasons: []\n", - "Concept ID 36555783 passes: True, reasons: []\n", - "Concept ID 36546267 passes: True, reasons: []\n", - "Concept ID 36562195 passes: True, reasons: []\n", - "Concept ID 36551089 passes: True, reasons: []\n", - "Concept ID 44503244 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36530754 passes: True, reasons: []\n", - "Concept ID 36554488 passes: True, reasons: []\n", - "Concept ID 36522871 passes: True, reasons: []\n", - "Concept ID 36532446 passes: True, reasons: []\n", - "Concept ID 36535512 passes: True, reasons: []\n", - "Concept ID 36555549 passes: True, reasons: []\n", - "Concept ID 36540190 passes: True, reasons: []\n", - "Concept ID 36540546 passes: True, reasons: []\n", - "Concept ID 36558156 passes: True, reasons: []\n", - "Concept ID 36539006 passes: True, reasons: []\n", - "Concept ID 36529306 passes: True, reasons: []\n", - "Concept ID 36529354 passes: True, reasons: []\n", - "Concept ID 36544991 passes: True, reasons: []\n", - "Concept ID 36540422 passes: True, reasons: []\n", - "Concept ID 36564215 passes: True, reasons: []\n", - "Concept ID 36538931 passes: True, reasons: []\n", - "Concept ID 36564805 passes: True, reasons: []\n", - "Concept ID 36547329 passes: True, reasons: []\n", - "Concept ID 36522919 passes: True, reasons: []\n", - "Concept ID 36519997 passes: True, reasons: []\n", - "Concept ID 36554171 passes: True, reasons: []\n", - "Concept ID 36539860 passes: True, reasons: []\n", - "Concept ID 36550651 passes: True, reasons: []\n", - "Concept ID 36557832 passes: True, reasons: []\n", - "Concept ID 1402159 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 36533393 passes: True, reasons: []\n", - "Concept ID 36524686 passes: True, reasons: []\n", - "Concept ID 36522009 passes: True, reasons: []\n", - "Concept ID 36550254 passes: True, reasons: []\n", - "Concept ID 36524606 passes: True, reasons: []\n", - "Concept ID 36552612 passes: True, reasons: []\n", - "Concept ID 36552673 passes: True, reasons: []\n", - "Concept ID 36555823 passes: True, reasons: []\n", - "Concept ID 36531461 passes: True, reasons: []\n", - "Concept ID 44499291 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 4193008 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36550982 passes: True, reasons: []\n", - "Concept ID 36525343 passes: True, reasons: []\n", - "Concept ID 36536937 passes: True, reasons: []\n", - "Concept ID 36534759 passes: True, reasons: []\n", - "Concept ID 36544273 passes: True, reasons: []\n", - "Concept ID 36524576 passes: True, reasons: []\n", - "Concept ID 36539551 passes: True, reasons: []\n", - "Concept ID 36563097 passes: True, reasons: []\n", - "Concept ID 36542713 passes: True, reasons: []\n", - "Concept ID 36553135 passes: True, reasons: []\n", - "Concept ID 36528203 passes: True, reasons: []\n", - "Concept ID 36542854 passes: True, reasons: []\n", - "Concept ID 36547980 passes: True, reasons: []\n", - "Concept ID 36528729 passes: True, reasons: []\n", - "Concept ID 36564471 passes: True, reasons: []\n", - "Concept ID 36546997 passes: True, reasons: []\n", - "Concept ID 36517406 passes: True, reasons: []\n", - "Concept ID 36540098 passes: True, reasons: []\n", - "Concept ID 36559329 passes: True, reasons: []\n", - "Concept ID 36523549 passes: True, reasons: []\n", - "Concept ID 45557022 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36564418 passes: True, reasons: []\n", - "Concept ID 36563465 passes: True, reasons: []\n", - "Concept ID 36553010 passes: True, reasons: []\n", - "Concept ID 36561016 passes: True, reasons: []\n", - "Concept ID 1402153 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 36541770 passes: True, reasons: []\n", - "Concept ID 36539539 passes: True, reasons: []\n", - "Concept ID 36541601 passes: True, reasons: []\n", - "Concept ID 44499285 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 45571546 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 4060382 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36548724 passes: True, reasons: []\n", - "Concept ID 45571547 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36560743 passes: True, reasons: []\n", - "Concept ID 36543142 passes: True, reasons: []\n", - "Concept ID 4038839 passes: True, reasons: []\n", - "Concept ID 36550302 passes: True, reasons: []\n", - "Concept ID 36548334 passes: True, reasons: []\n", - "Concept ID 36556788 passes: True, reasons: []\n", - "Concept ID 44503303 passes: True, reasons: []\n", - "Concept ID 36533424 passes: True, reasons: []\n", - "Concept ID 36560035 passes: True, reasons: []\n", - "Concept ID 36566276 passes: True, reasons: []\n", - "Concept ID 36548458 passes: True, reasons: []\n", - "Concept ID 36531213 passes: True, reasons: []\n", - "Concept ID 36543607 passes: True, reasons: []\n", - "Concept ID 36562882 passes: True, reasons: []\n", - "Concept ID 36559870 passes: True, reasons: []\n", - "Concept ID 44499846 passes: True, reasons: []\n", - "Concept ID 36526098 passes: True, reasons: []\n", - "Concept ID 36568016 passes: True, reasons: []\n", - "Concept ID 36520677 passes: True, reasons: []\n", - "Concept ID 44500701 passes: True, reasons: []\n", - "Concept ID 36563750 passes: True, reasons: []\n", - "Concept ID 36550020 passes: True, reasons: []\n", - "Concept ID 36520775 passes: True, reasons: []\n", - "Concept ID 36519413 passes: True, reasons: []\n", - "Concept ID 36550801 passes: True, reasons: []\n", - "Concept ID 36524061 passes: True, reasons: []\n", - "Concept ID 36402405 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36517333 passes: True, reasons: []\n", - "Concept ID 36560219 passes: True, reasons: []\n", - "Concept ID 36553422 passes: True, reasons: []\n", - "Concept ID 36559377 passes: True, reasons: []\n", - "Concept ID 36517718 passes: True, reasons: []\n", - "Concept ID 36532363 passes: True, reasons: []\n", - "Concept ID 36566181 passes: True, reasons: []\n", - "Concept ID 36556417 passes: True, reasons: []\n", - "Concept ID 36531663 passes: True, reasons: []\n", - "Concept ID 36402884 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36525354 passes: True, reasons: []\n", - "Concept ID 44503245 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36520536 passes: True, reasons: []\n", - "Concept ID 36539372 passes: True, reasons: []\n", - "Concept ID 36559900 passes: True, reasons: []\n", - "Concept ID 36552168 passes: True, reasons: []\n", - "Concept ID 36563875 passes: True, reasons: []\n", - "Concept ID 36402863 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36527828 passes: True, reasons: []\n", - "Concept ID 44503247 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36567010 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 44503258 passes: True, reasons: []\n", - "Concept ID 36562200 passes: True, reasons: []\n", - "Concept ID 44505367 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36543318 passes: True, reasons: []\n", - "Concept ID 36545927 passes: True, reasons: []\n", - "Concept ID 36525771 passes: True, reasons: []\n", - "Concept ID 36539413 passes: True, reasons: []\n", - "Concept ID 36563402 passes: True, reasons: []\n", - "Concept ID 44505370 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36528895 passes: True, reasons: []\n", - "Concept ID 36540505 passes: True, reasons: []\n", - "Concept ID 36519934 passes: True, reasons: []\n", - "Concept ID 36538455 passes: True, reasons: []\n", - "Concept ID 36520649 passes: True, reasons: []\n", - "Concept ID 36558015 passes: True, reasons: []\n", - "Concept ID 44503304 passes: True, reasons: []\n", - "Concept ID 36539801 passes: True, reasons: []\n", - "Concept ID 36561964 passes: True, reasons: []\n", - "Concept ID 36546733 passes: True, reasons: []\n", - "Concept ID 36542257 passes: True, reasons: []\n", - "Concept ID 36542510 passes: True, reasons: []\n", - "Concept ID 36548514 passes: True, reasons: []\n", - "Concept ID 36519891 passes: True, reasons: []\n", - "Concept ID 36564748 passes: True, reasons: []\n", - "Concept ID 36528562 passes: True, reasons: []\n", - "Concept ID 36553030 passes: True, reasons: []\n", - "Concept ID 36563512 passes: True, reasons: []\n", - "Concept ID 36564499 passes: True, reasons: []\n", - "Concept ID 36532544 passes: True, reasons: []\n", - "Concept ID 44503277 passes: True, reasons: []\n", - "Concept ID 36538610 passes: True, reasons: []\n", - "Concept ID 36535445 passes: True, reasons: []\n", - "Concept ID 36545232 passes: True, reasons: []\n", - "Concept ID 36541252 passes: True, reasons: []\n", - "Concept ID 36549104 passes: True, reasons: []\n", - "Concept ID 36519570 passes: True, reasons: []\n", - "Concept ID 36540537 passes: True, reasons: []\n", - "Concept ID 36530632 passes: True, reasons: []\n", - "Concept ID 36549683 passes: True, reasons: []\n", - "Concept ID 36527881 passes: True, reasons: []\n", - "Concept ID 36558733 passes: True, reasons: []\n", - "Concept ID 36538142 passes: True, reasons: []\n", - "Concept ID 36564389 passes: True, reasons: []\n", - "Concept ID 36554838 passes: True, reasons: []\n", - "Concept ID 36554328 passes: True, reasons: []\n", - "Concept ID 36530242 passes: True, reasons: []\n", - "Concept ID 36551920 passes: True, reasons: []\n", - "Concept ID 36402885 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36520549 passes: True, reasons: []\n", - "Concept ID 44503246 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36551886 passes: True, reasons: []\n", - "Concept ID 36520758 passes: True, reasons: []\n", - "Concept ID 36558628 passes: True, reasons: []\n", - "Concept ID 36537534 passes: True, reasons: []\n", - "Concept ID 36527691 passes: True, reasons: []\n", - "Concept ID 36564140 passes: True, reasons: []\n", - "Concept ID 36528246 passes: True, reasons: []\n", - "Concept ID 36560133 passes: True, reasons: []\n", - "Concept ID 36559970 passes: True, reasons: []\n", - "Concept ID 44505368 passes: True, reasons: []\n", - "Concept ID 36550586 passes: True, reasons: []\n", - "Concept ID 36537556 passes: True, reasons: []\n", - "Concept ID 36562737 passes: True, reasons: []\n", - "Concept ID 36546356 passes: True, reasons: []\n", - "Concept ID 36534693 passes: True, reasons: []\n", - "Concept ID 36548466 passes: True, reasons: []\n", - "Concept ID 36517855 passes: True, reasons: []\n", - "Concept ID 36525845 passes: True, reasons: []\n", - "Concept ID 44503056 passes: True, reasons: []\n", - "Concept ID 36552771 passes: True, reasons: []\n", - "Concept ID 36539411 passes: True, reasons: []\n", - "Concept ID 36526652 passes: True, reasons: []\n", - "Concept ID 36536262 passes: True, reasons: []\n", - "Concept ID 36562076 passes: True, reasons: []\n", - "Concept ID 36544433 passes: True, reasons: []\n", - "Concept ID 36551528 passes: True, reasons: []\n", - "Concept ID 36530578 passes: True, reasons: []\n", - "Concept ID 36559873 passes: True, reasons: []\n", - "Concept ID 36564174 passes: True, reasons: []\n", - "Concept ID 36520126 passes: True, reasons: []\n", - "Concept ID 36551654 passes: True, reasons: []\n", - "Concept ID 36552859 passes: True, reasons: []\n", - "Concept ID 36520491 passes: True, reasons: []\n", - "Concept ID 36518641 passes: True, reasons: []\n", - "Concept ID 36539359 passes: True, reasons: []\n", - "Concept ID 36557671 passes: True, reasons: []\n", - "Concept ID 36517880 passes: True, reasons: []\n", - "Concept ID 36527775 passes: True, reasons: []\n", - "Concept ID 36566772 passes: True, reasons: []\n", - "Concept ID 36553958 passes: True, reasons: []\n", - "Concept ID 36529623 passes: True, reasons: []\n", - "Concept ID 36527694 passes: True, reasons: []\n", - "Concept ID 36530297 passes: True, reasons: []\n", - "Concept ID 36536032 passes: True, reasons: []\n", - "Concept ID 36554277 passes: True, reasons: []\n", - "Concept ID 36551427 passes: True, reasons: []\n", - "Concept ID 36554051 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36543416 passes: True, reasons: []\n", - "Concept ID 36522599 passes: True, reasons: []\n", - "Concept ID 36523832 passes: True, reasons: []\n", - "Concept ID 36402700 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36540858 passes: True, reasons: []\n", - "Concept ID 36562621 passes: True, reasons: []\n", - "Concept ID 36560493 passes: True, reasons: []\n", - "Concept ID 36537988 passes: True, reasons: []\n", - "Concept ID 36533844 passes: True, reasons: []\n", - "Concept ID 36551962 passes: True, reasons: []\n", - "Concept ID 36520031 passes: True, reasons: []\n", - "Concept ID 36517564 passes: True, reasons: []\n", - "Concept ID 36402887 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36522111 passes: True, reasons: []\n", - "Concept ID 36562634 passes: True, reasons: []\n", - "Concept ID 36559386 passes: True, reasons: []\n", - "Concept ID 36535187 passes: True, reasons: []\n", - "Concept ID 36531320 passes: True, reasons: []\n", - "Concept ID 36541505 passes: True, reasons: []\n", - "Concept ID 36532744 passes: True, reasons: []\n", - "Concept ID 36529956 passes: True, reasons: []\n", - "Concept ID 36520385 passes: True, reasons: []\n", - "Concept ID 36559578 passes: True, reasons: []\n", - "Concept ID 36535390 passes: True, reasons: []\n", - "Concept ID 36402814 passes: True, reasons: []\n", - "Concept ID 36527223 passes: True, reasons: []\n", - "Concept ID 36564705 passes: True, reasons: []\n", - "Concept ID 44505369 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36541807 passes: True, reasons: []\n", - "Concept ID 36540965 passes: True, reasons: []\n", - "Concept ID 36543274 passes: True, reasons: []\n", - "Concept ID 36525223 passes: True, reasons: []\n", - "Concept ID 36552881 passes: True, reasons: []\n", - "Concept ID 36524875 passes: True, reasons: []\n", - "Concept ID 36537696 passes: True, reasons: []\n", - "Concept ID 36537811 passes: True, reasons: []\n", - "Concept ID 36527299 passes: True, reasons: []\n", - "Concept ID 36526973 passes: True, reasons: []\n", - "Concept ID 36546399 passes: True, reasons: []\n", - "Concept ID 36556465 passes: True, reasons: []\n", - "Concept ID 36526325 passes: True, reasons: []\n", - "Concept ID 36563907 passes: True, reasons: []\n", - "Concept ID 36566800 passes: True, reasons: []\n", - "Concept ID 36553461 passes: True, reasons: []\n", - "Concept ID 36565192 passes: True, reasons: []\n", - "Concept ID 36537886 passes: True, reasons: []\n", - "Concept ID 36523326 passes: True, reasons: []\n", - "Concept ID 36539930 passes: True, reasons: []\n", - "Concept ID 36546052 passes: True, reasons: []\n", - "Concept ID 36537059 passes: True, reasons: []\n", - "Concept ID 36543352 passes: True, reasons: []\n", - "Concept ID 36547555 passes: True, reasons: []\n", - "Concept ID 36518457 passes: True, reasons: []\n", - "Concept ID 36524724 passes: True, reasons: []\n", - "Concept ID 36531930 passes: True, reasons: []\n", - "Concept ID 36532682 passes: True, reasons: []\n", - "Concept ID 36540770 passes: True, reasons: []\n", - "Concept ID 36536300 passes: True, reasons: []\n", - "Concept ID 36552129 passes: True, reasons: []\n", - "Concept ID 36520765 passes: True, reasons: []\n", - "Concept ID 36521172 passes: True, reasons: []\n", - "Concept ID 36545134 passes: True, reasons: []\n", - "Concept ID 36554190 passes: True, reasons: []\n", - "Concept ID 36523834 passes: True, reasons: []\n", - "Concept ID 36527819 passes: True, reasons: []\n", - "Concept ID 36548945 passes: True, reasons: []\n", - "Concept ID 36549224 passes: True, reasons: []\n", - "Concept ID 36517906 passes: True, reasons: []\n", - "Concept ID 36539007 passes: True, reasons: []\n", - "Concept ID 36523387 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 44503259 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36522600 passes: True, reasons: []\n", - "Concept ID 36537533 passes: True, reasons: []\n", - "Concept ID 36558686 passes: True, reasons: []\n", - "Concept ID 36564281 passes: True, reasons: []\n", - "Concept ID 36564139 passes: True, reasons: []\n", - "Concept ID 36559839 passes: True, reasons: []\n", - "Concept ID 36549812 passes: True, reasons: []\n", - "Concept ID 36522847 passes: True, reasons: []\n", - "Concept ID 36542424 passes: True, reasons: []\n", - "Concept ID 36536227 passes: True, reasons: []\n", - "Concept ID 36553096 passes: True, reasons: []\n", - "Concept ID 36533892 passes: True, reasons: []\n", - "Concept ID 36529510 passes: True, reasons: []\n", - "Concept ID 36567835 passes: True, reasons: []\n", - "Concept ID 44503278 passes: True, reasons: []\n", - "Concept ID 36553983 passes: True, reasons: []\n", - "Concept ID 36533242 passes: True, reasons: []\n", - "Concept ID 36548311 passes: True, reasons: []\n", - "Concept ID 36551423 passes: True, reasons: []\n", - "Concept ID 36544662 passes: True, reasons: []\n", - "Concept ID 36528153 passes: True, reasons: []\n", - "Concept ID 36526036 passes: True, reasons: []\n", - "Concept ID 36557392 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36553323 passes: True, reasons: []\n", - "Concept ID 36555435 passes: True, reasons: []\n", - "Concept ID 36564469 passes: True, reasons: []\n", - "Concept ID 36521828 passes: True, reasons: []\n", - "Concept ID 36546067 passes: True, reasons: []\n", - "Concept ID 36543057 passes: True, reasons: []\n", - "Concept ID 36553601 passes: True, reasons: []\n", - "Concept ID 36526060 passes: True, reasons: []\n", - "Concept ID 36546752 passes: True, reasons: []\n", - "Concept ID 36546976 passes: True, reasons: []\n", - "Concept ID 36553101 passes: True, reasons: []\n", - "Concept ID 36542934 passes: True, reasons: []\n", - "Concept ID 36533254 passes: True, reasons: []\n", - "Concept ID 36522017 passes: True, reasons: []\n", - "Concept ID 36523947 passes: True, reasons: []\n", - "Concept ID 36532780 passes: True, reasons: []\n", - "Concept ID 36538581 passes: True, reasons: []\n", - "Concept ID 36543460 passes: True, reasons: []\n", - "Concept ID 36520540 passes: True, reasons: []\n", - "Concept ID 36531872 passes: True, reasons: []\n", - "Concept ID 36551410 passes: True, reasons: []\n", - "Concept ID 36535698 passes: True, reasons: []\n", - "Concept ID 36548056 passes: True, reasons: []\n", - "Concept ID 36532229 passes: True, reasons: []\n", - "Concept ID 36402770 passes: True, reasons: []\n", - "Concept ID 36540859 passes: True, reasons: []\n", - "Concept ID 36535354 passes: True, reasons: []\n", - "Concept ID 36537583 passes: True, reasons: []\n", - "Concept ID 36532095 passes: True, reasons: []\n", - "Concept ID 36549425 passes: True, reasons: []\n", - "Concept ID 36544108 passes: True, reasons: []\n", - "Concept ID 36531897 passes: True, reasons: []\n", - "Concept ID 36539205 passes: True, reasons: []\n", - "Concept ID 36531403 passes: True, reasons: []\n", - "Concept ID 36517486 passes: True, reasons: []\n", - "Concept ID 36550104 passes: True, reasons: []\n", - "Concept ID 36559200 passes: True, reasons: []\n", - "Concept ID 36561445 passes: True, reasons: []\n", - "Concept ID 36562670 passes: True, reasons: []\n", - "Concept ID 36402750 passes: True, reasons: []\n", - "Concept ID 36541550 passes: True, reasons: []\n", - "Concept ID 36558437 passes: True, reasons: []\n", - "Concept ID 36520029 passes: True, reasons: []\n", - "Concept ID 36562943 passes: True, reasons: []\n", - "Concept ID 36542493 passes: True, reasons: []\n", - "Concept ID 36567624 passes: True, reasons: []\n", - "Concept ID 36555200 passes: True, reasons: []\n", - "Concept ID 36557141 passes: True, reasons: []\n", - "Concept ID 36538937 passes: True, reasons: []\n", - "Concept ID 36522301 passes: True, reasons: []\n", - "Concept ID 36562951 passes: True, reasons: []\n", - "Concept ID 36561825 passes: True, reasons: []\n", - "Concept ID 36565413 passes: True, reasons: []\n", - "Concept ID 36542448 passes: True, reasons: []\n", - "Concept ID 36543887 passes: True, reasons: []\n", - "Concept ID 36543904 passes: True, reasons: []\n", - "Concept ID 36559104 passes: True, reasons: []\n", - "Concept ID 36555293 passes: True, reasons: []\n", - "Concept ID 36529361 passes: True, reasons: []\n", - "Concept ID 36526879 passes: True, reasons: []\n", - "Concept ID 36522977 passes: True, reasons: []\n", - "Concept ID 36555941 passes: True, reasons: []\n", - "Concept ID 36550912 passes: True, reasons: []\n", - "Concept ID 36536274 passes: True, reasons: []\n", - "Concept ID 36541962 passes: True, reasons: []\n", - "Concept ID 36522054 passes: True, reasons: []\n", - "Concept ID 36548812 passes: True, reasons: []\n", - "Concept ID 36556458 passes: True, reasons: []\n", - "Concept ID 36522950 passes: True, reasons: []\n", - "Concept ID 36528443 passes: True, reasons: []\n", - "Concept ID 36564456 passes: True, reasons: []\n", - "Concept ID 36567991 passes: True, reasons: []\n", - "Concept ID 36560659 passes: True, reasons: []\n", - "Concept ID 36521404 passes: True, reasons: []\n", - "Concept ID 36560122 passes: True, reasons: []\n", - "Concept ID 36545186 passes: True, reasons: []\n", - "Concept ID 36522826 passes: True, reasons: []\n", - "Concept ID 36549773 passes: True, reasons: []\n", - "Concept ID 36536719 passes: True, reasons: []\n", - "Concept ID 36528136 passes: True, reasons: []\n", - "Concept ID 36518588 passes: True, reasons: []\n", - "Concept ID 36548447 passes: True, reasons: []\n", - "Concept ID 36546146 passes: True, reasons: []\n", - "Concept ID 36519203 passes: True, reasons: []\n", - "Concept ID 36531243 passes: True, reasons: []\n", - "Concept ID 36529342 passes: True, reasons: []\n", - "Concept ID 36537029 passes: True, reasons: []\n", - "Concept ID 36539679 passes: True, reasons: []\n", - "Concept ID 36546245 passes: True, reasons: []\n", - "Concept ID 36526117 passes: True, reasons: []\n", - "Concept ID 36529025 passes: True, reasons: []\n", - "Concept ID 36525220 passes: True, reasons: []\n", - "Concept ID 36546613 passes: True, reasons: []\n", - "Concept ID 36533371 passes: True, reasons: []\n", - "Concept ID 36555845 passes: True, reasons: []\n", - "Concept ID 36517811 passes: True, reasons: []\n", - "Concept ID 36546053 passes: True, reasons: []\n", - "Concept ID 36550306 passes: True, reasons: []\n", - "Concept ID 36556023 passes: True, reasons: []\n", - "Concept ID 36521638 passes: True, reasons: []\n", - "Concept ID 36541209 passes: True, reasons: []\n", - "Concept ID 36549393 passes: True, reasons: []\n", - "Concept ID 36558519 passes: True, reasons: []\n", - "Concept ID 36549264 passes: True, reasons: []\n", - "Concept ID 36560372 passes: True, reasons: []\n", - "Concept ID 36533761 passes: True, reasons: []\n", - "Concept ID 36562461 passes: True, reasons: []\n", - "Concept ID 36562099 passes: True, reasons: []\n", - "Concept ID 36552298 passes: True, reasons: []\n", - "Concept ID 36517285 passes: True, reasons: []\n", - "Concept ID 36522436 passes: True, reasons: []\n", - "Concept ID 36526030 passes: True, reasons: []\n", - "Concept ID 36524149 passes: True, reasons: []\n", - "Concept ID 36523634 passes: True, reasons: []\n", - "Concept ID 36564235 passes: True, reasons: []\n", - "Concept ID 36545705 passes: True, reasons: []\n", - "Concept ID 36566430 passes: True, reasons: []\n", - "Concept ID 36533736 passes: True, reasons: []\n", - "Concept ID 36537786 passes: True, reasons: []\n", - "Concept ID 36524192 passes: True, reasons: []\n", - "Concept ID 36527026 passes: True, reasons: []\n", - "Concept ID 36550157 passes: True, reasons: []\n", - "Concept ID 36556221 passes: True, reasons: []\n", - "Concept ID 36544495 passes: True, reasons: []\n", - "Concept ID 36555802 passes: True, reasons: []\n", - "Concept ID 36533850 passes: True, reasons: []\n", - "Concept ID 36542112 passes: True, reasons: []\n", - "Concept ID 36556923 passes: True, reasons: []\n", - "Concept ID 36526645 passes: True, reasons: []\n", - "Concept ID 36559771 passes: True, reasons: []\n", - "Concept ID 36544123 passes: True, reasons: []\n", - "Concept ID 36545766 passes: True, reasons: []\n", - "Concept ID 36560972 passes: True, reasons: []\n", - "Concept ID 36548704 passes: True, reasons: []\n", - "Concept ID 36551487 passes: True, reasons: []\n", - "Concept ID 36543124 passes: True, reasons: []\n", - "Concept ID 36546485 passes: True, reasons: []\n", - "Concept ID 36549580 passes: True, reasons: []\n", - "Concept ID 36534765 passes: True, reasons: []\n", - "Concept ID 36545373 passes: True, reasons: []\n", - "Concept ID 36559106 passes: True, reasons: []\n", - "Concept ID 44502592 passes: True, reasons: []\n", - "Concept ID 36543684 passes: True, reasons: []\n", - "Concept ID 36539276 passes: True, reasons: []\n", - "Concept ID 36552643 passes: True, reasons: []\n", - "Concept ID 36562319 passes: True, reasons: []\n", - "Concept ID 36548017 passes: True, reasons: []\n", - "Concept ID 36521025 passes: True, reasons: []\n", - "Concept ID 36548981 passes: True, reasons: []\n", - "Concept ID 36529861 passes: True, reasons: []\n", - "Concept ID 36519652 passes: True, reasons: []\n", - "Concept ID 36525499 passes: True, reasons: []\n", - "Concept ID 36554740 passes: True, reasons: []\n", - "Concept ID 36547168 passes: True, reasons: []\n", - "Concept ID 36523951 passes: True, reasons: []\n", - "Concept ID 36546271 passes: True, reasons: []\n", - "Concept ID 36554525 passes: True, reasons: []\n", - "Concept ID 36553540 passes: True, reasons: []\n", - "Concept ID 36521807 passes: True, reasons: []\n", - "Concept ID 36561947 passes: True, reasons: []\n", - "Concept ID 36551245 passes: True, reasons: []\n", - "Concept ID 36565264 passes: True, reasons: []\n", - "Concept ID 36562301 passes: True, reasons: []\n", - "Concept ID 36555247 passes: True, reasons: []\n", - "Concept ID 36518959 passes: True, reasons: []\n", - "Concept ID 36565235 passes: True, reasons: []\n", - "Concept ID 36525038 passes: True, reasons: []\n", - "Concept ID 36556238 passes: True, reasons: []\n", - "Concept ID 36547859 passes: True, reasons: []\n", - "Concept ID 36551219 passes: True, reasons: []\n", - "Concept ID 36550415 passes: True, reasons: []\n", - "Concept ID 36519013 passes: True, reasons: []\n", - "Concept ID 36563099 passes: True, reasons: []\n", - "Concept ID 36547518 passes: True, reasons: []\n", - "Concept ID 36536334 passes: True, reasons: []\n", - "Concept ID 36559563 passes: True, reasons: []\n", - "Concept ID 36540062 passes: True, reasons: []\n", - "Concept ID 36534878 passes: True, reasons: []\n", - "Concept ID 36536420 passes: True, reasons: []\n", - "Concept ID 36547397 passes: True, reasons: []\n", - "Concept ID 36544224 passes: True, reasons: []\n", - "Concept ID 36553519 passes: True, reasons: []\n", - "Concept ID 36522120 passes: True, reasons: []\n", - "Concept ID 36541291 passes: True, reasons: []\n", - "Concept ID 36542102 passes: True, reasons: []\n", - "Concept ID 36551612 passes: True, reasons: []\n", - "Concept ID 36546015 passes: True, reasons: []\n", - "Concept ID 36550242 passes: True, reasons: []\n", - "Concept ID 36559862 passes: True, reasons: []\n", - "Concept ID 36553484 passes: True, reasons: []\n", - "Concept ID 36529438 passes: True, reasons: []\n", - "Concept ID 36535302 passes: True, reasons: []\n", - "Concept ID 44500108 passes: True, reasons: []\n", - "Concept ID 36553264 passes: True, reasons: []\n", - "Concept ID 44500279 passes: True, reasons: []\n", - "Concept ID 36519493 passes: True, reasons: []\n", - "Concept ID 36533119 passes: True, reasons: []\n", - "Concept ID 36567777 passes: True, reasons: []\n", - "Concept ID 36563602 passes: True, reasons: []\n", - "Concept ID 36559591 passes: True, reasons: []\n", - "Concept ID 36545395 passes: True, reasons: []\n", - "Concept ID 36548293 passes: True, reasons: []\n", - "Concept ID 36563486 passes: True, reasons: []\n", - "Concept ID 36554763 passes: True, reasons: []\n", - "Concept ID 36548636 passes: True, reasons: []\n", - "Concept ID 36520920 passes: True, reasons: []\n", - "Concept ID 36564779 passes: True, reasons: []\n", - "Concept ID 36541842 passes: True, reasons: []\n", - "Concept ID 36533676 passes: True, reasons: []\n", - "Concept ID 36521033 passes: True, reasons: []\n", - "Concept ID 36564361 passes: True, reasons: []\n", - "Concept ID 36529610 passes: True, reasons: []\n", - "Concept ID 36567301 passes: True, reasons: []\n", - "Concept ID 36538813 passes: True, reasons: []\n", - "Concept ID 36524694 passes: True, reasons: []\n", - "Concept ID 36518628 passes: True, reasons: []\n", - "Concept ID 36520966 passes: True, reasons: []\n", - "Concept ID 36552780 passes: True, reasons: []\n", - "Concept ID 36540197 passes: True, reasons: []\n", - "Concept ID 36547901 passes: True, reasons: []\n", - "Concept ID 36553968 passes: True, reasons: []\n", - "Concept ID 36561840 passes: True, reasons: []\n", - "Concept ID 36551152 passes: True, reasons: []\n", - "Concept ID 36547516 passes: True, reasons: []\n", - "Concept ID 36555729 passes: True, reasons: []\n", - "Concept ID 36533894 passes: True, reasons: []\n", - "Concept ID 36535258 passes: True, reasons: []\n", - "Concept ID 36553624 passes: True, reasons: []\n", - "Concept ID 36530671 passes: True, reasons: []\n", - "Concept ID 36564743 passes: True, reasons: []\n", - "Concept ID 36548107 passes: True, reasons: []\n", - "Concept ID 36554074 passes: True, reasons: []\n", - "Concept ID 36530772 passes: True, reasons: []\n", - "Concept ID 36545622 passes: True, reasons: []\n", - "Concept ID 36535912 passes: True, reasons: []\n", - "Concept ID 36517458 passes: True, reasons: []\n", - "Concept ID 36526405 passes: True, reasons: []\n", - "Concept ID 36563561 passes: True, reasons: []\n", - "Concept ID 36522159 passes: True, reasons: []\n", - "Concept ID 36550804 passes: True, reasons: []\n", - "Concept ID 36541860 passes: True, reasons: []\n", - "Concept ID 36563331 passes: True, reasons: []\n", - "Concept ID 44501297 passes: True, reasons: []\n", - "Concept ID 36554330 passes: True, reasons: []\n", - "Concept ID 36564322 passes: True, reasons: []\n", - "Concept ID 36564811 passes: True, reasons: []\n", - "Concept ID 36548079 passes: True, reasons: []\n", - "Concept ID 36552543 passes: True, reasons: []\n", - "Concept ID 36538372 passes: True, reasons: []\n", - "Concept ID 36555127 passes: True, reasons: []\n", - "Concept ID 36534327 passes: True, reasons: []\n", - "Concept ID 36541200 passes: True, reasons: []\n", - "Concept ID 36555248 passes: True, reasons: []\n", - "Concept ID 36545567 passes: True, reasons: []\n", - "Concept ID 36537266 passes: True, reasons: []\n", - "Concept ID 36562825 passes: True, reasons: []\n", - "Concept ID 36544953 passes: True, reasons: []\n", - "Concept ID 36556368 passes: True, reasons: []\n", - "Concept ID 36517614 passes: True, reasons: []\n", - "Concept ID 36561630 passes: True, reasons: []\n", - "Concept ID 36541415 passes: True, reasons: []\n", - "Concept ID 36524450 passes: True, reasons: []\n", - "Concept ID 36566253 passes: True, reasons: []\n", - "Concept ID 36540570 passes: True, reasons: []\n", - "Concept ID 36529524 passes: True, reasons: []\n", - "Concept ID 36563068 passes: True, reasons: []\n", - "Concept ID 36547831 passes: True, reasons: []\n", - "Concept ID 45581233 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36547653 passes: True, reasons: []\n", - "Concept ID 36530997 passes: True, reasons: []\n", - "Concept ID 36525778 passes: True, reasons: []\n", - "Concept ID 36558637 passes: True, reasons: []\n", - "Concept ID 36550928 passes: True, reasons: []\n", - "Concept ID 36552311 passes: True, reasons: []\n", - "Concept ID 36555366 passes: True, reasons: []\n", - "Concept ID 36528873 passes: True, reasons: []\n", - "Concept ID 36523986 passes: True, reasons: []\n", - "Concept ID 36519568 passes: True, reasons: []\n", - "Concept ID 36564254 passes: True, reasons: []\n", - "Concept ID 36562597 passes: True, reasons: []\n", - "Concept ID 36534405 passes: True, reasons: []\n", - "Concept ID 36556094 passes: True, reasons: []\n", - "Concept ID 36552600 passes: True, reasons: []\n", - "Concept ID 36563430 passes: True, reasons: []\n", - "Concept ID 36521037 passes: True, reasons: []\n", - "Concept ID 36523214 passes: True, reasons: []\n", - "Concept ID 36521115 passes: True, reasons: []\n", - "Concept ID 36555491 passes: True, reasons: []\n", - "Concept ID 36536150 passes: True, reasons: []\n", - "Concept ID 36536814 passes: True, reasons: []\n", - "Concept ID 36554627 passes: True, reasons: []\n", - "Concept ID 36545672 passes: True, reasons: []\n", - "Concept ID 36563328 passes: True, reasons: []\n", - "Concept ID 36538942 passes: True, reasons: []\n", - "Concept ID 36543586 passes: True, reasons: []\n", - "Concept ID 36528648 passes: True, reasons: []\n", - "Concept ID 36542555 passes: True, reasons: []\n", - "Concept ID 36539573 passes: True, reasons: []\n", - "Concept ID 36544446 passes: True, reasons: []\n", - "Concept ID 36531294 passes: True, reasons: []\n", - "Concept ID 36521000 passes: True, reasons: []\n", - "Concept ID 36551782 passes: True, reasons: []\n", - "Concept ID 36558034 passes: True, reasons: []\n", - "Concept ID 36528042 passes: True, reasons: []\n", - "Concept ID 36518385 passes: True, reasons: []\n", - "Concept ID 36567064 passes: True, reasons: []\n", - "Concept ID 36520447 passes: True, reasons: []\n", - "Concept ID 36554253 passes: True, reasons: []\n", - "Concept ID 36561018 passes: True, reasons: []\n", - "Concept ID 36557496 passes: True, reasons: []\n", - "Concept ID 36402824 passes: True, reasons: []\n", - "Concept ID 36535710 passes: True, reasons: []\n", - "Concept ID 36520850 passes: True, reasons: []\n", - "Concept ID 36556219 passes: True, reasons: []\n", - "Concept ID 36537407 passes: True, reasons: []\n", - "Concept ID 36549121 passes: True, reasons: []\n", - "Concept ID 36558422 passes: True, reasons: []\n", - "Concept ID 36524790 passes: True, reasons: []\n", - "Concept ID 44499847 passes: True, reasons: []\n", - "Concept ID 36521071 passes: True, reasons: []\n", - "Concept ID 36523885 passes: True, reasons: []\n", - "Concept ID 36523356 passes: True, reasons: []\n", - "Concept ID 36547296 passes: True, reasons: []\n", - "Concept ID 36558854 passes: True, reasons: []\n", - "Concept ID 36524540 passes: True, reasons: []\n", - "Concept ID 36521248 passes: True, reasons: []\n", - "Concept ID 36530044 passes: True, reasons: []\n", - "Concept ID 36552698 passes: True, reasons: []\n", - "Concept ID 36553990 passes: True, reasons: []\n", - "Concept ID 36550786 passes: True, reasons: []\n", - "Concept ID 36550297 passes: True, reasons: []\n", - "Concept ID 36540649 passes: True, reasons: []\n", - "Concept ID 36532735 passes: True, reasons: []\n", - "Concept ID 36548341 passes: True, reasons: []\n", - "Concept ID 36543707 passes: True, reasons: []\n", - "Concept ID 36538460 passes: True, reasons: []\n", - "Concept ID 36541386 passes: True, reasons: []\n", - "Concept ID 36556749 passes: True, reasons: []\n", - "Concept ID 36538053 passes: True, reasons: []\n", - "Concept ID 36533705 passes: True, reasons: []\n", - "Concept ID 36542571 passes: True, reasons: []\n", - "Concept ID 36528264 passes: True, reasons: []\n", - "Concept ID 36567796 passes: True, reasons: []\n", - "Concept ID 36559237 passes: True, reasons: []\n", - "Concept ID 36554788 passes: True, reasons: []\n", - "Concept ID 36538172 passes: True, reasons: []\n", - "Concept ID 36537357 passes: True, reasons: []\n", - "Concept ID 36537860 passes: True, reasons: []\n", - "Concept ID 36521007 passes: True, reasons: []\n", - "Concept ID 36561902 passes: True, reasons: []\n", - "Concept ID 36524379 passes: True, reasons: []\n", - "Concept ID 36563145 passes: True, reasons: []\n", - "Concept ID 36536136 passes: True, reasons: []\n", - "Concept ID 44500533 passes: True, reasons: []\n", - "Concept ID 36550059 passes: True, reasons: []\n", - "Concept ID 36544843 passes: True, reasons: []\n", - "Concept ID 36548987 passes: True, reasons: []\n", - "Concept ID 36554187 passes: True, reasons: []\n", - "Concept ID 36528893 passes: True, reasons: []\n", - "Concept ID 36553879 passes: True, reasons: []\n", - "Concept ID 36536346 passes: True, reasons: []\n", - "Concept ID 36552679 passes: True, reasons: []\n", - "Concept ID 36524881 passes: True, reasons: []\n", - "Concept ID 36533412 passes: True, reasons: []\n", - "Concept ID 36532188 passes: True, reasons: []\n", - "Concept ID 36546583 passes: True, reasons: []\n", - "Concept ID 36546288 passes: True, reasons: []\n", - "Concept ID 36548131 passes: True, reasons: []\n", - "Concept ID 36559419 passes: True, reasons: []\n", - "Concept ID 36551555 passes: True, reasons: []\n", - "Concept ID 36561986 passes: True, reasons: []\n", - "Concept ID 36542400 passes: True, reasons: []\n", - "Concept ID 36530897 passes: True, reasons: []\n", - "Concept ID 36524872 passes: True, reasons: []\n", - "Concept ID 36540655 passes: True, reasons: []\n", - "Concept ID 36533691 passes: True, reasons: []\n", - "Concept ID 36521724 passes: True, reasons: []\n", - "Concept ID 36522586 passes: True, reasons: []\n", - "Concept ID 36532873 passes: True, reasons: []\n", - "Concept ID 36551865 passes: True, reasons: []\n", - "Concept ID 36560409 passes: True, reasons: []\n", - "Concept ID 36555915 passes: True, reasons: []\n", - "Concept ID 36535291 passes: True, reasons: []\n", - "Concept ID 36545676 passes: True, reasons: []\n", - "Concept ID 36552031 passes: True, reasons: []\n", - "Concept ID 36557619 passes: True, reasons: []\n", - "Concept ID 36564541 passes: True, reasons: []\n", - "Concept ID 36552175 passes: True, reasons: []\n", - "Concept ID 36526939 passes: True, reasons: []\n", - "Concept ID 36559856 passes: True, reasons: []\n", - "Concept ID 36540565 passes: True, reasons: []\n", - "Concept ID 36559569 passes: True, reasons: []\n", - "Concept ID 36530028 passes: True, reasons: []\n", - "Concept ID 36530899 passes: True, reasons: []\n", - "Concept ID 36519974 passes: True, reasons: []\n", - "Concept ID 36530381 passes: True, reasons: []\n", - "Concept ID 45576348 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36551890 passes: True, reasons: []\n", - "Concept ID 36558253 passes: True, reasons: []\n", - "Concept ID 36539680 passes: True, reasons: []\n", - "Concept ID 36535568 passes: True, reasons: []\n", - "Concept ID 36517803 passes: True, reasons: []\n", - "Concept ID 36523911 passes: True, reasons: []\n", - "Concept ID 36527102 passes: True, reasons: []\n", - "Concept ID 36537754 passes: True, reasons: []\n", - "Concept ID 36542871 passes: True, reasons: []\n", - "Concept ID 36526754 passes: True, reasons: []\n", - "Concept ID 36541249 passes: True, reasons: []\n", - "Concept ID 36523438 passes: True, reasons: []\n", - "Concept ID 36545399 passes: True, reasons: []\n", - "Concept ID 36564785 passes: True, reasons: []\n", - "Concept ID 36565203 passes: True, reasons: []\n", - "Concept ID 36527463 passes: True, reasons: []\n", - "Concept ID 36564696 passes: True, reasons: []\n", - "Concept ID 36526486 passes: True, reasons: []\n", - "Concept ID 36522403 passes: True, reasons: []\n", - "Concept ID 36560991 passes: True, reasons: []\n", - "Concept ID 36537827 passes: True, reasons: []\n", - "Concept ID 36529281 passes: True, reasons: []\n", - "Concept ID 36524315 passes: True, reasons: []\n", - "Concept ID 36545528 passes: True, reasons: []\n", - "Concept ID 36567189 passes: True, reasons: []\n", - "Concept ID 36540620 passes: True, reasons: []\n", - "Concept ID 36521326 passes: True, reasons: []\n", - "Concept ID 36562861 passes: True, reasons: []\n", - "Concept ID 36544266 passes: True, reasons: []\n", - "Concept ID 36550724 passes: True, reasons: []\n", - "Concept ID 36558790 passes: True, reasons: []\n", - "Concept ID 36533395 passes: True, reasons: []\n", - "Concept ID 36565941 passes: True, reasons: []\n", - "Concept ID 36558308 passes: True, reasons: []\n", - "Concept ID 36526285 passes: True, reasons: []\n", - "Concept ID 36541941 passes: True, reasons: []\n", - "Concept ID 36538318 passes: True, reasons: []\n", - "Concept ID 36519753 passes: True, reasons: []\n", - "Concept ID 36524102 passes: True, reasons: []\n", - "Concept ID 36558921 passes: True, reasons: []\n", - "Concept ID 36539783 passes: True, reasons: []\n", - "Concept ID 36560986 passes: True, reasons: []\n", - "Concept ID 36550087 passes: True, reasons: []\n", - "Concept ID 36522399 passes: True, reasons: []\n", - "Concept ID 36534512 passes: True, reasons: []\n", - "Concept ID 36555291 passes: True, reasons: []\n", - "Concept ID 36563055 passes: True, reasons: []\n", - "Concept ID 36566119 passes: True, reasons: []\n", - "Concept ID 36537749 passes: True, reasons: []\n", - "Concept ID 36537263 passes: True, reasons: []\n", - "Concept ID 36529426 passes: True, reasons: []\n", - "Concept ID 36551794 passes: True, reasons: []\n", - "Concept ID 36566866 passes: True, reasons: []\n", - "Concept ID 36532157 passes: True, reasons: []\n", - "Concept ID 36528902 passes: True, reasons: []\n", - "Concept ID 36549996 passes: True, reasons: []\n", - "Concept ID 36562499 passes: True, reasons: []\n", - "Concept ID 36565857 passes: True, reasons: []\n", - "Concept ID 36535350 passes: True, reasons: []\n", - "Concept ID 36553704 passes: True, reasons: []\n", - "Concept ID 36519504 passes: True, reasons: []\n", - "Concept ID 36556345 passes: True, reasons: []\n", - "Concept ID 36563144 passes: True, reasons: []\n", - "Concept ID 36534426 passes: True, reasons: []\n", - "Concept ID 36559872 passes: True, reasons: []\n", - "Concept ID 36520721 passes: True, reasons: []\n", - "Concept ID 36533455 passes: True, reasons: []\n", - "Concept ID 36561095 passes: True, reasons: []\n", - "Concept ID 36543737 passes: True, reasons: []\n", - "Concept ID 36539903 passes: True, reasons: []\n", - "Concept ID 36560439 passes: True, reasons: []\n", - "Concept ID 36555282 passes: True, reasons: []\n", - "Concept ID 36524514 passes: True, reasons: []\n", - "Concept ID 36554725 passes: True, reasons: []\n", - "Concept ID 36566018 passes: True, reasons: []\n", - "Concept ID 36523079 passes: True, reasons: []\n", - "Concept ID 36525272 passes: True, reasons: []\n", - "Concept ID 36564987 passes: True, reasons: []\n", - "Concept ID 36535270 passes: True, reasons: []\n", - "Concept ID 36560276 passes: True, reasons: []\n", - "Concept ID 36525125 passes: True, reasons: []\n", - "Concept ID 36530952 passes: True, reasons: []\n", - "Concept ID 36552875 passes: True, reasons: []\n", - "Concept ID 36529583 passes: True, reasons: []\n", - "Concept ID 36554004 passes: True, reasons: []\n", - "Concept ID 36523188 passes: True, reasons: []\n", - "Concept ID 36551509 passes: True, reasons: []\n", - "Concept ID 36543942 passes: True, reasons: []\n", - "Concept ID 36549303 passes: True, reasons: []\n", - "Concept ID 36536282 passes: True, reasons: []\n", - "Concept ID 36550497 passes: True, reasons: []\n", - "Concept ID 36541929 passes: True, reasons: []\n", - "Concept ID 36539393 passes: True, reasons: []\n", - "Concept ID 36519778 passes: True, reasons: []\n", - "Concept ID 36540517 passes: True, reasons: []\n", - "Concept ID 36566104 passes: True, reasons: []\n", - "Concept ID 36526358 passes: True, reasons: []\n", - "Concept ID 36522875 passes: True, reasons: []\n", - "Concept ID 36525384 passes: True, reasons: []\n", - "Concept ID 36528077 passes: True, reasons: []\n", - "Concept ID 36566696 passes: True, reasons: []\n", - "Concept ID 36524611 passes: True, reasons: []\n", - "Concept ID 36563644 passes: True, reasons: []\n", - "Concept ID 36552304 passes: True, reasons: []\n", - "Concept ID 36526538 passes: True, reasons: []\n", - "Concept ID 36527879 passes: True, reasons: []\n", - "Concept ID 36566349 passes: True, reasons: []\n", - "Concept ID 36565873 passes: True, reasons: []\n", - "Concept ID 36554548 passes: True, reasons: []\n", - "Concept ID 36547333 passes: True, reasons: []\n", - "Concept ID 36402847 passes: True, reasons: []\n", - "Concept ID 36539456 passes: True, reasons: []\n", - "Concept ID 36544043 passes: True, reasons: []\n", - "Concept ID 36555583 passes: True, reasons: []\n", - "Concept ID 36552520 passes: True, reasons: []\n", - "Concept ID 36521470 passes: True, reasons: []\n", - "Concept ID 36551631 passes: True, reasons: []\n", - "Concept ID 36519576 passes: True, reasons: []\n", - "Concept ID 36566457 passes: True, reasons: []\n", - "Concept ID 36543335 passes: True, reasons: []\n", - "Concept ID 36525128 passes: True, reasons: []\n", - "Concept ID 36555430 passes: True, reasons: []\n", - "Concept ID 36563148 passes: True, reasons: []\n", - "Concept ID 36545204 passes: True, reasons: []\n", - "Concept ID 36535097 passes: True, reasons: []\n", - "Concept ID 36552703 passes: True, reasons: []\n", - "Concept ID 36532872 passes: True, reasons: []\n", - "Concept ID 36526091 passes: True, reasons: []\n", - "Concept ID 36537467 passes: True, reasons: []\n", - "Concept ID 36554871 passes: True, reasons: []\n", - "Concept ID 36535076 passes: True, reasons: []\n", - "Concept ID 36521077 passes: True, reasons: []\n", - "Concept ID 36538506 passes: True, reasons: []\n", - "Concept ID 36559588 passes: True, reasons: []\n", - "Concept ID 36542805 passes: True, reasons: []\n", - "Concept ID 36546675 passes: True, reasons: []\n", - "Concept ID 36564420 passes: True, reasons: []\n", - "Concept ID 36546233 passes: True, reasons: []\n", - "Concept ID 36555680 passes: True, reasons: []\n", - "Concept ID 36551887 passes: True, reasons: []\n", - "Concept ID 36532028 passes: True, reasons: []\n", - "Concept ID 36547568 passes: True, reasons: []\n", - "Concept ID 36520113 passes: True, reasons: []\n", - "Concept ID 36556952 passes: True, reasons: []\n", - "Concept ID 36557128 passes: True, reasons: []\n", - "Concept ID 36552497 passes: True, reasons: []\n", - "Concept ID 36554543 passes: True, reasons: []\n", - "Concept ID 36545040 passes: True, reasons: []\n", - "Concept ID 36527765 passes: True, reasons: []\n", - "Concept ID 36519485 passes: True, reasons: []\n", - "Concept ID 36539832 passes: True, reasons: []\n", - "Concept ID 36566216 passes: True, reasons: []\n", - "Concept ID 36544026 passes: True, reasons: []\n", - "Concept ID 36532218 passes: True, reasons: []\n", - "Concept ID 36527390 passes: True, reasons: []\n", - "Concept ID 36567191 passes: True, reasons: []\n", - "Concept ID 36525195 passes: True, reasons: []\n", - "Concept ID 36539272 passes: True, reasons: []\n", - "Concept ID 36545525 passes: True, reasons: []\n", - "Concept ID 36529011 passes: True, reasons: []\n", - "Concept ID 36538401 passes: True, reasons: []\n", - "Concept ID 36519596 passes: True, reasons: []\n", - "Concept ID 36556787 passes: True, reasons: []\n", - "Concept ID 36540231 passes: True, reasons: []\n", - "Concept ID 36548320 passes: True, reasons: []\n", - "Concept ID 36517976 passes: True, reasons: []\n", - "Concept ID 36549035 passes: True, reasons: []\n", - "Concept ID 36539797 passes: True, reasons: []\n", - "Concept ID 36566564 passes: True, reasons: []\n", - "Concept ID 36563220 passes: True, reasons: []\n", - "Concept ID 36553263 passes: True, reasons: []\n", - "Concept ID 36565769 passes: True, reasons: []\n", - "Concept ID 36528167 passes: True, reasons: []\n", - "Concept ID 36518597 passes: True, reasons: []\n", - "Concept ID 36549529 passes: True, reasons: []\n", - "Concept ID 36562498 passes: True, reasons: []\n", - "Concept ID 36565365 passes: True, reasons: []\n", - "Concept ID 36530249 passes: True, reasons: []\n", - "Concept ID 36558280 passes: True, reasons: []\n", - "Concept ID 36523314 passes: True, reasons: []\n", - "Concept ID 36559049 passes: True, reasons: []\n", - "Concept ID 36530566 passes: True, reasons: []\n", - "Concept ID 36532653 passes: True, reasons: []\n", - "Concept ID 36532934 passes: True, reasons: []\n", - "Concept ID 36545016 passes: True, reasons: []\n", - "Concept ID 36542137 passes: True, reasons: []\n", - "Concept ID 36546664 passes: True, reasons: []\n", - "Concept ID 36525862 passes: True, reasons: []\n", - "Concept ID 36523152 passes: True, reasons: []\n", - "Concept ID 36540661 passes: True, reasons: []\n", - "Concept ID 36524179 passes: True, reasons: []\n", - "Concept ID 36553339 passes: True, reasons: []\n", - "Concept ID 36554831 passes: True, reasons: []\n", - "Concept ID 36560474 passes: True, reasons: []\n", - "Concept ID 36535504 passes: True, reasons: []\n", - "Concept ID 36556621 passes: True, reasons: []\n", - "Concept ID 36533109 passes: True, reasons: []\n", - "Concept ID 36531384 passes: True, reasons: []\n", - "Concept ID 36526789 passes: True, reasons: []\n", - "Concept ID 36548581 passes: True, reasons: []\n", - "Concept ID 36559923 passes: True, reasons: []\n", - "Concept ID 36556466 passes: True, reasons: []\n", - "Concept ID 36523357 passes: True, reasons: []\n", - "Concept ID 36560773 passes: True, reasons: []\n", - "Concept ID 36550110 passes: True, reasons: []\n", - "Concept ID 36550542 passes: True, reasons: []\n", - "Concept ID 36536918 passes: True, reasons: []\n", - "Concept ID 36539725 passes: True, reasons: []\n", - "Concept ID 36567079 passes: True, reasons: []\n", - "Concept ID 36563324 passes: True, reasons: []\n", - "Concept ID 36528109 passes: True, reasons: []\n", - "Concept ID 36517956 passes: True, reasons: []\n", - "Concept ID 36533135 passes: True, reasons: []\n", - "Concept ID 36558748 passes: True, reasons: []\n", - "Concept ID 36558409 passes: True, reasons: []\n", - "Concept ID 36540566 passes: True, reasons: []\n", - "Concept ID 36556942 passes: True, reasons: []\n", - "Concept ID 36534159 passes: True, reasons: []\n", - "Concept ID 36554385 passes: True, reasons: []\n", - "Concept ID 36550368 passes: True, reasons: []\n", - "Concept ID 36526825 passes: True, reasons: []\n", - "Concept ID 36550346 passes: True, reasons: []\n", - "Concept ID 36536074 passes: True, reasons: []\n", - "Concept ID 36525113 passes: True, reasons: []\n", - "Concept ID 36563419 passes: True, reasons: []\n", - "Concept ID 36548261 passes: True, reasons: []\n", - "Concept ID 36532654 passes: True, reasons: []\n", - "Concept ID 36545400 passes: True, reasons: []\n", - "Concept ID 36521649 passes: True, reasons: []\n", - "Concept ID 36537158 passes: True, reasons: []\n", - "Concept ID 36563244 passes: True, reasons: []\n", - "Concept ID 36547544 passes: True, reasons: []\n", - "Concept ID 36552659 passes: True, reasons: []\n", - "Concept ID 36535891 passes: True, reasons: []\n", - "Concept ID 36530060 passes: True, reasons: []\n", - "Concept ID 36552412 passes: True, reasons: []\n", - "Concept ID 36554220 passes: True, reasons: []\n", - "Concept ID 36522237 passes: True, reasons: []\n", - "Concept ID 36558081 passes: True, reasons: []\n", - "Concept ID 36545265 passes: True, reasons: []\n", - "Concept ID 36542203 passes: True, reasons: []\n", - "Concept ID 36566918 passes: True, reasons: []\n", - "Concept ID 36542476 passes: True, reasons: []\n", - "Concept ID 36531636 passes: True, reasons: []\n", - "Concept ID 36558662 passes: True, reasons: []\n", - "Concept ID 36538353 passes: True, reasons: []\n", - "Concept ID 36519049 passes: True, reasons: []\n", - "Concept ID 36560692 passes: True, reasons: []\n", - "Concept ID 36534810 passes: True, reasons: []\n", - "Concept ID 36524002 passes: True, reasons: []\n", - "Concept ID 36528505 passes: True, reasons: []\n", - "Concept ID 36531048 passes: True, reasons: []\n", - "Concept ID 36549383 passes: True, reasons: []\n", - "Concept ID 36562514 passes: True, reasons: []\n", - "Concept ID 36523880 passes: True, reasons: []\n", - "Concept ID 36535667 passes: True, reasons: []\n", - "Concept ID 36545334 passes: True, reasons: []\n", - "Concept ID 36565217 passes: True, reasons: []\n", - "Concept ID 36566934 passes: True, reasons: []\n", - "Concept ID 36550562 passes: True, reasons: []\n", - "Concept ID 36536607 passes: True, reasons: []\n", - "Concept ID 36550774 passes: True, reasons: []\n", - "Concept ID 36524651 passes: True, reasons: []\n", - "Concept ID 36554177 passes: True, reasons: []\n", - "Concept ID 36551378 passes: True, reasons: []\n", - "Concept ID 36536477 passes: True, reasons: []\n", - "Concept ID 36519035 passes: True, reasons: []\n", - "Concept ID 36531942 passes: True, reasons: []\n", - "Concept ID 36541078 passes: True, reasons: []\n", - "Concept ID 36532489 passes: True, reasons: []\n", - "Concept ID 36523006 passes: True, reasons: []\n", - "Concept ID 36534569 passes: True, reasons: []\n", - "Concept ID 36535173 passes: True, reasons: []\n", - "Concept ID 36518716 passes: True, reasons: []\n", - "Concept ID 36542340 passes: True, reasons: []\n", - "Concept ID 36565963 passes: True, reasons: []\n", - "Concept ID 36553732 passes: True, reasons: []\n", - "Concept ID 36539327 passes: True, reasons: []\n", - "Concept ID 36520354 passes: True, reasons: []\n", - "Concept ID 36523904 passes: True, reasons: []\n", - "Concept ID 36552539 passes: True, reasons: []\n", - "Concept ID 36554768 passes: True, reasons: []\n", - "Concept ID 36546538 passes: True, reasons: []\n", - "Concept ID 36565489 passes: True, reasons: []\n", - "Concept ID 36536766 passes: True, reasons: []\n", - "Concept ID 36561741 passes: True, reasons: []\n", - "Concept ID 36567458 passes: True, reasons: []\n", - "Concept ID 36542640 passes: True, reasons: []\n", - "Concept ID 36561177 passes: True, reasons: []\n", - "Concept ID 36526441 passes: True, reasons: []\n", - "Concept ID 36539720 passes: True, reasons: []\n", - "Concept ID 36551268 passes: True, reasons: []\n", - "Concept ID 36559596 passes: True, reasons: []\n", - "Concept ID 36520205 passes: True, reasons: []\n", - "Concept ID 36556322 passes: True, reasons: []\n", - "Concept ID 36524181 passes: True, reasons: []\n", - "Concept ID 36539420 passes: True, reasons: []\n", - "Concept ID 36547799 passes: True, reasons: []\n", - "Concept ID 36565844 passes: True, reasons: []\n", - "Concept ID 36539017 passes: True, reasons: []\n", - "Concept ID 36528419 passes: True, reasons: []\n", - "Concept ID 36528608 passes: True, reasons: []\n", - "Concept ID 36566807 passes: True, reasons: []\n", - "Concept ID 36533852 passes: True, reasons: []\n", - "Concept ID 36526964 passes: True, reasons: []\n", - "Concept ID 36517519 passes: True, reasons: []\n", - "Concept ID 36561409 passes: True, reasons: []\n", - "Concept ID 36560842 passes: True, reasons: []\n", - "Concept ID 36534648 passes: True, reasons: []\n", - "Concept ID 36528747 passes: True, reasons: []\n", - "Concept ID 36558410 passes: True, reasons: []\n", - "Concept ID 36519947 passes: True, reasons: []\n", - "Concept ID 36554584 passes: True, reasons: []\n", - "Concept ID 36552737 passes: True, reasons: []\n", - "Concept ID 36566000 passes: True, reasons: []\n", - "Concept ID 36533258 passes: True, reasons: []\n", - "Concept ID 36560672 passes: True, reasons: []\n", - "Concept ID 36566290 passes: True, reasons: []\n", - "Concept ID 36524336 passes: True, reasons: []\n", - "Concept ID 36521907 passes: True, reasons: []\n", - "Concept ID 36534812 passes: True, reasons: []\n", - "Concept ID 36563472 passes: True, reasons: []\n", - "Concept ID 36517334 passes: True, reasons: []\n", - "Concept ID 36558479 passes: True, reasons: []\n", - "Concept ID 36525425 passes: True, reasons: []\n", - "Concept ID 36545192 passes: True, reasons: []\n", - "Concept ID 36537180 passes: True, reasons: []\n", - "Concept ID 36553168 passes: True, reasons: []\n", - "Concept ID 36536958 passes: True, reasons: []\n", - "Concept ID 36527425 passes: True, reasons: []\n", - "Concept ID 36527110 passes: True, reasons: []\n", - "Concept ID 36545071 passes: True, reasons: []\n", - "Concept ID 36536928 passes: True, reasons: []\n", - "Concept ID 36552141 passes: True, reasons: []\n", - "Concept ID 36557002 passes: True, reasons: []\n", - "Concept ID 36564179 passes: True, reasons: []\n", - "Concept ID 36561915 passes: True, reasons: []\n", - "Concept ID 36532952 passes: True, reasons: []\n", - "Concept ID 36532321 passes: True, reasons: []\n", - "Concept ID 36565338 passes: True, reasons: []\n", - "Concept ID 36549169 passes: True, reasons: []\n", - "Concept ID 36524259 passes: True, reasons: []\n", - "Concept ID 36527601 passes: True, reasons: []\n", - "Concept ID 36530791 passes: True, reasons: []\n", - "Concept ID 36524952 passes: True, reasons: []\n", - "Concept ID 36543028 passes: True, reasons: []\n", - "Concept ID 36533827 passes: True, reasons: []\n", - "Concept ID 36522255 passes: True, reasons: []\n", - "Concept ID 36556888 passes: True, reasons: []\n", - "Concept ID 36526656 passes: True, reasons: []\n", - "Concept ID 36528855 passes: True, reasons: []\n", - "Concept ID 36538683 passes: True, reasons: []\n", - "Concept ID 36537361 passes: True, reasons: []\n", - "Concept ID 36539685 passes: True, reasons: []\n", - "Concept ID 36567894 passes: True, reasons: []\n", - "Concept ID 36544748 passes: True, reasons: []\n", - "Concept ID 36530944 passes: True, reasons: []\n", - "Concept ID 36555258 passes: True, reasons: []\n", - "Concept ID 36567323 passes: True, reasons: []\n", - "Concept ID 36539955 passes: True, reasons: []\n", - "Concept ID 36554903 passes: True, reasons: []\n", - "Concept ID 36551669 passes: True, reasons: []\n", - "Concept ID 36535898 passes: True, reasons: []\n", - "Concept ID 36557164 passes: True, reasons: []\n", - "Concept ID 36542333 passes: True, reasons: []\n", - "Concept ID 36536615 passes: True, reasons: []\n", - "Concept ID 36528811 passes: True, reasons: []\n", - "Concept ID 36544207 passes: True, reasons: []\n", - "Concept ID 36541253 passes: True, reasons: []\n", - "Concept ID 36548900 passes: True, reasons: []\n", - "Concept ID 36539190 passes: True, reasons: []\n", - "Concept ID 36535588 passes: True, reasons: []\n", - "Concept ID 36567187 passes: True, reasons: []\n", - "Concept ID 36530482 passes: True, reasons: []\n", - "Concept ID 36551315 passes: True, reasons: []\n", - "Concept ID 36519820 passes: True, reasons: []\n", - "Concept ID 44503305 passes: True, reasons: []\n", - "Concept ID 36543712 passes: True, reasons: []\n", - "Concept ID 36559963 passes: True, reasons: []\n", - "Concept ID 44503307 passes: True, reasons: []\n", - "Concept ID 36525290 passes: True, reasons: []\n", - "Concept ID 36544952 passes: True, reasons: []\n", - "Concept ID 36526347 passes: True, reasons: []\n", - "Concept ID 36535550 passes: True, reasons: []\n", - "Concept ID 36549653 passes: True, reasons: []\n", - "Concept ID 36558809 passes: True, reasons: []\n", - "Concept ID 36549399 passes: True, reasons: []\n", - "Concept ID 36538804 passes: True, reasons: []\n", - "Concept ID 36532704 passes: True, reasons: []\n", - "Concept ID 36531306 passes: True, reasons: []\n", - "Concept ID 36553381 passes: True, reasons: []\n", - "Concept ID 36528543 passes: True, reasons: []\n", - "Concept ID 36525959 passes: True, reasons: []\n", - "Concept ID 36552361 passes: True, reasons: []\n", - "Concept ID 36565979 passes: True, reasons: []\n", - "Concept ID 36552496 passes: True, reasons: []\n", - "Concept ID 36557476 passes: True, reasons: []\n", - "Concept ID 36546572 passes: True, reasons: []\n", - "Concept ID 36548484 passes: True, reasons: []\n", - "Concept ID 36549802 passes: True, reasons: []\n", - "Concept ID 36532851 passes: True, reasons: []\n", - "Concept ID 36554200 passes: True, reasons: []\n", - "Concept ID 36537218 passes: True, reasons: []\n", - "Concept ID 36523394 passes: True, reasons: []\n", - "Concept ID 36550525 passes: True, reasons: []\n", - "Concept ID 36529103 passes: True, reasons: []\n", - "Concept ID 36546677 passes: True, reasons: []\n", - "Concept ID 36564056 passes: True, reasons: []\n", - "Concept ID 36557566 passes: True, reasons: []\n", - "Concept ID 36528948 passes: True, reasons: []\n", - "Concept ID 36565831 passes: True, reasons: []\n", - "Concept ID 36532245 passes: True, reasons: []\n", - "Concept ID 36547667 passes: True, reasons: []\n", - "Concept ID 36544823 passes: True, reasons: []\n", - "Concept ID 36527614 passes: True, reasons: []\n", - "Concept ID 36554806 passes: True, reasons: []\n", - "Concept ID 36546567 passes: True, reasons: []\n", - "Concept ID 36553734 passes: True, reasons: []\n", - "Concept ID 44502142 passes: True, reasons: []\n", - "Concept ID 36517346 passes: True, reasons: []\n", - "Concept ID 36545476 passes: True, reasons: []\n", - "Concept ID 36530807 passes: True, reasons: []\n", - "Concept ID 36566661 passes: True, reasons: []\n", - "Concept ID 36525017 passes: True, reasons: []\n", - "Concept ID 36549525 passes: True, reasons: []\n", - "Concept ID 36557538 passes: True, reasons: []\n", - "Concept ID 36523679 passes: True, reasons: []\n", - "Concept ID 36563681 passes: True, reasons: []\n", - "Concept ID 36544445 passes: True, reasons: []\n", - "Concept ID 36537290 passes: True, reasons: []\n", - "Concept ID 36539119 passes: True, reasons: []\n", - "Concept ID 36535308 passes: True, reasons: []\n", - "Concept ID 37165779 passes: True, reasons: []\n", - "Concept ID 36539202 passes: True, reasons: []\n", - "Concept ID 36545677 passes: True, reasons: []\n", - "Concept ID 36538187 passes: True, reasons: []\n", - "Concept ID 36517304 passes: True, reasons: []\n", - "Concept ID 36541480 passes: True, reasons: []\n", - "Concept ID 36520164 passes: True, reasons: []\n", - "Concept ID 36567716 passes: True, reasons: []\n", - "Concept ID 36562101 passes: True, reasons: []\n", - "Concept ID 36525828 passes: True, reasons: []\n", - "Concept ID 36541790 passes: True, reasons: []\n", - "Concept ID 36533613 passes: True, reasons: []\n", - "Concept ID 36564866 passes: True, reasons: []\n", - "Concept ID 36534967 passes: True, reasons: []\n", - "Concept ID 36526415 passes: True, reasons: []\n", - "Concept ID 36540186 passes: True, reasons: []\n", - "Concept ID 36548842 passes: True, reasons: []\n", - "Concept ID 36530576 passes: True, reasons: []\n", - "Concept ID 36541238 passes: True, reasons: []\n", - "Concept ID 36557649 passes: True, reasons: []\n", - "Concept ID 36551314 passes: True, reasons: []\n", - "Concept ID 36566388 passes: True, reasons: []\n", - "Concept ID 36521677 passes: True, reasons: []\n", - "Concept ID 36563876 passes: True, reasons: []\n", - "Concept ID 36535034 passes: True, reasons: []\n", - "Concept ID 36550534 passes: True, reasons: []\n", - "Concept ID 36541595 passes: True, reasons: []\n", - "Concept ID 36519832 passes: True, reasons: []\n", - "Concept ID 36545481 passes: True, reasons: []\n", - "Concept ID 36554482 passes: True, reasons: []\n", - "Concept ID 36553743 passes: True, reasons: []\n", - "Concept ID 36528983 passes: True, reasons: []\n", - "Concept ID 36562667 passes: True, reasons: []\n", - "Concept ID 36541037 passes: True, reasons: []\n", - "Concept ID 36555911 passes: True, reasons: []\n", - "Concept ID 36527844 passes: True, reasons: []\n", - "Concept ID 36536370 passes: True, reasons: []\n", - "Concept ID 44499848 passes: True, reasons: []\n", - "Concept ID 36551678 passes: True, reasons: []\n", - "Concept ID 36525134 passes: True, reasons: []\n", - "Concept ID 44503306 passes: True, reasons: []\n", - "Concept ID 36533517 passes: True, reasons: []\n", - "Concept ID 36527217 passes: True, reasons: []\n", - "Concept ID 44500702 passes: True, reasons: []\n", - "Concept ID 36520301 passes: True, reasons: []\n", - "Concept ID 36541666 passes: True, reasons: []\n", - "Concept ID 36540209 passes: True, reasons: []\n", - "Concept ID 36551013 passes: True, reasons: []\n", - "Concept ID 36538324 passes: True, reasons: []\n", - "Concept ID 36521351 passes: True, reasons: []\n", - "Concept ID 36551422 passes: True, reasons: []\n", - "Concept ID 36526634 passes: True, reasons: []\n", - "Concept ID 36547876 passes: True, reasons: []\n", - "Concept ID 36527724 passes: True, reasons: []\n", - "Concept ID 36567959 passes: True, reasons: []\n", - "Concept ID 36558789 passes: True, reasons: []\n", - "Concept ID 36562235 passes: True, reasons: []\n", - "Concept ID 36532518 passes: True, reasons: []\n", - "Concept ID 36554705 passes: True, reasons: []\n", - "Concept ID 36566753 passes: True, reasons: []\n", - "Concept ID 36556763 passes: True, reasons: []\n", - "Concept ID 36521214 passes: True, reasons: []\n", - "Concept ID 36533069 passes: True, reasons: []\n", - "Concept ID 36528665 passes: True, reasons: []\n", - "Concept ID 36563960 passes: True, reasons: []\n", - "Concept ID 36521106 passes: True, reasons: []\n", - "Concept ID 36551265 passes: True, reasons: []\n", - "Concept ID 36522882 passes: True, reasons: []\n", - "Concept ID 36517541 passes: True, reasons: []\n", - "Concept ID 36530073 passes: True, reasons: []\n", - "Concept ID 36556785 passes: True, reasons: []\n", - "Concept ID 36559142 passes: True, reasons: []\n", - "Concept ID 36519496 passes: True, reasons: []\n", - "Concept ID 36534566 passes: True, reasons: []\n", - "Concept ID 36545876 passes: True, reasons: []\n", - "Concept ID 36529589 passes: True, reasons: []\n", - "Concept ID 36553692 passes: True, reasons: []\n", - "Concept ID 36535826 passes: True, reasons: []\n", - "Concept ID 36523821 passes: True, reasons: []\n", - "Concept ID 36544866 passes: True, reasons: []\n", - "Concept ID 36561178 passes: True, reasons: []\n", - "Concept ID 36544103 passes: True, reasons: []\n", - "Concept ID 36555028 passes: True, reasons: []\n", - "Concept ID 36534766 passes: True, reasons: []\n", - "Concept ID 36556713 passes: True, reasons: []\n", - "Concept ID 44501318 passes: True, reasons: []\n", - "Concept ID 36528023 passes: True, reasons: []\n", - "Concept ID 36535116 passes: True, reasons: []\n", - "Concept ID 36562358 passes: True, reasons: []\n", - "Concept ID 36531298 passes: True, reasons: []\n", - "Concept ID 36533186 passes: True, reasons: []\n", - "Concept ID 36530616 passes: True, reasons: []\n", - "Concept ID 36540852 passes: True, reasons: []\n", - "Concept ID 36524376 passes: True, reasons: []\n", - "Concept ID 36520088 passes: True, reasons: []\n", - "Concept ID 36530490 passes: True, reasons: []\n", - "Concept ID 36553348 passes: True, reasons: []\n", - "Concept ID 36524170 passes: True, reasons: []\n", - "Concept ID 36536014 passes: True, reasons: []\n", - "Concept ID 36538690 passes: True, reasons: []\n", - "Concept ID 36526067 passes: True, reasons: []\n", - "Concept ID 36539335 passes: True, reasons: []\n", - "Concept ID 36522021 passes: True, reasons: []\n", - "Concept ID 36528698 passes: True, reasons: []\n", - "Concept ID 36402736 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36517155 passes: True, reasons: []\n", - "Concept ID 36547246 passes: True, reasons: []\n", - "Concept ID 36566954 passes: True, reasons: []\n", - "Concept ID 36551062 passes: True, reasons: []\n", - "Concept ID 36524395 passes: True, reasons: []\n", - "Concept ID 36547265 passes: True, reasons: []\n", - "Concept ID 36402728 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36527692 passes: True, reasons: []\n", - "Concept ID 36562732 passes: True, reasons: []\n", - "Concept ID 36544927 passes: True, reasons: []\n", - "Concept ID 36554243 passes: True, reasons: []\n", - "Concept ID 36535440 passes: True, reasons: []\n", - "Concept ID 36561141 passes: True, reasons: []\n", - "Concept ID 36547908 passes: True, reasons: []\n", - "Concept ID 36518745 passes: True, reasons: []\n", - "Concept ID 36545667 passes: True, reasons: []\n", - "Concept ID 36544296 passes: True, reasons: []\n", - "Concept ID 36519011 passes: True, reasons: []\n", - "Concept ID 36548274 passes: True, reasons: []\n", - "Concept ID 36547323 passes: True, reasons: []\n", - "Concept ID 36540405 passes: True, reasons: []\n", - "Concept ID 36545809 passes: True, reasons: []\n", - "Concept ID 36543983 passes: True, reasons: []\n", - "Concept ID 36527518 passes: True, reasons: []\n", - "Concept ID 36559346 passes: True, reasons: []\n", - "Concept ID 36518189 passes: True, reasons: []\n", - "Concept ID 36553294 passes: True, reasons: []\n", - "Concept ID 36522846 passes: True, reasons: []\n", - "Concept ID 36535176 passes: True, reasons: []\n", - "Concept ID 36521582 passes: True, reasons: []\n", - "Concept ID 36537270 passes: True, reasons: []\n", - "Concept ID 36526078 passes: True, reasons: []\n", - "Concept ID 36534379 passes: True, reasons: []\n", - "Concept ID 36553629 passes: True, reasons: []\n", - "Concept ID 36548047 passes: True, reasons: []\n", - "Concept ID 36551556 passes: True, reasons: []\n", - "Concept ID 36547628 passes: True, reasons: []\n", - "Concept ID 36533774 passes: True, reasons: []\n", - "Concept ID 36541122 passes: True, reasons: []\n", - "Concept ID 36539673 passes: True, reasons: []\n", - "Concept ID 36530981 passes: True, reasons: []\n", - "Concept ID 36560220 passes: True, reasons: []\n", - "Concept ID 36551547 passes: True, reasons: []\n", - "Concept ID 36551150 passes: True, reasons: []\n", - "Concept ID 36545634 passes: True, reasons: []\n", - "Concept ID 36558376 passes: True, reasons: []\n", - "Concept ID 36540253 passes: True, reasons: []\n", - "Concept ID 36542216 passes: True, reasons: []\n", - "Concept ID 36538907 passes: True, reasons: []\n", - "Concept ID 36563990 passes: True, reasons: []\n", - "Concept ID 36558812 passes: True, reasons: []\n", - "Concept ID 36539794 passes: True, reasons: []\n", - "Concept ID 36557165 passes: True, reasons: []\n", - "Concept ID 44499471 passes: True, reasons: []\n", - "Concept ID 36564642 passes: True, reasons: []\n", - "Concept ID 36549235 passes: True, reasons: []\n", - "Concept ID 36552599 passes: True, reasons: []\n", - "Concept ID 36554762 passes: True, reasons: []\n", - "Concept ID 36529225 passes: True, reasons: []\n", - "Concept ID 36523042 passes: True, reasons: []\n", - "Concept ID 36545457 passes: True, reasons: []\n", - "Concept ID 36549236 passes: True, reasons: []\n", - "Concept ID 36559999 passes: True, reasons: []\n", - "Concept ID 36546448 passes: True, reasons: []\n", - "Concept ID 36543143 passes: True, reasons: []\n", - "Concept ID 36523882 passes: True, reasons: []\n", - "Concept ID 36548232 passes: True, reasons: []\n", - "Concept ID 36550156 passes: True, reasons: []\n", - "Concept ID 36554050 passes: True, reasons: []\n", - "Concept ID 36561167 passes: True, reasons: []\n", - "Concept ID 36561834 passes: True, reasons: []\n", - "Concept ID 36561718 passes: True, reasons: []\n", - "Concept ID 36527868 passes: True, reasons: []\n", - "Concept ID 36523620 passes: True, reasons: []\n", - "Concept ID 36533919 passes: True, reasons: []\n", - "Concept ID 36519071 passes: True, reasons: []\n", - "Concept ID 36563970 passes: True, reasons: []\n", - "Concept ID 36402718 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36521668 passes: True, reasons: []\n", - "Concept ID 36547121 passes: True, reasons: []\n", - "Concept ID 36533793 passes: True, reasons: []\n", - "Concept ID 36564523 passes: True, reasons: []\n", - "Concept ID 36550153 passes: True, reasons: []\n", - "Concept ID 36564407 passes: True, reasons: []\n", - "Concept ID 36540371 passes: True, reasons: []\n", - "Concept ID 36533573 passes: True, reasons: []\n", - "Concept ID 36524788 passes: True, reasons: []\n", - "Concept ID 36546787 passes: True, reasons: []\n", - "Concept ID 36547291 passes: True, reasons: []\n", - "Concept ID 36567102 passes: True, reasons: []\n", - "Concept ID 36555957 passes: True, reasons: []\n", - "Concept ID 36563743 passes: True, reasons: []\n", - "Concept ID 36554109 passes: True, reasons: []\n", - "Concept ID 36522413 passes: True, reasons: []\n", - "Concept ID 36563545 passes: True, reasons: []\n", - "Concept ID 36557367 passes: True, reasons: []\n", - "Concept ID 36533921 passes: True, reasons: []\n", - "Concept ID 36552674 passes: True, reasons: []\n", - "Concept ID 36551461 passes: True, reasons: []\n", - "Concept ID 36548093 passes: True, reasons: []\n", - "Concept ID 36547591 passes: True, reasons: []\n", - "Concept ID 36537295 passes: True, reasons: []\n", - "Concept ID 36554887 passes: True, reasons: []\n", - "Concept ID 36561530 passes: True, reasons: []\n", - "Concept ID 36549044 passes: True, reasons: []\n", - "Concept ID 36533141 passes: True, reasons: []\n", - "Concept ID 36520228 passes: True, reasons: []\n", - "Concept ID 36521859 passes: True, reasons: []\n", - "Concept ID 36562411 passes: True, reasons: []\n", - "Concept ID 36567765 passes: True, reasons: []\n", - "Concept ID 36549123 passes: True, reasons: []\n", - "Concept ID 36520928 passes: True, reasons: []\n", - "Concept ID 36550944 passes: True, reasons: []\n", - "Concept ID 36547420 passes: True, reasons: []\n", - "Concept ID 36560458 passes: True, reasons: []\n", - "Concept ID 36543297 passes: True, reasons: []\n", - "Concept ID 36563974 passes: True, reasons: []\n", - "Concept ID 36562277 passes: True, reasons: []\n", - "Concept ID 36555135 passes: True, reasons: []\n", - "Concept ID 36545037 passes: True, reasons: []\n", - "Concept ID 36547997 passes: True, reasons: []\n", - "Concept ID 36541243 passes: True, reasons: []\n", - "Concept ID 36564448 passes: True, reasons: []\n", - "Concept ID 36534701 passes: True, reasons: []\n", - "Concept ID 36539883 passes: True, reasons: []\n", - "Concept ID 36564781 passes: True, reasons: []\n", - "Concept ID 36520132 passes: True, reasons: []\n", - "Concept ID 36564944 passes: True, reasons: []\n", - "Concept ID 36539590 passes: True, reasons: []\n", - "Concept ID 36556502 passes: True, reasons: []\n", - "Concept ID 36533638 passes: True, reasons: []\n", - "Concept ID 36566090 passes: True, reasons: []\n", - "Concept ID 36550707 passes: True, reasons: []\n", - "Concept ID 36540857 passes: True, reasons: []\n", - "Concept ID 36553166 passes: True, reasons: []\n", - "Concept ID 36553497 passes: True, reasons: []\n", - "Concept ID 36519494 passes: True, reasons: []\n", - "Concept ID 36520552 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 44502591 passes: True, reasons: []\n", - "Concept ID 36532456 passes: True, reasons: []\n", - "Concept ID 36545401 passes: True, reasons: []\n", - "Concept ID 36537173 passes: True, reasons: []\n", - "Concept ID 36542399 passes: True, reasons: []\n", - "Concept ID 36518998 passes: True, reasons: []\n", - "Concept ID 36552836 passes: True, reasons: []\n", - "Concept ID 36560023 passes: True, reasons: []\n", - "Concept ID 36535161 passes: True, reasons: []\n", - "Concept ID 36544499 passes: True, reasons: []\n", - "Concept ID 36565014 passes: True, reasons: []\n", - "Concept ID 36528609 passes: True, reasons: []\n", - "Concept ID 36551508 passes: True, reasons: []\n", - "Concept ID 44503057 passes: True, reasons: []\n", - "Concept ID 36549895 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 44501381 passes: True, reasons: []\n", - "Concept ID 36525183 passes: True, reasons: []\n", - "Concept ID 36555542 passes: True, reasons: []\n", - "Concept ID 36529305 passes: True, reasons: []\n", - "Concept ID 36527761 passes: True, reasons: []\n", - "Concept ID 36543889 passes: True, reasons: []\n", - "Concept ID 36518753 passes: True, reasons: []\n", - "Concept ID 36519890 passes: True, reasons: []\n", - "Concept ID 36535591 passes: True, reasons: []\n", - "Concept ID 36551526 passes: True, reasons: []\n", - "Concept ID 36547391 passes: True, reasons: []\n", - "Concept ID 36541148 passes: True, reasons: []\n", - "Concept ID 36547428 passes: True, reasons: []\n", - "Concept ID 36566354 passes: True, reasons: []\n", - "Concept ID 36559706 passes: True, reasons: []\n", - "Concept ID 36558924 passes: True, reasons: []\n", - "Concept ID 36565491 passes: True, reasons: []\n", - "Concept ID 36534491 passes: True, reasons: []\n", - "Concept ID 36531821 passes: True, reasons: []\n", - "Concept ID 36531392 passes: True, reasons: []\n", - "Concept ID 36525462 passes: True, reasons: []\n", - "Concept ID 36557750 passes: True, reasons: []\n", - "Concept ID 36531560 passes: True, reasons: []\n", - "Concept ID 36565525 passes: True, reasons: []\n", - "Concept ID 36521055 passes: True, reasons: []\n", - "Concept ID 36562830 passes: True, reasons: []\n", - "Concept ID 36526274 passes: True, reasons: []\n", - "Concept ID 36544161 passes: True, reasons: []\n", - "Concept ID 36530858 passes: True, reasons: []\n", - "Concept ID 36557571 passes: True, reasons: []\n", - "Concept ID 36524704 passes: True, reasons: []\n", - "Concept ID 36540373 passes: True, reasons: []\n", - "Concept ID 36518229 passes: True, reasons: []\n", - "Concept ID 36545948 passes: True, reasons: []\n", - "Concept ID 36564404 passes: True, reasons: []\n", - "Concept ID 36548755 passes: True, reasons: []\n", - "Concept ID 36561078 passes: True, reasons: []\n", - "Concept ID 36544767 passes: True, reasons: []\n", - "Concept ID 36539535 passes: True, reasons: []\n", - "Concept ID 36527598 passes: True, reasons: []\n", - "Concept ID 36565882 passes: True, reasons: []\n", - "Concept ID 36531489 passes: True, reasons: []\n", - "Concept ID 36562589 passes: True, reasons: []\n", - "Concept ID 36537794 passes: True, reasons: []\n", - "Concept ID 36532275 passes: True, reasons: []\n", - "Concept ID 44503279 passes: True, reasons: []\n", - "Concept ID 36562162 passes: True, reasons: []\n", - "Concept ID 36563807 passes: True, reasons: []\n", - "Concept ID 36525739 passes: True, reasons: []\n", - "Concept ID 36518785 passes: True, reasons: []\n", - "Concept ID 36528727 passes: True, reasons: []\n", - "Concept ID 36531066 passes: True, reasons: []\n", - "Concept ID 36541814 passes: True, reasons: []\n", - "Concept ID 36542666 passes: True, reasons: []\n", - "Concept ID 36552209 passes: True, reasons: []\n", - "Concept ID 36521667 passes: True, reasons: []\n", - "Concept ID 36555806 passes: True, reasons: []\n", - "Concept ID 36533459 passes: True, reasons: []\n", - "Concept ID 36529869 passes: True, reasons: []\n", - "Concept ID 36560449 passes: True, reasons: []\n", - "Concept ID 36560451 passes: True, reasons: []\n", - "Concept ID 36551170 passes: True, reasons: []\n", - "Concept ID 36527190 passes: True, reasons: []\n", - "Concept ID 36545981 passes: True, reasons: []\n", - "Concept ID 36535913 passes: True, reasons: []\n", - "Concept ID 36402747 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36557544 passes: True, reasons: []\n", - "Concept ID 44503282 passes: True, reasons: []\n", - "Concept ID 36544607 passes: True, reasons: []\n", - "Concept ID 36550494 passes: True, reasons: []\n", - "Concept ID 36556990 passes: True, reasons: []\n", - "Concept ID 36518330 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 44503260 passes: True, reasons: []\n", - "Concept ID 36536915 passes: True, reasons: []\n", - "Concept ID 36547175 passes: True, reasons: []\n", - "Concept ID 36535902 passes: True, reasons: []\n", - "Concept ID 36560235 passes: True, reasons: []\n", - "Concept ID 36543762 passes: True, reasons: []\n", - "Concept ID 36553316 passes: True, reasons: []\n", - "Concept ID 36527099 passes: True, reasons: []\n", - "Concept ID 36537701 passes: True, reasons: []\n", - "Concept ID 36519841 passes: True, reasons: []\n", - "Concept ID 36567525 passes: True, reasons: []\n", - "Concept ID 36567100 passes: True, reasons: []\n", - "Concept ID 36534400 passes: True, reasons: []\n", - "Concept ID 36554261 passes: True, reasons: []\n", - "Concept ID 36547059 passes: True, reasons: []\n", - "Concept ID 36402664 passes: True, reasons: []\n", - "Concept ID 36528539 passes: True, reasons: []\n", - "Concept ID 36535384 passes: True, reasons: []\n", - "Concept ID 36564892 passes: True, reasons: []\n", - "Concept ID 36526595 passes: True, reasons: []\n", - "Concept ID 36518098 passes: True, reasons: []\n", - "Concept ID 36535470 passes: True, reasons: []\n", - "Concept ID 36556771 passes: True, reasons: []\n", - "Concept ID 36526988 passes: True, reasons: []\n", - "Concept ID 36541588 passes: True, reasons: []\n", - "Concept ID 36517387 passes: True, reasons: []\n", - "Concept ID 36545767 passes: True, reasons: []\n", - "Concept ID 36556900 passes: True, reasons: []\n", - "Concept ID 36547116 passes: True, reasons: []\n", - "Concept ID 36563647 passes: True, reasons: []\n", - "Concept ID 36526553 passes: True, reasons: []\n", - "Concept ID 36541090 passes: True, reasons: []\n", - "Concept ID 36548072 passes: True, reasons: []\n", - "Concept ID 36519389 passes: True, reasons: []\n", - "Concept ID 36543137 passes: True, reasons: []\n", - "Concept ID 36524905 passes: True, reasons: []\n", - "Concept ID 36531731 passes: True, reasons: []\n", - "Concept ID 36535873 passes: True, reasons: []\n", - "Concept ID 44503280 passes: True, reasons: []\n", - "Concept ID 36517272 passes: True, reasons: []\n", - "Concept ID 36540850 passes: True, reasons: []\n", - "Concept ID 36525904 passes: True, reasons: []\n", - "Concept ID 36537582 passes: True, reasons: []\n", - "Concept ID 36557473 passes: True, reasons: []\n", - "Concept ID 36544447 passes: True, reasons: []\n", - "Concept ID 36532049 passes: True, reasons: []\n", - "Concept ID 36551567 passes: True, reasons: []\n", - "Concept ID 36533845 passes: True, reasons: []\n", - "Concept ID 36542215 passes: True, reasons: []\n", - "Concept ID 36557935 passes: True, reasons: []\n", - "Concept ID 36562633 passes: True, reasons: []\n", - "Concept ID 36551721 passes: True, reasons: []\n", - "Concept ID 36537562 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36561373 passes: True, reasons: []\n", - "Concept ID 36557121 passes: True, reasons: []\n", - "Concept ID 36553722 passes: True, reasons: []\n", - "Concept ID 36563279 passes: True, reasons: []\n", - "Concept ID 36530846 passes: True, reasons: []\n", - "Concept ID 36551907 passes: True, reasons: []\n", - "Concept ID 36556355 passes: True, reasons: []\n", - "Concept ID 36530540 passes: True, reasons: []\n", - "Concept ID 36561060 passes: True, reasons: []\n", - "Concept ID 36542105 passes: True, reasons: []\n", - "Concept ID 36518467 passes: True, reasons: []\n", - "Concept ID 36554092 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36550841 passes: True, reasons: []\n", - "Concept ID 36535536 passes: True, reasons: []\n", - "Concept ID 36521872 passes: True, reasons: []\n", - "Concept ID 36550892 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 44503261 passes: True, reasons: []\n", - "Concept ID 36547594 passes: True, reasons: []\n", - "Concept ID 36518817 passes: True, reasons: []\n", - "Concept ID 36528629 passes: True, reasons: []\n", - "Concept ID 36525813 passes: True, reasons: []\n", - "Concept ID 36521184 passes: True, reasons: []\n", - "Concept ID 36551289 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36531987 passes: True, reasons: []\n", - "Concept ID 36519343 passes: True, reasons: []\n", - "Concept ID 36523325 passes: True, reasons: []\n", - "Concept ID 36555413 passes: True, reasons: []\n", - "Concept ID 44503281 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36559408 passes: True, reasons: []\n", - "Concept ID 36559427 passes: True, reasons: []\n", - "Concept ID 36549856 passes: True, reasons: []\n", - "Concept ID 36530998 passes: True, reasons: []\n", - "Concept ID 36545666 passes: True, reasons: []\n", - "Concept ID 36560245 passes: True, reasons: []\n", - "Concept ID 36527688 passes: True, reasons: []\n", - "Concept ID 36556746 passes: True, reasons: []\n", - "Concept ID 36522658 passes: True, reasons: []\n", - "Concept ID 36556201 passes: True, reasons: []\n", - "Concept ID 36551895 passes: True, reasons: []\n", - "Concept ID 36557498 passes: True, reasons: []\n", - "Concept ID 36528675 passes: True, reasons: []\n", - "Concept ID 36564635 passes: True, reasons: []\n", - "Concept ID 36561889 passes: True, reasons: []\n", - "Concept ID 36526930 passes: True, reasons: []\n", - "Concept ID 36523988 passes: True, reasons: []\n", - "Concept ID 36526388 passes: True, reasons: []\n", - "Concept ID 36559268 passes: True, reasons: []\n", - "Concept ID 36562270 passes: True, reasons: []\n", - "Concept ID 36528178 passes: True, reasons: []\n", - "Concept ID 36564570 passes: True, reasons: []\n", - "Concept ID 36545623 passes: True, reasons: []\n", - "Concept ID 44499939 passes: True, reasons: []\n", - "Concept ID 36564465 passes: True, reasons: []\n", - "Concept ID 36550940 passes: True, reasons: []\n", - "Concept ID 36554638 passes: True, reasons: []\n", - "Concept ID 36556206 passes: True, reasons: []\n", - "Concept ID 36557140 passes: True, reasons: []\n", - "Concept ID 36543468 passes: True, reasons: []\n", - "Concept ID 36524421 passes: True, reasons: []\n", - "Concept ID 36529395 passes: True, reasons: []\n", - "Concept ID 36542025 passes: True, reasons: []\n", - "Concept ID 36550522 passes: True, reasons: []\n", - "Concept ID 36543746 passes: True, reasons: []\n", - "Concept ID 36517917 passes: True, reasons: []\n", - "Concept ID 36567611 passes: True, reasons: []\n", - "Concept ID 36535523 passes: True, reasons: []\n", - "Concept ID 36523894 passes: True, reasons: []\n", - "Concept ID 36557574 passes: True, reasons: []\n", - "Concept ID 36533918 passes: True, reasons: []\n", - "Concept ID 36545176 passes: True, reasons: []\n", - "Concept ID 36554063 passes: True, reasons: []\n", - "Concept ID 36563711 passes: True, reasons: []\n", - "Concept ID 36546822 passes: True, reasons: []\n", - "Concept ID 36561068 passes: True, reasons: []\n", - "Concept ID 36550927 passes: True, reasons: []\n", - "Concept ID 36559148 passes: True, reasons: []\n", - "Concept ID 36551082 passes: True, reasons: []\n", - "Concept ID 36534763 passes: True, reasons: []\n", - "Concept ID 36557491 passes: True, reasons: []\n", - "Concept ID 36546924 passes: True, reasons: []\n", - "Concept ID 45561842 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36564684 passes: True, reasons: []\n", - "Concept ID 36525169 passes: True, reasons: []\n", - "Concept ID 36564761 passes: True, reasons: []\n", - "Concept ID 36545137 passes: True, reasons: []\n", - "Concept ID 36562884 passes: True, reasons: []\n", - "Concept ID 36518389 passes: True, reasons: []\n", - "Concept ID 45566628 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36554481 passes: True, reasons: []\n", - "Concept ID 36518220 passes: True, reasons: []\n", - "Concept ID 36520616 passes: True, reasons: []\n", - "Concept ID 36558576 passes: True, reasons: []\n", - "Concept ID 36534383 passes: True, reasons: []\n", - "Concept ID 36524461 passes: True, reasons: []\n", - "Concept ID 36550491 passes: True, reasons: []\n", - "Concept ID 36536468 passes: True, reasons: []\n", - "Concept ID 36554504 passes: True, reasons: []\n", - "Concept ID 36539084 passes: True, reasons: []\n", - "Concept ID 36544111 passes: True, reasons: []\n", - "Concept ID 36566727 passes: True, reasons: []\n", - "Concept ID 36532667 passes: True, reasons: []\n", - "Concept ID 36555596 passes: True, reasons: []\n", - "Concept ID 36538214 passes: True, reasons: []\n", - "Concept ID 36548196 passes: True, reasons: []\n", - "Concept ID 36530785 passes: True, reasons: []\n", - "Concept ID 36561486 passes: True, reasons: []\n", - "Concept ID 36561249 passes: True, reasons: []\n", - "Concept ID 36538229 passes: True, reasons: []\n", - "Concept ID 36531201 passes: True, reasons: []\n", - "Concept ID 36548961 passes: True, reasons: []\n", - "Concept ID 44499938 passes: True, reasons: []\n", - "Concept ID 36547883 passes: True, reasons: []\n", - "Concept ID 36532001 passes: True, reasons: []\n", - "Concept ID 36402432 passes: True, reasons: []\n", - "Concept ID 36402374 passes: True, reasons: []\n", - "Concept ID 36534425 passes: True, reasons: []\n", - "Concept ID 36529694 passes: True, reasons: []\n", - "Concept ID 36543428 passes: True, reasons: []\n", - "Concept ID 36536742 passes: True, reasons: []\n", - "Concept ID 36554421 passes: True, reasons: []\n", - "Concept ID 36533127 passes: True, reasons: []\n", - "Concept ID 36553012 passes: True, reasons: []\n", - "Concept ID 36562528 passes: True, reasons: []\n", - "Concept ID 36540996 passes: True, reasons: []\n", - "Concept ID 36521711 passes: True, reasons: []\n", - "Concept ID 36518139 passes: True, reasons: []\n", - "Concept ID 36548760 passes: True, reasons: []\n", - "Concept ID 36564844 passes: True, reasons: []\n", - "Concept ID 36547679 passes: True, reasons: []\n", - "Concept ID 36546941 passes: True, reasons: []\n", - "Concept ID 36565877 passes: True, reasons: []\n", - "Concept ID 36547629 passes: True, reasons: []\n", - "Concept ID 36539388 passes: True, reasons: []\n", - "Concept ID 36531400 passes: True, reasons: []\n", - "Concept ID 36561640 passes: True, reasons: []\n", - "Concept ID 36565897 passes: True, reasons: []\n", - "Concept ID 36557674 passes: True, reasons: []\n", - "Concept ID 36538925 passes: True, reasons: []\n", - "Concept ID 36536665 passes: True, reasons: []\n", - "Concept ID 36548993 passes: True, reasons: []\n", - "Concept ID 36517176 passes: True, reasons: []\n", - "Concept ID 36526995 passes: True, reasons: []\n", - "Concept ID 36556748 passes: True, reasons: []\n", - "Concept ID 36523009 passes: True, reasons: []\n", - "Concept ID 36556573 passes: True, reasons: []\n", - "Concept ID 36534882 passes: True, reasons: []\n", - "Concept ID 36565116 passes: True, reasons: []\n", - "Concept ID 36538020 passes: True, reasons: []\n", - "Concept ID 36544458 passes: True, reasons: []\n", - "Concept ID 36553068 passes: True, reasons: []\n", - "Concept ID 36534021 passes: True, reasons: []\n", - "Concept ID 36537779 passes: True, reasons: []\n", - "Concept ID 36530268 passes: True, reasons: []\n", - "Concept ID 36522358 passes: True, reasons: []\n", - "Concept ID 36558003 passes: True, reasons: []\n", - "Concept ID 36557929 passes: True, reasons: []\n", - "Concept ID 36561158 passes: True, reasons: []\n", - "Concept ID 36563597 passes: True, reasons: []\n", - "Concept ID 36565005 passes: True, reasons: []\n", - "Concept ID 36539592 passes: True, reasons: []\n", - "Concept ID 36559906 passes: True, reasons: []\n", - "Concept ID 36565765 passes: True, reasons: []\n", - "Concept ID 36521578 passes: True, reasons: []\n", - "Concept ID 36523848 passes: True, reasons: []\n", - "Concept ID 36523449 passes: True, reasons: []\n", - "Concept ID 36540348 passes: True, reasons: []\n", - "Concept ID 36548119 passes: True, reasons: []\n", - "Concept ID 36522314 passes: True, reasons: []\n", - "Concept ID 36549194 passes: True, reasons: []\n", - "Concept ID 36560320 passes: True, reasons: []\n", - "Concept ID 36525186 passes: True, reasons: []\n", - "Concept ID 36560190 passes: True, reasons: []\n", - "Concept ID 36526515 passes: True, reasons: []\n", - "Concept ID 36524861 passes: True, reasons: []\n", - "Concept ID 36537817 passes: True, reasons: []\n", - "Concept ID 36565172 passes: True, reasons: []\n", - "Concept ID 36551114 passes: True, reasons: []\n", - "Concept ID 36522819 passes: True, reasons: []\n", - "Concept ID 36542246 passes: True, reasons: []\n", - "Concept ID 36548302 passes: True, reasons: []\n", - "Concept ID 36566493 passes: True, reasons: []\n", - "Concept ID 36565927 passes: True, reasons: []\n", - "Concept ID 36527368 passes: True, reasons: []\n", - "Concept ID 36532725 passes: True, reasons: []\n", - "Concept ID 36558737 passes: True, reasons: []\n", - "Concept ID 36562965 passes: True, reasons: []\n", - "Concept ID 36539043 passes: True, reasons: []\n", - "Concept ID 36536743 passes: True, reasons: []\n", - "Concept ID 36564289 passes: True, reasons: []\n", - "Concept ID 36539722 passes: True, reasons: []\n", - "Concept ID 36528470 passes: True, reasons: []\n", - "Concept ID 36531071 passes: True, reasons: []\n", - "Concept ID 36536481 passes: True, reasons: []\n", - "Concept ID 36529604 passes: True, reasons: []\n", - "Concept ID 36549172 passes: True, reasons: []\n", - "Concept ID 36551645 passes: True, reasons: []\n", - "Concept ID 36530318 passes: True, reasons: []\n", - "Concept ID 36542265 passes: True, reasons: []\n", - "Concept ID 36525118 passes: True, reasons: []\n", - "Concept ID 36530173 passes: True, reasons: []\n", - "Concept ID 36558679 passes: True, reasons: []\n", - "Concept ID 36522251 passes: True, reasons: []\n", - "Concept ID 36549661 passes: True, reasons: []\n", - "Concept ID 36518453 passes: True, reasons: []\n", - "Concept ID 36566770 passes: True, reasons: []\n", - "Concept ID 44502983 passes: True, reasons: []\n", - "Concept ID 44502143 passes: True, reasons: []\n", - "Concept ID 36542005 passes: True, reasons: []\n", - "Concept ID 36547618 passes: True, reasons: []\n", - "Concept ID 36528059 passes: True, reasons: []\n", - "Concept ID 36538055 passes: True, reasons: []\n", - "Concept ID 36530054 passes: True, reasons: []\n", - "Concept ID 36554905 passes: True, reasons: []\n", - "Concept ID 36557097 passes: True, reasons: []\n", - "Concept ID 36564562 passes: True, reasons: []\n", - "Concept ID 36556236 passes: True, reasons: []\n", - "Concept ID 36552660 passes: True, reasons: []\n", - "Concept ID 36544336 passes: True, reasons: []\n", - "Concept ID 36538281 passes: True, reasons: []\n", - "Concept ID 36525524 passes: True, reasons: []\n", - "Concept ID 36564368 passes: True, reasons: []\n", - "Concept ID 36550766 passes: True, reasons: []\n", - "Concept ID 36555887 passes: True, reasons: []\n", - "Concept ID 36537391 passes: True, reasons: []\n", - "Concept ID 36546867 passes: True, reasons: []\n", - "Concept ID 36547258 passes: True, reasons: []\n", - "Concept ID 36539964 passes: True, reasons: []\n", - "Concept ID 36529293 passes: True, reasons: []\n", - "Concept ID 36524310 passes: True, reasons: []\n", - "Concept ID 36548712 passes: True, reasons: []\n", - "Concept ID 36543981 passes: True, reasons: []\n", - "Concept ID 36525875 passes: True, reasons: []\n", - "Concept ID 36562366 passes: True, reasons: []\n", - "Concept ID 36518826 passes: True, reasons: []\n", - "Concept ID 36542548 passes: True, reasons: []\n", - "Concept ID 36552813 passes: True, reasons: []\n", - "Concept ID 36543134 passes: True, reasons: []\n", - "Concept ID 36536096 passes: True, reasons: []\n", - "Concept ID 44500829 passes: True, reasons: []\n", - "Concept ID 36540383 passes: True, reasons: []\n", - "Concept ID 36532694 passes: True, reasons: []\n", - "Concept ID 36551642 passes: True, reasons: []\n", - "Concept ID 36554749 passes: True, reasons: []\n", - "Concept ID 36546348 passes: True, reasons: []\n", - "Concept ID 36562363 passes: True, reasons: []\n", - "Concept ID 36538872 passes: True, reasons: []\n", - "Concept ID 36539825 passes: True, reasons: []\n", - "Concept ID 36543946 passes: True, reasons: []\n", - "Concept ID 36556011 passes: True, reasons: []\n", - "Concept ID 36562425 passes: True, reasons: []\n", - "Concept ID 36524103 passes: True, reasons: []\n", - "Concept ID 36539748 passes: True, reasons: []\n", - "Concept ID 36402399 passes: True, reasons: []\n", - "Concept ID 36532158 passes: True, reasons: []\n", - "Concept ID 36522312 passes: True, reasons: []\n", - "Concept ID 36543116 passes: True, reasons: []\n", - "Concept ID 36523669 passes: True, reasons: []\n", - "Concept ID 36543520 passes: True, reasons: []\n", - "Concept ID 36522822 passes: True, reasons: []\n", - "Concept ID 36549429 passes: True, reasons: []\n", - "Concept ID 36549231 passes: True, reasons: []\n", - "Concept ID 36556811 passes: True, reasons: []\n", - "Concept ID 36559037 passes: True, reasons: []\n", - "Concept ID 36522374 passes: True, reasons: []\n", - "Concept ID 36544369 passes: True, reasons: []\n", - "Concept ID 36523681 passes: True, reasons: []\n", - "Concept ID 36545698 passes: True, reasons: []\n", - "Concept ID 36555085 passes: True, reasons: []\n", - "Concept ID 36532021 passes: True, reasons: []\n", - "Concept ID 36537691 passes: True, reasons: []\n", - "Concept ID 36538769 passes: True, reasons: []\n", - "Concept ID 36526085 passes: True, reasons: []\n", - "Concept ID 36547462 passes: True, reasons: []\n", - "Concept ID 36548788 passes: True, reasons: []\n", - "Concept ID 36550798 passes: True, reasons: []\n", - "Concept ID 36537545 passes: True, reasons: []\n", - "Concept ID 36522981 passes: True, reasons: []\n", - "Concept ID 36555026 passes: True, reasons: []\n", - "Concept ID 36546970 passes: True, reasons: []\n", - "Concept ID 36521449 passes: True, reasons: []\n", - "Concept ID 36538488 passes: True, reasons: []\n", - "Concept ID 36559916 passes: True, reasons: []\n", - "Concept ID 36529571 passes: True, reasons: []\n", - "Concept ID 36548061 passes: True, reasons: []\n", - "Concept ID 36562338 passes: True, reasons: []\n", - "Concept ID 36537975 passes: True, reasons: []\n", - "Concept ID 36539830 passes: True, reasons: []\n", - "Concept ID 36560675 passes: True, reasons: []\n", - "Concept ID 36517448 passes: True, reasons: []\n", - "Concept ID 36566802 passes: True, reasons: []\n", - "Concept ID 36556077 passes: True, reasons: []\n", - "Concept ID 36532919 passes: True, reasons: []\n", - "Concept ID 36559723 passes: True, reasons: []\n", - "Concept ID 36556541 passes: True, reasons: []\n", - "Concept ID 36540871 passes: True, reasons: []\n", - "Concept ID 36558150 passes: True, reasons: []\n", - "Concept ID 36549068 passes: True, reasons: []\n", - "Concept ID 36557380 passes: True, reasons: []\n", - "Concept ID 36517508 passes: True, reasons: []\n", - "Concept ID 36528795 passes: True, reasons: []\n", - "Concept ID 36538898 passes: True, reasons: []\n", - "Concept ID 36545546 passes: True, reasons: []\n", - "Concept ID 36540512 passes: True, reasons: []\n", - "Concept ID 36534517 passes: True, reasons: []\n", - "Concept ID 36528507 passes: True, reasons: []\n", - "Concept ID 36543366 passes: True, reasons: []\n", - "Concept ID 36536092 passes: True, reasons: []\n", - "Concept ID 36547359 passes: True, reasons: []\n", - "Concept ID 36536381 passes: True, reasons: []\n", - "Concept ID 36544530 passes: True, reasons: []\n", - "Concept ID 36522809 passes: True, reasons: []\n", - "Concept ID 36550854 passes: True, reasons: []\n", - "Concept ID 36546122 passes: True, reasons: []\n", - "Concept ID 36526211 passes: True, reasons: []\n", - "Concept ID 36525951 passes: True, reasons: []\n", - "Concept ID 36525204 passes: True, reasons: []\n", - "Concept ID 36532074 passes: True, reasons: []\n", - "Concept ID 36546651 passes: True, reasons: []\n", - "Concept ID 36557231 passes: True, reasons: []\n", - "Concept ID 36523934 passes: True, reasons: []\n", - "Concept ID 36545511 passes: True, reasons: []\n", - "Concept ID 36528255 passes: True, reasons: []\n", - "Concept ID 36554103 passes: True, reasons: []\n", - "Concept ID 36560341 passes: True, reasons: []\n", - "Concept ID 36521595 passes: True, reasons: []\n", - "Concept ID 36564685 passes: True, reasons: []\n", - "Concept ID 36522464 passes: True, reasons: []\n", - "Concept ID 36531321 passes: True, reasons: []\n", - "Concept ID 36544640 passes: True, reasons: []\n", - "Concept ID 36519307 passes: True, reasons: []\n", - "Concept ID 36565446 passes: True, reasons: []\n", - "Concept ID 36535945 passes: True, reasons: []\n", - "Concept ID 36538653 passes: True, reasons: []\n", - "Concept ID 36402430 passes: True, reasons: []\n", - "Concept ID 36539443 passes: True, reasons: []\n", - "Concept ID 36540745 passes: True, reasons: []\n", - "Concept ID 36543194 passes: True, reasons: []\n", - "Concept ID 36522459 passes: True, reasons: []\n", - "Concept ID 36519091 passes: True, reasons: []\n", - "Concept ID 36538548 passes: True, reasons: []\n", - "Concept ID 36556265 passes: True, reasons: []\n", - "Concept ID 36548177 passes: True, reasons: []\n", - "Concept ID 36522240 passes: True, reasons: []\n", - "Concept ID 36539636 passes: True, reasons: []\n", - "Concept ID 36538222 passes: True, reasons: []\n", - "Concept ID 36540573 passes: True, reasons: []\n", - "Concept ID 36542594 passes: True, reasons: []\n", - "Concept ID 36538572 passes: True, reasons: []\n", - "Concept ID 36550567 passes: True, reasons: []\n", - "Concept ID 36538001 passes: True, reasons: []\n", - "Concept ID 36534209 passes: True, reasons: []\n", - "Concept ID 36543231 passes: True, reasons: []\n", - "Concept ID 36524515 passes: True, reasons: []\n", - "Concept ID 36548780 passes: True, reasons: []\n", - "Concept ID 36558996 passes: True, reasons: []\n", - "Concept ID 36523076 passes: True, reasons: []\n", - "Concept ID 36550447 passes: True, reasons: []\n", - "Concept ID 36520956 passes: True, reasons: []\n", - "Concept ID 36559137 passes: True, reasons: []\n", - "Concept ID 36533993 passes: True, reasons: []\n", - "Concept ID 36537542 passes: True, reasons: []\n", - "Concept ID 36542908 passes: True, reasons: []\n", - "Concept ID 36565583 passes: True, reasons: []\n", - "Concept ID 36525566 passes: True, reasons: []\n", - "Concept ID 36536993 passes: True, reasons: []\n", - "Concept ID 36546474 passes: True, reasons: []\n", - "Concept ID 36553300 passes: True, reasons: []\n", - "Concept ID 36530345 passes: True, reasons: []\n", - "Concept ID 36564223 passes: True, reasons: []\n", - "Concept ID 36552110 passes: True, reasons: []\n", - "Concept ID 36535872 passes: True, reasons: []\n", - "Concept ID 36521366 passes: True, reasons: []\n", - "Concept ID 36553160 passes: True, reasons: []\n", - "Concept ID 36518069 passes: True, reasons: []\n", - "Concept ID 36535396 passes: True, reasons: []\n", - "Concept ID 36545260 passes: True, reasons: []\n", - "Concept ID 36533506 passes: True, reasons: []\n", - "Concept ID 36538833 passes: True, reasons: []\n", - "Concept ID 36557580 passes: True, reasons: []\n", - "Concept ID 36518394 passes: True, reasons: []\n", - "Concept ID 36541635 passes: True, reasons: []\n", - "Concept ID 44501606 passes: True, reasons: []\n", - "Concept ID 36545670 passes: True, reasons: []\n", - "Concept ID 36560867 passes: True, reasons: []\n", - "Concept ID 36528607 passes: True, reasons: []\n", - "Concept ID 36530276 passes: True, reasons: []\n", - "Concept ID 36532223 passes: True, reasons: []\n", - "Concept ID 36534998 passes: True, reasons: []\n", - "Concept ID 36539228 passes: True, reasons: []\n", - "Concept ID 36543075 passes: True, reasons: []\n", - "Concept ID 36556192 passes: True, reasons: []\n", - "Concept ID 36549792 passes: True, reasons: []\n", - "Concept ID 36555379 passes: True, reasons: []\n", - "Concept ID 36523172 passes: True, reasons: []\n", - "Concept ID 36525207 passes: True, reasons: []\n", - "Concept ID 36542911 passes: True, reasons: []\n", - "Concept ID 36527798 passes: True, reasons: []\n", - "Concept ID 36529038 passes: True, reasons: []\n", - "Concept ID 36558530 passes: True, reasons: []\n", - "Concept ID 36548024 passes: True, reasons: []\n", - "Concept ID 36557133 passes: True, reasons: []\n", - "Concept ID 36535706 passes: True, reasons: []\n", - "Concept ID 36552810 passes: True, reasons: []\n", - "Concept ID 36541785 passes: True, reasons: []\n", - "Concept ID 36540201 passes: True, reasons: []\n", - "Concept ID 36522297 passes: True, reasons: []\n", - "Concept ID 36525158 passes: True, reasons: []\n", - "Concept ID 36523844 passes: True, reasons: []\n", - "Concept ID 36566535 passes: True, reasons: []\n", - "Concept ID 36556718 passes: True, reasons: []\n", - "Concept ID 36558296 passes: True, reasons: []\n", - "Concept ID 36523862 passes: True, reasons: []\n", - "Concept ID 36518865 passes: True, reasons: []\n", - "Concept ID 36549448 passes: True, reasons: []\n", - "Concept ID 37165781 passes: True, reasons: []\n", - "Concept ID 36538292 passes: True, reasons: []\n", - "Concept ID 36554985 passes: True, reasons: []\n", - "Concept ID 36554816 passes: True, reasons: []\n", - "Concept ID 36568254 passes: True, reasons: []\n", - "Concept ID 36527424 passes: True, reasons: []\n", - "Concept ID 36548954 passes: True, reasons: []\n", - "Concept ID 36521006 passes: True, reasons: []\n", - "Concept ID 36555056 passes: True, reasons: []\n", - "Concept ID 36527743 passes: True, reasons: []\n", - "Concept ID 36546813 passes: True, reasons: []\n", - "Concept ID 36522807 passes: True, reasons: []\n", - "Concept ID 36555153 passes: True, reasons: []\n", - "Concept ID 36549639 passes: True, reasons: []\n", - "Concept ID 36545960 passes: True, reasons: []\n", - "Concept ID 36556069 passes: True, reasons: []\n", - "Concept ID 36564724 passes: True, reasons: []\n", - "Concept ID 36518804 passes: True, reasons: []\n", - "Concept ID 36561225 passes: True, reasons: []\n", - "Concept ID 36566038 passes: True, reasons: []\n", - "Concept ID 36566723 passes: True, reasons: []\n", - "Concept ID 36534157 passes: True, reasons: []\n", - "Concept ID 36555707 passes: True, reasons: []\n", - "Concept ID 36538551 passes: True, reasons: []\n", - "Concept ID 36527428 passes: True, reasons: []\n", - "Concept ID 36549308 passes: True, reasons: []\n", - "Concept ID 36564181 passes: True, reasons: []\n", - "Concept ID 36529636 passes: True, reasons: []\n", - "Concept ID 36561708 passes: True, reasons: []\n", - "Concept ID 36550006 passes: True, reasons: []\n", - "Concept ID 36549366 passes: True, reasons: []\n", - "Concept ID 36556054 passes: True, reasons: []\n", - "Concept ID 36565119 passes: True, reasons: []\n", - "Concept ID 36525246 passes: True, reasons: []\n", - "Concept ID 36541876 passes: True, reasons: []\n", - "Concept ID 36563505 passes: True, reasons: []\n", - "Concept ID 36525478 passes: True, reasons: []\n", - "Concept ID 36543999 passes: True, reasons: []\n", - "Concept ID 36542523 passes: True, reasons: []\n", - "Concept ID 36535735 passes: True, reasons: []\n", - "Concept ID 36527324 passes: True, reasons: []\n", - "Concept ID 36547983 passes: True, reasons: []\n", - "Concept ID 36559940 passes: True, reasons: []\n", - "Concept ID 36523476 passes: True, reasons: []\n", - "Concept ID 36541365 passes: True, reasons: []\n", - "Concept ID 36545266 passes: True, reasons: []\n", - "Concept ID 36526047 passes: True, reasons: []\n", - "Concept ID 36538312 passes: True, reasons: []\n", - "Concept ID 36527728 passes: True, reasons: []\n", - "Concept ID 45552296 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36536820 passes: True, reasons: []\n", - "Concept ID 36564591 passes: True, reasons: []\n", - "Concept ID 36538141 passes: True, reasons: []\n", - "Concept ID 36532546 passes: True, reasons: []\n", - "Concept ID 36564309 passes: True, reasons: []\n", - "Concept ID 36553586 passes: True, reasons: []\n", - "Concept ID 36545050 passes: True, reasons: []\n", - "Concept ID 36540133 passes: True, reasons: []\n", - "Concept ID 36556558 passes: True, reasons: []\n", - "Concept ID 36553646 passes: True, reasons: []\n", - "Concept ID 36553912 passes: True, reasons: []\n", - "Concept ID 36522346 passes: True, reasons: []\n", - "Concept ID 36526391 passes: True, reasons: []\n", - "Concept ID 36538781 passes: True, reasons: []\n", - "Concept ID 36563759 passes: True, reasons: []\n", - "Concept ID 36534031 passes: True, reasons: []\n", - "Concept ID 36564233 passes: True, reasons: []\n", - "Concept ID 36548694 passes: True, reasons: []\n", - "Concept ID 36562666 passes: True, reasons: []\n", - "Concept ID 36532602 passes: True, reasons: []\n", - "Concept ID 36532122 passes: True, reasons: []\n", - "Concept ID 36566996 passes: True, reasons: []\n", - "Concept ID 36567574 passes: True, reasons: []\n", - "Concept ID 36558959 passes: True, reasons: []\n", - "Concept ID 36552235 passes: True, reasons: []\n", - "Concept ID 36562845 passes: True, reasons: []\n", - "Concept ID 36565985 passes: True, reasons: []\n", - "Concept ID 36554842 passes: True, reasons: []\n", - "Concept ID 36533220 passes: True, reasons: []\n", - "Concept ID 36552692 passes: True, reasons: []\n", - "Concept ID 36538719 passes: True, reasons: []\n", - "Concept ID 36521790 passes: True, reasons: []\n", - "Concept ID 36538046 passes: True, reasons: []\n", - "Concept ID 36521542 passes: True, reasons: []\n", - "Concept ID 36547768 passes: True, reasons: []\n", - "Concept ID 36522614 passes: True, reasons: []\n", - "Concept ID 36522504 passes: True, reasons: []\n", - "Concept ID 36519057 passes: True, reasons: []\n", - "Concept ID 36557319 passes: True, reasons: []\n", - "Concept ID 36567483 passes: True, reasons: []\n", - "Concept ID 36528623 passes: True, reasons: []\n", - "Concept ID 36560513 passes: True, reasons: []\n", - "Concept ID 36541629 passes: True, reasons: []\n", - "Concept ID 36541210 passes: True, reasons: []\n", - "Concept ID 36553615 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36534692 passes: True, reasons: []\n", - "Concept ID 36547332 passes: True, reasons: []\n", - "Concept ID 36545046 passes: True, reasons: []\n", - "Concept ID 36523051 passes: True, reasons: []\n", - "Concept ID 36544178 passes: True, reasons: []\n", - "Concept ID 36544635 passes: True, reasons: []\n", - "Concept ID 36543872 passes: True, reasons: []\n", - "Concept ID 36553315 passes: True, reasons: []\n", - "Concept ID 36538792 passes: True, reasons: []\n", - "Concept ID 36518410 passes: True, reasons: []\n", - "Concept ID 36520799 passes: True, reasons: []\n", - "Concept ID 36541694 passes: True, reasons: []\n", - "Concept ID 36517491 passes: True, reasons: []\n", - "Concept ID 36529001 passes: True, reasons: []\n", - "Concept ID 36562259 passes: True, reasons: []\n", - "Concept ID 36531033 passes: True, reasons: []\n", - "Concept ID 36541536 passes: True, reasons: []\n", - "Concept ID 36523517 passes: True, reasons: []\n", - "Concept ID 36524024 passes: True, reasons: []\n", - "Concept ID 36538816 passes: True, reasons: []\n", - "Concept ID 36564630 passes: True, reasons: []\n", - "Concept ID 36563047 passes: True, reasons: []\n", - "Concept ID 36523073 passes: True, reasons: []\n", - "Concept ID 36562958 passes: True, reasons: []\n", - "Concept ID 36567544 passes: True, reasons: []\n", - "Concept ID 36539708 passes: True, reasons: []\n", - "Concept ID 36556583 passes: True, reasons: []\n", - "Concept ID 36530567 passes: True, reasons: []\n", - "Concept ID 36540179 passes: True, reasons: []\n", - "Concept ID 36536703 passes: True, reasons: []\n", - "Concept ID 36547733 passes: True, reasons: []\n", - "Concept ID 36525243 passes: True, reasons: []\n", - "Concept ID 36540220 passes: True, reasons: []\n", - "Concept ID 36541435 passes: True, reasons: []\n", - "Concept ID 36537071 passes: True, reasons: []\n", - "Concept ID 36537536 passes: True, reasons: []\n", - "Concept ID 36549709 passes: True, reasons: []\n", - "Concept ID 36567346 passes: True, reasons: []\n", - "Concept ID 36520127 passes: True, reasons: []\n", - "Concept ID 36534576 passes: True, reasons: []\n", - "Concept ID 36518977 passes: True, reasons: []\n", - "Concept ID 36549575 passes: True, reasons: []\n", - "Concept ID 36561029 passes: True, reasons: []\n", - "Concept ID 36566647 passes: True, reasons: []\n", - "Concept ID 36546222 passes: True, reasons: []\n", - "Concept ID 36557975 passes: True, reasons: []\n", - "Concept ID 36534523 passes: True, reasons: []\n", - "Concept ID 36532829 passes: True, reasons: []\n", - "Concept ID 36565683 passes: True, reasons: []\n", - "Concept ID 36566296 passes: True, reasons: []\n", - "Concept ID 36552351 passes: True, reasons: []\n", - "Concept ID 36517254 passes: True, reasons: []\n", - "Concept ID 36553669 passes: True, reasons: []\n", - "Concept ID 36521610 passes: True, reasons: []\n", - "Concept ID 36554510 passes: True, reasons: []\n", - "Concept ID 36522377 passes: True, reasons: []\n", - "Concept ID 36550366 passes: True, reasons: []\n", - "Concept ID 36566777 passes: True, reasons: []\n", - "Concept ID 36564703 passes: True, reasons: []\n", - "Concept ID 36525490 passes: True, reasons: []\n", - "Concept ID 36542955 passes: True, reasons: []\n", - "Concept ID 36541797 passes: True, reasons: []\n", - "Concept ID 36549229 passes: True, reasons: []\n", - "Concept ID 36554670 passes: True, reasons: []\n", - "Concept ID 36542813 passes: True, reasons: []\n", - "Concept ID 36560532 passes: True, reasons: []\n", - "Concept ID 36526320 passes: True, reasons: []\n", - "Concept ID 36527192 passes: True, reasons: []\n", - "Concept ID 36539908 passes: True, reasons: []\n", - "Concept ID 36539239 passes: True, reasons: []\n", - "Concept ID 36565652 passes: True, reasons: []\n", - "Concept ID 36532802 passes: True, reasons: []\n", - "Concept ID 36566140 passes: True, reasons: []\n", - "Concept ID 36533179 passes: True, reasons: []\n", - "Concept ID 36519137 passes: True, reasons: []\n", - "Concept ID 36522874 passes: True, reasons: []\n", - "Concept ID 36553308 passes: True, reasons: []\n", - "Concept ID 36562429 passes: True, reasons: []\n", - "Concept ID 36531156 passes: True, reasons: []\n", - "Concept ID 36539035 passes: True, reasons: []\n", - "Concept ID 36532520 passes: True, reasons: []\n", - "Concept ID 36527469 passes: True, reasons: []\n", - "Concept ID 36555821 passes: True, reasons: []\n", - "Concept ID 36558851 passes: True, reasons: []\n", - "Concept ID 36541015 passes: True, reasons: []\n", - "Concept ID 36550476 passes: True, reasons: []\n", - "Concept ID 36538715 passes: True, reasons: []\n", - "Concept ID 36521597 passes: True, reasons: []\n", - "Concept ID 36518797 passes: True, reasons: []\n", - "Concept ID 36545743 passes: True, reasons: []\n", - "Concept ID 36555048 passes: True, reasons: []\n", - "Concept ID 36559337 passes: True, reasons: []\n", - "Concept ID 36526422 passes: True, reasons: []\n", - "Concept ID 36533904 passes: True, reasons: []\n", - "Concept ID 36525286 passes: True, reasons: []\n", - "Concept ID 36525809 passes: True, reasons: []\n", - "Concept ID 36528814 passes: True, reasons: []\n", - "Concept ID 36522307 passes: True, reasons: []\n", - "Concept ID 36542620 passes: True, reasons: []\n", - "Concept ID 36558719 passes: True, reasons: []\n", - "Concept ID 36553476 passes: True, reasons: []\n", - "Concept ID 36545355 passes: True, reasons: []\n", - "Concept ID 36548343 passes: True, reasons: []\n", - "Concept ID 36526303 passes: True, reasons: []\n", - "Concept ID 36542148 passes: True, reasons: []\n", - "Concept ID 36559854 passes: True, reasons: []\n", - "Concept ID 36552800 passes: True, reasons: []\n", - "Concept ID 36543045 passes: True, reasons: []\n", - "Concept ID 36524474 passes: True, reasons: []\n", - "Concept ID 36546646 passes: True, reasons: []\n", - "Concept ID 36524712 passes: True, reasons: []\n", - "Concept ID 36517243 passes: True, reasons: []\n", - "Concept ID 36560281 passes: True, reasons: []\n", - "Concept ID 36556977 passes: True, reasons: []\n", - "Concept ID 36557502 passes: True, reasons: []\n", - "Concept ID 36521704 passes: True, reasons: []\n", - "Concept ID 36539194 passes: True, reasons: []\n", - "Concept ID 36550890 passes: True, reasons: []\n", - "Concept ID 36540123 passes: True, reasons: []\n", - "Concept ID 36540120 passes: True, reasons: []\n", - "Concept ID 36536314 passes: True, reasons: []\n", - "Concept ID 36524436 passes: True, reasons: []\n", - "Concept ID 36551732 passes: True, reasons: []\n", - "Concept ID 36557311 passes: True, reasons: []\n", - "Concept ID 36537177 passes: True, reasons: []\n", - "Concept ID 36548659 passes: True, reasons: []\n", - "Concept ID 36548132 passes: True, reasons: []\n", - "Concept ID 36561958 passes: True, reasons: []\n", - "Concept ID 36534482 passes: True, reasons: []\n", - "Concept ID 36517929 passes: True, reasons: []\n", - "Concept ID 36522168 passes: True, reasons: []\n", - "Concept ID 36540934 passes: True, reasons: []\n", - "Concept ID 36535378 passes: True, reasons: []\n", - "Concept ID 36554077 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36561864 passes: True, reasons: []\n", - "Concept ID 36521424 passes: True, reasons: []\n", - "Concept ID 36519824 passes: True, reasons: []\n", - "Concept ID 36528190 passes: True, reasons: []\n", - "Concept ID 36566867 passes: True, reasons: []\n", - "Concept ID 36555397 passes: True, reasons: []\n", - "Concept ID 36520014 passes: True, reasons: []\n", - "Concept ID 36533632 passes: True, reasons: []\n", - "Concept ID 36553344 passes: True, reasons: []\n", - "Concept ID 36562592 passes: True, reasons: []\n", - "Concept ID 36549245 passes: True, reasons: []\n", - "Concept ID 36542929 passes: True, reasons: []\n", - "Concept ID 36552788 passes: True, reasons: []\n", - "Concept ID 36562014 passes: True, reasons: []\n", - "Concept ID 36537887 passes: True, reasons: []\n", - "Concept ID 36546624 passes: True, reasons: []\n", - "Concept ID 36521771 passes: True, reasons: []\n", - "Concept ID 36520922 passes: True, reasons: []\n", - "Concept ID 36519657 passes: True, reasons: []\n", - "Concept ID 36520598 passes: True, reasons: []\n", - "Concept ID 36542515 passes: True, reasons: []\n", - "Concept ID 36538466 passes: True, reasons: []\n", - "Concept ID 36557471 passes: True, reasons: []\n", - "Concept ID 36561842 passes: True, reasons: []\n", - "Concept ID 36522064 passes: True, reasons: []\n", - "Concept ID 36546904 passes: True, reasons: []\n", - "Concept ID 36527222 passes: True, reasons: []\n", - "Concept ID 36527340 passes: True, reasons: []\n", - "Concept ID 36543705 passes: True, reasons: []\n", - "Concept ID 36544377 passes: True, reasons: []\n", - "Concept ID 36542462 passes: True, reasons: []\n", - "Concept ID 36558411 passes: True, reasons: []\n", - "Concept ID 36547016 passes: True, reasons: []\n", - "Concept ID 36560808 passes: True, reasons: []\n", - "Concept ID 36520593 passes: True, reasons: []\n", - "Concept ID 36543533 passes: True, reasons: []\n", - "Concept ID 36543146 passes: True, reasons: []\n", - "Concept ID 36544293 passes: True, reasons: []\n", - "Concept ID 36561360 passes: True, reasons: []\n", - "Concept ID 36531556 passes: True, reasons: []\n", - "Concept ID 36555401 passes: True, reasons: []\n", - "Concept ID 36555489 passes: True, reasons: []\n", - "Concept ID 36539324 passes: True, reasons: []\n", - "Concept ID 36550974 passes: True, reasons: []\n", - "Concept ID 36531673 passes: True, reasons: []\n", - "Concept ID 36532809 passes: True, reasons: []\n", - "Concept ID 36558461 passes: True, reasons: []\n", - "Concept ID 36560258 passes: True, reasons: []\n", - "Concept ID 36547170 passes: True, reasons: []\n", - "Concept ID 36535339 passes: True, reasons: []\n", - "Concept ID 36525123 passes: True, reasons: []\n", - "Concept ID 36560826 passes: True, reasons: []\n", - "Concept ID 36540171 passes: True, reasons: []\n", - "Concept ID 36528560 passes: True, reasons: []\n", - "Concept ID 36525402 passes: True, reasons: []\n", - "Concept ID 36564540 passes: True, reasons: []\n", - "Concept ID 36539013 passes: True, reasons: []\n", - "Concept ID 36524278 passes: True, reasons: []\n", - "Concept ID 36561211 passes: True, reasons: []\n", - "Concept ID 36522506 passes: True, reasons: []\n", - "Concept ID 36555930 passes: True, reasons: []\n", - "Concept ID 36536451 passes: True, reasons: []\n", - "Concept ID 36539269 passes: True, reasons: []\n", - "Concept ID 36534323 passes: True, reasons: []\n", - "Concept ID 36527329 passes: True, reasons: []\n", - "Concept ID 36522003 passes: True, reasons: []\n", - "Concept ID 36543567 passes: True, reasons: []\n", - "Concept ID 36543261 passes: True, reasons: []\n", - "Concept ID 36550160 passes: True, reasons: []\n", - "Concept ID 36547339 passes: True, reasons: []\n", - "Concept ID 36526342 passes: True, reasons: []\n", - "Concept ID 36524653 passes: True, reasons: []\n", - "Concept ID 36566344 passes: True, reasons: []\n", - "Concept ID 36542914 passes: True, reasons: []\n", - "Concept ID 36540644 passes: True, reasons: []\n", - "Concept ID 36521512 passes: True, reasons: []\n", - "Concept ID 36526934 passes: True, reasons: []\n", - "Concept ID 36539988 passes: True, reasons: []\n", - "Concept ID 36535794 passes: True, reasons: []\n", - "Concept ID 36560848 passes: True, reasons: []\n", - "Concept ID 36563023 passes: True, reasons: []\n", - "Concept ID 36558082 passes: True, reasons: []\n", - "Concept ID 36554562 passes: True, reasons: []\n", - "Concept ID 36545657 passes: True, reasons: []\n", - "Concept ID 36540912 passes: True, reasons: []\n", - "Concept ID 36531587 passes: True, reasons: []\n", - "Concept ID 36519866 passes: True, reasons: []\n", - "Concept ID 36524575 passes: True, reasons: []\n", - "Concept ID 36560283 passes: True, reasons: []\n", - "Concept ID 36566641 passes: True, reasons: []\n", - "Concept ID 36565153 passes: True, reasons: []\n", - "Concept ID 36524285 passes: True, reasons: []\n", - "Concept ID 36528763 passes: True, reasons: []\n", - "Concept ID 36548989 passes: True, reasons: []\n", - "Concept ID 36520853 passes: True, reasons: []\n", - "Concept ID 36564189 passes: True, reasons: []\n", - "Concept ID 36522033 passes: True, reasons: []\n", - "Concept ID 36553190 passes: True, reasons: []\n", - "Concept ID 36550229 passes: True, reasons: []\n", - "Concept ID 36538844 passes: True, reasons: []\n", - "Concept ID 36517647 passes: True, reasons: []\n", - "Concept ID 36553086 passes: True, reasons: []\n", - "Concept ID 36559796 passes: True, reasons: []\n", - "Concept ID 36535612 passes: True, reasons: []\n", - "Concept ID 36524869 passes: True, reasons: []\n", - "Concept ID 36518972 passes: True, reasons: []\n", - "Concept ID 36538411 passes: True, reasons: []\n", - "Concept ID 36520638 passes: True, reasons: []\n", - "Concept ID 36540695 passes: True, reasons: []\n", - "Concept ID 36562409 passes: True, reasons: []\n", - "Concept ID 36550577 passes: True, reasons: []\n", - "Concept ID 36543882 passes: True, reasons: []\n", - "Concept ID 36519944 passes: True, reasons: []\n", - "Concept ID 36531760 passes: True, reasons: []\n", - "Concept ID 36553468 passes: True, reasons: []\n", - "Concept ID 36539902 passes: True, reasons: []\n", - "Concept ID 36561503 passes: True, reasons: []\n", - "Concept ID 36566312 passes: True, reasons: []\n", - "Concept ID 36518570 passes: True, reasons: []\n", - "Concept ID 36521397 passes: True, reasons: []\n", - "Concept ID 36542174 passes: True, reasons: []\n", - "Concept ID 36523871 passes: True, reasons: []\n", - "Concept ID 36538777 passes: True, reasons: []\n", - "Concept ID 36540687 passes: True, reasons: []\n", - "Concept ID 36527156 passes: True, reasons: []\n", - "Concept ID 36540381 passes: True, reasons: []\n", - "Concept ID 36551845 passes: True, reasons: []\n", - "Concept ID 36541125 passes: True, reasons: []\n", - "Concept ID 36523700 passes: True, reasons: []\n", - "Concept ID 36542732 passes: True, reasons: []\n", - "Concept ID 36534779 passes: True, reasons: []\n", - "Concept ID 36527024 passes: True, reasons: []\n", - "Concept ID 36542796 passes: True, reasons: []\n", - "Concept ID 36517600 passes: True, reasons: []\n", - "Concept ID 36518857 passes: True, reasons: []\n", - "Concept ID 36546784 passes: True, reasons: []\n", - "Concept ID 36533271 passes: True, reasons: []\n", - "Concept ID 36518538 passes: True, reasons: []\n", - "Concept ID 36533111 passes: True, reasons: []\n", - "Concept ID 36553515 passes: True, reasons: []\n", - "Concept ID 36546028 passes: True, reasons: []\n", - "Concept ID 36549821 passes: True, reasons: []\n", - "Concept ID 36551341 passes: True, reasons: []\n", - "Concept ID 36557809 passes: True, reasons: []\n", - "Concept ID 36561976 passes: True, reasons: []\n", - "Concept ID 36535240 passes: True, reasons: []\n", - "Concept ID 36547220 passes: True, reasons: []\n", - "Concept ID 36553247 passes: True, reasons: []\n", - "Concept ID 36555440 passes: True, reasons: []\n", - "Concept ID 36553915 passes: True, reasons: []\n", - "Concept ID 36540634 passes: True, reasons: []\n", - "Concept ID 36529126 passes: True, reasons: []\n", - "Concept ID 36561965 passes: True, reasons: []\n", - "Concept ID 36532319 passes: True, reasons: []\n", - "Concept ID 36565302 passes: True, reasons: []\n", - "Concept ID 36548098 passes: True, reasons: []\n", - "Concept ID 36561482 passes: True, reasons: []\n", - "Concept ID 36524307 passes: True, reasons: []\n", - "Concept ID 36541271 passes: True, reasons: []\n", - "Concept ID 36534274 passes: True, reasons: []\n", - "Concept ID 36564225 passes: True, reasons: []\n", - "Concept ID 36548154 passes: True, reasons: []\n", - "Concept ID 36529470 passes: True, reasons: []\n", - "Concept ID 36558556 passes: True, reasons: []\n", - "Concept ID 36538489 passes: True, reasons: []\n", - "Concept ID 36552349 passes: True, reasons: []\n", - "Concept ID 36527210 passes: True, reasons: []\n", - "Concept ID 36561555 passes: True, reasons: []\n", - "Concept ID 36558853 passes: True, reasons: []\n", - "Concept ID 36562262 passes: True, reasons: []\n", - "Concept ID 36568270 passes: True, reasons: []\n", - "Concept ID 36568245 passes: True, reasons: []\n", - "Concept ID 36546992 passes: True, reasons: []\n", - "Concept ID 36527809 passes: True, reasons: []\n", - "Concept ID 36524899 passes: True, reasons: []\n", - "Concept ID 36523132 passes: True, reasons: []\n", - "Concept ID 36568334 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36560454 passes: True, reasons: []\n", - "Concept ID 36517622 passes: True, reasons: []\n", - "Concept ID 36546701 passes: True, reasons: []\n", - "Concept ID 36537265 passes: True, reasons: []\n", - "Concept ID 36517621 passes: True, reasons: []\n", - "Concept ID 36524992 passes: True, reasons: []\n", - "Concept ID 36541192 passes: True, reasons: []\n", - "Concept ID 36521137 passes: True, reasons: []\n", - "Concept ID 36537114 passes: True, reasons: []\n", - "Concept ID 36559002 passes: True, reasons: []\n", - "Concept ID 36568274 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36567927 passes: True, reasons: []\n", - "Concept ID 36560077 passes: True, reasons: []\n", - "Concept ID 36522528 passes: True, reasons: []\n", - "Concept ID 36553727 passes: True, reasons: []\n", - "Concept ID 36557173 passes: True, reasons: []\n", - "Concept ID 36557807 passes: True, reasons: []\n", - "Concept ID 36541102 passes: True, reasons: []\n", - "Concept ID 36563079 passes: True, reasons: []\n", - "Concept ID 36518072 passes: True, reasons: []\n", - "Concept ID 36524935 passes: True, reasons: []\n", - "Concept ID 36551403 passes: True, reasons: []\n", - "Concept ID 36535342 passes: True, reasons: []\n", - "Concept ID 36567486 passes: True, reasons: []\n", - "Concept ID 36553783 passes: True, reasons: []\n", - "Concept ID 36541035 passes: True, reasons: []\n", - "Concept ID 36529843 passes: True, reasons: []\n", - "Concept ID 36530732 passes: True, reasons: []\n", - "Concept ID 36550894 passes: True, reasons: []\n", - "Concept ID 36542113 passes: True, reasons: []\n", - "Concept ID 36529178 passes: True, reasons: []\n", - "Concept ID 36557855 passes: True, reasons: []\n", - "Concept ID 36553042 passes: True, reasons: []\n", - "Concept ID 36542730 passes: True, reasons: []\n", - "Concept ID 36567507 passes: True, reasons: []\n", - "Concept ID 36560088 passes: True, reasons: []\n", - "Concept ID 36553216 passes: True, reasons: []\n", - "Concept ID 36537394 passes: True, reasons: []\n", - "Concept ID 36533344 passes: True, reasons: []\n", - "Concept ID 36537049 passes: True, reasons: []\n", - "Concept ID 36550001 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36533750 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36518949 passes: True, reasons: []\n", - "Concept ID 36555699 passes: True, reasons: []\n", - "Concept ID 36554222 passes: True, reasons: []\n", - "Concept ID 36531730 passes: True, reasons: []\n", - "Concept ID 36522438 passes: True, reasons: []\n", - "Concept ID 36564411 passes: True, reasons: []\n", - "Concept ID 36560613 passes: True, reasons: []\n", - "Concept ID 36553293 passes: True, reasons: []\n", - "Concept ID 36543619 passes: True, reasons: []\n", - "Concept ID 36534698 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36537760 passes: True, reasons: []\n", - "Concept ID 36560260 passes: True, reasons: []\n", - "Concept ID 36559867 passes: True, reasons: []\n", - "Concept ID 36546461 passes: True, reasons: []\n", - "Concept ID 36520756 passes: True, reasons: []\n", - "Concept ID 36520379 passes: True, reasons: []\n", - "Concept ID 36521224 passes: True, reasons: []\n", - "Concept ID 36551076 passes: True, reasons: []\n", - "Concept ID 36564890 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36524547 passes: True, reasons: []\n", - "Concept ID 36540728 passes: True, reasons: []\n", - "Concept ID 36530079 passes: True, reasons: []\n", - "Concept ID 36530055 passes: True, reasons: []\n", - "Concept ID 36560945 passes: True, reasons: []\n", - "Concept ID 36555348 passes: True, reasons: []\n", - "Concept ID 36556915 passes: True, reasons: []\n", - "Concept ID 36545434 passes: True, reasons: []\n", - "Concept ID 36563207 passes: True, reasons: []\n", - "Concept ID 36560322 passes: True, reasons: []\n", - "Concept ID 36559625 passes: True, reasons: []\n", - "Concept ID 36563195 passes: True, reasons: []\n", - "Concept ID 36525916 passes: True, reasons: []\n", - "Concept ID 36532168 passes: True, reasons: []\n", - "Concept ID 36524665 passes: True, reasons: []\n", - "Concept ID 36565478 passes: True, reasons: []\n", - "Concept ID 36528304 passes: True, reasons: []\n", - "Concept ID 36517966 passes: True, reasons: []\n", - "Concept ID 36553882 passes: True, reasons: []\n", - "Concept ID 36525367 passes: True, reasons: []\n", - "Concept ID 36560387 passes: True, reasons: []\n", - "Concept ID 36529326 passes: True, reasons: []\n", - "Concept ID 36543449 passes: True, reasons: []\n", - "Concept ID 36529993 passes: True, reasons: []\n", - "Concept ID 36561796 passes: True, reasons: []\n", - "Concept ID 36518190 passes: True, reasons: []\n", - "Concept ID 36518566 passes: True, reasons: []\n", - "Concept ID 36565293 passes: True, reasons: []\n", - "Concept ID 36530337 passes: True, reasons: []\n", - "Concept ID 36519221 passes: True, reasons: []\n", - "Concept ID 36536650 passes: True, reasons: []\n", - "Concept ID 36531803 passes: True, reasons: []\n", - "Concept ID 36551086 passes: True, reasons: []\n", - "Concept ID 36553977 passes: True, reasons: []\n", - "Concept ID 36548103 passes: True, reasons: []\n", - "Concept ID 36545467 passes: True, reasons: []\n", - "Concept ID 36548369 passes: True, reasons: []\n", - "Concept ID 36563274 passes: True, reasons: []\n", - "Concept ID 36526167 passes: True, reasons: []\n", - "Concept ID 36531531 passes: True, reasons: []\n", - "Concept ID 36528139 passes: True, reasons: []\n", - "Concept ID 36553177 passes: True, reasons: []\n", - "Concept ID 36544306 passes: True, reasons: []\n", - "Concept ID 36524734 passes: True, reasons: []\n", - "Concept ID 36538761 passes: True, reasons: []\n", - "Concept ID 36518140 passes: True, reasons: []\n", - "Concept ID 36558828 passes: True, reasons: []\n", - "Concept ID 36527384 passes: True, reasons: []\n", - "Concept ID 36545591 passes: True, reasons: []\n", - "Concept ID 36534706 passes: True, reasons: []\n", - "Concept ID 36562605 passes: True, reasons: []\n", - "Concept ID 36529206 passes: True, reasons: []\n", - "Concept ID 36536264 passes: True, reasons: []\n", - "Concept ID 36525893 passes: True, reasons: []\n", - "Concept ID 36520152 passes: True, reasons: []\n", - "Concept ID 36549129 passes: True, reasons: []\n", - "Concept ID 36517576 passes: True, reasons: []\n", - "Concept ID 36520502 passes: True, reasons: []\n", - "Concept ID 36540743 passes: True, reasons: []\n", - "Concept ID 36517805 passes: True, reasons: []\n", - "Concept ID 36554671 passes: True, reasons: []\n", - "Concept ID 36561949 passes: True, reasons: []\n", - "Concept ID 36524330 passes: True, reasons: []\n", - "Concept ID 36534393 passes: True, reasons: []\n", - "Concept ID 36567021 passes: True, reasons: []\n", - "Concept ID 36558027 passes: True, reasons: []\n", - "Concept ID 36538437 passes: True, reasons: []\n", - "Concept ID 36549462 passes: True, reasons: []\n", - "Concept ID 36519147 passes: True, reasons: []\n", - "Concept ID 36557494 passes: True, reasons: []\n", - "Concept ID 36544127 passes: True, reasons: []\n", - "Concept ID 36530843 passes: True, reasons: []\n", - "Concept ID 36566143 passes: True, reasons: []\n", - "Concept ID 36545859 passes: True, reasons: []\n", - "Concept ID 36544128 passes: True, reasons: []\n", - "Concept ID 36519000 passes: True, reasons: []\n", - "Concept ID 36547429 passes: True, reasons: []\n", - "Concept ID 36536524 passes: True, reasons: []\n", - "Concept ID 36561523 passes: True, reasons: []\n", - "Concept ID 36567730 passes: True, reasons: []\n", - "Concept ID 36531221 passes: True, reasons: []\n", - "Concept ID 36548329 passes: True, reasons: []\n", - "Concept ID 36547638 passes: True, reasons: []\n", - "Concept ID 36541591 passes: True, reasons: []\n", - "Concept ID 36566063 passes: True, reasons: []\n", - "Concept ID 36522756 passes: True, reasons: []\n", - "Concept ID 36550090 passes: True, reasons: []\n", - "Concept ID 36555931 passes: True, reasons: []\n", - "Concept ID 36548374 passes: True, reasons: []\n", - "Concept ID 36566539 passes: True, reasons: []\n", - "Concept ID 36530637 passes: True, reasons: []\n", - "Concept ID 36561014 passes: True, reasons: []\n", - "Concept ID 36533927 passes: True, reasons: []\n", - "Concept ID 36558741 passes: True, reasons: []\n", - "Concept ID 36558490 passes: True, reasons: []\n", - "Concept ID 36562049 passes: True, reasons: []\n", - "Concept ID 36544664 passes: True, reasons: []\n", - "Concept ID 36541915 passes: True, reasons: []\n", - "Concept ID 36527996 passes: True, reasons: []\n", - "Concept ID 36517947 passes: True, reasons: []\n", - "Concept ID 36525675 passes: True, reasons: []\n", - "Concept ID 36547451 passes: True, reasons: []\n", - "Concept ID 36549113 passes: True, reasons: []\n", - "Concept ID 36535646 passes: True, reasons: []\n", - "Concept ID 36518205 passes: True, reasons: []\n", - "Concept ID 36566638 passes: True, reasons: []\n", - "Concept ID 36535548 passes: True, reasons: []\n", - "Concept ID 36523553 passes: True, reasons: []\n", - "Concept ID 36518650 passes: True, reasons: []\n", - "Concept ID 36542508 passes: True, reasons: []\n", - "Concept ID 36566251 passes: True, reasons: []\n", - "Concept ID 36546497 passes: True, reasons: []\n", - "Concept ID 36561737 passes: True, reasons: []\n", - "Concept ID 36551504 passes: True, reasons: []\n", - "Concept ID 36563003 passes: True, reasons: []\n", - "Concept ID 36539550 passes: True, reasons: []\n", - "Concept ID 36551470 passes: True, reasons: []\n", - "Concept ID 36520771 passes: True, reasons: []\n", - "Concept ID 36541368 passes: True, reasons: []\n", - "Concept ID 36553423 passes: True, reasons: []\n", - "Concept ID 36548899 passes: True, reasons: []\n", - "Concept ID 36545458 passes: True, reasons: []\n", - "Concept ID 36519352 passes: True, reasons: []\n", - "Concept ID 36517928 passes: True, reasons: []\n", - "Concept ID 36519519 passes: True, reasons: []\n", - "Concept ID 36526767 passes: True, reasons: []\n", - "Concept ID 36564908 passes: True, reasons: []\n", - "Concept ID 36535790 passes: True, reasons: []\n", - "Concept ID 36552741 passes: True, reasons: []\n", - "Concept ID 36537677 passes: True, reasons: []\n", - "Concept ID 36539727 passes: True, reasons: []\n", - "Concept ID 36522766 passes: True, reasons: []\n", - "Concept ID 36566732 passes: True, reasons: []\n", - "Concept ID 36548800 passes: True, reasons: []\n", - "Concept ID 36524298 passes: True, reasons: []\n", - "Concept ID 36558412 passes: True, reasons: []\n", - "Concept ID 36519893 passes: True, reasons: []\n", - "Concept ID 36546438 passes: True, reasons: []\n", - "Concept ID 36522207 passes: True, reasons: []\n", - "Concept ID 36540458 passes: True, reasons: []\n", - "Concept ID 36555589 passes: True, reasons: []\n", - "Concept ID 36525233 passes: True, reasons: []\n", - "Concept ID 36524066 passes: True, reasons: []\n", - "Concept ID 36523877 passes: True, reasons: []\n", - "Concept ID 36541366 passes: True, reasons: []\n", - "Concept ID 36530185 passes: True, reasons: []\n", - "Concept ID 36519559 passes: True, reasons: []\n", - "Concept ID 36558177 passes: True, reasons: []\n", - "Concept ID 36554499 passes: True, reasons: []\n", - "Concept ID 36561233 passes: True, reasons: []\n", - "Concept ID 36536285 passes: True, reasons: []\n", - "Concept ID 36523052 passes: True, reasons: []\n", - "Concept ID 36517961 passes: True, reasons: []\n", - "Concept ID 36567404 passes: True, reasons: []\n", - "Concept ID 36527504 passes: True, reasons: []\n", - "Concept ID 36557443 passes: True, reasons: []\n", - "Concept ID 36549144 passes: True, reasons: []\n", - "Concept ID 36529600 passes: True, reasons: []\n", - "Concept ID 36537712 passes: True, reasons: []\n", - "Concept ID 36547503 passes: True, reasons: []\n", - "Concept ID 36545997 passes: True, reasons: []\n", - "Concept ID 36544466 passes: True, reasons: []\n", - "Concept ID 36519200 passes: True, reasons: []\n", - "Concept ID 36536344 passes: True, reasons: []\n", - "Concept ID 36521968 passes: True, reasons: []\n", - "Concept ID 36536020 passes: True, reasons: []\n", - "Concept ID 36549727 passes: True, reasons: []\n", - "Concept ID 36524560 passes: True, reasons: []\n", - "Concept ID 36546379 passes: True, reasons: []\n", - "Concept ID 36519715 passes: True, reasons: []\n", - "Concept ID 36521513 passes: True, reasons: []\n", - "Concept ID 36544946 passes: True, reasons: []\n", - "Concept ID 36522203 passes: True, reasons: []\n", - "Concept ID 36555159 passes: True, reasons: []\n", - "Concept ID 36548666 passes: True, reasons: []\n", - "Concept ID 36563584 passes: True, reasons: []\n", - "Concept ID 36554445 passes: True, reasons: []\n", - "Concept ID 36544918 passes: True, reasons: []\n", - "Concept ID 36550396 passes: True, reasons: []\n", - "Concept ID 36541657 passes: True, reasons: []\n", - "Concept ID 36554014 passes: True, reasons: []\n", - "Concept ID 36553702 passes: True, reasons: []\n", - "Concept ID 36530643 passes: True, reasons: []\n", - "Concept ID 36550602 passes: True, reasons: []\n", - "Concept ID 36550978 passes: True, reasons: []\n", - "Concept ID 36545168 passes: True, reasons: []\n", - "Concept ID 36565161 passes: True, reasons: []\n", - "Concept ID 36556050 passes: True, reasons: []\n", - "Concept ID 36540948 passes: True, reasons: []\n", - "Concept ID 36526607 passes: True, reasons: []\n", - "Concept ID 36565371 passes: True, reasons: []\n", - "Concept ID 36567110 passes: True, reasons: []\n", - "Concept ID 36539293 passes: True, reasons: []\n", - "Concept ID 36568023 passes: True, reasons: []\n", - "Concept ID 36556349 passes: True, reasons: []\n", - "Concept ID 36527555 passes: True, reasons: []\n", - "Concept ID 36547450 passes: True, reasons: []\n", - "Concept ID 36522798 passes: True, reasons: []\n", - "Concept ID 36519265 passes: True, reasons: []\n", - "Concept ID 36546596 passes: True, reasons: []\n", - "Concept ID 36566876 passes: True, reasons: []\n", - "Concept ID 36548316 passes: True, reasons: []\n", - "Concept ID 36521502 passes: True, reasons: []\n", - "Concept ID 36553969 passes: True, reasons: []\n", - "Concept ID 36548109 passes: True, reasons: []\n", - "Concept ID 36527421 passes: True, reasons: []\n", - "Concept ID 36553769 passes: True, reasons: []\n", - "Concept ID 36559456 passes: True, reasons: []\n", - "Concept ID 36547375 passes: True, reasons: []\n", - "Concept ID 36532880 passes: True, reasons: []\n", - "Concept ID 36527503 passes: True, reasons: []\n", - "Concept ID 36556191 passes: True, reasons: []\n", - "Concept ID 36561170 passes: True, reasons: []\n", - "Concept ID 36530901 passes: True, reasons: []\n", - "Concept ID 36548235 passes: True, reasons: []\n", - "Concept ID 36521447 passes: True, reasons: []\n", - "Concept ID 36546432 passes: True, reasons: []\n", - "Concept ID 36546962 passes: True, reasons: []\n", - "Concept ID 36524796 passes: True, reasons: []\n", - "Concept ID 36560903 passes: True, reasons: []\n", - "Concept ID 36548138 passes: True, reasons: []\n", - "Concept ID 36529142 passes: True, reasons: []\n", - "Concept ID 36536905 passes: True, reasons: []\n", - "Concept ID 36535093 passes: True, reasons: []\n", - "Concept ID 36535425 passes: True, reasons: []\n", - "Concept ID 36539338 passes: True, reasons: []\n", - "Concept ID 36544432 passes: True, reasons: []\n", - "Concept ID 36518698 passes: True, reasons: []\n", - "Concept ID 36520493 passes: True, reasons: []\n", - "Concept ID 36546928 passes: True, reasons: []\n", - "Concept ID 36560172 passes: True, reasons: []\n", - "Concept ID 36531074 passes: True, reasons: []\n", - "Concept ID 36545737 passes: True, reasons: []\n", - "Concept ID 36518323 passes: True, reasons: []\n", - "Concept ID 36561448 passes: True, reasons: []\n", - "Concept ID 36564807 passes: True, reasons: []\n", - "Concept ID 36537115 passes: True, reasons: []\n", - "Concept ID 36534513 passes: True, reasons: []\n", - "Concept ID 36559024 passes: True, reasons: []\n", - "Concept ID 36539029 passes: True, reasons: []\n", - "Concept ID 36538974 passes: True, reasons: []\n", - "Concept ID 36557875 passes: True, reasons: []\n", - "Concept ID 36552287 passes: True, reasons: []\n", - "Concept ID 36542099 passes: True, reasons: []\n", - "Concept ID 36558401 passes: True, reasons: []\n", - "Concept ID 36525704 passes: True, reasons: []\n", - "Concept ID 36523785 passes: True, reasons: []\n", - "Concept ID 36520233 passes: True, reasons: []\n", - "Concept ID 36527565 passes: True, reasons: []\n", - "Concept ID 36552318 passes: True, reasons: []\n", - "Concept ID 36537743 passes: True, reasons: []\n", - "Concept ID 36532225 passes: True, reasons: []\n", - "Concept ID 36553246 passes: True, reasons: []\n", - "Concept ID 36551139 passes: True, reasons: []\n", - "Concept ID 36531607 passes: True, reasons: []\n", - "Concept ID 36548344 passes: True, reasons: []\n", - "Concept ID 36522192 passes: True, reasons: []\n", - "Concept ID 36525994 passes: True, reasons: []\n", - "Concept ID 36532962 passes: True, reasons: []\n", - "Concept ID 36562129 passes: True, reasons: []\n", - "Concept ID 36557180 passes: True, reasons: []\n", - "Concept ID 36553337 passes: True, reasons: []\n", - "Concept ID 36533219 passes: True, reasons: []\n", - "Concept ID 36541300 passes: True, reasons: []\n", - "Concept ID 36551778 passes: True, reasons: []\n", - "Concept ID 36545163 passes: True, reasons: []\n", - "Concept ID 36555852 passes: True, reasons: []\n", - "Concept ID 36535516 passes: True, reasons: []\n", - "Concept ID 36560197 passes: True, reasons: []\n", - "Concept ID 36556684 passes: True, reasons: []\n", - "Concept ID 36567385 passes: True, reasons: []\n", - "Concept ID 36519347 passes: True, reasons: []\n", - "Concept ID 36563432 passes: True, reasons: []\n", - "Concept ID 36547433 passes: True, reasons: []\n", - "Concept ID 36526348 passes: True, reasons: []\n", - "Concept ID 36543353 passes: True, reasons: []\n", - "Concept ID 36559558 passes: True, reasons: []\n", - "Concept ID 36548936 passes: True, reasons: []\n", - "Concept ID 36518738 passes: True, reasons: []\n", - "Concept ID 36560426 passes: True, reasons: []\n", - "Concept ID 36566702 passes: True, reasons: []\n", - "Concept ID 36551549 passes: True, reasons: []\n", - "Concept ID 36540942 passes: True, reasons: []\n", - "Concept ID 36529642 passes: True, reasons: []\n", - "Concept ID 36532640 passes: True, reasons: []\n", - "Concept ID 36565207 passes: True, reasons: []\n", - "Concept ID 36543638 passes: True, reasons: []\n", - "Concept ID 36544930 passes: True, reasons: []\n", - "Concept ID 36541215 passes: True, reasons: []\n", - "Concept ID 36532552 passes: True, reasons: []\n", - "Concept ID 36522478 passes: True, reasons: []\n", - "Concept ID 36565035 passes: True, reasons: []\n", - "Concept ID 36550773 passes: True, reasons: []\n", - "Concept ID 36542806 passes: True, reasons: []\n", - "Concept ID 36563688 passes: True, reasons: []\n", - "Concept ID 37156353 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 42542317 passes: False, reasons: ['vocabulary HemOnc not allowed']\n", - "Concept ID 45456351 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 1553023 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 1567639 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45494916 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 3521538 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 3297125 passes: False, reasons: ['vocabulary Nebraska Lexicon not allowed']\n", - "Concept ID 3433419 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 777689 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 42542184 passes: False, reasons: ['vocabulary HemOnc not allowed']\n", - "Concept ID 42538851 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 42538850 passes: True, reasons: []\n", - "Concept ID 36032451 passes: False, reasons: [\"domain Meas Value not in ('Condition',)\"]\n", - "Concept ID 1525161 passes: False, reasons: ['vocabulary HemOnc not allowed']\n", - "Concept ID 3256850 passes: False, reasons: ['vocabulary Nebraska Lexicon not allowed']\n", - "Concept ID 37557338 passes: False, reasons: ['vocabulary HemOnc not allowed']\n", - "Concept ID 4030288 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 1567638 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 37094035 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37610067 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 1567672 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45561867 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 1406864 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1406865 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 42485220 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 43021273 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 45547533 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 3427699 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 37093692 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37606668 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 778012 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 45449657 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 45552293 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 1406747 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1406748 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1553020 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 1567635 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 1567636 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 42485184 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 3654298 passes: True, reasons: []\n", - "Concept ID 1567637 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 905680 passes: False, reasons: ['vocabulary HemOnc not allowed']\n", - "Concept ID 45605297 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45537870 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45439713 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 3545391 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 37165100 passes: True, reasons: []\n", - "Concept ID 4038841 passes: True, reasons: []\n", - "Concept ID 37093691 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37608114 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 45547532 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 42485183 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 3217619 passes: False, reasons: ['vocabulary Nebraska Lexicon not allowed']\n", - "Concept ID 3249092 passes: False, reasons: ['vocabulary Nebraska Lexicon not allowed']\n", - "Concept ID 45461557 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 44810290 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 45561658 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 1102859 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 3314898 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 3380161 passes: False, reasons: ['vocabulary Nebraska Lexicon not allowed']\n", - "Concept ID 45590932 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45609981 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 778001 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 1401910 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 44499280 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 3428798 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 607956 passes: True, reasons: []\n", - "Concept ID 45600536 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45605294 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 4030145 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 3354750 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 37093688 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37093689 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37608113 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 37610178 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 45420021 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 45516507 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 1243639 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 45605296 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45547530 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 45566622 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 1406741 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1406742 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1406743 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1406744 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 37093690 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 1102858 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 37615372 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 45489857 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 45571545 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 1406745 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1406746 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 42485180 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 42485181 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 37093687 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 1102890 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 37612415 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 45496439 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 1567634 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45586030 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 1406738 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1406739 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 42485179 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 1243642 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 42485182 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 778193 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 45557020 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45590959 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 778191 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 778011 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 3517201 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 3518031 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 37094034 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37613314 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 1567671 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45595715 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 1406847 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1406848 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 42485219 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 45595697 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 3260848 passes: False, reasons: ['vocabulary Nebraska Lexicon not allowed']\n", - "Concept ID 1102855 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 1102856 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45552294 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 1102857 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45600538 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 778194 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 778192 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 45581229 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45590929 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 1243643 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 45576347 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45600535 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45595696 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45600560 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45566625 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45547549 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45566626 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45600559 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 1260817 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 37094031 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37608115 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 3517767 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 36559784 passes: True, reasons: []\n", - "Concept ID 36541181 passes: True, reasons: []\n", - "Concept ID 36561218 passes: True, reasons: []\n", - "Concept ID 36521120 passes: True, reasons: []\n", - "Concept ID 36536953 passes: True, reasons: []\n", - "Concept ID 36402762 passes: True, reasons: []\n", - "Concept ID 36545852 passes: True, reasons: []\n", - "Concept ID 45547547 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 1406842 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 42485216 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 36532247 passes: True, reasons: []\n", - "Concept ID 36543242 passes: True, reasons: []\n", - "Concept ID 36539000 passes: True, reasons: []\n", - "Concept ID 36550313 passes: True, reasons: []\n", - "Concept ID 36555263 passes: True, reasons: []\n", - "Concept ID 36541227 passes: True, reasons: []\n", - "Concept ID 36536018 passes: True, reasons: []\n", - "Concept ID 36560373 passes: True, reasons: []\n", - "Concept ID 36563284 passes: True, reasons: []\n", - "Concept ID 36518421 passes: True, reasons: []\n", - "Concept ID 36536056 passes: True, reasons: []\n", - "Concept ID 36541460 passes: True, reasons: []\n", - "Concept ID 45537869 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45433071 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 36537881 passes: True, reasons: []\n", - "Concept ID 36518436 passes: True, reasons: []\n", - "Concept ID 36567300 passes: True, reasons: []\n", - "Concept ID 36557625 passes: True, reasons: []\n", - "Concept ID 36548815 passes: True, reasons: []\n", - "Concept ID 36556235 passes: True, reasons: []\n", - "Concept ID 36543442 passes: True, reasons: []\n", - "Concept ID 36552964 passes: True, reasons: []\n", - "Concept ID 36547060 passes: True, reasons: []\n", - "Concept ID 36551661 passes: True, reasons: []\n", - "Concept ID 36562853 passes: True, reasons: []\n", - "Concept ID 36550333 passes: True, reasons: []\n", - "Concept ID 36559905 passes: True, reasons: []\n", - "Concept ID 36566620 passes: True, reasons: []\n", - "Concept ID 36562015 passes: True, reasons: []\n", - "Concept ID 45537846 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45552292 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 1102854 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36564374 passes: True, reasons: []\n", - "Concept ID 45581230 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45605292 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45605295 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45542635 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45571544 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45537849 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45581228 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45590930 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 37203817 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 45605298 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36523619 passes: True, reasons: []\n", - "Concept ID 36548834 passes: True, reasons: []\n", - "Concept ID 36522121 passes: True, reasons: []\n", - "Concept ID 36557819 passes: True, reasons: []\n", - "Concept ID 36542301 passes: True, reasons: []\n", - "Concept ID 36523825 passes: True, reasons: []\n", - "Concept ID 36563006 passes: True, reasons: []\n", - "Concept ID 36521478 passes: True, reasons: []\n", - "Concept ID 36528206 passes: True, reasons: []\n", - "Concept ID 36537272 passes: True, reasons: []\n", - "Concept ID 36537442 passes: True, reasons: []\n", - "Concept ID 36533893 passes: True, reasons: []\n", - "Concept ID 36544602 passes: True, reasons: []\n", - "Concept ID 36553825 passes: True, reasons: []\n", - "Concept ID 36557390 passes: True, reasons: []\n", - "Concept ID 36521905 passes: True, reasons: []\n", - "Concept ID 45600539 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36553817 passes: True, reasons: []\n", - "Concept ID 36551672 passes: True, reasons: []\n", - "Concept ID 36561433 passes: True, reasons: []\n", - "Concept ID 36544122 passes: True, reasons: []\n", - "Concept ID 36556086 passes: True, reasons: []\n", - "Concept ID 36544434 passes: True, reasons: []\n", - "Concept ID 36533338 passes: True, reasons: []\n", - "Concept ID 36556856 passes: True, reasons: []\n", - "Concept ID 44500390 passes: True, reasons: []\n", - "Concept ID 36550753 passes: True, reasons: []\n", - "Concept ID 36553409 passes: True, reasons: []\n", - "Concept ID 36540104 passes: True, reasons: []\n", - "Concept ID 36563449 passes: True, reasons: []\n", - "Concept ID 36564492 passes: True, reasons: []\n", - "Concept ID 36537981 passes: True, reasons: []\n", - "Concept ID 36520305 passes: True, reasons: []\n", - "Concept ID 36549910 passes: True, reasons: []\n", - "Concept ID 42513895 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36525706 passes: True, reasons: []\n", - "Concept ID 36534011 passes: True, reasons: []\n", - "Concept ID 36553289 passes: True, reasons: []\n", - "Concept ID 36530837 passes: True, reasons: []\n", - "Concept ID 36519041 passes: True, reasons: []\n", - "Concept ID 36558231 passes: True, reasons: []\n", - "Concept ID 36554127 passes: True, reasons: []\n", - "Concept ID 36532523 passes: True, reasons: []\n", - "Concept ID 36547760 passes: True, reasons: []\n", - "Concept ID 36549173 passes: True, reasons: []\n", - "Concept ID 36519572 passes: True, reasons: []\n", - "Concept ID 36560562 passes: True, reasons: []\n", - "Concept ID 36538054 passes: True, reasons: []\n", - "Concept ID 36559203 passes: True, reasons: []\n", - "Concept ID 36532775 passes: True, reasons: []\n", - "Concept ID 1102889 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36537833 passes: True, reasons: []\n", - "Concept ID 36519344 passes: True, reasons: []\n", - "Concept ID 36533950 passes: True, reasons: []\n", - "Concept ID 36568007 passes: True, reasons: []\n", - "Concept ID 36525922 passes: True, reasons: []\n", - "Concept ID 36548380 passes: True, reasons: []\n", - "Concept ID 36517689 passes: True, reasons: []\n", - "Concept ID 36535241 passes: True, reasons: []\n", - "Concept ID 36541183 passes: True, reasons: []\n", - "Concept ID 36521094 passes: True, reasons: []\n", - "Concept ID 36562281 passes: True, reasons: []\n", - "Concept ID 36517897 passes: True, reasons: []\n", - "Concept ID 36563975 passes: True, reasons: []\n", - "Concept ID 36559756 passes: True, reasons: []\n", - "Concept ID 36548045 passes: True, reasons: []\n", - "Concept ID 36529673 passes: True, reasons: []\n", - "Concept ID 36564074 passes: True, reasons: []\n", - "Concept ID 36537917 passes: True, reasons: []\n", - "Concept ID 36548404 passes: True, reasons: []\n", - "Concept ID 36565604 passes: True, reasons: []\n", - "Concept ID 36559745 passes: True, reasons: []\n", - "Concept ID 36524650 passes: True, reasons: []\n", - "Concept ID 36536500 passes: True, reasons: []\n", - "Concept ID 36518625 passes: True, reasons: []\n", - "Concept ID 36552558 passes: True, reasons: []\n", - "Concept ID 36530274 passes: True, reasons: []\n", - "Concept ID 36557527 passes: True, reasons: []\n", - "Concept ID 36543255 passes: True, reasons: []\n", - "Concept ID 36545519 passes: True, reasons: []\n", - "Concept ID 36560961 passes: True, reasons: []\n", - "Concept ID 36549946 passes: True, reasons: []\n", - "Concept ID 36518661 passes: True, reasons: []\n", - "Concept ID 36538808 passes: True, reasons: []\n", - "Concept ID 36558841 passes: True, reasons: []\n", - "Concept ID 36538846 passes: True, reasons: []\n", - "Concept ID 36530131 passes: True, reasons: []\n", - "Concept ID 1567668 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36548831 passes: True, reasons: []\n", - "Concept ID 36560430 passes: True, reasons: []\n", - "Concept ID 36561342 passes: True, reasons: []\n", - "Concept ID 36535720 passes: True, reasons: []\n", - "Concept ID 36557661 passes: True, reasons: []\n", - "Concept ID 36526256 passes: True, reasons: []\n", - "Concept ID 36522851 passes: True, reasons: []\n", - "Concept ID 36544075 passes: True, reasons: []\n", - "Concept ID 36540603 passes: True, reasons: []\n", - "Concept ID 36521415 passes: True, reasons: []\n", - "Concept ID 36558814 passes: True, reasons: []\n", - "Concept ID 36548585 passes: True, reasons: []\n", - "Concept ID 45600537 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45561866 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36551906 passes: True, reasons: []\n", - "Concept ID 36529141 passes: True, reasons: []\n", - "Concept ID 36553893 passes: True, reasons: []\n", - "Concept ID 45576365 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36552263 passes: True, reasons: []\n", - "Concept ID 36547094 passes: True, reasons: []\n", - "Concept ID 36547063 passes: True, reasons: []\n", - "Concept ID 36519537 passes: True, reasons: []\n", - "Concept ID 45590931 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36531248 passes: True, reasons: []\n", - "Concept ID 36541119 passes: True, reasons: []\n", - "Concept ID 45590960 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 42513896 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 45561838 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45552290 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 3359976 passes: False, reasons: ['vocabulary Nebraska Lexicon not allowed']\n", - "Concept ID 40484012 passes: True, reasons: []\n", - "Concept ID 45561837 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36549298 passes: True, reasons: []\n", - "Concept ID 45556932 passes: False, reasons: ['vocabulary ICD10 not allowed']\n", - "Concept ID 37087113 passes: False, reasons: ['vocabulary ICD10GM not allowed']\n", - "Concept ID 37609862 passes: False, reasons: ['vocabulary CIM10 not allowed']\n", - "Concept ID 45426407 passes: False, reasons: ['vocabulary Read not allowed']\n", - "Concept ID 1404606 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 1404607 passes: False, reasons: ['vocabulary ICD10CN not allowed']\n", - "Concept ID 42484265 passes: False, reasons: ['vocabulary KCD7 not allowed']\n", - "Concept ID 36547581 passes: True, reasons: []\n", - "Concept ID 36561505 passes: True, reasons: []\n", - "Concept ID 44499607 passes: True, reasons: []\n", - "Concept ID 36557563 passes: True, reasons: []\n", - "Concept ID 36552239 passes: True, reasons: []\n", - "Concept ID 36527937 passes: True, reasons: []\n", - "Concept ID 36558285 passes: True, reasons: []\n", - "Concept ID 36542538 passes: True, reasons: []\n", - "Concept ID 36556545 passes: True, reasons: []\n", - "Concept ID 36551506 passes: True, reasons: []\n", - "Concept ID 36543749 passes: True, reasons: []\n", - "Concept ID 36554301 passes: True, reasons: []\n", - "Concept ID 36566494 passes: True, reasons: []\n", - "Concept ID 35704598 passes: False, reasons: [\"domain Procedure not in ('Condition',)\"]\n", - "Concept ID 36550140 passes: True, reasons: []\n", - "Concept ID 36517310 passes: True, reasons: []\n", - "Concept ID 36563820 passes: True, reasons: []\n", - "Concept ID 36519713 passes: True, reasons: []\n", - "Concept ID 36525881 passes: True, reasons: []\n", - "Concept ID 36550184 passes: True, reasons: []\n", - "Concept ID 36535263 passes: True, reasons: []\n", - "Concept ID 45537847 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45566623 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36524476 passes: True, reasons: []\n", - "Concept ID 36528630 passes: True, reasons: []\n", - "Concept ID 36563219 passes: True, reasons: []\n", - "Concept ID 36402361 passes: True, reasons: []\n", - "Concept ID 36558130 passes: True, reasons: []\n", - "Concept ID 45581231 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36527551 passes: True, reasons: []\n", - "Concept ID 36565335 passes: True, reasons: []\n", - "Concept ID 45586031 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45576346 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45542636 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45586058 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36559727 passes: True, reasons: []\n", - "Concept ID 36519646 passes: True, reasons: []\n", - "Concept ID 35704597 passes: False, reasons: [\"domain Procedure not in ('Condition',)\"]\n", - "Concept ID 45566642 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36528848 passes: True, reasons: []\n", - "Concept ID 36568013 passes: True, reasons: []\n", - "Concept ID 45566643 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36538288 passes: True, reasons: []\n", - "Concept ID 45561841 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45566627 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36558369 passes: True, reasons: []\n", - "Concept ID 36542557 passes: True, reasons: []\n", - "Concept ID 36527063 passes: True, reasons: []\n", - "Concept ID 36537150 passes: True, reasons: []\n", - "Concept ID 36521377 passes: True, reasons: []\n", - "Concept ID 36564029 passes: True, reasons: []\n", - "Concept ID 36535217 passes: True, reasons: []\n", - "Concept ID 36524626 passes: True, reasons: []\n", - "Concept ID 45557021 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36518878 passes: True, reasons: []\n", - "Concept ID 36525365 passes: True, reasons: []\n", - "Concept ID 36557615 passes: True, reasons: []\n", - "Concept ID 45557036 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45532943 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36528124 passes: True, reasons: []\n", - "Concept ID 36528615 passes: True, reasons: []\n", - "Concept ID 36564333 passes: True, reasons: []\n", - "Concept ID 36536838 passes: True, reasons: []\n", - "Concept ID 45561868 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36537763 passes: True, reasons: []\n", - "Concept ID 36562092 passes: True, reasons: []\n", - "Concept ID 36533325 passes: True, reasons: []\n", - "Concept ID 36532383 passes: True, reasons: []\n", - "Concept ID 36529690 passes: True, reasons: []\n", - "Concept ID 36520494 passes: True, reasons: []\n", - "Concept ID 36534020 passes: True, reasons: []\n", - "Concept ID 36526250 passes: True, reasons: []\n", - "Concept ID 36565487 passes: True, reasons: []\n", - "Concept ID 36526863 passes: True, reasons: []\n", - "Concept ID 36547793 passes: True, reasons: []\n", - "Concept ID 36550226 passes: True, reasons: []\n", - "Concept ID 36567151 passes: True, reasons: []\n", - "Concept ID 36566509 passes: True, reasons: []\n", - "Concept ID 36537478 passes: True, reasons: []\n", - "Concept ID 36544908 passes: True, reasons: []\n", - "Concept ID 36547610 passes: True, reasons: []\n", - "Concept ID 36536057 passes: True, reasons: []\n", - "Concept ID 36526886 passes: True, reasons: []\n", - "Concept ID 36562085 passes: True, reasons: []\n", - "Concept ID 36561417 passes: True, reasons: []\n", - "Concept ID 36524388 passes: True, reasons: []\n", - "Concept ID 36522657 passes: True, reasons: []\n", - "Concept ID 36534917 passes: True, reasons: []\n", - "Concept ID 36530081 passes: True, reasons: []\n", - "Concept ID 36523242 passes: True, reasons: []\n", - "Concept ID 36564611 passes: True, reasons: []\n", - "Concept ID 42513897 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 42513904 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 42513893 passes: True, reasons: []\n", - "Concept ID 45595694 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36519459 passes: True, reasons: []\n", - "Concept ID 36552169 passes: True, reasons: []\n", - "Concept ID 36535021 passes: True, reasons: []\n", - "Concept ID 42513898 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 45542634 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 42513901 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 42513900 passes: True, reasons: []\n", - "Concept ID 4247256 passes: False, reasons: [\"domain Type Concept not in ('Condition',)\"]\n", - "Concept ID 4227235 passes: False, reasons: [\"domain Type Concept not in ('Condition',)\"]\n", - "Concept ID 36556440 passes: True, reasons: []\n", - "Concept ID 45552295 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45561839 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45552291 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45605293 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45532942 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36518177 passes: True, reasons: []\n", - "Concept ID 45566624 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45547531 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36546061 passes: True, reasons: []\n", - "Concept ID 36518980 passes: True, reasons: []\n", - "Concept ID 45576364 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36547438 passes: True, reasons: []\n", - "Concept ID 37207600 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 37207606 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 36539204 passes: True, reasons: []\n", - "Concept ID 36524018 passes: True, reasons: []\n", - "Concept ID 36554414 passes: True, reasons: []\n", - "Concept ID 45566645 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45561840 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 42513906 passes: True, reasons: []\n", - "Concept ID 36556104 passes: True, reasons: []\n", - "Concept ID 36521223 passes: True, reasons: []\n", - "Concept ID 42513907 passes: True, reasons: []\n", - "Concept ID 45547550 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36538558 passes: True, reasons: []\n", - "Concept ID 36519300 passes: True, reasons: []\n", - "Concept ID 36533645 passes: True, reasons: []\n", - "Concept ID 36527231 passes: True, reasons: []\n", - "Concept ID 36532179 passes: True, reasons: []\n", - "Concept ID 42513905 passes: True, reasons: []\n", - "Concept ID 36552173 passes: True, reasons: []\n", - "Concept ID 42513899 passes: True, reasons: []\n", - "Concept ID 36556758 passes: True, reasons: []\n", - "Concept ID 36563355 passes: True, reasons: []\n", - "Concept ID 36526420 passes: True, reasons: []\n", - "Concept ID 45576345 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45537848 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 35704660 passes: False, reasons: [\"domain Procedure not in ('Condition',)\"]\n", - "Concept ID 36562755 passes: True, reasons: []\n", - "Concept ID 36528261 passes: True, reasons: []\n", - "Concept ID 45595695 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45557019 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 45566621 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36551016 passes: True, reasons: []\n", - "Concept ID 36541554 passes: True, reasons: []\n", - "Concept ID 36542361 passes: True, reasons: []\n", - "Concept ID 36542698 passes: True, reasons: []\n", - "Concept ID 36539697 passes: True, reasons: []\n", - "Concept ID 45557035 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36536234 passes: True, reasons: []\n", - "Concept ID 45566644 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 37018877 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 37016150 passes: False, reasons: ['concept is non-standard']\n", - "Concept ID 36557706 passes: True, reasons: []\n", - "Concept ID 45590928 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 35909772 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 36543666 passes: True, reasons: []\n", - "Concept ID 36545627 passes: True, reasons: []\n", - "Concept ID 36517471 passes: True, reasons: []\n", - "Concept ID 778138 passes: False, reasons: ['vocabulary OncoTree not allowed']\n", - "Concept ID 1260365 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 36540995 passes: True, reasons: []\n", - "Concept ID 36559089 passes: True, reasons: []\n", - "Concept ID 36541128 passes: True, reasons: []\n", - "Concept ID 45576363 passes: False, reasons: ['vocabulary ICD10CM not allowed']\n", - "Concept ID 36530412 passes: True, reasons: []\n", - "Concept ID 36532840 passes: True, reasons: []\n", - "Concept ID 36567903 passes: True, reasons: []\n", - "Concept ID 36544836 passes: True, reasons: []\n", - "Concept ID 36561904 passes: True, reasons: []\n", - "Concept ID 36544731 passes: True, reasons: []\n", - "Concept ID 36551969 passes: True, reasons: []\n", - "Concept ID 36567933 passes: True, reasons: []\n", - "Concept ID 36534857 passes: True, reasons: []\n", - "Concept ID 36533588 passes: True, reasons: []\n", - "Concept ID 36536028 passes: True, reasons: []\n", - "Concept ID 36532332 passes: True, reasons: []\n", - "Concept ID 36712837 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 3439711 passes: False, reasons: [\"domain Observation not in ('Condition',)\"]\n", - "Concept ID 1259499 passes: False, reasons: [\"domain Measurement not in ('Condition',)\"]\n", - "Concept ID 36529853 passes: True, reasons: []\n", - "Concept ID 36554387 passes: True, reasons: []\n", - "Concept ID 36558777 passes: True, reasons: []\n", - "Concept ID 36528719 passes: True, reasons: []\n", - "Concept ID 36525400 passes: True, reasons: []\n", - "Concept ID 36561493 passes: True, reasons: []\n", - "Concept ID 3387091 passes: False, reasons: ['vocabulary Nebraska Lexicon not allowed']\n", - "Concept ID 36716775 passes: True, reasons: []\n", - "Concept ID 36561608 passes: True, reasons: []\n", - "Concept ID 36567376 passes: True, reasons: []\n", - "Concept ID 36557294 passes: True, reasons: []\n", - "Concept ID 36554289 passes: True, reasons: []\n", - "Concept ID 36552339 passes: True, reasons: []\n", - "Concept ID 36530181 passes: True, reasons: []\n", - "Concept ID 36559182 passes: True, reasons: []\n" - ] - } - ], - "source": [ - "for h in hits:\n", - " ok, reasons = _passes_constraints(kg, h.concept_id, constraints)\n", - " print(f\"Concept ID {h.concept_id} passes: {ok}, reasons: {reasons}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "c026d1ef", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[CandidateHit(concept_id=36540835, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539567, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555113, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544940, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536539, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525284, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529144, resolver='partial_label'),\n", - " CandidateHit(concept_id=44505365, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535471, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538924, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524874, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554928, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565324, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539366, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542532, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563256, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565037, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544798, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546669, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563078, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561341, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538414, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538420, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538031, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524234, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553925, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565906, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536971, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551165, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532848, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536897, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567717, resolver='partial_label'),\n", - " CandidateHit(concept_id=44502493, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518352, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527498, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567190, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565589, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517582, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558535, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548127, resolver='partial_label'),\n", - " CandidateHit(concept_id=44505366, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540035, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532344, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537269, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535531, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527613, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538134, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524397, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527638, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548454, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518976, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542492, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543379, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543347, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537245, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519189, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531150, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552513, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565987, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527088, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540553, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523613, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559766, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529259, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526007, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565301, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546937, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528049, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539641, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518048, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522741, resolver='partial_label'),\n", - " CandidateHit(concept_id=44502199, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531135, resolver='partial_label'),\n", - " CandidateHit(concept_id=44499589, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559154, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540672, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546468, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524306, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518889, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556423, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531625, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550575, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549283, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546238, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549164, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549285, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563745, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554952, resolver='partial_label'),\n", - " CandidateHit(concept_id=44502590, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562539, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537854, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551388, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542254, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554486, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520979, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567547, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545929, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525913, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552608, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523122, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549595, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547341, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548303, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518882, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521026, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517697, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536069, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524399, resolver='partial_label'),\n", - " CandidateHit(concept_id=44501432, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538370, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565821, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544094, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561602, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526417, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562143, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554730, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562406, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530102, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539747, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565132, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554774, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544252, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556119, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561111, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536945, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551081, resolver='partial_label'),\n", - " CandidateHit(concept_id=36534154, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562032, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565356, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533038, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542922, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524543, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527529, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549301, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565762, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523804, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547847, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562942, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531475, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563813, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528915, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536182, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542599, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550918, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559992, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555624, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537962, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544936, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549807, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520326, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544451, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533710, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541420, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559070, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528269, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551094, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556068, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518129, resolver='partial_label'),\n", - " CandidateHit(concept_id=44501605, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529276, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561562, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540107, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560811, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543685, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532126, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533375, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518157, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536388, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558104, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554896, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545362, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556137, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535461, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518018, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550938, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528243, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548224, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538102, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527137, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561812, resolver='partial_label'),\n", - " CandidateHit(concept_id=36557817, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539796, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538359, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562138, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555783, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546267, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562195, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551089, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530754, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554488, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522871, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532446, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535512, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555549, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540190, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540546, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558156, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539006, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529306, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529354, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544991, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540422, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564215, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538931, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564805, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547329, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522919, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519997, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554171, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539860, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550651, resolver='partial_label'),\n", - " CandidateHit(concept_id=36557832, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533393, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524686, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522009, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550254, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524606, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552612, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552673, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555823, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531461, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550982, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525343, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536937, resolver='partial_label'),\n", - " CandidateHit(concept_id=36534759, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544273, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524576, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539551, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563097, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542713, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553135, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528203, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542854, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547980, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528729, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564471, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546997, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517406, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540098, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559329, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523549, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564418, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563465, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553010, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561016, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541770, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539539, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541601, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548724, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560743, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543142, resolver='partial_label'),\n", - " CandidateHit(concept_id=4038839, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550302, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548334, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556788, resolver='partial_label'),\n", - " CandidateHit(concept_id=44503303, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533424, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560035, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566276, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548458, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531213, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543607, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562882, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559870, resolver='partial_label'),\n", - " CandidateHit(concept_id=44499846, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526098, resolver='partial_label'),\n", - " CandidateHit(concept_id=36568016, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520677, resolver='partial_label'),\n", - " CandidateHit(concept_id=44500701, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563750, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550020, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520775, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519413, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550801, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524061, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517333, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560219, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553422, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559377, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517718, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532363, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566181, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556417, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531663, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525354, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520536, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539372, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559900, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552168, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563875, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527828, resolver='partial_label'),\n", - " CandidateHit(concept_id=44503258, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562200, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543318, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545927, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525771, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539413, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563402, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528895, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540505, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519934, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538455, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520649, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558015, resolver='partial_label'),\n", - " CandidateHit(concept_id=44503304, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539801, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561964, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546733, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542257, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542510, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548514, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519891, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564748, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528562, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553030, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563512, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564499, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532544, resolver='partial_label'),\n", - " CandidateHit(concept_id=44503277, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538610, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535445, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545232, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541252, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549104, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519570, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540537, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530632, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549683, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527881, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558733, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538142, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564389, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554838, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554328, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530242, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551920, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520549, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551886, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520758, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558628, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537534, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527691, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564140, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528246, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560133, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559970, resolver='partial_label'),\n", - " CandidateHit(concept_id=44505368, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550586, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537556, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562737, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546356, resolver='partial_label'),\n", - " CandidateHit(concept_id=36534693, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548466, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517855, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525845, resolver='partial_label'),\n", - " CandidateHit(concept_id=44503056, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552771, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539411, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526652, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536262, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562076, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544433, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551528, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530578, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559873, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564174, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520126, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551654, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552859, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520491, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518641, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539359, resolver='partial_label'),\n", - " CandidateHit(concept_id=36557671, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517880, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527775, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566772, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553958, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529623, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527694, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530297, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536032, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554277, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551427, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543416, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522599, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523832, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540858, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562621, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560493, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537988, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533844, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551962, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520031, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517564, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522111, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562634, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559386, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535187, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531320, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541505, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532744, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529956, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520385, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559578, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535390, resolver='partial_label'),\n", - " CandidateHit(concept_id=36402814, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527223, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564705, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541807, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540965, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543274, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525223, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552881, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524875, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537696, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537811, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527299, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526973, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546399, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556465, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526325, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563907, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566800, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553461, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565192, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537886, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523326, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539930, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546052, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537059, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543352, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547555, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518457, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524724, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531930, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532682, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540770, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536300, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552129, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520765, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521172, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545134, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554190, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523834, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527819, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548945, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549224, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517906, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539007, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522600, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537533, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558686, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564281, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564139, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559839, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549812, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522847, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542424, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536227, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553096, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533892, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529510, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567835, resolver='partial_label'),\n", - " CandidateHit(concept_id=44503278, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553983, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533242, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548311, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551423, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544662, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528153, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526036, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553323, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555435, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564469, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521828, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546067, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543057, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553601, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526060, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546752, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546976, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553101, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542934, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533254, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522017, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523947, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532780, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538581, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543460, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520540, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531872, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551410, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535698, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548056, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532229, resolver='partial_label'),\n", - " CandidateHit(concept_id=36402770, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540859, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535354, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537583, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532095, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549425, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544108, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531897, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539205, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531403, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517486, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550104, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559200, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561445, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562670, resolver='partial_label'),\n", - " CandidateHit(concept_id=36402750, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541550, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558437, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520029, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562943, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542493, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567624, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555200, resolver='partial_label'),\n", - " CandidateHit(concept_id=36557141, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538937, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522301, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562951, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561825, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565413, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542448, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543887, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543904, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559104, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555293, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529361, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526879, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522977, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555941, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550912, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536274, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541962, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522054, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548812, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556458, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522950, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528443, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564456, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567991, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560659, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521404, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560122, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545186, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522826, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549773, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536719, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528136, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518588, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548447, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546146, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519203, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531243, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529342, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537029, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539679, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546245, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526117, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529025, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525220, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546613, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533371, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555845, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517811, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546053, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550306, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556023, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521638, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541209, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549393, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558519, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549264, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560372, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533761, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562461, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562099, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552298, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517285, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522436, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526030, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524149, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523634, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564235, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545705, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566430, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533736, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537786, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524192, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527026, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550157, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556221, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544495, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555802, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533850, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542112, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556923, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526645, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559771, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544123, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545766, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560972, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548704, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551487, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543124, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546485, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549580, resolver='partial_label'),\n", - " CandidateHit(concept_id=36534765, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545373, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559106, resolver='partial_label'),\n", - " CandidateHit(concept_id=44502592, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543684, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539276, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552643, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562319, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548017, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521025, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548981, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529861, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519652, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525499, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554740, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547168, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523951, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546271, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554525, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553540, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521807, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561947, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551245, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565264, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562301, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555247, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518959, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565235, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525038, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556238, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547859, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551219, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550415, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519013, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563099, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547518, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536334, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559563, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540062, resolver='partial_label'),\n", - " CandidateHit(concept_id=36534878, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536420, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547397, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544224, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553519, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522120, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541291, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542102, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551612, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546015, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550242, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559862, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553484, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529438, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535302, resolver='partial_label'),\n", - " CandidateHit(concept_id=44500108, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553264, resolver='partial_label'),\n", - " CandidateHit(concept_id=44500279, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519493, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533119, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567777, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563602, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559591, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545395, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548293, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563486, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554763, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548636, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520920, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564779, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541842, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533676, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521033, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564361, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529610, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567301, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538813, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524694, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518628, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520966, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552780, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540197, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547901, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553968, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561840, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551152, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547516, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555729, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533894, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535258, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553624, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530671, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564743, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548107, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554074, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530772, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545622, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535912, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517458, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526405, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563561, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522159, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550804, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541860, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563331, resolver='partial_label'),\n", - " CandidateHit(concept_id=44501297, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554330, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564322, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564811, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548079, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552543, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538372, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555127, resolver='partial_label'),\n", - " CandidateHit(concept_id=36534327, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541200, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555248, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545567, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537266, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562825, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544953, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556368, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517614, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561630, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541415, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524450, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566253, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540570, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529524, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563068, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547831, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547653, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530997, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525778, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558637, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550928, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552311, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555366, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528873, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523986, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519568, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564254, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562597, resolver='partial_label'),\n", - " CandidateHit(concept_id=36534405, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556094, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552600, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563430, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521037, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523214, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521115, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555491, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536150, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536814, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554627, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545672, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563328, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538942, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543586, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528648, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542555, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539573, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544446, resolver='partial_label'),\n", - " CandidateHit(concept_id=36531294, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521000, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551782, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558034, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528042, resolver='partial_label'),\n", - " CandidateHit(concept_id=36518385, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567064, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520447, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554253, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561018, resolver='partial_label'),\n", - " CandidateHit(concept_id=36557496, resolver='partial_label'),\n", - " CandidateHit(concept_id=36402824, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535710, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520850, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556219, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537407, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549121, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558422, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524790, resolver='partial_label'),\n", - " CandidateHit(concept_id=44499847, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521071, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523885, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523356, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547296, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558854, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524540, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521248, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530044, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552698, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553990, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550786, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550297, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540649, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532735, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548341, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543707, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538460, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541386, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556749, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538053, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533705, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542571, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528264, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567796, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559237, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554788, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538172, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537357, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537860, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521007, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561902, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524379, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563145, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536136, resolver='partial_label'),\n", - " CandidateHit(concept_id=44500533, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550059, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544843, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548987, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554187, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528893, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553879, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536346, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552679, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524881, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533412, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532188, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546583, resolver='partial_label'),\n", - " CandidateHit(concept_id=36546288, resolver='partial_label'),\n", - " CandidateHit(concept_id=36548131, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559419, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551555, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561986, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542400, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530897, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524872, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540655, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533691, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521724, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522586, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532873, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551865, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560409, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555915, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535291, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545676, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552031, resolver='partial_label'),\n", - " CandidateHit(concept_id=36557619, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564541, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552175, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526939, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559856, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540565, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559569, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530028, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530899, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519974, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530381, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551890, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558253, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539680, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535568, resolver='partial_label'),\n", - " CandidateHit(concept_id=36517803, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523911, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527102, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537754, resolver='partial_label'),\n", - " CandidateHit(concept_id=36542871, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526754, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541249, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523438, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545399, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564785, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565203, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527463, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564696, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526486, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522403, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560991, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537827, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529281, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524315, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545528, resolver='partial_label'),\n", - " CandidateHit(concept_id=36567189, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540620, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521326, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562861, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544266, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550724, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558790, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533395, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565941, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558308, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526285, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541941, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538318, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519753, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524102, resolver='partial_label'),\n", - " CandidateHit(concept_id=36558921, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539783, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560986, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550087, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522399, resolver='partial_label'),\n", - " CandidateHit(concept_id=36534512, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555291, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563055, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566119, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537749, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537263, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529426, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551794, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566866, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532157, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528902, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549996, resolver='partial_label'),\n", - " CandidateHit(concept_id=36562499, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565857, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535350, resolver='partial_label'),\n", - " CandidateHit(concept_id=36553704, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519504, resolver='partial_label'),\n", - " CandidateHit(concept_id=36556345, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563144, resolver='partial_label'),\n", - " CandidateHit(concept_id=36534426, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559872, resolver='partial_label'),\n", - " CandidateHit(concept_id=36520721, resolver='partial_label'),\n", - " CandidateHit(concept_id=36533455, resolver='partial_label'),\n", - " CandidateHit(concept_id=36561095, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543737, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539903, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560439, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555282, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524514, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554725, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566018, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523079, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525272, resolver='partial_label'),\n", - " CandidateHit(concept_id=36564987, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535270, resolver='partial_label'),\n", - " CandidateHit(concept_id=36560276, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525125, resolver='partial_label'),\n", - " CandidateHit(concept_id=36530952, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552875, resolver='partial_label'),\n", - " CandidateHit(concept_id=36529583, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554004, resolver='partial_label'),\n", - " CandidateHit(concept_id=36523188, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551509, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543942, resolver='partial_label'),\n", - " CandidateHit(concept_id=36549303, resolver='partial_label'),\n", - " CandidateHit(concept_id=36536282, resolver='partial_label'),\n", - " CandidateHit(concept_id=36550497, resolver='partial_label'),\n", - " CandidateHit(concept_id=36541929, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539393, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519778, resolver='partial_label'),\n", - " CandidateHit(concept_id=36540517, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566104, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526358, resolver='partial_label'),\n", - " CandidateHit(concept_id=36522875, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525384, resolver='partial_label'),\n", - " CandidateHit(concept_id=36528077, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566696, resolver='partial_label'),\n", - " CandidateHit(concept_id=36524611, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563644, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552304, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526538, resolver='partial_label'),\n", - " CandidateHit(concept_id=36527879, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566349, resolver='partial_label'),\n", - " CandidateHit(concept_id=36565873, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554548, resolver='partial_label'),\n", - " CandidateHit(concept_id=36547333, resolver='partial_label'),\n", - " CandidateHit(concept_id=36402847, resolver='partial_label'),\n", - " CandidateHit(concept_id=36539456, resolver='partial_label'),\n", - " CandidateHit(concept_id=36544043, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555583, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552520, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521470, resolver='partial_label'),\n", - " CandidateHit(concept_id=36551631, resolver='partial_label'),\n", - " CandidateHit(concept_id=36519576, resolver='partial_label'),\n", - " CandidateHit(concept_id=36566457, resolver='partial_label'),\n", - " CandidateHit(concept_id=36543335, resolver='partial_label'),\n", - " CandidateHit(concept_id=36525128, resolver='partial_label'),\n", - " CandidateHit(concept_id=36555430, resolver='partial_label'),\n", - " CandidateHit(concept_id=36563148, resolver='partial_label'),\n", - " CandidateHit(concept_id=36545204, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535097, resolver='partial_label'),\n", - " CandidateHit(concept_id=36552703, resolver='partial_label'),\n", - " CandidateHit(concept_id=36532872, resolver='partial_label'),\n", - " CandidateHit(concept_id=36526091, resolver='partial_label'),\n", - " CandidateHit(concept_id=36537467, resolver='partial_label'),\n", - " CandidateHit(concept_id=36554871, resolver='partial_label'),\n", - " CandidateHit(concept_id=36535076, resolver='partial_label'),\n", - " CandidateHit(concept_id=36521077, resolver='partial_label'),\n", - " CandidateHit(concept_id=36538506, resolver='partial_label'),\n", - " CandidateHit(concept_id=36559588, resolver='partial_label'),\n", - " ...]" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "[h for h in hits if _passes_constraints(kg, h.concept_id, constraints)[0]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c79ceccd", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "kg-core (3.12.10)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/pyproject.toml b/pyproject.toml index 0ffce9a..c28d02f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,11 +62,11 @@ duckdb = [ dev = [ "ipython>=9.8.0", - "ty>=0.0.59", + "ty==0.0.90", "pytest>=9.0.2", "pytest-cov>=7.0.0", "rich>=14.2.0", - "ruff>=0.14.10", + "ruff==0.17.1", "omop-graph[duckdb,emb,faiss-cpu,pgvector,postgres]", "mkdocs<2.0", "mkdocs-material", @@ -99,3 +99,10 @@ packages = ["src/omop_graph"] [tool.uv] cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }] + +[tool.ruff] +line-length = 100 + +[tool.ty.rules] +# raises on stale suppressions +unused-ignore-comment = "error" diff --git a/scripts/benchmarks/benchmark.py b/scripts/benchmarks/benchmark.py index 748a6d4..afcaf44 100644 --- a/scripts/benchmarks/benchmark.py +++ b/scripts/benchmarks/benchmark.py @@ -8,13 +8,17 @@ from __future__ import annotations import json +from collections.abc import Sequence from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, List, Optional, Sequence, Tuple, cast, Annotated -import typer +from typing import Annotated, Any, cast import numpy as np -from oa_configurator import Resolver, ResolvedModel, ResolvedProvider +import typer +from oa_configurator import ResolvedModel, ResolvedProvider, Resolver +from omop_alchemy.cdm.query import ConceptFilter +from omop_emb.backends import EmbeddingBackend, open_vector_store_writer +from omop_emb.backends.index_config import index_config_from_index_type from omop_emb.config import ( IndexType, MetricType, @@ -22,12 +26,11 @@ parse_metric_type, ) from omop_emb.interface import EmbeddingRole -from omop_emb.backends import EmbeddingBackend, open_vector_store_writer -from omop_emb.backends.index_config import index_config_from_index_type + from omop_graph.config import OmopGraphConfig -from omop_graph.extensions.emb import get_embedding_writer_interface, MissingExtensionError +from omop_graph.db.session import cdm_session_factory +from omop_graph.extensions.emb import MissingExtensionError, get_embedding_writer_interface from omop_graph.extensions.omop_alchemy import PredicateKind -from omop_alchemy.cdm.query import ConceptFilter from omop_graph.graph.kg import KnowledgeGraph, KnowledgeGraphEmbeddingConfiguration from omop_graph.graph.scoring import StandardConceptWithScore from omop_graph.reasoning.grounding import GroundingConstraints, ground_term @@ -42,7 +45,7 @@ PartialLabelResolver, PartialSynonymResolver, ) -from omop_graph.db.session import cdm_session_factory + app = typer.Typer() @@ -50,13 +53,18 @@ def _main( verbose: Annotated[ int, - typer.Option("--verbose", "-v", count=True, help="Increase log verbosity (-v INFO, -vv DEBUG). Must come before the subcommand name."), + typer.Option( + "--verbose", + "-v", + count=True, + help="Increase log verbosity (-v INFO, -vv DEBUG). Must come before the subcommand name.", + ), ] = 0, ) -> None: OmopGraphConfig.configure_logging(verbosity=verbose) -def _normalize_parent_ids(raw_parent_ids: object) -> Optional[Tuple[int, ...]]: +def _normalize_parent_ids(raw_parent_ids: object) -> tuple[int, ...] | None: """Normalize parent_ids from JSON into an optional tuple of ints.""" if raw_parent_ids is None: @@ -65,9 +73,7 @@ def _normalize_parent_ids(raw_parent_ids: object) -> Optional[Tuple[int, ...]]: return (raw_parent_ids,) if isinstance(raw_parent_ids, (list, tuple)): return tuple(int(parent_id) for parent_id in raw_parent_ids) - raise TypeError( - "Invalid parent_ids value. Expected int, list[int], tuple[int, ...], or null." - ) + raise TypeError("Invalid parent_ids value. Expected int, list[int], tuple[int, ...], or null.") @dataclass(frozen=True) @@ -97,11 +103,11 @@ class BenchmarkCase: id: str text: str bucket: str - domain: Optional[str] = None - vocabularies: Optional[Tuple[str, ...]] = None - expected_concept_id: Optional[int] = None - expected_concept_name: Optional[str] = None - parent_ids: Optional[Tuple[int, ...]] = None + domain: str | None = None + vocabularies: tuple[str, ...] | None = None + expected_concept_id: int | None = None + expected_concept_name: str | None = None + parent_ids: tuple[int, ...] | None = None @dataclass(frozen=True) @@ -109,17 +115,17 @@ class GroundedBenchmarkConfig: """One ablation configuration for grounded benchmark evaluation.""" name: str - resolvers: Tuple[CandidateResolver, ...] + resolvers: tuple[CandidateResolver, ...] requires_embedding: bool = False -def load_cases(path: Path) -> List[BenchmarkCase]: +def load_cases(path: Path) -> list[BenchmarkCase]: """Load benchmark cases from JSON into typed dataclass instances.""" payload = json.loads(path.read_text(encoding="utf-8")) if isinstance(payload, list): - cases: List[BenchmarkCase] = [] + cases: list[BenchmarkCase] = [] for row in payload: if "parent_ids" in row: row = {**row, "parent_ids": _normalize_parent_ids(row["parent_ids"])} @@ -138,7 +144,7 @@ def load_cases(path: Path) -> List[BenchmarkCase]: return cases if isinstance(payload, dict): - cases: List[BenchmarkCase] = [] + cases: list[BenchmarkCase] = [] for bucket, bucket_cases in payload.items(): for row in bucket_cases: if "parent_ids" in row: @@ -175,13 +181,10 @@ def build_embedding_knowledge_graph( write=True, compute_missing_embeddings=True, ) - return KnowledgeGraph( - cdm_sessions, - emb_config=config - ) + return KnowledgeGraph(cdm_sessions, emb_config=config) -def case_constraints(case: BenchmarkCase) -> Optional[ConceptFilter]: +def case_constraints(case: BenchmarkCase) -> ConceptFilter | None: """Translate case metadata into OMOP search constraints when available.""" domains = (case.domain,) if case.domain else None @@ -194,7 +197,7 @@ def case_constraints(case: BenchmarkCase) -> Optional[ConceptFilter]: ) -def percentile(values: List[float], p: int) -> float: +def percentile(values: list[float], p: int) -> float: """Return a simple nearest-rank percentile for numeric values.""" if not values: @@ -205,11 +208,11 @@ def percentile(values: List[float], p: int) -> float: def mcnemar( - a: Sequence[Dict[str, float | int | bool | str]], - b: Sequence[Dict[str, float | int | bool | str]], + a: Sequence[dict[str, float | int | bool | str]], + b: Sequence[dict[str, float | int | bool | str]], *, field: str = "top1_correct", -) -> Dict[str, float]: +) -> dict[str, float]: """Compute continuity-corrected McNemar-style paired comparison.""" paired = [(float(x[field]), float(y[field])) for x, y in zip(a, b, strict=True)] @@ -226,9 +229,9 @@ def mcnemar( def ranking_metrics( predictions: Sequence[int], - expected: Optional[int], + expected: int | None, k: int, -) -> Dict[str, float]: +) -> dict[str, float]: """Compute top1/MRR/Recall@K for one ranked prediction list.""" if expected is None: @@ -245,7 +248,7 @@ def ranking_metrics( } -def build_grounded_configs() -> Tuple[GroundedBenchmarkConfig, ...]: +def build_grounded_configs() -> tuple[GroundedBenchmarkConfig, ...]: """Build resolver ablations for grounded benchmarking.""" basic = ( @@ -281,8 +284,8 @@ def build_grounded_configs() -> Tuple[GroundedBenchmarkConfig, ...]: def _resolve_parent_ids( case: BenchmarkCase, - default_parent_ids: Optional[Tuple[int, ...]], -) -> Optional[Tuple[int, ...]]: + default_parent_ids: tuple[int, ...] | None, +) -> tuple[int, ...] | None: """Resolve grounding parent IDs from case-level or CLI-level defaults.""" if case.parent_ids: @@ -299,13 +302,13 @@ def _bucket_sort_key(bucket: str) -> tuple[int, str]: return (1, normalised) -def _order_cases_for_report(cases: Sequence[BenchmarkCase]) -> List[BenchmarkCase]: +def _order_cases_for_report(cases: Sequence[BenchmarkCase]) -> list[BenchmarkCase]: """Order cases so the easy bucket is shown first.""" return sorted(cases, key=lambda case: (_bucket_sort_key(case.bucket), case.id)) -def _summarise_config(rows: Sequence[Dict[str, Any]], label: str) -> Dict[str, Any]: +def _summarise_config(rows: Sequence[dict[str, Any]], label: str) -> dict[str, Any]: """Aggregate case-level ranking metrics into one configuration summary.""" if not rows: @@ -321,9 +324,9 @@ def _summarise_config(rows: Sequence[Dict[str, Any]], label: str) -> Dict[str, A def _print_summary_report( - summaries: Dict[str, Dict[str, Any]], - bucket_summaries: Dict[str, Dict[str, Dict[str, Any]]], - significance: Dict[str, Dict[str, float]], + summaries: dict[str, dict[str, Any]], + bucket_summaries: dict[str, dict[str, dict[str, Any]]], + significance: dict[str, dict[str, float]], k: int, ) -> None: """Print a formatted benchmark summary table to stdout.""" @@ -333,7 +336,7 @@ def _print_summary_report( header = f"{'Config':<{col_w[0]}} {'Top-1':>{col_w[1]}} {'MRR':>{col_w[2]}} {rk_label:>{col_w[3]}} {'N':>{col_w[4]}}" sep = "-" * len(header) - def _row(name: str, s: Dict[str, Any]) -> str: + def _row(name: str, s: dict[str, Any]) -> str: return ( f"{name:<{col_w[0]}} " f"{float(s.get('top1_accuracy', 0.0)):>{col_w[1]}.3f} " @@ -381,14 +384,16 @@ def _row(name: str, s: Dict[str, Any]) -> str: def _grounded_element_to_dict( concept: StandardConceptWithScore, -) -> Dict[str, object]: - +) -> dict[str, object]: + return { "concept_id": int(concept.concept_id), "concept_name": concept.concept_name, "total_score": float(concept.total_score), "relevance": float(concept.relevance), - "embedding_score": float(concept.embedding_score) if concept.embedding_score is not None else 0.0, + "embedding_score": float(concept.embedding_score) + if concept.embedding_score is not None + else 0.0, "separation": int(concept.separation), "matched_concept_label": concept.matched_concept_label, "match_kind": str(concept.match_kind), @@ -396,12 +401,12 @@ def _grounded_element_to_dict( } -def _actual_payload(grounded: Sequence[StandardConceptWithScore]) -> Dict[str, object]: +def _actual_payload(grounded: Sequence[StandardConceptWithScore]) -> dict[str, object]: """Serialize the actual top grounded result for one config.""" actual_concept = grounded[0] if grounded else None - #return { + # return { # "actual": _grounded_element_to_dict(actual_concept) if actual_concept else None - #} + # } return { "actual": { "concept_id": int(actual_concept.concept_id) if actual_concept else None, @@ -415,9 +420,9 @@ def _evaluate_grounded_case( kg: KnowledgeGraph, case: BenchmarkCase, config: GroundedBenchmarkConfig, - default_parent_ids: Optional[Tuple[int, ...]], - grounding_kwargs: Optional[Dict[str, object]] = None, -) -> Dict[str, object]: + default_parent_ids: tuple[int, ...] | None, + grounding_kwargs: dict[str, object] | None = None, +) -> dict[str, object]: """Evaluate one case using ``ground_term`` and return poster-friendly results.""" search_constraint = case_constraints(case) @@ -430,7 +435,7 @@ def _evaluate_grounded_case( resolver_pipeline = ResolverPipeline(resolvers=config.resolvers) grounding_kwargs = grounding_kwargs or {} - text_embedding = cast(Optional[np.ndarray], grounding_kwargs.get("text_embedding")) + text_embedding = cast(np.ndarray | None, grounding_kwargs.get("text_embedding")) grounded = ground_term( resolver_pipeline=resolver_pipeline, @@ -455,7 +460,11 @@ def _evaluate_grounded_case( "target_concept_name": case.expected_concept_name, **_actual_payload(grounded), "target_idx_in_grounded": next( - (i for i, concept in enumerate(grounded) if concept.concept_id == case.expected_concept_id), + ( + i + for i, concept in enumerate(grounded) + if concept.concept_id == case.expected_concept_id + ), None, ), "grounded": [_grounded_element_to_dict(concept) for concept in grounded], @@ -464,59 +473,98 @@ def _evaluate_grounded_case( @app.command() def run_benchmark( - cases_file: Annotated[str, typer.Option( - "--cases-file", "-c", - help="Path to the JSON file containing benchmark cases.") + cases_file: Annotated[ + str, + typer.Option( + "--cases-file", "-c", help="Path to the JSON file containing benchmark cases." + ), ], - embedding_model: Annotated[Optional[str], typer.Option( - "--embedding-model", "-m", - help="Name of the embedding model to use (e.g., 'text-embedding-3-small'). Falls back to config.toml") + embedding_model: Annotated[ + str | None, + typer.Option( + "--embedding-model", + "-m", + help="Name of the embedding model to use (e.g., 'text-embedding-3-small'). Falls back to config.toml", + ), ] = None, - embedding_api_base_url: Annotated[Optional[str], typer.Option( - "--embedding-api-base-url", "-u", - help="Base URL for the embedding API (e.g., 'http://localhost:8000'). Falls back to config.toml.") + embedding_api_base_url: Annotated[ + str | None, + typer.Option( + "--embedding-api-base-url", + "-u", + help="Base URL for the embedding API (e.g., 'http://localhost:8000'). Falls back to config.toml.", + ), ] = None, - embedding_api_key: Annotated[Optional[str], typer.Option( - "--embedding-api-key", "-k", - help="API key for the embedding service, if required. Falls back to config.toml.") + embedding_api_key: Annotated[ + str | None, + typer.Option( + "--embedding-api-key", + "-k", + help="API key for the embedding service, if required. Falls back to config.toml.", + ), ] = None, - embedding_metric_type: Annotated[MetricType, typer.Option( - "--embedding-metric-type", "-M", - help="Distance metric type for embedding similarity.") + embedding_metric_type: Annotated[ + MetricType, + typer.Option( + "--embedding-metric-type", "-M", help="Distance metric type for embedding similarity." + ), ] = MetricType.COSINE, - embedding_index_type: Annotated[IndexType, typer.Option( - "--embedding-index-type", "-I", - help="Index type for embedding retrieval (e.g., 'flat'). Has to match the registered model.") + embedding_index_type: Annotated[ + IndexType, + typer.Option( + "--embedding-index-type", + "-I", + help="Index type for embedding retrieval (e.g., 'flat'). Has to match the registered model.", + ), ] = IndexType.FLAT, - out_file: Annotated[Optional[str], typer.Option( - "--out-file", "-o", - help="Path to the output JSON file where results will be saved. If not provided, results will be printed to stdout.") + out_file: Annotated[ + str | None, + typer.Option( + "--out-file", + "-o", + help="Path to the output JSON file where results will be saved. If not provided, results will be printed to stdout.", + ), ] = None, - k: Annotated[int, typer.Option( - "--k", "-K", - help="Number of nearest neighbors to retrieve for each case.") + k: Annotated[ + int, + typer.Option("--k", "-K", help="Number of nearest neighbors to retrieve for each case."), ] = 5, - domains: Annotated[Optional[List[str]], typer.Option( - "--allowed-domains", "-D", - help="Used to filter cases within the case file. For multiple domains, repeat the option (e.g., -D Condition -D Procedure).") + domains: Annotated[ + list[str] | None, + typer.Option( + "--allowed-domains", + "-D", + help="Used to filter cases within the case file. For multiple domains, repeat the option (e.g., -D Condition -D Procedure).", + ), ] = None, - allowed_vocabularies: Annotated[Optional[List[str]], typer.Option( - "--allowed-vocabularies", "-V", - help="Used to filter cases within the case file. For multiple vocabularies, repeat the option (e.g., -V SNOMED -V ICDO3).") + allowed_vocabularies: Annotated[ + list[str] | None, + typer.Option( + "--allowed-vocabularies", + "-V", + help="Used to filter cases within the case file. For multiple vocabularies, repeat the option (e.g., -V SNOMED -V ICDO3).", + ), ] = None, - parent_ids: Annotated[Optional[List[str]], typer.Option( - "--grounding-parent-ids", "-G", - help="Overwrites the parent_ids specified in individual cases. For multiple IDs, repeat the option (e.g., -G 443392 -G 413015).") + parent_ids: Annotated[ + list[str] | None, + typer.Option( + "--grounding-parent-ids", + "-G", + help="Overwrites the parent_ids specified in individual cases. For multiple IDs, repeat the option (e.g., -G 443392 -G 413015).", + ), ] = None, - vector_store_name: Annotated[Optional[str], typer.Option( - "--vector-store", "-e", - help="Name of a [vector_stores.*] entry to use for embedding storage. Defaults to the value configured via omop-config.") + vector_store_name: Annotated[ + str | None, + typer.Option( + "--vector-store", + "-e", + help="Name of a [vector_stores.*] entry to use for embedding storage. Defaults to the value configured via omop-config.", + ), ] = None, ): """Generalised benchmark interface.""" cases = load_cases(Path(cases_file)) - if domains: domain_filter = set(domains) cases = [c for c in cases if c.domain in domain_filter] @@ -533,15 +581,13 @@ def run_benchmark( else: grounding_parent_ids = None if grounding_parent_ids is None and all(c.parent_ids is None for c in cases): - raise RuntimeError( - "No grounding parent IDs provided." - ) + raise RuntimeError("No grounding parent IDs provided.") embedding_kg = None - query_embeddings: Dict[str, np.ndarray] = {} - resolved_embedding_index_type: Optional[IndexType] = None - resolved_embedding_metric_type: Optional[MetricType] = None - canonical_model: Optional[str] = None + query_embeddings: dict[str, np.ndarray] = {} + resolved_embedding_index_type: IndexType | None = None + resolved_embedding_metric_type: MetricType | None = None + canonical_model: str | None = None if embedding_model is not None and embedding_api_base_url is not None: resolved_embedding_index_type = parse_index_type(embedding_index_type) @@ -552,8 +598,10 @@ def run_benchmark( resolved_model = ResolvedModel( name="benchmark-embedding-model", provider=ResolvedProvider( - name="benchmark-embedding-provider", provider="ollama", - base_url=embedding_api_base_url, api_key=embedding_api_key, + name="benchmark-embedding-provider", + provider="ollama", + base_url=embedding_api_base_url, + api_key=embedding_api_key, ), model=embedding_model, embedding_dim=None, @@ -583,7 +631,9 @@ def run_benchmark( ) embedding_writer = get_embedding_writer_interface(embedding_kg) - assert embedding_writer is not None, "Embedding backend does not support writing embeddings, which is required for this benchmark configuration." + assert embedding_writer is not None, ( + "Embedding backend does not support writing embeddings, which is required for this benchmark configuration." + ) canonical_model = embedding_writer.model_backend.model @@ -601,20 +651,20 @@ def run_benchmark( kg = KnowledgeGraph(cdm_session_factory()) configs = build_grounded_configs() - errors: Dict[str, str] = {} - case_reports: List[Dict[str, Any]] = [] + errors: dict[str, str] = {} + case_reports: list[dict[str, Any]] = [] active_kg = embedding_kg if embedding_kg is not None else kg for case in cases: - config_results: List[Dict[str, Any]] = [] + config_results: list[dict[str, Any]] = [] for config in configs: - #try: + # try: if config.requires_embedding and embedding_kg is None: raise MissingExtensionError( "Embedding config requires omop-emb plus embedding model/api settings." ) - grounding_kwargs: Optional[Dict[str, object]] = None + grounding_kwargs: dict[str, object] | None = None if embedding_kg is not None and canonical_model is not None: grounding_kwargs = { "text_embedding": query_embeddings.get(case.id), @@ -629,7 +679,7 @@ def run_benchmark( ) config_results.append(row) - #except Exception as exc: + # except Exception as exc: # errors[f"{case.id}:{config.name}"] = str(exc) # config_results.append( # { @@ -652,7 +702,7 @@ def run_benchmark( ) # Build per-config ranking rows for summary statistics. - per_config: Dict[str, List[Dict[str, Any]]] = {} + per_config: dict[str, list[dict[str, Any]]] = {} for case_report in case_reports: for cfg_result in case_report["config_results"]: config_name = str(cfg_result.get("config", "")) @@ -666,32 +716,35 @@ def run_benchmark( top1 = 1.0 if target_idx == 0 else 0.0 mrr_val = 1.0 / (int(target_idx) + 1) rak = 1.0 if int(target_idx) < k else 0.0 - per_config.setdefault(config_name, []).append({ - "case_id": case_report["case_id"], - "bucket": case_report["bucket"], - "top1_correct": top1, - "mrr": mrr_val, - "recall_at_k": rak, - }) + per_config.setdefault(config_name, []).append( + { + "case_id": case_report["case_id"], + "bucket": case_report["bucket"], + "top1_correct": top1, + "mrr": mrr_val, + "recall_at_k": rak, + } + ) summaries = {name: _summarise_config(rows, name) for name, rows in per_config.items()} - bucket_summaries: Dict[str, Dict[str, Dict[str, Any]]] = {} + bucket_summaries: dict[str, dict[str, dict[str, Any]]] = {} for config_name, rows in per_config.items(): - by_bucket: Dict[str, List[Dict[str, Any]]] = {} + by_bucket: dict[str, list[dict[str, Any]]] = {} for row in rows: b = str(row["bucket"]) by_bucket.setdefault(b, []).append(row) bucket_summaries[config_name] = { - b: _summarise_config(b_rows, f"{config_name}:{b}") - for b, b_rows in by_bucket.items() + b: _summarise_config(b_rows, f"{config_name}:{b}") for b, b_rows in by_bucket.items() } - significance: Dict[str, Dict[str, float]] = {} + significance: dict[str, dict[str, float]] = {} if "basic" in per_config and "extended" in per_config: significance["basic_vs_extended"] = mcnemar(per_config["basic"], per_config["extended"]) if "extended" in per_config and "full_text" in per_config: - significance["extended_vs_full_text"] = mcnemar(per_config["extended"], per_config["full_text"]) + significance["extended_vs_full_text"] = mcnemar( + per_config["extended"], per_config["full_text"] + ) if "full_text" in per_config and "full_text_with_embedding" in per_config: significance["full_text_vs_full_text_with_embedding"] = mcnemar( per_config["full_text"], per_config["full_text_with_embedding"] diff --git a/scripts/benchmarks/enrich_gold_standard.py b/scripts/benchmarks/enrich_gold_standard.py index c418cc8..d391a74 100644 --- a/scripts/benchmarks/enrich_gold_standard.py +++ b/scripts/benchmarks/enrich_gold_standard.py @@ -22,9 +22,9 @@ import argparse import json from collections import defaultdict -from pathlib import Path -from typing import Dict, List from dataclasses import dataclass +from pathlib import Path +from typing import Self import sqlalchemy as sa from omop_alchemy.cdm.model.vocabulary import Concept, Concept_Ancestor @@ -32,12 +32,12 @@ from omop_graph.db.session import cdm_session_factory -def _domain_query(ids: List[int]) -> sa.Select: +def _domain_query(ids: list[int]) -> sa.Select: """Each target concept's domain.""" return sa.select(Concept.concept_id, Concept.domain_id).where(Concept.concept_id.in_(ids)) -def _ancestor_query(ids: List[int]) -> sa.Select: +def _ancestor_query(ids: list[int]) -> sa.Select: """Standard strict ancestors of each target, with their name and domain.""" return ( sa.select( @@ -55,6 +55,7 @@ def _ancestor_query(ids: List[int]) -> sa.Select: ) ) + @dataclass class LevelStats: empty: int = 0 @@ -62,23 +63,24 @@ class LevelStats: single: int = 0 multiple: int = 0 - def __iadd__(self, other: "LevelStats") -> "LevelStats": + def __iadd__(self, other: LevelStats) -> Self: self.empty += other.empty self.cross_domain_fallback += other.cross_domain_fallback self.single += other.single self.multiple += other.multiple return self + @dataclass class Stats: - levels: Dict[int, LevelStats] + levels: dict[int, LevelStats] no_target: int = 0 @classmethod - def empty_for(cls, levels: List[int]) -> "Stats": + def empty_for(cls, levels: list[int]) -> Stats: return cls(levels={level: LevelStats() for level in levels}) - def __iadd__(self, other: "Stats") -> "Stats": + def __iadd__(self, other: Stats) -> Self: for level, level_stats in other.levels.items(): self.levels.setdefault(level, LevelStats()) self.levels[level] += level_stats @@ -86,7 +88,7 @@ def __iadd__(self, other: "Stats") -> "Stats": return self -def enrich_cases(cdm_sessions, cases: List[Dict], levels: List[int]) -> Stats: +def enrich_cases(cdm_sessions, cases: list[dict], levels: list[int]) -> Stats: target_ids = sorted({c["expected_concept_id"] for c in cases if c.get("expected_concept_id")}) with cdm_sessions() as session: @@ -95,7 +97,7 @@ def enrich_cases(cdm_sessions, cases: List[Dict], levels: List[int]) -> Stats: for row in session.execute(_domain_query(target_ids)).all() } - ancestors_by_target: Dict[int, List] = defaultdict(list) + ancestors_by_target: dict[int, list] = defaultdict(list) for row in session.execute(_ancestor_query(target_ids)).all(): ancestors_by_target[row.descendant_concept_id].append(row) @@ -111,7 +113,7 @@ def enrich_cases(cdm_sessions, cases: List[Dict], levels: List[int]) -> Stats: case_["domain"] = target_domain all_ancestors = ancestors_by_target.get(target_id, []) - parent_ids_by_level: Dict[str, List[int]] = {} + parent_ids_by_level: dict[str, list[int]] = {} for level in levels: at_level = [a for a in all_ancestors if a.min_levels_of_separation == level] same_domain = [a for a in at_level if a.domain_id == target_domain] diff --git a/scripts/benchmarks/trace_example.py b/scripts/benchmarks/trace_example.py index 33a7da7..e0f5847 100644 --- a/scripts/benchmarks/trace_example.py +++ b/scripts/benchmarks/trace_example.py @@ -18,9 +18,10 @@ import logging from html import escape from pathlib import Path -from typing import Annotated, Dict, List, Optional, Tuple +from typing import Annotated import typer +from omop_alchemy.cdm.query import ConceptFilter from rich.console import Console from rich.progress import ( BarColumn, @@ -30,12 +31,12 @@ TextColumn, TimeElapsedColumn, ) +from sqlalchemy.exc import NoResultFound from omop_graph.config import OmopGraphConfig from omop_graph.db.session import cdm_session_factory from omop_graph.extensions.emb import get_embedding_writer_interface from omop_graph.extensions.omop_alchemy import PredicateKind -from omop_alchemy.cdm.query import ConceptFilter from omop_graph.graph.kg import KnowledgeGraph, KnowledgeGraphEmbeddingConfiguration from omop_graph.graph.nodes import LabelMatchKind from omop_graph.reasoning.grounding import GroundingConstraints, ground_term @@ -62,11 +63,17 @@ def _main( verbose: Annotated[ int, - typer.Option("--verbose", "-v", count=True, help="Increase log verbosity (-v INFO, -vv DEBUG). Must come before the subcommand name."), + typer.Option( + "--verbose", + "-v", + count=True, + help="Increase log verbosity (-v INFO, -vv DEBUG). Must come before the subcommand name.", + ), ] = 0, ) -> None: OmopGraphConfig.configure_logging(verbosity=verbose, console=console) + # Display name for each resolver (inferred from match_kind + synonym flag) RESOLVER_DISPLAY_NAMES = { (LabelMatchKind.EXACT, False): "Exact", @@ -89,9 +96,9 @@ def _main( ] # Groups synonym + non-synonym resolvers for the SVG column view -RESOLVER_GROUPS: Tuple[Tuple[str, Tuple[str, ...]], ...] = ( - ("Exact", ("Exact", "Exact Synonym")), - ("Partial", ("Partial", "Partial Synonym")), +RESOLVER_GROUPS: tuple[tuple[str, tuple[str, ...]], ...] = ( + ("Exact", ("Exact", "Exact Synonym")), + ("Partial", ("Partial", "Partial Synonym")), ("Full Text", ("Full Text", "Full Text Synonym")), ("Embedding", ("Embedding",)), ) @@ -99,7 +106,8 @@ def _main( # ─── Helpers ────────────────────────────────────────────────────────────────── -def _build_kg(metric_type=None, embedding_model: Optional[str] = None) -> KnowledgeGraph: + +def _build_kg(metric_type=None, embedding_model: str | None = None) -> KnowledgeGraph: """Build a KG with embedding support, resolved from OmopGraphConfig.""" cdm_sessions = cdm_session_factory() try: @@ -126,18 +134,28 @@ def _build_kg(metric_type=None, embedding_model: Optional[str] = None) -> Knowle resolved_model=resolved_model, write=True, ) - logger.info("Embedding config loaded (model=%s, metric=%s).", resolved_model.model, resolved_metric.value) + logger.info( + "Embedding config loaded (model=%s, metric=%s).", + resolved_model.model, + resolved_metric.value, + ) return KnowledgeGraph(cdm_sessions, emb_config=emb_config) except Exception as exc: - logger.warning("Error occurred while loading embedding config:\n%s.\nRunning without embedding.", exc) + logger.warning( + "Error occurred while loading embedding config:\n%s.\nRunning without embedding.", + exc, + exc_info=True, + ) return KnowledgeGraph(cdm_sessions) def _resolver_label(resolver) -> str: - return RESOLVER_DISPLAY_NAMES.get((resolver.match_kind, resolver.synonym), type(resolver).__name__) + return RESOLVER_DISPLAY_NAMES.get( + (resolver.match_kind, resolver.synonym), type(resolver).__name__ + ) -def _resolve_embedding_model(embedding_model: Optional[str]) -> str: +def _resolve_embedding_model(embedding_model: str | None) -> str: """Resolve the canonical embedding model name (override or config default). Mirrors the canonicalisation `_build_kg` does, but without opening a DB @@ -158,7 +176,7 @@ def _resolve_embedding_model(embedding_model: Optional[str]) -> str: return build_model_backend_from_resolved(resolved_model).model -def _model_dir_name(embedding_model: Optional[str]) -> str: +def _model_dir_name(embedding_model: str | None) -> str: """Filesystem-safe directory name for a (canonical) embedding model name. `embedding_model` is None when no embedding is configured for this run @@ -172,7 +190,7 @@ def _model_dir_name(embedding_model: Optional[str]) -> str: return RegistryManager.safe_model_name(embedding_model) -def _concept_info(kg: KnowledgeGraph, concept_id: int) -> Dict: +def _concept_info(kg: KnowledgeGraph, concept_id: int) -> dict: try: cv = kg.concept_view(concept_id) return { @@ -182,25 +200,27 @@ def _concept_info(kg: KnowledgeGraph, concept_id: int) -> Dict: "concept_code": cv.concept_code, "domain_id": cv.domain_id, } - except Exception: + except NoResultFound: return {"concept_id": concept_id, "concept_name": f"[concept {concept_id}]"} # ─── Core trace logic ───────────────────────────────────────────────────────── + def _run_trace( kg: KnowledgeGraph, query: str, case_id: str, - parent_ids: Tuple[int, ...], - search_constraint: Optional[ConceptFilter], - expected_concept_id: Optional[int], + parent_ids: tuple[int, ...], + search_constraint: ConceptFilter | None, + expected_concept_id: int | None, top_n: int, -) -> Dict: +) -> dict: # Stage 0: compute query embedding once (shared by individual resolvers + ground_term) query_embedding = None try: from omop_emb.interface import EmbeddingRole + writer = get_embedding_writer_interface(kg) if writer is not None: query_embedding = writer.embed_texts(texts=(query,), role=EmbeddingRole.QUERY) @@ -208,28 +228,32 @@ def _run_trace( else: logger.info("No embedding writer available; EmbeddingResolver will be skipped.") except Exception as exc: - logger.warning("Could not compute query embedding: %s", exc) + logger.warning("Could not compute query embedding: %s", exc, exc_info=True) # Stage 1: run every resolver individually to capture per-resolver attribution resolver_stages = [] seen: set = set() - concept_to_resolvers: Dict[int, List[str]] = {} + concept_to_resolvers: dict[int, list[str]] = {} for resolver in ORDERED_RESOLVERS: label = _resolver_label(resolver) hit_count = 0 previously_unseen_hits = 0 try: - for hit in resolver.resolve(kg=kg, query=query, constraints=search_constraint, - query_embedding=query_embedding): + for hit in resolver.resolve( + kg=kg, query=query, constraints=search_constraint, query_embedding=query_embedding + ): hit_count += 1 if hit.concept_id not in seen: previously_unseen_hits += 1 seen.add(hit.concept_id) concept_to_resolvers.setdefault(hit.concept_id, []).append(label) except Exception as exc: + logger.warning("Resolver %s skipped", label, exc_info=True) console.print(f" {label}: skipped ({exc})") - resolver_stages.append({"resolver": label, "hits": hit_count, "previously_unseen_hits": previously_unseen_hits}) + resolver_stages.append( + {"resolver": label, "hits": hit_count, "previously_unseen_hits": previously_unseen_hits} + ) # Stage 2: full grounding pipeline (hierarchy validation + scoring + embedding ranking) pipeline = ResolverPipeline(resolvers=tuple(ORDERED_RESOLVERS)) @@ -257,35 +281,45 @@ def _run_trace( info = _concept_info(kg, cid) if cid in scored_by_id: sc = scored_by_id[cid] - hierarchy_validation.append({ - "candidate_concept_id": cid, - "candidate_concept_name": info["concept_name"], - "resolver": resolvers_list[0], - "resolvers": resolvers_list, - "passed": True, - "standard_concept_id": sc.concept_id, - "standard_concept_name": sc.concept_name, - "separation": sc.separation, - "parent_id": parent_ids[0] if parent_ids else None, - }) + hierarchy_validation.append( + { + "candidate_concept_id": cid, + "candidate_concept_name": info["concept_name"], + "resolver": resolvers_list[0], + "resolvers": resolvers_list, + "passed": True, + "standard_concept_id": sc.concept_id, + "standard_concept_name": sc.concept_name, + "separation": sc.separation, + "parent_id": parent_ids[0] if parent_ids else None, + } + ) else: - hierarchy_validation.append({ - "candidate_concept_id": cid, - "candidate_concept_name": info["concept_name"], - "resolver": resolvers_list[0], - "resolvers": resolvers_list, - "passed": False, - }) + hierarchy_validation.append( + { + "candidate_concept_id": cid, + "candidate_concept_name": info["concept_name"], + "resolver": resolvers_list[0], + "resolvers": resolvers_list, + "passed": False, + } + ) if not scored: empty_group_scoring = [] for group_name, members in RESOLVER_GROUPS: group_hits = sum(s["hits"] for s in resolver_stages if s["resolver"] in members) - empty_group_scoring.append({ - "group": group_name, "members": list(members), - "hit_count": group_hits, "scored_count": 0, - "top1": None, "target_rank": None, "target_entry": None, - }) + empty_group_scoring.append( + { + "group": group_name, + "members": list(members), + "hit_count": group_hits, + "scored_count": 0, + "top1": None, + "target_rank": None, + "target_entry": None, + } + ) return { "case_id": case_id, "query": query, @@ -305,8 +339,9 @@ def _run_trace( resolver_scoring = [] for resolver in ORDERED_RESOLVERS: label = _resolver_label(resolver) - resolver_subset = [sc for sc in scored - if label in concept_to_resolvers.get(sc.concept_id, [])] + resolver_subset = [ + sc for sc in scored if label in concept_to_resolvers.get(sc.concept_id, []) + ] if not resolver_subset: continue top1 = resolver_subset[0] @@ -318,36 +353,49 @@ def _run_trace( resolver_target_rank = i + 1 target_sc = sc break - resolver_scoring.append({ - "resolver": label, - "hit_count": sum(1 for rlist in concept_to_resolvers.values() if label in rlist), - "scored_count": len(resolver_subset), - "top1": { - "concept_id": top1.concept_id, - "concept_name": top1.concept_name, - "total_score": round(top1.total_score, 4), - "is_target": top1.concept_id == expected_concept_id, - }, - "target_rank": resolver_target_rank, - "target_entry": { - "concept_id": target_sc.concept_id, - "concept_name": target_sc.concept_name, - "total_score": round(target_sc.total_score, 4), - } if target_sc and target_sc.concept_id != top1.concept_id else None, - }) + resolver_scoring.append( + { + "resolver": label, + "hit_count": sum(1 for rlist in concept_to_resolvers.values() if label in rlist), + "scored_count": len(resolver_subset), + "top1": { + "concept_id": top1.concept_id, + "concept_name": top1.concept_name, + "total_score": round(top1.total_score, 4), + "is_target": top1.concept_id == expected_concept_id, + }, + "target_rank": resolver_target_rank, + "target_entry": { + "concept_id": target_sc.concept_id, + "concept_name": target_sc.concept_name, + "total_score": round(target_sc.total_score, 4), + } + if target_sc and target_sc.concept_id != top1.concept_id + else None, + } + ) # Stage 2c: grouped view — synonym + non-synonym merged, for SVG columns resolver_group_scoring = [] for group_name, members in RESOLVER_GROUPS: - group_subset = [sc for sc in scored - if any(m in concept_to_resolvers.get(sc.concept_id, []) for m in members)] + group_subset = [ + sc + for sc in scored + if any(m in concept_to_resolvers.get(sc.concept_id, []) for m in members) + ] group_hits = sum(s["hits"] for s in resolver_stages if s["resolver"] in members) if not group_subset: - resolver_group_scoring.append({ - "group": group_name, "members": list(members), - "hit_count": group_hits, "scored_count": 0, - "top1": None, "target_rank": None, "target_entry": None, - }) + resolver_group_scoring.append( + { + "group": group_name, + "members": list(members), + "hit_count": group_hits, + "scored_count": 0, + "top1": None, + "target_rank": None, + "target_entry": None, + } + ) continue top1 = group_subset[0] group_target_rank = None @@ -358,27 +406,31 @@ def _run_trace( group_target_rank = i + 1 target_sc = sc break - resolver_group_scoring.append({ - "group": group_name, - "members": list(members), - "hit_count": group_hits, - "scored_count": len(group_subset), - "top1": { - "concept_id": top1.concept_id, - "concept_name": top1.concept_name, - "total_score": round(top1.total_score, 4), - "is_target": top1.concept_id == expected_concept_id, - }, - "target_rank": group_target_rank, - "target_entry": { - "concept_id": target_sc.concept_id, - "concept_name": target_sc.concept_name, - "total_score": round(target_sc.total_score, 4), - } if target_sc and target_sc.concept_id != top1.concept_id else None, - }) + resolver_group_scoring.append( + { + "group": group_name, + "members": list(members), + "hit_count": group_hits, + "scored_count": len(group_subset), + "top1": { + "concept_id": top1.concept_id, + "concept_name": top1.concept_name, + "total_score": round(top1.total_score, 4), + "is_target": top1.concept_id == expected_concept_id, + }, + "target_rank": group_target_rank, + "target_entry": { + "concept_id": target_sc.concept_id, + "concept_name": target_sc.concept_name, + "total_score": round(target_sc.total_score, 4), + } + if target_sc and target_sc.concept_id != top1.concept_id + else None, + } + ) # Stage 3: build output — annotate each result with which resolvers found it - target_rank: Optional[int] = None + target_rank: int | None = None if expected_concept_id is not None: for i, sc in enumerate(scored): if sc.concept_id == expected_concept_id: @@ -387,45 +439,51 @@ def _run_trace( scoring_list = [] for rank, sc in enumerate(scored[:top_n], start=1): - scoring_list.append({ - "rank": rank, - "concept_id": sc.concept_id, - "concept_name": sc.concept_name, - "relevance": round(sc.relevance, 4), - "parsimony_penalty": round(sc.parsimony_penalty, 4), - "broadness_bonus": round(sc.broadness_bonus, 4), - "total_score": round(sc.total_score, 4), - "is_target": sc.concept_id == expected_concept_id, - "resolvers": concept_to_resolvers.get(sc.concept_id, []), - }) + scoring_list.append( + { + "rank": rank, + "concept_id": sc.concept_id, + "concept_name": sc.concept_name, + "relevance": round(sc.relevance, 4), + "parsimony_penalty": round(sc.parsimony_penalty, 4), + "broadness_bonus": round(sc.broadness_bonus, 4), + "total_score": round(sc.total_score, 4), + "is_target": sc.concept_id == expected_concept_id, + "resolvers": concept_to_resolvers.get(sc.concept_id, []), + } + ) # Always include target even when it falls outside top_n if expected_concept_id and target_rank and target_rank > top_n: sc = scored[target_rank - 1] - scoring_list.append({ - "rank": target_rank, - "concept_id": sc.concept_id, - "concept_name": sc.concept_name, - "relevance": round(sc.relevance, 4), - "parsimony_penalty": round(sc.parsimony_penalty, 4), - "broadness_bonus": round(sc.broadness_bonus, 4), - "total_score": round(sc.total_score, 4), - "is_target": True, - "resolvers": concept_to_resolvers.get(sc.concept_id, []), - }) + scoring_list.append( + { + "rank": target_rank, + "concept_id": sc.concept_id, + "concept_name": sc.concept_name, + "relevance": round(sc.relevance, 4), + "parsimony_penalty": round(sc.parsimony_penalty, 4), + "broadness_bonus": round(sc.broadness_bonus, 4), + "total_score": round(sc.total_score, 4), + "is_target": True, + "resolvers": concept_to_resolvers.get(sc.concept_id, []), + } + ) top_n_results = [] for entry in scoring_list: info = _concept_info(kg, entry["concept_id"]) - top_n_results.append({ - "rank": entry["rank"], - "concept_id": entry["concept_id"], - "concept_name": entry["concept_name"], - "vocabulary_id": info.get("vocabulary_id", ""), - "concept_code": info.get("concept_code", ""), - "total_score": entry["total_score"], - "is_target": entry["is_target"], - "resolvers": entry["resolvers"], - }) + top_n_results.append( + { + "rank": entry["rank"], + "concept_id": entry["concept_id"], + "concept_name": entry["concept_name"], + "vocabulary_id": info.get("vocabulary_id", ""), + "concept_code": info.get("concept_code", ""), + "total_score": entry["total_score"], + "is_target": entry["is_target"], + "resolvers": entry["resolvers"], + } + ) return { "case_id": case_id, @@ -442,15 +500,15 @@ def _run_trace( def _constraints_dict( - sc: Optional[ConceptFilter], - parent_ids: Tuple[int, ...], + sc: ConceptFilter | None, + parent_ids: tuple[int, ...], kg: KnowledgeGraph, -) -> Dict: +) -> dict: parent_name = None if parent_ids: try: parent_name = kg.concept_view(parent_ids[0]).concept_name - except Exception: + except NoResultFound: pass return { "parent_ids": list(parent_ids), @@ -463,7 +521,8 @@ def _constraints_dict( # ─── SVG generation ─────────────────────────────────────────────────────────── -def _svg(trace: Dict, title: str) -> str: + +def _svg(trace: dict, title: str) -> str: groups = trace.get("resolver_group_scoring", []) n = max(len(groups), 1) @@ -483,32 +542,32 @@ def _svg(trace: Dict, title: str) -> str: ARROW_COL = "#495057" OUTPUT_BG = "#eaf2ff" OUTPUT_BORDER = "#1a3a5c" - COL_HDR_BG = "#1a6b2e" # dark green for resolver column header bars + COL_HDR_BG = "#1a6b2e" # dark green for resolver column header bars TEXT_DARK = "#212529" TEXT_MID = "#495057" TEXT_LIGHT = "#6c757d" TEXT_OK = "#1a6b2e" FONT = "system-ui, -apple-system, Arial, sans-serif" - FS_XS = 10 # eyebrow labels ("INPUT", "GLOBAL OUTPUT") - FS_SM = 12 # metadata / secondary text - FS_MID = 14 # normal body text / concept names - FS_HDR = 13 # blue bar primary label (group name) + FS_XS = 10 # eyebrow labels ("INPUT", "GLOBAL OUTPUT") + FS_SM = 12 # metadata / secondary text + FS_MID = 14 # normal body text / concept names + FS_HDR = 13 # blue bar primary label (group name) COL_GAP = 10 col_w = (CW - (n - 1) * COL_GAP) // n col_xs = [MARGIN + i * (col_w + COL_GAP) for i in range(n)] - max_chars = max(8, col_w // 8) # ~14px font ≈ 8px/char + max_chars = max(8, col_w // 8) # ~14px font ≈ 8px/char - COL_HDR_H = 86 # column header total (bar 38 + two stats lines + padding) - COL_BAR_H = 38 # green bar inside column header - ARROW_H = 22 - BLOCK_H = 80 # result block height + COL_HDR_H = 86 # column header total (bar 38 + two stats lines + padding) + COL_BAR_H = 38 # green bar inside column header + ARROW_H = 22 + BLOCK_H = 80 # result block height INPUT_BAR_H = 48 # blue bar inside INPUT block - OUT_BAR_H = 44 # blue bar inside GLOBAL OUTPUT block + OUT_BAR_H = 44 # blue bar inside GLOBAL OUTPUT block - parts: List[str] = [] - y = 12 # small top margin; no title text + parts: list[str] = [] + y = 12 # small top margin; no title text def rect(x, yy, w, h, fill, stroke, rx=6, sw=1.5): return f'' @@ -519,8 +578,8 @@ def text(x, yy, s, fs=FS_MID, fill=TEXT_DARK, anchor="start", bold=False): def col_arrow(cx, yy, h=ARROW_H): return ( - f'' - f'' + f'' + f'' ) def trunc(s, n_chars): @@ -532,55 +591,97 @@ def field_row(x, yy, fields): for i, (lbl, val) in enumerate(fields): if i > 0: tspans.append(f' | ') - tspans.append(f'{escape(str(lbl))}') + tspans.append( + f'{escape(str(lbl))}' + ) tspans.append(f' {escape(str(val))}') return f'{"".join(tspans)}' constraints = trace.get("constraints", {}) parent_id = (constraints.get("parent_ids") or [None])[0] - parent_name = constraints.get("parent_concept_name") or (f"concept {parent_id}" if parent_id else "—") + parent_name = constraints.get("parent_concept_name") or ( + f"concept {parent_id}" if parent_id else "—" + ) # Resolve expected concept for the TARGET block - target_result = next((r for r in trace.get("top_n_results", []) if r.get("is_target")), None) - expected_name = trace.get("expected_concept_name") or (target_result.get("concept_name") if target_result else None) - expected_id = target_result.get("concept_id") if target_result else None + target_result = next((r for r in trace.get("top_n_results", []) if r.get("is_target")), None) + expected_name = trace.get("expected_concept_name") or ( + target_result.get("concept_name") if target_result else None + ) + expected_id = target_result.get("concept_id") if target_result else None has_target_blk = expected_name is not None or expected_id is not None # ── 1. INPUT + TARGET ── - input_h = 104 # bar(48) + two body rows at 22px each + 12px padding - input_w = int(CW * 0.58) if has_target_blk else CW - target_w = CW - input_w - 12 # 12px gap + input_h = 104 # bar(48) + two body rows at 22px each + 12px padding + input_w = int(CW * 0.58) if has_target_blk else CW + target_w = CW - input_w - 12 # 12px gap # INPUT outer box parts.append(rect(MARGIN, y, input_w, input_h, BODY_BG, HEADER_BG, rx=6, sw=2)) # blue bar: rounded top, flat bottom parts.append(rect(MARGIN, y, input_w, INPUT_BAR_H, HEADER_BG, HEADER_BG, rx=6, sw=0)) - parts.append(f'') + parts.append( + f'' + ) # eyebrow + query in bar parts.append(text(MARGIN + 12, y + 17, "INPUT", FS_XS, HEADER_FG)) - parts.append(text(MARGIN + 12, y + 37, trunc(f'"{trace.get("query", "")}"', input_w // 8), FS_MID, HEADER_FG, bold=True)) + parts.append( + text( + MARGIN + 12, + y + 37, + trunc(f'"{trace.get("query", "")}"', input_w // 8), + FS_MID, + HEADER_FG, + bold=True, + ) + ) # body: single compact row domain_str = constraints.get("domain") or "—" vocabs_str = ", ".join(constraints.get("vocabularies") or []) parent_str = f"{parent_name} (concept {parent_id})" if parent_id else "—" - parts.append(field_row(MARGIN + 14, y + INPUT_BAR_H + 20, [ - ("Domain:", domain_str), - ("Vocabularies:", vocabs_str), - ])) - parts.append(field_row(MARGIN + 14, y + INPUT_BAR_H + 42, [ - ("Parent:", parent_str), - ])) + parts.append( + field_row( + MARGIN + 14, + y + INPUT_BAR_H + 20, + [ + ("Domain:", domain_str), + ("Vocabularies:", vocabs_str), + ], + ) + ) + parts.append( + field_row( + MARGIN + 14, + y + INPUT_BAR_H + 42, + [ + ("Parent:", parent_str), + ], + ) + ) # TARGET block (golden, right of INPUT) if has_target_blk: tx = MARGIN + input_w + 12 parts.append(rect(tx, y, target_w, input_h, AMBER_BG, AMBER_BORDER, rx=6, sw=2)) parts.append(rect(tx, y, target_w, INPUT_BAR_H, AMBER_BORDER, AMBER_BORDER, rx=6, sw=0)) - parts.append(f'') + parts.append( + f'' + ) parts.append(text(tx + 12, y + 17, "TARGET", FS_XS, HEADER_FG)) - parts.append(text(tx + 12, y + 37, trunc(expected_name or "—", target_w // 8), FS_MID, HEADER_FG, bold=True)) + parts.append( + text( + tx + 12, + y + 37, + trunc(expected_name or "—", target_w // 8), + FS_MID, + HEADER_FG, + bold=True, + ) + ) if expected_id: - parts.append(field_row(tx + 14, y + INPUT_BAR_H + 22, [("Concept ID:", str(expected_id))])) + parts.append( + field_row(tx + 14, y + INPUT_BAR_H + 22, [("Concept ID:", str(expected_id))]) + ) y += input_h + 16 @@ -603,11 +704,15 @@ def field_row(x, yy, fields): parts.append(rect(cx, cy, col_w, COL_HDR_H, BODY_BG, hdr_border, rx=6, sw=1.5)) if has_results: parts.append(rect(cx, cy, col_w, COL_BAR_H, COL_HDR_BG, COL_HDR_BG, rx=6, sw=0)) - parts.append(f'') + parts.append( + f'' + ) parts.append(text(ccx, cy + 25, label, FS_HDR, HEADER_FG, "middle", bold=True)) else: parts.append(rect(cx, cy, col_w, COL_BAR_H, MISS_BG, MISS_BORDER, rx=6, sw=0)) - parts.append(f'') + parts.append( + f'' + ) parts.append(text(ccx, cy + 25, label, FS_HDR, TEXT_MID, "middle", bold=True)) stats_y = cy + COL_BAR_H + 18 parts.append(text(ccx, stats_y, f"{hits:,} candidates", FS_SM, TEXT_MID, "middle")) @@ -627,7 +732,9 @@ def field_row(x, yy, fields): if not has_results: parts.append(rect(cx, cy, col_w, BLOCK_H, MISS_BG, MISS_BORDER, rx=5, sw=1)) parts.append(text(ccx, cy + 36, "—", 16, MISS_BORDER, "middle", bold=True)) - parts.append(text(ccx, cy + 60, "no results passed hierarchy", FS_XS, TEXT_LIGHT, "middle")) + parts.append( + text(ccx, cy + 60, "no results passed hierarchy", FS_XS, TEXT_LIGHT, "middle") + ) else: top1 = group_entry.get("top1") or {} is_tgt = top1.get("is_target", False) @@ -635,11 +742,40 @@ def field_row(x, yy, fields): b1_border = HIT_BORDER if is_tgt else "#adb5bd" rank_col = TEXT_OK if is_tgt else TEXT_DARK parts.append(rect(cx, cy, col_w, BLOCK_H, b1_fill, b1_border, rx=5, sw=1.5)) - parts.append(text(ccx, cy + 22, f"Rank 1 | {top1.get('concept_id', '')}", FS_SM, rank_col, "middle", bold=True)) - parts.append(text(ccx, cy + 46, trunc(top1.get("concept_name", "—"), max_chars), FS_MID, TEXT_DARK, "middle")) + parts.append( + text( + ccx, + cy + 22, + f"Rank 1 | {top1.get('concept_id', '')}", + FS_SM, + rank_col, + "middle", + bold=True, + ) + ) + parts.append( + text( + ccx, + cy + 46, + trunc(top1.get("concept_name", "—"), max_chars), + FS_MID, + TEXT_DARK, + "middle", + ) + ) if group_entry.get("target_entry"): trank = group_entry.get("target_rank", "?") - parts.append(text(ccx, cy + 66, f"Target Rank: {trank}", FS_SM, AMBER_BORDER, "middle", bold=True)) + parts.append( + text( + ccx, + cy + 66, + f"Target Rank: {trank}", + FS_SM, + AMBER_BORDER, + "middle", + bold=True, + ) + ) cy += BLOCK_H col_bottoms.append(cy) @@ -655,18 +791,22 @@ def field_row(x, yy, fields): parts.append(rect(MARGIN, y, CW, out_h, OUTPUT_BG, OUTPUT_BORDER, rx=8, sw=3)) # blue bar: rounded top, flat bottom parts.append(rect(MARGIN, y, CW, OUT_BAR_H, OUTPUT_BORDER, OUTPUT_BORDER, rx=8, sw=0)) - parts.append(f'') + parts.append( + f'' + ) parts.append(text(MARGIN + 14, y + 16, "GLOBAL OUTPUT", FS_XS, HEADER_FG)) if top_out: name_full = top_out.get("concept_name", "") - parts.append(text(MARGIN + 14, y + 34, trunc(name_full, CW // 8), FS_MID, HEADER_FG, bold=True)) + parts.append( + text(MARGIN + 14, y + 34, trunc(name_full, CW // 8), FS_MID, HEADER_FG, bold=True) + ) rank_color = TEXT_OK if target_rank == 1 else (AMBER_BORDER if target_rank else "#b02a37") - rank_val = str(target_rank) if target_rank else "not grounded" - tspans: List[str] = [] + rank_val = str(target_rank) if target_rank else "not grounded" + tspans: list[str] = [] for lbl, val in [ ("Vocabulary:", top_out.get("vocabulary_id", "—")), - ("Code:", top_out.get("concept_code", "—")), - ("Concept:", str(top_out.get("concept_id", "—"))), + ("Code:", top_out.get("concept_code", "—")), + ("Concept:", str(top_out.get("concept_id", "—"))), ]: if tspans: tspans.append(f' | ') @@ -680,7 +820,15 @@ def field_row(x, yy, fields): ) else: parts.append(text(MARGIN + 14, y + 34, "—", FS_MID, HEADER_FG, bold=True)) - parts.append(text(MARGIN + 14, y + OUT_BAR_H + 24, "No result — query could not be grounded.", FS_SM, "#b02a37")) + parts.append( + text( + MARGIN + 14, + y + OUT_BAR_H + 24, + "No result — query could not be grounded.", + FS_SM, + "#b02a37", + ) + ) y += out_h + 20 @@ -705,7 +853,7 @@ def field_row(x, yy, fields): ) -def _relationship_classification(kg: KnowledgeGraph, concept_id: int) -> Dict: +def _relationship_classification(kg: KnowledgeGraph, concept_id: int) -> dict: """Group a concept's outgoing edges by predicate_kind for the Panel A diagram. One representative edge (relationship + target concept name) per class, plus the total @@ -713,12 +861,16 @@ def _relationship_classification(kg: KnowledgeGraph, concept_id: int) -> Dict: vs. excluded by default (everything else) without enumerating every edge. """ edges = kg.edges(concept_ids=concept_id, direction="out", active_only=True, within_domain=False) - by_kind: Dict[PredicateKind, List] = {} + by_kind: dict[PredicateKind, list] = {} for edge in edges: by_kind.setdefault(edge.predicate_kind, []).append(edge) object_ids = tuple({edge.object_id for edge in edges}) - names = {cv.concept_id: cv.concept_name for cv in kg.concept_views(object_ids)} if object_ids else {} + names = ( + {cv.concept_id: cv.concept_name for cv in kg.concept_views(object_ids)} + if object_ids + else {} + ) groups = [] for kind in PREDICATE_KIND_ORDER: @@ -727,22 +879,26 @@ def _relationship_classification(kg: KnowledgeGraph, concept_id: int) -> Dict: groups.append({"kind": kind.value, "count": 0, "example": None}) continue rep = kind_edges[0] - groups.append({ - "kind": kind.value, - "count": len(kind_edges), - "example": { - "predicate_id": rep.predicate_id, - "predicate_name": kg.predicate_name(rep.predicate_id), - "object_id": rep.object_id, - "object_name": names.get(rep.object_id, f"[concept {rep.object_id}]"), - }, - }) + groups.append( + { + "kind": kind.value, + "count": len(kind_edges), + "example": { + "predicate_id": rep.predicate_id, + "predicate_name": kg.predicate_name(rep.predicate_id), + "object_id": rep.object_id, + "object_name": names.get(rep.object_id, f"[concept {rep.object_id}]"), + }, + } + ) concept_name = kg.concept_view(concept_id).concept_name return {"concept_id": concept_id, "concept_name": concept_name, "groups": groups} -def _pick_hierarchy_examples(hierarchy_validation: List[Dict], passed: bool, limit: int = 3) -> List[Dict]: +def _pick_hierarchy_examples( + hierarchy_validation: list[dict], passed: bool, limit: int = 3 +) -> list[dict]: """Pick up to `limit` distinct-by-name hierarchy_validation entries for Panel B. For failures, Embedding-only hits are surfaced first: an Embedding candidate that falls @@ -766,7 +922,7 @@ def _pick_hierarchy_examples(hierarchy_validation: List[Dict], passed: bool, lim return picked -def _panel_svg(rel_class: Dict, trace: Dict, title: str) -> str: +def _panel_svg(rel_class: dict, trace: dict, title: str) -> str: """Render the two-panel dashboard: Panel A (relationship classification / edge filtering) above Panel B (hierarchy-constrained candidate culling).""" MARGIN = 30 @@ -783,8 +939,8 @@ def _panel_svg(rel_class: Dict, trace: Dict, title: str) -> str: MISS_BORDER = "#ced4da" FAIL_BORDER = "#b02a37" ARROW_COL = "#495057" - COL_HDR_BG_WALK = "#1a6b2e" # green: walkable for grounding (Identity) - COL_HDR_BG_EXCL = "#6c757d" # gray: excluded by default (everything else) + COL_HDR_BG_WALK = "#1a6b2e" # green: walkable for grounding (Identity) + COL_HDR_BG_EXCL = "#6c757d" # gray: excluded by default (everything else) TEXT_DARK = "#212529" TEXT_MID = "#495057" TEXT_LIGHT = "#6c757d" @@ -792,7 +948,7 @@ def _panel_svg(rel_class: Dict, trace: Dict, title: str) -> str: FS_XS, FS_SM, FS_MID, FS_HDR = 10, 12, 14, 13 - parts: List[str] = [] + parts: list[str] = [] def rect(x, yy, w, h, fill, stroke, rx=6, sw=1.5): return f'' @@ -807,26 +963,52 @@ def trunc(s, n_chars): def fan_arrows(src_cx, src_y, col_xs, col_w, dst_y): """Draw a bus line fanning out from one source point down into N columns.""" bus_y = src_y + (dst_y - src_y) // 2 - out = [f''] + out = [ + f'' + ] col_centers = [cx + col_w // 2 for cx in col_xs] - out.append(f'') + out.append( + f'' + ) for ccx in col_centers: - out.append(f'') - out.append(f'') + out.append( + f'' + ) + out.append( + f'' + ) return "\n".join(out) y = 12 # ── Panel A: relationship classification ── - parts.append(text(MARGIN, y + 8, "PANEL A — Relationship classification (edge filtering)", FS_SM, TEXT_LIGHT, bold=True)) + parts.append( + text( + MARGIN, + y + 8, + "PANEL A — Relationship classification (edge filtering)", + FS_SM, + TEXT_LIGHT, + bold=True, + ) + ) y += 18 node_h = 56 parts.append(rect(MARGIN, y, CW, node_h, BODY_BG, HEADER_BG, rx=6, sw=2)) parts.append(rect(MARGIN, y, CW, 28, HEADER_BG, HEADER_BG, rx=6, sw=0)) - parts.append(f'') + parts.append(f'') parts.append(text(MARGIN + 12, y + 19, "CONCEPT", FS_XS, HEADER_FG)) - parts.append(text(MARGIN + 12, y + 48, trunc(f'{rel_class["concept_name"]} (concept {rel_class["concept_id"]})', CW // 8), FS_MID, TEXT_DARK, bold=True)) + parts.append( + text( + MARGIN + 12, + y + 48, + trunc(f"{rel_class['concept_name']} (concept {rel_class['concept_id']})", CW // 8), + FS_MID, + TEXT_DARK, + bold=True, + ) + ) concept_box_bottom = y + node_h concept_cx = MARGIN + CW // 2 y = concept_box_bottom + 36 @@ -851,12 +1033,14 @@ def fan_arrows(src_cx, src_y, col_xs, col_w, dst_y): parts.append(rect(cx, y, col_w, col_h, body_bg, body_border, rx=6, sw=1.5)) parts.append(rect(cx, y, col_w, 30, hdr_bg, hdr_bg, rx=6, sw=0)) - parts.append(f'') + parts.append(f'') parts.append(text(ccx, y + 20, group["kind"], FS_HDR, HEADER_FG, "middle", bold=True)) - parts.append(text(ccx, y + 46, f'{group["count"]} edges', FS_SM, TEXT_MID, "middle")) + parts.append(text(ccx, y + 46, f"{group['count']} edges", FS_SM, TEXT_MID, "middle")) if group["example"]: ex = group["example"] - edge_label = trunc(f'-[{ex["predicate_name"]}]-> {ex["object_name"]}', max(8, col_w // 7)) + edge_label = trunc( + f"-[{ex['predicate_name']}]-> {ex['object_name']}", max(8, col_w // 7) + ) parts.append(text(ccx, y + 66, edge_label, FS_SM, TEXT_DARK, "middle")) else: parts.append(text(ccx, y + 66, "no edges", FS_SM, TEXT_LIGHT, "middle")) @@ -869,17 +1053,30 @@ def fan_arrows(src_cx, src_y, col_xs, col_w, dst_y): # ── Panel B: hierarchy-constrained culling ── constraints = trace.get("constraints", {}) parent_id = (constraints.get("parent_ids") or [None])[0] - parent_name = constraints.get("parent_concept_name") or (f"concept {parent_id}" if parent_id else "—") + parent_name = constraints.get("parent_concept_name") or ( + f"concept {parent_id}" if parent_id else "—" + ) - parts.append(text(MARGIN, y + 8, "PANEL B — Hierarchy-constrained culling", FS_SM, TEXT_LIGHT, bold=True)) + parts.append( + text(MARGIN, y + 8, "PANEL B — Hierarchy-constrained culling", FS_SM, TEXT_LIGHT, bold=True) + ) y += 18 anchor_h = 56 parts.append(rect(MARGIN, y, CW, anchor_h, BODY_BG, HEADER_BG, rx=6, sw=2)) parts.append(rect(MARGIN, y, CW, 28, HEADER_BG, HEADER_BG, rx=6, sw=0)) - parts.append(f'') + parts.append(f'') parts.append(text(MARGIN + 12, y + 19, "ANCHOR", FS_XS, HEADER_FG)) - parts.append(text(MARGIN + 12, y + 48, trunc(f'{parent_name} (concept {parent_id})' if parent_id else "—", CW // 8), FS_MID, TEXT_DARK, bold=True)) + parts.append( + text( + MARGIN + 12, + y + 48, + trunc(f"{parent_name} (concept {parent_id})" if parent_id else "—", CW // 8), + FS_MID, + TEXT_DARK, + bold=True, + ) + ) anchor_box_bottom = y + anchor_h anchor_cx = MARGIN + CW // 2 y = anchor_box_bottom + 36 @@ -895,28 +1092,36 @@ def fan_arrows(src_cx, src_y, col_xs, col_w, dst_y): parts.append(fan_arrows(anchor_cx, anchor_box_bottom, pcol_xs, pcol_w, y)) - for col_idx, (label, examples, is_pass) in enumerate(( - ("PASSED hierarchy anchor", pass_examples, True), - ("FAILED hierarchy anchor", fail_examples, False), - )): + for col_idx, (label, examples, is_pass) in enumerate( + ( + ("PASSED hierarchy anchor", pass_examples, True), + ("FAILED hierarchy anchor", fail_examples, False), + ) + ): cx = pcol_xs[col_idx] ccx = cx + pcol_w // 2 hdr_bg = HIT_BORDER if is_pass else FAIL_BORDER body_border = HIT_BORDER if is_pass else FAIL_BORDER parts.append(rect(cx, y, pcol_w, pcol_h, "#ffffff", body_border, rx=6, sw=1.5)) parts.append(rect(cx, y, pcol_w, 28, hdr_bg, hdr_bg, rx=6, sw=0)) - parts.append(f'') + parts.append(f'') parts.append(text(ccx, y + 19, label, FS_HDR, HEADER_FG, "middle", bold=True)) if not examples: - parts.append(text(ccx, y + 28 + row_h // 2 + 4, "no examples", FS_SM, TEXT_LIGHT, "middle")) + parts.append( + text(ccx, y + 28 + row_h // 2 + 4, "no examples", FS_SM, TEXT_LIGHT, "middle") + ) for ri, entry in enumerate(examples): ry = y + 28 + ri * row_h + row_h // 2 name = trunc(entry.get("candidate_concept_name", ""), max(8, pcol_w // 7)) cid = entry.get("candidate_concept_id") resolver = entry.get("resolver", "?") fill = TEXT_OK if is_pass else FAIL_BORDER - parts.append(text(cx + 10, ry, f'{name} ({cid})', FS_SM, TEXT_DARK, bold=False)) - detail = f'sep {entry["separation"]} | found by: {resolver}' if is_pass else f'found by: {resolver} | outside anchor' + parts.append(text(cx + 10, ry, f"{name} ({cid})", FS_SM, TEXT_DARK, bold=False)) + detail = ( + f"sep {entry['separation']} | found by: {resolver}" + if is_pass + else f"found by: {resolver} | outside anchor" + ) parts.append(text(cx + 10, ry + 15, detail, FS_XS, fill)) y += pcol_h + 24 @@ -940,14 +1145,14 @@ def fan_arrows(src_cx, src_y, col_xs, col_w, dst_y): } -def _resolver_groups_for(resolvers: List[str]) -> List[str]: +def _resolver_groups_for(resolvers: list[str]) -> list[str]: """Map a candidate's full resolver list to its distinct, canonically-ordered group names (e.g. ["Exact", "Exact Synonym"] -> ["Exact"]; preserves RESOLVER_GROUPS order).""" resolver_set = set(resolvers) return [group_name for group_name, members in RESOLVER_GROUPS if resolver_set & set(members)] -def _resolve_identity_hop(kg: KnowledgeGraph, candidate_id: int, standard_id: Optional[int]) -> Dict: +def _resolve_identity_hop(kg: KnowledgeGraph, candidate_id: int, standard_id: int | None) -> dict: """Resolve the real Identity edge (if any) a candidate uses to reach a standard concept. Returns ``{"already_standard": bool, "predicate_name": str|None, "object_id": int|None, @@ -963,12 +1168,20 @@ def _resolve_identity_hop(kg: KnowledgeGraph, candidate_id: int, standard_id: Op cv = kg.concept_view(candidate_id) base = {"vocabulary_id": cv.vocabulary_id, "invalid_reason": cv.invalid_reason} if cv.standard_concept: - return {**base, "already_standard": True, "predicate_name": None, "object_id": None, "object_name": None} + return { + **base, + "already_standard": True, + "predicate_name": None, + "object_id": None, + "object_name": None, + } edges = kg.edges( - concept_ids=candidate_id, direction="out", + concept_ids=candidate_id, + direction="out", predicate_kinds=frozenset({PredicateKind.IDENTITY}), - active_only=True, within_domain=False, + active_only=True, + within_domain=False, ) target_edge = None if standard_id is not None: @@ -976,7 +1189,13 @@ def _resolve_identity_hop(kg: KnowledgeGraph, candidate_id: int, standard_id: Op if target_edge is None and edges: target_edge = edges[0] if target_edge is None: - return {**base, "already_standard": False, "predicate_name": None, "object_id": None, "object_name": None} + return { + **base, + "already_standard": False, + "predicate_name": None, + "object_id": None, + "object_name": None, + } object_name = kg.concept_view(target_edge.object_id).concept_name return { @@ -988,7 +1207,7 @@ def _resolve_identity_hop(kg: KnowledgeGraph, candidate_id: int, standard_id: Op } -def _select_funnel_candidates(trace: Dict, max_passed: int = 6, max_failed: int = 12) -> List[Dict]: +def _select_funnel_candidates(trace: dict, max_passed: int = 6, max_failed: int = 12) -> list[dict]: """Pick a small, representative set of `hierarchy_validation` entries for the funnel diagram's starting (stage 1) column -- enough to show real candidates getting eliminated at each stage, without enumerating every candidate the resolvers found. @@ -1007,7 +1226,7 @@ def _select_funnel_candidates(trace: Dict, max_passed: int = 6, max_failed: int passed = [e for e in hierarchy_validation if e.get("passed")] failed = [e for e in hierarchy_validation if not e.get("passed")] - by_standard: Dict[int, List[Dict]] = {} + by_standard: dict[int, list[dict]] = {} for e in passed: by_standard.setdefault(e["standard_concept_id"], []).append(e) @@ -1027,7 +1246,9 @@ def _select_funnel_candidates(trace: Dict, max_passed: int = 6, max_failed: int selected_passed = ([winner_rep] if winner_rep else []) + other_reps selected_passed = selected_passed[:max_passed] - failed_sorted = sorted(failed, key=lambda e: "Embedding" not in (e.get("resolvers") or [e.get("resolver")])) + failed_sorted = sorted( + failed, key=lambda e: "Embedding" not in (e.get("resolvers") or [e.get("resolver")]) + ) # Exclude failed candidates sharing a name with an already-selected passed one: these are # almost always the same surface form under a different (duplicate vocabulary source) # concept ID, which renders confusingly -- a "failed" lane whose live Identity-edge lookup @@ -1048,7 +1269,7 @@ def _select_funnel_candidates(trace: Dict, max_passed: int = 6, max_failed: int return selected_passed + selected_failed -def _build_funnel_nodes(kg: KnowledgeGraph, trace: Dict, entries: List[Dict]) -> List[Dict]: +def _build_funnel_nodes(kg: KnowledgeGraph, trace: dict, entries: list[dict]) -> list[dict]: """Resolve each selected candidate's real Identity hop and attach resolver/score metadata, ready for the funnel renderer.""" top_n_results = trace.get("top_n_results", []) @@ -1075,21 +1296,27 @@ def _build_funnel_nodes(kg: KnowledgeGraph, trace: Dict, entries: List[Dict]) -> resolvers = entry.get("resolvers") or [entry.get("resolver")] assert isinstance(resolvers, list) and all(isinstance(r, str) for r in resolvers) - nodes.append({ - "candidate_id": candidate_id, - "candidate_name": candidate_name, - "candidate_vocabulary_id": hop["vocabulary_id"], - "candidate_invalid_reason": hop["invalid_reason"], - "resolver_groups": _resolver_groups_for(resolvers), # type: ignore - "already_standard": hop["already_standard"], - "edge_predicate": hop["predicate_name"], - "standard_id": final_standard_id, - "standard_name": final_standard_name, - "passed": passed, - "separation": entry.get("separation") if passed else None, - "score": score_by_id.get(final_standard_id) if final_standard_id is not None else None, - "is_winner": passed and final_standard_id is not None and final_standard_id == winner_id, - }) + nodes.append( + { + "candidate_id": candidate_id, + "candidate_name": candidate_name, + "candidate_vocabulary_id": hop["vocabulary_id"], + "candidate_invalid_reason": hop["invalid_reason"], + "resolver_groups": _resolver_groups_for(resolvers), # type: ignore + "already_standard": hop["already_standard"], + "edge_predicate": hop["predicate_name"], + "standard_id": final_standard_id, + "standard_name": final_standard_name, + "passed": passed, + "separation": entry.get("separation") if passed else None, + "score": score_by_id.get(final_standard_id) + if final_standard_id is not None + else None, + "is_winner": passed + and final_standard_id is not None + and final_standard_id == winner_id, + } + ) # Drop failed nodes whose live-Identity-edge fallback happens to land on a standard concept # that another lane independently confirmed *passes* the anchor -- internally contradictory @@ -1100,7 +1327,9 @@ def _build_funnel_nodes(kg: KnowledgeGraph, trace: Dict, entries: List[Dict]) -> return nodes -def _trim_funnel_nodes(nodes: List[Dict], max_passed: int = 3, max_no_mapping: int = 1, max_hierarchy_fail: int = 1) -> List[Dict]: +def _trim_funnel_nodes( + nodes: list[dict], max_passed: int = 3, max_no_mapping: int = 1, max_hierarchy_fail: int = 1 +) -> list[dict]: """Cut the built node list down to a small, clean set for the figure: the winner plus a couple of other genuinely-scored alternatives, and one example of each failure flavor (no Identity mapping at all vs. a real mapping that falls outside the hierarchy anchor) -- so @@ -1120,12 +1349,14 @@ def _trim_funnel_nodes(nodes: List[Dict], max_passed: int = 3, max_no_mapping: i (n for n in nodes if not n["passed"] and n["standard_id"] is None), key=lambda n: n["candidate_invalid_reason"] is not None, )[:max_no_mapping] - hierarchy_fail = [n for n in nodes if not n["passed"] and n["standard_id"] is not None][:max_hierarchy_fail] + hierarchy_fail = [n for n in nodes if not n["passed"] and n["standard_id"] is not None][ + :max_hierarchy_fail + ] return selected_passed + no_mapping + hierarchy_fail -def _resolve_anchor_concepts(kg: KnowledgeGraph, trace: Dict) -> List[Dict]: +def _resolve_anchor_concepts(kg: KnowledgeGraph, trace: dict) -> list[dict]: """Resolve every hierarchy-anchor parent ID's concept name (not just the first) for the funnel diagram's top banner.""" parent_ids = trace.get("constraints", {}).get("parent_ids") or [] @@ -1133,10 +1364,13 @@ def _resolve_anchor_concepts(kg: KnowledgeGraph, trace: Dict) -> List[Dict]: return [] views = kg.concept_views(tuple(parent_ids)) name_by_id = {v.concept_id: v.concept_name for v in views} - return [{"concept_id": pid, "concept_name": name_by_id.get(pid, f"[concept {pid}]")} for pid in parent_ids] + return [ + {"concept_id": pid, "concept_name": name_by_id.get(pid, f"[concept {pid}]")} + for pid in parent_ids + ] -def _funnel_svg(nodes: List[Dict], trace: Dict, anchor_concepts: List[Dict]) -> str: +def _funnel_svg(nodes: list[dict], trace: dict, anchor_concepts: list[dict]) -> str: """Render the 4-stage left-to-right funnel: candidates -> standard concepts (Identity hop) -> hierarchy-validated -> scored/winner. Candidates are stacked vertically; nodes that converge on the same standard concept merge into one box in later stages. Eliminated @@ -1185,11 +1419,17 @@ def wrap_words(s, max_chars, max_lines=2): # ── Hierarchy anchor banner sizing (computed up front so the grid leaves it enough room) ── ANCHOR_LABEL = "Hierarchy anchor: " if anchor_concepts: - anchor_value = ", ".join(f'{a["concept_name"]} [{a["concept_id"]}]' for a in anchor_concepts) + anchor_value = ", ".join( + f"{a['concept_name']} [{a['concept_id']}]" for a in anchor_concepts + ) else: anchor_value = "—" anchor_chars_per_line = max(20, int((W - 2 * MARGIN - 24) / 7.3)) - anchor_lines = [line for line in wrap_words(ANCHOR_LABEL + anchor_value, anchor_chars_per_line, max_lines=4) if line] or ["—"] + anchor_lines = [ + line + for line in wrap_words(ANCHOR_LABEL + anchor_value, anchor_chars_per_line, max_lines=4) + if line + ] or ["—"] ANCHOR_BOX_H = 16 + len(anchor_lines) * ANCHOR_LINE_H # Anchor banner sits directly under the search-term banner with no gap; headers/grid start @@ -1198,16 +1438,16 @@ def wrap_words(s, max_chars, max_lines=2): ANCHOR_BOX_Y = SEARCH_BOX_Y + SEARCH_BOX_H HEADER_Y = ANCHOR_BOX_Y + ANCHOR_BOX_H + 24 TOP_Y = HEADER_Y + 9 - BOTTOM_MARGIN = 7 # gap below the "Found by" legend to the canvas edge - LEGEND_RECT_H = 14 # the colored resolver-swatch height in the legend - GRID_LEGEND_GAP = 16 # gap between the last candidate row and the "Found by" legend + BOTTOM_MARGIN = 7 # gap below the "Found by" legend to the canvas edge + LEGEND_RECT_H = 14 # the colored resolver-swatch height in the legend + GRID_LEGEND_GAP = 16 # gap between the last candidate row and the "Found by" legend FOOTER_RESERVED = GRID_LEGEND_GAP + LEGEND_RECT_H + BOTTOM_MARGIN n = max(len(nodes), 1) total_h = n * NODE_H + (n - 1) * NODE_GAP H = max(420, TOP_Y + total_h + FOOTER_RESERVED) - parts: List[str] = [] + parts: list[str] = [] def rect(x, yy, w, h, fill, stroke, rx=6, sw=1.5, dashed=False): dash = ' stroke-dasharray="6,4"' if dashed else "" @@ -1223,7 +1463,7 @@ def text_label_value(x, yy, label, value, fs=FS_SM, fill=TEXT_DARK): f'' f'{escape(str(label))}' f'{escape(str(value))}' - f'' + f"" ) def split_label(label): @@ -1244,11 +1484,15 @@ def arrow(x1, y1, x2, y2, color, dashed=False, bold=False, label=None): ux, uy = dx / dist, dy / dist head = 8 lx2, ly2 = x2 - ux * head, y2 - uy * head - out = [f''] + out = [ + f'' + ] px, py = -uy, ux p1 = (x2 - ux * head + px * 4, y2 - uy * head + py * 4) p2 = (x2 - ux * head - px * 4, y2 - uy * head - py * 4) - out.append(f'') + out.append( + f'' + ) if label: mx, my = (x1 + lx2) / 2, (y1 + ly2) / 2 if isinstance(label, tuple): @@ -1268,7 +1512,9 @@ def stripe(x, yy, w, groups): out = [] for i, g in enumerate(groups): color = RESOLVER_STRIPE_COLORS.get(g, TEXT_LIGHT) - out.append(f'') + out.append( + f'' + ) return "\n".join(out) def name_id_box(x, y, name, cid): @@ -1320,23 +1566,58 @@ def outcome_colors(passed, winner): # ── Search-term banner (full width, at the very top) ── query = trace.get("query") or "—" - parts.append(rect(MARGIN, SEARCH_BOX_Y, W - 2 * MARGIN, SEARCH_BOX_H, SEARCH_BG, SEARCH_BORDER, rx=6, sw=1.5)) - parts.append(text_label_value( - MARGIN + 12, SEARCH_BOX_Y + SEARCH_BOX_H / 2 + 4, "Search term: ", f'"{query}"', FS_SM, SEARCH_BORDER, - )) + parts.append( + rect( + MARGIN, + SEARCH_BOX_Y, + W - 2 * MARGIN, + SEARCH_BOX_H, + SEARCH_BG, + SEARCH_BORDER, + rx=6, + sw=1.5, + ) + ) + parts.append( + text_label_value( + MARGIN + 12, + SEARCH_BOX_Y + SEARCH_BOX_H / 2 + 4, + "Search term: ", + f'"{query}"', + FS_SM, + SEARCH_BORDER, + ) + ) # ── Hierarchy anchor banner (full width, directly below the search-term banner, no gap) ── - parts.append(rect(MARGIN, ANCHOR_BOX_Y, W - 2 * MARGIN, ANCHOR_BOX_H, ANCHOR_BG, ANCHOR_BORDER, rx=6, sw=1.5)) + parts.append( + rect( + MARGIN, + ANCHOR_BOX_Y, + W - 2 * MARGIN, + ANCHOR_BOX_H, + ANCHOR_BG, + ANCHOR_BORDER, + rx=6, + sw=1.5, + ) + ) yy = ANCHOR_BOX_Y + 8 + 12 # 8px top padding + ~12px baseline-from-top for a 12px font for i, line in enumerate(anchor_lines): if i == 0 and line.startswith(ANCHOR_LABEL): - parts.append(text_label_value(MARGIN + 12, yy, ANCHOR_LABEL, line[len(ANCHOR_LABEL):], FS_SM, ANCHOR_BORDER)) + parts.append( + text_label_value( + MARGIN + 12, yy, ANCHOR_LABEL, line[len(ANCHOR_LABEL) :], FS_SM, ANCHOR_BORDER + ) + ) else: parts.append(text(MARGIN + 12, yy, line, FS_SM, ANCHOR_BORDER)) yy += ANCHOR_LINE_H for i, hdr in enumerate(headers): - parts.append(text(col_xs[i] + COL_W / 2, HEADER_Y, hdr, FS_SM, TEXT_LIGHT, "middle", bold=True)) + parts.append( + text(col_xs[i] + COL_W / 2, HEADER_Y, hdr, FS_SM, TEXT_LIGHT, "middle", bold=True) + ) n_nodes = len(nodes) start_y = TOP_Y + max(0, (H - TOP_Y - FOOTER_RESERVED - total_h) / 2) @@ -1350,17 +1631,23 @@ def outcome_colors(passed, winner): parts.append(stripe(x, y, COL_W, node["resolver_groups"])) box_parts, next_y = name_id_box(x, y, node["candidate_name"], node["candidate_id"]) parts.extend(box_parts) - parts.append(text(x + 10, next_y, f'Vocabulary: {node["candidate_vocabulary_id"]}', FS_XS, TEXT_LIGHT)) + parts.append( + text( + x + 10, next_y, f"Vocabulary: {node['candidate_vocabulary_id']}", FS_XS, TEXT_LIGHT + ) + ) # ── Stage 1 -> 2: Identity hop (or dead end if no mapping at all) ── - std_order: List[int] = [] - std_indices: Dict[int, List[int]] = {} + std_order: list[int] = [] + std_indices: dict[int, list[int]] = {} for i, node in enumerate(nodes): sid = node["standard_id"] if sid is None: x1, y1 = col_xs[0] + COL_W, node_cy[i] parts.append(arrow(x1, y1, x1 + 26, y1, FAIL_BORDER, dashed=True)) - parts.append(text(x1 + 32, y1 + 4, "✗ no Identity mapping found", FS_XS, FAIL_BORDER, bold=True)) + parts.append( + text(x1 + 32, y1 + 4, "✗ no Identity mapping found", FS_XS, FAIL_BORDER, bold=True) + ) continue std_indices.setdefault(sid, []) if sid not in std_order: @@ -1369,10 +1656,17 @@ def outcome_colors(passed, winner): std_cy = {sid: sum(node_cy[i] for i in idxs) / len(idxs) for sid, idxs in std_indices.items()} std_passed = {sid: any(nodes[i]["passed"] for i in idxs) for sid, idxs in std_indices.items()} - std_winner = {sid: any(nodes[i]["is_winner"] for i in idxs) for sid, idxs in std_indices.items()} - std_name = {sid: next(nodes[i]["standard_name"] for i in idxs) for sid, idxs in std_indices.items()} + std_winner = { + sid: any(nodes[i]["is_winner"] for i in idxs) for sid, idxs in std_indices.items() + } + std_name = { + sid: next(nodes[i]["standard_name"] for i in idxs) for sid, idxs in std_indices.items() + } std_sep = { - sid: min((nodes[i]["separation"] for i in idxs if nodes[i]["separation"] is not None), default=None) + sid: min( + (nodes[i]["separation"] for i in idxs if nodes[i]["separation"] is not None), + default=None, + ) for sid, idxs in std_indices.items() } std_score = { @@ -1382,7 +1676,11 @@ def outcome_colors(passed, winner): std_edge_label = {} for sid, idxs in std_indices.items(): rep = nodes[idxs[0]] - std_edge_label[sid] = "Already standard (no hop)" if rep["already_standard"] else (rep["edge_predicate"] or "Identity mapping") + std_edge_label[sid] = ( + "Already standard (no hop)" + if rep["already_standard"] + else (rep["edge_predicate"] or "Identity mapping") + ) for i, node in enumerate(nodes): sid = node["standard_id"] @@ -1390,16 +1688,32 @@ def outcome_colors(passed, winner): continue x1, y1 = col_xs[0] + COL_W, node_cy[i] x2, y2 = col_xs[1], std_cy[sid] - color = WIN_BORDER if node["is_winner"] else (PASS_BORDER if node["passed"] else FAIL_BORDER) - parts.append(arrow(x1, y1, x2, y2, color, dashed=not node["passed"], bold=node["is_winner"])) + color = ( + WIN_BORDER if node["is_winner"] else (PASS_BORDER if node["passed"] else FAIL_BORDER) + ) + parts.append( + arrow(x1, y1, x2, y2, color, dashed=not node["passed"], bold=node["is_winner"]) + ) for sid in std_order: x, y = col_xs[1], std_cy[sid] - NODE_H / 2 bg, border = outcome_colors(std_passed[sid], std_winner[sid]) - parts.append(rect(x, y, COL_W, NODE_H, bg, border, rx=6, sw=2 if std_winner[sid] else 1.5, dashed=not std_passed[sid])) + parts.append( + rect( + x, + y, + COL_W, + NODE_H, + bg, + border, + rx=6, + sw=2 if std_winner[sid] else 1.5, + dashed=not std_passed[sid], + ) + ) box_parts, next_y = name_id_box(x, y, std_name[sid], sid) parts.extend(box_parts) - parts.append(text(x + 10, next_y, f'via: {std_edge_label[sid]}', FS_XS, TEXT_LIGHT)) + parts.append(text(x + 10, next_y, f"via: {std_edge_label[sid]}", FS_XS, TEXT_LIGHT)) # ── Stage 2 -> 3: hierarchy validation (kept vs. culled) ── for sid in std_order: @@ -1412,10 +1726,25 @@ def outcome_colors(passed, winner): color = WIN_BORDER if std_winner[sid] else PASS_BORDER parts.append(arrow(x1, y1, x2, y1, color, bold=std_winner[sid])) bg, border = outcome_colors(True, std_winner[sid]) - parts.append(rect(x2, y1 - NODE_H / 2, COL_W, NODE_H, bg, border, rx=6, sw=2 if std_winner[sid] else 1.5)) + parts.append( + rect( + x2, + y1 - NODE_H / 2, + COL_W, + NODE_H, + bg, + border, + rx=6, + sw=2 if std_winner[sid] else 1.5, + ) + ) ccx = x2 + COL_W / 2 - parts.append(text(ccx, y1 - 4, "✓ Passed hierarchy constraint", FS_SM, border, "middle", bold=True)) - dist_str = f'Distance to Hierarchy Anchor: {std_sep[sid]}' if std_sep[sid] is not None else "" + parts.append( + text(ccx, y1 - 4, "✓ Passed hierarchy constraint", FS_SM, border, "middle", bold=True) + ) + dist_str = ( + f"Distance to Hierarchy Anchor: {std_sep[sid]}" if std_sep[sid] is not None else "" + ) parts.append(text(ccx, y1 + 14, dist_str, FS_XS, TEXT_LIGHT, "middle")) # ── Stage 3 -> 4: scoring (final rank) ── @@ -1429,15 +1758,26 @@ def outcome_colors(passed, winner): parts.append(arrow(x1, y, x2, y, color, bold=std_winner[sid])) bg, border = outcome_colors(True, std_winner[sid]) box_y = y - NODE_H / 2 - parts.append(rect(x2, box_y, COL_W, NODE_H, bg, border, rx=6, sw=2 if std_winner[sid] else 1.5)) + parts.append( + rect(x2, box_y, COL_W, NODE_H, bg, border, rx=6, sw=2 if std_winner[sid] else 1.5) + ) box_parts, next_y = name_id_box(x2, box_y, std_name[sid], sid) parts.extend(box_parts) - score_str = f'{std_score[sid]:.3f}' if std_score[sid] is not None else "n/a" + score_str = f"{std_score[sid]:.3f}" if std_score[sid] is not None else "n/a" if std_winner[sid]: status = f"★ WINNER (rank 1), Score: {score_str}" else: status = f"Score: {score_str}" - parts.append(text(x2 + 10, next_y, status, FS_XS, WIN_BORDER if std_winner[sid] else TEXT_LIGHT, bold=std_winner[sid])) + parts.append( + text( + x2 + 10, + next_y, + status, + FS_XS, + WIN_BORDER if std_winner[sid] else TEXT_LIGHT, + bold=std_winner[sid], + ) + ) # ── Legend: resolver-attribution stripe colors ── legend_y = H - BOTTOM_MARGIN - 4 @@ -1446,7 +1786,9 @@ def outcome_colors(passed, winner): lx += 58 for g in ("Exact", "Partial", "Full Text", "Embedding"): color = RESOLVER_STRIPE_COLORS[g] - parts.append(f'') + parts.append( + f'' + ) parts.append(text(lx + 20, legend_y + 1, g, FS_XS, TEXT_MID)) lx += 9 * len(g) + 50 @@ -1459,42 +1801,112 @@ def outcome_colors(passed, winner): # ─── CLI commands ────────────────────────────────────────────────────────────── + @app.command() def trace( - cases_file: Annotated[Optional[str], typer.Option("--cases-file", "-c", - help="JSON benchmark cases file. Runs all cases unless --case-ids is given.")] = None, - case_ids: Annotated[Optional[List[str]], typer.Option("--case-id", "-i", - help="Optional case IDs to run. For multiple IDs, repeat the option (e.g., -i case1 -i case2). Default: None (all cases run)")] = None, - query: Annotated[Optional[str], typer.Option("--query", "-q", - help="Query text for on-demand evaluation, circumventing the cases file.")] = None, - parent_ids: Annotated[Optional[List[int]], typer.Option("--parent-id", "-G", - help="Provide parent concept IDs for hierarchy anchoring. For multiple IDs, repeat the option (e.g., -G 443392 -G 413015).")] = None, - domains: Annotated[Optional[List[str]], typer.Option("--domain", "-D", - help="OMOP domain filter. For multiple domains, repeat the option (e.g., -D Condition -D Procedure).")] = None, - vocabularies: Annotated[Optional[List[str]], typer.Option("--vocabulary", "-V", - help="Vocabulary filter. For multiple vocabularies, repeat the option (e.g., -V SNOMED -V ICDO3).")] = None, - query_expected_concept_id: Annotated[Optional[int], typer.Option("--expected-concept-id", "-e", - help="Expected concept ID for on-demand evaluation.")] = None, - top_n: Annotated[int, typer.Option("--top-n", "-n", - help="Number of top results to include per case.")] = 5, - out_dir: Annotated[Optional[str], typer.Option("--out-dir", "-o", - help="Base output directory. Writes one file per case to //trace_.json. If omitted, prints combined JSON to stdout.")] = None, - metric_type: Annotated[str, typer.Option("--metric-type", "-m", - help="Distance metric used when embedding was indexed (cosine or l2). Must match the metric registered for the model.")] = "cosine", - embedding_model: Annotated[Optional[str], typer.Option("--embedding-model", "-E", - help="Embedding model name to use, overriding the one configured in config.toml (e.g. to compare multiple ingested models).")] = None, - parent_id_level: Annotated[Optional[int], typer.Option("--parent-id-level", "-L", - help="Use case['parent_ids_by_level'][N] as the anchor instead of case['parent_ids'] " - "(see enrich_gold_standard.py). Falls back to case['parent_ids'] for cases/files " - "without per-level data. Also nests output under parent_id_level_/.")] = None, + cases_file: Annotated[ + str | None, + typer.Option( + "--cases-file", + "-c", + help="JSON benchmark cases file. Runs all cases unless --case-ids is given.", + ), + ] = None, + case_ids: Annotated[ + list[str] | None, + typer.Option( + "--case-id", + "-i", + help="Optional case IDs to run. For multiple IDs, repeat the option (e.g., -i case1 -i case2). Default: None (all cases run)", + ), + ] = None, + query: Annotated[ + str | None, + typer.Option( + "--query", + "-q", + help="Query text for on-demand evaluation, circumventing the cases file.", + ), + ] = None, + parent_ids: Annotated[ + list[int] | None, + typer.Option( + "--parent-id", + "-G", + help="Provide parent concept IDs for hierarchy anchoring. For multiple IDs, repeat the option (e.g., -G 443392 -G 413015).", + ), + ] = None, + domains: Annotated[ + list[str] | None, + typer.Option( + "--domain", + "-D", + help="OMOP domain filter. For multiple domains, repeat the option (e.g., -D Condition -D Procedure).", + ), + ] = None, + vocabularies: Annotated[ + list[str] | None, + typer.Option( + "--vocabulary", + "-V", + help="Vocabulary filter. For multiple vocabularies, repeat the option (e.g., -V SNOMED -V ICDO3).", + ), + ] = None, + query_expected_concept_id: Annotated[ + int | None, + typer.Option( + "--expected-concept-id", "-e", help="Expected concept ID for on-demand evaluation." + ), + ] = None, + top_n: Annotated[ + int, typer.Option("--top-n", "-n", help="Number of top results to include per case.") + ] = 5, + out_dir: Annotated[ + str | None, + typer.Option( + "--out-dir", + "-o", + help="Base output directory. Writes one file per case to //trace_.json. If omitted, prints combined JSON to stdout.", + ), + ] = None, + metric_type: Annotated[ + str, + typer.Option( + "--metric-type", + "-m", + help="Distance metric used when embedding was indexed (cosine or l2). Must match the metric registered for the model.", + ), + ] = "cosine", + embedding_model: Annotated[ + str | None, + typer.Option( + "--embedding-model", + "-E", + help="Embedding model name to use, overriding the one configured in config.toml (e.g. to compare multiple ingested models).", + ), + ] = None, + parent_id_level: Annotated[ + int | None, + typer.Option( + "--parent-id-level", + "-L", + help="Use case['parent_ids_by_level'][N] as the anchor instead of case['parent_ids'] " + "(see enrich_gold_standard.py). Falls back to case['parent_ids'] for cases/files " + "without per-level data. Also nests output under parent_id_level_/.", + ), + ] = None, ): """Trace grounding cases through every resolver stage. Outputs {\"cases\": [...]}.""" # Build the list of cases to run - cases_to_run: List[Dict] = [] - + cases_to_run: list[dict] = [] + if cases_file: payload = json.loads(Path(cases_file).read_text()) - rows = payload if isinstance(payload, list) else [r for bucket in payload.values() for r in bucket] + rows = ( + payload + if isinstance(payload, list) + else [r for bucket in payload.values() for r in bucket] + ) if case_ids: case_ids_str = {str(cid) for cid in case_ids} rows = [r for r in rows if str(r.get("id")) in case_ids_str] @@ -1506,12 +1918,14 @@ def trace( if not parent_ids: console.print("--parent-ids required with --query.") raise typer.Exit(1) - cases_to_run = [{ - "id": "on-demand", - "text": query, - "expected_concept_id": query_expected_concept_id, - "expected_concept_name": None, - }] + cases_to_run = [ + { + "id": "on-demand", + "text": query, + "expected_concept_id": query_expected_concept_id, + "expected_concept_name": None, + } + ] else: console.print("Provide --cases-file or --query.") raise typer.Exit(1) @@ -1522,6 +1936,7 @@ def trace( console.print("Building knowledge graph…") from omop_emb.config import parse_metric_type + kg = _build_kg(metric_type=parse_metric_type(metric_type), embedding_model=embedding_model) used_embedding_model = ( kg.embedding_configuration.model_name if kg.embedding_configuration else None @@ -1541,13 +1956,17 @@ def trace( case_id = case.get("id") case_query = case.get("text") case_domains = domains or ([case.get("domain")] if case.get("domain") else None) - case_vocab = vocabularies or ([case.get("vocabulary")] if case.get("vocabulary") else None) + case_vocab = vocabularies or ( + [case.get("vocabulary")] if case.get("vocabulary") else None + ) if parent_ids: case_parent_ids = parent_ids elif parent_id_level is not None: by_level = case.get("parent_ids_by_level") if by_level is None: - console.print(f"Case '{case_id}': no parent_ids_by_level, falling back to parent_ids.") + console.print( + f"Case '{case_id}': no parent_ids_by_level, falling back to parent_ids." + ) case_parent_ids = case.get("parent_ids") else: case_parent_ids = by_level.get(str(parent_id_level)) @@ -1570,8 +1989,12 @@ def trace( continue search_constraint = ConceptFilter( - domains=tuple([str(c) for c in case_domains]) if isinstance(case_domains, list) else None, - vocabularies=tuple([str(v) for v in case_vocab]) if isinstance(case_vocab, list) else None, + domains=tuple([str(c) for c in case_domains]) + if isinstance(case_domains, list) + else None, + vocabularies=tuple([str(v) for v in case_vocab]) + if isinstance(case_vocab, list) + else None, require_standard=False, ) @@ -1623,20 +2046,45 @@ def trace( @app.command(name="pipeline-svg") def pipeline_svg( - trace_dir: Annotated[str, typer.Option("--trace-dir", "-t", - help="Base output directory passed to 'trace --out-dir'. The model-specific " - "subdirectory is resolved the same way 'trace' does, via --embedding-model " - "or config.toml.")], - embedding_model: Annotated[Optional[str], typer.Option("--embedding-model", "-E", - help="Embedding model name used to resolve the subdirectory, overriding " - "config.toml (same semantics as 'trace --embedding-model').")] = None, - case_id: Annotated[Optional[str], typer.Option("--case-id", "-i", - help="Case ID to render. Omitted, empty, or 'all' renders every case found.")] = None, - title: Annotated[str, typer.Option("--title", - help="Figure title shown at the top.")] = "omop-graph Concept Grounding", - parent_id_level: Annotated[Optional[int], typer.Option("--parent-id-level", "-L", - help="Same --parent-id-level passed to 'trace' -- resolves the matching " - "parent_id_level_/ subdirectory.")] = None, + trace_dir: Annotated[ + str, + typer.Option( + "--trace-dir", + "-t", + help="Base output directory passed to 'trace --out-dir'. The model-specific " + "subdirectory is resolved the same way 'trace' does, via --embedding-model " + "or config.toml.", + ), + ], + embedding_model: Annotated[ + str | None, + typer.Option( + "--embedding-model", + "-E", + help="Embedding model name used to resolve the subdirectory, overriding " + "config.toml (same semantics as 'trace --embedding-model').", + ), + ] = None, + case_id: Annotated[ + str | None, + typer.Option( + "--case-id", + "-i", + help="Case ID to render. Omitted, empty, or 'all' renders every case found.", + ), + ] = None, + title: Annotated[ + str, typer.Option("--title", help="Figure title shown at the top.") + ] = "omop-graph Concept Grounding", + parent_id_level: Annotated[ + int | None, + typer.Option( + "--parent-id-level", + "-L", + help="Same --parent-id-level passed to 'trace' -- resolves the matching " + "parent_id_level_/ subdirectory.", + ), + ] = None, ): """Generate resolver-pipeline flowchart SVG(s) from trace JSON files written by the trace command. Writes them to the plots/pipeline/ subdirectory of the same directory as the traces.""" @@ -1649,7 +2097,7 @@ def pipeline_svg( if not json_files: typer.echo(f"No JSON trace files found in {trace_dir_pl}.", err=True) raise typer.Exit(1) - cases: List[Dict] = [] + cases: list[dict] = [] for json_file in json_files: payload = json.loads(json_file.read_text(encoding="utf-8")) cases.extend(payload.get("cases", [payload])) @@ -1674,23 +2122,57 @@ def pipeline_svg( @app.command(name="panel-svg") def panel_svg( - trace_dir: Annotated[str, typer.Option("--trace-dir", "-t", - help="Base output directory passed to 'trace --out-dir'. The model-specific " - "subdirectory is resolved the same way 'trace' does, via --embedding-model " - "or config.toml.")], - embedding_model: Annotated[Optional[str], typer.Option("--embedding-model", "-E", - help="Embedding model name used to resolve the subdirectory, overriding " - "config.toml (same semantics as 'trace --embedding-model').")] = None, - case_id: Annotated[Optional[str], typer.Option("--case-id", "-i", - help="Case ID to render. Omitted, empty, or 'all' renders every case found.")] = None, - concept_id: Annotated[Optional[int], typer.Option("--concept-id", - help="Override the concept whose outgoing edges Panel A classifies. Defaults to the " - "case's winning (target) concept. Only meaningful when rendering a single case.")] = None, - title: Annotated[str, typer.Option("--title", - help="Figure title (kept for CLI parity with pipeline-svg; not currently rendered).")] = "omop-graph Graph Traversal", - parent_id_level: Annotated[Optional[int], typer.Option("--parent-id-level", "-L", - help="Same --parent-id-level passed to 'trace' -- resolves the matching " - "parent_id_level_/ subdirectory.")] = None, + trace_dir: Annotated[ + str, + typer.Option( + "--trace-dir", + "-t", + help="Base output directory passed to 'trace --out-dir'. The model-specific " + "subdirectory is resolved the same way 'trace' does, via --embedding-model " + "or config.toml.", + ), + ], + embedding_model: Annotated[ + str | None, + typer.Option( + "--embedding-model", + "-E", + help="Embedding model name used to resolve the subdirectory, overriding " + "config.toml (same semantics as 'trace --embedding-model').", + ), + ] = None, + case_id: Annotated[ + str | None, + typer.Option( + "--case-id", + "-i", + help="Case ID to render. Omitted, empty, or 'all' renders every case found.", + ), + ] = None, + concept_id: Annotated[ + int | None, + typer.Option( + "--concept-id", + help="Override the concept whose outgoing edges Panel A classifies. Defaults to the " + "case's winning (target) concept. Only meaningful when rendering a single case.", + ), + ] = None, + title: Annotated[ + str, + typer.Option( + "--title", + help="Figure title (kept for CLI parity with pipeline-svg; not currently rendered).", + ), + ] = "omop-graph Graph Traversal", + parent_id_level: Annotated[ + int | None, + typer.Option( + "--parent-id-level", + "-L", + help="Same --parent-id-level passed to 'trace' -- resolves the matching " + "parent_id_level_/ subdirectory.", + ), + ] = None, ): """Generate the Panel A/B dashboard SVG(s): Panel A shows a concept's outgoing edges grouped by relationship class (Identity = walkable for grounding vs. everything else = excluded by @@ -1706,7 +2188,7 @@ def panel_svg( if not json_files: typer.echo(f"No JSON trace files found in {trace_dir_pl}.", err=True) raise typer.Exit(1) - cases: List[Dict] = [] + cases: list[dict] = [] for json_file in json_files: payload = json.loads(json_file.read_text(encoding="utf-8")) cases.extend(payload.get("cases", [payload])) @@ -1720,19 +2202,28 @@ def panel_svg( selected = cases kg = KnowledgeGraph(cdm_session_factory()) - rel_class_cache: Dict[int, Dict] = {} + rel_class_cache: dict[int, dict] = {} out_dir = trace_dir_pl / "plots" / "panel" out_dir.mkdir(parents=True, exist_ok=True) for trace_data in selected: cid = trace_data.get("case_id") or "ad-hoc" - target_result = next((r for r in trace_data.get("top_n_results", []) if r.get("is_target")), None) - panel_a_concept_id = concept_id or (target_result.get("concept_id") if target_result else None) + target_result = next( + (r for r in trace_data.get("top_n_results", []) if r.get("is_target")), None + ) + panel_a_concept_id = concept_id or ( + target_result.get("concept_id") if target_result else None + ) if panel_a_concept_id is None: - typer.echo(f"Case '{cid}': no winning target concept to classify edges for, skipping.", err=True) + typer.echo( + f"Case '{cid}': no winning target concept to classify edges for, skipping.", + err=True, + ) continue if panel_a_concept_id not in rel_class_cache: - rel_class_cache[panel_a_concept_id] = _relationship_classification(kg, panel_a_concept_id) + rel_class_cache[panel_a_concept_id] = _relationship_classification( + kg, panel_a_concept_id + ) svg_content = _panel_svg(rel_class_cache[panel_a_concept_id], trace_data, title) out_file = out_dir / f"panel_{cid}.svg" out_file.write_text(svg_content, encoding="utf-8") @@ -1741,18 +2232,42 @@ def panel_svg( @app.command(name="graph-svg") def graph_svg( - trace_dir: Annotated[str, typer.Option("--trace-dir", "-t", - help="Base output directory passed to 'trace --out-dir'. The model-specific " - "subdirectory is resolved the same way 'trace' does, via --embedding-model " - "or config.toml.")], - embedding_model: Annotated[Optional[str], typer.Option("--embedding-model", "-E", - help="Embedding model name used to resolve the subdirectory, overriding " - "config.toml (same semantics as 'trace --embedding-model').")] = None, - case_id: Annotated[Optional[str], typer.Option("--case-id", "-i", - help="Case ID to render. Omitted, empty, or 'all' renders every case found.")] = None, - parent_id_level: Annotated[Optional[int], typer.Option("--parent-id-level", "-L", - help="Same --parent-id-level passed to 'trace' -- resolves the matching " - "parent_id_level_/ subdirectory.")] = None, + trace_dir: Annotated[ + str, + typer.Option( + "--trace-dir", + "-t", + help="Base output directory passed to 'trace --out-dir'. The model-specific " + "subdirectory is resolved the same way 'trace' does, via --embedding-model " + "or config.toml.", + ), + ], + embedding_model: Annotated[ + str | None, + typer.Option( + "--embedding-model", + "-E", + help="Embedding model name used to resolve the subdirectory, overriding " + "config.toml (same semantics as 'trace --embedding-model').", + ), + ] = None, + case_id: Annotated[ + str | None, + typer.Option( + "--case-id", + "-i", + help="Case ID to render. Omitted, empty, or 'all' renders every case found.", + ), + ] = None, + parent_id_level: Annotated[ + int | None, + typer.Option( + "--parent-id-level", + "-L", + help="Same --parent-id-level passed to 'trace' -- resolves the matching " + "parent_id_level_/ subdirectory.", + ), + ] = None, ): """Generate the 4-stage funnel diagram SVG(s): candidates (stacked vertically, left, each striped by which resolver(s) found it) -> standard concepts (via a real, live-looked-up @@ -1769,7 +2284,7 @@ def graph_svg( if not json_files: typer.echo(f"No JSON trace files found in {trace_dir_pl}.", err=True) raise typer.Exit(1) - cases: List[Dict] = [] + cases: list[dict] = [] for json_file in json_files: payload = json.loads(json_file.read_text(encoding="utf-8")) cases.extend(payload.get("cases", [payload])) @@ -1790,7 +2305,10 @@ def graph_svg( cid = trace_data.get("case_id") or "ad-hoc" candidates = _select_funnel_candidates(trace_data) if not candidates: - typer.echo(f"Case '{cid}': no resolver hits to build a funnel diagram from, skipping.", err=True) + typer.echo( + f"Case '{cid}': no resolver hits to build a funnel diagram from, skipping.", + err=True, + ) continue nodes = _trim_funnel_nodes(_build_funnel_nodes(kg, trace_data, candidates)) anchor_concepts = _resolve_anchor_concepts(kg, trace_data) diff --git a/scripts/system_information.py b/scripts/system_information.py index a212275..96534b4 100644 --- a/scripts/system_information.py +++ b/scripts/system_information.py @@ -1,22 +1,23 @@ import platform import sys + def get_version(package_name): try: module = __import__(package_name) - return getattr(module, '__version__', 'unknown') + return getattr(module, "__version__", "unknown") except ImportError: - return 'not installed' + return "not installed" -print('--- System Information ---') -print(f'Platform: {platform.platform()}') -print(f'Python: {sys.version}') -print(f'omop-graph: {get_version("omop_graph")}') -print(f'omop-emb: {get_version("omop_emb")}') -print(f'pgvector: {get_version("pgvector")}') -print(f'FAISS CPU: {get_version("faiss-cpu")}') -print(f'FAISS GPU: {get_version("faiss-gpu")}') -print(f'NumPy: {get_version("numpy")}') -print(f'SQLAlchemy: {get_version("sqlalchemy")}') -print('--------------------------') \ No newline at end of file +print("--- System Information ---") +print(f"Platform: {platform.platform()}") +print(f"Python: {sys.version}") +print(f"omop-graph: {get_version('omop_graph')}") +print(f"omop-emb: {get_version('omop_emb')}") +print(f"pgvector: {get_version('pgvector')}") +print(f"FAISS CPU: {get_version('faiss-cpu')}") +print(f"FAISS GPU: {get_version('faiss-gpu')}") +print(f"NumPy: {get_version('numpy')}") +print(f"SQLAlchemy: {get_version('sqlalchemy')}") +print("--------------------------") diff --git a/src/omop_graph/__init__.py b/src/omop_graph/__init__.py index 86da8dd..e6609cc 100644 --- a/src/omop_graph/__init__.py +++ b/src/omop_graph/__init__.py @@ -1,17 +1,17 @@ from .api import ( - KnowledgeGraph, GraphPath, - Subgraph, GraphTrace, + KnowledgeGraph, PathExplanation, PathProfile, + Subgraph, ) __all__ = [ - "KnowledgeGraph", "GraphPath", - "Subgraph", "GraphTrace", + "KnowledgeGraph", "PathExplanation", "PathProfile", + "Subgraph", ] diff --git a/src/omop_graph/api.py b/src/omop_graph/api.py index cb5b066..1b0470c 100644 --- a/src/omop_graph/api.py +++ b/src/omop_graph/api.py @@ -11,26 +11,26 @@ from omop_graph.graph.kg import KnowledgeGraph from omop_graph.graph.paths import GraphPath, PathExplanation, PathProfile -from omop_graph.graph.traverse import Subgraph, GraphTrace +from omop_graph.graph.traverse import GraphTrace, Subgraph + +from .reasoning.grounding import ( + GroundingConstraints, +) from .reasoning.resolvers import ( CandidateResolver, ExactLabelResolver, ResolverPipeline, ) -from .reasoning.grounding import ( - GroundingConstraints, -) - __all__ = [ - "KnowledgeGraph", - "GraphPath", - "Subgraph", - "GraphTrace", "CandidateResolver", "ExactLabelResolver", + "GraphPath", + "GraphTrace", "GroundingConstraints", - "ResolverPipeline", + "KnowledgeGraph", "PathExplanation", "PathProfile", + "ResolverPipeline", + "Subgraph", ] diff --git a/src/omop_graph/cli.py b/src/omop_graph/cli.py index dff286e..bec051a 100644 --- a/src/omop_graph/cli.py +++ b/src/omop_graph/cli.py @@ -2,27 +2,25 @@ import tempfile from importlib import resources from pathlib import Path -from typing import Annotated, Optional, cast +from typing import Annotated import pandas as pd import sqlalchemy as sa -import typer import sqlalchemy.orm as so - +import typer from oa_configurator import ResolvedCDMDatabase, open_connection - +from omop_alchemy.cdm.model.vocabulary.relationship import Relationship from orm_loader.backends import STAGING_SCHEMA, resolve_backend from orm_loader.helpers.metadata import Base -from omop_alchemy.cdm.model.vocabulary.relationship import Relationship +from omop_graph.cli_utils import populate_test_data from omop_graph.config import OmopGraphConfig -from omop_graph.db.session import resolve_cdm_database, open_cdm_sessions +from omop_graph.db.session import open_cdm_sessions, resolve_cdm_database from omop_graph.extensions.omop_alchemy import ( RelationshipClass, RelationshipMapping, create_extension_tables, ) -from omop_graph.cli_utils import populate_test_data app = typer.Typer() logger = logging.getLogger(__name__) @@ -77,9 +75,8 @@ def populate_with_test_data(): f"Refusing to populate {resolved.name!r} with synthetic test data: " "both its primary and vocab connections must be test_only." ) - with open_cdm_sessions(resolved) as cdm_sessions: - with cdm_sessions() as session: - populate_test_data(session) + with open_cdm_sessions(resolved) as cdm_sessions, cdm_sessions() as session: + populate_test_data(session) def packaged_predicate_csv_dir() -> Path: @@ -94,7 +91,7 @@ def packaged_predicate_csv_dir() -> Path: def relationship_classification( - pred_class_dir: Optional[str] = None, + pred_class_dir: str | None = None, *, resolved: ResolvedCDMDatabase | None = None, ) -> None: @@ -116,9 +113,7 @@ def relationship_classification( with open_cdm_sessions(resolved) as cdm_sessions: with cdm_sessions() as session: engine = session.get_bind(RelationshipMapping).engine - relationship_ids = set( - session.scalars(sa.select(Relationship.relationship_id)).all() - ) + relationship_ids = set(session.scalars(sa.select(Relationship.relationship_id)).all()) _load_relationship_classification( pred_class_dir, resolved=resolved, @@ -128,29 +123,23 @@ def relationship_classification( def _load_relationship_classification( - pred_class_dir: Optional[str], + pred_class_dir: str | None, *, resolved: ResolvedCDMDatabase, engine: sa.Engine, relationship_ids: set[str], ) -> None: """Body of :func:`relationship_classification`, on *engine*, which hosts the extension tables.""" - pred_class_dir_pl = ( - Path(pred_class_dir) if pred_class_dir else packaged_predicate_csv_dir() - ) + pred_class_dir_pl = Path(pred_class_dir) if pred_class_dir else packaged_predicate_csv_dir() if not pred_class_dir_pl.is_dir(): raise NotADirectoryError(f"{pred_class_dir_pl} is not a valid directory.") pred_mapping_file = pred_class_dir_pl / "predicate_mapping.csv" if not pred_mapping_file.is_file(): - raise FileNotFoundError( - f"`predicate_mapping.csv` not found in {pred_class_dir_pl}" - ) + raise FileNotFoundError(f"`predicate_mapping.csv` not found in {pred_class_dir_pl}") pred_class_file = pred_class_dir_pl / "predicate_classification.csv" if not pred_class_file.is_file(): - raise FileNotFoundError( - f"`predicate_classification.csv` not found in {pred_class_dir_pl}" - ) + raise FileNotFoundError(f"`predicate_classification.csv` not found in {pred_class_dir_pl}") df_class = pd.read_csv(pred_class_file) df_mapping = pd.read_csv(pred_mapping_file) @@ -163,11 +152,11 @@ def _load_relationship_classification( check = df_rel_cls.groupby(["predicate_kind", "predicate_subkind"])[ ["description", "semantics", "inference"] ].nunique(dropna=True) - violations = cast(pd.DataFrame, check[(check > 1).any(axis=1)]) + violations = check[(check > 1).any(axis=1)] if not violations.empty: - conflicting_data = cast(pd.DataFrame, df_rel_cls[ + conflicting_data = df_rel_cls[ df_rel_cls["predicate_subkind"].isin(violations.index.tolist()) - ]).sort_values("predicate_subkind") + ].sort_values("predicate_subkind") logger.error( f"Validation Failed! {len(violations)} predicate_subkinds have conflicting definitions: {conflicting_data}" ) @@ -186,13 +175,9 @@ def _load_relationship_classification( ) df_rel_mapping = df_rel_mapping[ ["relationship_id", "predicate_kind", "predicate_subkind"] - ].dropna(subset=["predicate_kind", "predicate_subkind"], how="all") # type: ignore[call-overload] - df_rel_mapping = _filter_unknown_relationship_ids( - df_rel_mapping, relationship_ids - ) - invalid_mask = ( - df_rel_mapping[["predicate_kind", "predicate_subkind"]].isna().any(axis=1) - ) + ].dropna(subset=["predicate_kind", "predicate_subkind"], how="all") + df_rel_mapping = _filter_unknown_relationship_ids(df_rel_mapping, relationship_ids) + invalid_mask = df_rel_mapping[["predicate_kind", "predicate_subkind"]].isna().any(axis=1) dropped_ids = df_rel_mapping.loc[invalid_mask, "relationship_id"].unique().tolist() if dropped_ids: @@ -232,7 +217,7 @@ def _load_relationship_classification( RelationshipClass.__table__, ] with open_connection(engine) as connection: - Base.metadata.drop_all(bind=connection, tables=tables_to_drop, checkfirst=True) # type: ignore + Base.metadata.drop_all(bind=connection, tables=tables_to_drop, checkfirst=True) # ty: ignore[invalid-argument-type] create_extension_tables(connection, resolved=resolved) with tempfile.TemporaryDirectory() as tmp_dir, so.Session(engine) as session: @@ -265,7 +250,7 @@ def _load_relationship_classification( @app.command(name="relationship-classification") def relationship_classification_cmd( pred_class_dir: Annotated[ - Optional[str], + str | None, typer.Option( help=( "Path to the directory containing `predicate_classification.csv` " diff --git a/src/omop_graph/cli_utils/cli_add_test_data.py b/src/omop_graph/cli_utils/cli_add_test_data.py index 9af3e74..4eb8728 100644 --- a/src/omop_graph/cli_utils/cli_add_test_data.py +++ b/src/omop_graph/cli_utils/cli_add_test_data.py @@ -1,27 +1,26 @@ -from random import randint, choice -import numpy as np from datetime import date, timedelta -import pandas as pd +from random import choice, randint +import numpy as np +import pandas as pd import sqlalchemy as sa -from sqlalchemy.orm import Session - -from omop_alchemy.cdm.model.structural.episode import Episode -from omop_alchemy.cdm.model.structural.episode_event import Episode_Event -from omop_alchemy.cdm.model.clinical import Observation_Period -from omop_alchemy.cdm.model.health_system import ( - Location, - Care_Site, - Provider, - Visit_Occurrence, -) from omop_alchemy.cdm.model.clinical import ( - Person, Condition_Occurrence, Death, Measurement, + Observation_Period, + Person, ) +from omop_alchemy.cdm.model.health_system import ( + Care_Site, + Location, + Provider, + Visit_Occurrence, +) +from omop_alchemy.cdm.model.structural.episode import Episode +from omop_alchemy.cdm.model.structural.episode_event import Episode_Event from omop_alchemy.cdm.model.vocabulary import Concept, Concept_Ancestor +from sqlalchemy.orm import Session def populate_reference_data( @@ -221,9 +220,7 @@ def populate_conditions_and_modifiers( episode_concept_id=32533, # Episode of care episode_object_concept_id=condition.condition_concept_id, episode_start_date=condition.condition_start_date, - episode_end_date=( - death.death_date if death else obs.observation_period_end_date - ), + episode_end_date=(death.death_date if death else obs.observation_period_end_date), episode_type_concept_id=choice(avail_types), # EHR / registry / derived ) episodes.append(episode) @@ -288,34 +285,22 @@ def populate_test_data(session): "Type Concept", ] ), - sa.and_( - Concept.domain_id == "Condition", Concept.vocabulary_id == "ICDO3" - ), + sa.and_(Concept.domain_id == "Condition", Concept.vocabulary_id == "ICDO3"), ) ) ) - avail_gender = list( - concept_by_domain[concept_by_domain.domain_id == "Gender"].concept_id - ) - avail_ethnicity = list( - concept_by_domain[concept_by_domain.domain_id == "Ethnicity"].concept_id - ) - avail_race = list( - concept_by_domain[concept_by_domain.domain_id == "Race"].concept_id - ) + avail_gender = list(concept_by_domain[concept_by_domain.domain_id == "Gender"].concept_id) + avail_ethnicity = list(concept_by_domain[concept_by_domain.domain_id == "Ethnicity"].concept_id) + avail_race = list(concept_by_domain[concept_by_domain.domain_id == "Race"].concept_id) avail_place_of_service = list( concept_by_domain[concept_by_domain.domain_id == "Visit"].concept_id ) avail_country = list( concept_by_domain[concept_by_domain.concept_class_id == "Location"].concept_id ) - avail_provider = list( - concept_by_domain[concept_by_domain.domain_id == "Provider"].concept_id - ) - avail_types = list( - concept_by_domain[concept_by_domain.domain_id == "Type Concept"].concept_id - ) + avail_provider = list(concept_by_domain[concept_by_domain.domain_id == "Provider"].concept_id) + avail_types = list(concept_by_domain[concept_by_domain.domain_id == "Type Concept"].concept_id) cancers = list( concept_by_domain[ (concept_by_domain.domain_id == "Condition") @@ -337,9 +322,7 @@ def populate_test_data(session): staging_sets = {} for axis in ["T", "N", "M", "Stage"]: - parents = list( - staging_parents[staging_parents.concept_name.str.contains(axis)].concept_id - ) + parents = list(staging_parents[staging_parents.concept_name.str.contains(axis)].concept_id) s = pd.DataFrame( session.query(*Concept.__table__.columns) .join( diff --git a/src/omop_graph/config.py b/src/omop_graph/config.py index e45eb39..cab7db8 100644 --- a/src/omop_graph/config.py +++ b/src/omop_graph/config.py @@ -4,8 +4,14 @@ from typing import Annotated, ClassVar, Final +from oa_configurator import ( + CDMDatabaseConfig, + ModelConfig, + PackageConfigBase, + RefTo, + VectorStoreConfig, +) from pydantic import Field -from oa_configurator import CDMDatabaseConfig, ModelConfig, PackageConfigBase, RefTo, VectorStoreConfig TOOL_NAME: Final[str] = "omop_graph" @@ -37,24 +43,18 @@ class OmopGraphConfig(PackageConfigBase): ) cdm_db: Annotated[str, RefTo(CDMDatabaseConfig)] = "cdm_db" - test_cdm_db_pg: Annotated[ - str | None, RefTo(CDMDatabaseConfig, is_test=True) - ] = Field( + test_cdm_db_pg: Annotated[str | None, RefTo(CDMDatabaseConfig, is_test=True)] = Field( default=None, description="Real PostgreSQL test CDM database, for Postgres-only integration testing.", ) - test_cdm_db_sqlite: Annotated[ - str | None, RefTo(CDMDatabaseConfig, is_test=True) - ] = Field( + test_cdm_db_sqlite: Annotated[str | None, RefTo(CDMDatabaseConfig, is_test=True)] = Field( default=None, description=( "Disposable SQLite test database; left unconfigured by design " "(isolated_test_database(..., dialect='sqlite') provisions one automatically)." ), ) - test_cdm_db_duckdb: Annotated[ - str | None, RefTo(CDMDatabaseConfig, is_test=True) - ] = Field( + test_cdm_db_duckdb: Annotated[str | None, RefTo(CDMDatabaseConfig, is_test=True)] = Field( default=None, description=( "Disposable DuckDB test CDM database; left unconfigured by design " diff --git a/src/omop_graph/db/session.py b/src/omop_graph/db/session.py index 826f088..9135c03 100644 --- a/src/omop_graph/db/session.py +++ b/src/omop_graph/db/session.py @@ -2,20 +2,20 @@ from __future__ import annotations -from typing import Optional from collections.abc import Generator from contextlib import contextmanager + import sqlalchemy.orm as so -from sqlalchemy.engine import Engine from oa_configurator import ResolvedCDMDatabase, Resolver from omop_alchemy.config import create_cdm_engines from omop_alchemy.cross_database import CDMSession, cdm_sessionmaker +from sqlalchemy.engine import Engine from sqlalchemy.orm import sessionmaker from omop_graph.config import OmopGraphConfig -def resolve_cdm_database(name: Optional[str] = None) -> ResolvedCDMDatabase: +def resolve_cdm_database(name: str | None = None) -> ResolvedCDMDatabase: """Resolve a CDM database from the active oa-configurator config. Parameters @@ -44,7 +44,7 @@ def resolve_cdm_database(name: Optional[str] = None) -> ResolvedCDMDatabase: def cdm_session_factory( - name: Optional[str] = None, *, resolved: ResolvedCDMDatabase | None = None + name: str | None = None, *, resolved: ResolvedCDMDatabase | None = None ) -> sessionmaker[CDMSession]: """Read-only routed session factory on a CDM database from the active config. @@ -65,9 +65,7 @@ def _create_cdm_sessions( resolved: ResolvedCDMDatabase, *, register_claims: bool ) -> tuple[sessionmaker[CDMSession], Engine, Engine]: """Build routed sessions and their owned engine pair through omop-alchemy.""" - primary, vocab = create_cdm_engines( - resolved, register_claims=register_claims - ) + primary, vocab = create_cdm_engines(resolved, register_claims=register_claims) return cdm_sessionmaker(resolved, primary=primary, vocab=vocab), primary, vocab @@ -87,9 +85,7 @@ def open_cdm_sessions( ------ sqlalchemy.orm.sessionmaker[CDMSession] """ - cdm_sessions, primary, vocab = _create_cdm_sessions( - resolved, register_claims=register_claims - ) + cdm_sessions, primary, vocab = _create_cdm_sessions(resolved, register_claims=register_claims) try: yield cdm_sessions finally: diff --git a/src/omop_graph/extensions/emb.py b/src/omop_graph/extensions/emb.py index 054ccd2..a8d10e0 100644 --- a/src/omop_graph/extensions/emb.py +++ b/src/omop_graph/extensions/emb.py @@ -1,44 +1,39 @@ from __future__ import annotations -import logging import importlib.util -from typing import TYPE_CHECKING, Optional, Sequence, TypeAlias, Tuple -import numpy as np +import logging +from collections.abc import Sequence +from typing import TYPE_CHECKING +import numpy as np from omop_alchemy.cdm.query import ConceptFilter as CDMConceptFilter HAS_OMOP_EMB = importlib.util.find_spec("omop_emb") is not None if TYPE_CHECKING: # Optional embedding-specific ones - from omop_emb import BackendType, MetricType, IndexType - from omop_emb import EmbeddingWriterInterface, EmbeddingReaderInterface - from omop_emb import EmbeddingRole - from omop_emb.utils.embedding_utils import NearestConceptMatch - from omop_emb.utils.embedding_utils import EmbeddingConceptFilter - - EmbeddingBackendType: TypeAlias = BackendType - EmbeddingMetricType: TypeAlias = MetricType - EmbeddingIndexType: TypeAlias = IndexType - EmbeddingProviderType: TypeAlias = str - EmbeddingRoleType: TypeAlias = EmbeddingRole + from omop_emb import ( + BackendType, + EmbeddingReaderInterface, + EmbeddingWriterInterface, + MetricType, + ) + from omop_emb.utils.embedding_utils import EmbeddingConceptFilter, NearestConceptMatch # Circular imports for static type hints from omop_graph.graph.kg import KnowledgeGraph from omop_graph.graph.paths import StandardConcept -else: - EmbeddingBackendType = str - EmbeddingMetricType = str - EmbeddingIndexType = str - EmbeddingProviderType = str - EmbeddingRoleType = str -SUPPORTED_BACKENDS: Tuple[str, ...] = () -SUPPORTED_METRICS: Tuple[str, ...] = () +SUPPORTED_BACKENDS: tuple[str, ...] = () +SUPPORTED_METRICS: tuple[str, ...] = () if HAS_OMOP_EMB: - from omop_emb import BackendType, EmbeddingRole, MetricType - from omop_emb import EmbeddingReaderInterface, EmbeddingWriterInterface + from omop_emb import ( + BackendType, + EmbeddingReaderInterface, + EmbeddingWriterInterface, + MetricType, + ) # Extract the string values from the StrEnums SUPPORTED_BACKENDS = tuple(v.value for v in BackendType) @@ -59,7 +54,7 @@ def __init__(self, feature: str = "Embedding functionality"): def _get_embedding_interface( kg: KnowledgeGraph, -) -> Optional[EmbeddingReaderInterface | EmbeddingWriterInterface]: +) -> EmbeddingReaderInterface | EmbeddingWriterInterface | None: """ Internal utility to retrieve the embedding interface from the KG, without distinguishing between reader/writer. This is only used for internal logic where we don't need to differentiate between reader and writer capabilities. @@ -74,9 +69,7 @@ def _get_embedding_interface( try: return kg.emb except ValueError: - logger.debug( - "Embedding interface not available: no EmbeddingConfiguration provided." - ) + logger.debug("Embedding interface not available: no EmbeddingConfiguration provided.") return None except MissingExtensionError as exc: logger.warning(f"Embedding interface not available: {exc}") @@ -85,7 +78,7 @@ def _get_embedding_interface( def get_embedding_reader_interface( kg: KnowledgeGraph, -) -> Optional["EmbeddingReaderInterface"]: +) -> EmbeddingReaderInterface | None: """ Utility to safely retrieve the embedding reader interface from the KG. Returns None if the extension is not available or if the interface is not a reader. @@ -96,15 +89,13 @@ def get_embedding_reader_interface( and HAS_OMOP_EMB and not isinstance(interface, EmbeddingReaderInterface) ): - raise TypeError( - f"Expected embedding interface to be a reader, but got {type(interface)}." - ) + raise TypeError(f"Expected embedding interface to be a reader, but got {type(interface)}.") return interface def get_embedding_writer_interface( kg: KnowledgeGraph, -) -> Optional["EmbeddingWriterInterface"]: +) -> EmbeddingWriterInterface | None: """ Utility to retrieve the embedding writer interface from the KG when write-capability is already guaranteed some other way (e.g. by the caller's own contract). Raises if @@ -119,22 +110,20 @@ def get_embedding_writer_interface( instead. """ interface = _get_embedding_interface(kg) - if ( - interface is not None - and HAS_OMOP_EMB - and not isinstance(interface, EmbeddingWriterInterface) - ): + if interface is None: + return None + if not (HAS_OMOP_EMB and isinstance(interface, EmbeddingWriterInterface)): raise TypeError( f"Expected embedding interface to be a writer, but got {type(interface)}. " "Instantiate the KG with an embedding configuration that has write=True " "to get a writer interface." ) - return interface # ty: ignore[invalid-return-type] + return interface def try_get_embedding_writer_interface( kg: KnowledgeGraph, -) -> Optional["EmbeddingWriterInterface"]: +) -> EmbeddingWriterInterface | None: """ Utility to opportunistically retrieve the embedding writer interface from the KG. @@ -171,7 +160,7 @@ def semantic_similarity( kg: KnowledgeGraph, standard_concepts: Sequence[StandardConcept], query_embedding: np.ndarray, -) -> Optional[Tuple[Tuple[NearestConceptMatch, ...], ...]]: +) -> tuple[tuple[NearestConceptMatch, ...], ...] | None: """ Calculates similarity between a query embedding and stored concept embeddings. @@ -197,9 +186,7 @@ def semantic_similarity( embedding_reader = get_embedding_reader_interface(kg) if embedding_reader is None: - logger.info( - "Embedding reader interface not found in KG. Skipping similarity calculation." - ) + logger.info("Embedding reader interface not found in KG. Skipping similarity calculation.") return None from omop_emb.utils.embedding_utils import EmbeddingConceptFilter @@ -267,9 +254,9 @@ def semantic_similarity( def get_neareast_concepts( kg: KnowledgeGraph, query_embedding: np.ndarray, - concept_filter: Optional[EmbeddingConceptFilter], - k: Optional[int] = None, -) -> Optional[Tuple[Tuple[NearestConceptMatch, ...], ...]]: + concept_filter: EmbeddingConceptFilter | None, + k: int | None = None, +) -> tuple[tuple[NearestConceptMatch, ...], ...] | None: """ RAG retrieval for concept similarity scores. The query_embedding is compared against stored embeddings using the metric and model already configured on the KG's embedding @@ -311,8 +298,6 @@ def get_neareast_concepts( k=k, ) if not nearest_concepts: - logger.info( - "No nearest concepts found for the given query embedding and filter." - ) + logger.info("No nearest concepts found for the given query embedding and filter.") return None return nearest_concepts diff --git a/src/omop_graph/extensions/omop_alchemy.py b/src/omop_graph/extensions/omop_alchemy.py index 984a768..7434182 100644 --- a/src/omop_graph/extensions/omop_alchemy.py +++ b/src/omop_graph/extensions/omop_alchemy.py @@ -1,19 +1,19 @@ # Extension to omop-alchemy package +from dataclasses import dataclass +from enum import Enum +from typing import cast + import sqlalchemy as sa import sqlalchemy.orm as so -from orm_loader.helpers import Base, create_tables +from oa_configurator import ResolvedCDMDatabase, Role from omop_alchemy.cdm.base import ( + CDMTableBase, ReferenceTable, cdm_table, - CDMTableBase, merge_table_args, role_fk, ) -from oa_configurator import ResolvedCDMDatabase, Role - -from enum import Enum -from dataclasses import dataclass -from typing import cast +from orm_loader.helpers import Base, create_tables class PredicateKind(Enum): @@ -58,9 +58,7 @@ class RelationshipClass(ReferenceTable, CDMTableBase, Base): predicate_kind: so.Mapped[PredicateKind] = so.mapped_column( _predicate_kind_enum(), primary_key=True ) - predicate_subkind: so.Mapped[str] = so.mapped_column( - sa.String(20), primary_key=True - ) + predicate_subkind: so.Mapped[str] = so.mapped_column(sa.String(20), primary_key=True) description: so.Mapped[str] = so.mapped_column(sa.String(80), nullable=False) semantics: so.Mapped[str] = so.mapped_column(sa.String(40), nullable=False) inference: so.Mapped[str] = so.mapped_column(sa.String(40), nullable=False) @@ -82,9 +80,7 @@ class RelationshipMapping(ReferenceTable, CDMTableBase, Base): predicate_kind: so.Mapped[PredicateKind] = so.mapped_column( _predicate_kind_enum(), primary_key=True ) - predicate_subkind: so.Mapped[str] = so.mapped_column( - sa.String(20), primary_key=True - ) + predicate_subkind: so.Mapped[str] = so.mapped_column(sa.String(20), primary_key=True) # Must match RelationshipClass's schema, or SQLAlchemy silently builds a second, unlinked Table object. __table_args__ = merge_table_args( @@ -150,8 +146,6 @@ def load_relationship_mapping( """Load the entire RelationshipMapping table and return it as a dict keyed by relationship_id.""" results = session.query(RelationshipMapping).all() return { - row.relationship_id: RelationshipMappingElement.from_relationship_mapping_entry( - row - ) + row.relationship_id: RelationshipMappingElement.from_relationship_mapping_entry(row) for row in results } diff --git a/src/omop_graph/graph/__init__.py b/src/omop_graph/graph/__init__.py index ac27018..14522de 100644 --- a/src/omop_graph/graph/__init__.py +++ b/src/omop_graph/graph/__init__.py @@ -1,12 +1,12 @@ -from .traverse import traverse -from .paths import find_shortest_paths, GraphPath, PathStep from .kg import KnowledgeGraph, KnowledgeGraphEmbeddingConfiguration +from .paths import GraphPath, PathStep, find_shortest_paths +from .traverse import traverse __all__ = [ - "traverse", - "find_shortest_paths", "GraphPath", - "PathStep", "KnowledgeGraph", "KnowledgeGraphEmbeddingConfiguration", + "PathStep", + "find_shortest_paths", + "traverse", ] diff --git a/src/omop_graph/graph/base.py b/src/omop_graph/graph/base.py index 9e08d4b..b56aa67 100644 --- a/src/omop_graph/graph/base.py +++ b/src/omop_graph/graph/base.py @@ -1,7 +1,10 @@ from __future__ import annotations + from abc import ABC, abstractmethod +from collections.abc import Iterable from datetime import date -from typing import Iterable, Optional, Literal +from typing import Literal + from sqlalchemy.orm import Session from ..extensions.omop_alchemy import PredicateKind @@ -26,7 +29,7 @@ def predicate_kind(self, relationship_id: str) -> PredicateKind: ... def predicate_name(self, relationship_id: str) -> str: ... @abstractmethod - def reverse_predicate_id(self, relationship_id: str) -> Optional[str]: ... + def reverse_predicate_id(self, relationship_id: str) -> str | None: ... @abstractmethod def iter_edges( @@ -35,9 +38,9 @@ def iter_edges( concept_ids: int | tuple[int, ...], direction: Literal["in", "out"], predicate_ids: frozenset[str] | None, - predicate_kinds: Optional[frozenset[PredicateKind]] = None, + predicate_kinds: frozenset[PredicateKind] | None = None, active_only: bool = True, - on: Optional[date] = None, + on: date | None = None, within_domain: bool = False, ) -> Iterable[EdgeView]: """ diff --git a/src/omop_graph/graph/edges.py b/src/omop_graph/graph/edges.py index 638348f..0105f12 100644 --- a/src/omop_graph/graph/edges.py +++ b/src/omop_graph/graph/edges.py @@ -20,8 +20,7 @@ import logging from dataclasses import dataclass from datetime import date -from typing import TYPE_CHECKING, Optional - +from typing import TYPE_CHECKING from omop_alchemy.cdm.model import normalised_flag @@ -61,9 +60,9 @@ class EdgeView: subject_id: int predicate_id: str object_id: int - valid_start_date: Optional[date] - valid_end_date: Optional[date] - invalid_reason: Optional[str] + valid_start_date: date | None + valid_end_date: date | None + invalid_reason: str | None predicate_kind: PredicateKind predicate_subkind: str @@ -118,7 +117,7 @@ class Predicate: relationship_id: str name: str - reverse_id: Optional[str] + reverse_id: str | None is_hierarchical: bool anc_up: bool anc_down: bool @@ -147,11 +146,11 @@ def __repr__(self) -> str: def is_active( - start: Optional[date], - end: Optional[date], - invalid_reason: Optional[str], + start: date | None, + end: date | None, + invalid_reason: str | None, *, - on: Optional[date] = None, + on: date | None = None, ) -> bool: """ Check if a relationship is active on a given date. diff --git a/src/omop_graph/graph/kg.py b/src/omop_graph/graph/kg.py index 0072510..f507dbd 100644 --- a/src/omop_graph/graph/kg.py +++ b/src/omop_graph/graph/kg.py @@ -17,32 +17,31 @@ import functools import logging import re -from datetime import date from collections import defaultdict -from typing import Callable, Dict, Optional, Tuple, Literal, Generator, TYPE_CHECKING +from collections.abc import Callable, Generator from dataclasses import dataclass +from datetime import date +from typing import TYPE_CHECKING, Literal -from sqlalchemy import Row -from sqlalchemy.orm import Session -from sqlalchemy.engine import Engine +from oa_configurator import ResolvedModel from omop_alchemy.backends import BackendNotSupportedError, FullTextError from omop_alchemy.cdm.model.vocabulary import Concept from omop_alchemy.cdm.query import ConceptFilter -from oa_configurator import ResolvedModel +from sqlalchemy import Row +from sqlalchemy.engine import Engine +from sqlalchemy.orm import Session if TYPE_CHECKING: from omop_emb import ( EmbeddingBackend, + EmbeddingReaderInterface, EmbeddingStoreReader, EmbeddingWriterInterface, - EmbeddingReaderInterface, + MetricType, ) # Local Application Imports -from ..extensions.emb import ( - MissingExtensionError, - EmbeddingMetricType, -) +from ..extensions.emb import MissingExtensionError from ..extensions.omop_alchemy import ( PredicateKind, RelationshipMappingElement, @@ -58,6 +57,7 @@ ) from .queries import ( q_all_predicates_with_ancestry, + q_children, q_concept_domain_ids, q_concept_id_by_code, q_concept_name_fulltext, @@ -71,15 +71,14 @@ q_concept_views, q_concept_vocabulary_ids, q_edges, + q_entities, q_leaves, q_parents, - q_children, q_predicate_name, q_predicate_row_with_ancestry, + q_relationships, q_roots, q_singletons, - q_entities, - q_relationships, ) logger = logging.getLogger(__name__) @@ -92,13 +91,13 @@ class KnowledgeGraphEmbeddingConfiguration: A complete configuration: whenever embedding support is used at all (read or write), a real backend and a real resolved model are both - required. The caller resolves the vector store and the model, + required. The caller resolves the vector store and the model, builds the backend, and passes both in here. Used to enhance the knowledge graph with embedding-based grounding and similarity scoring. Parameters ---------- - metric_type : EmbeddingMetricType + metric_type : MetricType The similarity/distance metric to use for embedding comparisons (e.g., cosine, euclidean). This is required to ensure that the correct type of index is used in the backend and that similarity computations are consistent. @@ -126,12 +125,12 @@ class KnowledgeGraphEmbeddingConfiguration: straight through to ``EmbeddingReaderInterface``. """ - metric_type: EmbeddingMetricType - backend: "EmbeddingBackend | EmbeddingStoreReader" + metric_type: MetricType + backend: EmbeddingBackend | EmbeddingStoreReader resolved_model: ResolvedModel write: bool = False compute_missing_embeddings: bool = False - faiss_cache_dir: Optional[str] = None + faiss_cache_dir: str | None = None def __post_init__(self) -> None: if self.compute_missing_embeddings and not self.write: @@ -187,7 +186,7 @@ def __init__( self, cdm_sessions: Callable[[], Session], *, - emb_config: Optional[KnowledgeGraphEmbeddingConfiguration] = None, + emb_config: KnowledgeGraphEmbeddingConfiguration | None = None, ): if isinstance(cdm_sessions, Engine) or not callable(cdm_sessions): raise TypeError( @@ -218,7 +217,7 @@ def __init__( self._emb = None @property - def emb(self) -> "EmbeddingWriterInterface | EmbeddingReaderInterface": + def emb(self) -> EmbeddingWriterInterface | EmbeddingReaderInterface: """Namespace for all embedding operations. Returns an ``EmbeddingWriterInterface`` when ``self._emb_config.write`` is True, @@ -234,8 +233,8 @@ def emb(self) -> "EmbeddingWriterInterface | EmbeddingReaderInterface": try: from omop_emb.interface import ( - EmbeddingWriterInterface, EmbeddingReaderInterface, + EmbeddingWriterInterface, ) if self._emb_config is None: @@ -283,23 +282,21 @@ def emb(self) -> "EmbeddingWriterInterface | EmbeddingReaderInterface": ) raise MissingExtensionError() from e raise - except ImportError as e: + except ImportError: logger.info( "Embedding functionality failed to initialize due to an import error in the optional embedding stack." ) - raise e + raise @property - def embedding_configuration(self) -> Optional[KnowledgeGraphEmbeddingConfiguration]: + def embedding_configuration(self) -> KnowledgeGraphEmbeddingConfiguration | None: """Returns the current embedding configuration, if set.""" return self._emb_config @property def compute_missing_embeddings(self) -> bool: """Indicates whether on-the-fly computation of missing concept embeddings is enabled.""" - return ( - self._emb_config.compute_missing_embeddings if self._emb_config else False - ) + return self._emb_config.compute_missing_embeddings if self._emb_config else False def concept_view(self, concept_id: int) -> ConceptView: """ @@ -360,9 +357,7 @@ def concept_id_by_code(self, vocabulary_id: str, concept_code: str) -> int: """ with self.session_factory() as session: concept_id = int( - session.execute( - q_concept_id_by_code(vocabulary_id, concept_code) - ).scalar_one() + session.execute(q_concept_id_by_code(vocabulary_id, concept_code)).scalar_one() ) return concept_id @@ -371,7 +366,7 @@ def concept_lookup( query_term: str, match_kind: LabelMatchKind, synonym: bool = False, - search_constraint: Optional[ConceptFilter] = None, + search_constraint: ConceptFilter | None = None, sort: bool = True, ) -> tuple[LabelMatch, ...]: """ @@ -429,7 +424,7 @@ def concept_lookup( ) return matches - def concept_ids_by_label(self, label: str) -> Tuple[int, ...]: + def concept_ids_by_label(self, label: str) -> tuple[int, ...]: """ Find concept IDs that match the label exactly (case-insensitive). """ @@ -462,9 +457,7 @@ def predicate_name(self, relationship_id: str) -> str: """ # TODO: Not really necessary. The "ID" is mostly human-readable anyways. with self.session_factory() as session: - predicate_name = session.execute( - q_predicate_name(relationship_id) - ).scalar_one() + predicate_name = session.execute(q_predicate_name(relationship_id)).scalar_one() return predicate_name def predicate_kind(self, relationship_id: str) -> PredicateKind: @@ -480,9 +473,7 @@ def _mapping_for(self, relationship_id: str) -> RelationshipMappingElement: raise AttributeError(f"`{relationship_id}` not in relationship mapping.") return item - def predicate_kinds( - self, relationship_ids: tuple[str, ...] - ) -> Tuple[PredicateKind, ...]: + def predicate_kinds(self, relationship_ids: tuple[str, ...]) -> tuple[PredicateKind, ...]: """ Classify a batch of predicates. """ @@ -494,7 +485,7 @@ def relationships( predicates: tuple[str, ...] | None, objects: tuple[int, ...] | None, invert: bool = False, - ) -> Generator[Tuple[int, str, int], None, None]: + ) -> Generator[tuple[int, str, int], None, None]: """ Query relationships between concepts. @@ -534,7 +525,7 @@ def relationships( ): yield s, p, o - def reverse_predicate_id(self, relationship_id: str) -> Optional[str]: + def reverse_predicate_id(self, relationship_id: str) -> str | None: """ Get the reverse relationship ID, if it exists. """ @@ -548,12 +539,12 @@ def _normalise_query_term(self, query_term: str) -> str: def edges( self, - concept_ids: Tuple[int, ...] | int, + concept_ids: tuple[int, ...] | int, direction: Literal["in", "out"], - predicate_ids: Optional[frozenset[str]] = None, - predicate_kinds: Optional[frozenset[PredicateKind]] = None, + predicate_ids: frozenset[str] | None = None, + predicate_kinds: frozenset[PredicateKind] | None = None, active_only: bool = True, - on: Optional[date] = None, + on: date | None = None, within_domain: bool = False, ) -> tuple[EdgeView, ...]: """ @@ -600,10 +591,10 @@ def iter_edges( session: Session, concept_ids: int | tuple[int, ...], direction: Literal["in", "out"], - predicate_ids: Optional[frozenset[str]] = None, - predicate_kinds: Optional[frozenset[PredicateKind]] = None, + predicate_ids: frozenset[str] | None = None, + predicate_kinds: frozenset[PredicateKind] | None = None, active_only: bool = True, - on: Optional[date] = None, + on: date | None = None, within_domain: bool = False, ) -> Generator[EdgeView, None, None]: """Yield edges of *concept_ids* on *session*, classified from the relationship mapping. @@ -691,9 +682,7 @@ def roots( """ with self.session_factory() as session: roots = tuple( - session.execute( - q_roots(domain_id=domain_id, vocabulary_id=vocabulary_id) - ).scalars() + session.execute(q_roots(domain_id=domain_id, vocabulary_id=vocabulary_id)).scalars() ) return roots @@ -756,17 +745,13 @@ def _valid_vocabularies(self) -> frozenset[str]: rows = session.execute(q_concept_vocabulary_ids()).all() return frozenset(row.vocabulary_id for row in rows) - def get_potential_ancestor( - self, child_id: int, parent_id: int - ) -> Optional[AncestorMatch]: + def get_potential_ancestor(self, child_id: int, parent_id: int) -> AncestorMatch | None: """ Check if an ancestry relationship exists between a child and parent. """ with self.session_factory() as session: - row = session.execute( - q_concept_potential_ancestor(child_id, parent_id) - ).first() + row = session.execute(q_concept_potential_ancestor(child_id, parent_id)).first() if row is None: return None @@ -778,9 +763,9 @@ def get_potential_ancestor( def get_potential_ancestors_batch( self, - child_ids: Tuple[int, ...], - parent_ids: Tuple[int, ...], - ) -> Dict[int, Dict[int, AncestorMatch]]: + child_ids: tuple[int, ...], + parent_ids: tuple[int, ...], + ) -> dict[int, dict[int, AncestorMatch]]: """Check which candidate parents are ancestors of one or more children. Parameters @@ -799,11 +784,9 @@ def get_potential_ancestors_batch( return {} with self.session_factory() as session: - rows = session.execute( - q_concept_potential_ancestors_batch(child_ids, parent_ids) - ).all() + rows = session.execute(q_concept_potential_ancestors_batch(child_ids, parent_ids)).all() - result: Dict[int, Dict[int, AncestorMatch]] = defaultdict(dict) + result: dict[int, dict[int, AncestorMatch]] = defaultdict(dict) for row in rows: result[row.descendant_concept_id][row.ancestor_concept_id] = AncestorMatch( ancestor_concept_id=row.ancestor_concept_id, @@ -812,7 +795,7 @@ def get_potential_ancestors_batch( ) return result - def get_num_ancestors(self, concept_ids: tuple[int, ...]) -> Dict[int, int]: + def get_num_ancestors(self, concept_ids: tuple[int, ...]) -> dict[int, int]: """ Get the count of ancestors for a batch of concepts. """ @@ -830,9 +813,7 @@ def check_search_constraints(self, constraints: ConceptFilter) -> None: ) if constraints.vocabularies is not None: - invalid = [ - v for v in constraints.vocabularies if v not in self._valid_vocabularies - ] + invalid = [v for v in constraints.vocabularies if v not in self._valid_vocabularies] if invalid: raise ValueError( f"Invalid vocabulary constraint(s): {invalid}. " diff --git a/src/omop_graph/graph/nodes.py b/src/omop_graph/graph/nodes.py index 49ac585..846e08e 100644 --- a/src/omop_graph/graph/nodes.py +++ b/src/omop_graph/graph/nodes.py @@ -14,16 +14,15 @@ import logging from collections import defaultdict +from collections.abc import Iterable from dataclasses import dataclass from datetime import date from enum import Enum from html import escape from itertools import chain -from typing import Dict, Iterable, List, Optional, Tuple - -from sqlalchemy import Row from omop_alchemy.cdm.model import normalised_flag +from sqlalchemy import Row logger = logging.getLogger(__name__) @@ -75,7 +74,7 @@ class ConceptView: standard_concept: bool valid_start_date: date valid_end_date: date - invalid_reason: Optional[str] + invalid_reason: str | None # Defaulted so rows projected before the classification label was added # still construct; the query layer always supplies it. classification_concept: bool = False @@ -186,7 +185,7 @@ class LabelMatchKind(Enum): PARTIAL = 2 EMBEDDING = 3 - def __lt__(self, other: "LabelMatchKind") -> bool: + def __lt__(self, other: LabelMatchKind) -> bool: return self.value < other.value @@ -275,7 +274,7 @@ class LabelMatchGroupView: A dictionary mapping Concept ID to a tuple of sorted LabelMatches. """ - groups: Dict[int, Tuple[LabelMatch, ...]] + groups: dict[int, tuple[LabelMatch, ...]] @classmethod def from_matches(cls, matches: Iterable[LabelMatch]) -> LabelMatchGroupView: @@ -298,13 +297,12 @@ def from_matches(cls, matches: Iterable[LabelMatch]) -> LabelMatchGroupView: LabelMatchGroupView The grouped view. """ - grouped: Dict[int, List[LabelMatch]] = defaultdict(list) + grouped: dict[int, list[LabelMatch]] = defaultdict(list) for m in matches: grouped[m.matched_concept_id].append(m) grouped_tuple = { - cid: tuple(sorted(ms, key=lambda m: m.match_kind.value)) - for cid, ms in grouped.items() + cid: tuple(sorted(ms, key=lambda m: m.match_kind.value)) for cid, ms in grouped.items() } return cls(groups=grouped_tuple) diff --git a/src/omop_graph/graph/paths.py b/src/omop_graph/graph/paths.py index 1fda7d2..5981600 100644 --- a/src/omop_graph/graph/paths.py +++ b/src/omop_graph/graph/paths.py @@ -20,19 +20,13 @@ from typing import ( TYPE_CHECKING, Any, - Dict, - List, - Optional, - Set, - Tuple, - Union, ) # Local Application Imports from omop_graph.extensions.omop_alchemy import PredicateKind from omop_graph.graph.edges import EdgeView -from omop_graph.graph.traverse import GraphTrace, TraceStep from omop_graph.graph.nodes import LabelMatchKind +from omop_graph.graph.traverse import GraphTrace, TraceStep from omop_graph.reasoning.resolvers import CandidateHit if TYPE_CHECKING: @@ -105,7 +99,7 @@ def start_concept_id(self) -> int: raise ValueError("Empty path has no start concept") return self.steps[0].subject.concept_id - def get_first_standard_concept_id(self) -> Optional[int]: + def get_first_standard_concept_id(self) -> int | None: """ Find the ID of the first Standard Concept encountered in the path. @@ -127,9 +121,7 @@ def nodes(self) -> tuple[int, ...]: """ if not self.steps: return () - return (self.steps[0].subject.concept_id,) + tuple( - s.object.concept_id for s in self.steps - ) + return (self.steps[0].subject.concept_id,) + tuple(s.object.concept_id for s in self.steps) def __getitem__(self, index): return self.steps[index] @@ -142,7 +134,7 @@ def __repr__(self) -> str: return "GraphPath()" return f"GraphPath(len={len(self.steps)})" - def explain(self, kg: "KnowledgeGraph") -> str: + def explain(self, kg: KnowledgeGraph) -> str: """ Generate a human-readable string explaining the path. @@ -176,7 +168,7 @@ def reconstruct_paths( meet, parents_fwd, parents_bwd, - concept_standard_map: Dict[int, bool], + concept_standard_map: dict[int, bool], ): """ Reconstruct full paths from bidirectional BFS parent pointers. @@ -214,16 +206,16 @@ def right(n): def find_shortest_paths( - kg: "KnowledgeGraph", + kg: KnowledgeGraph, source: int, target: int, - predicate_kinds: Optional[frozenset[PredicateKind]] = None, + predicate_kinds: frozenset[PredicateKind] | None = None, max_depth: int = 6, - on: Optional[Any] = None, + on: Any | None = None, max_paths: int = 20, traced: bool = False, within_domain: bool = True, -) -> Tuple[List[GraphPath], Optional[GraphTrace]]: +) -> tuple[list[GraphPath], GraphTrace | None]: """ Find shortest paths between source and target using bidirectional BFS. @@ -258,9 +250,7 @@ def find_shortest_paths( if source == target: path = GraphPath(steps=()) trace = ( - GraphTrace( - seeds=(source,), steps=[], terminated_reason="source_equals_target" - ) + GraphTrace(seeds=(source,), steps=[], terminated_reason="source_equals_target") if traced else None ) @@ -272,16 +262,16 @@ def find_shortest_paths( depth_fwd = {source: 0} depth_bwd = {target: 0} - parents_fwd: Dict[int, List[Tuple[int, str]]] = defaultdict(list) - parents_bwd: Dict[int, List[Tuple[int, str]]] = defaultdict(list) + parents_fwd: dict[int, list[tuple[int, str]]] = defaultdict(list) + parents_bwd: dict[int, list[tuple[int, str]]] = defaultdict(list) - best_total_depth: Optional[int] = None - meeting_nodes: Set[int] = set() - trace_steps: List[TraceStep] = [] + best_total_depth: int | None = None + meeting_nodes: set[int] = set() + trace_steps: list[TraceStep] = [] while q_fwd and q_bwd: expand_forward = len(q_fwd) <= len(q_bwd) - expanded: List[EdgeView] = [] + expanded: list[EdgeView] = [] if expand_forward: cur = q_fwd.popleft() @@ -359,9 +349,7 @@ def find_shortest_paths( meeting_nodes.add(prev) if traced: - trace_steps.append( - TraceStep(depth=d, node=cur, expanded_edges=tuple(expanded)) - ) + trace_steps.append(TraceStep(depth=d, node=cur, expanded_edges=tuple(expanded))) # Stop if we found a path and current searches exceed optimal depth if best_total_depth is not None: @@ -390,16 +378,14 @@ def find_shortest_paths( # One batched lookup to get is_standard for every discovered concept so that # reconstructed Node objects carry the correct flag (zero extra per-node DB calls). all_discovered = tuple(set(depth_fwd.keys()) | set(depth_bwd.keys())) - concept_standard_map: Dict[int, bool] = { + concept_standard_map: dict[int, bool] = { v.concept_id: v.standard_concept for v in kg.concept_views(all_discovered) } - paths: List[GraphPath] = [] + paths: list[GraphPath] = [] for meet in meeting_nodes: paths.extend( - reconstruct_paths( - source, target, meet, parents_fwd, parents_bwd, concept_standard_map - ) + reconstruct_paths(source, target, meet, parents_fwd, parents_bwd, concept_standard_map) ) if len(paths) >= max_paths: break @@ -418,15 +404,15 @@ def find_shortest_paths( def find_shortest_paths_batch( - kg: "KnowledgeGraph", + kg: KnowledgeGraph, source: int, target: int, - predicate_kinds: Union[Set[PredicateKind], frozenset[PredicateKind], None] = None, + predicate_kinds: set[PredicateKind] | frozenset[PredicateKind] | None = None, max_depth: int = 6, - on: Optional[Any] = None, + on: Any | None = None, max_paths: int = 20, within_domain: bool = True, -) -> List[GraphPath]: +) -> list[GraphPath]: """ Find shortest paths using an optimized batch-BFS approach. @@ -471,11 +457,11 @@ def find_shortest_paths_batch( depth_bwd = {target: 0} # Parents for path reconstruction - parents_fwd: Dict[int, List[Tuple[int, str]]] = defaultdict(list) - parents_bwd: Dict[int, List[Tuple[int, str]]] = defaultdict(list) + parents_fwd: dict[int, list[tuple[int, str]]] = defaultdict(list) + parents_bwd: dict[int, list[tuple[int, str]]] = defaultdict(list) - best_total_depth: Optional[int] = None - meeting_nodes: Set[int] = set() + best_total_depth: int | None = None + meeting_nodes: set[int] = set() # Loop until frontiers are empty while fwd_frontier and bwd_frontier: @@ -544,9 +530,7 @@ def find_shortest_paths_batch( # 4. Stop Condition check if best_total_depth is not None: # Shallowest possible node in the NEXT layer we just built - min_current = min( - (current_depth_map[n] for n in next_frontier), default=999 - ) + min_current = min((current_depth_map[n] for n in next_frontier), default=999) # Shallowest possible node waiting in the OTHER frontier min_other = min((other_depth_map[n] for n in other_frontier), default=999) @@ -565,16 +549,14 @@ def find_shortest_paths_batch( return [] all_discovered = tuple(set(depth_fwd.keys()) | set(depth_bwd.keys())) - concept_standard_map: Dict[int, bool] = { + concept_standard_map: dict[int, bool] = { v.concept_id: v.standard_concept for v in kg.concept_views(all_discovered) } - paths: List[GraphPath] = [] + paths: list[GraphPath] = [] for meet in meeting_nodes: paths.extend( - reconstruct_paths( - source, target, meet, parents_fwd, parents_bwd, concept_standard_map - ) + reconstruct_paths(source, target, meet, parents_fwd, parents_bwd, concept_standard_map) ) if len(paths) >= max_paths: break @@ -620,7 +602,7 @@ class StandardConcept: How far this standard concept is from where it needs to be, with a meaning that depends on whether ancestor targets were given to ``find_standard_paths``: - Targets given (ancestor-constrained grounding): this is the ancestor- - hierarchy distance (via ``concept_ancestor.min_levels_of_separation``) + hierarchy distance (via ``concept_ancestor.min_levels_of_separation``) from this concept to the required parent. - No targets given (unconstrained grounding): this is the hop count from the original found concept to the standard concept. @@ -657,8 +639,8 @@ class StandardConcept: def get_unique_standard_concepts( - concepts: List[StandardConcept], -) -> List[StandardConcept]: + concepts: list[StandardConcept], +) -> list[StandardConcept]: """ Filter a list of StandardConcepts to keep only the best match per Concept ID. @@ -685,14 +667,14 @@ def get_unique_standard_concepts( def find_standard_paths( - kg: "KnowledgeGraph", - targets: Optional[Tuple[int, ...]], + kg: KnowledgeGraph, + targets: tuple[int, ...] | None, candidate: CandidateHit, - predicate_kinds: Optional[frozenset[Any]] = None, + predicate_kinds: frozenset[Any] | None = None, max_depth: int = 6, - max_concepts: Optional[int] = None, + max_concepts: int | None = None, within_domain: bool = True, -) -> List[StandardConcept]: +) -> list[StandardConcept]: """ Search for standard concepts reachable from a candidate, optionally verified against ancestor targets. @@ -714,9 +696,9 @@ def find_standard_paths( 2. When ``targets`` is None or empty, there is no ancestor to verify against: the first standard concept reached on each branch is accepted directly and not expanded - further. This is a looser, unconstrained form of grounding. It "grounds" a - candidate to its standard form without being able to disambiguate against a known - hierarchy branch. See ``StandardConcept.separation`` for how distance is measured in + further. This is a looser, unconstrained form of grounding. It "grounds" a + candidate to its standard form without being able to disambiguate against a known + hierarchy branch. See ``StandardConcept.separation`` for how distance is measured in this mode. Parameters @@ -781,16 +763,14 @@ def find_standard_paths( ] # Track the shallowest iteration we have enqueued per concept to avoid # unbounded duplicate growth in high-degree neighborhoods. - visited_min_iteration: Dict[int, int] = {candidate.concept_id: 0} + visited_min_iteration: dict[int, int] = {candidate.concept_id: 0} - found_standard_concepts: Dict[int, List[StandardConcept]] = ( - {target_id: [] for target_id in targets} - if targets - else {unconstrained_placeholder: []} + found_standard_concepts: dict[int, list[StandardConcept]] = ( + {target_id: [] for target_id in targets} if targets else {unconstrained_placeholder: []} ) while queue: - wave: List[QueueItem] = [] + wave: list[QueueItem] = [] while queue: # Drain the entire heap to process them at once and reduce round-trips to DB for ancestor checks. wave.append(heapq.heappop(queue)) @@ -805,7 +785,7 @@ def find_standard_paths( if standard_items: # Dedup - dedup_standard_items: Dict[int, QueueItem] = {} + dedup_standard_items: dict[int, QueueItem] = {} for item in standard_items: if item.node.concept_id not in dedup_standard_items: dedup_standard_items[item.node.concept_id] = item @@ -900,17 +880,14 @@ def find_standard_paths( continue next_iterations = iterations + 1 - + # Early-stop for unconstrained mode: # Number of hops from candidate to standard concept - # exceeds max_depth to prevent infinite expansion + # exceeds max_depth to prevent infinite expansion if not targets and next_iterations > max_depth: continue prev_best_iteration = visited_min_iteration.get(object_id) - if ( - prev_best_iteration is not None - and prev_best_iteration <= next_iterations - ): + if prev_best_iteration is not None and prev_best_iteration <= next_iterations: continue visited_min_iteration[object_id] = next_iterations @@ -969,11 +946,11 @@ def __repr__(self) -> str: @classmethod def from_path( cls, - kg: "KnowledgeGraph", + kg: KnowledgeGraph, path: GraphPath, match_kind: LabelMatchKind, - source_concept_id: Optional[int] = None, - ) -> "PathProfile": + source_concept_id: int | None = None, + ) -> PathProfile: """ Analyze a path to determine the 'Standard Anchor'. @@ -1013,7 +990,7 @@ def get_view(idx): return view_map[node_ids[idx]] predicate_kinds = kg.predicate_kinds(tuple(p.predicate for p in path.steps)) - standard_anchor: Optional[Tuple[int, str]] = None + standard_anchor: tuple[int, str] | None = None for step_idx in range(len(path.steps)): next_view = get_view(step_idx + 1) @@ -1051,7 +1028,7 @@ class PathExplanationStep: """ step: PathStep - traversal_depth: Optional[int] + traversal_depth: int | None predicate_kind: PredicateKind reason: str @@ -1069,23 +1046,21 @@ class PathExplanation: @classmethod def from_path( cls, - kg: "KnowledgeGraph", + kg: KnowledgeGraph, path: GraphPath, trace: GraphTrace, match_kind: LabelMatchKind, - ) -> "PathExplanation": + ) -> PathExplanation: """ Construct an explanation by combining the path, the trace log, and semantic profiles. """ - steps: List[PathExplanationStep] = [] + steps: list[PathExplanationStep] = [] source = ( path.steps[0].subject.concept_id if path.steps else (trace.seeds[0] if trace.seeds else None) ) - profile = PathProfile.from_path( - kg, path, match_kind=match_kind, source_concept_id=source - ) + profile = PathProfile.from_path(kg, path, match_kind=match_kind, source_concept_id=source) for step in path.steps: ts = trace_contains_step(trace, step) @@ -1106,7 +1081,7 @@ def from_path( ) -def trace_contains_step(trace: GraphTrace, step: PathStep) -> Optional[TraceStep]: +def trace_contains_step(trace: GraphTrace, step: PathStep) -> TraceStep | None: """ Check if a specific path step appears in the search trace. """ @@ -1114,9 +1089,6 @@ def trace_contains_step(trace: GraphTrace, step: PathStep) -> Optional[TraceStep if ts.node != step.subject.concept_id: continue for e in ts.expanded_edges: - if ( - e.object_id == step.object.concept_id - and e.predicate_id == step.predicate - ): + if e.object_id == step.object.concept_id and e.predicate_id == step.predicate: return ts return None diff --git a/src/omop_graph/graph/queries.py b/src/omop_graph/graph/queries.py index f561979..b1dafc1 100644 --- a/src/omop_graph/graph/queries.py +++ b/src/omop_graph/graph/queries.py @@ -14,24 +14,8 @@ from __future__ import annotations -from typing import Optional, Tuple, Literal, Union, cast from datetime import date - -from sqlalchemy import ( - and_, - case, - exists, - func, - literal, - or_, - select, - Connection, - Engine, - Table, -) -from sqlalchemy.orm import aliased -from sqlalchemy.sql import Select - +from typing import Literal, cast from omop_alchemy.backends import ( resolve_backend, @@ -44,6 +28,20 @@ Relationship, ) from omop_alchemy.cdm.query import ConceptFilter +from sqlalchemy import ( + Connection, + Engine, + Table, + and_, + case, + exists, + func, + literal, + or_, + select, +) +from sqlalchemy.orm import aliased +from sqlalchemy.sql import Select from ..extensions.omop_alchemy import RelationshipMapping @@ -135,7 +133,7 @@ def q_concept_view(concept_id: int) -> Select: ).where(Concept.concept_id == concept_id) -def q_concept_views(concept_ids: Tuple[int, ...], sort: bool = True) -> Select: +def q_concept_views(concept_ids: tuple[int, ...], sort: bool = True) -> Select: """ Query for multiple concepts by their IDs. @@ -228,7 +226,7 @@ def q_concept_synonym() -> Select: def q_concept_name_match( query_concept_name: str, - search_constraint: Optional[ConceptFilter] = None, + search_constraint: ConceptFilter | None = None, synonym: bool = False, sort: bool = True, ) -> Select: @@ -249,23 +247,17 @@ def q_concept_name_match( Select The query statement. """ - name_expr = ( - Concept_Synonym.concept_synonym_name if synonym else Concept.concept_name - ) + name_expr = Concept_Synonym.concept_synonym_name if synonym else Concept.concept_name if synonym: base_stmt = q_concept_synonym().where( func.lower(name_expr) == func.lower(query_concept_name) ) else: - base_stmt = q_concept_name().where( - func.lower(name_expr) == func.lower(query_concept_name) - ) + base_stmt = q_concept_name().where(func.lower(name_expr) == func.lower(query_concept_name)) if search_constraint: if not isinstance(search_constraint, ConceptFilter): - raise TypeError( - "search_constraint must be an instance of ConceptFilter" - ) + raise TypeError("search_constraint must be an instance of ConceptFilter") base_stmt = search_constraint.apply(base_stmt) if sort: base_stmt = base_stmt.order_by(*_concept_match_order_terms(name_expr)) @@ -274,7 +266,7 @@ def q_concept_name_match( def q_concept_name_ilike( query_concept_name: str, - search_constraint: Optional[ConceptFilter] = None, + search_constraint: ConceptFilter | None = None, synonym: bool = False, sort: bool = True, ) -> Select: @@ -295,24 +287,18 @@ def q_concept_name_ilike( Select The query statement. """ - name_expr = ( - Concept_Synonym.concept_synonym_name if synonym else Concept.concept_name - ) + name_expr = Concept_Synonym.concept_synonym_name if synonym else Concept.concept_name if "%" in query_concept_name: raise ValueError("query_concept_name should not contain wildcards like '%'.") if synonym: - base_stmt = q_concept_synonym().where( - name_expr.ilike(f"%{query_concept_name}%") - ) + base_stmt = q_concept_synonym().where(name_expr.ilike(f"%{query_concept_name}%")) else: base_stmt = q_concept_name().where(name_expr.ilike(f"%{query_concept_name}%")) if search_constraint: if not isinstance(search_constraint, ConceptFilter): - raise TypeError( - "search_constraint must be an instance of ConceptFilter" - ) + raise TypeError("search_constraint must be an instance of ConceptFilter") base_stmt = search_constraint.apply(base_stmt) if sort: base_stmt = base_stmt.order_by(*_concept_match_order_terms(name_expr)) @@ -323,7 +309,7 @@ def q_concept_name_fulltext( query_concept_name: str, *, engine: Engine | Connection, - search_constraint: Optional[ConceptFilter] = None, + search_constraint: ConceptFilter | None = None, synonym: bool = False, sort: bool = True, ) -> Select: @@ -354,12 +340,12 @@ def q_concept_name_fulltext( FullTextError If the stored tsvector column is missing. """ - name_expr = ( - Concept_Synonym.concept_synonym_name if synonym else Concept.concept_name - ) + name_expr = Concept_Synonym.concept_synonym_name if synonym else Concept.concept_name target_table = Concept_Synonym if synonym else Concept stmt = q_concept_synonym() if synonym else q_concept_name() - vector = resolve_backend(engine).fulltext_vector_column(engine, cast(Table, target_table.__table__)) + vector = resolve_backend(engine).fulltext_vector_column( + engine, cast(Table, target_table.__table__) + ) query = func.plainto_tsquery("english", query_concept_name) stmt = stmt.where(vector.op("@@")(query)) # Hits the GIN index instantly @@ -369,9 +355,7 @@ def q_concept_name_fulltext( if sort: stmt = stmt.order_by( - *_concept_match_order_terms( - name_expr, rank_expr=func.ts_rank(vector, query) - ) + *_concept_match_order_terms(name_expr, rank_expr=func.ts_rank(vector, query)) ) return stmt @@ -438,11 +422,11 @@ def q_all_predicates_with_ancestry() -> Select: def q_edges( - concept_ids: Union[Tuple[int, ...], int], + concept_ids: tuple[int, ...] | int, direction: Literal["in", "out"], - predicate_ids: Optional[frozenset[str]] = None, + predicate_ids: frozenset[str] | None = None, active_only: bool = False, - on: Optional[date] = None, + on: date | None = None, within_domain: bool = False, ) -> Select: """Query edges for a batch of concept IDs, in either direction. @@ -517,9 +501,9 @@ def q_ancestors(concept_id: int) -> Select: def q_relationships( - subjects: Optional[tuple[int, ...]], - predicates: Optional[tuple[str, ...]], - objects: Optional[tuple[int, ...]], + subjects: tuple[int, ...] | None, + predicates: tuple[str, ...] | None, + objects: tuple[int, ...] | None, ) -> Select: stmt = select( @@ -552,9 +536,7 @@ def q_entities( ).apply(select(Concept.concept_id)) -def q_concept_filtered( - vocabulary_id: Optional[str] = None, domain_id: Optional[str] = None -) -> Select: +def q_concept_filtered(vocabulary_id: str | None = None, domain_id: str | None = None) -> Select: """Helper query for selecting standard concepts filtered by vocab/domain.""" return ConceptFilter( domains=(domain_id,) if domain_id else None, @@ -570,9 +552,7 @@ def q_concept_synonym_filtered(concept_id: int) -> Select: return stmt.where(Concept_Synonym.concept_id == concept_id) -def q_singletons( - *, vocabulary_id: Optional[str] = None, domain_id: Optional[str] = None -) -> Select: +def q_singletons(*, vocabulary_id: str | None = None, domain_id: str | None = None) -> Select: """ Query for singleton concepts (no parents, no children). Uses NOT EXISTS subqueries on the ancestor table. @@ -600,9 +580,7 @@ def q_singletons( ) -def q_roots( - *, vocabulary_id: Optional[str] = None, domain_id: Optional[str] = None -) -> Select: +def q_roots(*, vocabulary_id: str | None = None, domain_id: str | None = None) -> Select: """Query for root concepts (no parents).""" stmt = q_concept_filtered(vocabulary_id=vocabulary_id, domain_id=domain_id) return stmt.where( @@ -617,9 +595,7 @@ def q_roots( ) -def q_leaves( - *, vocabulary_id: Optional[str] = None, domain_id: Optional[str] = None -) -> Select: +def q_leaves(*, vocabulary_id: str | None = None, domain_id: str | None = None) -> Select: """Query for leaf concepts (no children).""" stmt = q_concept_filtered(vocabulary_id=vocabulary_id, domain_id=domain_id) return stmt.where( @@ -641,11 +617,7 @@ def q_concept_domain_ids() -> Select: def q_concept_vocabulary_ids() -> Select: """Query distinct vocabulary IDs.""" - return ( - select(Concept.vocabulary_id) - .distinct() - .where(Concept.vocabulary_id.is_not(None)) - ) + return select(Concept.vocabulary_id).distinct().where(Concept.vocabulary_id.is_not(None)) def q_concept_potential_ancestor(child_id: int, parent_id: int) -> Select: @@ -666,7 +638,7 @@ def q_concept_potential_ancestor(child_id: int, parent_id: int) -> Select: def q_concept_potential_ancestors_batch( - child_ids: Tuple[int, ...], parent_ids: Tuple[int, ...] + child_ids: tuple[int, ...], parent_ids: tuple[int, ...] ) -> Select: """ Check which of several candidate parents are ancestors of one or more children. @@ -691,7 +663,7 @@ def q_concept_potential_ancestors_batch( ) -def q_concept_num_ancestors(concept_ids: Tuple[int, ...]) -> Select: +def q_concept_num_ancestors(concept_ids: tuple[int, ...]) -> Select: """ Count the number of ancestors for each concept in the batch. """ @@ -710,7 +682,7 @@ def q_concept_num_ancestors(concept_ids: Tuple[int, ...]) -> Select: ) -def q_concept_num_descendants(concept_ids: Tuple[int, ...]) -> Select: +def q_concept_num_descendants(concept_ids: tuple[int, ...]) -> Select: """ Count the number of descendants for each concept in the batch. """ @@ -719,9 +691,7 @@ def q_concept_num_descendants(concept_ids: Tuple[int, ...]) -> Select: Concept.concept_id, func.count(Concept_Ancestor.descendant_concept_id).label("num_descendants"), ) - .join( - Concept_Ancestor, Concept.concept_id == Concept_Ancestor.ancestor_concept_id - ) + .join(Concept_Ancestor, Concept.concept_id == Concept_Ancestor.ancestor_concept_id) .where(Concept.concept_id.in_(concept_ids)) .where(Concept_Ancestor.min_levels_of_separation > 0) .group_by(Concept.concept_id) diff --git a/src/omop_graph/graph/scoring.py b/src/omop_graph/graph/scoring.py index 338afc9..1f8cdcd 100644 --- a/src/omop_graph/graph/scoring.py +++ b/src/omop_graph/graph/scoring.py @@ -14,7 +14,7 @@ import re from dataclasses import dataclass, field from difflib import SequenceMatcher -from typing import TYPE_CHECKING, List, Optional, Tuple +from typing import TYPE_CHECKING import numpy as np @@ -23,9 +23,10 @@ from omop_graph.graph.paths import StandardConcept if TYPE_CHECKING: - from omop_graph.graph.kg import KnowledgeGraph from omop_emb.utils.embedding_utils import NearestConceptMatch + from omop_graph.graph.kg import KnowledgeGraph + logger = logging.getLogger(__name__) @@ -51,7 +52,7 @@ class StandardConceptWithScore(StandardConcept): """ total_score: float = field(compare=True, default=0.0) - embedding_score: Optional[float] = field(compare=False, default=None) + embedding_score: float | None = field(compare=False, default=None) relevance: float = field(compare=False, default=0.0) parsimony_penalty: float = field(compare=False, default=0.0) broadness_bonus: float = field(compare=False, default=0.0) @@ -67,12 +68,12 @@ def __repr__(self) -> str: def from_standard_concept( cls, standard_concept: StandardConcept, - embedding_score: Optional[float], + embedding_score: float | None, relevance: float, parsimony_penalty: float, broadness_bonus: float, total_score: float, - ) -> "StandardConceptWithScore": + ) -> StandardConceptWithScore: """ Factory method to promote a StandardConcept to a scored version. """ @@ -89,11 +90,9 @@ def from_standard_concept( def score_standard_concepts( text: str, standard_concepts: tuple[StandardConcept, ...], - kg: "KnowledgeGraph", - nearest_concept_matches: Optional[ - Tuple[Tuple[NearestConceptMatch, ...], ...] - ] = None, -) -> List[StandardConceptWithScore]: + kg: KnowledgeGraph, + nearest_concept_matches: tuple[tuple[NearestConceptMatch, ...], ...] | None = None, +) -> list[StandardConceptWithScore]: """ Attach scoring metrics to each standard concept. @@ -118,8 +117,8 @@ def score_standard_concepts( Notes ----- - Standard concept scoring is ONLY performed for concepts that were matched via the embedding resolver, - and only if nearest_concept_matches are provided. Concepts matched via non-embedding resolvers will still + Standard concept scoring is ONLY performed for concepts that were matched via the embedding resolver, + and only if nearest_concept_matches are provided. Concepts matched via non-embedding resolvers will still receive parsimony and broadness scoring, but their relevance will be based solely on textual similarity. Returns @@ -163,11 +162,11 @@ def score_standard_concepts( def _score_standard_concept( - kg: "KnowledgeGraph", + kg: KnowledgeGraph, text: str, standard_concept: StandardConcept, num_ancestors: int, - similarity_score: Optional[float], + similarity_score: float | None, alpha: float = 0.05, beta: float = 0.01, ) -> StandardConceptWithScore: @@ -261,7 +260,7 @@ def _textual_similarity_score( # 1. Tokenize (keep standard normalization) stop_words = {"of", "the", "in", "and", "or", "to", "nos", "a", "an"} - def tokenize(text: str) -> List[str]: + def tokenize(text: str) -> list[str]: tokens = re.findall(r"\w+", text.lower()) return [t for t in tokens if t not in stop_words] diff --git a/src/omop_graph/graph/traverse.py b/src/omop_graph/graph/traverse.py index efa995b..2e64830 100644 --- a/src/omop_graph/graph/traverse.py +++ b/src/omop_graph/graph/traverse.py @@ -12,9 +12,10 @@ from __future__ import annotations from collections import deque +from collections.abc import Iterable from dataclasses import dataclass from datetime import date -from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple +from typing import TYPE_CHECKING # Local Application Imports from omop_graph.extensions.omop_alchemy import PredicateKind @@ -79,10 +80,10 @@ class GraphTrace: """ seeds: tuple[int, ...] - steps: List[TraceStep] - terminated_reason: Optional[str] = None + steps: list[TraceStep] + terminated_reason: str | None = None - def summary(self, kg: "KnowledgeGraph", max_steps: int = 10) -> str: + def summary(self, kg: KnowledgeGraph, max_steps: int = 10) -> str: """ Generate a human-readable summary of the traversal. @@ -112,14 +113,14 @@ def summary(self, kg: "KnowledgeGraph", max_steps: int = 10) -> str: def traverse( - kg: "KnowledgeGraph", + kg: KnowledgeGraph, seeds: Iterable[int], - predicate_kinds: Optional[Set[PredicateKind]], + predicate_kinds: set[PredicateKind] | None, max_depth: int, - on: Optional[date], - max_nodes: Optional[int], + on: date | None, + max_nodes: int | None, trace: bool, -) -> Tuple[Subgraph, Optional[GraphTrace]]: +) -> tuple[Subgraph, GraphTrace | None]: """ Perform a Breadth-First Search (BFS) traversal starting from seed nodes. @@ -150,8 +151,8 @@ def traverse( unique_seeds = tuple(dict.fromkeys(seeds)) visited = set() - edges_out: List[EdgeView] = [] - trace_steps: List[TraceStep] = [] + edges_out: list[EdgeView] = [] + trace_steps: list[TraceStep] = [] # Queue stores (concept_id, depth) q = deque((s, 0) for s in unique_seeds) @@ -172,7 +173,7 @@ def traverse( if depth >= max_depth: continue - expanded: List[EdgeView] = [] + expanded: list[EdgeView] = [] # Iterate over outgoing edges with kg.session_factory() as session: @@ -180,9 +181,7 @@ def traverse( session=session, concept_ids=node, direction="out", - predicate_kinds=frozenset(predicate_kinds) - if predicate_kinds is not None - else None, + predicate_kinds=frozenset(predicate_kinds) if predicate_kinds is not None else None, active_only=True, on=on, ): @@ -196,18 +195,14 @@ def traverse( q.append((nxt, depth + 1)) if trace: - trace_steps.append( - TraceStep(depth=depth, node=node, expanded_edges=tuple(expanded)) - ) + trace_steps.append(TraceStep(depth=depth, node=node, expanded_edges=tuple(expanded))) # Deduplicate edges and drop any that target an unvisited node. # The latter can happen when max_nodes terminates the loop while neighbour # nodes are still queued but never expanded, which would break the invariant # that every edge in the Subgraph is closed over its node set. dedup = { - (e.subject_id, e.predicate_id, e.object_id): e - for e in edges_out - if e.object_id in visited + (e.subject_id, e.predicate_id, e.object_id): e for e in edges_out if e.object_id in visited } sg = Subgraph(nodes=frozenset(visited), edges=tuple(dedup.values())) diff --git a/src/omop_graph/oaklib_interface/omop_implementation.py b/src/omop_graph/oaklib_interface/omop_implementation.py index f3fc47e..aff809c 100644 --- a/src/omop_graph/oaklib_interface/omop_implementation.py +++ b/src/omop_graph/oaklib_interface/omop_implementation.py @@ -1,11 +1,11 @@ import logging from collections import defaultdict -from typing import ClassVar, Dict, Iterable, Iterator, List, Optional, Tuple +from collections.abc import Iterable, Iterator +from typing import ClassVar import numpy as np -from omop_alchemy.cdm.query import ConceptFilter - from linkml_runtime.linkml_model.annotations import Annotation +from oa_configurator import ResolvedCDMDatabase from oaklib.datamodels.search import ( SearchConfiguration, SearchProperty, @@ -27,19 +27,19 @@ BasicOntologyInterface, ) from oaklib.interfaces.text_annotator_interface import nen_annotation +from oaklib.resource import OntologyResource from oaklib.types import CURIE, PRED_CURIE +from omop_alchemy.cdm.query import ConceptFilter +from sqlalchemy.exc import NoResultFound -from omop_graph.graph import KnowledgeGraph, KnowledgeGraphEmbeddingConfiguration +from omop_graph.db.session import cdm_session_factory, resolve_cdm_database from omop_graph.extensions.omop_alchemy import PredicateKind +from omop_graph.graph import KnowledgeGraph, KnowledgeGraphEmbeddingConfiguration from omop_graph.graph.nodes import LabelMatchKind +from omop_graph.oaklib_interface.omop_resource import OMOPOntologyResource from omop_graph.reasoning.grounding import GroundingConstraints, ground_term from omop_graph.reasoning.resolvers.resolver_pipeline import ResolverPipeline from omop_graph.render import bind_default_renderers -from omop_graph.oaklib_interface.omop_resource import OMOPOntologyResource -from omop_graph.db.session import cdm_session_factory, resolve_cdm_database - -from oa_configurator import ResolvedCDMDatabase -from oaklib.resource import OntologyResource logger = logging.getLogger(__name__) @@ -73,10 +73,7 @@ def _normalise_properties(config: SearchConfiguration) -> list[str]: list[str] A list of property strings. """ - return [ - getattr(p, "text", None) or str(p) - for p in (config.properties or []) # type: ignore[union-attr] - ] + return [getattr(p, "text", None) or str(p) for p in (config.properties or [])] class OMOPBaseInterface: @@ -93,7 +90,7 @@ def __init__(self, kg: KnowledgeGraph, **kwargs): super().__init__(**kwargs) self.kg = kg - def prefix_map(self) -> Dict[str, str]: + def prefix_map(self) -> dict[str, str]: """ Return the prefix map for OMOP entities. @@ -213,9 +210,9 @@ def __init__(self, kg: KnowledgeGraph, **kwargs): def annotate_text( self, text: str, - configuration: Optional[TextAnnotationConfiguration] = None, - query_embedding: Optional[np.ndarray] = None, - annotations: Optional[Dict[str, Annotation]] = None, + configuration: TextAnnotationConfiguration | None = None, + query_embedding: np.ndarray | None = None, + annotations: dict[str, Annotation] | None = None, max_depth: int = 6, ) -> Iterator[TextAnnotation]: """ @@ -253,20 +250,14 @@ def annotate_text( if isinstance(text, str) and configuration.token_exclusion_list: text = " ".join( - [ - term - for term in text.split() - if term not in configuration.token_exclusion_list - ] + [term for term in text.split() if term not in configuration.token_exclusion_list] ) elif isinstance(text, tuple) and configuration.token_exclusion_list: - filtered_text = tuple() + filtered_text = () # text is a tuple of string(s) for token in text: filtered_text = filtered_text + tuple( - term - for term in token.split() - if term not in configuration.token_exclusion_list + term for term in token.split() if term not in configuration.token_exclusion_list ) text = " ".join(filtered_text) @@ -274,27 +265,19 @@ def annotate_text( def split_annotations(ann): if ann is not None and isinstance(ann.value, str): - return tuple( - val.strip() for val in ann.value.split(ANNOTATIONS_SPLIT_CHAR) - ) + return tuple(val.strip() for val in ann.value.split(ANNOTATIONS_SPLIT_CHAR)) return None try: - parent_ids = split_annotations( - annotations.get(ANNOTATIONS_PARENT_ID_KEY, None) - ) - vocabs = split_annotations( - annotations.get(ANNOTATIONS_VOCABS_KEY, None) - ) - domains = split_annotations( - annotations.get(ANNOTATIONS_DOMAINS_KEY, None) - ) - except Exception as e: + parent_ids = split_annotations(annotations.get(ANNOTATIONS_PARENT_ID_KEY, None)) + vocabs = split_annotations(annotations.get(ANNOTATIONS_VOCABS_KEY, None)) + domains = split_annotations(annotations.get(ANNOTATIONS_DOMAINS_KEY, None)) + except Exception: logger.error( f"Annotations passed in wrong format. Expected key: val, " f"where val is separated by '{ANNOTATIONS_SPLIT_CHAR}' if using multiple values." ) - raise e + raise else: parent_ids = None vocabs = None @@ -302,9 +285,7 @@ def split_annotations(ann): constraints = GroundingConstraints( parent_ids=( - tuple(self._parse_concept(pid) for pid in parent_ids) - if parent_ids - else None + tuple(self._parse_concept(pid) for pid in parent_ids) if parent_ids else None ), search_constraint=ConceptFilter( domains=domains, @@ -347,7 +328,7 @@ def __init__(self, *args, kg: KnowledgeGraph, **kwargs): def basic_search( self, search_term: str, - config: Optional[SearchConfiguration] = None, + config: SearchConfiguration | None = None, ) -> Iterable[CURIE]: """ Perform a basic search over the knowledge graph. @@ -416,16 +397,15 @@ def basic_search( if ( SearchProperty.IDENTIFIER.text in props or SearchProperty.MAPPED_IDENTIFIER.text in props - ): - if ":" in search_term: - vocab, code = search_term.split(":", 1) - try: - cid = self.kg.concept_id_by_code(vocab, code) - if cid not in seen: - seen.add(cid) - yield self._concept_curie(cid) - except Exception: - pass + ) and ":" in search_term: + vocab, code = search_term.split(":", 1) + try: + cid = self.kg.concept_id_by_code(vocab, code) + if cid not in seen: + seen.add(cid) + yield self._concept_curie(cid) + except NoResultFound: + pass class OMOPRelationGraphInterface(OMOPBaseInterface, BasicOntologyInterface): @@ -442,7 +422,7 @@ def __init__(self, *args, kg: KnowledgeGraph, **kwargs): def supports_reasoning(self) -> bool: return False - def entity_aliases(self, curie: CURIE) -> List[str]: + def entity_aliases(self, curie: CURIE) -> list[str]: """ Retrieve aliases (synonyms and codes) for a given entity. @@ -467,7 +447,7 @@ def entity_aliases(self, curie: CURIE) -> List[str]: # vocabulary-qualified code alias aliases.add(f"{cv.vocabulary_id}:{cv.concept_code}") - return list(sorted(aliases)) + return sorted(aliases) def parents(self, curie: CURIE) -> Iterable[CURIE]: """ @@ -486,7 +466,7 @@ def children(self, curie: CURIE) -> Iterable[CURIE]: yield self._concept_curie(child_id) @property - def default_language(self) -> Optional[str]: + def default_language(self) -> str | None: return "en" def ontologies(self) -> Iterable[CURIE]: @@ -563,7 +543,7 @@ def roots( def leafs( self, - predicates: Optional[List[PRED_CURIE]] = None, + predicates: list[PRED_CURIE] | None = None, ignore_owl_nothing=True, filter_obsoletes=True, ) -> Iterable[CURIE]: @@ -574,7 +554,7 @@ def leafs( yield self._concept_curie(cid) def singletons( - self, predicates: Optional[List[PRED_CURIE]] = None, filter_obsoletes=True + self, predicates: list[PRED_CURIE] | None = None, filter_obsoletes=True ) -> Iterable[CURIE]: """ Retrieve singleton nodes (no parents or children). @@ -582,14 +562,14 @@ def singletons( for cid in self.kg.singletons(): yield self._concept_curie(cid) - def label(self, curie: CURIE, lang: Optional[str] = None) -> Optional[str]: + def label(self, curie: CURIE, lang: str | None = None) -> str | None: """ Get the preferred label (concept_name) for a CURIE. """ concept_id = self._parse_concept(curie) return self.kg.concept_view(concept_id).concept_name - def curies_by_label(self, label: str) -> List[CURIE]: + def curies_by_label(self, label: str) -> list[CURIE]: """ Retrieve CURIEs that match the exact label. """ @@ -607,7 +587,7 @@ def relationships( # ty: ignore[invalid-method-override] objects: Iterable[CURIE] | None = None, invert: bool = False, **kwargs, - ) -> Iterable[Tuple[CURIE, PRED_CURIE, CURIE]]: + ) -> Iterable[tuple[CURIE, PRED_CURIE, CURIE]]: """ Query relationships between concepts. @@ -629,15 +609,11 @@ def relationships( # ty: ignore[invalid-method-override] """ subject_ids = ( - tuple([self._parse_concept(s) for s in subjects]) - if subjects is not None - else None + tuple([self._parse_concept(s) for s in subjects]) if subjects is not None else None ) predicate_ids = tuple(predicates) if predicates is not None else None object_ids = ( - tuple([self._parse_concept(o) for o in objects]) - if objects is not None - else None + tuple([self._parse_concept(o) for o in objects]) if objects is not None else None ) relationships = tuple( @@ -656,7 +632,7 @@ def relationships( # ty: ignore[invalid-method-override] self._concept_curie(o), ) - def hierarchical_parents(self, curie: CURIE, isa_only: bool = False) -> List[CURIE]: + def hierarchical_parents(self, curie: CURIE, isa_only: bool = False) -> list[CURIE]: """ Get hierarchical parents. """ @@ -726,7 +702,7 @@ def entailed_outgoing_relationships( self, curie: CURIE, predicates: list[PRED_CURIE] | None = None, - ) -> Iterable[Tuple[PRED_CURIE, CURIE]]: + ) -> Iterable[tuple[PRED_CURIE, CURIE]]: """ Retrieve outgoing relationships, including those implied by the hierarchy. @@ -736,9 +712,7 @@ def entailed_outgoing_relationships( raise NotImplementedError("Changes to the CDM currently prevents this function") concept_id = self._parse_concept(curie) - pred_filter = ( - {self._parse_predicate(p) for p in predicates} if predicates else None - ) + pred_filter = {self._parse_predicate(p) for p in predicates} if predicates else None with self.kg.session_factory() as session: for edge in self.kg.iter_edges( @@ -761,25 +735,21 @@ def entailed_outgoing_relationships( def entailed_outputgoing_relationships_by_curie( self, *args, **kwargs - ) -> Iterable[Tuple[PRED_CURIE, CURIE]]: + ) -> Iterable[tuple[PRED_CURIE, CURIE]]: # return self.entailed_outgoing_relationships(*args, **kwargs) - raise NotImplementedError( - "Not Implemented: use entailed_outgoing_relationships instead" - ) + raise NotImplementedError("Not Implemented: use entailed_outgoing_relationships instead") def entailed_incoming_relationships( self, curie: CURIE, predicates: list[PRED_CURIE] | None = None, - ) -> Iterable[Tuple[PRED_CURIE, CURIE]]: + ) -> Iterable[tuple[PRED_CURIE, CURIE]]: """ Retrieve incoming relationships. """ concept_id = self._parse_concept(curie) - pred_filter = ( - {self._parse_predicate(p) for p in predicates} if predicates else None - ) + pred_filter = {self._parse_predicate(p) for p in predicates} if predicates else None with self.kg.session_factory() as session: for edge in self.kg.iter_edges( @@ -795,11 +765,9 @@ def entailed_incoming_relationships( def entailed_incoming_relationships_by_curie( self, *args, **kwargs - ) -> Iterable[Tuple[PRED_CURIE, CURIE]]: + ) -> Iterable[tuple[PRED_CURIE, CURIE]]: # return self.entailed_incoming_relationships(*args, **kwargs) - raise NotImplementedError( - "Not Implemented: use entailed_incoming_relationships instead" - ) + raise NotImplementedError("Not Implemented: use entailed_incoming_relationships instead") def entailed_relationships_between( self, @@ -812,9 +780,7 @@ def entailed_relationships_between( Currently disabled: always raises ``NotImplementedError``, per the raise message below, since a CDM change broke the body's assumptions. """ - raise NotImplementedError( - "Change in OMOP CDM made this function not work anymore" - ) + raise NotImplementedError("Change in OMOP CDM made this function not work anymore") subj_id = self._parse_concept(subject) obj_id = self._parse_concept(object) @@ -830,7 +796,7 @@ def entailed_relationships_between( yield self._predicate_curie("is_a") -class OMOPAlchemyImplementation( # type: ignore[override] +class OMOPAlchemyImplementation( OMOPRelationGraphInterface, OMOPSearchInterface, OMOPTextAnnotatorInterface, @@ -877,7 +843,7 @@ def __init__( *, resolved: ResolvedCDMDatabase | None = None, kg: KnowledgeGraph | None = None, - kg_emb_config: Optional[KnowledgeGraphEmbeddingConfiguration] = None, + kg_emb_config: KnowledgeGraphEmbeddingConfiguration | None = None, **kwargs, ): if resource is None: @@ -906,28 +872,22 @@ def _all_entailed_relationships(self): @property def edge_index(self): - raise NotImplementedError( - "edge_index is not supported for OMOP-backed adapters" - ) + raise NotImplementedError("edge_index is not supported for OMOP-backed adapters") @property def entailed_edge_index(self): - raise NotImplementedError( - "entailed_edge_index is not supported for OMOP-backed adapters" - ) + raise NotImplementedError("entailed_edge_index is not supported for OMOP-backed adapters") def obsoletes(self, include_merged=True) -> Iterable[CURIE]: - raise NotImplementedError( - "OMOP does not support OWL-style obsolescence semantics" - ) + raise NotImplementedError("OMOP does not support OWL-style obsolescence semantics") - def dangling(self, curies: Optional[Iterable[CURIE]] = None) -> Iterable[CURIE]: + def dangling(self, curies: Iterable[CURIE] | None = None) -> Iterable[CURIE]: raise NotImplementedError("OMOP does not support dangling term semantics") def ontology_versions(self, ontology: CURIE) -> Iterable[str]: raise NotImplementedError("OMOP does not support ontology version semantics") - def ontology_metadata_map(self, ontology: CURIE) -> Dict[PRED_CURIE, List[str]]: + def ontology_metadata_map(self, ontology: CURIE) -> dict[PRED_CURIE, list[str]]: raise NotImplementedError("OMOP does not expose ontology-level metadata") def subsets(self) -> Iterable[CURIE]: @@ -964,7 +924,7 @@ def save(self): def clone(self, resource): raise NotImplementedError("Cloning is not supported for OMOP adapters") - def definition(self, curie: CURIE, lang: Optional[str] = None) -> Optional[str]: + def definition(self, curie: CURIE, lang: str | None = None) -> str | None: raise NotImplementedError("OMOP does not support term definitions") def comments(self, curies, allow_none=True, lang=None): diff --git a/src/omop_graph/oaklib_interface/omop_resource.py b/src/omop_graph/oaklib_interface/omop_resource.py index e1b1238..99ea301 100644 --- a/src/omop_graph/oaklib_interface/omop_resource.py +++ b/src/omop_graph/oaklib_interface/omop_resource.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import Optional from oaklib.resource import OntologyResource @@ -22,9 +21,8 @@ class OMOPOntologyResource(OntologyResource): Whether the resource is read-only. Defaults to True. """ - slug: Optional[str] = None # type: ignore[assignment] - scheme: str = "omop" # type: ignore[assignment] - readonly: bool = True # type: ignore[assignment] + scheme: str | None = "omop" + readonly: bool | None = True def valid(self) -> bool: """ diff --git a/src/omop_graph/reasoning/concept_handlers/concept_helpers.py b/src/omop_graph/reasoning/concept_handlers/concept_helpers.py index 6a5381d..85a9fe0 100644 --- a/src/omop_graph/reasoning/concept_handlers/concept_helpers.py +++ b/src/omop_graph/reasoning/concept_handlers/concept_helpers.py @@ -5,16 +5,14 @@ concepts to their standard counterparts) using the Knowledge Graph. """ -from typing import Dict, Set - # Local Application Imports from omop_graph.graph.kg import KnowledgeGraph def standardise_ids( - ids: Set[int], + ids: set[int], kg: KnowledgeGraph, -) -> Dict[int, int]: +) -> dict[int, int]: """ Map a set of concept IDs to their Standard Concept IDs. @@ -40,7 +38,7 @@ def standardise_ids( dict[int, int] A dictionary mapping the original input ID to its standardized ID. """ - mapping: Dict[int, int] = {} + mapping: dict[int, int] = {} raise NotImplementedError( "predicate search has changed. Needs to change here too. Subsumes no longer valid." diff --git a/src/omop_graph/reasoning/grounding.py b/src/omop_graph/reasoning/grounding.py index 2daa409..606b449 100644 --- a/src/omop_graph/reasoning/grounding.py +++ b/src/omop_graph/reasoning/grounding.py @@ -17,31 +17,27 @@ import logging from dataclasses import dataclass -from typing import List, Optional, Tuple import numpy as np from omop_alchemy.cdm.query import ConceptFilter +from omop_graph.extensions.emb import ( + HAS_OMOP_EMB, + semantic_similarity, + try_get_embedding_writer_interface, +) from omop_graph.extensions.omop_alchemy import PredicateKind from omop_graph.graph.kg import KnowledgeGraph +from omop_graph.graph.nodes import LabelMatchKind from omop_graph.graph.paths import ( StandardConcept, find_standard_paths, ) -from omop_graph.graph.scoring import ( - StandardConceptWithScore, - score_standard_concepts -) +from omop_graph.graph.scoring import StandardConceptWithScore, score_standard_concepts from omop_graph.reasoning.resolvers import ( CandidateHit, - ResolverPipeline, EmbeddingResolver, -) -from omop_graph.graph.nodes import LabelMatchKind -from omop_graph.extensions.emb import ( - try_get_embedding_writer_interface, - semantic_similarity, - HAS_OMOP_EMB, + ResolverPipeline, ) logger = logging.getLogger(__name__) @@ -72,8 +68,8 @@ class GroundingConstraints: expanding candidates to unrelated concepts and diluting grounding results. """ - parent_ids: Optional[Tuple[int, ...]] - search_constraint: Optional[ConceptFilter] + parent_ids: tuple[int, ...] | None + search_constraint: ConceptFilter | None max_depth: int = 6 predicate_kinds: frozenset[PredicateKind] = frozenset({PredicateKind.IDENTITY}) @@ -85,7 +81,7 @@ def __post_init__(self) -> None: ) -def _query_text_with_context(query: str, context: Optional[str]) -> str: +def _query_text_with_context(query: str, context: str | None) -> str: """Fold optional free-form context into the text used for on-demand query embedding. Notes @@ -114,11 +110,11 @@ def ground_term( resolver_pipeline: ResolverPipeline, kg: KnowledgeGraph, query: str, - query_embedding: Optional[np.ndarray], + query_embedding: np.ndarray | None, constraints: GroundingConstraints, - max_candidates: Optional[int] = None, - context: Optional[str] = None, -) -> List[StandardConceptWithScore]: + max_candidates: int | None = None, + context: str | None = None, +) -> list[StandardConceptWithScore]: """ Ground a text string to a ranked list of standard OMOP concepts. @@ -146,7 +142,7 @@ def ground_term( list[StandardConceptWithScore] A list of standard concepts sorted by their total score (descending). """ - standard_concepts: List[StandardConcept] = [] + standard_concepts: list[StandardConcept] = [] search_constraints = constraints.search_constraint if search_constraints is not None: @@ -154,7 +150,9 @@ def ground_term( # Only do on demand embedding calculation if available and needed (having a EmbeddingResolver). # Falls back to None to disable embedding-based features if not available or not required. - require_embedding = HAS_OMOP_EMB and any(isinstance(resolver, EmbeddingResolver) for resolver in resolver_pipeline.resolvers) + require_embedding = HAS_OMOP_EMB and any( + isinstance(resolver, EmbeddingResolver) for resolver in resolver_pipeline.resolvers + ) if query_embedding is None and require_embedding: embedding_writer = try_get_embedding_writer_interface(kg) @@ -218,11 +216,9 @@ def ground_term( standard_concepts.extend(candidate_standard_concepts) if not standard_concepts: - logger.info( - f"No standard concepts found for '{query}' after hierarchy validation." - ) + logger.info(f"No standard concepts found for '{query}' after hierarchy validation.") return [] - + # Only calculate nearest concept matches for the embedding resolver matches # split is done that semantic similarity has less concepts to search for matched_standard_concepts_for_embedding = [ @@ -230,13 +226,13 @@ def ground_term( ] if matched_standard_concepts_for_embedding: if query_embedding is None: - raise RuntimeError("Query embedding cannot be None if the EmbeddingResolver is used and returned matches.") - nearest_concept_matches_for_standard_embedding_concepts = ( - semantic_similarity( - kg=kg, - standard_concepts=matched_standard_concepts_for_embedding, - query_embedding=query_embedding + raise RuntimeError( + "Query embedding cannot be None if the EmbeddingResolver is used and returned matches." ) + nearest_concept_matches_for_standard_embedding_concepts = semantic_similarity( + kg=kg, + standard_concepts=matched_standard_concepts_for_embedding, + query_embedding=query_embedding, ) embedding_standard_concepts_with_score = score_standard_concepts( text=query, @@ -247,16 +243,20 @@ def ground_term( else: embedding_standard_concepts_with_score = [] - # Score the other standard concepts that were not matched via embedding resolver + # Score the other standard concepts that were not matched via embedding resolver non_embedding_standard_concepts_with_score = score_standard_concepts( text=query, - standard_concepts=tuple(sc for sc in standard_concepts if sc.match_kind != LabelMatchKind.EMBEDDING), + standard_concepts=tuple( + sc for sc in standard_concepts if sc.match_kind != LabelMatchKind.EMBEDDING + ), kg=kg, nearest_concept_matches=None, # No embedding-based scoring for non-embedding matches ) # combine the two different ones - standard_concepts_with_score = embedding_standard_concepts_with_score + non_embedding_standard_concepts_with_score + standard_concepts_with_score = ( + embedding_standard_concepts_with_score + non_embedding_standard_concepts_with_score + ) best_by_concept_id: dict[int, StandardConceptWithScore] = {} for concept in standard_concepts_with_score: @@ -267,21 +267,17 @@ def ground_term( deduped_ranked = sorted( best_by_concept_id.values(), key=lambda sc: sc.total_score, reverse=True ) - return ( - deduped_ranked[:max_candidates] - if max_candidates is not None - else deduped_ranked - ) + return deduped_ranked[:max_candidates] if max_candidates is not None else deduped_ranked def find_standard_concepts( kg: KnowledgeGraph, candidate: CandidateHit, - parent_ids: Optional[Tuple[int, ...]], + parent_ids: tuple[int, ...] | None, max_depth: int, - max_paths: Optional[int] = 3, + max_paths: int | None = 3, predicate_kinds: frozenset[PredicateKind] = frozenset({PredicateKind.IDENTITY}), -) -> List[StandardConcept]: +) -> list[StandardConcept]: """ Identify standard concepts related to a candidate, optionally satisfying parent constraints. diff --git a/src/omop_graph/reasoning/phenotypes/phenotype_simplifier.py b/src/omop_graph/reasoning/phenotypes/phenotype_simplifier.py index b5830ef..fc4184a 100644 --- a/src/omop_graph/reasoning/phenotypes/phenotype_simplifier.py +++ b/src/omop_graph/reasoning/phenotypes/phenotype_simplifier.py @@ -1,7 +1,8 @@ -from dataclasses import dataclass, field from collections import defaultdict -from typing import Dict, Set, List +from dataclasses import dataclass, field + from omop_graph.graph.kg import KnowledgeGraph + from ..concept_handlers import standardise_ids @@ -114,16 +115,12 @@ def find_common_parents( stats.purity = stats.coverage / denom if denom else 0.0 stats.completeness = stats.coverage / max(len(seeds), 1) - return { - parent: stats - for parent, stats in final.items() - if stats.coverage >= min_coverage - } + return {parent: stats for parent, stats in final.items() if stats.coverage >= min_coverage} def greedy_parent_cover( - seeds: Set[int], - candidates: Dict[int, ParentStatistics], + seeds: set[int], + candidates: dict[int, ParentStatistics], *, target_coverage_ratio: float = 1.0, # e.g. 0.95 for “good enough” alpha: float = 1.0, @@ -131,9 +128,9 @@ def greedy_parent_cover( gamma: float = 0.3, delta: float = 0.7, min_gain: int = 1, -) -> List[int]: +) -> list[int]: remaining = set(seeds) - selected: List[int] = [] + selected: list[int] = [] def score(c: ParentStatistics, gain: int) -> float: if gain <= 0: @@ -151,7 +148,7 @@ def score(c: ParentStatistics, gain: int) -> float: best_id = None best_score = -1.0 - best_gain_set: Set[int] = set() + best_gain_set: set[int] = set() for cid, c in candidates.items(): gain_set = c.found & remaining diff --git a/src/omop_graph/reasoning/resolvers/__init__.py b/src/omop_graph/reasoning/resolvers/__init__.py index d8492b4..fc80e3b 100644 --- a/src/omop_graph/reasoning/resolvers/__init__.py +++ b/src/omop_graph/reasoning/resolvers/__init__.py @@ -1,19 +1,19 @@ +from .resolver_pipeline import ResolverPipeline from .resolvers import ( CandidateHit, + CandidateResolver, + EmbeddingResolver, ExactLabelResolver, ExactSynonymResolver, PartialLabelResolver, - CandidateResolver, - EmbeddingResolver, ) -from .resolver_pipeline import ResolverPipeline __all__ = [ "CandidateHit", + "CandidateResolver", + "EmbeddingResolver", "ExactLabelResolver", "ExactSynonymResolver", "PartialLabelResolver", - "CandidateResolver", "ResolverPipeline", - "EmbeddingResolver", ] diff --git a/src/omop_graph/reasoning/resolvers/resolver_pipeline.py b/src/omop_graph/reasoning/resolvers/resolver_pipeline.py index cc8c3e3..c8098f1 100644 --- a/src/omop_graph/reasoning/resolvers/resolver_pipeline.py +++ b/src/omop_graph/reasoning/resolvers/resolver_pipeline.py @@ -9,7 +9,7 @@ from __future__ import annotations import logging -from typing import Generator, Optional, Tuple, Type +from collections.abc import Generator from omop_alchemy.cdm.query import ConceptFilter @@ -48,8 +48,8 @@ class ResolverPipeline: def __init__( self, - resolvers: Tuple[CandidateResolver, ...], - stop_after_resolver: Optional[str | Type[CandidateResolver]] = None, + resolvers: tuple[CandidateResolver, ...], + stop_after_resolver: str | type[CandidateResolver] | None = None, ): self._resolver_map = {type(r): r for r in resolvers} @@ -62,11 +62,7 @@ def __init__( # If they passed a string name, find the matching type if isinstance(stop_after_resolver, str): match = next( - ( - t - for t in self._resolver_map - if t.__name__ == stop_after_resolver - ), + (t for t in self._resolver_map if t.__name__ == stop_after_resolver), None, ) if not match: @@ -83,11 +79,11 @@ def __repr__(self) -> str: return f"ResolverPipeline(resolvers={resolver_names}, stop_after_resolver={self.stop_at.__name__ if self.stop_at else None})" @property - def resolvers(self) -> Tuple[CandidateResolver, ...]: + def resolvers(self) -> tuple[CandidateResolver, ...]: return self._resolvers @property - def stop_at(self) -> Optional[Type[CandidateResolver]]: + def stop_at(self) -> type[CandidateResolver] | None: """ The class type of the resolver where the pipeline is configured to truncate. Returns None if the pipeline is configured to run to completion. @@ -95,7 +91,7 @@ def stop_at(self) -> Optional[Type[CandidateResolver]]: return self._stop_at @classmethod - def with_all_resolvers(cls) -> "ResolverPipeline": + def with_all_resolvers(cls) -> ResolverPipeline: """ Create a pipeline configured with all available resolvers. @@ -111,7 +107,7 @@ def resolve( self, kg: KnowledgeGraph, query: str, - constraints: Optional[ConceptFilter] = None, + constraints: ConceptFilter | None = None, **kwargs, ) -> Generator[CandidateHit, None, None]: """ @@ -135,9 +131,7 @@ def resolve( seen = set() for resolver in self.resolvers: - hits = resolver.resolve( - kg=kg, query=query, constraints=constraints, **kwargs - ) + hits = resolver.resolve(kg=kg, query=query, constraints=constraints, **kwargs) for hit in hits: if hit.concept_id not in seen: diff --git a/src/omop_graph/reasoning/resolvers/resolvers.py b/src/omop_graph/reasoning/resolvers/resolvers.py index 564677a..defcbef 100644 --- a/src/omop_graph/reasoning/resolvers/resolvers.py +++ b/src/omop_graph/reasoning/resolvers/resolvers.py @@ -11,20 +11,21 @@ from __future__ import annotations -from dataclasses import dataclass -from typing import TYPE_CHECKING, Iterable, Optional, Tuple import logging -import numpy as np +from collections.abc import Iterable +from dataclasses import dataclass +from typing import TYPE_CHECKING +import numpy as np from omop_alchemy.cdm.query import ConceptFilter -from omop_graph.graph.kg import KnowledgeGraph -from omop_graph.graph.nodes import LabelMatch, LabelMatchKind from omop_graph.extensions.emb import ( HAS_OMOP_EMB, MissingExtensionError, get_neareast_concepts, ) +from omop_graph.graph.kg import KnowledgeGraph +from omop_graph.graph.nodes import LabelMatch, LabelMatchKind if TYPE_CHECKING: from omop_graph.graph.kg import KnowledgeGraph @@ -88,10 +89,10 @@ def get_matches( self, kg: KnowledgeGraph, query: str, - constraints: Optional[ConceptFilter] = None, + constraints: ConceptFilter | None = None, sort: bool = False, **kwargs, - ) -> Tuple[LabelMatch, ...]: + ) -> tuple[LabelMatch, ...]: """ Execute the search strategy against the Knowledge Graph. @@ -125,7 +126,7 @@ def resolve( self, kg: KnowledgeGraph, query: str, - constraints: Optional[ConceptFilter] = None, + constraints: ConceptFilter | None = None, **kwargs, ) -> Iterable[CandidateHit]: """ @@ -236,17 +237,17 @@ def get_matches( self, kg: KnowledgeGraph, query: str, - constraints: Optional[ConceptFilter] = None, + constraints: ConceptFilter | None = None, sort: bool = False, - query_embedding: Optional[np.ndarray] = None, + query_embedding: np.ndarray | None = None, **kwargs, - ) -> Tuple[LabelMatch, ...]: + ) -> tuple[LabelMatch, ...]: if not HAS_OMOP_EMB: raise MissingExtensionError("EmbeddingResolver") from omop_emb.utils.embedding_utils import EmbeddingConceptFilter - concept_filter: Optional[EmbeddingConceptFilter] = ( + concept_filter: EmbeddingConceptFilter | None = ( EmbeddingConceptFilter( concept_ids=constraints.concept_ids, domains=constraints.domains, @@ -267,9 +268,7 @@ def get_matches( kg=kg, query_embedding=query_embedding, concept_filter=concept_filter, - k=constraints.limit - if isinstance(constraints, ConceptFilter) - else None, + k=constraints.limit if isinstance(constraints, ConceptFilter) else None, ) if matches is None: return () @@ -300,7 +299,7 @@ def get_matches( # Default sequence of resolvers to be used in a pipeline -ALL_RESOLVERS: Tuple[CandidateResolver, ...] = ( +ALL_RESOLVERS: tuple[CandidateResolver, ...] = ( ExactLabelResolver(), ExactSynonymResolver(), PartialLabelResolver(), diff --git a/src/omop_graph/render/__init__.py b/src/omop_graph/render/__init__.py index 1d777b2..ce4a0d1 100644 --- a/src/omop_graph/render/__init__.py +++ b/src/omop_graph/render/__init__.py @@ -11,17 +11,17 @@ """ from .auto import ( + bind_default_renderers, + render_explained_path, + render_path, render_subgraph, render_trace, - render_path, - render_explained_path, - bind_default_renderers, ) __all__ = [ + "bind_default_renderers", + "render_explained_path", + "render_path", "render_subgraph", "render_trace", - "render_path", - "render_explained_path", - "bind_default_renderers", ] diff --git a/src/omop_graph/render/auto.py b/src/omop_graph/render/auto.py index 3ee92ac..732f48d 100644 --- a/src/omop_graph/render/auto.py +++ b/src/omop_graph/render/auto.py @@ -1,38 +1,38 @@ from __future__ import annotations -from typing import Literal +from collections.abc import Callable +from typing import Any, Literal from omop_graph.graph.paths import GraphPath, PathExplanation -from omop_graph.graph.traverse import Subgraph, GraphTrace +from omop_graph.graph.traverse import GraphTrace, Subgraph from .html import ( + explained_path_html, + path_html, subgraph_html, trace_html_with_cards, - path_html, - explained_path_html, +) +from .mmd import ( + path_mermaid, + subgraph_mermaid, ) from .text import ( + explained_path_text, + path_text, subgraph_text, trace_text, - path_text, - explained_path_text, ) -from .mmd import ( - subgraph_mermaid, - path_mermaid, -) - Format = Literal["auto", "html", "text", "mmd"] def _in_notebook() -> bool: try: - from IPython.core.getipython import get_ipython # type: ignore[import-unresolved] + from IPython.core.getipython import get_ipython ip = get_ipython() return ip is not None and "IPKernelApp" in ip.config - except Exception: + except ImportError: return False @@ -94,9 +94,14 @@ def render_explained_path( return explained_path_text(kg, explanation) +def _install_repr_html(cls: type[Any], render: Callable[[Any], str]) -> None: + """Set render as the IPython rich-display hook ``_repr_html_`` of cls.""" + cls._repr_html_ = render + + def bind_default_renderers(kg): - from omop_graph.render import render_subgraph, render_trace, render_path + from omop_graph.render import render_path, render_subgraph, render_trace - Subgraph._repr_html_ = lambda self: render_subgraph(kg, self, format="html") # type: ignore - GraphTrace._repr_html_ = lambda self: render_trace(kg, self, format="html") # type: ignore - GraphPath._repr_html_ = lambda self: render_path(kg, self, format="html") # type: ignore + _install_repr_html(Subgraph, lambda self: render_subgraph(kg, self, format="html")) + _install_repr_html(GraphTrace, lambda self: render_trace(kg, self, format="html")) + _install_repr_html(GraphPath, lambda self: render_path(kg, self, format="html")) diff --git a/src/omop_graph/render/html.py b/src/omop_graph/render/html.py index 373c5d4..27801b6 100644 --- a/src/omop_graph/render/html.py +++ b/src/omop_graph/render/html.py @@ -3,8 +3,8 @@ from html import escape from omop_graph.graph.nodes import ConceptView -from omop_graph.graph.traverse import Subgraph, GraphTrace from omop_graph.graph.paths import GraphPath, PathExplanation +from omop_graph.graph.traverse import GraphTrace, Subgraph def concept_card(c: ConceptView) -> str: diff --git a/src/omop_graph/render/mmd.py b/src/omop_graph/render/mmd.py index 268c590..36bb054 100644 --- a/src/omop_graph/render/mmd.py +++ b/src/omop_graph/render/mmd.py @@ -1,7 +1,7 @@ from __future__ import annotations -from omop_graph.graph.traverse import Subgraph from omop_graph.graph.paths import GraphPath +from omop_graph.graph.traverse import Subgraph def subgraph_mermaid(kg, sg: Subgraph) -> str: diff --git a/src/omop_graph/render/text.py b/src/omop_graph/render/text.py index 436723d..f283a81 100644 --- a/src/omop_graph/render/text.py +++ b/src/omop_graph/render/text.py @@ -1,7 +1,7 @@ from __future__ import annotations from omop_graph.graph.paths import GraphPath, PathExplanation -from omop_graph.graph.traverse import Subgraph, GraphTrace +from omop_graph.graph.traverse import GraphTrace, Subgraph def subgraph_text(kg, sg: Subgraph) -> str: @@ -67,9 +67,7 @@ def explained_path_text(kg, explanation: PathExplanation) -> str: for s in explanation.steps: subj = kg.concept_view(s.step.subject.concept_id) obj = kg.concept_view(s.step.object.concept_id) - depth = ( - f"depth={s.traversal_depth}" if s.traversal_depth is not None else "depth=?" - ) + depth = f"depth={s.traversal_depth}" if s.traversal_depth is not None else "depth=?" lines.append( f" {subj.concept_name} --[{s.step.predicate}]--> {obj.concept_name} " f"({s.predicate_kind.name}, {depth}) {s.reason}" diff --git a/tests/conftest.py b/tests/conftest.py index ed094b8..70c4ff2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,11 +7,10 @@ def _isolated_cdm_test_database(request, field_name, *, dialect=None): from oa_configurator.testing import isolated_test_database + from omop_graph.config import OmopGraphConfig - return isolated_test_database( - OmopGraphConfig, field_name, dialect=dialect, request=request - ) + return isolated_test_database(OmopGraphConfig, field_name, dialect=dialect, request=request) @pytest.fixture diff --git a/tests/fixtures/helpers.py b/tests/fixtures/helpers.py index 7de6513..fa1e6ea 100644 --- a/tests/fixtures/helpers.py +++ b/tests/fixtures/helpers.py @@ -2,22 +2,25 @@ from __future__ import annotations -from typing import cast +from typing import Any import sqlalchemy as sa -from omop_alchemy.cdm.model.vocabulary import ( - Concept, - Concept_Class, - Domain, - Vocabulary -) - -VOCAB_TABLES = cast( - "tuple[sa.Table, ...]", - (Domain.__table__, Vocabulary.__table__, Concept_Class.__table__, Concept.__table__), -) -"""Domain/Vocabulary/Concept_Class/Concept: the minimal vocab bootstrap set -every split/non-split fixture needs, in FK dependency order. A consumer -needing more tables (e.g. Relationship, Concept_Relationship) extends this -tuple rather than redefining its own copy of the shared core. -""" +from oa_configurator import ResolvedCDMDatabase, ResolvedDatabase +from omop_alchemy.cdm.model.vocabulary import Concept, Concept_Class, Domain, Vocabulary + + +def require_cdm(resolved: ResolvedDatabase | None) -> ResolvedCDMDatabase: + """Narrow a resolved database to the CDM kind that the CDM session factory requires.""" + assert isinstance(resolved, ResolvedCDMDatabase) + return resolved + + +def table_of(model: Any) -> sa.Table: + """The Table a mapped class is bound to, narrowed from the declarative base's FromClause.""" + table = model.__table__ + assert isinstance(table, sa.Table) + return table + + +# The minimal vocab bootstrap set every split/non-split fixture needs, in FK dependency order. +VOCAB_TABLES = tuple(table_of(model) for model in (Domain, Vocabulary, Concept_Class, Concept)) diff --git a/tests/fixtures/mock_cdm.py b/tests/fixtures/mock_cdm.py index 5752771..13c31ff 100644 --- a/tests/fixtures/mock_cdm.py +++ b/tests/fixtures/mock_cdm.py @@ -1,15 +1,12 @@ from __future__ import annotations +from collections.abc import Iterator from datetime import date -from typing import Iterator, cast +from typing import cast import pytest import sqlalchemy as sa -from sqlalchemy.orm import Session, sessionmaker - from oa_configurator.testing import isolated_test_database, scoped_test_schema -from orm_loader.helpers import Base -from orm_loader.backends import resolve_backend from omop_alchemy.cdm.model.vocabulary.concept import Concept from omop_alchemy.cdm.model.vocabulary.concept_ancestor import Concept_Ancestor from omop_alchemy.cdm.model.vocabulary.concept_class import Concept_Class @@ -18,7 +15,11 @@ from omop_alchemy.cdm.model.vocabulary.domain import Domain from omop_alchemy.cdm.model.vocabulary.relationship import Relationship from omop_alchemy.cdm.model.vocabulary.vocabulary import Vocabulary +from orm_loader.backends import resolve_backend +from orm_loader.helpers import Base +from sqlalchemy.orm import Session, sessionmaker +from fixtures.helpers import require_cdm from omop_graph.config import OmopGraphConfig from omop_graph.extensions.omop_alchemy import ( PredicateKind, @@ -34,10 +35,12 @@ @pytest.fixture(scope="module") def mock_cdm_engine() -> Iterator[sa.Engine]: - with isolated_test_database(OmopGraphConfig, "test_cdm_db_pg") as db: - with scoped_test_schema(db.resolved, prefix="mock_cdm") as scoped: - _create_mock_cdm_tables(scoped.engine) - yield scoped.engine + with ( + isolated_test_database(OmopGraphConfig, "test_cdm_db_pg") as db, + scoped_test_schema(require_cdm(db.resolved), prefix="mock_cdm") as scoped, + ): + _create_mock_cdm_tables(scoped.engine) + yield scoped.engine @pytest.fixture(scope="module") @@ -93,17 +96,13 @@ def _create_mock_cdm_tables(engine: sa.Engine) -> None: ), ] ) -def mock_cdm_kg( - request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch -) -> KnowledgeGraph: +def mock_cdm_kg(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) -> KnowledgeGraph: # Grounding tests here focus on SQL + resolver + path pipeline. monkeypatch.setattr( "omop_graph.reasoning.grounding.try_get_embedding_writer_interface", lambda _kg: None, ) - fixture_name = ( - "mock_cdm_engine_sqlite" if request.param == "sqlite" else "mock_cdm_engine" - ) + fixture_name = "mock_cdm_engine_sqlite" if request.param == "sqlite" else "mock_cdm_engine" engine = request.getfixturevalue(fixture_name) return KnowledgeGraph(sessionmaker(bind=engine)) @@ -412,7 +411,11 @@ def seed_relationship_vocabulary(engine: sa.Engine) -> None: valid_start_date=date(2020, 1, 1), valid_end_date=date(2099, 12, 31), ), - Domain(domain_id="Metadata", domain_name="Metadata", domain_concept_id=CONCEPT_META_ID), + Domain( + domain_id="Metadata", + domain_name="Metadata", + domain_concept_id=CONCEPT_META_ID, + ), Vocabulary( vocabulary_id="OMOP", vocabulary_name="OMOP", diff --git a/tests/test_concept_queries.py b/tests/test_concept_queries.py index cef9bc3..8d41eb7 100644 --- a/tests/test_concept_queries.py +++ b/tests/test_concept_queries.py @@ -2,19 +2,18 @@ from __future__ import annotations +from collections.abc import Iterator from datetime import date -from typing import Iterator import pytest import sqlalchemy as sa -from sqlalchemy.orm import Session - +from fixtures.helpers import VOCAB_TABLES from oa_configurator.testing import scoped_test_schema -from orm_loader.helpers import Base -from orm_loader.backends import resolve_backend - from omop_alchemy.cdm.model.vocabulary import Concept, Concept_Class, Domain, Vocabulary from omop_alchemy.cdm.query import ConceptFilter +from orm_loader.backends import resolve_backend +from orm_loader.helpers import Base +from sqlalchemy.orm import Session from omop_graph.graph.nodes import ConceptView from omop_graph.graph.queries import ( @@ -24,8 +23,6 @@ q_entities, ) -from fixtures.helpers import VOCAB_TABLES - _META_CONCEPT_ID = 0 @@ -72,8 +69,16 @@ def concept( valid_start_date=valid_from, valid_end_date=valid_until, ), - Domain(domain_id="Metadata", domain_name="Metadata", domain_concept_id=_META_CONCEPT_ID), - Domain(domain_id="Condition", domain_name="Condition", domain_concept_id=_META_CONCEPT_ID), + Domain( + domain_id="Metadata", + domain_name="Metadata", + domain_concept_id=_META_CONCEPT_ID, + ), + Domain( + domain_id="Condition", + domain_name="Condition", + domain_concept_id=_META_CONCEPT_ID, + ), Vocabulary( vocabulary_id="OMOP", vocabulary_name="OMOP", @@ -153,9 +158,7 @@ def test_concept_views_use_canonical_standard_and_active_flags( view.concept_id: view for view in ( ConceptView.from_row(row) - for row in session.execute( - q_concept_views((1, 2, 3, 4, 5), sort=False) - ) + for row in session.execute(q_concept_views((1, 2, 3, 4, 5), sort=False)) ) } @@ -188,9 +191,7 @@ def test_label_query_projects_and_orders_canonical_flags( rows = session.execute(q_concept_name_match("Shared label")).all() assert [row.concept_id for row in rows] == [1, 2, 4, 3, 5] - statuses = { - row.concept_id: (row.is_standard, row.is_active) for row in rows - } + statuses = {row.concept_id: (row.is_standard, row.is_active) for row in rows} assert statuses == { 1: (True, True), 2: (True, True), diff --git a/tests/test_edges_cross_domain_regression.py b/tests/test_edges_cross_domain_regression.py index 97c1435..230fc14 100644 --- a/tests/test_edges_cross_domain_regression.py +++ b/tests/test_edges_cross_domain_regression.py @@ -11,17 +11,17 @@ from __future__ import annotations import csv +from collections.abc import Iterator from datetime import date -from importlib.resources import files -from typing import Iterator, cast +from importlib.resources import as_file, files +from typing import cast import pytest import sqlalchemy as sa -from sqlalchemy.orm import sessionmaker - -from orm_loader.helpers import Base from omop_alchemy.cdm.model.vocabulary.concept import Concept from omop_alchemy.cdm.model.vocabulary.concept_relationship import Concept_Relationship +from orm_loader.helpers import Base +from sqlalchemy.orm import sessionmaker from omop_graph.extensions.omop_alchemy import ( PredicateKind, @@ -114,7 +114,9 @@ def procedure_kg() -> Iterator[KnowledgeGraph]: session.add_all( RelationshipMapping( relationship_id=relationship_id, - predicate_kind=PredicateKind.HIERARCHY if relationship_id == "Is a" else PredicateKind.ATTRIBUTE, + predicate_kind=PredicateKind.HIERARCHY + if relationship_id == "Is a" + else PredicateKind.ATTRIBUTE, predicate_subkind=subkind, ) for relationship_id, _, subkind in edges @@ -127,7 +129,9 @@ def procedure_kg() -> Iterator[KnowledgeGraph]: def _attribute_pairs(edges) -> set[tuple[str, int]]: - return {(e.predicate_id, e.object_id) for e in edges if e.predicate_kind == PredicateKind.ATTRIBUTE} + return { + (e.predicate_id, e.object_id) for e in edges if e.predicate_kind == PredicateKind.ATTRIBUTE + } EXPECTED_ATTRIBUTE_PAIRS = {(rel, obj) for rel, obj, _ in ATTRIBUTE_EDGES} @@ -140,7 +144,9 @@ def test_edges_include_cross_domain_attributes_by_default(procedure_kg: Knowledg assert ("Is a", PARENT_PROCEDURE) in {(e.predicate_id, e.object_id) for e in edges} -def test_attribute_kind_filter_returns_cross_domain_attributes(procedure_kg: KnowledgeGraph) -> None: +def test_attribute_kind_filter_returns_cross_domain_attributes( + procedure_kg: KnowledgeGraph, +) -> None: edges = procedure_kg.edges( KNEE_REPLACEMENT, direction="out", @@ -200,7 +206,7 @@ def test_traverse_reaches_cross_domain_attributes(procedure_kg: KnowledgeGraph) def test_shipped_mapping_classes_snomed_defining_attributes_as_attributes() -> None: mapping_file = files("omop_graph") / "data" / "predicate_mapping.csv" - with mapping_file.open(newline="") as handle: + with as_file(mapping_file) as path, path.open(newline="") as handle: classes = {row["r_id"]: (row["class"], row["subclass"]) for row in csv.DictReader(handle)} assert {r_id: classes.get(r_id) for r_id in DEFINING_ATTRIBUTES} == DEFINING_ATTRIBUTES diff --git a/tests/test_edges_same_connection_regression.py b/tests/test_edges_same_connection_regression.py index 5fea044..104bce9 100644 --- a/tests/test_edges_same_connection_regression.py +++ b/tests/test_edges_same_connection_regression.py @@ -33,7 +33,9 @@ def test_edges_are_classified_from_the_relationship_mapping( assert edge.predicate_subkind == "mapping" -@pytest.mark.parametrize(("kinds", "expected"), [({PredicateKind.IDENTITY}, 1), ({PredicateKind.HIERARCHY}, 0)]) +@pytest.mark.parametrize( + ("kinds", "expected"), [({PredicateKind.IDENTITY}, 1), ({PredicateKind.HIERARCHY}, 0)] +) def test_predicate_kinds_filter_edges(mock_cdm_kg: KnowledgeGraph, kinds, expected) -> None: edges = mock_cdm_kg.edges( concept_ids=900001, diff --git a/tests/test_embedding_optional.py b/tests/test_embedding_optional.py index b73f243..7d420aa 100644 --- a/tests/test_embedding_optional.py +++ b/tests/test_embedding_optional.py @@ -9,9 +9,9 @@ import numpy as np import pytest - from oa_configurator.resolver import ResolvedModel, ResolvedProvider from omop_alchemy.cdm.query import ConceptFilter +from omop_emb import MetricType from omop_graph.extensions import emb as emb_ext from omop_graph.extensions.emb import MissingExtensionError @@ -45,9 +45,7 @@ class BrokenKG: def emb(self): raise MissingExtensionError() - assert ( - emb_ext.get_embedding_reader_interface(cast(KnowledgeGraph, BrokenKG())) is None - ) + assert emb_ext.get_embedding_reader_interface(cast(KnowledgeGraph, BrokenKG())) is None def test_knowledge_graph_emb_raises_missing_extension_error_when_omop_emb_unavailable( @@ -103,19 +101,15 @@ def test_fallback_flag_true_logs_attempt_when_concepts_missing( write=True, backend=Mock(), resolved_model=_make_resolved_model(), - metric_type=cast(emb_ext.EmbeddingMetricType, "cosine"), + metric_type=MetricType.COSINE, ) fake_reader = Mock() # concept 196653 ("Malignant tumor of kidney") exists in the dummy CDM DB - fake_reader.get_concepts_without_embedding.return_value = { - 196653: "Malignant tumor of kidney" - } + fake_reader.get_concepts_without_embedding.return_value = {196653: "Malignant tumor of kidney"} monkeypatch.setattr(emb_ext, "HAS_OMOP_EMB", True) - monkeypatch.setattr( - emb_ext, "get_embedding_reader_interface", lambda _: fake_reader - ) + monkeypatch.setattr(emb_ext, "get_embedding_reader_interface", lambda _: fake_reader) # No writer injected: simulates a writer that failed to materialize for some # other reason (config says write=True, but the lookup itself returns None). monkeypatch.setattr(emb_ext, "try_get_embedding_writer_interface", lambda _: None) @@ -124,9 +118,7 @@ def test_fallback_flag_true_logs_attempt_when_concepts_missing( with caplog.at_level(logging.DEBUG, logger="omop_graph.extensions.emb"): emb_ext.semantic_similarity( kg=mock_cdm_kg, - standard_concepts=[ - _make_standard_concept(196653, "Malignant tumor of kidney") - ], + standard_concepts=[_make_standard_concept(196653, "Malignant tumor of kidney")], query_embedding=np.zeros((1, 3), dtype=np.float32), ) @@ -148,26 +140,20 @@ def test_fallback_flag_false_logs_disabled_when_concepts_missing( compute_missing_embeddings=False, backend=Mock(), resolved_model=_make_resolved_model(), - metric_type=cast(emb_ext.EmbeddingMetricType, "cosine"), + metric_type=MetricType.COSINE, ) fake_reader = Mock() - fake_reader.get_concepts_without_embedding.return_value = { - 196653: "Malignant tumor of kidney" - } + fake_reader.get_concepts_without_embedding.return_value = {196653: "Malignant tumor of kidney"} monkeypatch.setattr(emb_ext, "HAS_OMOP_EMB", True) - monkeypatch.setattr( - emb_ext, "get_embedding_reader_interface", lambda _: fake_reader - ) + monkeypatch.setattr(emb_ext, "get_embedding_reader_interface", lambda _: fake_reader) monkeypatch.setattr(emb_ext, "get_neareast_concepts", lambda **_: None) with caplog.at_level(logging.INFO, logger="omop_graph.extensions.emb"): emb_ext.semantic_similarity( kg=mock_cdm_kg, - standard_concepts=[ - _make_standard_concept(196653, "Malignant tumor of kidney") - ], + standard_concepts=[_make_standard_concept(196653, "Malignant tumor of kidney")], query_embedding=np.zeros((1, 3), dtype=np.float32), ) @@ -182,7 +168,7 @@ class TestEmbeddingConfigurationValidation: def test_compute_missing_embeddings_requires_write(self): with pytest.raises(ValueError, match="compute_missing_embeddings=True requires write=True"): KnowledgeGraphEmbeddingConfiguration( - metric_type=cast(emb_ext.EmbeddingMetricType, "cosine"), + metric_type=MetricType.COSINE, backend=Mock(), resolved_model=_make_resolved_model(), compute_missing_embeddings=True, @@ -191,7 +177,7 @@ def test_compute_missing_embeddings_requires_write(self): def test_model_name_and_provider_type_derive_from_resolved_model(self): resolved = _make_resolved_model() cfg = KnowledgeGraphEmbeddingConfiguration( - metric_type=cast(emb_ext.EmbeddingMetricType, "cosine"), + metric_type=MetricType.COSINE, backend=Mock(), resolved_model=resolved, write=True, @@ -224,9 +210,7 @@ def emb(self): monkeypatch.setattr(emb_ext, "HAS_OMOP_EMB", True) with caplog.at_level(logging.DEBUG, logger="omop_graph.extensions.emb"): - result = emb_ext.try_get_embedding_writer_interface( - cast(KnowledgeGraph, ReadOnlyKG()) - ) + result = emb_ext.try_get_embedding_writer_interface(cast(KnowledgeGraph, ReadOnlyKG())) assert result is None assert "unittest.mock.Mock" in caplog.text assert "write=False" not in caplog.text @@ -242,7 +226,9 @@ class NoConfigKG: def emb(self): raise ValueError("Embedding configuration is not set.") - assert emb_ext.try_get_embedding_writer_interface(cast(KnowledgeGraph, NoConfigKG())) is None + assert ( + emb_ext.try_get_embedding_writer_interface(cast(KnowledgeGraph, NoConfigKG())) is None + ) def test_returns_none_when_extension_missing(self): class MissingExtensionKG: @@ -250,7 +236,10 @@ class MissingExtensionKG: def emb(self): raise MissingExtensionError() - assert emb_ext.try_get_embedding_writer_interface(cast(KnowledgeGraph, MissingExtensionKG())) is None + assert ( + emb_ext.try_get_embedding_writer_interface(cast(KnowledgeGraph, MissingExtensionKG())) + is None + ) # ── omop-emb#48 split: k threaded explicitly instead of via filter.limit ── @@ -264,13 +253,14 @@ def test_semantic_similarity_splits_cdm_and_knn_filters( the KNN call gets a plain EmbeddingConceptFilter (no limit) with k passed explicitly instead. """ - from omop_emb.utils.embedding_utils import CDMConceptFilter, EmbeddingConceptFilter + from omop_alchemy.cdm.query import ConceptFilter as CDMConceptFilter + from omop_emb.utils.embedding_utils import EmbeddingConceptFilter mock_cdm_kg._emb_config = KnowledgeGraphEmbeddingConfiguration( compute_missing_embeddings=False, backend=Mock(), resolved_model=_make_resolved_model(), - metric_type=cast(emb_ext.EmbeddingMetricType, "cosine"), + metric_type=MetricType.COSINE, ) fake_reader = Mock() @@ -280,9 +270,7 @@ def test_semantic_similarity_splits_cdm_and_knn_filters( fake_reader.get_nearest_concepts.return_value = () monkeypatch.setattr(emb_ext, "HAS_OMOP_EMB", True) - monkeypatch.setattr( - emb_ext, "get_embedding_reader_interface", lambda _: fake_reader - ) + monkeypatch.setattr(emb_ext, "get_embedding_reader_interface", lambda _: fake_reader) emb_ext.semantic_similarity( kg=mock_cdm_kg, @@ -293,9 +281,7 @@ def test_semantic_similarity_splits_cdm_and_knn_filters( query_embedding=np.zeros((1, 3), dtype=np.float32), ) - cdm_filter = fake_reader.get_concepts_without_embedding.call_args.kwargs[ - "concept_filter" - ] + cdm_filter = fake_reader.get_concepts_without_embedding.call_args.kwargs["concept_filter"] assert isinstance(cdm_filter, CDMConceptFilter) assert cdm_filter.limit == 2 diff --git a/tests/test_fulltext_optional.py b/tests/test_fulltext_optional.py index cbb4c43..d7cb23b 100644 --- a/tests/test_fulltext_optional.py +++ b/tests/test_fulltext_optional.py @@ -1,9 +1,10 @@ -import pytest +from typing import Any +import pytest import sqlalchemy as sa +from omop_alchemy.backends import FullTextError from sqlalchemy import Engine from sqlalchemy.orm import sessionmaker -from omop_alchemy.backends import FullTextError from omop_graph.graph.kg import KnowledgeGraph from omop_graph.graph.nodes import LabelMatchKind @@ -13,9 +14,7 @@ @pytest.mark.parametrize("synonym", [False, True]) @pytest.mark.postgresql @pytest.mark.db_dialect -def test_fulltext_query_requires_tsvector_columns( - synonym: bool, mock_cdm_engine: Engine -): +def test_fulltext_query_requires_tsvector_columns(synonym: bool, mock_cdm_engine: Engine): """Full-text query raises FullTextError when tsvector columns are absent from the database. The mock CDM's own table creation never adds tsvector columns (that's a @@ -23,9 +22,7 @@ def test_fulltext_query_requires_tsvector_columns( q_concept_name_fulltext (which inspects the live DB schema) always fires here. """ with pytest.raises(FullTextError): - q_concept_name_fulltext( - "kidney cancer", synonym=synonym, engine=mock_cdm_engine - ) + q_concept_name_fulltext("kidney cancer", synonym=synonym, engine=mock_cdm_engine) def test_knowledge_graph_fulltext_lookup_skips_unsupported_sqlite( @@ -36,7 +33,7 @@ def test_knowledge_graph_fulltext_lookup_skips_unsupported_sqlite( def test_knowledge_graph_rejects_engine_instead_of_session_factory(): - engine = sa.create_engine("sqlite+pysqlite:///:memory:") + engine: Any = sa.create_engine("sqlite+pysqlite:///:memory:") try: with pytest.raises(TypeError, match="callable session factory.*not an Engine"): KnowledgeGraph(engine) diff --git a/tests/test_fulltext_vocab_schema_postgres.py b/tests/test_fulltext_vocab_schema_postgres.py index b9f0065..f33a52c 100644 --- a/tests/test_fulltext_vocab_schema_postgres.py +++ b/tests/test_fulltext_vocab_schema_postgres.py @@ -10,6 +10,7 @@ import pytest import sqlalchemy as sa +from fixtures.helpers import VOCAB_TABLES from oa_configurator import Role from oa_configurator.testing import scoped_test_schema from omop_alchemy.backends import CONCEPT_NAME_TSVECTOR_COLUMN, FullTextError, resolve_backend @@ -17,8 +18,6 @@ from omop_graph.graph.queries import q_concept_name_fulltext -from fixtures.helpers import VOCAB_TABLES - pytestmark = [pytest.mark.postgresql, pytest.mark.db_dialect] @@ -42,7 +41,9 @@ def test_fulltext_query_finds_the_tsvector_column_in_the_vocab_schema(pg_db): # split, not a lucky same-schema coincidence. inspector = sa.inspect(engine) assert not inspector.has_table("concept", schema=scoped.schemas[Role.PRIMARY]) - columns = {c["name"] for c in inspector.get_columns("concept", schema=scoped.schemas[Role.VOCAB])} + columns = { + c["name"] for c in inspector.get_columns("concept", schema=scoped.schemas[Role.VOCAB]) + } assert CONCEPT_NAME_TSVECTOR_COLUMN in columns stmt = q_concept_name_fulltext("kidney cancer", engine=engine) diff --git a/tests/test_grounding.py b/tests/test_grounding.py index f91216d..ed711c8 100644 --- a/tests/test_grounding.py +++ b/tests/test_grounding.py @@ -1,9 +1,10 @@ from __future__ import annotations import pytest +from fixtures.mock_cdm import PARENT_CANCER_ID +from omop_alchemy.cdm.query import ConceptFilter from omop_graph.extensions.omop_alchemy import PredicateKind -from omop_alchemy.cdm.query import ConceptFilter from omop_graph.graph.kg import KnowledgeGraph from omop_graph.reasoning.grounding import ( GroundingConstraints, @@ -17,7 +18,6 @@ PartialLabelResolver, PartialSynonymResolver, ) -from fixtures.mock_cdm import PARENT_CANCER_ID class TestQueryTextWithContext: diff --git a/tests/test_kg_ancestry.py b/tests/test_kg_ancestry.py index dfe4dba..9e2ee60 100644 --- a/tests/test_kg_ancestry.py +++ b/tests/test_kg_ancestry.py @@ -1,10 +1,12 @@ from __future__ import annotations +from fixtures.mock_cdm import CONCEPT_META_ID, PARENT_CANCER_ID + from omop_graph.graph.kg import KnowledgeGraph from omop_graph.graph.nodes import LabelMatchKind from omop_graph.graph.paths import find_standard_paths from omop_graph.reasoning.resolvers.resolvers import CandidateHit -from fixtures.mock_cdm import PARENT_CANCER_ID, CONCEPT_META_ID + def test_get_potential_ancestors_batch_returns_only_real_ancestors( mock_cdm_kg: KnowledgeGraph, diff --git a/tests/test_oaklib_interface.py b/tests/test_oaklib_interface.py index ebfe371..13652d0 100644 --- a/tests/test_oaklib_interface.py +++ b/tests/test_oaklib_interface.py @@ -9,9 +9,7 @@ def test_annotate_text_standardizes_non_standard_candidate_without_parent_annota ) -> None: interface = OMOPTextAnnotatorInterface(kg=mock_cdm_kg) - annotations = list( - interface.annotate_text(text="Kidney carcinoma term", annotations=None) - ) + annotations = list(interface.annotate_text(text="Kidney carcinoma term", annotations=None)) assert len(annotations) == 1 assert annotations[0].object_id == "OMOP:196653" diff --git a/tests/test_oaklib_schema_awareness.py b/tests/test_oaklib_schema_awareness.py index bca03d2..d667097 100644 --- a/tests/test_oaklib_schema_awareness.py +++ b/tests/test_oaklib_schema_awareness.py @@ -16,15 +16,15 @@ import pytest import sqlalchemy as sa import sqlalchemy.orm -from oaklib import get_adapter - +from fixtures.helpers import require_cdm +from fixtures.mock_cdm import seed_relationship_vocabulary from oa_configurator import Resolver, Role from oa_configurator.testing import scoped_test_schema +from oaklib import get_adapter from omop_alchemy.cdm.model.vocabulary import Concept, Concept_Class, Domain, Vocabulary from omop_alchemy.cross_database import cdm_sessionmaker -from orm_loader.backends import staging_schema_claim +from orm_loader.backends import resolve_backend, staging_schema_claim from orm_loader.helpers import Base -from orm_loader.backends import resolve_backend from omop_graph.cli import relationship_classification from omop_graph.db.session import resolve_cdm_database @@ -32,7 +32,6 @@ from omop_graph.graph.kg import KnowledgeGraph from omop_graph.oaklib_interface import omop_implementation from omop_graph.oaklib_interface.omop_implementation import OMOPAlchemyImplementation -from fixtures.mock_cdm import seed_relationship_vocabulary _META_CONCEPT_ID = 0 _CONCEPT_ID = 1001 @@ -70,7 +69,11 @@ def _seed_one_concept(engine: sa.Engine, *, concept_id: int, name: str) -> None: valid_start_date=_TODAY, valid_end_date=_FAR_FUTURE, ), - Domain(domain_id="Metadata", domain_name="Metadata", domain_concept_id=_META_CONCEPT_ID), + Domain( + domain_id="Metadata", + domain_name="Metadata", + domain_concept_id=_META_CONCEPT_ID, + ), Vocabulary( vocabulary_id="OMOP", vocabulary_name="OMOP", @@ -96,11 +99,15 @@ def _populate(scoped, *, name: str) -> None: relationship_classification(resolved=scoped.resolved) -@pytest.mark.parametrize(("descriptor", "expected_name"), [("omop:named_db", "named_db"), ("omop:", None)]) +@pytest.mark.parametrize( + ("descriptor", "expected_name"), [("omop:named_db", "named_db"), ("omop:", None)] +) def test_get_adapter_resolves_the_slug_as_the_database_name( pg_db, monkeypatch, descriptor, expected_name ) -> None: - with scoped_test_schema(pg_db.resolved, prefix="oaklib_get_adapter", schema_claims=[staging_schema_claim()]) as scoped: + with scoped_test_schema( + pg_db.resolved, prefix="oaklib_get_adapter", schema_claims=[staging_schema_claim()] + ) as scoped: _populate(scoped, name="Adapter concept") def fake_resolve_cdm_database(name): @@ -126,14 +133,18 @@ def test_resolve_cdm_database_resolves_a_named_entry(pg_db) -> None: def test_resolved_path_reuses_one_engine_without_a_vocab_split(pg_db) -> None: - with scoped_test_schema(pg_db.resolved, prefix="oaklib_resolved", schema_claims=[staging_schema_claim()]) as scoped: + with scoped_test_schema( + pg_db.resolved, prefix="oaklib_resolved", schema_claims=[staging_schema_claim()] + ) as scoped: _populate(scoped, name="Resolved-path concept") - adapter = OMOPAlchemyImplementation(resolved=scoped.resolved) + adapter = OMOPAlchemyImplementation(resolved=require_cdm(scoped.resolved)) assert _one_engine_for_both_roles(adapter.kg) with adapter.kg.session_factory() as session: - assert session.get_bind(Concept).pool._pre_ping is True + bind = session.get_bind(Concept) + assert isinstance(bind, sa.Engine) + assert bind.pool._pre_ping is True assert adapter.label(f"OMOP:{_CONCEPT_ID}") == "Resolved-path concept" @@ -145,25 +156,41 @@ def test_two_connection_entries_for_one_database_collapse_to_one_engine(pg_db) - name = pg_db.resolved.name resolver = Resolver.from_active_config() resolver = resolver.with_overrides( - connections={"oaklib_split_vocab": resolver.config.connections[pg_db.resolved.connection.name]}, - databases={name: resolver.config.databases[name].model_copy(update={"vocab_connection": "oaklib_split_vocab"})}, + connections={ + "oaklib_split_vocab": resolver.config.connections[pg_db.resolved.connection.name] + }, + databases={ + name: resolver.config.databases[name].model_copy( + update={"vocab_connection": "oaklib_split_vocab"} + ) + }, ) - with scoped_test_schema(resolver.resolve_database(name), prefix="oaklib_split", resolver=resolver, schema_claims=[staging_schema_claim()]) as scoped: - assert scoped.resolved.connection.name != scoped.resolved.vocab_connection.name - assert scoped.resolved.foreign_key_can_span(Role.PRIMARY, Role.VOCAB) + with scoped_test_schema( + resolver.resolve_database(name), + prefix="oaklib_split", + resolver=resolver, + schema_claims=[staging_schema_claim()], + ) as scoped: + resolved = require_cdm(scoped.resolved) + assert resolved.connection.name != resolved.vocab_connection.name + assert resolved.foreign_key_can_span(Role.PRIMARY, Role.VOCAB) _populate(scoped, name="Split-wiring concept") - adapter = OMOPAlchemyImplementation(resolved=scoped.resolved) + adapter = OMOPAlchemyImplementation(resolved=resolved) assert _one_engine_for_both_roles(adapter.kg) assert adapter.label(f"OMOP:{_CONCEPT_ID}") == "Split-wiring concept" def test_kg_injection_path_uses_the_injected_knowledge_graph(pg_db) -> None: - with scoped_test_schema(pg_db.resolved, prefix="oaklib_kg", schema_claims=[staging_schema_claim()]) as scoped: + with scoped_test_schema( + pg_db.resolved, prefix="oaklib_kg", schema_claims=[staging_schema_claim()] + ) as scoped: _populate(scoped, name="Injected concept") kg = KnowledgeGraph( - cdm_sessionmaker(scoped.resolved, primary=scoped.engine, vocab=scoped.engine) + cdm_sessionmaker( + require_cdm(scoped.resolved), primary=scoped.engine, vocab=scoped.engine + ) ) adapter = OMOPAlchemyImplementation(kg=kg) diff --git a/tests/test_predicate_mapping_data.py b/tests/test_predicate_mapping_data.py index 490371e..fbfb94a 100644 --- a/tests/test_predicate_mapping_data.py +++ b/tests/test_predicate_mapping_data.py @@ -1,21 +1,18 @@ import csv +import io from importlib import resources def _read_csv(filename: str) -> list[dict[str, str]]: path = resources.files("omop_graph").joinpath("data", filename) - with path.open("r", encoding="utf-8-sig", newline="") as source: + with path.open("rb") as raw, io.TextIOWrapper(raw, encoding="utf-8-sig", newline="") as source: return list(csv.DictReader(source)) def test_every_mapping_class_subclass_has_a_classification(): - mapping_pairs = { - (row["class"], row["subclass"]) - for row in _read_csv("predicate_mapping.csv") - } + mapping_pairs = {(row["class"], row["subclass"]) for row in _read_csv("predicate_mapping.csv")} classification_pairs = { - (row["class"], row["subclass"]) - for row in _read_csv("predicate_classification.csv") + (row["class"], row["subclass"]) for row in _read_csv("predicate_classification.csv") } assert mapping_pairs <= classification_pairs diff --git a/tests/test_relationship_classification.py b/tests/test_relationship_classification.py index af1bb27..177f3ff 100644 --- a/tests/test_relationship_classification.py +++ b/tests/test_relationship_classification.py @@ -12,24 +12,26 @@ import logging import sqlalchemy as sa +from fixtures.helpers import require_cdm +from fixtures.mock_cdm import seed_relationship_vocabulary from oa_configurator import Role from oa_configurator.testing import scoped_test_schema - from orm_loader.backends import staging_schema_claim from orm_loader.helpers import Base from omop_graph.cli import relationship_classification from omop_graph.extensions.omop_alchemy import RelationshipClass, RelationshipMapping -from fixtures.mock_cdm import seed_relationship_vocabulary def test_relationship_classification_respects_the_configured_schema(pg_db): - with scoped_test_schema(pg_db.resolved, prefix="relclass_routing", schema_claims=[staging_schema_claim()]) as scoped: + with scoped_test_schema( + pg_db.resolved, prefix="relclass_routing", schema_claims=[staging_schema_claim()] + ) as scoped: schema = scoped.schemas[Role.PRIMARY] Base.metadata.create_all(bind=scoped.engine, checkfirst=True) seed_relationship_vocabulary(scoped.engine) - relationship_classification(resolved=scoped.resolved) + relationship_classification(resolved=require_cdm(scoped.resolved)) with scoped.engine.connect() as conn: n_class = conn.execute( @@ -54,12 +56,14 @@ def test_relationship_classification_respects_the_configured_schema(pg_db): def test_relationship_classification_is_idempotent(pg_db): """Re-running against the same schema, the real-world redeploy case the DROP TABLE/enum-drop cleanup exists for, must not fail.""" - with scoped_test_schema(pg_db.resolved, prefix="relclass_idempotent", schema_claims=[staging_schema_claim()]) as scoped: + with scoped_test_schema( + pg_db.resolved, prefix="relclass_idempotent", schema_claims=[staging_schema_claim()] + ) as scoped: Base.metadata.create_all(bind=scoped.engine, checkfirst=True) seed_relationship_vocabulary(scoped.engine) - relationship_classification(resolved=scoped.resolved) - relationship_classification(resolved=scoped.resolved) + relationship_classification(resolved=require_cdm(scoped.resolved)) + relationship_classification(resolved=require_cdm(scoped.resolved)) with scoped.engine.connect() as conn: n_class = conn.execute( @@ -68,9 +72,7 @@ def test_relationship_classification_is_idempotent(pg_db): assert n_class and n_class > 0 -def test_relationship_classification_warns_and_drops_unknown_ids_colocated( - pg_db, tmp_path, caplog -): +def test_relationship_classification_warns_and_drops_unknown_ids_colocated(pg_db, tmp_path, caplog): (tmp_path / "predicate_classification.csv").write_text( "class,subclass,description,semantics,inference\n" "Identity,mapping,Identity mapping,identity,none\n" @@ -90,13 +92,11 @@ def test_relationship_classification_warns_and_drops_unknown_ids_colocated( seed_relationship_vocabulary(scoped.engine) with caplog.at_level(logging.WARNING, logger="omop_graph.cli"): - relationship_classification(str(tmp_path), resolved=scoped.resolved) + relationship_classification(str(tmp_path), resolved=require_cdm(scoped.resolved)) with scoped.engine.connect() as connection: relationship_ids = set( - connection.execute( - sa.select(RelationshipMapping.relationship_id) - ).scalars() + connection.execute(sa.select(RelationshipMapping.relationship_id)).scalars() ) assert relationship_ids == {"Maps to"} diff --git a/tests/test_relationship_classification_duckdb.py b/tests/test_relationship_classification_duckdb.py index 73cc635..3997c31 100644 --- a/tests/test_relationship_classification_duckdb.py +++ b/tests/test_relationship_classification_duckdb.py @@ -32,9 +32,7 @@ def _seed_duckdb_vocabulary(engine, mapping_rows: list[dict[str, str]]) -> set[s sa.Column("relationship_id", sa.String(20), primary_key=True), schema=Role.VOCAB.value, ) - relationship_ids = { - row["r_id"].strip() for row in mapping_rows if row.get("r_id", "").strip() - } + relationship_ids = {row["r_id"].strip() for row in mapping_rows if row.get("r_id", "").strip()} with engine.begin() as connection: relationship_table.create(connection) connection.execute( @@ -70,15 +68,18 @@ def test_relationship_classification_runs_and_is_idempotent_on_duckdb( cli.relationship_classification(resolved=resolved) with duckdb_db.committing_engine.connect() as connection: - checks = connection.execute( - sa.text( - "SELECT constraint_text FROM duckdb_constraints() " - "WHERE table_name = 'relationship_class' AND constraint_type = 'CHECK'" + checks = ( + connection.execute( + sa.text( + "SELECT constraint_text FROM duckdb_constraints() " + "WHERE table_name = 'relationship_class' AND constraint_type = 'CHECK'" + ) ) - ).scalars().all() + .scalars() + .all() + ) assert any( - "predicate_kind" in check - and all(f"'{kind.value}'" in check for kind in PredicateKind) + "predicate_kind" in check and all(f"'{kind.value}'" in check for kind in PredicateKind) for check in checks ) actual_classes = { diff --git a/tests/test_resolvers_mock.py b/tests/test_resolvers_mock.py index e4ff0a4..ad38503 100644 --- a/tests/test_resolvers_mock.py +++ b/tests/test_resolvers_mock.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Dict, List, cast +from typing import cast from omop_graph.graph.kg import KnowledgeGraph from omop_graph.graph.nodes import LabelMatch, LabelMatchKind @@ -17,7 +17,7 @@ @dataclass class _Case: text: str - hits: Dict[str, List[int]] + hits: dict[str, list[int]] class _KG: diff --git a/tests/test_schema_provenance_guard.py b/tests/test_schema_provenance_guard.py index 98acaa3..e0855c4 100644 --- a/tests/test_schema_provenance_guard.py +++ b/tests/test_schema_provenance_guard.py @@ -16,6 +16,8 @@ from __future__ import annotations import pytest +from fixtures.helpers import require_cdm +from fixtures.mock_cdm import seed_relationship_vocabulary from oa_configurator import Role, SchemaDriftError from oa_configurator.testing import ( guarded_resolver, @@ -23,11 +25,9 @@ resolve_with_role_schemas, scoped_test_schema, ) - from orm_loader.helpers import Base from omop_graph import cli as omop_graph_cli -from fixtures.mock_cdm import seed_relationship_vocabulary pytestmark = [pytest.mark.postgresql, pytest.mark.db_dialect] @@ -51,4 +51,4 @@ def test_relationship_classification_guard_fires_on_reconfigured_schema( resolver=resolver, ) with pytest.raises(SchemaDriftError): - omop_graph_cli.relationship_classification(resolved=drifted) + omop_graph_cli.relationship_classification(resolved=require_cdm(drifted)) diff --git a/tests/test_vocab_split_connection.py b/tests/test_vocab_split_connection.py index e121da2..6482dc4 100644 --- a/tests/test_vocab_split_connection.py +++ b/tests/test_vocab_split_connection.py @@ -12,19 +12,20 @@ from __future__ import annotations +from collections.abc import Iterator from datetime import date -from typing import Iterator, NamedTuple +from typing import NamedTuple import pytest import sqlalchemy as sa import sqlalchemy.orm as so - +from fixtures.helpers import VOCAB_TABLES, require_cdm, table_of from oa_configurator import ResolvedCDMDatabase, Resolver, Role -from oa_configurator.testing import isolated_test_database, resolve_with_role_schemas, scoped_test_schema -from orm_loader.config import OrmLoaderConfig -from orm_loader.helpers import Base -from orm_loader.backends import resolve_backend - +from oa_configurator.testing import ( + isolated_test_database, + resolve_with_role_schemas, + scoped_test_schema, +) from omop_alchemy.cdm.model.vocabulary import ( Concept, Concept_Class, @@ -34,6 +35,9 @@ Vocabulary, ) from omop_alchemy.cross_database import cdm_sessionmaker +from orm_loader.backends import resolve_backend +from orm_loader.config import OrmLoaderConfig +from orm_loader.helpers import Base from omop_graph.cli import relationship_classification from omop_graph.config import OmopGraphConfig @@ -44,8 +48,6 @@ ) from omop_graph.graph.kg import KnowledgeGraph -from fixtures.helpers import VOCAB_TABLES - pytestmark = [pytest.mark.postgresql, pytest.mark.db_dialect] META_CONCEPT_ID = 0 @@ -56,7 +58,7 @@ # Relationship/Concept_Relationship aren't in the shared minimal vocab # bootstrap, but are exactly what the split-connection merge is testing. -_VOCAB_TABLES = VOCAB_TABLES + (Relationship.__table__, Concept_Relationship.__table__) +_VOCAB_TABLES = VOCAB_TABLES + (table_of(Relationship), table_of(Concept_Relationship)) # Postgres has no cross-database inline FK, so RelationshipMapping's FK to # relationship.relationship_id can't be created as DDL across genuinely @@ -115,31 +117,39 @@ def split_engines() -> Iterator[_Engines]: isolated_test_database(OmopGraphConfig, "test_cdm_db_pg") as primary_db, isolated_test_database(OrmLoaderConfig, "test_orm_db_pg") as vocab_db, ): - name = primary_db.resolved.name + vocab_resolved = require_cdm(vocab_db.resolved) + name = require_cdm(primary_db.resolved).name resolver = Resolver.from_active_config() resolver = resolver.with_overrides( databases={ name: resolver.config.databases[name].model_copy( - update={"vocab_connection": vocab_db.resolved.connection.name} + update={"vocab_connection": vocab_resolved.connection.name} ) } ) - split = resolver.resolve_database(name) + split = require_cdm(resolver.resolve_database(name)) with scoped_test_schema(split, prefix="vocab_split", resolver=resolver) as scoped: - primary_engine, vocab_engine = scoped.resolved.create_engines() + scoped_resolved = require_cdm(scoped.resolved) + primary_engine, vocab_engine = scoped_resolved.create_engines() # Vocab tables are created through the vocab server's own resolved: # create_all on the split vocab engine also emits the PRIMARY-tagged # predicatekind type, whose schema only exists on the primary server. vocab_schema = scoped.schemas[Role.VOCAB] - vocab_only = resolve_with_role_schemas( - vocab_db.resolved, {role: vocab_schema for role in vocab_db.resolved.schema_tags()} + vocab_only = require_cdm( + resolve_with_role_schemas( + vocab_resolved, {role: vocab_schema for role in vocab_resolved.schema_tags()} + ) ) vocab_setup_engine, _ = vocab_only.create_engines() try: _shadow_metadata.create_all(primary_engine) Base.metadata.create_all(vocab_setup_engine, tables=_VOCAB_TABLES, checkfirst=True) _seed(primary_engine, vocab_setup_engine) - yield _Engines(primary=primary_engine, vocab=vocab_engine, resolved=scoped.resolved) + yield _Engines( + primary=primary_engine, + vocab=vocab_engine, + resolved=scoped_resolved, + ) finally: vocab_setup_engine.dispose() primary_engine.dispose() @@ -184,8 +194,16 @@ def _seed(primary_engine: sa.Engine, vocab_engine: sa.Engine) -> None: valid_start_date=_TODAY, valid_end_date=_FAR_FUTURE, ), - Domain(domain_id="Metadata", domain_name="Metadata", domain_concept_id=META_CONCEPT_ID), - Domain(domain_id="Condition", domain_name="Condition", domain_concept_id=META_CONCEPT_ID), + Domain( + domain_id="Metadata", + domain_name="Metadata", + domain_concept_id=META_CONCEPT_ID, + ), + Domain( + domain_id="Condition", + domain_name="Condition", + domain_concept_id=META_CONCEPT_ID, + ), Vocabulary( vocabulary_id="OMOP", vocabulary_name="OMOP", @@ -334,7 +352,9 @@ def test_concept_id_by_code_resolves_against_the_vocab_connection(split_engines: assert kg.concept_id_by_code("SNOMED", "SUBJ") == SUBJECT_CONCEPT_ID -def test_concept_ids_by_label_resolves_against_the_vocab_connection(split_engines: _Engines) -> None: +def test_concept_ids_by_label_resolves_against_the_vocab_connection( + split_engines: _Engines, +) -> None: kg = _split_kg(split_engines) assert kg.concept_ids_by_label("Subject concept") == (SUBJECT_CONCEPT_ID,) diff --git a/uv.lock b/uv.lock index d5b3df3..cf3bb52 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", @@ -2301,8 +2301,8 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.0.0" }, { name = "rich", specifier = ">=13.0" }, { name = "rich", marker = "extra == 'dev'", specifier = ">=14.2.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.10" }, - { name = "ty", marker = "extra == 'dev'", specifier = ">=0.0.59" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.17.1" }, + { name = "ty", marker = "extra == 'dev'", specifier = "==0.0.90" }, { name = "typer", specifier = ">=0.12" }, { name = "typing-extensions", specifier = ">=4.15.0" }, ] @@ -3337,27 +3337,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" }, - { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" }, - { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" }, - { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" }, - { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" }, - { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" }, - { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" }, - { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" }, - { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" }, - { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" }, - { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" }, - { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" }, +version = "0.17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e7/e6d5749983b020e6cb1055abec0a6f361a8e94f0184cdcf56d4760995f02/ruff-0.17.1.tar.gz", hash = "sha256:5bb796c5112e9fb9527f2ef2faa0a130d6312d19de92bcfaddd70dbb2d41020f", size = 5323349, upload-time = "2026-10-15T15:23:18.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/78/90c46ca6250709f7c37e397b60d06061adee8d0f7d13959ed6a8e49b14fd/ruff-0.17.1-py3-none-linux_armv6l.whl", hash = "sha256:1796c41eafec66f4635125dd1e074a7aa27b8d20c187d484360acf11d247fc4a", size = 10147528, upload-time = "2026-10-15T15:22:33.667Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d2/a2e28bc35b083ae51fa07dfba5280a667c8b01ecaee7802fc8849dcd5c46/ruff-0.17.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8eadb09a788010be29c895418bdfa513e4e1c65e64b94e5b19e4cd381f6cc7fb", size = 10225974, upload-time = "2026-10-15T15:22:36.328Z" }, + { url = "https://files.pythonhosted.org/packages/8e/71/7a9029a81c8206ee01a98f6cfeeb56e6c095af86857581a37045c46d7e94/ruff-0.17.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:29b454f18ca3eeaf37fbf140841dcf27d36109e29dee9aae8acecb68c26c6f74", size = 9270361, upload-time = "2026-10-15T15:22:38.651Z" }, + { url = "https://files.pythonhosted.org/packages/c0/8c/e075e9c917cb0575297880c96768304e881240171c870cbd8d9b7b54bec2/ruff-0.17.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872e03dd231744d6c5ec5c8af6be52b71323fc6ee096e4c8b34d9f844ee3b978", size = 9938300, upload-time = "2026-10-15T15:22:41.284Z" }, + { url = "https://files.pythonhosted.org/packages/87/27/0b4bb926a49de2383f161272b4d863571a4c7ecdbf595a6bc58813a13367/ruff-0.17.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c0d6cb5f8fbac77eec9af49f4fc0eaf856b05545e372b0694d24b4e76b1c640", size = 9948464, upload-time = "2026-10-15T15:22:43.681Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/518d57e0e02aecb8ea4acac6b57b51a53173d25fa734952bfdd7d531e88e/ruff-0.17.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78beced4a3c94dfedbee4b999b95b6a27b35820fdc0afbacc6d37a8da26c1cd1", size = 10638639, upload-time = "2026-10-15T15:22:46.081Z" }, + { url = "https://files.pythonhosted.org/packages/01/82/d89a4e498ade59e6cee52d67f8da569019372212b87468c1fdeae884601c/ruff-0.17.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7feefc63921202894f0e158b0e32f45a5950d70a7fb693ccb9264816c25b1c05", size = 11337238, upload-time = "2026-10-15T15:22:48.894Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7c/3b0fdef9352656317c977086fe1d802902406e4f2a0d8578da90c0e6d1c5/ruff-0.17.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684154861a819bf1f433669d2a0ce9c3e54677c65b8ebb866cc2a7ec86345785", size = 10896327, upload-time = "2026-10-15T15:22:51.45Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/5566290219708ce2928f3a29c4f843a1185b341ec6111c4631a33c5ceced/ruff-0.17.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ef98e51de8ef38082f90653ceeb84944bd7673783eea13e1647c5b9b064189", size = 10350811, upload-time = "2026-10-15T15:22:54.401Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d2/ca8462dabb3c413990d49703ba306c30c862195a75448d080fa8fcce9775/ruff-0.17.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8f9aa5b5bd5fa1937803ea609c8f153b8de39bf3e0e149897ae34b0c5b52f467", size = 10569994, upload-time = "2026-10-15T15:22:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ad/dd1864c1f1db17c8cfb297ce70b352388e6977620f66f7182c52855407df/ruff-0.17.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:80e53b0e5faa3c5667f035daaa032893ae42b7cfe708c886d39ed131e54688ab", size = 10059672, upload-time = "2026-10-15T15:23:00.084Z" }, + { url = "https://files.pythonhosted.org/packages/65/45/3f56cf1d2168c2165cdd5ac6f9668aee66a4cfdd4caf2b2cc5ccb2b1d568/ruff-0.17.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:056c5d5fa0ac790051ae165d7035c7bf956d02de2db557f2e9b31d877a505fca", size = 9947803, upload-time = "2026-10-15T15:23:03.132Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4b/5457cb2be4c1d1ea5e919558131a0f197a4a454a0530d9e30820e8206e4c/ruff-0.17.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd8f5a55b10f371642eacd0e8665a42a8b97da15c9a7187c79e697b77b5e0ce4", size = 10351807, upload-time = "2026-10-15T15:23:05.628Z" }, + { url = "https://files.pythonhosted.org/packages/49/fb/db15b4f8fa24a1e36b99e1401f42ef6df16ee299a48670d5d262b771e7d7/ruff-0.17.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ce05e6f3973611d9550a600e04bb531061e368baba31610eb57c1509a8f21a0a", size = 10765716, upload-time = "2026-10-15T15:23:08.092Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e8/1e186578076aff7a80735f625005fde678412ab609cd323ed182eb7d6b87/ruff-0.17.1-py3-none-win32.whl", hash = "sha256:1de0030a8aa78c65c5fecb84b2e5c390ac684c0452875a02afc7c083d49a496a", size = 9895093, upload-time = "2026-10-15T15:23:10.582Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2e/da3d9589bae8e8b58f3a48bd5bc0b980232fb14a83bf02c3be7660f87c1b/ruff-0.17.1-py3-none-win_amd64.whl", hash = "sha256:5df76f16580fab58d0f6c98a3ac254a4abceb7b2489b8b678335869cff77b3d7", size = 10097955, upload-time = "2026-10-15T15:23:13.309Z" }, + { url = "https://files.pythonhosted.org/packages/df/c9/0f2a4dea06a5e12c1191ee3d96b1e1233797d0d5e18f97a2b909a95a9836/ruff-0.17.1-py3-none-win_arm64.whl", hash = "sha256:42400b9a6ff8515ff27796f07f92e0195be011c86e81e381606f58ea5502fef4", size = 9853542, upload-time = "2026-10-15T15:23:16.05Z" }, ] [[package]] @@ -3746,27 +3746,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.61" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/63/6944925d0fe9a4bb9cc744e6c045a42bbd2ee4654c103190674577a36c3f/ty-0.0.61.tar.gz", hash = "sha256:acbf0d914cc7e2e57ccc440036af36114819e2a604a5ffb554e72e4ca7dd65a2", size = 6234957, upload-time = "2026-07-18T01:39:54.696Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/cf/044f31523e2768e3e64b0ca2ec32f70b3a731d4a2caa6ea110baf26e251c/ty-0.0.61-py3-none-linux_armv6l.whl", hash = "sha256:148779b8675eac93f40ec58bd70037fe67537117f20a23272264f8f136d41336", size = 11891448, upload-time = "2026-07-18T01:39:18.449Z" }, - { url = "https://files.pythonhosted.org/packages/d2/55/558cfe76b65d91d1854bbfac336020bd42fd887caa632d845d13c0c539eb/ty-0.0.61-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:08217382b3385808ee7288501ea3214b32631b08d1fd091ece6799b0c95264c5", size = 11602442, upload-time = "2026-07-18T01:39:20.914Z" }, - { url = "https://files.pythonhosted.org/packages/27/be/78c0ae6634cd606a68e5b46b338db427a48a1800c96a749b2d2f7a702e03/ty-0.0.61-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d99c729011b47dec20e78a32ac9c8f6defd4cf62f7bb851bbccf70dde6cee50", size = 11125286, upload-time = "2026-07-18T01:39:22.893Z" }, - { url = "https://files.pythonhosted.org/packages/a4/18/a40793962f1b6337938ddb0bca7496b54e70879e23b4d2cc8dfd7e5d1af3/ty-0.0.61-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cda607978ae271b77e51c947663218bce635c3507e256865444b10c37cdb60d", size = 11663403, upload-time = "2026-07-18T01:39:25.017Z" }, - { url = "https://files.pythonhosted.org/packages/98/c1/7879244da5b30407dc368946d36be5024380073408b079f144ffe034030e/ty-0.0.61-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d78f160a0f9434d570cdcdbc4dafba1f6aac3c47a32f9f63995b3cb55ffe4b6", size = 11715250, upload-time = "2026-07-18T01:39:27.045Z" }, - { url = "https://files.pythonhosted.org/packages/35/c4/8a4637cd58abd37f315dd515e24c582986cb1bfdf2edc4786882f5a4f69a/ty-0.0.61-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09aeab4800b36e93e4ce918699004da642d74988cac920b7592a6a2b9be6611c", size = 12393876, upload-time = "2026-07-18T01:39:29.197Z" }, - { url = "https://files.pythonhosted.org/packages/27/4b/27e7c640b1272743503229aa17ae2167a538040c4716a2fa1777c2b34fea/ty-0.0.61-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dccc8136df44142a109953a168be17b4915c99876b047d0b6672c31dae939bdf", size = 12958187, upload-time = "2026-07-18T01:39:31.308Z" }, - { url = "https://files.pythonhosted.org/packages/3a/f5/70eaaefb6081fb0a8115cff66fbfaa20dafac8c646df2477adad95a59de2/ty-0.0.61-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:220760c2d13a887d027ee1093172c24ac35b6e634805329c93a30908ae4d3f5c", size = 12560101, upload-time = "2026-07-18T01:39:33.35Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/17bae3b6429b5c479dc6c1e344d34e1f79efbc27531f15f3ee5b5da63745/ty-0.0.61-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:effefbb89da7128d18059529d1c2ea390fe7f1f3882690d257ca2143d49a0c34", size = 12225389, upload-time = "2026-07-18T01:39:35.436Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0e/2ac380ba20d6395542c8df1d6fa4f00e2aead784c2e6aaefa1e02ed0610c/ty-0.0.61-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ba8b28a5ef811d5bb6461e37d76110c06fd20487474865c323d3d18b08b972b2", size = 12548403, upload-time = "2026-07-18T01:39:37.556Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e5/7da4b73e825e1a9808c26d68b0156e9a37aede1846191210dfffb8c64042/ty-0.0.61-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:88ecd6d9b05e8174b1860dac9bd3e188d6cef5702b0d3239fd9f94f6ac73a29d", size = 11621813, upload-time = "2026-07-18T01:39:39.919Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3c/5b58015e998cd0d89b17a463b6321421457d86d987574e8dac65ddfceba3/ty-0.0.61-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb0cdfe4c48542ffb9a1139825dfa3d4aae49e96e966682ef7da762ab97831ff", size = 11734101, upload-time = "2026-07-18T01:39:42.097Z" }, - { url = "https://files.pythonhosted.org/packages/a6/21/294f4cc819b7b12ed659fd860e5cdfbd592d4c768c8f23596685dbc43e6b/ty-0.0.61-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dff03873c0c3d0b44738f8b6d403b0756a31cf54c65136397df7624c6159b1f0", size = 11988401, upload-time = "2026-07-18T01:39:44.183Z" }, - { url = "https://files.pythonhosted.org/packages/2e/26/0f96f79fdac118521a9771e9eef3f9b3f447d647b2c77953e80a1715c7e8/ty-0.0.61-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a9210e80e3d41c1dfc751e9e8e0980272f475031fafd0fb0f48aee233c78da03", size = 12330624, upload-time = "2026-07-18T01:39:46.662Z" }, - { url = "https://files.pythonhosted.org/packages/e3/08/1e62d1bca5c0cebdc7a34db1f4b61557aab85961cedd56953dd2c32d3e66/ty-0.0.61-py3-none-win32.whl", hash = "sha256:e3e1fe06f49a5492a922a5df2739834aa5ee978c7dd10414119dc8755cc40c9c", size = 11313991, upload-time = "2026-07-18T01:39:48.761Z" }, - { url = "https://files.pythonhosted.org/packages/26/f1/d8e33b3aeb36b73d81ae34d10e46ec4abf506d68f4e0a1491a76a593dd42/ty-0.0.61-py3-none-win_amd64.whl", hash = "sha256:25f2291169e0298fcdbba1b1fea64f8207a6c1908dddef32346fd5e3e6ac9221", size = 12311717, upload-time = "2026-07-18T01:39:50.881Z" }, - { url = "https://files.pythonhosted.org/packages/e1/14/7caec26d93a943c0e7d15eb7374644508d08cbd387d112b722b12d14e044/ty-0.0.61-py3-none-win_arm64.whl", hash = "sha256:3e496f7698bc4b5bbb1eb66d8b5799ba87596d88d36604ca359083893fa2fc49", size = 11693485, upload-time = "2026-07-18T01:39:52.73Z" }, +version = "0.0.90" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/b1/613b5534624442a9118828d7a9e54d50b16f0f5c8b67c80eb6623f33b921/ty-0.0.90.tar.gz", hash = "sha256:25cef63ec07a0254c2f5d885a63b001d0bfa437b20caa0a408f4f52e66b133e7", size = 7996206, upload-time = "2026-10-20T14:25:19.477Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/33/0e3395afa2c31c0e8e91547629858d2a5de213c69af237e6e50f37545565/ty-0.0.90-py3-none-linux_armv6l.whl", hash = "sha256:0dedb54977b40bc86638fec2c90eefd593f3a6683003a86b7e6cb67c17bcc476", size = 14281945, upload-time = "2026-10-20T14:24:21.018Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/5d632b2f88d5c18991a4936da8643c7f204cfbc4980f8c7cd9080af988ad/ty-0.0.90-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3d7c30ceaac76dfbfcf36b72d36272d21c3898e98fc4de296aa9cbae956dec9d", size = 13786355, upload-time = "2026-10-20T14:24:24.392Z" }, + { url = "https://files.pythonhosted.org/packages/79/08/0d2f37bb4f766ab9630f01b07f4d11f1f7a6a6188f920d5ca9fa8a4676b1/ty-0.0.90-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c0b7e4d9ae0939294f0a18652de9ad7d81da2d7f567cf93f1edda1263008c124", size = 12856535, upload-time = "2026-10-20T14:24:27.386Z" }, + { url = "https://files.pythonhosted.org/packages/e4/77/70658f6b80c345ae0c12f2192a5cc89b8ca13eccfe06616b0fac3806c261/ty-0.0.90-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c4a095a86a132df374e652894fe08392aeb1601d6347e1eb4b58808094a302", size = 13696342, upload-time = "2026-10-20T14:24:30.413Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3e/693049f93653d2f6412bb0c2c0af1d01675e31643af29bef7177d3ff7e79/ty-0.0.90-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e24a8aa07c05d9d14648289e79a88b7c7c2c522c58b53bb5f466a6ba4704e22", size = 14014180, upload-time = "2026-10-20T14:24:33.783Z" }, + { url = "https://files.pythonhosted.org/packages/27/08/19e373f97501eaadc8fc42e8c07cd54a64e375fbcf1470797be13590d5ba/ty-0.0.90-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40c3ea8fc1dfcf7502f71b93b12e80da1f7a8a5fd7e55ec9feabcf4ac39f72ea", size = 14935189, upload-time = "2026-10-20T14:24:37.459Z" }, + { url = "https://files.pythonhosted.org/packages/33/54/3331984f2c896a7383b6b18c86e968a63dd046baf503119a8e8e7682c752/ty-0.0.90-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1af75532bbe3e305089c30806fa5e804f8b01de231c1fa07918f7f3b335626da", size = 15311482, upload-time = "2026-10-20T14:24:40.87Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f3/179acf8d730c2c864767ffcc9081f5ea6fdb1b34ef711f1bd226200e9cff/ty-0.0.90-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f09449b34d837a044dab043aeb2b4b6ff2d2cef5eaf8116c7c19d34cc2a16a21", size = 15278844, upload-time = "2026-10-20T14:24:44.539Z" }, + { url = "https://files.pythonhosted.org/packages/71/7e/661bc2fa20e0ac84c4d5f92e4c098ec667a0e920851a6d10c10a0251f0dc/ty-0.0.90-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc868ffa7dfcb3169eea3ba7d634e5a3283c7133fb932c6e47a2e16620d26ca5", size = 14558774, upload-time = "2026-10-20T14:24:47.814Z" }, + { url = "https://files.pythonhosted.org/packages/f4/2e/3f65f61f4545f75255373be7646a606e004bb4cdc851e236bf17ca94bdc2/ty-0.0.90-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e2405206533dc896a2b711e781f2955d5883d85536f4df0ac262fca6517103c2", size = 14788158, upload-time = "2026-10-20T14:24:51.223Z" }, + { url = "https://files.pythonhosted.org/packages/1e/10/3c91a529591667b89d63c86806bfe2ac56d6989c7e0a09ee0964b7512deb/ty-0.0.90-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0dca4a29e8778285d2fc204af19254571486106fd14aed60769bfd272c795f6f", size = 13687177, upload-time = "2026-10-20T14:24:54.611Z" }, + { url = "https://files.pythonhosted.org/packages/21/b2/1b6d48606c67614bfb0c8e61a5d67a053ca1e3c5956ddb584f8569b8dc3d/ty-0.0.90-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d004b8e4e898fead929eceea669b73936444671748e0dd44e624cc109346c0dd", size = 14027780, upload-time = "2026-10-20T14:24:59.045Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c4/a0771debf725312524ca1434f8a146d37e573d45257ca4bebb9cc8e35bbc/ty-0.0.90-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a6be182da4281b4581c4a1b27c6a0caebeed7212e4bdc98c6b8fe0d9bc36dea0", size = 14355890, upload-time = "2026-10-20T14:25:03.034Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4c/036724b6936ffc38e28ff5e20ab7972d609c65a3dd9645f3d10f57cfd16f/ty-0.0.90-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0ba6fe0a24ff4cbc5034f3aa1c88b3ad1397315dd3369357e694fd157a11864e", size = 14692306, upload-time = "2026-10-20T14:25:07.461Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2a/c74fd5fc3fb5b046fe06a9067cabf5417b73e28249dc9af90ef72700f8fd/ty-0.0.90-py3-none-win32.whl", hash = "sha256:4c46a79caf84c4da0f7293e16bd769bd467c7cbc38015cac7608ab4e5ec8ba9c", size = 13235434, upload-time = "2026-10-20T14:25:10.545Z" }, + { url = "https://files.pythonhosted.org/packages/d2/54/be5cd6b17d8087a5e4c26892311c07e39d42b66c925b0f06cfc51a9ce83b/ty-0.0.90-py3-none-win_amd64.whl", hash = "sha256:8d77c16d20201b7a07ccaafb610eaf60488783dbba9865c4f9182e584f1426f8", size = 13855668, upload-time = "2026-10-20T14:25:13.648Z" }, + { url = "https://files.pythonhosted.org/packages/33/68/6dea089a897e8b314504eecc2633de3c8fe14f70e71a35606d14a2ba169e/ty-0.0.90-py3-none-win_arm64.whl", hash = "sha256:bfb63e74eeedb0a29d49cf58d611c540bfdfc0975dfce26efc6575bcf439208d", size = 13330853, upload-time = "2026-10-20T14:25:16.642Z" }, ] [[package]]