📄 ドキュメント ID: primary-owner-logic
🚧 status: draft
📅 作成: 2026-05-30
🔗 関連: user-levels, members
📂 正本: docs/requirements/primary-owner-logic.md
関係人口 正担当自動算出ロジック
目的: 472 名の関係人口に対し、各支持者の「正担当」をスタッフ別関係値スコアから自動算出する。鈴木代表理事の負担軽減・小澤/藤原/佐々生での負荷分散・各理事の専門性活用を機械的に実現する。
1. 要求 (Why)
関係者・ステークホルダー
- L1 IT 管理: 実装・一括算出 endpoint 実行
- L2 代表理事・事務局: スコア入力 + 保存時の正担当確認
- L3 理事: 担当割当の影響を受ける
2. 要件 (What — 受け入れ基準)
2.1 スコア体系
- 各スタッフは支持者 1 名に対して S / A / B / C / Z の 5 段階で評価
- スコア配点:
S=4 / A=3 / B=2 / C=1 / Z=0
- 「A 以上」= 関係値が高い(HIGH 閾値)
- 既存
calcLocalScore(平均ベースの関係値算出)はそのまま維持。本仕様はそれと別軸で「正担当」を算出する
2.2 担当者カテゴリ
| カテゴリ | 該当者 | 定義 |
CORE (基本担当 3 名) | 小澤 / 藤原 / 佐々生 | 正担当の主軸。負荷分散の対象 |
PRESIDENT (負担軽減対象 1 名) | 鈴木 | 鈴木しか高関係値が無い場合のみ正担当に |
OTHER_DIR (他理事・5 名) | 渡部 / 畠中 / 中村 / 吉田 / 正樹 | CORE で適任者なしの時の次候補 |
※ 瀬川は 2026-05-30 元理事化のため算出対象外(既に削除済)
2.3 算出アルゴリズム
Step 1: CORE で A 以上の人がいる → tie_break(CORE 内候補, 'CORE')
Step 2: OTHER_DIR で A 以上の人がいる → tie_break(OTHER_DIR 内候補, 'OTHER_DIR')
Step 3: 鈴木のみ A 以上 → 鈴木
Step 4: 誰も A 以上いない → CORE 最高スコアの人 + status='UNDETERMINED' フラグ
2.4 tie_break ルール
CORE 内
- 件数最少優先(負荷分散)
- 藤原の上限: 藤原の担当件数は
min(小澤件数, 佐々生件数) × 1.5 まで OK
- 上限超過で藤原が候補から外れた場合、残り(小澤・佐々生)で件数最少
- 同点時の最終固定順: 藤原 → 小澤 → 佐々生
OTHER_DIR 内
- 件数最少優先(純粋な負荷分散)
- 同点時の固定順: 渡部 → 畠中 → 中村 → 吉田 → 正樹(初期実装は配列順・要確認)
2.5 算出タイミング
| 方式 | 説明 | 対象 |
| A (保存時即時算出) | members.html の保存ボタン押下時、サーバー側で算出 → Notion「正担当」列に書き込み | 個別保存時 |
| B (一括算出 endpoint) | 既存 472 件に対して 1 回限り GET /admin/recalc-primary-owners(L1 のみ)で全件再算出 → Notion 一括 PATCH | 全件再算出時 |
※ 定期バッチは導入しない(件数変動は保存時に再算出で十分)
2.6 保存先
- Notion DB「正担当」select 列(新規追加)
- options:
鈴木 / 渡部 / 畠中 / 中村 / 吉田 / 小澤 / 正樹 / 藤原 / 佐々生 / 未定
- 「未定」= Step 4 fallback(要再評価フラグ相当)
- 既存「担当者」rich_text 列はそのまま残す(D1 audit log に保存・フロント非表示)
- D1
members_update_log に変更履歴記録(既存 Tier 2.23 audit log の仕組みを利用)
2.7 既存バグ修正
- PATCH
/members/{id} でスタッフ別評価 10 列(鈴木評価 / 渡部評価 / ... / 佐々生評価)の Notion 保存処理を追加
- POST
/members でも同様
2.8 UI 変更
members.html 編集モーダル
- 「担当者」入力欄は画面から削除(D1 / Notion には保持)
- 「正担当 (自動算出)」表示欄を新設(読込のみ・保存時にサーバーから返却された値を表示)
- 「正担当」select 列が「未定」のとき、注意アイコン + 「要再評価」ラベルを表示
2.9 一括算出仕様
- L1 のみ実行可能:
GET /api/ikizama/admin/recalc-primary-owners?dry_run=1|0
dry_run=1 → 算出結果を JSON 返却のみ、Notion 書込なし
dry_run=0 → Notion 全件 PATCH 実行
- 算出順序による件数偏りを避けるため: 2 パス処理
- パス 1: 全件の正担当候補を仮算出
- パス 2: 件数を整える調整(簡易ロジック: 件数多い人を減らす方向で再配分は試みない・初期実装は単純パス 1 結果を採用)
- レスポンスに
{ ok, total, by_owner: {小澤: N, 藤原: N, ...}, undetermined: N }
3. 実装 (How)
3.1 関連ファイル
_worker.js L362-461: POST/PATCH /members に staff scores 保存追加 + 正担当算出追加
lib/notion.js: 正担当算出ロジック(純粋関数)を追加
members.html: 担当者欄削除 + 正担当表示欄追加 + フォーム送信時に staff scores を含める
docs/requirements/primary-owner-logic.md: 本ドキュメントの正本
3.2 API エンドポイント
POST /api/ikizama/members — 新規登録時に正担当自動算出
PATCH /api/ikizama/members/{id} — 更新時に正担当再算出
GET /api/ikizama/admin/recalc-primary-owners?dry_run=1|0(新規・L1 のみ)— 一括再算出
3.3 Notion スキーマ変更
新規列(手動 or /admin/ensure-primary-owner-column で確保):
- 名前:
正担当
- タイプ: select
- options: 鈴木 / 渡部 / 畠中 / 中村 / 吉田 / 小澤 / 正樹 / 藤原 / 佐々生 / 未定
3.4 外部依存
- Notion API(DB スキーマ変更・properties 更新)
3.5 既知の制限・残課題
- 件数情報のクライアント取得: 保存時の件数把握はサーバー側で実施(クライアントへの fetch なし)
- OTHER_DIR 同点 tie-breaker: 初期実装は配列順(渡部 → 畠中 → 中村 → 吉田 → 正樹)。運用後に固定優先順位を再決定する場合は要件追記
- 算出順序による偏り: 初期実装は単純 1 パス。集計結果に偏りが大きければパス 2 リバランスを後追加
4. 変更履歴
- 2026-05-30 (続): 対象 DB を NOTION_SUPPORT_DB → NOTION_MEMBER_DB に切替。当初要件で対象を「members」と書いていたが、実装で誤って supporters DB を対象にしていた。dry_run 結果で全件 UNDETERMINED となり判明(supporters には評価がほぼ未入力だったため)。members DB は既に POST/PATCH で staff scores 保存処理あり・既存「担当1/2/3」(members.html topStaff)でクライアント側評価表示もあり、本来の対象だった。
- 2026-05-30: 初版作成(議論内容を集約)
📌 正本: 本 HTML は docs/requirements/primary-owner-logic.md の内容を表示用に整形したものです。仕様変更時はマークダウンを正本とし、本 HTML も同期更新してください。