無人運用の記事バリデーション — 「終了日が無い」を書き漏らしと未公表に機械で仕分ける

セール情報を毎日無人で追記していく静的サイトを運用していると、記事データの品質チェックを人手のレビュー無しで通さざるを得ない。ここで地味に効くのが「必須フィールドが空のとき、それをエラーにするか許容するか」の設計だ。とくに期間限定セールの終了日は、読者がいちばん知りたい情報でありながら、「書き忘れた」場合と「調べたが公表されていない」場合の両方がある。この2つを同じ扱いにすると、記入漏れを素通りさせるか、正当な記事まで弾くかのどちらかに倒れる。

実際に運用しているセール速報サイト manga-deals では、この判定をビルド時の機械バリデーションに落とし込んでいる。同じ設計を JavaScript で組み立ててみる。

問題: 空欄の意味は一つではない

記事は次のような JSON で持っている。

1
2
3
4
5
6
7
8
9
const article = {
slug: "sale-example-20260725",
title: "【7/31まで】◯◯フェア 最大50%オフ",
articleType: "fair", // fair(まとめセール) / work(単品) / evergreen(恒常記事)
metrics: {
deadline: "2026-07-31", // セール終了日
// deadlineNote: "..." // 終了日が無い“理由”を書く欄
},
};

metrics.deadline が空のとき、ありうる状況は3つある。

  1. 書き漏らし: 本当は終了日があるのに入れ忘れた。これはエラーにしたい。
  2. 調べたが未公表: Amazon のタイムセールなどは終了日が公表されないことが実際にある。日付を推測で埋めるのは捏造なので、空欄のままにしたい。だがエラーで止めたくはない。
  3. そもそも期間の概念が無い記事: 「買い方ガイド」のような恒常記事(evergreen)には終了日が存在しない。これは正常。

この3つを空欄という一つの状態から区別できないのが問題の核心だ。「空欄=エラー」にすると 2 と 3 が誤検知になり、「空欄=許容」にすると 1 が漏れる。

解決: 意図を表す明示フィールドを足す

区別できないのは、状態が足りないからだ。そこで書いた側の意図を表す明示フィールドを2つ用意する。

  • articleType: 記事の種別。evergreen なら期間の概念が無い(状況3)。
  • metrics.deadlineNote: 終了日が無い理由を書く欄。ここに文章があれば「調べた結果として無い」(状況2)とみなす。

空欄のまま deadlineNote も無いものだけを、状況1(書き漏らし)としてエラーにする。fail-closed —「判断できないものは通さない」— の考え方だ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function checkDeadline(article) {
const type = article.articleType || (article.multi ? "fair" : "work");
const mt = article.metrics || {};
const deadline = mt.deadline || article.deadline || null;

if (deadline) {
return { level: "ok" }; // 終了日がある。別途フォーマットを検証
}
// ここから下は「終了日が無い」ケースの仕分け
if (type === "evergreen") {
return { level: "ok" }; // 状況3: 期間の概念が無い記事なので正常
}
if (mt.deadlineNote) {
return { // 状況2: 調べたが未公表。warn に落とす
level: "warn",
message: `終了日が未公表(${mt.deadlineNote})。記事に「終了日未公表」と明示すること`,
};
}
return { // 状況1: 書き漏らし。error で止める
level: "error",
message: "終了日が無い。未公表なら metrics.deadlineNote に理由を書く(日付を推測で埋めない)",
};
}

ポイントは、警告(warn)とエラー(error)を分けていることだ。「調べたが未公表」は正当だが、読者には「終了日未公表」と見せる必要があるので、黙って通さず warn として記録に残す。ビルド自体は止めない。書き漏らしだけが error でビルドを止める。

動かして確かめる

4種類の記事を通してみる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const cases = [
{ slug: "a", articleType: "fair", metrics: { deadline: "2026-07-31" } },
{ slug: "b", articleType: "fair", metrics: {} }, // 書き漏らし
{ slug: "c", articleType: "fair",
metrics: { deadlineNote: "Amazonタイムセールで終了日は非公表" } }, // 未公表
{ slug: "d", articleType: "evergreen", metrics: {} }, // 恒常記事
];

for (const a of cases) {
const r = checkDeadline(a);
console.log(a.slug, "=>", r.level, r.message ? `(${r.message})` : "");
}
// a => ok
// b => error (終了日が無い。未公表なら metrics.deadlineNote に理由を書く…)
// c => warn (終了日が未公表(Amazonタイムセールで…)。記事に「終了日未公表」と明示すること)
// d => ok

同じ「終了日が無い」状態でも、b はエラー、c は警告、d は正常と、意図に応じて三者三様に分かれる。これがビルド時に走るので、無人で追記された記事のうち書き漏らしだけが確実に止まる。

応用: 「終了日を埋めなかった」ことを積極的に記録する

この設計の副産物として、deadlineNote は「調べた結果として終了日が無かった」という確認の痕跡になる。あとから記事を見返したとき、空欄が「未処理」なのか「確認済みで無し」なのかが一目で分かる。無人運用では、この「確認したという事実」を残せるかどうかが、データの信頼性を左右する。

同じ発想は終了日以外にも使える。たとえば「価格を確認したが変動するため断定しない」ことを表すフラグ、「実在を2ソースで確認した」ことを表す confirmed 日付など、空欄の意味を一意にするための明示フィールドを足していくと、レビュー無しでも品質を保ちやすくなる。

実際の運用例は manga-deals(Kindleマンガのセール速報) で見られる。各記事に確認日と出典、そして終了日(または未公表である旨)が付いているのは、ここで作ったのと同じ仕分けをビルド時に通しているからだ。

まとめ

  • 「必須フィールドが空」を一律にエラーにも許容にもしない。空欄の意味が複数あるなら、意図を表す明示フィールドで一意にする
  • fail-closed(判断できないものは通さない)を基本に、「書き漏らし=error」「確認したが無し=warn」「概念が無い=ok」を分ける。
  • 「確認したが無い」を記録に残せると、無人運用でもデータの信頼性を保てる。

無人運用の記事バリデーション — 「終了日が無い」を書き漏らしと未公表に機械で仕分ける
https://blog.hashito.biz/2026/07/25/unattended-content-validation-deadline-missing-vs-unpublished/
著者
hashito
作成日
2026年7月25日
著作権