Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5e625df
Absorbing the old files into new cli_ files, updated tests and docs
nicoloesch May 21, 2026
9035207
Big rework regarding backends and CLI, removal of standalone fulltext…
nicoloesch May 22, 2026
a3f4cc3
Wrap common interface with omop_command
nicoloesch May 24, 2026
9f56015
Split up cli_schema into various smaller ones
nicoloesch May 24, 2026
d93cb54
Utilise backend_supports in schema_reconcile instead of dialect
nicoloesch May 24, 2026
46f435d
Get rid of redundant IndexAction
nicoloesch May 24, 2026
00e106d
Update the docs and docstrings
nicoloesch May 24, 2026
51473d4
Ruff check: Remove unsused imports, other code issues
nicoloesch May 24, 2026
c8097f6
Clean up mkdocs and utilise correct files created
nicoloesch May 24, 2026
0533caa
Accelerate load with pool and reload of conn
nicoloesch May 25, 2026
2f92a5d
Updated CLI for vocab ingestion and speed
nicoloesch May 27, 2026
624c936
First pass inclusion of oa-configurator
nicoloesch May 27, 2026
91cfd15
Absorb DB connection config, update docs for config
nicoloesch May 28, 2026
dd4d8c3
Updated docs to reflect new state
nicoloesch May 28, 2026
5765561
Test configuration inclusion
nicoloesch May 28, 2026
88050bd
Have correct resource that this package provides
nicoloesch May 28, 2026
b6fb46e
Remove dotenv references, have CDM utils for building connections for…
nicoloesch May 28, 2026
e0f3837
Global TOOL_NAME param for re-usability
nicoloesch May 28, 2026
d8394c4
Remove logger_config.py since it has been absorbed by the respective …
nicoloesch May 28, 2026
8c7eb1e
Absorb the db into the config to overwrite the engine configuration
nicoloesch May 28, 2026
97c5ae7
Remove legacy artifact
nicoloesch Jun 3, 2026
47de848
Additional docs for CDM context fn
nicoloesch Jun 3, 2026
b7ddad9
Updated with docker-compose
nicoloesch Jun 4, 2026
bb60d56
Add schema for CDM tables
nicoloesch Jun 4, 2026
8473af7
Updated CFG to not reconfigure
nicoloesch Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copy to .env and edit to override defaults. All vars below are optional;
# docker-compose uses the shown defaults when .env is absent.
OMOP_CDM_DB_USER=omop
OMOP_CDM_DB_PASSWORD=omop
OMOP_CDM_DB_NAME=omop_cdm
5 changes: 0 additions & 5 deletions .omop-maint.toml

This file was deleted.

3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM python:3.12-slim
RUN pip install --no-cache-dir ".[postgres]"
WORKDIR /workspace
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,43 @@ The API is stabilising, but some modules may change as real-world use cases expa
### Some additional background

This work builds on earlier research and tooling presented at the 2023 OHDSI APAC Symposium
> see [background paper](https://github.com/AustralianCancerDataNetwork/OMOP_Alchemy/blob/main/notebooks/ORMforResearchReadyData_APAC2023.pdf).
> see [background paper](https://github.com/AustralianCancerDataNetwork/OMOP_Alchemy/blob/main/notebooks/ORMforResearchReadyData_APAC2023.pdf).

---

## Configuration

OMOP Alchemy reads all database connection and schema settings from
[oa-configurator](https://github.com/AustralianCancerDataNetwork/oa-configurator).
No `.env` files or `ENGINE` environment variables are needed.

Run once after installation:

```bash
omop-config init
omop-config configure omop_alchemy
```

See [Configuration](docs/getting-started/configuration.md) for full details.

---

## Docker Compose

The included `docker-compose.yaml` provides a PostgreSQL database and a Python
container with the `[postgres]` extra pre-installed. Default credentials work out of the box:

```bash
docker compose up
```

The `python-alchemy` service runs `omop-config configure` at startup and writes
`~/.config/omop/config.toml` on the host on first start; subsequent starts skip
configuration automatically.

To override credentials, copy `.env.example` to `.env` and edit before starting:

```bash
cp .env.example .env
docker compose up
```
48 changes: 48 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
services:
omop-cdm-db:
image: postgres:16-alpine
restart: always
environment:
POSTGRES_USER: ${OMOP_CDM_DB_USER:-omop}
POSTGRES_PASSWORD: ${OMOP_CDM_DB_PASSWORD:-omop}
POSTGRES_DB: ${OMOP_CDM_DB_NAME:-omop_cdm}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- db_data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- omop-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${OMOP_CDM_DB_USER:-omop}"]
interval: 5s
timeout: 5s
retries: 5

python-alchemy:
build: .
restart: unless-stopped
depends_on:
omop-cdm-db:
condition: service_healthy
volumes:
- ${HOME}/.config/omop:/root/.config/omop
networks:
- omop-net
command: >
bash -c "
omop-config configure omop_alchemy --skip-if-configured
--conn-name cdm --dialect postgresql+psycopg
--host omop-cdm-db --port 5432
--user ${OMOP_CDM_DB_USER:-omop}
--password ${OMOP_CDM_DB_PASSWORD:-omop}
--database ${OMOP_CDM_DB_NAME:-omop_cdm} --cdm-schema omop &&
sleep infinity
"

networks:
omop-net:
name: omop-net

volumes:
db_data:
7 changes: 0 additions & 7 deletions docs/advanced/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ the immutability and interpretability of the underlying CDM tables.

---

## Read-Only Views

- [Views](views.md)

---

## Timelines & Longitudinal Analysis

- [Event Timelines](timelines.md)
Expand All @@ -23,4 +17,3 @@ the immutability and interpretability of the underlying CDM tables.

- [Backend Compatibility](backends.md)
- [PostgreSQL Full-Text Search](fulltext.md)
- [Query Patterns](query_patterns.md)
Empty file removed docs/advanced/query_patterns.md
Empty file.
108 changes: 108 additions & 0 deletions docs/advanced/timelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Patient Timelines

OMOP Alchemy includes a lightweight timeline layer that projects OMOP CDM ORM objects
into a **unified, time-ordered event stream** per patient.

It is primarily intended for feature construction and exploratory analysis — not for
production query pipelines where raw SQLAlchemy queries are more appropriate.

---

## Core concepts

### `EventTime`

A canonical temporal representation. Every clinical event has a start datetime; an end
datetime is optional. The `kind` property returns `"point"` or `"interval"`.

::: omop_alchemy.cdm.handlers.timeline.event_timeline.EventTime

---

### `EventValue`

The value associated with a clinical event — numeric, concept, string, or none.

::: omop_alchemy.cdm.handlers.timeline.event_timeline.EventValue

---

### `EventMapping`

Declares which ORM fields supply the concept, start/end datetimes, and value for a
particular CDM table. Subclasses of `ClinicalEvent` set `_mapping` to an `EventMapping`
instance at class level.

::: omop_alchemy.cdm.handlers.timeline.event_timeline.EventMapping

---

## The `ClinicalEvent` mixin

`ClinicalEvent` is a mixin that adds timeline behaviour to any CDM ORM class. It reads
`_mapping` to implement `event_time`, `event_value`, `event_metadata`, `to_dict`, and
`to_json`.

::: omop_alchemy.cdm.handlers.timeline.event_timeline.ClinicalEvent

---

## Concrete event classes

Three CDM tables are pre-wired with `EventMapping`s:

| Class | CDM table | Concept field | Value fields |
|-------|-----------|---------------|--------------|
| `Condition_Event` | `condition_occurrence` | `condition_concept_id` | — |
| `Measurement_Event` | `measurement` | `measurement_concept_id` | `value_as_number`, `value_as_concept_id`, `value_as_string` |
| `Drug_Exposure_Event` | `drug_exposure` | `drug_concept_id` | `quantity` |

::: omop_alchemy.cdm.handlers.timeline.event_timeline.Condition_Event

::: omop_alchemy.cdm.handlers.timeline.event_timeline.Measurement_Event

::: omop_alchemy.cdm.handlers.timeline.event_timeline.Drug_Exposure_Event

---

## `Person_Timeline`

Extends the `Person` ORM class with `.events` and `.timeline` properties. Requires an
active SQLAlchemy session (i.e. the object must have been loaded from a session, not
constructed in memory).

::: omop_alchemy.cdm.handlers.timeline.event_timeline.Person_Timeline

---

## Usage example

```python
from sqlalchemy.orm import Session
from omop_alchemy.cdm.handlers.timeline import Person_Timeline

with Session(engine) as session:
person = session.get(Person_Timeline, 42)
for event in person.timeline: # sorted by event_time.start
print(event)
print(event.to_dict())
```

---

## Extending to new tables

To add a new CDM table to the timeline, subclass both `ClinicalEvent` and the target ORM
class and set `_mapping`:

```python
from omop_alchemy.cdm.handlers.timeline.event_timeline import ClinicalEvent, EventMapping
from omop_alchemy.cdm.model.clinical import Procedure_Occurrence

class Procedure_Event(Procedure_Occurrence, ClinicalEvent):
_mapping = EventMapping(
concept_field="procedure_concept_id",
start_date_field="procedure_date",
start_datetime_field="procedure_datetime",
)
```
Empty file removed docs/advanced/views.md
Empty file.
Empty file removed docs/api/configuration.md
Empty file.
106 changes: 106 additions & 0 deletions docs/api/typing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Typing

OMOP Alchemy exposes a set of **Protocols and typed containers** for code that needs to
interact with CDM classes without coupling to specific ORM implementations.

These live in two modules:

| Module | Contents |
|--------|---------|
| `omop_alchemy.cdm.base.typing` | Runtime-checkable Protocols for structural checking |
| `omop_alchemy.cdm.model.typing` | Typed row containers |

---

## Protocols (`cdm.base.typing`)

### `HasConceptId`

Satisfied by any object with an integer `concept_id` attribute.

::: omop_alchemy.cdm.base.typing.HasConceptId
options:
heading_level: 4
show_root_heading: false
members: true

---

### `HasPersonId`

Satisfied by any object with an integer `person_id` attribute.

::: omop_alchemy.cdm.base.typing.HasPersonId
options:
heading_level: 4
show_root_heading: false
members: true

---

### `HasEpisodeId`

Satisfied by any object with an integer `episode_id` attribute.

::: omop_alchemy.cdm.base.typing.HasEpisodeId
options:
heading_level: 4
show_root_heading: false
members: true

---

### `DomainSemanticTable`

Structural protocol for CDM ORM classes that participate in domain validation. A class
satisfies this protocol if it has `__tablename__`, `__mapper__`, `__expected_domains__`,
and a `collect_domain_rules()` classmethod.

::: omop_alchemy.cdm.base.typing.DomainSemanticTable
options:
heading_level: 4
show_root_heading: false
members: true

---

### `ClinicalEvent` (Protocol)

Minimal protocol for ORM rows that represent a clinical event — a concept, a person, a
start date, and an optional end date. Used as the structural contract for domain-level
utilities that operate across multiple CDM tables.

!!! note
The concrete mixin of the same name lives in
`omop_alchemy.cdm.handlers.timeline.event_timeline`. The Protocol here is the
structural interface; the mixin there is the implementation.

::: omop_alchemy.cdm.base.typing.ClinicalEvent
options:
heading_level: 4
show_root_heading: false
members: true

---

### `ConceptResolver`

Protocol for objects that can look up whether a set of concept IDs are standard.

::: omop_alchemy.cdm.base.typing.ConceptResolver
options:
heading_level: 4
---

## Typed row containers (`cdm.model.typing`)

### `ConceptRow`

A frozen dataclass representing the core fields of a concept lookup row. Used where a
lightweight, hashable concept record is preferable to a full ORM object.

::: omop_alchemy.cdm.model.typing.ConceptRow
options:
heading_level: 4
show_root_heading: false
members: true
Loading
Loading