ホーム / お役立ち記事 / Python で値を書いた Excel の計算結果が古いままの理由|数式のキャッシュを再計算する

Knowledge

Python で​値を​書いた​ Excel の​計算結果が​古いままの​理由|​数式の​キャッシュを​再計算する

Python で既存の Excel に値を書き込むと、その値を使う数式の計算結果が、書く前の数字のまま残ることがあります。数式のセルは最後に計算した結果(キャッシュ)をファイルに持ち、値を書いたプログラムは計算し直さないためです。弊社が切り分けた事例では、約4,000セルが古い値でした。原因の切り分け、見分け方、LibreOffice で計算し直して結果だけを移す直し方、落とし穴、確かめ方を、弊社の実測と公開の仕様でまとめます。

著者: 村山 悠太(ゴムマリ) 最終更新: 2026年11月

Python で既存の Excel ファイルに値を書き込むと、その値を使う合計などの数式の計算結果が、書く前の数字のまま残ることがあります。

原因は、数式のセルが「最後に計算した結果」をファイルの中に持っていて、値を書いたプログラムがそれを計算し直さないことです。この最後の結果を、この記事ではキャッシュと呼びます。

弊社が切り分けた事例では、書き込んだ値の影響範囲の約5,900セルのうち、約4,000セルが書く前の値のままでした。エラーは出ず、もっともらしい古い数字が並んでいました。

直し方は、LibreOffice でファイル全体を計算し直し、元と食い違う計算結果だけを、元のファイルへ移すことです。弊社が試した構成では、部品を元のまま保ち、計算結果だけを LibreOffice が計算し直した値にできました。

この記事は、帳票や管理表の Excel を自動で作る仕組みを任されている業務の担当者・情報システムの担当者に向けて書きます。

原因の切り分け、見分け方、直し方、落とし穴、確かめ方を、弊社の実測と、ECMA-376(Office のファイルの形式の国際規格)・openpyxl・LibreOffice・Microsoft の文書で整理します。

原因: 数式の​セルは、​最後の​計算結果を​ファイルに​持っている

.xlsx の中身は、ZIP にまとめた XML のファイル(パート)の束です。シートの XML では、数式のセルが、数式と一緒に計算結果の値を持っています。

ECMA-376 の Part 1 は、数式を持つセルは、数式の最後の計算結果をこの値の要素(v)に表す、と定めています(18.3.1.96 節)。

<c r="A3"><f>A1+A2</f><v>3</v></c>

上の例では、A1+A2 が数式で、3 が最後に計算した結果です。Excel で保存したファイルの数式のセルには、この値が入っています。

Python の openpyxl は、数式を計算しません。openpyxl の文書は「openpyxl never evaluates formula」と書いています。

openpyxl の data_only=True で読める値は、文書の言葉では「Excel が最後にシートを読んだときに保存された値」です。つまり、ファイルに残っているキャッシュです。

そのため、プログラムで値だけを書き換えると、その値を参照する数式のキャッシュは、誰も計算し直さないまま残ります。

書き方に​よって、​壊れ方が​2つに​分かれる

既存の Excel に値を書く方法は、大きく2つあります。どちらも、数式のキャッシュを正しい値にはしません。

書き方 数式のキャッシュ
openpyxl で開いて保存し直す 空になる
ZIP の中のシートの XML だけを書き換える 書く前の値のまま残る

openpyxl で開いて保存し直すと、数式のセルのキャッシュは空になります。プログラムで読むと、数式のセルは None になります。

弊社が 2026年11月1日に、openpyxl 3.1.5(Python 3.14.6)と合成のファイルで試すと、キャッシュは空の <v></v> になりました。

同じ保存で、ブックの計算の設定には fullCalcOnLoad="1"(開いたときに全部計算し直す印)が付きました。

弊社の以前の版の仕組みは、この書き方でした。記録では、受け取った人が Excel で開いて保存し直すまで、合計の欄が空のままでした。

openpyxl で保存し直すと、チェックボックスなどの部品も落ちます。その件はExcel帳票への自動書き込みでファイルが壊れる理由にまとめています。

部品を守るために、ZIP の中のシートの XML だけを書き換える方式にすると、今度はキャッシュが書く前の値のまま残ります。空なら気づけますが、古い数字は正しく見えます。

Excel で​開けば​計算し直されるとは​限らない

ECMA-376 の Part 1 は、fullCalcOnLoad が真なら、アプリケーションはブックを開くときに全部計算し直す(shall)と定めています(18.2.2 節)。

同じ節は、手動の計算の設定のブックでは、この印があっても開くときに全部は計算し直さない、という注を付けています。

Microsoft の文書は、今の版で作ったブックを開くと、変わったセルに依存する数式だけを計算し直す、と書いています。

古い版で保存したブックを開いたときは、手動の計算の設定の場合を除いて、全部の数式を計算し直す、とも書いています。

外のプログラムが ZIP の中で書いたセルを、Excel が「変わったセル」と見るかは、文書には書かれていません。弊社はこの点を Excel で開いて確かめていません。

もう1つの問題は、ファイルを読むのが Excel だけではないことです。プログラムで読むと、計算はされず、キャッシュがそのまま返ります。

弊社は 2026年11月1日に、合成のファイルで、ZIP の中の値を 1 から 10 に書き換えました。

その後も、openpyxl 3.1.5 の data_only=True と pandas 3.0.5 の read_excel は、合計の古い値を返しました。

弊社の​実測: 書いた​値の​影響範囲の​約7割が​古い値だった

弊社は、お客様の既存の Excel の管理表に、別の資料から作った値を毎月書き込む仕組みを作っています。値は空欄のセルにだけ書き、数式のセルには書きません。

書き込みは、ZIP の中のシートの XML だけを文字列で書き換える方式です。この方式で、書いたセルに依存する数式のキャッシュがどうなるかを数えました。

項目 内容
実測の日 2026年8月
ブック 数式のセル約20万
書いた値 空欄のセル約60
計算し直し LibreOffice で全部(版の番号は記録していない)

結果は次のとおりです。影響範囲は、書いたセルを直接・間接に参照する数式のセルを、数式の参照をたどって数えたものです。数え方は、漏れが出ないよう広めに拾う側に倒しています。

数 内容
約5,900セル 書いた値の影響範囲(数式のセル全体の約2.9%)
約4,000セル 計算し直すと値が変わったセル(同じく約2.0%)

残りのセルは、計算し直しても値が同じでした。値が変わった約4,000セルは、計算し直すまで、書く前のもっともらしい数字を持っていました。

書いたのは約60セルでも、古くなったのは約4,000セルでした。数式のセル全体から見ると約2%で、目で見て探せる数ではありません。

見分け方​: ​空か、​古い​値か

まず、数式のセルのキャッシュが空なのか、古い値なのかで分けます。

見え方 疑う所
プログラムで読むと数式のセルが None 保存し直してキャッシュが空
書いたのに合計が書く前の数字 キャッシュが書く前の値のまま
計算し直すと値が変わるセルがある 上の2つのどちらか(互換の設定の誤りと、実行のたびに変わる関数は除く)

3つ目は、この記事のコードの check で数えられます。ファイルを LibreOffice で全部計算し直し、数式のセルごとに元のキャッシュと比べます。

直し方​: LibreOffice で​計算し直し、​食い​違う​結果だけを​移す

弊社が実際に通した手順は次のとおりです。

  1. ZIP の中で値を書き、ブックの計算の設定に fullCalcOnLoad を立てる
  2. LibreOffice を画面なしで起動し、マクロから calculateAll を呼んで全部計算し直し、別のファイルに保存する
  3. 計算し直したファイルのうち、1 のファイルと食い違う数式のキャッシュだけを、1 のファイルへ移す
  4. fullCalcOnLoad を外す

2 で保存したファイルを、そのまま渡さないのが要点です。LibreOffice の保存は、原本の部品(コメント・印刷設定・変更の履歴など)を保たないためです(弊社の実装の記録)。

1 のファイルは部品を元のまま持っているので、3 で計算結果だけを移すと、「部品は原本のまま・計算結果は最新」が両立します。

calculateAll は、LibreOffice の文書で「recalculates all cells」(全部のセルを計算し直す)とされる命令です。

同じ文書の calculate は、参照先が変わった後にまだ計算していないセルだけを計算し直します。

弊社は、これを Excel の Ctrl+Alt+F9(Microsoft の文書では、開いている全ブックの全セルを計算し直す操作)に相当するものとして使っています。

3 で移さないセルもあります。Excel では値だったのに、LibreOffice では誤り(エラー)になったセルです。これは落とし穴の節で書きます。

次のコードは、弊社の実装から計算し直しと移す部分を縮めたものです。値を書く部分は持たず、値を書いた後のファイルを渡します。

import re
import shutil
import subprocess
import sys
import tempfile
import zipfile
from pathlib import Path

SOFFICE = shutil.which("soffice") or "/Applications/LibreOffice.app/Contents/MacOS/soffice"
TOL = 1e-12  # Excel と LibreOffice の桁の書き方の違いは同じ値とみなす
MACRO = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" \
script:language="StarBasic">
Sub HardRecalc(sIn as String, sOut as String)
  Dim oDoc, aProps(1) as New com.sun.star.beans.PropertyValue
  Dim aSave(0) as New com.sun.star.beans.PropertyValue
  aProps(0).Name = "Hidden" : aProps(0).Value = True
  aProps(1).Name = "UpdateDocMode" : aProps(1).Value = com.sun.star.document.UpdateDocMode.NO_UPDATE
  oDoc = StarDesktop.loadComponentFromURL(ConvertToURL(sIn), "_blank", 0, aProps())
  oDoc.calculateAll()
  aSave(0).Name = "FilterName" : aSave(0).Value = "Calc MS Excel 2007 XML"
  oDoc.storeToURL(ConvertToURL(sOut), aSave())
  oDoc.close(False)
End Sub
</script:module>
"""
XLB = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE library:library PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "library.dtd">
<library:library xmlns:library="http://openoffice.org/2000/library" library:name="Standard" \
library:readonly="false" library:passwordprotected="false">
 <library:element library:name="Module1"/>
</library:library>
"""
XLC = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE library:libraries PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "libraries.dtd">
<library:libraries xmlns:library="http://openoffice.org/2000/library" \
xmlns:xlink="http://www.w3.org/1999/xlink">
 <library:library library:name="Standard" xlink:href="$(USER)/basic/Standard/script.xlb/" \
xlink:type="simple" library:link="false"/>
</library:libraries>
"""
CELL_RE = re.compile(r'<c r="([A-Z]+\d+)"([^>]*?)(/>|>)')
VTAG_RE = re.compile(r"<v\s*/>|<v>(.*?)</v>", re.S)


def lo_recalc(src, dst):
    """LibreOffice で全部のセルを計算し直し(calculateAll)、dst に書き出す。"""
    work = Path(tempfile.mkdtemp(prefix="recalc_"))
    prof = work / "profile"
    subprocess.run([SOFFICE, "--headless", "--norestore", f"-env:UserInstallation=file://{prof}",
                    "--terminate_after_init"], capture_output=True, timeout=900)
    d = prof / "user" / "basic" / "Standard"
    d.mkdir(parents=True, exist_ok=True)
    (d / "Module1.xba").write_text(MACRO, encoding="utf-8")
    (d / "script.xlb").write_text(XLB, encoding="utf-8")
    (d.parent / "script.xlc").write_text(XLC, encoding="utf-8")
    si, so = work / "in.xlsx", work / "out.xlsx"  # マクロの引数は英数字だけのパスで渡す
    shutil.copy2(src, si)
    subprocess.run([SOFFICE, "--headless", "--norestore", f"-env:UserInstallation=file://{prof}",
                    f"macro:///Standard.Module1.HardRecalc({si},{so})"], capture_output=True, timeout=1800)
    if not so.exists():
        raise SystemExit("LibreOffice の計算し直しに失敗しました")
    shutil.move(str(so), dst)
    shutil.rmtree(work, ignore_errors=True)


def sheet_parts(z):
    """シート名 → シートのパート名(パートの番号とシートの並びは一致しないことがある)。"""
    wb = z.read("xl/workbook.xml").decode("utf-8")
    rels = z.read("xl/_rels/workbook.xml.rels").decode("utf-8")
    rid = {}
    for m in re.finditer(r"<Relationship\b[^>]*/>", rels):
        s = m.group(0)
        if "/worksheet" in s:
            t = re.search(r'Target="([^"]+)"', s).group(1)
            rid[re.search(r'Id="([^"]+)"', s).group(1)] = t[1:] if t.startswith("/") else "xl/" + t
    return {re.search(r'name="([^"]*)"', m.group(0)).group(1):
            rid[re.search(r'r:id="([^"]+)"', m.group(0)).group(1)]
            for m in re.finditer(r"<sheet\b[^>]*/>", wb)}


def formula_cells(xml):
    """数式のセルごとに (番地, t属性, キャッシュの値, <v>の位置, 置き換え方, セルの始まり, 終わり)。"""
    for m in CELL_RE.finditer(xml):
        if m.group(3) == "/>":
            continue
        ce = xml.index("</c>", m.end())
        inner = xml[m.end():ce]
        if "<f" not in inner:
            continue
        t = re.search(r'\bt="([^"]*)"', m.group(2))
        vm = VTAG_RE.search(inner)
        if vm is None:
            cur, span, mode = None, None, None
        elif vm.group(1) is None:
            cur, span, mode = "", (m.end() + vm.start(), m.end() + vm.end()), "element"
        else:
            cur, span, mode = vm.group(1), (m.end() + vm.start(1), m.end() + vm.end(1)), "text"
        yield m.group(1), t.group(1) if t else "", cur, span, mode, m.start(), ce + 4


def cached(path):
    with zipfile.ZipFile(path) as z:
        return {name: {r: (t, v) for r, t, v, *_ in formula_cells(z.read(p).decode("utf-8"))}
                for name, p in sheet_parts(z).items()}


def norm_t(t):
    return "n" if t in ("", "n") else t  # Excel は数値の t を省き、LibreOffice は t="n" と書く


def same(a, b):
    (ta, va), (tb, vb) = a, b
    if va is None or vb is None:
        return va == vb and norm_t(ta) == norm_t(tb)
    if norm_t(ta) != norm_t(tb):
        return False
    if norm_t(ta) == "n":
        try:
            x, y = float(va), float(vb)
        except ValueError:
            return va == vb
        return x == y or abs(x - y) <= TOL * max(abs(x), abs(y))
    return va == vb


def copy_zip(zin, out, replaced):
    """差し替えたパート以外は、元のバイトと順序のまま詰め直す。"""
    with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as zo:
        for info in zin.infolist():
            ni = zipfile.ZipInfo(info.filename, date_time=info.date_time)
            ni.compress_type, ni.external_attr = info.compress_type, info.external_attr
            zo.writestr(ni, replaced.get(info.filename) or zin.read(info.filename))


def fix(src, out):
    work = Path(tempfile.mkdtemp(prefix="cache_"))
    p1, p2 = work / "p1.xlsx", work / "p2.xlsx"
    with zipfile.ZipFile(src) as z:  # 1. 開いたら全部計算し直す印を立てる
        wb = z.read("xl/workbook.xml").decode("utf-8")
        m = re.search(r"<calcPr\b[^>]*/>", wb)
        if m and "fullCalcOnLoad" not in m.group(0):
            wb = wb[:m.start()] + m.group(0)[:-2] + ' fullCalcOnLoad="1"/>' + wb[m.end():]
        elif not m:
            wb = wb.replace("</workbook>", '<calcPr calcId="191029" fullCalcOnLoad="1"/></workbook>')
        copy_zip(z, p1, {"xl/workbook.xml": wb.encode("utf-8")})
    lo_recalc(p1, p2)  # 2. LibreOffice で全部計算し直す
    new = cached(p2)
    z = zipfile.ZipFile(p1)  # 3. 食い違うキャッシュだけを p1 へ移す
    replaced, updated, blocked = {}, 0, []
    for name, part in sheet_parts(z).items():
        xml = z.read(part).decode("utf-8")
        edits = []
        for ref, t, cur, span, mode, cs, ce in formula_cells(xml):
            nt, nv = new.get(name, {}).get(ref, (None, None))
            if nv is None or same((t, cur), (nt, nv)):
                continue
            if nt == "e" and t != "e":  # LibreOffice だけが誤りを返したセルは移さない
                blocked.append((name, ref, cur, nv))
                continue
            updated += 1
            if mode is None:
                seg = xml[cs:ce]
                fend = cs + seg.index("</f>") + 4 if "</f>" in seg else cs + re.search(r"<f[^>]*/>", seg).end()
                edits.append((fend, fend, f"<v>{nv}</v>"))
            elif mode == "element":
                edits.append((span[0], span[1], f"<v>{nv}</v>"))
            else:
                edits.append((span[0], span[1], nv))
            if norm_t(nt) != norm_t(t):  # 数値・文字列・誤りの型が変わったときだけ t を直す
                he = xml.index(">", cs)
                head = re.sub(r'\s+t="[^"]*"', "", xml[cs:he]) + (f' t="{nt}"' if nt else "")
                edits.append((cs, he, head))
        if edits:
            buf, last = [], 0
            for s, e, txt in sorted(edits, key=lambda x: (x[0], x[1])):
                buf += [xml[last:s], txt]
                last = e
            replaced[part] = ("".join(buf) + xml[last:]).encode("utf-8")
    wb = z.read("xl/workbook.xml").decode("utf-8").replace(' fullCalcOnLoad="1"', "")
    replaced["xl/workbook.xml"] = wb.encode("utf-8")  # 4. 印を外す
    copy_zip(z, out, replaced)
    z.close()
    shutil.rmtree(work, ignore_errors=True)
    print(f"更新したキャッシュ {updated}セル / LibreOffice だけが誤りを返して移さなかったセル {len(blocked)}")
    for b in blocked:
        print("  移さなかった:", *b)
    return 0


def check(path):
    work = Path(tempfile.mkdtemp(prefix="check_"))
    lo_recalc(path, work / "re.xlsx")
    new, cur = cached(work / "re.xlsx"), cached(path)
    shutil.rmtree(work, ignore_errors=True)
    diff = [(s, r, a, new[s][r]) for s, cells in cur.items() for r, a in cells.items()
            if r in new.get(s, {}) and new[s][r][1] is not None and not same(a, new[s][r])]
    print("計算し直すと変わるキャッシュ:", len(diff))
    for d in diff[:20]:
        print("  ", *d)
    return 1 if diff else 0


if __name__ == "__main__":
    mode = sys.argv[1]
    sys.exit(fix(sys.argv[2], sys.argv[3]) if mode == "fix" else check(sys.argv[2]))

使い方は python recalc_cache.py fix 値を書いた.xlsx 出力.xlsx と、python recalc_cache.py check 出力.xlsx(食い違いがあれば終了コード1)です。

fix の、食い違いの判定・誤りのセルを移さない条件・移し方は、弊社の実装と同じです。足したのは、計算し直すと変わるセルを数える check です。

弊社は 2026年11月1日に、LibreOffice 26.2.4.2・Python 3.14.6・合成のファイルで、このコードを確かめました。

確かめたのは、check が古いキャッシュを見つけること、fix の後にキャッシュが正しい値になり check が0になること、シートのパート以外がバイト同一のままであることです。

同じ LibreOffice の出力を、このコードと弊社の実装の両方に与え、出力が全パートで同じになることも確かめました(誤りを返すセルを入れた版を含む)。

弊社の実装には、このコードに無い工程があります。値を書く前のファイルを計算し直して基準を取る工程と、確かめ方の節の4つの検査です。

落とし穴: 実際に​踏んだ​もの

  • LibreOffice の変換の命令では、全部は計算し直されなかったsoffice --convert-to に fullCalcOnLoad を付けたファイルを渡すと、2026年8月の実測では約40セルが古いまま残りました。理由は切り分けていません。
  • LibreOffice だけが誤りを返すセルがあった書く前のファイルを計算し直すと、Lotus 1-2-3 との互換の設定が付いたシートで、約30セルが LibreOffice だけ誤りになりました(2026年8月)。
  • 実行のたびに値が変わりうる関数があるTODAY などの関数を使う数セルは、書き込みと関係なく値が変わりました。食い違いとして数えるときは、別に分けます。
  • LibreOffice で保存したファイルを渡すと、部品が落ちる計算し直したファイルは、計算結果の材料にだけ使い、渡すのは元の部品を持つファイルにします。

2026年11月1日に、LibreOffice 26.2.4.2 と合成の小さなファイルでも変換の命令を試しました。確かめた3セルのキャッシュは、変換の後も変わりませんでした。

マクロから calculateAll を呼ぶ経路では、同じ合成のファイルのキャッシュは全部正しい値になりました。弊社は、成果物を作る工程では変換の命令を使いません。

2つ目の件で、transitionEvaluation はシートの設定です。ECMA-376 の Part 1 は、Lotus との互換の評価の設定が有効かを表す、とだけ定めています(18.3.1.82 節)。

弊社の実装は、この誤りのセルを移さず、元の値を残します。移さなかったセルが書いた値の影響範囲に入っていたら、出力を作らずに止めます。

LibreOffice をプログラムから動かすときの注意も2つあります。どちらも弊社の実装の記録にあるものです。

  • マクロの引数はカンマで区切って渡るので、日本語・空白・カンマを含むパスは直接渡さず、英数字だけの一時の場所へ写してから渡す
  • 新しい作業用のプロファイルは、LibreOffice が最初の起動で作り直すので、先に1回起動させてからマクロを置く(順を逆にすると消される)

確かめ方​: 計算し直しても​変わらない​ことと、​部品が​元のままである​こと

弊社の実装は、出力を作る前に次の4つを確かめ、全部に合格したときだけ出力を作ります。不合格なら、途中の状態のファイルを渡しません。

  1. 部品消えた・足した部品が0で、シート以外の部品は元のファイルと中身が一致(CRC で比べる)
  2. セルの差分全シートの全セルの差分が、書いたセルと、計算し直して移したキャッシュだけ
  3. セルの数値の入ったセルの数が、元のファイルの数・書いた数・新しくキャッシュを入れた数の和に一致
  4. 計算し直し書く前のファイルを計算し直したときに、既知の2種類(Lotus 互換の誤りと、実行のたびに値が変わりうる関数)以外の食い違いが0で、移さなかったセルが影響範囲に無い

短い時間で直ったかを見るなら、出力を check に通します。計算し直しても変わるキャッシュが0なら、キャッシュは LibreOffice が今の値で計算した結果と合っています。

check は、Lotus 互換の誤りと日付の関数を分けずに、全部を数えて出します。0にならないときは、出たセルがその2種類かを見ます。

部品が消えていないかの検査は、Excel帳票への自動書き込みでファイルが壊れる理由の5つの検査で見ます。

ただし、この記事の直し方はシートのパートを書き換えるので、その記事の「パートの差分」は、シート以外の部品で読み替えます。この記事の検査が見るのは、数式のキャッシュの値です。

同じ困りごとへの別の直し方として、ひな形の数式のセルから計算結果(v 要素)を取り除く方法も、その記事に書いています。取り除いた場合、プログラムで読むと値は空になります(上の見分け方の表の1行目)。

Excel で開くと修復を求められないかは、また別の層です。その確かめ方はPython で書き出した Excel が「修復」を求める理由にまとめています。

自力で​どこまで​やれるか

書くセルが少なく、それを参照する数式も少なくて、どれかを把握できているなら、自力で足ります。その数式の結果を、書いた値で計算し直して突き合わせる工程を足します。

自力の壁は次の3つです。

  • 書いたセルに依存する数式が、シートをまたいで数千セルに広がり、目では確かめきれない
  • 計算し直す工程(LibreOffice の起動・マクロ・作業用のプロファイル)を、毎回の書き込みに組み込む必要がある
  • LibreOffice と Excel で答えが違うセルを見つけ、移さずに止める判定が要る

外注する​場合の​判断基準

外注の分かれ目は、値を書くたびに、数式の計算結果まで正しいことを確かめる工程を、自社で持ち続けられるかです。外注するなら、見積もりを比べる前に次の3点を確かめてください。

  • 値を書いた後に、数式の計算結果を計算し直す工程が入っているか
  • 「書いたセルの値が合っている」だけで完成としていないか
  • 計算し直せなかったセルや、Excel と答えが違うセルが出たときに、出力を止める作りになっているか

ゴムマリでは、既存の Excel の様式へ書き出す業務を含め、1つの業務を回す AI エージェントを御社の業務に合わせて作る仕事を、AIエージェント導入として承ります。決めた確かめを通らない出力は、出さずに止める作りにします。

お渡しするのは、その業務の AI エージェント、判断の基準を書いた文書、使い方の手順です。料金は1つの業務ごとに初期50万円から、月額5万円から、初期構築は4週間からです。

月額には、出す前の確かめを保ち、足していくことを含みます。進め方は相談からご連絡ください。

次の​一歩

プログラムで値を書いている Excel を1本選び、書いたセルを参照する合計の欄を1つ見てください。書いた値で計算し直した数字と違えば、この記事の原因です。

数が多くて見きれないときは、この記事のコードの check に通します。計算し直すと変わるキャッシュが出たら、fix を通した版でもう一度 check が0になるかを確かめます。

帳票を自動で作るときに、数字が正しいことを仕組みで確かめる方法は、既存Excel様式を自動生成するときの品質保証にまとめています。

関連記事: Excel帳票への自動書き込みでファイルが壊れる理由/Python で書き出した Excel が「修復」を求める理由/既存Excel様式を自動生成するときの品質保証

この記事を書いた人

代表 村山悠太のポートレート

村山 悠太(ゴムマリ代表)

ワークスアプリケーションズで大手企業向けERPの営業と導入を担当し、外資系SaaS企業と国内大手放送局で販路と新規事業の立ち上げ、AI学習用データを扱うAIスタートアップ2社でマーケティングと営業を担った。2024年10月から生成AIの実装に入り、いまは大手企業の現場で、AIエージェントを要件定義から運用まで手がけている。

代表の経歴を見る

FAQ

よく​ある​質問

openpyxl で、数式を計算させることはできますか?

できません。openpyxl の文書は、openpyxl は数式を計算しない、と書いています。data_only=True で読めるのは、ファイルに残っている最後の計算結果です。計算し直すには、Excel か LibreOffice のような、計算できるソフトが要ります。弊社は LibreOffice を使っています。

Excel で開けば、古い計算結果は直りますか?

Microsoft の文書は、今の版で作ったブックを開くと、変わったセルに依存する数式だけを計算し直す、と書いています。外のプログラムが書いたセルを Excel が「変わった」と見るかは書かれておらず、弊社は Excel で開いて確かめていません。

fullCalcOnLoad を付けておけば十分ですか?

弊社が試した構成では、十分ではありませんでした。仕様では、開くときに全部計算し直す印です。ただし、プログラムで読む側は計算しないので、古い値か空を読みます。LibreOffice の変換の命令(--convert-to)も、印を付けたファイルを全部は計算し直しませんでした。

LibreOffice で計算し直した結果は、Excel と同じになりますか?

弊社が切り分けた事例では、ほとんどのセルで同じでした。違ったのは、Lotus 1-2-3 との互換の設定が付いたシートの約30セルと、日付の関数の数セルです。どのブックでも同じとは限らないので、書く前のファイルで計算し直して、元の結果と食い違わないかを先に確かめます。

どのセルが古いかを、Excel を開かずに見分けられますか?

見分けられます。ファイルを LibreOffice で全部計算し直し、数式のセルごとに、元のキャッシュと計算し直した結果を比べます。食い違うセルが、古い値のセルです(Lotus 1-2-3 との互換の設定で誤りになるセルと、実行のたびに値が変わりうる関数のセルは除きます)。この記事のコードの check は、この比べ方で数えます。

Related

関連

ご相談は​こちらから

相談は無料です。

相談する