Skip to content

Commit

Permalink
endpoints to update person cluster on patient (#101)
Browse files Browse the repository at this point in the history
## Description
Adding two new endpoints to support updating the Person cluster a
Patient belongs to. One is for assigning the Patient to a specific
cluster, and another is for assigning them to a new cluster.

## Related Issues
closes #96 

## Additional Notes
- `POST /patient/<ref-id>/person` takes an empty payload, creates a new
Person cluster, assigns the Patient to that cluster and returns back
reference ids for both the Patient and Person.
- `PATCH /patient/<ref-id>/person` takes a person ref id payload,
assigned the Patient to that existing Person and returns back reference
ids for both the Patient and Person.
  • Loading branch information
ericbuckley authored Oct 29, 2024
1 parent 2533adb commit e89fdda
Show file tree
Hide file tree
Showing 9 changed files with 588 additions and 40 deletions.
11 changes: 6 additions & 5 deletions docs/mpi-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,22 @@ The following diagram illustrates the relationships between the `Person`, `Patie
```mermaid
erDiagram
Person {
bigint id PK "Primary Key"
uuid internal_id "Internal UUID"
bigint id PK "Primary Key (auto-generated)"
uuid reference_id "Reference UUID (auto-generated)"
}
Patient {
bigint id PK "Primary Key"
bigint id PK "Primary Key (auto-generated)"
bigint person_id FK "Foreign Key to Person"
json data "Patient Data"
json data "Patient Data JSON Object"
uuid reference_id "Reference UUID (auto-generated)"
string external_patient_id "External Patient ID"
string external_person_id "External Person ID"
string external_person_source "External Person Source"
}
BlockingValue {
bigint id PK "Primary Key"
bigint id PK "Primary Key (auto-generated)"
bigint patient_id FK "Foreign Key to Patient"
smallint blockingkey "Blocking Key Type"
string value "Blocking Value"
Expand Down
41 changes: 40 additions & 1 deletion src/recordlinker/linking/mpi_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
"""

import typing
import uuid

from sqlalchemy import orm
from sqlalchemy import select
from sqlalchemy.sql import expression

from recordlinker import models
Expand All @@ -29,7 +31,7 @@ def get_block_data(
# has a matching Blocking Value for all the Blocking Keys, then it
# is considered a match.
for idx, key_id in enumerate(algorithm_pass.blocking_keys):
#get the BlockingKey obj from the id
# get the BlockingKey obj from the id
if not hasattr(models.BlockingKey, key_id):
raise ValueError(f"No BlockingKey with id {id} found.")
key = getattr(models.BlockingKey, key_id)
Expand Down Expand Up @@ -109,3 +111,40 @@ def insert_blocking_keys(
if commit:
session.commit()
return values


def get_patient_by_reference_id(
session: orm.Session, reference_id: uuid.UUID
) -> models.Patient | None:
"""
Retrieve the Patient by their reference id
"""
query = select(models.Patient).where(models.Patient.reference_id == reference_id)
return session.scalar(query)


def get_person_by_reference_id(
session: orm.Session, reference_id: uuid.UUID
) -> models.Person | None:
"""
Retrieve the Person by their reference id
"""
query = select(models.Person).where(models.Person.reference_id == reference_id)
return session.scalar(query)


def update_person_cluster(
session: orm.Session,
patient: models.Patient,
person: models.Person | None = None,
commit: bool = True,
) -> models.Person:
"""
Update the cluster for a given patient.
"""
patient.person = person or models.Person()
session.flush()

if commit:
session.commit()
return patient.person
2 changes: 2 additions & 0 deletions src/recordlinker/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from recordlinker.linking import algorithm_service
from recordlinker.linking import link
from recordlinker.routes.algorithm_router import router as algorithm_router
from recordlinker.routes.patient_router import router as patient_router

# Instantiate FastAPI via DIBBs' BaseService class
app = BaseService(
Expand All @@ -35,6 +36,7 @@
app.add_middleware(middleware.CorrelationIdMiddleware)
app.add_middleware(middleware.AccessLogMiddleware)
app.include_router(algorithm_router, prefix="/algorithm", tags=["algorithm"])
app.include_router(patient_router, prefix="/patient", tags=["patient"])


# Request and response models
Expand Down
4 changes: 2 additions & 2 deletions src/recordlinker/models/mpi.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Person(Base):
__tablename__ = "mpi_person"

id: orm.Mapped[int] = orm.mapped_column(get_bigint_pk(), autoincrement=True, primary_key=True)
reference_id: orm.Mapped[uuid.UUID] = orm.mapped_column(default=uuid.uuid4)
reference_id: orm.Mapped[uuid.UUID] = orm.mapped_column(default=uuid.uuid4, unique=True, index=True)
patients: orm.Mapped[list["Patient"]] = orm.relationship(back_populates="person")

def __hash__(self):
Expand Down Expand Up @@ -50,7 +50,7 @@ class Patient(Base):
external_person_id: orm.Mapped[str] = orm.mapped_column(sqltypes.String(255), nullable=True)
external_person_source: orm.Mapped[str] = orm.mapped_column(sqltypes.String(100), nullable=True)
blocking_values: orm.Mapped[list["BlockingValue"]] = orm.relationship(back_populates="patient")
reference_id: orm.Mapped[uuid.UUID] = orm.mapped_column(default=uuid.uuid4)
reference_id: orm.Mapped[uuid.UUID] = orm.mapped_column(default=uuid.uuid4, unique=True, index=True)

@classmethod
def _scrub_empty(cls, data: dict) -> dict:
Expand Down
66 changes: 66 additions & 0 deletions src/recordlinker/routes/patient_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
recordlinker.routes.patient_router
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module implements the patient router for the RecordLinker API. Exposing
the patient API endpoints.
"""

import uuid

import fastapi
import sqlalchemy.orm as orm

from recordlinker import schemas
from recordlinker.database import get_session
from recordlinker.linking import mpi_service as service

router = fastapi.APIRouter()


@router.post(
"/{patient_reference_id}/person",
summary="Assign Patient to new Person",
status_code=fastapi.status.HTTP_201_CREATED,
)
def create_person(
patient_reference_id: uuid.UUID, session: orm.Session = fastapi.Depends(get_session)
) -> schemas.PatientPersonRef:
"""
Create a new Person in the MPI database and link the Patient to them.
"""
patient = service.get_patient_by_reference_id(session, patient_reference_id)
if patient is None:
raise fastapi.HTTPException(status_code=fastapi.status.HTTP_404_NOT_FOUND)

person = service.update_person_cluster(session, patient, commit=False)
return schemas.PatientPersonRef(
patient_reference_id=patient.reference_id, person_reference_id=person.reference_id
)


@router.patch(
"/{patient_reference_id}/person",
summary="Assign Patient to existing Person",
status_code=fastapi.status.HTTP_200_OK,
)
def update_person(
patient_reference_id: uuid.UUID,
data: schemas.PersonRef,
session: orm.Session = fastapi.Depends(get_session),
) -> schemas.PatientPersonRef:
"""
Update the Person linked on the Patient.
"""
patient = service.get_patient_by_reference_id(session, patient_reference_id)
if patient is None:
raise fastapi.HTTPException(status_code=fastapi.status.HTTP_404_NOT_FOUND)

person = service.get_person_by_reference_id(session, data.person_reference_id)
if person is None:
raise fastapi.HTTPException(status_code=fastapi.status.HTTP_400_BAD_REQUEST)

person = service.update_person_cluster(session, patient, person, commit=False)
return schemas.PatientPersonRef(
patient_reference_id=patient.reference_id, person_reference_id=person.reference_id
)
4 changes: 4 additions & 0 deletions src/recordlinker/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .algorithm import Algorithm
from .algorithm import AlgorithmPass
from .algorithm import AlgorithmSummary
from .mpi import PatientPersonRef
from .mpi import PersonRef
from .pii import Feature
from .pii import PIIRecord

Expand All @@ -10,4 +12,6 @@
"AlgorithmSummary",
"Feature",
"PIIRecord",
"PersonRef",
"PatientPersonRef",
]
12 changes: 12 additions & 0 deletions src/recordlinker/schemas/mpi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import uuid

import pydantic


class PersonRef(pydantic.BaseModel):
person_reference_id: uuid.UUID


class PatientPersonRef(pydantic.BaseModel):
patient_reference_id: uuid.UUID
person_reference_id: uuid.UUID
Loading

0 comments on commit e89fdda

Please sign in to comment.