索引に戻る

データの来歴と変換

ネットワーク
生成可能
データの来歴と変換

作品として見る

持ち帰る

コピーして貼るか、ファイルに保存して渡す。ネットにつながらない環境には「全部入り」を 1 つ渡せば足りる。

全部入り

data-lineage-stages-everything.md

このパネルの中身を、URL の先にあるファイルの中身まで含めて 1 つにまとめた Markdown

依頼文

1.5 KB
「データの来歴と変換」でHTML資料を作ってください。この型は実行できるので、版面を組み直す必要はありません — 必要なのは中身です。

## この型がすること
入力・変換・出力のデータを、参照IDを持つ線でつなぐ。

## 渡す必要があるもの
- 線は入力にある参照だけを描く。並置しただけのデータ間に関係を仮定しない。

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

入力の機械可読な検証は schema.ts が持ちます。足りない入力は推測せず質問してください。

## 向かない場合
- 同じ条件・単位で比較できないデータには使わない

## 作り方
入力を JSON にまとめ、次を実行します。

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

出力は 1 ファイルで完結し、外部から何も読み込みません。

## 原本
- 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

ファイル

テンプレート・入力スキーマ・サンプルデータ・メタ情報。この文書はこの 4 つから生成されています。 生成された文書

template.tsx

7.7 KB

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

中身を見る
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

中身を見る
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

中身を見る
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

中身を見る
{
  "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": "属性別に分割"
    }
  ]
}