注文用の型式コードを組み立てたいのに、参照できるAPIもスキーマも無い。あるのはベンダーのカタログページに載った表と図だけ——という状況で、AIに入力支援ツールを書かせた。表を読み込ませて終わり、ではない。表に書かれていない制約をどうUIの制約に翻訳するかが本題だった。
APIの無い仕様をUIに翻訳するときの3つの具体的な判断——選択肢が条件で切り替わるときの落とし所、上限をUIのどこで縛るか、仕様に無い便利機能を足すときの実装の型。そして「正しさを保証できない」前提での見せ方の作り込み方。
アルミフレームに追加工(穴あけ・タップ・面取りなど)を指定して発注するとき、MISUMIの体系では型式コードという1本の文字列にすべての指定を詰め込む。シリーズ名・全長・穴の位置・穴径……それぞれの指定をハイフンでつないだ、人間が手で組み立てる注文用の暗号のようなものだ。
この体系にはAPIもスキーマも無い。仕様が存在する場所は、公開カタログページに載っている表と図だけだ。「シリーズが6のときだけ選べるレンチ穴径」「方向ごとに最大5箇所まで」というような制約は、コードのどこにも構造化された形では書かれていない。読んで、頭の中で組み立てるしかない。
この「読んで組み立てる」作業を、入力フォームに翻訳した。ゴールはシンプルで、選んだ内容から型式コードの文字列を機械的に生成すること。だが実際に手を動かすと、面倒なのはコード生成そのものより、カタログの表に書かれた条件分岐をどうUIの制約として表現するかだった。
カタログの制約は放っておくと「選んだ後にエラーを出す」実装になりがちだ。それを避けて、選択肢そのものを条件に応じて絞る方向で作った。
ザグリ穴の径は、フレームのシリーズ系列(5角・6角・8角)ごとに選べる値が違う。シリーズを切り替えたときに選択中の値が新しい選択肢の中に無ければ、自動で先頭の値に落とす——これだけで「無効な組み合わせのまま送信される」事故を防げる。バリデーションを後段に置くより、選べる状態そのものを常に正しく保つほうが単純だった。
コード体系はレンチ穴・ザグリ穴とも、1方向あたりA〜Eの5箇所までしか指定できない(型式コード中の英字がA〜Eの5種類しか用意されていないため)。この上限は「6箇所目を追加しようとした瞬間」に弾くのではなく、追加ボタンを押した時点でその方向の現在の点数を数え、超えるなら追加自体を拒否する形にした。エラーメッセージより先に、そもそも壊れた状態を作らせない。
// シリーズ切り替え時、選択中の穴径が無効なら先頭へ落とす
const zOpts = Z_OPTIONS[seriesGroup(state.series)];
if (!zOpts.find(z => z[0] === prevZ)) {
state.zg.d = zOpts[0][0]; // 選べない値のまま残さない
}
// 方向ごと最大5箇所(コード体系のA〜Eに対応)
if (arr.length + need > 5) {
// 追加ボタンの時点で拒否する。送信後には流さない
return;
}
レンチ穴径の指定欄も、シリーズ系列が「6」のときだけ表示している。これも「選べるが無視される項目」を画面に残さないための処理で、フォームの見た目自体がカタログの適用条件を代弁する形になった。
アルミフレームの追加工は、全長の中心に対して左右対称に穴をあけたい場面が多い。しかしMISUMIの型式コード自体には「対称にする」という指定は存在しない——あるのはあくまで個々の位置指定だけだ。そこで、カタログの体系を壊さない範囲で入力側にだけ便利機能を足すことにした。
「全長中心に対して対称配置」をONにすると、入力できる範囲が中心までの半分に制限される。中心までの1点を入力すると、全長 − 入力値で反対側の位置を自動計算して、ペアで2点を追加する。ぴったり中心の場合だけは1点のまま扱う。
pairIdで紐付け、上限判定を先に済ませる1回の追加操作で2点増えることがあるので、「方向ごと最大5箇所」の上限判定は実際に追加する前に必要な点数(1点か2点か)を計算してから行う必要がある。先に判定を済ませてから配列に積む、という順序を守らないと、上限を超えた状態が一瞬でも成立してしまう。
// 中心までの入力値から、反対側の位置を計算
const mirrored = Math.round((全長 - 入力値) * 2) / 2;
const isCenter = Math.abs(mirrored - 入力値) < 1e-6;
const need = isCenter ? 1 : 2; // 追加前に必要点数を確定
if (現在の点数 + need > 5) { /* 上限超過として拒否 */ }
if (isCenter) {
points.push({ pos: 入力値, pairId: null });
} else {
const pairId = uid++;
points.push({ pos: 入力値, pairId });
points.push({ pos: mirrored, pairId }); // 反対側も同じpairIdで紐付け
}
この機能はMISUMIのカタログには存在しない。あくまで「入力の手間を減らすための翻訳レイヤー」であって、コード体系そのものを勝手に拡張しているわけではない——最終的に生成される型式コードは、あくまで公式の記法に沿った文字列だけだ。
このツールには弱点がある。生成した型式コードが実際の仕様と完全に一致する保証は無い。カタログの表を読んで実装しただけで、MISUMI側の検証を通したわけではないし、フレームの断面形状や他の追加工との組み合わせによっては、そもそも指定できない組み合わせもある。
この「保証できなさ」を隠さずに、代わりに人間が目視で確認できる形を用意した。選んだ位置をフレームの見取り図(ブループリント風のSVG)に描き戻し、ミラーで追加した2点は曲線でつないで視覚的にペアだと分かるようにしている。文字列だけを見て正しさを判断させるより、図に描き戻したほうが入力ミスに気づきやすい。
権威を主張しない、という設計判断。ツールのフッターには「必ず最新の追加工価格表・カタログでご確認のうえご注文ください」という一文を明示した。これは単なる免責文言ではなく、このツールが担っているのは"組み立てを楽にする"ことまでで、"正しさの保証"はしていないという役割分担を、使う人に誤解させないための一文だ。
今回の題材はアルミフレームの型式コードだったが、同じ型は「仕様が非構造化ドキュメントの中にしか無い」対象全般に使える。
専門知識なしでAIに実務ツールを書かせる話の裏返しとして読むと分かりやすい。あちらは「専門ソフトにAPIがある」場面の話だったのに対し、今回はAPIも仕様書も無い領域だった。API が無いことは、ツール化できない理由にはならない——カタログの読み方さえ形式化できれば、翻訳レイヤーとして十分に成立する。