Files
palladium/assessments/CX_AI_Diagnostic/tests/test_value_math.py
Robert Helewka a967f73d09 feat: add master notebook library scaffolding and review tooling
Add CLAUDE.md defining the Palladium master notebook conventions and
Red Panda Approval criteria, plus a review-notebook slash command for
LLM-driven notebook review.

Expand .gitignore to block client/engagement documents and generated
exports, keeping masters client-clean while allowing text/image sources.

Normalize slider widget numeric values from floats to integers in
notebook JSON.
2026-07-31 16:16:07 +00:00

146 lines
6.5 KiB
Python
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.
"""Value-math pins — every number hand-checked before pinning.
Mock scenario (the notebook's widget seeds use the same values):
volume 1,200,000 · $6.50/contact · 450 agents · 30% attrition ·
20% containment · 420s AHT · weakest foundation = data_readiness @ 2
Hand arithmetic:
deflection low 1.2M × 0.15 × 6.50 = 1,170,000 high ×0.35 = 2,730,000
AHT low 1.2M × 6.50 × 0.15 = 1,170,000 high ×0.25 = 1,950,000
attrition low 450 × 0.30 × 0.10 × 15,000 = 202,500 high ×0.20 = 405,000
theoretical low 2,542,500 high 5,085,000
band @2 = (0.25, 0.40)
realizable 18mo low 2,542,500 × 0.25 × 1.5 = 953,437.50
high 5,085,000 × 0.40 × 1.5 = 3,051,000
trapped (annual) low 2,542,500 × (10.40) = 1,525,500
high 5,085,000 × (10.25) = 3,813,750
"""
import pytest
from diaglib import (
binding_constraints,
driver_value,
money,
value_at_stake,
weakest_foundational_score,
)
approx = pytest.approx
def test_driver_pins(config, baseline):
by_id = {d.id: driver_value(d, baseline) for d in config.value_drivers}
assert by_id["deflection_lift"].theoretical_low == approx(1_170_000)
assert by_id["deflection_lift"].theoretical_high == approx(2_730_000)
assert by_id["aht_reduction"].theoretical_low == approx(1_170_000)
assert by_id["aht_reduction"].theoretical_high == approx(1_950_000)
assert by_id["attrition_reduction"].theoretical_low == approx(202_500)
assert by_id["attrition_reduction"].theoretical_high == approx(405_000)
def test_value_at_stake_pins(config, baseline, scores):
vas = value_at_stake(config, baseline, scores)
assert vas.theoretical_annual_value_low == approx(2_542_500)
assert vas.theoretical_annual_value_high == approx(5_085_000)
assert vas.weakest_foundational_score == 2
assert (vas.realization_factor_low, vas.realization_factor_high) == (0.25, 0.40)
assert vas.realizable_18mo_low == approx(953_437.50)
assert vas.realizable_18mo_high == approx(3_051_000)
assert vas.trapped_value_low == approx(1_525_500)
assert vas.trapped_value_high == approx(3_813_750)
assert vas.binding_constraints == ["data_readiness"]
assert vas.warnings == [] # nothing flagged unknown in the fixture
def test_unlock_sequence_pins(config, baseline, scores):
vas = value_at_stake(config, baseline, scores)
m1, m2, m3 = vas.unlock_sequence
# Move 1 — the unique weakest foundation lifts alone.
assert m1.competency_ids == ["data_readiness"]
assert (m1.current_level, m1.target_level) == (2, 3)
assert (m1.est_cost_low, m1.est_cost_high, m1.est_weeks) == (300_000, 600_000, 12)
assert m1.value_unlocked_low == approx(2_542_500 * 0.25) # 635,625
assert m1.value_unlocked_high == approx(5_085_000 * 0.25) # 1,271,250
# Move 2 — all three foundations now tie at 3: joint lift.
assert m2.competency_ids == [
"process_discovery", "data_readiness", "technical_architecture"]
assert (m2.current_level, m2.target_level) == (3, 4)
assert m2.est_cost_low == approx(150_000 + 400_000 + 250_000) # 800,000
assert m2.est_cost_high == approx(300_000 + 800_000 + 500_000) # 1,600,000
assert m2.est_weeks == 16 # longest workstream
assert m2.value_unlocked_low == approx(2_542_500 * 0.15) # 381,375
assert m2.value_unlocked_high == approx(5_085_000 * 0.20) # 1,017,000
assert "joint lift" in m2.note
# Move 3 — the trio lifts again, 4 → 5.
assert (m3.current_level, m3.target_level) == (4, 5)
assert m3.est_cost_low == approx(200_000 + 500_000 + 350_000) # 1,050,000
assert m3.est_cost_high == approx(400_000 + 1_000_000 + 700_000) # 2,100,000
assert m3.est_weeks == 20
assert m3.value_unlocked_low == approx(2_542_500 * 0.15)
assert m3.value_unlocked_high == approx(5_085_000 * 0.15) # 762,750
# The ratio walk declines — the first unlock is the cheapest value.
ratios = [((m.value_unlocked_low + m.value_unlocked_high) / 2)
/ ((m.est_cost_low + m.est_cost_high) / 2)
for m in (m1, m2, m3)]
assert ratios[0] > ratios[1] > ratios[2]
def test_structural_ties_hold_at_any_scores(config, baseline, scores):
vas = value_at_stake(config, baseline, scores)
assert vas.theoretical_annual_value_low <= vas.theoretical_annual_value_high
assert vas.realizable_18mo_low <= vas.realizable_18mo_high
assert vas.trapped_value_low <= vas.trapped_value_high
assert sum(d.theoretical_low for d in vas.driver_values) == approx(
vas.theoretical_annual_value_low)
assert sum(d.theoretical_high for d in vas.driver_values) == approx(
vas.theoretical_annual_value_high)
assert set(vas.binding_constraints) <= set(config.foundational_competencies)
assert len(vas.unlock_sequence) <= 3
def test_weakest_and_binding_with_ties(config, baseline, scores):
assert weakest_foundational_score(config, scores) == 2
# Drag process_discovery down to 2 as well — binding set becomes a pair.
tied = [s.model_copy(update={"score": 2})
if s.competency_id == "process_discovery" else s for s in scores]
assert binding_constraints(config, tied) == ["process_discovery", "data_readiness"]
vas = value_at_stake(config, baseline, tied)
m1 = vas.unlock_sequence[0]
assert m1.competency_ids == ["process_discovery", "data_readiness"]
assert "joint lift" in m1.note
assert m1.est_cost_low == approx(120_000 + 300_000)
def test_unknown_inputs_raise_warnings(config, baseline, scores):
flagged = baseline.model_copy(update={"field_confidence": {
**baseline.field_confidence, "annual_contact_volume": "unknown"}})
vas = value_at_stake(config, flagged, scores)
assert any("annual_contact_volume" in w and "unknown" in w for w in vas.warnings)
def test_stub_config_yields_empty_value(stub_config, baseline, scores):
vas = value_at_stake(stub_config, baseline, scores)
assert vas.theoretical_annual_value_low == 0
assert vas.theoretical_annual_value_high == 0
assert vas.unlock_sequence == []
assert any("no value drivers" in w for w in vas.warnings)
def test_money_two_significant_figures():
assert money(953_437.50) == "$950K"
assert money(2_542_500) == "$2.5M"
assert money(1_271_250) == "$1.3M"
assert money(5_085_000) == "$5.1M"
assert money(15_000_000) == "$15M"
assert money(202_500) == "$200K"
assert money(-450_000) == "-$450K"
assert money(85) == "$85"
assert money(0) == "$0"