この記事では、個人開発の家計・ライフプランWebアプリで「投資助言業(金商法)」に抵触しないようにUI文言・実装パターンを設計した記録を共有します。
前提:私は弁護士ではありません
最初に明確にしておきます。私は弁護士ではなく、この記事は法律的助言ではありません。実装上の参考パターンを共有するもので、最終的な法的判断は必ず弁護士・専門家にご相談ください。
その上で、個人開発で家計・投資・ライフプラン系のWebアプリを作るとき、多くの開発者が踏みやすい落とし穴があります。それを実装側でどう回避するかをまとめます。
なぜUI文言が問題になるのか
金融商品取引法では、**「投資助言業」**として登録していない事業者が、特定の金融商品やサービスを推奨する行為は規制対象です。
個人開発のWebアプリでよくある「やらかしパターン」:
- 「あなたへのおすすめは○○証券」
- 「このプランで決めましょう」
- 「○○ファンドを買えば資産が増えます」
- 「節約型がベストです」
**「データを見せる」と「これが正解です」**の境界を踏み越えると、規制対象になり得ます。
NG / OK 言い換えパターン
実際にみらいコンパスというライフプランシミュレーターを作る中で整理した言い換え一覧です。
眉ラベル(ヒーローカードのトップ)
| ❌ NG | ✅ OK |
|---|---|
| 良いニュース | シミュレーション結果 |
| あなたへのおすすめ | 試算結果 |
| ベストプラン | 入力値変更時の試算例 |
| 注意:このままだと危険 | 試算上の検討点 |
本文
| ❌ NG | ✅ OK |
|---|---|
| 「〜すべきです」 | 「〜という選択肢があります」 |
| 「このプランで決めましょう」 | 「このプランをシミュレーションする」 |
| 「○○がおすすめ」 | 「○○という選択肢」 |
| 「素晴らしい状態です」 | 「まだ余地のある試算結果です」 |
CTAボタン
| ❌ NG | ✅ OK |
|---|---|
| 「○○証券で口座を開く」 | 「NISA制度について詳しく見る」 |
| 「このファンドを購入する」 | 「商品の特徴を確認する」 |
| 「○○を始めよう」 | 「制度の詳細を見る」 |
ポイントは 「主語が特定企業・特定商品」 だとアウトということです。
実装パターン1:状態判定 → 中立メッセージ生成
メインプランのシミュレーション結果を「事実」として 4 状態に分類するだけ。
function renderInsightHero() {
const mainSim = calcRetirementSim();
const sufficiency = mainSim.assetsAtRetire / mainSim.requiredAssets;
const depleted = mainSim.postData.find(d => d.depleted);
let state, eyebrow, title, body;
if (depleted) {
state = 'warning';
eyebrow = '⚠️ 試算上の検討点';
title = `現在の入力では ${depleted.age}歳で資産がゼロになる試算です`;
body = '想定余命より早く資産が枯渇する試算結果です。以下は入力値を一部変更した場合の試算例です。';
} else if (sufficiency >= 1.5) {
state = 'success';
eyebrow = '🎉 シミュレーション結果';
title = `${mainSim.lifeExpectancy}歳まで持続する試算(必要資産比 ${Math.round(sufficiency*100)}%)`;
body = '想定余命までの生活費を確保した上で、まだ余地のある試算結果です。';
} else {
// ...
}
// 「良い悪い」を直接書かず、「結果」として淡々と提示する
return renderCard({ state, eyebrow, title, body });
}
ポイント
- 眉ラベルは「シミュレーション結果」で統一
- 本文は「○○する試算です」と事実形式
- 「良い」「悪い」「危険」など主観評価を避ける
実装パターン2:「What-if 試算」の数値提示
「○○すれば改善できる」を選択肢の試算結果として並列表示。
// 入力値を一部変更してシミュレーションを連続実行
function generateImprovementOptions() {
const options = [];
// 月の生活費を1〜8万円減らした場合
for (const reduction of [1, 2, 3, 5, 8]) {
const expenseMod = -reduction / baseExpense;
const sim = calcRetirementSimWithOpts({ expenseMod });
const dep = sim.find(d => d.depleted);
if (!dep) {
// 持続したケースを「試算結果」として記録
options.push({
emoji: '🍙',
label: `月の生活費を${reduction}万円減らす`,
detail: `月${baseExpense}万→${baseExpense - reduction}万に圧縮すると、${lifeExpectancy}歳まで持続見込み`,
});
break; // 最少コストで達成できる選択肢を採用
}
}
// リタイアを延長した場合
for (const extend of [1, 2, 3, 5]) {
// targetAge を一時書換でシミュレーション
const orig = state.retirement.targetAge;
state.retirement.targetAge = parseInt(orig) + extend;
const sim = calcRetirementSimWithOpts({});
state.retirement.targetAge = orig; // 必ず復元
if (!sim.find(d => d.depleted)) {
options.push({
emoji: '⏰',
label: `リタイアを${origAge + extend}歳に延長`,
detail: `あと${extend}年働くと、${lifeExpectancy}歳まで持続見込み`,
});
break;
}
}
return options;
}
UIでは「あなたはこうすべき」ではなく、**「以下のような試算結果があります」**と並べて提示します。
<div class="hero-card">
<h3>試算上の検討点</h3>
<p>以下は入力値を一部変更した場合の試算例です。</p>
<div class="option">🍙 月の生活費を3万円減らす → 90歳まで持続</div>
<div class="option">⏰ リタイアを65歳→67歳に延長 → 88歳まで持続</div>
<button>テーマ別シミュレーションを見る</button>
</div>
実装パターン3:免責文を必ず入れる
各ヒーローカード・各シミュレーション結果の下部に 明示的な免責文 を入れます。
<div style="font-size:10px;color:#888;margin-top:10px;border-top:1px solid #eee;padding-top:8px">
※ 上記は入力値に基づくシミュレーション結果であり、特定の投資商品・
金融サービスの選択や採否を推奨するものではありません。具体的な投資
判断・運用方針については、ファイナンシャルプランナー等の専門家にご
相談ください。
</div>
これは「データを見せること」と「推奨」の間に明確な線を引く役目を果たします。
実装パターン4:保存済みプラン同士の比較
複数のプランを保存した時、差分の事実だけ並べる。
function renderSavedScenariosInsight(planA, planB) {
const diffs = [];
// 各KPIの差分を計算(ただし「どっちが良いか」は言わない)
if (planA.targetAge !== planB.targetAge) {
diffs.push({
label: 'リタイア年齢',
a: `${planA.targetAge}歳`,
b: `${planB.targetAge}歳`,
delta: `${planB.targetAge - planA.targetAge > 0 ? '+' : ''}${planB.targetAge - planA.targetAge}年`,
});
}
// ...
return `
<table>
<tr><th>指標</th><th>${planA.name}</th><th>${planB.name}</th><th>差分(B-A)</th></tr>
${diffs.map(d => `<tr><td>${d.label}</td><td>${d.a}</td><td>${d.b}</td><td>${d.delta}</td></tr>`).join('')}
</table>
<p style="font-size:10px">
※ 上記は2つの入力値セットを並べたシミュレーション結果の差分を示すものです。
プランの優劣・採否を推奨するものではありません。
</p>
`;
}
「プランA は プランB より◯歳長く持続」のような事実は OK。
「だからプランB を選ぶべき」のような推奨は NG。
チェックリスト:あなたの実装は大丈夫?
リリース前にこのチェックリストを通すと、踏み外しを防げます。
- 眉ラベルが「シミュレーション結果」「試算結果」で統一されているか
- 本文に「〜すべき」「〜がおすすめ」が出ていないか
- CTAボタンが「特定企業×行動喚起」になっていないか(「○○証券で口座開設」NG)
- 各カード下部に免責文が入っているか
- 「良い」「悪い」「危険」などの主観評価が削除されているか
- 比較表で「どちらが優れている」と書いていないか(差分の数字だけ)
- 自動生成のヒントで「採用すべき」と促していないか
みらいコンパスでの試算は中立な情報提供
みらいコンパス は完全ローカル(データは端末から外に出ない)で動く家計・ライフプランシミュレーターです。
入力した数値に基づいて将来の資産推移をグラフで表示しますが、上記の文言設計を全機能に通底させています。気づきヒーロー、What-if 試算、保存済みプラン比較、すべて「事実の提示」に徹しています。
技術スタックは Vanilla JS + Chart.js + localStorage + Service Worker のシンプル構成です。
まとめ
- 金融系の個人開発Webアプリでは、「データ提示」と「推奨」の境界に注意
- 中立な眉ラベル(「シミュレーション結果」)と免責文を全箇所に
- 「○○すべき」「○○がおすすめ」を避け、「○○という選択肢」「試算結果」で統一
- 比較画面では数字の事実差分だけを並べ、優劣判断は書かない
- 最終的な法的判断は必ず弁護士・専門家に相談
個人開発で家計・投資系のアプリを作る方の参考になれば幸いです。