プレミアリーグU-11 アプリ / データ蓄積基盤
現行の利用規約(2026年4月1日施行)とプライバシーポリシーを読んだうえでの、コンディション機能の実装可否判定と、縦断データを貯めるためのスキーマ設計です。
利用目的に「競技成長管理」「データ分析」「研究・教育」「スポンサー連携」が既にあり、取得項目にも身体情報(身長・体重・怪我・睡眠・体調)が明記されています。スポンサーへの提供も「原則として匿名加工情報または統計情報のみ」と先回りして書かれている。基礎はできています。 ただし口腔・歯科まわりに4つの穴があり、うち2つは着手前に必ず塞ぐ必要があります。
Part 1 / 現行規約の棚卸し
追加の同意取得なしに実装できる範囲です。プロトタイプで言えば 01 ホーム・04 マウスガード・05 学ぶ・07 指導者ビューはここに収まります。
成長記録はそのまま実装可能です。任意入力と明記されている点も適切。マウスガードの交換判定は身長データから導出するので、04 マウスガード管理も現行規約の範囲で作れます。
9つの利用目的のうち「データ分析」「研究・教育」「スポンサー連携」が既に入っています。これは大きい。全国集計レポート(プロトタイプ08)を出す根拠が最初から用意されている状態です。
多くのサービスがここで詰まって規約を作り直します。先回りして書かれています。
個票を出さない線引きが明文化済み。SCOとの交渉でも「規約でこう約束しています」と言えるので、むしろ守りの材料になります。
同意の主体が保護者であること、保護者が閲覧・修正・削除を請求できることが明記済み。データモデルの guardians と consents はこの構造をそのまま実装します。
縦断データの土台になります。卒業後もアカウントが使える設定になっているとのことなので、個票の継続保有と匿名統計の永続利用が両方成立します。ここは想定より条件が良い。
クラウドや分析サービスが海外にある前提が書かれています。分析基盤の選定で詰まりません。
Part 2 / 穴
4件。上2件は必須、下2件は口腔内写真や歯科ブースを扱う段階までに必要です。
身体情報は「身長、体重、怪我情報、睡眠、体調等」。口腔セルフチェックの回答、歯の生え替わり記録、歯科の受診歴、口腔内写真を「体調等」に読み込ませるのは無理があります。利用目的の特定は具体的でなければならないので、「等」で押し切ると後で全部が使えなくなる。
ここが最大の穴です。1文の追記で塞げます。
PP 第4条3項 追記案
3 身体情報 - 身長、体重、怪我情報、睡眠、体調等(任意入力) + 身長、体重、怪我情報、睡眠、体調、口腔に関する情報 + (セルフチェックの回答、歯の生え替わりの記録、歯科受診の有無、 + マウスガードの使用状況、口腔内の写真を含みます)等(任意入力)
第3条で用語定義はしていますが、取得する旨・同意の取り方・利用範囲の条項がありません。ここで線引きが要ります。
| データ | 要配慮個人情報か | 必要な同意 |
|---|---|---|
| 保護者のセルフチェック回答 (口が開いている、喉が渇く 等) | 該当しない可能性が高い。医師の診療結果でも健康診断結果でもなく、保護者の観察の自己申告 | 取得項目への追記で足りる |
| 歯科の受診歴 | グレー。病歴に近づく | 安全側に倒して個別同意 |
| 大会の歯科ブースでの検診結果 | 明確に該当。医師等による診療・健診の結果 | 個別の明示同意が必須。オプトアウト不可 |
前回「健康情報=要配慮個人情報」とまとめて書きましたが、正確にはこの3段階です。セルフチェックだけなら軽く、歯科医が関わった瞬間に重くなります。実装順序に直結するので、ここを分けて設計します。
広報用の試合写真を想定した条項ですが、文言上口腔内写真がここに読み込まれかねません。歯の記録(プロトタイプ03)を実装するなら、除外を明記してください。
PP 第12条 追記案(新3項)
+ 3 前項にかかわらず、口腔内の写真その他健康状態の記録を目的として + 取得した画像は、広報活動およびスポンサーへの提供の対象外とします。
なお規約 第9条3項は「いつでも同意を撤回できる」と書かれていて、この点は適切です。
プロトタイプ07は「指導者には実施率だけ、個人の回答は見せない」設計にしました。現行条文は成長記録まで閲覧可能と読めるので、仕様と規約が食い違います。
子どもの健康情報がチーム内の起用判断に流れないようにするのは仕様の根幹なので、規約側を仕様に合わせてください。
PP 第10条 追記案
+ ただし、口腔に関する情報その他健康状態に関する情報については、 + 指導者は個々の選手の内容を閲覧できないものとし、 + チーム単位の実施状況のみを閲覧できるものとします。
上記は公開されている規約・ポリシーを読んだうえでの整理です。特にセルフチェック回答が要配慮個人情報にあたらないという判断は、設問の書きぶり次第で変わり得ます。スキーマを固める前に1回レビューを入れてください。設計に入ってからの手戻りより、はるかに安く済みます。
Part 3 / データモデル
卒業後もアカウントが使える前提で設計しています。設計上の争点は5つ。
players は永続、enrollments が年度・カテゴリ・チームを持ちます。これで小5を卒業してもIDが生き、U-10 → U-11 → その先が1本の線としてつながります。
選手テーブルにチームIDを持たせてはいけません。 持たせた瞬間に縦断データが作れなくなります。
consents は付与も撤回も新しい行として積みます。「今の同意状態」はビューで導出。
過去のある時点の同意状態を再現できないと、そのとき集めたデータの適法性を証明できません。 上書きした瞬間に証明手段が消えます。
policy_documents に本文ハッシュと施行日を持ち、consents から参照します。規約は必ず改定されるので、「この人が同意したのはどの文面か」が後から言えることが要ります。
セルフチェックの設問は必ず変わります。oral_check_questions を版管理し、回答は版を参照。文言が変わっても過去と比較できます。
歯科医による検診結果は clinical_screenings に分離。暗号化・アクセスログ・保存期間・権限がすべて他と違うので、同じテーブルに混ぜると全体が重くなります。
anon_facts は player_id を持ちません。個票へ戻れないことが匿名加工情報の要件です。生成時のk値を anon_batches に記録します。
erDiagram
players ||--o{ enrollments : "年度ごとの所属"
players ||--o{ measurements : "身長体重"
players ||--o{ oral_check_sessions : "月次チェック"
players ||--o{ dentition_records : "歯式"
players ||--o{ mouthguards : "用具"
players ||--o{ clinical_screenings : "歯科検診"
players ||--o{ media_assets : "画像"
guardians ||--o{ guardian_links : ""
players ||--o{ guardian_links : ""
guardians ||--o{ consents : "同意の主体"
players ||--o{ consents : "対象"
consent_purposes ||--o{ consents : "目的"
policy_documents ||--o{ consents : "同意時の版"
oral_check_sessions ||--o{ oral_check_answers : ""
oral_check_questions ||--o{ oral_check_answers : "設問の版"
anon_batches ||--o{ anon_facts : "生成バッチ"
players {
uuid id PK
date birth_date
char sex
text status
date graduated_on
}
enrollments {
uuid id PK
uuid player_id FK
int season
text category
uuid team_id
}
consents {
uuid id PK
uuid guardian_id FK
uuid player_id FK
text purpose_code FK
uuid policy_id FK
text action
timestamptz acted_at
}
measurements {
uuid id PK
uuid player_id FK
text metric
numeric value
date observed_on
uuid superseded_by
}
clinical_screenings {
uuid id PK
uuid player_id FK
date screened_on
text provider
jsonb findings
}
anon_facts {
uuid id PK
uuid batch_id FK
text cohort_key
text metric
numeric value
int n
}
主要テーブルの役割
| テーブル | 役割 | 設計上の注意 |
|---|---|---|
players | 選手の永続マスタ | チーム・学年を持たせない。 卒業後も行を消さず status で表現 |
enrollments | 年度×カテゴリ×チームの所属履歴 | 1選手に複数行。移籍もここで表現 |
guardians / guardian_links | 保護者と紐付け | 両親の2アカウント、兄弟の複数子に対応 |
policy_documents | 規約・PPの版管理 | 本文ハッシュを持つ。改定のたびに新行 |
consent_purposes | 同意目的のマスタ | 成長記録/口腔チェック/統計利用/広告受信/歯科検診 を個別に |
consents | 同意ログ(追記型) | UPDATE・DELETE禁止。 撤回も新行 |
measurements | 身長・体重など時系列 | 上書きしない。訂正は新行+superseded_by |
oral_check_questions | 設問の版管理 | code+version。文言変更でも比較可能に |
oral_check_sessions / _answers | 月次チェックの実施と回答 | 算出した「サイン件数」は保存せず、都度導出 |
dentition_records | 歯式(歯の位置ごとの状態) | FDI歯式番号で持つ。観察日つきの追記型 |
mouthguards | 使用開始・終了・交換理由 | 交換判定はmeasurementsと結合して導出 |
clinical_screenings | 要配慮個人情報 | 別スキーマ/別権限/列暗号化/アクセスログ必須 |
media_assets | 口腔内写真等 | 既定は端末内。アップロードは同意時のみ、広報利用フラグは常にfalse |
sensitive_access_logs | 閲覧記録 | 要配慮に触れた操作は全件記録 |
anon_batches / anon_facts | 匿名加工の集計系統 | player_idを持たない。 k値と加工方法を記録 |
Part 4 / DDL
新設分のみ。既存の teams 等は参照だけしています。
01_identity.sql — 選手の永続化
-- 選手は永続。卒業しても行は消さない。所属は enrollments が持つ。 CREATE TABLE players ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), birth_date date NOT NULL, sex char(1), status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','graduated','withdrawn','deleted')), graduated_on date, created_at timestamptz NOT NULL DEFAULT now() ); -- 年度ごとの所属。移籍・カテゴリ変更もここで表現する。 CREATE TABLE enrollments ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), player_id uuid NOT NULL REFERENCES players(id), season int NOT NULL, category text NOT NULL, -- 'U-10' | 'U-11' | ... team_id uuid NOT NULL, prefecture text NOT NULL, jersey_no int, UNIQUE (player_id, season, category) ); CREATE TABLE guardians ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), email citext NOT NULL UNIQUE, created_at timestamptz NOT NULL DEFAULT now() ); -- 両親2アカウント、兄弟の複数子に対応するため多対多。 CREATE TABLE guardian_links ( guardian_id uuid NOT NULL REFERENCES guardians(id), player_id uuid NOT NULL REFERENCES players(id), relation text, PRIMARY KEY (guardian_id, player_id) );
02_consent.sql — 同意(追記型・改変禁止)
-- 同意した時点の規約文面を特定できるようにする。 CREATE TABLE policy_documents ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), kind text NOT NULL CHECK (kind IN ('terms','privacy')), version text NOT NULL, body_sha256 text NOT NULL, effective_on date NOT NULL, UNIQUE (kind, version) ); -- 目的は必ず個別に。まとめて1つにしないこと。 CREATE TABLE consent_purposes ( code text PRIMARY KEY, label text NOT NULL, is_sensitive boolean NOT NULL DEFAULT false, default_granted boolean NOT NULL DEFAULT false ); INSERT INTO consent_purposes VALUES ('growth_record', '成長の記録', false, true ), ('oral_selfcheck', '口腔セルフチェック', false, false), ('clinical_screen', '歯科検診の結果', true, false), ('anon_stats', '匿名統計としての利用', false, false), ('ad_optin', '歯科医院の案内の受信', false, false); -- 付与も撤回も追記。UPDATE / DELETE は行わない。 -- 「現在の状態」は下のビューで導出する。 CREATE TABLE consents ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), guardian_id uuid NOT NULL REFERENCES guardians(id), player_id uuid NOT NULL REFERENCES players(id), purpose_code text NOT NULL REFERENCES consent_purposes(code), policy_id uuid NOT NULL REFERENCES policy_documents(id), action text NOT NULL CHECK (action IN ('grant','withdraw')), acted_at timestamptz NOT NULL DEFAULT now(), ip_hash text, ui_surface text ); CREATE INDEX ON consents (player_id, purpose_code, acted_at DESC); -- 現在の同意状態。過去の任意時点も acted_at で絞れば再現できる。 CREATE VIEW v_consent_now AS SELECT DISTINCT ON (player_id, purpose_code) player_id, purpose_code, action, acted_at FROM consents ORDER BY player_id, purpose_code, acted_at DESC;
03_records.sql — 記録(時系列・追記型)
-- 上書きしない。訂正は新しい行を入れて旧行に superseded_by を張る。 CREATE TABLE measurements ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), player_id uuid NOT NULL REFERENCES players(id), metric text NOT NULL CHECK (metric IN ('height_cm','weight_kg')), value numeric(5,1) NOT NULL, observed_on date NOT NULL, recorded_by uuid REFERENCES guardians(id), superseded_by uuid REFERENCES measurements(id), created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX ON measurements (player_id, metric, observed_on); -- 設問は版管理する。文言を変えても過去と比較できるように。 CREATE TABLE oral_check_questions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), code text NOT NULL, -- 'mouth_breathing' 等 version int NOT NULL, body text NOT NULL, flag_answer text NOT NULL, -- サインとして数える回答 retired_at timestamptz, UNIQUE (code, version) ); CREATE TABLE oral_check_sessions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), player_id uuid NOT NULL REFERENCES players(id), answered_by uuid NOT NULL REFERENCES guardians(id), period_ym date NOT NULL, -- 月初日で保持 completed_at timestamptz, UNIQUE (player_id, period_ym) ); -- 「サイン◯件」は保存しない。設問版と結合して都度導出する。 -- 判定ロジックを変えたとき、過去分も新ロジックで見直せる。 CREATE TABLE oral_check_answers ( session_id uuid NOT NULL REFERENCES oral_check_sessions(id), question_id uuid NOT NULL REFERENCES oral_check_questions(id), answer text NOT NULL CHECK (answer IN ('yes','no','unknown')), PRIMARY KEY (session_id, question_id) ); -- FDI歯式番号(11-48)。観察日つきの追記型。 CREATE TABLE dentition_records ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), player_id uuid NOT NULL REFERENCES players(id), tooth_fdi int NOT NULL, state text NOT NULL CHECK (state IN ('primary','erupting','permanent','missing')), observed_on date NOT NULL ); CREATE INDEX ON dentition_records (player_id, observed_on DESC); -- 交換目安は measurements と結合して導出する(保存しない)。 CREATE TABLE mouthguards ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), player_id uuid NOT NULL REFERENCES players(id), started_on date NOT NULL, ended_on date, end_reason text, height_at_fit numeric(5,1) );
04_sensitive.sql — 要配慮個人情報(隔離)
-- 別スキーマに置き、アプリの通常ロールからは触れないようにする。 CREATE SCHEMA sensitive; REVOKE ALL ON SCHEMA sensitive FROM app_readwrite; -- 歯科医による検診結果。取得には個別の明示同意が必須。 CREATE TABLE sensitive.clinical_screenings ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), player_id uuid NOT NULL, screened_on date NOT NULL, event_id uuid, -- 大会ブース等 provider text NOT NULL, -- 実施した歯科医院 findings bytea NOT NULL, -- 列暗号化して格納 consent_id uuid NOT NULL, -- 根拠となる同意レコード retain_until date NOT NULL ); -- 口腔内写真。既定は端末内保存。アップロードは同意時のみ。 CREATE TABLE sensitive.media_assets ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), player_id uuid NOT NULL, kind text NOT NULL CHECK (kind IN ('intraoral','other')), storage_key text NOT NULL, captured_on date NOT NULL, consent_id uuid NOT NULL, -- PP第12条の広報利用の対象外であることをスキーマで固定する promo_allowed boolean NOT NULL DEFAULT false CHECK (promo_allowed = false) ); -- 要配慮に触れた操作は全件記録する。 CREATE TABLE sensitive.access_logs ( id bigserial PRIMARY KEY, actor_id uuid NOT NULL, actor_role text NOT NULL, player_id uuid NOT NULL, resource text NOT NULL, action text NOT NULL, at timestamptz NOT NULL DEFAULT now() );
05_anonymized.sql — 匿名加工(片方向)
-- 生成のたびに1バッチ。加工方法とk値を必ず残す。 CREATE TABLE anon_batches ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), generated_on date NOT NULL, method text NOT NULL, k_anonymity int NOT NULL CHECK (k_anonymity >= 10), source_note text NOT NULL, published_at timestamptz -- 法定の公表を行った日時 ); -- player_id を持たない。個票へ戻れないことが匿名加工情報の要件。 -- cohort_key は 都道府県 x 学年 x 性別 のように粒度を固定する。 CREATE TABLE anon_facts ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), batch_id uuid NOT NULL REFERENCES anon_batches(id), cohort_key text NOT NULL, metric text NOT NULL, value numeric NOT NULL, n int NOT NULL CHECK (n >= 10) -- 小さすぎる母数は出さない );
consents に UPDATE / DELETE の権限を与えない。DBロールのレベルで塞ぐv_consent_now を必ず確認する。同意がない状態で書けてしまう経路を作らないanon_facts を生成するジョブから、個票への参照を残さないanon_facts は残す(規約 第16条2項の建て付けどおり)Part 5
policy_documents / consent_purposes / consents。記録テーブルより先です。逆にすると同意なしデータが生まれます。measurements から。成長記録が動けばマウスガードの交換判定が導出できます。縦断データは後から遡って作れません。2026年度に enrollments を分離せず選手テーブルにチームを持たせてしまうと、その学年は永久に追跡できなくなります。同意ログを上書き型で作った場合も同じで、その年に集めたデータの適法性を後から証明できません。
逆に言えば、この2つさえ最初に正しく作れば、他は後から直せます。