Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Séance 2 sur 37 livrablesQuiz corrigé

BrickFinder Le catalogue & le cerveau

Ton back renvoie un inventaire propre. Aujourd'hui on lui donne une mémoire (MongoDB), un catalogue de sets (API Rebrickable), et un cerveau : le moteur qui dit « avec tes pièces, tu peux construire ça ».

Par Josselyn C., prof de BTS SIO. Publié le , mis à jour le .

On construit
une base MongoDB + un catalogue rempli par l'API Rebrickable + le moteur de faisabilité
On installe
MongoDB (Atlas gratuit ou Docker), Mongoose — pas à pas
Pré-requis
le back du Milestone 1 (qui renvoie l'inventaire). On repart de là.

Milestone 2 sur 3. À la fin : tu envoies un inventaire, ton serveur répond « sets constructibles + pièces manquantes ».

Où on en est — et où on va

Au Milestone 1, ton back sait transformer une photo en inventaire JSON vérifié :

Ce que tu dois voir
POST /api/inventaire  (une photo)
  ->  [ { "type":"brique2x4", "couleur":"rouge", "quantite":5 }, ... ]

Aujourd'hui, on répond à la vraie question : « avec ces pièces, qu'est-ce que je peux construire ? ». Pour ça, il faut deux choses nouvelles :

BriqueRôleOutil
Un cataloguela liste des pièces de vrais sets Lego (la « recette » de chacun)API Rebrickable + MongoDB
Un moteurcomparer ton inventaire à chaque set → faisable à X %, pièces manquanteston code (l'algo)

Schéma : ton inventaire · (Milestone 1) · catalogue des sets · Rebrickable -> Mongo · LE MOTEUR · ton algo · faisable 90% … · + pièces manquantes

1. C'est quoi une base de données ?

Jusqu'ici, si tu arrêtes ton serveur, tout est oublié. Une base de données (BDD) garde les informations, même éteint. On va y stocker notre catalogue de sets.

Monde SQLMonde MongoDB
une tableune collection (ex. modeles)
une ligneun document (ex. un set)
une colonneun champ (ex. nom, pieces)

Démarrer une base (le plus simple)

Deux options — choisis-en une :

Mets ta chaîne dans le .env (à côté de ta clé Claude) :

MONGO_URI=mongodb+srv://user:motdepasse@cluster0.xxxx.mongodb.net/brickfinder

Flag A1

Checkpoint

Rends : quelle option tu as choisie (Atlas ou Docker), et la forme de ta chaîne de connexion sans le mot de passe (remplace-le par ***).

0 sur 7 dans cette étape

2. Mongoose : parler à la base

Mongoose est la librairie qui fait le pont entre ton code et MongoDB. Elle permet de définir la forme de tes documents (un schéma) et de les lire/écrire facilement.

Installer et se connecter

powershell
npm install mongoose

Crée un fichier db.js :

python
import mongoose from "mongoose"

export async function connecterBase() {
  await mongoose.connect(process.env.MONGO_URI)
  console.log("Base connectée")
}

Dans index.js, appelle-la au démarrage (tout en haut, après les imports) :

python
import { connecterBase } from "./db.js"
await connecterBase()
Ce que tu dois voir
Base connectée
Serveur prêt sur http://localhost:3000

Le schéma d'un modèle (un set)

Crée models/Modele.js. Un set a un numéro, un nom, et une liste de pièces (sa « recette ») :

python
import mongoose from "mongoose"

const PieceSchema = new mongoose.Schema({
  type: String,
  couleur: String,
  quantite: Number
}, { _id: false })

const ModeleSchema = new mongoose.Schema({
  setNum: { type: String, unique: true },
  nom: String,
  image: String,
  pieces: [PieceSchema]
})

export const Modele = mongoose.model("Modele", ModeleSchema)

Flag B1

Production

Dans index.js, après la connexion, crée un set « test » à la main et enregistre-le :
await Modele.create({ setNum: "TEST-1", nom: "Petit test", pieces: [{ type:"brique2x4", couleur:"rouge", quantite:4 }] })
Relance, puis vérifie dans Atlas (Browse Collections) ou avec une route. Rends : une capture du document créé dans la base. (Puis supprime cette ligne de création.)

0 sur 7 dans cette étape

3. Le catalogue : l'API Rebrickable

On ne va pas inventer les recettes des sets : elles existent déjà. Rebrickable est un site qui connaît la liste exacte des pièces de chaque set Lego, et il offre une API gratuite.

Obtenir une clé (gratuite)

Crée un compte sur rebrickable.com → Account → Settings → API → copie ta clé. Mets-la dans le .env :

REBRICKABLE_KEY=ta-cle-rebrickable

Le premier appel

L'adresse pour les pièces d'un set : https://rebrickable.com/api/v3/lego/sets/{numéro}/parts/. Essayons avec le set 31147-1 (un Creator) :

const KEY = process.env.REBRICKABLE_KEY
const setNum = "31147-1"
const url = `https://rebrickable.com/api/v3/lego/sets/${setNum}/parts/?key=${KEY}&page_size=1000`

const rep = await fetch(url)
const data = await rep.json()
console.log("Pièces reçues :", data.results.length)
console.log(data.results[0])
Ce que tu dois voir
Pièces reçues : 182
{
  part: { part_num: "3001", name: "Brick 2 x 4" },
  color: { name: "Red" },
  quantity: 6,
  ...
}

Flag D1

Investigation

Sur rebrickable.com, trouve le numéro de 2 petits sets que tu pourrais acheter (≈100-250 pièces). Rends : leurs numéros (format xxxxx-1) et leur nombre de pièces.

0 sur 7 dans cette étape

4. Normaliser & stocker le catalogue

La réponse de Rebrickable n'a pas la même forme que notre inventaire. On va la traduire vers notre format { type, couleur, quantite }, puis l'enregistrer en base.

Traduire une pièce Rebrickable

function normaliser(resultats) {
  return resultats.map(r => ({
    type: r.part.name,
    couleur: r.color.name,
    quantite: r.quantity
  }))
}

Une fonction qui va chercher ET stocke un set

async function importerSet(setNum) {
  const base = "https://rebrickable.com/api/v3/lego"
  const key = process.env.REBRICKABLE_KEY

  // 1. les infos du set : son NOM et son IMAGE (la photo de la boîte !)
  const info = await (await fetch(`${base}/sets/${setNum}/?key=${key}`)).json()

  // 2. ses pièces
  const data = await (await fetch(`${base}/sets/${setNum}/parts/?key=${key}&page_size=1000`)).json()
  const pieces = normaliser(data.results)

  await Modele.findOneAndUpdate(
    { setNum },
    { setNum, nom: info.name, image: info.set_img_url, pieces },
    { upsert: true }
  )
  console.log(`Set ${setNum} (${info.name}) importé : ${pieces.length} types de pièces`)
}

Une route pour remplir le catalogue

app.post("/api/catalogue/:setNum", async (req, res) => {
  await importerSet(req.params.setNum)
  res.json({ importe: req.params.setNum })
})
Ce que tu dois voir
Set 31147-1 importé : 182 types de pièces
{"importe":"31147-1"}

Flag B2

Production

Importe 3 sets (les tiens du Flag D1, ou 31147-1, 31135-1, 30669-1). Vérifie dans la base que tu as bien 3 documents dans modeles. Rends : les 3 numéros + le nombre de types de pièces de chacun.

0 sur 7 dans cette étape

5. Le moteur de faisabilité

Le cœur de BrickFinder. On a l'inventaire (ce que tu as) et le catalogue (ce qu'il faut pour chaque set). Question : pour chaque set, peux-tu le construire, et à combien de % ? On construit l'algo étape par étape.

Étape 1 — retrouver vite « combien j'ai de telle pièce »

// une clé "propre" : on compare malgré la casse et les espaces
function cle(type, couleur) {
  return `${type}|${couleur}`.toLowerCase().replace(/\s+/g, "")
}

function indexer(inventaire) {
  const index = {}
  for (const p of inventaire) {
    index[cle(p.type, p.couleur)] = p.quantite
  }
  return index
}

Étape 2 — évaluer UN set

function evaluer(modele, index) {
  let ratioMin = Infinity
  const manquantes = []

  for (const piece of modele.pieces) {
    const dispo = index[cle(piece.type, piece.couleur)] || 0
    const ratio = dispo / piece.quantite
    if (ratio < ratioMin) ratioMin = ratio
    if (dispo < piece.quantite) {
      manquantes.push({ ...piece, manque: piece.quantite - dispo })
    }
  }

  return {
    nom: modele.nom,
    setNum: modele.setNum,
    image: modele.image,
    pourcentage: Math.round(Math.min(ratioMin, 1) * 100),
    constructible: manquantes.length === 0,
    manquantes
  }
}

Étape 3 — évaluer TOUT le catalogue et classer

async function faisabilite(inventaire) {
  const index = indexer(inventaire)
  const modeles = await Modele.find()
  return modeles
    .map(m => evaluer(m, index))
    .sort((a, b) => b.pourcentage - a.pourcentage)
}

Flag B3

Production (le gros morceau)

Teste ta fonction avec un inventaire écrit à la main qui contient une partie des pièces d'un set importé. Rends : le résultat (nom, pourcentage, pièces manquantes) et vérifie à la main le facteur limitant sur une pièce.

0 sur 7 dans cette étape

6. Brancher le tout : l'endpoint

On relie le moteur à une vraie route. Idéalement, on part de l'inventaire renvoyé par le Milestone 1. Ici, on fait une route qui reçoit un inventaire et renvoie la faisabilité.

app.post("/api/faisabilite", express.json(), async (req, res) => {
  const inventaire = req.body.inventaire
  if (!Array.isArray(inventaire)) {
    return res.status(400).json({ erreur: "inventaire (tableau) requis" })
  }
  const resultats = await faisabilite(inventaire)
  res.json({ resultats })
})

Teste (Thunder Client, POST, Body → JSON) avec par ex. :

Ce que tu dois voir
Envoi : { "inventaire": [ {"type":"Brick 2 x 4","couleur":"Red","quantite":3}, ... ] }

Réponse :
{ "resultats": [
    { "nom":"31147-1", "pourcentage":60, "constructible":false,
      "manquantes":[ {"type":"Plate 1 x 2","couleur":"Blue","quantite":4,"manque":2} ] },
    ...
] }

Flag C1

Debug / robustesse

Envoie une requête sans champ inventaire, puis avec un inventaire vide []. Rends : ce que répond le serveur dans chaque cas, et vérifie qu'il ne plante pas (il renvoie 400 ou une liste à 0 %).

0 sur 7 dans cette étape

7. La liste de courses

Dernière valeur ajoutée, très concrète : « qu'est-ce qu'il me manque en tout pour débloquer plus de sets ? ». On agrège les pièces manquantes.

function listeDeCourses(resultats) {
  const total = {}
  for (const r of resultats) {
    for (const m of r.manquantes) {
      const cle = `${m.type}|${m.couleur}`
      total[cle] = (total[cle] || 0) + m.manque
    }
  }
  return Object.entries(total).map(([cle, manque]) => {
    const [type, couleur] = cle.split("|")
    return { type, couleur, manque }
  })
}

Flag E1

Extension

Ajoute un endpoint POST /api/liste-courses qui renvoie cette liste à partir d'un inventaire. Rends : la liste obtenue pour ton inventaire de test, triée par manque décroissant (indice : .sort((a,b)=>b.manque-a.manque)).

0 sur 7 dans cette étape

Bravo — ton back a un cerveau

BriqueCe que tu sais faire
MongoDB / Mongooseconnecter une base, définir un schéma, créer/lire des documents
API Rebrickableinterroger une API externe, normaliser sa réponse vers ton format
Catalogueremplir et mettre à jour une collection sans doublons (upsert)
Moteur de faisabilitécomparer inventaire ↔ catalogue : % faisable, facteur limitant, manquantes
Liste de coursesagréger les manques sur plusieurs sets

Mini-quiz (vérifie-toi)

Exercice Réussi À revoir

  1. En MongoDB, qu'est-ce qu'une collection et un document ?
  2. À quoi sert upsert: true dans findOneAndUpdate ?
  3. Dans le moteur, qu'est-ce que le facteur limitant ?
  4. Pourquoi transforme-t-on l'inventaire en dictionnaire (indexer) ?
  5. Un set a besoin de 10 briques rouges, tu en as 4. Quel ratio pour cette pièce ? Combien en manque-t-il ?
Voir le corrigéMasquer le corrigé de l'exercice

1. Une collection = un groupe de documents (≈ une table) ; un document = un enregistrement façon JSON (≈ une ligne).
2. À créer le document s'il n'existe pas, sinon le mettre à jour — donc pas de doublon en relançant l'import.
3. La pièce avec le plus petit rapport (dispo ÷ requis) : c'est elle qui limite le nombre de sets constructibles.
4. Pour retrouver une quantité instantanément par type|couleur, au lieu de reparcourir toute la liste à chaque pièce.
5. Ratio = 4 ÷ 10 = 0,4 (40 %) ; il en manque 6.

Tu avais trouvé ?

Glossaire

MotDéfinition
Base de donnéesun stockage persistant (garde les données même serveur éteint)
MongoDB / NoSQLBDD qui range des documents façon JSON (pas de tables)
Collection / documentgroupe de documents / un enregistrement
Mongooselibrairie Node pour parler à MongoDB (schémas, requêtes)
Schémala forme attendue d'un document (champs + types)
upsertupdate-or-insert : met à jour si existe, crée sinon
API externeun service d'un autre serveur interrogé par fetch (ex. Rebrickable)
Normalisertraduire des données vers son propre format
Facteur limitantla pièce au plus petit rapport dispo÷requis

Compétences mobilisées — référentiel BTS SIO SLAM

Séance 2 terminé ?

Coche chaque livrable quand tu l'as : c'est la preuve que l'étape est acquise.

livrables
–
quiz réussi
–
de série
–
Passer à la séance 3 : BrickFinder Le front & la sécuritéVoir toutes les étapes du TP