# ガイド: 分析（発言量・話者ごとの統計）

あなたは会議や配信の「誰がどれだけ話したか」「いつ盛り上がったか」を集計できます。

## どのデータを使うか

| 知りたいこと | データ | 注意 |
|---|---|---|
| 話者ごとの発言数・文字数 | 書き出しの JSON の `utterances`（`final` / `refined` だけが入っている） | セッションが終わった後の書き出しで集計すると確実 |
| 時間帯ごとの発言の多さ | `startedAt`（エポックミリ秒） | `startedAt` は、文字おこしくんが最初の音声を受け取った時刻を表す。`startedAt` は、話者をまたいで比べてよい |
| 話している時間 | WebSocket の `speech_started` / `speech_ended`（トラック単位） | リアルタイムに受け取って自分で数える。書き出しには入らない |
| 音声認識にかけた音声の秒数（利用量） | [`GET /v1/usage`](../http.md#getUsage)（`usage:read` の API キー） | 利用量は UTC の日ごとに出る。文字おこしくんは、利用量をトラックごとに数えて足す（2 人が同時に話せば 2 倍になる） |

- **`partial` は集計に使わない**でください。同じ発話が何度も届くので、数えると何倍にもなります。
- 話者分離が推定した話者（`inferred`）の発話は、後の改訂で別の `speakerId` の話者に付け替わることがあります。リアルタイムに集計するなら、`utteranceId` ごとに最新の改訂だけを数え直してください。
- 話者が分からない発話（書き出しでは `speakerId` が無い）は、トラック単位でまとめるか「不明」として別に数えます。

## 例: 書き出しから話者ごとの発言量を出す（Node）

```js
// node stats.mjs transcript.json
import { readFileSync } from "node:fs";

const transcript = JSON.parse(readFileSync(process.argv[2], "utf8"));
const stats = new Map(); // 話者 → { name, count, chars, first, last }
for (const u of transcript.utterances) {
  const key = u.speakerId ?? `track:${u.trackId}`;
  const s = stats.get(key) ?? { name: u.speakerName, count: 0, chars: 0, first: u.startedAt, last: u.startedAt };
  s.count += 1;
  s.chars += [...u.text].length; // コードポイント数で数える
  s.last = Math.max(s.last, u.startedAt);
  stats.set(key, s);
}
const total = [...stats.values()].reduce((sum, s) => sum + s.chars, 0) || 1;
for (const s of [...stats.values()].sort((a, b) => b.chars - a.chars)) {
  console.log(`${s.name}\t${s.count} 回\t${s.chars} 文字\t${((s.chars / total) * 100).toFixed(1)}%`);
}
```

## 例: 5 分ごとの発言数（Python）

```python
import json, sys
from collections import Counter
from datetime import datetime, timezone, timedelta

transcript = json.load(open(sys.argv[1], encoding="utf-8"))
jst = timezone(timedelta(hours=9))
buckets = Counter()
for u in transcript["utterances"]:
    t = datetime.fromtimestamp(u["startedAt"] / 1000, jst)
    buckets[t.replace(minute=t.minute - t.minute % 5, second=0, microsecond=0)] += 1
for t, n in sorted(buckets.items()):
    print(t.strftime("%H:%M"), "#" * n)
```

## 例: 話している時間をリアルタイムに数える

```js
const speakingSince = new Map(); // trackId → 話し始めた時刻（受信時刻）
const speakingMs = new Map();    // trackId → 合計

function onMessage(m) {
  if (m.type === "speech_started" && !speakingSince.has(m.trackId)) speakingSince.set(m.trackId, Date.now());
  if (m.type === "speech_ended" || m.type === "track_closed") {
    const since = speakingSince.get(m.trackId);
    if (since !== undefined) speakingMs.set(m.trackId, (speakingMs.get(m.trackId) ?? 0) + Date.now() - since);
    speakingSince.delete(m.trackId);
  }
  if (m.type === "welcome") {
    // 再接続: 切断中の分は分からないので、今話しているトラックから数え直す
    speakingSince.clear();
    for (const trackId of m.speakingTrackIds) speakingSince.set(trackId, Date.now());
  }
}
```

あなたは `welcome.tracks` と `track_opened` で届くトラックのうち、`mode: "single"` のトラックの `speakerId` から、トラックと話者の対応を取れます（`mixed` のトラックには複数人の声が入っています）。

## プライバシー

発言量の集計も、参加者の発言に関する情報です。集計は参加者に伝えた目的の範囲で使い、集計を公開する場合は本人の了承を得てください（[プライバシーポリシー](../../legal/privacy.html)）。

## コーディングエージェント向けのプロンプト

```text
文字おこしくんの書き出し JSON（GET /v1/sessions/:id/transcript）から、話者ごとの発言回数・文字数・割合と、5 分ごとの発言数を集計して CSV に出すスクリプトを書いてください。
仕様: https://moji.kaitoranma.com/docs/guides/analytics.md

- utterances には final / refined だけが入っている。speakerId が無い発話は track:<trackId> として数える。
- 文字数はコードポイント数で数える。時刻は startedAt（エポックミリ秒）を Asia/Tokyo に変換する。
- リアルタイム版を作る場合は partial を数えず、utteranceId ごとに最新の改訂だけを数え直す（inferred の話者は付け替わる）。
- 入力 JSON の小さな fixture でテストする。
```
