preflight
Stop your AI analyst from silently answering the wrong question.
Static, cross-layer ambiguity detection for governed analytics. preflight reads your dbt, Cube, or
MetricFlow definitions and flags the pairs that read alike but resolve to different numbers, before
an agent (or a person) grounds a question on the wrong one. It runs on definitions alone, before any
query. No warehouse, no model.
Quickstart · Finding catalog · Library · CI / guardrail
Point it at a dbt project and it finds real traps. Here is one in dbt-labs' own Semantic Layer template, found in seconds and cited to the line an analytics engineer edits:
$ preflight scan . --dialect dbt-manifest
14 findings — high 5, medium 0, low 9
HIGH (5)
models/marts/customer360/orders.yml:139: [SCOPE_TRAP] food_orders[sem] ~ large_order[sem] ~ orders[sem] ~ ...
same measure; 'large_order' is 'orders' plus a filter — bare question silently scoped, swap invisible
models/marts/customer360/order_items.yml:39: [CONCEPT_FORK] food_revenue[sem] ~ drink_revenue[sem] ~ revenue[sem]
same entity/table aggregated the same way over different columns — a bare concept resolves to different numbers
...
food_orders is orders with a hidden filter, so a bare "how many orders?" silently under-counts, and
the number looks completely plausible. That is the failure preflight exists to catch.
Why it helps. When two plausible metrics exist for one question, an AI agent (or a hurried human)
can silently pick the wrong one, and the number looks fine. preflight finds those forks in CI, each
cited to the source .yml/.sql line, so you fix them before they ship. It covers the selection
half of grounding safety: two valid definitions exist and the wrong one gets picked. (Whether a single
definition is internally correct is a separate job.)
Install
uv tool install preflight-analytics # or: pip install preflight-analytics
uv tool install "preflight-analytics[embeddings]" # + the sharper, validated gate (docs/gate.md)
The distribution is preflight-analytics (the bare preflight name was taken on PyPI); the import
package and the CLI command are both preflight.
Use
preflight scan . --dialect dbt-manifest # scan a dbt project
preflight scan . --dialect dbt-manifest --detail # + the offending source line
Every finding is cited to path:line. New here? docs/dbt-quickstart.md walks a real dbt
project (jaffle shop) end to end in a few commands.
What it finds
| type | one line |
|---|---|
| SCOPE_TRAP | a metric is another metric plus a hidden filter |
| CONCEPT_FORK | one concept, several metrics over different columns |
| GRAIN_MISMATCH | a number that should not be added up over time, offered per day |
| DEFINITION_DIVERGENCE | one term defined two ways, or a metric its docs never describe |
| NAME_COLLISION | two names read alike, or one column name reused across tables |
| DUPLICATE | the same thing under two names |
| VERSIONED_TWIN | users beside users_v2 or a dated backup: a version or leftover, and nothing marks which is current |
| FACT_TWIN | the same count over one process at two grains: a transaction fact and its own snapshot |
| SIBLING | the same measure under two incomparable scopes |
Each is a real confusion that returns a wrong number. A worked example and the recommended fix for every one: docs/catalog.md.
How it decides
preflight reads what each metric or column actually means (which rows it counts, which column it adds up, and how), not just its name. Then it looks for two definitions someone (or an AI agent) could reasonably mix up and asks one question: would they return different numbers? If yes, it flags the pair and points at the trap.
The idea in one line: it judges by meaning, not spelling. Two metrics with nearly the same name can be perfectly fine, and two with different names can quietly disagree. It's the second case that burns you.
- Read each definition into a plain shape: what it measures, how it's aggregated, from which table, over which rows.
- Pair up the ones worth comparing: names a reader could confuse.
- Judge from the shapes, not the names: same measure but one is a filtered slice of the other → a scope trap; same table and math over a different column → a forked concept; one term defined two ways → a divergent definition. One rule per kind of confusion.
- Report: grouped, worst first, each cited to the exact file and line.
What that looks like on real definitions:
SCOPE_TRAP: a metric that is secretly a filtered slice of another
users = count of all users
active_users = count of users active in the last 30 days
→ active_users is "users" plus a hidden filter, and smaller. Ask "how
many users?" and you can silently get the active count instead.
CONCEPT_FORK: one name, but computed from different columns
revenue = SUM(amount)
net_revenue = SUM(net_amount)
gross_revenue = SUM(gross_amount)
→ same table, same SUM, three different columns. "Revenue" is not one
number. Which column did you mean?
It runs no model and no queries, so the same definitions always produce the same findings. (An optional
smarter matcher also catches synonyms like revenue ≈ sales, but it only widens what gets compared,
never the final call.)
dbt
dbt parse # your dbt, your profile -> target/manifest.json
preflight scan . --dialect dbt-manifest
Generating the manifest is your dbt project's job; preflight only reads it (if you already use dbt, CI has produced it). Full walkthrough, both jaffle projects, and the manifest details: docs/dbt-quickstart.md.
Not on dbt? preflight reads Cube models directly (no build step) with --dialect cube
(walkthrough: docs/cube-quickstart.md), plus raw dbt model SQL
(--dialect dbt), MetricFlow YAML (--dialect metricflow), and a native semantic/warehouse/docs
layout (--dialect env).
Library
from preflight import scan
for f in scan("path/to/environment"):
print(f.danger, f.type, f.note)
Adapters, detect_collisions, DetectConfig, and JSON output: docs/library.md.
Guardrail (CI / pre-commit)
preflight scan . --dialect dbt-manifest --fail-on high # non-zero exit on a HIGH finding
Drops into GitHub Actions or a pre-commit hook. Setup in docs/ci.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file preflight_analytics-0.4.0.tar.gz.
File metadata
- Download URL: preflight_analytics-0.4.0.tar.gz
- Upload date:
- Size: 46.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38ae50832faf3e36e8ad1b854b8cd21392fb49ec87726ac506153b16ed03abbf
|
|
| MD5 |
e69c174edd040f0faef9d31e5868138c
|
|
| BLAKE2b-256 |
5b63f86ed5ea9ba20168c1664a41a36d233d8adbd0956ea28f9d6b4d4717b3e0
|
Provenance
The following attestation bundles were made for preflight_analytics-0.4.0.tar.gz:
Publisher:
publish.yml on d-n-ust/preflight-analytics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
preflight_analytics-0.4.0.tar.gz -
Subject digest:
38ae50832faf3e36e8ad1b854b8cd21392fb49ec87726ac506153b16ed03abbf - Sigstore transparency entry: 2550183814
- Sigstore integration time:
-
Permalink:
d-n-ust/preflight-analytics@8c2fdb347b1b0770aaeb08792bb5f8175e418a85 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/d-n-ust
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8c2fdb347b1b0770aaeb08792bb5f8175e418a85 -
Trigger Event:
push
-
Statement type:
File details
Details for the file preflight_analytics-0.4.0-py3-none-any.whl.
File metadata
- Download URL: preflight_analytics-0.4.0-py3-none-any.whl
- Upload date:
- Size: 41.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ff1fb66fee5efc60d18f7f5d79130eb4a71021238dc2e6dffe43d5b1191f5c0
|
|
| MD5 |
dea00f0aa77a057f550aca5586d140e3
|
|
| BLAKE2b-256 |
b4e608901ac048fa8881da0fa939f1067385565560a665d0513f6ed6a864f18a
|
Provenance
The following attestation bundles were made for preflight_analytics-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on d-n-ust/preflight-analytics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
preflight_analytics-0.4.0-py3-none-any.whl -
Subject digest:
3ff1fb66fee5efc60d18f7f5d79130eb4a71021238dc2e6dffe43d5b1191f5c0 - Sigstore transparency entry: 2550183844
- Sigstore integration time:
-
Permalink:
d-n-ust/preflight-analytics@8c2fdb347b1b0770aaeb08792bb5f8175e418a85 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/d-n-ust
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8c2fdb347b1b0770aaeb08792bb5f8175e418a85 -
Trigger Event:
push
-
Statement type: