Skip to content

Comments

Implement modality worklist#7

Merged
steventux merged 24 commits intomainfrom
feat/modality-worklist
Jan 13, 2026
Merged

Implement modality worklist#7
steventux merged 24 commits intomainfrom
feat/modality-worklist

Conversation

@steventux
Copy link
Contributor

@steventux steventux commented Jan 6, 2026

Description

  • Adds Modality Worklist (MWL) server with C-FIND handler.
  • Supports C-FIND with patient ID, scheduled date and modality filters.
  • Provides a way to configure the MWL SQLite database separate from the PACS instance.
  • Allows Docker container to run both servers in separate threads.
  • Adds a script which can create worklist items from the CLI.

Jira link

https://nhsd-jira.digital.nhs.uk/browse/DTOSS-11839

Review notes

Review checklist

  • Check database queries are correctly scoped to current_provider

@steventux steventux changed the title WIP refactor storage to accommodate worklist db Implement modality worklist Jan 6, 2026
Adds basic CRUD operations for worklist_items
@steventux steventux force-pushed the feat/modality-worklist branch from 9c237de to 19d6a04 Compare January 6, 2026 14:58
@carlosmartinez carlosmartinez force-pushed the feat/modality-worklist branch from 7bca5a3 to 83b3027 Compare January 6, 2026 16:05
@steventux steventux force-pushed the feat/modality-worklist branch from abbeb6a to ec431c8 Compare January 7, 2026 17:49
@steventux steventux marked this pull request as ready for review January 8, 2026 10:37
And an ADR for our threading policy
@steventux steventux requested a review from a team January 8, 2026 14:22
As opposed to separate threads
Copy link

@MatMoore MatMoore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a few suggestions inline, but overall looks good. Would you mind squashing the ADR commits as the earlier one is redundant now?

It was a bit hard for me to spot whether we are diverging from spec at all... I'm not sure if there is somebody who is more familiar who can look at it from that angle, or if there is tooling we can use or are already using to check conformance?


-- Scheduling information
scheduled_date TEXT NOT NULL, -- YYYYMMDD format
scheduled_time TEXT NOT NULL, -- HHMMSS format

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the protocol have a timezone to go with this time? If not it might be helpful to include in the comment what we expect it to be when stored in this db (e.g. UTC).


-- DICOM identifiers
study_instance_uid TEXT,
mpps_instance_uid TEXT,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we define the acronym MPPS in a comment?

CREATE INDEX IF NOT EXISTS idx_worklist_status
ON worklist_items(status);

-- Index for MPPS lookups by study instance UID

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want an index on mpps_instance_uid as well?

"""
Add a new worklist item.

Args:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about extracting this long list of args into a dataclass for the worklist item?

This method then becomes add_worklist_item(self, item: WorklistItem)

find_worklist_item could then return list[WorklistItem] rather than list[Dict], pywright will be able to fully type check it, and intellisense will be able to autocomplete the individual attributes of the return object.

It looks like since 3.14, if you use dataclass.field you can pass a doc attribute as well so you can document the fields individually https://docs.python.org/3/library/dataclasses.html#dataclasses.field

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9159198 a lot cleaner than working with a dictionary 👍

patient_sex, scheduled_date, scheduled_time, modality,
study_description, procedure_code, study_instance_uid,
source_message_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sqlite3 and the python db api also support a named placeholder style, which is a bit less error prone when you have a lot of params.

Example from the docs:

# This is the named style used with executemany():
data = (
    {"name": "C", "year": 1972},
    {"name": "Fortran", "year": 1957},
    {"name": "Python", "year": 1991},
    {"name": "Go", "year": 2009},
)
cur.executemany("INSERT INTO lang VALUES(:name, :year)", data)

- **Operational simplicity** - Single container to deploy, monitor, and manage
- **Shared resources** - Both services share the same volume mounts and environment configuration
- **pynetdicom compatibility** - The library's `AE.start_server()` is designed to block and handle connections, making threads a natural fit
- **Lower overhead** - Threads have less overhead than separate processes or containers
Copy link

@MatMoore MatMoore Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What resources were we concerned about here?

**Why this approach:**

- **Operational simplicity** - Single container to deploy, monitor, and manage
- **Shared resources** - Both services share the same volume mounts and environment configuration

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a circular argument? If we picked one of the other options I don't think this would be true?

Do we need to share the same volume mounts, and environment configuration? Or is this just the same argument of operational simplicity?


- **No independent scaling or deployment** - Cannot scale or deploy PACS and MWL separately if one receives significantly more load
- **Restart granularity** - Cannot restart one service without restarting both

Copy link

@MatMoore MatMoore Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue is they share a logging configuration, and potentially configuration for other instrumentation. Maybe this can be mitigated by including the thread name in the format if you haven't done so already Never mind- this is addressed in the final ADR

@steventux steventux merged commit 52bfa28 into main Jan 13, 2026
4 checks passed
@steventux steventux deleted the feat/modality-worklist branch January 13, 2026 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants