# Getting Started
## Installation
Add `hl7v2` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:hl7v2, "~> 3.0"}
]
end
```
Then run `mix deps.get`.
## Parsing
HL7v2 supports two parsing modes: **raw** (canonical round-trip, delimiter-based) and **typed** (structs with named fields).
### Raw mode
```elixir
text = "MSH|^~\\&|HIS|HOSP|PACS|IMG|20260322||ADT^A01|MSG001|P|2.5\rPID|1||12345^^^MRN||Smith^John||19800315|M"
{:ok, raw} = HL7v2.parse(text)
raw.type # => {"ADT", "A01"}
raw.segments # => [{"MSH", [...]}, {"PID", [...]}]
```
### Typed mode
```elixir
{:ok, typed} = HL7v2.parse(text, mode: :typed)
msh = hd(typed.segments)
msh.sending_application.namespace_id # => "HIS"
msh.message_type.message_code # => "ADT"
pid = Enum.find(typed.segments, &is_struct(&1, HL7v2.Segment.PID))
hd(pid.patient_name).given_name # => "John"
```
### Parse + validate in one step
```elixir
{:ok, typed} = HL7v2.parse(text, mode: :typed, validate: true)
```
## Building Messages
Build HL7v2 messages programmatically with typed structs:
```elixir
alias HL7v2.Segment.{PID, EVN, PV1}
alias HL7v2.Type.{CX, XPN, FN, PL}
msg =
HL7v2.new("ADT", "A01",
sending_application: "MyApp",
sending_facility: "Hospital",
receiving_application: "PACS",
receiving_facility: "IMG"
)
|> HL7v2.Message.add_segment(%EVN{
event_type_code: "A01"
})
|> HL7v2.Message.add_segment(%PID{
patient_identifier_list: [%CX{id: "12345"}],
patient_name: [%XPN{family_name: %FN{surname: "Smith"}, given_name: "John"}]
})
|> HL7v2.Message.add_segment(%PV1{
patient_class: "I",
assigned_patient_location: %PL{point_of_care: "ICU", room: "101"}
})
wire = HL7v2.encode(msg)
```
## Acknowledgments (ACK/NAK)
Build ACK responses from the original message's MSH segment:
```elixir
# Parse the incoming message
{:ok, typed} = HL7v2.parse(incoming_wire, mode: :typed)
original_msh = hd(typed.segments)
# Accept
{ack_msh, msa} = HL7v2.Ack.accept(original_msh)
accept_wire = HL7v2.Ack.encode({ack_msh, msa})
# Reject with error details
{ack_msh, msa, err} = HL7v2.Ack.reject(original_msh,
error_code: "207",
error_text: "Application internal error",
text: "Could not process message"
)
reject_wire = HL7v2.Ack.encode({ack_msh, msa, err})
```
The shortcut `HL7v2.ack/2` is equivalent to `HL7v2.Ack.accept/2`:
```elixir
{ack_msh, msa} = HL7v2.ack(original_msh)
```
## Validation
```elixir
{:ok, typed} = HL7v2.parse(text, mode: :typed)
case HL7v2.validate(typed) do
:ok ->
# Message is valid
:ok
{:error, errors} ->
# errors is a list of maps with :level, :location, :field, :message
for err <- errors do
IO.puts("#{err.level} in #{err.location}: #{err.message}")
end
end
```
## MLLP Transport
HL7v2 includes an integrated MLLP transport layer built on Ranch 2.x.
### Server
Define a handler module implementing the `HL7v2.MLLP.Handler` behaviour:
```elixir
defmodule MyHandler do
@behaviour HL7v2.MLLP.Handler
@impl true
def handle_message(message, _meta) do
case HL7v2.parse(message, mode: :typed) do
{:ok, typed} ->
msh = hd(typed.segments)
{ack_msh, msa} = HL7v2.Ack.accept(msh)
{:ok, HL7v2.Ack.encode({ack_msh, msa})}
{:error, _reason} ->
{:error, :parse_failed}
end
end
end
```
Start the listener:
```elixir
{:ok, _pid} = HL7v2.MLLP.Listener.start_link(
port: 2575,
handler: MyHandler
)
```
### Client
```elixir
{:ok, client} = HL7v2.MLLP.Client.start_link(host: "localhost", port: 2575)
{:ok, ack} = HL7v2.MLLP.Client.send_message(client, wire)
:ok = HL7v2.MLLP.Client.close(client)
```
### TLS
Both listener and client support TLS:
```elixir
# Server with TLS
{:ok, _} = HL7v2.MLLP.Listener.start_link(
port: 2576,
handler: MyHandler,
tls: [certfile: "server.pem", keyfile: "server-key.pem", cacertfile: "ca.pem"]
)
# Client with TLS
{:ok, client} = HL7v2.MLLP.Client.start_link(
host: "remote.host",
port: 2576,
tls: [verify: :verify_peer, cacertfile: "ca.pem"]
)
```
## Working with Unknown Segments
Real-world HL7 messages contain segments the library doesn't have typed definitions for.
These are preserved losslessly — you never lose data:
```elixir
{:ok, msg} = HL7v2.parse(text, mode: :typed)
Enum.each(msg.segments, fn
%HL7v2.Segment.PID{} = pid ->
# Typed — access fields by name
IO.inspect(pid.patient_name)
%HL7v2.Segment.ZXX{segment_id: id, raw_fields: fields} ->
# Z-segment — preserved with original segment ID
IO.puts("Z-segment #{id}: #{inspect(fields)}")
{name, raw_fields} ->
# Unknown segment — preserved as raw tuple
IO.puts("Unknown #{name}: #{length(raw_fields)} fields")
end)
# Path access works on all forms:
HL7v2.get(msg, "PID-5") # typed struct field
HL7v2.get(msg, "ZPD-1") # ZXX raw field by position
HL7v2.get(msg, "PR1-3") # raw tuple field by position
```
All forms encode back to valid wire format with `HL7v2.encode/1`.
## Next Steps
- Browse the [API reference](https://hexdocs.pm/hl7v2) for full module documentation
- See `HL7v2.Segment` modules for available segment types and their fields
- See `HL7v2.Type` modules for composite data type structs