diff --git a/openmed/.jules/bolt.md b/openmed/.jules/bolt.md new file mode 100644 index 0000000..8fcb703 --- /dev/null +++ b/openmed/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-24 - Cache alias lookups for clinical section detection +**Learning:** In `openmed/openmed/clinical/sections/detect.py`, `_alias_lookups` was repeatedly parsing section header lexicons and building dictionaries for each line of a document. For large, many-line documents, this caused significant slowdowns because the section lookup computation is deterministic based on the language. Additionally, since the return values needed caching, a regular dict couldn't be safely cached due to mutability. +**Action:** When caching dictionaries generated from configuration or lexicons, use `@functools.lru_cache` and ensure the return type is explicitly wrapped in `types.MappingProxyType` to prevent any downstream modifications to the cached object. diff --git a/openmed/openmed/clinical/sections/detect.py b/openmed/openmed/clinical/sections/detect.py index 8716746..3293ed0 100644 --- a/openmed/openmed/clinical/sections/detect.py +++ b/openmed/openmed/clinical/sections/detect.py @@ -2,6 +2,8 @@ from __future__ import annotations +import functools +import types from collections.abc import Iterable, Mapping from dataclasses import dataclass from typing import Any @@ -244,6 +246,7 @@ def _is_underline(text: str) -> bool: return len(stripped) >= 3 and set(stripped) <= _UNDERLINE_CHARS +@functools.lru_cache(maxsize=16) def _alias_lookups(language: str | None) -> tuple[tuple[str, Mapping[str, str]], ...]: languages = ( tuple(dict.fromkeys((get_section_lexicon(language).language, "en"))) @@ -253,14 +256,15 @@ def _alias_lookups(language: str | None) -> tuple[tuple[str, Mapping[str, str]], return tuple((code, _aliases_for_language(code)) for code in languages) -def _aliases_for_language(language: str) -> dict[str, str]: +@functools.lru_cache(maxsize=32) +def _aliases_for_language(language: str) -> Mapping[str, str]: lexicon = get_section_lexicon(language) aliases: dict[str, str] = {} for label, headers in lexicon.sections.items(): aliases[normalize_section_header(label)] = label for header in headers: aliases[normalize_section_header(header)] = label - return aliases + return types.MappingProxyType(aliases) def _dedupe_hits(hits: Iterable[_HeaderHit]) -> tuple[_HeaderHit, ...]: