Appearance
求人エンリッチメント
概要
求人エンリッチメントバッチは、Gemini API(Google Search Grounding 有効)を使って求人情報を自動拡充するスクリプトです。
| 項目 | 内容 |
|---|---|
| 目的 | 求人情報に AI 説明文・カテゴリ分類・構造化データを付与 |
| 使用 API | Gemini 3 Flash Preview(Google Search Grounding 有効) |
| 実行方法 | CLI: npm run enrich:jobs [OPTIONS] / UI: 求人詳細画面「AIで構造化」ボタン |
| 対象 | enrichment_status = 'pending' の求人(デフォルト) |
処理フロー
ステップ詳細
- CLI 起動:
enrich-jobs.tsがコマンドライン引数をパースし、環境変数を検証 - 対象求人取得:
jobsテーブルからcompanies(name)を JOIN して取得。enrichment_statusでフィルタリング - カテゴリマスタ取得:
job_categoriesテーブルから全カテゴリをdisplay_order順で取得 - 求人ループ: 各求人に対して順次処理(2 秒間隔)
- 成功時: カテゴリマッピングの DELETE & INSERT → エンリッチメントデータ保存(
status = 'success') - 失敗時: エラーメッセージを記録(
status = 'failed')
UI からの実行
求人詳細画面から個別の求人に対してエンリッチメントを実行できます。
操作方法
| 状態 | UI表示 | 操作 |
|---|---|---|
未エンリッチ(structured_requirements = null) | 「AIで構造化」ボタン | ボタン押下で実行 |
| エンリッチ済み | 構造化データカード + 「再生成」ボタン | 再生成ボタンで再実行 |
| 処理中 | ローディングスピナー | 完了まで待機 |
API エンドポイント
| メソッド | パス | 説明 |
|---|---|---|
POST | /api/v1/jobs/:id/enrich | 指定求人のエンリッチメントを実行 |
- 成功時: 更新後の求人詳細データを返却
- 失敗時:
{ error: { code: 'INTERNAL_ERROR', message: '...' } } GEMINI_API_KEY環境変数が未設定の場合は 500 エラー
CLI オプション
| オプション | 短縮形 | 型 | 説明 |
|---|---|---|---|
--company-id | -c | number | 企業 ID で絞り込み |
--dry-run | -d | boolean | DB 保存せず結果を表示のみ |
--force | -f | boolean | 処理済み(success/failed/skipped)も再実行 |
--limit | -l | number | 処理件数の上限を指定 |
--help | -h | boolean | ヘルプを表示 |
使用例
bash
# 全 pending 求人を処理
npm run enrich:jobs
# 特定企業のみ処理(dry-run)
npm run enrich:jobs -- --company-id 42 --dry-run
# 処理済みを含めて最大10件を再処理
npm run enrich:jobs -- --force --limit 10終了コード
| コード | 意味 |
|---|---|
0 | 全件成功 |
1 | 1 件以上の失敗、または致命的エラー |
データモデル
jobs テーブル(エンリッチメント関連カラム)
| カラム | 型 | 説明 |
|---|---|---|
ai_description | text | AI 生成の求人説明文 |
enrichment_status | varchar(20) | ステータス(下記参照) |
enrichment_error | text | 失敗時のエラーメッセージ |
enriched_at | timestamptz | 最終エンリッチメント実行日時 |
structured_requirements | jsonb | 構造化要件データ(GIN インデックス) |
ai_description_embedding | vector(3072) | 説明文の埋め込みベクトル(HNSW + halfvec インデックス) |
enrichment_status 遷移
| 値 | 意味 |
|---|---|
pending | 未処理(新規求人のデフォルト) |
success | エンリッチメント完了 |
failed | エンリッチメント失敗 |
skipped | 意図的にスキップ |
structured_requirements フィールド一覧
構造化要件は 16 フィールドの JSONB データです。
基本マッチング項目
| フィールド | 型 | 説明 | 例 |
|---|---|---|---|
min_salary | number | null | 最低年収(万円) | 500 |
max_salary | number | null | 最高年収(万円) | 800 |
required_skills | string[] | 必須スキル(歓迎スキルは除外) | ["TypeScript", "React"] |
min_experience_years | number | null | 必須経験年数 | 3 |
work_style | enum | null | 勤務形態 | "フルリモート" / "ハイブリッド" / "出社" |
work_locations | string[] | 勤務地(都道府県・エリア) | ["東京都", "大阪府"] |
拡張マッチング項目
| フィールド | 型 | 説明 | 例 |
|---|---|---|---|
target_seniority | enum | null | 対象レベル | "ジュニア" / "ミドル" / "シニア" / "リード" |
industry_domain | string | null | 業界ドメイン | "SIer" / "Web系自社開発" |
career_paths | string[] | キャリアパス | ["テックリード", "マネジメント"] |
culture_keywords | string[] | 社風キーワード | ["裁量が大きい", "フラット"] |
english_requirement | enum | null | 英語要件 | "不要" / "日常会話" / "ビジネス" / "ネイティブ" |
team_size | enum | null | チーム規模 | "少人数" / "中規模" / "大規模" |
エージェント向けナラティブ項目
| フィールド | 型 | 説明 |
|---|---|---|
position_summary | string | 候補者ができること(2-3 文) |
environment | string | チーム・環境の説明(2-3 文) |
selling_points | string[] | 求人のハイライト(3-5 点) |
agent_notes | string | null | 非公開条件(年齢制限、学歴要件等) |
バリデーションルール
- enum フィールド(
work_style,target_seniority,english_requirement,team_size): 不正値はnullにフォールバック category_ids: マスタに存在しない ID は自動除外- 年収:
typeof === 'number'でない場合はnull - 配列フィールド: 欠損・不正値は
[]にフォールバック
Gemini API 連携
モデル設定
| 項目 | 値 |
|---|---|
| モデル | gemini-3-flash-preview |
| Temperature | 0.0(決定的出力) |
| レスポンス形式 | application/json |
| ツール | Google Search Grounding |
プロンプト構成
GeminiClient.buildJobPrompt() が以下の情報を含む単一プロンプトを構築します:
- 入力データ: 企業名、求人タイトル、求人詳細(未設定時は
"(求人詳細情報なし)") - タスク 1 — カテゴリマッピング: カテゴリマスタ一覧(
{id}: {name}形式)を埋め込み、該当 ID を選択。どのカテゴリにも該当しない求人(事務職、販売職、製造業等の IT エンジニア・コンサルタント領域外)のみ「その他」を選択 - タスク 2 — AI 説明文: 約 300 文字の差別化サマリーとターゲットペルソナ
- タスク 3 — 構造化要件: 16 フィールドの抽出ルールと出力 JSON スキーマ
リトライ・エラーハンドリング
リトライ条件
以下のキーワードがエラーメッセージに含まれる場合にリトライ:
rate limittimeout503429temporarily
指数バックオフ
| 試行 | 待機時間 | 計算式 |
|---|---|---|
| 1 回目失敗 | 1 秒 | 1000 * 2^0 |
| 2 回目失敗 | 2 秒 | 1000 * 2^1 |
| 3 回目失敗 | — | 最大試行回数に到達、エラー返却 |
JSON パースエラー
Gemini のレスポンスが JSON としてパースできない場合は即座にエラーとして処理されます(リトライ対象外)。
環境変数
| 変数名 | 必須 | 説明 |
|---|---|---|
GEMINI_API_KEY | ○ | Gemini API キー |
SUPABASE_URL | ○ | Supabase プロジェクト URL |
SUPABASE_SERVICE_ROLE_KEY | ○ | Supabase サービスロールキー(RLS バイパス) |
NODE_ENV | — | production 以外の場合 .env.local を自動読み込み |
ローカル開発時は backend/.env.local に設定します(.env.example がテンプレート)。
関連ファイル
| 役割 | パス |
|---|---|
| CLI エントリーポイント | backend/src/scripts/enrich-jobs.ts |
| アプリケーションサービス | backend/src/application/services/JobEnrichmentService.ts |
| Gemini API クライアント | backend/src/infrastructure/external/gemini/GeminiClient.ts |
| Gemini 型定義 | backend/src/infrastructure/external/gemini/types.ts |
| Job エンティティ | backend/src/domain/entities/Job.ts |
| JobRepository 実装 | backend/src/infrastructure/repositories/supabase/JobRepository.ts |
| MasterRepository 実装 | backend/src/infrastructure/repositories/supabase/MasterRepository.ts |
| API ルート(enrich) | backend/src/interfaces/routes/jobs.ts |
| フロントエンド API 関数 | frontend/src/lib/api/jobs.ts |
| フロントエンド Hook | frontend/src/hooks/useJobs.ts |
| 求人詳細ページ | frontend/src/pages/JobDetailPage.tsx |
| 環境変数テンプレート | backend/.env.example |