[{"data":1,"prerenderedAt":757},["ShallowReactive",2],{"content-/excel-diff-cf-mapping":3,"all-pages-for-dir":755,"og-image-/excel-diff-cf-mapping":756},{"id":4,"title":5,"body":6,"category":733,"description":734,"extension":735,"meta":736,"navigation":737,"path":738,"project_name":739,"published":740,"publishedAt":741,"seo":742,"stem":743,"tags":744,"todo":752,"unpublished":740,"updatedAt":753,"__hash__":754},"pages/2026-04/2026-04-17/excel-diff-cf-mapping.md","Excel差分検出スキルとCFマッピングJSON自動生成 - Codex4ラウンドで計画を磨いた1日",{"type":7,"value":8,"toc":717},"minimark",[9,14,18,21,24,29,33,36,40,43,111,115,118,377,381,441,444,447,451,454,457,461,464,528,531,533,537,544,560,563,565,568,671,673,676,693,695,698,713],[10,11,13],"h1",{"id":12},"excel差分検出スキルとcfマッピングjson自動生成","Excel差分検出スキルとCFマッピングJSON自動生成",[15,16,17],"p",{},"朝、手修正したExcelファイル（_KKサフィックス付き）とスクリプトの出力を並べて見比べていた。セルの数が多すぎて、目視では差分を拾いきれない。「これ、スクリプトに任せたい」と手が止まったところから今日の作業が始まった。",[15,19,20],{},"もう一つの軸は、仕訳データからCF項目の対応表を自動で引き抜く仕組み。全38論点のExcelを横断して、借方・貸方のCF科目をQ番号ごとに整理する。",[15,22,23],{},"どちらも「計画を書いてからCodexに叩いてもらう」フローを4往復回して、実装に入る前に設計の穴を埋め切った1日だった。",[25,26,28],"h2",{"id":27},"_1-excel差分検出スキルの設計","1. Excel差分検出スキルの設計",[30,31,32],"h3",{"id":32},"背景",[15,34,35],{},"eurekapu-nuxt4では、Pythonスクリプトが各論点のExcelファイルを自動生成する。ユーザーが手修正した版（_KKサフィックス）と比較して「何が変わったか」を把握したい。だが手作業では、行の挿入・削除が絡むと前後のセルがずれて追跡できなくなる。",[30,37,39],{"id":38},"_6カテゴリの変更検出","6カテゴリの変更検出",[15,41,42],{},"Codexレビューの初回で「セル単位の差分比較では行列の挿入削除を見落とす」と指摘が飛んできた。設計を2次元グリッドベースに書き直し、以下の6カテゴリで変更を拾う方針に落ち着いた。",[44,45,46,59],"table",{},[47,48,49],"thead",{},[50,51,52,56],"tr",{},[53,54,55],"th",{},"カテゴリ",[53,57,58],{},"検出方法",[60,61,62,71,79,87,95,103],"tbody",{},[50,63,64,68],{},[65,66,67],"td",{},"行挿入",[65,69,70],{},"グリッド行数の増加 + ヘッダ文字列のマッチング",[50,72,73,76],{},[65,74,75],{},"行削除",[65,77,78],{},"グリッド行数の減少 + 欠落行の特定",[50,80,81,84],{},[65,82,83],{},"列挿入",[65,85,86],{},"列ヘッダの差分検出",[50,88,89,92],{},[65,90,91],{},"列削除",[65,93,94],{},"同上",[50,96,97,100],{},[65,98,99],{},"セル値変更",[65,101,102],{},"対応セル同士の値比較",[50,104,105,108],{},[65,106,107],{},"式変更",[65,109,110],{},"openpyxlで数式文字列を直接比較",[30,112,114],{"id":113},"_kkサフィックスがないケースへの対応","_KKサフィックスがないケースへの対応",[15,116,117],{},"全ファイルに_KKが付いているわけではない。同じディレクトリに同名ファイルが複数ある場合、タイムスタンプの新旧で「手修正版」と「スクリプト出力版」を判定するロジックを追加した。",[119,120,125],"pre",{"className":121,"code":122,"language":123,"meta":124,"style":124},"language-python shiki shiki-themes vitesse-light vitesse-light","def resolve_pair(directory: Path, base_name: str) -> tuple[Path, Path]:\n    kk = directory / f\"{base_name}_KK.xlsx\"\n    if kk.exists():\n        return kk, directory / f\"{base_name}.xlsx\"\n    # _KKなし: タイムスタンプで新旧判定\n    candidates = sorted(directory.glob(f\"{base_name}*.xlsx\"), key=lambda p: p.stat().st_mtime)\n    return candidates[-1], candidates[0]\n","python","",[126,127,128,189,224,243,270,277,346],"code",{"__ignoreMap":124},[129,130,133,137,141,145,149,152,155,158,161,163,167,170,173,176,179,182,184,186],"span",{"class":131,"line":132},"line",1,[129,134,136],{"class":135},"stQ0i","def",[129,138,140],{"class":139},"senZ8"," resolve_pair",[129,142,144],{"class":143},"shFtX","(",[129,146,148],{"class":147},"sG7-3","directory",[129,150,151],{"class":143},":",[129,153,154],{"class":147}," Path",[129,156,157],{"class":143},",",[129,159,160],{"class":147}," base_name",[129,162,151],{"class":143},[129,164,166],{"class":165},"sz8Xr"," str",[129,168,169],{"class":143},")",[129,171,172],{"class":143}," ->",[129,174,175],{"class":147}," tuple",[129,177,178],{"class":143},"[",[129,180,181],{"class":147},"Path",[129,183,157],{"class":143},[129,185,154],{"class":147},[129,187,188],{"class":143},"]:\n",[129,190,192,195,198,201,204,207,211,215,218,221],{"class":131,"line":191},2,[129,193,194],{"class":147},"    kk ",[129,196,197],{"class":143},"=",[129,199,200],{"class":147}," directory ",[129,202,203],{"class":135},"/",[129,205,206],{"class":135}," f",[129,208,210],{"class":209},"sdGka","\"",[129,212,214],{"class":213},"snbK4","{",[129,216,217],{"class":147},"base_name",[129,219,220],{"class":213},"}",[129,222,223],{"class":209},"_KK.xlsx\"\n",[129,225,227,231,234,237,240],{"class":131,"line":226},3,[129,228,230],{"class":229},"sHkkW","    if",[129,232,233],{"class":147}," kk",[129,235,236],{"class":143},".",[129,238,239],{"class":147},"exists",[129,241,242],{"class":143},"():\n",[129,244,246,249,251,253,255,257,259,261,263,265,267],{"class":131,"line":245},4,[129,247,248],{"class":229},"        return",[129,250,233],{"class":147},[129,252,157],{"class":143},[129,254,200],{"class":147},[129,256,203],{"class":135},[129,258,206],{"class":135},[129,260,210],{"class":209},[129,262,214],{"class":213},[129,264,217],{"class":147},[129,266,220],{"class":213},[129,268,269],{"class":209},".xlsx\"\n",[129,271,273],{"class":131,"line":272},5,[129,274,276],{"class":275},"sxvE3","    # _KKなし: タイムスタンプで新旧判定\n",[129,278,280,283,285,288,290,292,294,297,299,302,304,306,308,310,313,316,320,322,325,328,330,332,334,337,340,343],{"class":131,"line":279},6,[129,281,282],{"class":147},"    candidates ",[129,284,197],{"class":143},[129,286,287],{"class":165}," sorted",[129,289,144],{"class":143},[129,291,148],{"class":147},[129,293,236],{"class":143},[129,295,296],{"class":147},"glob",[129,298,144],{"class":143},[129,300,301],{"class":135},"f",[129,303,210],{"class":209},[129,305,214],{"class":213},[129,307,217],{"class":147},[129,309,220],{"class":213},[129,311,312],{"class":209},"*.xlsx\"",[129,314,315],{"class":143},"),",[129,317,319],{"class":318},"s4oTP"," key",[129,321,197],{"class":143},[129,323,324],{"class":135},"lambda",[129,326,327],{"class":147}," p",[129,329,151],{"class":143},[129,331,327],{"class":147},[129,333,236],{"class":143},[129,335,336],{"class":147},"stat",[129,338,339],{"class":143},"().",[129,341,342],{"class":147},"st_mtime",[129,344,345],{"class":143},")\n",[129,347,349,352,355,357,360,364,367,369,371,374],{"class":131,"line":348},7,[129,350,351],{"class":229},"    return",[129,353,354],{"class":147}," candidates",[129,356,178],{"class":143},[129,358,359],{"class":135},"-",[129,361,363],{"class":362},"sM54T","1",[129,365,366],{"class":143},"],",[129,368,354],{"class":147},[129,370,178],{"class":143},[129,372,373],{"class":362},"0",[129,375,376],{"class":143},"]\n",[30,378,380],{"id":379},"codexが突いた致命的4点","Codexが突いた致命的4点",[44,382,383,396],{},[47,384,385],{},[50,386,387,390,393],{},[53,388,389],{},"#",[53,391,392],{},"指摘内容",[53,394,395],{},"対応",[60,397,398,408,419,430],{},[50,399,400,402,405],{},[65,401,363],{},[65,403,404],{},"セル単位比較では行列の挿入削除を検出できない",[65,406,407],{},"2次元グリッドベース設計に全面書き直し",[50,409,410,413,416],{},[65,411,412],{},"2",[65,414,415],{},"ヘッダ行の特定がハードコードされている",[65,417,418],{},"先頭N行のパターンマッチで自動検出に変更",[50,420,421,424,427],{},[65,422,423],{},"3",[65,425,426],{},"マージセルの扱いが未定義",[65,428,429],{},"openpyxlのmerged_cellsを展開してから比較",[50,431,432,435,438],{},[65,433,434],{},"4",[65,436,437],{},"出力がstdoutのみで確認しづらい",[65,439,440],{},"HTMLレポート + JSON出力を追加",[15,442,443],{},"4番目は自分でも気づいていなかった。標準出力だけに差分を流していたところ、ユーザー（自分自身）に「どこに出力されてるの？」と指摘されて目が覚めた。",[445,446],"hr",{},[25,448,450],{"id":449},"_2-cfマッピングjsoncsvの自動生成","2. CFマッピングJSON/CSVの自動生成",[30,452,453],{"id":453},"やりたいこと",[15,455,456],{},"仕訳のE列（借方CF科目）とJ列（貸方CF科目）を全38論点のExcelから抽出し、Q番号ごとのCF項目対応表をJSON/CSVで吐き出す。リファクタリングで「勘定科目 -> CF区分」のマッピングをハードコードからデータ駆動に切り替えるための下準備。",[30,458,460],{"id":459},"codex-4ラウンドの軌跡","Codex 4ラウンドの軌跡",[15,462,463],{},"計画書v1を書いてCodexに投げたら「実装はまだいい、計画書をまずドキュメントにして」とユーザーに止められた。そこからv4まで4回のイテレーションを回した。",[44,465,466,479],{},[47,467,468],{},[50,469,470,473,476],{},[53,471,472],{},"Version",[53,474,475],{},"Codex指摘",[53,477,478],{},"修正内容",[60,480,481,492,506,517],{},[50,482,483,486,489],{},[65,484,485],{},"v1",[65,487,488],{},"検証の方向が逆（JSON->Excelではなく、Excel->JSONで検証すべき）",[65,490,491],{},"検証フローを反転",[50,493,494,497,503],{},[65,495,496],{},"v2",[65,498,499,502],{},[126,500,501],{},"_EXPLICIT_CF_TYPE_FROM_JSON","をマスタdictにすると、論点固有の例外を吸収できない",[65,504,505],{},"論点別オーバーライド層を追加",[50,507,508,511,514],{},[65,509,510],{},"v3",[65,512,513],{},"ラベル正規化が不十分（全角半角・括弧の揺れ）",[65,515,516],{},"unicodedata.normalizeとカスタム正規化関数を導入",[50,518,519,522,525],{},[65,520,521],{},"v4",[65,523,524],{},"LGTM",[65,526,527],{},"--",[15,529,530],{},"v2の「マスタdict化が危険」という指摘は、手が止まった。38論点のうち3つだけ特殊なCFラベルを使っていて、マスタ辞書で一律に引くと上書きされてしまう。論点IDをキーにしたオーバーライド層を挟むことで、汎用マスタと個別例外を両立させた。",[445,532],{},[25,534,536],{"id":535},"_3-古いexcelファイルの整理","3. 古いExcelファイルの整理",[15,538,539,540,543],{},"作業の合間に、ディレクトリに積み上がっていた古いExcelファイルを ",[126,541,542],{},"old/"," に移動した。",[545,546,547,551,554],"ul",{},[548,549,550],"li",{},"移動前: 649ファイル",[548,552,553],{},"移動後: 410ファイル（_KKと最新日付のみ残す）",[548,555,556,557,559],{},"239ファイルを ",[126,558,542],{}," へ退避",[15,561,562],{},"ファイル数が減っただけで、スクリプトの実行時間が体感で縮んだ。globのマッチ対象が減るので当然だが、散らかった部屋を片付けた後のような気分だった。",[445,564],{},[25,566,567],{"id":567},"試行錯誤",[44,569,570,588],{},[47,571,572],{},[50,573,574,576,579,582,585],{},[53,575,389],{},[53,577,578],{},"テーマ",[53,580,581],{},"試したこと",[53,583,584],{},"結果",[53,586,587],{},"気づき",[60,589,590,606,622,638,654],{},[50,591,592,594,597,600,603],{},[65,593,363],{},[65,595,596],{},"差分レポート出力先",[65,598,599],{},"stdoutのみに出力",[65,601,602],{},"ユーザーに「どこ？」と聞かれる",[65,604,605],{},"HTMLレポートとJSONを併用すべき",[50,607,608,610,613,616,619],{},[65,609,412],{},[65,611,612],{},"計画書v1",[65,614,615],{},"いきなり実装コードを書き始める",[65,617,618],{},"ユーザーに止められる",[65,620,621],{},"計画書を先にドキュメント化してレビューに回す",[50,623,624,626,629,632,635],{},[65,625,423],{},[65,627,628],{},"マスタdict一本化",[65,630,631],{},"全論点共通の辞書で引く設計",[65,633,634],{},"3論点で例外が壊れる",[65,636,637],{},"オーバーライド層を挟む",[50,639,640,642,645,648,651],{},[65,641,434],{},[65,643,644],{},"ラベル比較",[65,646,647],{},"文字列の完全一致",[65,649,650],{},"全角半角の揺れで不一致多発",[65,652,653],{},"normalize + カスタム正規化を前段に入れる",[50,655,656,659,662,665,668],{},[65,657,658],{},"5",[65,660,661],{},"検証方向",[65,663,664],{},"JSON -> Excel方向で検証",[65,666,667],{},"Codexに「逆だ」と指摘される",[65,669,670],{},"Excel（原本）-> JSON（生成物）が正しい検証方向",[445,672],{},[25,674,675],{"id":675},"今日の学び",[545,677,678,681,684,687,690],{},[548,679,680],{},"差分検出はセル単位で比較するとずれる。行列の挿入削除を先に検出してからセルを対応付ける2段階が必要",[548,682,683],{},"「標準出力に流しておけばいい」は自分だけの感覚。確認しやすい形式（HTML/JSON）で出力する習慣をつける",[548,685,686],{},"Codexレビューを計画段階で回すと、実装前に設計の穴が4つ見つかった。コードを書いてから見つけるより手戻りが圧倒的に少ない",[548,688,689],{},"マスタ辞書の一本化は魅力的だが、例外が3件あるだけで破綻する。汎用層+オーバーライド層の2層構造が安定する",[548,691,692],{},"「実装はまだいい、まず計画書を書け」というブレーキは正しかった。計画書v1からv4まで4回書き直す過程で、実装時の迷いがほぼ消えた",[445,694],{},[25,696,697],{"id":697},"関連リンク",[545,699,700,707],{},[548,701,702],{},[703,704,706],"a",{"href":705},"/cfws-matrix-based-refactor-plan","CFWSマトリックスベース生成への改修計画",[548,708,709],{},[703,710,712],{"href":711},"/cfws-q3-all-sheets-fix","CFWS全37Q網羅修正",[714,715,716],"style",{},"html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .sG7-3, html code.shiki .sG7-3{--shiki-default:#393A34;--shiki-dark:#393A34}html pre.shiki code .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .snbK4, html code.shiki .snbK4{--shiki-default:#A65E2B;--shiki-dark:#A65E2B}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":124,"searchDepth":191,"depth":191,"links":718},[719,725,729,730,731,732],{"id":27,"depth":191,"text":28,"children":720},[721,722,723,724],{"id":32,"depth":226,"text":32},{"id":38,"depth":226,"text":39},{"id":113,"depth":226,"text":114},{"id":379,"depth":226,"text":380},{"id":449,"depth":191,"text":450,"children":726},[727,728],{"id":453,"depth":226,"text":453},{"id":459,"depth":226,"text":460},{"id":535,"depth":191,"text":536},{"id":567,"depth":191,"text":567},{"id":675,"depth":191,"text":675},{"id":697,"depth":191,"text":697},"dev","ユーザー手修正Excelとスクリプト出力の差分を6カテゴリで検出するPythonスキルを設計。CF項目マッピングJSON/CSVをQ38論点から自動抽出。Codexレビュー4回で致命的欠陥を潰し切った記録。","md",{},true,"/excel-diff-cf-mapping","eurekapu-nuxt4",false,"2026-04-17T00:00:00.000Z",{"title":5,"description":734},"2026-04/2026-04-17/excel-diff-cf-mapping",[745,746,747,748,749,750,751],"Excel","差分検出","キャッシュフロー計算書","Claude Code","Codex","Python","リファクタリング","memo",null,"MTxd7w2q2ueKryFBU7_JtrKqYTTeloJrx8MTzxWZ4dA",[],"https://log.eurekapu.com/og/blog/excel-diff-cf-mapping.png?v=2026-04-17T00%3A00%3A00.000Z&title=Excel%E5%B7%AE%E5%88%86%E6%A4%9C%E5%87%BA%E3%82%B9%E3%82%AD%E3%83%AB%E3%81%A8CF%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0JSON%E8%87%AA%E5%8B%95%E7%94%9F%E6%88%90%20-%20Codex4%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E3%81%A7%E8%A8%88%E7%94%BB%E3%82%92%E7%A3%A8%E3%81%84%E3%81%9F1%E6%97%A5&author=Kei%20Komatsu&sig=cf997c68f56bc80b",1780786052547]