Files
palladium/calculators/Genesys_Token_Calculator/genesyscalc/ratecard.py

359 lines
11 KiB
Python

"""THE VERBATIM ANCHOR — Genesys Cloud tokens model, as published.
Transcribed from
https://help.genesys.cloud/articles/genesys-cloud-tokens-model/
**last updated 2026-07-12**. Feature names and rate strings below are the
article's own wording, character for character.
VERBATIM — DO NOT EDIT. A vendor republication is a deliberate
**re-anchoring**, not a patch (docs/Calculator_Pattern_V1-00.md):
1. diff the published table against this file
2. update the meters AND bump RATE_CARD_SOURCE_DATE in the same edit
3. update the pins in tests/test_rate_card.py in the same commit
4. re-execute the notebook
5. report the cost moves honestly
6. Show-first — the user sees the rate diff before it lands
Never partially re-anchor; never bump the date without the pins.
Negotiated or contracted values NEVER land here. They are the overlay, and
they live in the notebook's ``engagement-data`` cell — this file is the
vendor's record, not the client's deal.
"""
from __future__ import annotations
from .meters import LicenceModel, Meter, MeterBasis, Tier
RATE_CARD_SOURCE: str = "https://help.genesys.cloud/articles/genesys-cloud-tokens-model/"
RATE_CARD_SOURCE_DATE: str = "2026-07-12"
# Every meter below is published on the same page on the same date, so the
# source pair is spelled out once here and passed explicitly. Explicit beats
# a splat: this file is diffed against the article by eye.
_URL = RATE_CARD_SOURCE
_DATE = RATE_CARD_SOURCE_DATE
#: The published meter table, verbatim. Twenty rows, in the article's order.
METERS_VERBATIM: tuple[Meter, ...] = (
Meter(
key="bots_voice",
feature="Bots (Voice)",
published_rate="17 minutes per token",
basis=MeterBasis.VOICE_MINUTES,
rate=1.0 / 17.0,
tier=Tier.BOT,
unit_label="bot minutes",
note=(
"Genesys rounds up each call to the next 15-second increment "
"(clarified 2026-07-12)."
),
source_url=_URL,
source_date=_DATE,
),
Meter(
key="bots_digital",
feature="Bots (Digital)",
published_rate="51 sessions per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 51.0,
tier=Tier.BOT,
unit_label="sessions",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="virtual_agent",
feature="Virtual Agent",
published_rate="0.5 tokens per Virtual Agent interaction",
basis=MeterBasis.TOKENS_PER_UNIT,
rate=0.5,
tier=Tier.VIRTUAL_AGENT,
unit_label="interactions",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="agentic_virtual_agent",
feature="Agentic Virtual Agent",
published_rate="1.2 tokens per interaction",
basis=MeterBasis.TOKENS_PER_UNIT,
rate=1.2,
tier=Tier.AGENTIC_VIRTUAL_AGENT,
unit_label="interactions",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="agent_copilot_named",
feature="Agent Copilot [named]",
published_rate="40 tokens per user",
basis=MeterBasis.PER_USER_PER_MONTH,
per_user_named=40.0,
per_user_concurrent=60.0,
unit_label="users",
note="Enabling Copilot makes Supervisor summaries and insights free.",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="agent_copilot_concurrent",
feature="Agent Copilot [concurrent]",
published_rate="60 tokens per user",
basis=MeterBasis.PER_USER_PER_MONTH,
per_user_named=40.0,
per_user_concurrent=60.0,
unit_label="users",
note="The concurrent-licence face of the same meter.",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="ai_scoring",
feature="AI Scoring",
published_rate="20 evaluations per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 20.0,
unit_label="evaluations",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="ai_translate",
feature="AI Translate",
published_rate="2 translations per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 2.0,
unit_label="translations",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="ai_summary_and_insights",
feature="AI Summary and Insights",
published_rate="50 summaries/insights per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 50.0,
unit_label="summaries",
note=(
"Free when Agent Copilot is enabled — see "
"COPILOT_COVERS_SUMMARY_RULE."
),
source_url=_URL,
source_date=_DATE,
),
Meter(
key="apple_messages_for_business",
feature="Apple Messages for Business",
published_rate="400 inbound or outbound messages per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 400.0,
unit_label="messages",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="facebook_messenger",
feature="Facebook Messenger",
published_rate="400 messages per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 400.0,
unit_label="messages",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="instagram_direct_messaging",
feature="Instagram Direct Messaging",
published_rate="400 messages per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 400.0,
unit_label="messages",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="whatsapp_messaging",
feature="WhatsApp Messaging",
published_rate="400 messages per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 400.0,
unit_label="messages",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="x_direct_messaging",
feature="X Direct Messaging",
published_rate="400 messages per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 400.0,
unit_label="messages",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="genesys_cloud_social",
feature="Genesys Cloud Social",
published_rate="400 social post ingestions per channel per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 400.0,
unit_label="post ingestions",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="social_post_responses",
feature="Social Post Responses",
published_rate="400 outbound messages per channel per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 400.0,
unit_label="outbound messages",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="predictive_routing",
feature="Predictive Routing",
published_rate="17 routes per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 17.0,
unit_label="routes",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="speech_and_text_analytics_named",
feature="Speech and Text Analytics [named]",
published_rate="30 tokens per user",
basis=MeterBasis.PER_USER_PER_MONTH,
per_user_named=30.0,
per_user_concurrent=45.0,
unit_label="users",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="speech_and_text_analytics_concurrent",
feature="Speech and Text Analytics [concurrent]",
published_rate="45 tokens per user",
basis=MeterBasis.PER_USER_PER_MONTH,
per_user_named=30.0,
per_user_concurrent=45.0,
unit_label="users",
note="The concurrent-licence face of the same meter.",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="genesys_cloud_copilot",
feature="Genesys Cloud Copilot",
published_rate="20 AI actions per token",
basis=MeterBasis.UNITS_PER_TOKEN,
rate=1.0 / 20.0,
unit_label="AI actions",
note="Genesys Cloud knowledge queries are not charged.",
source_url=_URL,
source_date=_DATE,
),
)
#: Published as included at no token cost. Shown on stage as explicit $0
#: lines — a zero the vendor put in writing is a finding, not an omission.
ZERO_RATED_VERBATIM: tuple[Meter, ...] = (
Meter(
key="predictive_engagement",
feature="Predictive Engagement",
published_rate="No charge for token usage",
basis=MeterBasis.ZERO_RATED,
unit_label="interactions",
source_url=_URL,
source_date=_DATE,
),
Meter(
key="knowledge_queries",
feature="Genesys Cloud knowledge queries",
published_rate="no charge for Genesys Cloud knowledge queries",
basis=MeterBasis.ZERO_RATED,
unit_label="queries",
source_url=_URL,
source_date=_DATE,
),
)
#: "Named organizations receive 250 tokens; concurrent organizations receive
#: 350 tokens" per month.
FREE_TOKENS_PER_MONTH: dict[LicenceModel, int] = {
LicenceModel.NAMED: 250,
LicenceModel.CONCURRENT: 350,
}
#: "These tokens renew each month and do not carry over to future months."
ALLOWANCE_CARRIES_OVER: bool = False
#: "Genesys rounds up each call to the next 15-second increment."
#: Clarified in the 2026-07-12 revision. Applied PER CALL, then summed.
VOICE_BOT_ROUNDUP_SECONDS: int = 15
#: The published highest-tier rule, verbatim. Renders on stage.
HIGHEST_TIER_RULE: str = (
"In cases where an interaction uses multiple AI resources, such as bot "
"flows, virtual agents, and agentic virtual agents, Genesys bases charges "
"on the highest tier (price) resource that Genesys Cloud uses during the "
"interaction."
)
#: The published Copilot exclusion, verbatim. Renders on stage.
COPILOT_COVERS_SUMMARY_RULE: str = (
"if you enable Agent Copilot simultaneously, then Supervisor Copilot "
"summaries and insights do not consume tokens"
)
#: List price per token. The article points to the pricing hub and cites
#: "USD 1.00 to JPY 120 per token by currency".
LIST_PRICE_BY_CURRENCY: dict[str, float] = {"USD": 1.00, "JPY": 120.0}
#: Every meter by key — the consumption table plus the zero-rated lines.
METERS: dict[str, Meter] = {
m.key: m for m in METERS_VERBATIM + ZERO_RATED_VERBATIM
}
def meter(key: str) -> Meter:
"""Look up a published meter, with a useful error when the key is wrong."""
try:
return METERS[key]
except KeyError:
raise KeyError(
f"{key!r} is not a published Genesys meter. Known keys: "
f"{', '.join(sorted(METERS))}"
) from None
def per_user_meter(licence: LicenceModel, family: str) -> Meter:
"""The Copilot / STA meter face matching ``licence``.
``family`` is ``"agent_copilot"`` or ``"speech_and_text_analytics"``.
Both rates live on both faces, so this is presentation sugar — it picks
the row whose published wording matches the licence being modelled.
"""
return meter(f"{family}_{licence.value}")
def rate_card_rows() -> list[dict[str, str]]:
"""The published table as display rows, in publication order."""
return [
{
"Feature": m.feature,
"Published rate": m.published_rate,
"Confidence": m.confidence.icon,
"Source": m.source_date or "",
}
for m in METERS_VERBATIM + ZERO_RATED_VERBATIM
]