shadowjoin について
shadowjoin は、データパイプラインや分析で完全一致結合が取りこぼす件数を測定するツールです。正規化ルールを検証し、回復可能な一致数の下限を示します。正解データがあれば結果も照合できます。
主な機能
- 01結合の取りこぼし下限を測定
- 02正規化ルールを検証
- 03正解データで一致を照合
- 04CSV、JSON、データベースに対応
- 05CI で損失しきい値を検査
活用シーン
- 01パイプライン結合の漏れを調査
- 02採用候補の正規化ルールを比較
- 03CI に損失しきい値を設定
MCP 対応
MCP サーバーは結合スキャン、未一致の説明、正規化の提案、実行結果の比較を提供します。設定済みのデータソースのみを扱い、任意の SQL 実行ツールやツール呼び出しでの接続文字列受け取りはありません。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
shadowjoin
完全一致の結合でひそかに取りこぼしている一致を測定します。
パイプラインでは、名前、メールアドレス、SKU、会社名を使って2つのデータソースを結合しています。一致するものは一致します。それ以外は、ただ……存在しません。エラーも例外もアラートもありません。クエリは返り、ダッシュボードは表示され、本来一致するはずの行の4分の1が、気づかれないまま一致しません。
shadowjoinは、実際の結合キーと並行してシャドーマッチャーを実行し、失われている一致の数、それぞれを復元できる正規化ルール、そして正解データが少しでもあれば、復元結果が正しいことを証明します。
npx shadowjoin scan \
--left customers.csv --left-key company_name \
--right crm_export.csv --right-key account_name
音声なしで14秒。動画内の数値はすべて、下記のコミット済みサンプルからのCLI出力をそのまま使っています — 高解像度版。
上の進行状況の出力は実際のものです。shadowjoinは処理中に何をしているかを表示します。stdoutがターミナルでない場合はすべて自動的に抑制されるため、ファイルへのパイプ、jqへのパイプ、CIでの実行でも、以前とバイト単位で同一の出力が得られます。また、postinstallスクリプトはありません。本番データベースを対象にするツールが、インストール時にコードを実行するべきではないからです。
公開データで見つかったもの
S&P 500の構成銘柄リストとNYSE + Nasdaqの上場銘柄リストを会社名で結合します。どちらのファイルにもティッカーシンボルが含まれているため、復元された一致は推測ではなく検証できます。
left rows 503 right rows 6,709
exact join matches 1 0.2%
recovered by shadow join 428 85.09%
still unexplained 74
LOWER BOUND ON LOST MATCHES: 85.09% of left rows
verified against ground truth: 419 correct, 1 wrong, 8 ambiguous (precision 99.76%)
rules worth adopting, in order (greedy by marginal recovery):
1. strip security/listing boilerplate (common stock, ADS, ...) +68 68
2. strip corporate suffixes (inc, corp, ltd, plc, gmbh ...) +319 387
3. strip punctuation +33 420
4. sort tokens (order-insensitive match) +4 424
5. remove all whitespace +1 425
完全一致で見つかったのは1件です。実際には428件あり、そのうち419件はティッカーで正しいことが確認されています。
自分で再現できます — データはリポジトリに同梱されています。
git clone https://github.com/harsh-chandak/shadowjoin && cd shadowjoin
npm install && npm run build
node dist/cli.js scan \
--left examples/sp500.csv --left-key Security --left-id Symbol --left-truth Symbol \
--right examples/listings.json --right-key name --right-id symbol --right-truth symbol
実際に使える結合
上の例は、短いインデックス名と正式な証券名の照合という、意図的に印象の悪い例です。こちらは現実的なケースです。
Unicode CLDRの国名(UIに表示する名前)と国連の正式な国名(参照データで使われる名前)を国名で結合し、ISO 3166 alpha-3を正解データとして使います。これは毎週のように書かれる結合です。
exact join matches 205 / 249 82.33%
recovered by shadow join 5 2.01% precision 100%
still unexplained 39
rules worth adopting:
1. & -> and +4
2. remove all whitespace +1
問題なさそうに見えます。82%が一致し、エラーもなく、調査する人もいません。しかし、Antigua & Barbuda、Trinidad & Tobago、São Tomé & Príncipe、Turks & Caicos Islands、Vietnamがひそかに取りこぼされています。最後のものは、国連がViet Namと表記しているためです。
この例で、最初の例にはなかったことが2つわかります。
すべてのルールを適用しても何も見つかりません。 すべてのルールを適用してもここでは何も復元されませんが、2つのルールに絞ると5件復元されます。過剰な正規化は、本来区別すべきキーを同一化します。shadowjoinは、ルールが多いほどよいと思い込ませるのではなく、より適切なサブセットを示します。
機械的な不一致と意味的な不一致を区別します。 説明できない39件は、正規化の問題ではありません。
Vatican City vs Holy See
North Korea vs Democratic People's Republic of Korea
Cape Verde vs Cabo Verde
Bosnia vs Bosnia and Herzegovina
Hong Kong vs China, Hong Kong Special Administrative Region
文字列変換では、これらを復元できません。エイリアステーブルか、本格的なエンティティ解決ライブラリが必要です。どの不一致が機械的なもので、どれが意味的なものかを知ることが、1行の修正で済むか、プロジェクトが必要になるかの分かれ目です。
node dist/cli.js scan \
--left examples/countries_ui.csv --left-key country --left-truth iso3 \
--right examples/countries_reference.csv --right-key country --right-truth iso3
この結合は、短いインデックス名と正式な証券名を照合するという、意図的に印象の悪い例です。これは測定方法を示す実例であり、実際にこの結合を使っている人がいるという主張ではありません。ご自身のデータに適用すれば、ご自身の状況を示す数値が得られます。
数値は推定値ではなく、下限です
ラベルなしでは再現率を計算できません。エンティティ解決の文献ではこの指標をペア完全性と呼び、本番環境では利用できないオラクルがあることを前提としています。
そのため、shadowjoin が報告するのは下限です。シャドーマッチャーが見つけた、完全一致では見つからなかったマッチの数です。シャドーマッチャー自体の再現率は不明なので、実際の損失は少なくともこの大きさであり、さらに大きい可能性もあります。すべての数値には、人が手作業で確認できるサンプルペアが付属します。また、両側で正解データを利用できる場合は、レポートに適合率も記載されます。
これは意図的に控えめな主張です。そして、問いただされても揺るがない主張でもあります。
LLM にチェックを頼むだけではだめですか?
小さなテーブルを一度だけ確認するなら、それでかまいません。そのほうが速く、このツールは不要です。しかし、次のような場合にはうまくいきません。
- 同じ数値をもう一度得る必要がある場合。 エージェントは実行ごとに異なる SQL を書くため、パイプラインが変わったのか、クエリが変わったのか判断できません。
- 誰も思いつかなかったルールが必要な場合。 「この結合は損失を生んでいるか」と聞くと、エージェントは
lower()とtrim()を試します。しかし、NFKD による折りたたみ、発音区別符号、先頭のゼロ、企業形態の接尾辞、トークンの並べ替え、掲載文の定型文などを体系的に試すことはありません。網羅的なルールセットこそが、このプロダクトです。 - CI で実行する必要がある場合。
--max-lossはゼロ以外の終了コードを返します。「Claude に聞く」では終了コードを得られません。 - 本番環境での探索的な SQL の実行にコストがかかる場合。 事前計算したキーを使ったハッシュ結合を、ステートメントタイムアウトの下、読み取り専用トランザクション内で実行します。
インストール
npm install shadowjoin # or run it with npx, no install
実行時の依存関係はありません。pg と @modelcontextprotocol/sdk はオプションのピア依存関係であり、Postgres アダプターまたは MCP サーバーを使う場合にのみ読み込まれます。
使い方
CLI
# CSV or JSON, no database involved
npx shadowjoin scan --left a.csv --left-key name --right b.json --right-key title
# with ground truth on both sides, so recoveries are verified
npx shadowjoin scan \
--left a.csv --left-key name --left-truth customer_id \
--right b.csv --right-key title --right-truth customer_id
# straight at Postgres
npx shadowjoin scan \
--left 'postgres://ro@host/db' --left-table orders --left-key customer_name \
--right 'postgres://ro@host/db' --right-table customers --right-key name
# in CI: fail the build if the floor climbs above 2%
npx shadowjoin scan --left a.csv --left-key name --right b.csv --right-key name --max-loss 2
# MySQL and SQLite work the same way
npx shadowjoin scan \
--left 'mysql://ro@host/db' --left-table accounts --left-key company_name \
--right ./reference.sqlite --right-table vendors --right-key name
npx shadowjoin rules # list the normalization rules
データソース: CSV、JSON、Postgres、MySQL、SQLite に対応しています。SQLite は Node に組み込まれた node:sqlite(Node 22.5 以降)を使うため、インストールは不要です。Postgres と MySQL では pg または mysql2 が必要ですが、どちらもオプションのピア依存関係です。
それ以外のデータソースは、コアが接続ではなく 2 つの配列を受け取るため、ライブラリを使って 3 行で利用できます。
import { scan } from "shadowjoin";
const [rows] = await anyDriver.query("SELECT id, company_name FROM accounts");
const left = rows.map((r) => ({ id: String(r.id), key: r.company_name }));
scan(left, right);
ライブラリ
import { scan, normalize, rulesByIds, toSql } from "shadowjoin";
const report = scan(leftRows, rightRows); // rows are { id, key, truth? }
console.log(report.lowerBoundLossPct, report.stack);
// adopt the recommended rules in your own pipeline
const rules = rulesByIds(report.stack.map((s) => s.rule));
const key = (value) => normalize(value, rules);
toSql("company_name", rules); // the same thing as Postgres SQL
Claude Code プラグイン(最も簡単)
1 つのコマンドでスキルと MCP サーバーをまとめてインストールし、設定ファイルのパスを尋ねます。
claude plugin marketplace add harsh-chandak/shadowjoin
claude plugin install shadowjoin@harsh-chandak
公式 MCP レジストリにも io.github.harsh-chandak/shadowjoin として掲載されています。
MCP サーバー
エージェントが結合を測定し、実際の例を調べ、修正案を提示して、さらに修正が機能したことを証明できるようにします。測定値はエージェント自身が書き込むものではないため、偽装できない一連の流れです。
{
"mcpServers": {
"shadowjoin": {
"command": "npx",
"args": ["-y", "-p", "shadowjoin", "-p", "@modelcontextprotocol/sdk", "shadowjoin-mcp"],
"env": { "SHADOWJOIN_CONFIG": "/abs/path/to/shadowjoin.config.json" }
}
}
}
{
"sources": {
"prod": { "type": "postgres", "connectionString": "postgres://shadowjoin_ro@host/db" },
"crm": { "type": "csv", "path": "./crm_export.csv" }
}
}
ツール:scan_join、explain_misses、suggest_normalization、compare_runs。
この MCP サーバーに意図的に備わっていない機能が 2 つあります。
- 任意の SQL を実行するツールはありません。 エージェントが指定できるのは、設定済みのデータソース、テーブル、カラムです。それが利用できる機能のすべてです。
- 接続文字列を受け取るツールはありません。 認証情報は設定ファイル内に置かれ、ツール呼び出しに含まれることはありません。ツール呼び出しはトランスクリプトに残るためです。
実際のデータベースに接続する
shadowjoin は、statement_timeout、lock_timeout、idle_in_transaction_session_timeout を設定した READ ONLY トランザクションを開始します。また、接続に使用したロールが読み取り対象のテーブルに書き込み可能な場合は、実行を拒否します。
信頼に頼る必要のないロールを用意してください。
CREATE ROLE shadowjoin_ro LOGIN PASSWORD 'xxx';
GRANT CONNECT ON DATABASE yourdb TO shadowjoin_ro;
GRANT USAGE ON SCHEMA public TO shadowjoin_ro;
GRANT SELECT ON public.orders, public.customers TO shadowjoin_ro;
可能であれば、レプリカを指定してください。
指定したデータソース以外への外部ネットワーク接続は行いません。 テレメトリはなく、オプトアウトも不要です。インポートを確認してみてください。依存関係は 3 つあり、その中に HTTP クライアントはありません。
制限事項
数値を引用する前に、以下をお読みください。
- この数値は下限です。 シャドーマッチャー自体の再現率は不明です。実際の損失はこれ以上である可能性があります。
- 復元結果が誤っている場合があります。 同梱の例では、428 件中 1 件は正解データと矛盾し、8 件は曖昧でした。曖昧なケースの大半は株式クラスで、「Class A」を取り除くと、実際には異なる証券が同じものとして扱われます。曖昧なマッチには正規化ルールではなく、タイブレーカーが必要です。必ず
samplePairsを確認してください。 - ルールごとの件数は重複するため、合計しても総数にはなりません。 各件数は、ルールの実行順による影響を意図的に排除するため、厳密なベースラインに対して独立して測定されます。実際に採用するルールは、貪欲な順序で示される
stackを使って判断してください。 - ルールは多ければよいとは限りません。 すべてのルールを適用すると、分けておくべきキーが過剰な正規化によって統合され、最適なサブセットよりも少ないマッチしか復元できないことがあります。このような場合、shadowjoin は警告を出し、より適切なサブセットを示します。
- 結合キーを読み取るため、実行中はそれらの値がメモリ上に存在します。
--redactはサンプル出力内の値をハッシュ化し、サーバー側の正規化では行全体がデータベースに保持されますが、いずれもキーを見えなくするものではありません。規制対象のデータを扱う場合は、自社環境内のレプリカで実行してください。 - データの書き込み、修正、移行は行いません。 測定のみを行います。
- 生成される SQL の
unaccentには、同名の Postgres 拡張機能が必要です。
先行研究
ブロッキング再現率、つまり ペア完全性 は、ペア品質や削減率とともに、エンティティ解決の研究分野で確立された指標です。
これらの論文は、ラベル付きのゴールドスタンダードを前提としています。shadowjoin の貢献は指標そのものではなく、ラベルが一切ないライブパイプラインに対して、その指標の妥当性を説明できる形で算出し、点推定ではなく上限・下限として報告することです。
実際にマッチングを行い、見逃しているものを測定するのではなく解決したい場合は、 Splink、 Dedupe、または recordlinkage をお使いください。shadowjoin はこれらを補完するものであり、必要性を判断するのに役立ちます。
ライセンス
MIT © Harsh N. Chandak
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。
