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:

  1. No Match (404): Use PAPI to create a new case
  2. Single Match (singleton_match: true): Returns one case_id for targeted PAPI update
  3. Multiple Matches (singleton_match: false): Returns array of cases for:
    • Batch updates (iterate through each case_id with PAPI)
    • Selection logic (choose specific cases to update)

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: MaleM, FemaleF; 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-01 from 2024-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 Section
  • COLONOSCOPY, ERCP, Cardioversion, IUP
  • Repeat C-Section, 0Repeat C-Section, Repeat C/S, Repeat CS
  • Labor Epidural, L ESWL, R ESWL, Lap apy
  • SAR, 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_datetimes extracts "2024-01-01" from the start_time field
  • remove_diacriticals converts "José García" to "Jose Garcia"
  • to_upper converts 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_datetimes normalization 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_match is true

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 name3 rules match using only the first 3 letters of names, useful for handling nicknames or spelling variations
  • The ops_data fields 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_rules array

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. Returns 404 if multiple matches are found.
  • With singleton_match: false: Returns all matching case_ids for batch operations
  • Empty array in a 200 response 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. Returns 404 for 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

  1. Root-level matching_synthetic_case_ids - This is your actual result:

    • Empty array = 404 (no matches found)
    • Populated array = 200 (matches found)
  2. 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
  3. Multiple matching_synthetic_case_ids arrays - 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
  4. Rule evaluation stops - When singleton_match: true, evaluation stops after the first successful rule. In Example 3, once the fin rule matched, no further rules were evaluated.