The MOP (Money or Percentage) data type was introduced in HL7 v2.5 to express insurance plan parameters that can be quoted either as a flat dollar amount or as a percentage — for example, a $250 deductible versus a 20% coinsurance. The first component is an indicator that disambiguates which interpretation applies; the second is the numeric value; the third carries an ISO 4217 currency code when the indicator is AT; the fourth optionally bounds an age range over which the value applies. MOP appears throughout IN2 insurance plan benefit fields and in IN1 deductibles.
Purpose
US-style insurance plans routinely mix fixed and proportional cost-sharing in the same benefit table: a $30 copay for primary care, 20% coinsurance for specialty care, a $250 deductible, an out-of-pocket maximum of $5,000, and pediatric riders that apply only under age 19. Without an explicit indicator, a receiver looking at the number 20 cannot tell whether it means $20 or 20%. MOP solves this by requiring the indicator (AT for amount, PC for percent) as the first component, then carrying the value, the currency (only meaningful for amounts), and the optional age range over which the benefit value applies.
Components
Source: HAPI HL7v2 v2.8.1 javadoc (MOP).
| Comp | Name | Sub-type | Length | Required | Description |
|---|---|---|---|---|---|
| MOP.1 | Money or Percentage Indicator | id | — | R | HL7 table 0148: AT (amount) or PC (percent). |
| MOP.2 | Money or Percentage Quantity | nm | — | R | The numeric value — a monetary amount when MOP.1 = AT, a percentage when MOP.1 = PC. |
| MOP.3 | Monetary Denomination | id | — | O | ISO 4217 alpha-3 currency code (HL7 table 0913); meaningful only when MOP.1 = AT. |
| MOP.4 | Age | nr | — | O | Age range (low–high) over which the value applies; used for age-banded benefits. |
Most-used components
- MOP.1 Indicator — the routing component; everything downstream depends on whether the value is a dollar amount or a percentage.
- MOP.2 Quantity — the actual value, parsed against MOP.1's interpretation.
- MOP.3 Denomination — populated whenever MOP.1 = AT; omitted (or empty) when MOP.1 = PC.
Where it's used
- IN2-21 Special Coverage Approval Title and benefit-percent fields.
- IN2-23 Patient ID Disclosure-related cost-share extensions in some profiles.
- IN1-8 Group Number cost-share extensions, where deductibles attach to the plan record.
- IN1 deductible/coinsurance overlay fields in eligibility-and-benefits profiles.
- IN3-23 Daily Charge fields when plans express a per-diem either flat or as a percent.
- Custom Z-segments emitted by US-payer EDI bridges that pair MOP with age-banded riders.
- ORC/FT1 financial extensions that need to express patient-responsibility splits.
Version differences
- HL7 v2.5 — MOP introduced with the four-component layout (indicator, quantity, denomination, age).
- HL7 v2.6 through v2.8 — no structural changes; HL7 table 0148 (AT/PC) and table 0913 (currency) bindings stabilised.
- HL7 v2.8.1 — HAPI javadoc confirms the four-component definition unchanged.
- HL7 v2.8.2 — no structural changes; table refreshes only.
Common mistakes
- Omitting MOP.1 indicator and sending just the value — receivers cannot tell whether
20means $20 or 20%; some engines default to AT, others to PC, and the resulting cost-share disagreement is silent. - Populating MOP.3 currency when MOP.1 = PC — a "20% USD" payload is nonsense and causes receivers to log warnings or reject.
- Putting
%or$glyphs in MOP.2 — MOP.2 is NM; the indicator already carries the unit semantics. - Misusing MOP.4 Age for the policyholder's age — MOP.4 is the age range over which the benefit applies, not the subscriber's current age.
- Using values like
0.20for 20% in MOP.2 when MOP.1 = PC — MOP.2 is the percentage as quoted (20for 20%), not a decimal fraction.
Examples
Minimal value — flat $150 amount in USD:
AT^150.00^USD
Minimal value — 20% coinsurance:
PC^20
Multi-component value — $30 copay with an age range applicable to pediatric coverage (0–18):
AT^30.00^USD^0-18
Fully populated edge case — 50% coinsurance applicable only to ages 65 and over:
PC^50^^65-120
In context — IN2-21 deductible line in an eligibility response:
IN2|1|||||||||||||||||||||AT^250.00^USD|||||||||||||||||||||||||||
In context — IN2 coinsurance percentage:
IN2|1|||||||||||||||||||||PC^20|||||||||||||||||||||||||||
Common pitfall — omitting the indicator:
^20^USD <- WRONG; receiver cannot tell AT vs PC
AT^20^USD <- CORRECT (interpret as $20)
PC^20 <- CORRECT (interpret as 20%)
FHIR mapping
The v2-to-FHIR Implementation Guide does not publish a per-datatype ConceptMap for MOP. The mapping splits on MOP.1:
| MOP value | FHIR target |
|---|---|
| MOP.1 = AT | Money — MOP.2 → Money.value, MOP.3 → Money.currency. |
| MOP.1 = PC | decimal (the percentage) carried on Coverage.costToBeneficiary.valueQuantity with unit %, or via a percentage extension. |
| MOP.4 = age range | applicable-age qualifier on the cost-to-beneficiary element (often modelled as an extension or a Coverage.class modifier). |
The FHIR Coverage.costToBeneficiary element allows either valueQuantity (used for percentages with unit %) or valueMoney (used for flat amounts), which mirrors MOP's split.
Engine considerations
- Indicator interpretation: route on MOP.1 before parsing MOP.2 — applying currency validation to a percentage or percent semantics to a dollar amount silently corrupts cost-share.
- Currency validation: when MOP.1 = AT, MOP.3 must be a valid ISO 4217 alpha-3 (HL7 table 0913); when MOP.1 = PC, MOP.3 must be empty and engines should reject (or strip with provenance) any value present.
- Percentage range sanity: percentages outside
0–100are almost always payer-data defects (a120coinsurance is meaningless); engines should flag these for curation rather than passing through. - Age-range parsing: MOP.4 is an NR — two numerics with a dash. Engines that treat it as a single NM will mis-parse the upper bound.
- BigDecimal precision: amounts carry minor units and require
BigDecimal; percentages routinely carry one or two decimal places (17.5) and must not be truncated to integer.
How Vorro parses and produces MOP
On inbound, Vorro routes on MOP.1 first. When the indicator is AT, MOP.2 is parsed as a BigDecimal and MOP.3 is validated against ISO 4217; the result is held as a Money value object that round-trips losslessly. When the indicator is PC, MOP.2 is parsed as a BigDecimal percentage (no implicit division by 100), MOP.3 is rejected with a curation note if populated, and the value is held as a percentage object distinct from money. MOP.4 is parsed as an NR (low, high) age range and attached to the benefit as an applicability qualifier.
On outbound, Vorro emits MOP.1 explicitly on every message — never relying on receiver defaults. Currency is written only when the indicator is AT; percentages are written as the quoted value (20 for 20%, not 0.20). Age ranges are populated only when the source benefit record carries one; partial age ranges (open-ended high) are emitted using site-configured upper bounds (typically 120) rather than left blank.
