Picarones / tests /measurements /test_sprint79_cost_projection.py
Claude
test: rΓ©organiser les 110 fichiers tests/test_*.py par cercle architectural
d109222 unverified
Raw
History Blame
9.41 kB
"""Tests Sprint 79 β€” A.I.6 : projection de coΓ»t en volume cible.
Couvre :
1. ``project_cost_total`` :
- Calcul linΓ©aire en target_pages
- target_pages = 0 β†’ 0
- target_pages nΓ©gatif β†’ None
- cost_per_1k_pages_eur = None β†’ None
2. ``project_co2_total`` : idem COβ‚‚.
3. ``project_engine`` : retour structurΓ©.
4. ``project_all_engines`` : N moteurs en une passe ; ValueError
sur target nΓ©gatif.
5. ``cost_gap_table`` :
- Delta absolu/relatif vs baseline
- Baseline = 0 β†’ delta_rel None
- Baseline inconnue β†’ KeyError
6. Cas rΓ©aliste BnF : 80 000 pages BMS β€” 4 moteurs avec coΓ»ts
diffΓ©rents β†’ projection cohΓ©rente.
"""
from __future__ import annotations
import pytest
from picarones.measurements.cost_projection import (
ProjectedCost,
cost_gap_table,
project_all_engines,
project_co2_total,
project_cost_total,
project_engine,
)
from picarones.measurements.pricing import EngineCost
def _ec(name: str, cost_1k: float | None, co2_1k: float | None = None,
type_: str = "cloud_api") -> EngineCost:
return EngineCost(
engine_key=name,
type=type_,
cost_per_1k_pages_eur=cost_1k,
co2_per_1k_pages_g=co2_1k,
)
# ──────────────────────────────────────────────────────────────────────────
# 1. project_cost_total
# ──────────────────────────────────────────────────────────────────────────
class TestProjectCostTotal:
def test_linear_scaling(self) -> None:
ec = _ec("x", 10.0) # 10 €/1000 pages
assert project_cost_total(ec, 1000) == 10.0
assert project_cost_total(ec, 10000) == 100.0
assert project_cost_total(ec, 80000) == 800.0
def test_zero_pages(self) -> None:
ec = _ec("x", 10.0)
assert project_cost_total(ec, 0) == 0.0
def test_negative_returns_none(self) -> None:
ec = _ec("x", 10.0)
assert project_cost_total(ec, -100) is None
def test_no_cost_data(self) -> None:
ec = _ec("x", None)
assert project_cost_total(ec, 1000) is None
def test_fractional_pages(self) -> None:
ec = _ec("x", 10.0)
# 500 pages β†’ 5 €
assert project_cost_total(ec, 500) == 5.0
# ──────────────────────────────────────────────────────────────────────────
# 2. project_co2_total
# ──────────────────────────────────────────────────────────────────────────
class TestProjectCo2:
def test_linear_scaling(self) -> None:
ec = _ec("x", None, co2_1k=50.0) # 50 g/1000 pages
assert project_co2_total(ec, 10000) == 500.0
def test_no_co2_data(self) -> None:
ec = _ec("x", 10.0) # cost mais pas co2
assert project_co2_total(ec, 1000) is None
# ──────────────────────────────────────────────────────────────────────────
# 3. project_engine
# ──────────────────────────────────────────────────────────────────────────
class TestProjectEngine:
def test_full_struct(self) -> None:
ec = _ec("tess", 5.0, co2_1k=20.0, type_="local")
proj = project_engine(ec, 80000)
assert isinstance(proj, ProjectedCost)
assert proj.engine_key == "tess"
assert proj.target_pages == 80000
assert proj.cost_total_eur == 400.0
assert proj.co2_total_g == 1600.0
assert proj.cost_per_1k_pages_eur == 5.0
assert proj.type == "local"
def test_as_dict(self) -> None:
ec = _ec("tess", 5.0)
proj = project_engine(ec, 1000)
d = proj.as_dict()
assert d["engine_key"] == "tess"
assert d["cost_total_eur"] == 5.0
# ──────────────────────────────────────────────────────────────────────────
# 4. project_all_engines
# ──────────────────────────────────────────────────────────────────────────
class TestProjectAll:
def test_multi_engines(self) -> None:
engines = {
"tess": _ec("tess", 1.0, type_="local"),
"mistral": _ec("mistral", 3.5, type_="cloud_api"),
"gpt4o": _ec("gpt4o", 7.5, type_="cloud_api"),
}
result = project_all_engines(engines, 10000)
assert result["tess"].cost_total_eur == 10.0
assert result["mistral"].cost_total_eur == 35.0
assert result["gpt4o"].cost_total_eur == 75.0
def test_negative_target_raises(self) -> None:
with pytest.raises(ValueError, match="target_pages"):
project_all_engines({"x": _ec("x", 1.0)}, -1)
def test_engine_without_data_kept(self) -> None:
engines = {
"known": _ec("known", 1.0),
"unknown": _ec("unknown", None),
}
result = project_all_engines(engines, 1000)
assert result["known"].cost_total_eur == 1.0
assert "unknown" in result
assert result["unknown"].cost_total_eur is None
# ──────────────────────────────────────────────────────────────────────────
# 5. cost_gap_table
# ──────────────────────────────────────────────────────────────────────────
class TestCostGapTable:
def _projections(self) -> dict:
return {
"tess": project_engine(_ec("tess", 1.0), 10000),
"mistral": project_engine(_ec("mistral", 5.0), 10000),
}
def test_gap_vs_baseline(self) -> None:
projs = self._projections()
gaps = cost_gap_table(projs, baseline_engine="tess")
# tess : 10 €, mistral : 50 €
assert gaps["tess"]["delta_abs"] == 0.0
assert gaps["mistral"]["delta_abs"] == 40.0
assert gaps["mistral"]["delta_rel"] == 4.0 # 40/10
def test_unknown_baseline_raises(self) -> None:
with pytest.raises(KeyError):
cost_gap_table(self._projections(), "nonexistent")
def test_baseline_zero_relative_none(self) -> None:
projs = {
"tess": project_engine(_ec("tess", 0.0, type_="local"), 10000),
"mistral": project_engine(_ec("mistral", 5.0), 10000),
}
gaps = cost_gap_table(projs, "tess")
# tess Γ  0 β†’ relative non calculable
assert gaps["mistral"]["delta_rel"] is None
# absolute reste calculable
assert gaps["mistral"]["delta_abs"] == 50.0
def test_engine_without_data_skipped_in_calc(self) -> None:
projs = {
"known": project_engine(_ec("known", 1.0), 1000),
"unknown": project_engine(_ec("unknown", None), 1000),
}
gaps = cost_gap_table(projs, "known")
assert gaps["unknown"]["delta_abs"] is None
assert gaps["unknown"]["delta_rel"] is None
# ──────────────────────────────────────────────────────────────────────────
# 6. Cas rΓ©aliste BnF
# ──────────────────────────────────────────────────────────────────────────
class TestRealisticBnF:
def test_80k_pages_bms_scenario(self) -> None:
# ScΓ©nario : registres paroissiaux, 80 000 pages
engines = {
"tesseract": _ec("tesseract", 0.04, type_="local"),
"pero": _ec("pero", 0.0, type_="local"),
"mistral": _ec("mistral", 3.5, type_="cloud_api"),
"gpt4o": _ec("gpt4o", 7.5, type_="cloud_api"),
}
target = 80000
result = project_all_engines(engines, target)
# VΓ©rifications quantitatives
assert result["tesseract"].cost_total_eur == pytest.approx(3.2)
assert result["pero"].cost_total_eur == 0.0
assert result["mistral"].cost_total_eur == 280.0
assert result["gpt4o"].cost_total_eur == 600.0
# Gap vs Tesseract : Mistral coΓ»te 276,8 € de plus
gaps = cost_gap_table(result, "tesseract")
assert gaps["mistral"]["delta_abs"] == pytest.approx(276.8)