Files
palladium/docs/Calculator_Pattern_V1-00.md

265 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 04, 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.