Back to index

Value Stream Map with WIP and Lead Time

Flow
Generates documents
Value Stream Map with WIP and Lead Time

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

value-stream-map-everything.md

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

Prompt

2.1 KB
Build an HTML document with "Value Stream Map with WIP and Lead Time". This layout runs, so there is no composition to rebuild — what it needs is content.

## What this layout does
Map process cycle, changeover and uptime alongside inter-process inventory, then separate value-added time from demand-based waiting against takt.

## What you have to supply
- Provide customer demand and net available time per shift in the same time unit; takt is derived as available time divided by demand.
- Supply cycle and changeover time, uptime, and operators for three to eight ordered processes, plus WIP for every adjacent process pair.

## The shape of the input
- `title` — string — 処理時間と滞留の違いを伝える見出し
- `subtitle` — string? — 対象製品・期間・生産条件
- `timeUnit` — string — 時間値の共通単位
- `demandPerShift` — number — 一勤務あたりの顧客需要量
- `netAvailableTime` — number — 一勤務あたりの正味稼働時間
- `operations` — array (id, label, cycleTime, changeoverTime, uptimePercent, operators) — 3〜8工程
- `buffers` — array (id, afterOperationId, label, inventoryUnits) — 各隣接工程間の仕掛在庫。待ち時間は在庫÷需要×正味稼働時間

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

## When not to use it
- Use process-flow for simple step order and branching without WIP and takt analysis.
- Use SIPOC for a high-level boundary overview; this layout requires quantified inventory and time values.

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

```bash
node scripts/render.mjs value-stream-map <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/value-stream-map/source/template.tsx
- https://design-vault.pages.dev/designs/value-stream-map/source/schema.ts
- https://design-vault.pages.dev/designs/value-stream-map/source/sample-data.json
- https://design-vault.pages.dev/designs/value-stream-map/sample.html

Files

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

template.tsx

9.9 KB

/designs/value-stream-map/source/template.tsx

Show contents
import type { Props } from "./schema";

const CSS = String.raw`
.asset{font-family:var(--font-sans);color:var(--color-on-surface);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:2.25rem 2.5rem;line-height:1.5}.asset *{box-sizing:border-box}.asset h1{font-size:var(--text-h1);line-height:1.3;margin:0}.asset .subtitle{color:var(--color-secondary);margin:.6rem 0 1.25rem}.asset .eyebrow{font:600 var(--text-label) var(--font-mono);letter-spacing:.1em;color:var(--color-tertiary)}.asset .plot{display:block;width:100%;height:auto}.asset .plot text{font-family:var(--font-sans);font-size:12px;fill:var(--color-secondary)}.asset .plot .ink{fill:var(--color-on-surface)}.asset .summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.75rem;margin:1rem 0 1.5rem}.asset .metric{padding:.8rem;background:var(--color-surface-subtle);border-top:3px solid var(--color-tertiary);min-width:0}.asset .metric dt{font-size:var(--text-label);color:var(--color-secondary)}.asset .metric dd{margin:.25rem 0 0;font:700 var(--text-h3) var(--font-mono);overflow-wrap:anywhere}.asset .ledger{border-collapse:collapse;width:100%;font-variant-numeric:tabular-nums}.asset .ledger th,.asset .ledger td{text-align:left;vertical-align:top;padding:.65rem .75rem;border-bottom:1px solid var(--color-outline);overflow-wrap:anywhere}.asset .ledger th{color:var(--color-secondary)}.asset .ledger .mono{font-family:var(--font-mono)}.asset details{margin-top:1rem}.asset summary{cursor:pointer;min-height:2.75rem;padding:.5rem 0;color:var(--color-secondary)}.asset .note{border-left:3px solid var(--color-outline);padding:.75rem 1rem;color:var(--color-secondary)}
@media(max-width:760px){.asset{padding:1.5rem 1rem}.asset h1{font-size:var(--text-h2)}.asset .summary{grid-template-columns:1fr 1fr}.asset .ledger,.asset .ledger tbody,.asset .ledger tr,.asset .ledger td{display:block}.asset .ledger thead{display:none}.asset .ledger tr{padding:.65rem 0;border-bottom:1px solid var(--color-outline)}.asset .ledger td{display:grid;grid-template-columns:9rem minmax(0,1fr);gap:.75rem;border:0;padding:.2rem 0}.asset .ledger td:before{content:attr(data-label);font-size:var(--text-label);color:var(--color-secondary)}.asset-value-stream-map .plot{display:none}}
`;

type Operation = Props["operations"][number];
type Buffer = Props["buffers"][number];
const fmt = (value: number) => Number.isInteger(value) ? value.toLocaleString("ja-JP") : value.toLocaleString("ja-JP", { maximumFractionDigits: 3 });
const waitFor = (buffer: Buffer, data: Props) => buffer.inventoryUnits / data.demandPerShift * data.netAvailableTime;
const shorten = (value: string, size = 20) => [...value].length > size ? [...value].slice(0, size - 1).join("") + "…" : value;

export default function Template(d: Props) {
  const takt = d.netAvailableTime / d.demandPerShift;
  const valueAdded = d.operations.reduce((sum, operation) => sum + operation.cycleTime, 0);
  const waiting = d.buffers.reduce((sum, buffer) => sum + waitFor(buffer, d), 0);
  const leadTime = valueAdded + waiting;
  const bottleneck = [...d.operations].sort((a, b) => b.cycleTime - a.cycleTime)[0];
  const xAt = (index: number) => 42 + index * 150;
  const bufferAfter = (operation: Operation) => d.buffers.find(buffer => buffer.afterOperationId === operation.id)!;
  const waitWidth = leadTime ? waiting / leadTime * 920 : 0;
  return <section className="asset asset-value-stream-map">
    <style>{CSS}</style>
    <header>
      <p className="eyebrow">VALUE STREAM MAP / LEAD TIME AND FLOW</p>
      <h1>{d.title}</h1>
      {d.subtitle && <p className="subtitle">{d.subtitle}</p>}
    </header>
    <svg className="plot" data-layout-still="true" width="1280" height="720" viewBox="0 0 1280 720" role="img" aria-labelledby="vsm-title vsm-desc" fontFamily="sans-serif" fontSize="12" fill="var(--color-secondary)">
      <title id="vsm-title">{d.title}</title>
      <desc id="vsm-desc">顧客需要 {fmt(d.demandPerShift)} 個/勤務、タクト {fmt(takt)} {d.timeUnit}。工程処理時間 {fmt(valueAdded)} {d.timeUnit}、在庫換算の待ち {fmt(waiting)} {d.timeUnit}、総リードタイム {fmt(leadTime)} {d.timeUnit}。</desc>
      <rect x="0" y="0" width="1280" height="720" fill="var(--color-surface)" />
      <text x="54" y="35" fontSize="13" fontWeight="700" letterSpacing="2" fill="var(--color-tertiary)">VALUE STREAM MAP</text>
      <text x="54" y="70" fontSize="21" fontWeight="700" fill="var(--color-on-surface)">{d.title}</text>
      <text x="54" y="108">顧客需要 {fmt(d.demandPerShift)} 個 / 勤務  正味稼働 {fmt(d.netAvailableTime)} {d.timeUnit} / 勤務  タクト {fmt(takt)} {d.timeUnit} / 個</text>
      <text x="42" y="155" fontWeight="700" fill="var(--color-on-surface)">工程間在庫 → 需要と稼働時間から待ちを換算</text>
      {d.buffers.map((buffer, index) => {
        const x = xAt(index) + 136;
        return <g key={buffer.id} data-vsm-record={buffer.id}>
          <polygon points={`${x},180 ${x - 13},204 ${x + 13},204`} fill="var(--color-secondary)" />
          <text x={x} y="220" textAnchor="middle" fontSize="10">{fmt(buffer.inventoryUnits)}個</text>
          <text x={x} y="237" textAnchor="middle" fontSize="10">待ち {fmt(waitFor(buffer, d))}{d.timeUnit}</text>
        </g>;
      })}
      {d.operations.map((operation, index) => {
        const x = xAt(index);
        const y = 266;
        const queue = index < d.operations.length - 1 ? bufferAfter(operation) : undefined;
        return <g key={operation.id} data-vsm-record={operation.id}>
          <rect x={x} y={y} width="122" height="151" fill="var(--color-surface)" stroke={operation.id === bottleneck.id ? "var(--color-tertiary)" : "var(--color-outline)"} strokeWidth={operation.id === bottleneck.id ? 2.5 : 1.2} />
          <rect x={x} y={y} width="122" height="7" fill={operation.id === bottleneck.id ? "var(--color-tertiary)" : "var(--color-outline)"} />
          <text x={x + 8} y={y + 31} fontSize="13" fontWeight="700" fill="var(--color-on-surface)">P{String(index + 1).padStart(2, "0")}</text>
          <text x={x + 8} y={y + 72} fontSize="9" fill={operation.cycleTime > takt ? "var(--color-tertiary)" : "var(--color-secondary)"}>C/T {fmt(operation.cycleTime)}</text>
          <text x={x + 8} y={y + 91} fontSize="9">Takt {fmt(takt)}</text>
          <text x={x + 8} y={y + 110} fontSize="9">段取 {fmt(operation.changeoverTime)}</text>
          <text x={x + 8} y={y + 130} fontSize="9">稼働 {fmt(operation.uptimePercent)}%/{operation.operators}人</text>
          {queue && <g>
            <line x1={x + 122} y1="430" x2={x + 145} y2="430" stroke="var(--color-tertiary)" strokeWidth="2" />
            <path d={`M ${x + 138} 425 L ${x + 145} 430 L ${x + 138} 435`} fill="none" stroke="var(--color-tertiary)" strokeWidth="2" />
          </g>}
        </g>;
      })}
      <line x1="54" y1="462" x2="1225" y2="462" stroke="var(--color-outline)" />
      <text x="54" y="491" fontWeight="700" fill="var(--color-on-surface)">総リードタイム内訳</text>
      <text x="54" y="528">待ち / 在庫換算</text>
      <rect x="245" y="510" width={waitWidth} height="24" fill="var(--color-secondary)" />
      <rect x={245 + waitWidth} y="510" width={920 - waitWidth} height="24" fill="var(--color-tertiary)" />
      <text x="245" y="557">待ち {fmt(waiting)} {d.timeUnit}</text>
      <text x="720" y="557">処理 {fmt(valueAdded)} {d.timeUnit}</text>
      <text x="54" y="596" fontWeight="700" fill="var(--color-on-surface)">総リードタイム {fmt(leadTime)} {d.timeUnit} | 付加価値時間 {leadTime ? fmt(valueAdded / leadTime * 100) : 0}% | 最長処理 {shorten(bottleneck.label)}</text>
      <text x="54" y="635">段取時間・稼働率・担当人数は工程条件。仕掛在庫の待ち換算は需要一定を前提とする。</text>
      <text x="54" y="675">工程順、個別実績、境界条件は下の台帳を参照。</text>
    </svg>
    <dl className="summary">
      <div className="metric"><dt>タクトタイム</dt><dd>{fmt(takt)} {d.timeUnit} / 個</dd></div>
      <div className="metric"><dt>付加価値処理</dt><dd>{fmt(valueAdded)} {d.timeUnit}</dd></div>
      <div className="metric"><dt>在庫換算の待ち</dt><dd>{fmt(waiting)} {d.timeUnit}</dd></div>
      <div className="metric"><dt>総リードタイム</dt><dd>{fmt(leadTime)} {d.timeUnit}</dd></div>
    </dl>
    <p className="note">タクトは正味稼働時間÷顧客需要です。バッファの待ちは在庫量を一定需要で時間換算します。需要変動・故障・工程間の相互依存はこの現在状態図に含みません。</p>
    <details open>
      <summary>工程・工程間在庫のデータを確認({d.operations.length + d.buffers.length}件)</summary>
      <table className="ledger"><thead><tr><th>順序 / 項目</th><th>処理・在庫</th><th>段取・待ち</th><th>稼働条件</th></tr></thead>
        <tbody>
          {d.operations.map((operation, index) => <tr key={operation.id} data-vsm-record={operation.id}>
            <td data-label="順序 / 項目">工程 {index + 1} / {operation.label}</td><td data-label="処理・在庫" className="mono">C/T {fmt(operation.cycleTime)} {d.timeUnit}</td><td data-label="段取・待ち" className="mono">段取 {fmt(operation.changeoverTime)} {d.timeUnit}</td><td data-label="稼働条件">稼働 {fmt(operation.uptimePercent)}% ・ {operation.operators}人{operation.cycleTime > takt ? " ・ タクト超過" : ""}</td>
          </tr>)}
          {d.buffers.map((buffer, index) => <tr key={buffer.id} data-vsm-record={buffer.id}>
            <td data-label="順序 / 項目">工程{index + 1}後 / {buffer.label}</td><td data-label="処理・在庫" className="mono">{fmt(buffer.inventoryUnits)}個</td><td data-label="段取・待ち" className="mono">待ち {fmt(waitFor(buffer, d))} {d.timeUnit}</td><td data-label="稼働条件">需要 {fmt(d.demandPerShift)}個 / 勤務</td>
          </tr>)}
        </tbody>
      </table>
    </details>
  </section>;
}

schema.ts

2.5 KB

/designs/value-stream-map/source/schema.ts

Show contents
import { z } from "zod";

const text = z.string().trim().min(1).max(160);
const label = z.string().trim().min(1).max(30);
const id = z.string().trim().min(1).max(24);
const finite = z.number().finite();

const operation = z.object({
  id: id.describe("工程の一意なID"),
  label: label.describe("工程名"),
  cycleTime: finite.positive().describe("一単位あたりの処理時間。timeUnitと同じ単位"),
  changeoverTime: finite.nonnegative().describe("品種切替え時間。timeUnitと同じ単位"),
  uptimePercent: finite.min(0).max(100).describe("稼働可能時間の割合"),
  operators: z.number().int().min(1).max(50).describe("工程の標準担当人数"),
}).strict();
const buffer = z.object({
  id: id.describe("工程間バッファの一意なID"),
  afterOperationId: id.describe("仕掛在庫の直前にある工程ID"),
  label: label.describe("仕掛・待ち行列の名前"),
  inventoryUnits: finite.nonnegative().describe("次工程の前に滞留する在庫個数"),
}).strict();
const unique = (values: string[]) => new Set(values).size === values.length;

export const schema = z.object({
  title: text.describe("処理時間と滞留の違いを伝える見出し"),
  subtitle: text.optional().describe("対象製品・期間・生産条件"),
  timeUnit: z.string().trim().min(1).max(12).describe("時間値の共通単位"),
  demandPerShift: finite.positive().describe("一勤務あたりの顧客需要量"),
  netAvailableTime: finite.positive().describe("一勤務あたりの正味稼働時間。timeUnitと同じ単位"),
  operations: z.array(operation).min(3).max(8).describe("価値付加処理の順序と工程別データ"),
  buffers: z.array(buffer).min(2).max(7).describe("隣接工程間の仕掛在庫"),
}).strict().superRefine((data, ctx) => {
  if (data.buffers.length !== data.operations.length - 1) {
    ctx.addIssue({ code: "custom", path: ["buffers"], message: "隣接する各工程間にバッファを一つずつ設定" });
  }
  data.buffers.forEach((item, index) => {
    if (data.operations[index]?.id !== item.afterOperationId) {
      ctx.addIssue({ code: "custom", path: ["buffers", index, "afterOperationId"], message: "バッファは対応する直前工程の直後に並ぶ" });
    }
  });
  if (!unique([...data.operations.map(item => item.id), ...data.buffers.map(item => item.id)])) {
    ctx.addIssue({ code: "custom", path: ["operations"], message: "工程とバッファのIDはすべて一意" });
  }
});

export type Props = z.infer<typeof schema>;

meta.yaml

3.0 KB

/designs/value-stream-map/source/meta.yaml

Show contents
id: value-stream-map
name:
  ja: 仕掛在庫とリードタイムの価値流れ図
  en: Value Stream Map with WIP and Lead Time
purpose:
  ja: 工程別サイクルタイム・段取・稼働条件と工程間在庫を並べ、顧客需要から算出するタクトに対して、付加価値時間と在庫換算の待ちを分けて読む。
  en: Map process cycle, changeover and uptime alongside inter-process inventory, then separate value-added time from demand-based waiting against takt.
status: published
still: ./still.png
category: operations
catalogOrder: 840
jobs:
  - prioritize-diagnose
  - plan-sequence
useCases:
  - process
  - prioritization
layoutKinds:
  - flow
tags:
  - value-stream-map
  - lean-operations
  - takt-time
  - work-in-process
  - offline
  - responsive
whenToUse:
  - ja: 顧客需要に必要なタクトと工程別処理能力を比べ、在庫が積み上がる箇所や待ち時間の大きい区間を見つける。
    en: Use to compare process capacity with demand-based takt and locate inventory buildup or long waits.
  - ja: リードタイム全体のうち実際の処理時間が占める割合を示し、改善対象を選ぶ。
    en: Use to separate processing time from total lead time when choosing where to improve.
avoidWhen:
  - ja: 工程順・分岐だけを簡潔に示す場合はprocess-flowを使う。仕掛在庫やタクトはこの契約には含まれない。
    en: Use process-flow for simple step order and branching without WIP and takt analysis.
  - ja: 工程の前後関係や開始・終了境界だけを概観する場合はSIPOCを使う。ここでは在庫・時間の定量値を要する。
    en: Use SIPOC for a high-level boundary overview; this layout requires quantified inventory and time values.
requiredInputs:
  - ja: 一勤務あたりの顧客需要と正味稼働時間を同じ時間単位で示す。タクトは稼働時間÷需要で導出する。
    en: Provide customer demand and net available time per shift in the same time unit; takt is derived as available time divided by demand.
  - ja: 3〜8工程のサイクル・段取時間、稼働率、担当人数と、隣接工程間の仕掛在庫を順序どおりに指定する。
    en: Supply cycle and changeover time, uptime, and operators for three to eight ordered processes, plus WIP for every adjacent process pair.
relatedLayouts:
  - process-flow
  - sipoc-overview
  - critical-path-gantt
dataSchema:
  title: string — 処理時間と滞留の違いを伝える見出し
  subtitle: string? — 対象製品・期間・生産条件
  timeUnit: string — 時間値の共通単位
  demandPerShift: number — 一勤務あたりの顧客需要量
  netAvailableTime: number — 一勤務あたりの正味稼働時間
  operations: array (id, label, cycleTime, changeoverTime, uptimePercent, operators) — 3〜8工程
  buffers: array (id, afterOperationId, label, inventoryUnits) — 各隣接工程間の仕掛在庫。待ち時間は在庫÷需要×正味稼働時間
sources: []

sample-data.json

1.2 KB

/designs/value-stream-map/source/sample-data.json

Show contents
{
  "title": "滞留在庫が処理時間より長いリードタイムを生む",
  "subtitle": "架空サンプル|電子部品の受注組立ライン・1勤務あたりの顧客需要",
  "timeUnit": "時間",
  "demandPerShift": 300,
  "netAvailableTime": 7.5,
  "operations": [
    { "id": "p-receive", "label": "部品と受注仕様を照合", "cycleTime": 0.018, "changeoverTime": 0.04, "uptimePercent": 96, "operators": 1 },
    { "id": "p-prepare", "label": "基板と部材を準備", "cycleTime": 0.025, "changeoverTime": 0.08, "uptimePercent": 94, "operators": 2 },
    { "id": "p-assemble", "label": "基板へ部品を実装", "cycleTime": 0.04, "changeoverTime": 0.12, "uptimePercent": 91, "operators": 3 },
    { "id": "p-inspect", "label": "通電と外観を検査", "cycleTime": 0.02, "changeoverTime": 0.03, "uptimePercent": 98, "operators": 1 }
  ],
  "buffers": [
    { "id": "b01", "afterOperationId": "p-receive", "label": "部材確認後の仕掛", "inventoryUnits": 48 },
    { "id": "b02", "afterOperationId": "p-prepare", "label": "実装待ち基板", "inventoryUnits": 30 },
    { "id": "b03", "afterOperationId": "p-assemble", "label": "検査待ち完成品", "inventoryUnits": 60 }
  ]
}