見積と請求の差を、説明のつく差とつかない差に分ける
見積書と請求書を突き合わせる処理を書くと、最初は単純な引き算に見えます。
const diff = invoice.total - estimate.total;
これでは使えません。差が出ること自体は異常ではないからです。必要なのは、差があるかどうかではなく、その差に説明が付くかどうかの判定です。
葬儀の見積書と請求書を題材に、この判定をどう組むかを考えます(2026年9月9日に確認した内容を使います)。
→ 見積書と請求書の差額を確認するなら 弁護士による葬儀ガード
費目には2種類ある
前提として、参照したコラムに費目の分類がありました。
動かない側は、祭壇、棺、寝台車、式場代、火葬料。動く側は、料理、返礼品、礼状、安置料、ドライアイス。動く理由は人数と日数です。
この分類が、そのまま判定ロジックの土台になります。
const KIND = {
fixed: ['祭壇', '棺', '寝台車', '式場代', '火葬料'],
variable: ['料理', '返礼品', '会葬礼状', '安置料', 'ドライアイス'],
};
fixedに差が出たら、原則として説明が要る-
variableに差が出るのは正常。ただし傾きと整合しているかを見る
差分の絶対値だけを見ると、この違いが消えます。
変動費目は「単価 × 数量」に分解して比べる
コラムには、あるべき書き方の例が挙げられていました。料理は「1人前◯円 × ◯人分」、返礼品は「1個◯円 × ◯個」、会葬礼状は「1枚◯円 × ◯枚」。安置・保管は「1日◯円 × ◯日」、ドライアイスは「1回◯円 × ◯回」、寝台車・霊柩車は「◯km以内◯円 × ◯回」という形です。
この形で両方が書かれていれば、差の原因を単価側と数量側に切り分けられます。
function explain(est, inv) {
if (est.unit === inv.unit && est.qty !== inv.qty) {
return { verdict: 'qty_changed', delta_qty: inv.qty - est.qty }; // 想定内
}
if (est.unit !== inv.unit) {
return { verdict: 'unit_changed', from: est.unit, to: inv.unit }; // 要確認
}
return { verdict: 'same' };
}
数量が増えたのは説明が付きます。単価が変わったのは、説明を聞く必要があります。総額だけを比べていると、この2つが同じ「増えた」になってしまいます。
「一式」は判定不能として返す
現実のデータでは、この分解ができないことがあります。
「葬儀一式 ◯◯万円」としか書かれていない場合です。コラムには、この書き方だと後から何が含まれていたのかを確かめられないとありました。
ここで qty = 1, unit = 総額 と補完してはいけません。分解できていないという事実そのものが、確認すべき点だからです。
if (est.unit == null || est.qty == null) {
return { verdict: 'not_comparable', reason: 'lump_sum' };
}
not_comparable を same に倒さないこと。比較できなかったものを「差がなかった」と表示するのが、いちばん危ない実装です。
なお、コラムには、その場で「項目ごとの金額を入れた見積書をいただけますか」と伝えてよいとありました。データが取れないなら、取れる形で出し直してもらうのが正しい対処です。
見積に無い行が請求に出てくる
もう1つのパターンが、片側にしか存在しない行です。
別会計になりやすい項目として挙がっていたのは、火葬料、式場使用料、宗教者へのお布施・御車代・御膳料、安置が延びた分の追加保管料、参列者が増えた分の料理・返礼品でした。火葬料は自治体や施設で差があり、無料のところも十数万円のところもあるとのことです。
これらは見積書に載っていないのが正常な場合があります。載っていないから不正、ではありません。
const reconcile = (est, inv) => ({
matched: join(est, inv),
only_in_estimate: est.filter(r => !inv.some(i => i.name === r.name)),
only_in_invoice: inv.filter(r => !est.some(e => e.name === r.name)),
});
only_in_invoice に出たものは、「想定されていた範囲外の項目」のリストとして出します。異常として赤くするのではなく、確認対象として並べる。判定を下すのは人の仕事です。
規模感を持っておく
どの程度の差なら想定内か、という感覚を持つために数字を引いておきます。
紹介されている調査の平均額は、総額が96.73万円。その内訳として飲食費が11.67万円、返礼品費が13.03万円でした。合計すると約25万円。総額の4分の1ほどが、契約の時点では未確定という整理です。
つまり、総額の25%前後は動きうるという前提で設計する必要があります。「5%以上の差でアラート」のような閾値を総額に対して置くと、正常な差で鳴り続けます。
閾値を置くなら、variable の行に対して数量の変化率で置くほうが筋が通ります。
傾きを先に取っておく
差分を後から説明するより、事前に傾きを記録しておくほうが確実です。
参照したコラムには、時間がないときに確認する項目が3つ挙げられていました。
- 見積もりに含まれない項目
- 参列者が1人増えたときの増加額
- 安置が1日延びたときの増加額
データ設計としては、これがそのまま入力項目になります。
{
"excluded": ["火葬料", "式場使用料", "お布施"],
"slope": {
"per_attendee": 0,
"per_storage_day": 0
},
"recorded_at": "…",
"confirmed_by": "email"
}
confirmed_by を持たせているのは、コラムに答えはその場でメモし、できればその日のうちにメールで送って確認しておくとあったからです。口頭の記録と、書面で確認済みの記録は、あとで差分を検証するときの重みが違います。
前提条件が違えば、そもそも比較できない
見積どうしを比べる場合には、もう1つ条件があります。
そろえるべき項目として、参列者の人数・式場・通夜の有無・安置日数・火葬料の扱いという5つが挙げられていました。
この5つを比較のキーとして持ち、一致しない場合は比較を実行しない設計にします。
const COMPARE_KEYS = ['attendees', 'venue', 'wake', 'storage_days', 'cremation_fee_handling'];
if (COMPARE_KEYS.some(k => a[k] !== b[k])) {
return { verdict: 'precondition_mismatch', keys: COMPARE_KEYS.filter(k => a[k] !== b[k]) };
}
条件が違うのに数字を並べて表示しない。並べた時点で、利用者は比べてしまいます。
出力の形
最終的に出すのは、判定ではなく分類です。
■ 想定内(数量の変化で説明が付く)
料理 50人 → 63人 単価は同じ
安置・保管 3日 → 4日 単価は同じ
■ 確認したい(単価が変わっている)
返礼品 単価 ◯円 → ◯円
■ 見積に無かった項目
式場使用料
■ 比較できなかった
葬儀一式(内訳なし)
「異常あり/なし」を返さない。金額の妥当性は、このデータだけでは判断できません。できるのは、人が確認すべき箇所を絞り込むところまでです。
人に見てもらう前提を織り込む
実際のところ、この整理をした先で判断が要ります。
弁護士が中立の立場で見積書・契約書・請求書を確認するサービスがあり、葬儀後の請求確認として、見積書と請求書の差額や追加請求の内容を検証する、という範囲が示されていました。葬儀が終わった後でも相談できるとのことです。相談は何度でも無料、24時間受付、匿名相談可とも書かれていました。
葬儀社から紹介料・広告料等の金銭を一切受け取らず、葬儀社の紹介・斡旋は行わない、という記載もあります。
ツールが出すのは材料まで、というのは、設計上も割り切っておいたほうがいい線引きだと思います。
まとめ
- 差分を単一の数値で判定しない。費目の種類で扱いを分ける
- 変動費目は単価と数量に分解し、どちらが動いたかを返す
- 分解できないものは
not_comparable。「差なし」に倒さない - 片側にしかない行は、異常ではなく確認対象として出す
- 閾値を総額に対して置かない。25%前後は動きうる前提で考える
- 傾き(1人あたり・1日あたり)を事前に記録しておく
- 前提条件が違う見積は並べて表示しない
書式も条件も発行元によって変わります。実装に取り込むなら、対象の書面の様式を確かめたうえで行ってください。
本ページはプロモーションが含まれています


Top comments (0)