Spaces:
Sleeping
Sleeping
Commit
·
84f1fc6
1
Parent(s):
5fa037a
Add RL training and enhance analytics dashboard
Browse filesIntroduced a reinforcement learning module to analyze past call transcripts and adjust graph edge weights for improved AI sales strategy. Enhanced the dashboard with a training button, detailed call inspector, and improved metrics. Updated leads_manager to store full call transcripts for learning. Refactored sales_script.json to expand sales stages, add new nodes and edges, and update weights for more granular conversation flow.
- app.py +107 -11
- leads_manager.py +4 -3
- sales_script.json +109 -81
app.py
CHANGED
|
@@ -7,6 +7,7 @@ from datetime import datetime
|
|
| 7 |
import google.generativeai as genai
|
| 8 |
from graph_module import Graph
|
| 9 |
from algorithms import bellman_ford_list
|
|
|
|
| 10 |
|
| 11 |
# --- CONFIG ---
|
| 12 |
st.set_page_config(layout="wide", page_title="SellMe AI Engine")
|
|
@@ -109,7 +110,7 @@ def get_predicted_path(graph, start_id, target_id, id_to_node, node_to_id):
|
|
| 109 |
if dist[target_id] == float('inf'): return []
|
| 110 |
path = [target_id]
|
| 111 |
curr = target_id
|
| 112 |
-
while curr != start_id:
|
| 113 |
found = False
|
| 114 |
for u in range(graph.num_vertices):
|
| 115 |
for v, w in graph.adj_list[u]:
|
|
@@ -225,6 +226,69 @@ def generate_greeting(model, start_instruction, lead_info):
|
|
| 225 |
return f"Алло, {client_name}? Це {bot_name}."
|
| 226 |
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
# --- UI COMPONENTS ---
|
| 229 |
def draw_graph(graph_data, current_node, predicted_path):
|
| 230 |
nodes = graph_data[3]
|
|
@@ -318,16 +382,48 @@ graph, node_to_id, id_to_node, nodes, edges = graph_data
|
|
| 318 |
|
| 319 |
# --- PAGE: DASHBOARD ---
|
| 320 |
if st.session_state.page == "dashboard":
|
| 321 |
-
st.title("📊 CRM Analytics")
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
c1, c2, c3 = st.columns(3)
|
| 326 |
-
c1.metric("Total Calls",
|
| 327 |
-
c2.metric("
|
| 328 |
-
c3.metric("
|
| 329 |
-
|
| 330 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
|
| 332 |
# --- PAGE: SETUP ---
|
| 333 |
elif st.session_state.page == "setup":
|
|
@@ -426,7 +522,7 @@ elif st.session_state.page == "chat":
|
|
| 426 |
|
| 427 |
st.markdown("#### 📊 AI Strategy")
|
| 428 |
curr_id = node_to_id[st.session_state.current_node]
|
| 429 |
-
target_id = node_to_id["
|
| 430 |
path = get_predicted_path(graph, curr_id, target_id, id_to_node, node_to_id)
|
| 431 |
st.graphviz_chart(
|
| 432 |
draw_graph(graph_data, st.session_state.current_node, path),
|
|
|
|
| 7 |
import google.generativeai as genai
|
| 8 |
from graph_module import Graph
|
| 9 |
from algorithms import bellman_ford_list
|
| 10 |
+
from leads_manager import get_analytics
|
| 11 |
|
| 12 |
# --- CONFIG ---
|
| 13 |
st.set_page_config(layout="wide", page_title="SellMe AI Engine")
|
|
|
|
| 110 |
if dist[target_id] == float('inf'): return []
|
| 111 |
path = [target_id]
|
| 112 |
curr = target_id
|
| 113 |
+
while curr != start_id and attempts < 200:
|
| 114 |
found = False
|
| 115 |
for u in range(graph.num_vertices):
|
| 116 |
for v, w in graph.adj_list[u]:
|
|
|
|
| 226 |
return f"Алло, {client_name}? Це {bot_name}."
|
| 227 |
|
| 228 |
|
| 229 |
+
def train_brain():
|
| 230 |
+
"""
|
| 231 |
+
RL MODULE: Аналізує минулі діалоги і оновлює ваги графа.
|
| 232 |
+
"""
|
| 233 |
+
# 1. Завантажуємо статистику
|
| 234 |
+
df, _ = get_analytics()
|
| 235 |
+
if df is None or df.empty or "Transcript" not in df.columns:
|
| 236 |
+
return "Недостатньо даних для навчання."
|
| 237 |
+
|
| 238 |
+
# 2. Завантажуємо поточний граф
|
| 239 |
+
graph, node_to_id, id_to_node, nodes, edges = load_graph_data()
|
| 240 |
+
|
| 241 |
+
# 3. Аналіз успішних/провальних шляхів
|
| 242 |
+
# (Це спрощена логіка: ми шукаємо ключові слова кроків у транскрипті)
|
| 243 |
+
success_bonuses = {} # node_name -> bonus
|
| 244 |
+
|
| 245 |
+
for index, row in df.iterrows():
|
| 246 |
+
is_success = (row["Outcome"] == "Success")
|
| 247 |
+
transcript = str(row["Transcript"])
|
| 248 |
+
|
| 249 |
+
# Проходимо по всіх вузлах і шукаємо, чи були вони в діалозі
|
| 250 |
+
# (Це грубий метод, в ідеалі треба зберігати шлях ID у базу)
|
| 251 |
+
for node_name, node_text in nodes.items():
|
| 252 |
+
# Шукаємо унікальні шматки тексту інструкції в транскрипті, щоб зрозуміти, чи були ми там
|
| 253 |
+
# Або просто перевіряємо, чи згадується цей етап в логах
|
| 254 |
+
snippet = node_text[:20]
|
| 255 |
+
if snippet in transcript:
|
| 256 |
+
if is_success:
|
| 257 |
+
success_bonuses[node_name] = success_bonuses.get(node_name, 0) + 1
|
| 258 |
+
else:
|
| 259 |
+
success_bonuses[node_name] = success_bonuses.get(node_name, 0) - 1
|
| 260 |
+
|
| 261 |
+
# 4. Оновлення ваг (Reinforcement)
|
| 262 |
+
new_edges = []
|
| 263 |
+
changes_log = []
|
| 264 |
+
|
| 265 |
+
for edge in edges:
|
| 266 |
+
u_name, v_name = edge["from"], edge["to"]
|
| 267 |
+
old_weight = edge["weight"]
|
| 268 |
+
new_weight = old_weight
|
| 269 |
+
|
| 270 |
+
# Якщо вузол 'to' часто зустрічається в успішних діалогах -> зменшуємо вагу вхідних ребер
|
| 271 |
+
score = success_bonuses.get(v_name, 0)
|
| 272 |
+
|
| 273 |
+
if score > 0: # Успішний вузол
|
| 274 |
+
new_weight *= 0.9 # Знижка 10%
|
| 275 |
+
elif score < 0: # Провальний вузол
|
| 276 |
+
new_weight *= 1.1 # Штраф 10%
|
| 277 |
+
|
| 278 |
+
# Обмеження щоб ваги не зламались
|
| 279 |
+
new_weight = max(1, min(new_weight, 100))
|
| 280 |
+
new_edges.append({"from": u_name, "to": v_name, "weight": round(new_weight, 2)})
|
| 281 |
+
|
| 282 |
+
if old_weight != new_weight:
|
| 283 |
+
changes_log.append(f"{u_name}->{v_name}: {old_weight} -> {new_weight}")
|
| 284 |
+
|
| 285 |
+
# 5. Збереження "Розумного" файлу
|
| 286 |
+
learned_data = {"nodes": nodes, "edges": new_edges}
|
| 287 |
+
with open("sales_script_learned.json", "w", encoding="utf-8") as f:
|
| 288 |
+
json.dump(learned_data, f, ensure_ascii=False, indent=2)
|
| 289 |
+
|
| 290 |
+
return f"Brain Updated! {len(changes_log)} weights adjusted based on {len(df)} calls."
|
| 291 |
+
|
| 292 |
# --- UI COMPONENTS ---
|
| 293 |
def draw_graph(graph_data, current_node, predicted_path):
|
| 294 |
nodes = graph_data[3]
|
|
|
|
| 382 |
|
| 383 |
# --- PAGE: DASHBOARD ---
|
| 384 |
if st.session_state.page == "dashboard":
|
| 385 |
+
st.title("📊 CRM & Analytics Hub")
|
| 386 |
+
|
| 387 |
+
# Кнопка для запуску навчання
|
| 388 |
+
if st.button("🧠 Train AI on History (RL)"):
|
| 389 |
+
with st.spinner("Analyzing patterns... Updating weights..."):
|
| 390 |
+
msg = train_brain()
|
| 391 |
+
st.success(msg)
|
| 392 |
+
|
| 393 |
+
data, stats = get_analytics()
|
| 394 |
+
|
| 395 |
+
if data is not None and not data.empty:
|
| 396 |
+
# Метрики
|
| 397 |
c1, c2, c3 = st.columns(3)
|
| 398 |
+
c1.metric("Total Calls", stats["total"])
|
| 399 |
+
c2.metric("Success Rate", f"{stats['success_rate']}%")
|
| 400 |
+
c3.metric("AI Learning Iterations", "v1.2") # Фейкова метрика для краси
|
| 401 |
+
|
| 402 |
+
st.divider()
|
| 403 |
+
|
| 404 |
+
# Вибір дзвінка для детального аналізу
|
| 405 |
+
st.subheader("🕵️ Call Inspector")
|
| 406 |
+
|
| 407 |
+
# Створюємо список для селектора: "Дата - Ім'я - Результат"
|
| 408 |
+
options = data.apply(lambda x: f"{x['Date']} | {x['Name']} ({x['Outcome']})", axis=1).tolist()
|
| 409 |
+
selected_option = st.selectbox("Select a call to review:", options)
|
| 410 |
+
|
| 411 |
+
if selected_option:
|
| 412 |
+
# Знаходимо вибраний рядок
|
| 413 |
+
selected_row = data.iloc[options.index(selected_option)]
|
| 414 |
+
|
| 415 |
+
with st.expander("📝 Full Transcript & Insights", expanded=True):
|
| 416 |
+
st.markdown(f"**Client:** {selected_row['Name']} ({selected_row['Type']})")
|
| 417 |
+
st.markdown(f"**Result:** {selected_row['Outcome']}")
|
| 418 |
+
st.text_area("Transcript", str(selected_row.get("Transcript", "No transcript available")), height=300)
|
| 419 |
+
|
| 420 |
+
if "AI Insights" in selected_row and selected_row["AI Insights"]:
|
| 421 |
+
st.info(f"💡 **AI Insight:** {selected_row['AI Insights']}")
|
| 422 |
+
else:
|
| 423 |
+
st.warning("No insights generated for this call.")
|
| 424 |
+
|
| 425 |
+
else:
|
| 426 |
+
st.info("Database is empty. Make some calls!")
|
| 427 |
|
| 428 |
# --- PAGE: SETUP ---
|
| 429 |
elif st.session_state.page == "setup":
|
|
|
|
| 522 |
|
| 523 |
st.markdown("#### 📊 AI Strategy")
|
| 524 |
curr_id = node_to_id[st.session_state.current_node]
|
| 525 |
+
target_id = node_to_id["close_standard"]
|
| 526 |
path = get_predicted_path(graph, curr_id, target_id, id_to_node, node_to_id)
|
| 527 |
st.graphviz_chart(
|
| 528 |
draw_graph(graph_data, st.session_state.current_node, path),
|
leads_manager.py
CHANGED
|
@@ -43,13 +43,14 @@ def save_lead_to_db(lead_info, chat_history, outcome):
|
|
| 43 |
if not sheet.get_all_values():
|
| 44 |
sheet.append_row([
|
| 45 |
"Date", "Name", "Company", "Type", "Context",
|
| 46 |
-
"Pain Point", "Budget", "Outcome", "
|
| 47 |
])
|
| 48 |
except:
|
| 49 |
pass # Таблиця може бути новою
|
| 50 |
|
| 51 |
# Формуємо рядок даних
|
| 52 |
-
|
|
|
|
| 53 |
|
| 54 |
row = [
|
| 55 |
datetime.now().strftime("%Y-%m-%d %H:%M"),
|
|
@@ -60,7 +61,7 @@ def save_lead_to_db(lead_info, chat_history, outcome):
|
|
| 60 |
"AI Pending", # Тут можна додати AI аналіз
|
| 61 |
"Unknown",
|
| 62 |
outcome,
|
| 63 |
-
|
| 64 |
]
|
| 65 |
|
| 66 |
# Додаємо рядок
|
|
|
|
| 43 |
if not sheet.get_all_values():
|
| 44 |
sheet.append_row([
|
| 45 |
"Date", "Name", "Company", "Type", "Context",
|
| 46 |
+
"Pain Point", "Budget", "Outcome", "Transcript"
|
| 47 |
])
|
| 48 |
except:
|
| 49 |
pass # Таблиця може бути новою
|
| 50 |
|
| 51 |
# Формуємо рядок даних
|
| 52 |
+
# Збираємо весь текст діалогу для навчання
|
| 53 |
+
transcript = "\n".join([f"{msg['role']}: {msg['content']}" for msg in chat_history])
|
| 54 |
|
| 55 |
row = [
|
| 56 |
datetime.now().strftime("%Y-%m-%d %H:%M"),
|
|
|
|
| 61 |
"AI Pending", # Тут можна додати AI аналіз
|
| 62 |
"Unknown",
|
| 63 |
outcome,
|
| 64 |
+
transcript
|
| 65 |
]
|
| 66 |
|
| 67 |
# Додаємо рядок
|
sales_script.json
CHANGED
|
@@ -1,151 +1,179 @@
|
|
| 1 |
{
|
| 2 |
"nodes": {
|
| 3 |
-
"start": "ПРИВІТАННЯ.
|
| 4 |
-
"
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
-
"
|
| 9 |
-
"
|
| 10 |
-
"
|
| 11 |
-
"
|
| 12 |
-
"
|
| 13 |
-
"
|
| 14 |
-
"
|
| 15 |
-
"
|
| 16 |
-
"
|
| 17 |
-
"
|
|
|
|
|
|
|
|
|
|
| 18 |
},
|
| 19 |
"edges": [
|
| 20 |
{
|
| 21 |
"from": "start",
|
| 22 |
-
"to": "
|
| 23 |
-
"weight":
|
| 24 |
},
|
| 25 |
{
|
| 26 |
"from": "start",
|
| 27 |
-
"to": "
|
| 28 |
-
"weight":
|
| 29 |
},
|
| 30 |
{
|
| 31 |
"from": "start",
|
| 32 |
-
"to": "
|
| 33 |
-
"weight":
|
| 34 |
},
|
| 35 |
{
|
| 36 |
-
"from": "
|
| 37 |
-
"to": "
|
| 38 |
-
"weight":
|
| 39 |
},
|
| 40 |
{
|
| 41 |
-
"from": "
|
| 42 |
-
"to": "
|
| 43 |
-
"weight":
|
| 44 |
},
|
| 45 |
{
|
| 46 |
-
"from": "
|
| 47 |
-
"to": "
|
| 48 |
-
"weight":
|
| 49 |
},
|
| 50 |
{
|
| 51 |
-
"from": "
|
| 52 |
-
"to": "
|
| 53 |
-
"weight":
|
| 54 |
},
|
| 55 |
{
|
| 56 |
-
"from": "
|
| 57 |
-
"to": "
|
| 58 |
"weight": 5
|
| 59 |
},
|
| 60 |
{
|
| 61 |
-
"from": "
|
| 62 |
-
"to": "
|
| 63 |
"weight": 1
|
| 64 |
},
|
| 65 |
{
|
| 66 |
-
"from": "
|
| 67 |
-
"to": "
|
| 68 |
"weight": 1
|
| 69 |
},
|
| 70 |
{
|
| 71 |
-
"from": "
|
| 72 |
-
"to": "
|
| 73 |
-
"weight":
|
| 74 |
},
|
| 75 |
{
|
| 76 |
-
"from": "
|
| 77 |
-
"to": "
|
| 78 |
-
"weight":
|
| 79 |
},
|
| 80 |
{
|
| 81 |
-
"from": "
|
| 82 |
-
"to": "
|
| 83 |
"weight": 4
|
| 84 |
},
|
| 85 |
{
|
| 86 |
-
"from": "
|
| 87 |
-
"to": "
|
| 88 |
-
"weight":
|
| 89 |
},
|
| 90 |
{
|
| 91 |
-
"from": "
|
| 92 |
-
"to": "
|
| 93 |
-
"weight":
|
| 94 |
},
|
| 95 |
{
|
| 96 |
-
"from": "
|
| 97 |
-
"to": "
|
| 98 |
"weight": 2
|
| 99 |
},
|
| 100 |
{
|
| 101 |
-
"from": "
|
| 102 |
-
"to": "
|
| 103 |
-
"weight":
|
| 104 |
},
|
| 105 |
{
|
| 106 |
-
"from": "
|
| 107 |
-
"to": "
|
| 108 |
"weight": 10
|
| 109 |
},
|
| 110 |
{
|
| 111 |
-
"from": "
|
| 112 |
-
"to": "
|
| 113 |
-
"weight":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
},
|
| 115 |
{
|
| 116 |
-
"from": "
|
| 117 |
-
"to": "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
"weight": 4
|
| 119 |
},
|
| 120 |
{
|
| 121 |
-
"from": "
|
| 122 |
-
"to": "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
"weight": 1
|
| 124 |
},
|
| 125 |
{
|
| 126 |
-
"from": "
|
| 127 |
-
"to": "
|
| 128 |
-
"weight":
|
| 129 |
},
|
| 130 |
{
|
| 131 |
-
"from": "
|
| 132 |
-
"to": "
|
| 133 |
-
"weight":
|
| 134 |
},
|
| 135 |
{
|
| 136 |
-
"from": "
|
| 137 |
-
"to": "
|
| 138 |
-
"weight":
|
| 139 |
},
|
| 140 |
{
|
| 141 |
-
"from": "
|
| 142 |
-
"to": "
|
| 143 |
"weight": 1
|
| 144 |
},
|
| 145 |
{
|
| 146 |
-
"from": "
|
| 147 |
-
"to": "
|
| 148 |
-
"weight":
|
| 149 |
}
|
| 150 |
]
|
| 151 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"nodes": {
|
| 3 |
+
"start": "ЕТАП: ПРИВІТАННЯ. Задача: Коротко представитися (назви своє ім'я та компанію SellMe). Перевір, чи зручно говорити. Зроби 'гачок' про автоматизацію, але не продавай в лоб.",
|
| 4 |
+
"hook_rejection": "ЕТАП: УТРИМАННЯ УВАГИ. Клієнт хоче кинути трубку. Задача: Не сперечатися. Використати техніку 'Тільки 30 секунд'. Скажи, що якщо не сподобається — сам підеш. Будь впевненим.",
|
| 5 |
+
"qualification_needs": "ЕТАП: КВАЛІФІКАЦІЯ. Задача: Дізнатися, як вони зараз працюють. Запитай про CRM або Excel. Ціль — змусити клієнта визнати, що ручна робота — це незручно.",
|
| 6 |
+
"pain_followup": "ЕТАП: ТИСК НА БІЛЬ. Клієнт сказав, що менеджери забувають дзвонити. Задача: Підсилити цей біль. Запитай: 'А скільки грошей ви втрачаєте через це щомісяця?'.",
|
| 7 |
+
"pain_closing": "ЕТАП: ТИСК НА БІЛЬ. Проблема із закриттям угод. Задача: Поясни, що це людський фактор. Скажи, що AI не втомлюється і пам'ятає всі скрипти.",
|
| 8 |
+
"pitch_main": "ЕТАП: ПРЕЗЕНТАЦІЯ РІШЕННЯ. Задача: Презентуй SellMe як 'Суфлера для менеджерів'. Говори мовою вигоди (економія часу, ріст конверсії). Назви ціну (100$) в кінці.",
|
| 9 |
+
"price_shock": "ЕТ��П: РОБОТА З 'ДОРОГО'. Задача: Не виправдовуйся. Порівняй ціну з вартістю втраченого клієнта. Використай прийом: 'Це дешевше, ніж кава для офісу'.",
|
| 10 |
+
"downsell_offer": "ЕТАП: ПОНИЖЕННЯ СТАВКИ. Задача: Клієнт не тягне повну версію. Запропонуй Light-версію за 20$. Скажи, що це ідеально для старту.",
|
| 11 |
+
"upsell_offer": "ЕТАП: ПІДВИЩЕННЯ ЧЕКУ. Клієнт зацікавлений. Задача: Запропонуй VIP-інтеграцію. Скажи, що це зробить систему повністю автономною.",
|
| 12 |
+
"objection_tech": "ЕТАП: ТЕХНІЧНІ СУМНІВИ. Задача: Заспокой клієнта. Скажи, що це плагін для браузера, ставиться за 1 хвилину. Ніяких айтішників не треба.",
|
| 13 |
+
"competitor_trap": "ЕТАП: ВІДСТРОЙКА ВІД КОНКУРЕНТІВ. Задача: Похвали конкурента, але покажи нашу унікальність (ми не просто пишемо дзвінки, ми підказуємо онлайн).",
|
| 14 |
+
"close_standard": "ЕТАП: ЗАКРИТТЯ (Standard). Задача: Переходь до діла. Запитай куди виставити рахунок або коли зручно підписати договір.",
|
| 15 |
+
"close_light": "ЕТАП: ЗАКРИТТЯ (Light). Задача: Закрий на дешевий тариф. Головне — почати співпрацю.",
|
| 16 |
+
"close_vip": "ЕТАП: ЗАКРИТТЯ (VIP). Задача: Закріпи успіх. Привітай з правильним рішенням.",
|
| 17 |
+
"referral_ask": "ЕТАП: РЕКОМЕНДАЦІЯ. Задача: Угода зірвалася. Ввічливо попроси контакт партнера, кому це може бути цікаво.",
|
| 18 |
+
"exit_success": "ЕТАП: ПРОЩАННЯ (Успіх). Задача: Подякуй, домовся про наступний крок (лист/дзвінок). Попрощайся на позитиві.",
|
| 19 |
+
"exit_fail": "ЕТАП: ПРОЩАННЯ (Відмова). Задача: Залиш двері відкритими. Скажи, що будеш радий допомогти в майбутньому.",
|
| 20 |
+
"exit_neutral": "ЕТАП: ПРОЩАННЯ (Нейтрально). Задача: Ввічливо заверши розмову."
|
| 21 |
},
|
| 22 |
"edges": [
|
| 23 |
{
|
| 24 |
"from": "start",
|
| 25 |
+
"to": "hook_rejection",
|
| 26 |
+
"weight": 2
|
| 27 |
},
|
| 28 |
{
|
| 29 |
"from": "start",
|
| 30 |
+
"to": "qualification_needs",
|
| 31 |
+
"weight": 1
|
| 32 |
},
|
| 33 |
{
|
| 34 |
"from": "start",
|
| 35 |
+
"to": "exit_fail",
|
| 36 |
+
"weight": 1000
|
| 37 |
},
|
| 38 |
{
|
| 39 |
+
"from": "hook_rejection",
|
| 40 |
+
"to": "qualification_needs",
|
| 41 |
+
"weight": 1
|
| 42 |
},
|
| 43 |
{
|
| 44 |
+
"from": "hook_rejection",
|
| 45 |
+
"to": "exit_fail",
|
| 46 |
+
"weight": 50
|
| 47 |
},
|
| 48 |
{
|
| 49 |
+
"from": "qualification_needs",
|
| 50 |
+
"to": "pain_followup",
|
| 51 |
+
"weight": 1
|
| 52 |
},
|
| 53 |
{
|
| 54 |
+
"from": "qualification_needs",
|
| 55 |
+
"to": "pain_closing",
|
| 56 |
+
"weight": 1
|
| 57 |
},
|
| 58 |
{
|
| 59 |
+
"from": "qualification_needs",
|
| 60 |
+
"to": "pitch_main",
|
| 61 |
"weight": 5
|
| 62 |
},
|
| 63 |
{
|
| 64 |
+
"from": "pain_followup",
|
| 65 |
+
"to": "pitch_main",
|
| 66 |
"weight": 1
|
| 67 |
},
|
| 68 |
{
|
| 69 |
+
"from": "pain_closing",
|
| 70 |
+
"to": "pitch_main",
|
| 71 |
"weight": 1
|
| 72 |
},
|
| 73 |
{
|
| 74 |
+
"from": "pitch_main",
|
| 75 |
+
"to": "price_shock",
|
| 76 |
+
"weight": 5
|
| 77 |
},
|
| 78 |
{
|
| 79 |
+
"from": "pitch_main",
|
| 80 |
+
"to": "objection_tech",
|
| 81 |
+
"weight": 3
|
| 82 |
},
|
| 83 |
{
|
| 84 |
+
"from": "pitch_main",
|
| 85 |
+
"to": "competitor_trap",
|
| 86 |
"weight": 4
|
| 87 |
},
|
| 88 |
{
|
| 89 |
+
"from": "pitch_main",
|
| 90 |
+
"to": "upsell_offer",
|
| 91 |
+
"weight": 10
|
| 92 |
},
|
| 93 |
{
|
| 94 |
+
"from": "pitch_main",
|
| 95 |
+
"to": "close_standard",
|
| 96 |
+
"weight": 2
|
| 97 |
},
|
| 98 |
{
|
| 99 |
+
"from": "price_shock",
|
| 100 |
+
"to": "downsell_offer",
|
| 101 |
"weight": 2
|
| 102 |
},
|
| 103 |
{
|
| 104 |
+
"from": "price_shock",
|
| 105 |
+
"to": "pitch_main",
|
| 106 |
+
"weight": 5
|
| 107 |
},
|
| 108 |
{
|
| 109 |
+
"from": "price_shock",
|
| 110 |
+
"to": "close_standard",
|
| 111 |
"weight": 10
|
| 112 |
},
|
| 113 |
{
|
| 114 |
+
"from": "downsell_offer",
|
| 115 |
+
"to": "close_light",
|
| 116 |
+
"weight": 1
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"from": "downsell_offer",
|
| 120 |
+
"to": "exit_fail",
|
| 121 |
+
"weight": 10
|
| 122 |
},
|
| 123 |
{
|
| 124 |
+
"from": "objection_tech",
|
| 125 |
+
"to": "pitch_main",
|
| 126 |
+
"weight": 2
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"from": "objection_tech",
|
| 130 |
+
"to": "upsell_offer",
|
| 131 |
+
"weight": 5
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
"from": "competitor_trap",
|
| 135 |
+
"to": "pitch_main",
|
| 136 |
+
"weight": 2
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"from": "competitor_trap",
|
| 140 |
+
"to": "downsell_offer",
|
| 141 |
"weight": 4
|
| 142 |
},
|
| 143 |
{
|
| 144 |
+
"from": "upsell_offer",
|
| 145 |
+
"to": "close_vip",
|
| 146 |
+
"weight": 2
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"from": "upsell_offer",
|
| 150 |
+
"to": "close_standard",
|
| 151 |
"weight": 1
|
| 152 |
},
|
| 153 |
{
|
| 154 |
+
"from": "close_standard",
|
| 155 |
+
"to": "exit_success",
|
| 156 |
+
"weight": 0
|
| 157 |
},
|
| 158 |
{
|
| 159 |
+
"from": "close_light",
|
| 160 |
+
"to": "exit_success",
|
| 161 |
+
"weight": 0
|
| 162 |
},
|
| 163 |
{
|
| 164 |
+
"from": "close_vip",
|
| 165 |
+
"to": "exit_success",
|
| 166 |
+
"weight": 0
|
| 167 |
},
|
| 168 |
{
|
| 169 |
+
"from": "exit_fail",
|
| 170 |
+
"to": "referral_ask",
|
| 171 |
"weight": 1
|
| 172 |
},
|
| 173 |
{
|
| 174 |
+
"from": "referral_ask",
|
| 175 |
+
"to": "exit_neutral",
|
| 176 |
+
"weight": 0
|
| 177 |
}
|
| 178 |
]
|
| 179 |
}
|