TL;DR
- オペレーションズ/システムエンジニアが、AIコラボレーションを通じて最近ソフトウェア側へ移りました。
- Claude Codeを使って、数回の夜間セッションでドメイン固有のエンティティ解決ツールを構築しました。
- 過去8週間分の履歴データに対して再実行したところ、人手で検出された照合エラーの約99.2%を捕捉できました。
- 「熟練のベテランだけがやる」週次タスクを、チームの誰でも実行できる形にしました。
- 設計は、デュアルプロセス理論、ゲシュタルト心理学、アンカリングバイアスへの防御に予想以上にうまく適合しました。
- ソースとなる業務記録は一度もLLMに到達しませんでした。決定論的パイプライン+人手によるレビューのみです。
1. 隠れた問題:500×500が認知の壁になるとき
多くの企業は、複数のシステムにまたがって同じ業務エンティティを維持しています。
- 小売業者は、社内のマスターでもAmazon/Rakuten/ShopifyのエクスポートでもSKUを追跡している。
- クリニックは、EMRと保険請求システムの両方で患者記録を保持している。
- メーカーは社内の在庫を持つだけでなく、パートナーの在庫フィードも受け取る。
- 会計チームは、総勘定元帳の仕訳を銀行明細と照合する。
これらの組み合わせには、定期的な照合が必要です。技術文献ではこれをエンティティ解決(Entity Resolution)、またはデータ照合(Data Reconciliation)と呼びます。これは、ほぼすべての中〜大規模なビジネスが、最終的には必ず直面する普遍的な問題です。
このケーススタディでは小売SKUとマーケットプレイスの掲載情報という枠組みを使います。(私が実際に扱っている業界は、意図的に抽象化していますが、構造はそのまま通用します。)2つのシステムでそれぞれ約500行、週次の照合。熟練した人間が必要としたのは週あたり約3時間。新しく参加した人は半日〜1日。隠れた詳細:小さな行数が、実際の難しさを覆い隠しているのです。
なぜ500×500は難しいのか?
250,000問題
500×500の組(ペア)を手作業で照合するには、頭の中で最大250,000通りの組み合わせを評価させられます。1,000ではありません、250,000です。さらに、タイプミス許容、表記ゆれ(全角と半角、混在する書記体系、略語、句読点)、部分一致もあります。各ペアごとの判断はO(1)ではありません。
これを力任せ(ブルートフォース)でやることは、計算的には「1,000ノードのフルメッシュでpingチェック」ではなく「1,000ノードのフラットな生存確認チェック」を回すのと同程度に似ています。負荷の桁が違います。
ワーキングメモリのオーバーフロー
Millerの「マジックナンバー」によれば、短期記憶は7±2チャンクです(Miller, 1956)。フォーマットが揺れる1,000件の候補から一致を探し続けると、ワーキングメモリが連続的にあふれ、セッション全体を通してシステム2(遅い思考)が張り付きます。ベテランが経験する3時間の消耗は、不満というより神経学的に避けられない必然です。
「短い作業だからといって「簡単」とは限らない」のが、認知労働です。
再現性の劣化
単発の照合であれば、ブルートフォースでやってしまえます。しかし、10週以上にわたって週次で繰り返されると、判断のブレは避けられません:
- 「先週は『A Co.』と『A. Company』を同一のエンティティとして扱った。この週は別のものとして扱った。」
- 「先週はタイプミスXを許容したが、この週はそれを却下した。」
このドリフトこそが、長期的にデータ品質を壊してしまう本質です。これは、インフラ運用における「設定レビューの基準がレビュアーごとに異なる」というのと同じ構造的な失敗モードです。
実際のターゲット
そこで、このツールが実際に解決したのは「週3時間を短縮すること」ではなく、次の内容でした:
250,000回の判断 × 10週間の一貫した再現性――人間には身体的に維持できない品質基準――それを決定論的な機械で支える。
さらに、スキル依存を取り除きました。「これを3時間でできるのはただ一人のベテランだけ」というのは単一障害点です。ツール導入後は、誰でも一貫した品質で実行できるようになります。
2. 背景:私が誰で、何を解いていたのか
私はオペレーションズ/システムエンジニアです。設定、妥当性確認、ランブック作成、監視、トラブルシューティング――その側です。ソフトウェア開発は主な技能ではありませんでしたが、スクリプト作成は常に仕事の一部でした。
最近、新しい業務ドメインへ移りました(約2か月前)。ツールの対象システムに触っていたのはせいぜい約1か月でした。ユーザー側からはワークフローの長さは見えていましたが、開発者の視点ではありません。
翻訳すると、設計/バリデーション/ランブックの規律は固まっている一方で、Pythonとアプリケーション開発はほぼ未知でした。
この記事は「自分が出荷したものを見て」系の内容ではありません。未慣れのドメインでの、AI支援ソフトウェア作業に対して、オペレーション側の規律がそのまま移植される様子の記録です。
この記事は誰向けか
| 読者 | 役に立つセクション |
|---|---|
| オペレーション/SREエンジニアでAI支援を探っている人 | すべて |
| キャリア中盤で技術ドメインをまたいで移るエンジニア | 背景、アーキテクチャ、認知設計 |
| AI支援による開発に不慣れなエンジニア | アーキテクチャ、認知設計、PII |
| チームに対するAI活用を考えているマネージャー | 結果と、認知負荷の議論 |
3. PII/コンプライアンス上の考慮事項
エンティティ解決の記事へのコメントで必ず出てくる疑問があります:データはどこへ行くのか? まずはそこに答える価値があります。
この実装では:
- ソースの業務記録は、どのLLMにも到達しません。 入力ファイル(社内マスター+外部システムのエクスポート)の両方は、Pythonスクリプトによってローカルで読み取られます。
-
マッチングは完全に決定論的です。 類似度にはPandas、openpyxl、
difflib.SequenceMatcherを使用します。埋め込み(embedding)APIはありません。実行時にリモート推論も行いません。 - LLMの役割はコード側であり、データ側ではありません。 Claude Codeは、マッチングロジック、バリデーション用スクリプト、設計レビュー、ドキュメント作成を手助けしました。実際の記録が送られたことは一度もありません。
- テスト目的のみ、プロンプトにはマスクした合成データを使用しました。実在の氏名、金額、住所は、プロンプトがローカル環境から外に出る前に、合成の同等データに置き換えています。
- イレギュラー(エッジケース)は人間のところに残します。 決定論的パイプラインが判断できない場合、フラグ付きの行を人間のレビュー用に提示します――LLMに「第二の意見」を求めるためではありません。
この分離は意図的です。マッチング作業は決定論的ロジックに非常に適しています。LLMは、品質向上につながらないのに、コスト、レイテンシー、コンプライアンス上の露出だけを増やすことになります。
チームに「業務データを外部のAIに入れない」という、たとえ緩いポリシーがあるなら、このパターンは完全に両立できます。
4. アーキテクチャ:2段階マッチング+認知ゲート
スタック
- Python 3.11
- pandas + openpyxl(Excel I/O、色分け出力)
-
difflib.SequenceMatcherでファジーな類似度を計算 - ルールベースで一貫。機械学習は使いません。
- 約1,100行、単一スクリプト。
フェーズ
フェーズ1:利害関係者名を完全一致でマッチ(または別名グループ)
フェーズ2:名前の類似度が0.6以上でクロスマッチ(救済:タイプミス)
フェーズ3:姓のみ+構造の一致(タイプミスは1箇所まで許容)
フェーズ4:重複登録の検出(同一の利害関係者+類似度が0.8以上)
フェーズ5:利害関係者名がない行を救済(属性一致)
フェーズ5.5:属性不一致のペア救済(識別子の類似度が0.7以上、フェーズ2)
フェーズ6:行の生成+色の判断
スコア関数(キーとなるゲート)
返却形式: {"translated": "翻訳されたHTML"}def compute_score(row_a, row_b):
# ハードゲート:領域(リージョン)は一致していなければならない — リージョンをまたぐ偽陽性を潰す
if region_a != region_b:
return 0.0
# ハードゲート:数値属性は十分に近くなければならない
if abs(value_a - value_b) > THRESHOLD:
return 0.0
# 識別子ゲート:row_b の識別子が、row_a の識別子に埋め込める必要がある
if not is_identifier_match(addr_a, identifier_b):
return 0.0
# サブ識別子ゲート:アンカリングバイアスへの防御
if sub_id not in addr_a:
return 0.0
# ソフトスコア(すべてのハードゲートを通過した後だけ)
score = max(identifier_match_score, similarity, value_fallback)
return score if score >= 0.6 else 0.0
この形はなぜ?
小売の SKU(商品コード)として考えると、ここでの意図が分かりやすくなります。マーケットプレイス上の同じ商品は、あなたのマスタでは iPhone15 として現れ、マーケットプレイスでは iPhone 15 Pro Max として現れるかもしれません。同じ商品ファミリーですが、表面上の表現(見た目の形式)が違う。重要な洞察が2つあります:
- まずハードゲート。 「異なるリージョン」や「値の差が > N」は、絶対的な失格条件です。高価な類似度計算の前に実行します。
- 最後にソフトスコア。 ハードゲートをすべて通過したら類似度を計算しますが、0.6 未満には上限を設けて「不確実、つまり人間が表面上を見て判断すべき」とします。
なぜ ML / ベクトル DB / 埋め込み(embeddings)ではないの?
意図的に、決定論的なルールベースを選びました。監査可能性(auditability)が要件だったためです。フラグが立った行が誤っている場合、運用チームは「どのゲートが」「なぜ」発火したのかを、正確に追跡できなければなりません。説明のない 0.81 というブラックボックスの類似度スコアは、レビューできず、ユニットテストもできず、コンプライアンス監査で弁護もできません。
ラベル付きの学習データ、学習基盤、そして継続的な評価パイプラインが揃っているなら、ML は良い選択肢です。ですが、ここではそれらがすべて当てはまりませんでした。運用上の制約はこうでした:「チームの誰もがコードを読んで、なぜその判断を下したのかを理解できるべき」。この制約が、決定論的なロジックを強制します。
抽象化された構造
| ドメイン固有の用語 | 抽象概念 |
|---|---|
| アイテム / SKU | エンティティ |
| 利害関係者(ベンダ / エージェント) | 利害関係者の属性 |
| 価格 / 金額 | 主要な数値属性 |
| 住所 / ロケーション | 識別子(複数属性) |
| 建物 / SKU 名 | 補助的な識別子 |
| 詳細番号 / バーコード | サブ識別子 |
| フォーマットのばらつき(カナ/ラテン/大小文字) | データ品質の問題 |
| ドメイン判断 | 暗黙知 |
これは、「フォーマットのドリフトがある2つのシステム間で、同一のエンティティをマッチさせる」という普遍的な問題です。このパターンは、EC、ヘルスケア、HR(人事)、会計、製造、出版など、どこでも再登場します。2つのシステムが同じ業務上の対象を別々の形で表現しているなら、どこでも同じです。
5. 認知科学の設計原則(ひねり)
私は認知科学を意識してこの仕組みを設計したわけではありません。作ってみたらうまく動いて、その後に構造化された Gemini の会話の中で、根底にある原則が姿を現しました。後付けですが、なかなか不気味なほど整合的です。
5.1 二重過程理論(ダニエル・カーネマン)
この2つのフェーズは、2つの思考モードに対応します:
- システム 1(速い)= フェーズ 1〜5。 「だいたい同じものか?」という曖昧な判断 — 類似度スコア、識別子のマッチング、属性の近さ。
-
システム 2(遅い)=
determine_color()。 値の不一致、フォーマットの不整合、識別子の混在に対する厳密なチェック。
色分けされた人間のレビューは、システム1の曖昧な通過と、システム2の厳格さの注釈の両方を提供します。これは、人間が最終判断を下すために必要な入力形そのものです。
5.2 ゲシュタルト心理学
人間は「文字の並び」ではなく「まとまり(全体)」を認識します。文字列の厳密な同一性は失敗していても、iPhone15 と iPhone 15 Pro Max は同じ製品ファミリーのように感じられます。そこで:
def is_identifier_match(addr_a, identifier_b):
"""混在した表記体系や区切り文字があっても、チャンク化された同一性を認識します。"""
chunks = re.split(r'[A-Za-z0-9\s\-_]+', identifier_b)
return all(chunk in addr_a for chunk in chunks if len(chunk) >= 2)
チャンクによるマッチングなら、空白、区切り文字、表記体系(スクリプト)の違いがあっても生き残ります。
5.3 アンカリング & 確証バイアスへの防御
人間が直感的な近道に走ってしまうのを拒否するために、ハードゲートが存在します:
- 「同じ価格なら、同じ商品に違いない」— サブ識別子ゲートで却下。
- 「同じ名前なら、同じ人物に違いない」— リージョンゲートで却下。
機械の仕事は、人間が自信過剰になりがちなところで、冷たく懐疑的であることです。
5.4 人間の認知負荷を減らす(ヒューマン・イン・ザ・ループ)
人間にフラグが立った行の確認を求めるとき、「マッチスコア 0.62」のような不透明な情報は渡しません。代わりに、1行の注釈を渡します:
同一エンティティが一致 | [値の不一致] 差 ¥2,000,000 (5.4%)
(A: ¥34,900,000 / B: ¥36,900,000) · identifier(識別子)形式が一貫していない
人間は、その行がフラグ付けされた理由を改めて導き直して無駄にサイクルを消費しません。認知負荷は急激に下がります。
5.5 幽霊を自動化しない
この部分はGhost in the Shellから借用しています。判断の中には、ルールに落とし込めない暗黙のビジネス知識に依存するものがあります。それらを符号化しているふりをするヒューリスティックを作らないでください。行を注意信号(caution signal)として表に出し、人間に暗黙の層を適用してもらいます。
論理を締め付けることは、幽霊を再現する道ではありません。
幽霊が必要とされる場所を明らかにする道です。
マッピングのまとめ
| 認知の概念 | 実装 |
|---|---|
| システム1(高速) | フェーズ1〜5(ファジーマッチ) |
| システム2(遅い) |
determine_color() の厳密なチェック |
| 二段階/デュアルパス | ステージ1 + ステージ2(フェーズ5.5) |
| ゲシュタルトのグルーピング |
similarity / is_identifier_match
|
| アンカーリング防御 | サブ識別子ゲート、識別子ゲート |
| 認知負荷の削減 | 集約した [reason] diff X の注釈 |
| ヒューマン・イン・ザ・ループ | 暗黙の知識ゾーンのための注意信号 |
6. 結果
過去データ8週間におけるリコール
| 指標 | 値 |
|---|---|
| 人間がフラグ付けした誤り(外れ値の週を除く) | ~130 |
| ツールが検知した誤り | ~129 |
| リコール | ~99.2% |
見逃した単一のケースは、人間のレビュアーによって「ここは人間でも決められない」と注釈されていました。実質的に、このツールは、人間が確信を持った判断を下すあらゆるケースをすべて検知します。
(注意:これは8週間分の、あるチームのデータに対するリコールであって、ベンチマークの主張ではありません。異なる領域では、それぞれ独自の計測が必要になります。)
時間とスキル負荷
| 項目 | Before | After |
|---|---|---|
| 熟練のベテランの処理能力 | ~3 hrs/week | ~30 min/week(レビューのみ) |
| 新人の処理能力 | 半日〜1日 | ~30 min/week |
| スキル依存 | あり(単一障害点) | なし(誰でも実行できる) |
時間の数字は価値を過小評価しています。実際の変化はスキルSPOFを壊すことです。ベテランが体調不良で休む、離脱する、あるいは別の優先事項に埋もれても、仕事は同じ品質のまま続きます。
フォールスポジティブに関する注記
リコールは99.2%ですが、このツールは、精度(precision)が高いよりもリコールを高くするよう意図的に調整されています。フォールスポジティブ――つまり、人間のレビューのためにフラグが立ったものの、実際には問題のないペア――は、そのトレードオフとして受け入れます。人間のレビューでかかる~30分/週が、それらを無理なく処理します。
ヒューマン・イン・ザ・ループなしのデプロイでは、このトレードオフはまったく別物になります。ここではフォールスポジティブは安い(人間レビュアーが一瞥するだけ)一方、フォールスネガティブ(見逃した照合エラー)は高くつきます(データドリフトがビジネスレポートへ伝播するためです)。
7. フローチャート
判断の流れを図として描くことで、コードレビューでは見えてこなかったものが浮かび上がりました。以下は、実行順に4つのフェーズをそれぞれ別の図として示します。
7.1 フェーズ1:ハードゲート(順次の失格条件)
領域 → 数値の値 → 補助識別子 → サブ識別子。各ゲートは絶対的な失格条件です。どんな「No」でもペアを落とします。順序が重要です――最も安い失格条件が最初に実行されます。
7.2 フェーズ2:ソフトマッチ
すべてのハードゲートを通過したペアに対して、compute_score がソフトな類似度を評価します。0.6未満 → ドロップ。0.6以上 → 同一エンティティとしてペアを固定します。
7.3 フェーズ3:並列のフラグチェック
確認された一致(confirmed matches)に対して、6つの独立したチェックが並列に発火します。それぞれ「一致しているが、ここに食い違いがある」というシグナルを提示します。タグは集約され、チェック間で早期リターンによる汚染はありません。
7.4 フェーズ4:最終判断とドロップ集約
タグを色(color)の判断(verdict)に集約します。フェーズ1およびフェーズ2からのドロップは「Unmatched」レーンに収束し、人間レビュー出力では単体として表示されます。
図として描いた後にしか見えないこと
これらはコードを読んでいる間は見えず、描いて初めて明らかになったものです:
- フェーズ1のハードゲートは、計算コスト順に並んでいる。 領域 → 数値 → 補助 → サブ識別子。直感で配置しましたが、図を見ると、すでに最適になっていて――最も安い失格条件が先に来るようになっていました。
- フェーズ3の並列フラグチェックは、本当に独立している。 6つのチェックが並列に発火し、早期リターンによる汚染はありません。図によって、チェック間に見えない依存関係がないことが確認できました。
-
Drop1〜Drop5のすべての経路が、同じUnmatchedノードに収束する。 私はドロップ理由を捨てていました。「なぜこのペアは却下されたのか?」の再実行は不可能でした。修正:行の注釈にドロップ理由をログ出力する。
フローチャートを描くことは、実運用前にインフラのトポロジを描く行為とだいたい同じです。図はラバーダックです。
8. まとめ
この構築から得られる、転用可能な学びは3つです:
- 認知的負荷は「短い」反復的な判断タスクの隠れたコストです。人員・稼働時間の計算は、燃え尽きの現実やスキルの単一障害点(SPOF)リスクを過小評価してしまいます。
- 認知科学の原則は、後から振り返ると「良い設計」の結果として現れます。私は最初からそれらを意識して設計したわけではありませんでした。原則が見えてきたのは、(もう一つのAIを用いた)構造化されたレビューを通してだけです。既知の原則に対して設計がうまく適合するなら、それは裏付けになります。適合しないなら、それは不吉な兆候(匂い)です。
- LLMはあなたのデータに「触れる必要がありません」。ほとんどのエンティティ解決の作業では、それらはそもそも不要です。コード作成、設計レビュー、ドキュメント化に活用してください。業務記録はローカルに保ち、決定論的に扱います。
実装自体は社内利用のみを目的としており、オープンソース化されることはありません。このパターンは、EC、ヘルスケア、HR、会計、製造、出版など、どの「二つのシステム間の」エンティティ整合にもきれいに一般化できます。
9. What's Next
パート2で登場:そもそもこの一連の仕組みがどのように作られたのか――AIコラボレーションのパターン、私が遭遇したアンチパターン、そして運用からソフトウェア開発へと移ってきた領域横断の知見です。(公開されたらA2へのリンクを貼ります。)
エンティティ解決、反復タスクにおける認知的負荷、または領域横断のエンジニアリング経験についてのコメントも歓迎します。








