1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

料金比較の診断ツールで「うそを書かない」ための設計:出典・確認日・広告と順位の分離をテストで守る

1
Last updated at Posted at 2026-10-18

スマホの乗り換え診断のような「料金を比べるツール」を作るとき、いちばん怖いのはロジックのバグより 古い料金・確かめていない数字・広告に引っぱられた順位 です。利用者はその数字を見て契約を決めるので、ここがずれると信用をなくします。

静的サイト(GitHub Pages)で作ったスマホ乗り換え診断で、それを仕組みとテストで防いだ方法をまとめます。

方針

  1. 料金は、各社の公式ページで確かめた 税込 の数字だけを入れる。確かめられない値は入れない(推測で埋めない)
  2. どの数字にも 出典URLと確認日 を持たせ、画面に確認日を出す
  3. 広告は順位の計算に一切渡さない。それをテストで確かめる
  4. 「最安」「キャンペーン中」のような言い切り・期間限定の言葉を画面に出さない

料金データは出典つきのJSONにする

ロジックと料金を分け、料金は data/plans.json に置きます。1プランはこんな形です。

{
  "id": "linemo-best",
  "kind": "online",
  "brand": "LINEMO",
  "name": "LINEMOベストプラン",
  "tiers": [
    { "gb": 3, "price": 990 },
    { "gb": 10, "price": 2090 }
  ],
  "call": {
    "rate": "22円/30秒",
    "short": { "name": "通話準定額(5分)", "price": 550 },
    "long": { "name": "通話定額(かけ放題)", "price": 1650 }
  },
  "discounts": [],
  "notes": ["通話オプションは契約から7か月目までキャンペーン価格がありますが、ここでは通常の料金で計算しています。"],
  "source_url": "https://www.linemo.jp/plan/",
  "checked": "2026-10-19"
}

ポイントは次の3つです。

  • checked(確認日)をプランごとに持つ。ファイル先頭にも全体の確認日を置き、画面に「料金は○年○月○日に各社公式サイトで確認」と出す
  • 確かめられなかった項目は キーごと消す。null は「確認できず」として画面で「公式で確認してください」と出す
  • キャンペーン価格は入れず、notes に「通常の料金で計算している」と書く

割引は「回答からわかるものだけ」引く

「家族割を全部使うと○円」のような表示は、条件を満たさない人には誤解を生みます。そこで割引を2つに分けました。

  • 回答から判断できる割引(家族の人数→家族割、家の回線→光セット割)だけを月額から引く
  • カード払い割など、回答からわからない割引は「ほかの割引(条件を満たせば)」として表示するだけで引かない

さらに、同時に使えない割引(家族割と光セット割のどちらか一方、など)は、同じ exclusive を持たせて大きい方だけを残します。

discounts.forEach(function (d) {
  var item = { d: d, amount: amountFor(d, answers) };
  if (d.exclusive) { // 同じグループは大きい方1つだけ
    var prev = ex[d.exclusive];
    if (prev && prev.amount >= item.amount) { other.push(d); return; }
    if (prev) { likely.splice(likely.indexOf(prev), 1); other.push(prev.d); }
    ex[d.exclusive] = item;
  }
  likely.push(item);
});

広告が順位に影響しないことをテストで確かめる

診断結果の下に広告枠を置いています(アフィリエイトで運営費をまかなうため)。広告は ads.json に分け、画面側だけが読みます。

ここで大事なのは「広告を出している会社が上に来ない」ことです。口で言うだけでなく、Nodeの単体テストで2つの方向から確かめています。

// 1) 判定ロジックのソースに、広告を読むコードがないこと
const src = fs.readFileSync(path.join(ROOT, 'js', 'logic.js'), 'utf8');
ok(!/ads|ADS|sponsor|affiliate/i.test(src), 'logic.js has no ad inputs');

// 2) データに広告の情報を混ぜても、順位が変わらないこと
const R1 = JSON.stringify(SC.recommend(D, { data: 'm' }).plans.map(r => r.plan.id));
const D2 = JSON.parse(JSON.stringify(D));
D2.ads = { slots: [{ tag: 'x', boost: 'linemo-best' }] };
ok(JSON.stringify(SC.recommend(D2, { data: 'm' }).plans.map(r => r.plan.id)) === R1,
   'extra ad fields do not change order');

1つ目は雑に見えますが、「将来だれかが判定ロジックに広告の重みを足す」変更を確実に止められます。

言い切りの言葉もテストで止める

料金は毎月のように変わるので、「最安」と書くと、いつかうそになります。全プランの表示テキストに対して、言い切りや期間限定の言葉が入っていないかを確かめています。

ok(!/最安|ポイント還元|キャンペーン中/.test(txt), p.id + ' no 最安/campaign wording');

あわせて、全プランの checked が最新の確認日になっているかもテストします。料金を直したのに日付を直し忘れる、を防ぐためです。

ok(D.checked === '2026-10-19', 'data checked date');
D.plans.forEach(p => ok(p.checked === '2026-10-19', p.id + ' checked'));

予告されている料金改定はREADMEに書いておく

すでに発表されている改定(例:ドコモの2026年12月1日の料金改定)は、READMEの「次回の確認で必ず見ること」に出典URLと一緒に書いておきます。その日が来たら、データを差し替えてテストの期待値と確認日を直す、という手順です。

回答は外に送らない

診断の回答は端末の中だけで判定し、サーバーには送りません。CSPで connect-src 'self' にして、外部への通信をブラウザに止めてもらっています(広告画像だけ img-src で広告配信元を許可)。回答を結果URLに入れるときも # のあと(ハッシュ)に入れるので、サーバーに送られません。

<meta http-equiv="Content-Security-Policy"
  content="default-src 'none'; script-src 'self'; style-src 'self';
           img-src 'self' data: https://*.a8.net; connect-src 'self'; form-action 'none'">

まとめ

  • 料金は出典URLと確認日つきのJSONに分け、確かめられない値は入れない
  • 割引は回答からわかるものだけ引き、同時に使えないものは大きい方だけ
  • 「広告が順位に影響しない」「最安と書かない」「確認日が最新」をテストで守る

作ったツールは無料で公開しています(AIを活用して作り、料金は公式ページで確かめています)。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?