Skip to content
OpesCare Developers

Architecture and flows

Duplicate detection and reversible merge

Two Health IDs for one person split a medical history. The master patient index finds likely duplicates, but a person decides, and every merge can be reversed.

The flow

Scroll through the steps below the diagram, or hover a step, to highlight it in the diagram.

Duplicate detection and reversible merge Scheduled scans propose likely duplicate Health IDs, a person reviews each pair, and a merge keeps a survivor, turns the other into an alias and can be undone. Scheduler nightly and weekly Score candidate pairs Duplicate scan Queue pairs for review Review desk Compare side by side Review desk One Health ID old ID kept as an alias Lookups follow the merge Patient register Merge: survivor + alias Review desk Or reject the pair Review desk Records separated merge undone Unmerge if needed Review desk scheduled same person undo Person or device OpesCare service Stored data Decision Outcome: done Outcome: refused Request Response Queued / async Refusal or failure
  1. Run duplicate scan Scans run nightly and weekly on the schedule.
  2. Score candidate pairs Records are compared within blocks so the scan stays fast on a national register.
  3. Queue pairs for review Likely duplicates are raised as candidates. Nothing merges automatically.
  4. Compare side by side An administrator reviews both records. A pair can also be raised by hand.
  5. Merge: survivor + alias One Health ID survives; the other becomes an alias that still resolves.
  6. Lookups follow the merge Searches, scans and links on the old ID land on the survivor.
  7. Or reject the pair A rejected pair is dismissed and both records stay as they are.
  8. Unmerge if needed Merges are reversible, last in first out, from the Merged tab.
How this diagram is verified 21 of 21 checks pass

The picture, the steps, the PDF and the JSON are generated from one specification. An automated test proves the graph and checks every reference below against the running application.

  • Every card has a unique id
  • Every card has a known kind
  • Every arrow joins two existing cards
  • Exactly one starting point (a person, device or partner)
  • Every card is reachable from the start
  • Every card leads to an outcome (no dead ends)
  • No cycles except declared retry loops
  • Every numbered step follows real arrows
  • Outcomes end the flow
  • No two cards share a grid cell
  • Every card names its evidence
  • Every route, class, method and setting exists
  • Every icon is from the Lucide set
  • Every icon fits its card kind
  • Every label exists in English and French
  • Every label fits its card
  • No arrow crosses a card
  • No two unrelated arrows run on top of each other
  • No arrow label covers a card or another label
  • Every arrowhead is visible
  • Connected cards of different kinds never share a colour
CardEvidence in the code
Scheduler command:mpi:detect class:App\Console\Commands\DetectDuplicatePatients
Score candidate pairs method:App\Modules\MasterPatientIndex\Services\MasterPatientIndexService::detectDuplicates method:App\Modules\MasterPatientIndex\Services\MasterPatientIndexService::scoreMatch
Queue pairs for review class:App\Models\MpiCandidate route:portals.admin.mpi.index route:portals.admin.mpi.candidates.raise
Compare side by side route:portals.admin.mpi.candidates.show
One Health ID class:App\Models\PatientMergeAlias
Lookups follow the merge method:App\Models\Patient::findExistingIdentity class:App\Models\PatientMerge
Merge: survivor + alias route:portals.admin.mpi.candidates.merge method:App\Modules\MasterPatientIndex\Services\PatientMergeService::merge
Or reject the pair route:portals.admin.mpi.candidates.reject method:App\Modules\MasterPatientIndex\Services\MasterPatientIndexService::rejectMatch
Records separated method:App\Modules\MasterPatientIndex\Services\PatientMergeService::unmerge
Unmerge if needed route:portals.admin.mpi.merges.unmerge

All flows as JSON

What this guarantees

  • No identity is merged without a person deciding.
  • The retired Health ID keeps working as an alias.
  • Every merge can be undone.

Where it lives in the code

  • command:mpi:detect
  • class:App\Console\Commands\DetectDuplicatePatients
  • method:App\Modules\MasterPatientIndex\Services\MasterPatientIndexService::detectDuplicates
  • method:App\Modules\MasterPatientIndex\Services\MasterPatientIndexService::scoreMatch
  • class:App\Models\MpiCandidate
  • route:portals.admin.mpi.index
  • route:portals.admin.mpi.candidates.raise
  • route:portals.admin.mpi.candidates.show
  • class:App\Models\PatientMergeAlias
  • method:App\Models\Patient::findExistingIdentity
  • class:App\Models\PatientMerge
  • route:portals.admin.mpi.candidates.merge
  • method:App\Modules\MasterPatientIndex\Services\PatientMergeService::merge
  • route:portals.admin.mpi.candidates.reject
  • method:App\Modules\MasterPatientIndex\Services\MasterPatientIndexService::rejectMatch
  • method:App\Modules\MasterPatientIndex\Services\PatientMergeService::unmerge
  • route:portals.admin.mpi.merges.unmerge