Expand description
§ER7 redact
website • documentation • source • crate • email
Remove patient detail from HL7 v2 messages in the ER7 pipe-hat encoding — without breaking the message.
A redacted message still parses, still declares the same delimiters, and still holds a value in every position that held one before, so everything downstream of it behaves the way it did on the original. That is the whole design: redaction rewrites leaf text and nothing else (D1).
Example:
use er7_redact::{Policy, Redactor};
let text = "MSH|^~\\&|LAB|ACME|EHR|CLINIC|20260815120000||ADT^A08|MSG9|P|2.5\r\
PID|1||PATID1234^^^ACME^MR||EVERYWOMAN^EVE^E||19610615|F|||\
12 ELM ST^^BOSTON^MA^02101|||555-555-1111";
let mut message = er7::parse(text)?;
let report = Redactor::new(Policy::patient_identifiers()).redact(&mut message);
// The name is a placeholder, the birth date is a year, the address and
// the phone number are gone, and the record number is a pseudonym.
assert_eq!(message.query("PID-5")?.as_deref(), Some("REDACTED^REDACTED^REDACTED"));
assert_eq!(message.query("PID-7")?.as_deref(), Some("1961"));
assert_eq!(message.query("PID-11")?.as_deref(), Some("^^^^"));
assert_eq!(message.query("PID-13")?.as_deref(), Some(""));
assert_ne!(message.query("PID-3.1")?.as_deref(), Some("PATID1234"));
// The shape did not move: the assigning authority is still there, in
// the component it was in, and the message still parses.
assert_eq!(message.query("PID-3.4")?.as_deref(), Some("ACME"));
assert!(er7::parse(&message.to_er7()).is_ok());
// And there is a record of exactly what changed.
assert_eq!(report.changes[0].path.to_string(), "PID[1]-3[1].1.1");§Accept by default, or reject by default
Every policy is one of the two, and says which (Posture, spec §2.6):
use er7_redact::{Action, Policy, Redactor};
// Accept by default — redact the positions the policy lists.
let listed = Policy::patient_identifiers();
// Reject by default — redact everything except what a `keep` rule names.
let all_but = Policy::all_but_the_header().with("OBX-5", Action::Keep)?;Accepting by default is the safer message — it leaves the clinical
content a test needs. Rejecting by default is the safer posture — it
is the only one that covers a Z segment nobody documented, or a field
an interface started sending last week. Where the two disagree inside
one policy, the reject wins (D19).
§What is here
| Item | Purpose |
|---|---|
Redactor | a policy and a pseudonym key; the only thing that edits a message |
Policy, Rule, Action | what to redact, where, and how |
Posture | accept by default, or reject by default |
Unrecognised | what a payload that is not ER7 gets |
Policy::patient_identifiers | the curated default: PID, NK1, PV1, GT1, IN1 |
Policy::all_but_the_header | the other posture, curated: redact all but what you name |
Policy::accept_all, Policy::reject_all | the two bare postures, with no rules at all |
Policy::parse | read a policy file |
Report, Change | what a redaction did, with no values in it |
pseudonym() | the stable stand-in an identifier is replaced by |
§What is deliberately not here
This crate is a positional editor, not a compliance tool. It does not know whether the positions it redacts are the ones your senders use; it cannot tell you whether what remains is de-identified, because that is a judgement about a whole data set made by a person who is accountable for it; and it cannot find an identifier written into free text, because no positional rule can.
There is also no way back. No mapping table, no key escrow, no undo.
A message this crate has redacted is a message with less in it, which is progress, and is not the same thing as a safe one.
§Documentation
spec/index.md in the repository is the normative specification of
everything above; where this documentation and that document disagree,
that document is right. Section references such as “spec §5.1” and rule
IDs such as “D1” throughout these docs point into it.
The repository also holds a tutorial (docs/usage/), the policy
reference (docs/policies/), an FAQ (docs/faq/), and runnable
programs (examples/).
For the encoding layer underneath — parsing, queries, escape sequences,
the absent/empty/null distinction — see the er7 crate, which is
this crate’s only dependency.
Re-exports§
pub use crate::action::Action;pub use crate::policy::Policy;pub use crate::policy::Posture;pub use crate::policy::Rule;pub use crate::policy::Unrecognised;pub use crate::pseudonym::pseudonym;pub use crate::redact::Change;pub use crate::redact::Redactor;pub use crate::redact::Report;
Modules§
- action
- The eight things redaction can do to a value.
- policy
- Rules, policies, the four built-in policies, and the policy file format.
- pseudonym
- Stable stand-ins for identifiers.
- redact
- The engine: applying a policy to a message, and reporting what changed.
Enums§
- Error
- What can go wrong.