Defining Your Healthcare Data Schema in YAML
Spritely turns a declarative schema into encryption rules, audit trails, and access policies. This guide walks through a realistic YAML model for a small practice: patient demographics, PHI flags, staff roles, and record relationships, ending with a production deploy.
If you are shipping a healthcare workflow app, the hardest part is rarely the UI. It is getting the data model right the first time: which fields are PHI, who can see them, how records relate, and how those decisions propagate into encryption and compliance behavior. Spritely encodes those decisions in YAML so they stay reviewable in pull requests and reproducible across environments. The following sections build a schema from a single patient record through roles, relationships, and deployment.
Why YAML?
YAML keeps the schema human-readable without sacrificing structure: you can scan a file and understand record types, field types, and policy in one pass. Because it is plain text, you get first-class version control: diffs show exactly what changed in your compliance surface area, not buried logic inside a GUI. That makes reviews faster for engineers and for practice stakeholders who need to sign off on data handling.
Compared with JSON, YAML avoids noisy braces and quotes for large nested trees, which matters when you are iterating on dozens of fields across multiple record types. You still get strict parsing: the Spritely CLI rejects ambiguous anchors, duplicate keys, and type mismatches before anything reaches your environments.
A minimal schema
Start with a single record type that mirrors how your front desk thinks about a person. The top-level schema_version pins the Spritely compiler to a known contract. Under records, each key is a stable API name; nested fields declare storage types and validation. Email stays plain text for routing, while government identifiers and clinical dates carry phi: true so Spritely can route them through the PHI pipeline. The roles block at the bottom names the actors you will bind to permissions later.
schema_version: "2025.11"
records:
patient:
label: "Patient"
description: "Demographics and identifiers for a person receiving care."
fields:
full_name:
type: string
required: true
max_length: 160
email:
type: email
required: true
unique: true
date_of_birth:
type: date
phi: true
ssn_last_four:
type: string
pattern: "^[0-9]{4}$"
phi: true
roles:
- admin
- provider
- front_desk
The patient record is intentionally boring: strings, an email, a date. That is a feature. Spritely generates typed storage and API handlers from these declarations, so you are not hand-writing serializers for each field. unique: true on email enforces a single account per address at the data layer, which saves you from race-prone application checks. Optional metadata like label and description flows into generated admin screens and OpenAPI docs, so the same file stays the source of truth for humans and machines.
When you add constraints such as pattern on ssn_last_four, validation runs at the API boundary before data ever touches storage. That keeps bad rows out of migrations and gives client authors a clear contract: failed requests return field-scoped errors instead of generic 500s from the database.
Marking PHI fields
In Spritely, phi: true is not a label for spreadsheets. It is a compile-time switch that wires the field into AWS KMS-backed encryption: values are encrypted at rest with AES-256 using keys managed per environment, and key rotation follows your AWS account policy. Reads and writes to PHI fields also emit structured audit events automatically, including actor identity, timestamp, record identifier, and field path, so your security team gets an access trail without custom logging code in every route handler.
records:
patient:
fields:
date_of_birth:
type: date
phi: true
ssn_last_four:
type: string
pattern: "^[0-9]{4}$"
phi: true
Non-PHI fields still live in the same record; they simply skip the KMS envelope and audit hooks. That split is how you keep operational data like internal tags or feature flags cheap to query while keeping regulated content under strict controls. If you later promote a field to PHI, the deploy step plans a backfill migration: existing plaintext values are re-encrypted under the new policy, and audit coverage attaches going forward.
Defining roles and permissions
Roles map people in your organization to concrete abilities on each record type. The pattern below grants admins full read/write on patients and appointments, limits providers to clinical objects, and gives front desk staff demographic access without opening clinical notes. Spritely materializes these rules into server-side guards and generated client SDKs, so a front_desk session cannot call an API that returns PHI fields it is not allowed to see, even if someone tampers with the browser.
access_control:
roles:
admin:
description: "Practice administrator — full operational access."
grants:
- resource: "record:patient"
actions: ["create", "read", "update", "delete"]
- resource: "record:appointment"
actions: ["create", "read", "update", "delete"]
- resource: "record:clinical_note"
actions: ["create", "read", "update", "delete"]
provider:
description: "Licensed clinician — clinical data, limited admin."
grants:
- resource: "record:patient"
actions: ["read", "update"]
- resource: "record:appointment"
actions: ["create", "read", "update"]
- resource: "record:clinical_note"
actions: ["create", "read", "update"]
front_desk:
description: "Scheduling and intake — no clinical notes."
grants:
- resource: "record:patient"
actions: ["create", "read", "update"]
- resource: "record:appointment"
actions: ["create", "read", "update"]
At runtime, Spritely resolves the authenticated user's role, intersects it with field-level PHI policy, and returns only permitted slices of each record. Denied paths do not leak in error messages; the API shape stays stable while unauthorized fields are omitted or masked depending on your configuration. That behavior is what lets you iterate on YAML without rewriting authorization middleware for every new field. Service accounts and integration tokens use the same matrix, which keeps nightly ETL jobs from accidentally inheriting clinical scopes meant for humans.
Record relationships
Healthcare data is relational: a patient has many appointments; an appointment belongs to a provider; a clinical note attaches to a single encounter. Declaring those edges in YAML lets Spritely enforce referential integrity and generate joins in the API without you hand-maintaining foreign keys in three places. Use explicit cardinality and inverse names so migrations stay predictable when you add new record types.
records:
patient:
fields:
full_name:
type: string
required: true
relationships:
appointments:
type: has_many
target: appointment
foreign_key: patient_id
on_delete: restrict
appointment:
fields:
starts_at:
type: datetime
required: true
status:
type: enum
values: ["scheduled", "checked_in", "completed", "cancelled"]
relationships:
patient:
type: belongs_to
target: patient
foreign_key: patient_id
provider:
type: belongs_to
target: staff_profile
foreign_key: provider_id
staff_profile:
fields:
display_name:
type: string
required: true
npi:
type: string
max_length: 10
phi: true
With on_delete: restrict, Spritely blocks deleting a patient who still has appointments, which is usually what you want in regulated settings. If your workflow requires cascading archival instead, switch the policy and let Spritely propagate soft-delete flags consistently across related rows. Indexed foreign keys implied by these relationships also keep list endpoints fast when you paginate appointments by provider or filter a patient's upcoming visits.
Deploying your schema
When the YAML matches your intent, ship it through the CLI. From your project root:
spritely deploy --env production
The deploy pipeline validates the schema against the pinned schema_version, provisions or updates KMS key aliases for PHI fields, applies access-control matrices to the API layer, and regenerates the app surfaces (REST handlers, typed clients, and admin tools) to match. If validation fails, the command exits non-zero with a line-numbered error pointing at the YAML, which keeps production from accepting a half-defined model. Staging and production each get isolated key material, so a mistake in a test account never bleeds into live patient data.
What's next
You now have a working mental model: YAML describes records, PHI, roles, and relationships; Spritely turns that into encryption, auditing, and enforcement. For an end-to-end path from install to deployed app, read Deploy a HIPAA-Compliant App in Under 10 Minutes. If you want the systems rationale for how Spritely removes undifferentiated compliance work from your roadmap, see Zero Compliance Engineering: How Spritely Eliminates HIPAA Busywork.