feat: add GenesysCX study and fix Streamlit chart key collisions
- Add 202512_GenesysCX TEI study (config, seed data, notebooks, README) with NPV $10.8M / ROI 266% including AI-token cost line - Add explicit `key` parameter to all chart wrappers in app/components to prevent StreamlitDuplicateElementId errors when the same figure type renders across Summary/Benefits/Costs tabs - Render benefits bar and cost pie charts on their respective tabs - Add benefits_vs_costs_by_year chart wrapper
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
"""
|
||||
CTM default inputs and the Genesys meter catalogue.
|
||||
|
||||
⚠️ Site volumes/AHTs/costs outside NAM are PLACEHOLDERS flagged
|
||||
ESTIMATED — confirm with CTM data before client use. NAM volumes are
|
||||
from the CTM discovery pack. Named users across all sites total the
|
||||
contracted licence count (2,088).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .inputs import CostTakeout, FeatureScope, SiteInput
|
||||
from .meters import Confidence, MeterType, TokenMeter, TokenPricing
|
||||
from .rollout import RolloutPlan
|
||||
|
||||
# ── Platform ─────────────────────────────────────────────────────────
|
||||
|
||||
#: Genesys Cloud CX 3 named-user list rate, USD/user/month.
|
||||
#: Source: Genesys Cloud public pricing (CX 3 tier), planning figure.
|
||||
PLATFORM_RATE_PER_USER_MONTHLY = 111.28
|
||||
|
||||
#: CTM contracted named-user count — UI warns when site totals diverge.
|
||||
CONTRACTED_NAMED_USERS = 2_088
|
||||
|
||||
#: Business-case discount rate (CTM treasury planning assumption).
|
||||
DEFAULT_DISCOUNT_RATE = 0.08
|
||||
|
||||
#: One-off implementation estimate, amortized straight-line over the
|
||||
#: 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/"
|
||||
)
|
||||
|
||||
# ── Token meters ─────────────────────────────────────────────────────
|
||||
# Rates per the published Genesys AI Experience token tables unless
|
||||
# flagged otherwise. UNKNOWN meters carry working defaults (clearly
|
||||
# labelled) so the model still produces a range.
|
||||
|
||||
DEFAULT_METERS: dict[str, TokenMeter] = {
|
||||
m.feature: m
|
||||
for m in [
|
||||
TokenMeter(
|
||||
feature="Voice Bot",
|
||||
meter_type=MeterType.PER_MINUTE,
|
||||
units_per_token=17.0,
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Agentic Virtual Agent",
|
||||
meter_type=MeterType.PER_INTERACTION,
|
||||
units_per_token=0.833,
|
||||
tokens_per_unit=1.2,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="Agentic VA; 1.2 tokens per interaction.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="AI Summary & Insights",
|
||||
meter_type=MeterType.PER_SUMMARY,
|
||||
units_per_token=50.0,
|
||||
tokens_per_unit=0.02,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes=(
|
||||
"Supervisor standalone summarization; 50 summaries per token. "
|
||||
"NOT metered where Agent Copilot is assigned — see cost model."
|
||||
),
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
),
|
||||
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,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Social Listening",
|
||||
meter_type=MeterType.PER_MESSAGE,
|
||||
units_per_token=400.0,
|
||||
tokens_per_unit=0.0025,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="400 messages per token.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Social Responses",
|
||||
meter_type=MeterType.PER_MESSAGE,
|
||||
units_per_token=400.0,
|
||||
tokens_per_unit=0.0025,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="400 messages per token.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
),
|
||||
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,
|
||||
confidence=Confidence.CONFIRMED,
|
||||
notes="STA: 30 tokens per named user per month.",
|
||||
source_url=_GENESYS_TOKEN_FAQ,
|
||||
),
|
||||
TokenMeter(
|
||||
feature="Agent Copilot",
|
||||
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_FAQ,
|
||||
),
|
||||
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
|
||||
confidence=Confidence.UNKNOWN,
|
||||
notes="Rate not yet sourced. Working default 30 tokens/user/month.",
|
||||
),
|
||||
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
|
||||
confidence=Confidence.UNKNOWN,
|
||||
notes="Rate not yet sourced. Working default 20 tokens/user/month.",
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
#: Features metered per named user per month.
|
||||
PER_USER_FEATURES = [
|
||||
f for f, m in DEFAULT_METERS.items()
|
||||
if m.meter_type is MeterType.PER_USER_PER_MONTH
|
||||
]
|
||||
|
||||
# ── Token pricing ────────────────────────────────────────────────────
|
||||
# $1/token US list confirmed; other regions default to the same list
|
||||
# rate until regional figures are sourced (override in UI).
|
||||
|
||||
DEFAULT_PRICING: dict[str, TokenPricing] = {
|
||||
"US": TokenPricing(region="US", list_rate_per_token=1.0),
|
||||
"EU": TokenPricing(region="EU", list_rate_per_token=1.0), # TBD — assumed US list
|
||||
"AU": TokenPricing(region="AU", list_rate_per_token=1.0), # TBD — assumed US list
|
||||
"APAC": TokenPricing(region="APAC", list_rate_per_token=1.0), # TBD
|
||||
}
|
||||
|
||||
# ── CTM sites ────────────────────────────────────────────────────────
|
||||
# NAM figures from CTM discovery. ALL OTHER SITES + every AHT/ACW and
|
||||
# labour-cost figure are ESTIMATED placeholders — confirm with CTM.
|
||||
# Named users sum to CONTRACTED_NAMED_USERS (2,088).
|
||||
|
||||
_COMMON = {
|
||||
"voice_aht_seconds": 300, # placeholder — flag as estimate
|
||||
"email_aht_seconds": 600,
|
||||
"chat_aht_seconds": 480,
|
||||
"voice_acw_seconds": 60,
|
||||
}
|
||||
|
||||
CTM_DEFAULT_SITES: list[SiteInput] = [
|
||||
SiteInput(
|
||||
"NAM", "US", agents=890, supervisors=60, # split TBD
|
||||
voice_volume_monthly=1_214_358,
|
||||
email_volume_monthly=275_800,
|
||||
chat_volume_monthly=110,
|
||||
sms_volume_monthly=1_040,
|
||||
fully_loaded_agent_cost_annual=65_000, # placeholder
|
||||
fully_loaded_supervisor_cost_annual=95_000,
|
||||
languages=["English", "French", "Spanish"],
|
||||
**_COMMON,
|
||||
),
|
||||
SiteInput(
|
||||
"EMEA", "EU", agents=320, supervisors=25,
|
||||
voice_volume_monthly=420_000,
|
||||
email_volume_monthly=95_000,
|
||||
chat_volume_monthly=40,
|
||||
sms_volume_monthly=400,
|
||||
fully_loaded_agent_cost_annual=60_000,
|
||||
fully_loaded_supervisor_cost_annual=88_000,
|
||||
languages=["English", "French", "German", "Italian", "Spanish"],
|
||||
**_COMMON,
|
||||
),
|
||||
SiteInput(
|
||||
"AUZ", "AU", agents=180, supervisors=15,
|
||||
voice_volume_monthly=250_000,
|
||||
email_volume_monthly=56_000,
|
||||
chat_volume_monthly=25,
|
||||
sms_volume_monthly=250,
|
||||
fully_loaded_agent_cost_annual=70_000,
|
||||
fully_loaded_supervisor_cost_annual=100_000,
|
||||
languages=["English"],
|
||||
**_COMMON,
|
||||
),
|
||||
SiteInput(
|
||||
"APAC HK", "APAC", agents=120, supervisors=10,
|
||||
voice_volume_monthly=160_000,
|
||||
email_volume_monthly=38_000,
|
||||
chat_volume_monthly=15,
|
||||
sms_volume_monthly=150,
|
||||
fully_loaded_agent_cost_annual=55_000,
|
||||
fully_loaded_supervisor_cost_annual=80_000,
|
||||
languages=["English", "Cantonese", "Mandarin"],
|
||||
**_COMMON,
|
||||
),
|
||||
SiteInput(
|
||||
"APAC SG", "APAC", agents=110, supervisors=10,
|
||||
voice_volume_monthly=150_000,
|
||||
email_volume_monthly=34_000,
|
||||
chat_volume_monthly=15,
|
||||
sms_volume_monthly=120,
|
||||
fully_loaded_agent_cost_annual=55_000,
|
||||
fully_loaded_supervisor_cost_annual=80_000,
|
||||
languages=["English", "Mandarin", "Malay"],
|
||||
**_COMMON,
|
||||
),
|
||||
SiteInput(
|
||||
"APAC SH", "APAC", agents=130, supervisors=10,
|
||||
voice_volume_monthly=175_000,
|
||||
email_volume_monthly=40_000,
|
||||
chat_volume_monthly=15,
|
||||
sms_volume_monthly=130,
|
||||
fully_loaded_agent_cost_annual=35_000,
|
||||
fully_loaded_supervisor_cost_annual=55_000,
|
||||
languages=["Mandarin"],
|
||||
**_COMMON,
|
||||
),
|
||||
SiteInput(
|
||||
"APAC GZ", "APAC", agents=90, supervisors=8,
|
||||
voice_volume_monthly=120_000,
|
||||
email_volume_monthly=28_000,
|
||||
chat_volume_monthly=10,
|
||||
sms_volume_monthly=100,
|
||||
fully_loaded_agent_cost_annual=35_000,
|
||||
fully_loaded_supervisor_cost_annual=55_000,
|
||||
languages=["Mandarin", "Cantonese"],
|
||||
**_COMMON,
|
||||
),
|
||||
SiteInput(
|
||||
"APAC JP", "APAC", agents=60, supervisors=6,
|
||||
voice_volume_monthly=80_000,
|
||||
email_volume_monthly=19_000,
|
||||
chat_volume_monthly=8,
|
||||
sms_volume_monthly=80,
|
||||
fully_loaded_agent_cost_annual=60_000,
|
||||
fully_loaded_supervisor_cost_annual=85_000,
|
||||
languages=["Japanese"],
|
||||
**_COMMON,
|
||||
),
|
||||
SiteInput(
|
||||
"APAC TW", "APAC", agents=40, supervisors=4,
|
||||
voice_volume_monthly=54_000,
|
||||
email_volume_monthly=12_000,
|
||||
chat_volume_monthly=5,
|
||||
sms_volume_monthly=50,
|
||||
fully_loaded_agent_cost_annual=40_000,
|
||||
fully_loaded_supervisor_cost_annual=60_000,
|
||||
languages=["Mandarin"],
|
||||
**_COMMON,
|
||||
),
|
||||
]
|
||||
|
||||
ALL_SITE_NAMES = [s.site_name for s in CTM_DEFAULT_SITES]
|
||||
|
||||
# ── Cost takeouts ────────────────────────────────────────────────────
|
||||
|
||||
CTM_DEFAULT_TAKEOUTS: list[CostTakeout] = [
|
||||
CostTakeout(
|
||||
"NICE IEX (NAM)",
|
||||
annual_cost=1_300_000,
|
||||
start_year=1,
|
||||
start_month=7, # can only switch off after NAM go-live (month 6)
|
||||
confidence=Confidence.ESTIMATED,
|
||||
notes="Mid-band estimate; needs CTM contract confirmation.",
|
||||
),
|
||||
CostTakeout(
|
||||
"Legacy CC platform",
|
||||
annual_cost=0,
|
||||
start_year=2,
|
||||
confidence=Confidence.UNKNOWN,
|
||||
notes="Placeholder — populate once retirement scope is confirmed.",
|
||||
),
|
||||
]
|
||||
|
||||
# ── Default rollout & ramp ───────────────────────────────────────────
|
||||
# 12-month build. Genesys bills the licence commit from contract start;
|
||||
# the 6-month ramp gives a 50% first-year credit on the platform commit.
|
||||
# AI token usage (and benefits) start only when each region goes live.
|
||||
|
||||
CTM_DEFAULT_ROLLOUT = RolloutPlan(
|
||||
contract_start=None, # set when known — "Date Genesys starts billing"
|
||||
build_months=12,
|
||||
ramp_months=6,
|
||||
first_year_platform_discount=0.50,
|
||||
go_live_month={
|
||||
"NAM": 6,
|
||||
"EMEA": 9,
|
||||
"AUZ": 12,
|
||||
"APAC HK": 12,
|
||||
"APAC SG": 12,
|
||||
"APAC SH": 12,
|
||||
"APAC GZ": 12,
|
||||
"APAC JP": 12,
|
||||
"APAC TW": 12,
|
||||
},
|
||||
)
|
||||
|
||||
# ── Default feature scoping / phasing ────────────────────────────────
|
||||
# Phase = model year the feature switches on. Consumption features ramp
|
||||
# via adoption_curve; per-user licences are paid in full from the phase
|
||||
# year.
|
||||
|
||||
_RAMP = {1: 0.70, 2: 1.0, 3: 1.0}
|
||||
|
||||
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),
|
||||
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),
|
||||
]
|
||||
Reference in New Issue
Block a user