13週目に売上がゼロになった — 受信契約をコードにする
目標
毎週届くファイルからスキーマを取り出してフィンガープリントとして固め、カラムの追加・削除・名前の変更・型の変更を分類して、互換性ルールで自動判定するツールschema.pyを作ります。最後に、名前も型もそのままで意味だけが変わったカラムを、値の分布で検出します。
なぜ重要なのか
他人が渡すファイルのスキーマは、通知なしに変わります。送る側は自分のシステムのフィールドを整理しただけで、受け取る側はそれを契約として使っていただけです。私たちは変える側を管理できないので、できることは、変わったという事実をパイプラインが先に察知できるようにすることだけです。 察知したあとのほうが、さらに重要です。知らないカラムが1つ増えたせいでロードを止めれば、誰もその警告を信頼しなくなり、必須カラムが消えたのにそのまま通せば、数字が静かに間違います。そのため、何が壊れて何が壊れないかを、ルールとして明文化しておきます。基本は2行です。知らないカラムは通し、なくなった必須カラムは中断します。 そして、スキーマ検査が絶対に見られないものが1つあります。金額の単位がウォンから千ウォンに変わると、カラム名も型もそのままです。検査はすべて通過し、売上だけが1000分の1になります。こうした変化は値の分布でしか見えず、分布は証拠ではなく手がかりです。 採点ツールは、提出された文言を信じません。一時ディレクトリに、採点ツールが作った週次のファイルを用意し、作成したツールを実際に実行して、分類と判定を照合します。社名と金額は、実行ごとに変わります。
ステップ
- /root/drift/gen_weeks.pyを作成して実行し、weeksディレクトリの下に
w01.csvからw07.csvまでを作成してください(保存先: /root/drift/weeks)。注文30件はずっと同じで、スキーマだけが変わります。 - /root/drift/schema.pyに
fingerprintを作成して、カラム名・型・値サンプルとフィンガープリントを出力させ、w01.csvのフィンガープリントを保存してください(保存先: /root/drift/baseline.json)。 diff <기준 JSON> <파일>を追加して(プレースホルダーは基準JSONとファイルです)、カラムの追加と削除を分類させてください。diffが、値サンプルが大きく重なる組を探して、名前の変更として分類するようにしてください。名前の変更として検出されたカラムは、追加・削除の一覧から外れます。diffが、同じカラムの型の変更を分類するようにしてください。名前が変わると同時に型も変わった場合まで見ます。- /root/drift/contract.jsonに必須・任意のカラムとルールを宣言し、
gate <기준 JSON> <파일>がpass・warn・stopを判定するようにしてください(プレースホルダーは基準JSONとファイルです)。 watch <기준 CSV> <파일>を追加して(プレースホルダーは基準CSVとファイルです)、数値カラムの中央値の変化を測らせ、単位が変わった週を書いてください(保存先: /root/drift/meaning.json)。- 7週間分を一度に判定して、/root/drift/drift_report.jsonと、/root/drift/drift_report.mdを作成してください。
参考
- 実行契約:
python3 /root/drift/schema.py fingerprint <파일>・diff <기준 JSON> <파일>・gate <기준 JSON> <파일>・watch <기준 CSV> <파일>(プレースホルダーは、ファイルと、基準のJSONまたはCSVです)。成功すれば終了コード0、ファイルがなければ3、コマンドや引数の数が間違っていれば2です。 fingerprintの応答:{"rows": 정수, "columns": [{"name": 이름, "type": 타입, "sample": [값...]}], "digest": 12자 16진수}(プレースホルダーは順に、整数、名前、型、値、16進数12桁です)。サンプルは、そのカラムの異なる値を並べ替えて、先頭から20個までです。- フィンガープリント(digest)は、
[[이름, 타입], ...](プレースホルダーは名前と型です)を空白なしのJSONにしてsha256を求め、先頭12文字を使います。サンプルはフィンガープリントに入れません。データが変わってもスキーマが同じなら、フィンガープリントは同じでなければならないからです。 - 型の基準は、このラボの前提です。空の値を除いた残りがすべて整数の形なら
int、小数まで含むならfloat、すべてYYYY-MM-DDならdate、それ以外はstr、値が1つもなければemptyです。 diffの応答:{"added": [이름...], "removed": [이름...], "renamed": [[옛이름, 새이름]...], "retyped": [[이름, 옛타입, 새타입]...]}(プレースホルダーは順に、名前、旧名と新名、名前と旧型と新型です)。- 名前の変更は、消えたカラムと増えたカラムの値サンプルのジャッカード類似度が0.8以上のときとみなします。このしきい値もこのラボの前提です。
gateの応答:{"verdict": "pass"|"warn"|"stop", "reasons": [문자열...]}(プレースホルダーは文字列です)。reasonsは並べ替えて出力します。必須・任意のカラムの一覧は、/root/drift/contract.jsonから読みます。- 判定ルール: 知らないカラムは通過、なくなった任意カラムはwarn、なくなった必須カラムはstop、名前の変更はwarn、
intからfloatに広がったものはwarn、それ以外の型の変更は、必須ならstop、任意ならwarnです。 watchの応答:{"columns": {이름: {"median_before": 수, "median_after": 수, "ratio": 수, "flag": true|false}}}(プレースホルダーは名前と数値です)。中央値は小数第3位、比率は第4位で四捨五入します。flagは、比率が3以上、または3分の1以下のときtrueです。- 公式ドキュメント: python csv・python hashlib・python statistics・python json
- よくあるミス: フィンガープリントに値サンプルまで入れて、データが変わるたびにフィンガープリントが変わること、名前の変更を名前だけを見て判断すること、知らないカラムで止まること、分布の変化を証拠として扱うこと。
7週間分の受信ファイルを作る
/root/drift/gen_weeks.pyを作成して実行し、weeksディレクトリの下にw01.csvからw07.csvまでを作成してください(保存先: /root/drift/weeks)。注文30件はずっと同じで、カラムだけが週ごとに変わります。
基準の週の行を辞書のリストとして作っておき、週ごとにカラムのリストと値だけを手直しして、書き直せば足ります。値がずっと同じでなければ、あとで値サンプルから名前の変更を見つけられません。単位が変わる週だけ、値を直します。
スキーマをフィンガープリントとして固める
/root/drift/schema.pyにfingerprint <파일>を作成し(プレースホルダーはファイルです)、rows・columns・digestを出力させ、w01.csvのフィンガープリントを保存してください(保存先: /root/drift/baseline.json)。
カラムごとに値を集めて型を推論し、異なる値を並べ替えて、先頭20個をサンプルとして入れます。フィンガープリントは、名前と型だけをつなげてハッシュします。サンプルまで入れると、データが変わるたびにフィンガープリントが変わって、役に立たなくなります。
増えたカラムとなくなったカラムを分ける
diff <기준 JSON> <파일>を追加して(プレースホルダーは基準JSONとファイルです)、added・removed・renamed・retypedの4つの一覧を出力させてください。このステップでは、追加と削除だけを埋めてもかまいません。
基準のフィンガープリントと新しいファイルのフィンガープリントから名前の集合を取り出して、差集合を求めれば、追加と削除が出ます。一覧は並べ替えて出力してください。順序が実行ごとに変わると、比較できません。
名前だけが変わったカラムを突き止める
diffが、消えたカラムと増えたカラムの値サンプルのジャッカード類似度が0.8以上なら、名前の変更として分類するようにしてください。名前の変更として検出されたカラムは、addedとremovedから外れます。
名前だけを見れば、消えたもの1つと増えたもの1つです。値を見れば、同じカラムです。消えたカラムごとに、増えたカラムとのサンプル集合の積集合の大きさを和集合の大きさで割り、最も高い組を選んで、しきい値を超えたときだけ名前の変更とみなします。
型が変わったカラムを見分ける
diffが、同じ名前のカラムの型の変更を、retypedに[이름, 옛타입, 새타입]として入れるようにしてください(プレースホルダーは順に、名前、旧型、新型です)。名前が変わると同時に型も変わった場合は、新しい名前で入れます。
型は推論した値なので、資料が少し変わるだけで変わることがあります。そのため、整数が小数の表記に変わったものと、数字が文字列になったものを区別できるように、旧型と新型を一緒に入れておいてください。次のステップで、この2つを別の等級として扱います。
互換性ルールをコードで明文化する
/root/drift/contract.jsonにrequired・optional・rulesを宣言し、gate <기준 JSON> <파일>が{"verdict": …, "reasons": [...]}を出力するようにしてください(プレースホルダーは基準JSONとファイルです)。知らないカラムは通過、なくなった必須カラムはstopです。
必須カラムの一覧は、業務が決めるものです。order_id・shop_id・qty・amount_krw・ordered_atがなければ集計を作れず、shop_nameとweightはなくても集計は出ます。reasonsは並べ替えて出力し、理由ごとに、どのカラムが原因かの名前を付けてください。
スキーマが見られない変化を検出する
watch <기준 CSV> <파일>を追加し(プレースホルダーは基準CSVとファイルです)、2つのファイルの両方にある数値カラムの中央値と比率を測らせてください。そして、単位が変わった週を、column・median_before・median_after・ratio・flag・schema_verdictとして書いてください(保存先: /root/drift/meaning.json)。
この変化は、カラム名も型もそのままなので、前のステップの判定がすべて通過します。値の分布だけが落ち込みます。比率が3以上、または3分の1以下なら、人が見る必要があるという印を付けてください。証拠ではなく手がかりです。schema_verdictには、同じ週に対するgateの判定を一緒に書き、スキーマ検査がこれを見られないという事実を残してください。
7週間分を1枚で報告する
7週間分を基準の週と照合して、baseline・weeks・pass・warn・stopを書き(保存先: /root/drift/drift_report.json)、/root/drift/drift_report.mdに、## 무엇이 바뀌었나、## 무엇이 깨지고 무엇이 안 깨지나、## 자동으로 잡히지 않는 것、## 보내는 쪽과 맞출 것の4つの節で書いてください(韓国語の見出しは順に「何が変わったか」「何が壊れ、何が壊れないか」「自動では検出できないもの」「送る側と合わせること」という意味です)。
weeksは、ファイル名をキーにして、verdict・added・removed・renamed・retypedを入れたオブジェクトです。基準の週自身も入れると、判定がpassになって、照合しやすくなります。レポートには、stopになった週とその理由を、数字とともに書いてください。