PLAN: Dataset integration block — surface Atlas's derived layer on each dataset page
IMPLEMENTATION RULES: Before implementing this plan, read and follow:
- WORKFLOW.md - The implementation process
- PLANS.md - Plan structure and best practices
Status: Completed
Completed: 2026-05-12
Branch (stacks): feature/sources-catalog-foundation (same branch as PR #104).
Goal: Make Atlas's derived layer visible from each per-dataset page — the dbt-built indicators, facts, and api_v1.* consumer views that consume this dataset. This is Option B+ from the indicators-surface discussion: one catalogue umbrella (/datasets), no separate /indicators page, but each dataset page surfaces the Atlas integration context contextually so users like Lisa (grant officer) see methodology and query target on one screen instead of bouncing between two URLs.
Last Updated: 2026-05-12
Prerequisites:
- ✅ PLAN-002 — sources catalog foundation.
- ✅ PLAN-catalog-vocabulary — dataset / topic / publisher terminology.
- ✅
atlas-data/dbt/seeds/sources/lineage.csv— 129 edges,model_name,source_idshape (already in the repo).
Blocks: PLAN-003 (merchandising) — collections can recommend dataset+indicator pairs that this PLAN makes legible.
What changes
Each per-dataset MDX page gains two new sections between Provenance and Dimensions:
"In Atlas"
Surfaces the Atlas mart / api_v1.* views that consume this dataset:
- For each
mart_<feature>that reads from this dataset's raw tables (vialineage.csv):- Sample PostgREST URL (
https://api-atlas.sovereignsky.no/<feature>?limit=5). - One-line description of what the mart does.
- "Try this query at /api →" link.
- Sample PostgREST URL (
- Plus a fallback note when no mart consumes this dataset yet (e.g. for
dim_*references or fresh sources).
"Joined with"
Surfaces other datasets that co-occur with this one in the same dbt model — that is, the datasets you'd typically combine with this one in a real query:
- Computed by walking
lineage.csv: for each model that includes this dataset, list every other dataset that also feeds the model. - Aggregated, deduped, and ranked by how many shared models a pair has (the more often two datasets appear in the same mart, the closer the relationship).
- Capped at 6 cards.
- Each card shows the other dataset's name + the dbt models they share.
Both sections are derived at generator time from lineage.csv — no editorial input needed. The data is honest about what Atlas actually composes today.
Why this matters for personas
- Lisa (grant officer) lands on
ssb-08764, reads methodology, then sees "This dataset is queryable viaapi_v1.indicator_latest_values. It joins withssb-06944,ssb-12944,bufdir-barnefattigdomin the kommune low-income mart." — citation source and query target on one screen. - Dev sees the Atlas-mart layer without leaving the dataset page; doesn't need to learn dbt-model names from
/lineage/. - Ola sees which datasets compose into Atlas indicators — a transparency signal about how Atlas mediates between upstream and queryable surface.
Phase 1: Extend the generator
Tasks
-
1.1 Add
LINEAGE_CSVconstant pointing at../atlas-data/dbt/seeds/sources/lineage.csvand aloadLineage()function that parses the CSV (nocsv-parsedep needed — simple two-column shape). -
1.2 For each source, compute:
consuming_marts[]: array of{ mart_name, api_v1_name, description, sample_query }. Filter the lineage edges to model names starting withmart_; mapmart_<feature>→api_v1.<feature>per Atlas's naming convention; build a?limit=5sample query URL.consuming_models[]: array of all model names that read from this dataset's raw tables, grouped by prefix (indicators__,dim_,fact_,mart_,ref_,crosswalk_, etc.). Useful for the "internal" lineage drill-down.joined_with[]: array of{ source_id, shared_models: [model_name, ...] }. For each dbt model that includes this dataset's source_id, find every OTHER source_id that also feeds the model. Dedupe; sort byshared_models.lengthdesc; cap at 8.
-
1.3 Where a mart's description isn't available in
lineage.csv(it isn't — the CSV is just edges), source a one-line description from the dbt project'sschema.ymlfiles OR from a hand-authored map inatlas-data/ingest/src/sources/mart-descriptions.yaml(new optional companion file). For v1, accept "no description available" as a fallback; the mart name itself reads as a hint.Decision deferred: Phase 1.3 implementation — see Implementation Notes.
Validation
cd website && npm run sources:generate
jq '.sources[] | select(.source_id == "fhi-mobbing") | {consuming_marts, joined_with}' src/data/sources-registry.json
Expected: fhi-mobbing shows ~3 consuming marts (e.g. mart_indicator_latest_values, mart_indicator_summary, mart_coverage_gap_barnefattigdom if applicable) and joined-with datasets including the geography reference (ssb-klass-kommuner), the age denominators (ssb-07459), and the youth education siblings.
User confirms phase is complete.
Phase 2: New React components
Tasks
-
2.1
<DatasetAtlasIntegration source={source}>— the wrapper rendering both sub-sections. Lives atwebsite/src/components/sources/DatasetAtlasIntegration.tsx. -
2.2
<DatasetQueryMarts source={source}>— the "In Atlas" section. For eachconsuming_marts[]entry, renders a card with:- Mart name as title (e.g.
api_v1.indicator_latest_values). - Short description (if available).
- Copy-clickable sample query URL.
- "Try this query at /api →" link to
/api. - "View in lineage →" link to
pathname:///lineage/#!/model/model.atlas.mart_<feature>.
- Mart name as title (e.g.
-
2.3
<DatasetJoinedWith source={source}>— the "Joined with" section. For eachjoined_with[]entry:- Card with: target dataset title, publisher logo, topic emoji.
- Subtitle: "Joined in:
mart_low_income_summary,fact_kommune_indicators" (model names as join-context evidence). - Click →
/datasets/<source_id>.
-
2.4 Shared styles in
styles.module.css— extend the existing.relatedStrippattern.
Validation
- Components typecheck.
- Components render with mock data (or live data from a regenerated registry).
Phase 3: MDX template + sidebar
Tasks
-
3.1 Update
renderSourceMdx()inwebsite/scripts/generate-sources-registry.mjsto import and render<DatasetAtlasIntegration>between Provenance and Dimensions.Section heading:
## In Atlas {#in-atlas}(new stable anchor — add to the [Q34] contract). -
3.2 Stable anchor IDs contract: add
#in-atlasand#joined-withto the list of stable anchors. Document indatasets-catalog.md.
Validation
cd website && npm run sources:generate && npm run build
Browse a few per-dataset pages, confirm the new section renders for datasets with downstream consumers.
User confirms phase is complete.
Phase 4: Close PLAN
- 4.1 Move PLAN to
plans/completed/. - 4.2 Update
datasets-catalog.mdCross-references and stable-anchor list.
Acceptance Criteria
- Generator emits
consuming_marts[],consuming_models[],joined_with[]on every source entry. - Per-dataset MDX renders an "In Atlas" section listing consuming
mart_*views with sample queries and lineage links. - Per-dataset MDX renders a "Joined with" section listing co-occurring datasets with the dbt models that join them.
- Stable anchor IDs
#in-atlasand#joined-withexist on every per-dataset page. - Reference datasets (e.g.
ssb-klass-kommuner) with many downstream consumers render gracefully (capped lists). - Datasets with no current downstream (if any —
frris a candidate) render an honest "no Atlas indicators consume this dataset yet" note instead of an empty section. - Local build clean.
Implementation Notes
-
Why parse
lineage.csvrather than dbt'smanifest.json—manifest.jsonis regenerated bydbt parseand is environment-specific (paths, profile resolution). The CSV is a stable, version-controlled seed maintained byscripts/extract_lineage.py. Catalogue generator stays self-contained. -
Mart descriptions —
lineage.csvonly has edges. For mart descriptions, the long path is parsingatlas-data/dbt/models/marts/api/*.ymlfiles. The short path is hand-authoring a smallmart-descriptions.yamlin the website repo. Lean: short path. Eight or so marts, one or two lines each. -
api_v1 naming convention — Atlas's
regenerate-api-v1.shscript strips themart_prefix when wrapping marts asapi_v1.*views. Somart_distrikt_summary→api_v1.distrikt_summary. The generator follows this rule mechanically. -
Why include
dim_*andfact_*inconsuming_models[]but not in the visible "In Atlas" section — dim/fact models are internal; they don't have api_v1 wrappers and aren't directly queryable. They're useful as lineage drill-down metadata (powering links to/lineage/) but shouldn't appear as "use this in queries" suggestions. -
Why "Joined with" is more interesting than "Related by topic" — related_by_topic groups datasets editorially (same
tags.topic). joined_with groups them by actual Atlas composition — these are the datasets the user will, in practice, combine in a single query. Different signal, more honest. -
Stable anchor IDs are a contract per [Q34]. Adding
#in-atlasand#joined-withextends the contract — once shipped, renaming is a breaking change for any deep-linker.
Files to Modify or Create
New files
website/src/components/sources/DatasetAtlasIntegration.tsxwebsite/src/components/sources/DatasetQueryMarts.tsxwebsite/src/components/sources/DatasetJoinedWith.tsxatlas-data/ingest/src/sources/mart-descriptions.yaml(optional, Phase 1.3 — hand-authored one-liners per mart)
Modified files
website/scripts/generate-sources-registry.mjs— read lineage CSV, compute the three new fields, regenerate MDX template with the new section.website/src/types/sources.ts— add types forconsuming_marts,consuming_models,joined_with.website/src/components/sources/styles.module.css— minor styling additions.website/docs/contributors/datasets-catalog.md— document the new stable anchors + the integration block.