SMS - Medaxion/open_api_resources GitHub Wiki
Subject Matching Service
The Subject Matching Service (SMS) wraps the Pulse Open API Find Case endpoint, adding configurable normalization and matching rule functionality.
Integration with PAPI Service
SMS typically works in conjunction with the PAPI Service for complete case management workflows:
Common Integration Patterns:
- No Match (404): Use PAPI to create a new case
- Single Match (
singleton_match: true): Returns onecase_idfor targeted PAPI update - Multiple Matches (
singleton_match: false): Returns array of cases for:- Batch updates (iterate through each
case_idwith PAPI) - Selection logic (choose specific cases to update)
- Batch updates (iterate through each
Workflow Examples:
SMS search → 404 (no match) → PAPI create new case
SMS search → 200 (single match) → PAPI update with case_id
SMS search → 200 (multiple matches) → Select/iterate cases → PAPI updates
Functional Endpoint
POST => {api_host}/v1/
Documentation Endpoints
GET => {api_host}/v1/normalizations => will return all implemented normalizations available
GET => {api_host}/v1/matching_rules => will return all implemented matching rules and the fields addressed by each rule
Request Format
- HTTP Method:
POST - Content Type:
application/json
Request Parameters
| Parameter | Required | Description |
|---|---|---|
api_token |
Yes | Pulse Open API authentication token. |
api_host |
Yes | Pulse API endpoint URL. |
pulse_location_ids |
No | Array of integers to limit matching to specific locations. |
normalizations |
No | Array of normalization keywords (see details below). |
singleton_match |
No | Boolean; defaults to false. If true, matching succeeds only if exactly one case matches. |
matching_rules |
Yes | Array of matching rule keywords (see below); evaluated in order. |
case_json |
Yes | JSON object representing a case with required metadata per matching rules. |
Important Note: SMS and PAPI use identical case_json structures. Any case object prepared for SMS matching can be used directly with PAPI for creating or updating cases, and vice versa. This enables seamless workflow integration: search with SMS using your case data, then pass the exact same request structure to PAPI for operations.
Example Request
{
"api_token": "your_token",
"api_host": "your_api_host",
"pulse_location_ids": [1, 2, 3],
"normalizations": ["remove_diacriticals", "to_upper"],
"singleton_match": true,
"matching_rules": ["reduce_on_first_last", "reduce_on_mrn"],
"case_json": {
...
}
}
Normalizations
Normalizations temporarily modify field values from the case_json request to enhance matching accuracy. They do not affect data outside SMS.
Important: The normalizations operate on the actual JSON field names you provide in your case_json request. Some normalizations internally map these fields for processing (e.g., number is treated as FIN, patient_number as MRN, and start_time is used for DOS comparisons).
Field Name Mapping Reference
| JSON Field (what you provide) | Internal Reference | Common Name |
|---|---|---|
number |
case_number | FIN |
patient_number |
patient_number | MRN |
patient_first_name |
patient_first_name | First Name |
patient_last_name |
patient_last_name | Last Name |
patient_dob |
patient_dob | DOB |
gender |
gender | Gender |
start_time |
intraop_dos, postop_dos, scheduled_dos | DOS (Date of Service) |
Available Normalizations
| Normalization Keyword | Description | Affected JSON Fields | Notes |
|---|---|---|---|
remove_suffixes |
Removes name suffixes: jr., sr., jr, sr, iii |
patient_first_name, patient_last_name |
|
remove_diacriticals |
Removes diacritical marks (é→e, ñ→n) using Unicode NFD decomposition | patient_first_name, patient_last_name |
Enables matching regardless of accent marks |
remove_spaces_and_special |
Removes all non-alphanumeric characters /[^a-zA-Z0-9]/g |
patient_first_name, patient_last_name |
|
remove_non_alpha |
Removes all non-alphabetic characters except spaces /[^a-zA-Z ]/g |
patient_first_name, patient_last_name |
|
sanitize_dob |
Clears DOBs that are: future-dated, >100 years old, or today's date | patient_dob |
Returns empty string for invalid dates |
remove_repeated_chars |
Removes consecutive duplicate characters /(.)\1{1,}/g |
number, patient_number |
e.g., "1112223" → "123" |
to_upper |
Converts all letters to uppercase | patient_first_name, patient_last_name |
|
dob_blacklist |
Clears DOB if it matches blacklisted values: 9999-99-99, 1900-01-01, 0000-00-00 |
patient_dob |
Common placeholder dates |
abbreviate_gender |
Translates: Male→M, Female→F; clears unrecognized values |
gender |
Standardizes gender format |
mrn_fin_blacklist |
Clears field if it matches blacklisted values (see list below) | number, patient_number |
Removes common non-ID values |
dos_from_datetimes |
Extracts date portion from datetime for DOS comparisons | start_time |
REQUIRED for all DOS-related matching rules |
Critical Note on DOS Matching
The dos_from_datetimes normalization is required for any matching rule that references DOS (Date of Service). Without this normalization:
- The system will attempt to match the full datetime string from
start_time - DOS-based matching rules will likely fail
- The normalization extracts just the date portion (e.g.,
2024-01-01from2024-01-01T07:33:06-05:00)
MRN/FIN Blacklist Values
The following values in number or patient_number fields will be cleared when using mrn_fin_blacklist:
Common medical procedures mistakenly entered as IDs:
AQ,Lap chole,Ex lap,EGD,Labor,C/S,CS,C SectionCOLONOSCOPY,ERCP,Cardioversion,IUPRepeat C-Section,0Repeat C-Section,Repeat C/S,Repeat CSLabor Epidural,L ESWL,R ESWL,Lap apySAR,Tvugor,CLE
Single digits and symbols often used as placeholders:
0,1,2,3,4,5,6,7,8,9?,?-
Example Usage
{
"normalizations": [
"dos_from_datetimes", // Required for DOS matching rules
"remove_diacriticals",
"to_upper",
"mrn_fin_blacklist"
],
"matching_rules": ["intraop_dos_and_fin"],
"case_json": {
"number": "12345",
"start_time": "2024-01-01T07:33:06-05:00",
"patient_first_name": "José",
"patient_last_name": "García"
}
}
In this example:
dos_from_datetimesextracts "2024-01-01" from thestart_timefieldremove_diacriticalsconverts "José García" to "Jose Garcia"to_upperconverts to "JOSE GARCIA"- The matching rule then uses the normalized date and FIN values for comparison
Matching Rules
Matching rules evaluate provided data against existing cases to find matches. Rules are applied sequentially to candidates returned from the initial Pulse Open API Find Case query.
Important:
- DOS-related matching rules typically benefit from the
dos_from_datetimesnormalization for date-only comparison - Rules use the actual JSON field names from your
case_json(see Field Name Reference below) - Matching stops after the first successful match if
singleton_matchistrue
Field Name Reference
| What the rule name says | Actual JSON field to provide | Description |
|---|---|---|
fin |
number |
Case/FIN number |
mrn |
patient_number |
Patient/MRN number |
name |
patient_first_name, patient_last_name |
Patient name fields |
name3 |
patient_first_name, patient_last_name |
Uses first 3 letters only |
dob |
patient_dob |
Date of birth |
gender |
gender |
Patient gender |
ops_data_*_uid |
ops_data array with matching key |
Ops data lookup |
*_dos |
start_time |
Benefits from dos_from_datetimes normalization |
Available Matching Rules
| Matching Rule Keyword | Fields Used for Matching | DOS Normalization Recommended |
|---|---|---|
dob |
patient_dob | No |
fin |
number | No |
fin_and_ops_data_aux_uid |
number, ops_data[key="aux_uid"] | No |
gender |
gender | No |
intraop_dos |
start_time (compared as intraop date) | Yes |
intraop_dos_and_fin |
number, start_time (compared as intraop date) | Yes |
intraop_dos_and_mrn |
patient_number, start_time (compared as intraop date) | Yes |
intraop_dos_and_name |
patient_first_name, patient_last_name, start_time (compared as intraop date) | Yes |
intraop_dos_and_name_and_dob |
patient_first_name, patient_last_name, patient_dob, start_time (compared as intraop date) | Yes |
mrn |
patient_number | No |
mrn_and_name_and_dob_and_fin_and_intraop_dos |
patient_number, patient_first_name, patient_last_name, patient_dob, number, start_time (compared as intraop date) | Yes |
mrn_and_name_and_dob_and_gender_and_fin |
patient_number, patient_first_name, patient_last_name, patient_dob, gender, number | No |
mrn_and_name_and_dob_and_gender_and_intraop_dos |
patient_number, patient_first_name, patient_last_name, patient_dob, gender, start_time (compared as intraop date) | Yes |
mrn_and_name_and_gender_intraop_dos |
patient_number, patient_first_name, patient_last_name, gender, start_time (compared as intraop date) | Yes |
mrn_and_name_and_intraop_dos_and_gender_and_fin |
patient_number, patient_first_name, patient_last_name, start_time (compared as intraop date), gender, number | Yes |
mrn_and_name3_and_dob_and_intraop_dos_and_gender |
patient_number, patient_first_name (first 3 letters), patient_last_name (first 3 letters), patient_dob, start_time (compared as intraop date), gender | Yes |
name |
patient_first_name, patient_last_name | No |
name3 |
patient_first_name (first 3 letters), patient_last_name (first 3 letters) | No |
name3_and_fin_and_dob_and_gender_and_intraop_dos |
patient_first_name (first 3 letters), patient_last_name (first 3 letters), number, patient_dob, gender, start_time (compared as intraop date) | Yes |
name_and_dob |
patient_first_name, patient_last_name, patient_dob | No |
name_and_dob_and_gender |
patient_first_name, patient_last_name, patient_dob, gender | No |
name_and_dob_and_gender_intraop_dos |
patient_first_name, patient_last_name, patient_dob, gender, start_time (compared as intraop date) | Yes |
name_and_gender |
patient_first_name, patient_last_name, gender | No |
ops_data_aux_uid |
ops_data[key="aux_uid"] | No |
ops_data_case_uid |
ops_data[key="case_uid"] | No |
patient_first_name3 |
patient_first_name (first 3 letters) | No |
patient_last_name3 |
patient_last_name (first 3 letters) | No |
postop_dos |
start_time (compared as postop date) | Yes |
postop_dos_and_fin |
number, start_time (compared as postop date) | Yes |
postop_dos_and_mrn |
patient_number, start_time (compared as postop date) | Yes |
postop_dos_and_name |
patient_first_name, patient_last_name, start_time (compared as postop date) | Yes |
postop_dos_and_name_and_dob |
patient_first_name, patient_last_name, patient_dob, start_time (compared as postop date) | Yes |
scheduled_dos |
start_time (compared as scheduled date) | Yes |
scheduled_dos_and_fin |
number, start_time (compared as scheduled date) | Yes |
scheduled_dos_and_mrn |
patient_number, start_time (compared as scheduled date) | Yes |
scheduled_dos_and_name |
patient_first_name, patient_last_name, start_time (compared as scheduled date) | Yes |
scheduled_dos_and_name_and_dob |
patient_first_name, patient_last_name, patient_dob, start_time (compared as scheduled date) | Yes |
Example Request with DOS Matching
{
"api_token": "your_token",
"api_host": "your_api_host",
"normalizations": [
"dos_from_datetimes", // Recommended for date-only DOS matching
"to_upper"
],
"matching_rules": [
"scheduled_dos_and_mrn", // Will use start_time as scheduled date
"name_and_dob" // Fallback if first rule doesn't match
],
"case_json": {
"patient_number": "123456",
"patient_first_name": "John",
"patient_last_name": "Smith",
"patient_dob": "1980-05-15",
"start_time": "2024-01-15T07:30:00-05:00"
}
}
Example Request with Ops Data Matching
{
"api_token": "your_token",
"api_host": "your_api_host",
"matching_rules": ["ops_data_aux_uid"],
"case_json": {
"ops_data": [
{
"key": "aux_uid",
"value": "ABC123"
},
{
"key": "other_data",
"value": "XYZ"
}
]
}
}
Notes
- The
name3rules match using only the first 3 letters of names, useful for handling nicknames or spelling variations - The
ops_datafields look for specific key-value pairs within the ops_data array - Without
dos_from_datetimes, DOS rules compare full datetimes (date + time) - With
dos_from_datetimes, DOS rules compare dates only (time stripped) - Use the normalization for intraop/postop matching when source systems only have date information
- Skip the normalization for scheduled cases if you need to distinguish multiple appointments on the same day
- Rules are evaluated in the order specified in the
matching_rulesarray
Response Format
- Content Type:
application/json
HTTP Response Codes
| Code | Meaning |
|---|---|
200 |
One or more matches found. |
400 |
Invalid input (details provided in response). |
404 |
No matching cases found. |
Response Body
| Field | Description |
|---|---|
error |
Error message, if applicable |
matching_synthetic_case_ids |
Array of matching case IDs (single element if singleton_match: true) |
normalizations |
Array of normalizations that were applied |
match_report |
Detailed results for each matching rule evaluated |
Response Behaviors:
- With
singleton_match: true: Returns at most one case_id, even if multiple matches exist. Returns404if multiple matches are found. - With
singleton_match: false: Returns all matching case_ids for batch operations - Empty array in a
200response indicates matches were found but filtered out
Matching Logic
Matching rules are applied sequentially to candidates returned from the initial Pulse Open API Find Case query.
Singleton Match Behavior:
- When
singleton_match: true: Matching succeeds only if exactly one match is found. Returns404for no matches or multiple matches. - When
singleton_match: false: All matching cases are returned, allowing for batch operations or selection logic.
SMS returns 404 Not Found if no successful match is found. Use this response to trigger new case creation via PAPI.
Response Examples
Understanding the Response Structure
The SMS response contains detailed matching information at multiple levels. The root-level matching_synthetic_case_ids array is the actual result - this determines whether the API returns 200 (matches found) or 404 (no matches).
Example 1: No Candidates Found (404 Response)
When no cases meet the initial query criteria:
{
"response": {
"match_report": [],
"matching_synthetic_case_ids": [], // ← Empty = 404 response
"normalizations": ["dos_from_datetimes"]
}
}
Example 2: Candidates Found but No Matches (404 Response)
When cases are found and evaluated but none match the rules:
{
"response": {
"match_report": [
{
"matching_rule": "scheduled_dos_and_fin",
"is_success": false, // Rule did not match
"matching_synthetic_case_ids": [], // No matches for this rule
"candidate_results": [
{
"synthetic_case_id": "uat-acustomer-1768786",
"name": "scheduled_dos_and_fin",
"is_match": false, // This candidate didn't match
"comparisons": [
{
"name": "scheduled_dos",
"is_match": false,
"candidate": {
"path": "scheduled_at",
"raw": "2024-10-31T12:15:00Z",
"normalized": "2024-10-31"
},
"inbound": {
"path": "start_time",
"raw": null,
"normalized": null
}
}
]
}
]
},
{
"matching_rule": "fin",
"is_success": false,
"matching_synthetic_case_ids": [],
"candidate_results": [
{
"synthetic_case_id": "uat-acustomer-1768786",
"name": "fin",
"is_match": false,
"comparisons": [
{
"name": "case_number",
"is_match": false,
"candidate": {
"path": "case_number",
"raw": "266438727",
"normalized": "266438727"
},
"inbound": {
"path": "number",
"raw": "681417418",
"normalized": "681417418"
}
}
]
}
]
}
],
"matching_synthetic_case_ids": [], // ← Empty = 404 response
"normalizations": ["dos_from_datetimes", "abbreviate_gender"]
}
}
Example 3: Successful Match (200 Response)
When at least one case matches the rules:
{
"response": {
"match_report": [
{
"matching_rule": "scheduled_dos_and_fin",
"is_success": false, // First rule didn't match
"matching_synthetic_case_ids": [],
"candidate_results": [
// ... candidates that didn't match this rule
]
},
{
"matching_rule": "fin",
"is_success": true, // Second rule matched!
"matching_synthetic_case_ids": ["uat-acustomer-2514379"], // Matches for this rule
"candidate_results": [
{
"synthetic_case_id": "uat-acustomer-1772017",
"name": "fin",
"is_match": false, // First candidate didn't match
"comparisons": [
{
"name": "case_number",
"is_match": false,
"candidate": {
"normalized": "267069334"
},
"inbound": {
"normalized": "682622785"
}
}
]
},
{
"synthetic_case_id": "uat-acustomer-2514379",
"name": "fin",
"is_match": true, // Second candidate matched!
"comparisons": [
{
"name": "case_number",
"is_match": true,
"candidate": {
"normalized": "682622785"
},
"inbound": {
"normalized": "682622785"
}
}
]
}
]
}
],
"matching_synthetic_case_ids": ["uat-acustomer-2514379"], // ← Has matches = 200 response
"normalizations": ["dos_from_datetimes", "abbreviate_gender"]
}
}
Key Points About the Response
-
Root-level
matching_synthetic_case_ids- This is your actual result:- Empty array = 404 (no matches found)
- Populated array = 200 (matches found)
-
match_report- Detailed debugging information showing:- Which rules were evaluated (
matching_rule) - Whether each rule succeeded (
is_success) - Which candidates were considered
- How each field comparison was performed
- Which rules were evaluated (
-
Multiple
matching_synthetic_case_idsarrays - Don't confuse these:- Root level = actual result (use this!)
- Inside match_report entries = matches for that specific rule
- These are provided for debugging, not as the primary result
-
Rule evaluation stops - When
singleton_match: true, evaluation stops after the first successful rule. In Example 3, once thefinrule matched, no further rules were evaluated.