索引に戻る

要件トレーサビリティ表

表
生成可能
要件トレーサビリティ表

作品として見る

持ち帰る

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

全部入り

requirement-traceability-matrix-everything.md

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

依頼文

1.7 KB
「要件トレーサビリティ表」でHTML資料を作ってください。この型は実行できるので、版面を組み直す必要はありません — 必要なのは中身です。

## この型がすること
要求・受け入れ条件・検証ケース・証跡を結び、未検証の範囲を追跡する。

## 渡す必要があるもの
- 要件ID、受け入れ条件、責任者、検証ケースID・方法・結果・証跡を定義する。

## 入力の形
- `title` — string — 追跡対象の結論
- `subtitle` — string? — 対象リリース・基準日・範囲
- `project` — string — 対象案件・リリース
- `requirements` — array (3–10) — 優先度、受け入れ条件、責任者、検証ケースID
- `tests` — array (2–16) — 検証方法、結果、証跡

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

## 向かない場合
- 実装可否や適合状況だけを評価する場合は、fit-gap-assessmentを使う。

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

```bash
node scripts/render.mjs requirement-traceability-matrix <入力.json> --theme ink -o out.html
```

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

## 原本
- https://design-vault.pages.dev/designs/requirement-traceability-matrix/source/template.tsx
- https://design-vault.pages.dev/designs/requirement-traceability-matrix/source/schema.ts
- https://design-vault.pages.dev/designs/requirement-traceability-matrix/source/sample-data.json
- https://design-vault.pages.dev/designs/requirement-traceability-matrix/sample.html

ファイル

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

template.tsx

9.3 KB

/designs/requirement-traceability-matrix/source/template.tsx

中身を見る
import type { Props } from "./schema";

const CSS = String.raw`
.asset-requirement-traceability-matrix{font-family:var(--font-sans);color:var(--color-primary);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:3rem 2.5rem;line-height:1.6;font-size:var(--text-body-sm)}
.asset-requirement-traceability-matrix *{box-sizing:border-box}.asset-requirement-traceability-matrix header{margin-bottom:1.5rem}.asset-requirement-traceability-matrix .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-tertiary);margin-bottom:.75rem}.asset-requirement-traceability-matrix h1{font-size:var(--text-h1);line-height:1.3;max-width:28em}.asset-requirement-traceability-matrix .subtitle{margin-top:.75rem;color:var(--color-secondary)}
.asset-requirement-traceability-matrix .summary{display:flex;gap:2rem;flex-wrap:wrap;border-top:2px solid var(--color-primary);border-bottom:1px solid var(--color-outline);padding:.8rem 0;margin-bottom:1rem}.asset-requirement-traceability-matrix .summary p{margin:0;color:var(--color-secondary);font-size:var(--text-label)}.asset-requirement-traceability-matrix .summary strong{font-family:var(--font-mono);font-size:var(--text-h3);color:var(--color-primary);margin-right:.35rem}
.asset-requirement-traceability-matrix .requirements{border-top:1px solid var(--color-outline)}.asset-requirement-traceability-matrix .requirement{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.25fr);gap:1.4rem;padding:1.1rem 0;border-bottom:1px solid var(--color-outline);min-width:0}.asset-requirement-traceability-matrix .requirement>*{min-width:0}.asset-requirement-traceability-matrix .req-head{display:flex;gap:.65rem;align-items:baseline;flex-wrap:wrap}.asset-requirement-traceability-matrix .req-head code{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-tertiary)}.asset-requirement-traceability-matrix .req-head h2{font-size:var(--text-body-md);margin:0}.asset-requirement-traceability-matrix .priority{font-size:var(--text-label);color:var(--color-secondary);border:1px solid var(--color-outline);padding:.05rem .4rem}.asset-requirement-traceability-matrix .statement{margin:.5rem 0;font-weight:600;overflow-wrap:anywhere}.asset-requirement-traceability-matrix .acceptance{margin:0;color:var(--color-secondary);overflow-wrap:anywhere}.asset-requirement-traceability-matrix .owner{margin-top:.45rem;font-size:var(--text-label);color:var(--color-secondary)}.asset-requirement-traceability-matrix .test-list{display:flex;flex-direction:column;gap:.45rem;margin:0;padding:0;list-style:none}.asset-requirement-traceability-matrix .test{display:grid;grid-template-columns:minmax(5.5rem,.7fr) minmax(0,1.5fr) auto;gap:.7rem;align-items:start;padding:.45rem .6rem;border-left:2px solid var(--color-tertiary);background:var(--color-surface-subtle);min-width:0}.asset-requirement-traceability-matrix .test-id{font-family:var(--font-mono);font-size:var(--text-label);overflow-wrap:anywhere}.asset-requirement-traceability-matrix .test-name{font-weight:600;overflow-wrap:anywhere}.asset-requirement-traceability-matrix .test-meta{font-size:var(--text-label);color:var(--color-secondary);text-align:right;white-space:nowrap}.asset-requirement-traceability-matrix .evidence{grid-column:2/4;color:var(--color-secondary);font-size:var(--text-label);overflow-wrap:anywhere}.asset-requirement-traceability-matrix .empty-evidence{color:var(--color-tertiary);font-weight:600}.asset-requirement-traceability-matrix footer{margin-top:1.2rem;padding-top:.75rem;border-top:1px solid var(--color-outline);color:var(--color-secondary);font-size:var(--text-label)}.asset-requirement-traceability-matrix .still-svg{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media(max-width:680px){.asset-requirement-traceability-matrix{padding:1.5rem 1rem}.asset-requirement-traceability-matrix h1{font-size:var(--text-h2)}.asset-requirement-traceability-matrix .requirement{grid-template-columns:minmax(0,1fr);gap:.7rem;padding:1rem 0}.asset-requirement-traceability-matrix .test{grid-template-columns:minmax(0,1fr) auto;gap:.2rem .6rem}.asset-requirement-traceability-matrix .test-id{grid-column:1}.asset-requirement-traceability-matrix .test-name{grid-column:1}.asset-requirement-traceability-matrix .test-meta{grid-column:2;grid-row:1/3}.asset-requirement-traceability-matrix .evidence{grid-column:1/3}.asset-requirement-traceability-matrix .summary{gap:1rem}}
@media print{.asset-requirement-traceability-matrix{max-width:none;padding:1rem}.asset-requirement-traceability-matrix .requirement{break-inside:avoid}}
`;

const methods = { unit: "単体", integration: "結合", acceptance: "受入", manual: "手動" };
const results = { passed: "確認済み", failed: "不合格", pending: "未実施", blocked: "保留" };
const compact = (value: string, max: number) => [...value].length > max ? `${[...value].slice(0, max - 1).join("")}…` : value;

function Still({ data }: { data: Props }) {
  const byId = new Map(data.tests.map((testCase) => [testCase.id, testCase]));
  const rows = data.requirements.slice(0, 10);
  const rowHeight = 51;
  return <svg className="still-svg" data-layout-still="true" width="1280" height="720" viewBox="0 0 1280 720" aria-hidden="true">
    <rect width="1280" height="720" fill="var(--color-surface)" />
    <text x="48" y="48" fontSize="14" fontFamily="sans-serif" fill="var(--color-secondary)">{data.project}</text>
    <text x="48" y="88" fontSize="28" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(data.title, 56)}</text>
    <text x="48" y="117" fontSize="13" fontFamily="sans-serif" fill="var(--color-secondary)">要求から受け入れ検証までの対応を追跡</text>
    <text x="48" y="145" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">要求</text>
    <text x="560" y="145" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">紐づく検証ケース</text>
    {rows.map((requirement, index) => {
      const y = 174 + index * rowHeight;
      return <g key={requirement.id}>
        <line x1="48" x2="1232" y1={y - 16} y2={y - 16} stroke="var(--color-outline)" />
        <text x="52" y={y + 5} fontSize="11" fontFamily="sans-serif" fill="var(--color-tertiary)">{requirement.priority}</text>
        <text x="102" y={y + 5} fontSize="12" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{requirement.id}</text>
        <text x="190" y={y + 5} fontSize="12" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(requirement.statement, 48)}</text>
        {requirement.testIds.slice(0, 4).map((id, testIndex) => {
          const testCase = byId.get(id);
          return <g key={id}>
            <line x1={560 + testIndex * 164} x2={578 + testIndex * 164} y1={y} y2={y} stroke="var(--color-tertiary)" strokeWidth="2" />
            <circle cx={560 + testIndex * 164} cy={y} r="4" fill="var(--color-tertiary)" />
            <text x={584 + testIndex * 164} y={y + 4} fontSize="11" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(testCase?.id ?? id, 16)}</text>
          </g>;
        })}
      </g>;
    })}
    <text x="48" y="704" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">{data.requirements.length}要件 · {data.tests.length}検証ケース</text>
  </svg>;
}

export default function Template(data: Props) {
  const byId = new Map(data.tests.map((testCase) => [testCase.id, testCase]));
  const linkCount = data.requirements.reduce((count, requirement) => count + requirement.testIds.length, 0);
  return <section className="asset asset-requirement-traceability-matrix">
    <style>{CSS}</style>
    <header>
      <p className="eyebrow">{data.project} · 要件の検証状況</p>
      <h1>{data.title}</h1>
      {data.subtitle && <p className="subtitle">{data.subtitle}</p>}
    </header>
    <div className="summary" aria-label="追跡件数">
      <p><strong>{data.requirements.length}</strong>要件</p>
      <p><strong>{data.tests.length}</strong>検証ケース</p>
      <p><strong>{linkCount}</strong>対応づけ</p>
    </div>
    <div className="requirements">
      {data.requirements.map((requirement) => <article className="requirement" key={requirement.id}>
        <div>
          <div className="req-head"><code>{requirement.id}</code><span className="priority">{requirement.priority}</span><h2>{requirement.owner}</h2></div>
          <p className="statement">{requirement.statement}</p>
          <p className="acceptance"><strong>受け入れ条件:</strong> {requirement.acceptance}</p>
        </div>
        <ul className="test-list" aria-label={`${requirement.id}を検証するケース`}>
          {requirement.testIds.map((id) => {
            const testCase = byId.get(id)!;
            return <li className="test" key={id}>
              <code className="test-id">{testCase.id}</code>
              <span className="test-name">{testCase.name}</span>
              <span className="test-meta">{methods[testCase.method]} · {results[testCase.result]}</span>
              <span className={testCase.evidence ? "evidence" : "evidence empty-evidence"}>{testCase.evidence ?? "証跡を記録"}</span>
            </li>;
          })}
        </ul>
      </article>)}
    </div>
    <footer>要求の完了は、対応づいた検証ケースと証跡を確認して判断する。</footer>
    <Still data={data} />
  </section>;
}

schema.ts

2.8 KB

/designs/requirement-traceability-matrix/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);

export const requirementSchema = z.object({
  id: short.describe("要求の一意な識別子"),
  priority: z.enum(["P0", "P1", "P2", "P3"]).describe("要求の優先度"),
  statement: text.describe("実現する要求"),
  acceptance: text.describe("満たしたと判断できる条件"),
  owner: short.describe("要求の確認責任者"),
  testIds: z.array(short).min(1).max(4).describe("要求を検証するケースID"),
}).strict();

export const verificationSchema = z.object({
  id: short.describe("検証ケースの一意な識別子"),
  name: short.describe("検証ケースの名称"),
  method: z.enum(["unit", "integration", "acceptance", "manual"]).describe("検証方法"),
  result: z.enum(["passed", "failed", "pending", "blocked"]).describe("実行結果"),
  evidence: text.optional().describe("実行記録・証跡・未実施理由"),
}).strict();

export const schema = z.object({
  title: text.describe("追跡対象の結論"),
  subtitle: text.optional().describe("対象リリース・基準日・範囲"),
  project: short.describe("対象案件・リリース"),
  requirements: z.array(requirementSchema).min(3).max(10).describe("要求から検証ケースへの対応"),
  tests: z.array(verificationSchema).min(2).max(16).describe("検証ケースと結果"),
}).strict().superRefine((data, ctx) => {
  const requirementIds = data.requirements.map((item) => item.id);
  const testIds = data.tests.map((item) => item.id);
  if (new Set(requirementIds).size !== requirementIds.length) {
    ctx.addIssue({ code: "custom", path: ["requirements"], message: "要求IDは一意にすること" });
  }
  if (new Set(testIds).size !== testIds.length) {
    ctx.addIssue({ code: "custom", path: ["tests"], message: "検証ケースIDは一意にすること" });
  }
  const knownTests = new Set(testIds);
  const usedTests = new Set<string>();
  data.requirements.forEach((requirement, index) => {
    if (new Set(requirement.testIds).size !== requirement.testIds.length) {
      ctx.addIssue({ code: "custom", path: ["requirements", index, "testIds"], message: "同じ検証ケースを重複指定できない" });
    }
    requirement.testIds.forEach((id) => {
      if (!knownTests.has(id)) {
        ctx.addIssue({ code: "custom", path: ["requirements", index, "testIds"], message: `未登録の検証ケース: ${id}` });
      }
      usedTests.add(id);
    });
  });
  data.tests.forEach((testCase, index) => {
    if (!usedTests.has(testCase.id)) {
      ctx.addIssue({ code: "custom", path: ["tests", index, "id"], message: `要求から参照されていない検証ケース: ${testCase.id}` });
    }
  });
});

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

meta.yaml

1.6 KB

/designs/requirement-traceability-matrix/source/meta.yaml

中身を見る
id: requirement-traceability-matrix
name:
  ja: 要件トレーサビリティ表
  en: Requirement Traceability Matrix
purpose:
  ja: 要求・受け入れ条件・検証ケース・証跡を結び、未検証の範囲を追跡する。
  en: Link requirements to acceptance criteria, verification cases, and evidence to find untested scope.
status: published
still: ./still.png
category: governance
catalogOrder: 800
jobs:
  - trace-flow
  - assign-governance
useCases:
  - requirements
layoutKinds:
  - table
tags:
  - requirements
  - verification
  - traceability
  - offline
  - responsive
whenToUse:
  - ja: 要求ごとに受け入れ条件と検証ケースを結び、実施結果と証跡を追うとき。
    en: Use to connect each requirement to its acceptance criteria, verification cases, and evidence.
avoidWhen:
  - ja: 実装可否や適合状況だけを評価する場合は、fit-gap-assessmentを使う。
    en: Use fit-gap-assessment when the task is only to evaluate implementation fit.
requiredInputs:
  - ja: 要件ID、受け入れ条件、責任者、検証ケースID・方法・結果・証跡を定義する。
    en: Provide requirement IDs, acceptance criteria, owners, test IDs, methods, results, and evidence.
relatedLayouts:
  - fit-gap-assessment
  - release-readiness
dataSchema:
  title: string — 追跡対象の結論
  subtitle: string? — 対象リリース・基準日・範囲
  project: string — 対象案件・リリース
  requirements: array (3–10) — 優先度、受け入れ条件、責任者、検証ケースID
  tests: array (2–16) — 検証方法、結果、証跡
sources: []

sample-data.json

2.6 KB

/designs/requirement-traceability-matrix/source/sample-data.json

中身を見る
{
  "title": "受け入れ条件から実施証跡までを追跡する",
  "subtitle": "架空の顧客管理刷新|2026年度 第2リリース",
  "project": "顧客管理刷新",
  "requirements": [
    {
      "id": "REQ-01",
      "priority": "P0",
      "statement": "利用者は登録済み顧客を検索できる",
      "acceptance": "氏名または顧客番号で検索し、該当する顧客だけを表示する",
      "owner": "プロダクト担当",
      "testIds": [
        "TEST-01",
        "TEST-02"
      ]
    },
    {
      "id": "REQ-02",
      "priority": "P0",
      "statement": "変更履歴を顧客詳細から確認できる",
      "acceptance": "変更日時・変更者・変更項目が新しい順で表示される",
      "owner": "業務担当",
      "testIds": [
        "TEST-03"
      ]
    },
    {
      "id": "REQ-03",
      "priority": "P1",
      "statement": "権限のない利用者は顧客情報を変更できない",
      "acceptance": "閲覧権限だけの利用者による更新要求を拒否し、記録する",
      "owner": "セキュリティ担当",
      "testIds": [
        "TEST-04",
        "TEST-05"
      ]
    },
    {
      "id": "REQ-04",
      "priority": "P2",
      "statement": "顧客一覧をCSV形式で出力できる",
      "acceptance": "画面で指定した絞り込みと列順を出力へ反映する",
      "owner": "運用担当",
      "testIds": [
        "TEST-06"
      ]
    }
  ],
  "tests": [
    {
      "id": "TEST-01",
      "name": "顧客番号で検索",
      "method": "acceptance",
      "result": "passed",
      "evidence": "受入記録 AC-14。完全一致と該当なしを確認"
    },
    {
      "id": "TEST-02",
      "name": "氏名の一部で検索",
      "method": "integration",
      "result": "passed",
      "evidence": "結合試験 INT-22。表記ゆれを含む3件を確認"
    },
    {
      "id": "TEST-03",
      "name": "変更履歴の並び順",
      "method": "acceptance",
      "result": "pending",
      "evidence": "リリース候補で担当者レビューを予定"
    },
    {
      "id": "TEST-04",
      "name": "閲覧者の更新拒否",
      "method": "integration",
      "result": "passed",
      "evidence": "権限試験 ACL-08。更新と監査記録を確認"
    },
    {
      "id": "TEST-05",
      "name": "監査ログの記録",
      "method": "unit",
      "result": "passed",
      "evidence": "テスト結果 UT-91"
    },
    {
      "id": "TEST-06",
      "name": "絞り込みCSV出力",
      "method": "manual",
      "result": "blocked",
      "evidence": "文字コード確認用の実データを準備中"
    }
  ]
}