1

Ajoute la richesse chromatique par set

This commit is contained in:
2025-12-02 16:59:59 +01:00
parent f94669d82e
commit d067e2075f
8 changed files with 592 additions and 11 deletions

View File

@@ -0,0 +1,196 @@
"""Tests des métriques de richesse chromatique."""
import csv
from pathlib import Path
from lib.rebrickable.color_richness import (
build_richness_by_set,
build_richness_by_year,
write_richness_by_set,
write_richness_by_year,
)
def write_csv(path: Path, headers: list[str], rows: list[list[str]]) -> None:
"""Écrit un CSV simple pour les besoins de tests."""
with path.open("w", newline="") as csv_file:
writer = csv.writer(csv_file)
writer.writerow(headers)
writer.writerows(rows)
def test_build_richness_by_set_computes_shares_and_counts(tmp_path: Path) -> None:
"""Calcule les partages de couleurs principales et les dénombrements."""
colors_by_set = tmp_path / "colors_by_set.csv"
write_csv(
colors_by_set,
[
"set_num",
"set_id",
"year",
"color_rgb",
"is_translucent",
"color_name",
"quantity_total",
"quantity_non_spare",
"quantity_minifig",
"quantity_non_minifig",
],
[
["1000-1", "1000", "2020", "AAAAAA", "false", "Gray", "10", "10", "0", "10"],
["1000-1", "1000", "2020", "BBBBBB", "false", "Blue", "5", "5", "5", "0"],
["2000-1", "2000", "2021", "CCCCCC", "true", "Trans", "3", "3", "0", "3"],
],
)
sets_enriched = tmp_path / "sets_enriched.csv"
write_csv(
sets_enriched,
["set_num", "set_id", "name", "year", "in_collection"],
[
["1000-1", "1000", "Set A", "2020", "true"],
["2000-1", "2000", "Set B", "2021", "false"],
],
)
richness = build_richness_by_set(colors_by_set, sets_enriched)
assert richness == [
{
"set_num": "1000-1",
"set_id": "1000",
"name": "Set A",
"year": "2020",
"in_collection": "true",
"colors_distinct": "2",
"colors_minifig": "1",
"colors_non_minifig": "1",
"total_parts_non_spare": "15",
"top_color_name": "Gray",
"top_color_share": "0.6667",
"top3_share": "1.0000",
},
{
"set_num": "2000-1",
"set_id": "2000",
"name": "Set B",
"year": "2021",
"in_collection": "false",
"colors_distinct": "1",
"colors_minifig": "0",
"colors_non_minifig": "1",
"total_parts_non_spare": "3",
"top_color_name": "Trans",
"top_color_share": "1.0000",
"top3_share": "1.0000",
},
]
def test_build_richness_by_year_aggregates_metrics(tmp_path: Path) -> None:
"""Agrège les métriques par année."""
richness_rows = [
{
"set_num": "s1",
"set_id": "1",
"name": "A",
"year": "2020",
"in_collection": "true",
"colors_distinct": "4",
"colors_minifig": "1",
"colors_non_minifig": "3",
"total_parts_non_spare": "10",
"top_color_name": "Red",
"top_color_share": "0.5000",
"top3_share": "0.9000",
},
{
"set_num": "s2",
"set_id": "2",
"name": "B",
"year": "2020",
"in_collection": "false",
"colors_distinct": "2",
"colors_minifig": "0",
"colors_non_minifig": "2",
"total_parts_non_spare": "5",
"top_color_name": "Blue",
"top_color_share": "0.6000",
"top3_share": "1.0000",
},
{
"set_num": "s3",
"set_id": "3",
"name": "C",
"year": "2021",
"in_collection": "true",
"colors_distinct": "3",
"colors_minifig": "1",
"colors_non_minifig": "3",
"total_parts_non_spare": "7",
"top_color_name": "Green",
"top_color_share": "0.5714",
"top3_share": "1.0000",
},
]
yearly = build_richness_by_year(richness_rows)
assert yearly == [
{
"year": "2020",
"average_colors_distinct": "3.00",
"median_colors_distinct": "3.00",
"max_colors_distinct": "4",
"min_colors_distinct": "2",
"average_top3_share": "0.9500",
"median_top3_share": "0.9500",
},
{
"year": "2021",
"average_colors_distinct": "3.00",
"median_colors_distinct": "3.00",
"max_colors_distinct": "3",
"min_colors_distinct": "3",
"average_top3_share": "1.0000",
"median_top3_share": "1.0000",
},
]
def test_write_richness_outputs_csv(tmp_path: Path) -> None:
"""Sérialise les métriques par set et par année."""
by_set_path = tmp_path / "color_richness_by_set.csv"
by_year_path = tmp_path / "color_richness_by_year.csv"
sample_set_rows = [
{
"set_num": "s1",
"set_id": "1",
"name": "A",
"year": "2020",
"in_collection": "true",
"colors_distinct": "1",
"colors_minifig": "1",
"colors_non_minifig": "1",
"total_parts_non_spare": "5",
"top_color_name": "Red",
"top_color_share": "1.0000",
"top3_share": "1.0000",
}
]
sample_year_rows = [
{
"year": "2020",
"average_colors_distinct": "1.00",
"median_colors_distinct": "1.00",
"max_colors_distinct": "1",
"min_colors_distinct": "1",
"average_top3_share": "1.0000",
"median_top3_share": "1.0000",
}
]
write_richness_by_set(by_set_path, sample_set_rows)
write_richness_by_year(by_year_path, sample_year_rows)
assert by_set_path.exists()
assert by_year_path.exists()

View File

@@ -0,0 +1,38 @@
"""Tests des visuels de richesse chromatique."""
import matplotlib
from pathlib import Path
from lib.plots.color_richness import (
plot_concentration_scatter,
plot_richness_boxplot,
plot_richness_top_sets,
)
matplotlib.use("Agg")
def test_plot_richness_outputs_images(tmp_path: Path) -> None:
"""Génère les trois graphiques principaux."""
richness_path = tmp_path / "color_richness_by_set.csv"
richness_path.write_text(
"set_num,set_id,name,year,in_collection,colors_distinct,colors_minifig,colors_non_minifig,total_parts_non_spare,top_color_name,top_color_share,top3_share\n"
"1000-1,1000,Set A,2020,true,6,2,5,50,Red,0.4000,0.6500\n"
"2000-1,2000,Set B,2021,false,4,1,3,30,Blue,0.5000,0.7500\n"
"3000-1,3000,Set C,2021,true,5,1,4,40,Green,0.3000,0.5500\n"
)
boxplot_dest = tmp_path / "figures" / "step28" / "color_richness_boxplot.png"
top_dest = tmp_path / "figures" / "step28" / "color_richness_top_sets.png"
scatter_dest = tmp_path / "figures" / "step28" / "color_concentration_scatter.png"
plot_richness_boxplot(richness_path, boxplot_dest)
plot_richness_top_sets(richness_path, top_dest)
plot_concentration_scatter(richness_path, scatter_dest)
assert boxplot_dest.exists()
assert top_dest.exists()
assert scatter_dest.exists()
assert boxplot_dest.stat().st_size > 0
assert top_dest.stat().st_size > 0
assert scatter_dest.stat().st_size > 0

View File

@@ -102,17 +102,6 @@ def test_build_rare_parts_detects_exclusive_variations(tmp_path: Path) -> None:
},
]
assert rare_by_set == [
{
"set_num": "1000-1",
"set_id": "1000",
"name": "Set A",
"year": "2020",
"in_collection": "true",
"rare_parts_distinct": "1",
"rare_parts_quantity": "1",
"rare_minifig_parts_distinct": "1",
"rare_minifig_quantity": "1",
},
{
"set_num": "2000-1",
"set_id": "2000",
@@ -124,6 +113,17 @@ def test_build_rare_parts_detects_exclusive_variations(tmp_path: Path) -> None:
"rare_minifig_parts_distinct": "0",
"rare_minifig_quantity": "0",
},
{
"set_num": "1000-1",
"set_id": "1000",
"name": "Set A",
"year": "2020",
"in_collection": "true",
"rare_parts_distinct": "1",
"rare_parts_quantity": "1",
"rare_minifig_parts_distinct": "1",
"rare_minifig_quantity": "1",
},
]