Skip to content
>GLB_
Go back

Tuva column extensions: carry a custom column from eligibility to core

// data engineering · tuva

TL;DR

Prefix a custom column with x_ in your connector’s eligibility model. It arrives in core__eligibility with no YAML and no edits to Tuva’s models. One dbt var (passthrough.strip) removes the prefix in the final Core output.

The problem

I needed contract_type in core__eligibility. The field exists in the source data, but it is not part of Tuva’s standard eligibility contract.

Tuva’s Core models use explicit column lists. Without pass-through support, custom fields are preserved in input_layer but dropped in downstream core models that use explicit selections. The column is present in your data, then missing where you need it.

What I tried

These are the options I looked at before finding the supported one.

Option 1: edit core__eligibility.sql

It works, but you are now maintaining a fork. Every package upgrade means re-applying the patch, and it puts business logic inside a package-owned model.

Option 2: rejoin downstream

Build a separate model that joins core.eligibility back to the source on member_id and data_source to recover contract_type. It also works, but it means more custom SQL in host projects, more tables/views to manage, and higher maintenance and compute costs.

Option 3: column extensions

Tuva has a documented mechanism for this case. A connector adds organization-specific columns to a supported Input Layer Model and retains them in the same-named Core table. The docs call it column extensions, and it is the only one of the three that needs no patching and no rejoin.

What actually worked

How the mechanism works

The path is deliberately narrow. Column extensions do not propagate arbitrary columns through the Tuva DAG. The flow is:

connector Input Layer Model      (eligibility)
  -> Tuva Input Layer Wrapper    (input_layer__eligibility)
  -> intermediate + normalized   (int_eligibility_casting, normalized__eligibility)
  -> Core Model                  (core__eligibility)

Three rules define the contract:

  1. Prefix-based detection. A column is an extension column if its name starts with the configured prefix (default x_). Two vars control this: passthrough.prefix (default 'x_') and passthrough.strip (default false).
  2. Introspection, not declaration. The macro select_extension_columns(...) inspects the upstream relation’s columns. Nothing is listed in YAML. Matching is case-insensitive, and the macro validates collisions against Tuva’s fixed columns.
  3. Each layer appends them itself. Core staging/final models call select_extension_columns(...) to append extension columns dynamically. The column does not travel on its own. Each layer’s model has the macro call in its SQL.

In this repo, the call sites for eligibility are:

ModelCall
int_eligibility_castingselect_extension_columns(ref('input_layer__eligibility'), alias='elig', strip_prefix=false)
normalized__eligibilitysame pattern over int_eligibility_casting
core__eligibilityselect_extension_columns(ref('normalized__eligibility'))

The intermediate and normalized relations retain the extension prefix and are not the supported destination. Only the Core table is the supported place to read the column.

Step 0: check the Tuva version

The contract described here is Tuva Core v1.0.0, and earlier Core versions use legacy extension behavior. Pass-through itself was first introduced in v0.17.0. Confirm what your connector resolves to:

# packages.yml
packages:
  - package: tuva-health/the_tuva_project
    version: [">=1.0.0", "<2.0.0"]   # example range, verify against the guide

If you started from a Connector Template revision whose package range ends below 1.0, align that dependency first.

Step 1: add the column in your Input Layer Model

This is the connector-owned eligibility model, the one input_layer__eligibility references. Add the field with the prefix:

-- models/input_layer/eligibility.sql  (connector project)
select
    person_id
  , member_id
  , enrollment_start_date
  , enrollment_end_date
  , payer
  , plan
  -- ... rest of the Tuva eligibility contract ...
  , contract_type as x_contract_type   -- extension column
  , data_source
from {{ source('raw', 'eligibility_feed') }}

That is the only change to your code.

Step 2: build and verify

dbt build --select +core__eligibility
select x_contract_type, count(*) as members
from core.eligibility
group by 1
order by 2 desc;

If x_contract_type is in core.eligibility, the path works end to end.

Step 3 (optional): drop the prefix in Core

To get contract_type instead of x_contract_type:

# dbt_project.yml
vars:
  passthrough:
    prefix: 'x_'
    strip: true

strip only affects the final Core output. The intermediate layers are expected to keep the prefix. Read strip as “rename at the last hop”. It does not rename the column everywhere.

Gotchas

What this does not do: filtering rows

Column extensions expose a column. They do not filter rows. If you need core__eligibility restricted to one contract_type, that is business logic, and it belongs in a downstream model in your project:

-- models/marts/eligibility_by_contract.sql  (downstream project)
select *
from {{ ref('core__eligibility') }}
where x_contract_type = 'medicare_advantage'   -- illustrative value

Editing core__eligibility.sql means forking the package. It is the same problem as Option 1.

Key takeaways

References


16 Oct 2026 · #tuva #dbt #dataengineering #healthcaredata

GLB_


Share this post:

Related Posts


Previous Post
Connecting Clinical and Operational Data to Tuva with dbt
Next Post
Extracting the last part of a string after a delimiter in SQL Server