サウンドマインド
オディヤ保護者アプリ
GPSは頻繁に間違えます。間違ったものをもっともらしく見せる代わりに、分からないことは分からないと言う画面を作りました。
- 役割
- 位置解釈ロジックと画面の設計 · 実装
- 期間
- 2025.07 ~ 現在
- 使用技術
- React NativeTypeScriptTanStack QueryZustandNaver MapSTOMP
改善結果
89件
位置解釈ロジックに付けた単体テスト
956駅
地下鉄推論に使用する駅データ (47路線)
3段
移動手段の推測が通過すべきフィルター
01課題
保護者が見る画面は、子どもの端末が送った座標をそのまま打点するだけでは役に立ちません。家に2時間いた、地下鉄で移動したようだ、今は学校の近くにいる、といった言葉に変換して初めて情報になります。問題は、元のデータがきれいではないという点でした。屋内ではGPSが揺れ、地下鉄では完全に途切れます。解釈を誤ると保護者が間違った情報を事実として信じてしまい、子どもの安全に直結するサービスにおいて、これは機能がないことよりも悪いのです。
02制約
最大の制約はデータではなくユーザー側にありました。子ども端末のアプリはストア経由で配布されますが、実際に現場で動いている端末の大半が旧バージョンでした。新しく作った解釈画面は、旧バージョンが送らないフィールドを必要としていました。リアルタイム追跡には別の問題がありました。アプリとサーバーがそれぞれ状態を判断するため、互いに食い違うことが頻繁にありました。
- 屋内でのGPSの揺れにより、一つの場所での滞在が複数に分割されて表示されました
- 地下鉄区間は座標が丸ごと欠落しており、推測以外に方法がありません
- 旧バージョンの子ども向けアプリは解釈に必要なフィールドを送りません
- リアルタイム追跡ではアプリとサーバーがそれぞれ判断し、状態が食い違いました
03検討した代替案
GPSが途切れた区間を画面上でどう表現するかが核心でした。空白の時間をそのままにすると保護者が不安になり、埋めてしまうと間違う可能性があります。
| 案 | 利点 | 課題 |
|---|---|---|
| 空白を直線でつなぐ | 経路が途切れず見た目が自然 | 建物を貫通する直線が描かれ、実際には通っていない経路を事実のように見せてしまう |
| 空白を空白のままにする | 間違う余地がなく実装が単純 | 地下鉄で移動した30分が丸ごと消え、保護者が位置追跡は故障したと認識してしまう |
| 条件をすべて満たす場合のみ推論採用 | 正しい場合のみ表示するため信頼を失わず、そうでない場合は空白のまま残して誤解を生まない | フィルターを通過できない実際の移動も表示されず、カバレッジを一部諦める必要がある |
04選択と根拠
カバレッジを一部諦める側を選びました。子どもの安全に直結する画面では、一度の誤表示は機能がないことよりも損害が大きいのです。保護者が一度画面を疑い始めると、正しい情報まで信じなくなるからです。そこで、間違えるくらいなら表示しないことを原則とし、解釈が入るすべての箇所に同じ基準を適用しました。
地下鉄移動を表示するまで
カバレッジを諦める代わりに、画面に出たものは信じられます
- 地下鉄移動は駅マッチング、距離、所要時間の3条件をすべて通過して初めて表示
- 経路の空白判定は1か所でのみ計算し、異なる値で描画されて線が重なることを防止
- 滞在クラスターは始点から半径の3倍を超えた場合に分離し、日付をまたぐ滞在は日付ごとに分割
- 旧バージョンの子ども向けアプリのユーザーには解釈画面自体を無効化
- リアルタイム追跡はサーバーセッションを唯一の真実とし、プッシュは次の照会を前倒しする信号としてのみ使用
05実装と試行錯誤
解釈ルールは言葉で決めるのは簡単でも、コードで守るのは難しいものでした。条件を一つ直すと別の画面が静かに壊れることが繰り返されたため、ルールを画面から切り離して純粋関数に移し、テストで固定しました。
- ① 滞在判定を始点基準の固定半径で作ったところ、ゆっくり移動する区間が一つの場所にまとまってしまいました。現在の中心に追従しつつ、始点から半径の3倍を超えたら分離するように変更しました。
- ② 日付をまたぐ滞在が両方の日付にそれぞれ全体時間として計上されていました。日付境界で切って各日に合算するように修正しました。
- ③ 経路の空白を画面ごとに別々に計算したところ、同じ区間に線が重なって描かれました。計算箇所を一つに統一しました。
- ④ チャットメッセージが途中で切れて届きました。フレーム終端文字で文字列が切断されることを確認し、送信方式を変えて解決しました。
- ⑤ OSの文字拡大設定で地図上のラベルが重なりました。テキストコンポーネントをラップして拡大を無効化しましたが、従来の方法は静かに動作を停止していた状態でした。
06結果
解釈ルールはすべて純粋関数に分離し、単体テストを89件付けました。半径を何倍で見るか、日付境界をどう処理するか、空白をどこで判定するかといったルールが微妙で、コードだけでは守られないためです。屋内滞在が分割される問題と誤った地下鉄推測がなくなり、旧バージョンのユーザーに空の画面が表示される問題も解消しました。付随して、チャットメッセージが途中で切れて届く原因を追跡し、React Nativeが文字列を送信する際に特定の文字で切断することを確認して、送信方式を変えて解決しました。