入札で重要なのは、技術提案書が入札説明書の必須要件に漏れなく応答しているかを確認することです。しかし、この照合作業は単純な文字列検索では対応できません。
たとえば、入札説明書に「資本金 5,000 万円以上」とある場合、技術提案書の「資本金 1 億 2,000 万円」が要件を満たすことは、単純な文字列一致だけでは判断できません。数値の単位や表現、記載位置が異なるためです。
さらに、見落としのリスクは誤検知より大きくなります。余分な要件を 1 つ拾っても人手で確認できますが、必須要件を 1 つ見落とすと、重要な確認漏れにつながります。しかも必須要件は、参加資格、契約条件、技術要件など、文書のさまざまな箇所に分散しています。
文書形式の違いも問題になります。入札説明書は PDF、技術提案書は Word で作成されることが多く、人手で照合する場合は、2 つの文書を行き来しながら該当箇所を探す必要があります。
本記事では、C# と Spire.Agent.Office を使って、この照合作業を自動化する方法を紹介します。最終的に、次の 3 つの成果物を生成します。
- 必須要件一覧
- 適合性チェック表
- 注記付き技術提案書
ただし、これは自主点検を支援するためのツールです。評価委員会の判断や落札結果を代替するものではありません。
設計上は、役割を明確に分けます。必須要件に該当するか、技術提案書が応答しているかといった意味の判断は AI に任せ、原文、条項番号、不足項目などの正確性は C# と文書エンジンで担保します。
3 つの成果物
実行が終わると、ディスク上に 3 つのファイルが残ります。性質はそれぞれ異なります。
| 成果物 | どこから来るか | 誰が見るか |
|---|---|---|
| 入札必須要件一覧.xlsx | 入札説明書を通読し、失格事由にあたる要求を抜き出す | 点検担当者。以降のすべての判断の基準になる |
| 適合性チェック表.xlsx | 一覧に沿って技術提案書を 1 条ずつ確認し、対応状況を判定する | 確認担当者。結論ごとに出典と理由が付く |
| 技術提案書_注記版.docx | 通らなかった項目を技術提案書の該当位置に書き戻す | 担当責任者。開けば何を補うべきか分かる |
本記事を処理手順ではなく成果物で組み立てているのは、3 つの更新周期が大きく異なるためです。一覧は入札説明書に従います。1 つの文書に 1 つの一覧が対応し、発注者から質問回答や補遺が出れば作り直します。チェック表は技術提案書に従います。1 つの入札説明書に複数の入札者がいれば、その数だけ生成されます。注記版は一度きりの納品物で、送ってしまえば完了です。3 つを同一パイプラインの 3 段階として扱うより、独立した 3 つの資産として設計したほうが、実際の使われ方に近くなります。
以降のコードは次のディレクトリ構成を前提とします。
D:\bid-check\
├─ in\
│ ├─ 入札説明書.pdf # 発注者が公表した確定版(PDF)
│ └─ 技術提案書.docx # 自社が作成した応答文書(Word)
├─ out\ # 3 つの成果物
└─ sessions\ # AI セッション・ディレクトリ:中間スクリプトと入力の副本
out と sessions は分離します。成果物は納品と保管に使うもの、セッション・ディレクトリは過程のファイル——AI が生成した実行スクリプトと入力の副本——を置く場所です。調査するときは後者を、納品するときは前者だけを参照します。
サンプル文書
再現できるよう、サンプルの入札説明書には失格事由となる要求を 8 条配置しました。発注者は青葉市産業振興公社(入札事務代理は東都オフィスサービス株式会社)、入札者は株式会社グリーンネクサス(代表取締役 高梨 誠、配置予定の管理技術者 大西 亮介)、入札番号は NK-2025-0417 です。
| 条項 | 内容 | 区分 |
|---|---|---|
| 1.1 | 日本国内に本店を有する法人であり、資本金が 5,000 万円以上(登記事項証明書の提出) | ★ |
| 1.2 | 令和 4 年 1 月 1 日以降に完了した類似案件 2 件以上、うち 1 件は契約金額 2 億円以上 | ★ |
| 1.3 | ISO/IEC 27001(ISMS)認証を取得し、認証登録証の写しを提出 | ★ |
| 1.4 | 配置予定の管理技術者が技術士(情報工学部門)の資格を有し、実務経験 5 年以上 | ★ |
| 2.1 | 入札保証金 100 万円を、入札締切時刻の 24 時間前までに電信送金で納付 | ★ |
| 2.2 | 入札金額が予定価格 8,600 万円を超えないこと | ★ |
| 2.3 | 支払条件(契約締結後 30%、検査合格後 60%、保証期間満了後 10%)を受諾し、付帯条件を付けない | ★ |
| 3.1 | 省エネ計測ゲートウェイが Modbus RTU / TCP および BACnet/IP に対応し、第三者試験報告書を提出 | ▲ |
| 4.2 | 入札書類に代表者または代理人が署名し、社印を押印すること | ★ |
3.1 は ▲ の評価項目であり、失格事由ではありません。意図的に混入した項目です。抽出の段階でこれを一覧に含めてしまえば、以降の照合作業の相当部分が失格に結びつかない内容に費やされます。同時に、抽出段階の精度を測る指標にもなります。
技術提案書には 2 か所の不適合を用意しました。1.3 が求める ISMS の認証登録証は全文に記載がなく未対応、1.4 は「豊富なプロジェクトマネジメント経験を有します」の一行のみで資格証も実務年数を示す経歴書もなく対応不十分です。照合後にこの 2 条を正しく判定できているかどうかで、この流れが実用になるかはほぼ判断できます。
出典はページ番号ではなく条項番号で示します。条項番号は文書がもともと備えている安定した識別子であり、ページ番号は組版の結果にすぎません。ページ番号を答えさせること自体、AI が得意としない作業をさせることになります。
環境と依存関係
dotnet new console -n BidCheck -f net10.0
cd BidCheck
dotnet add package Spire.Agent.Office
NuGet からこのパッケージを 1 つ入れるだけで足ります。Spire.Doc / Spire.XLS / Spire.PDF などの文書エンジンは自動的に取り込まれます。公式サイトからアセンブリを取得してローカル DLL として参照する方法もありますが、その場合は各エンジンの .NETStandard 版に加えて Microsoft.Extensions.*、Polly、SkiaSharp などの依存関係を自分で揃える必要があり、構成コストが明らかに高くなるため推奨しません。
実行には有効な SpireToken キーが必要です。Spire.Agent.Office 公式サイトから取得できます。AI のパラメータは 1 つの AIOptions インスタンスに集約し、サービス層で 1 度だけ生成して使い回すことを推奨します。
using Spire.Agent.Office.AI;
// AI 処理の共通設定
AIOptions CreateOptions(string spireToken)
{
return new AIOptions
{
SpireToken = spireToken,
TimeoutMs = 1000000, // 1 回の呼び出しは 1 周分の Agent ループ。タイムアウトは余裕をもたせる
WorkDir = @"D:\bid-check\sessions" // セッション・ディレクトリ:中間スクリプトと入力の副本を保存し、後から追跡できる
};
}
| パラメータ | 本記事での値 | 説明 |
|---|---|---|
SpireToken |
取得したキー | 未設定・失効の場合は例外で中断します。黙って縮退することはありません |
TimeoutMs |
1000000 | 1 回の指示は「計画 → スクリプト生成 → 実行 → 成果物検証」の 1 周を含みます。300000 以上を推奨 |
WorkDir |
D:\bid-check\sessions |
指定すると各段階の入力、中間スクリプト、成果物が残ります。証跡が必要な場面では省略しないこと |
必須要件の抽出
3 つの成果物のうち、一覧は最も目立たない存在ですが、後続のすべての判断の上限を決めます。一覧から必須要件を 1 条落とせば、照合の段階でその条項が読まれることはなく、後ろのどの工程でも取り返せません。
この段階が答えるのは 1 つだけです。どの条項が必須要件に当たるか。技術提案書は読まず、推論もしません。切り出すことには実利もあります。1 つの入札説明書に 1 つの一覧が対応するため、複数の入札者でも複数回の修正でも同じ番号の組で照合でき、2 回の点検の基準が揃います。
指示を書くうえでの要点は、推論の過程ではなく出力の構造を縛ることです。列名を明示し、採番規則を示し、「原文を逐語で抜粋すること」を絶対条件として加えるほうが、「よく考えて分析してください」を積み上げるより効果的です。とくに最後の条件が重要です。原文の要約や改変を許すと、照合の段階が生成する「不足内容」が書き換えられた文章を引用することになり、人手で確認するときに原文と突き合わせられなくなります。
using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Pdf;
/// <summary>
/// ステップ 1:入札説明書を通読し、必須(失格事由)要件をすべて抽出する
/// </summary>
static AIResult ExtractMandatoryRequirements(
string tenderPath, // 入札説明書.pdf
string checklistPath, // 入札必須要件一覧.xlsx
AIOptions options)
{
using (PdfDocument tender = new PdfDocument())
{
tender.LoadFromFile(tenderPath);
AIDocumentProcessor processor = tender.AI(options);
string instruction =
"この入札説明書を通読し、含まれるすべての【必須(失格事由)要件】を漏れなく抽出して、" +
"Excel ブック(.xlsx 形式)に整理してください。最初のシート名は「入札必須要件一覧」とし、" +
"次の 5 列のみを含めてください:要件番号、要件原文、該当条項、必須区分、判定ポイント。条件:" +
"1) 必須要件の判定基準:条項の前に ★ が付されているもの、または条項本文に「〜すること/" +
" 〜してはならない/失格/無効/不受領」といった失格表現が含まれるもの;" +
"2) ▲ のみが付された評価項目、および記述的な技術指標や落札者決定方式の説明は一切都れないこと;" +
"3) 要件原文は条項の原文を逐語で抜粋し、要約・改変・圧縮をしてはならない;" +
"4) 該当条項には条項番号と所属する章を記入すること(例:「1.3(第1章 入札参加資格)」);" +
"5) 要件番号は M-01、M-02 … の順に採番し、番号を飛ばしてはならない;" +
"6) 出力は必ず .xlsx 形式の Excel ブックとし、Word 文書として出力してはならない。";
AIResult result = processor.ExecuteInstruction(
tender, instruction, checklistPath);
if (result == null || !result.Success)
{
string reason = result?.ErrorMessage ?? "不明なエラー";
throw new InvalidOperationException("必須要件の抽出に失敗しました:" + reason);
}
return result;
}
}
実装上の要点です。主文書は入札説明書そのもので、LoadFromFile のあとそのまま第 1 引数に渡します。PDF エンジンがこれを AI 層に解析させます。★ と ▲ は組版上の記号にすぎず、モデルはどちらが失格を意味するかを知りません。したがって「★ または失格表現を含むものを必須、▲ の評価項目は記載しない」を明示する必要があります。この段階の精度は、ほぼこの一文で決まります。出力形式は拡張子で決まります。checklistPath に .xlsx を渡せばブックになり、.docx や .pdf に変えれば同じ内容が別形式で出力されます。
実行結果です。
8 条、3.1 は拾われず、番号は M-01 から M-08 まで連続しています。抽出後に「要件原文」列を人手で抜き取り確認することを勧めます。とくに言い回しが複雑な条項は確認の価値があります。一覧はバージョン管理に載せる価値もあります。入札説明書に質問回答や補遺が出たときに作り直し、前版と差分を取れば、その差分が今回の点検で重点的に見るべき箇所になります。
対応状況の照合
この段階が流れの中心で、クロスフォーマットの能力が最もよく現れる部分です。主文書は PDF の入札説明書のままですが、追加文書には Excel(前段の一覧)と Word(技術提案書)が同時に並びます。
ExecuteInstruction の最後の引数が追加文書です。主文書以外のファイルを参照する必要があるとき——一覧に沿って 1 条ずつ照合しつつ、技術提案書を開いて対応箇所を探すとき——ここに渡します。
// 主文書(入札説明書)+ 追加文書(必須要件一覧、技術提案書)
result = processor.ExecuteInstruction(
tender, // 主文書:入札説明書 PDF
instruction, // 自然言語の指示
conformityPath, // 出力パス
new[] { checklistPath, bidPath }); // 追加文書:Excel の一覧 + Word の技術提案書
4 つの位置に何を置くかは次のとおりです。第 4 引数はこの API で最も見落とされやすいところです。
| 位置 | 置くもの | 本シナリオでの値 |
|---|---|---|
| 1 | 主文書。今回の処理の中心となるファイル | 入札説明書 PDF(PdfDocument) |
| 2 | 処理目標を記述した自然言語。日本語・英語いずれも可 | 下記の照合指示 |
| 3 | 成果物の保存先。拡張子が出力形式を決める | 適合性チェック表.xlsx |
| 4 | あわせて参照する他の文書の一覧 | 必須要件一覧 + 技術提案書 |
全体のコードです。
using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Pdf;
/// <summary>
/// ステップ 2:技術提案書の必須要件への対応状況を 1 条ずつ照合する
/// </summary>
static AIResult CheckConformity(
string tenderPath, // 入札説明書.pdf(主文書)
string checklistPath, // 入札必須要件一覧.xlsx(追加文書)
string bidPath, // 技術提案書.docx(追加文書)
string conformityPath, // 適合性チェック表.xlsx(出力)
AIOptions options)
{
using (PdfDocument tender = new PdfDocument())
{
tender.LoadFromFile(tenderPath);
AIDocumentProcessor processor = tender.AI(options);
string instruction =
"「入札説明書」の必須要件を基準として、「技術提案書」がその要件に応答しているかを 1 条ずつ照合し、" +
"出力は必ず Excel ブック(.xlsx 形式)とし、シート名は「適合性チェック表」、" +
"次の 7 列のみを含めてください:要件番号、要件原文、該当条項、必須区分、" +
"技術提案書の対応箇所、判定、不足内容。条件:" +
"1) 技術提案書の対応箇所には、技術提案書中で応答している章番号と要点を記入すること;" +
"2) 判定の値は次の三者いずれか 1 つに限る:対応済み / 対応不十分 / 未対応;" +
" 対応済みとは、技術提案書が当該要件に必要な内容を明確に示している場合;" +
" 対応不十分とは、関連記載はあるが、要件で明示された重要な要素(証明書類、資格、年数等)を欠く場合;" +
" 未対応とは、技術提案書中に対応する記載がまったく見当たらない場合;" +
"3) 不足内容は、判定が「対応不十分」または「未対応」の場合は何が不足しているかを具体的に記し、対応済みの場合は空欄とすること;" +
"4) 必ず 1 条ずつ照合し、必須要件を 1 条たりとも漏らさず、また一覧にない項目を追加してはならない;" +
"5) 業界慣行や常識に基づいて入札者が「備えているはず」の資格を推測することは固く禁ずる。技術提案書の実際の記載内容のみを根拠とすること;" +
"6) 出力は必ず .xlsx 形式の Excel ブックとし、Word 文書として出力してはならない。";
// 追加文書の引数で、一覧と技術提案書をまとめて AI に渡す
AIResult result = processor.ExecuteInstruction(
tender,
instruction,
conformityPath,
new[] { checklistPath, bidPath });
if (result == null || !result.Success)
{
string reason = result?.ErrorMessage ?? "不明なエラー";
throw new InvalidOperationException("適合性の照合に失敗しました:" + reason);
}
return result;
}
}
この段階が依拠するのは意味の対応関係です。「技術提案書 2.1 に記載された資本金 1 億 2,000 万円が、1.1 の 5,000 万円という基準を満たすか」。こうした判断は、コードによる逐語比較ではまず正しくできません。数値の単位、言い回し、記載位置がいずれも一定しないためです。意味の突き合わせが最も得意とする領域です。
指示のうち 3 か所は意図的な設計です。
判定を 3 値に限定し、自由記述にしない。 対応済み / 対応不十分 / 未対応 は後続の自動化の前提です。注記の段階は判定に応じて項目を絞り込みます。値が自由に書かれると絞り込みのロジックが機能しません。
「対応不十分」の段階は省略できない。 実務で多いのは、まったく書かれていないことではなく、書かれてはいるが重要な要素が欠けているケースです。「対応済み / 未対応」の 2 段階しかなければ、「管理技術者には言及しているが資格証がない」を対応済みと誤判定し、最も見つけにくいリスクをちょうど見逃します。
常識による補完を禁じる。 この場面で最も危険な誤判定は「この会社なら当然 ISMS 認証を持っているだろう」というものです。指示で業界慣行に基づく推測を明示的に禁じ、判断の根拠を技術提案書の実際の記載内容に固定します。
8 行、M-03 が未対応、M-04 が対応不十分、残る 6 条が対応済みです。サンプルに用意した 2 か所と一致しています。
| 要件番号 | 判定 | 技術提案書の対応箇所 | 不足内容 |
|---|---|---|---|
| M-01 | 対応済み | 二、会社の資格証明 2.1:日本国内に本店を有する法人であり、資本金 1 億 2,000 万円 | |
| M-02 | 対応済み | 二、会社の資格証明 2.2:類似案件 3 件、最大単項 3 億 8,000 万円 | |
| M-03 | 未対応 | なし(技術提案書の全文および添付資料一〜四のいずれにも該当記載なし) | ISO/IEC 27001(ISMS)認証登録証の写しが提出されていません。技術提案書の全文および添付資料一覧のいずれにも当該資料がありません |
| M-04 | 対応不十分 | 五、実施体制 5.1:配置予定の管理技術者 大西 亮介 | 「豊富なプロジェクトマネジメント経験を有します」との記載のみで、技術士(情報工学部門)の資格証の写しがなく、実務経験 5 年以上を示す経歴書も添付されていません |
| M-05 | 対応済み | 三、契約条件への対応 3.1:令和 7 年 4 月 6 日に電信送金で納付、振込控えを添付資料三に添付 | |
| M-06 | 対応済み | 一、提案概要:提案金額 8,120 万円、予定価格 8,600 万円を超えていない | |
| M-07 | 対応済み | 三、契約条件への対応 3.2:全面的に受諾、付帯条件なし | |
| M-08 | 対応済み | 一、提案概要:代表取締役 高梨 誠が署名し社印を押印 |
上の表は読みやすさのため 4 列だけを示しています(「要件原文 / 該当条項 / 必須区分」を省き、列順も入れ替えています)。実際の成果物の列順は、指示で宣言した 7 列に従います。
この段階で警戒すべきは誤検知ではなく見落としです。本来「未対応」であるべき条項を「対応済み」と判定すると、点検表は一見すべて通過に見えますが、評価の場ではそのまま失格になります。人手の点検より危険な場合さえあります。偽の確信を与えるからです。指示で推測を禁じることに加え、次の 2 つの対策を取ります。
1 つはAI を使わないキーワード走査によるクロスチェックです。文書エンジンで技術提案書を決定的に走査し、「ISMS」「技術士」「入札保証金」「社印」といった語を検索して、ヒット位置と AI が答えた対応箇所を突き合わせます。食い違う項目だけ人手で確認します。キーワード走査は再現率が高いとは限りませんが、誤った結論は出しません。
もう 1 つは同じ入力を 2 回実行し、結果の和集合を取ることです。AI の判定には確率性があるため、独立した 2 回の結果を重ねれば偶発的な見落としの多くを補えます。
これに加えて、チェック表をディスクから読み戻して自ら集計するコードを 1 本用意します。モデルの自己報告に依存しないためです。
using Spire.Xls;
/// <summary>
/// 検証:チェック表を読み戻し、未通過の項目を集計して一覧の件数と突き合わせる
/// </summary>
static void VerifyConformity(string conformityPath, int expectedCount)
{
using (Workbook workbook = new Workbook())
{
workbook.LoadFromFile(conformityPath);
Worksheet sheet = workbook.Worksheets[0];
// 1 行目はヘッダー
int dataRows = sheet.LastRow - 1;
var distribution = new Dictionary<string, int>();
for (int row = 2; row <= sheet.LastRow; row++)
{
// 数値型セルは Value で読む。Text は空文字列を返す
string verdict = sheet.Range[row, 6].Value;
if (string.IsNullOrEmpty(verdict))
verdict = sheet.Range[row, 6].Text;
if (string.IsNullOrWhiteSpace(verdict))
continue;
distribution[verdict] = distribution.TryGetValue(verdict, out int n) ? n + 1 : 1;
}
Console.WriteLine($"チェック表の項目数:{dataRows}(必須要件一覧は {expectedCount} 条のはず)");
foreach (var item in distribution)
{
Console.WriteLine($" {item.Key}:{item.Value} 件");
}
if (dataRows != expectedCount)
{
Console.WriteLine("⚠ 項目数が必須要件一覧と一致しません。照合漏れまたは重複の可能性があります。人手で再確認してください。");
}
}
}
件数の不一致は見落としの最も直接的なシグナルです。1 条ずつ結論を追うよりはるかに速く異常に気づけます。一覧が 8 条なら、チェック表は 8 行でなければなりません。判定の分布は 2 つ目の確認手段です。もし 未対応 がゼロで、しかもこの案件がこれまで ISMS 認証を取得したことがないのであれば、全対応ではなく見落としである可能性のほうが高くなります。
不足項目の注記
チェック表は結論を出しますが、実際に作業を進めるのは担当責任者にそのまま送れる文書です。技術提案書に各不足を書き込んでおけば、開いた人が何を補うべきかを一目で把握できます。
この段階の形態はやや特殊です。同一の文書、順序づけられた複数の指示、最終的な出力は 1 ファイルです。1 つ目の指示が注意書きを 1 行挿入し、2 つ目の指示が同じドキュメント・インスタンス上で次の行を挿入し、修正が 1 条ずつ累積します。対応するメソッドは ExecuteInstructionsAsync です。
// 同一文書 + 複数の指示 + 単一の出力:1 条ずつ処理し修正を累積する
List<AIResult> results = processor
.ExecuteInstructionsAsync(bid, instructions, annotatedPath)
.GetAwaiter().GetResult();
重要なのは、指示が固定ではなく、コードがチェック表から読み出して組み立てたものである点です。この段階は AI をまったく呼び出さない純粋なコード・ロジックです。したがって不足項目の一覧がモデルによって「生成される」ことはありません。
using Spire.Xls;
/// <summary>
/// チェック表を読み戻し、検証に通らなかった項目を注記用の指示に変換する(純コード、AI 呼び出しなし)
/// </summary>
static List<string> BuildGapInstructions(string conformityPath)
{
var instructions = new List<string>();
using (Workbook wb = new Workbook())
{
wb.LoadFromFile(conformityPath);
Worksheet sheet = wb.Worksheets[0];
// ヘッダー名で列を特定し、列順の変更に影響されないようにする
var columns = new Dictionary<string, int>();
for (int c = 1; c <= sheet.LastColumn; c++)
{
columns[HeaderText(sheet, c)] = c;
}
for (int r = 2; r <= sheet.LastRow; r++)
{
string verdict = Cell(sheet, columns, r, "判定");
if (verdict == "対応済み" || verdict.Length == 0)
{
continue; // 検証に通った項目は注記不要
}
string code = Cell(sheet, columns, r, "要件番号");
string clause = Cell(sheet, columns, r, "該当条項");
string missing = Cell(sheet, columns, r, "不足内容");
// 各指示はすべての文脈を自ら備える:一括指示メソッドには追加文書の引数がない
instructions.Add(
"本文書において、入札説明書の必須要件 " + code + "(" + clause + ")に対応する章を特定してください。" +
"照合の結果、本技術提案書の当該要件に対する判定は「" + verdict + "」であり、問題は次のとおりです:" + missing + "。" +
"対応する章の末尾に改行して、赤色・太字の注意書きを 1 行挿入してください。内容は:" +
"「⚠ 必須要件未対応:" + code + "(" + clause + ") 判定:" + verdict + " —— " + missing + "」" +
"この 1 行の挿入以外に、文書中のその他の内容を変更・削除・書き換えしてはなりません。");
}
}
return instructions;
}
細かい点が 3 つあります。列はヘッダー名で特定し、列番号はハードコードしません。チェック表が将来列を増減しても読み違えません。指示は自己完結でなければなりません。ExecuteInstructionsAsync が受け取るのは文書、指示の配列、出力パスだけで、追加文書の引数がありません。条項番号、判定、不足内容はすべて各指示に記載する必要があり、モデルが自分でチェック表を参照することを期待してはいけません。最後に「その他の内容を変更・削除・書き換えしてはならない」も明記します。記載しないと、モデルが挿入の際に既存の段落まで書き換えてしまい、技術提案書の厳粛性が損なわれるおそれがあります。
実行部分です。
using Spire.Doc;
/// <summary>
/// ステップ 3:技術提案書の副本に不足項目を 1 条ずつ注記する
/// </summary>
static List<AIResult> AnnotateBidDocument(
string bidPath, // 技術提案書.docx
List<string> instructions, // 前ステップのチェック表から生成
string annotatedPath, // 技術提案書_注記版.docx
AIOptions options)
{
using (Document bid = new Document())
{
bid.LoadFromFile(bidPath);
AIDocumentProcessor processor = bid.AI(options);
List<AIResult> results = processor
.ExecuteInstructionsAsync(bid, instructions.ToArray(), annotatedPath)
.GetAwaiter().GetResult();
foreach (AIResult r in results)
{
if (r == null || !r.Success)
{
string reason = r?.ErrorMessage ?? "不明なエラー";
throw new InvalidOperationException("注記指示の実行に失敗しました:" + reason);
}
}
return results;
}
}
2 つの注意書きがそれぞれ該当する章の末尾に入り、ほかの内容は変更されていません。
サービスクラスへの集約
3 つの段階を 1 つのサービスクラスにまとめ、外部には 1 つのメソッドだけ公開します。
using Spire.Agent.Office.AI;
/// <summary>
/// 入札適合性点検サービス:必須要件の固定化 → 1 条ずつの適合性照合 → 不足項目の注記
/// </summary>
public class BidComplianceService
{
private readonly AIOptions _options;
public BidComplianceService(string spireToken)
{
_options = new AIOptions
{
SpireToken = spireToken,
TimeoutMs = 1000000,
WorkDir = @"D:\bid-check\sessions"
};
}
/// <summary>
/// 1 回の完全な自主点検を実行し、3 つの成果物のパスを返す
/// </summary>
public (string Requirements, string Conformity, string Annotated) RunCheck(
string tenderPath, string bidPath, string outputDir)
{
if (!Directory.Exists(outputDir))
Directory.CreateDirectory(outputDir);
string requirementsPath = Path.Combine(outputDir, "入札必須要件一覧.xlsx");
string conformityPath = Path.Combine(outputDir, "適合性チェック表.xlsx");
string annotatedPath = Path.Combine(outputDir, "技術提案書_注記版.docx");
// ステップ 1:入札説明書 → 必須要件一覧
ExtractMandatoryRequirements(tenderPath, requirementsPath, _options);
// ステップ 2:入札説明書 + 一覧 + 技術提案書 → 適合性チェック表
CheckConformity(tenderPath, requirementsPath, bidPath, conformityPath, _options);
// ステップ 3:チェック表 → 不足項目の指示 → 注記版技術提案書
List<string> instructions = BuildGapInstructions(conformityPath);
if (instructions.Count > 0)
{
AnnotateBidDocument(bidPath, instructions, annotatedPath, _options);
}
return (requirementsPath, conformityPath, annotatedPath);
}
// 3 ステップの具体的な実装は、上記の ExtractMandatoryRequirements / CheckConformity /
// BuildGapInstructions / AnnotateBidDocument を private メソッドとして取り込み(static を外す)だけでよい。
}
呼び出し方です。
BidComplianceService service = new BidComplianceService("SpireToken key");
var (requirements, conformity, annotated) = service.RunCheck(
tenderPath: @"D:\bid-check\in\入札説明書.pdf",
bidPath: @"D:\bid-check\in\技術提案書.docx",
outputDir: @"D:\bid-check\out");
Console.WriteLine($"必須要件一覧:{requirements}");
Console.WriteLine($"適合性チェック表:{conformity}");
Console.WriteLine($"注記版技術提案書:{annotated}");
1 つの入札説明書が複数の入札者に対応する場合、第 1 段階は 1 回で済みます。以降は各技術提案書について第 2・第 3 段階を繰り返し、チェック表を横断比較表にまとめます。一覧を独立させた見返りはここにあります。入札説明書に質問回答や補遺が出たときは一覧を作り直すだけで、コードは変更不要です。
実運用で判明した問題
以下はいずれも設計やコンパイルの段階では表面化せず、実際の実行で生じたものです。
成果物が指定したパスに現れるとは限りません。 実際には 2 つのケースがあります。エンジンが指定パスに直接書き込む場合と、セッション・ディレクトリ配下の .office_use_tmp/<形式>/<タイムスタンプ>/output_<ファイル名> に書き込む場合です。堅実な方法は、まず目標パスを確認し、次に AIResult.OutputFiles を走査してファイル名で照合し、どちらにもなければ明確にエラーを出して停止することです。「別のファイルをコピーして名前だけ変える」という処理に落とし込んではいけません。ファイル名だけ正しく中身が異なる偽の成果物ができあがり、エラーよりずっと危険です。
エンジンが書き出す Excel には余分なシートが付くことがあります。 空のワークシートやサンプルのタブが混ざると、下流のシステムや人が開くときに支障になります。納品前に最初のワークシートだけを残して上書き保存します。
workbook.Worksheets.RemoveAt(index);
workbook.SaveToFile(path);
CellRange.Text は数値列を読み取ると静かに空文字列を返します。 Text はテキスト型のセルにしか内容を返しません。金額、数量、合計といった数値型のセルを読み取ると空文字列になり、しかも例外を送出しません。気づいたときには「0 件しか読めていない」という状態になります。堅実な書き方は Value を優先し Text をフォールバックにする形です。
string text = cell.Value;
if (string.IsNullOrEmpty(text))
text = cell.Text;
前述の BuildGapInstructions が読み取っているのは「判定」のようなテキスト列なので Text でも問題ありません。金額や合計の列に変更する場合は、必ず上記の形にしてください。
1 回の実行に 2〜6 分かかり、ばらつきも大きくなります。 ExecuteInstruction 1 回は 1 周分の Agent ループ(計画、スクリプト生成、実行、成果物検証)です。所要時間はおおむね出力される内容量に比例します。同期 HTTP リクエストに載せるべきではありません。遅かれ早かれタイムアウトします。「タスクを投入してタスク番号を返す + バックグラウンド・キューで実行 + 進捗を照会する」形式に変更してください。入札締切前には余裕をもった時間枠を確保し、3 段階で 20 分以上を見込むのが安全です。
同名の型が衝突します。 3 つのエンジンを同時に導入すると、Spire.Doc と Spire.Xls にそれぞれ FileFormat があり、FileFormat.Docx と書くと CS0104(参照があいまい)になります。エイリアスを 1 行追加すれば解決します。
using DocFileFormat = Spire.Doc.FileFormat;
Word 側の保存も DocFileFormat.Docx に変更します。HorizontalAlignment、VerticalAlignment も同様です。
コードとは関係ありませんが、必ず手当てすべき事項が 2 つあります。データ・セキュリティ:技術提案書には見積金額、要員、資格といった機密情報が含まれます。AIOptions の BaseUrl と Model を自社のモデルサービスに向ければ、データ処理を制御可能な範囲に収められます。コスト:呼び出しのたびに Token を消費します。AIResult.TokenUsage がそのまま計量の入口になるので、案件別または月別に集計しておきます。
対応範囲
| 能力 | 対応 | 説明 |
|---|---|---|
| PDF / Word / Excel / PPT の相互読み書き | 対応 | 主文書と追加文書はクロスフォーマットで任意に組み合わせ可能。本シナリオは PDF 主文書 + Excel/Word 追加文書 |
| 拡張子による出力形式の指定 | 対応 | 同一の分析結果を .xlsx / .docx / .pdf / .md として出力可能 |
| 同一文書への複数の指示による累積修正 | 対応 |
ExecuteInstructionsAsync(doc, string[], outputPath)。本シナリオでは不足の注意書きを 1 条ずつ挿入するために使用 |
| スキャン文書、画像、手書き内容の読み取り | 非対応 | 入札説明書と技術提案書はテキスト型文書である必要があります。スキャン文書は事前に OCR でテキスト化が必要 |
| ネットワーク経由での資格の真偽確認、企業信用の照会 | 非対応 | 判定は渡された文書の内容のみに依拠します。真正性の確認は人手または外部システムが必要 |
| 1 回の呼び出しで複数の技術提案書を並行処理 | 非対応 | 1 つの指示で処理するのは 1 つの主文書。複数入札者は呼び出し側でのループとなり、それはオーケストレーション層の責務 |
| 評価委員会に代わって失格の結論を下す | 非対応 | 出力は「不足項目の提示」であり、最終的な認定権は評価委員会にあります |
利用にあたっては、チェック表の結論は必ず人手による確認を経る必要があり、入札判断の唯一の根拠にはできないこと、まして「適合性は審査を通過した」と対外的に述べてはならないことを明記してください。
適用できる場面
この構成が解決するのは速度ではありません。人手で 2 回確認しても半日程度ですから、短縮できる時間は限られています。解決するのは**「漏れがない」ことを証明できない**という問題です。一覧があり、チェック表があり、8 条に対して 8 行あり、注記版の 2 行がどの要件に対応するのか——すべて開いて示すことができます。これができない点検表は、その信頼性を根拠づけられません。
AI の位置づけも明確です。条項を読み、意味を突き合わせる——ここは人より速い。ただし公式やルールで確定できる部分、すなわち条文の原文、番号、件数の集計、不足項目の一覧は、AI を経由させるべきではありません。これは精度の問題である以上に、追跡性の問題です。結論を問い直されたとき、それがどの行の、どの条項から導かれたのかを示せなければなりません。
「一方が要求を出し、他方が応答する」文書の照合——入札の自主点検、取引先の資格確認、契約条項への応答確認——であれば、この構造はそのまま移せます。変更が必要になるのは、たいてい第 1 段階の抽出ルールと第 2 段階の判定基準だけです。
公式リソース:




