ガイド: 分析(発言量・話者ごとの統計)

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

どのデータを使うか

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

例: 書き出しから話者ごとの発言量を出す(Node)

// 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)

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)

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

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 のトラックには複数人の声が入っています)。

プライバシー

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

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

文字おこしくんの書き出し 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 でテストする。