Skip to content

Commit 5f47d04

Browse files
committed
Merge dev: oidm-common-0.2.5, anatomic-locations-0.2.4, findingmodel-1.0.3, oidm-maintenance-0.2.3
2 parents 5e4c9dd + 379da34 commit 5f47d04

20 files changed

Lines changed: 1606 additions & 404 deletions

File tree

.claude/rules/anatomic-locations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Anatomic location ontology navigation: hierarchy traversal, laterality variants,
1818
- Auto-open: `_ensure_connection()` opens the connection on first use; no explicit `open()` required
1919
- Hybrid search (FTS + vector) with RRF fusion
2020
- Laterality variant generation (left, right, bilateral)
21+
- **Hydration pattern**: All location queries go through `_fetch_locations(conn, suffix_sql, params)` — a single entry point that appends WHERE/ORDER to `_LOCATION_SELECT` and returns hydrated objects. `_LOCATION_SELECT` uses DuckDB correlated subqueries to pull codes, synonyms, and references inline in one query (DuckDB optimizes these into a join plan). `_build_location(row)` is a pure transform using `AnatomicLocation.model_validate(data)` — Pydantic handles enum coercion, nested-model construction, and extra-key ignoring automatically. `_get_locations_by_ids(conn, ids)` wraps `_fetch_locations` and re-sorts to preserve input order.
22+
- **Schema evolution safety**: `_LOCATION_SELECT` uses `SELECT al.* EXCLUDE (search_text, vector)`. Only exclude columns present in ALL schema versions. `synonyms_text` (added v0.2.3) is NOT excluded — old schemas lack it and `EXCLUDE` raises a `Binder Error` for missing columns. Extra columns from `SELECT *` are silently ignored by `model_validate`. Correlated subqueries alias codes/synonyms/refs inline — also safe on old schemas since all 3 related tables exist in every version.
2123

2224
## CLI (`anatomic-locations`)
2325

.claude/rules/index-duckdb.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,29 @@ See Serena `duckdb_architecture` for the 3-step process:
4747
- `FINDINGMODEL_DB_PATH` – override auto-download location for finding models
4848
- `ANATOMIC_DB_PATH` – override for anatomic locations database
4949

50+
## Row Hydration Pattern
51+
52+
Both `AnatomicLocationIndex` and `FindingModelIndex` use **named dict access** for row hydration, provided by two helpers on `ReadOnlyDuckDBIndex`:
53+
54+
```python
55+
# Single row — returns dict[str, object] | None
56+
row = self._execute_one(conn, "SELECT * EXCLUDE (search_text, vector) FROM anatomic_locations WHERE id = ?", [id])
57+
if row is not None:
58+
description = str(row["description"]) # Named access — schema-change safe
59+
60+
# Multiple rows — returns list[dict[str, object]]
61+
rows = self._execute_all(conn, "SELECT * EXCLUDE (search_text, vector) FROM anatomic_locations WHERE region = ?", [region])
62+
locations = [self._row_to_location(row) for row in rows]
63+
```
64+
65+
**Why this matters:** Positional indexing (`row[N]`) breaks silently when columns are added, removed, or reordered. Named dict access via `cursor.description` is immune to schema evolution.
66+
67+
**`SELECT * EXCLUDE (search_text, vector)` pattern:**
68+
- EXCLUDE `search_text` and `vector` — large columns not needed for object construction; exist in **all** schema versions
69+
- Do NOT EXCLUDE `synonyms_text` — it was added in v0.2.3 and is absent from older production DBs; EXCLUDEing it would raise a `Binder Error` on old schemas. Named dict access simply won't find the key when it's absent.
70+
71+
**Bulk methods:** Use `_execute_all()` + direct `_row_to_location()` (single query per method, no N+1 per-row re-fetch).
72+
5073
## Serena References
5174

5275
- `duckdb_architecture` – consolidated design decisions and patterns

.claude/rules/oidm-common.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Shared infrastructure (DuckDB auto-download, embedding client, database utilitie
1616
- Database auto-download via `pooch` with checksum verification
1717
- OpenAI embedding client (optional dependency via `[openai]` extra)
1818
- Protocol-based backend pattern for extensibility
19+
- **`_execute_one(conn, sql, params)`** — execute a query and return the single result row as a `dict[str, object] | None`. Uses `cursor.description` to map column names; eliminates positional indexing brittleness. Subclasses use this instead of `.fetchone()` + `row[N]`.
20+
- **`_execute_all(conn, sql, params)`** — execute a query and return all result rows as `list[dict[str, object]]`. Same column-name mapping. Use for bulk hydration (no N+1 per-row re-fetch).
1921

2022
## Serena References
2123
- `duckdb_development_patterns` – DuckDB conventions and auto-download logic

.claude/rules/oidm-maintenance.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ Maintainer-only tools for building and publishing OIDM databases (finding model
2222
- DuckDB index building with HNSW/FTS
2323
- Checksum generation for pooch auto-download
2424

25+
## Schema Change Checklist
26+
When modifying the `anatomic_locations` or `finding_models` table schema in `build.py`:
27+
1. **No positional index updates needed** — both `AnatomicLocationIndex._row_to_location()` and `FindingModelIndex._fetch_index_entry()` use named dict access via `_execute_one`/`_execute_all` from `ReadOnlyDuckDBIndex`. New columns are automatically available by name; removed columns will raise `KeyError` only if code references them.
28+
2. Rebuild the test fixture: `uv run python packages/oidm-maintenance/scripts/build_anatomic_test_fixture.py`
29+
3. Run `task test` and verify all tests pass.
30+
2531
## Serena References
2632
- `index_duckdb_migration_status_2025` – index build pipeline
2733
- `duckdb_development_patterns` – DuckDB conventions

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,37 @@ All notable changes to this project will be documented in this file.
66
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
77
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## oidm-common 0.2.5 - 2026-02-28
10+
11+
### Added
12+
13+
- `_execute_one` and `_execute_all` helpers on `ReadOnlyDuckDBIndex` — return named dicts via `cursor.description`, eliminating positional `row[N]` brittleness across all subclasses
14+
15+
## anatomic-locations 0.2.4 - 2026-02-28
16+
17+
### Changed
18+
19+
- Rewrote hydration with DuckDB correlated subqueries: codes, synonyms, and references fetched inline in a single query per operation (was 1+3N queries); `model_validate` replaces 95 lines of manual field extraction; `index.py` reduced from 1186 to 915 lines
20+
- FTS search now matches synonyms (requires rebuilt database from oidm-maintenance 0.2.3)
21+
22+
### Fixed
23+
24+
- `_ref()` now requires both `id` and `display` non-null — previously a NULL display field caused `ValidationError` during model validation
25+
- Malformed STRUCT entries in `containment_children`/`partof_children` are now filtered before model validation, preserving prior null-tolerant behavior
26+
27+
## findingmodel 1.0.3 - 2026-02-28
28+
29+
### Fixed
30+
31+
- `_fetch_index_entry` now uses named-dict access via `_execute_one` — eliminates positional `row[N]` brittleness
32+
33+
## oidm-maintenance 0.2.3 - 2026-02-28
34+
35+
### Added
36+
37+
- `synonyms_text` column added to `anatomic_locations` schema and FTS index — synonyms are now keyword-searchable (requires database rebuild)
38+
- Roundtrip build→read integration tests using `AnatomicLocationIndex`
39+
940
## oidm-common 0.2.4 - 2026-02-26
1041

1142
### Fixed

0 commit comments

Comments
 (0)