Claude Media
検査成績書とミルシートのPDFを規格値と突合する手順 — 合否一覧をClaudeで作る

検査成績書とミルシートのPDFを規格値と突合する手順 — 合否一覧をClaudeで作る

仕入先ごとに様式が違う検査成績書(COA)やミルシートのPDFから測定値を抜き、受入規格の上下限と突合して合否一覧にするAPIの手順です。原本で確かめる線引きまで扱います。

受入検査で検査成績書(COA)やミルシートを規格値と見比べる作業は、仕入先ごとに様式が違うせいで、表を目で追う時間がそのまま工数になります。Claude APIにPDFを1件ずつ渡して測定値をJSONで抜かせ、規格値との上下限比較は自作のスクリプトで行えば、ロットごとの合否一覧がCSVで出ます。

分担は単純です。読み取りはClaude、合否の判定はコード、原本との照合は人が受け持ちます。合否をClaudeに言わせない理由は後で述べます。以下の規格値と測定値は説明用の架空の数字です。

発注・納品・請求の金額を突き合わせる三点照合はClaudeで発注書・納品書・請求書を三点照合する手順にあります。ここで扱うのは、成績書の測定値を規格の数値範囲と突き合わせる別の作業です。

突合の仕組みを3つに分ける

手順

成績書PDFから合否一覧ができるまで

  1. 1

    規格値表を自社で持つ

    品目ごとの項目名、単位、下限、上限をCSVにします。仕入先の様式には依存させません。

  2. 2

    Claudeが成績書から測定値を抜く

    項目名と値を、印字どおりの文字列と数値の両方で、ページ番号つきのJSONにします。

  3. 3

    コードが規格値と比べる

    JSONと規格値表を突き合わせ、合格・不合格・要確認に分けてCSVに書きます。

判定を3つ目に置くのは、数値の大小比較をモデルの出力に任せる必然性がないからです。同じ入力なら必ず同じ結果になるコードのほうが、監査で説明しやすくなります。さらに、構造化出力のスキーマには minimum や maximum のような数値の範囲制約を書けません。公式のJSON Schemaの制限事項に、数値制約と文字列長の制約が非対応として挙がっています。規格の上下限をスキーマに埋め込む案は、はじめから使えません。

規格値表を先に作る

仕入先の成績書を読ませる前に、自社の受入規格を機械が読める形にします。ここが曖昧だと、後段の突合がすべて曖昧になります。

item,unit,min,max,aliases
C,%,,0.20,C|炭素|Carbon
P,%,,0.030,P|燐|リン|Phosphorus
引張強さ,N/mm2|MPa,400,510,TS|Tensile Strength|引張強度
降伏点,N/mm2|MPa,245,,YP|Yield Point|耐力
伸び,%,21,,EL|Elongation

aliases 列は、仕入先ごとに揺れる項目名を自社の項目名に寄せるための表です。成績書には同じ項目が「炭素」「C」「Carbon」のように載るので、表記の候補を並べておきます。単位も同様で、MPaとN/mm2は数値が同じなので、受け入れる単位として unit 列に両方を許す運用にしてもかまいません。

下限・上限の境界値を合格とするか、規格値の桁で丸めてから比べるかは、会社の受入規格と取引の取り決めで決まります。コードを書く前に決めて、規格値表の備考に残します。

成績書から抜く項目を決める

次にJSONの形を決めます。要点は、印字された文字列(value_raw)と、数値に直した値(value)を別々に返させることです。数値だけを返させると、桁の読み違いが起きても元の表記が残らず、人が原本と照らす手がかりを失います。

{
  "type": "object",
  "properties": {
    "supplier": {"type": "string"},
    "lot_no": {"type": "string"},
    "material": {"type": "string"},
    "rows": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "item_raw": {"type": "string"},
          "value_raw": {"type": "string"},
          "value": {"anyOf": [{"type": "number"}, {"type": "null"}]},
          "qualifier": {"type": "string", "enum": ["eq", "lt", "gt", "nd"]},
          "unit_raw": {"type": "string"},
          "page": {"type": "integer"},
          "legibility": {"type": "string", "enum": ["clear", "unclear"]},
          "supplier_judgement": {"type": "string"}
        },
        "required": ["item_raw", "value_raw", "value", "qualifier",
                     "unit_raw", "page", "legibility", "supplier_judgement"],
        "additionalProperties": false
      }
    }
  },
  "required": ["supplier", "lot_no", "material", "rows"],
  "additionalProperties": false
}

各フィールドの役割は次のとおりです。

  • value_raw:印字どおりの文字列。「0.018」「<0.005」「ND」をそのまま入れる
  • value と qualifier:数値と、等号・未満・超過・不検出の区別。「<0.005」なら値は0.005、区分は lt
  • page:その値が載っているPDFのページ。人が原本を開くときの目印になる
  • legibility:読み取りに自信がなければ unclear。この値の行は自動で要確認に回す
  • supplier_judgement:成績書に仕入先の合否欄があれば、その印字。判定には使わず、自社の判定との食い違いを見る材料にする

仕入先の判定欄を判定に使わないのは、仕入先の規格と自社の受入規格が一致している保証がないからです。両者がずれるロットを拾うために、あえて残します。

PDFを渡してJSONで返させる

リクエストは、PDFの document ブロック、続けて指示文を入れたMessages APIの形です。公式の改善策には、PDFをテキストより前に置くことが挙がっています。構造化出力はスキーマを output_config.format に type: "json_schema" で渡します。

添付のPDFは、仕入先が発行した検査成績書です。
測定値の行をすべて、指定のスキーマで返してください。
- 印字されている数字と項目名は、書かれたとおりに転記する
- 数値に直せない値(ND、<0.005など)は qualifier で区別する
- 数字が不鮮明、または手書きの訂正と重なるときは legibility を unclear にする
- 書かれていない項目を補わない。推測で埋めない
- ページ番号は、PDFビューアーで見える通し番号で答える

この指示文を prompt.txt に、上のスキーマを schema.json に保存し、次のようにリクエストを組みます。

base64 < coa.pdf | tr -d '\n' > coa.b64
 
jq -n --rawfile pdf coa.b64 --rawfile prompt prompt.txt \
  --slurpfile schema schema.json '{
  model: "claude-opus-5-5",
  max_tokens: 8192,
  messages: [{role: "user", content: [
    {type: "document", source: {type: "base64",
      media_type: "application/pdf", data: $pdf}},
    {type: "text", text: $prompt}
  ]}],
  output_config: {format: {type: "json_schema", schema: $schema[0]}}
}' > request.json
 
curl https://api.anthropic.com/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d @request.json > response.json

モデル名は公式のPDF入門に載っている例に合わせました。契約中のモデル名に置き換えてください。max_tokens は大きめにします。測定項目が多い成績書で出力が途中で切れると、stop_reason が max_tokens になり、スキーマに合わないJSONが返る可能性があるためです。拒否(refusal)でも同様にスキーマに合わない出力になるので、stop_reason が end_turn でないレスポンスは自動処理に流さず、別の置き場に分けます。

PDFの条件も確認しておきます。標準的なPDFであればよく、パスワードや暗号化のあるファイルは受け付けません。1リクエストの上限はサイズが32MB、ページ数が600ページ(コンテキストウィンドウが1Mトークン未満のリクエストでは100ページ)です。エラーの切り分けは「Could not process PDF」400エラーの原因と対処法にまとめています。

規格値との照合はコードで行う

返ってきたJSONと規格値表を突き合わせるスクリプトです。判定は3通りで、迷う余地のある行は不合格ではなく要確認に落とします。

import csv, json
 
with open("spec.csv", encoding="utf-8") as f:
    spec = {r["item"]: r for r in csv.DictReader(f)}
alias = {a: k for k, r in spec.items() for a in r["aliases"].split("|")}
 
def num(s):
    return float(s) if s else None
 
def judge(row, s):
    if row["legibility"] == "unclear" or row["value"] is None:
        return "要確認"
    if row["unit_raw"].replace("²", "2") not in s["unit"].split("|"):
        return "要確認"  # 単位が規格値表と違う
    v, lo, hi = row["value"], num(s["min"]), num(s["max"])
    if row["qualifier"] == "lt":  # 「<0.005」は上限側だけ判断できる
        return "合格" if lo is None and hi is not None and v <= hi else "要確認"
    if row["qualifier"] != "eq":
        return "要確認"
    if (lo is not None and v < lo) or (hi is not None and v > hi):
        return "不合格"
    return "合格"
 
data = json.load(open("result.json", encoding="utf-8"))
out = []
for r in data["rows"]:
    key = alias.get(r["item_raw"])
    s = spec.get(key)
    status = judge(r, s) if s else "規格値表に項目なし"
    out.append([data["lot_no"], key or r["item_raw"], r["value_raw"],
                r["unit_raw"], status, r["page"], r["supplier_judgement"]])
 
# 規格値表にあるのに成績書に無い項目も拾う
seen = {alias.get(r["item_raw"]) for r in data["rows"]}
for k in spec:
    if k not in seen:
        out.append([data["lot_no"], k, "", "", "成績書に記載なし", "", ""])
 
with open("verdict.csv", "w", encoding="utf-8", newline="") as f:
    w = csv.writer(f)
    w.writerow(["lot", "item", "value_raw", "unit", "判定", "page", "仕入先判定"])
    w.writerows(out)

spec.csv の unit 列は、受け入れる単位を N/mm2|MPa のように縦棒でつなぎます。後半の「成績書に記載なし」の行が肝心です。成績書に載っていない項目は、読み落としでも、そもそも試験をしていない場合でも、規格値表の側から探さなければ表に現れません。

出力は、例えば次のような形になります(数値は架空)。

ロット項目印字値単位判定ページ仕入先判定
A2310-07項目C印字値0.18単位%判定合格ページ1仕入先判定OK
A2310-07項目P印字値0.031単位%判定不合格ページ1仕入先判定OK
A2310-07項目引張強さ印字値452単位N/mm2判定合格ページ2仕入先判定OK
A2310-07項目降伏点印字値2?8単位N/mm2判定要確認ページ2仕入先判定OK
A2310-07項目伸び印字値単位判定成績書に記載なしページ仕入先判定

2行目は、仕入先は合格としているのに自社規格では上限を超える例です。判定欄を判定に流用しないのは、このずれを見つけるためです。

読み取り値を原本で確かめる線引き

Claudeの読み取りは、全行を無条件に信じる運用にも、全行を目視し直す運用にも向きません。確かめる行を機械的に絞り、残りを信頼する線を先に決めます。

原本を開く行

次の行は、判定の結果にかかわらず人がPDFの該当ページを開きます。

行の種類原本確認の扱い理由
不合格原本確認の扱い必ず見る理由返品・特採の判断に直結する
要確認原本確認の扱い必ず見る理由読み取りか単位か、規格値表の不備かを人が決める
規格の限界値に近い合格原本確認の扱い近さの基準を決めて見る理由1桁の読み違いで合否が変わる
スキャン画像由来のPDF原本確認の扱い重要項目は抜き取りで見る理由画像の読み取りには限界がある
初めて取引する仕入先の様式原本確認の扱い最初の数ロットは全項目を見る理由様式の読み方に癖が出るため

「近さの基準」は、規格幅に対する割合などで自社で決めます。基準の数字そのものに一般的な正解はありません。

画像の読み取りに限界がある理由

PDFは各ページが画像に変換され、抽出されたテキストと並べてClaudeに渡されます。テキストの埋め込みがないスキャンでも、画像として読めます。その代わり、画像の説明ページには、低品質・回転した・200ピクセル未満の小さな画像で、誤解釈や幻覚が起こり得ると書かれています。成績書の小さな数字、受領印や手書きの訂正と重なった値は、まさにこの条件です。

対策は、指示文で不鮮明な値を unclear に分けさせることと、入力を整えることです。PDFの改善策として公式が挙げるのは、標準フォントを使う、文字を鮮明にする、ページを正しい向きにそろえる、大きなPDFは分割する、の4点です。スキャン時に解像度を上げて傾きを直す前処理は、この延長にあります。

根拠の引用機能は併用できない

「値の根拠を引用で示させたい」と考えて、citations(引用)を有効にする案は成り立ちません。citationsを有効にした文書と output_config.format を同じリクエストに入れると400エラーになります。しかも、スキャンでテキストを抽出できないPDFは、画像の引用が未対応のため引用の対象になりません。理由と条件はCitationsとstructured outputsが併用できない理由と400エラー条件に詳しくあります。

この手順で根拠を示す手段は、value_raw と page の2つです。人はページ番号を頼りに原本を開き、印字値と見比べます。引用の代わりに、印字どおりの文字列を返させることで、突き合わせる相手を作っています。

件数が多いときはバッチで回す

ロット数が多い月次の棚卸しや、仕入先の切り替え時の一括確認には、Message Batches APIが使えます。バッチの料金は標準APIの50%で、1バッチは10万リクエストまたは256MBが上限です。結果が揃うまでは最大24時間かかりますが、ほとんどのバッチは1時間以内に完了します。結果は作成から29日間ダウンロードできます。構造化出力はバッチとも併用できます。

1ロット=1PDF=1リクエストにして、custom_id にロット番号を入れておくと、結果の突き合わせが簡単です。請求・契約といった別の書類での組み方は契約台帳をPDFから一括抽出する手順にあります。受入時の即日判定のように待てない場面は、通常のMessages APIで1件ずつ呼びます。

よくあるつまずき

項目名が規格値表に当たらない

「炭素」「C」「Carbon」のような揺れは aliases で吸収できますが、初めて見る表記は「規格値表に項目なし」になります。この行が出たら、まず別名として登録してよいかを人が決め、aliases に足します。モデルに「近い項目に寄せて」と任せると、別の項目に誤って寄せる事故が起きます。

1つのPDFに複数ロットが入っている

ロットごとに成績書が分かれていない様式では、lot_no を1つしか持たないスキーマだと値が混ざります。この場合は rows の各行にロット番号の欄を持たせるか、PDFをロットごとに分割してから渡します。

「ND」や「未満」の値をどう扱うか

不検出(ND)や「<0.005」は、上限だけがある項目なら合格と見なせる場合があります。下限がある項目では判断できないため、サンプルのコードは要確認に回しています。検出限界が規格の上限より高い場合の扱いは、受入規格か取引先との取り決めで決めておきます。

表が複雑でページをまたぐ

細かい表や小さな文字のページが多いPDFは、ページ数の上限に届く前にコンテキストを使い切って失敗することがあります。ページ単位や製品単位でPDFを分け、page は元のPDFの通し番号で答えさせます。分割後は番号がずれるため、結果を戻すときにページの対応表を持っておきます。

まとめ

成績書の突合は、読み取り・判定・原本確認を別々の担当に切り分けると回ります。Claudeには印字値と数値とページ番号を返させ、規格の上下限との比較は決まったコードに任せ、不合格・要確認・限界値に近い行だけを人がPDFで確かめます。

最初に作るのは規格値表です。受入規格を機械が読める形にしておけば、仕入先が増えても様式の違いはClaudeが吸収します。品質記録としての保管や監査対応を含めた運用は、ClaudeでISO 9001の内部監査チェックリストを作り記録と突合するの考え方が使えます。

この記事を共有:XはてブLinkedIn