# 实时自行车可用性：如何在应用中显示？

> 使用ROOTE API在您的应用中显示自行车可用性：时间戳、新鲜度、未知值、刷新和过期数据处理。

Source: https://www.roote.ai/zh/guides/%E5%AE%9E%E6%97%B6%E8%87%AA%E8%A1%8C%E8%BD%A6%E5%8F%AF%E7%94%A8%E6%80%A7-%E5%BA%94%E7%94%A8%E4%B8%AD%E5%A6%82%E4%BD%95%E6%98%BE%E7%A4%BA/
Language: zh
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 | 取车功能不可用，即使计数为正 |
| 查询错误 | 数据暂不可用，不应显示零 |

unknown或static状态不应标记为新鲜。站点信息可能稳定，计数快速变化。保留响应中的警告和归属信息。

## 渲染前的标准化示例

下面函数从已验证符合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转换为确认可取。计数依然是观察结果。如产品帮助用户选站，应显示取车上下文。

## 避免无谓地频繁刷新

根据公布的过期时间、服务条件和用户行为调整刷新频率。合并相同请求，避免后台在非活动页调用，取消被覆盖搜索请求。

本地缓存延时不代表数据源新鲜。出错时可保留最后带时间戳的观察数据，界面需明确显示为旧数据。成功恢复时若数据仍过期，勿清除此旧数据区分。

## 理解与GBFS的关联

GBFS描述共享出行服务及其公开状态。直接集成需解析文件、版本和时间戳。使用标准API时应遵循API协议，不要添减GBFS中未声明的字段。

[GTFS、GTFS实时与GBFS的选择](https://www.roote.ai/zh/guides/gtfs-gtfs-rt-et-gbfs-quelles-differences/)

## 测试易混淆情况

测试真实零值、未知值、过期、取车关闭和有效结果后的错误。检查显示时区。正计数不应显示“已预订”；不可用不应显示为伪零。

[处理空响应与错误](https://www.roote.ai/zh/guides/api-no-result-vs-error-chinese/)

[在人工智能助手中应用这些规则](https://www.roote.ai/zh/guides/create-mobility-assistant-around-address/)

[用户找车指南](https://www.roote.ai/zh/guides/zenyang-zhaoji-jiejin-fuzhoubian-de-zixingshi/)

## 常见问题

### 正数数量保证我到达时有车吗？

不保证。它描述一次观察，可能在查询和到达间发生变化。

### 可以用零代替null吗？

不行。null表示未知，零是已知数量，含义不同。

### 需要每几秒刷新吗？

根据有效期、服务限制和界面需求决定。随意频率不保证更鲜数据。
