CF精算表パイプラインの大幅強化
朝、年次推移表のExcelを開いて目が止まった。セルをクリックしても数式バーに数値しか表示されない。Pythonで計算した値をベタ打ちしているから当然だが、「この数字はどこから来たのか」をExcel上で追えない。教材として渡すファイルがこれでは、開いた瞬間にブラックボックスに見える。年次推移表をSUMIFS数式に全面置換する作業を起点に、共通モジュール化と5論点への横展開まで一気に走った一日だった。
301借入金に営業経費シートを追加
顧問料と家賃 --- 未払費用と前払費用の対比
借入金の精算表に営業経費シートを追加した。月次の固定費として税理士顧問料33,000円と家賃110,000円を計上する。
この2つを同じシートに並べたのは、経過勘定の対比を見せるためだ。顧問料は後払いなので、役務提供を受けた月に費用が発生し、翌月の支払時までは「未払費用」になる。家賃は前払いなので、支払月に現金が出ていき、翌月の費用発生まで「前払費用」として資産に載る。同じ固定費でもキャッシュフローへの影響タイミングが逆になる。1シートに並べることで、BS科目がどう動くかを追える教材になった。
現金売上と仕入の追加
営業経費だけを追加すると、PLが費用のみで赤字になり、普通預金残高がマイナスに沈む。ファイルを開いたとき「この会社は何で稼いでいるのか」が見えない。教材として不自然だった。
現金売上と現金仕入のシートを追加して解消した。ここでCodexが指摘を入れてきた。generate_daily_purchases 関数が在庫バッファを持つ設計になっており、仕入量が売上量を上回る期間が生じる。教材にはゼロ在庫(仕入=売上原価)のモデルが適切なので、在庫バッファのロジックを削除した。
中間テーブルSUMIFSの簡素化
J列(勘定科目フィルタ)が不要だった
中間テーブルにはSUMIFS数式が並んでおり、J列に勘定科目名のフィルタ条件を持たせていた。しかしデータ構造を見直すと、各行は既に特定の勘定科目に対応している。フィルタ条件が冗長だった。
J列を削除すると、SUMIFS数式から条件が1つ減り、数式の長さが半分近くになった。数式バーを見ただけで何を集計しているか読み取れるようになった。
合計テーブルの新設と撤回
中間テーブルの集計結果をさらに合計テーブルにまとめる案を実装した。しかし動かしてみると、中間テーブルと合計テーブルで同じ数値を二重に持つだけで、CFWSから参照するのは結局どちらか一方だけだった。冗長なレイヤーを一枚剥がして合計テーブルを削除した。
合計テーブル廃止に伴い、利息のINDEX/MATCH数式のスコープを中間テーブルの該当範囲に限定し直した。
年次推移表のSUMIFS化
ここが今日の最大の作業だった。
構造の変更
ベタ打ちの値を消し、仕訳シートを参照するSUMIFS数式に全面置換した。年次推移表の各セルが「仕訳シートの取引日がFrom〜Toの範囲で、かつ勘定科目が一致する行の金額を合計する」数式になる。
追加した要素:
- From/To行: 各年度列のヘッダ直下にFrom日付とTo日付を配置。SUMIFS数式の日付範囲条件がこの行を参照する
- BS科目のFrom固定: PL科目のFromは期首日(4/1)だが、BS科目のFromは設立日に固定。BS残高は設立以来の累積だから、期首リセットしない
仕訳シートの日付がテキスト文字列だった
SUMIFS数式を入れて再計算させると、全セルが0を返した。数式の構文は正しいのに、SUMIFSが1行もヒットしない。
仕訳シートの取引日列をクリックすると、セルの表示形式が「文字列」になっていた。Pythonで仕訳データを書き込む際、日付をISO文字列("2024-04-01")としてセルに入れていた。SUMIFSの日付比較は数値型のシリアル値を期待するため、文字列のままでは条件が一致しない。
# Before: 文字列として書き込み
ws.cell(row=r, column=1, value="2024-04-01")
# After: date型に変換して書き込み
from datetime import date
ws.cell(row=r, column=1, value=date(2024, 4, 1))
ws.cell(row=r, column=1).number_format = "YYYY-MM-DD"
date型に変換して書き込むようにしたところ、SUMIFSが正しく集計を始めた。表示上は同じ 2024-04-01 でも、Excelの内部表現が文字列と数値で異なる。
ACCOUNT_DBのキー名が'sign'だった
年次推移表のSUMIFS数式で借方・貸方の符号を正しく反映するには、ACCOUNT_DBから勘定科目の正常残高方向を取得する必要がある。コード上で account['normal'] と書いて KeyError が出た。
ACCOUNT_DBの定義を開いて確認すると、キー名は 'normal' ではなく 'sign' だった。過去のどこかでリネームされたまま、年次推移表の新コードが旧名を参照していた。
# Before: KeyError
sign = ACCOUNT_DB[account_name]['normal']
# After: 正しいキー名
sign = ACCOUNT_DB[account_name]['sign']
補助科目の統合
二重シート問題
これまで年次推移表には2種類のシートがあった。「年次推移表」(補助科目なし)と「年次推移表_補助科目あり」の2枚だ。両方に同じ親科目の数値が入っており、メンテナンスコストが二重に発生していた。
補助科目ありシートを廃止し、アンダースコア命名(未払費用_未払利息、未払費用_未払営業経費)で1シートに統合した。親科目の行には子科目のSUM数式を入れ、展開しなくても合計値が見える構造にした。
xlsx_helpers.py共通モジュール化
Phase 1: 5スクリプトから共通関数を抽出
借入金・株主資本・固定資産・運転資本・貸付金の5つの生成スクリプトに、同じセル書き込み関数やフォーマット設定関数が散在していた。
xlsx_helpers.py を新設し、共通関数を集約した。対象は以下の5スクリプト:
gen_301_borrowing.pygen_302_equity.pygen_303_fixed_asset.pygen_304_working_capital.pygen_305_lending.py
各スクリプトから write_header_row, apply_number_format, set_column_width などの関数を抽出し、importに切り替えた。
Phase 2: auto_fit_columnsの移動と改善
列幅を自動調整する auto_fit_columns 関数が、数式セルの文字列長(=SUMIFS(...) のような長い文字列)をそのまま幅計算に含めていた。数式が長い列だけ異常に幅が広がる。
数式セルを幅計算から除外し、最大幅を20文字にキャップして修正した。この関数も xlsx_helpers.py に移動し、全スクリプトから共通利用する形にした。
def auto_fit_columns(ws, max_width=20):
for col in ws.columns:
lengths = []
for cell in col:
if cell.data_type == 'f': # 数式セルは除外
continue
if cell.value is not None:
lengths.append(len(str(cell.value)))
if lengths:
width = min(max(lengths) + 2, max_width)
ws.column_dimensions[col[0].column_letter].width = width
5論点への横展開
SUMIFS化と共通モジュール化を301借入金で確立した後、残り4論点(302株主資本、303固定資産、304運転資本、305貸付金)に同じ変更を適用した。
各論点で固有の対応が必要だった箇所:
- 303固定資産: パソコン(工具器具備品)を資産として追加。償却計算表に1行増える
- 302株主資本: BS科目のFrom日付を設立日に固定する処理が、他の論点と微妙に異なる(資本金は増減がないためSUMIFSではなく固定値参照にした)
5論点すべてで年次推移表のSUMIFS化とxlsx_helpers.pyへの移行が完了した。
Excel生成ルールの整備
横展開の過程で、スクリプト間でフォーマットの揺れが目についた。ルールとして明文化した主な項目:
- 合計列にはSUM関数を必ず入れる: ベタ打ちの合計値は禁止。数式で集計することで検算が自動化される
- A列スペーサーは新規シートのみ: 既存シートにA列を挿入すると、他のシートからの参照がズレる
- auto_fit_columnsの最大幅20、数式除外: 前述のルール
その他の整備
「当期」表示から実年度名に統一
年次推移表の列ヘッダが「前々期」「前期」「当期」という相対表記だった。ファイルを翌年に開いたとき「当期」がどの年度を指すのか分からなくなる。「2022年度」「2023年度」「2024年度」のような実年度名に統一した。
IBフォーマット適用
全シートにIB(投資銀行)フォーマットのカラーリングルールを適用した。入力セルは青字、参照セルは黒字、ハードコードは緑字。セルの性質が色で区別できる。
命名規則違反の修正
一部のスクリプトやシート名が CF_xxx プレフィックスを使っていた。現行の命名規則は cf_xxx(小文字)統一なので、grepで CF_ を洗い出し全箇所を cf_ に置換した。
振り返り
SUMIFSの日付型不一致で全セルが0を返したとき、数式の構文ばかりを疑って30分近く空転した。セルをダブルクリックして参照先のハイライトを確認し、「条件は合っているのにヒットしない」と首をひねった。仕訳シートの日付列を右クリックして「セルの書式設定」を開いた瞬間、「文字列」の表示が目に飛び込んできて原因がつながった。見た目が同じでも内部表現が違う --- この落とし穴を踏んだことで、以後のスクリプトではdate型で書き込むことを徹底するようになった。
ACCOUNT_DBの 'sign' キー問題も似た構造だ。「あるはずのキーがない」というエラーメッセージだけ見て新規追加しかけた。定義ファイルを開いてキー名の一覧を目で確認したら、リネームされた痕跡がそのまま残っていた。辞書のキー名を変更したら、参照側を全文検索で洗い出さないと漏れる。
xlsx_helpers.pyへの共通化で、5スクリプトに散らばっていた同一コードが1箇所に集まった。auto_fit_columnsの数式幅問題のような修正が、1回の変更で全論点に行き渡る。「同じ関数を5回コピペしている」と気づいた時点でもっと早く手を動かせたはずだ、という反省が残る。