# リアルタイムの自転車空き状況：アプリでの表示方法は？

> ROOTE APIを使い、自転車の空き状況を鮮度やタイムスタンプ、不明値、更新、期限切れデータとともにアプリに表示する方法をご紹介します。

Source: https://www.roote.ai/ja/guides/%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%BF%E3%82%A4%E3%83%A0%E3%81%AE%E8%87%AA%E8%BB%A2%E8%BB%8A%E7%A9%BA%E3%81%8D%E6%83%85%E5%A0%B1%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95/
Language: ja
Author: ROOTE

アプリでリアルタイムの自転車空き情報を表示するには、返された台数を鮮度と車両の貸出可能状況に紐づけます。観測値はある時点で情報元が知っていた内容を表し、到着時に自転車がまだ存在することは保証しません。

ROOTEモビリティ契約では、ステーション、個別の車両、その状態を区別します。インターフェースは、不明値、空のステーション、一時的に利用不可の情報源を混同しないよう訳し分ける必要があります。

## ステーションと個別車両を区別する

ステーションは自転車と返却スペースのカウンターを公開できます。個別車両は空き状態やその他の情報を持ちます。ステーションを自転車としてカウントしたり、同じ在庫を表すカウンターを合算することは避けてください。

ROOTEモビリティDTOでは、availability.bikesやavailability.docksは不明となる可能性があります。駆動方式やバッテリーに関するフィールドは存在し処理可能である場合のみ表示すべきです。

[ROOTE OpenAPI契約](https://api.roote.ai/openapi.json)

## 鮮度とタイムスタンプの読み方

| フィールド | 解釈 |
| --- | --- |
| freshness.state | 表示状態：fresh（新鮮）、stale（古い）、unknown（不明）、static（固定） |
| freshness.source_updated_at | 情報元の更新日時（分かる場合） |
| freshness.received_at | 契約で示された受信日時 |
| freshness.expires_at | 有効期限（分かる場合） |
| availability.bikes | 既知の数量または不明値 |
| pickup.enabled と pickup.state | ステーションでの自転車貸出情報 |

呼び出し時刻は必ずしも観測時刻と一致しません。例えば10時に受信した結果は9時45分に更新された情報を含むことがあります。受信時間だけで「今更新」と表示しないでください。

## 異なる表示状態を用意する

| 受信データ | 表示例 |
| --- | --- |
| 既知の数量かつ新鮮なデータ | 観測された数量と時間表示 |
| 数量がゼロの場合 | 自転車がゼロで、その時間的文脈の表示 |
| 数量がnullの場合 | 空き状況不明 |
| stale状態または有効期限切れ | 古いデータ；更新を促す表示 |
| pickup.enabled=false | 貸出不可。カウンターが正でも貸出不可とする |
| 検索エラー | 一時的に空き不明、0として扱わない |

unknownやstatic状態はfreshとして扱わないでください。ステーション情報は安定でもカウンターは急変する可能性があります。応答に必要な警告や属性は保持してください。

## レンダリング前の正規化例

以下の関数はROOTEスキーマで検証済みステーションから表示状態を生成します。完全な応答検証器ではなく、表示用ラベルはインターフェースの翻訳キーから取得すべきです。

```
function availabilityView(station, now = Date.now()) {
  const freshness = station.freshness;
  const expiresAt = freshness.expires_at
    ? Date.parse(freshness.expires_at) : null;
  const expired = expiresAt !== null &&
    Number.isFinite(expiresAt) && expiresAt <= now;
  if (station.pickup.enabled === false ||
      station.pickup.state === 'unavailable_now') {
    return { state: 'pickup_unavailable', count: null };
  }
  if (expired || freshness.state === 'stale') {
    return { state: 'stale', count: null };
  }
  const count = station.availability.bikes;
  if (freshness.state !== 'fresh' || count === null ||
      !Number.isFinite(count) || count < 0) {
    return { state: 'unknown', count: null };
  }
  return {
    state: count === 0 ? 'empty' : 'observed', count,
    sourceUpdatedAt: freshness.source_updated_at,
    receivedAt: freshness.received_at,
    pickupState: station.pickup.state
  };
}
```

observed状態であってもpickupState=unknownを貸出確定に変換しないでください。カウンターは観測値のままです。ユーザーがステーション選択を助ける場合は貸出状況の文脈を表示してください。

## 無駄な呼び出し増加なく更新する

公開された有効期限、サービス状況、ユーザー行動に合わせて更新を調整します。同一要求はまとめ、非アクティブページでの裏バックグラウンド呼び出しを避け、置き換えられた検索呼び出しはキャンセルしてください。

ローカルキャッシュ時間は情報元の鮮度を保証しません。エラー後は最後の観測情報を古いと明示して表示できます。情報元がstaleのままなら最初の成功時でもこの区別を消さないでください。

## GBFSとの関係を理解する

GBFSは共有モビリティサービスとその状態を記述します。統合時はファイル、バージョン、フィードのタイムスタンプを解釈します。標準APIではAPI契約を使い、不明なGBFSフィールドを追加しないでください。

[GTFS、GTFS Realtime、GBFSの選択](https://www.roote.ai/ja/guides/gtfs-gtfs-rt-gbfs-%E3%81%AE%E3%81%A1%E3%81%8C%E3%81%84/)

## 読者が誤解する状況をテストする

実際にゼロ、未知値、有効期限切れ、貸出無効、エラー後の正常結果のテスト。表示タイムゾーンも検証してください。正のカウンターは「予約済み」とせず、空き不明は「ゼロ」にしないでください。

[空応答とエラーの処理](https://www.roote.ai/ja/guides/api%E7%B5%90%E6%9E%9C%E3%81%AA%E3%81%97%E3%81%A8%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AE%E9%81%95%E3%81%84/)

[AIアシスタントでの規則適用](https://www.roote.ai/ja/guides/%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E5%91%A8%E8%BE%BA%E3%81%AE%E3%83%A2%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3%E3%82%A2%E3%82%B7%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%88%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9/)

[自転車検索ユーザーガイド](https://www.roote.ai/ja/guides/jibun-no-chikaku-de-jiyuridesu-sa-bai-suko-no-mitsukekata/)

## よくある質問

### 正の数量は到着時の自転車保証か？

いいえ。観測値であり、検索と到着間に変化する可能性があります。

### nullをゼロに置き換えられるか？

いいえ。nullは不明値を示し、ゼロは既知の数量で異なる意味があります。

### 数秒ごとに更新すべきか？

有効期限やサービス制限、インターフェース要件に従ってください。任意の頻度は情報元の新鮮さを保証しません。
