> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://imo.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://imo.ferndocs.com/_mcp/server.

# Normalizing terms and codes

`POST /precision/normalize/v1` is the general-purpose engine. It works across all
clinical domains, is code-system agnostic, and honors your custom lexical maps.

## Term-based vs code-based matching

| You supply                         | Behavior                                                                       |
| ---------------------------------- | ------------------------------------------------------------------------------ |
| `input_term`                       | Fuzzy, scored matching. Returns every lexical at or above `threshold`, ranked. |
| `input_code` + `input_code_system` | Deterministic lookup. Returns at most one lexical with `score: 1`.             |
| Both                               | `preferences.match_field_pref` decides which wins.                             |

The response's `response.match_field` tells you which path was actually taken.

### Choosing between them

```json
{
  "client_request_id": "batch-001",
  "preferences": { "match_field_pref": "input_code_first" },
  "requests": [
    {
      "record_id": "1",
      "domain": "Problem",
      "input_term": "chest pain",
      "input_code": "R07.9",
      "input_code_system": "ICD-10-CM"
    }
  ]
}
```

| `match_field_pref` | Meaning                             |
| ------------------ | ----------------------------------- |
| `input_term`       | Only ever match on the term         |
| `input_code`       | Only ever match on the code         |
| `input_term_first` | Try the term; fall back to the code |
| `input_code_first` | Try the code; fall back to the term |

## Preferences

**`threshold`** `number` — default: varies by domain

Minimum score a lexical must reach to be returned, up to 3 decimal places.
Irrelevant for code-based matching, where a match is always `1`.

---

**`size`** `integer`

Maximum lexicals returned per record. When more match than the limit, the
highest-scoring win; ties break toward the more concise lexical.

---

**`distinct_field`** `enum`

`lexical_code` or `default_lexical_code`. Which field to de-duplicate results on.

---

**`discrepancy_check`** `boolean`

When `true`, flags records where the supplied term and code disagree. The
mismatch surfaces in each item's `discrepancy` object.

---

**`use_custom_maps`** `boolean`

Apply your organization's custom lexical mappings before falling back to
IMO's standard content.

---

**`use_domain_relevance_filter`** `boolean`

Drop matches that don't belong to the requested `domain`.

---

**`use_semantic_match`** `boolean`

Enable semantic (meaning-based) matching in addition to lexical similarity.

---

## Transformations

Transformations rewrite the input term **before** matching — useful for stripping
site-specific prefixes or expanding local abbreviations.

```json
{
  "preferences": {
    "transformations": [
      { "type": "string_replace", "from": "hx of ", "to": "history of ",
        "location": "beginning" },
      { "type": "multiple_string_replace", "from": ["r/o", "R/O"],
        "to": "rule out", "location": "*" }
    ]
  }
}
```

| Field      | Values                                      |
| ---------- | ------------------------------------------- |
| `type`     | `string_replace`, `multiple_string_replace` |
| `location` | `beginning`, `end`, `*` (anywhere)          |

The rewritten term comes back as `transformed_term` on both the record and its
`response`, so you can see exactly what was matched.

## Filters

Filters constrain results to a subset of IMO content — currently scoped to USCDI
profile attributes.

```json
{
  "preferences": {
    "filters": [
      { "field": "uscdi.profiles.data_class", "operator": "eq",
        "value": "Health Status Assessments" }
    ]
  }
}
```

| Field                         | Purpose                                          |
| ----------------------------- | ------------------------------------------------ |
| `uscdi.profiles.valueset_oid` | Restrict to a specific USCDI value set           |
| `uscdi.profiles.hrsn_domain`  | Restrict to a health-related social needs domain |
| `uscdi.profiles.data_element` | Restrict to a USCDI data element                 |
| `uscdi.profiles.data_class`   | Restrict to a USCDI data class                   |

Only the `eq` operator is supported today.

## Batching

One request carries up to **300 records**. Batch aggressively — it amortizes
round-trip latency and counts as a single call against your rate limit.

**`TypeScript`**

```typescript title="TypeScript"
const rows = await loadClinicalRows(); // your data

// The API caps a batch at 300 records.
for (let i = 0; i < rows.length; i += 300) {
  const chunk = rows.slice(i, i + 300);

  const result = await client.normalize.normalize({
    clientRequestId: `batch-${i / 300}`,
    preferences: { threshold: 0.5, size: 1 },
    requests: chunk.map((row) => ({
      recordId: row.id,
      domain: "Problem",
      inputTerm: row.text,
    })),
  });

  await persist(result.requests ?? []);
}
```

**`Python`**

```python title="Python"
from imo_precision_normalize import (
    NormalizeRequestPreferences,
    NormalizeRequestRequestsItem,
)

rows = load_clinical_rows()  # your data

# The API caps a batch at 300 records.
for i in range(0, len(rows), 300):
    chunk = rows[i : i + 300]

    result = client.normalize.normalize(
        client_request_id=f"batch-{i // 300}",
        preferences=NormalizeRequestPreferences(threshold=0.5, size=1),
        requests=[
            NormalizeRequestRequestsItem(
                record_id=r["id"], domain="Problem", input_term=r["text"]
            )
            for r in chunk
        ],
    )

    persist(result.requests or [])
```

> **Warning**
>
> Exceeding 300 records returns `413 Payload Too Large`. Chunk client-side.

## Lab results and LOINC parts

For the `Lab` domain, supplying LOINC axis hints in `context` sharply improves
matching, and the response returns resolved `loinc_parts`:

```json
{
  "record_id": "lab-1",
  "domain": "Lab",
  "input_term": "hemoglobin",
  "context": {
    "unit_of_measure": "g/dL",
    "sample_type": "Blood",
    "loinc_property": "MCnc"
  }
}
```

The matching `response.loinc_parts` breaks out `component`, `property`,
`sample_type`, `method`, `scale`, and `time`.