docs: update workshop README for notebook-first content model
Rewrite the CX Discovery Workshop README to reflect the architecture shift where content lives in the notebook's `topic-bank` cell rather than in `discoverylib/topics.py`. The library now holds code only, the notebook is the deliverable (no longer generated via build_notebook.py), and tests pin content read directly from the notebook. Update layout, run, and extending sections accordingly.
This commit is contained in:
@@ -10,18 +10,13 @@ carries the facilitator's question script and the captured per-topic notes,
|
||||
which the data-appendix export hands to an LLM to draft the survey write-up
|
||||
or feed a downstream business-case study.
|
||||
|
||||
Engine/presentation split (Pattern §2): the topic bank and all status /
|
||||
progress logic live here; the notebook only arranges and renders them.
|
||||
Code/content split: this package holds the **schema and logic only** —
|
||||
:class:`Topic`/:class:`SubTopic`, the status vocabulary, and the session
|
||||
engine. The content (the topic bank itself) lives in the ``topic-bank``
|
||||
cell of ``notebooks/cx_discovery.ipynb``, where it is edited in Jupyter;
|
||||
every engine function takes the bank as its first argument.
|
||||
"""
|
||||
|
||||
from .topics import (
|
||||
TOPIC_BY_KEY,
|
||||
TOPIC_KEYS,
|
||||
TOPICS,
|
||||
SubTopic,
|
||||
Topic,
|
||||
topic,
|
||||
)
|
||||
from .session import (
|
||||
COMPLETE,
|
||||
IN_PROGRESS,
|
||||
@@ -33,6 +28,8 @@ from .session import (
|
||||
STATUSES,
|
||||
ChecklistItem,
|
||||
Progress,
|
||||
SubTopic,
|
||||
Topic,
|
||||
TopicState,
|
||||
active_topic_key,
|
||||
agenda_minutes,
|
||||
@@ -45,11 +42,11 @@ from .session import (
|
||||
)
|
||||
from .staging import backstage, on_stage
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__version__ = "0.2.0"
|
||||
|
||||
__all__ = [
|
||||
# topic bank
|
||||
"TOPICS", "TOPIC_BY_KEY", "TOPIC_KEYS", "Topic", "SubTopic", "topic",
|
||||
# schema
|
||||
"Topic", "SubTopic",
|
||||
# status vocabulary
|
||||
"STATUSES", "STATUS_LABEL", "STATUS_GLYPH", "STATUS_COLOR",
|
||||
"NOT_STARTED", "IN_PROGRESS", "COMPLETE", "SKIPPED", "normalize_status",
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
Session engine — the discovery workshop's "math".
|
||||
|
||||
There are no dollars here; the quantities a facilitator and client watch are
|
||||
**status** and **progress**. This module owns all of it so the notebook only
|
||||
arranges outputs (Mercury Notebook Pattern §2, engine/presentation split):
|
||||
**status** and **progress**. This module owns the schema and all of the
|
||||
logic, but none of the content: the topic bank itself lives in the
|
||||
``topic-bank`` cell of ``notebooks/cx_discovery.ipynb`` (content belongs on
|
||||
the Jupyter surface, where it is edited; ``.py`` files hold code only).
|
||||
|
||||
* :class:`Topic` / :class:`SubTopic` — the schema the bank is written in,
|
||||
* the topic-status vocabulary (:data:`STATUSES`) and its display glyphs,
|
||||
* :func:`progress` — the "3 / 8 topics complete" headline and its ratios,
|
||||
* :func:`subtopic_checklist` — the live tick-list for the active topic,
|
||||
@@ -12,7 +15,7 @@ arranges outputs (Mercury Notebook Pattern §2, engine/presentation split):
|
||||
raw widget inputs (per-topic status + free-text notes),
|
||||
* :func:`session_json` — the machine-readable export payload (Pattern §5).
|
||||
|
||||
Everything is a pure function of ``(status_by_topic, notes_by_topic,
|
||||
Everything is a pure function of ``(topics, status_by_topic, notes_by_topic,
|
||||
done_subtopics)`` so the in-notebook gate can pin it and the export can dump
|
||||
it. Unknown / missing keys degrade gracefully to "not started" so a
|
||||
half-filled live session never crashes the render.
|
||||
@@ -20,10 +23,34 @@ half-filled live session never crashes the render.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
from .topics import TOPICS, TOPIC_BY_KEY, Topic
|
||||
|
||||
# ── Schema — what the notebook's topic-bank cell is written in ───────
|
||||
@dataclass(frozen=True)
|
||||
class SubTopic:
|
||||
"""A discussion thread within a topic; its title shows on stage when the
|
||||
topic is active, its prompts are the backstage facilitation script."""
|
||||
|
||||
key: str
|
||||
title: str
|
||||
prompts: tuple[str, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Topic:
|
||||
"""A client-facing discovery topic: the unit the progress bar counts."""
|
||||
|
||||
key: str
|
||||
title: str
|
||||
scope: str
|
||||
minutes: int
|
||||
subtopics: tuple[SubTopic, ...] = field(default_factory=tuple)
|
||||
|
||||
@property
|
||||
def prompt_count(self) -> int:
|
||||
return sum(len(st.prompts) for st in self.subtopics)
|
||||
|
||||
|
||||
# ── Status vocabulary ────────────────────────────────────────────────
|
||||
@@ -94,13 +121,15 @@ class Progress:
|
||||
return f"{self.completed}/{self.total} topics complete"
|
||||
|
||||
|
||||
def progress(status_by_topic: dict[str, str]) -> Progress:
|
||||
def progress(
|
||||
topics: tuple[Topic, ...], status_by_topic: dict[str, str]
|
||||
) -> Progress:
|
||||
"""Tally topic statuses into the headline :class:`Progress`."""
|
||||
counts = {s: 0 for s in STATUSES}
|
||||
for t in TOPICS:
|
||||
for t in topics:
|
||||
counts[normalize_status(status_by_topic.get(t.key))] += 1
|
||||
return Progress(
|
||||
total=len(TOPICS),
|
||||
total=len(topics),
|
||||
completed=counts[COMPLETE],
|
||||
skipped=counts[SKIPPED],
|
||||
in_progress=counts[IN_PROGRESS],
|
||||
@@ -108,13 +137,15 @@ def progress(status_by_topic: dict[str, str]) -> Progress:
|
||||
)
|
||||
|
||||
|
||||
def active_topic_key(status_by_topic: dict[str, str]) -> str | None:
|
||||
def active_topic_key(
|
||||
topics: tuple[Topic, ...], status_by_topic: dict[str, str]
|
||||
) -> str | None:
|
||||
"""The topic to spotlight on stage: the first in-progress topic, else
|
||||
the first not-started one, else None (everything resolved)."""
|
||||
for t in TOPICS:
|
||||
for t in topics:
|
||||
if normalize_status(status_by_topic.get(t.key)) == IN_PROGRESS:
|
||||
return t.key
|
||||
for t in TOPICS:
|
||||
for t in topics:
|
||||
if normalize_status(status_by_topic.get(t.key)) == NOT_STARTED:
|
||||
return t.key
|
||||
return None
|
||||
@@ -146,9 +177,9 @@ def subtopic_id(topic_key: str, subtopic_key: str) -> str:
|
||||
|
||||
|
||||
# ── Agenda ───────────────────────────────────────────────────────────
|
||||
def agenda_minutes() -> int:
|
||||
def agenda_minutes(topics: tuple[Topic, ...]) -> int:
|
||||
"""Sum of the nominal per-topic minute budgets."""
|
||||
return sum(t.minutes for t in TOPICS)
|
||||
return sum(t.minutes for t in topics)
|
||||
|
||||
|
||||
# ── Full session state (render + export) ─────────────────────────────
|
||||
@@ -168,19 +199,22 @@ class TopicState:
|
||||
|
||||
|
||||
def build_session(
|
||||
topics: tuple[Topic, ...],
|
||||
status_by_topic: dict[str, str],
|
||||
notes_by_topic: dict[str, str] | None = None,
|
||||
done_subtopics: set[str] | frozenset[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Assemble the complete session state the notebook renders and exports.
|
||||
|
||||
Pure function of the three raw inputs; safe against missing keys.
|
||||
Pure function of the bank and the three raw inputs; safe against
|
||||
missing keys.
|
||||
"""
|
||||
notes_by_topic = notes_by_topic or {}
|
||||
done_subtopics = frozenset(done_subtopics or ())
|
||||
by_key = {t.key: t for t in topics}
|
||||
|
||||
topic_states: list[TopicState] = []
|
||||
for t in TOPICS:
|
||||
for t in topics:
|
||||
status = normalize_status(status_by_topic.get(t.key))
|
||||
done = sum(
|
||||
1 for st in t.subtopics if subtopic_id(t.key, st.key) in done_subtopics
|
||||
@@ -201,14 +235,14 @@ def build_session(
|
||||
)
|
||||
)
|
||||
|
||||
prog = progress(status_by_topic)
|
||||
active = active_topic_key(status_by_topic)
|
||||
prog = progress(topics, status_by_topic)
|
||||
active = active_topic_key(topics, status_by_topic)
|
||||
return {
|
||||
"topics": topic_states,
|
||||
"progress": prog,
|
||||
"active_topic_key": active,
|
||||
"active_topic": TOPIC_BY_KEY[active] if active else None,
|
||||
"agenda_minutes": agenda_minutes(),
|
||||
"active_topic": by_key[active] if active else None,
|
||||
"agenda_minutes": agenda_minutes(topics),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ must appear for LLM consumption) but never render in the Mercury app.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
|
||||
def on_stage() -> bool:
|
||||
@@ -23,7 +24,7 @@ def on_stage() -> bool:
|
||||
return os.getenv("MERCURY_CONFIG_DIR") is not None
|
||||
|
||||
|
||||
def backstage(*args, **kwargs) -> None:
|
||||
def backstage(*args: object, **kwargs: Any) -> None:
|
||||
"""``print`` that renders only backstage (JupyterLab, nbconvert)."""
|
||||
if not on_stage():
|
||||
print(*args, **kwargs)
|
||||
|
||||
@@ -1,428 +0,0 @@
|
||||
"""
|
||||
The discovery topic bank — verbatim source record for the CX Exploration &
|
||||
Discovery workshop.
|
||||
|
||||
This is the study's ``*_VERBATIM`` anchor (Mercury Notebook Pattern): the
|
||||
facilitation content, structured but **never paraphrased away from** the
|
||||
source survey ``docs/cx_discovery_survey.md`` (the original ``cxxm.md``).
|
||||
Editing the wording of a prompt here is editing the anchor — do it against
|
||||
the survey, not the notebook.
|
||||
|
||||
Shape
|
||||
-----
|
||||
``TOPICS`` is an ordered tuple of :class:`Topic`. Each topic owns an ordered
|
||||
tuple of :class:`SubTopic`; each sub-topic owns the ordered facilitator
|
||||
prompts (the questions you actually ask). The stage shows topic titles, the
|
||||
one-line ``scope`` per topic, and — for the active topic — the sub-topic
|
||||
titles as a live checklist. The prompts stay **backstage** (your script);
|
||||
they never render on the client's screen.
|
||||
|
||||
Every topic carries:
|
||||
|
||||
* ``key`` — stable slug (snake_case); the identity used by widgets, notes,
|
||||
status, and the JSON export. NEVER renumber or rename casually —
|
||||
captured notes key off it.
|
||||
* ``title`` — client-facing heading shown on stage.
|
||||
* ``scope`` — one neutral line shown under the title on stage, so the client
|
||||
stays oriented without being led by the questions.
|
||||
* ``minutes`` — nominal facilitation budget, summed into the agenda estimate.
|
||||
|
||||
The content is deliberately data, not prose in the notebook: it is testable,
|
||||
diffable, and exportable, and the notebook only arranges it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SubTopic:
|
||||
"""A discussion thread within a topic; its title shows on stage when the
|
||||
topic is active, its prompts are the backstage facilitation script."""
|
||||
|
||||
key: str
|
||||
title: str
|
||||
prompts: tuple[str, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Topic:
|
||||
"""A client-facing discovery topic: the unit the progress bar counts."""
|
||||
|
||||
key: str
|
||||
title: str
|
||||
scope: str
|
||||
minutes: int
|
||||
subtopics: tuple[SubTopic, ...] = field(default_factory=tuple)
|
||||
|
||||
@property
|
||||
def prompt_count(self) -> int:
|
||||
return sum(len(st.prompts) for st in self.subtopics)
|
||||
|
||||
|
||||
# ── The topic bank ───────────────────────────────────────────────────
|
||||
# Ordered as a workshop runs: context first, then the operational areas,
|
||||
# then the enabling functions, closing on reporting & insight. Wording of
|
||||
# prompts tracks docs/cx_discovery_survey.md (cxxm.md).
|
||||
|
||||
TOPICS: tuple[Topic, ...] = (
|
||||
Topic(
|
||||
key="background",
|
||||
title="Background & Organization",
|
||||
scope="Lines of business, channels, org structure, decision-making",
|
||||
minutes=15,
|
||||
subtopics=(
|
||||
SubTopic(
|
||||
"lines_of_business", "Lines of business & scope",
|
||||
(
|
||||
"How many distinct lines of business are supported in the "
|
||||
"contact centre? (e.g. commercial, residential, retail, "
|
||||
"wholesale)",
|
||||
"Are you using the contact centre for internal uses such as "
|
||||
"help desk, finance, HR?",
|
||||
"Payment card / PCI in scope?",
|
||||
"What are the availability SLAs or targets for your "
|
||||
"technology platform?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"channels_supported", "Channels supported",
|
||||
(
|
||||
"Which channels do you support today? (Apps, Voice, Video, "
|
||||
"Chat, SMS, Email, Social Media, Digital Assistants)",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"org_structure", "Org & reporting structure",
|
||||
(
|
||||
"Mini org chart — reporting structure up to the executive "
|
||||
"leader.",
|
||||
"Are your contact centres managed by the same person? "
|
||||
"(Managers / Team Leads / Agents)",
|
||||
"Who carries the cost of contact centre agents and "
|
||||
"supervisors?",
|
||||
"What revenue is generated by the contact centre?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"decision_making", "Decision-making & priorities",
|
||||
(
|
||||
"Is decision-making centralized or decentralized?",
|
||||
"Who are the key decision makers for changes to the contact "
|
||||
"centre?",
|
||||
"How important is CX & EX to your organization's strategy? "
|
||||
"Is there an executive accountable for CX (CXO, CDO)?",
|
||||
"Of your leading offers, what is the customer's top priority?",
|
||||
"Competitive pressures?",
|
||||
"Any pending or recent acquisitions or spinoffs affecting IT "
|
||||
"infrastructure and services?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"problems_today", "Problems & causes today",
|
||||
(
|
||||
"What do you view as the problems in your contact centre "
|
||||
"today, and the primary causes?",
|
||||
"Any in-flight projects — CX or EX improvement initiatives?",
|
||||
"Are your customer experiences personalized?",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Topic(
|
||||
key="cx_strategy",
|
||||
title="CX Strategy",
|
||||
scope="How CX value is defined, measured, and organized to deliver",
|
||||
minutes=15,
|
||||
subtopics=(
|
||||
SubTopic(
|
||||
"value_definition", "Value & strategic position",
|
||||
(
|
||||
"How is the value of Customer Experience defined within your "
|
||||
"organization? Is CX a key BPI, recognized & measured as a "
|
||||
"financial value?",
|
||||
"What are the key capabilities / services delivered by the "
|
||||
"contact centre?",
|
||||
"How are you using CX innovation to create market "
|
||||
"disruption?",
|
||||
"How are competitors using CX to create competitive "
|
||||
"differentiation?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"operating_model", "Organization & operating model",
|
||||
(
|
||||
"Is there a CX team? Do the CX insights team regularly "
|
||||
"educate the business?",
|
||||
"Who are the key decision makers for investment and changes "
|
||||
"to the contact centre? Channel management strategy?",
|
||||
"What are the teams, and how are processes managed?",
|
||||
"Describe your automation strategy. Who is your leader for "
|
||||
"Data & AI?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"insight_and_voc", "Insight & Voice of Customer",
|
||||
(
|
||||
"How do you use analytics & data to generate a consolidated "
|
||||
"view of your customer experience?",
|
||||
"Is there a VOC program in place? ROI / business value "
|
||||
"known?",
|
||||
"How do customers rate the experience they receive (0–5)?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"continuous_improvement", "Continuous improvement",
|
||||
(
|
||||
"How is customer insight used to drive CX improvement, "
|
||||
"loyalty and profitability? How do you anticipate needs?",
|
||||
"Do you have a clear set of CX design guidelines? (Personas, "
|
||||
"Journey Mapping, Tools)",
|
||||
"KPI targets for CX — CSAT, NPS, CES? How are you doing?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"employee_engagement", "Employee engagement",
|
||||
(
|
||||
"Describe your employee / agent engagement strategy.",
|
||||
"How engaged are your people in delivering the customer "
|
||||
"experience?",
|
||||
"Do you have a VoA / VoE program in place? How are you "
|
||||
"doing?",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Topic(
|
||||
key="channels",
|
||||
title="Channels",
|
||||
scope="Inbound & outbound volumes, metrics, contact reasons",
|
||||
minutes=20,
|
||||
subtopics=(
|
||||
SubTopic(
|
||||
"inbound_context", "Inbound context",
|
||||
(
|
||||
"Hours of operation — any 24×7?",
|
||||
"Who contacts the contact centre? (Demographics, their "
|
||||
"situation)",
|
||||
"Are certain callers or groups prioritized?",
|
||||
"Languages? (English, Canadian French, Spanish, other)",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"voice_metrics", "Voice & video metrics",
|
||||
(
|
||||
"Average number of active agents (voice / video).",
|
||||
"Toll-free numbers & DIDs — approximate quantities.",
|
||||
"Average Wait Time, Abandon rate, Handle Time, Hold Time, "
|
||||
"After-call work time.",
|
||||
"Time to authenticate a caller.",
|
||||
"% of calls transferred — internally / externally (3rd "
|
||||
"parties)?",
|
||||
"Courtesy callback / virtual hold? Post-call survey? First "
|
||||
"Call Resolution rate? Average revenue per call (sales)?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"digital_channels", "Digital channels",
|
||||
(
|
||||
"Email — range of logged-in users, response time, email "
|
||||
"server.",
|
||||
"Chat — web (internal/external), app-embedded, SMS, "
|
||||
"Messenger, WhatsApp, Telegram, iMessage.",
|
||||
"Website forms, mobile apps.",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"inbound_reasons", "Inbound contact reasons",
|
||||
(
|
||||
"Top 3–5 inbound contact reasons and approximate % of calls.",
|
||||
"Cost per call? Average Handle Time?",
|
||||
"Busiest / least busy days? Seasonal variances?",
|
||||
"Most difficult, commonly occurring calls? Easiest commonly "
|
||||
"occurring call?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"outbound", "Outbound",
|
||||
(
|
||||
"Hours of operation.",
|
||||
"Voice (preview / predictive dialer volume), email, SMS, "
|
||||
"recorded announcement ports.",
|
||||
"Self-service applications, live agent connect, campaign "
|
||||
"management, DNC management.",
|
||||
"Top 3–5 outbound contact reasons and approximate % of "
|
||||
"calls.",
|
||||
"After-call work?",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Topic(
|
||||
key="agent_environment",
|
||||
title="Agent & Supervisor Environment",
|
||||
scope="Locations, endpoints, desktop, applications, knowledge",
|
||||
minutes=10,
|
||||
subtopics=(
|
||||
SubTopic(
|
||||
"locations_endpoints", "Locations & endpoints",
|
||||
(
|
||||
"Location types — WFH / offices.",
|
||||
"Hard phone, soft phone, CODEC, wireless headset?",
|
||||
"Agent greeting / pre-recorded messages, whisper "
|
||||
"announcement.",
|
||||
"PC — desktop, laptop, VDI.",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"desktop_apps", "Desktop & applications",
|
||||
(
|
||||
"Agent & supervisor desktop — omnichannel? Custom gadgets, "
|
||||
"screen pops, workflows.",
|
||||
"Standard browser? SSO?",
|
||||
"Applications used to handle calls.",
|
||||
"Knowledge Management.",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Topic(
|
||||
key="routing_automation",
|
||||
title="Routing & Automation",
|
||||
scope="Self-service, IVR, speech, virtual agents, agent assist, RPA",
|
||||
minutes=15,
|
||||
subtopics=(
|
||||
SubTopic(
|
||||
"self_service", "Self-service & IVR",
|
||||
(
|
||||
"IVR persona — branding, style guides, voice actors?",
|
||||
"DTMF and/or speech? Speech recognition, TTS, NLU, voice "
|
||||
"biometrics.",
|
||||
"Self-service applications — ID & validate, deflection, "
|
||||
"situational offer (outage / time of day / scheduled).",
|
||||
"API integration (CRM). Intent capture, intent prediction, "
|
||||
"offer push on prediction / account attribute.",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"virtual_agents", "Virtual agents & assist",
|
||||
(
|
||||
"Virtual agents — which processes?",
|
||||
"Agent assist?",
|
||||
"RPA?",
|
||||
"Current challenges or desired capabilities. Desire to "
|
||||
"automate. DevOps team?",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"digital_assistants", "Digital assistant apps",
|
||||
(
|
||||
"Alexa, Google Assistant, Siri?",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Topic(
|
||||
key="workforce_engagement",
|
||||
title="Workforce Engagement",
|
||||
scope="Call recording / QM, WFM, agent self-service, integrations",
|
||||
minutes=20,
|
||||
subtopics=(
|
||||
SubTopic(
|
||||
"recording_qm", "Call recording & Quality Management",
|
||||
(
|
||||
"How is call recording used? Compliance, screen capture, "
|
||||
"voice transcription (real-time?), desktop analytics, "
|
||||
"retention period.",
|
||||
"Quality team — who do they report to, how many people? "
|
||||
"Scorecards, score method & metrics, number of assessments, "
|
||||
"coaching, live monitor.",
|
||||
"Locations with call recording. Number of named agents.",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"workforce_management", "Workforce management",
|
||||
(
|
||||
"Recruitment process, required qualifications, average "
|
||||
"tenure, attrition rate, internal moves.",
|
||||
"Do you measure agent & supervisor experience? Recognition "
|
||||
"program?",
|
||||
"Adherence measure — KPIs, gamification, shift length, "
|
||||
"breaks, annualized utilization %.",
|
||||
"Forecasting & scheduling — peak volume, historical data, "
|
||||
"algorithms, WFM interval (15/30 min).",
|
||||
"Intraday / real-time adherence — what happens when out of "
|
||||
"compliance?",
|
||||
"Payroll integration (ADP / Workday). Satisfaction with "
|
||||
"current tool(s)? Multiskilled agents, FTE calculations.",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"agent_self_service", "Agent self-service & integrations",
|
||||
(
|
||||
"Agent self-service — absences, shift bids / swaps, "
|
||||
"performance metrics.",
|
||||
"Number of named agents.",
|
||||
"Integration to 3rd party — outsourcer / overflow, payroll.",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Topic(
|
||||
key="training",
|
||||
title="Training",
|
||||
scope="Onboarding, format, assessment, eLearning, QM integration",
|
||||
minutes=5,
|
||||
subtopics=(
|
||||
SubTopic(
|
||||
"onboarding", "Onboarding & enablement",
|
||||
(
|
||||
"Onboarding process — how long, training format, "
|
||||
"assessment?",
|
||||
"eLearning?",
|
||||
"QM integration?",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Topic(
|
||||
key="reporting_insights",
|
||||
title="Reporting & Insights",
|
||||
scope="Real-time & historical reporting, analytics, BI, CRM",
|
||||
minutes=10,
|
||||
subtopics=(
|
||||
SubTopic(
|
||||
"reporting", "Reporting",
|
||||
(
|
||||
"Real-time and historical — what are the key metrics you "
|
||||
"report on?",
|
||||
"Data source integration, dashboards, wallboards, agent & "
|
||||
"supervisor status.",
|
||||
),
|
||||
),
|
||||
SubTopic(
|
||||
"analytics_insights", "Analytics & insights",
|
||||
(
|
||||
"Integrated view of customer details and contact history? "
|
||||
"CRM?",
|
||||
"Does the contact centre collect and use customer insight? "
|
||||
"Predictive engagement?",
|
||||
"What data analysis do you perform? Analytics / BI team — how "
|
||||
"many people? BI platform?",
|
||||
"Executive-level reporting? Marketing team interlock?",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# ── Lookups ──────────────────────────────────────────────────────────
|
||||
TOPIC_BY_KEY: dict[str, Topic] = {t.key: t for t in TOPICS}
|
||||
TOPIC_KEYS: tuple[str, ...] = tuple(t.key for t in TOPICS)
|
||||
|
||||
|
||||
def topic(key: str) -> Topic:
|
||||
"""Return the topic with ``key`` (raises ``KeyError`` if unknown)."""
|
||||
return TOPIC_BY_KEY[key]
|
||||
Reference in New Issue
Block a user