ClassLab Engineering の Dev チームメンバーが執筆しました。
1. 背景
コールセンターのオペレーターは、顧客との通話中にトークスクリプトを参照しながら案内を進めます。ClassLab では電気・ガス・水道・引越しなど複数サービスの案内を行っており、顧客の回答や契約状況によって案内すべき内容が大きく変わります。
従来は紙のマニュアルと Excel ベースのフローチャートで運用していましたが、以下の問題がありました。
- サービスの追加・条件変更のたびに全フローチャートを手動更新
- オペレーターが条件分岐を誤り、案内漏れや誤案内が発生
- 通話中の入力内容を後から Salesforce に転記する二重作業
- Salesforce プラットフォーム上で完結させる(外部サーバー不可)
- 既存の入居管理・サービス案内オブジェクトとの統合必須
- オペレーターが通話中に使うため、レスポンスは体感 1 秒以内
これらを解消するため、Salesforce Lightning Web Components(LWC)で動的トークスクリプトシステムを構築しました。
2. 課題
| 課題 | 影響 | 定量データ |
|---|---|---|
| 条件分岐の複雑化 | サービス増加で分岐パターンが指数的に増加 | 電気・ガス・水道・引越し・不要品回収など複数サービス |
| 案内品質のばらつき | 経験年数で案内精度に差 | 新人オペレーターは月平均数件の案内漏れ |
| 二重入力の非効率 | 通話後に Salesforce へ転記 | 1件あたりの後処理時間が長い |
| マニュアル更新コスト | Excel フロー修正に数日 | サービス条件変更のたびに全員分配布 |
制約条件:
3. 設計
アーキテクチャ概要
graph TB
subgraph "LWC フロントエンド"
IM[interactiveManual<br/>親コンポーネント]
SC[interactiveManualStartCall<br/>架電開始]
TK[interactiveManualTalk<br/>トーク表示・回答入力]
IS[interactiveManualItemSelect<br/>サービス選択]
OUTR[interactiveManualOutReturn<br/>アウト返し検索]
TD[interactiveManualTempDataList<br/>一時保存履歴]
end
subgraph "Apex バックエンド"
CTRL[InteractiveManualController]
SVC[InteractiveManualService]
HDL[InteractiveManualHandler]
end
subgraph "データモデル"
TG[TalkGroup__c]
T[Talk__c]
CB[ConditionBranch__c]
TA[TalkAnswer__c]
MI[入居管理]
SG[サービス案内]
end
IM --> SC
IM --> TK
IM --> IS
IM --> OUTR
IM --> TD
IM -->|@AuraEnabled| CTRL
CTRL --> SVC
CTRL --> HDL
SVC --> TG
SVC --> T
SVC --> CB
SVC --> TA
SVC --> MI
SVC --> SG
データモデル設計
トークスクリプトの動的生成は、4 つのカスタムオブジェクトの連鎖で実現しています。
graph LR
TG["TalkGroup__c<br/>トークグループ"] -->|StartTalk__c| T["Talk__c<br/>トーク"]
TG -->|NextTalkGroup__c| TG
T -->|NextConditionBranches__r| CB["ConditionBranch__c<br/>条件分岐"]
T -->|Answers__r| TA["TalkAnswer__c<br/>回答選択肢"]
CB -->|NextTalk__c| T
TA -->|NextTalk__c| T
CB -->|Conditions__r| C["Condition__c<br/>条件式"]
TA -->|AutoSetColumns__r| ASC["AutoSetColumn__c<br/>自動設定値"]
| オブジェクト | 役割 | 主要フィールド |
|---|---|---|
| TalkGroup__c | トークの論理グループ | StartTalk__c, NextTalkGroup__c, ConditionsLogic__c |
| Talk__c | 個別のトーク(質問・説明) | Type__c, Body__c, Precaution__c, Service__c |
| ConditionBranch__c | 条件に基づく次トークの決定 | Priority__c, ConditionsLogic__c, NextTalk__c |
| TalkAnswer__c | オペレーターの回答選択肢 | Answer__c, Type__c, SetColumnApiName__c |
条件分岐エンジンの設計判断
技術選定で最も議論したのが、条件分岐の評価方法です。
| 方式 | メリット | デメリット | 採否 |
|---|---|---|---|
| Salesforce Flow | ノーコードで管理可能 | 複雑な条件のネストに限界、LWC との連携がリアルタイムでない | 不採用 |
| Apex ハードコード | パフォーマンス最高 | 条件変更のたびにデプロイ必要 | 不採用 |
| **カスタムオブジェクト+優先度ベース評価** | 管理画面から条件変更可能、デプロイ不要 | 初期設計コスト | **採用** |
採用した方式では、ConditionBranch__c に優先度(Priority__c)を持たせ、降順で評価します。最初に全条件を満たした分岐先の NextTalk__c に遷移する仕組みです。
評価順序:
1. ConditionBranch(Priority DESC)をループ
2. 各分岐の Conditions__r を ConditionsLogic__c(AND/OR)で評価
3. 最初に true になった分岐の NextTalk__c へ遷移
4. どの分岐にも該当しない --> デフォルト分岐(DefaultFlag__c)
この設計により、サービスの条件変更は Salesforce の管理画面からレコードを編集するだけで反映されます。
通話中のデータ整合性
運用上の重要なポイントとして、通話セッション中にマスタデータが変更された場合の挙動があります。本システムでは、架電開始時に TalkGroup・Talk・ConditionBranch・TalkAnswer の全マスタデータをフロントエンドにキャッシュします。そのため、管理者がレコードを編集しても進行中の通話には影響せず、次回の架電開始から新しいフローが適用されます。この設計により、オペレーターが通話途中でスクリプトが変わる事故を防止しています。
具体的な条件分岐の例
たとえば電気サービスの案内では、以下のような分岐が動的に構築されます。
顧客の現在の電力会社を確認
├─ Aエリア(関東) かつ 既存契約あり --> サービス変更フロー
├─ Aエリア(関東) かつ 既存契約なし --> 新規契約フロー
├─ Bエリア(関西) --> 関西向け専用フロー
└─ 上記以外 --> 汎用案内フロー(デフォルト分岐)
この分岐は ConditionBranch__c のレコードとして管理されており、たとえば「Dエリア(中部)向け専用フロー」を追加したい場合は、管理画面から ConditionBranch レコードを 1 件追加し、対応する Talk レコードを紐づけるだけで完了します。
4. 実装
LWC コンポーネント構成
親コンポーネント interactiveManual が全体を統括し、3 カラムのモーダル UI を構成します。
┌──────────────────────────────────────────────────────┐
│ 動的マニュアル │
├──────────┬────────────────────┬───────────┤
│ 入居情報 │ トークスクリプト │ メモ │
│ (25%) │ (50%) │ (25%) │
│ │ │ │
│ 一時保存 │ [トーク表示] │ アウト返し │
│ データ │ [回答入力] │ 検索 │
│ │ [サービス選択] │ │
│ 履歴 │ [架電完了] │ │
└──────────┴────────────────────┴───────────┘
条件分岐の評価(フロントエンド側)
親コンポーネントの JavaScript(約 2,000 行)から、条件分岐のコア部分を抜粋します。
// ※記事用に簡略化した擬似コード。
// 実装では親コンポーネント内で複合条件(AND/OR)を含む
// より複雑なロジックで処理しています。
// 条件分岐の各 Condition を評価
// ColumnApiName(例: "ElectricCompany__c")の現在値を取得し、
// Operator("=", "!=", "contains" 等)と Value を比較
evaluateCondition(condition, recordData) {
const fieldValue = recordData[condition.ColumnApiName__c];
switch (condition.Operator__c) {
case '=':
return String(fieldValue) === String(condition.Value__c);
case '!=':
return String(fieldValue) !== String(condition.Value__c);
case 'contains':
return String(fieldValue).includes(condition.Value__c);
case 'isNull':
return !fieldValue;
case 'isNotNull':
return !!fieldValue;
default:
return false;
}
}
回答による自動項目設定(Apex 側)
オペレーターが回答を選択すると、TalkAnswer__c に紐づく AutoSetColumn__c の定義に従って、入居管理オブジェクトやサービス案内オブジェクトのフィールドが自動設定されます。
// InteractiveManualService.updateData() の概要
// inputValues: LWC から送られた回答リスト
// 各回答の serviceId/column/value に基づき、
// 対象オブジェクトのフィールドを動的に設定
for (InteractiveManualInputValueInfo inputValue : inputValues) {
String objApiName = determineObjectApiName(
inputValue.column
);
SObject targetObj = updateSObjMap.get(objApiName);
targetObj.put(inputValue.column, inputValue.value);
}
// 入居管理オブジェクトと関連オブジェクトを一括 update
// サービス案内は upsert(サービスごとに1レコード)
この仕組みにより、オペレーターは通話中に回答を選択するだけで、裏側で Salesforce レコードが自動更新されます。通話後の転記作業が不要になりました。
Mermaid.js によるフローチャート可視化
管理者向けに、トークスクリプトの分岐構造を Mermaid.js で自動描画する機能も実装しました。baseFlowChart コンポーネントが Talk__c のツリー構造を再帰的に走査し、Mermaid 記法のテキストを生成します。
// baseFlowChart.js - トーク構造を再帰的にMermaid記法へ変換
createMarmaidText(id) {
let currentTalk = this.data[id];
if (this.processedList.includes(id)) return;
this.processedList.push(id);
if (currentTalk?.Type__c == "条件分岐") {
for (let ncb of currentTalk.NextConditionBranches__r) {
this.chartText += "\n" + ncb.mermaidText__c;
this.createMarmaidText(ncb.NextTalk__c);
}
} else if (currentTalk?.Type__c == "アンサー") {
for (let ans of currentTalk.Answers__r) {
this.chartText += "\n" + ans.mermaidText__c;
if (ans.NextTalk__c) {
this.createMarmaidText(ans.NextTalk__c);
}
}
}
}
各レコードの mermaidText__c 数式フィールドに Mermaid 記法が格納されており、フローチャートのノード表現を Salesforce 側で管理できます。
テスト
Apex 側は InteractiveManualTest と InteractiveManualStartControllerTest の 2 クラスでコントローラとサービスの単体テストを実施しています。LWC 側は手動の結合テスト(架電開始–>回答入力–>一時保存–>架電完了の一連フロー)でカバーしています。
5. 結果
| 指標 | Before | After | 改善率 |
|---|---|---|---|
| 案内フロー更新 | Excel 修正+全員配布(2-3営業日) | Salesforce レコード編集(即時反映) | リードタイム 95%短縮 |
| 通話後の転記作業 | 手動で Salesforce に入力(1件5-10分) | 通話中に自動設定(0分) | 転記作業ゼロ |
| 新人の案内品質 | 経験依存(分岐を暗記、案内漏れ月数件) | システムが次のトークを提示 | 分岐ミス起因の案内漏れほぼゼロ(QA抜き打ちモニタリングで確認) |
| コンポーネント規模 | — | LWC 10 + Apex 7 クラス | — |
| 対応サービス数 | — | 電気・ガス・水道・引越し・不要品回収等 | 追加はレコード操作のみ |
| Apex クエリ応答 | — | 架電開始時の全マスタ取得 200-400ms | 制約「体感1秒」を充足 |
特に大きな成果は、サービス追加時のコスト削減です。新しいサービスの案内フローを追加する場合、従来はフローチャートの再設計と配布に数日かかっていましたが、現在は TalkGroup__c / Talk__c / TalkAnswer__c のレコードを作成するだけで即時反映されます。デプロイは不要です。
6. 展望
現在取り組んでいる改善は以下の 3 点です。
1. トークスクリプトのバージョン管理: 現在はレコード編集が即時反映されるため、誤編集のリスクがあります。承認プロセスとバージョニング機能の追加を検討中です。
2. 分岐パターンの分析: どの条件分岐でオペレーターが詰まりやすいかをログから分析し、トークスクリプトの改善に活かす仕組みを構築予定です。
3. AI による回答サジェスト: 通話内容のリアルタイム文字起こしと連携し、次の回答候補を自動提示する機能を検討しています。これは通話録音の全件文字起こしでQAレビュー工数を80%削減した話の音声認識基盤を活用する計画です。
—
採用情報
ClassLab では一緒に技術的挑戦に取り組むエンジニアを募集しています。
- Findy でカジュアル面談する
- 採用情報を見る
—
ClassLab Engineering チームメンバーが執筆しました。
>
ClassLab.では、一緒にプロダクトを作るエンジニアを募集しています。
カジュアル面談も大歓迎です!
>