はじめに
小さな会社・お店向けに、「どの業務を、どの方法で自動化すると良いか」を出す無料の診断アプリを作りました。業種と業務、頻度と時間、パソコンの環境を選ぶと、次の3つが出ます。
- 年間で減らせる時間の目安
- 方法の比較(Excelの関数・GAS・Power Automate・定型文など)
- 進める順のチェックリスト(推進プラン)
日本版は40業種・86業務、英語版(米・英・加・豪など)は40業種・59業務です。フレームワークは使わず、素のJavaScript・GitHub Pages・Node.jsのテストだけで作っています。
この記事では、作るときに決めた4つの設計を紹介します。
- 判定ロジックを「画面から切り離した純粋関数」にする
- 匿名の集計は「送る項目を関数で固定」する
- 広告は「おすすめに影響しない」ことをコードで分ける
- データ全体をテストで総当たりする
※この記事とアプリは、AI(Claude)を活用して作成し、動作とテストを確認しています。
構成
dx/
index.html … CSPで外部への通信先を限定
js/data.js … 業種・業務・方法のデータ
js/logic.js … 判定ロジック(DOMを触らない)
js/app.js … 画面の組み立て
js/ads.js … 広告タグの取り込み
js/config.js … 集計の送信先
test/test.js … Node.jsで実行するテスト
1. 判定ロジックは純粋関数にする
logic.js はDOMに一切触れず、入力(選んだ業務・頻度・環境)を受け取って結果を返すだけにしました。たとえば年間時間の目安は、次のような関数です。
function round1(x) { return Math.round(x * 10) / 10; }
function annualHours(freq, min, rate) {
var n = FREQ[freq] ? FREQ[freq].n : 0;
return round1(n * min * rate / 60);
}
画面には「回数/年 × 1回の分数 × 削減率 ÷ 60」という式もそのまま表示します。目安の数字だけを出すより、どう計算したかが見えるほうが信用されると考えたからです。
ブラウザでもNodeでも読めるように、最後はこう書いています。
if (typeof module !== 'undefined') module.exports = API; else root.DX = API;
2. 匿名の集計は「送る項目」を関数で固定する
どの業種でどの業務に困っているかが分かると、次に何を作るべきかが分かります。そこで、選んだ内容だけを匿名で集計しています。
気をつけたのは、送る項目を1つの関数に閉じ込めることです。
function buildPayload(answers, diag, now) {
var env = normalizeEnv(answers && answers.env);
var d = diag || diagnose(answers);
var tasks = d.items.map(function (it) { return it.task.id + ':' + it.freq + ':' + it.min; }).sort();
var methods = d.items.map(function (it) { return it.task.id + ':' + it.rec.id; }).sort();
return {
industry: d.industry,
tasks: tasks.join(','),
env: ['sheet=' + env.sheet, 'pc=' + env.pc, 'm365=' + env.m365, 'code=' + env.code, 'budget=' + env.budget].join(';'),
methods: methods.join(','),
date: dayString(now instanceof Date ? now : new Date()),
version: APP_VERSION
};
}
業種・業務のID・環境・おすすめの方法・日付・バージョンの6項目だけで、自由記入の文章や名前・連絡先は入りません。日付は時刻を含めず日単位にしています。テストでは「この6項目以外のキーが無いこと」を確認しています。
送信はGoogle Apps Scriptのウェブアプリに fetch で投げています。
window.fetch(CFG.submitUrl.trim(), {
method: 'POST', mode: 'no-cors', credentials: 'omit', cache: 'no-store',
keepalive: true, referrerPolicy: 'no-referrer',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: DX.encodeForm(payload, CFG.fields)
}).catch(function () { /* 送れなくても画面はそのまま */ });
-
credentials: 'omit'でCookieを送らない -
referrerPolicy: 'no-referrer'でどのページから来たかを送らない - 同じ内容は同じタブで二度送らない(内容のハッシュを sessionStorage に記録)
-
?demo=1のときは送らない
CSPの connect-src も、自サイトとApps Scriptのドメインだけにしています。仮にコードに誤りがあっても、それ以外の場所には通信できません。
3. 広告は「おすすめ」と別のファイルにする
結果画面には、会計ソフトなどのアフィリエイト広告を出しています。ここで大事なのは、広告が診断結果に影響しないことです。
-
logic.jsは広告の設定ファイル(ads.json)もads.jsも読みません - 広告は、結果が出たあとに画面の別の枠へ入れるだけ
- 広告タグは、a8.net の
<a><img><br>だけを取り込み、それ以外は捨てる
var A8 = /^https:\/\/([a-z0-9-]+\.)*a8\.net\//i;
function okUrl(u) { u = String(u || '').trim(); return A8.test(u) ? u : null; }
タグ文字列を innerHTML で入れるのではなく、DOMParserで読んでから、許可した要素だけを作り直しています。リンクには rel="noopener sponsored" を付けます。
開いたときの全画面広告は1日1回だけ、×・Esc・外側のクリックで閉じられるようにしました。
4. データ全体をテストで総当たりする
業種・業務・方法のデータは手で書いているので、抜けや矛盾が入りやすい部分です。そこで、Node.jsだけで動く小さなテストを書きました(テストフレームワークは使っていません)。
function ok(cond, msg) { if (cond) pass++; else { fail++; console.log('FAIL: ' + msg); } }
確認している例です。
- 業務IDと方法IDが重複していない
- どの業務にも方法が2つ以上ある
- 「はじめの一歩」は3〜5個
- 削減率は 0〜90% の範囲
- 販売中の商品を紹介する方法は、商品データが存在する
- 環境(スプレッドシート・PC・Microsoft 365・コードを書けるか・予算)の全組み合わせで、どの業務にもおすすめが1つ以上出る
最後の「全組み合わせ」があるので、テストの件数は日本版で76,788件、英語版で191,412件になりました。件数が多くても、すべて純粋関数なので数秒で終わります。
公開は、テストが全部通ったときだけにしています。
node test/test.js | grep -q " 0 failed" && git push
(一度、テストが1件落ちたままpushしてしまったことがあり、それ以来この形にしています。)
まとめ
- 判定は純粋関数にして、画面・広告・送信から切り離す
- 集計で送る項目は1つの関数に固定し、テストで確認する
- 広告はおすすめのロジックが読まないファイルに分ける
- データは全組み合わせでテストし、通ったときだけ公開する
小さなアプリでも、この4つを決めておくと、あとから業種や業務を増やしても安心して直せました。よければ診断を試してみてください。