Mercury Genesys Token Calculator
This commit is contained in:
264
docs/Calculator_Pattern_V1-00.md
Normal file
264
docs/Calculator_Pattern_V1-00.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Calculator Pattern v1.0.0
|
||||
|
||||
Standardizes **Calculators** — Palladium masters that price a vendor's product
|
||||
from that vendor's own **published rate card**: a verbatim rate-card anchor,
|
||||
a client overlay in tagged cells, and a re-anchoring protocol for when the
|
||||
vendor republishes.
|
||||
|
||||
## 🐾 Red Panda Approval™
|
||||
|
||||
This pattern follows Red Panda Approval standards (see `CLAUDE.md` for the
|
||||
rubric).
|
||||
|
||||
**Audience note:** written to be loaded whole as context by an LLM agent
|
||||
building or modifying a Calculator. Rules are imperative (MUST/SHOULD/NEVER),
|
||||
each with a one-line *why*. It **specializes**
|
||||
[`Mercury_Notebook_Pattern_V1-00.md`](Mercury_Notebook_Pattern_V1-00.md) —
|
||||
read that first; the shared mechanics (reactivity, widget-pairs, gate,
|
||||
staging, appendix, packaging) are **not** restated here. `CLAUDE.md` is the
|
||||
always-on contract and takes precedence where documents disagree.
|
||||
|
||||
Reference implementation:
|
||||
[`calculators/Genesys_Token_Calculator/`](../calculators/Genesys_Token_Calculator/).
|
||||
|
||||
---
|
||||
|
||||
## What a Calculator is — and why it's a third kind
|
||||
|
||||
| | Study | Assessment | **Calculator** |
|
||||
|---|---|---|---|
|
||||
| Anchored to | a **dated publication** | — (it *is* the instrument) | a **living rate card** |
|
||||
| Naming | `YYYYMM_…` dated | `Instrument_Name` undated | `Vendor_Subject_Calculator` undated |
|
||||
| Anchor lifetime | immutable **forever** | n/a | immutable **until the vendor republishes** |
|
||||
| Its "numbers" | NPV / ROI / payback | status & progress | **run-rate, $/unit, per-feature cost** |
|
||||
| Lives in | `studies/` | `assessments/` | `calculators/` |
|
||||
|
||||
The distinguishing property is the **anchor's lifetime**. A Study's anchor is
|
||||
frozen forever — the December 2025 Forrester PDF will never change, so a dated
|
||||
directory name is honest. A Calculator's anchor is frozen *until the vendor
|
||||
republishes*, at which point updating it is a deliberate, test-breaking,
|
||||
dated act. A `YYYYMM_` prefix would start lying the moment the vendor updated
|
||||
their page, so **Calculators are undated**; the publication date lives inside
|
||||
the anchor (`RATE_CARD_SOURCE_DATE`), pinned by a test and shown on stage.
|
||||
|
||||
> **Note.** Mercury Pattern *Variant 3 — Exploratory calculator* describes the
|
||||
> pre-master form of this: a what-if surface inside a study. This pattern
|
||||
> promotes it to a first-class master type. A Variant-3 notebook that outlives
|
||||
> its study should graduate into `calculators/`.
|
||||
|
||||
---
|
||||
|
||||
## Required Structure & Contracts
|
||||
|
||||
Everything the Mercury pattern requires, **plus** the four below.
|
||||
|
||||
### 1 · The rate card is a verbatim anchor, and it lives in `.py`
|
||||
|
||||
`CLAUDE.md` says content never lives in `.py`. A published vendor rate card is
|
||||
**not content**, and the boundary is who authors and edits the thing:
|
||||
|
||||
> **In `.py` (the engine):** the vendor's published rate card — verbatim, with
|
||||
> a source URL and a source date. A third-party record nobody in this repo
|
||||
> authors. **In tagged notebook cells:** everything the consultant authors or
|
||||
> the client supplies — volumes, headcount, licence model, the **negotiated**
|
||||
> rate, feature enablement, adoption assumptions, scenario definitions, and
|
||||
> the narrative.
|
||||
|
||||
*Why:* the content rule exists because *"the notebook is the document the
|
||||
consultant edits"*. Nobody edits the vendor's rate card — editing it is
|
||||
**forbidden**, and changing it requires a republication plus the re-anchoring
|
||||
protocol below. Putting an immutable anchor on the surface designated as
|
||||
editable invites exactly the casual edit the anchor rule prevents, and makes
|
||||
the most-likely-wrong numbers checkable only via a notebook parse. Precedent:
|
||||
`studies/202602_TEI_Amazon_Connect/teicalc/anchor.py` holds Forrester's
|
||||
published tables the same way.
|
||||
|
||||
**Corollary that has teeth:** the *contracted overlay* is **client data** and
|
||||
belongs in the `engagement-data` cell, never in `.py`. A Calculator's engine
|
||||
MUST ship **no client volumes at all** — no `defaults.py` full of real sites.
|
||||
(`studies/202607_CTM_GenesysCX/tokencalc/defaults.py` does exactly that; it is
|
||||
the anti-precedent, not the model.)
|
||||
|
||||
The anchor module MUST carry:
|
||||
|
||||
```python
|
||||
RATE_CARD_SOURCE: str # the URL
|
||||
RATE_CARD_SOURCE_DATE: str # the publication's own "last updated" date
|
||||
METERS_VERBATIM: tuple[Meter, ...] # feature wording AND rate string, exact
|
||||
```
|
||||
|
||||
Each meter keeps the vendor's **exact wording** (`feature`) and **exact rate
|
||||
string** (`published_rate`) beside the parsed number. *Why:* the string is
|
||||
what makes a transcription slip catchable — a test pins the strings, and a
|
||||
second test proves the float still agrees with the string beside it.
|
||||
|
||||
### 2 · The `topic-bank` cell holds the catalogue, not the rates
|
||||
|
||||
Since the rates are in the engine, the required `topic-bank` cell holds what a
|
||||
consultant genuinely authors: the **feature catalogue** — per capability, a
|
||||
client-facing title, a one-line description, and the **sizing question to
|
||||
ask**. Each `key` MUST match a published meter key, pinned by a test. *Why:*
|
||||
this keeps the tag taxonomy honest rather than tag-stuffed, and the cross-layer
|
||||
pin catches a rename that would otherwise silently unprice a feature.
|
||||
|
||||
### 3 · Costs the vendor bills outside the primary meter are a separate line
|
||||
|
||||
A vendor usually bills something adjacent on a different basis (here: Enhanced
|
||||
TTS, per character rather than per token). Such a line MUST have its **own
|
||||
source URL and date**, its own confidence flag, and its own subtotal — and the
|
||||
grand total MUST show the components labelled, never one blended figure.
|
||||
*Why:* the headline is understated without it and wrong with it silently
|
||||
folded in; a reader must be able to see which meter produced which dollar.
|
||||
|
||||
### 4 · Every published rule that changes the arithmetic is modelled and reported
|
||||
|
||||
Vendors bury billing rules in prose. Each one that moves the number MUST be
|
||||
enforced in the engine, pinned with hand-checked arithmetic, and **surfaced in
|
||||
the result** (a `warnings` tuple), not applied silently. *Why:* a total that
|
||||
shrank for a published reason nobody can see is indistinguishable from a bug.
|
||||
In the reference implementation these are the per-call round-up, the free
|
||||
monthly allowance, the highest-tier rule, and the Copilot exclusion.
|
||||
|
||||
---
|
||||
|
||||
## The Re-anchoring Protocol
|
||||
|
||||
The section unique to this kind. A Calculator tracks a *living* rate card, so
|
||||
updating the anchor is expected — but never casual. Re-anchoring is
|
||||
**Show-first** (`CLAUDE.md` § Risk tier), unlike editing a Study anchor, which
|
||||
is forbidden outright.
|
||||
|
||||
When the vendor republishes:
|
||||
|
||||
1. **Diff** the published table against the anchor module.
|
||||
2. **Update the meters AND bump `RATE_CARD_SOURCE_DATE` in the same edit.**
|
||||
3. **Update the test pins in the same commit.**
|
||||
4. **Re-execute** the notebook; re-pin the gate's live-state numbers if the
|
||||
defaults moved.
|
||||
5. **Report the cost moves honestly** — say what got more expensive.
|
||||
6. **Show the user the rate diff** and the KPI move before it lands.
|
||||
|
||||
> Never partially re-anchor. Never bump the date without the pins. A date that
|
||||
> claims a rate card the numbers don't match is worse than a stale one, because
|
||||
> it is stale *and* it lies about it.
|
||||
|
||||
---
|
||||
|
||||
## Standard Choice Values
|
||||
|
||||
Beyond the Mercury pattern's confidence legend (🟢 confirmed / 🟡 estimated /
|
||||
🔴 unknown), Calculators standardize:
|
||||
|
||||
```python
|
||||
class Confidence(Enum): # 🟢 requires source_url AND source_date — enforced
|
||||
CONFIRMED = "confirmed"
|
||||
ESTIMATED = "estimated"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
class LicenceModel(Enum): # vendors price per-seat features by licence model
|
||||
NAMED = "named"
|
||||
CONCURRENT = "concurrent"
|
||||
```
|
||||
|
||||
A 🟢 meter that carries no source URL and date MUST fail to construct. *Why:*
|
||||
it is the one invariant that stops an unsourced number wearing a green tick on
|
||||
a client-facing stage.
|
||||
|
||||
---
|
||||
|
||||
## Recommended Practices
|
||||
|
||||
- **The list → negotiated walk.** Carry `list_cost` beside the effective cost
|
||||
and show both. The client sees MSRP → their deal explicitly (the Mercury
|
||||
pattern's deck-frame column, applied to pricing).
|
||||
- **Published zeroes are findings.** A feature the vendor publishes as free
|
||||
renders as an explicit `$0` line with the quoted wording — not an omission.
|
||||
- **Adoption as a range.** Adoption is the largest uncertainty in any
|
||||
consumption estimate; ship conservative / base / aggressive rather than a
|
||||
single point.
|
||||
- **A `$/unit` figure.** Cost per interaction is the number a client remembers
|
||||
and the one that survives a change of volume assumptions.
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- ❌ **Don't build a business case in a Calculator.** NPV, payback, ROI, a
|
||||
benefit model, a P&L — any of these means the work has become a **Study**.
|
||||
Move it, don't grow it here.
|
||||
- ❌ **Don't edit `RATE_CARD_VERBATIM` to reflect a negotiated rate.** That is
|
||||
the overlay, and it belongs in `engagement-data`. The anchor is what the
|
||||
vendor published, not what the client pays.
|
||||
- ❌ **Don't bump the source date without updating the pins** (see the
|
||||
protocol).
|
||||
- ❌ **Don't ship a rate without a source URL and a confidence flag** — the
|
||||
schema should make this impossible.
|
||||
- ❌ **Don't put engagement volumes in the engine.** Masters stay client-clean;
|
||||
an engine with real client sites in it cannot be shared.
|
||||
- ❌ **Don't fold a differently-metered cost into the headline meter's
|
||||
subtotal** — separate line, separate source, labelled components.
|
||||
- ❌ **Don't model a per-unit round-up by averaging first.** Round per billing
|
||||
event, then sum; averaging first understates short events, sometimes by
|
||||
multiples.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
The Mercury pattern's layers 0–4, plus **layer 1a — the rate-card tripwire**,
|
||||
which is the highest-value test in a Calculator:
|
||||
|
||||
```python
|
||||
def test_published_meter_table_verbatim():
|
||||
"""Exact feature wording AND exact rate string, every row.
|
||||
|
||||
A transcription slip or an un-noticed republication breaks the build
|
||||
rather than quietly moving a client-facing number.
|
||||
"""
|
||||
assert {m.feature: m.published_rate for m in METERS_VERBATIM} == PUBLISHED
|
||||
|
||||
def test_source_pinned():
|
||||
assert RATE_CARD_SOURCE_DATE == "2026-07-12"
|
||||
|
||||
def test_numeric_rates_match_their_published_strings():
|
||||
"""Catches a float drifting away from the wording beside it."""
|
||||
|
||||
def test_every_confirmed_meter_carries_source_url_and_date(): ...
|
||||
```
|
||||
|
||||
Then **hand-check the arithmetic before pinning it** — compute each published
|
||||
rule's effect by hand, write the working into the test's docstring, and pin
|
||||
the result:
|
||||
|
||||
```python
|
||||
def test_voice_bot_15_second_roundup():
|
||||
"""1,000 calls at a 40s average.
|
||||
|
||||
ceil(40/15) = 3 increments = 45s = 0.75 min/call → 750.0 min/month.
|
||||
The naive 40/60 × 1,000 = 666.67 understates by 12.5%.
|
||||
"""
|
||||
assert voice_bot_billable_minutes(VoiceBotUsage(1_000, 40)) == pytest.approx(750.0)
|
||||
```
|
||||
|
||||
The in-notebook **gate** additionally asserts *structural ties that hold at any
|
||||
widget setting* — the allowance identity, effective ≤ list, components summing
|
||||
to the grand total, and each published exclusion — so a stakeholder moving a
|
||||
slider cannot produce an incoherent number.
|
||||
|
||||
---
|
||||
|
||||
## Adding a Calculator
|
||||
|
||||
1. Copy `calculators/Genesys_Token_Calculator/` and rename it
|
||||
`Vendor_Subject_Calculator` (underscores — directories are Python packages).
|
||||
2. Rename the engine package; keep `staging.py` byte-for-byte.
|
||||
3. Replace the anchor module with the new vendor's published table —
|
||||
**verbatim**, with its own source URL and date — and rewrite the rate-card
|
||||
tripwire against it.
|
||||
4. Rewrite the `topic-bank` catalogue and reset `engagement-data` to
|
||||
placeholders.
|
||||
5. Register the notebook in [`tests/nbcheck.py`](../tests/nbcheck.py)
|
||||
(`NOTEBOOK_FIRST`) — the completeness test fails until you do.
|
||||
6. Run the full verification: `pytest`, `mypy`, headless `nbconvert --execute`,
|
||||
`python scripts/export_report.py`, a stage simulation, and an actual
|
||||
`mercury --working-dir .` render.
|
||||
Reference in New Issue
Block a user