0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

anydocで変換した提案書をAIに渡す前のPython検査

0
Posted at

実は逆です。Office資料をMarkdownへ変換しやすくなるほど、要約前の確認は省けません。

8月5日にFirecrawlが公開したanydocは、Office資料をMarkdownへ変換できるRust製ライブラリです。提案書、旧版の見積書、顧客から届いたExcelを同じ入口で扱えるのは、営業企画では助かります。

ただ、変換結果がMarkdownになっていることと、要約に必要な情報が残っていることは別です。表の金額、納期、検収条件のどれかが落ちても、生成された要約は妙に自然な文章で返ります。自然だから見落とす。ここが厄介でした。

僕なら、変換直後に「この資料で絶対に落としてはいけない語」を機械的に確認してから、要約やCRM登録へ流します。Markdown を信用する前に、今回の検査を一段挟みます。

変換の成否と、業務で使えるかは別に見る

anydocのREADMEには、変換できなかった画像だけのPDFや暗号化ファイルをエラーとして扱う説明があります。まずこのエラーを記録して、変換できたファイルだけを次へ進めます。

その次に見るのが内容です。たとえば提案書なら、顧客名、見積金額、納期。料金表なら表そのもの。全部を人が読み返すのではなく、資料ごとに最低限の条件をJSONへ置きます。

文字数だけでは足りません。巻末の免責事項で文字数を満たしても、本文の金額が消えていれば困るからです。見出し数とMarkdown表の数も見ます。要約前の足切りとしては十分に効きます。

先に検査条件を書く

checks.json はこんな形です。必須語は、要約結果に欲しい語ではなく、元資料から消えたら困る語にします。

[
  {
    "file": "proposal.md",
    "min_chars": 80,
    "min_headings": 2,
    "min_tables": 1,
    "required": ["北関東物流", "1280000円", "2026年9月30日"]
  }
]

金額を「1280000円」と決め打ちにするのは、正式版の見積書を取り込むときだけです。毎月更新される料金表なら、見積金額 のような見出しを必須語にして、金額の妥当性は別の処理で確かめます。ここを一つの万能ルールにしない方が、後から困りません。

Markdownを止めるPython

標準ライブラリだけで動きます。変換済みのMarkdownとJSONを置き、次のコードを check_markdown.py として保存します。

import argparse
import json
import re
import sys
from pathlib import Path

def count_tables(markdown: str) -> int:
    return sum(
        1
        for line in markdown.splitlines()
        if re.match(r"^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$", line)
    )

def inspect(item: dict) -> list[str]:
    path = Path(item["file"])
    if not path.is_file():
        return ["Markdownファイルが見つかりません"]

    markdown = path.read_text(encoding="utf-8")
    compact = re.sub(r"\s+", "", markdown)
    headings = len(re.findall(r"(?m)^#{1,6}\s+", markdown))
    tables = count_tables(markdown)
    errors = []

    min_chars = item.get("min_chars", 1)
    min_headings = item.get("min_headings", 0)
    min_tables = item.get("min_tables", 0)
    if len(compact) < min_chars:
        errors.append(f"文字数不足: {len(compact)} < {min_chars}")
    if headings < min_headings:
        errors.append(f"見出し不足: {headings} < {min_headings}")
    if tables < min_tables:
        errors.append(f"表不足: {tables} < {min_tables}")

    for word in item.get("required", []):
        if word not in markdown:
            errors.append(f"必須語がありません: {word}")
    return errors

parser = argparse.ArgumentParser()
parser.add_argument("manifest", help="変換後Markdownの検査条件JSON")
args = parser.parse_args()

items = json.loads(Path(args.manifest).read_text(encoding="utf-8"))
failed = False
for item in items:
    errors = inspect(item)
    name = item["file"]
    if errors:
        failed = True
        print(f"NG  {name}")
        print("\n".join(f"    - {error}" for error in errors))
    else:
        print(f"OK  {name}")

sys.exit(1 if failed else 0)

実行はこれだけです。

python3 check_markdown.py checks.json

手元のサンプルでは、こう出ました。

OK  proposal.md

checks.json に存在しない 検収条件 を一語足すと、終了コードは1になり、出力はこう変わります。

NG  proposal.md
    - 必須語がありません: 検収条件

この時点では要約APIを呼びません。元のdocxかPDFを開いて、変換に失敗したのか、そもそも資料に書かれていないのかを確認します。昨日、必須語を一つ足しただけでこのNGが出たとき、変換結果を目で斜め読みする運用よりずっと安心できました。ここ地味に効きます。

表があるから正しい、ではない

このコードは表の罫線を数えるだけです。セル結合で列がずれた、数式の表示値だけが入った、といった内容までは判定しません。

金額や日付を自動で使うなら、検査に通ったMarkdownをCSVやJSONへ抜き出し、元ファイルの該当セルと突き合わせる工程を分けます。全部を一つのスクリプトへ詰めると、ルール変更のたびに壊れます。提案書の受付では必須語と見出し、料金表では列名と行数、という具合に小さく増やす方が現場では続きます。

現場では変換の後ろに置く

資料変換が速くなった分、AIに渡すファイルは増えます。そこで必要なのは、変換ツールを増やすことより、止める場所を決めることです。

この検査を通ったMarkdownだけを要約やCRM登録へ渡せば、欠けた資料をもっともらしい文章にしてしまう事故を減らせます。まずは失うと困る語を三つだけ書く。営業企画の自動化では、その三つを先に守る方が、プロンプトを長くするより成果につながります。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?