Token Calculator
This commit is contained in:
@@ -206,7 +206,7 @@ def calculate_sta_benefit(
|
||||
return _df(rows)
|
||||
|
||||
|
||||
def calculate_bot_deflection_benefit(
|
||||
def calculate_va_deflection_benefit(
|
||||
sites: list[SiteInput],
|
||||
feature_scope: FeatureScope,
|
||||
scenario: str | Scenario,
|
||||
@@ -214,41 +214,85 @@ def calculate_bot_deflection_benefit(
|
||||
params: str = "realistic",
|
||||
rollout: RolloutPlan | None = None,
|
||||
) -> pd.DataFrame:
|
||||
"""Agent labour avoided on calls deflected to Voice Bot / Agentic VA.
|
||||
"""Agent labour avoided on calls deflected to Voice Bot or Agentic VA.
|
||||
|
||||
Not in the original function list but required for a complete net
|
||||
case — deflected volume never reaches an agent, so the full AHT is
|
||||
avoided.
|
||||
**Layered (sequential) deflection model** — Voice Bot runs first on
|
||||
the full call pool; Agentic VA handles a share of the *residual*
|
||||
(calls the bot did not deflect). The two mechanisms are substitutes
|
||||
operating on the same call base, not independent additive benefits.
|
||||
|
||||
Effective total deflection:
|
||||
bot_rate + (1 − bot_rate) × va_rate
|
||||
e.g. 35% + 65% × 15% = 44.75% (not 50%)
|
||||
|
||||
**Three realization haircuts** are applied to convert raw deflected
|
||||
volume into realizable labour savings:
|
||||
|
||||
1. ``completion_rate`` — share of "deflected" calls that don't
|
||||
escalate to an agent mid-session (bot/VA fully handles the call).
|
||||
2. ``labour_realization`` — staffing flexibility factor; deflected
|
||||
volume doesn't reduce headcount 1:1 due to minimums, shrinkage,
|
||||
and occupancy ceilings.
|
||||
3. ``callback_discount`` — fraction of deflected calls that re-enter
|
||||
as repeat contacts (poorly-handled deflections drive callbacks).
|
||||
|
||||
Combined realistic factor: 0.70 × 0.80 × (1 − 0.05) ≈ 0.53
|
||||
|
||||
The ``params="claim"`` path sets all three factors to their
|
||||
``claim`` values (1.0 / 1.0 / 0.0) to reproduce the original
|
||||
Genesys ROI-doc figures for side-by-side comparison.
|
||||
"""
|
||||
sc = get_scenario(scenario) if isinstance(scenario, str) else scenario
|
||||
ro = rollout or NO_ROLLOUT
|
||||
realization = sc.realization(year)
|
||||
|
||||
# Realization haircuts — read from BENEFIT_PARAMS so claim/realistic
|
||||
# paths are consistent with all other benefit lines.
|
||||
completion_rate = _param("va_completion_rate", params)
|
||||
labour_real = _param("va_labour_realization", params)
|
||||
callback_disc = _param("va_callback_discount", params)
|
||||
realization_factor = completion_rate * labour_real * (1.0 - callback_disc)
|
||||
|
||||
rows = []
|
||||
for s in sites:
|
||||
if not feature_scope.active(s.site_name, year):
|
||||
continue
|
||||
|
||||
if feature_scope.feature == "Voice Bot":
|
||||
deflection = (
|
||||
# Bot operates on the full call pool.
|
||||
bot_rate = (
|
||||
feature_scope.deflection_target
|
||||
if feature_scope.deflection_target is not None
|
||||
else sc.voice_bot_deflection
|
||||
)
|
||||
deflected_calls = s.voice_volume_monthly * MONTHS_PER_YEAR * bot_rate
|
||||
|
||||
else: # Agentic Virtual Agent
|
||||
deflection = (
|
||||
# VA operates on the residual after the bot has deflected its share.
|
||||
# If Voice Bot is not in scope (VA-only deployment), bot_rate = 0
|
||||
# and the VA works on the full pool — still correct.
|
||||
bot_rate = sc.voice_bot_deflection
|
||||
va_rate = (
|
||||
feature_scope.deflection_target
|
||||
if feature_scope.deflection_target is not None
|
||||
else sc.agentic_va_deflection
|
||||
)
|
||||
seconds_saved = (
|
||||
s.voice_volume_monthly * MONTHS_PER_YEAR
|
||||
* deflection * s.voice_aht_seconds * realization
|
||||
)
|
||||
residual_calls = (
|
||||
s.voice_volume_monthly * MONTHS_PER_YEAR * (1.0 - bot_rate)
|
||||
)
|
||||
deflected_calls = residual_calls * va_rate
|
||||
|
||||
seconds_saved = deflected_calls * s.voice_aht_seconds * realization
|
||||
rows.append(
|
||||
{
|
||||
"benefit_line": f"{feature_scope.feature} deflection (labour avoided)",
|
||||
"scope": s.site_name,
|
||||
"annual_value": seconds_saved * s.agent_cost_per_second
|
||||
* ro.fraction_live(s.site_name, year),
|
||||
"annual_value": (
|
||||
seconds_saved
|
||||
* s.agent_cost_per_second
|
||||
* realization_factor
|
||||
* ro.fraction_live(s.site_name, year)
|
||||
),
|
||||
"confidence": Confidence.ESTIMATED.value,
|
||||
}
|
||||
)
|
||||
@@ -320,19 +364,30 @@ def calculate_predictive_routing_benefit(
|
||||
|
||||
|
||||
#: Which calculator handles which feature scope.
|
||||
#: Agent Copilot and STA exist in named/concurrent variants — both map
|
||||
#: to the same benefit calculators.
|
||||
#: Voice Bot and Agentic VA both route to calculate_va_deflection_benefit,
|
||||
#: which implements the layered sequential model — VA operates on the
|
||||
#: residual after the bot has deflected its share.
|
||||
_BENEFIT_DISPATCH = {
|
||||
"Agent Copilot": (
|
||||
"Agent Copilot [named]": (
|
||||
calculate_voice_handle_time_benefit,
|
||||
calculate_acw_summarization_benefit,
|
||||
),
|
||||
"Agent Copilot [concurrent]": (
|
||||
calculate_voice_handle_time_benefit,
|
||||
calculate_acw_summarization_benefit,
|
||||
),
|
||||
"AI Summary & Insights": (), # benefit carried by Copilot where present
|
||||
"Speech & Text Analytics": (calculate_sta_benefit,),
|
||||
"Voice Bot": (calculate_bot_deflection_benefit,),
|
||||
"Agentic Virtual Agent": (calculate_bot_deflection_benefit,),
|
||||
"Email AI (Auto-Respond)": (calculate_email_ai_benefit,),
|
||||
"Speech & Text Analytics [named]": (calculate_sta_benefit,),
|
||||
"Speech & Text Analytics [concurrent]": (calculate_sta_benefit,),
|
||||
"Voice Bot": (calculate_va_deflection_benefit,),
|
||||
"Agentic Virtual Agent": (calculate_va_deflection_benefit,),
|
||||
"Predictive Routing": (calculate_predictive_routing_benefit,),
|
||||
}
|
||||
|
||||
_COPILOT_FEATURES = {"Agent Copilot [named]", "Agent Copilot [concurrent]"}
|
||||
|
||||
|
||||
def calculate_total_benefit(
|
||||
sites: list[SiteInput],
|
||||
@@ -346,10 +401,18 @@ def calculate_total_benefit(
|
||||
"""All benefit lines for one scenario-year, aggregated per line.
|
||||
|
||||
Returns DataFrame: benefit_line, scope, annual_value, confidence.
|
||||
|
||||
Voice Bot and Agentic VA deflection benefits use the layered
|
||||
sequential model: the bot deflects from the full call pool; the VA
|
||||
deflects from the residual. The two features are NOT additive on
|
||||
the same base — see :func:`calculate_va_deflection_benefit`.
|
||||
"""
|
||||
sc = get_scenario(scenario) if isinstance(scenario, str) else scenario
|
||||
frames: list[pd.DataFrame] = []
|
||||
copilot_scope = _scope_for(feature_scopes, "Agent Copilot")
|
||||
# Find whichever Copilot variant is in scope (named or concurrent).
|
||||
copilot_scope = next(
|
||||
(s for s in feature_scopes if s.feature in _COPILOT_FEATURES), None
|
||||
)
|
||||
|
||||
for scope in feature_scopes:
|
||||
for fn in _BENEFIT_DISPATCH.get(scope.feature, ()): # type: ignore[arg-type]
|
||||
|
||||
@@ -7,8 +7,8 @@ Correctness rules implemented here (see spec §4.1):
|
||||
is enabled at a site, AI Summary & Insights consumption at that site
|
||||
is forced to zero — Copilot's per-user token rate already includes
|
||||
interaction summarization. Source: Genesys Cloud AI Experience
|
||||
tokens FAQ,
|
||||
https://help.mypurecloud.com/articles/genesys-cloud-ai-experience-tokens-faqs/
|
||||
token metering,
|
||||
https://help.genesys.cloud/articles/genesys-cloud-tokens-model/
|
||||
2. **Token rounding.** Genesys rounds consumption up at billing —
|
||||
``math.ceil`` is applied to each site's MONTHLY consumption token
|
||||
total before the rate. Per-user totals (users × tokens/user/month)
|
||||
@@ -133,12 +133,18 @@ def _monthly_units(site: SiteInput, feature: str, scope: FeatureScope,
|
||||
site.voice_volume_monthly * deflection * scenario.voice_bot_avg_minutes
|
||||
) # minutes
|
||||
if feature == "Agentic Virtual Agent":
|
||||
deflection = (
|
||||
# Layered model: VA operates on the residual volume after the voice bot
|
||||
# has already deflected its share. Cost base = residual × va_rate.
|
||||
# This is consistent with the benefit model and avoids double-counting
|
||||
# the same call pool across both deflection mechanisms.
|
||||
bot_deflection = scenario.voice_bot_deflection
|
||||
va_deflection = (
|
||||
scope.deflection_target
|
||||
if scope.deflection_target is not None
|
||||
else scenario.agentic_va_deflection
|
||||
)
|
||||
return site.voice_volume_monthly * deflection # interactions
|
||||
residual = site.voice_volume_monthly * (1.0 - bot_deflection)
|
||||
return residual * va_deflection # interactions
|
||||
if feature == "Virtual Agent (legacy)":
|
||||
deflection = scope.deflection_target or 0.0
|
||||
return site.voice_volume_monthly * deflection
|
||||
@@ -159,6 +165,11 @@ def _monthly_units(site: SiteInput, feature: str, scope: FeatureScope,
|
||||
if feature in ("Direct Messaging", "Social Listening", "Social Responses"):
|
||||
eligibility = scope.eligibility_pct if scope.eligibility_pct is not None else 1.0
|
||||
return (site.chat_volume_monthly + site.sms_volume_monthly) * eligibility
|
||||
if feature == "AI Translate":
|
||||
# Each voice interaction generates one translation; eligibility_pct
|
||||
# can be used to scope to a subset of interactions (e.g. non-English only).
|
||||
eligibility = scope.eligibility_pct if scope.eligibility_pct is not None else 1.0
|
||||
return site.voice_volume_monthly * eligibility # translations
|
||||
raise KeyError(f"No consumption-volume mapping for feature {feature!r}")
|
||||
|
||||
|
||||
@@ -260,11 +271,12 @@ def calculate_total_cost(
|
||||
|
||||
# Rule 1: Agent Copilot covers Supervisor AI Summary. Sites where
|
||||
# Copilot is active this year are excluded from AI Summary billing —
|
||||
# Copilot's 40 tokens/user/month already includes summarization.
|
||||
# https://help.mypurecloud.com/articles/genesys-cloud-ai-experience-tokens-faqs/
|
||||
# Copilot's per-user token rate already includes interaction summarization.
|
||||
# https://help.genesys.cloud/articles/genesys-cloud-tokens-model/
|
||||
_COPILOT_FEATURES = {"Agent Copilot [named]", "Agent Copilot [concurrent]"}
|
||||
copilot_sites: set[str] = set()
|
||||
for scope in feature_scopes:
|
||||
if scope.feature == "Agent Copilot":
|
||||
if scope.feature in _COPILOT_FEATURES:
|
||||
copilot_sites |= {
|
||||
s.site_name for s in sites if scope.active(s.site_name, year)
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ DEFAULT_DISCOUNT_RATE = 0.08
|
||||
#: analysis horizon in the P&L. ESTIMATED — confirm with delivery team.
|
||||
DEFAULT_IMPLEMENTATION_COST = 0.0
|
||||
|
||||
_GENESYS_TOKEN_FAQ = (
|
||||
"https://help.mypurecloud.com/articles/genesys-cloud-ai-experience-tokens-faqs/"
|
||||
_GENESYS_TOKEN_METERS = (
|
||||
"https://help.genesys.cloud/articles/genesys-cloud-tokens-model/"
|
||||
)
|
||||
|
||||
# ── Token meters ─────────────────────────────────────────────────────
|
||||
@@ -41,6 +41,7 @@ _GENESYS_TOKEN_FAQ = (
|
||||
DEFAULT_METERS: dict[str, TokenMeter] = {
|
||||
m.feature: m
|
||||
for m in [
|
||||
# ── Voice / Bot ───────────────────────────────────────────────
|
||||
TokenMeter(
|
||||
feature="Voice Bot",
|
||||
meter_type=MeterType.PER_MINUTE,
|
||||
@@ -48,16 +49,26 @@ DEFAULT_METERS: dict[str, TokenMeter] = {
|
||||
tokens_per_unit=1 / 17, # 0.0588
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="IVR self-service voice bot minutes; 17 min per token.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Digital Bot",
|
||||
meter_type=MeterType.PER_INTERACTION,
|
||||
units_per_token=51.0,
|
||||
tokens_per_unit=1 / 51, # 0.0196
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="Digital (non-voice) bot sessions; 51 sessions per token.",
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
# ── Virtual Agent ─────────────────────────────────────────────
|
||||
TokenMeter(
|
||||
feature="Virtual Agent (legacy)",
|
||||
meter_type=MeterType.PER_INTERACTION,
|
||||
units_per_token=2.0,
|
||||
tokens_per_unit=0.5,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="Legacy (non-agentic) virtual agent; 2 interactions per token.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
notes="Legacy (non-agentic) virtual agent; 0.5 tokens per interaction.",
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Agentic Virtual Agent",
|
||||
@@ -66,7 +77,42 @@ DEFAULT_METERS: dict[str, TokenMeter] = {
|
||||
tokens_per_unit=1.2,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="Agentic VA; 1.2 tokens per interaction.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
# ── Agent Copilot (named vs concurrent) ───────────────────────
|
||||
TokenMeter(
|
||||
feature="Agent Copilot [named]",
|
||||
meter_type=MeterType.PER_USER_PER_MONTH,
|
||||
units_per_token=0.0,
|
||||
tokens_per_unit=40.0,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes=(
|
||||
"40 tokens per named user per month. Includes interaction "
|
||||
"summarization (covers AI Summary & Insights)."
|
||||
),
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Agent Copilot [concurrent]",
|
||||
meter_type=MeterType.PER_USER_PER_MONTH,
|
||||
units_per_token=0.0,
|
||||
tokens_per_unit=60.0,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes=(
|
||||
"60 tokens per concurrent user per month. Includes interaction "
|
||||
"summarization (covers AI Summary & Insights)."
|
||||
),
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
# ── AI Quality / Analytics ────────────────────────────────────
|
||||
TokenMeter(
|
||||
feature="AI Scoring",
|
||||
meter_type=MeterType.PER_INTERACTION,
|
||||
units_per_token=20.0,
|
||||
tokens_per_unit=0.05,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="AI-scored quality evaluations; 20 evaluations per token.",
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="AI Summary & Insights",
|
||||
@@ -78,16 +124,50 @@ DEFAULT_METERS: dict[str, TokenMeter] = {
|
||||
"Supervisor standalone summarization; 50 summaries per token. "
|
||||
"NOT metered where Agent Copilot is assigned — see cost model."
|
||||
),
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
# ── Speech & Text Analytics (named vs concurrent) ─────────────
|
||||
TokenMeter(
|
||||
feature="Speech & Text Analytics [named]",
|
||||
meter_type=MeterType.PER_USER_PER_MONTH,
|
||||
units_per_token=0.0,
|
||||
tokens_per_unit=30.0,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="STA named licence; 30 tokens per named user per month.",
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Speech & Text Analytics [concurrent]",
|
||||
meter_type=MeterType.PER_USER_PER_MONTH,
|
||||
units_per_token=0.0,
|
||||
tokens_per_unit=45.0,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="STA concurrent licence; 45 tokens per concurrent user per month.",
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
# ── Routing / Engagement ──────────────────────────────────────
|
||||
TokenMeter(
|
||||
feature="Predictive Routing",
|
||||
meter_type=MeterType.PER_INTERACTION,
|
||||
units_per_token=17.0,
|
||||
tokens_per_unit=1 / 17, # 0.0588
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="Predictive routing; 17 routes per token.",
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
# ── Messaging ─────────────────────────────────────────────────
|
||||
TokenMeter(
|
||||
feature="Direct Messaging",
|
||||
meter_type=MeterType.PER_MESSAGE,
|
||||
units_per_token=400.0,
|
||||
tokens_per_unit=0.0025,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="FB/IG/WhatsApp messages; 400 messages per token.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
notes=(
|
||||
"Apple Messages for Business, Facebook Messenger, Instagram DM, "
|
||||
"WhatsApp, and X (Twitter) DM; 400 inbound or outbound messages "
|
||||
"per token. Additional carrier charges apply for WhatsApp and X."
|
||||
),
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Social Listening",
|
||||
@@ -95,8 +175,8 @@ DEFAULT_METERS: dict[str, TokenMeter] = {
|
||||
units_per_token=400.0,
|
||||
tokens_per_unit=0.0025,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="400 messages per token.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
notes="Genesys Cloud Social; 400 social post ingestions per channel per token.",
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Social Responses",
|
||||
@@ -104,53 +184,48 @@ DEFAULT_METERS: dict[str, TokenMeter] = {
|
||||
units_per_token=400.0,
|
||||
tokens_per_unit=0.0025,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="400 messages per token.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
notes="Social Post Responses; 400 outbound messages per channel per token.",
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
# ── Language / Translation ────────────────────────────────────
|
||||
TokenMeter(
|
||||
feature="Speech & Text Analytics",
|
||||
meter_type=MeterType.PER_USER_PER_MONTH,
|
||||
units_per_token=0.0, # n/a for per-user meters
|
||||
tokens_per_unit=30.0,
|
||||
feature="AI Translate",
|
||||
meter_type=MeterType.PER_INTERACTION,
|
||||
units_per_token=2.0,
|
||||
tokens_per_unit=0.5,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="STA: 30 tokens per named user per month.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
notes="AI translation; 2 translations per token.",
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
# ── Genesys Cloud Copilot ─────────────────────────────────────
|
||||
TokenMeter(
|
||||
feature="Agent Copilot",
|
||||
meter_type=MeterType.PER_USER_PER_MONTH,
|
||||
units_per_token=0.0,
|
||||
tokens_per_unit=40.0,
|
||||
feature="Genesys Cloud Copilot",
|
||||
meter_type=MeterType.PER_INTERACTION,
|
||||
units_per_token=20.0,
|
||||
tokens_per_unit=0.05,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes=(
|
||||
"40 tokens per named user per month. Includes interaction "
|
||||
"summarization (covers AI Summary & Insights)."
|
||||
"20 AI actions per token; Genesys Cloud knowledge queries "
|
||||
"are not charged."
|
||||
),
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
source_url=_GENESYS_TOKEN_METERS,
|
||||
),
|
||||
# ── Email AI (rates not yet published) ────────────────────────
|
||||
TokenMeter(
|
||||
feature="Email AI (Auto-Suggest)",
|
||||
meter_type=MeterType.PER_USER_PER_MONTH,
|
||||
units_per_token=0.0,
|
||||
tokens_per_unit=30.0, # TBD — working default
|
||||
tokens_per_unit=0.0,
|
||||
confidence=Confidence.UNKNOWN,
|
||||
notes="Rate not yet sourced. Working default 30 tokens/user/month.",
|
||||
notes="Requires Agent Copilot. Token rate not yet published.",
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Email AI (Auto-Respond)",
|
||||
meter_type=MeterType.PER_MESSAGE,
|
||||
units_per_token=2.0, # TBD
|
||||
tokens_per_unit=0.5, # TBD — working default
|
||||
confidence=Confidence.UNKNOWN,
|
||||
notes="Rate not yet sourced. Working default 0.5 tokens/message.",
|
||||
),
|
||||
TokenMeter(
|
||||
feature="AI Translate",
|
||||
meter_type=MeterType.PER_USER_PER_MONTH,
|
||||
units_per_token=0.0,
|
||||
tokens_per_unit=20.0, # TBD — working default
|
||||
tokens_per_unit=0.0,
|
||||
confidence=Confidence.UNKNOWN,
|
||||
notes="Rate not yet sourced. Working default 20 tokens/user/month.",
|
||||
notes="Feature not yet available; rate TBD.",
|
||||
),
|
||||
]
|
||||
}
|
||||
@@ -342,14 +417,13 @@ CTM_DEFAULT_FEATURE_SCOPES: list[FeatureScope] = [
|
||||
FeatureScope("Voice Bot", ALL_SITE_NAMES, phase=1, adoption_curve=_RAMP),
|
||||
FeatureScope("Agentic Virtual Agent", ["NAM", "EMEA"], phase=2,
|
||||
adoption_curve={2: 0.70, 3: 1.0}),
|
||||
FeatureScope("Speech & Text Analytics", ALL_SITE_NAMES, phase=1),
|
||||
FeatureScope("Agent Copilot", ALL_SITE_NAMES, phase=1),
|
||||
# CTM has named licences — use the [named] variant for both STA and Copilot.
|
||||
FeatureScope("Speech & Text Analytics [named]", ALL_SITE_NAMES, phase=1),
|
||||
FeatureScope("Agent Copilot [named]", ALL_SITE_NAMES, phase=1),
|
||||
FeatureScope("AI Summary & Insights", ALL_SITE_NAMES, phase=1,
|
||||
adoption_curve=_RAMP),
|
||||
FeatureScope("Direct Messaging", ALL_SITE_NAMES, phase=1, adoption_curve=_RAMP),
|
||||
FeatureScope("Email AI (Auto-Suggest)", ["NAM", "EMEA"], phase=2),
|
||||
FeatureScope("Email AI (Auto-Respond)", ["NAM", "EMEA"], phase=2,
|
||||
adoption_curve={2: 0.70, 3: 1.0}),
|
||||
FeatureScope("AI Translate",
|
||||
["APAC HK", "APAC SG", "APAC SH", "APAC GZ", "APAC JP", "APAC TW"],
|
||||
phase=3),
|
||||
|
||||
@@ -36,9 +36,15 @@ class SiteInput:
|
||||
voice_acw_seconds: int
|
||||
fully_loaded_agent_cost_annual: float
|
||||
fully_loaded_supervisor_cost_annual: float
|
||||
licence_type: str = "named" # "named" | "concurrent"
|
||||
languages: list[str] = field(default_factory=list)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.licence_type not in ("named", "concurrent"):
|
||||
raise ValueError(
|
||||
f"{self.site_name}: licence_type must be 'named' or 'concurrent', "
|
||||
f"got {self.licence_type!r}"
|
||||
)
|
||||
if self.agents < 0 or self.supervisors < 0:
|
||||
raise ValueError(f"{self.site_name}: agent/supervisor counts must be >= 0")
|
||||
for name in (
|
||||
|
||||
@@ -18,12 +18,28 @@ class Scenario:
|
||||
# ── Cost-side drivers ───────────────────────────────────────────
|
||||
voice_bot_deflection: float # share of voice volume deflected to bot
|
||||
voice_bot_avg_minutes: float # bot minutes per deflected call
|
||||
agentic_va_deflection: float # share of voice volume to agentic VA
|
||||
# Agentic VA deflection is INCREMENTAL — applied to the residual volume
|
||||
# after the voice bot has already handled its share (layered model).
|
||||
# Effective total deflection = bot_rate + (1 − bot_rate) × va_rate.
|
||||
agentic_va_deflection: float # share of RESIDUAL voice volume to agentic VA
|
||||
voice_summarization_eligibility: float
|
||||
voice_knowledge_eligibility: float
|
||||
email_auto_respond_rate: float # share of email auto-responded
|
||||
email_auto_suggest_acceptance: float
|
||||
|
||||
# ── Virtual Agent benefit realization factors ───────────────────
|
||||
# Applied to both Voice Bot and Agentic VA deflection benefits.
|
||||
# completion_rate — share of "deflected" calls that don't escalate to an agent
|
||||
# mid-session (bot/VA fully handles the interaction).
|
||||
# labour_realization — staffing flexibility: deflected volume doesn't reduce
|
||||
# headcount 1:1 due to minimums, shrinkage, occupancy ceilings.
|
||||
# callback_discount — fraction of deflected calls that re-enter as repeat contacts
|
||||
# (poorly-handled deflections drive callbacks).
|
||||
# Combined realistic factor: 0.70 × 0.80 × (1 − 0.05) ≈ 0.53
|
||||
va_completion_rate: float = 0.70
|
||||
va_labour_realization: float = 0.80
|
||||
va_callback_discount: float = 0.05
|
||||
|
||||
# year -> fraction of full benefit realized
|
||||
benefit_realization: dict[int, float] = field(default_factory=dict)
|
||||
|
||||
@@ -59,10 +75,16 @@ BENEFIT_PARAMS: dict[str, dict[str, float]] = {
|
||||
"digital_aht_reduction": {"claim": 0.18, "realistic": 0.085}, # 5-12% Y1
|
||||
"digital_acw_reduction": {"claim": 1.00, "realistic": 0.40}, # 30-50% Y1
|
||||
"sta_aht_reduction": {"claim": 0.04, "realistic": 0.015}, # 1-2% Y1
|
||||
"email_auto_suggest_time_saving": {"claim": 0.30, "realistic": 0.30}, # × acceptance
|
||||
"email_auto_suggest_time_saving": {"claim": 0.40, "realistic": 0.30}, # × acceptance; Genesys claims 40%
|
||||
# ESTIMATED lines (no Genesys claim published):
|
||||
"supervisor_copilot_time_saving": {"claim": 0.10, "realistic": 0.05},
|
||||
"predictive_routing_aht_reduction": {"claim": 0.04, "realistic": 0.02},
|
||||
# Virtual Agent realization factors.
|
||||
# ``claim`` = 100% realization (original model assumption — no haircuts).
|
||||
# ``realistic`` = production-calibrated midpoints per the spec analysis.
|
||||
"va_completion_rate": {"claim": 1.00, "realistic": 0.70}, # 60-75% voice bot; 50-70% agentic VA Y1
|
||||
"va_labour_realization": {"claim": 1.00, "realistic": 0.80}, # 70-85% staffing flexibility
|
||||
"va_callback_discount": {"claim": 0.00, "realistic": 0.05}, # 5-10% deflected re-enter as repeat contacts
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +98,11 @@ SCENARIOS: dict[str, Scenario] = {
|
||||
voice_knowledge_eligibility=0.40,
|
||||
email_auto_respond_rate=0.10,
|
||||
email_auto_suggest_acceptance=0.25,
|
||||
# VA realization: conservative — low completion, limited staffing flex
|
||||
# Combined: 0.60 × 0.70 × (1 − 0.05) ≈ 0.40
|
||||
va_completion_rate=0.60,
|
||||
va_labour_realization=0.70,
|
||||
va_callback_discount=0.05,
|
||||
benefit_realization={1: 0.30, 2: 0.60, 3: 0.80},
|
||||
),
|
||||
"realistic": Scenario(
|
||||
@@ -87,6 +114,11 @@ SCENARIOS: dict[str, Scenario] = {
|
||||
voice_knowledge_eligibility=0.60,
|
||||
email_auto_respond_rate=0.20,
|
||||
email_auto_suggest_acceptance=0.40,
|
||||
# VA realization: production midpoints per spec analysis
|
||||
# Combined: 0.70 × 0.80 × (1 − 0.05) ≈ 0.53
|
||||
va_completion_rate=0.70,
|
||||
va_labour_realization=0.80,
|
||||
va_callback_discount=0.05,
|
||||
benefit_realization={1: 0.50, 2: 0.80, 3: 0.95},
|
||||
),
|
||||
"stretch": Scenario(
|
||||
@@ -98,6 +130,11 @@ SCENARIOS: dict[str, Scenario] = {
|
||||
voice_knowledge_eligibility=0.80,
|
||||
email_auto_respond_rate=0.50,
|
||||
email_auto_suggest_acceptance=0.60,
|
||||
# VA realization: optimistic — high completion, good staffing flexibility
|
||||
# Combined: 0.75 × 0.85 × (1 − 0.03) ≈ 0.62
|
||||
va_completion_rate=0.75,
|
||||
va_labour_realization=0.85,
|
||||
va_callback_discount=0.03,
|
||||
benefit_realization={1: 0.75, 2: 0.95, 3: 1.00},
|
||||
),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user