# データの不具合を直すときの7つの教訓：すでにあるデータ・一括処理の事故・重複の数え方・通知の関門

> データの不具合を直すときに踏んだ7つの記録。関門を直しても既存データは直らない、後から掃除が入口の不具合を隠す、一括処理の事故、重複の数え方、通知は取り消せない。

- URL: https://koshinstudio.com/notes/data-repair/
- 公開: 2026-10-08
- 書いた人: Koshin（https://koshinstudio.com/about/）
- サイト: Koshin Studio

  English version (https://koshinstudio.com/en/notes/data-repair/)

トレカダッシュ 開発の記録 ／ 7 件のまとめ

  入口の不具合を直しても、すでに入ってしまったデータは直りません。逆に、後から掃除する仕組みがあると、入口の不具合が見えなくなります。ここには、データの不具合を直すときに実際に踏んだ 7 つの失敗と、そこから決めた手順を、起きた順にまとめました。

  それぞれの節は、もとは別々に公開していた記録です（2026 年 10 月から）。数字と出典は、その時の作業の記録のままです。テーマごとに読めるよう、1 つの記事にまとめ直しました（2026 年 11 月 7 日）。

## 一括処理が「全記事」に当たった：書く前に数える、テストは壊して確かめる

  2026 年 10 月 8 日の記録。

iOS アプリ「トレカダッシュ」で、記事の状態をまとめて直す新しい処理が、最初に流したとき、対象にするつもりのなかった記事まで、全部に当たりました。別のサイト由来の 18 件が、誤って、表示に戻っていました。事故の直し方と、その後に、同じ穴をふさぐテストを作ったとき、「そのテストが、本当に効くか」を、どう確かめたかを、HANDOFF の記録で書きます。

  このサイトには、トレカダッシュの作品ページがあります（トレカダッシュ (https://koshinstudio.com/works/torecadash/)）。ここに書くのは、開発の記録です。

### 何をする処理だったか

  アプリには、出品の価格が妥当かを調べる「価格の関門」があります（別の記事 (https://koshinstudio.com/notes/silent-failures/#s7)に書きました）。この関門で、価格だけを理由に非表示にされた記事のうち、商品の取り違えが原因で、本当は正しい記事を、表示に戻す処理を足しました。戻す条件は、「正しい商品の定価と、出品価格が一致すること」です。

### 事故：全記事に当たった

  この処理は、最初、取り込み元を区別せず、全記事に当たりました。その結果、別のサイト（Amazon、カードラボ、ミニストップなど）由来の記事 18 件が、表示に戻ってしまいました。本来、非表示のままであるべきものです。

  直し方は、二つです。

- 範囲を絞る。処理が当たる記事を、その取り込み元のものだけに、限りました（サイトの識別子が、その取り込み元で始まるもの）。

- 戻したものを、元に戻す。誤って表示に戻した 18 件は、記事の状態の履歴（statusHistory）から特定して、元の状態（非表示で、価格で却下されたもの）に戻しました。

  ただし、この 18 件には、取り返せなかったものがあります。処理が、元の定価の欄を、出品価格で上書きしていました。値は、出品価格のままです。

  原因は、書き込みの前に、対象の範囲を、数えていなかったことでした。記録にも、そう書いてあります。「一括の書き込みは、対象の範囲を先に数えてから」。

### 同じ穴をふさぐ、テストを作る

  この日の点検で見つかった問題（9 種セットの誤分割、単品カードの混入、カードではない商品の混入など）を、二度と起こさないために、テストを作りました。catalog-guards.test.js に 66 件、ほかと合わせて、全 6 本を、1 つの手順（run-all.sh）で通せるようにしてあります。

  あわせて、毎日の運用連絡に、警告を足しました。突き合わせの取りこぼし率が、基準（抽選図鑑 80%、ポケチュー 75%、X のまとめ 75%）を下回ったとき、30 時間動いていないとき、商品リストに、単品カードや、カードではない名前の商品が現れたときです。判定は、副作用のない純粋な関数で書いてあり、直れば、次の回で、自然に消えます。

### テストが効くかを、壊して確かめる

  テストは、書いただけでは、効いているか分かりません。常に通るテストは、何も守っていないかもしれないからです。そこで、本番のコードの複製の、規則を 1 つずつ外して、テストが失敗することを、確かめました。

- 規則を 1 つ外す（壊す）。

- テストを流す。

- 失敗すれば、そのテストは、その規則を、守っている。

  この手順で、6 種類の改変を試して、6 種類とも、テストが失敗しました（全部、検出できた）。

  運用のきまりも、記録に残しました。functions/index.js を触ったら、配備の前に、必ず、全テストを通します。

### 分かったこと

> 一括の書き込みは、先に数える。テストは、壊して確かめる。

- 書く前に、「何件に当たるか」を数える。下見をせずに流すと、範囲の間違いに、書き終わってから気づきます。

- 元に戻せる形で書く。履歴から特定できたので、18 件を戻せました。ただし、上書きした欄は、戻せません。

- 通るテストより、壊れたら落ちるテスト。規則を外して、落ちることを見て、はじめて、そのテストを信じられます。

  この記事は、HANDOFF（§272）の記録（2026年10月7日）から書きました。件数は、記録に書かれたものです。

## 「重複は実質ゼロ」と数えたのに、監査は112組を出していた

  2026 年 10 月 8 日の記録。

iOS アプリ「トレカダッシュ」には、同じ商品が複数の商品として登録されてしまう「重複」を、毎晩、統合する処理があります。ある日、自分で数えて、「重複は実質ゼロ」と記録しました。ところが、リポジトリの監査は、112 組を出していました。自分の測り方が、間違っていたのです。コミットの記録で、経緯を書きます。

  このサイトには、トレカダッシュの作品ページはまだありません。ここに書くのは、開発の記録です。

### 「ゼロ」と数えて、112 組が出た

  112 組の実体を見ると、たとえば、ヴァイスシュヴァルツの「ブースターパック ラブライブ！サンシャイン!!」が、7 件に割れて登録されていました。統合の処理が、毎晩動いているはずなのに、です。

### 原因：毎晩、途中で時間切れになっていた

  統合の処理は、全ジャンルを通すと、約 15 分かかります。ところが、毎晩の定期実行に与えている持ち時間は、480 秒（8 分）でした。順番は、毎回、同じジャンル（ポケモン）から始まります。つまり、毎晩、ポケモンの途中で時間切れになり、後ろのジャンルには、永久に届かなかったのです。

  数えたときに「ゼロ」だったのは、見ていた範囲（先頭のジャンル）だけが、きれいだったからでした。

### 直し方

- 持ち回りにする。前回の続きのジャンルから始めて、時間予算（6 分）が尽きたら、止めます。次の晩は、止まったところから続きます。

- まず、手で完走させる。全ジャンルを手で最後まで流すと、重複は 112 組から 0 組に、商品は 2,753 件から 2,570 件になりました。

### その前に分かっていたこと：1 回では終わらない

  この 2 日前（9 月 5 日）にも、統合について、わかったことがありました。A を B に統合すると、B の名前が、別の C と同じになることがあります。すると、1 回では終わりません。実データでは、こうでした。

    回 | 統合した件数 | その結果

    1 回目 | 138 | 新たに 53 組の同名が発生

    2 回目 | 54 | 同名の重複は 0 組

  商品は 2,935 件から 2,713 件になりました。そこで、毎日の枠では、統合が 0 件になるまで繰り返す（上限 3 回）ようにしました。今回の「時間切れ」は、この繰り返しの、さらに手前の問題でした。

### 同じ日に、ほかにも三つ

- 受付中の抽選 6 件が、別の商品の定価で、消えていた。7,200 円（公式の 360 円 × 20 パック）の抽選が、記事に残っていた別の商品の定価 27,500 円と比べられて、価格で却下されていました。直す条件に、「証明できるときだけ直す」を足しました。実データの全件で、条件に合うのは 10 件で、全部が正規の価格の販売でした。11 件を、表示に戻しています。

- 突き合わせの誤報。「記事はあるのに非表示」の 17 件のうち、9 件は、同じ店の別の書き方で、代わりの記事が表示されていました（「千歳鳥山店」と「千歳烏山店」のように）。代わりが表示中なら、カバー済みと数えるようにして、カバー率は 87% から 90% に、非表示は 17 件から 4 件になりました。

- 監査のほうが、止まっていた。監査用の読み取りの部品が、=> のすぐ後ろにある正規表現を、割り算と読み違えて、監査 4 本が、ReferenceError で落ちていました。切り出せなかった名前を、黙って飛ばさず、理由を出すように直しています。

### 分かったこと

> 「ゼロ」と数えたら、まず、何を数えたかを疑う。

- 見た範囲が、全体とは限らない。毎晩、先頭のジャンルだけが処理されていたので、先頭だけを数えれば、きれいに見えます。

- 時間に制限のある処理は、「終わったか」を見る。動いていることと、最後まで届いていることは、別です。

- 数える道具も、壊れる。監査が止まっているときは、結果が「0 件」に見えます。止まっていることに、気づく仕組みが要ります。

  この記事は、コミット（2026年9月5日、9月7日）の記録から書きました。

## 「後から掃除するから大丈夫」が、入口の不具合を隠していた：商品が毎日 400 件そっくり作り直されていた

  2026 年 10 月 17 日の記録。

iOS アプリ「トレカダッシュ (https://koshinstudio.com/works/torecadash/)」の商品のデータには、毎日夜に、重複した商品をまとめる掃除の処理があります。「後から掃除するから、大丈夫」。そう思っていた場所に、毎日、そっくりな商品が作り直されている入口の不具合が、隠れていました。

  2026 年 8 月 27 日の総点検で、「この日だけで商品が 437 件、作られていて、そのうち 410 件は、記事が 1 件も付いていない」と分かりました。

  数字は、2026 年 8 月 27 日の作業の記録から書いています。

### 原因：「名前の完全一致」でしか、探していなかった

  公式のカタログから、商品を登録する処理は、「同じ商品が、すでにあるか」を、名前の完全一致だけで判定していました。

  既存「拡張パック「古代の咆哮」」  ↔  カタログ「拡張パック 古代の咆哮」
既存「Vスタートデッキ草（全角空白）フシギバナ」  ↔  カタログ「Vスタートデッキ草 フシギバナ」
  括弧、空白、全角と半角の違いだけで、「まだ無い」と判定され、毎日、そっくりな商品が作り直されていました。

  作られた重複は、後から、週 1 回と毎日の統合の処理が片付けていました。だから、気づけませんでした。ただ、その間は、利用者に同じ商品が 2 つ見え、記事の紐付け先も割れます。

  直し方は、統合の処理が使っているのと同じ正規化（括弧、空白、全角と半角を吸収する）で比べることでした。カタログ側にも、表記ゆれの重複があるので、同じ実行の中では、同じキーを 1 つに絞ります。

  教訓は、「後から掃除するジョブがある」仕組みは、入口の不具合を隠してしまうことです。掃除の件数が、毎回同じくらい出ているなら、片付いているのではなく、毎日、作られ続けています。

### もう 1 つ：6 つの商品が、1 つに吸い込まれていた

  マジック：ザ・ギャザリングの「FINAL FANTASY」の統率者デッキ 6 種（サイオンズ・スペル、リミットブレイク、カウンター・ブリッツ、トランス・リアニメイト、ほか）が、すべて 1 つの商品に吸い込まれていました。

- 商品の別表記に、ほかの弾の名前が入る。

- それが、AI の候補に出る。

- 次の誤った照合を呼ぶ。

  という、自己増殖の形でした。別表記を掃除するだけでは、直りませんでした。AI が、同じ商品を選び続けるからです。そこで、括弧の中の固有名が違う商品を、照合の候補から外す判定（namesDifferentQuotedVariant）を、候補のフィルタにも入れました。候補から消えると、AI は「新しい商品」と答えられるので、正しい商品が作られます。

  誤検知を潰すために、括弧の中が、総称、地名、店名、数量、状態のときは、固有名として扱いません。実データで確かめると、この条件を入れる前は、12 件のうち 6 件が誤検知でした。

  また、採用しなかった案もあります。「商品名の固有名が、別表記に含まれないなら、消す」という強い規則も試しました。43 件のうち 6 件が誤検知（型番が同じ別の弾、「＋（プラス）」の付く名前など）だったので、見送りました。

### 自分の修復で、先着に締切を付けてしまった

  同じ告知のページの記事から、日付を引き継ぐ修復の処理（inheritDatesFromSameAnnouncement）を流しました。すると、先着（無くなり次第終了）の記事に、同じページの抽選記事の締切を、渡してしまいました。先着には、締切という概念がありません。利用者に、「その日まで買える」という嘘の期限を見せることになります。

  引き継ぐ側の 2 か所（保存のときの処理と、修復の処理）の両方で、先着には、締切を渡さないようにしました。付けてしまった 1 件は、消しました。

  教訓は、修復の処理を流したら、流す前と流した後で、監査を比べることです。「直った件数」だけを見ていると、自分が増やした異常に、気づけません。

### 分かったこと

> 後から掃除する仕組みは、入口の不具合を隠す。

- 掃除の件数が毎回同じなら、毎日作られている。片付いているのではありません。

- 入口と掃除で、同じ正規化を使う。入口が厳しいと、掃除が肩代わりします。

- 別表記の掃除だけでは、自己増殖は止まらない。AI の候補から外す必要があります。

- 強い規則は、誤検知を数えてから。43 件で 6 件の誤検知なら、見送ります。

- 修復の前後で、監査を比べる。自分が増やした異常を見つけるためです。

## 「変な記事がめっちゃ増えてる」：作られた経路ごとに数えて入口を塞いだ。数え方も 2 回、間違えた

  2026 年 10 月 17 日の記録。

iOS アプリ「トレカダッシュ (https://koshinstudio.com/works/torecadash/)」について、ユーザーから、こういう指示が来ました。「変な記事が、めっちゃ増えてる。締切も入っていないし、おかしい記事ばかり。恒久的に解決して。ほかの経路で作った記事も、徹底的に調べて」。

  この記事は、記事を作られた経路ごとに数えて、入口を塞いだ記録です。そして、数え方を、2 回間違えた記録でもあります。

  数字は、2026 年 8 月 28 日の作業の記録から書いています。

### 経路ごとに、数えた

  記事は、いくつかの経路（X の店の公式の投稿、店のサイト、まとめサイト、AI による探索など）で作られます。経路ごとに、どんな「変な記事」があるかを、数えました。

    経路 | 抽選 | 締切なし | 検索結果が行き先 | トップページが行き先 | まとめ自身が行き先

      X（店の公式） | 118 | 11 | 0 | 1 | 0

      店のサイト（直接） | 63 | 1 | 1 | 0 | 0

      まとめサイト A | 39 | 33 | 3 | 2 | 8

      まとめサイト B | 28 | 11 | 0 | 1 | 5

      AI による探索 | 4 | 2 | 0 | 0 | 0

      合計 | 252 | 58 | 4 | 4 | 13

### 数え方を、2 回、訂正した

- 最初は、全記事で数えて、「1,398 件中 1,204 件が、締切なし（86%）」と出しました。締切は、抽選だけの概念です。先着の販売（1,055 件）と予約（91 件）には、元々ありません。項目の意味を、確かめずに数えました。

- 「保存された塊が、別の店（98%）」と書きましたが、実際は、投稿の全文が保存されていて、先頭がたまたま Amazon だっただけでした。先頭のマーカーだけを見て、断定しました。

### 見つけた穴と、直し

  1. まとめの表に残る「終わった回」を、毎日取り込んでいた（33 件）
  相手のまとめサイトは、過去の回も、表に残します。実データでは、あるお店の「7 月 13 日〜14 日」を、8 月 27 日に取り込み、締切は過去なので採用されず、締切が空の記事になっていました。締切が無いと、「締切が過ぎたら消す」仕組みが働かず、永久に残ります。

  入口を、3 段で塞ぎました。

- 受付期間を日付で読んで、終了済みの行は、取らない。

- 締切が取れなければ、作らない。

- 店の行が終了済みなら、作らない。

  実地で確かめました。印を消して、取り込みを再実行し、24 店を読んで、新規は 0 件でした。

  2. 応募リンクが、応募ページでなかった（報告 5 件）
  検索結果のページ（?keywords=）や、サイトのトップページが、行き先として採用されていました。応募リンクを採用する、唯一の関門に、「検索結果の URL らしさ」と「トップページの URL らしさ」の判定を足しました。全部の経路が、この関門 1 本を通るので、ここだけで効きます。

  3. 記事が投稿の全文を持ち、読む側が切り忘れると、先頭の店を読む（39 件）
  記事の元の文（sourceText）には、その店の塊だけを入れるようにしました。全文は、検証用に、別の項目へ。読む側が何をしても、別の店の値を拾えない形にしました。

  4. 締切が、自分の店の行と食い違っていた（6 件中 5 件）
  締切は、「本文の塊、応募リンク先、添付の画像」の順に探します。しかし、あとの 2 つは、その店の情報とは限りません。実データでは、保存された締切と、その店の行が、53 日、18 日、20 日ずれていました。どれも、終わった抽選が、受付中として並ぶ状態です。

  「終わりが明示されたときだけ返す」関数を作り、締切を確定させる最後に、行と食い違ったら、行を信じる関門を入れました。注意点は、「6 月 19 日〜」のような、始まりだけの行でも、その日を返す関数を、そのまま上書きに使ってはいけないことです。

### 掃除は、ドライランから

- 「締切が無い記事を隠す」処理：33 件。実行する前に、1 件ずつ、自分の行の期間を読み、33 件全部が終了済みで、受付中は 0 件と確かめてから、実行。

- 「締切を、行に合わせ直す」処理：5 件。隠さず、過ぎていれば、既存の締切超過の仕組みに、消させる。

  どちらの修復も、毎日 4 時 20 分と 4 時 30 分に、自動で実行するようにしました（190 個から 192 個）。

### 結果

     | 対応前 | 対応後

      表示中の抽選 | 252 件 | 199 件

      締切なし | 58 件 | 25 件

      まとめ A 由来の締切なし | 33 件 | 0 件

      締切が過ぎたまま表示 | 5 件 | 0 件

      「相手は終了、こちらは受付中」 | 12 件 | 7 件

  残る 7 件は、こちらが新しい回をつかんでいる側の可能性が高いものです（相手の表が古い）。

### 分かったこと

> 経路ごとに数える。そして、数え方そのものを疑う。

- 経路ごとに、数える。どこから変な記事が入るのかが、表で見えます。

- 項目の意味を、確かめてから数える。締切は、抽選だけの概念でした。

- 先頭のマーカーだけで、断定しない。全文が保存されていただけでした。

- 締切が取れなければ、作らない。空の締切の記事は、永久に残ります。

- 別の店の値を拾えない形にする。読む側の注意に、頼らない。

- 食い違ったら、その店の行を信じる。応募リンク先や画像は、その店の情報とは限りません。

## 関門を直しても、すでにあるデータは直らない：「もう試した」印・URL の目印・対応表の 3 つの実例

  2026 年 10 月 17 日の記録。

iOS アプリ「トレカダッシュ (https://koshinstudio.com/works/torecadash/)」の開発では、「直したのに、直っていない」という報告が、何度も、同じ形で来ました。コードを直す。テストも通る。ところが、画面には、まだ間違いが残っている。

  理由は、コードを直しても、すでに保存されているデータは、書き換わらないことです。この記事では、その 3 つの形を、実例で書きます。

  数字は、2026 年 8 月 26 日、9 月 3 日、9 月 8 日の作業の記録から書いています。

### 1. 関門をゆるめたのに、「もう試した」印が残っていた（9 月 3 日）

  商品の照合の判定（関門）に、直すべき間違いがありました。直して、配備しました。ところが、すでにある記事 32 件のうち、12 件が直らないままでした。

  理由は、「見送りの印」でした。関門が一度、「この組み合わせは違う」と見送ると、その記事には、「もう試した」という印が付き、7 日間は再挑戦しません。判定を直しても、印のあいだは、見直されません。しかも、その 12 件の中に、締切が 9 月 5 日と 6 日の、受付中の抽選が 4 件ありました。抽選は数日で締め切るので、印の期限を待っていると、そのまま消えます。同じ日に、同じ形（複数の商品に分割する処理の、14 日間のクールダウン）が、もう 1 つ見つかりました。

  直し方は、印を見て「見送る」前に、いまの判定で、もう一度確かめることでした。判定を、AI も外部も使わない、純粋な関数に切り出せば、費用は増えません。あわせて、「そのとき、どの材料で判断したか」（画像を見たか、など）を印に残しておくと、材料が増えたときだけ、再挑戦できます。

### 2. 状態の目印を、URL に載せていた（9 月 3 日）

  分割して作った記事（子）には、「分割済み」の目印が必要です。当時は、その目印を、URL の一部（?_product=商品名）に置いていました。「この印が付いていたら、二度と分割しない」。

  ところが、このプロジェクトは、応募先の URL を、あとから店の公式のページに張り替えます。同じ日に、その張り替えを全件に回すようにしました。すると、URL に載せた情報は、必ず失われます。子の記事 832 件のうち、111 件が、目印を失いました。目印がなくなった子は、「まだ分割していない親」に見え、再び分割されました。自分自身を「代わりの記事」として指す、壊れた記事が、30 分で 8 件から 14 件に増えていました。過去にも、同じ経路で、1 件が 18 件に増えた事故がありました。

  直し方は、判定を、書き換わらない項目（splitFromArticleId のように、記事に残る項目）で行うことでした。もう 1 つの教訓は、「今まで一部にしか当たっていなかった処理を、全件に回す」直しを入れた日は、その処理が書き換える項目に頼っている所を、必ず洗うことです。

### 3. 対応表からキーを外しても、書かれた値は消えない（8 月 26 日）

  ある店のチェーンを、「いちばん近い支店を探す」対応表から外しました。コードの表からは消えました。ところが、記事に書かれた「近い支店の鍵」は、そのまま残っていました。アプリは、「鍵があるから、距離を出そう」と判断して、準備だけして、答えが返ってきません（サーバーは、解決できない鍵に 400 を返します）。永久に埋まらない記事が、3 件ありました。

  表の変更は、コードの変更で、データの移行までは自動で起きません。表から何かを外すときは、既存のデータの掃除も、セットで入れます。これは、「新しい非表示の印を足したのに、元に戻す処理に足し忘れる」ミスと、対になる型です。あちらが足す側の漏れで、こちらが引く側の漏れです。

  掃除を入れると、監査に新しい異常が出ることがあります。それは、隠れていた本物の穴が、見えただけです。鍵を戻して、隠さないことにしました。

### 同じ形の、5 つの報告（9 月 8 日）

  9 月 8 日に、監査 61 本を流して見つけた 7 件のうち、5 件が、同じ形でした。「直す仕組みはあるのに、対象に届いていない、または順番が回らない」。

    症状 | 根っこ

      店名に営業日が混ざる（報告 4 回目） | 直す側と関門が、別の関数。コメントの「同じ物差し」が、嘘になっていた

      通知を押しても、記事が無い（322 件） | 埋める処理の対象に、その印（分割済み）が、入っていなかった

      公式イベントが表示されたまま | 弾く関門は、あるのに、当て直しの順番が回っていなかった

      定価の目安の単位ずれ（24 件） | 直しが、25 本の持ち回りに埋もれて、一巡 2.5 日

      「時間予算のため 19 件を次回へ」 | 安い直しが、重い直しと、同じ予算・同じ持ち回りに入っていた

  関門は、新しい記事しか守りません。すでにある記事を直すのは、別の処理です。そこが動いていないと、「直したのに、直っていない」が、延々と繰り返します。同じ形の報告が何度も来るなら、たいてい、入口を直して、出口（すでにあるデータ）を直していません。

  共通の解決策は、関門そのものである「保存の関門を、すでにある記事にも当て直す処理」を、持ち回りから外して、毎回走らせることでした。中身は、記事を 1 回読んで関門に通すだけです（AI も外部も使わず、1 回 20 件と印 50 件の上限つき）。

### 分かったこと

> 入口を直したら、出口（すでにあるデータ）も直す。

- 関門を足したら、当て直しが毎回走るかを確かめる。関門は、新しい記事しか守りません。

- 「もう試した」印は、いまの判定で確かめ直す。期限を待つと、抽選は終わっています。

- 状態の目印は、書き換わらない項目に置く。URL は、あとから張り替わります。

- 表から外したら、データも掃除する。コードの変更では、データは動きません。

- 同じ報告が 2 回来たら、出口を疑う。入口の直しだけでは、足りていません。

## 自分で作った仕組みが、自分を傷つけた 4 つの記録：要約が条件を捨て、1 つの未定義が保存を止め、点検が本番の IP を閉め出された

  2026 年 10 月 17 日の記録。

iOS アプリ「トレカダッシュ (https://koshinstudio.com/works/torecadash/)」の開発では、外から来る不具合のほかに、自分で作った仕組みや、自分でした点検が、自分に被害を出したことが何度かありました。この記事は、そのうちの 4 つの記録です。どれも、数字で見えたあとに、直しました。

  数字と日付は、2026 年 8 月 28 日〜9 月 15 日の作業の記録から書いています。

### 1. 「15 文字程度」の指示が、応募の条件を捨てていた（9 月 14 日）

  アプリは、抽選の応募の条件（フォローとリポストが必要、会員証が必要、など）を、AI に短くまとめさせて画面に出しています。表示中の抽選 671 件を、元の告知（X の投稿の本文と画像）と、AI で突き合わせて点検しました。

  食い違いより、「抜け」が多く見つかりました。

    抜けていた条件 | 件数

      リポスト | 23

      会員 | 20

      アプリ | 13

      LivePocket（応募サービス） | 13

  原因は、AI への指示文でした。要約を「15 文字程度」と指定していたので、AI は条件を 1〜2 個に削っていました。さらに、「受け取りや購入の条件は書かない」という規則を、「購入時に会員証を確認」まで広げて捨てていました。会員証は、持っていないと買えないので、応募の条件です。

  文字数の制約は、AI に「どれを捨てるか」を選ばせます。捨てる規則を書くと、境界にある条件まで捨てられます。直し方は、「全部、上限は何文字」と頼み、必ず残す要素を並べることでした。点検でも、AI の判定を数えるだけで終わらず、代表例を元の画像で確かめてから直しました。AI の検品は、うるさい（誤って指摘する）ことがあるからです。

### 2. 1 つの値が「未定義」だと、保存が全部失敗した（8 月 28 日）

  データを、まとめて書き戻す処理がありました。取得サービス（Scrape.do）の設定の記録を、全体を 1 つの塊として書き込みます。ある日、この書き込みが、毎回、こういうエラーで失敗していました。

  Cannot use "undefined" as a Firestore value (found in field "domains.`www.yamada-denkiweb.com`.super")
  1 つのサイトの項目に、undefined（値が定義されていない）が 1 つ混ざっていただけで、その後、どのサイトの保存も失敗し続けました。

  被害は、別のサイトに出ました。「どの設定でも取得できないサイトを、6 時間休止する」という記録が、保存できなかったのです。あるサイトを、巡回のたびに 3 つの設定（1 + 10 + 25 = 36 クレジット）で叩き続け、1 日 13,183 クレジット、その日の全消費の 41% を溶かしていました。取得サービスは、日次の予算を時刻で按分するので、1 つのサイトがその日の 41% を使うと、ほかのサイトに回せる分が、その分だけ減ります。

  教訓は、まとめて書き戻す形の処理は、1 つの汚れが全体の保存を壊すことです。値を書く前に、undefined を取り除く。そして、保存が失敗したら、黙らず、目立つ形で知らせます。

### 3. 点検のために、本番と同じ IP を閉め出された（9 月 15 日）

  在庫の点検のスクリプトを、本番と同じサーバーから動かしました。通販サイト（Yahoo!ショッピング）の商品ページ 501 件を、6 並列で、素のリクエストで読みました。数分で、そのサーバーの IP アドレスごと、HTTP 489（アクセスを拒否する応答）が返ってくるようになりました。数時間たっても解けませんでした。

  本番の取り込みも、在庫の再検証も、同じ IP から Yahoo! を読んでいます。点検の都合で、本番の目を潰してしまったのです。しかも、点検側は、読めなかったページを取得サービスへ回すので、そのクレジットも無駄に使い始めていました。

  直し方（というより、決まりごと）は、本番と同じサーバーから通販サイトをまとめて読むときは、1 並列、1 件ごとに間を空けることです。最初に数件だけ流して、応答が 200 のままかを見てから、広げます。在庫は、まずページの中の構造化データ（在庫の有無が書かれた部分）を、少数で確かめ、全件の確認は、本番の再検証の処理に任せます。

### 4. 直っても鳴り続ける警告を、一晩に 4 つ作った（8 月 29 日）

  異常を知らせる警告を、たくさん入れてきました。2026 年 8 月 29 日の 1 晩で、直す道があるのに鳴り続ける警告を 3 つ直し、自分で 4 つ目を作りました。4 つ目は、AI の利用残高が切れたときの印で、入金して復旧しても、24 時間、消えない作りでした。

  同じ形は、前にも出ています。

- データ取得の費用の警告が、「月末には必ず 8 割を超える」。

- 取得サービスの残りの警告が、「月末には必ず残りが 2 割になる」。

- 「0 件」が正常な状態なのに、0 件で警告が鳴る。

- 取り消した定価が、翌朝、元に書き戻される往復。

  消えない警告は、本物の異常を埋もれさせます。それ以来、警告を書くときは、鳴る条件と同時に消える条件を作ります。自分に聞く質問は 3 つです。

- 問題が直ったとき、この警告は、誰が消すのか。

- 月末や月初など、時間がたつだけで、必ず鳴る指標になっていないか。

- 0 件や低い値が、正常な状態ではないか。

  消す人がいない警告は、作りません。成功したときに印を消す処理を、警告と同じ変更に入れます。

### 分かったこと

> 自分の道具は、自分にも効く。

- 制約は、捨てるものを選ばせる。文字数の上限を付けるなら、残すものを書く。

- まとめて書く処理は、1 つの汚れで全体が止まる。書く前に、取り除く。

- 点検は、本番の邪魔をしない。同じ IP、同じ上限を使う処理は、控えめに。

- 警告は、消える条件とセットで作る。

## 通知は取り消せない：直近 72 時間の通知の 19% が誤通知で、関門が通知の「後」に効いていた

  2026 年 10 月 17 日の記録。

iOS アプリ「トレカダッシュ (https://koshinstudio.com/works/torecadash/)」は、トレカの抽選販売の情報を集めて、新しい情報が見つかると、利用者のスマートフォンに通知します。集めた記事には、「本当に応募できる抽選か」を確かめる関門（フィルター）があります。当選者の発表、買取の告知、カード以外の商品、などを出さないための仕組みです。

  ところが、2026 年 9 月 20 日の点検で、通知した記事の約 5 件に 1 件が、関門を通るべきではない記事だったと分かりました。関門はあったのに、通知は止まっていませんでした。

  この記事は、2026 年 9 月 20 日の再点検の記録から書いています。

### 直近 72 時間の通知を、全部数えた

  点検の方法は、単純です。直近 72 時間に通知した記事を全部取り出して、1 件ずつ「通知してよかったか」を判定し直しました。

    通知した件数 | 誤通知 | 割合

    644 件 | 122 件 | 19%

  誤通知の内訳は、次のとおりです。

    誤通知の理由 | 件数

      当選者向けの案内（応募できない） | 30

      古い投稿（何日も前の投稿が、新着のように通知された） | 27

      期限切れ | 11

      応募する手段が無い | 14

      カード以外の商品 | 10

      参加条件のある抽選 | 8

      そのほか | 22

### 関門は、「出すか」を後から決めていた

  記事の関門は、あとから記事を「非表示」に下げる形でも働いていました。ところが、通知は、記事を保存した約 0.6 秒後に飛びます。関門が後から記事を下げても、通知は、もう送られています。

  実際に、関門が後から非表示にした記事 30 件のうち、25 件は、すでにプッシュ通知が届いた後でした。画面からは消えても、スマートフォンに届いた通知は、取り消せません。

### もう 1 つの原因：取り戻しで、古い投稿が新着になった

  X の監視が止まっていた期間がありました。9 月 19 日 12:44 から、その分を取り戻す処理が動きました。このとき、9 月 13 日から 18 日の投稿が、新着として 367 件まとめて通知されました。

  止まっていた間の投稿を取り込むのは正しいことです。しかし、「いつ見つけたか」ではなく、投稿の日付で見ないと、何日も前の投稿が、いま出たばかりの情報に見えてしまいます。

### 直し方：通知を送る側で、同じ関門を通す

  直し方は、「保存のときに効く関門」を増やすだけでは足りない、という点にありました。通知は取り消せないので、通知を送る関数の入口に、記事の関門と同じ判定を通すようにしました。

- 判定は、記事を保存する前の関門（screenArticleBeforeSave）に足す。通知を送る関数の入口も、同じ関数を通る。判定が 2 か所に別々に書かれて、食い違うことがない。

- 古い投稿は、保存した日ではなく、投稿日で見る。

- 取り戻しや再起動のあとは、通知を見送った理由の内訳を、必ず確認する。

### 分かったこと

> 取り消せないものは、出す前に止める。

- 関門は、取り消せない出口の手前に置く。画面に出す関門は、あとから下げられます。通知は、下げられません。

- 保存時の判定と、出口の判定は、同じものを使う。片方にだけ足すと、すり抜ける道が残ります。

- 「新しい」は、日付で決める。見つけた日ではなく、投稿された日です。

- 全件を数える。通知した 644 件を 1 件ずつ判定し直して、はじめて 19% と分かりました。

## それぞれの要点

- 一括処理が「全記事」に当たった：記事の状態をまとめて直す処理が、全記事に当たり、18件が誤って表示に戻った。履歴から戻した経緯と、テストが効くかを、規則を1つずつ外して確かめた記録。

- 「重複は実質ゼロ」と数えたのに、監査は112組を出していた：重複を統合する処理が、毎晩、先頭のジャンルの途中で時間切れになっていた。「ゼロ」と数えた自分の測り方が間違っていた。持ち回りに直して112組を0組にした記録。

- 「後から掃除するから大丈夫」が、入口の不具合を隠していた：カタログからの商品登録が名前の完全一致でしか探さず、括弧・空白・全角の違いで毎日そっくりな商品を作り直し、この日だけで 437 件（410 件は記事なし）。統合の掃除が後から片付けていたため気づけなかった。

- 「変な記事がめっちゃ増えてる」：表示中の抽選 252 件を経路ごとに数えた。まとめの表に残る終わった回を毎日取り込んで締切が空になっていた（33 件）ほか、応募リンクが検索結果やトップ、締切が自分の店の行と食い違うものを、入口で塞いだ。

- 関門を直しても、すでにあるデータは直らない：判定を直したのに既存の 32 件のうち 12 件が直らなかった（印が 7 日残る）、URL に載せた目印が張り替えで失われて 111 件が再分割された、対応表から外しても書かれた値が残った。同じ形の報告が 5 件だった監査の記録。

- 自分で作った仕組みが、自分を傷つけた 4 つの記録：「15 文字程度」の指示で応募の条件が 671 件の点検で抜けていた、undefined が 1 つで保存が全部失敗し 1 日の消費の 41% を溶かした、点検の 6 並列で本番と同じ IP が拒否された、直っても鳴り続ける警告を作った。

- 通知は取り消せない：直近 72 時間に通知した 644 件のうち 122 件（19%）が誤通知だった。記事の関門は後から記事を下げるが、通知は保存の約 0.6 秒後に飛ぶ。下げた 30 件のうち 25 件はもう通知済みだった。通知を送る側の入口に同じ関門を通した記録。

  ほかの記録は、Notes の一覧 (https://koshinstudio.com/notes/)から、テーマごとに読めます。トレカダッシュそのものは、作品ページ (https://koshinstudio.com/works/torecadash/)に書いています。
