104 lines
3.9 KiB
Python
104 lines
3.9 KiB
Python
"""Extraction des couleurs de têtes de minifigs sur le catalogue complet."""
|
|
|
|
import csv
|
|
from pathlib import Path
|
|
from typing import Dict, Iterable, List, Set, Tuple
|
|
|
|
from lib.rebrickable.parts_inventory import normalize_boolean, select_latest_inventories
|
|
|
|
|
|
HEAD_CATEGORIES = {"59"}
|
|
|
|
|
|
def load_head_parts(parts_path: Path, head_categories: Set[str] | None = None) -> Set[str]:
|
|
"""Construit l'ensemble des références de têtes via leur catégorie."""
|
|
categories = head_categories or HEAD_CATEGORIES
|
|
head_parts: Set[str] = set()
|
|
with parts_path.open() as parts_file:
|
|
reader = csv.DictReader(parts_file)
|
|
for row in reader:
|
|
if row["part_cat_id"] in categories:
|
|
head_parts.add(row["part_num"])
|
|
return head_parts
|
|
|
|
|
|
def build_sets_year_lookup(sets_path: Path) -> Dict[str, str]:
|
|
"""Indexe les années par set_num."""
|
|
lookup: Dict[str, str] = {}
|
|
with sets_path.open() as sets_file:
|
|
reader = csv.DictReader(sets_file)
|
|
for row in reader:
|
|
lookup[row["set_num"]] = row["year"]
|
|
return lookup
|
|
|
|
|
|
def build_color_lookup(colors_path: Path) -> Dict[str, dict]:
|
|
"""Construit un index des couleurs par identifiant."""
|
|
lookup: Dict[str, dict] = {}
|
|
with colors_path.open() as colors_file:
|
|
reader = csv.DictReader(colors_file)
|
|
for row in reader:
|
|
lookup[row["id"]] = {
|
|
"rgb": row["rgb"],
|
|
"is_translucent": row["is_trans"].lower(),
|
|
"name": row["name"],
|
|
}
|
|
return lookup
|
|
|
|
|
|
def aggregate_global_heads_by_year(
|
|
inventories_path: Path,
|
|
inventory_parts_path: Path,
|
|
parts_path: Path,
|
|
colors_path: Path,
|
|
sets_path: Path,
|
|
head_categories: Set[str] | None = None,
|
|
) -> List[dict]:
|
|
"""Agrège les couleurs de têtes par année sur le catalogue complet."""
|
|
head_parts = load_head_parts(parts_path, head_categories)
|
|
latest_inventories = select_latest_inventories(inventories_path)
|
|
latest_inventory_ids = {data["id"]: set_num for set_num, data in latest_inventories.items()}
|
|
colors_lookup = build_color_lookup(colors_path)
|
|
sets_year = build_sets_year_lookup(sets_path)
|
|
aggregates: Dict[Tuple[str, str, str], dict] = {}
|
|
with inventory_parts_path.open() as parts_file:
|
|
reader = csv.DictReader(parts_file)
|
|
for row in reader:
|
|
inventory_id = row["inventory_id"]
|
|
if inventory_id not in latest_inventory_ids:
|
|
continue
|
|
if row["part_num"] not in head_parts:
|
|
continue
|
|
if normalize_boolean(row["is_spare"]) == "true":
|
|
continue
|
|
set_num = latest_inventory_ids[inventory_id]
|
|
year = sets_year.get(set_num)
|
|
if year is None:
|
|
continue
|
|
color = colors_lookup[row["color_id"]]
|
|
key = (year, color["rgb"], color["is_translucent"])
|
|
existing = aggregates.get(key)
|
|
if existing is None:
|
|
aggregates[key] = {
|
|
"year": year,
|
|
"color_rgb": color["rgb"],
|
|
"is_translucent": color["is_translucent"],
|
|
"color_name": color["name"],
|
|
"quantity": 0,
|
|
}
|
|
existing = aggregates[key]
|
|
existing["quantity"] += int(row["quantity"])
|
|
results = list(aggregates.values())
|
|
results.sort(key=lambda r: (int(r["year"]), r["color_name"], r["is_translucent"]))
|
|
return results
|
|
|
|
|
|
def write_global_heads_by_year(destination_path: Path, rows: Iterable[dict]) -> None:
|
|
"""Sérialise l'agrégat global par année."""
|
|
fieldnames = ["year", "color_rgb", "is_translucent", "color_name", "quantity"]
|
|
with destination_path.open("w", newline="") as csv_file:
|
|
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
|
writer.writeheader()
|
|
for row in rows:
|
|
writer.writerow(row)
|