1

Analyse des têtes dual-face

This commit is contained in:
2025-12-02 17:15:43 +01:00
parent 2050d73105
commit 4ad46b4ede
10 changed files with 627 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
"""Tests des têtes dual-face."""
import csv
from pathlib import Path
from lib.rebrickable.minifig_head_faces import (
aggregate_by_character,
aggregate_by_set,
aggregate_by_year,
build_head_faces,
detect_dual_face,
)
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_detect_dual_face_matches_keywords() -> None:
"""Reconnaît les mentions dual-face dans le nom."""
assert detect_dual_face("Minifig Head Dual Sided Smile") == "true"
assert detect_dual_face("Minifig Head w/ Alt Face") == "true"
assert detect_dual_face("Minifig Head Angry") == "false"
def test_build_head_faces_and_aggregates(tmp_path: Path) -> None:
"""Construit l'annotation dual-face puis agrège par set/année/personnage."""
minifigs_by_set = tmp_path / "minifigs_by_set.csv"
write_csv(
minifigs_by_set,
["set_num", "part_num", "known_character", "fig_num", "gender"],
[
["1000-1", "p1", "Alice", "fig-1", "female"],
["1000-1", "p2", "Bob", "fig-2", "male"],
["2000-1", "p1", "Alice", "fig-1", "female"],
],
)
parts_catalog = tmp_path / "parts.csv"
write_csv(
parts_catalog,
["part_num", "name", "part_cat_id"],
[
["p1", "Minifig Head Dual Sided Grin", "59"],
["p2", "Minifig Head Serious", "59"],
],
)
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"],
],
)
heads = build_head_faces(minifigs_by_set, parts_catalog, sets_enriched)
by_year = aggregate_by_year(heads)
by_set = aggregate_by_set(heads)
by_character = aggregate_by_character(heads)
assert heads == [
{
"set_num": "1000-1",
"set_id": "1000",
"year": "2020",
"name": "Set A",
"in_collection": "true",
"part_num": "p1",
"part_name": "Minifig Head Dual Sided Grin",
"fig_num": "fig-1",
"known_character": "Alice",
"gender": "female",
"is_dual_face": "true",
},
{
"set_num": "1000-1",
"set_id": "1000",
"year": "2020",
"name": "Set A",
"in_collection": "true",
"part_num": "p2",
"part_name": "Minifig Head Serious",
"fig_num": "fig-2",
"known_character": "Bob",
"gender": "male",
"is_dual_face": "false",
},
{
"set_num": "2000-1",
"set_id": "2000",
"year": "2021",
"name": "Set B",
"in_collection": "false",
"part_num": "p1",
"part_name": "Minifig Head Dual Sided Grin",
"fig_num": "fig-1",
"known_character": "Alice",
"gender": "female",
"is_dual_face": "true",
},
]
assert by_year == [
{"year": "2020", "total_heads": "2", "dual_heads": "1", "share_dual": "0.5000"},
{"year": "2021", "total_heads": "1", "dual_heads": "1", "share_dual": "1.0000"},
]
assert by_set == [
{
"set_num": "2000-1",
"set_id": "2000",
"name": "Set B",
"year": "2021",
"in_collection": "false",
"total_heads": "1",
"dual_heads": "1",
"share_dual": "1.0000",
},
{
"set_num": "1000-1",
"set_id": "1000",
"name": "Set A",
"year": "2020",
"in_collection": "true",
"total_heads": "2",
"dual_heads": "1",
"share_dual": "0.5000",
},
]
assert by_character == [
{
"known_character": "Alice",
"gender": "female",
"total_heads": "2",
"dual_heads": "2",
"share_dual": "1.0000",
},
{
"known_character": "Bob",
"gender": "male",
"total_heads": "1",
"dual_heads": "0",
"share_dual": "0.0000",
},
]

View File

@@ -0,0 +1,45 @@
"""Tests des graphiques des têtes dual-face."""
import matplotlib
from pathlib import Path
from lib.plots.minifig_head_faces import (
plot_dual_faces_characters,
plot_dual_faces_timeline,
plot_dual_faces_top_sets,
)
matplotlib.use("Agg")
def test_plot_minifig_head_faces_outputs_images(tmp_path: Path) -> None:
"""Génère les visuels dual-face."""
by_year = tmp_path / "minifig_head_faces_by_year.csv"
by_year.write_text("year,total_heads,dual_heads,share_dual\n2020,2,1,0.5\n2021,3,2,0.6667\n")
by_set = tmp_path / "minifig_head_faces_by_set.csv"
by_set.write_text(
"set_num,set_id,name,year,in_collection,total_heads,dual_heads,share_dual\n"
"1000-1,1000,Set A,2020,true,2,1,0.5\n"
"2000-1,2000,Set B,2021,false,3,2,0.6667\n"
)
by_character = tmp_path / "minifig_head_faces_by_character.csv"
by_character.write_text(
"known_character,gender,total_heads,dual_heads,share_dual\n"
"Alice,female,2,2,1.0\n"
"Bob,male,1,0,0.0\n"
)
timeline_dest = tmp_path / "figures" / "step30" / "minifig_head_faces_timeline.png"
top_sets_dest = tmp_path / "figures" / "step30" / "minifig_head_faces_top_sets.png"
characters_dest = tmp_path / "figures" / "step30" / "minifig_head_faces_characters.png"
plot_dual_faces_timeline(by_year, timeline_dest)
plot_dual_faces_top_sets(by_set, top_sets_dest)
plot_dual_faces_characters(by_character, characters_dest)
assert timeline_dest.exists()
assert top_sets_dest.exists()
assert characters_dest.exists()
assert timeline_dest.stat().st_size > 0
assert top_sets_dest.stat().st_size > 0
assert characters_dest.stat().st_size > 0