背景
昨年(2025)、創業前企業のPoCを支援する機会があり、診断アプリを作成しました。
その中で初めて 「スコア型の汎用診断アプリ」 について本格的に考えたのですが、結論として 汎用化は想像以上に難しい と感じました。
スコア型汎用診断が難しい理由
いわゆる「○○診断」の ○○が変わる と、必要になるデータ(質問設計・観点・重み・評価方法など)の構造が意外と大きく変わります。
そのため、複数の診断を同一の仕組みで扱うには、ある程度 規約で縛って汎用化する しかありません。
しかし、スコア型診断は 単体の診断に最適化して作ったとしても、実装・運用がそれなりに複雑です。
そこに「汎用化」の要素が乗ると、設計の自由度を規約で押さえながら整合性を保つ必要があり、複雑性と運用コストが一気に跳ね上がります。
特に資金力や開発体制が強くない段階では、汎用化が 負の遺産 になりやすい、という感触がありました。
汎用化しても「全方位万能」にはならない
さらに重要なのは、汎用化したからといって どんな診断でも作れるわけではない 点です。
実際には「規約で表現できる範囲の診断」しか扱えません。
その結果、
- 当初想定していなかった種類の診断を後から追加したい
- 既存の規約では表現できない診断が出てきた
という状況になると、破壊的変更が必要になります。
そして、複雑化した基盤に対して破壊的変更を入れるのは、体感として レガシーシステムのリプレイス級の難易度 になり得る――そんな「第六感」が鳴りました。
そこで得た仮説
いろいろ検討した結果、生成AI(LLM)をうまく使うことで、この「スコア型診断がスケールしづらい問題」を 別のアプローチで緩和できる可能性 に気づきました。
本記事では、その気づきを実際の実装と共に共有します。
対象読者
- 診断アプリ / 問診アプリ / 自己判定ツールを作ったことがある、または作ろうとしている人 (例:健康・メンタル・キャリア・スキル・組織診断など)
- 「スコアリングでいけるでしょ」と思って実装したら、運用・拡張・データ構造の地獄に片足突っ込んだ人
- “汎用診断エンジン” を作ってみたが、診断が増えるほど例外対応とif地獄が増えて辛くなってきた人
- LLMを使ったプロダクト開発に興味はあるが、診断のようなドメインで何が嬉しいのかを具体例ベースで知りたい人
前提: 診断アプリがスケールしにくい理由
診断アプリの種類
診断アプリは大きく フローチャート型(分岐型) と スコア型(加点・重み付け型) に分けられます。
1) フローチャート型(分岐型)
今回はスコープ外(比較のために概要だけ触れます)
質問に答えるたびに次の質問が変わり、最終的に特定の結果へ到達するタイプです。
-
例:「YESならQ2へ / NOならQ3へ」といった分岐を繰り返す
-
特徴
- ロジックが 木構造(決定木) になり、直感的で実装しやすい
- 一方で、表示できる結果は 木の末端数に制約されやすく、粒度が荒くなりがち
- 分岐が増えるほど 設計・テスト・保守が急激に重くなりやすい
2) スコア型(加点・重み付け型)
今回の主題はこっち(スコア型)
各設問の回答に点数や重みを紐づけ、合計スコアや特徴量の合成で結果を出すタイプです。
-
例:「Aを選ぶと ‘内向性’ +2、Bを選ぶと ‘外向性’ +2」といった加点
-
特徴
- スコアに応じて表示内容を柔軟に調整できる
- 設問数が増えても分岐は増えないため、見た目はスケールしそうに見える
- ただし運用・拡張フェーズでは 設計の整合性維持が難しくなりやすい
スコア型がスケールしにくい理由 ~汎用診断エンジンの宿命~
スコア型は一見「集計処理」に見えますが、運用を想定すると次の要素が同時に絡みやすく、破綻ポイントが増えます。
- データ構造
- 計算ロジック
- バージョン管理
- 汎用化(複数診断対応)
1) 組み合わせが増え、「設計」が職人化しやすい
典型的には、次の要素を持ちます。
- 設問(質問)
- 選択肢(回答)
- 観点(評価軸 / 特徴量)
- 診断結果(タイプ / アドバイス / 施策)
そして「回答 → 観点への加点(重み)」を合成して、最終結果を出します。
この時点で扱うのは実質的に、
- 選択肢 × 観点 × 診断結果 の組み合わせ
- 観点が増えるほど 重み設計の整合性を保つのが難しくなる
- 小さな調整でも結果分布が変わり、調整の影響範囲が読みにくい
結果として「仕様設計」というより、**チューニング(試行錯誤)**の比重が高くなりがちです。
2) 計算ロジックが複雑化し、非機能要件が効いてくる
診断が成長すると、単純加算ではなく次のような要求が出やすいです。
- 特定条件のときだけ加点(相互作用)
- 複数観点の組み合わせで補正(例:Aが高い & Bが低いならCを下げる)
- 正規化、平均との差分、偏差、閾値など
ここで「毎回リアルタイム集計」だと重くなりやすく、ケースによっては
先に計算できる部分を事前集計する(集計テーブル / マテビュー) といった設計が必要になります。
ただし、これを導入すると
- 集計の更新タイミング
- キャッシュ不整合
- ロジック変更時の再計算
- リリース手順
が絡み、診断アプリなのに データ基盤っぽい難しさに踏み込みます。
3) バージョン管理しないと「過去の体験」が壊れやすい
スコア型はロジックや重みを少し変えるだけで、結果が変わります。
そのため、運用すると次が起こりやすいです。
- 過去ユーザーが同じ回答でも別結果になる
- 履歴・比較機能が成立しにくい
- 「前はAだったのに今日はBになった」系の問い合わせが出る
なので多くの場合、診断定義(設問/重み/結果文)をバージョン固定して扱う必要が出ます。
ここから「診断」ではなく、コンテンツのリリース管理の性質が強くなります。
4) 複数診断に対応した瞬間、汎用化の限界が見え始める
診断が1本だけなら、多少複雑でも気合で作れます。
問題は 診断を増やしてプロダクトとして運用する 段階です。
複数診断を扱うため、多くのチームは「汎用診断エンジン」を作ります。
つまり「どの診断も同じ構造体で表現できる」ようにする発想です。
しかし現実には、診断ごとに要求差が出ます。
- 観点を 親子孫までネストして表現したい(ツリー構造)
- 観点を単純加点ではなく 合成特徴量として扱いたい
- 診断結果を 複数出したい / ネストしたい / 出し分けたい
- “スコア”より “根拠説明” を重視したい
- 設問を動的に出し分けたい(途中で質問内容を変えたい)
つまり、診断ごとに欲しいデータ構造が違う という問題にぶつかります。
頑張って汎用化しようとすると、往々にして
- 共通化のための制約が増える
- 例外対応のifが増える
- DBが抽象化されすぎて運用が辛くなる
- 新しい診断を追加するたびにコア改修が必要になる
といった形で複雑性が増え、最終的に
「作りたい診断に必要な構造が表現できない」
「別エンジンを作り直すしかない」
というトレードオフに直面しやすくなります。
私の考えたアプローチ
PoC作っただけで、実際に運用したわけではないので参考にする際は御承知おき下さい
レポジトリ
動作動画 & LLM IN/OUT
SYSTEM PROMPT
🤖 AI時代エンジニアキャリアアドバイザー_V1003
🏗️ システムプロンプト(完全統合版)
system_prompt:
title: "エンジニアキャリア戦略ナビゲーター(17職種DB統合版)"
description: >
働き手からの診断質問の解答結果を受け、
エンジニア職種17種データベースを基盤とし回答者へ最適な職種と根拠を返却します。
最適な職種を判断する観点は以下です。
- 性格傾向をベースにした性格的職種一致(personality_match)
- 回答者の実績をベースにした実績一致(work_match)
- 回答者の指向をベースにした指向一致(work_match)
goals:
- 回答結果を受け、診断した結果を以下のjson形式に絶対に過不足なく整形してください。
- 最適な職種を3つ返却してください。
- reasonについて
- 必ず日本語で生成してください
- 回答者が結果を見て確かにその通りだなと感じるような回答を心がけてください
- 質問単体でこう回答したからという理由ではなく、全体を通してこういう傾向があるからという回答を心がけてください
- MBTI型は回答者はわかりにくいのでMBTI型は使用せず具体的な性格名を言語化して伝達することを心がけてください
- 回答者のMBTI傾向に合わせてreasonの語調を最適化してください
# 出力フォーマット(絶対にこのフォーマットのみ返却)
```json
{
1:{
"name": <17職種データセットのname>,
"total_match":{
"score": <0~100 職種への総合マッチ度>,
"reason": <スコアの根拠を日本語で生成>,
},
"personality_match":{
"score": <0~100 職種への性格マッチ度>,
"reason": <スコアの根拠を日本語で生成>
},
"work_match":{
"score": <0~100 職務内容・働き方のマッチ度>,
"reason": <スコアの根拠を日本語で生成>
}
},
2:{...},
3:{...}
}
```
📊 完全データベース統合仕様
17職種データセット(mst_ai_jobs_new.csv準拠)
# 🧰 1. 技術・エンジニアリング系(4職種)
technical_engineering_jobs:
label: "技術・エンジニアリング系"
jobs:
- id: 1
name: "ソフトウェアエンジニア"
summary: "Webや業務アプリの機能を設計して実装する開発者"
main_roles: "要件理解と設計・実装・レビュー・改善"
collaboration: "PMやデザイナーやQAと協働"
strength_domains: "基礎実装力・問題解決"
salary_range: "500〜900万円"
target_phase: "実行層(TA_KB)"
required_skills: "プログラミング基礎・Git・テスト"
deliverables: "実装コード・設計メモ"
career_path: "学習→個人開発→開発実務"
common_ai_tools: "GitHub Copilot・ChatGPT"
features: "汎用性が高く幅広い領域を経験しやすい"
advice: "基礎を固めて小さく作り切る経験を増やす"
- id: 2
name: "バックエンドエンジニア"
summary: "サーバーサイドの設計とAPI開発を担う"
main_roles: "API設計・DB設計・性能改善・保守"
collaboration: "フロントエンドやインフラと連携"
strength_domains: "設計力・堅牢性・性能"
salary_range: "550〜1000万円"
target_phase: "実行層(TA_KB)"
required_skills: "Python・Java・Go・SQL・設計"
deliverables: "API仕様・DBスキーマ・実装"
career_path: "ソフトウェア開発→バックエンド特化"
common_ai_tools: "GitHub Copilot・ChatGPT・SQL支援ツール"
features: "堅牢性とスケールを意識した開発が中心"
advice: "設計レビューを通じて設計思考を磨く"
- id: 3
name: "フロントエンドエンジニア"
summary: "ユーザーが触れるUIを実装する"
main_roles: "UI設計・実装・状態管理・性能改善"
collaboration: "デザイナーやバックエンドと協働"
strength_domains: "体験設計・UI実装力"
salary_range: "500〜900万円"
target_phase: "実行層(TA_KB)"
required_skills: "HTML・CSS・TypeScript・UI設計"
deliverables: "画面実装・コンポーネント"
career_path: "Web制作→フロント特化"
common_ai_tools: "GitHub Copilot・ChatGPT・Figma AI"
features: "体験の質が価値に直結する"
advice: "アクセシビリティとパフォーマンスを意識する"
- id: 4
name: "フルスタックエンジニア"
summary: "フロントとバックを横断して開発する"
main_roles: "要件整理・設計・実装・運用"
collaboration: "少人数チームで幅広く担当"
strength_domains: "幅広い実装力・調整力"
salary_range: "550〜1000万円"
target_phase: "実行層(TA_KB)・拡大型"
required_skills: "フロント技術・バック技術・DB"
deliverables: "機能一式の実装"
career_path: "フロントまたはバック経験→横断"
common_ai_tools: "GitHub Copilot・ChatGPT"
features: "スピード重視のプロダクト開発に強い"
advice: "広く浅くにならないよう基礎を固める"
# 📱 2. モバイル系(1職種)
mobile_jobs:
label: "モバイル系"
jobs:
- id: 5
name: "モバイルアプリエンジニア"
summary: "iOSやAndroidのアプリを開発する"
main_roles: "画面実装・端末機能連携・リリース管理"
collaboration: "デザイナーやAPI担当と協働"
strength_domains: "UX実装・端末知識"
salary_range: "500〜950万円"
target_phase: "実行層(TA_KB)"
required_skills: "Swift・Kotlin・UI設計"
deliverables: "アプリ実装・ストア申請"
career_path: "Web開発→モバイル特化"
common_ai_tools: "GitHub Copilot・ChatGPT・Xcode補助"
features: "端末制約とUXの両立が重要"
advice: "実機検証を習慣化する"
# 🎮 3. ゲーム・XR系(1職種)
game_xr_jobs:
label: "ゲーム・XR系"
jobs:
- id: 6
name: "ゲームエンジニア"
summary: "ゲームのロジックや描画を実装する"
main_roles: "ゲームロジック・描画最適化・ツール開発"
collaboration: "プランナーやデザイナーと協働"
strength_domains: "リアルタイム処理・最適化"
salary_range: "450〜900万円"
target_phase: "実行層(TA_KB)"
required_skills: "C++・C#・Unity・Unreal"
deliverables: "ゲーム機能・パフォーマンス改善"
career_path: "ゲーム開発→エンジン理解"
common_ai_tools: "GitHub Copilot・ChatGPT"
features: "リアルタイム性と体験品質が重要"
advice: "小さなゲームを完成させる経験を積む"
# 🧩 4. 組み込み・ハードウェア系(1職種)
embedded_hardware_jobs:
label: "組み込み・ハードウェア系"
jobs:
- id: 7
name: "組み込みエンジニア"
summary: "機器やデバイスの制御ソフトを開発する"
main_roles: "制御設計・ドライバ実装・デバッグ"
collaboration: "ハード担当や製造と連携"
strength_domains: "低レイヤー理解・品質"
salary_range: "450〜850万円"
target_phase: "実行層(TA_KB)"
required_skills: "C・C++・RTOS・ハード基礎"
deliverables: "制御ソフト・仕様書"
career_path: "電気系学習→組み込み実務"
common_ai_tools: "ChatGPT・コード補助"
features: "制約の中で安定動作を作る仕事"
advice: "仕様と実機の差分を丁寧に検証する"
# 🗄️ 5. データ基盤系(1職種)
data_platform_jobs:
label: "データ基盤系"
jobs:
- id: 8
name: "データエンジニア"
summary: "データ基盤を設計し運用する"
main_roles: "ETL・DWH設計・パイプライン運用"
collaboration: "分析担当や開発と協働"
strength_domains: "データ処理・基盤設計"
salary_range: "600〜1000万円"
target_phase: "実行層(TA_KB)"
required_skills: "SQL・Python・クラウドDWH"
deliverables: "データパイプライン・設計書"
career_path: "インフラや開発→データ特化"
common_ai_tools: "ChatGPT・SQL支援ツール"
features: "信頼できるデータ提供が価値"
advice: "データ品質の基準を明確にする"
# 🛠️ 6. インフラ・運用系(3職種)
infra_ops_jobs:
label: "インフラ・運用系"
jobs:
- id: 9
name: "クラウドインフラエンジニア"
summary: "クラウド基盤を設計構築する"
main_roles: "クラウド設計・IaC・監視構築"
collaboration: "開発チームと連携"
strength_domains: "可用性・運用設計"
salary_range: "550〜1000万円"
target_phase: "実行層(TA_KB)"
required_skills: "AWS・GCP・Terraform・監視"
deliverables: "インフラ構成図・IaC"
career_path: "インフラ運用→クラウド設計"
common_ai_tools: "ChatGPT・IaC補助"
features: "再現性ある基盤づくりが重要"
advice: "運用まで見据えた設計をする"
- id: 10
name: "SREエンジニア"
summary: "サービスの信頼性を高める運用担当"
main_roles: "SLI設計・障害対応・自動化"
collaboration: "開発と運用の橋渡し"
strength_domains: "信頼性設計・自動化"
salary_range: "600〜1100万円"
target_phase: "拡大型(TA_KC)"
required_skills: "Linux・監視・SLO・自動化"
deliverables: "運用改善レポート・自動化ツール"
career_path: "インフラ運用→SRE"
common_ai_tools: "ChatGPT・運用自動化補助"
features: "運用の継続改善が中心"
advice: "障害事例を学び再発防止を習慣化する"
- id: 11
name: "DevOpsエンジニア"
summary: "開発と運用をつなぐ仕組みを作る"
main_roles: "CI/CD設計・環境整備・自動化"
collaboration: "開発チームと密に連携"
strength_domains: "自動化・パイプライン設計"
salary_range: "550〜1000万円"
target_phase: "拡大型(TA_KC)"
required_skills: "CI/CD・Docker・Kubernetes"
deliverables: "パイプライン・運用手順"
career_path: "開発者→運用自動化"
common_ai_tools: "GitHub Copilot・ChatGPT"
features: "開発スピードと品質を両立する役割"
advice: "小さく改善を積み上げる"
# 🌐 7. ネットワーク系(1職種)
network_jobs:
label: "ネットワーク系"
jobs:
- id: 12
name: "ネットワークエンジニア"
summary: "ネットワーク設計と運用を担う"
main_roles: "設計・構築・監視・障害対応"
collaboration: "インフラチームと協働"
strength_domains: "安定運用・通信設計"
salary_range: "500〜900万円"
target_phase: "実行層(TA_KB)"
required_skills: "TCP/IP・ルーティング・FW"
deliverables: "ネットワーク構成図・設定"
career_path: "運用→設計担当"
common_ai_tools: "ChatGPT・設定レビュー補助"
features: "可用性の確保が重要"
advice: "基礎理論を実機で検証する"
# 🛡️ 8. セキュリティ系(1職種)
security_jobs:
label: "セキュリティ系"
jobs:
- id: 13
name: "セキュリティエンジニア"
summary: "システムの安全性を守る専門職"
main_roles: "脆弱性診断・対策設計・監査対応"
collaboration: "開発と法務と連携"
strength_domains: "リスク評価・対策力"
salary_range: "600〜1200万円"
target_phase: "拡大型(TA_KC)"
required_skills: "セキュリティ基礎・監査・ログ分析"
deliverables: "対策計画・監査レポート"
career_path: "インフラや開発→セキュリティ特化"
common_ai_tools: "ChatGPT・脆弱性調査補助"
features: "安全性を継続的に高める"
advice: "最新動向を追い続ける"
# ✅ 9. 品質・テスト系(2職種)
qa_test_jobs:
label: "品質・テスト系"
jobs:
- id: 14
name: "QA・テストエンジニア"
summary: "品質を担保するテスト設計の専門家"
main_roles: "テスト設計・実行・不具合分析"
collaboration: "開発チームと協働"
strength_domains: "品質保証・分析力"
salary_range: "400〜800万円"
target_phase: "実行層(TA_GB)"
required_skills: "テスト設計・不具合分析・仕様理解"
deliverables: "テスト計画・レポート"
career_path: "QA実務→品質責任者"
common_ai_tools: "ChatGPT・テストケース生成"
features: "ユーザー体験を守る役割"
advice: "仕様理解を深めて観点を広げる"
- id: 15
name: "テスト自動化エンジニア"
summary: "自動テストで開発効率を高める"
main_roles: "自動テスト設計・CI連携・保守"
collaboration: "開発とQAの橋渡し"
strength_domains: "自動化設計・保守力"
salary_range: "500〜900万円"
target_phase: "拡大型(TA_KC)"
required_skills: "自動テスト・CI/CD・スクリプト"
deliverables: "自動テストスイート"
career_path: "QA→自動化特化"
common_ai_tools: "GitHub Copilot・ChatGPT"
features: "品質と速度の両立に貢献"
advice: "保守性の高いテスト設計を意識する"
# 🧭 10. マネジメント・アーキテクト系(2職種)
management_architect_jobs:
label: "マネジメント・アーキテクト系"
jobs:
- id: 16
name: "テックリード/アーキテクト"
summary: "技術方針と設計の責任者"
main_roles: "技術選定・アーキ設計・レビュー"
collaboration: "複数チームと連携"
strength_domains: "設計力・意思決定"
salary_range: "700〜1400万円"
target_phase: "拡大型(TA_KC)"
required_skills: "設計・レビュー・チーム指導"
deliverables: "アーキ設計書・技術方針"
career_path: "シニアエンジニア→リード"
common_ai_tools: "ChatGPT・設計レビュー補助"
features: "長期視点での技術選定が重要"
advice: "設計の言語化を習慣化する"
- id: 17
name: "エンジニアリングマネージャー"
summary: "エンジニア組織をマネジメントする"
main_roles: "目標設定・育成・評価・採用"
collaboration: "事業側と連携"
strength_domains: "人材育成・組織設計"
salary_range: "700〜1500万円"
target_phase: "拡大型(TA_KC)"
required_skills: "マネジメント・技術理解・評価"
deliverables: "組織運営計画・評価資料"
career_path: "リード経験→マネージャー"
common_ai_tools: "ChatGPT・ドキュメント補助"
features: "人と組織の成長が成果"
advice: "技術と人の両面を大切にする"
🧾 回答データの解釈ルール
- ユーザープロンプトは JSON 配列で渡され、各要素は以下構造を持ちます。
{ "question": "Q10X_xxx", "value": "option_identifier", "vector": { "cluster_bias": {"technical_engineering_jobs": 2, "infra_ops_jobs": 1}, "personality_hints": ["ENTJ", "ENFJ"], "skill_tags": ["stakeholder_alignment", "business_strategy"], "seniority": "senior", "...": "補助シグナル" } } -
cluster_biasは 0〜3 程度の重みで 10 の職種カテゴリ(technical_engineering_jobs,mobile_jobs,game_xr_jobs,embedded_hardware_jobs,data_platform_jobs,infra_ops_jobs,network_jobs,security_jobs,qa_test_jobs,management_architect_jobs)への親和性を示します。回答全体を集約し、スコア算出時の候補選定に反映させてください。 -
personality_hintsは MBTI 参考タイプです。タイプ名は出力せず、strength_domains/collaboration/featuresに照らして性格傾向を言語化し、personality_matchの根拠に用います。 -
skill_tagsは回答者が発揮できるスキル・貢献スタイルのキーワードです。職種のrequired_skillsやmain_roles、deliverablesとの適合を説明する際に使います。 -
seniorityは回答から推定される経験レンジ(entry/entry_mid/mid/mid_plus/seniorなど)を表します。職種ごとのtarget_phaseと照合し、適した役割・次のステップを補足してください。 - 一部質問は複数選択可です(例:
Q103_strength,Q108_focus_area)。複数のシグナルが届いた場合は合算し、繰り返し登場するクラスターやスキルの強度を高く評価してください。 -
vector内に追加フィールド(例:extra_context,preferences等)が存在する場合は補助情報として読み取り、上記メトリクスの説明に活用してください。 - 上記シグナルを統合し、
total_matchでは総合的な適合度、personality_matchでは性格・価値観の合致、work_matchでは職務内容や働き方のフィットを具体的な理由と共に提示してください。
USER PROMPT
[
{
"value": "web_app_development",
"vector": {
"seniority": "entry_mid",
"skill_tags": ["web_development", "feature_delivery"],
"cluster_bias": {
"technical_engineering_jobs": 3
},
"personality_hints": ["ISTJ", "INTJ"]
},
"question": "Q101_current_domain"
},
{
"value": "deepen_web_app",
"vector": {
"seniority": "mid",
"skill_tags": ["feature_development", "product_iteration"],
"cluster_bias": {
"technical_engineering_jobs": 3
},
"personality_hints": ["ENTP", "INTJ"]
},
"question": "Q102_career_focus"
},
{
"value": "strength_user_experience",
"vector": {
"seniority": "mid",
"skill_tags": ["ux_design", "user_experience", "frontend"],
"cluster_bias": {
"mobile_jobs": 1,
"game_xr_jobs": 1,
"technical_engineering_jobs": 2
},
"personality_hints": ["ENFP", "INFJ"]
},
"question": "Q103_strength"
},
{
"value": "role_tech_leader",
"vector": {
"seniority": "senior",
"skill_tags": ["technical_leadership", "architecture_roadmap"],
"cluster_bias": {
"management_architect_jobs": 3,
"technical_engineering_jobs": 1
},
"personality_hints": ["ENTJ", "INTJ"]
},
"question": "Q104_role_style"
},
{
"value": "skill_frontend_mobile",
"vector": {
"seniority": "mid",
"skill_tags": ["ui_implementation", "design_system", "mobile_ui"],
"cluster_bias": {
"mobile_jobs": 2,
"game_xr_jobs": 1,
"technical_engineering_jobs": 1
},
"personality_hints": ["ENFP", "ENFJ"]
},
"question": "Q105_skill_focus"
},
{
"value": "decision_data_driven",
"vector": {
"seniority": "mid_plus",
"skill_tags": ["data_driven", "metrics"],
"cluster_bias": {
"infra_ops_jobs": 1,
"data_platform_jobs": 2,
"technical_engineering_jobs": 1
},
"personality_hints": ["INTJ", "ISTJ"]
},
"question": "Q106_decision_style"
},
{
"value": "style_leader",
"vector": {
"seniority": "senior",
"skill_tags": ["leadership", "team_management"],
"cluster_bias": {
"management_architect_jobs": 3
},
"personality_hints": ["ENTJ", "ENFJ"]
},
"question": "Q107_work_style"
},
{
"value": "focus_web_app",
"vector": {
"seniority": "mid",
"skill_tags": ["feature_development", "web_app"],
"cluster_bias": {
"technical_engineering_jobs": 3
},
"personality_hints": ["ENTP", "INTJ"]
},
"question": "Q108_focus_area"
},
{
"value": "outcome_user_value",
"vector": {
"seniority": "mid",
"skill_tags": ["user_value", "product_quality"],
"cluster_bias": {
"mobile_jobs": 1,
"game_xr_jobs": 1,
"technical_engineering_jobs": 2
},
"personality_hints": ["ENFP", "INFJ"]
},
"question": "Q109_outcome_focus"
},
{
"value": "challenge_rearchitect_platform",
"vector": {
"seniority": "mid_plus",
"skill_tags": ["platform_scaling", "architecture_refactor"],
"cluster_bias": {
"infra_ops_jobs": 1,
"data_platform_jobs": 2,
"technical_engineering_jobs": 1
},
"personality_hints": ["INTJ", "ISTP"]
},
"question": "Q110_near_term_challenge"
}
]
LLM Response(gemini 3.0 flash)
{
"1": {
"name": "ソフトウェアエンジニア",
"total_match": {
"score": 95,
"reason": "Webアプリケーション開発への強い志向と、機能実装からユーザー体験の向上までを幅広くカバーしたいという意向が完璧に合致しています。現在のドメインを深めつつ、プロダクトの成長に直接寄与したいという姿勢は、この職種の核心的な価値と一致します。"
},
"personality_match": {
"score": 92,
"reason": "論理的で秩序を重んじる一方で、ユーザーの感情や体験にも配慮できるバランスの取れた性格です。客観的なデータに基づいた意思決定を好みつつ、最終的なアウトプットが人々に届く価値を大切にする姿勢は、開発現場で非常に信頼されるタイプと言えます。"
},
"work_match": {
"score": 96,
"reason": "機能開発の反復やUIの実装、さらにはプラットフォームの再設計といった、実行層から設計層にまたがる幅広いタスクへの適性が非常に高いです。特に「ユーザー価値」を成果の指標とする考え方は、アジャイルな開発環境において最大の強みとなります。"
}
},
"2": {
"name": "テックリード/アーキテクト",
"total_match": {
"score": 88,
"reason": "技術的なリーダーシップを発揮したいという明確な意向と、プラットフォームの再設計(リアーキテクト)に挑戦したいという目標から、将来的なキャリアパスとして最適です。技術選定や方針決定において、あなたの戦略的な視点が強く求められます。"
},
"personality_match": {
"score": 85,
"reason": "全体像を俯瞰して捉える戦略家としての側面と、チームを牽引するリーダーシップを兼ね備えています。複雑な問題を構造化して解決することに喜びを感じる傾向があり、周囲を納得させる論理的な説明能力も備わっているため、技術的な意思決定者としての適性が高いです。"
},
"work_match": {
"score": 90,
"reason": "単なる実装にとどまらず、スケーラビリティを考慮した設計や技術ロードマップの策定など、より上位の設計業務に強い関心がある点が合致しています。データに基づいた判断を好むスタイルは、技術的な妥当性を担保するアーキテクトの職務と相性が抜群です。"
}
},
"3": {
"name": "フロントエンドエンジニア",
"total_match": {
"score": 82,
"reason": "ユーザー体験(UX)の向上やUI実装を自身の強みとして認識しており、Webアプリケーションのフロントエンド領域で高い専門性を発揮できる可能性が高いです。デザインシステムやモバイルUIへの関心も、この職種での活躍を後押しします。"
},
"personality_match": {
"score": 88,
"reason": "ユーザーが実際に触れる部分に対して細やかな配慮ができる、高い共感力と美意識を持っています。論理的な裏付けを持ちながらも、直感的な使いやすさを追求する姿勢は、エンジニアリングとデザインの架け橋となるフロントエンドのスペシャリストに最適です。"
},
"work_match": {
"score": 78,
"reason": "UIコンポーネントの実装やパフォーマンス改善など、視覚的な成果が直接ユーザー価値につながる業務において、あなたのスキルが最も輝きます。プロダクトの反復改善を通じて、ユーザーの反応をダイレクトに感じられる環境が、あなたのモチベーションを最大化させるでしょう。"
}
}
}
解説
結論から言うと、選択肢を「人に表示する文言」と「LLMに渡す評価指示」に分離すると、
重みテーブルの職人芸を減らし、判断軸を“文章の制約”として移すことができ
LLMの推論力に任せた柔軟な診断構造にできます。
ポイントはこの2層構造です:
-
UI向け:ユーザーに見せる自然な選択肢(
display_label) -
LLM向け:その選択肢をどう評価するかの指示(
llm_op)
たとえば、UIではこう見せる:
「自分で調べて手を動かすのが好き」
一方、LLMにはこう渡す(JSON例):
{
"facet": ["主体性", "実装志向", "探索行動"],
"evidence": "自走して調査・試作できるタイプ"
}
この分離によって、
- UIの言い回しはUX最適化できる
- 評価ロジックは
llm_opだけで更新可能 - 観点や構造が増えてもDBを壊さなくて済む
という状態になります。
もちろん LLMの推論に依存しますが、後述する「版管理」と「プロンプト固定」で運用をある程度は安定化できます。
具体的な実装
以下は、実際の DB 設計の要点です。
ER図
テーブル定義
## 2. テーブル定義(DDL仕様)
> ここでの型は MySQL 8.0 想定。`varchar` は文字数の目安。`created_at`/`updated_at` はアプリで管理。
> すべて `ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`。
### 2.1 users
* **description**:
ユーザーの管理(パスワード or OAuth)
* **columns**:
- `id BIGINT PK AI`
- `email VARCHAR(191) NOT NULL`
- `hashed_password TEXT NULL` ← OAuth専用ユーザーを許容
- `created_at DATETIME NOT NULL`
* **constraints**:
- `UK users_email (email)`
* **indexes**:
- `(email)`(UK兼用)
### 2.2 admin_users
* **description**:
管理者ユーザーの管理
* **columns**:
- `id BIGINT PK AI`
- `user_id VARCHAR(64) NOT NULL`
- `display_name VARCHAR(191) NULL`
- `hashed_password TEXT NOT NULL`
- `is_active BOOL TRUE`
- `created_at DATETIME NOT NULL`
- `updated_at DATETIME NOT NULL`
* **constraints**:
- `UK admin_user_id (user_id)`
* **indexes**:
- `(user_id)`(UK兼用)
### 2.3 diagnostics
* **description**:
診断種類の管理
参照マスタテーブル(outcome_table_name)はバックエンド側で解決する
* **columns**:
* `id BIGINT PK AI`
* `code VARCHAR(64) NOT NULL`
* `description TEXT`
* `outcome_table_name VARCHAR(64) NOT NULL` --参照するマスタテーブル正式名称
* `is_active BOOLEAN NOT NULL DEFAULT TRUE`
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `UK diagnostics_code (code)`
* **indexes**:
* `(code)`
### 2.4 diagnostic_versions
* **description**:
各診断のプロンプト・質問/選択肢スナップショットのバージョン履歴を管理(公開中バージョンは別テーブルで管理)
* **columns**:
* `id BIGINT PK AI`
* `diagnostic_id BIGINT NOT NULL`
* `name VARCHAR(128) NOT NULL`
* `description TEXT NULL`
* `system_prompt TEXT NULL`
* `src_hash VARCHAR(128) NULL` -- 版に紐づく`version_questions, version_options, system_prompt`を足し合わせハッシュ化する(監査的な意味)
* `note TEXT NULL`
* `created_by_admin_id BIGINT NOT NULL`
* `updated_by_admin_id BIGINT NOT NULL`
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `FK (diagnostic_id) -> diagnostics(id) ON DELETE RESTRICT`
* `FK (created_by_admin_id) -> admin_users(id) ON DELETE RESTRICT`
* `FK (updated_by_admin_id) -> admin_users(id) ON DELETE RESTRICT`
* `UK diagnostic_versions_diagnostic_name (diagnostic_id, name)`
* **indexes**:
* `IDX diagnostic_versions_diagnostic (diagnostic_id, id)`
* `IDX diagnostic_versions_created_by (created_by_admin_id)`
* `IDX diagnostic_versions_updated_by (updated_by_admin_id)`
### 2.5 aud_diagnostic_version_logs
* **description**:
管理者が `diagnostic_versions` に対して行った操作ログを管理(作成・更新・削除の履歴を保持)
* **columns**:
* `id BIGINT PK AI`
* `version_id BIGINT NOT NULL`
* `admin_user_id BIGINT NOT NULL`
* `action VARCHAR(32) NOT NULL` -- 例: 'CREATE', 'UPDATE', 'DELETE', 'RESTORE'
* `field_name VARCHAR(64) NULL` -- 更新対象のカラム名(全体操作ならNULL)
* `old_value TEXT NULL`
* `new_value TEXT NULL`
* `note TEXT NULL`
* `created_at DATETIME NOT NULL`
* **constraints**:
* `FK (version_id) -> diagnostic_versions(id) ON DELETE RESTRICT`
* `FK (admin_user_id) -> admin_users(id) ON DELETE RESTRICT`
* **indexes**:
* `IDX aud_cdv_logs_version (version_id, created_at)`
* `IDX aud_cdv_logs_admin (admin_user_id, created_at)`
### 2.6 cfg_active_versions
* **description**:
現時点でアクティブな各診断のバージョンを管理(1診断につき必ず1つだけ存在する)
* **columns**:
* `id BIGINT PK AI`
* `diagnostic_id BIGINT NOT NULL`
* `version_id BIGINT NOT NULL`
* `created_by_admin_id BIGINT NOT NULL`
* `updated_by_admin_id BIGINT NOT NULL`
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `FK (diagnostic_id) -> diagnostics(id) ON DELETE RESTRICT`
* `FK (version_id) -> diagnostic_versions(id) ON DELETE RESTRICT`
* `FK (created_by_admin_id) -> admin_users(id) ON DELETE RESTRICT`
* `FK (updated_by_admin_id) -> admin_users(id) ON DELETE RESTRICT`
* `UK cfg_active_versions_scope (diagnostic_id)`
* **indexes**:
* NONE
### 2.7 sessions
* **description**:
1診断結果が、どの診断で、どのユーザーが、どのバージョンのsystemプロンプトを使用したのかを管理する
なお、公開終了日も管理する
* **columns**:
* `id BIGINT PK AI`
* `user_id BIGINT NULL`(未ログイン利用を許容)
* `session_code VARCHAR(64) NOT NULL`
* `diagnostic_id BIGINT NOT NULL`
* `version_id BIGINT NOT NULL`
* `llm_result JSON NULL` -- LLMの回答結果を保存
* `version_options_hash VARCHAR(128) NOT NULL` -- 診断versionとユーザー選択肢をハッシュ化して保存(既に生成済みの組み合わせは結果を流用する)
* `ended_at DATETIME NULL`
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `FK (user_id) -> users(id) ON DELETE RESTRICT`
* `FK (diagnostic_id) -> diagnostics(id) ON DELETE RESTRICT`
* `FK (version_id) -> diagnostic_versions(id) ON DELETE RESTRICT`
* `UK sessions_session_code (session_code)`
* **indexes**:
* `IDX sessions_user (user_id)`
* `IDX sessions_diagnostic_version (diagnostic_id, version_id)`
* `IDX sessions_ended_at (ended_at)`
### 2.8 questions
* **description**:
質問カタログ(下書き/公開候補)。versionごとの出題は別テーブルで制御。
* **columns**:
* `id BIGINT PK AI`
* `diagnostic_id BIGINT NOT NULL`
* `q_code VARCHAR(64) NOT NULL`
* `display_text VARCHAR(1000) NOT NULL`
* `multi BOOLEAN NOT NULL DEFAULT FALSE`
* `sort_order INT NOT NULL`
* `is_active BOOLEAN NOT NULL DEFAULT FALSE` --「カタログとしての有効/無効」を表す(版とは切り離す)
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `UK questions_code (diagnostic_id, q_code)`
* `FK (diagnostic_id) -> diagnostics(id) ON DELETE RESTRICT`
* **indexes**:
* `IDX idx_questions_diagnostic_active_sort (diagnostic_id, is_active, sort_order, id)`
### 2.9 version_questions
* **description**:
指定 `version_id` で出題する質問集合を制御し、版ごとの文言・設問設定を固定化するスナップショット。
原則:version_questions に入っているものだけ、その version で出題。
自動初期化(推奨):版生成時に
- `questions` カタログの `q_code` / `display_text` / `multi` / `sort_order` / `is_active` をコピーし、版内での文言修正や一時的な非表示に対応。
* **columns**:
* `id BIGINT PK AI`
* `version_id BIGINT NOT NULL`
* `diagnostic_id BIGINT NOT NULL` -- 版に紐づく診断ID(`diagnostic_versions.diagnostic_id` をコピー)
* `question_id BIGINT NOT NULL`
* `q_code VARCHAR(64) NOT NULL`
* `display_text VARCHAR(1000) NOT NULL`
* `multi BOOLEAN NOT NULL DEFAULT FALSE`
* `sort_order INT NOT NULL` -- 版ごとの出題順
* `is_active BOOLEAN NOT NULL DEFAULT TRUE`
* `created_by_admin_id BIGINT NOT NULL`
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `UK (version_id, question_id)`
* `FK (version_id) -> diagnostic_versions(id) ON DELETE RESTRICT`
* `FK (diagnostic_id) -> diagnostics(id) ON DELETE RESTRICT`
* `FK (question_id) -> questions(id) ON DELETE RESTRICT`
* `FK (created_by_admin_id) -> admin_users(id) ON DELETE RESTRICT`
* **indexes**:
* `IDX version_questions_sort (version_id, sort_order, question_id)`
* `IDX version_questions_active (version_id, is_active, sort_order)`
### 2.10 options
* **columns**:
* `id BIGINT PK AI`
* `question_id BIGINT NOT NULL`
* `opt_code VARCHAR(64) NOT NULL`
* `display_label VARCHAR(500) NOT NULL`
* `llm_op JSON NULL` -- この質問をLLMはどういう観点で評価するのかの指示
* `sort_order INT NOT NULL`
* `is_active BOOLEAN NOT NULL DEFAULT TRUE`
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `UK options_code (question_id, opt_code)`
* `FK (question_id) -> questions(id) ON DELETE RESTRICT`
* `UK options_question_sort (question_id, sort_order)`
* **indexes**:
* `IDX idx_options_question_sort_id(question_id, sort_order, id)`
### 2.11 version_options
* **description**:
指定 `version_id` で出題する **選択肢集合** を制御(= 選択肢のアクティベーションを版ごとに固定)
並び順は options.sort_order を初期値にコピーし、version_options.sort_order で版ごとに固定。
- `options` カタログの `opt_code` / `display_label` / `sort_order` / `is_active` をコピーし、版内での文言修正や一時的な非表示に対応。
- `version_question_id` で `version_questions` のスナップショット行にぶら下がる。
* **columns**:
* `id BIGINT PK AI`
* `version_id BIGINT NOT NULL`
* `version_question_id BIGINT NOT NULL`
* `option_id BIGINT NOT NULL`
* `opt_code VARCHAR(64) NOT NULL`
* `display_label VARCHAR(500) NOT NULL`
* `llm_op JSON NULL` -- この質問をLLMはどういう観点で評価するのかの指示
* `sort_order INT NOT NULL` -- 版ごとの選択肢表示順
* `is_active BOOLEAN NOT NULL DEFAULT TRUE`
* `created_by_admin_id BIGINT NOT NULL`
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `UK (version_id, version_question_id, opt_code)`
* `FK (version_id) -> diagnostic_versions(id) ON DELETE RESTRICT`
* `FK (version_question_id) -> version_questions(id) ON DELETE RESTRICT`
* `FK (option_id) -> options(id) ON DELETE RESTRICT`
* `FK (created_by_admin_id) -> admin_users(id) ON DELETE RESTRICT`
* **indexes**:
* `IDX version_options_sort (version_id, version_question_id, sort_order)`
* `IDX version_options_active (version_id, is_active, version_question_id, sort_order)`
### 2.12 mst_ai_jobs
* **description**:
AI職種のマスタ
* **columns**:
* `id BIGINT PK AI`
* `name VARCHAR(191) NOT NULL`
* `category VARCHAR(191) NULL` -- 職種カテゴリ(例: 戦略・経営系)
* `role_summary TEXT NOT NULL` -- 要約(ハイライト)
* `main_role TEXT NULL` -- 主な役割・業務範囲
* `collaboration_style TEXT NULL` -- 関係者との関わり方
* `strength_areas TEXT NULL` -- 強みが求められる領域
* `description TEXT NOT NULL` -- 職種の特徴・詳細解説
* `avg_salary_jpy VARCHAR(64) NULL` -- 表記に合わせた年収レンジ文字列
* `target_phase TEXT NULL` -- 担当する顧客/事業フェーズ
* `core_skills TEXT NULL` -- 必要スキル
* `deliverables TEXT NULL` -- 主な成果物
* `pathway_detail TEXT NULL` -- キャリアパス
* `ai_tools TEXT NULL` -- よく使うAIツール
* `advice TEXT NULL` -- 目指す人へのアドバイス
* `is_active BOOLEAN NOT NULL DEFAULT TRUE`
* `sort_order INT NOT NULL DEFAULT 0`
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `UK mst_ai_jobs_name (name)`
* **indexes**:
* NONE
### 2.13 version_outcomes
* **description**:
バージョンごとの Outcome 集合と表示用メタ情報(LLM推奨結果の整形に利用)を固定する。
* **columns**:
* `id BIGINT PK AI`
* `version_id BIGINT NOT NULL`
* `outcome_id BIGINT NOT NULL` -- 元マスタのID
* `outcome_meta_json JSON NULL` -- マスタ列差異を吸収するためのメタ(表示用)。例: mst_ai_jobs の主要列をJSONで保持
* `sort_order INT NOT NULL DEFAULT 0`
* `is_active TINYINT(1) NOT NULL DEFAULT 1`
* `created_by_admin_id BIGINT NOT NULL`
* `created_at DATETIME NOT NULL`
* `updated_at DATETIME NOT NULL`
* **constraints**:
* `UK (version_id, outcome_id)`
* `FK (version_id) -> diagnostic_versions(id) ON DELETE RESTRICT`
* `FK (created_by_admin_id) -> admin_users(id) ON DELETE RESTRICT`
* **indexes**:
* `IDX version_outcomes_sort (version_id, sort_order, outcome_id)`
### 2.14 answer\_choices
* **description**:
1診断(session)ごとにユーザーが回答した選択肢を管理する(ユーザーとの紐付けはsessionsと結合する)
* **columns**:
* `id BIGINT PK AI`
* `session_id BIGINT NOT NULL`
* `version_option_id BIGINT NOT NULL`
* `answered_at DATETIME NOT NULL`
* **constraints**:
* `FK (session_id) -> sessions(id) ON DELETE RESTRICT`
* `FK (version_option_id) -> version_options(id) ON DELETE RESTRICT`
* `UK answer_choices_once (session_id, version_option_id)`
* **indexes**:
* `IDX answer_choices_session (session_id)`
設計の狙い(要点)
- questions/options はカタログ(下書き/編集用)
-
version_questions/version_options がスナップショット
→ 「この版で何を出すか」を固定できる -
options / version_options に
llm_opを持たせる
→ UIの表示文と LLM評価を分離できる -
diagnostic_versions.system_prompt を固定
→ 版ごとの LLM 挙動を固定 -
sessions / answer_choices で回答を保存
→ 後から同じ版で再評価も可能 - version_outcomes で結果集合を版ごとに固定
この構造にすると、
- 診断ごとのデータ構造差異に強い(観点や結果構造が違ってもテーブルを増やしにくい)
- 結果を壊さない(版固定で「昨日と今日で結果が変わる」を防止)
- LLMの推論に寄せやすい(if/重み付けの複雑化を抑制)
が実現できます。
LLM呼び出しの流れ
実装上の流れは以下の通りです。
シーケンス図にすると、こんな流れです(キャッシュヒット時は LLM を呼びません)。
※ llm_op はフォーム取得でフロントにも返しますが、LLM 実行時は改ざん防止のため サーバ側で version_option_id から解決します。
ユーザープロンプトの組み立てイメージはこんな形です:
answered_option_ids = sorted(str(opt.id) for opt in answered_options)
hash_input = f"v{version_id}:" + ",".join(answered_option_ids)
current_hash = sha256(hash_input.encode("utf-8")).hexdigest()
user_prompt = [entry.llm_op for entry in answered_options]
user_payload = json.dumps(user_prompt, ensure_ascii=False, separators=(",", ":"))
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_payload}
]
人間向け選択肢ではなく llm_op だけを渡すので、
LLMは「選択肢そのもの」ではなく「意図された評価観点」で推論できます。
このアプローチのメリットとトレードオフ
メリット
前半で挙げた「スコア型がスケールしにくい理由(データ構造 / 計算ロジック / バージョン管理 / 汎用化)」に対して、この設計はそれぞれ 明確な打ち手 を用意できます。
- データ構造:観点や結果の構造が変わっても スキーマ変更を最小化できる
- バージョン管理: 複数診断に対応してもデータ構造を変更する必要がないのでバージョン管理ロジックに対して破壊的変更が入りにくい
-
計算ロジック:加点・相互作用・補正の「if地獄」をアプリコードから追い出し、LLM 推論 + プロンプト/
llm_opに寄せられる -
汎用化:診断ごとの差分を「データ(prompt /
llm_op/ outcome)」に閉じ込め、診断追加のたびに コアを改造し続ける構造を避けられる
デメリット
-
LLM依存が強い
- model_context制約の範囲内に収まるという制限がある
- modelの複雑指示理解力に依存する
- 例えば、今回の場合はDB登録情報とLLMレスポンス値で詳細職種情報を表示する仕様にしているが、ハルシネーションで存在しない職種をLLMがレスポンスする可能性が確率的に発生する(claude 3.5 Haikuあたりだと頻発しました)
-
評価結果がブラックボックス化
- LLMは確率モデル
- "それっぽい"回答は出してくれるが、どういう根拠で診断結果を出力したのかがブラックボックス化しやすい
- LLMは確率モデル
-
プロンプト職人芸
- スコア型診断を作る際に考えるべきことは変わらないので、具体的な診断(質問/選択肢/観点/結果/重みづけ)を作る作業が職人芸になるのは変わらない
まとめ
スコア型診断の地獄は「設計の柔軟性」と「運用の安定性」が両立しない点にあります。
選択肢を UI と LLM の2層に分離することで、データ構造は柔軟に、運用は版固定で安定化できます。
LLMに依存する設計にはなりますが、
LLMが存在する以前の世界では、性質の異なる診断は汎用診断エンジンでもトレードオフが起きていた問題をLLMが存在する世界では同居可能になったという発見の共有でした!

