The CP (Composite Price) data type was introduced in HL7 v2.3 to express more than a bare monetary amount. CP wraps a MO price together with a price-type qualifier (actual, deposit, indirect, total), a numeric range (from / to), the units of that range, and a range-type code that says whether the price is flat, banded, or a percentage. CP is the dominant pricing composite in HL7 v2's financial segments (FT1) and pharmacy order/encoded segments (RXO, RXE).
Purpose
A single price field rarely carries enough context for downstream billing systems. A medication's price varies by quantity dispensed; a procedure's charge varies by duration; a deposit differs from the actual charge. CP packages all of that into one composite: the amount (CP.1), what kind of amount it is (CP.2), and — when the price is range-based — the numeric range, its units, and whether it's a flat band or a percentage (CP.3 – CP.6).
In practice most senders populate only CP.1 and CP.2; the range components are used by pharmacy and by tiered fee schedules where the price applies across a quantity range.
Component reference
Source: HAPI HL7v2 v2.8.1 javadocs — CP. CP has 6 components separated by ^. Length is not published in the javadocs (—).
| Seq | Name | Data Type | Length | Req | Description |
|---|---|---|---|---|---|
| CP.1 | Price | mo | — | R | The monetary amount, as quantity + ISO 4217 currency code. |
| CP.2 | Price Type | id | — | O | Drawn from HL7 table [HL70205]: AP actual price, DP deposit, IP indirect, TP total price. |
| CP.3 | From Value | nm | — | O | Lower bound of the range over which CP.1 applies (e.g. first ml dispensed). |
| CP.4 | To Value | nm | — | O | Upper bound of the range. |
| CP.5 | Range Units | cwe | — | O | Units for CP.3 / CP.4 — typically UCUM (ml, mg, min). |
| CP.6 | Range Type | id | — | O | Drawn from HL7 table [HL70298]: F flat rate, FT from-to, P percentage. |
CP.1 is the only required component. Everything else qualifies it.
Most-used components
- CP.1 Price — the MO value. The quantity-plus-currency pair carries the actual money. Without CP.1 the composite is meaningless.
- CP.2 Price Type — distinguishes a deposit from the actual charge from the total. Critical for billing systems that post different price types to different ledgers.
- CP.5 Range Units — when CP.3 / CP.4 are populated, the units must be unambiguous. UCUM codes are the modern default.
- CP.6 Range Type — tells the receiver whether to apply CP.1 as a flat amount across the range, as a from-to rate, or as a percentage.
Where it's used
- FT1-22 Transaction Amount — Extended — in v2.5+ profiles that retyped the original MO field to CP for richer pricing.
- FT1-23 Transaction Quantity — paired with CP for unit-priced charges.
- RXO-23 / RXO-24 Pharmacy Order pricing — drug cost and dispense cost.
- RXE-29 Give Per (Time Unit) pricing variants in some profiles.
- BLG-1 / BLG-3 Billing segment charge amounts.
- Various Z-segment fee fields in vendor charge-master interfaces.
CP is overwhelmingly an ADT/billing and pharmacy data type; clinical result segments rarely see it.
Version differences
- v2.3 — CP introduced with 6 components.
- v2.3.1 / v2.4 / v2.5 — structurally stable. CP.5 carried CE in early versions and was retyped to CWE as the standard migrated coded fields.
- v2.6 / v2.7 — CP.5 firmly CWE; HL70205 / HL70298 stable.
- v2.7.1 / v2.8 / v2.8.1 / v2.8.2 — structurally stable per the HAPI v2.8.1 javadoc. Same six components.
Common mistakes
- Sending CP.1 as a bare number (
100) rather than a full MO (100^USD). The MO composite requires the currency code; engines that strict-parse will reject the field. - Omitting CP.2 when CP.1 is a deposit. Without the price-type qualifier, billing systems post the value as an actual charge.
- Populating CP.3 / CP.4 without CP.5 — the receiver cannot interpret a range whose units are unspecified.
- Using a non-ISO-4217 currency code in CP.1.2 (the MO currency sub-component).
US$,Dollars,USD(trailing space) all break downstream rules. - Treating CP.6 =
Pas the amount being a percentage of CP.1. The percentage interpretation is of the range, not of the price; senders that confuse this end up off by orders of magnitude.
Examples
Minimal — flat actual price with currency only:
100^USD
With price type — explicit actual price:
100^USD^AP
Multi-component — actual price, flat rate:
100^USD^AP^^^F
From-to range with units — $0.50 per ml from 1 to 100 ml:
0.50^USD^AP^1^100^ml^UCUM^FT
(Note the CP.5 CWE: code ml, system UCUM.)
Percentage range type — a 10% surcharge band:
10^USD^IP^0^1000^USD^UCUM^P
In context — FT1 transaction with a deposit and an actual price (repetitions):
FT1|1|TXN-1001||20260610|20260610|CG|99213^Office visit^CPT4||1|||100^USD^AP~25^USD^DP|||||||||MERCY^^^ISO
Common pitfall — bare amount with no currency:
FT1|1|TXN-1001||20260610|20260610|CG|99213^Office visit^CPT4||1|||100|||||||||MERCY^^^ISO
The receiver parses CP.1 as the literal string 100 and cannot resolve the currency; some engines default to a site-configured currency and silently mis-bill cross-border encounters.
FHIR mapping
The HL7 v2-to-FHIR Implementation Guide does not publish a dedicated ConceptMap for CP — the datatype maps index lists Money and ChargeItem-adjacent maps but not CP itself. In practice CP decomposes across Money, optionally Range, and (in charge contexts) into ChargeItem.priceComponent.
| HL7 v2 | FHIR target | Notes |
|---|---|---|
| CP.1 Price | Money (.value + .currency) | The MO sub-components map directly. |
| CP.2 Price Type | ChargeItem.priceComponent.type or an extension | HL70205 → FHIR InvoicePriceComponentType where it aligns (base, deduction, etc.). |
| CP.3 / CP.4 From / To Value | Range.low.value / Range.high.value | Carried as an extension when CP.6 = FT. |
| CP.5 Range Units | Range.low.unit / .system / .code | UCUM mapping straight through. |
| CP.6 Range Type | Extension on the price component | No first-class FHIR analogue. |
In a ChargeItem-centric workflow, CP populates ChargeItem.priceComponent (one component for the base price, additional components for deposits or surcharges). In an Invoice-centric workflow, CP populates Invoice.lineItem.priceComponent.
Engine considerations
- MO nesting — CP.1 is itself a composite. Engines must split CP on
^then split CP.1 on&(sub-component delimiter) to recover the MO quantity and currency. A flat parse that treats CP.1 as a scalar will mis-handle the currency. - Currency normalization — sites occasionally emit non-ISO currency labels. The engine should map to ISO 4217 at the boundary and emit a warning, not silently rewrite.
- HL70205 / HL70298 validation — CP.2 and CP.6 are ID fields drawn from fixed HL7 tables. Unknown values should be rejected at parse time rather than passed through.
- Range semantics — when CP.3 / CP.4 are populated, downstream consumers must read CP.6 to know how to apply CP.1. Engines that drop CP.6 silently make all ranges flat.
- HAPI typing —
ca.uhn.hl7v2.model.v281.datatype.CPexposesgetCp1_Price()returningMO,getCp2_PriceType()returningID,getCp3_FromValue()/getCp4_ToValue()returningNM,getCp5_RangeUnits()returningCWE, andgetCp6_RangeType()returningID.
How Vorro parses and produces CP
On inbound, Vorro parses CP composites with explicit MO decomposition: CP.1 becomes a typed Money value with ISO-4217 currency normalization at the boundary, and any non-conforming currency tokens (US$, Dollars) are mapped to USD with a structured warning attached to the message — not silently rewritten. CP.5 Range Units are validated against the UCUM dictionary; non-UCUM units are preserved but flagged so downstream FHIR converters can route through an alternative units extension.
On outbound, Vorro emits CP only when the destination profile defines the field as CP. For v2.5+ destinations that retyped FT1-22 from MO to CP, Vorro promotes a bare MO to MO^AP^^^^F so the price type and range type are explicit. For ChargeItem-shaped FHIR sources we expand Invoice.lineItem.priceComponent into multiple CP repetitions (one per price component), preserving deposit / actual / total distinctions via CP.2.
