これまでこの連載で書いてきた道具は、全部が自分のためのものだった。自分の時間を測る、自分の資産を眺める、自分の健康を追う。今回はじめて、自分以外の人のために作った道具の話を書く。
妻が週に5日走る、片道30分ほどの決まった道がある。そこが今日いつもより混んでいるかどうかを、家を出る前に知らせる。やりたかったのはそれだけだ。それだけなのに、いちばん難しかったのは「知らせすぎないこと」だった。
「毎日鳴る通知」は、通知していないのと同じ
最初に思いつく実装は単純だ。ルートの所要時間を朝に取ってきて、35分を超えていたら通知する。しきい値を決めて、超えたら鳴らす。
これは確実に失敗する。理由は、通勤時間帯はそもそも常に遅いからだ。
地図サービスは「渋滞がゼロだったら何分か」という理論値も返してくれるのだが、朝夕のこの道は、その理論値より毎日10分近く遅い。つまり平常運転がすでに「遅い」。理論値を基準に固定しきい値を置くと、毎朝きっちり通知が飛ぶことになる。
毎日鳴る通知は、3日で無視される。無視される通知は、存在しないのと同じどころか、本当に伝えたい日の一件まで一緒に埋める。だからこの道具の設計目標は最初から「速く知らせる」ではなく、**「普段は絶対に黙っていること」**になった。
「普段」を人間が決めない
そこで、しきい値を人間が決めるのをやめた。
やっていることはこうだ。毎回の実測値を、時間帯ごと(朝の便・夕方の便)に分けて記録していく。直近の記録から中央値を取って、それをその時間帯の「普段」とみなす。そして、
- 普段の1.3倍を超えていて、かつ
- 普段より5分以上遅い
この2つを両方満たしたときだけ通知する。
平均ではなく中央値にしたのは、事故で一度だけ跳ねた日に「普段」を引っ張られたくないからだ。中央値なら、たまの外れ値は順位が上にずれるだけで基準そのものは動かない。
そして2条件をANDにしたのがいちばん効いている。倍率だけだと、もともと5分で着く道が7分になっただけで鳴る。差分だけだと、1時間かかる道の+5分という誤差で鳴る。両方求めることで、「率としても大きく、絶対量としても体感できる」遅れだけが残る。
さらに、記録が4件たまるまでは何があっても通知しないようにした。サンプル3件の中央値を「普段」と呼ぶのは、ただの思い込みだからだ。導入直後の1週間、この道具は何も言わずにひたすら見ているだけになる。設計としてそれが正しい。
3週間走らせて、朝と夕方の性格が違うと分かった
実際に3週間動かして貯まった実測値がこれだ(単位は分)。
| 朝の便 | 夕方の便 | |
|---|---|---|
| いちばん速い日 | 27.6 | 30.2 |
| いちばん遅い日 | 38.4 | 33.6 |
| 中央値 | 31.5 | 31.7 |
| ブレ幅 | 10.8分 | 3.4分 |
面白いのは、中央値はほぼ同じ(31.5分と31.7分)なのに、ブレ幅が3倍以上違うことだ。
夕方は、どの日も30〜34分の間にきれいに収まる。ほぼ定刻運行と言っていい。対して朝は、27分で着く日と38分かかる日が同居している。同じ道、同じ距離、同じ運転で、日によって10分違う。
そして朝の内訳を曜日で並べ直すと、答えははっきりした。土日は27〜29分、平日は31〜38分。週末の朝は、平日より10分速い。
「朝は混む」というのは誰でも知っている。でも「うちの道は、平日と週末で朝の性格が別物」というのは、測るまで誰も口にしていなかった。日々の生活の中では、この10分は「今日はなんか流れてたね」という感覚に丸められて消えていく。
3週間、一度も通知していない
この期間、異常通知は一度も飛んでいない。
いちばん遅かった朝の38.4分でも、中央値31.5分の1.3倍=約41分には届かなかった。「5分以上遅い」のほうはクリアしていたが、ANDにしているので鳴らない。設計どおりだ。
作ったものが3週間なにも喋らないというのは、普通なら失敗を疑う。でもこの道具に限っては、それが正常動作である。沈黙が仕様という珍しい種類の道具になった。もちろん、本当にただ壊れて黙っているだけという可能性もあるので、それは毎朝ぜんぶの道具の死活を見張っている監視役のほうに任せてある。
正直な限界
ひとつ、設計上まだ解けていない穴がある。
上で見たとおり、朝の所要時間は平日と週末で10分違う。ところがいまの実装は、時間帯でしか分けていないので、中央値の中に平日と週末が混ざっている。混ざった中央値は当然、平日と週末のあいだに落ちる。
これが何を意味するかというと、日曜の朝に36分かかったとする。日曜としては明らかに異常だが、平日込みの中央値から見れば「まあそういう日もある」の範囲に見えてしまい、通知は飛ばない。週末の異常を見逃す方向にバイアスがかかっている。曜日タイプ別に基準を分ければ解決するが、それをやるとサンプルが1/2になって「4件たまるまで黙る」期間が倍になる。いまはまだ、データを貯めるほうを優先している。
もうひとつは、中央値が慣れも学習してしまうことだ。工事などでこの道が恒久的に5分遅くなったら、数週間後にはそれが新しい「普段」になり、道具は何も言わなくなる。異常検知としては正しい挙動だが、「前より遅くなった」という長期の変化を教える役ではない、ということでもある。
事故や災害の速報については、最初から追いかけないと決めた。そこは既存のニュースアプリのほうが速いし正確だ。同じことを二重に鳴らすと、結局どちらも読まれなくなる。この道具の担当は「事故ではないけれど、なぜか今日は流れが悪い」という、誰も速報してくれない領域だけでいい。
使っている技術
所要時間は Google Maps Platform の Routes API から取っている。渋滞を織り込んだ実勢の所要時間と、渋滞ゼロの理論値の両方が返ってくる。呼び出しは1日2回だけなので、無料枠にきれいに収まっている。
通知先は LINE で、妻専用の公式アカウントを新しく作った。既存の自分用アカウントに相乗りさせると、月間の無料通数を食い合ううえに、通知の性格が混ざるからだ。友だちが1人しかいないので、宛先を指定せず全員に配信する方式にしている。アクセストークンは有効期限が切れると静かに止まるので、期限切れを検知したら自動で取り直すようにした。家族に渡す道具は、自分用より一段ぶん壊れにくく作る必要がある。自分用なら「あれ止まってるな」で済むが、家族用は止まったことにすら気づかれないまま信用だけが減る。
実行は平日と週末の朝夕、決まった時刻にOSのタスクスケジューラから。走らない曜日は設定ファイルに書いてある。
記録メモ: 自分のための道具は、雑に作っても自分が困るだけなので、だんだん雑になる。人のための道具はそれができない。「誤報を1回出したら二度と読まれない」という制約が最初からあるおかげで、いままででいちばん設計を考えた道具になった。使う人が自分じゃないというだけで、こんなに変わるのかと思う。
家庭を自動化する道具を何十個も作ってきたが、そのほとんどは自分の困りごとから始まっていた。次に作るものは、頼まれてもいないのに家族の困りごとから始めてみようと思っている。
【追記】この記事を公開した直後に、前提が崩れた
公開した当日に、この道具について指摘を受けた。
ルートは下道優先になっている? 渋滞時に高速を使うルートになってしまうと、時間が変わらなくて通知が来ないのでは。
慌ててAPIのリクエストを見返したら、経路の条件を何も指定していなかった。つまり道を固定していない。その場で叩いて出てきたのがこれだ。
| 返ってきた経路 | 所要 | 渋滞なし理論値 | 距離 |
|---|---|---|---|
| 高速道路経由(実際にこれが返っていた) | 35.9分 | 31.4分 | 30.8km |
| 国道(下道) | 39.6分 | 41.9分 | 29.4km |
| 別の高速経由 | 43.2分 | 41.8分 | 37.1km |
3つの経路が8分差の中に密集している。経路APIは黙っていれば「その時いちばん速い道」を返すので、この条件では一位が日によって入れ替わる。そして妻が実際に走っているのは、下道のほうだった。
指摘のとおりだった。しかも害は2つあった。
1つめは、指摘そのもの。 下道が混むとAPIは高速側に逃げるので、返ってくる所要時間はほとんど伸びない。渋滞した日ほど「平常」に見える。異常検知として、これ以上ない壊れ方をしていた。
2つめは、自分でも気づいていなかったほう。 日によって違う道の記録が同じ履歴に積まれていたので、「普段」として計算していた中央値が、複数の別々の道の平均になっていた。基準そのものが存在していなかったことになる。
そして、この記事で「平日と週末で朝の性格が別物だ」と書いた部分——土日27〜29分、平日31〜38分というあの二極化も、交通量の差ではなく、単に日によって違う道を測っていただけかもしれない。測定値は本物だが、解釈は取り下げる。どちらが原因だったかは、履歴に経路を保存していなかったので、もう遡って確かめられない。
直したことと、失ったもの
修正自体は、経路の条件に「高速と有料道路を使わない」を足すだけで終わった。1行ちょっとだ。合わせて、履歴に経路名と距離も保存するようにした。今後は「別の道が混ざった」を後から検証できるし、経路が前回と変わったら警告も出る。
痛かったのはそこではなく、貯めた3週間ぶんのデータを捨てたことだった。
旧データの基準値は高速込みの約31.5分。固定後に測り直した下道の実測は約40分。まったく別の道の数字なので、そのまま残すと初日から毎日「異常です」と鳴り続ける。この記事で散々「毎日鳴る通知は無視される」と書いておいて、修正した瞬間にそれを自分でやるところだった。履歴は退避してリセットし、また4サンプル貯まるまで黙るところからやり直している。
(ついでに、検証用の --dry-run が履歴に書き込んでいたのも直した。修正作業の最中に、火曜の昼過ぎに走らせた値が「朝の記録」として1件混ざった。汚染を直している最中に汚染した。)
沈黙は、仕様だったのか故障だったのか
いちばん考えさせられたのはここだ。
本文で僕は「3週間、一度も通知していない」を設計どおりの成功として書いた。沈黙が仕様である、と。
ところが実際には、この道具は測るべきでない道を測っていて、混雑を検知できない構造だった。つまりあの沈黙は、仕様どおりの沈黙ではなく、壊れているがゆえの沈黙だった可能性が高い。外から見ると、この2つはまったく同じ顔をしている。
道具の死活を毎朝見張る監視役は立ててある。でもあれが見ているのは「プロセスが正常終了したか」までで、「正しい対象を測っているか」は見ていない。あの記事にも限界として書いたとおりだ。正常に終了し、もっともらしい数字を返し、それでいて中身が間違っている——監視から最も漏れやすいのは、この形をした故障だと思う。
経路APIは「速い道」を返す道具であって、「いつもの道」を返す道具ではない。当たり前のことなのだが、返ってきた数字がもっともらしい範囲に収まっていたせいで、3週間まったく疑わなかった。
追記メモ: 自分が作った道具の穴を、他人からの一言で突かれたのは初めてだった。しかも指摘は技術的な内容ではなく、「その道、実際そんなふうに走ってる?」という現実のほうからの問いだ。データの正しさは、データの中を見ても分からない。測っている対象が現実と一致しているかは、現実を知っている人に聞くしかない。
というわけで、この道具の学習期間はまた最初からになった。次に「今日は混んでる」が飛ぶとしたら、今度こそ本当に混んでいる日だと思う。