[{"data":1,"prerenderedAt":790},["ShallowReactive",2],{"content-/cf-fixed-asset-completion":3,"all-pages-for-dir":788,"og-image-/cf-fixed-asset-completion":789},{"id":4,"title":5,"body":6,"category":766,"description":767,"extension":768,"meta":769,"navigation":770,"path":771,"project_name":772,"published":773,"publishedAt":774,"seo":775,"stem":776,"tags":777,"todo":785,"updatedAt":786,"__hash__":787},"pages/2026-04/2026-04-05/cf-fixed-asset-completion.md","固定資産のCF精算表が完成した --- 年次推移表・CFWS・check=0検証の全工程",{"type":7,"value":8,"toc":743},"minimark",[9,14,23,28,32,35,38,51,54,58,65,68,71,145,148,247,250,254,258,261,264,267,278,281,296,299,302,305,320,326,330,341,345,352,356,359,374,662,665,669,672,675,686,689,692,695,706,720,723,726,729,736,739],[10,11,13],"h1",{"id":12},"固定資産のcf精算表が完成した","固定資産のCF精算表が完成した",[15,16,17,18,22],"p",{},"朝、固定資産の年次推移表生成スクリプトを走らせた。ターミナルに ",[19,20,21],"code",{},"KeyError: '減価償却累計額'"," が吐き出されて止まった。前日に貸付金で同じパターン（未登録勘定科目）を踏んでいたので、原因の見当はすぐについた。ACCOUNT_DBを開いて「減価償却累計額」を追加し、再実行。今度は最後まで通った。ここから夕方まで、CFWS生成・数式問題の迂回・SUM数式ロジックの書き直しが続いた。",[24,25,27],"h2",{"id":26},"step-b-年次推移表の生成","Step b: 年次推移表の生成",[29,30,31],"h3",{"id":31},"減価償却累計額の未登録",[15,33,34],{},"固定資産には建物・車両・備品といった取得原価の科目と、それぞれに対応する減価償却累計額の科目がある。取得原価側はACCOUNT_DBに登録済みだったが、累計額側がごっそり抜けていた。",[15,36,37],{},"追加した科目:",[39,40,41,45,48],"ul",{},[42,43,44],"li",{},"減価償却累計額（建物）",[42,46,47],{},"減価償却累計額（車両運搬具）",[42,49,50],{},"減価償却累計額（工具器具備品）",[15,52,53],{},"SUB_ACCOUNT_EXPANDにも子科目を追加し、補助科目展開つきの年次推移表が最後まで生成されるようになった。",[29,55,57],{"id":56},"sum数式の走査ロジック改善","SUM数式の走査ロジック改善",[15,59,60,61,64],{},"年次推移表シートの合計行にSUM数式を埋め込む ",[19,62,63],{},"_add_sum_formulas"," 関数で、indentレベルの走査がうまく動かない箇所があった。",[15,66,67],{},"問題は、indent=1の科目行が連続するとき、indent=1同士の合計行がどこまでを範囲に含めるかを誤認するケースだった。走査ロジックが「自分より深いindentの行だけを拾う」前提で書かれていたが、indent=1の合計行（たとえば「有形固定資産合計」）の直下にまたindent=1の科目行（たとえば「無形固定資産」）が続くと、次のセクションまで巻き込んでしまう。",[15,69,70],{},"修正前のロジック:",[72,73,78],"pre",{"className":74,"code":75,"language":76,"meta":77,"style":77},"language-python shiki shiki-themes vitesse-light vitesse-light","# 合計行から上方向に走査し、indentが浅くなったら停止\nwhile row > 0 and get_indent(row) >= target_indent:\n    row -= 1\n","python","",[19,79,80,89,133],{"__ignoreMap":77},[81,82,85],"span",{"class":83,"line":84},"line",1,[81,86,88],{"class":87},"sxvE3","# 合計行から上方向に走査し、indentが浅くなったら停止\n",[81,90,92,96,100,104,108,111,114,118,121,124,127,130],{"class":83,"line":91},2,[81,93,95],{"class":94},"sHkkW","while",[81,97,99],{"class":98},"sG7-3"," row ",[81,101,103],{"class":102},"stQ0i",">",[81,105,107],{"class":106},"sM54T"," 0",[81,109,110],{"class":102}," and",[81,112,113],{"class":98}," get_indent",[81,115,117],{"class":116},"shFtX","(",[81,119,120],{"class":98},"row",[81,122,123],{"class":116},")",[81,125,126],{"class":102}," >=",[81,128,129],{"class":98}," target_indent",[81,131,132],{"class":116},":\n",[81,134,136,139,142],{"class":83,"line":135},3,[81,137,138],{"class":98},"    row ",[81,140,141],{"class":116},"-=",[81,143,144],{"class":106}," 1\n",[15,146,147],{},"修正後は、合計行の直上から走査を開始し、同じindentまたはそれより深い行を拾いつつ、同じindentの合計行に当たったらそこで打ち切るようにした。",[72,149,151],{"className":74,"code":150,"language":76,"meta":77,"style":77},"while row > 0:\n    indent = get_indent(row)\n    if indent \u003C target_indent:\n        break\n    if indent == target_indent and is_sum_row(row):\n        break  # 同レベルの合計行は範囲外\n    row -= 1\n",[19,152,153,165,182,197,203,229,238],{"__ignoreMap":77},[81,154,155,157,159,161,163],{"class":83,"line":84},[81,156,95],{"class":94},[81,158,99],{"class":98},[81,160,103],{"class":102},[81,162,107],{"class":106},[81,164,132],{"class":116},[81,166,167,170,173,175,177,179],{"class":83,"line":91},[81,168,169],{"class":98},"    indent ",[81,171,172],{"class":116},"=",[81,174,113],{"class":98},[81,176,117],{"class":116},[81,178,120],{"class":98},[81,180,181],{"class":116},")\n",[81,183,184,187,190,193,195],{"class":83,"line":135},[81,185,186],{"class":94},"    if",[81,188,189],{"class":98}," indent ",[81,191,192],{"class":102},"\u003C",[81,194,129],{"class":98},[81,196,132],{"class":116},[81,198,200],{"class":83,"line":199},4,[81,201,202],{"class":94},"        break\n",[81,204,206,208,210,213,216,219,222,224,226],{"class":83,"line":205},5,[81,207,186],{"class":94},[81,209,189],{"class":98},[81,211,212],{"class":102},"==",[81,214,215],{"class":98}," target_indent ",[81,217,218],{"class":102},"and",[81,220,221],{"class":98}," is_sum_row",[81,223,117],{"class":116},[81,225,120],{"class":98},[81,227,228],{"class":116},"):\n",[81,230,232,235],{"class":83,"line":231},6,[81,233,234],{"class":94},"        break",[81,236,237],{"class":87},"  # 同レベルの合計行は範囲外\n",[81,239,241,243,245],{"class":83,"line":240},7,[81,242,138],{"class":98},[81,244,141],{"class":116},[81,246,144],{"class":106},[15,248,249],{},"この修正で、固定資産のように合計行が複数段に分かれる構造でも正しい範囲のSUM数式が入るようになった。",[24,251,253],{"id":252},"step-c-cfws生成","Step c: CFWS生成",[29,255,257],{"id":256},"dual_accounts設定","DUAL_ACCOUNTS設定",[15,259,260],{},"固定資産にはBS科目とPL科目の両面がある。取得時はBS（建物）、毎期の費用計上はPL（減価償却費）。DUAL_ACCOUNTS辞書にこの対応関係を登録した。",[29,262,263],{"id":263},"中間テーブル関数の新規作成",[15,265,266],{},"借入金や貸付金と異なり、固定資産は取引モジュール側に返済予定表がない。代わりに、減価償却計算表から年度ごとの償却額と帳簿価額を抽出する中間テーブル関数を新たに書いた。",[15,268,269,270,273,274,277],{},"既存の ",[19,271,272],{},"_build_interest_table"," を参考に、固定資産用の ",[19,275,276],{},"_build_depreciation_table"," を作成。償却方法（定額法・定率法）ごとの分岐と、期中取得時の月割計算を含めた。",[29,279,280],{"id":280},"集約シート検出ロジックの更新",[15,282,283,284,287,288,291,292,295],{},"CFWS生成時に取引モジュールの集約シートを探すロジックが、シート名の前方一致で ",[19,285,286],{},"集約_借入金","、",[19,289,290],{},"集約_貸付金"," を検出していた。固定資産用の ",[19,293,294],{},"集約_固定資産"," を追加するため、検出対象のリストを拡張した。",[24,297,298],{"id":298},"openpyxl数式問題との格闘",[15,300,301],{},"ここが一番時間を食った。",[29,303,304],{"id":304},"症状",[15,306,307,308,311,312,315,316,319],{},"openpyxlでExcelファイルを読み込むと、数式セル（たとえば ",[19,309,310],{},"=B10-B11"," のような税引前当期純利益の計算式）の値が ",[19,313,314],{},"None"," になる。openpyxlは数式の文字列は保持するが、計算結果は持たない。",[19,317,318],{},"data_only=True"," で開いても、Excelアプリで一度開いて保存しないとキャッシュ値が入らない。",[15,321,322,323,325],{},"Pythonスクリプトの中で「税引前当期純利益」の値を参照してCFWS調整列の数値を組み立てる処理があり、ここで ",[19,324,314],{}," が伝播してCF全体が壊れていた。",[29,327,329],{"id":328},"試行1-xlcalcで数式を評価する","試行1: xlcalcで数式を評価する",[15,331,332,333,336,337,340],{},"最初に ",[19,334,335],{},"xlcalc"," ライブラリで数式をPython上で評価しようとした。しかし、シート間参照やINDEX/MATCH関数には対応しておらず、固定資産の年次推移表にある複雑な参照式で ",[19,338,339],{},"NotImplementedError"," が出た。",[29,342,344],{"id":343},"試行2-com経由でexcelアプリに計算させる","試行2: COM経由でExcelアプリに計算させる",[15,346,347,348,351],{},"Windows環境なのでCOM経由で ",[19,349,350],{},"win32com"," を使い、Excelアプリにファイルを開かせて再計算させる方法も試した。動くことは動くが、Excelアプリの起動に数秒かかり、ファイルを閉じるときのダイアログ処理も不安定だった。CI環境やヘッドレス環境では使えない。",[29,353,355],{"id":354},"解決策-resolve_rowsパイプライン","解決策: resolve_rowsパイプライン",[15,357,358],{},"最終的に、Excelの数式に頼らず、Pythonで直接計算してJSON出力するパイプラインを組んだ。",[360,361,362,365,368,371],"ol",{},[42,363,364],{},"年次推移表のExcelからセル値を読み取る（値セルのみ）",[42,366,367],{},"数式セルに相当する計算をPython側の関数で再実装する",[42,369,370],{},"計算結果をJSON中間ファイルに書き出す",[42,372,373],{},"CFWS生成スクリプトはJSONから値を読む",[72,375,377],{"className":74,"code":376,"language":76,"meta":77,"style":77},"def resolve_rows(wb, sheet_name: str) -> dict:\n    \"\"\"数式セルをPythonで再計算し、全行の値を辞書で返す\"\"\"\n    ws = wb[sheet_name]\n    values = {}\n    for row in ws.iter_rows(min_row=2, values_only=False):\n        label = row[0].value\n        if label is None:\n            continue\n        # 数式セルはPython側で計算\n        amount = row[1].value\n        if amount is None and row[1].data_type == 'f':\n            amount = _evaluate(row[1], values)\n        values[label] = amount\n    return values\n",[19,378,379,416,429,448,458,500,521,537,543,549,568,606,633,653],{"__ignoreMap":77},[81,380,381,384,388,390,393,396,399,402,406,408,411,414],{"class":83,"line":84},[81,382,383],{"class":102},"def",[81,385,387],{"class":386},"senZ8"," resolve_rows",[81,389,117],{"class":116},[81,391,392],{"class":98},"wb",[81,394,395],{"class":116},",",[81,397,398],{"class":98}," sheet_name",[81,400,401],{"class":116},":",[81,403,405],{"class":404},"sz8Xr"," str",[81,407,123],{"class":116},[81,409,410],{"class":116}," ->",[81,412,413],{"class":404}," dict",[81,415,132],{"class":116},[81,417,418,422,426],{"class":83,"line":91},[81,419,421],{"class":420},"sMJiu","    \"\"\"",[81,423,425],{"class":424},"sdGka","数式セルをPythonで再計算し、全行の値を辞書で返す",[81,427,428],{"class":420},"\"\"\"\n",[81,430,431,434,436,439,442,445],{"class":83,"line":135},[81,432,433],{"class":98},"    ws ",[81,435,172],{"class":116},[81,437,438],{"class":98}," wb",[81,440,441],{"class":116},"[",[81,443,444],{"class":98},"sheet_name",[81,446,447],{"class":116},"]\n",[81,449,450,453,455],{"class":83,"line":199},[81,451,452],{"class":98},"    values ",[81,454,172],{"class":116},[81,456,457],{"class":116}," {}\n",[81,459,460,463,465,468,471,474,477,479,483,485,488,490,493,495,498],{"class":83,"line":205},[81,461,462],{"class":94},"    for",[81,464,99],{"class":98},[81,466,467],{"class":94},"in",[81,469,470],{"class":98}," ws",[81,472,473],{"class":116},".",[81,475,476],{"class":98},"iter_rows",[81,478,117],{"class":116},[81,480,482],{"class":481},"s4oTP","min_row",[81,484,172],{"class":116},[81,486,487],{"class":106},"2",[81,489,395],{"class":116},[81,491,492],{"class":481}," values_only",[81,494,172],{"class":116},[81,496,497],{"class":94},"False",[81,499,228],{"class":116},[81,501,502,505,507,510,512,515,518],{"class":83,"line":231},[81,503,504],{"class":98},"        label ",[81,506,172],{"class":116},[81,508,509],{"class":98}," row",[81,511,441],{"class":116},[81,513,514],{"class":106},"0",[81,516,517],{"class":116},"].",[81,519,520],{"class":98},"value\n",[81,522,523,526,529,532,535],{"class":83,"line":240},[81,524,525],{"class":94},"        if",[81,527,528],{"class":98}," label ",[81,530,531],{"class":102},"is",[81,533,534],{"class":94}," None",[81,536,132],{"class":116},[81,538,540],{"class":83,"line":539},8,[81,541,542],{"class":94},"            continue\n",[81,544,546],{"class":83,"line":545},9,[81,547,548],{"class":87},"        # 数式セルはPython側で計算\n",[81,550,552,555,557,559,561,564,566],{"class":83,"line":551},10,[81,553,554],{"class":98},"        amount ",[81,556,172],{"class":116},[81,558,509],{"class":98},[81,560,441],{"class":116},[81,562,563],{"class":106},"1",[81,565,517],{"class":116},[81,567,520],{"class":98},[81,569,571,573,576,578,580,582,584,586,588,590,593,595,598,601,604],{"class":83,"line":570},11,[81,572,525],{"class":94},[81,574,575],{"class":98}," amount ",[81,577,531],{"class":102},[81,579,534],{"class":94},[81,581,110],{"class":102},[81,583,509],{"class":98},[81,585,441],{"class":116},[81,587,563],{"class":106},[81,589,517],{"class":116},[81,591,592],{"class":98},"data_type ",[81,594,212],{"class":102},[81,596,597],{"class":420}," '",[81,599,600],{"class":424},"f",[81,602,603],{"class":420},"'",[81,605,132],{"class":116},[81,607,609,612,614,617,619,621,623,625,628,631],{"class":83,"line":608},12,[81,610,611],{"class":98},"            amount ",[81,613,172],{"class":116},[81,615,616],{"class":98}," _evaluate",[81,618,117],{"class":116},[81,620,120],{"class":98},[81,622,441],{"class":116},[81,624,563],{"class":106},[81,626,627],{"class":116},"],",[81,629,630],{"class":98}," values",[81,632,181],{"class":116},[81,634,636,639,641,644,647,650],{"class":83,"line":635},13,[81,637,638],{"class":98},"        values",[81,640,441],{"class":116},[81,642,643],{"class":98},"label",[81,645,646],{"class":116},"]",[81,648,649],{"class":116}," =",[81,651,652],{"class":98}," amount\n",[81,654,656,659],{"class":83,"line":655},14,[81,657,658],{"class":94},"    return",[81,660,661],{"class":98}," values\n",[15,663,664],{},"Excelファイルは「人が開いて確認するための出力物」に徹し、スクリプト間のデータ受け渡しはJSONを経由する。この分離を入れたことで、openpyxlの数式未計算問題を根本的に迂回できた。",[24,666,668],{"id":667},"全4年度でcheck0を達成","全4年度でcheck=0を達成",[15,670,671],{},"CFWS生成後、BS・PL・CFの整合性チェックスクリプトを回した。4年度分すべてでcheck=0（差異なし）を確認。",[15,673,674],{},"チェックの内容:",[39,676,677,680,683],{},[42,678,679],{},"BS: 前期末残高 + 当期増減 = 当期末残高",[42,681,682],{},"PL: 収益合計 - 費用合計 = 当期純利益",[42,684,685],{},"CF: 営業CF + 投資CF + 財務CF = 現金増減",[15,687,688],{},"固定資産で引っかかりやすいのは投資CFの符号だ。取得は支出（マイナス）、売却は収入（プラス）。減価償却費はPLには出るがCFの調整項目として加算される。この符号の整合が4年度分ズレなく通ったことで、中間テーブル関数の計算ロジックが正しいことを確認できた。",[24,690,691],{"id":691},"スキルファイルと進捗文書の更新",[29,693,694],{"id":694},"固定資産スキルファイルの作成",[15,696,697,698,701,702,705],{},"借入金スキル（",[19,699,700],{},"cf-lifecycle-borrowing.md","）の構造を踏襲して、固定資産用のスキルファイル ",[19,703,704],{},"cf-lifecycle-fixed-asset.md"," を作成した。記載内容:",[39,707,708,711,714,717],{},[42,709,710],{},"対象勘定科目の一覧（建物、車両、備品、各累計額、減価償却費）",[42,712,713],{},"Step a / b / c の実行手順",[42,715,716],{},"DUAL_ACCOUNTSの設定値",[42,718,719],{},"固定資産固有の注意点（月割計算、除売却損益の扱い）",[29,721,722],{"id":722},"進捗チェックリストの更新",[15,724,725],{},"全論点の進捗管理表で、固定資産のStep b / Step cを完了に更新した。残りの未完了論点を確認し、次に着手する科目の優先順を整理した。",[24,727,728],{"id":728},"振り返り",[15,730,731,732,735],{},"openpyxlの数式問題で3時間近くハマった。xlcalcを試し、COMを試し、最終的に「Excelに計算させない」という判断に至った。遠回りに見えるが、resolve_rowsパイプラインを入れたことで、スクリプト間のデータフローからExcel依存が消えた。JSONを挟む一手間が、デバッグ時に中間値を ",[19,733,734],{},"jq"," で覗ける利点にもつながっている。",[15,737,738],{},"SUM数式のindent走査ロジックは、借入金や貸付金では露見しなかったエッジケースだった。固定資産のように「有形 / 無形 / 投資その他」と同じindentレベルで合計行が複数並ぶ構造で初めて顕在化した。テストケースの網羅性について、科目の階層パターンをもう少し洗い出しておく必要がある。",[740,741,742],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .sG7-3, html code.shiki .sG7-3{--shiki-default:#393A34;--shiki-dark:#393A34}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}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 .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}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 pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}",{"title":77,"searchDepth":91,"depth":91,"links":744},[745,749,754,760,761,765],{"id":26,"depth":91,"text":27,"children":746},[747,748],{"id":31,"depth":135,"text":31},{"id":56,"depth":135,"text":57},{"id":252,"depth":91,"text":253,"children":750},[751,752,753],{"id":256,"depth":135,"text":257},{"id":263,"depth":135,"text":263},{"id":280,"depth":135,"text":280},{"id":298,"depth":91,"text":298,"children":755},[756,757,758,759],{"id":304,"depth":135,"text":304},{"id":328,"depth":135,"text":329},{"id":343,"depth":135,"text":344},{"id":354,"depth":135,"text":355},{"id":667,"depth":91,"text":668},{"id":691,"depth":91,"text":691,"children":762},[763,764],{"id":694,"depth":135,"text":694},{"id":722,"depth":135,"text":722},{"id":728,"depth":91,"text":728},"dev","固定資産（勘定科目303）の年次推移表とCF精算表を生成し、openpyxl数式問題の回避策・SUM数式の走査ロジック改善・4年度分のcheck=0検証通過までの記録","md",{},true,"/cf-fixed-asset-completion","eurekapu-nuxt4",false,"2026-04-05T00:00:00.000Z",{"title":5,"description":767},"2026-04/2026-04-05/cf-fixed-asset-completion",[778,779,780,781,782,783,784],"CF精算表","固定資産","openpyxl","Python","Excel自動化","年次推移表","CFWS","memo",null,"Op7p4VH8j_JWPbAPuTtc-JEqf9rt5HXKrOTVumOkrd0",[],"https://log.eurekapu.com/favicon.svg",1775511574569]