Skip to content

Update physical exam with modal-based detailed findings and data migration#6750

Closed
dmabram wants to merge 18 commits intodevelopfrom
dabrams/otr-2114-update-general-exam-with-new-sections-and-more-detail
Closed

Update physical exam with modal-based detailed findings and data migration#6750
dmabram wants to merge 18 commits intodevelopfrom
dabrams/otr-2114-update-general-exam-with-new-sections-and-more-detail

Conversation

@dmabram
Copy link
Copy Markdown
Contributor

@dmabram dmabram commented Mar 27, 2026

Summary

  • New modal-exam component type: Checkboxes with pencil icons that open detailed findings dialogs. Sub-items stored as FHIR Observation.component[] on a single parent observation rather than separate resources.
  • Paired L/R modals: Bilateral body parts (Shoulder, Elbow, Wrist, etc.) display as a single row with a two-column Left/Right modal instead of separate L/R entries.
  • Extremities overhaul: 8 body parts x L/R with Inspection, Palpation, Range of Motion, Neurovascular, and body-part-specific Special Tests sections.
  • Nose detail modals: External nose, Anterior rhinoscopy L/R, Epistaxis, Sinus tenderness.
  • Lymph nodes detail modals: 7 node groups (L/R where applicable) with Status and Node Characteristics.
  • Skin overhaul: Common findings picker (31 items with clinical descriptions, split adult/pediatric), Lesion characteristics, Distribution, Location, Wound/laceration, Abscess/cellulitis, Burn, Bite/sting, Associated findings — all as modal-exam.
  • Multi-select to modal conversion: Murmur grades, Wheezing/Crackles/Breath sounds locations, Retractions, Abdomen tender locations.
  • New exam sections: GU Male, GU Female (separated from old GU/Rectal).
  • Added checkboxes: Distress levels, Head bony tenderness/hematoma, Eye lid findings, Neck meningismus, Heart murmur grades, Extremities normal gait, Neurologic focal deficit/sensory loss/tremor, Psychiatric normal affect/anxious.
  • Exam data migration framework: Version-numbered migrations stamped on encounters. V0→V1 converts old multi-select standalone observations to component-based storage. User-triggered via "Migrate Exam Data" button.
  • Stale data warnings: Warning banner with migrate button on Exam tab and Review & Sign page when unmatched observations detected.
  • "Other findings" fallback: Unrecognized observations display in Review & Sign and PDF using stored labels from FHIR code.text.
  • Template staleness scan: "Scan for Stale Templates" button on global templates admin page with field-name comparison.
  • Label on ExamObservationDTO: Carries code.text from FHIR for backward-compatible display of old findings.

Test plan

  • Open Exam tab, verify all new body part modals open and save correctly
  • Verify paired L/R modals show two columns and save to separate observations
  • Check skin common findings modal shows descriptions, displays them on main exam
  • Verify progress note (Review & Sign + PDF) renders modal-exam findings correctly
  • Apply an old template, verify stale data warning appears with migrate button
  • Click migrate button, verify data converts and warning disappears
  • Open old note, verify "Other findings" section shows unrecognized observations
  • Test "Scan for Stale Templates" on admin page
  • Verify new GU Male/Female sections render correctly
  • Test template save/apply round-trip preserves component[] data

🤖 Generated with Claude Code

Daniel Abrams and others added 5 commits March 23, 2026 16:12
- Add freeSolo prop to AutocompleteInput component for freetext input
- Enable freeSolo on Procedure Type field so users can type custom
  procedure names not in the ValueSet
- Fall back to raw procedure name when saving/applying quick picks
  for custom procedure types without a ValueSet code
- Auto-suggest quick pick name from current form: procedure name |
  site location | side of body | complications | cpt codes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce a new modal-exam exam component that renders a checkbox with a
pencil icon to open a detailed findings dialog. Sub-items are stored as
FHIR Observation.component[] on the parent observation.

Changes include:
- New ExamCardModalExamComponent type with sections/groups/options
- ExamModalCheckbox React component with batched save on modal close
- L/R body part pairing in ExamTable for compact layout
- Extremities: 16 body parts (L/R) with Inspection, Palpation, ROM,
  Neurovascular, and body-part-specific Special Tests
- Nose: External nose, Anterior rhinoscopy L/R, Epistaxis, Sinus tenderness
- Lymph Nodes: 7 node groups (L/R) with Status and Node Characteristics
- Skin: Common findings picker with descriptions, Lesion characteristics,
  Distribution, Location, Wound/laceration, Abscess/cellulitis, Burn,
  Bite/sting, Associated findings
- New sections: GU Male, GU Female, separated from Rectal
- Added checkboxes: General distress levels, Head bony tenderness/hematoma,
  Eye lid erythema/tenderness, Neck meningismus, Heart murmur grades,
  Extremities normal gait, Neurologic focal deficit/sensory loss/tremor,
  Psychiatric normal affect/anxious
- Progress note and review tab support for modal-exam observations
- Template compatibility warning when unrecognized exam fields detected

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e-if-no-existing-match' into feature/physical-exam-improvements
…vements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, and stale data warnings

- Convert multi-select exam items (murmur, wheezing, crackles, breath
  sounds, retractions, abdomen tender) to modal-exam with component storage
- Add paired L/R modal for all bilateral body parts (single row, two-column
  modal with shared sections)
- Add exam data migration framework with version numbering on encounters
- Add migrate-exam-data zambda for on-demand migration triggered by user
- Add ExamMigrationWarning component with migrate button on Exam tab and
  Review & Sign page
- Add "Other findings" fallback in Review & Sign and PDF for unmatched
  observations with stored labels
- Add label field to ExamObservationDTO populated from FHIR Observation
  code.text for backward-compatible display
- Add "Scan for Stale Templates" to global templates admin page
- Add new exam sections (GU Male, GU Female) and checkboxes across
  General, Head, Eyes, Neck, Heart, Extremities, Neurologic, Psychiatric
- Add LLQ to abdomen tender options
- Fix deleteField crash on never-saved observations in modal components

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dmabram dmabram requested a review from Copilot March 27, 2026 17:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands the EHR UI with a new Admin area (including billing/payment-location management), broad reporting UX updates, and significant visit-note enhancements (exam rendering/migration warnings, orders, and various UI label/validation changes).

Changes:

  • Introduces /admin with consolidated tabs (Schedules, Virtual Locations, Employees/Providers, Insurance, Billing Configuration, Quick Picks, Global Templates) and new RCM payment-location query/api wiring.
  • Updates Exam rendering to support modal-exam, adds stale/unmatched exam detection + migration warning UI, and adjusts review/progress-note rendering.
  • Improves multiple workflows (patient documents delete, ZIP validation, eRx enrollment pending dialog, immunization orders display, reports refresh behavior) and CI secret population via a composite action.

Reviewed changes

Copilot reviewed 168 out of 610 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
apps/ehr/tests/component/BillingConfiguration.test.tsx Adds component coverage for BillingConfiguration tabs & payment locations
apps/ehr/src/state/nav.store.ts Renames Telemedicine tab to Virtual
apps/ehr/src/rcm/state/payments/payments.queries.ts Adds react-query hooks for payment/stripe/terminal endpoints
apps/ehr/src/rcm/state/payments/payments.api.ts Adds zambda API wrappers for payments admin
apps/ehr/src/rcm/state/payments/index.ts Re-exports payments API/query modules
apps/ehr/src/rcm/state/index.ts Reorders/extends RCM exports
apps/ehr/src/rcm/state/fee-schedules/index.ts Re-exports fee schedule API/query modules
apps/ehr/src/pages/reports/index.ts Exports new CompleteEncounters report
apps/ehr/src/pages/reports/VisitsOverview.tsx Prevents auto-fetch for custom dates; clears stale report UI
apps/ehr/src/pages/reports/RecentPatients.tsx Adds Refresh button, new “Source” column, custom-date fetch behavior
apps/ehr/src/pages/reports/PracticeKpis.tsx Adds Refresh button; custom-date fetch behavior
apps/ehr/src/pages/reports/IncompleteEncounters.tsx Switches to getEncountersReport + disables auto-fetch for custom dates
apps/ehr/src/pages/reports/DailyPayments.tsx Stops auto-fetch for custom; clears stale report UI
apps/ehr/src/pages/reports/AiAssistedEncounters.tsx Disables auto-fetch for custom dates
apps/ehr/src/pages/VisitDetailsPage.tsx Adds fax history query + “Legacy Data” button + activity log wiring
apps/ehr/src/pages/TelemedAdminPage.tsx Removes old telemed admin page (replaced by AdminPage)
apps/ehr/src/pages/Schedules.tsx Replaces tab UI with button group; adapts schedule type handling
apps/ehr/src/pages/SchedulePage.tsx Updates routes/breadcrumbs to new /admin schedule paths
apps/ehr/src/pages/Reports.tsx Adds “Complete Encounters” report tile
apps/ehr/src/pages/PatientDocumentsExplorerPage.tsx Wires delete action into patient docs explorer
apps/ehr/src/pages/GroupPage.tsx Updates breadcrumbs for new /admin structure
apps/ehr/src/pages/Employees.tsx Refactors into param-driven page; updates routes to /admin
apps/ehr/src/pages/EditEmployee.tsx Updates breadcrumbs and schedule links to /admin
apps/ehr/src/pages/AdminPage.tsx Adds consolidated /admin tab page
apps/ehr/src/pages/AddSchedulePage.tsx Updates navigation/breadcrumbs to /admin schedule paths
apps/ehr/src/pages/AddEmployeePage.tsx Updates post-create navigation/breadcrumbs to /admin
apps/ehr/src/hooks/useMergedQuickPicks.ts Adds merged quick-picks fetching hook
apps/ehr/src/hooks/useGetPatientDocs.ts Adds deleteDocumentAction and query refresh logic
apps/ehr/src/hooks/useGetOrdersForTrackingBoard.tsx Adds immunization orders into tracking-board aggregation
apps/ehr/src/hooks/useAppClients.tsx Adjusts zambda client config, local services override
apps/ehr/src/helpers/activityLogsUtils.ts Adds fax activity log entries + activity link support
apps/ehr/src/features/visits/telemed/utils/appointments.ts Adds appointmentId to request params type
apps/ehr/src/features/visits/telemed/components/tracking-board/TrackingBoardBody.tsx Removes provider notification settings UI from tracking board
apps/ehr/src/features/visits/telemed/components/appointment/AppointmentSidePanel.tsx Switches to useGetTelemedAppointments for hasUnread messaging
apps/ehr/src/features/visits/telemed/components/admin/VirtualLocationsPage.tsx Moves queries import + adjusts layout spacing
apps/ehr/src/features/visits/telemed/components/admin/RadiologyQuickPicksPage.tsx Adds radiology quick picks admin page
apps/ehr/src/features/visits/telemed/components/admin/Insurance.tsx Moves queries import + adjusts layout spacing
apps/ehr/src/features/visits/telemed/components/admin/EditVirtualLocationPage.tsx Updates breadcrumbs and schedule edit route to /admin
apps/ehr/src/features/visits/telemed/components/admin/BillingConfiguration.tsx Adds billing sub-tabs + payment locations list
apps/ehr/src/features/visits/shared/utils/appointment-accessibility.helper.ts Guards encounter access (avoid crash when undefined)
apps/ehr/src/features/visits/shared/stores/tracking-board/tracking-board.queries.ts Adds appointmentId support into telemed appointments query
apps/ehr/src/features/visits/shared/stores/appointment/appointment.store.tsx Fixes query invalidation dependency on encounter id
apps/ehr/src/features/visits/shared/stores/appointment/appointment.queries.ts Removes SMS model query hook implementation
apps/ehr/src/features/visits/shared/components/vitals/hooks/visionCptAutoAdd.ts Adds vision CPT auto-add helper
apps/ehr/src/features/visits/shared/components/vitals/hooks/useSaveVitals.ts Auto-adds vision CPT codes after vitals save
apps/ehr/src/features/visits/shared/components/vitals/hooks/useBatchSaveVitals.ts Auto-adds vision CPT codes after batch vitals save
apps/ehr/src/features/visits/shared/components/templates/useListTemplates.ts Changes template options to include ids + filters missing titles
apps/ehr/src/features/visits/shared/components/staff-add-visit/AddVisitPatientSearchFields.tsx Adds DOB search field (editable + readonly modes)
apps/ehr/src/features/visits/shared/components/staff-add-visit/AddVisitPatientInformationCard.tsx Adds DOB search filter + updates patient search flow
apps/ehr/src/features/visits/shared/components/review-tab/components/ProceduresContainer.tsx Joins technique array for display
apps/ehr/src/features/visits/shared/components/review-tab/components/MedicationsContainer.tsx Adds dose/confirmation info to medication display
apps/ehr/src/features/visits/shared/components/review-tab/components/ExaminationContainer.tsx Adds modal-exam rendering + “Other findings” fallback
apps/ehr/src/features/visits/shared/components/review-tab/components/ChiefComplaintContainer.tsx Renames header to “Additional information”
apps/ehr/src/features/visits/shared/components/review-tab/ReviewAndSignButton.tsx Removes in-person Chief Complaint gating requirement
apps/ehr/src/features/visits/shared/components/review-tab/MissingCard.tsx Removes missing Chief Complaint prompt logic
apps/ehr/src/features/visits/shared/components/plan-tab/ERxContainer.tsx Uses local timezone in date formatting
apps/ehr/src/features/visits/shared/components/patient/patientRecordValidation.ts Uses shared zipRegex; updates ZIP message
apps/ehr/src/features/visits/shared/components/patient/patientRecordValidation.test.ts Adjusts tests for enable/disable trigger values
apps/ehr/src/features/visits/shared/components/patient/info/IdentifiersRow.tsx Adds click-to-copy PID with icon feedback
apps/ehr/src/features/visits/shared/components/patient/docs/PatientDocumentsExplorerTable.tsx Adds delete dialog/action to docs table
apps/ehr/src/features/visits/shared/components/patient/PatientRecordFormSection.tsx Moves config-types imports
apps/ehr/src/features/visits/shared/components/patient/PatientRecordFormField.tsx Moves config-types imports; adds ZIP placeholder/mask
apps/ehr/src/features/visits/shared/components/patient/AddInsuranceModal.tsx Updates ZIP validation error message
apps/ehr/src/features/visits/shared/components/medical-history-tab/MedicalConditions/MedicalConditionsProviderColumn.tsx Switches medical condition quick picks to merged hook
apps/ehr/src/features/visits/shared/components/medical-history-tab/CurrentMedications/CurrentMedicationsProviderColumn.tsx Adds “could not confirm dosage” + merged quick picks
apps/ehr/src/features/visits/shared/components/medical-history-tab/CurrentMedications/CurrentMedicationGroup.tsx Displays added medication intake info
apps/ehr/src/features/visits/shared/components/known-allergies/KnownAllergiesProviderColumn.tsx Switches allergy quick picks to merged hook
apps/ehr/src/features/visits/shared/components/exam-tab/useUnmatchedExamFields.ts Adds hook to detect stale/unmatched exam fields
apps/ehr/src/features/visits/shared/components/exam-tab/ExamTable.tsx Adds modal-exam rendering + L/R paired modal rows
apps/ehr/src/features/visits/shared/components/exam-tab/ExamTab.tsx Shows migration banner when unmatched exam fields exist
apps/ehr/src/features/visits/shared/components/exam-tab/ExamMigrationWarning.tsx Adds user-triggered exam migration UI
apps/ehr/src/features/visits/shared/components/QuickPicksButton.tsx Adds optional search + admin “Add/Update” item
apps/ehr/src/features/visits/shared/components/OrdersIconsToolTip.tsx Adds immunization orders to orders tooltip
apps/ehr/src/features/visits/shared/components/HpiSection.tsx Renames Chief Complaint label to Additional information
apps/ehr/src/features/visits/shared/components/ERX.tsx Adds pending enrollment dialog + sentry warning message
apps/ehr/src/features/visits/shared/components/AiPotentialDiagnosesCard.tsx Handles alternate “seen in last 3 years” field
apps/ehr/src/features/visits/in-person/layout/InPersonLayout.tsx Uses fixed height layout
apps/ehr/src/features/visits/in-person/hooks/useTasks.ts Adds ERX task handling + completable flag
apps/ehr/src/features/visits/in-person/hooks/useImmunization.ts Switches enabled condition to encounterIds input
apps/ehr/src/features/visits/in-person/components/screening/useScreeningQuestionsHandler.ts Adds debounced field support
apps/ehr/src/features/visits/in-person/components/screening/AskThePatient.tsx Adds single date question type + ISO date range outputs
apps/ehr/src/features/visits/in-person/components/progress-note/ProgressNoteDetails.tsx Adds exam migration banner + immunization & labs refinements
apps/ehr/src/features/visits/in-person/components/medication-administration/medication-history/MedicationHistoryEntity.tsx Adds dosage confirmation display
apps/ehr/src/features/visits/in-person/components/medication-administration/mar/MarTable.tsx Disables hyphenation in MAR table cells
apps/ehr/src/features/visits/in-person/components/follow-up-note/FollowUpNoteDetails.tsx Updates immunization input + labs refinements
apps/ehr/src/features/visits/in-person/components/EncounterSwitcher.tsx Expands encounter switcher by default
apps/ehr/src/features/visits/ChiefComplaintField.tsx Renames “Chief Complaint” to “Additional information”
apps/ehr/src/features/tasks/pages/Tasks.tsx Adds DoseSpot task action with SSO open
apps/ehr/src/features/tasks/components/CreateTaskDialog.tsx Refreshes order options on open; adjusts option labeling
apps/ehr/src/features/tasks/common.ts Adds refreshKey to orders hooks; adds ERX category label
apps/ehr/src/features/radiology/pages/RadiologyOrdersListPage.tsx Adds “studyName” column
apps/ehr/src/features/radiology/pages/RadiologyOrderDetails.tsx Shows study name + read-only x-ray consent checkbox
apps/ehr/src/features/radiology/components/RadiologyTableRow.tsx Renders studyName cell
apps/ehr/src/features/radiology/components/RadiologyTable.tsx Extends column type + widths + headers
apps/ehr/src/features/notifications/notifications.store.ts Splits notification toggles + adds phoneNumber
apps/ehr/src/features/notifications/notifications.queries.ts Updates practitioner notification patching + phone telecom update
apps/ehr/src/features/notifications/ProviderNotifications.tsx Updates store wiring to new notification fields
apps/ehr/src/features/in-house-labs/components/details/InHouseLabResults.tsx Hides repeat/reflex actions when read-only
apps/ehr/src/features/in-house-labs/components/details/CollectSampleView.tsx Removes sourceType gating from collect button
apps/ehr/src/features/immunization/components/VaccineDetailsCardList.tsx Switches immunizations query to encounterIds input
apps/ehr/src/features/immunization/components/OrderStatusChip.tsx Prevents status chip text wrapping
apps/ehr/src/features/immunization/components/OrderHistoryTable.tsx Switches immunizations query to encounterIds input
apps/ehr/src/features/external-labs/pages/CreateExternalLabOrder.tsx Renames CPT-add input + only adds when not PSC
apps/ehr/src/constants/index.ts Extends ZIP regex to allow ZIP+4
apps/ehr/src/constants/feature-flags.ts Adds legacy data + mailing paper statements flags
apps/ehr/src/constants/data-test-ids.ts Adds procedureCodes test ids
apps/ehr/src/components/navigation/UserMenu.tsx Adds Settings link + pending eRx review dialog
apps/ehr/src/components/navigation/Navbar.tsx Updates nav tabs (Virtual/Admin) + URL mappings
apps/ehr/src/components/input/AutocompleteInput.tsx Adds freeSolo support (type casts)
apps/ehr/src/components/dialogs/index.ts Exports SendStatementToPatientDialog
apps/ehr/src/components/dialogs/PendingErxEnrollmentDialog.tsx Adds dialog for pending enrollment review
apps/ehr/src/components/dialogs/EditPatientDialog.tsx Switches ZIP input to PatternFormat ZIP+4
apps/ehr/src/components/ScheduleInformation.tsx Updates schedule routes to /admin
apps/ehr/src/components/PatientEncountersGrid.tsx Adds service category filtering + display
apps/ehr/src/components/GroupSelect.tsx Removes GroupSelect component
apps/ehr/src/components/EmployeeInformation/BasicInformation.tsx Updates ZIP validation message
apps/ehr/src/components/AppointmentTabs.tsx Replaces groups filter prop with serviceCategories; renames tab label
apps/ehr/src/components/AppointmentTableRow.tsx Refactors status/type/time display; always shows time column
apps/ehr/src/components/AppointmentTableHeader.tsx Removes showTime/visible props; always renders Time column
apps/ehr/src/components/AppointmentTable.tsx Adds immunizations to orders + always shows time header
apps/ehr/src/components/ActivityLogRow.tsx Links activity-by text when activityLink provided
apps/ehr/src/App.tsx Adds /admin routes + billing/payment/global templates routes + legacy data flag route
apps/ehr/public/fee-schedule-template.csv Adds fee schedule CSV template
apps/ehr/playwright.config.ts Stops ignoring employees.spec.ts
apps/ehr/package.json Updates version; builds tsc+vite concurrently
apps/ehr/env/.env.template-iac Adds new feature flag env vars
apps/ehr/env/.env.local-template Adds mailing paper statements feature flag
E2E_README.md Updates config repo naming/details
CLAUDE.md Adds repo guidance for Claude Code
.vscode/settings.json Pins TypeScript SDK path
.github/workflows/iac-deploy-services.yml Lowers runner size; uses populate-secrets action
.github/workflows/e2e-intake.yml Uses populate-secrets action + OIDC permissions
.github/workflows/e2e-ehr.yml Uses populate-secrets action + OIDC permissions
.github/workflows/deploy-and-test-reusable.yml Uses populate-secrets action + passes aws_deploy_role
.github/workflows/automated-tests.yml Uses populate-secrets action + OIDC permissions
.github/actions/populate-secrets/action.yml Adds composite action for secrets + optional TF state pull
.gitattributes Updates union-merge paths to config/oystehr-core
Comments suppressed due to low confidence (1)

apps/ehr/src/features/immunization/components/VaccineDetailsCardList.tsx:1

  • This will crash if encounter is undefined during initial render/loading because encounter.id is accessed unconditionally. Pass encounter?.id safely (and omit/empty the array when missing) so the hook can remain disabled until the encounter is available.
import { Box, Stack, Typography } from '@mui/material';

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const locations: PaymentLocation[] = React.useMemo(() => data || [], [data]);

const filteredLocations = React.useMemo(
() => locations.filter((item) => item.location.name?.toLowerCase().includes(searchText.toLowerCase())),
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

This can throw when item.location.name is undefined: name?.toLowerCase() yields undefined, then .includes(...) is invoked on undefined. Use a null-safe string fallback (e.g., (item.location.name ?? '')...) or optional-chain the includes call as well.

Suggested change
() => locations.filter((item) => item.location.name?.toLowerCase().includes(searchText.toLowerCase())),
() => locations.filter((item) => (item.location.name ?? '').toLowerCase().includes(searchText.toLowerCase())),

Copilot uses AI. Check for mistakes.
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.

Reply from Claude Code: Valid concern, but this file was brought in via a merge from develop and is not part of the exam changes in this PR. Noted for follow-up.

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.

This code is from the develop merge, not changes in this PR.

This comment is from Claude (claude-opus-4-6).

Comment on lines +15 to +20
const handleCopy = (): void => {
if (!id) return;
void navigator.clipboard.writeText(id);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

Clipboard writes can reject (permissions/insecure context), and the setTimeout can update state after unmount. Consider awaiting writeText with a try/catch (and showing feedback on failure) and clearing the timeout in an effect cleanup to avoid potential unhandled rejections and memory leaks.

Copilot uses AI. Check for mistakes.
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.

Reply from Claude Code: This file was brought in via a merge from develop — not part of the exam changes in this PR. Valid point about error handling, noted for follow-up.

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.

This code is from the develop merge, not changes in this PR.

This comment is from Claude (claude-opus-4-6).

Comment on lines +37 to +84
<TabList onChange={() => {}} aria-label={`${pageTab} page`}>
<Tab
label="Schedules"
value={PageTab.schedules}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.schedules}`)}
/>
<Tab
label="Virtual Locations"
value={PageTab['virtual-locations']}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab['virtual-locations']}`)}
/>
<Tab
label="Employees"
value={PageTab.employees}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.employees}`)}
/>
<Tab
label="Providers"
value={PageTab.providers}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.providers}`)}
/>
<Tab
label="Insurance"
value={PageTab.insurance}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.insurance}`)}
/>
<Tab
label="Billing Configuration"
value={PageTab.billing}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.billing}`)}
/>
<Tab
label="Quick Picks"
value={PageTab['quick-picks']}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab['quick-picks']}`)}
/>
<Tab
label="Global Templates"
value={PageTab['global-templates']}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab['global-templates']}`)}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

Using a no-op TabList onChange and relying on per-Tab onClick breaks expected keyboard tab behavior (arrow keys / selection) and can reduce accessibility. Prefer handling navigation in onChange (using newValue) and remove per-Tab onClick handlers so MUI Tabs behave correctly.

Suggested change
<TabList onChange={() => {}} aria-label={`${pageTab} page`}>
<Tab
label="Schedules"
value={PageTab.schedules}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.schedules}`)}
/>
<Tab
label="Virtual Locations"
value={PageTab['virtual-locations']}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab['virtual-locations']}`)}
/>
<Tab
label="Employees"
value={PageTab.employees}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.employees}`)}
/>
<Tab
label="Providers"
value={PageTab.providers}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.providers}`)}
/>
<Tab
label="Insurance"
value={PageTab.insurance}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.insurance}`)}
/>
<Tab
label="Billing Configuration"
value={PageTab.billing}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab.billing}`)}
/>
<Tab
label="Quick Picks"
value={PageTab['quick-picks']}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab['quick-picks']}`)}
/>
<Tab
label="Global Templates"
value={PageTab['global-templates']}
sx={{ textTransform: 'none', fontWeight: 500 }}
onClick={() => navigate(`/admin/${PageTab['global-templates']}`)}
<TabList
onChange={(_, newValue) => navigate(`/admin/${newValue}`)}
aria-label={`${pageTab} page`}
>
<Tab
label="Schedules"
value={PageTab.schedules}
sx={{ textTransform: 'none', fontWeight: 500 }}
/>
<Tab
label="Virtual Locations"
value={PageTab['virtual-locations']}
sx={{ textTransform: 'none', fontWeight: 500 }}
/>
<Tab
label="Employees"
value={PageTab.employees}
sx={{ textTransform: 'none', fontWeight: 500 }}
/>
<Tab
label="Providers"
value={PageTab.providers}
sx={{ textTransform: 'none', fontWeight: 500 }}
/>
<Tab
label="Insurance"
value={PageTab.insurance}
sx={{ textTransform: 'none', fontWeight: 500 }}
/>
<Tab
label="Billing Configuration"
value={PageTab.billing}
sx={{ textTransform: 'none', fontWeight: 500 }}
/>
<Tab
label="Quick Picks"
value={PageTab['quick-picks']}
sx={{ textTransform: 'none', fontWeight: 500 }}
/>
<Tab
label="Global Templates"
value={PageTab['global-templates']}
sx={{ textTransform: 'none', fontWeight: 500 }}

Copilot uses AI. Check for mistakes.
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.

Reply from Claude Code: The AdminPage tab structure was pre-existing — our merge resolution kept both sets of tabs (billing + global templates). The accessibility suggestion is valid and could be addressed in a follow-up.

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.

This code is from the develop merge, not changes in this PR.

This comment is from Claude (claude-opus-4-6).

getOptionLabel={getOptionLabel as any}
isOptionEqualToValue={isOptionEqualToValue as any}
freeSolo={freeSolo}
onChange={(_e, option: any) => field.onChange(option ?? null)}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

@mui/material's Autocomplete does not support a getOptionKey prop, and the added as any casts will hide type/prop issues (including potentially forwarding unknown props). Consider removing getOptionKey and instead ensuring stable keys via renderOption (or by normalizing options) and tightening types for freeSolo so the component doesn't accept incompatible prop combinations.

Suggested change
onChange={(_e, option: any) => field.onChange(option ?? null)}
onChange={(_e, option) => field.onChange(option ?? null)}

Copilot uses AI. Check for mistakes.
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.

Reply from Claude Code: This came from the procedure naming branch merge (dabrams/otr-2077). The as any cast was needed because MUI Autocomplete types do not expose getOptionKey but it works at runtime for freeSolo mode. Can revisit in a follow-up.

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.

The getOptionKey prop is supported in our MUI version (v5.14+). The type casts were narrowed from any to explicit typed casts in a recent commit to address type safety. A few remain because MUI's Autocomplete widens the option type to string | Value when freeSolo is a boolean prop.

This comment is from Claude (claude-opus-4-6).

Comment on lines +158 to 166
{filteredQuickPicks.map((item, index) => (
<MenuItem
key={getLabel(item)}
onClick={() => handleSelect(item)}
selected={searchable && index === highlightedIndex}
>
{getLabel(item)}
</MenuItem>
))}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

Using getLabel(item) as the React key can cause collisions if labels are duplicated (which can lead to incorrect item reuse/rendering). Prefer a stable unique identifier (e.g., an id field) or allow callers to pass a getKey function; falling back to index is safer than non-unique keys.

Copilot uses AI. Check for mistakes.
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.

Reply from Claude Code: This file was brought in via a merge from develop — not part of the exam changes. The key uniqueness concern is valid and noted for follow-up.

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.

Labels are unique within each quick pick category in practice. Switched from index to getLabel(item) to fix a React key stability issue when items are filtered via search.

This comment is from Claude (claude-opus-4-6).

label="Order"
options={orderOptions.map((order) => order.id)}
getOptionLabel={(option) => orderOptions.find((opt) => opt.id === option)?.label ?? option}
getOptionLabel={(option) => orderOptions.find((opt) => opt.id === option)?.label ?? ''}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

Returning an empty string when the option label can't be resolved can make the selected value appear blank and can also make the dropdown hard to use while options are loading. A safer fallback is to return option (or a short placeholder) so the UI remains intelligible even if orderOptions hasn't populated yet.

Suggested change
getOptionLabel={(option) => orderOptions.find((opt) => opt.id === option)?.label ?? ''}
getOptionLabel={(option) => orderOptions.find((opt) => opt.id === option)?.label ?? option}

Copilot uses AI. Check for mistakes.
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.

Reply from Claude Code: This file was brought in via a merge from develop — not part of the exam changes. Good suggestion to return option as fallback instead of empty string.

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.

This code is from the develop merge, not changes in this PR.

This comment is from Claude (claude-opus-4-6).

Comment on lines +68 to +80
try {
if (GET_PAYMENT_LOCATIONS_ZAMBDA_ID == null) {
throw new Error('get-payment-locations zambda ID could not be loaded');
}

const response = await oystehr.zambda.execute({
id: GET_PAYMENT_LOCATIONS_ZAMBDA_ID,
});
return chooseJson(response);
} catch (error: unknown) {
console.log(error);
throw error;
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

The *_ZAMBDA_ID == null checks are dead code since these IDs are hard-coded string constants; they add noise without providing safety. Also console.log in catch blocks makes error triage harder than console.error (and can be inconsistent with structured logging). Consider removing the null checks and logging with console.error or a centralized logger.

Copilot uses AI. Check for mistakes.
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.

Reply from Claude Code: This file was brought in via a merge from develop — not part of the exam changes. Agreed the null checks on hardcoded constants are dead code.

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.

This code is from the develop merge, not changes in this PR.

This comment is from Claude (claude-opus-4-6).

Comment thread apps/ehr/src/App.tsx
Comment on lines +83 to +89
export const INSURANCES_URL = '/admin/insurances';
export const FEE_SCHEDULES_URL = '/admin/fee-schedule';
export const CHARGE_MASTERS_URL = '/admin/charge-masters';
export const VIRTUAL_LOCATIONS_URL = '/admin/virtual-locations';
export const BILLING_URL = '/admin/billing';
export const PAYMENT_LOCATIONS_URL = '/admin/billing/payments/locations';
export const GLOBAL_TEMPLATES_URL = '/admin/global-templates';
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

The PR description is focused on physical exam modal findings and exam data migration, but this PR also introduces a large /admin restructuring, billing/payment-location features, reporting changes, notifications refactors, and CI workflow updates. Please update the PR description (or split the PR) so reviewers and release notes accurately reflect the full scope of changes.

Copilot uses AI. Check for mistakes.
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.

Reply from Claude Code: Fair point — this PR includes merge commits from develop, feature/global-templates-admin, and dabrams/otr-2077 which bring in unrelated changes (billing, payment locations, admin restructuring, etc.). The exam-specific changes are in the two non-merge commits: ae1c86b and a6d6a88. The PR description focuses on the exam work which is the primary intent of this branch.

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.

The develop branch was merged into this feature branch, which brought in unrelated changes. The PR description and scope cover only the exam module improvements. The other changes are from develop and will appear in their own PRs.

This comment is from Claude (claude-opus-4-6).

Daniel Abrams and others added 7 commits March 27, 2026 15:25
… reorder skin modals

- Add abnormal flag to ExamObservationComponentDTO and modal-exam config options
- Classify modal options as normal/abnormal via NORMAL_LABELS set and opt() helper
- Style indented sub-items with red/bold (abnormal) or green (normal) in exam tab, review tab, and PDF
- Add hip special tests (FABER, FADIR, Log roll, Trendelenburg, Greater trochanter)
- Remove Soft, Mobile, Non-tender from NORMAL_LABELS (not normal in lymph node context)
- Move skin distribution pattern below location to group modifier modals together

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pdate-general-exam-with-new-sections-and-more-detail
- Unit tests (32): exam config structure, NORMAL_LABELS classification,
  extremity/lymph node/skin modal validation, L/R pairing, migration map
  contents, V0→V1 conversion, idempotency
- Integration tests (4): component round-trip save/get, clear components,
  paired L/R independence, label persistence
- E2E tests (6): single modal open/close, select/deselect items, paired
  L/R two-column layout, Review & Sign verification
- Remove disablePortal from exam Dialog components (invisible to Playwright
  when rendered inside table cells)
- Export migration functions for unit test access

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… flag, rectal findings, skin location reorder

- Split Hand/fingers into Hand/Wrist (with wrist special tests) and
  Fingers (organized by joint: DIP, PIP, MCP)
- Add legacy flag to ExamCardCheckboxComponent to hide deprecated fields
  unless existing data is present
- Mark Swelling/tenderness/decreased ROM and Point tenderness over bone
  as legacy
- Add left/right side checkboxes for scrotal edema and hernia
- Add rectal abnormal findings (hemorrhoids, skin tags, fissure, abscess)
- Move Location directly below Common skin findings in Skin section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pdate-general-exam-with-new-sections-and-more-detail
…and exam config updates

- Parent checkbox saves non-lateralized observation; modal saves L/R
- Progress note consolidates as "Hand/Wrist L: Appearance: Swelling, Deformity; R: Tenderness: Joint"
- Normal-only modal selections display as normal on progress note
- Clearing all modal items unchecks parent
- Skin modal shows description on hover; diagnosis labels show exam findings on note
- Add legacy flag, rectal findings, skin location reorder, hand/wrist+fingers split
- Register base keys for L/R pairs in exam field metadata

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@alexwillingham alexwillingham changed the base branch from main to develop April 2, 2026 15:12
Daniel Abrams and others added 6 commits April 2, 2026 11:14
…pdate-general-exam-with-new-sections-and-more-detail
…into shared utilities

- Extract extractObservationsFromExamComponents and collectKnownExamFields to packages/utils for shared use by review page, PDF, and admin templates
- Add L/R paired modal-exam handling to PDF to match review page display
- Extract buildAllOptions, buildDescriptionMap, buildAbnormalMap to exam-modal-helpers for ExamModalCheckbox and ExamPairedModalCheckbox
- Move template detail types (TemplateExamFinding, TemplateCodeInfo, etc.) to packages/utils/lib/types/data/list-template.types.ts
- Extract verifyIsTemplate and findHolderList to shared template-helpers for admin zambdas

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove duplicate template types from list-template.types.ts in favor of
admin-template.types.ts from the release branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
buildAbnormalFieldCodes and buildFieldLabels now handle modal-exam,
dropdown, multi-select, form, and column types so template admin
detail page correctly labels and classifies all exam findings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No thyroid conditions are asked about in this section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@szaccagni
Copy link
Copy Markdown
Contributor

This branch was iterated on and merged in another PR #7030

@szaccagni szaccagni closed this Apr 16, 2026
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.

3 participants