116 lines
3.7 KiB
Python
116 lines
3.7 KiB
Python
"""Engagement data — SHAPE pins only.
|
|
|
|
The master ships placeholders; an engagement copy fills them in. So these
|
|
tests pin the *shape* of the cell and never its emptiness — asserting that
|
|
``client`` is blank would fail the moment the copy is used for real, which
|
|
is precisely backwards.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import nbformat
|
|
|
|
from tests.conftest import NOTEBOOK
|
|
|
|
IDENTITY_KEYS = {"client", "prepared_date", "prepared_by"}
|
|
|
|
VOLUME_KEYS = {
|
|
"voice_inbound_monthly",
|
|
"voice_outbound_monthly",
|
|
"digital_sessions_monthly",
|
|
"email_monthly",
|
|
"messaging_monthly",
|
|
"social_posts_monthly",
|
|
"social_responses_monthly",
|
|
"translations_monthly",
|
|
"evaluations_monthly",
|
|
"ai_actions_monthly",
|
|
"routed_interactions_monthly",
|
|
}
|
|
|
|
COMMERCIAL_KEYS = {
|
|
"agent_users",
|
|
"licence_model",
|
|
"currency",
|
|
"contracted_rate_per_token",
|
|
"concession_pct",
|
|
}
|
|
|
|
TTS_KEYS = {"tts_chars_per_call", "tts_voice_tier"}
|
|
|
|
|
|
def test_engagement_shape(engagement: dict[str, Any]) -> None:
|
|
assert set(engagement) == (
|
|
IDENTITY_KEYS | VOLUME_KEYS | COMMERCIAL_KEYS | TTS_KEYS
|
|
)
|
|
|
|
|
|
def test_identity_fields_are_strings(engagement: dict[str, Any]) -> None:
|
|
"""Blank in the master, filled in the copy — either way, strings."""
|
|
for key in IDENTITY_KEYS:
|
|
assert isinstance(engagement[key], str)
|
|
|
|
|
|
def test_volumes_are_non_negative_numbers(engagement: dict[str, Any]) -> None:
|
|
for key in VOLUME_KEYS:
|
|
value = engagement[key]
|
|
assert isinstance(value, (int, float)) and not isinstance(value, bool)
|
|
assert value >= 0, f"{key} must not be negative"
|
|
|
|
|
|
def test_commercial_terms_are_well_formed(engagement: dict[str, Any]) -> None:
|
|
assert isinstance(engagement["agent_users"], int)
|
|
assert engagement["agent_users"] >= 0
|
|
assert engagement["licence_model"] in ("named", "concurrent")
|
|
assert isinstance(engagement["currency"], str) and engagement["currency"]
|
|
assert 0.0 <= float(engagement["concession_pct"]) < 1.0
|
|
contracted = engagement["contracted_rate_per_token"]
|
|
assert contracted is None or float(contracted) >= 0
|
|
|
|
|
|
def test_currency_is_one_the_rate_card_publishes(engagement: dict[str, Any]) -> None:
|
|
from genesyscalc.ratecard import LIST_PRICE_BY_CURRENCY
|
|
|
|
assert engagement["currency"] in LIST_PRICE_BY_CURRENCY
|
|
|
|
|
|
def test_tts_settings(engagement: dict[str, Any]) -> None:
|
|
from genesyscalc.tts import TTS_PRICE_PER_MILLION_CHARS
|
|
|
|
assert isinstance(engagement["tts_chars_per_call"], int)
|
|
assert engagement["tts_chars_per_call"] >= 0
|
|
assert engagement["tts_voice_tier"] in TTS_PRICE_PER_MILLION_CHARS
|
|
|
|
|
|
def test_master_carries_no_client_identity(engagement: dict[str, Any]) -> None:
|
|
"""The one emptiness check that IS correct: masters stay client-clean.
|
|
|
|
This guards the master in THIS repo. An engagement copy lives outside
|
|
Palladium, so it never runs this suite — filling the cell there does not
|
|
break anything (CLAUDE.md § Confidentiality).
|
|
"""
|
|
assert engagement["client"] == "", (
|
|
"a client name in the master means the copy-out step was skipped — "
|
|
"copy the directory out of Palladium before entering client data"
|
|
)
|
|
|
|
|
|
def test_engagement_cell_sits_above_the_widget_cell() -> None:
|
|
"""Client data must not re-run on a sidebar change (Mercury re-runs only
|
|
cells below a changed widget)."""
|
|
nb = nbformat.read(NOTEBOOK, as_version=4)
|
|
eng = [
|
|
i
|
|
for i, c in enumerate(nb.cells)
|
|
if "engagement-data" in c.metadata.get("tags", [])
|
|
]
|
|
widgets = [
|
|
i
|
|
for i, c in enumerate(nb.cells)
|
|
if c.cell_type == "code" and "mr.Select(" in c.source
|
|
]
|
|
assert len(eng) == 1 and widgets
|
|
assert eng[0] < min(widgets)
|