# 競合ウォッチ自動化の実験素材

記事「競合ウォッチを自動化する」で使ったコードと、実験の記録です。日付はすべて2026-10-16（調査時点）。

## 中身

| フォルダ | 内容 |
|---|---|
| `watcher/` | 記事で作った監視の道具一式。`watch.py`（取得・比較）、`textify.py`（HTMLを文字だけに）、`summarize.py`（差分をAIにまとめさせる）、`prompt.md`、`targets.json`、GitHub Actions の `competitor-watch.yml`、動かしたときの出力例 `example-run/` |
| `noise/` | 実験1。12ページを約2分あけて2回取り、生のHTMLと文字だけで差分を比べた。`results.json` が結果。`slack_sim.py` は Slack の料金ページを20回取り、n回分を合算したときの誤検知率を出す（結果は `slack-sim.json`） |
| `replay/` | 実験2。Wayback Machine の月次スナップショット（8サイト・167組）を文字だけにして比べた。`pairs.json` が結果 |
| `summarize-test/` | 実験3。差分9件を、2通りのプロンプトで各5回ずつAIにまとめさせた（90回）。`cases.json` が入力の差分、`run.py` が実行、`score.py` が採点、`quotecheck.py` が引用行の実在チェック。`out/` は90回分の生の出力（zipにだけ入れている） |
| `make_figures.py` | 記事の図（明暗2枚ずつ）を出力する |

## watcher の動き方

- 毎回 `watch.py` → `summarize.py` の順に動かします。GitHub Actions の `competitor-watch.yml` もこの順です。
- `watch.py` は動くたびに、その日の `changes/<日付>/*.diff` をいったん消してから、今回取れた差分だけを書きます。同じ日に何度か動かしたとき、`changes/<日付>/` に残るのは最後に動かした回の差分です。それより前の回の差分は、保存した Git の履歴で見てください。
- `reports/<日付>.md` は、その日に最後にまとめた内容です。差分がない状態で動かし直しても、新しいメモは作りません（AIも呼びません）。
- `summarize.py` だけを何度も動かすと、そのたびにAIを呼びます。二重に料金がかかるのを防ぐ仕組みは入れていません。

## 再現するとき

- 標準ライブラリだけで動きます（Python 3.12）。実験3と `summarize.py` は Claude Code（`claude` コマンド）が要ります。
- 実験3のモデルは Sonnet 5.5。`claude -p` に `--tools "" --setting-sources "" --strict-mcp-config --no-session-persistence --model claude-sonnet-5-5` を付け、空のフォルダで実行しています。
- ページの中身は取るたびに変わります。数字は再現しないことがあります。特に Slack の料金ページは、取るたびに別の中身が返ります。
- 実験3の集計だけなら、AIを呼ばずにできます。zip を展開して `summarize-test/` で `python3 score.py`（採点）と `python3 quotecheck.py`（引用の実在チェック）を動かすと、保存した90回分の出力 `out/` を集計します。`run.py` はモデルを呼んで出力を作り直すもので、料金がかかります。
- 生のHTML（`noise/raw/`、Wayback の `replay/snaps/`）は大きいので入れていません。`noise/fetch.sh`、`replay/cdx.py` と `replay/fetch.py` で取り直せます。

## 限界

- 実験3の差分は9件、モデルは1つ、実施日は1日です。「ルールつきが常に良い」とは言えません。
- 「推測の言葉」の数え方は、語のリストによる機械的なものです（`score.py` の `SPEC` に1回以上一致した出力を数える）。意味を読んで判定してはいません。
- Wayback のスナップショットは、収集の時刻や地域、ページの出し分けが揃っていません。差分の件数は、実際の更新の頻度より多く出ます。
- 差分に出る変化の「正解」は、私が差分を読んで決めています。
