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.
- Run duplicate scan Scans run nightly and weekly on the schedule.
- Score candidate pairs Records are compared within blocks so the scan stays fast on a national register.
- Queue pairs for review Likely duplicates are raised as candidates. Nothing merges automatically.
- Compare side by side An administrator reviews both records. A pair can also be raised by hand.
- Merge: survivor + alias One Health ID survives; the other becomes an alias that still resolves.
- Lookups follow the merge Searches, scans and links on the old ID land on the survivor.
- Or reject the pair A rejected pair is dismissed and both records stay as they are.
- 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
| Card | Evidence 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 |
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:detectclass:App\Console\Commands\DetectDuplicatePatientsmethod:App\Modules\MasterPatientIndex\Services\MasterPatientIndexService::detectDuplicatesmethod:App\Modules\MasterPatientIndex\Services\MasterPatientIndexService::scoreMatchclass:App\Models\MpiCandidateroute:portals.admin.mpi.indexroute:portals.admin.mpi.candidates.raiseroute:portals.admin.mpi.candidates.showclass:App\Models\PatientMergeAliasmethod:App\Models\Patient::findExistingIdentityclass:App\Models\PatientMergeroute:portals.admin.mpi.candidates.mergemethod:App\Modules\MasterPatientIndex\Services\PatientMergeService::mergeroute:portals.admin.mpi.candidates.rejectmethod:App\Modules\MasterPatientIndex\Services\MasterPatientIndexService::rejectMatchmethod:App\Modules\MasterPatientIndex\Services\PatientMergeService::unmergeroute:portals.admin.mpi.merges.unmerge