Skip to content

Add patient_sex_banner extension - #424

Open
canvas-investigator[bot] wants to merge 6 commits into
mainfrom
add-patient-sex-banner
Open

Add patient_sex_banner extension#424
canvas-investigator[bot] wants to merge 6 commits into
mainfrom
add-patient-sex-banner

Conversation

@canvas-investigator

@canvas-investigator canvas-investigator Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

🧵 Jessica Herbert cc @JessicaHerbert

What it does

Adds patient_sex_banner, a reference plugin that puts a banner alert on a patient's chart whenever their sex at birth is recorded as something other than Female or Male (e.g. Other, Unknown, or unset):

WARNING: Patient sex is {value}. EPCS Rx requires a sex of F or M for successful transmission.

The banner is placed on the chart timeline, chart, and patient profile. When the sex at birth is corrected to Female or Male, the banner is removed automatically.

  • Protocol (event handler) responds to PATIENT_CREATED / PATIENT_UPDATED and reconciles that one patient's banner via Patient.objects.get(id=self.target).
  • BackfillBanners (cron task) backfills existing patients after install: it sweeps the active-patient panel in bounded pages (PAGE_SIZE=500, cursor on dbid), adds the banner to anyone whose sex at birth isn't M/F, then goes dormant. This covers pre-existing patients without the all-at-once, instance-wide scan that plugin-lifecycle events would cause.

Shared banner-building logic lives in banner.py so the two paths always build the same banner.

Who it's for

Practices that electronically prescribe controlled substances (EPCS) and may register patients whose sex at birth is something other than M/F — the mismatch is surfaced on the chart before prescribing, rather than surfacing as a failed transmission at the pharmacy.

Layout

extensions/patient_sex_banner/
  CANVAS_MANIFEST.json
  LICENSE
  pyproject.toml
  README.md
  __init__.py
  banner.py
  protocols/my_protocol.py
  protocols/backfill.py
  tests/test_banner.py
  tests/protocols/test_my_protocol.py
  tests/protocols/test_backfill.py

No secrets or settings. Banner text, placements, and intent are fixed in code.

Open Source Plugin Publishing Checklist

  • Security & data sanitization — no PHI, secrets, credentials, or customer-identifiable strings; scanned by regex + manual read. Second-person review still required before merge.
  • Generalization — no hardcoded IDs/URLs/customer config; no customer-specific workflow logic; works against a clean instance.
  • Code quality & performance — event handler is single-patient; backfill is paged + dormant (never an all-at-once scan). No known bugs; follows SDK conventions.
  • Documentation & visual assets — manifest name/description populated; README has all six required sections + a screenshot.
  • Licensing & legalLICENSE (MIT) and license = "MIT" in pyproject.toml; manifest license also set to MIT.

Tests

14 pytests, all green (Canvas SDK mocked in conftest.py, so the suite runs without a live instance):

  • tests/test_banner.py — the shared add/remove/reconcile helpers.
  • tests/protocols/test_my_protocol.py — event handler reconciles only the event patient (never objects.all()), and returns no effects for a stale/deleted patient.
  • tests/protocols/test_backfill.py — cron sweeps from zero, advances the cursor on a full page, goes dormant on a partial/empty page, and no-ops (without querying) once dormant.

Review fix

Addressed the Cerberus finding: removed PLUGIN_CREATED / PLUGIN_UPDATED from RESPONDS_TO. The handler previously ran Patient.objects.all() on plugin lifecycle events, emitting one effect per patient across the whole instance on every install/update. Existing-patient coverage is now provided by the paged, dormant-when-done BackfillBanners cron instead.

Generated by the Investigator

canvas-investigator Bot and others added 2 commits July 29, 2026 19:57
Covers the Licensing & legal section of the Open Source Plugin
Publishing Checklist.
Updated README to improve clarity and added image.
@JessicaHerbert

Copy link
Copy Markdown
Collaborator

cerberus review

@canvas-cerberus canvas-cerberus Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🐶 Cerberus review

1 important · 0 nit · 0 pre-existing


Review cost: $1.10.

Comment thread extensions/patient_sex_banner/protocols/my_protocol.py
@JessicaHerbert

Copy link
Copy Markdown
Collaborator

Can you fix that? Add appropriate tests

Drop PLUGIN_CREATED / PLUGIN_UPDATED from RESPONDS_TO so the protocol
only ever reconciles the single patient on a PATIENT_CREATED /
PATIENT_UPDATED event, instead of running Patient.objects.all() and
emitting one effect per patient on every install/update.

Add pytest coverage for the add / remove / missing-patient paths.
Add BackfillBanners, a CronTask that reconciles the sex banner across the
active-patient panel in bounded pages (PAGE_SIZE=500) after install, then
goes dormant — restoring existing-patient coverage without the all-at-once
scan that lifecycle events caused. Extract the shared banner-building logic
into banner.py so the event handler and the backfill stay in sync, and
register the cron in CANVAS_MANIFEST.json.

Add pytest coverage for the backfill (sweep / advance / dormant paths) and
the shared helper; update the README to describe the paged backfill.
@JessicaHerbert

Copy link
Copy Markdown
Collaborator

cerberus review

@canvas-cerberus canvas-cerberus Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🐶 Cerberus review

0 important · 1 nit · 0 pre-existing

No blocking issues found.


Review cost: $0.64.

]

def compute(self) -> list[Effect]:
try:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Nit — Use of deprecated self.target API

The code uses self.target to access the patient ID, but this property is deprecated in SDK 0.11.0 and will be removed in 1.0.0. Replace with self.event.target.id to use the non-deprecated API.

Why cerberus flagged this

The context digest explicitly confirms: self.target is a deprecated property (deprecated_in="0.11.0", removed in 1.0.0) returning self.event.target.id. The PR's my_protocol.py:25 uses Patient.objects.get(id=self.target), which is functionally correct today but relies on a deprecated API that will be removed in a future SDK version. This is not a runtime-breaking bug now, not a security issue, not data corruption — it's a forward-compatibility/style concern. That aligns with REVIEW.md's nit bucket (not one of the "Always check" Important categories). The claim is well-grounded in the provided definitions and doesn't overstate severity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 9fb20c7 — replaced the deprecated self.target with self.event.target.id, the supported accessor for the event's patient key since SDK 0.11.0. Updated the handler test's fixture to match; 14 pytests still green.

self.target is deprecated in SDK 0.11.0 and slated for removal in 1.0.0;
self.event.target.id is the supported accessor for the event's patient key.
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.

1 participant