Back to index

Data Lineage Stages

Network
Generates documents
Data Lineage Stages

Seen as a design

Take it with you

Copy and paste, or save a file and hand it over. For a machine with no network, one “Everything” file is enough.

Everything

data-lineage-stages-everything.md

Everything in this panel, including the contents behind each URL, in one Markdown file

Prompt

1.4 KB
Build an HTML document with "Data Lineage Stages". This layout runs, so there is no composition to rebuild — what it needs is content.

## What this layout does
Data Lineage Stages with explicit source values and reading conditions.

## What you have to supply
- Provide the source values, labels and decision conditions described by the schema.

## The shape of the input
- `title` — string — 結論を伝える見出し
- `subtitle` — string? — 出典・時点・母集団や仮定
- `nodes` — array (id, label, stage, owner) — ID・データ名・段階・責任者。各段階1〜3個
- `links` — array (from, to, operation) — 前段から次段への来歴。変換内容を必ず記録

schema.ts holds the machine-checkable validation. Ask rather than guess when an input is missing.

## When not to use it
- Do not compare values with incompatible units, populations, or assumptions.

## How to build it
Put the input in a JSON file and run:

```bash
node scripts/render.mjs data-lineage-stages <input.json> --theme ink -o out.html
```

The output is one self-contained file and fetches nothing at render time.

## The source
- https://design-vault.pages.dev/designs/data-lineage-stages/source/template.tsx
- https://design-vault.pages.dev/designs/data-lineage-stages/source/schema.ts
- https://design-vault.pages.dev/designs/data-lineage-stages/source/sample-data.json
- https://design-vault.pages.dev/designs/data-lineage-stages/sample.html

Files

Template, input schema, sample data and metadata. This document was generated from those four. The generated document

template.tsx

7.7 KB

/designs/data-lineage-stages/source/template.tsx

Show contents
import type { ReactNode } from "react";
import type { Props } from "./schema";
function Ledger({heads,rows,caption,className}:{heads:string[];rows:ReactNode[][];caption?:string;className?:string}) { return <table className={`ledger ${className ?? ""}`}>{caption && <caption>{caption}</caption>}<thead><tr>{heads.map((h,i)=><th key={i} scope="col">{h}</th>)}</tr></thead><tbody>{rows.map((r,i)=><tr key={i}>{r.map((v,j)=><td key={j} data-label={heads[j]}>{v}</td>)}</tr>)}</tbody></table>; }
function Notes({children}:{children:ReactNode}) { return <details><summary>データ・判断条件を確認</summary>{children}</details>; }

const CSS = String.raw`
.asset{font-family:var(--font-sans);color:var(--color-primary);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:3rem 2rem;line-height:1.6;font-size:var(--text-body-sm)}
.asset *{box-sizing:border-box}.asset header{margin-bottom:2rem}.asset .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);letter-spacing:.08em;color:var(--color-secondary);margin-bottom:.75rem}.asset h1{font-size:var(--text-h1);font-weight:700;line-height:1.35;letter-spacing:-.025em;max-width:32em}.asset h2{font-size:var(--text-h3);font-weight:600;margin-bottom:.75rem}.asset h3{font-size:var(--text-body-md);font-weight:600}.asset .subtitle,.asset .muted{color:var(--color-secondary)}.asset .subtitle{margin-top:.75rem}.asset .number{font-variant-numeric:tabular-nums;font-family:var(--font-mono)}.asset .hero-number{font-size:var(--text-display);line-height:1.2;font-weight:700;letter-spacing:-.04em}.asset .callout{border-left:.25rem solid var(--color-tertiary);padding:1rem 1.5rem;background:var(--color-surface-subtle);margin-bottom:1.5rem}.asset .grid2{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}.asset .grid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1.5rem}.asset .panel{padding:1.5rem;background:var(--color-surface-subtle);border-radius:var(--radius-md);min-width:0}.asset .rule{border-top:1px solid var(--color-outline);padding-top:1rem;margin-top:1rem}.asset .pill{display:inline-block;padding:.25rem .75rem;border:1px solid var(--color-outline);border-radius:var(--radius-full);font-size:var(--text-label)}.asset .focus{background:var(--color-tertiary-container);color:var(--color-on-tertiary-container)}.asset .dark{background:var(--color-primary);color:var(--color-on-primary)}.asset .dark .muted{color:var(--color-on-primary-container)}.asset .row{display:grid;grid-template-columns:12rem minmax(0,1fr) 6rem;gap:1rem;align-items:center;padding:.75rem 0;border-bottom:1px solid var(--color-outline)}.asset .row>*{min-width:0}.asset .track{height:1rem;position:relative;background:var(--color-neutral);border-radius:var(--radius-sm)}.asset .bar{height:100%;background:var(--color-tertiary);border-radius:var(--radius-sm)}.asset .track.tall{height:2rem}.asset .flex{display:flex;gap:1rem;align-items:center;justify-content:space-between;flex-wrap:wrap}.asset .stack>*+*{margin-top:1rem}.asset ul{list-style:disc;padding-left:1.25rem}.asset li+li{margin-top:.5rem}.asset .plot{display:block;width:100%;height:auto;overflow:visible}.asset .plot text{font-family:var(--font-sans);font-size:14px;fill:var(--color-secondary)}.asset .plot .ink{fill:var(--color-primary)}.asset .plot .axis{stroke:var(--color-outline);stroke-width:1}.asset .plot .accent{stroke:var(--color-tertiary);fill:none;stroke-width:3}.asset .legend{display:flex;gap:1.5rem;flex-wrap:wrap;font-size:var(--text-label);margin-bottom:1rem}.asset .legend b{display:inline-block;width:.75rem;height:.75rem;margin-right:.5rem}.asset .ledger{border-collapse:collapse;width:100%;font-variant-numeric:tabular-nums}.asset .ledger caption{text-align:left;font-weight:600;margin:1rem 0}.asset .ledger th{font-weight:600;color:var(--color-secondary);text-align:left}.asset .ledger th,.asset .ledger td{padding:.75rem 1rem;border-bottom:1px solid var(--color-outline);vertical-align:top;overflow-wrap:anywhere}.asset .ledger th:first-child,.asset .ledger td:first-child{padding-left:0}.asset .ledger th:last-child,.asset .ledger td:last-child{padding-right:0}.asset .ledger tbody tr:hover{background:var(--color-surface-subtle)}.asset details{border-top:1px solid var(--color-outline);margin-top:1.5rem;padding-top:1rem}.asset summary{cursor:pointer;color:var(--color-secondary);padding:.5rem 0;min-height:2.75rem}.asset summary:focus-visible{outline:2px solid var(--color-tertiary);outline-offset:.25rem}.asset .small{font-size:var(--text-label)}.asset .mobile-only{display:none}.asset .horizontal-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.asset .board-column{border-top:.25rem solid var(--color-primary);padding-top:1rem;min-width:0}.asset .board-item{padding:1rem 0;border-bottom:1px solid var(--color-outline)}.asset .board-item p{margin-top:.5rem}.asset .statement{font-size:var(--text-h2);line-height:1.5;font-weight:600}.asset .meta-line{margin-top:.75rem;font-size:var(--text-label);color:var(--color-secondary)}.asset pre{white-space:pre-wrap;overflow-wrap:anywhere}.asset footer{margin-top:2rem;font-size:var(--text-label);color:var(--color-secondary)}
@media(max-width:640px){.asset{padding:1.5rem 1rem}.asset h1{font-size:var(--text-h2)}.asset header{margin-bottom:1.5rem}.asset .grid2,.asset .grid3,.asset .horizontal-list{grid-template-columns:1fr}.asset .row{grid-template-columns:minmax(0,1fr) 5rem;gap:.5rem}.asset .row>:first-child{grid-column:1/-1}.asset .desktop-only{display:none!important}.asset .mobile-only{display:block}.asset .panel{padding:1rem}.asset .ledger,.asset .ledger tbody,.asset .ledger tr,.asset .ledger td{display:block}.asset .ledger thead{display:none}.asset .ledger tr{padding:.75rem 0;border-bottom:1px solid var(--color-outline)}.asset .ledger td{display:grid;grid-template-columns:7rem minmax(0,1fr);gap:.75rem;border:0;padding:.25rem 0!important}.asset .ledger td:before{content:attr(data-label);color:var(--color-secondary);font-size:var(--text-label)}.asset .hero-number{font-size:var(--text-h1)}}
@media print{.asset{max-width:none;padding:1rem}.asset details>*{display:block}.asset summary{display:none}}
`;

export default function Template(d: Props) {
const stages=["input","transform","output"];const pos=new Map(d.nodes.map(n=>{const col=stages.indexOf(n.stage);const row=d.nodes.filter(r=>r.stage===n.stage).findIndex(r=>r.id===n.id);return [n.id,{x:160+col*320,y:80+row*90}];}));
return <section className="asset asset-data-lineage-stages"><style>{CSS}</style><header><p className="eyebrow">DATA LINEAGE STAGES</p><h1>{d.title}</h1>{d.subtitle && <p className="subtitle">{d.subtitle}</p>}</header>
<div className="legend"><span>入力</span><span>→ 変換</span><span>→ 出力</span></div><svg className="plot desktop-only" viewBox="0 0 960 330" role="img" aria-label="ID付きのデータ来歴">{d.links.map((l,i)=>{const a=pos.get(l.from)!,b=pos.get(l.to)!;return <g key={i}><line x1={a.x+75} y1={a.y} x2={b.x-75} y2={b.y} stroke="var(--color-outline)" strokeWidth="2"/><path d={"M "+(b.x-84)+" "+(b.y-5)+" L "+(b.x-75)+" "+b.y+" L "+(b.x-84)+" "+(b.y+5)} fill="none" stroke="var(--color-secondary)"/></g>})}{d.nodes.map(n=>{const p=pos.get(n.id)!;return <g key={n.id}><rect x={p.x-75} y={p.y-24} width="150" height="48" rx="6" fill="var(--color-surface-subtle)" stroke="var(--color-outline)"/><text x={p.x} y={p.y+5} textAnchor="middle" className="ink">{n.id}</text></g>})}</svg><Ledger className="lineage-node-ledger" heads={["ID","データ・処理","責任者"]} rows={d.nodes.map(n=>[n.id,n.label,n.owner])}/><Notes><Ledger heads={["送信元","送信先","変換・利用"]} rows={d.links.map(l=>[l.from,l.to,l.operation])}/></Notes>
<footer>線は入力にある参照だけを描く。並置しただけのデータ間に関係を仮定しない。</footer></section>;
}

schema.ts

1.3 KB

/designs/data-lineage-stages/source/schema.ts

Show contents
import { z } from "zod";
const text = z.string().trim().min(1).max(160);
const short = z.string().trim().min(1).max(48);
const value = z.number().finite();
const positive = value.positive();
const unique = (values: string[]) => new Set(values).size === values.length;

export const schema = z.object({
  title: text.describe("結論を伝える見出し"),
  subtitle: text.optional().describe("出典・時点・母集団や仮定"),
  nodes:z.array(z.object({id:z.string().min(1).max(12),label:short,stage:z.enum(["input","transform","output"]),owner:short}).strict()).min(3).max(9).describe("ID・データ名・段階・責任者。各段階1〜3個"),
  links:z.array(z.object({from:short,to:short,operation:text}).strict()).min(2).max(12).describe("前段から次段への来歴。変換内容を必ず記録"),
}).strict().refine(d=>{const order={input:0,transform:1,output:2};return unique(d.nodes.map(n=>n.id))&&["input","transform","output"].every(s=>{const count=d.nodes.filter(n=>n.stage===s).length;return count>=1&&count<=3;})&&d.links.every(l=>{const a=d.nodes.find(n=>n.id===l.from),b=d.nodes.find(n=>n.id===l.to);return a&&b&&order[b.stage]===order[a.stage]+1;})&&unique(d.links.map(l=>JSON.stringify([l.from,l.to])));},"各段階1〜3個、IDは一意、線は実在する次段へ接続");
export type Props = z.infer<typeof schema>;

meta.yaml

1.4 KB

/designs/data-lineage-stages/source/meta.yaml

Show contents
id: data-lineage-stages
name:
  ja: データの来歴と変換
  en: Data Lineage Stages
purpose:
  ja: 入力・変換・出力のデータを、参照IDを持つ線でつなぐ。
  en: Data Lineage Stages with explicit source values and reading conditions.
status: published
still: ./still.png
category: specification
catalogOrder: 640
jobs:
  - trace-flow
useCases:
  - process
layoutKinds:
  - network
tags:
  - data-lineage-stages
  - specification
  - offline
  - responsive
whenToUse:
  - ja: 入力・変換・出力のデータを、参照IDを持つ線でつなぐ。
    en: Use to communicate data lineage stages.
avoidWhen:
  - ja: 同じ条件・単位で比較できないデータには使わない
    en: Do not compare values with incompatible units, populations, or assumptions.
requiredInputs:
  - ja: 線は入力にある参照だけを描く。並置しただけのデータ間に関係を仮定しない。
    en: Provide the source values, labels and decision conditions described by the schema.
relatedLayouts:
  - system-landscape-map
  - integration-contract-catalog
dataSchema:
  title: string — 結論を伝える見出し
  subtitle: string? — 出典・時点・母集団や仮定
  nodes: array (id, label, stage, owner) — ID・データ名・段階・責任者。各段階1〜3個
  links: array (from, to, operation) — 前段から次段への来歴。変換内容を必ず記録
sources: []

sample-data.json

1.2 KB

/designs/data-lineage-stages/source/sample-data.json

Show contents
{
  "title": "集計値の出所を、入力から出力まで追えるようにする",
  "subtitle": "架空サンプル|レイアウトの使用例",
  "nodes": [
    {
      "id": "D1",
      "label": "受付データ",
      "stage": "input",
      "owner": "業務部"
    },
    {
      "id": "D2",
      "label": "顧客マスター",
      "stage": "input",
      "owner": "顧客管理部"
    },
    {
      "id": "T1",
      "label": "重複排除",
      "stage": "transform",
      "owner": "データ基盤"
    },
    {
      "id": "T2",
      "label": "属性付与",
      "stage": "transform",
      "owner": "データ基盤"
    },
    {
      "id": "O1",
      "label": "申込集計",
      "stage": "output",
      "owner": "企画部"
    }
  ],
  "links": [
    {
      "from": "D1",
      "to": "T1",
      "operation": "受付IDで重複を除く"
    },
    {
      "from": "D1",
      "to": "T2",
      "operation": "顧客IDを照合"
    },
    {
      "from": "D2",
      "to": "T2",
      "operation": "属性を左結合"
    },
    {
      "from": "T1",
      "to": "O1",
      "operation": "件数を集計"
    },
    {
      "from": "T2",
      "to": "O1",
      "operation": "属性別に分割"
    }
  ]
}