|
| 1 | +# Configure Merge View |
| 2 | + |
| 3 | +By default, the merge UI displays a fixed set of patient fields (ID, name, gender, birth date). The **MergeViewModel** feature lets you customize which fields appear in the merge view — and in what order — without changing any code. |
| 4 | + |
| 5 | +A MergeViewModel is a database record containing an ordered list of field descriptors. Each field has a human-readable **label** and a **JSONPath expression** that tells the system how to read and write values from FHIR Patient resources. |
| 6 | + |
| 7 | +## How it works |
| 8 | + |
| 9 | +1. You create a `MergeViewModel` record in the database with the desired field configuration. |
| 10 | +2. You set the `MERGE_VIEW_MODEL_ID` environment variable to the record's ID. |
| 11 | +3. When a clinician opens the merge UI, the system loads the configured fields and renders them instead of the defaults. |
| 12 | + |
| 13 | +If the environment variable is not set or the record is not found, the system falls back to the default field list automatically. |
| 14 | + |
| 15 | +## Environment variable |
| 16 | + |
| 17 | +| Variable | Required | Default | Description | |
| 18 | +|----------|----------|---------|-------------| |
| 19 | +| `MERGE_VIEW_MODEL_ID` | No | — | ID of the MergeViewModel record to use. If unset, the default fields are shown. | |
| 20 | + |
| 21 | +## Create a MergeViewModel |
| 22 | + |
| 23 | +Insert a record directly into the database: |
| 24 | + |
| 25 | +```sql |
| 26 | +INSERT INTO mpi.mergeviewmodel (id, version, resource) |
| 27 | +VALUES ( |
| 28 | + 'my-merge-view', |
| 29 | + 1, |
| 30 | + '{ |
| 31 | + "id": "my-merge-view", |
| 32 | + "name": "Custom Merge View", |
| 33 | + "fields": [ |
| 34 | + { "label": "ID", "path": "$.id" }, |
| 35 | + { "label": "First Name", "path": "$.name[0].given[0]" }, |
| 36 | + { "label": "Last Name", "path": "$.name[0].family" }, |
| 37 | + { "label": "Gender", "path": "$.gender" }, |
| 38 | + { "label": "Birth Date", "path": "$.birthDate" }, |
| 39 | + { "label": "Phone", "path": "$.telecom[?(@.system==\"phone\")].value" }, |
| 40 | + { "label": "Email", "path": "$.telecom[?(@.system==\"email\")].value" } |
| 41 | + ] |
| 42 | + }'::jsonb |
| 43 | +); |
| 44 | +``` |
| 45 | + |
| 46 | +Then set the environment variable for the MDM **frontend** service: |
| 47 | + |
| 48 | +```bash |
| 49 | +MERGE_VIEW_MODEL_ID=my-merge-view |
| 50 | +``` |
| 51 | + |
| 52 | +After restarting the service, the merge UI will display the configured fields. |
| 53 | + |
| 54 | +## Field descriptor format |
| 55 | + |
| 56 | +Each entry in the `fields` array has two properties: |
| 57 | + |
| 58 | +| Property | Type | Description | |
| 59 | +|----------|------|-------------| |
| 60 | +| `label` | string | Display name shown in the merge UI | |
| 61 | +| `path` | string | JSONPath expression for reading and writing the value | |
| 62 | + |
| 63 | +The same JSONPath expression is used for both **reading** the value from each patient and **writing** the selected value into the merge result. The order of fields in the array determines the display order in the UI. |
| 64 | + |
| 65 | +## Supported JSONPath expressions |
| 66 | + |
| 67 | +Field paths use [JSONPath](https://goessner.net/articles/JsonPath/) syntax compatible with the `jsonpath-plus` library. Examples: |
| 68 | + |
| 69 | +| Path | Description | |
| 70 | +|------|-------------| |
| 71 | +| `$.id` | Patient ID | |
| 72 | +| `$.name[0].given[0]` | First given name | |
| 73 | +| `$.name[0].given[1]` | Second given name (middle name) | |
| 74 | +| `$.name[0].family` | Family name | |
| 75 | +| `$.gender` | Gender | |
| 76 | +| `$.birthDate` | Date of birth | |
| 77 | +| `$.address[0].line[0]` | First address line | |
| 78 | +| `$.address[0].line[1]` | Second address line | |
| 79 | +| `$.address[0].city` | City | |
| 80 | +| `$.address[0].state` | State | |
| 81 | +| `$.address[0].postalCode` | ZIP / postal code | |
| 82 | +| `$.telecom[?(@.system=="phone")].value` | Phone number (filter expression) | |
| 83 | +| `$.telecom[?(@.system=="email")].value` | Email address (filter expression) | |
| 84 | + |
| 85 | +{% hint style="info" %} |
| 86 | +Filter expressions like `[?(@.system=="phone")]` let you target specific entries in arrays based on a condition, rather than relying on a fixed array index. |
| 87 | +{% endhint %} |
| 88 | + |
| 89 | +## Default fields |
| 90 | + |
| 91 | +When no MergeViewModel is configured, the system uses the following default fields: |
| 92 | + |
| 93 | +| Label | Path | |
| 94 | +|-------|------| |
| 95 | +| ID | `$.id` | |
| 96 | +| First Name | `$.name[0].given[0]` | |
| 97 | +| Second Name | `$.name[0].given[1]` | |
| 98 | +| Last Name | `$.name[0].family` | |
| 99 | +| Gender | `$.gender` | |
| 100 | +| Birth Date | `$.birthDate` | |
| 101 | + |
| 102 | +## Merge UI behavior |
| 103 | + |
| 104 | +The merge view displays each configured field as a row with values from both patient records: |
| 105 | + |
| 106 | +- If both patients have the **same value** for a field, it is displayed without a selection control. |
| 107 | +- If the values **differ**, radio buttons allow the clinician to pick which value to keep in the merged result. |
| 108 | +- If a JSONPath expression resolves to **no value** in one or both patients, an empty string is shown. |
| 109 | + |
| 110 | +The clinician can also select an entire patient column at once using the header radio buttons, which copies all field values from that patient into the merge result. |
| 111 | + |
| 112 | +## Update a MergeViewModel |
| 113 | + |
| 114 | +To change the field configuration, update the record in the database: |
| 115 | + |
| 116 | +```sql |
| 117 | +UPDATE mpi.mergeviewmodel |
| 118 | +SET resource = '{ |
| 119 | + "id": "my-merge-view", |
| 120 | + "name": "Updated Merge View", |
| 121 | + "fields": [ |
| 122 | + { "label": "ID", "path": "$.id" }, |
| 123 | + { "label": "First Name", "path": "$.name[0].given[0]" }, |
| 124 | + { "label": "Last Name", "path": "$.name[0].family" }, |
| 125 | + { "label": "Birth Date", "path": "$.birthDate" } |
| 126 | + ] |
| 127 | +}'::jsonb, |
| 128 | +version = version + 1 |
| 129 | +WHERE id = 'my-merge-view'; |
| 130 | +``` |
| 131 | + |
| 132 | +Changes take effect on the next merge page load — no redeployment is required. |
| 133 | + |
| 134 | +## Database schema |
| 135 | + |
| 136 | +The MergeViewModel is stored in two tables: |
| 137 | + |
| 138 | +**`mpi.mergeviewmodel`** — active configuration: |
| 139 | + |
| 140 | +| Column | Type | Description | |
| 141 | +|--------|------|-------------| |
| 142 | +| `id` | `text` (PK) | Unique identifier | |
| 143 | +| `version` | `integer` | Incremented on each update | |
| 144 | +| `cts` | `timestamptz` | Created / last updated timestamp | |
| 145 | +| `resource` | `jsonb` | Configuration including the `fields` array | |
| 146 | + |
| 147 | +**`mpi.mergeviewmodel_history`** — audit trail of previous versions: |
| 148 | + |
| 149 | +| Column | Type | Description | |
| 150 | +|--------|------|-------------| |
| 151 | +| `id` | `text` | Record identifier | |
| 152 | +| `version` | `integer` | Version number | |
| 153 | +| `cts` | `timestamptz` | Timestamp of this version | |
| 154 | +| `resource` | `jsonb` | Configuration snapshot | |
| 155 | +| | | UNIQUE (`id`, `version`) | |
0 commit comments