Initial commit: frontend + backend integration
This commit is contained in:
128
backend/tests/test_economy_cost.py
Normal file
128
backend/tests/test_economy_cost.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import math
|
||||
from datetime import datetime
|
||||
|
||||
import matplotlib
|
||||
import pandas as pd
|
||||
|
||||
from beyond_metrics.dimensions.EconomyCost import EconomyCostMetrics, EconomyConfig
|
||||
|
||||
matplotlib.use("Agg")
|
||||
|
||||
|
||||
def _sample_df() -> pd.DataFrame:
|
||||
data = [
|
||||
{
|
||||
"interaction_id": "id1",
|
||||
"datetime_start": datetime(2024, 1, 1, 10, 0),
|
||||
"queue_skill": "ventas",
|
||||
"channel": "voz",
|
||||
"duration_talk": 600,
|
||||
"hold_time": 60,
|
||||
"wrap_up_time": 30,
|
||||
},
|
||||
{
|
||||
"interaction_id": "id2",
|
||||
"datetime_start": datetime(2024, 1, 1, 10, 5),
|
||||
"queue_skill": "ventas",
|
||||
"channel": "voz",
|
||||
"duration_talk": 300,
|
||||
"hold_time": 30,
|
||||
"wrap_up_time": 20,
|
||||
},
|
||||
{
|
||||
"interaction_id": "id3",
|
||||
"datetime_start": datetime(2024, 1, 1, 11, 0),
|
||||
"queue_skill": "soporte",
|
||||
"channel": "chat",
|
||||
"duration_talk": 400,
|
||||
"hold_time": 20,
|
||||
"wrap_up_time": 30,
|
||||
},
|
||||
]
|
||||
return pd.DataFrame(data)
|
||||
|
||||
|
||||
def test_init_and_required_columns():
|
||||
df = _sample_df()
|
||||
cfg = EconomyConfig(labor_cost_per_hour=20.0, overhead_rate=0.1, tech_costs_annual=10000.0)
|
||||
em = EconomyCostMetrics(df, cfg)
|
||||
assert not em.is_empty
|
||||
|
||||
# Falta de columna obligatoria -> ValueError
|
||||
df_missing = df.drop(columns=["duration_talk"])
|
||||
import pytest
|
||||
with pytest.raises(ValueError):
|
||||
EconomyCostMetrics(df_missing, cfg)
|
||||
|
||||
|
||||
def test_metrics_without_config_do_not_crash():
|
||||
df = _sample_df()
|
||||
em = EconomyCostMetrics(df, None)
|
||||
|
||||
assert em.cpi_by_skill_channel().empty
|
||||
assert em.annual_cost_by_skill_channel().empty
|
||||
assert em.cost_breakdown() == {}
|
||||
assert em.inefficiency_cost_by_skill_channel().empty
|
||||
assert em.potential_savings() == {}
|
||||
|
||||
|
||||
def test_basic_cpi_and_annual_cost():
|
||||
df = _sample_df()
|
||||
cfg = EconomyConfig(labor_cost_per_hour=20.0, overhead_rate=0.1)
|
||||
em = EconomyCostMetrics(df, cfg)
|
||||
|
||||
cpi = em.cpi_by_skill_channel()
|
||||
assert not cpi.empty
|
||||
# Debe haber filas para ventas/voz y soporte/chat
|
||||
assert ("ventas", "voz") in cpi.index
|
||||
assert ("soporte", "chat") in cpi.index
|
||||
|
||||
annual = em.annual_cost_by_skill_channel()
|
||||
assert "annual_cost" in annual.columns
|
||||
# costes positivos
|
||||
assert (annual["annual_cost"] > 0).any()
|
||||
|
||||
|
||||
def test_cost_breakdown_and_potential_savings():
|
||||
df = _sample_df()
|
||||
cfg = EconomyConfig(
|
||||
labor_cost_per_hour=20.0,
|
||||
overhead_rate=0.1,
|
||||
tech_costs_annual=5000.0,
|
||||
automation_cpi=0.2,
|
||||
automation_volume_share=0.5,
|
||||
automation_success_rate=0.8,
|
||||
)
|
||||
em = EconomyCostMetrics(df, cfg)
|
||||
|
||||
breakdown = em.cost_breakdown()
|
||||
assert "labor_pct" in breakdown
|
||||
assert "overhead_pct" in breakdown
|
||||
assert "tech_pct" in breakdown
|
||||
|
||||
total_pct = (
|
||||
breakdown["labor_pct"]
|
||||
+ breakdown["overhead_pct"]
|
||||
+ breakdown["tech_pct"]
|
||||
)
|
||||
|
||||
# Permitimos pequeño error por redondeo a 2 decimales
|
||||
assert abs(total_pct - 100.0) < 0.2
|
||||
|
||||
savings = em.potential_savings()
|
||||
assert "annual_savings" in savings
|
||||
assert savings["annual_savings"] >= 0.0
|
||||
|
||||
|
||||
def test_plot_methods_return_axes():
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
df = _sample_df()
|
||||
cfg = EconomyConfig(labor_cost_per_hour=20.0, overhead_rate=0.1)
|
||||
em = EconomyCostMetrics(df, cfg)
|
||||
|
||||
ax1 = em.plot_cost_waterfall()
|
||||
ax2 = em.plot_cpi_by_channel()
|
||||
|
||||
assert isinstance(ax1, Axes)
|
||||
assert isinstance(ax2, Axes)
|
||||
Reference in New Issue
Block a user