0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Power Queryで仕上げていた定期レポートをExcel・PPTの生成までDatabricks JOBに移した話

0
Posted at

はじめに

定期レポートの集計を行うDatabricks JOBは動いているのに、最後のExcel更新とPPT作成だけは人がやっていました。そこで、案件の識別子を渡すと完成したExcelとPPTがストレージに出るところまで、Databricks JOBに移しました。難しかったのはファイル生成より、元のレポートと同じものを納品できると確かめることでした。

対象読者: ExcelとPPTで定期レポートを作成しているデータアナリスト。特に、集計の自動化はできているが、納品ファイルへの仕上げが手作業で残っている人

以下は複数の定型レポートで行ったことを、架空の1案件にまとめ直したものです。実際の顧客、レポート名、データ、ファイル構成は載せていません。

参考文献

CSVまで自動でも、レポートは完成していなかった

元の流れは、Databricks JOBが集計結果をCSVに出し、担当者がExcelを開いてPower Queryを更新し、数式とグラフを確認してからPPTを仕上げるというものです。CSVは正しくても、納品ファイルはまだありません。

この工程は単純な「CSVをExcelに変換する」作業でもありませんでした。Power Queryが列を整え、Excel側の数式や書式が表を作り、PPTにはExcelから作った図を配置します。処理を移すなら、この役割をそれぞれ把握する必要があります。

image.png

最初に対象を1つの定型レポートに絞りました。すべての帳票を同時に移すと、集計値が違うのか、Excelの書式が違うのか、PPTの配置が違うのかを切り分けにくくなります。

Excelは新規作成せず、テンプレートの対象XMLを更新した

一番慎重になったのはExcelです。既存テンプレートにはグラフ、図形、条件付き書式、数式が入っていました。はじめはopenpyxlで開いて値を入れ、保存する素直な方法を試しましたが、このテンプレートでは一部の図形などが失われました。コードは正常終了するので、開くまで気づきません。

そこで、.xlsxをZIPとして開き、値を入れるワークシートなど変更が必要なパーツだけを更新しました。残りのパーツは、読み出した内容をそのまま新しいZIPへ書き戻します。

.xlsxの構造は以前の「Excelの実体って結局何なのか?」で、既存テンプレートを壊さない編集と検証は「なぜopenpyxlではなくXML直接編集が良いのか?」で扱いました。ここでは、XML編集をレポート生成JOBの一工程としてどう使ったかを書きます。

patch_ooxml.py
from pathlib import Path
from zipfile import ZipFile


def replace_parts(template: Path, output: Path, patches: dict[str, bytes]) -> None:
    """既存のOOXMLパーツを差し替える。新しいパーツの追加は扱わない。"""
    with ZipFile(template, "r") as source, ZipFile(output, "w") as target:
        members = source.infolist()
        names = {member.filename for member in members}
        missing = set(patches) - names
        if missing:
            raise ValueError(f"テンプレートにないパーツ: {sorted(missing)}")

        for member in members:
            data = patches.get(member.filename)
            if data is None:
                data = source.read(member.filename)
            target.writestr(member, data)

これはZIPのパーツを差し替える部分だけのコードです。実際のセル更新では、対象セルが想定どおり1件見つかることを確認し、文字列はinlineStr、数値は数値セルとして書きました。数式の結果は古いキャッシュが残るため、Excelを開いたときの再計算も設定します。XML編集の具体的なコードと落とし穴は、上にリンクした記事に譲ります。

image.png

「他のパーツはそのまま」といっても、ZIPファイル全体のバイト列が元と同一になるわけではありません。比較するのは、解凍した各パーツの内容です。また、シートや画像を新たに追加する場合は、XML本体だけでなく.relsと[Content_Types].xmlの参照関係も更新する必要があります。

PPTは画像だけでなく、画像への参照も追った

PPTも既存テンプレートを活かしました。集計結果から図を描画して差し替える際、スライド番号から画像ファイル名を決め打ちすると、テンプレートの版が変わったときに別の画像を置換しかねません。

.pptx内のスライドと画像の関係は、ppt/slides/_rels/slideN.xml.relsに記録されています。差し替え対象の画像をこの参照から特定し、必要なスライドXMLと画像パーツを更新しました。元画像と新画像の形式が違うときは、画像の参照先やコンテンツタイプも合わせて直します。Excelと同じく、テンプレートにあるものをどこまで残すかが設計の中心でした。

図を入れ替えた後は、ファイルを開けることと、文字・凡例・余白が見本どおりに見えることを別々に確認します。PPTXが正常なZIPでも、図の縦横比が変わって枠からはみ出せば納品物としては失敗です。

移行判定は「数値」と「開いた画面」を分けた

検証には、すでに提出済みの案件と同じ条件を使いました。旧Power Query版とJOB生成版を並べ、まず表の人数や合計値を突き合わせます。次にExcelとPPTを実際に開き、表示を確認します。

確認するもの 見る理由
集計値・合計値 データの絞り込み、結合、分母が旧版と合うか
Excel内の部品とXML グラフや図形が落ちていないか、参照が壊れていないか
Excelの再計算後のセル 数式エラーや古いキャッシュ値が残らないか
PPTを画像化した各ページ 図の位置、凡例、桁数、余白が見本と合うか

実際に、構成比の値は0.213なのに、セルの表示形式が整数になっていて画面では「0」と出たことがありました。元データとグラフは正しく、Excelを開いて初めて気づく種類の不具合です。「数値が一致したからOK」で止めていたら見落としていました。

移行直後は旧手順も並行して動かし、案件ごとに結果を突き合わせました。検証期間を置いたことで、生成処理のバグと、もともとのテンプレート側の問題も分けて見られました。

自動化後も、例外の確認は残した

定型の案件なら、JOBの入力からExcel・PPTの出力までをつなげられました。一方、案件ごとに変わる注釈や、元の依頼と納品物が一致しているかは、完成ファイルを開いて確認しています。自動生成できることと、内容を確認せず渡せることは別です。

今回いちばん時間を使ったのは、Pythonでファイルを作るコードそのものより、旧版のどの値・どの見た目を正解とするか決める作業でした。レポートの自動化は、最後のファイルまで作って、開いた画面で旧版と比べて初めて終わる。CSVで止めていた頃には、その境界が見えていませんでした。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?