[{"data":1,"prerenderedAt":769},["ShallowReactive",2],{"content-/2026-05-03-case100-excel-typescript-pipeline":3,"all-pages-for-dir":767,"og-image-/2026-05-03-case100-excel-typescript-pipeline":768},{"id":4,"title":5,"body":6,"category":748,"description":749,"extension":750,"meta":751,"navigation":410,"path":752,"project_name":753,"published":754,"publishedAt":755,"seo":756,"stem":757,"tags":758,"todo":765,"updatedAt":765,"__hash__":766},"pages/2026-05/2026-05-03/case100-excel-typescript-pipeline.md","会計の参考書100ケースをExcel→TypeScriptで一気にパイプライン化した話",{"type":7,"value":8,"toc":732},"minimark",[9,34,39,42,45,78,81,84,87,94,97,103,106,117,175,185,189,192,218,225,276,280,283,309,321,325,342,353,360,369,389,423,430,434,440,443,447,450,458,465,496,500,507,527,530,534,541,547,629,632,635,644,650,662,668,679,682,728],[10,11,12,13,17,18,21,22,25,26,29,30,33],"p",{},"朝6時51分、Claude Codeに最初の指示を投げ込んだ。「ある会計の参考書、100ケース分の仕訳をExcelで一元管理して、そこからTypeScript静的データを吐き出すパイプラインを作る」。昼前には79論点まで自動生成できる仕組みが立ち上がり、",[14,15,16],"code",{},"verify_xlsx"," が ",[14,19,20],{},"BS check=0","、",[14,23,24],{},"PL↔BS整合通過"," と返した。途中、PowerShellのUTF-8 BOMが日本語を文字化けさせ、",[14,27,28],{},"run.ps1"," の ",[14,31,32],{},"$root"," が1階層浅く動かず、Codexのサンドボックスが入力ファイルを読まずに落ちた。Codexレビュー（GPT-5.5）を3ラウンド回して計画書から致命指摘を5件潰し、引き継ぎメモも別ラウンドで「初見でわかるか」を別のCodexに点検させた。4論点→18論点→55論点→79論点と段階的に拡張するたびに verify を通し、最後は基本ケース9件のうち2件（ID 1007/1008）と通常ケース ID 48〜63 を加えた構成に着地した。",[35,36,38],"h2",{"id":37},"朝-書籍構成を3層に整理してid体系を決める","朝: 書籍構成を3層に整理してID体系を決める",[10,40,41],{},"参考書の構成を眺めた。基本ケース9件、通常ケース100件、コラム6件。3層に分かれている。Web側でURLや配列キーに直結させたいので、書籍番号と整数IDを直結させたい。",[10,43,44],{},"ID体系をこう決めた。",[46,47,48,60,69],"ul",{},[49,50,51,55,56,59],"li",{},[52,53,54],"strong",{},"基本ケース",": ",[14,57,58],{},"1001〜1009","（9件）",[49,61,62,55,65,68],{},[52,63,64],{},"通常ケース",[14,66,67],{},"1〜100","（書籍番号と一致）",[49,70,71,55,74,77],{},[52,72,73],{},"コラム",[14,75,76],{},"2001〜2006","（6件）",[10,79,80],{},"書籍番号と直結させた瞬間、「この論点はケース48です」とユーザーが指せば、Excel・TypeScript・Vueページのどこを触ればいいかが3秒で分かるようになった。",[35,82,83],{"id":83},"共通期首仕訳は論点ごとに複製する",[10,85,86],{},"最初に悩んだのは「設立出資」「機械装置取得」など、複数論点で共通する期首仕訳をどう扱うか。普通ならマスタテーブルに切り出して論点側からFKで参照したくなる。",[10,88,89,90,93],{},"だが今回は逆に振り切った。",[52,91,92],{},"共通仕訳でも論点ごとに複製する","。",[10,95,96],{},"理由はひとつ。集計を単純にしたい。論点ごとに「この論点で発生した全仕訳」を1テーブルから引けば期首BSも当期PLも全部計算できる。マスタを参照して再構築するロジックは書きたくない。",[10,98,99,100,102],{},"正規化を捨てた瞬間、",[14,101,16],{}," の集計コードが10行で書けた。",[35,104,105],{"id":105},"純資産は感情科目レベルで増減内容を持たせる",[10,107,108,109,112,113,116],{},"純資産科目は、ただの「資本金」ではなくて、",[14,110,111],{},"資本金_新株発行"," ",[14,114,115],{},"資本金_設立出資"," のようにアンダースコア区切りで増減内容を後ろに付ける形にした。",[118,119,124],"pre",{"className":120,"code":121,"language":122,"meta":123,"style":123},"language-python shiki shiki-themes vitesse-light vitesse-light","# 純資産科目の命名規則\n\"資本金_設立出資\"     # 設立時の出資による増加\n\"資本金_新株発行\"     # 新株発行による増加\n\"利益剰余金_当期純利益\" # PLからの振替\n","python","",[14,125,126,135,150,162],{"__ignoreMap":123},[127,128,131],"span",{"class":129,"line":130},"line",1,[127,132,134],{"class":133},"sxvE3","# 純資産科目の命名規則\n",[127,136,138,142,145,147],{"class":129,"line":137},2,[127,139,141],{"class":140},"sMJiu","\"",[127,143,115],{"class":144},"sdGka",[127,146,141],{"class":140},[127,148,149],{"class":133},"     # 設立時の出資による増加\n",[127,151,153,155,157,159],{"class":129,"line":152},3,[127,154,141],{"class":140},[127,156,111],{"class":144},[127,158,141],{"class":140},[127,160,161],{"class":133},"     # 新株発行による増加\n",[127,163,165,167,170,172],{"class":129,"line":164},4,[127,166,141],{"class":140},[127,168,169],{"class":144},"利益剰余金_当期純利益",[127,171,141],{"class":140},[127,173,174],{"class":133}," # PLからの振替\n",[10,176,177,178,180,181,184],{},"CFで見せるときに「資本金が動いた理由」を1行で表示できるし、PL→BSの振替も ",[14,179,169],{}," でラベルが立つ。BS残高表示時は ",[14,182,183],{},"_"," で区切って前半だけ集計すれば普通の純資産表示に戻る。",[35,186,188],{"id":187},"excel側はpythontypescript側はtsxで生成","Excel側はPython、TypeScript側はtsxで生成",[10,190,191],{},"パイプラインを2段に切った。",[193,194,195,205],"ol",{},[49,196,197,204],{},[52,198,199,200,203],{},"Python (",[14,201,202],{},"xlsx_helpers",")",": Excel仕訳DBを生成・検証",[49,206,207,213,214,217],{},[52,208,209,210,203],{},"TypeScript (",[14,211,212],{},"tsx",": ExcelからVue.js用静的データ（",[14,215,216],{},"cases.ts","）を生成",[10,219,220,221,224],{},"Pythonでやる理由は ",[14,222,223],{},"openpyxl"," の安定性と、検証ロジックを書きやすい点。TypeScriptでやる理由は出力先がVueのコンポーネントなので型を揃えたい点。",[118,226,230],{"className":227,"code":228,"language":229,"meta":123,"style":123},"language-bash shiki shiki-themes vitesse-light vitesse-light","# パイプラインの叩き方\npython xlsx_helpers/generate.py --cases 1,2,3,...,79\npython xlsx_helpers/verify.py    # BS check=0 / PL↔BS 整合\nnpx tsx scripts/xlsx-to-ts.ts    # cases.ts を生成\n","bash",[14,231,232,237,252,262],{"__ignoreMap":123},[127,233,234],{"class":129,"line":130},[127,235,236],{"class":133},"# パイプラインの叩き方\n",[127,238,239,242,245,249],{"class":129,"line":137},[127,240,122],{"class":241},"senZ8",[127,243,244],{"class":144}," xlsx_helpers/generate.py",[127,246,248],{"class":247},"snbK4"," --cases",[127,250,251],{"class":144}," 1,2,3,...,79\n",[127,253,254,256,259],{"class":129,"line":152},[127,255,122],{"class":241},[127,257,258],{"class":144}," xlsx_helpers/verify.py",[127,260,261],{"class":133},"    # BS check=0 / PL↔BS 整合\n",[127,263,264,267,270,273],{"class":129,"line":164},[127,265,266],{"class":241},"npx",[127,268,269],{"class":144}," tsx",[127,271,272],{"class":144}," scripts/xlsx-to-ts.ts",[127,274,275],{"class":133},"    # cases.ts を生成\n",[35,277,279],{"id":278},"段階拡張-4185579論点","段階拡張: 4→18→55→79論点",[10,281,282],{},"一気に79論点を生成しようとすると失敗時の切り分けがしにくい。段階的に拡張した。",[46,284,285,291,297,303],{},[49,286,287,290],{},[52,288,289],{},"4論点",": パイプラインの骨格確認。Excel生成→verify→TypeScript出力まで通すだけ",[49,292,293,296],{},[52,294,295],{},"18論点",": 基本ケース9件＋通常ケースの最初の数件。BS整合のテストパターンを増やす",[49,298,299,302],{},[52,300,301],{},"55論点",": 通常ケースを大幅追加。設立出資・機械装置取得の複製パターンが現実的になる",[49,304,305,308],{},[52,306,307],{},"79論点",": 最終的な目標。ID 48〜63 を加えてカバレッジを伸ばす",[10,310,311,312,314,315,317,318,320],{},"各段階で ",[14,313,16],{}," を通した。",[14,316,20],{}," と ",[14,319,24],{}," の両方が出るまで次に進まない。",[35,322,324],{"id":323},"試行錯誤-powershell-utf-8-bom-で日本語が崩れた","試行錯誤: PowerShell UTF-8 BOM で日本語が崩れた",[10,326,327,328,330,331,334,335,337,338,341],{},"最初に書いた ",[14,329,28],{}," で ",[14,332,333],{},"Set-Content -Encoding UTF8"," を使っていたら、出力された ",[14,336,216],{}," の先頭に BOM が入って、tsx が読み込んだ瞬間に日本語が ",[14,339,340],{},"���"," に化けた。",[10,343,344,345,348,349,352],{},"PowerShell の ",[14,346,347],{},"-Encoding UTF8"," は BOM 付きのUTF-8を意味する。BOMなしを使いたい場合は ",[14,350,351],{},"[System.IO.File]::WriteAllText"," を使うか、tsx 側で書き出すしかない。",[10,354,355,356,359],{},"切り替えた。tsx 側で ",[14,357,358],{},"fs.writeFileSync(path, content, 'utf-8')"," に統一。BOMなしの素のUTF-8で出力されて、Vue側のインポートも問題なく通った。",[35,361,363,364,29,366,368],{"id":362},"試行錯誤-runps1-の-root-が1階層浅かった","試行錯誤: ",[14,365,28],{},[14,367,32],{}," が1階層浅かった",[10,370,371,373,374,377,378,380,381,384,385,388],{},[14,372,28],{}," の冒頭で ",[14,375,376],{},"$root = Split-Path $PSScriptRoot -Parent"," と書いていたら、想定より1階層浅いディレクトリを ",[14,379,32],{}," として認識していた。スクリプトの設置場所を ",[14,382,383],{},"xlsx_helpers/scripts/"," に移したのに ",[14,386,387],{},"Parent"," が1回ぶん足りなかった。",[118,390,394],{"className":391,"code":392,"language":393,"meta":123,"style":123},"language-powershell shiki shiki-themes vitesse-light vitesse-light","# Before\n$root = Split-Path $PSScriptRoot -Parent\n\n# After\n$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent\n","powershell",[14,395,396,401,406,412,417],{"__ignoreMap":123},[127,397,398],{"class":129,"line":130},[127,399,400],{},"# Before\n",[127,402,403],{"class":129,"line":137},[127,404,405],{},"$root = Split-Path $PSScriptRoot -Parent\n",[127,407,408],{"class":129,"line":152},[127,409,411],{"emptyLinePlaceholder":410},true,"\n",[127,413,414],{"class":129,"line":164},[127,415,416],{},"# After\n",[127,418,420],{"class":129,"line":419},5,[127,421,422],{},"$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent\n",[10,424,425,426,429],{},"修正後、",[14,427,428],{},"xlsx_helpers/cases/*.xlsx"," が正しく見つかるようになった。",[35,431,433],{"id":432},"試行錯誤-excel-が文字化け","試行錯誤: Excel が文字化け",[10,435,436,437,439],{},"途中で生成した Excel をブラウザで開いたら、シート名が文字化けしていた。Python 側で ",[14,438,223],{}," のシート作成時に内部エンコーディングを CP932 と勘違いして書き出していたのが原因。明示的に UTF-8 を指定して書き直したら直った。",[10,441,442],{},"書き直し後の Excel を再読込してverifyを通した。BS check=0、PL↔BS整合通過。",[35,444,446],{"id":445},"試行錯誤-codex-のサンドボックスエラー","試行錯誤: Codex のサンドボックスエラー",[10,448,449],{},"計画書のレビューを Codex GPT-5.5 に投げたら、サンドボックスが入力ファイルを読み込めずに落ちた。",[118,451,456],{"className":452,"code":454,"language":455},[453],"language-text","codex exec -m gpt-5.5 \"...\"\n# Error: cannot read file in sandbox\n","text",[14,457,454],{"__ignoreMap":123},[10,459,460,461,464],{},"Codex CLI のサンドボックスは外部ファイルへのアクセスを制限している。",[14,462,463],{},"--bypass"," 系のフラグを付けて再試行したら通った。",[118,466,468],{"className":227,"code":467,"language":229,"meta":123,"style":123},"codex exec --dangerously-bypass-approvals-and-sandbox -m gpt-5.5 \"...\"\n",[14,469,470],{"__ignoreMap":123},[127,471,472,475,478,481,484,487,490,493],{"class":129,"line":130},[127,473,474],{"class":241},"codex",[127,476,477],{"class":144}," exec",[127,479,480],{"class":247}," --dangerously-bypass-approvals-and-sandbox",[127,482,483],{"class":247}," -m",[127,485,486],{"class":144}," gpt-5.5",[127,488,489],{"class":140}," \"",[127,491,492],{"class":144},"...",[127,494,495],{"class":140},"\"\n",[35,497,499],{"id":498},"codex-レビュー-3ラウンドで5件の致命指摘を反映","Codex レビュー: 3ラウンドで5件の致命指摘を反映",[10,501,502,503,506],{},"計画書（",[14,504,505],{},"memo/2026-05-03/case100-pipeline-plan.md","）を Codex GPT-5.5 にレビューさせた。3ラウンド回した。",[46,508,509,515,521],{},[49,510,511,514],{},[52,512,513],{},"Round 1",": ID体系の衝突可能性、純資産科目の命名一貫性、verify ロジックの境界条件",[49,516,517,520],{},[52,518,519],{},"Round 2",": from2 の前年度集計が論点をまたぐ場合の挙動、PL会計年度の境界",[49,522,523,526],{},[52,524,525],{},"Round 3",": 引き継ぎメモが「初見でわかるか」を別のCodexに点検させる",[10,528,529],{},"致命指摘5件を計画書に反映してから着手した。瑣末な点へのクソリプは無視し、根本的な設計上の穴だけ拾った。",[35,531,533],{"id":532},"from2-で前年度まで集計すれば-bs-は引き継げる","from2 で前年度まで集計すれば BS は引き継げる",[10,535,536,537,540],{},"期首BSをどう作るかで悩んだ。論点ごとに ",[14,538,539],{},"preposted"," を全部書くのは冗長すぎる。",[10,542,543,546],{},[14,544,545],{},"from2"," カラムを足した。各仕訳に「会計年度2 から有効」のフラグを立てる。期首BSは「from2 より前の年度の仕訳をすべて集計した結果」として動的に計算する。",[118,548,552],{"className":549,"code":550,"language":551,"meta":123,"style":123},"language-typescript shiki shiki-themes vitesse-light vitesse-light","// 期首BS計算\nconst openingBS = journals\n  .filter(j => j.fiscalYear \u003C currentFY)\n  .reduce(accumulateBalance, {})\n","typescript",[14,553,554,559,576,611],{"__ignoreMap":123},[127,555,556],{"class":129,"line":130},[127,557,558],{"class":133},"// 期首BS計算\n",[127,560,561,565,569,573],{"class":129,"line":137},[127,562,564],{"class":563},"stQ0i","const ",[127,566,568],{"class":567},"s4oTP","openingBS",[127,570,572],{"class":571},"shFtX"," =",[127,574,575],{"class":567}," journals\n",[127,577,578,581,584,587,590,593,596,599,602,605,608],{"class":129,"line":152},[127,579,580],{"class":571},"  .",[127,582,583],{"class":241},"filter",[127,585,586],{"class":571},"(",[127,588,589],{"class":567},"j",[127,591,592],{"class":571}," =>",[127,594,595],{"class":567}," j",[127,597,598],{"class":571},".",[127,600,601],{"class":567},"fiscalYear",[127,603,604],{"class":571}," \u003C",[127,606,607],{"class":567}," currentFY",[127,609,610],{"class":571},")\n",[127,612,613,615,618,620,623,626],{"class":129,"line":164},[127,614,580],{"class":571},[127,616,617],{"class":241},"reduce",[127,619,586],{"class":571},[127,621,622],{"class":567},"accumulateBalance",[127,624,625],{"class":571},",",[127,627,628],{"class":571}," {})\n",[10,630,631],{},"PLは会計年度のフラグだけでフローを拾えばいい。BSは累積、PLはフロー、という会計の基本構造をそのままコードに落とした瞬間、verify が通った。",[35,633,634],{"id":634},"学び",[10,636,637,640,641,643],{},[52,638,639],{},"正規化を捨てると集計が10行で書ける。"," 共通仕訳をマスタに切り出してFKで引っ張ると見た目はキレイだが、検証コードが分岐だらけになる。論点ごとに複製した瞬間、「この論点の全仕訳を1テーブルから引く」だけで済むようになって、",[14,642,16],{}," が10行で書き上がった。",[10,645,646,649],{},[52,647,648],{},"段階拡張すると失敗の切り分けがしやすい。"," 一気に79論点を生成して落ちると、どの論点が原因か見つけるのに30分かかる。4→18→55→79と4段階に分けたら、各段階で30秒で原因論点を特定できた。",[10,651,652,658,659,661],{},[52,653,654,655,657],{},"PowerShellの ",[14,656,347],{}," はBOM付き。"," Windows特有の罠。tsx側で ",[14,660,358],{}," に統一した瞬間、文字化けが消えた。Windows で Web向けデータを書く時は PowerShell の Set-Content を避ける。",[10,663,664,667],{},[52,665,666],{},"Codex レビューは3ラウンド回して致命点だけ拾う。"," 1ラウンドだと表面的な指摘しか出ない。3ラウンド目で「初見でわかるか」を別のCodexに点検させると、引き継ぎメモの抜けが浮き彫りになった。",[10,669,670,675,676,678],{},[52,671,672,674],{},[14,673,545],{}," で前年度集計すればBSは引き継げる。"," 期首BSを ",[14,677,539],{}," で全部書く必要はない。各仕訳に from2 フラグを立てて、当期より前の年度を全部集計すればBS残高は自動的に再現される。会計の基本構造（BSは累積、PLはフロー）をコードに落としただけだが、書き上がるまで気づかなかった。",[35,680,681],{"id":681},"明日やること",[46,683,686,695,701,709,718],{"className":684},[685],"contains-task-list",[49,687,690,694],{"className":688},[689],"task-list-item",[691,692],"input",{"disabled":410,"type":693},"checkbox"," 残り21論点（80〜100）をパイプラインに流して通常ケースを完成させる",[49,696,698,700],{"className":697},[689],[691,699],{"disabled":410,"type":693}," コラム6件（ID 2001〜2006）の構造をパイプラインに組み込む",[49,702,704,112,706,708],{"className":703},[689],[691,705],{"disabled":410,"type":693},[14,707,216],{}," をVue側で読み込んで、79論点のインタラクティブHTMLを生成するエージェントを並列起動する",[49,710,712,112,714,717],{"className":711},[689],[691,713],{"disabled":410,"type":693},[14,715,716],{},"xlsx_helpers/verify.py"," の出力に「どの論点でBS不整合が出たか」をJSON形式で吐き出す機能を追加して、生成失敗時の切り分けを30秒→3秒に短縮する",[49,719,721,723,724,727],{"className":720},[689],[691,722],{"disabled":410,"type":693}," 引き継ぎメモを ",[14,725,726],{},"memo/2026-05-03/case100-pipeline-handover.md"," に固定し、明日朝のリマインドに添付する",[729,730,731],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}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);}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .snbK4, html code.shiki .snbK4{--shiki-default:#A65E2B;--shiki-dark:#A65E2B}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}",{"title":123,"searchDepth":137,"depth":137,"links":733},[734,735,736,737,738,739,740,742,743,744,745,746,747],{"id":37,"depth":137,"text":38},{"id":83,"depth":137,"text":83},{"id":105,"depth":137,"text":105},{"id":187,"depth":137,"text":188},{"id":278,"depth":137,"text":279},{"id":323,"depth":137,"text":324},{"id":362,"depth":137,"text":741},"試行錯誤: run.ps1 の $root が1階層浅かった",{"id":432,"depth":137,"text":433},{"id":445,"depth":137,"text":446},{"id":498,"depth":137,"text":499},{"id":532,"depth":137,"text":533},{"id":634,"depth":137,"text":634},{"id":681,"depth":137,"text":681},"dev","書籍100ケースをWebコンテンツ化する前段として、Excel仕訳DBとTypeScript静的データを連携させるパイプラインを構築。1日で79論点まで自動生成可能にした","md",{},"/2026-05-03-case100-excel-typescript-pipeline","eurekapu-nuxt4",false,"2026-05-03T00:00:00.000Z",{"title":5,"description":749},"2026-05/2026-05-03/case100-excel-typescript-pipeline",[759,760,761,762,763,764],"case100","Excel","TypeScript","Python","パイプライン","簿記",null,"st1DeVHXHsrOXqdoC9TsDDTZtfCwn3ApgnlSUhut4TI",[],"https://log.eurekapu.com/favicon.svg",1778379990144]