個人開発の記録です。作っている都営浅草線のアプリに、ついに「答え合わせ」機能をつけました。アプリが出した「待つべきか、乗るべきか」の判断を、あとで実際の結果と突き合わせて、アプリ自身に採点させる仕組みです。

そして初日の成績は——満点でした。なのに、私はまったく喜べませんでした。今日はその話を書きます。

Contents

まず結論

初日の的中率は満点。でもそれは「テストが簡単すぎただけ」でした。そして本当に見るべきだったのは、点数ではなく、その裏に隠れていた「判定できませんでした」が半分近くもあったという事実のほうでした。

怪しい数字を追いかけた結果、分かったことが3つあります。

  • 「分からない」にも、種類がある
  • 仕込んだはずの見張り番が、初日から死んでいた
  • 仮に置いた数字は、実データが正してくれる

そもそも「答え合わせ」とは何をする機能か

このアプリは、電車が乱れた瞬間に「待つ/乗る」の判断を出します。でも今まで、その判断が当たっていたのかどうかを、誰も確かめていませんでした

実はこの「答え合わせ」、設計の最初期からやると決めていた宿題でした。予測を出すアプリには「正解データ」がありません。だから当初は「自分で実際に乗って目視で確かめる」と計画していたのです。それを、アプリ自身に自動でやらせる形にしたのが今回の機能です。

初学者向けに言うと、アプリに”成績表”を持たせたということです。判断を出した記録を残しておき、あとから実際にどうなったかを照合して、当たり/外れを自動で採点する。物差しがなければ、改善したかどうかも分からない——検証フェーズで学んだ「ベースライン」の話が、ここで形になりました。

満点を、喜ばなかった理由

結果は満点。普通なら「やった、精度100%だ」と書きたくなるところです。でも、そうしませんでした。理由は単純で、その日は電車が平穏だったからです。

乱れていない日に「乗ってOK」と答えて当たるのは、当たり前です。難しい問題が一問も出ていないテストで満点を取っても、実力の証明にはならない。このアプリの本当の試験は、次に接続する路線が大きく乱れる日にしか訪れません。

ここは、このブログで何度も書いてきた姿勢がそのまま効いた場面でした。数字の良い方ではなく、怪しい方を深掘りする。だから私が見たのは、満点という結果ではなく、その隣にあった別の数字でした。

本命:「判定できません」が半分近くあった

成績表をよく見ると、当たりでも外れでもなく「判定不能」に分類された判断が、半分近くありました。つまり採点しようとしたのに、答え合わせができなかったケースです。

これは無視できません。半分が採点できていないなら、残り半分の満点にも意味がないからです。そこで、判定不能になったケースを1件ずつ全部追いかけました

すると、原因はほぼ全部同じでした。答え合わせをしようとした時点で、その電車がもう追跡できる範囲の外に出ていたのです。浅草線は他社線と直通運転をしているので、電車は境界を越えて相互に乗り入れていきます。境界を越えた電車は、私が見ているデータの中からすっと消えてしまう。だから「その後どうなったか」が確かめられなかった、というわけです。

つまりこれはバグではなく、この検証手法そのものの構造的な限界でした。以前から「境界をまたぐ電車の扱い」には苦労してきたのですが、同じ問題が、答え合わせ機能でも同じ形で再登場したことになります。

学び①:「分からない」にも種類がある

ここから一番大きな学びが出ました。ひとくちに「判定不能」と言っても、中身は2種類あったのです。

  • 構造的に分からない:電車が追跡範囲の外へ出ていった。多くても正常
  • データが取れなくて分からない:そもそも情報が届いていない。異常のサイン

この2つを同じ箱に入れてはいけません。病院SEの仕事で例えると分かりやすくて、「検査していない」と「検査したが検出できなかった」を混ぜたら、診断を誤ります。同じ「陰性」に見えて、意味がまったく違うからです。

学び②:見張り番が、初日から死んでいた

これが今回いちばんゾッとした発見です。

実は「判定不能」には、もう一つ役割を持たせるつもりでした。「判定不能が急に増えたら、それは接続が切れているサイン」——つまり成績表に、前回の記事で書いた”静かに切れる接続”の見張り番も兼ねさせる、という設計です。

ところが、構造的な判定不能が常時半分近く混ざると分かった。ということは、本当に接続が切れて判定不能が増えても、その大量のノイズに埋もれて、まったく気づけません。仕込んだはずの見張り番が、初日から機能していなかったのです。

警報装置は、鳴らないことより「鳴りっぱなしで意味を失う」ほうが怖い。だから対策は、上の2種類をきちんと分けて数えること。「構造的な分からない」を除いて初めて、「異常な分からない」が見えるようになります。

学び③:仮に置いた数字は、実データが正す

もう一つ。答え合わせのタイミングを、私は「判断の一定時間後に結果を見る」という設計にしていました。でもこれ、実は深い根拠なく仮に置いた数字でした。

そして実データが、それを「長すぎる」と教えてくれたのです。よく考えれば、利用者にとって答えが出る瞬間は、その電車が自分の駅に来た瞬間です。そこまで待つ必要はなかった。しかも、その瞬間ならまだ電車は追跡範囲の中にいるので、判定不能の大半が消えることも分かりました。

ここが面白いところです。答え合わせ機能が、答え合わせ機能自身を改善する材料を出してきた。設計 → 実測 → 設計の修正、という輪が、初めて自分のアプリの中で回りました。前の記事で書いた「仮に置いた値は風景になって見えなくなる」という教訓も、こうしてデータが引きずり出してくれたわけです。

おわりに ── 成績表の本当の使い方

今回、あえて触らないと決めたものがあります。満点という的中率と、判定の基準そのものです。平穏な日の満点は、いじる根拠になりません。今やるべきは、採点の器を正しく整えて、本当の試験日を待つことだけです。

成績表を持つ意味は、良い点数を眺めることではありませんでした。自分の物差しが、どこまで正しく測れて、どこから測れないのかを知ること——それが答え合わせの本体だったのだと思います。

次に路線が大きく乱れる日。そこで「待て」「乗れ」が出て、それが当たるか。そのとき初めて、意味のある成績が出ます。その日を、少し楽しみに待っています。


個人開発 / 検証 / 実測主義 / 都営浅草線

このブログでは、ローカルLLM・Aider・個人開発の試行錯誤をそのまま記録しています。