HYROX LIVE TIMING
WORLD SERIES OF FITNESS RACING
OFFICIAL RESULT
POS BIB ATHLETE (選手名) DIVISION CURRENT STAGE & PROGRESS (現在の種目 / 全進捗) START TIME TIME (ネットタイム / スプリット)
「速報更新」ボタンを押すとリアルタイム公式タイムを集計します。
💡 データ構造 & 実装コード解説

/api/getTagData から取得される「受信検知ログ(JSON)」と、それを解析してリーダーボードを出力する「集計JavaScriptコード」のサンプルです。

📄 1. APIから受信するRFID検知データ (rawApiLogs サンプル)
[
  {
    "apiUid": "rXdemo00000001",
    "date": "2026-09-01",
    "serverTimestamp": 1788220890000,
    "stationId": 1,
    "tagId": "A1B2C3D4E5F6789012345678",
    "time": "09:01:30",
    "name": "山崎 翔太",
    "number": "101",
    "category": "Hyrox Pro Men"
  },
  {
    "apiUid": "rXdemo00000001",
    "date": "2026-09-01",
    "serverTimestamp": 1788221130000,
    "stationId": 2,
    "tagId": "A1B2C3D4E5F6789012345678",
    "time": "09:05:30",
    "name": "山崎 翔太",
    "number": "101",
    "category": "Hyrox Pro Men"
  },
  {
    "apiUid": "rXdemo00000001",
    "date": "2026-09-01",
    "serverTimestamp": 1788221340000,
    "stationId": 2,
    "tagId": "A1B2C3D4E5F6789012345678",
    "time": "09:09:00",
    "name": "山崎 翔太",
    "number": "101",
    "category": "Hyrox Pro Men"
  }
]
💻 2. リーダーボード集計JavaScriptコード
function processHyroxLogs(rawApiLogs) {
  const athleteMap = {};

  // 1. タグIDごとにログをグループ化
  rawApiLogs.forEach(log => {
    if (!athleteMap[log.tagId]) {
      athleteMap[log.tagId] = {
        name: log.name || '未登録',
        bib: log.number || log.bibNumber || '-',
        category: log.category || 'Hyrox',
        logs: []
      };
    }
    athleteMap[log.tagId].logs.push(log);
  });

  // 2. スタート時間・ネットタイム・区間タイムを計算
  return Object.values(athleteMap).map(athlete => {
    athlete.logs.sort((a, b) => a.serverTimestamp - b.serverTimestamp);

    const startLog = athlete.logs[0];
    const lastLog = athlete.logs[athlete.logs.length - 1];

    // 総経過時間 (ミリ秒)
    const netTimeMs = lastLog.serverTimestamp - startLog.serverTimestamp;

    // ワークアウト区間タイム (Station 2〜9 入場〜退場)
    const workoutSplits = {};
    for (let s = 2; s <= 9; s++) {
      const sLogs = athlete.logs.filter(l => l.stationId === s);
      if (sLogs.length >= 2) {
        workoutSplits[`WORKOUT_${s - 1}`] = 
          sLogs[sLogs.length - 1].serverTimestamp - sLogs[0].serverTimestamp;
      }
    }

    return {
      bib: athlete.bib,
      name: athlete.name,
      startTime: startLog.time,
      netTimeMs: netTimeMs,
      workoutSplits: workoutSplits
    };
  });
}