コンテンツにスキップ

機能

機能一覧

機能名 概要 対応ロール ステータス
Google OAuth ログイン Google アカウントで管理画面にログインし、JWT を取得する 全ユーザー 実装済み
プロジェクト管理 インタビュー企画の作成・更新・削除・一覧・ダッシュボード 参照: viewer / 更新: member 実装済み
アレンジフロー(STEP1〜3) 日程条件・面接官・仮枠 → セグメントと候補者選定 → 追加質問 member 実装済み
面接官管理 プロジェクトへの面接官の追加・削除 参照: viewer / 更新: member 実装済み
空き枠算出 面接官の Google Calendar から予約可能枠を算出する viewer 実装済み
仮枠押さえ 算出した枠をプロジェクトカレンダーに暫定イベントとして一括作成/削除 member 実装済み
候補者インポート(TSV) TSV ファイルをアップロードし、列マッピングを指定して取り込む member 実装済み
候補者インポート(アンケート) Databricks のアンケート回答から、マッピングとフィルタを指定して取り込む member 実装済み
候補者一覧・絞り込み ステータス・選定区分・セグメント・スコア・回答内容で絞り込む viewer 実装済み
スコアリング プロジェクトのスコアリングルールで候補者を自動採点・再計算する member 実装済み
候補者選定 本命/補欠の選定区分とセグメントを一括更新する member 実装済み
ステータス管理 候補者ステータスの遷移と履歴記録 member 実装済み
メールテンプレート管理 システム共通/プロジェクト個別のテンプレート CRUD 参照: viewer / 更新: member 実装済み
メール送信 テンプレートを差し込んで候補者へ一括送信、プレビュー、送信ログ member 実装済み
日程調整(候補者向け公開ページ) トークン URL から案件確認・空き枠取得・希望日程送信 認証なし 実装済み
日程確定 候補者の希望日程から予約を作成し、仮枠を確定イベントに変換する member 実装済み
予約管理 予約の一覧・詳細・キャンセル 参照: viewer / 更新: member 実装済み
アンケートデータ閲覧 アンケート・設問・回答者(パネル)・回答のブラウズ viewer 実装済み
個人情報クリーンアップ 保持期間を超えた個人情報をバッチで匿名化する バッチ 実装済み

機能詳細

アレンジフロー(STEP1〜STEP3)

プロジェクト詳細画面の中心となる段階的ウィザード。状態は projects.arrange_settings(JSON カラム 1 個)にまとめて保存され、フロントエンドのロジックは use-project-arrange.ts に集約されている。

対応ロール: member

ユースケース:

  1. STEP1 — 日程条件と面接官

    1. 調整期間(開始日・終了日)、時間帯(開始時刻・終了時刻)、曜日条件を指定する
    2. メイン面接官・サブ面接官を選択する(複数可)、目標人数を入力する
    3. 「空き枠を算出」で GET /projects/{id}/interviewers/availability を呼び、面接官の Google Calendar の空き時間から枠を算出する
    4. 算出結果を確認し、「仮枠を押さえる」で POST /projects/{id}/interviewers/tentative-slots を実行してプロジェクトカレンダーに暫定イベントを作成する
    5. 作成したイベント情報は arrange_settings.step1.tentativeEventIds に保存される

    枠の並び方

    • 営業時間は平日 9:00〜18:00(JST)。土日は対象外
    • 開始時刻は所要時間の倍数。90 分なら 9:00-10:30 / 10:30-12:00 / … / 16:30-18:00 の 6 枠で、枠同士は重ならない
    • 営業時間をはみ出す枠は作らない(90 分なら 16:30 開始が最後)
    • 昼休みは除外していない。面接官のカレンダーに予定が入っていれば空き時間の照会で自動的に外れる
    • 所要時間は画面で選んだ値が API に渡る(durationMinutes)。プロジェクトへの保存を待たずに算出結果へ反映される
    • STEP2 — セグメント分けと候補者選定
    • アンケートの設問と選択肢を条件にセグメントを定義する(arrange_settings.step2.segments)
    • セグメントごとに候補者を一覧し、本命(primary)/補欠(reserve)を選ぶ
    • PUT /projects/{id}/candidates/selection で選定区分とセグメント ID を一括保存する
    • STEP3 — 追加質問
    • セグメント別・候補者別に、面接で聞きたい追加質問を登録する
    • arrange_settings.step3.questions に保存される

型の二重定義

ArrangeSettings の型は backend の src/db/schema.ts と frontend の src/types/arrange-settings.ts に二重定義されている(OpenAPI では JSON カラムの内部構造まで表現されないため)。片方を変えたらもう片方も直すこと。

候補者インポート

対応ロール: member

TSV とアンケートの 2 経路がある。いずれも import_logs に履歴が残る(import_type: manual_tsv / manual_survey / auto_survey)。

TSV インポートのユースケース:

  1. POST /projects/{id}/candidates/import/upload に TSV(UTF-16LE、タブ区切り)をアップロードする
  2. レスポンスの列情報・サンプル値・推奨マッピングを見て、画面上でマッピングを組み立てる
  3. POST /projects/{id}/candidates/import/{fileId}/execute にマッピングを渡して実行する(非同期)
  4. GET /projects/{id}/candidates/import/{importId}/status で進捗と結果を確認する

アンケートインポートのユースケース:

  1. POST /projects/{id}/candidates/import/survey/preview でマッピング・フィルタを指定し、取り込み結果を試算する
  2. 問題なければ POST /projects/{id}/candidates/import/survey で実行する
  3. GET /projects/{id}/candidates/import/history で履歴を確認する

スコアリング

対応ロール: member

projects.scoring_rules に定義したルール群を、候補者の attributes と survey_responses に適用して合計点を算出する。

  • ルールは field(参照先フィールド)、condition、values、score の 4 項目
  • condition は equals / not_equals / in / not_in / contains / between / greater_than / less_than
  • field は attributes.xxx / survey_responses.xxx のようにプレフィックスで参照先を指定できる。プレフィックスがない場合は attributes を先に探し、なければ survey_responses を探す
  • max_score を指定すると合計点の上限になる
  • POST /projects/{id}/candidates/score でプロジェクト内の全候補者を再計算する

候補者ステータス管理

対応ロール: member

project_candidates.status は 10 種類の enum で、遷移可能な組み合わせが VALID_STATUS_TRANSITIONS に定義されている。不正な遷移は InvalidStatusTransitionError(400 / INVALID_STATUS_TRANSITION)になる。

stateDiagram-v2
  [*] --> not_contacted
  not_contacted --> contacted
  not_contacted --> declined
  not_contacted --> cancelled
  contacted --> waiting_response
  contacted --> bounced
  contacted --> declined
  contacted --> cancelled
  waiting_response --> scheduling
  waiting_response --> declined
  waiting_response --> cancelled
  scheduling --> scheduled
  scheduling --> declined
  scheduling --> cancelled
  scheduled --> interviewed
  scheduled --> cancelled
  interviewed --> completed
  interviewed --> cancelled
  bounced --> contacted
  cancelled --> not_contacted
  completed --> [*]
  declined --> [*]
ステータス 意味
not_contacted 未連絡
contacted 連絡済み(案内メール送信済み)
waiting_response 返答待ち
scheduling 日程調整中(候補者が希望日程を送信した状態)
scheduled 日程確定(予約が作成された状態)
interviewed 面接実施済み
completed 完了
declined 辞退
bounced メール不達
cancelled キャンセル

遷移のたびに status_histories にレコードが追加され、old_status / new_status / changed_by / note が記録される。

日程調整

対応ロール: 候補者(認証なし)+ member

sequenceDiagram
    participant C as 候補者
    participant FE as 公開ページ<br/>/scheduling/{token}
    participant API as Backend API
    participant DB as PostgreSQL
    participant GC as Google Calendar
    participant M as 管理者(member)

    M->>API: 案内メール送信
    API->>DB: scheduling_tokens 発行
    API-->>C: トークン URL 付きメール
    C->>FE: トークン URL を開く
    FE->>API: GET /scheduling/{token}
    API-->>FE: 案件情報・期限・予約状況
    FE->>API: GET /scheduling/{token}/slots
    API-->>FE: 予約可能枠一覧
    C->>FE: 希望枠を選択(1〜20 件)
    FE->>API: POST /scheduling/{token}/submit-availability
    API->>DB: candidate_available_dates を置き換え
    API->>DB: status を scheduling に更新
    API-->>C: 受付完了メール
    M->>API: POST /projects/{id}/candidates/{cid}/confirm-schedule
    API->>DB: reservations を作成
    API->>GC: 仮枠を確定イベントへ変換
    API-->>C: 確定通知メール
  • 調整トークンの有効期限は SCHEDULING_TOKEN_TTL_DAYS(既定 14 日)
  • 希望日程は送信のたびに全置換される(追記ではない)
  • 既に確定予約がある場合の送信は 409 SLOT_NOT_AVAILABLE
  • PROTOTYPE_MODE=true(dev の既定)の間はメール送信とカレンダー書き込みがスキップされる。stg は false なので実際に反映される

Google カレンダー連携

プロジェクトカレンダー

プロジェクトを作成すると [インタビュー] {プロジェクト名} という専用カレンダーが作られ、projects.google_calendar_id に保存される。所有者はプロジェクト作成者で、サービスアカウントはその人になりすまして読み書きする。作成時に失敗していた場合や、カレンダーがない古いプロジェクトの場合は、仮枠を押さえる時点で作成される。

書き込み先と参加者

仮枠から確定枠まで、すべてこのプロジェクトカレンダーに入る。面接官個人のカレンダーには直接書き込まない。

操作 カレンダー attendee Google からの通知
空き時間の照会 面接官本人(freebusy のみ) - -
仮枠の作成・削除 プロジェクトカレンダー 担当の面接官 送らない
日程確定 プロジェクトカレンダー メイン面接官+サブ面接官 送る
予約のキャンセル プロジェクトカレンダー 同上 送る
  • 面接官は仮枠の段階から attendee に入れる。 そうしないと押さえた枠が本人の空き時間照会に反映されず、同じ時間に別の枠を作ってしまう
  • 仮枠は一度に数十件作るため、その通知は送らない。確定とキャンセルだけ通知する
  • 候補者は attendee に入れない。 候補者への連絡はアプリからのメールで行う

イベント名

状態 書式 例
仮枠 [仮] {プロジェクト名} インタビュー枠 [仮] 新商品調査 インタビュー枠
確定 {候補者名}様 インタビュー(担当:{面接官名})/ {プロジェクト名} 山田太郎様 インタビュー(担当:渡辺)/ 新商品調査

仮枠の時点では候補者が決まっていないため、確定時に候補者名と担当者名を入れて書き換える。

メール送信

対応ロール: member

  • テンプレートは「システム共通」(project_id が NULL)と「プロジェクト個別」の 2 種類
  • 種別(type)は invitation / reminder / confirmation / cancellation
  • 件名・本文には {{candidate_name}} {{project_name}} のようなプレースホルダを埋め込める
  • POST /projects/{id}/emails/preview で 1 候補者分の差し込み結果を確認できる
  • POST /projects/{id}/emails/send で複数候補者へ一括送信し、結果は sent_count / failed_count / errors で返る
  • 送信内容は email_logs に本文ごと保存される

アンケートデータ連携(Creative Survey / Ask One)

対応ロール: viewer(参照のみ)

回答データは Databricks の Unity Catalog から直接読む。Postgres にキャッシュするのは調査一覧に使うメタデータだけ。

参照先 用途
cs.cs_dm.dm_answers_<survey_id> 調査ごとの回答。回答とパネルが結合済み
databricks_surveys(Postgres) 調査一覧・詳細。日次バッチで更新する
  • メタの集計は約 6,800 万行のスキャンで 15 秒前後かかるため、リクエストごとには実行しない
  • 更新は src/batch/databricks-meta-sync.ts を ECS Fargate タスクとして走らせる
  • 詳細は Databricks 連携 を参照

以前は S3 の CSV をキャッシュしていた

Creative Survey が S3 に出力する answers.csv.gz / panels.csv.gz を自前で取り込んでいたが、同じファイルを Databricks 側のパイプラインも取り込んでいたため二重管理になっていた。同期機能は廃止した。

個人情報クリーンアップ

対応ロール: バッチ

保持期間(既定 3 ヶ月)を超えた個人情報を pii-cleanup バッチで匿名化する。集計のため関連レコード自体は残す。

bun run src/batch/pii-cleanup.ts '{"dryRun":true}'