前回までで、標準化の対象を人ではなく課題に置く、という方針が固まりました。今回からは実装の話です。まず、その課題をどんなデータのかたちで持つかを決めるところから始めました。

課題ごとに専用画面を作ると、増やせなくなる

最初に思いついたのは、課題ごとに専用の画面を作る方法でした。サイト障害用の画面、権限追加用の画面、というふうに。作りやすそうに見えます。

でも少し考えると、この方針は行き詰まります。課題が増えるたびに画面を一つ作ることになり、確認観点を追加するたびにあちこちの画面を直すことになります。共通のはずの「確認して、判断して、証跡を残す」という流れが、画面ごとにばらばらに実装されてしまいます。

やりたかったのは、確認観点をデータとして持たせ、同じ仕組みに載せることでした。だとすれば、画面は共通にして、課題の中身だけをデータで差し替えられる形にするのが自然です。

CaseとStepという二段構えにした

そこで、課題を表す単位を「Case」、その中の確認観点の一つひとつを「Step」として分けました。Caseが課題全体、Stepがその中で順番にたどる確認のかたまり、というイメージです。

Caseは、課題全体のメタ情報を持ちます。タイトル、概要、難易度、目安の所要時間、前提条件、そして「この課題ではAWS環境を変更しない」といった安全上の注意です。

Stepは、確認観点そのものです。ここに、人の頭の中にあった暗黙知を移していきます。実際の型定義から、Stepの中身を抜き出すとこうなっています。

export interface CaseStep {
  id: string
  /** 1始まりの表示順。orderedSteps で昇順に並べ替える */
  order: number
  title: string
  /** 作業目的 */
  purpose: string
  /** 背景・なぜこの確認をするか */
  background: string
  /** GUI手順(AWSコンソールのサービス→メニュー→タブ→確認項目) */
  instructions: string[]
  /** 確認値(何を見るか) */
  expectedValues: string[]
  /** 正常例(正常の判断基準) */
  normalCriteria: string[]
  /** 異常例(異常の判断基準) */
  abnormalCriteria: string[]
  /** ステップ証跡の必須/任意入力欄 */
  evidenceRequirements: EvidenceRequirement[]
  /** 完了条件(人間可読の説明) */
  completionConditions: string[]
  /** 正常/異常の判断記録を完了必須にするか */
  requiresJudgement: boolean
  // ...(warnings / hints / aiContext / operationType)
}

暗黙知を、フィールドの形にした

このフィールドの並びが、前回まで書いてきた「手順書から抜け落ちる部分」への回答になっています。

purposebackgroundは、なぜこの確認をするのかを言葉にする場所です。instructionsが操作、expectedValuesが見るべき値。そしてnormalCriteriaabnormalCriteriaが、正常と異常の判断基準です。ここが、経験者の頭の中にしかなかった基準を、はっきり書き出すための場所になります。

completionConditionsは、そのStepが終わったといえる条件です。requiresJudgementは、正常か異常かの記録を残さないと完了にできない、という制約のためのフラグです。証跡の入力欄はevidenceRequirementsにまとめてあり、必須か任意か、何を入れる欄かを一つずつ定義できます。

つまり、目的・背景・確認値・正常例・異常例・証跡・完了条件という「観点をたどるのに必要なもの」を、課題データの側に持たせられるようにしたわけです。担当者の出身分野に関係なく、Stepを開けばこれらが並んでいる、という状態を目指しました。

Zodで構造を検証する

データで課題を差し替える方針にすると、逆に心配なのは、そのデータ自体が壊れていないかです。正常例が空だったり、確認手順が抜けていたりしたら、せっかくの標準化が崩れます。

そこで、課題データを読み込むときにZodで構造を検証するようにしました。スキーマの一部を抜き出すと、こういう形です。

export const caseStepSchema = z.object({
  id: nonEmpty,
  order: z.number().int().positive(),
  title: nonEmpty,
  purpose: nonEmpty,
  instructions: z.array(nonEmpty).min(1, 'GUI手順は1つ以上必要です'),
  normalCriteria: z.array(nonEmpty).min(1, '正常の判断基準は1つ以上必要です'),
  abnormalCriteria: z.array(nonEmpty).min(1, '異常の判断基準は1つ以上必要です'),
  completionConditions: z.array(nonEmpty).min(1, '完了条件は1つ以上必要です'),
  requiresJudgement: z.boolean(),
  // ...
})

正常例・異常例・完了条件を、それぞれ1つ以上必須にしてあります。加えて、Stepの表示順(order)が重複していないか、IDがぶつかっていないかも、読み込み時にまとめてチェックしています。データに不備があれば、その場で気づける仕組みです。

課題を足すのは、データを1つ加えるだけ

この形にしたことで、新しい課題を追加する作業は、原則として課題データのファイルを一つ書いて、一覧の配列へ登録するだけになりました。画面やロジックには手を入れません。

実際、後の回で書く3つの課題は、いずれもこの同じCase/Stepモデルの上に載っています。性質のかなり違う課題を、同じ器で扱えるかどうかは、このモデルがうまくできているかの試金石でもありました。その検証結果は、シリーズの後半でまとめます。

現時点でできているのは、あくまで「確認観点を構造として持てるようにした」ところまでです。データの中身が本当に現場の暗黙知を写し取れているかは、課題ごとに丁寧に書くしかありません。器を作った、という段階だと思っています。

次回は、このCaseとStepを、実際にどう画面に並べたのか。確認観点を一画面に集めるための3カラムUIの設計を書きます。