# Étude de sets LEGO

## Présentation du projet

Ce projet vise à étudier statistiquement des sets [LEGO](https://www.lego.com/fr-fr).
Le projet est construit autour du thème [_Jurassic World_](https://www.lego.com/fr-fr/themes/jurassic-world), mais le paramétrage des scripts devrait permettre d'étudier les sets de n'importe quel thème LEGO.

L'objectif est de mettre en lumière des éléments spécifiques, tels que des pièces rares, des couleurs originales, ou des _minifigs_ particulières.
Cette étude doit permettre de piquer la curiosité en explorant l'évolution de notre thème préféré à travers le temps.

Cette étude a pour ambition de satisfaire ma curiosité, mon désir d'approfondir un sujet qui m'est cher, et mon besoin compulsif d'exhaustivité.
Découvrir qu'un nouveau set LEGO _Jurassic World_, posséder le set, le construire, puis l'exposer, jouer avec, recréer l'ambiance des œuvres originales, en imprégner mon bureau ; tout cela n'est qu'une partie de cet univers, qui recèle d'autres informations à découvrir et explorer.

Enfin, sur un plan plus technique, je souhaite améliorer mes compétences en python sur des sujets concrets que je maitrise, en l'exploitant notamment avec des librairies destinées à la production de statistiques.

## Organisation actuelle

- Les fichiers téléchargés ou produits sont rangés dans `data/` :
  - `data/raw/` contient les données brutes Rebrickable (fichiers compressés et décompressés).
  - `data/intermediate/` regroupe les transformations intermédiaires (filtres, enrichissements, rapports).
  - `data/final/` stocke les exports finaux prêts pour les statistiques et graphiques.
- Les scripts créent automatiquement les répertoires parents nécessaires pour leurs sorties.
- Les scripts d'orchestration se trouvent dans `scripts/`.
- Le code mutualisé est rangé dans `lib/`.

## Ordre d'exécution

### Étape 1 : récupérer les thèmes Rebrickable

1. `source .venv/bin/activate`
2. `python -m scripts.download_themes`

Le script télécharge le fichier compressé `themes.csv.gz` depuis Rebrickable vers `data/raw/`, le décompresse immédiatement en `themes.csv`, supprime l'archive `.gz`, et ne retélécharge pas le fichier si `themes.csv` a moins de 7 jours.

### Étape 2 : définir les thèmes à étudier

Renseigner dans `.env` la liste des identifiants de thèmes (séparés par des virgules). Pour l'univers _Jurassic Park / Jurassic World_, les identifiants relevés dans `data/raw/themes.csv` sont :

- 274 (`Jurassic Park III`, parent `Studios`)
- 602 (`Jurassic World`, parent racine)
- 620 (`Jurassic World: Fallen Kingdom`, parent `Juniors`)

L'identifiant 722 (`Jurassic World`, parent `Duplo`) est volontairement ignoré.

### Étape 3 : récupérer les sets Rebrickable

1. `source .venv/bin/activate`
2. `python -m scripts.download_sets`

Le script télécharge le fichier compressé `sets.csv.gz` depuis Rebrickable vers `data/raw/`, le décompresse immédiatement en `sets.csv`, supprime l'archive `.gz`, et ne retélécharge pas le fichier si `sets.csv` a moins de 7 jours.

### Étape 4 : filtrer les sets sur les thèmes ciblés

1. `source .venv/bin/activate`
2. `python -m scripts.filter_sets`

Le script lit `THEME_IDS` depuis `.env`, prend `data/raw/sets.csv` en entrée, applique les corrections déclarées dans `config/num_parts_overrides.csv`, et produit `data/intermediate/sets_filtered.csv` contenant uniquement les lignes dont le `theme_id` appartient aux thèmes sélectionnés et dont `num_parts` est strictement supérieur à 0.

Corrections manuelles connues (`config/num_parts_overrides.csv`) :

| set_num  | num_parts | commentaire                           |
| -------- | --------- | ------------------------------------- |
| 122220-1 | 30        | Sachet promotionnel annoncé 30 pièces |

### Étape 5 : enrichir les sets filtrés

1. `source .venv/bin/activate`
2. `python -m scripts.enrich_sets`

Le script lit `data/intermediate/sets_filtered.csv`, ajoute :

- `set_id` (partie avant le tiret dans `set_num`)
- `rebrickable_url` (URL publique du set sur Rebrickable)
- `in_collection` (`true/false` selon la présence du set dans le dossier `MY_SETS`)

La variable `MY_SETS` (définie dans `.env`) doit pointer vers un dossier contenant un sous-dossier par identifiant LEGO possédé. Si la variable est vide, que le dossier est absent ou vide, la colonne `in_collection` sera à `false` pour tous les sets. Les sorties sont `data/intermediate/sets_enriched.csv` et `data/final/sets_missing.md`.

### Étape 6 : calculer des statistiques basiques

1. `source .venv/bin/activate`
2. `python -m scripts.compute_stats`

Le script lit `data/raw/themes.csv`, `data/raw/sets.csv`, `data/intermediate/sets_filtered.csv` et `data/intermediate/sets_enriched.csv`, puis écrit `data/final/stats.csv` avec deux colonnes (`libelle`, `valeur`) contenant notamment :

- nombre total de sets (catalogue complet)
- nombre de sets filtrés et pourcentage vs total
- nombre moyen de sets par thème (catalogue complet)
- sets en collection / sets manquants
- taux de possession
- moyenne, médiane et total de pièces pour les thèmes filtrés
- moyenne de sets commercialisés par an
- bornes d'années et nombre de thèmes filtrés
- année la plus prolifique
- set le plus fourni / le moins fourni en pièces
- set le plus ancien / le plus récent
- moyenne de pièces des sets possédés / manquants
- total de pièces des sets possédés
- pourcentage de pièces possédées
- moyenne de sets par thème (catalogue complet, via `themes.csv`)
- nombre total de thèmes (catalogue complet)

### Milestones (jalons chronologiques)

Les jalons sont configurés dans `config/milestones.csv` (colonnes `year`, `description`). Ils sont indépendants des thèmes sélectionnés : pour un autre univers (ex. Star Wars), il suffit de remplacer ou adapter ce fichier sans modifier le code.

### Étape 7 : graphique du nombre de sets par année

1. `source .venv/bin/activate`
2. `python -m scripts.plot_sets_per_year`

Le script lit `data/intermediate/sets_enriched.csv`, les jalons `config/milestones.csv`, et produit `figures/step07/sets_per_year.png` montrant :

- le nombre de sets par année (barres)
- la moyenne cumulative des sets (courbe)
- le total de pièces par année (barres)
- la moyenne cumulative des pièces par set (courbe) annoté avec les jalons chronologiques

En parallèle, le script `python -m scripts.plot_parts_per_set` génère `figures/step07/avg_parts_per_set.png` avec la moyenne annuelle de pièces par set et une moyenne glissante (3 ans) pour mettre en évidence la tendance sans diluer l'historique.

### Étape 8 : télécharger les données détaillées des pièces

1. `source .venv/bin/activate`
2. `python -m scripts.download_parts_data`

Le script télécharge les fichiers compressés `inventories.csv.gz`, `inventory_parts.csv.gz`, `inventory_minifigs.csv.gz`, `minifigs.csv.gz`, `parts.csv.gz` et `colors.csv.gz` vers `data/raw/`, les décompresse immédiatement en supprimant chaque archive `.gz`, et ne retélécharge pas les fichiers âgés de moins de 7 jours (cache fondé sur les CSV décompressés). Ces données complètent les sets en décrivant leurs inventaires, les pièces individuelles, les minifigs associées et les couleurs disponibles.

### Étape 9 : assembler l'inventaire des pièces par set

1. `source .venv/bin/activate`
2. `python -m scripts.build_parts_inventory`

Le script lit `data/intermediate/sets_enriched.csv`, `data/raw/inventories.csv`, `data/raw/inventory_parts.csv`, `data/raw/inventory_minifigs.csv`, `data/raw/minifigs.csv` et `data/raw/colors.csv`, sélectionne la version d'inventaire la plus récente pour chaque set, puis produit `data/intermediate/parts_filtered.csv` contenant : `part_num`, `color_rgb`, `is_translucent`, `set_num`, `set_id`, `quantity_in_set`, `is_spare`. Les minifigs sont éclatées en pièces en exploitant leur propre inventaire (présent dans `inventories.csv` + `inventory_parts.csv`) et leurs quantités dans `inventory_minifigs.csv`. Ce fichier sert de base aux analyses ultérieures sans relire les CSV bruts.
Le fichier agrégé inclut également l'année du set (`year`) et un indicateur `is_minifig_part` pour distinguer les pièces issues des minifigs.

### Étape 10 : identifier les écarts d'inventaire

1. `source .venv/bin/activate`
2. `python -m scripts.report_inventory_gaps`

Le script lit `data/intermediate/sets_enriched.csv` et `data/intermediate/parts_filtered.csv`, calcule pour chaque set filtré le total de pièces (rechanges incluses), et produit `data/intermediate/inventory_gaps.csv` avec les colonnes :

- `set_num`
- `set_id`
- `expected_parts` (`num_parts` dans `sets_enriched.csv`)
- `inventory_parts` (somme de `quantity_in_set` dans `parts_filtered.csv`, rechanges incluses)
- `delta` (valeur absolue de `expected_parts - inventory_parts`)
- `in_collection` (valeur issue de `sets_enriched.csv`)

Seuls les sets dont les totaux diffèrent figurent dans ce fichier. Aucune tentative de correction n'est effectuée : l'inventaire existant reste la référence malgré les éventuels manques du catalogue Rebrickable.

Un tableau Markdown est également généré dans `data/final/inventory_gaps.md` listant ces sets avec leur nom, l'écart observé et un lien vers les instructions LEGO.

### Étape 11 : statistiques simples sur les pièces

1. `source .venv/bin/activate`
2. `python -m scripts.compute_parts_stats`

Le script lit `data/intermediate/parts_filtered.csv` et `data/final/stats.csv` (pour le total catalogue filtré), puis produit `data/final/parts_stats.csv` avec : nombre de variations de pièces (hors rechanges), pièce la moins utilisée, pièce la plus commune, nombre de couleurs utilisées, total de pièces hors rechanges, écart entre le total de pièces attendu (stats catalogue) et l'inventaire agrégé, nombre de sets présentant un écart inventaire/catalogue et écart maximal observé.

### Étape 12 : visualiser la palette globale

1. `source .venv/bin/activate`
2. `python -m scripts.plot_colors_grid`

Le script lit `data/intermediate/parts_filtered.csv` et `data/raw/colors.csv`, puis génère deux visuels : `figures/step12/colors_grid.png` pour l'ensemble des pièces (rechanges incluses) et `figures/step12/colors_grid_minifigs.png` pour la seule palette des minifigs. Les couleurs sont triées perceptuellement et mises en scène sur une grille hexagonale.

### Étape 13 : palette de couleurs par set

1. `source .venv/bin/activate`
2. `python -m scripts.build_colors_by_set`

Le script agrège `data/intermediate/parts_filtered.csv` avec les libellés de couleurs `data/raw/colors.csv` et produit `data/intermediate/colors_by_set.csv` contenant, pour chaque set et chaque couleur, les quantités totales, hors rechanges, issues des minifigs et hors minifigs. Ce fichier sert de base aux visualisations et matrices de palette.

### Étape 14 : évolution annuelle des palettes

1. `source .venv/bin/activate`
2. `python -m scripts.compute_colors_timeline`

Le script lit `data/intermediate/colors_by_set.csv` et produit deux agrégats : `data/intermediate/colors_timeline.csv` (statistiques annuelles : nombre de couleurs distinctes, nouvelles, perdues, part des translucides, top couleurs) et `data/intermediate/colors_year_color_matrix.csv` (quantités totales année × couleur) pour préparer heatmaps et analyses temporelles.

### Étape 15 : visualiser l'évolution des palettes

1. `source .venv/bin/activate`
2. `python -m scripts.plot_colors_timeline`

Le script lit les agrégats de l'étape 14 et produit `figures/step15/colors_translucent_share.png` (part des pièces translucides par année et nombre de couleurs distinctes), `figures/step15/colors_heatmap_linear.png` (heatmap année × couleur en quantités brutes) et `figures/step15/colors_heatmap_log.png` (heatmap avec échelle log1p).
Une troisième variante normalise les quantités par année : `figures/step15/colors_heatmap_share.png`. Dans cette vue, chaque colonne (année) est ramenée à une part relative (0–1) du total de pièces de l'année. Cela met en évidence la structure de palette indépendamment du volume : deux années restent comparables même si leur nombre total de pièces diffère fortement, mais l'information de volume absolu n'apparaît plus (à privilégier pour les comparaisons de proportions, pas pour mesurer la rareté volumique).
