# ローカル記録・分析仕様 v1.1（2026-10-05）

v0.2ではpeopleは検出された解析対象顔の数（最大4）、tracksは顔の短時間トラックです。旧記録はSSD人物検出数で定義が異なります。既存データを変換せず保持し、分析画面にも差異を表示します。両モデルが新鮮（顔1.5秒以内、スマホ3秒以内）の場合だけobservedとします。

## 構成と対象

既存ビューアに明示的な「記録開始 / 記録停止」を追加し、同一オリジンのNode.js HTTP API + SQLiteへ数値を保存する。追加外部サービス・有料契約・外部公開は行わない。Node.js 22.22.3の組込み `node:sqlite` を使用（同版ではexperimental警告あり）。APIと静的画面を `127.0.0.1:8019` で配信。Cloudflare Pagesの静的配置だけではDB/APIは動かない。

保存するのはUTC時刻、セッションUUID、入力区分(camera/synthetic)、連番、数値カウント、セッション内の一時トラック番号と3状態、および派生イベントだけ。画像、映像、ランドマーク、座標、特徴量、名前、カメラデバイスID、恒久人物IDは保存しない。記録開始はカメラ開始とは別操作。合成入力は明示分類して分析画面で切り替える。

## サンプルとイベント

- 原則1秒に1回のスナップショット。`status=observed` は有効な新しい解析結果（人数0も有効）。`missing` は準備中・結果が古い等の欠測。`stopped` は記録終了。欠測/停止のカウントはnullで0とは区別。
- サンプル: sessionId, seq（0起点の連続整数）, at（UTC Unix ms）, status, counts{people,phones,front,away,unknown}, tracks[{id,state}]。人数0〜20、顔方向front/away合計最大4、人物数=front+away+unknown=tracks.length。
- sessionId+seqを一意キーとする。完全同一の再送は成功扱い・追加なし。異なる内容の同一キー、順序逆転、終了後の新規追加は409。書込はSQLiteトランザクション。
- 派生イベント: record_start/stop、track_seen（そのセッションで初めて）、track_lost（離場の証明ではない）、front_start/end、state_change、missing。イベントにUTC時刻と必要時のみ一時ID・状態・継続msを保存。
- 前のサンプルは最大2秒だけ有効とする。2秒超の間隔は欠測。front区間はunknown・away・見失い・停止・欠測で閉じる。突然の終了は最終サンプル+2秒で上限を設け、サーバー再起動時に残った未終了セッションを回収する。注意力や広告視聴の証拠ではない。

## 集計定義

- 平均解析対象数: 有効観測時間による時間加重平均（無検出0を含み、欠測を除く）。最大解析対象数: 期間内の有効人数の最大値。
- 延べトラック数: 期間内に初観測されたsessionId+trackIdの件数。フレーム数でもユニーク実人数でもない。再入場・見失いによる新IDを重複排除できない。
- カメラ方向への推定注視開始回数: front_startイベント件数。状態の切替で再び数える場合がある。
- 推定注視継続時間: frontの有効区間を人物ごとに合計した人秒。複数人が同時なら時間を加算。終了イベントには区間の継続時間も記録。期間境界で時間を切り詰める。
- 期間は半開区間[from,to)。UTC保存、入力・ラベル・イベント表はAsia/Tokyo（JST）。時間帯集計はJSTの各暦時間。観測が無ければ平均/最大はnull、グラフは線を途切れさせる。停止後・未記録もゼロにしない。
- 時系列は最大約1000点に時間加重集約。イベントは新しい500件まで、全件数と上限を表示。期間は最大31日、集計対象最大200000サンプル。超える場合は期間を狭める案内。CSVは時系列集計/イベントをUTF-8 BOM、UTCとJST両方付きで出力。

## 保存・失敗・アクセス制御

DBはデモ内 `data/analytics.sqlite`（WAL/SHM含めgitignore、静的配信禁止）。30日超の終了済みセッションを起動時に削除。500000サンプル/DB・WAL合計256 MiBを上限に新規サンプルを拒否して表示。終了マーカーは上限時も受理する。削除の代わりにユーザーが停止後のDB一式を別途保管できる。1 Hzの連続記録は上限に約5.8日で到達するため長期運用は今後の集約/保管設計が必要。

フロントは最大120件のメモリキューで同じ連番・内容を再試行。失敗/未保存件数を常時表示。上限では記録採取を停止し、未保存は保持して再試行する。ブラウザ終了時に未保存分が失われ得るため「全件保存済み」を確認して閉じる。ブラウザの永続保存は使わない。

loopback bind、Host検証、書込のOrigin完全一致と起動ごとランダムCSRFトークン、JSON Content-Type、64 KiB body上限、厳密キー検証・型/時刻/個数/状態の範囲検証。CORS許可を出さない。静的配信は許可パスだけでDB、ソース、node_modulesを公開しない。同一PCの信頼できないプロセスへの認証基盤ではない。

同時に開ける記録は1セッションのみとし、時刻の重複した別セッションは拒否する。30秒以上無応答のセッションは新規開始時にも回収する。サーバー再起動で回収されたセッションへの新規未保存サンプルは409（同一の保存済みサンプル再送は成功）。未保存の自動的な別セッション移植は行わない。

## 表示上の限界

「広告を見た人数」とは表示しない。「カメラ方向への推定注視」と明示し、カメラと広告の位置が違えば計測対象も違うことを説明する。unknownを非注視へ置き換えない。合成試験のみで検証し、実カメラの記録はユーザーの明示操作に限る。
