diff --git a/CHANGELOG.md b/CHANGELOG.md index 0529231..f7377a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -172,6 +172,12 @@ « Cible de la somme de redondance », qui ne servait qu'à cette colonne, quitte la section des propositions : un fichier qui le porte s'ouvre sans message, et le perd au premier enregistrement. +- La section « Propositions » offre « Garder les 10 meilleures » : les dix + premières du classement restent, et celle d'où vient le placement retenu + aussi, les autres s'effacent. Le nombre se règle dans le champ voisin. Une + fenêtre dit d'abord combien de propositions partent, et « Défaire » les rend. + Une longue liste allégée garde le logiciel rapide ; le nombre choisi ne + s'enregistre pas dans l'événement. **Version de format des fichiers** : 2. Les étiquettes, les objectifs, leurs règles, les cases nouvelles, le mode des objectifs et l'ordre des priorités diff --git a/GUIDE-USAGE.md b/GUIDE-USAGE.md index 266180a..b76dcef 100644 --- a/GUIDE-USAGE.md +++ b/GUIDE-USAGE.md @@ -868,6 +868,7 @@ l’historique ; les noms des exemples sont inventés. | `enregistrerGeneration` | Généré 5 propositions (n° 3 à 7) | | `retenirProposition` | Retenu la proposition n° 3 | | `effacerPropositions` | Effacé 4 propositions, gardé la retenue (n° 2) | +| `garderMeilleures` | Gardé les 10 meilleures propositions, effacé 37 propositions, gardé aussi la retenue (n° 42) | | `creerTable` | Ajouté la table 3 | | `supprimerTable` | Supprimé la table 2 ; retirés : 2 réservations et 1 titre | | `deplacerTable` | Déplacé la table 3 | diff --git a/doc/captures/11-propositions.png b/doc/captures/11-propositions.png index 04780d6..ce94fa2 100644 Binary files a/doc/captures/11-propositions.png and b/doc/captures/11-propositions.png differ diff --git a/doc/captures/manifeste.json b/doc/captures/manifeste.json index 216cc90..edd5350 100644 --- a/doc/captures/manifeste.json +++ b/doc/captures/manifeste.json @@ -18,7 +18,7 @@ {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"4fb4ffe86dad81d901ab14b0e6e64d902f2e15c713c82f81ffb6840ceeff4724","etape":"7","etat":"0229d0d8208ef262e3b3485bc2f606cad900fe487d038276d3733a5423cb6a5b","facteurEchelle":1,"fichier":"09-refus-sur-place","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"31c13e2a0c8b0091bc834c9f93e0fb7451a7c5aab8a567229277862526914075"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":15.09,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.82,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":16.48,"role":"marque","seuil":3},{"jeton":"--couleur-liste-texte","ratio":14.82,"role":"texte","seuil":4.5},{"jeton":"--couleur-marque","ratio":3.63,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":13.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5},{"jeton":"--couleur-poignee","ratio":7.44,"role":"marque","seuil":3},{"jeton":"--couleur-refus","ratio":8.34,"role":"texte","seuil":4.5},{"jeton":"--couleur-selection","ratio":4.57,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":7.44,"role":"marque","seuil":3}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-chaise-pleine","ratio":14.94,"role":"marque","seuil":3},{"jeton":"--couleur-chaise-trait","ratio":8.89,"role":"marque","seuil":3},{"jeton":"--couleur-echelle","ratio":14.62,"role":"marque","seuil":3},{"jeton":"--couleur-hachure","ratio":15.21,"role":"marque","seuil":3},{"jeton":"--couleur-marque","ratio":3.5,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5},{"jeton":"--couleur-poignee","ratio":11.32,"role":"marque","seuil":3},{"jeton":"--couleur-refus","ratio":8.14,"role":"texte","seuil":4.5},{"jeton":"--couleur-selection","ratio":4.18,"role":"marque","seuil":3},{"jeton":"--couleur-table-trait","ratio":5.59,"role":"marque","seuil":3}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Qualité","Produire","Badges","Plan","Réservations","Tour affiché","1","2","3","Zoom +","Zoom −","Ajuster","Listes de noms","Légende","Plein écran","1. (réservée, tous les tours)","Basile Lanterneau","—","animation","2. (libre)","3. (libre)","4. (libre)","5. (libre)","6. (libre)","7. (libre)","8. (libre)","9. (libre)","10. (libre)","1. (libre)","— titre","animation","non pourvu","2. (réservée, tour 1)","Capucine Pommerel","3. (libre)","4. (libre)","5. (libre)","6. (libre)","7. (libre)","8. (libre)","Table 1, siège 1","Le siège 1 est réservé à une autre personne à tous les tours : déplacer sa réservation libère la place.","Choisir une place","Fermer","1","m","disposition relative — l'encombrement dans la salle n'est pas vérifié","Diagnostic","Chœur de l’Anse","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Société des Hérons","4 personnes : au moins 6 collisions cumulées sur les 3 tours, portées par au moins 2 paires distinctes","Cercle des Saules","3 personnes : au moins 3 collisions cumulées sur les 3 tours, portées par au moins 1 paire distincte","Tout l'événement","au moins 8 appartenances recroisées par personne","plafond a priori 11 rencontres pour 1 ancré et 11 mobiles","2 retours imposés par 1 ancrage","Listes de travail","Titres de place non pourvus","table 2 : titre","animation","non pourvu","Titres pressentis sans réservation","Gaspard Marbrelle","— titre pressenti","animation","Réserve","Personnes en attente au tour 1","Gaspard Marbrelle","Ysaline Sablonnet","Anselme Givrecœur","Côme Fenouillet","Apolline Coudraie","Basile Lanterneau","Brunœil","Faustine Ormelin","Edgar Ardoisier","Bertille Cressonnier","Table 1, siège 1","1. (réservée, tous les tours)","Basile Lanterneau","—","animation","Personne choisie :","Gaspard Marbrelle","Assigner","Retirer la réservation","Retirer le titre","Oublier le choix","Menu de","Basile Lanterneau","Déplacer","Échanger","Retirer du tour","Déplacer sa réservation","Exclure","Mettre en évidence","Ouvrir la fiche"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"5109fd63a112340134205ad495233ed17d9d2de83b790b1eae38f974a97e85c6","etape":"8","etat":"4d37d30748068354162752743c26aa7e0ab00d6e9b2aa1996b6f9fb410848280","facteurEchelle":1,"fichier":"10-diagnostic","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"633949b83c1d50135c859dad6dfd1815d54d0121963d86c7ff25c26a44904d2f"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":13.78,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Lecture","Liste des événements","Clair","Sombre","À propos","Lecture","Rien ne change ; « Modifier » passe en écriture.","Modifier","Lecture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Grande démonstration 1","260 participants","33 tables · 260 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Qualité","Produire","Badges","Diagnostic","Tout l'événement","écart d'itinéraire maximal : plancher 1, au moins 24 mobiles finissent sous leur plafond a priori","aucune relance ne le réduit : il tient aux tables pleines dont les capacités ou les ancrages diffèrent","plafond a priori 24 rencontres pour 4 ancrés","seules les capacités et les réservations le changent : aucune relance ne le dépasse","plafond a priori 28 rencontres pour 29 ancrés et 227 mobiles","seules les capacités et les réservations le changent : aucune relance ne le dépasse","99 retours imposés par 33 ancrages","les réservations les imposent : ils ne comptent pas parmi les retours que choisit le moteur"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"910ac5d3639d7c9ea4b52cfef4e402b5277fdf38379c9a0d196494f43727c5b5","etape":"9","etat":"1025fe8c346e3a503f00abcaf78cf004f33fe2c01a46bebd87da1bbf4d0b3f53","facteurEchelle":1,"fichier":"10a-ordre-priorites","graine":null,"image":{"hauteur":700,"largeur":1366,"sha256":"bb31be5ff2aee95f48210375982ec1594f4f2d73aabd8f6afb17dbb928b76618"},"instant":"2026-10-06T19:00:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.16,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Qualité","Produire","Badges","Génération","Meilleure proposition du classement","Aucune proposition classée : sans proposition comparable, le classement n'en désigne aucune.","Réglages de la génération","Graine (nombre entier, de 0 à 4 294 967 295)","Propositions par génération (nombre de propositions)","Compte d'arrêt (mouvements évalués par proposition)","Historique d'acceptation (longueur, en mouvements)","Contraintes de la génération","Séparer les appartenances","Nouveaux voisins à chaque tour","Nouvelle table à chaque tour","Varier les appartenances rencontrées","Respecter les objectifs des tables","Objectifs des tables : chercher d'abord","le plus de places servies","Le plus grand total de tours servis : le critère compare les tours hors objectif.","chacun au moins une fois","D'abord le plus de personnes servies à un tour au moins, puis le plus grand total : le critère compare les personnes jamais servies, puis les tours hors objectif.","l'équité d'abord","Le minimum d'abord, puis le total : d'abord la personne la plus mal servie, mesurée contre ses propres tours possibles, puis le plus grand total. Le critère compare le plus de tours hors objectif d'une personne, puis les tours hors objectif.","Rencontrer le plus d'animateurs différents","Attribuer les sièges","Ordre des priorités","Deux propositions se comparent palier par palier : un palier ne départage que des propositions égales sur ceux qui le précèdent. Dans un palier de plusieurs critères, chacun compte selon son poids.","Ordre réglé pour l'événement.","Palier 1 sur 6","écart au plafond a priori","Descendre « écart au plafond a priori » d'un palier","Palier 2 sur 6","excédent de collisions","Poids de « excédent de collisions » (nombre entier)","Monter le palier de « excédent de collisions »","Descendre le palier de « excédent de collisions »","Séparer « excédent de collisions » en palier propre","tours hors objectif","Poids de « tours hors objectif » (nombre entier)","Monter le palier de « tours hors objectif »","Descendre le palier de « tours hors objectif »","Séparer « tours hors objectif » en palier propre","Palier 3 sur 6","collisions cumulées","Monter « collisions cumulées » d'un palier","Descendre « collisions cumulées » d'un palier","Joindre « collisions cumulées » au palier du dessus","Palier 4 sur 6","rencontres répétées et retours","Monter « rencontres répétées et retours » d'un palier","Descendre « rencontres répétées et retours » d'un palier","Palier 5 sur 6","animateurs non rencontrés","décoché","Monter « animateurs non rencontrés » d'un palier","Descendre « animateurs non rencontrés » d'un palier","Palier 6 sur 6","redondance d'appartenance","Monter « redondance d'appartenance » d'un palier","Joindre « redondance d'appartenance » au palier du dessus","« tours hors objectif » est au palier 2 sur 6, avec « excédent de collisions ».","Rétablir l'ordre par défaut","Cœurs de calcul","Cœurs automatiques (un par processeur logique du poste)","Valeur automatique sur ce poste : 4 cœurs, un par processeur logique, hyperthreads compris.","Une génération n'emploie jamais plus de cœurs que de propositions demandées. Ce réglage vaut pour cette séance du logiciel sur ce poste : il ne s'enregistre pas dans l'événement.","Aucun calcul n'est encore mesuré sur ce poste pendant cette séance : la durée d'une génération est inconnue.","Générer"],"theme":"clair","versionReelle":"2026.10.05.01"}, -{"cadre":{"hauteur":700,"largeur":1366},"empreinte":"51623e46e66ecc4d93173f11996e6b0110a6f79a622553b6c1ea3003779c0f0e","etape":"9","etat":"b42451443701c80d433d632788edaab7dd1afea7a57add55a37fcedfdaee98ff","facteurEchelle":1,"fichier":"11-propositions","graine":4242,"image":{"hauteur":700,"largeur":1366,"sha256":"e1bc8ec4b87315960282b99459649f66b90ee4f839c2fd31303ef28ff5ba11d7"},"instant":"2026-10-06T19:02:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.02,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Qualité","Produire","Badges","Propositions","Propositions classées","Effacer les propositions","Ordre du classement, chaque critère au plus faible d'abord :","écart au plafond a priori",", puis","ensemble,","excédent de collisions","(poids 1)",",","tours hors objectif","(poids 3)",", puis","collisions cumulées",", puis","rencontres répétées",", puis","redondance d'appartenance",", puis","l'identifiant croissant",".","Personnes rencontrées","Collisions d'appartenance","Rencontres répétées et retours","Diversité d'appartenance","Manque et plafond a priori","Objectifs des tables et animateurs","Rang","Proposition","Personnes distinctes rencontrées, minimum : tous","Personnes distinctes rencontrées, minimum : mobiles","Personnes distinctes rencontrées, minimum : ancrés","Collisions cumulées (paire et tour)","Paires distinctes de même appartenance","Excédent de collisions (retrouvailles au-delà de la première)","Rencontres répétées choisies (paires)","Rencontres d'une même paire, au plus","Retours choisis à une table déjà occupée","Appartenances rencontrées A(p), minimum","Redondance d'appartenance, somme Σ r(p)","Taux de diversité d(p), minimum","Plus grand manque (personnes)","Personnes à qui il en manque","Écart d'itinéraire maximal (personnes)","Écart au plafond a priori maximal (personnes)","Tours hors objectif (personne et tour)","Tours selon l'objectif s(p), minimum","Certificat","Imposé par les réservations","Commandes","Placement retenu","3","Tel que la proposition n° 3.","11","11","11","26","15","11","30","3","11","3","85","0,27","0","0","0","0","1","1","minimum atteint","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","1","3","retenue","11","11","11","26","15","11","30","3","11","3","85","0,27","0","0","0","0","1","1","minimum atteint","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir","2","2","11","11","11","33","15","18","54","3","16","3","85","0,27","0","0","0","0","2","1","minimum atteint","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir","3","5","10","10","10","20","15","5","22","3","12","3","83","0,27","1","1","1","1","1","1","atteignabilité inconnue","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir","4","1","10","10","11","29","15","14","46","3","13","3","82","0,30","1","4","0","1","0","2","atteignabilité inconnue","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir","5","4","10","10","11","31","14","17","46","3","17","3","82","0,30","1","4","0","1","0","2","atteignabilité inconnue","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir"],"theme":"clair","versionReelle":"2026.10.05.01"}, +{"cadre":{"hauteur":700,"largeur":1366},"empreinte":"ab49c85c00eeaf21c01dfcb98bbbd6f10c164c3c5abd32bc6a5d2a2b6697b227","etape":"9","etat":"b42451443701c80d433d632788edaab7dd1afea7a57add55a37fcedfdaee98ff","facteurEchelle":1,"fichier":"11-propositions","graine":4242,"image":{"hauteur":700,"largeur":1366,"sha256":"5ac77c8a6f42eb8202e3ae30a3512a77ccb70fe56d92f1508317763b589ef03b"},"instant":"2026-10-06T19:02:00-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.02,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Soirée d’essai","13 participants","2 tables · 18 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Qualité","Produire","Badges","Propositions","Propositions classées","Propositions à garder","Garder les 10 meilleures","Effacer les propositions","Ordre du classement, chaque critère au plus faible d'abord :","écart au plafond a priori",", puis","ensemble,","excédent de collisions","(poids 1)",",","tours hors objectif","(poids 3)",", puis","collisions cumulées",", puis","rencontres répétées",", puis","redondance d'appartenance",", puis","l'identifiant croissant",".","Personnes rencontrées","Collisions d'appartenance","Rencontres répétées et retours","Diversité d'appartenance","Manque et plafond a priori","Objectifs des tables et animateurs","Rang","Proposition","Personnes distinctes rencontrées, minimum : tous","Personnes distinctes rencontrées, minimum : mobiles","Personnes distinctes rencontrées, minimum : ancrés","Collisions cumulées (paire et tour)","Paires distinctes de même appartenance","Excédent de collisions (retrouvailles au-delà de la première)","Rencontres répétées choisies (paires)","Rencontres d'une même paire, au plus","Retours choisis à une table déjà occupée","Appartenances rencontrées A(p), minimum","Redondance d'appartenance, somme Σ r(p)","Taux de diversité d(p), minimum","Plus grand manque (personnes)","Personnes à qui il en manque","Écart d'itinéraire maximal (personnes)","Écart au plafond a priori maximal (personnes)","Tours hors objectif (personne et tour)","Tours selon l'objectif s(p), minimum","Certificat","Imposé par les réservations","Commandes","Placement retenu","3","Tel que la proposition n° 3.","11","11","11","26","15","11","30","3","11","3","85","0,27","0","0","0","0","1","1","minimum atteint","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","1","3","retenue","11","11","11","26","15","11","30","3","11","3","85","0,27","0","0","0","0","1","1","minimum atteint","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir","2","2","11","11","11","33","15","18","54","3","16","3","85","0,27","0","0","0","0","2","1","minimum atteint","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir","3","5","10","10","10","20","15","5","22","3","12","3","83","0,27","1","1","1","1","1","1","atteignabilité inconnue","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir","4","1","10","10","11","29","15","14","46","3","13","3","82","0,30","1","4","0","1","0","2","atteignabilité inconnue","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir","5","4","10","10","11","31","14","17","46","3","17","3","82","0,30","1","4","0","1","0","2","atteignabilité inconnue","2 retours imposés par 1 ancrage.","Aucune rencontre répétée imposée.","Voir sur le plan","Qualité","Retenir"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"86a1859ab516d61b63161a9fb476f1d9d64bf356a6a8bd5dd45232ba7d2d8096","etape":"10","etat":"15fb09087464cf5b429c79e33325a0cd64579f1c650d7fd7aeabf6c3cbf977c0","facteurEchelle":1,"fichier":"12-qualite-profil","graine":4242,"image":{"hauteur":700,"largeur":1366,"sha256":"bf3ddc675d33f81c2a5d3d2f13fef4a1a837d9839996d71ebdce9b5d4930b0bb"},"instant":"2026-10-06T19:03:02-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-graphique-axe","ratio":5.64,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-borne","ratio":4.83,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-marque","ratio":9.67,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-repere","ratio":6.15,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-serie-1","ratio":5.1,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-serie-2","ratio":5.57,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":14.16,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-graphique-axe","ratio":5.38,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-borne","ratio":6.17,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-marque","ratio":10.64,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-repere","ratio":7.01,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-serie-1","ratio":6.89,"role":"marque","seuil":3},{"jeton":"--couleur-graphique-serie-2","ratio":7.83,"role":"marque","seuil":3},{"jeton":"--couleur-page-texte","ratio":11.23,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Grande démonstration 1","260 participants","33 tables · 260 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Qualité","Produire","Badges","Qualité","Placement mesuré :","Proposition n° 5 : un geste y pose une réservation","Revenir aux réservations","En-tête chiffré","Profil trié des rencontres","Collisions d'appartenance","Appartenances à chaque table","Paires répétées","Objectifs des tables","Animateurs rencontrés","Diversité par animateur","Performance du calcul","En-tête chiffré","Ce placement est-il bon, et qui s'y trouve mal servi ?","Population mesurée : 260 personnes.","Personnes rencontrées","Personnes distinctes rencontrées, minimum : tous","23","95 %","minimum 23 sur 24","chez François Ivrivet","plafond réalisé 24","plafond a priori 24","Personnes distinctes rencontrées, minimum : mobiles","24","Personnes distinctes rencontrées, minimum : ancrés","23","Collisions d'appartenance","Collisions cumulées (paire et tour)","52","Paires distinctes de même appartenance","52","Excédent de collisions (retrouvailles au-delà de la première)","0","Rencontres répétées et retours","Rencontres répétées choisies (paires)","103","0 imposée, à part","Rencontres d'une même paire, au plus","3","Retours choisis à une table déjà occupée","29","99 imposés, à part","Diversité d'appartenance","Appartenances rencontrées A(p), minimum","17","chez François Ivrivet","borne de diversité 24","Redondance d'appartenance, somme Σ r(p)","1 288","Taux de diversité d(p), minimum","0,64","Manque et plafond a priori","Plus grand manque (personnes)","3","chez Marc Luskouze","131 personnes au manque maximal","plafond réalisé 27","Personnes à qui il en manque","131","Écart d'itinéraire maximal (personnes)","4","plancher 1","Écart au plafond a priori maximal (personnes)","4","Objectifs des tables et animateurs","Animateurs rencontrés, minimum","3","ne classe pas","chez Zoé WAMMERWOLM-MAWWEMMEL","borne des animateurs 4","Animateurs non rencontrés","29","ne classe pas","atteignabilité inconnue","Profil trié des rencontres","Qui est le plus mal servi, de combien, et combien sont-ils ?","Profil trié des rencontres, en comptes de personnes","0","10","20","50","100","150","200","250","Personnes, de la moins servie à la mieux servie","rencontres","appartenances rencontrées","plafond réalisé","borne de diversité","rencontres","appartenances rencontrées","plafond réalisé","borne de diversité","ancré","Minimum 23 personnes rencontrées, 3 personnes au minimum, 131 personnes sous leur plafond réalisé.","Profil trié des rencontres, en pourcentage du plafond réalisé","0 %","50 %","100 %","50","100","150","200","250","Personnes, de la moins servie à la mieux servie","rencontres","100 %","rencontres","100 %","ancré","La personne au rang 1, François Ivrivet, rencontre","95 %","minimum 23 sur 24","Voir le tableau","Collisions d'appartenance","D'où vient le problème, et relancer peut-il y changer quelque chose ?","Collisions d'appartenance, par appartenance","0","2","4","6","8","Imprimerie Yzorne","effectif 12, 9 paires distinctes, excédent 0","Fabrique Sorbagne","effectif 11, 7 paires distinctes, excédent 0","Ateliers Falvigne","effectif 8, 3 paires distinctes, excédent 0","Ateliers Rossolde","effectif 11, 3 paires distinctes, excédent 0","Fonderie Estrolle","effectif 9, 3 paires distinctes, excédent 0","Verrerie Vaubrenne","effectif 10, 3 paires distinctes, excédent 0","Ateliers Tavrenne","effectif 8, 2 paires distinctes, excédent 0","Fonderie Xandrelle","effectif 6, 2 paires distinctes, excédent 0","Menuiserie Estrolle","effectif 9, 2 paires distinctes, excédent 0","Tannerie Nervanche","effectif 6, 2 paires distinctes, excédent 0","Ateliers Aubrenc","effectif 3, 1 paire distincte, excédent 0","Ateliers Cassorgue","effectif 3, 1 paire distincte, excédent 0","Brasserie Xandrelle","effectif 2, 1 paire distincte, excédent 0","Comptoir Gravelune","effectif 3, 1 paire distincte, excédent 0","Coopérative Jolvane","effectif 4, 1 paire distincte, excédent 0","Fabrique Kalvenne","effectif 3, 1 paire distincte, excédent 0","Filature Zelvaine","effectif 4, 1 paire distincte, excédent 0","Fonderie Sorbagne","effectif 5, 1 paire distincte, excédent 0","Menuiserie Gravelune","effectif 3, 1 paire distincte, excédent 0","Menuiserie Hostrenne","effectif 5, 1 paire distincte, excédent 0","Menuiserie Quimvaux","effectif 3, 1 paire distincte, excédent 0","Menuiserie Sorbagne","effectif 3, 1 paire distincte, excédent 0","Papeterie Hostrenne","effectif 2, 1 paire distincte, excédent 0","Papeterie Ulvardes","effectif 4, 1 paire distincte, excédent 0","Tannerie Belvarre","effectif 5, 1 paire distincte, excédent 0","Verrerie Aubrenc","effectif 2, 1 paire distincte, excédent 0","Ateliers Estrolle","effectif 3, 0 paire distincte, excédent 0","Ateliers Ivoranche","effectif 4, 0 paire distincte, excédent 0","Brasserie Belvarre","effectif 9, 0 paire distincte, excédent 0","Brasserie Dravenne","effectif 1, 0 paire distincte, excédent 0","Brasserie Lavorneau","effectif 2, 0 paire distincte, excédent 0","Brasserie Morvalle","effectif 2, 0 paire distincte, excédent 0","Brasserie Sorbagne","effectif 4, 0 paire distincte, excédent 0","Brasserie Wassorel","effectif 2, 0 paire distincte, excédent 0","Comptoir Ivoranche","effectif 1, 0 paire distincte, excédent 0","Comptoir Jolvane","effectif 3, 0 paire distincte, excédent 0","Comptoir Lavorneau","effectif 1, 0 paire distincte, excédent 0","Comptoir Sorbagne","effectif 4, 0 paire distincte, excédent 0","Comptoir Xandrelle","effectif 3, 0 paire distincte, excédent 0","Comptoir Yzorne","effectif 2, 0 paire distincte, excédent 0","Coopérative Dravenne","effectif 3, 0 paire distincte, excédent 0","Coopérative Gravelune","effectif 3, 0 paire distincte, excédent 0","Coopérative Morvalle","effectif 3, 0 paire distincte, excédent 0","Coopérative Rossolde","effectif 4, 0 paire distincte, excédent 0","Fabrique Ivoranche","effectif 3, 0 paire distincte, excédent 0","Fabrique Morvalle","effectif 1, 0 paire distincte, excédent 0","Fabrique Ulvardes","effectif 3, 0 paire distincte, excédent 0","Filature Dravenne","effectif 1, 0 paire distincte, excédent 0","Filature Olbrecq","effectif 2, 0 paire distincte, excédent 0","Filature Wassorel","effectif 3, 0 paire distincte, excédent 0","Fonderie Aubrenc","effectif 2, 0 paire distincte, excédent 0","Fonderie Ivoranche","effectif 2, 0 paire distincte, excédent 0","Fonderie Kalvenne","effectif 3, 0 paire distincte, excédent 0","Imprimerie Falvigne","effectif 3, 0 paire distincte, excédent 0","Imprimerie Hostrenne","effectif 1, 0 paire distincte, excédent 0","Menuiserie Aubrenc","effectif 1, 0 paire distincte, excédent 0","Menuiserie Ivoranche","effectif 1, 0 paire distincte, excédent 0","Menuiserie Jolvane","effectif 1, 0 paire distincte, excédent 0","Menuiserie Nervanche","effectif 1, 0 paire distincte, excédent 0","Menuiserie Olbrecq","effectif 4, 0 paire distincte, excédent 0","Menuiserie Tavrenne","effectif 1, 0 paire distincte, excédent 0","Menuiserie Ulvardes","effectif 6, 0 paire distincte, excédent 0","Menuiserie Wassorel","effectif 1, 0 paire distincte, excédent 0","Papeterie Estrolle","effectif 3, 0 paire distincte, excédent 0","Papeterie Wassorel","effectif 1, 0 paire distincte, excédent 0","Tannerie Aubrenc","effectif 3, 0 paire distincte, excédent 0","Tannerie Falvigne","effectif 1, 0 paire distincte, excédent 0","Tannerie Olbrecq","effectif 1, 0 paire distincte, excédent 0","Tannerie Quimvaux","effectif 1, 0 paire distincte, excédent 0","Verrerie Gravelune","effectif 1, 0 paire distincte, excédent 0","Verrerie Ivoranche","effectif 6, 0 paire distincte, excédent 0","Verrerie Nervanche","effectif 1, 0 paire distincte, excédent 0","Verrerie Olbrecq","effectif 2, 0 paire distincte, excédent 0","Verrerie Pravette","effectif 2, 0 paire distincte, excédent 0","reste","prouvé inévitable","reste","L'excédent le plus grand est celui de « Imprimerie Yzorne » : 0 collision.","Voir le tableau","Appartenances à chaque table","Où les mêmes cercles se retrouvent-ils à une même table ?","Ce que la grille compte","appartenances par siège","appartenances par personne assise","doublons parmi les affiliés","Appartenances à chaque table, tables par tours","Table 4","Table 12","Table 16","Table 20","Table 26","Table 33","Table 9","Table 10","Table 15","Table 24","Table 25","Table 29","Table 31","Table 32","Table 1","Table 2","Table 5","Table 6","Table 7","Table 8","Table 13","Table 14","Table 17","Table 18","Table 19","Table 21","Table 22","Table 23","Table 27","Table 28","Table 30","Table 3","Table 11","Tour 1","Tour 2","Tour 3","Tour 4","2","1","0","0","1","2","0","0","0","0","2","1","0","0","1","2","0","2","0","1","0","1","0","2","0","0","2","0","0","1","0","1","1","1","0","0","0","1","1","0","0","1","1","0","1","0","1","0","0","1","0","1","1","0","1","0","0","0","1","0","0","1","0","0","0","0","0","1","1","0","0","0","0","1","0","0","0","0","0","1","0","1","0","0","0","0","0","1","0","0","0","1","1","0","0","0","1","0","0","0","0","0","1","0","0","1","0","0","0","0","0","1","0","1","0","0","0","1","0","0","1","0","0","0","0","0","0","0","0","0","0","0","La table 4 porte la case la plus haute : 2.","Voir le tableau","Paires répétées","Qui revoit trop souvent la même personne ?","Paires 1 à 100 sur 103","Suivantes","Paires que la recherche réunit plus d'une fois","Paire","Rencontres de la paire","Tours","Samuel Cloveval","Menuiserie Ulvardes","Hélène Bralmeval","Brasserie Dravenne","3 fois","Voir au tour 3","Tour 1, table 23","Tour 3, table 15","Tour 4, table 32","Hugo Fralvançon","Brasserie Wassorel","Léa Fralvault","Fonderie Sorbagne","2 fois","Voir au tour 3","Tour 2, table 11","Tour 3, table 2","Hugo Fralvançon","Brasserie Wassorel","Louise Quilmançon","Menuiserie Estrolle","2 fois","Voir au tour 3","Tour 1, table 13","Tour 3, table 2","Zoé WAMMERWOLM-MAWWEMMEL","Menuiserie Ulvardes","Catherine Hulveron","Coopérative Rossolde","2 fois","Voir au tour 4","Tour 2, table 15","Tour 4, table 15","Catherine Urvesson","Fabrique Ulvardes","Olivier Bralmançon","Comptoir Lavorneau","2 fois","Voir au tour 4","Tour 2, table 29","Tour 4, table 6","Karim Hulvivet","Fonderie Xandrelle","Guillaume Luskichon","Brasserie Belvarre","2 fois","Voir au tour 3","Tour 1, table 2","Tour 3, table 2","Léa Fralvault","Fonderie Sorbagne","Lucie Tolvesson","Filature Zelvaine","2 fois","Voir au tour 2","Tour 1, table 15","Tour 2, table 11","Léa Fralvault","Fonderie Sorbagne","Samuel Abrevard","Filature Dravenne","2 fois","Voir au tour 4","Tour 2, table 11","Tour 4, table 4","Fatima Obrivet","Fabrique Sorbagne","Chantal Mirvumet","Menuiserie Gravelune","2 fois","Voir au tour 3","Tour 1, table 33","Tour 3, table 29","Philippe Voskeval","Menuiserie Estrolle","Émilie Korvard","Verrerie Vaubrenne","2 fois","Voir au tour 3","Tour 2, table 6","Tour 3, table 6","Zoé Dulveval","Menuiserie Quimvaux","Fatima Rozvumet","Menuiserie Hostrenne","2 fois","Voir au tour 4","Tour 2, table 15","Tour 4, table 13","Paul Korveux","Filature Olbrecq","Manon Quilmumet","Fonderie Sorbagne","2 fois","Voir au tour 3","Tour 1, table 15","Tour 3, table 5","Paul Korveux","Filature Olbrecq","Geneviève Mirvançon","Brasserie Belvarre","2 fois","Voir au tour 2","Tour 1, table 15","Tour 2, table 3","Thomas Rozvotte","Verrerie Ivoranche","Antoine Quilmeron","Fabrique Kalvenne","2 fois","Voir au tour 4","Tour 3, table 33","Tour 4, table 26","Isabelle Bralmumet","Verrerie Aubrenc","Félix Korveval","Tannerie Nervanche","2 fois","Voir au tour 2","Tour 1, table 19","Tour 2, table 28","Isabelle Bralmumet","Verrerie Aubrenc","Louis Mirveron","Filature Wassorel","2 fois","Voir au tour 4","Tour 3, table 22","Tour 4, table 16","Simon Mirvivet","Ateliers Ivoranche","Hélène Gorzumet","Fonderie Aubrenc","2 fois","Voir au tour 3","Tour 1, table 9","Tour 3, table 18","Lucie Voskeux","Coopérative Morvalle","Samuel Cloveval","Menuiserie Ulvardes","2 fois","Voir au tour 3","Tour 2, table 14","Tour 3, table 15","Fatima Korvouze","Ateliers Tavrenne","Olivier Jusvançon","Brasserie Morvalle","2 fois","Voir au tour 3","Tour 2, table 3","Tour 3, table 3","Étienne Fralvichon","Papeterie Ulvardes","Marc Luskouze","Ateliers Rossolde","2 fois","Voir au tour 4","Tour 1, table 21","Tour 4, table 33","Louise Quilmançon","Menuiserie Estrolle","Paul Obrichon","Ateliers Rossolde","2 fois","Voir au tour 2","Tour 1, table 13","Tour 2, table 21","Louise Quilmançon","Menuiserie Estrolle","Étienne Eskesson","Menuiserie Ulvardes","2 fois","Voir au tour 4","Tour 2, table 21","Tour 4, table 7","Pierre Abreveron","Ateliers Rossolde","Manon Tolveron","Brasserie Belvarre","2 fois","Voir au tour 3","Tour 1, table 5","Tour 3, table 5","Léa Tolvande","Ateliers Rossolde","Anne Abrevesson","Fonderie Estrolle","2 fois","Voir au tour 3","Tour 2, table 2","Tour 3, table 14","Antoine Quilmeron","Fabrique Kalvenne","Samuel Bralmeux","Ateliers Rossolde","2 fois","Voir au tour 3","Tour 2, table 33","Tour 3, table 33","Michel Eskeux","Papeterie Wassorel","Pierre Mirvard","Verrerie Ivoranche","2 fois","Voir au tour 4","Tour 1, table 1","Tour 4, table 21","Louise Hulvault","Coopérative Jolvane","Karim Fralveval","Coopérative Gravelune","2 fois","Voir au tour 4","Tour 3, table 24","Tour 4, table 4","Catherine Hulveron","Coopérative Rossolde","Alexandre Korvotte","Menuiserie Nervanche","2 fois","Voir au tour 4","Tour 3, table 15","Tour 4, table 15","François Ivrivet","Menuiserie Sorbagne","François Gorzichon","Ateliers Estrolle","2 fois","Voir au tour 4","Tour 3, table 32","Tour 4, table 32","Claire Fralvivet","Imprimerie Yzorne","Étienne Selvouze","Ateliers Tavrenne","2 fois","Voir au tour 3","Tour 1, table 14","Tour 3, table 27","Karim Gorzesson","Papeterie Ulvardes","Sophie Clovard","Brasserie Belvarre","2 fois","Voir au tour 4","Tour 1, table 20","Tour 4, table 12","Manon Urvançon","Ateliers Tavrenne","Guillaume Tolvignac","Menuiserie Sorbagne","2 fois","Voir au tour 4","Tour 3, table 1","Tour 4, table 1","Sylvie Quilmande","Imprimerie Yzorne","Antoine Bralmault","Fabrique Sorbagne","2 fois","Voir au tour 4","Tour 1, table 4","Tour 4, table 4","Sylvie Quilmande","Imprimerie Yzorne","Caroline Rozvançon","Brasserie Belvarre","2 fois","Voir au tour 2","Tour 1, table 4","Tour 2, table 14","Sylvie Quilmande","Imprimerie Yzorne","Hélène Rozveval","Comptoir Ivoranche","2 fois","Voir au tour 4","Tour 2, table 14","Tour 4, table 4","Amina Pruvesson","Fonderie Sorbagne","Amina Hulvouze","Coopérative Dravenne","2 fois","Voir au tour 4","Tour 1, table 18","Tour 4, table 16","Amina Pruvesson","Fonderie Sorbagne","Léa Obrard","Comptoir Sorbagne","2 fois","Voir au tour 2","Tour 1, table 18","Tour 2, table 31","Lucie Quilmière","Fabrique Ivoranche","Anne Urvivet","Fonderie Kalvenne","2 fois","Voir au tour 2","Tour 1, table 21","Tour 2, table 9","Louis Selvivet","Menuiserie Hostrenne","Guillaume Hulvotte","Verrerie Vaubrenne","2 fois","Voir au tour 4","Tour 2, table 25","Tour 4, table 25","Lucie Tolvesson","Filature Zelvaine","Manon Quilmumet","Fonderie Sorbagne","2 fois","Voir au tour 4","Tour 1, table 15","Tour 4, table 25","Olivier Quilmesson","Comptoir Gravelune","Jean Neskeron","Filature Zelvaine","2 fois","Voir au tour 3","Tour 2, table 33","Tour 3, table 9","Gabriel Neskault","Menuiserie Tavrenne","Geneviève Abrevignac","Brasserie Morvalle","2 fois","Voir au tour 4","Tour 2, table 20","Tour 4, table 18","Gabriel Neskault","Menuiserie Tavrenne","François Clovotte","Coopérative Rossolde","2 fois","Voir au tour 3","Tour 2, table 20","Tour 3, table 25","Gabriel Neskault","Menuiserie Tavrenne","Chantal Abrevotte","Ateliers Ivoranche","2 fois","Voir au tour 3","Tour 2, table 20","Tour 3, table 25","Olivier Jusvançon","Brasserie Morvalle","Hélène Selveux","Brasserie Lavorneau","2 fois","Voir au tour 4","Tour 1, table 24","Tour 4, table 25","Gabriel Pruveux","Papeterie Estrolle","Guillaume Obrançon","Papeterie Hostrenne","2 fois","Voir au tour 4","Tour 1, table 31","Tour 4, table 27","Manon Clovière","Ateliers Ivoranche","Marc Vandergrolcquesmirtavelle-Ostrovaubelindigues","Tannerie Aubrenc","2 fois","Voir au tour 4","Tour 3, table 10","Tour 4, table 10","François Obrouze","Tannerie Olbrecq","Camille Tolvumet","Fonderie Estrolle","2 fois","Voir au tour 3","Tour 2, table 31","Tour 3, table 31","Claire Gorzeron","Menuiserie Hostrenne","Guillaume Gorzivet","Fonderie Estrolle","2 fois","Voir au tour 4","Tour 1, table 32","Tour 4, table 8","Hélène Luskumet","Brasserie Xandrelle","Julie Quilmignac","Menuiserie Jolvane","2 fois","Voir au tour 3","Tour 2, table 23","Tour 3, table 8","Louis Fralvande","Ateliers Rossolde","Philippe Ivroubre","Ateliers Tavrenne","2 fois","Voir au tour 4","Tour 1, table 19","Tour 4, table 19","Isabelle Korvichon","Menuiserie Ulvardes","Marie Dulvesson","Ateliers Cassorgue","2 fois","Voir au tour 4","Tour 1, table 7","Tour 4, table 10","Isabelle Korvichon","Menuiserie Ulvardes","Karim Clovivet","Fonderie Kalvenne","2 fois","Voir au tour 3","Tour 2, table 18","Tour 3, table 23","Fatima Tolvoubre","Papeterie Ulvardes","Marc Luskouze","Ateliers Rossolde","2 fois","Voir au tour 4","Tour 3, table 8","Tour 4, table 33","Zoé Gorzière","Menuiserie Olbrecq","François Eskard","Fonderie Xandrelle","2 fois","Voir au tour 3","Tour 2, table 7","Tour 3, table 25","Antoine Obreval","Ateliers Cassorgue","Alexandre Ivreux","Tannerie Aubrenc","2 fois","Voir au tour 2","Tour 1, table 25","Tour 2, table 6","Antoine Obreval","Ateliers Cassorgue","Hugo Bralmouze","Tannerie Falvigne","2 fois","Voir au tour 4","Tour 3, table 23","Tour 4, table 23","Michel Korveron","Fonderie Sorbagne","Julie Quilmignac","Menuiserie Jolvane","2 fois","Voir au tour 4","Tour 3, table 8","Tour 4, table 8","Marie Dulvesson","Ateliers Cassorgue","Amina Hulvouze","Coopérative Dravenne","2 fois","Voir au tour 3","Tour 2, table 16","Tour 3, table 17","Youssef Jusvard","Tannerie Aubrenc","Simon Hulvumet","Ateliers Ivoranche","2 fois","Voir au tour 4","Tour 2, table 21","Tour 4, table 21","Gabriel Mirvotte","Tannerie Belvarre","Paul Neskivet","Comptoir Jolvane","2 fois","Voir au tour 4","Tour 3, table 21","Tour 4, table 7","Marc Luskouze","Ateliers Rossolde","Isabelle Selvichon","Tannerie Nervanche","2 fois","Voir au tour 3","Tour 1, table 21","Tour 3, table 8","Louis Hulvichon","Ateliers Falvigne","Nicolas Ivrichon","Fabrique Sorbagne","2 fois","Voir au tour 2","Tour 1, table 17","Tour 2, table 24","Pierre Pruveval","Comptoir Xandrelle","Julie Quilmignac","Menuiserie Jolvane","2 fois","Voir au tour 3","Tour 1, table 10","Tour 3, table 8","Geneviève Abrevignac","Brasserie Morvalle","Pierre Mirvard","Verrerie Ivoranche","2 fois","Voir au tour 3","Tour 1, table 1","Tour 3, table 7","Olivier Bralmançon","Comptoir Lavorneau","Véronique Jusvouze","Verrerie Vaubrenne","2 fois","Voir au tour 4","Tour 1, table 28","Tour 4, table 6","Marc Luskoubre","Verrerie Vaubrenne","Thomas Quilmichon","Fonderie Estrolle","2 fois","Voir au tour 4","Tour 1, table 33","Tour 4, table 5","Camille Jusvumet","Fonderie Estrolle","Guillaume Eskotte","Verrerie Nervanche","2 fois","Voir au tour 3","Tour 1, table 27","Tour 3, table 11","Antoine Selvière","Coopérative Dravenne","Jean Neskeron","Filature Zelvaine","2 fois","Voir au tour 3","Tour 1, table 11","Tour 3, table 9","Antoine Bralmault","Fabrique Sorbagne","Amina Rozvichon","Verrerie Olbrecq","2 fois","Voir au tour 3","Tour 2, table 4","Tour 3, table 4","Antoine Bralmault","Fabrique Sorbagne","Antoine Pruvouze","Imprimerie Yzorne","2 fois","Voir au tour 2","Tour 1, table 4","Tour 2, table 4","Émilie Abrevière","Imprimerie Yzorne","Fatima Hulvard","Ateliers Estrolle","2 fois","Voir au tour 4","Tour 2, table 29","Tour 4, table 29","Émilie Abrevière","Imprimerie Yzorne","Geneviève Obresson","Coopérative Gravelune","2 fois","Voir au tour 3","Tour 1, table 29","Tour 3, table 29","Émilie Abrevière","Imprimerie Yzorne","Chantal Mirvumet","Menuiserie Gravelune","2 fois","Voir au tour 4","Tour 3, table 29","Tour 4, table 29","Olivier Tolvichon","Fonderie Xandrelle","Étienne Gorzeval","Papeterie Ulvardes","2 fois","Voir au tour 3","Tour 1, table 33","Tour 3, table 6","Félix Korveval","Tannerie Nervanche","Thomas Selvignac","Brasserie Sorbagne","2 fois","Voir au tour 3","Tour 2, table 28","Tour 3, table 28","Émilie Dulvichon","Menuiserie Ulvardes","Lucie Voskeron","Verrerie Ivoranche","2 fois","Voir au tour 4","Tour 3, table 10","Tour 4, table 20","Émilie Dulvichon","Menuiserie Ulvardes","Julie Voskesson","Brasserie Belvarre","2 fois","Voir au tour 4","Tour 3, table 10","Tour 4, table 20","Claire Jusvivet","Imprimerie Yzorne","Hugo Voskumet","Ateliers Tavrenne","2 fois","Voir au tour 3","Tour 1, table 20","Tour 3, table 20","Amina Quilmault","Coopérative Jolvane","Philippe Abreveval","Ateliers Falvigne","2 fois","Voir au tour 3","Tour 1, table 3","Tour 3, table 4","Hélène Gorzumet","Fonderie Aubrenc","Émilie Quilmeux","Ateliers Falvigne","2 fois","Voir au tour 4","Tour 1, table 9","Tour 4, table 21","Sophie Ivrault","Coopérative Morvalle","Philippe Fralvignac","Menuiserie Estrolle","2 fois","Voir au tour 4","Tour 3, table 13","Tour 4, table 26","Nicolas Korvande","Comptoir Sorbagne","Chantal Obrignac","Imprimerie Yzorne","2 fois","Voir au tour 3","Tour 2, table 27","Tour 3, table 22","Fatima Urvignac","Ateliers Aubrenc","Claire Bralmotte","Ateliers Tavrenne","2 fois","Voir au tour 3","Tour 2, table 23","Tour 3, table 21","François Clovotte","Coopérative Rossolde","Chantal Abrevotte","Ateliers Ivoranche","2 fois","Voir au tour 3","Tour 2, table 20","Tour 3, table 25","Philippe Dulvoubre","Brasserie Belvarre","Émilie Quilmeux","Ateliers Falvigne","2 fois","Voir au tour 2","Tour 1, table 9","Tour 2, table 10","Chantal Mirvumet","Menuiserie Gravelune","Geneviève Mirvançon","Brasserie Belvarre","2 fois","Voir au tour 4","Tour 2, table 3","Tour 4, table 29","Camille Neskard","Menuiserie Gravelune","Amina Hulvouze","Coopérative Dravenne","2 fois","Voir au tour 4","Tour 2, table 16","Tour 4, table 16","Sophie Pruvière","Ateliers Rossolde","Raphaël Jusvault","Fabrique Sorbagne","2 fois","Voir au tour 2","Tour 1, table 16","Tour 2, table 2","Fatima Abrevançon","Tannerie Nervanche","Caroline Jusvoubre","Fabrique Sorbagne","2 fois","Voir au tour 2","Tour 1, table 13","Tour 2, table 13","Fatima Abrevançon","Tannerie Nervanche","Fatima Rozvumet","Menuiserie Hostrenne","2 fois","Voir au tour 4","Tour 1, table 13","Tour 4, table 13","Karim Clovançon","Fabrique Sorbagne","Caroline Rozvançon","Brasserie Belvarre","2 fois","Voir au tour 4","Tour 2, table 14","Tour 4, table 17","Félix Eskouze","Filature Zelvaine","Sylvie Abreveux","Comptoir Jolvane","2 fois","Voir au tour 3","Tour 1, table 3","Tour 3, table 19","Olivier Korvière","Ateliers Falvigne","Manon Pruvivet","Menuiserie Aubrenc","2 fois","Voir au tour 2","Tour 1, table 20","Tour 2, table 10","Félix Voskande","Ateliers Falvigne","Guillaume Mirvoubre","Comptoir Sorbagne","2 fois","Voir au tour 4","Tour 2, table 7","Tour 4, table 7","Lucie Voskeron","Verrerie Ivoranche","Julie Voskesson","Brasserie Belvarre","2 fois","Voir au tour 4","Tour 3, table 10","Tour 4, table 20","Thomas Selvignac","Brasserie Sorbagne","Étienne Voskançon","Menuiserie Olbrecq","2 fois","Voir au tour 4","Tour 1, table 28","Tour 4, table 28","Jean Neskeron","Filature Zelvaine","Zoé Mirveval","Fabrique Kalvenne","2 fois","Voir au tour 2","Tour 1, table 11","Tour 2, table 33","Sophie Clovard","Brasserie Belvarre","Marc Rozvouze","Ateliers Estrolle","2 fois","Voir au tour 4","Tour 3, table 1","Tour 4, table 12","Alexandre Eskançon","Menuiserie Hostrenne","François Eskard","Fonderie Xandrelle","2 fois","Voir au tour 3","Tour 1, table 17","Tour 3, table 25","Qui a rencontré qui","La matrice n'est pas dessinée : 260 personnes dépassent le seuil de 44. La fiche de chaque personne porte ses rencontres.","Objectifs des tables","Qui n'est jamais assis à une table qui lui correspond, et à quel tour l'objectif perd-il ?","Aucune personne n'est concernée par les objectifs des tables : la vue n'a rien à montrer.","Animateurs rencontrés","Qui a rencontré le moins d'animateurs ?","Animateurs rencontrés, de la personne la moins servie à la mieux servie","0","1","2","3","4","50","100","150","200","rang","animateurs rencontrés","borne des animateurs","animateurs rencontrés","borne des animateurs","Minimum 3 animateurs rencontrés, 29 personnes au minimum.","Voir le tableau","Diversité par animateur","Quel animateur voit le moins de diversité à sa table ?","Aucune personne présente ne porte d'étiquette : seule la colonne des appartenances se dessine.","Appartenances rencontrées en animant","le moins varié","17","le plus varié","27","au minimum","1","Diversité rencontrée en animant, sur la soirée","0","10","20","François Ivrivet","4 tours animés, 23 personnes rencontrées en animant","Samuel Bralmeux","4 tours animés, 23 personnes rencontrées en animant","Geneviève Dulvumet","4 tours animés, 24 personnes rencontrées en animant","François Obrouze","4 tours animés, 23 personnes rencontrées en animant","Antoine Bralmault","4 tours animés, 25 personnes rencontrées en animant","Catherine Hulveron","4 tours animés, 26 personnes rencontrées en animant","Camille Neskard","4 tours animés, 27 personnes rencontrées en animant","Guillaume Luskichon","4 tours animés, 27 personnes rencontrées en animant","Philippe Ivroubre","4 tours animés, 27 personnes rencontrées en animant","Marie Urvard","4 tours animés, 28 personnes rencontrées en animant","Sophie Pruvotte","4 tours animés, 28 personnes rencontrées en animant","Thomas Selvignac","4 tours animés, 26 personnes rencontrées en animant","Philippe Voskeval","4 tours animés, 27 personnes rencontrées en animant","Manon Urvançon","4 tours animés, 27 personnes rencontrées en animant","Marc Vandergrolcquesmir…","4 tours animés, 27 personnes rencontrées en animant","Fatima Abrevançon","4 tours animés, 26 personnes rencontrées en animant","Claire Jusvivet","4 tours animés, 27 personnes rencontrées en animant","Félix Voskande","4 tours animés, 27 personnes rencontrées en animant","Louise Neskichon","4 tours animés, 28 personnes rencontrées en animant","Gabriel Abrevault","4 tours animés, 28 personnes rencontrées en animant","Émilie Abrevière","4 tours animés, 25 personnes rencontrées en animant","Youssef Jusvard","4 tours animés, 27 personnes rencontrées en animant","Manon Tolveron","4 tours animés, 27 personnes rencontrées en animant","Hugo Bralmouze","4 tours animés, 27 personnes rencontrées en animant","Marie Bralmesson","4 tours animés, 28 personnes rencontrées en animant","Louis Selvivet","4 tours animés, 27 personnes rencontrées en animant","Michel Korveron","4 tours animés, 27 personnes rencontrées en animant","Youssef Neskière","4 tours animés, 28 personnes rencontrées en animant","Jean Fralvumet","4 tours animés, 28 personnes rencontrées en animant","Fatima Korvouze","4 tours animés, 27 personnes rencontrées en animant","Gabriel Ivreval","4 tours animés, 28 personnes rencontrées en animant","Guillaume Dulvivet","4 tours animés, 28 personnes rencontrées en animant","Étienne Tolvançon","4 tours animés, 28 personnes rencontrées en animant","Appartenances rencontrées en animant","Appartenances rencontrées en animant","Le moins varié rencontre 17 appartenances en animant ; 1 animateur au minimum.","Voir le tableau","Performance du calcul","Relancer plus longtemps aurait-il donné mieux ?","2 000 mouvements évalués sur 2 000","durée observée 2,5 s","1 cœur de calcul","cadence 4 000 mouvements par seconde","génération de 5 propositions","Valeurs mesurées aux seuls points de relevé, sous les réglages courants, reliées par un trait : entre deux points, rien n'est mesuré.","minimum brut, aux points de relevé","0","5","10","15","20","1","10","100","2 000","Mouvements évalués, échelle logarithmique","minimum brut","minimum brut","Au premier point de relevé, minimum brut : 21 ; au dernier : 23.","écart au plafond a priori, aux points de relevé","0","2","4","6","1","10","100","2 000","Mouvements évalués, échelle logarithmique","écart au plafond a priori","écart au plafond a priori","Au premier point de relevé, écart au plafond a priori : 6 ; au dernier : 4.","excédent de collisions, aux points de relevé","0","1","2","3","4","1","10","100","2 000","Mouvements évalués, échelle logarithmique","excédent de collisions","excédent de collisions","Au premier point de relevé, excédent de collisions : 3 ; au dernier : 0.","collisions cumulées, aux points de relevé","0","20","40","60","1","10","100","2 000","Mouvements évalués, échelle logarithmique","collisions cumulées","collisions cumulées","Au premier point de relevé, collisions cumulées : 67 ; au dernier : 52.","rencontres répétées, aux points de relevé","0","50","100","1","10","100","2 000","Mouvements évalués, échelle logarithmique","rencontres répétées","rencontres répétées","Au premier point de relevé, rencontres répétées : 145 ; au dernier : 103.","redondance d'appartenance, aux points de relevé","0","500","1 000","1","10","100","2 000","Mouvements évalués, échelle logarithmique","redondance d'appartenance","redondance d'appartenance","Au premier point de relevé, redondance d'appartenance : 1 298 ; au dernier : 1 288.","Une valeur a changé au dernier point de relevé, à 2 000 mouvements.","Voir le tableau"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"17901ca2dd320a78adb4011bc7ebe0c7360d548b65179380a1676bd3023c39ff","etape":"10","etat":"b0cdfdc39ba1cb940f71e0266e14706cc8bb2e7808ad0d42f907e320498349cb","facteurEchelle":1,"fichier":"12a-qualite-sans-placement","graine":4242,"image":{"hauteur":700,"largeur":1366,"sha256":"1dadccb14b0f2dbe84b5e6bc60ebe64647db1daa75068fe2648c96f420b8bed5"},"instant":"2026-10-06T19:03:02-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":13.78,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.57,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.07,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Lecture","Liste des événements","Clair","Sombre","À propos","Lecture","Rien ne change ; « Modifier » passe en écriture.","Modifier","Lecture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Petite démonstration, conflit inévitable 1","12 participants","4 tables · 12 sièges","4 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Qualité","Produire","Badges","Qualité","Placement mesuré :","aucun, le plan ne montre que les réservations","Aucun placement à mesurer : ni proposition ni placement retenu. Le diagnostic dit ce que la configuration rend inévitable.","Diagnostic","Ateliers Jolvane","5 personnes : au moins 4 collisions cumulées sur les 4 tours, portées par au moins 1 paire distincte","Tout l'événement","au moins 5 appartenances recroisées par personne","plafond a priori 8 rencontres pour 12 mobiles"],"theme":"clair","versionReelle":"2026.10.05.01"}, {"cadre":{"hauteur":700,"largeur":1366},"empreinte":"7d2742b5210124adf45c7dea1bbcae3b0ee0d237833e12d1eec36fee9b0d35ea","etape":"10","etat":"5d6ecc1054a87ebe7527e876248fd7169cfac07500c8468e939c6e7e85b28e45","facteurEchelle":1,"fichier":"12b-qualite-une-table","graine":4242,"image":{"hauteur":700,"largeur":1366,"sha256":"d2625f8ca7bcab6b3094501d65d44be8999b84a2534dfecacc2219bb24b0c825"},"instant":"2026-10-06T19:03:02-04:00","mode":"chromium-headless-shell","pdf":null,"source":"navigateur","styles":{"clair":[{"jeton":"--couleur-bandeau-fond","ratio":15.94,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":6.09,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":9.05,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":14.16,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":5.81,"role":"texte","seuil":4.5}],"sombre":[{"jeton":"--couleur-action-texte","ratio":8.45,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-fond","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte","ratio":12.84,"role":"texte","seuil":4.5},{"jeton":"--couleur-bandeau-texte-secondaire","ratio":7.6,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte","ratio":11.23,"role":"texte","seuil":4.5},{"jeton":"--couleur-page-texte-secondaire","ratio":6.72,"role":"texte","seuil":4.5}]},"texte":["Gestion table tournante Libre","construction de documentation","documentation","Écriture","Liste des événements","Clair","Sombre","À propos","Écriture","Chaque geste s’enregistre aussitôt ; « Revenir en lecture » arrête l’écriture.","Revenir en lecture","Écriture","Défaire","État du plan","Brouillon","Proposé","Retenu","Bloqué","Imprimer…","Une seule table","6 participants","1 table · 6 sièges","3 tours","Préparer","Import","Participants","Tables et tours","Placer","Plan","Diagnostic","Génération","Propositions","Qualité","Produire","Badges","Qualité","Placement mesuré :","Proposition n° 1 : un geste y pose une réservation","Revenir aux réservations","En-tête chiffré","Profil trié des rencontres","Collisions d'appartenance","Appartenances à chaque table","Paires répétées","Objectifs des tables","Animateurs rencontrés","Diversité par animateur","Performance du calcul","En-tête chiffré","Ce placement est-il bon, et qui s'y trouve mal servi ?","Population mesurée : 6 personnes.","Personnes rencontrées","Personnes distinctes rencontrées, minimum : tous","5","100 %","minimum 5 sur 5","chez Iris Ombrelle","plafond réalisé 5","plafond a priori 5","Personnes distinctes rencontrées, minimum : mobiles","5","Personnes distinctes rencontrées, minimum : ancrés","—","Collisions d'appartenance","Collisions cumulées (paire et tour)","9","plancher 9, le meilleur atteint","Paires distinctes de même appartenance","3","plancher 3, le meilleur atteint","Excédent de collisions (retrouvailles au-delà de la première)","6","plancher 6, le meilleur atteint","Rencontres répétées et retours","Rencontres répétées choisies (paires)","15","0 imposée, à part","Rencontres d'une même paire, au plus","3","Retours choisis à une table déjà occupée","12","0 imposé, à part","Diversité d'appartenance","Appartenances rencontrées A(p), minimum","3","chez Iris Ombrelle","borne de diversité 3","Redondance d'appartenance, somme Σ r(p)","12","Taux de diversité d(p), minimum","0,60","Manque et plafond a priori","Plus grand manque (personnes)","0","chez Iris Ombrelle","0 personne au manque maximal","plafond réalisé 5","Personnes à qui il en manque","0","Écart d'itinéraire maximal (personnes)","0","Écart au plafond a priori maximal (personnes)","0","minimum atteint","Profil trié des rencontres","Qui est le plus mal servi, de combien, et combien sont-ils ?","Une seule table : chacun y rencontre tous les autres, et le profil dirait la même chose de chacun.","Collisions d'appartenance","D'où vient le problème, et relancer peut-il y changer quelque chose ?","Une seule table : chacun y retrouve chaque membre de son appartenance, à chaque tour.","Appartenances à chaque table","Où les mêmes cercles se retrouvent-ils à une même table ?","Une seule table : elle réunit toutes les appartenances à chaque tour.","Paires répétées","Qui revoit trop souvent la même personne ?","Une seule table : chaque paire se revoit à chaque tour.","Objectifs des tables","Qui n'est jamais assis à une table qui lui correspond, et à quel tour l'objectif perd-il ?","Aucune personne n'est concernée par les objectifs des tables : la vue n'a rien à montrer.","Animateurs rencontrés","Qui a rencontré le moins d'animateurs ?","Aucune personne n'est concernée par les animateurs, ou aucun animateur n'est désigné : la vue n'a rien à montrer.","Diversité par animateur","Quel animateur voit le moins de diversité à sa table ?","Aucun animateur n'est désigné : la vue n'a rien à montrer.","Performance du calcul","Relancer plus longtemps aurait-il donné mieux ?","300 mouvements évalués sur 300","durée inconnue","1 cœur de calcul","génération de 5 propositions","Valeurs mesurées aux seuls points de relevé, sous les réglages courants, reliées par un trait : entre deux points, rien n'est mesuré.","minimum brut, aux points de relevé","0","2","4","1","10","100","300","Mouvements évalués, échelle logarithmique","minimum brut","minimum brut","Au premier point de relevé, minimum brut : 5 ; au dernier : 5.","écart au plafond a priori, aux points de relevé","0","1","10","100","300","Mouvements évalués, échelle logarithmique","écart au plafond a priori","écart au plafond a priori","Au premier point de relevé, écart au plafond a priori : 0 ; au dernier : 0.","excédent de collisions, aux points de relevé","0","2","4","6","1","10","100","300","Mouvements évalués, échelle logarithmique","excédent de collisions","excédent de collisions","Au premier point de relevé, excédent de collisions : 6 ; au dernier : 6.","collisions cumulées, aux points de relevé","0","2","4","6","8","1","10","100","300","Mouvements évalués, échelle logarithmique","collisions cumulées","collisions cumulées","Au premier point de relevé, collisions cumulées : 9 ; au dernier : 9.","rencontres répétées, aux points de relevé","0","5","10","15","1","10","100","300","Mouvements évalués, échelle logarithmique","rencontres répétées","rencontres répétées","Au premier point de relevé, rencontres répétées : 15 ; au dernier : 15.","redondance d'appartenance, aux points de relevé","0","5","10","1","10","100","300","Mouvements évalués, échelle logarithmique","redondance d'appartenance","redondance d'appartenance","Au premier point de relevé, redondance d'appartenance : 12 ; au dernier : 12.","Aucune valeur n'a changé entre 1 et 300 mouvements ; une autre graine est l'autre façon de chercher.","Voir le tableau"],"theme":"clair","versionReelle":"2026.10.05.01"}, diff --git a/scripts/documentation.js b/scripts/documentation.js index 12cf69f..557d6b6 100644 --- a/scripts/documentation.js +++ b/scripts/documentation.js @@ -365,6 +365,7 @@ export const EXEMPLES_COMMANDES = Object.freeze({ enregistrerGeneration: { n: 5, premier: 3, dernier: 7 }, retenirProposition: { id: 3 }, effacerPropositions: { n: 4, gardee: 2 }, + garderMeilleures: { n: 10, effacees: 37, gardee: 42 }, creerTable: { numero: 3 }, supprimerTable: { numero: 2, reservations: 2, titres: 1 }, deplacerTable: { numero: 3 }, diff --git a/spec.md b/spec.md index c394326..2d3a394 100644 --- a/spec.md +++ b/spec.md @@ -69,7 +69,8 @@ Aucun des deux ne dépend de l'étape atteinte. geste (§ 5.9), **là où le geste a eu lieu** : sur la place pour un geste du plan, dans l'aperçu d'import pour une ligne de CSV (§ 10.1), sur le champ pour une saisie. Seul un geste qui détruit de la saisie ouvre une fenêtre à - confirmer. + confirmer, et « Garder les N meilleures », qui efface d'un coup des + propositions que l'écran ne montre pas toutes (§ 5.7). Les étapes 8 et 10 — lire le diagnostic, comparer — ne modifient rien et restent vivantes en lecture (§ 8.4). @@ -1029,13 +1030,57 @@ réglages ajoute des candidats plutôt que d'effacer les précédents, et un classement unique les ordonne tous. Une commande efface la liste en épargnant celui qui est retenu. +**« Garder les N meilleures » allège la liste sans la vider.** La commande +`garderMeilleures {n}` garde les n premières propositions du classement unique +— les n premières lignes que le tableau montre, dans son ordre : les +comparables dans l'ordre du classement, puis celles hors classement par +identifiant —, et la proposition d'où vient le placement retenu quand elle +vient après elles : n + 1 au plus. Les autres s'effacent, en une seule entrée +d'historique que « Défaire » reprend (§ 8.3), libellée « Gardé les 10 +meilleures propositions, effacé 37 propositions », et « gardé aussi la retenue +(n° 42) » quand la proposition du retenu reste en plus. Le partage se lit sur +la mesure qui classe le tableau, jamais sur un ordre que l'écran referait. Le +compteur ne recule pas (§ 8.9), et l'état du plan ne change pas : une +proposition au moins reste. Rien à effacer — n au moins le nombre de +propositions, ou la seule au-delà est celle du retenu — : le geste est sans +effet, refusé comme tel (§ 8.2). En lecture et sur un plan bloqué, la séance le +refuse comme tout geste qui modifie (§ 8.4, § 9) ; une configuration qui ne se +normalise pas ne se classe pas, et son refus nomme sa cause, sans remède. Les +courses des propositions effacées quittent le compagnon des courses comme à +l'effacement (§ 8.6) ; rien de nouveau ne s'écrit dans le fichier (§ 8.9). + +**N est une saisie de la section, jamais écrite.** Il se saisit dans un champ +étroit à côté du bouton, dix au départ, un entier d'au moins 1, et le bouton le +nomme : « Garder les 10 meilleures », « Garder la meilleure ». Comme le filtre +d'une liste, ni le fichier ni le journal ne le portent : il se règle en +lecture, et revient à dix quand la section se rouvre. Une saisie qui n'est pas +un entier d'au moins 1 se refuse contre le champ, et le bouton garde le dernier +nombre accepté. + +**Une fenêtre à confirmer précède le nettoyage**, parce qu'il efface d'un coup +des lignes que l'opérateur ne voit pas toutes : « Effacer 37 propositions et +garder les 10 meilleures ? ». Elle dit que les premières du classement restent, +que la proposition du retenu reste aussi quand elle vient après elles, et que +« Défaire » rend les effacées. Ouverte, elle se recompte sur chaque écriture +qui arrive pendant qu'elle attend — une génération qui s'achève en +arrière-plan ajoute des propositions à effacer —, et le compte qu'elle montre +est celui que « Confirmer » applique, sauf pour une écriture déjà en file au +moment du clic, que la commande voit avant elle : l'annonce donne alors le +compte réel, et « Défaire » rend tout. « Annuler » et Échap la referment sans +rien écrire, le focus revenu au bouton. Un geste que la séance refuserait, ou +qui n'effacerait rien, part sans fenêtre, et son refus se pose à côté du +bouton. Abouti, une annonce dit le nombre de propositions effacées, celui que +la commande rend dans son résumé (`{effacees}`), et la page montrée qui +n'existe plus ramène à la première. + Au-delà de cent propositions, le tableau se lit par pages de cent : la page découpe ce classement unique sans le retrier, chaque ligne y garde son rang, et la ligne du retenu reste en tête de chaque page, hors du compte. La page est une posture de la navigation, jamais écrite (§ 8.4) : elle repart de la première à chaque ouverture d'un événement et après une génération, se borne à la dernière -quand des propositions partent, et amène la proposition visée à chaque demande -au plan, même répétée. +quand des propositions partent — à la première quand « Garder les N meilleures » +fait disparaître la page montrée —, et amène la proposition visée à chaque +demande au plan, même répétée. **La commande prend une graine entière et un compte d'arrêt**, et une longueur d'historique d'acceptation dont l'algorithme fixe la valeur par défaut. Chaque @@ -1310,8 +1355,9 @@ dans l'outil. Un refus **nomme sa cause et propose le geste qui le lève**, et se manifeste **sur la place concernée**, pas dans une boîte de dialogue. Seul un geste qui -détruit de la saisie ouvre une fenêtre à confirmer : une suite de modales -apprend à cliquer sans lire, et le premier refus qui comptait passe inaperçu. +détruit de la saisie ouvre une fenêtre à confirmer — et « Garder les N +meilleures », qui efface en bloc (§ 5.7) — : une suite de modales apprend à +cliquer sans lire, et le premier refus qui comptait passe inaperçu. **Quand aucun geste ne lève le refus, il nomme sa cause seule.** Ainsi d'une valeur qui se corrige dans le champ même, dont le texte dit la valeur attendue — diff --git a/src/application/commandes.js b/src/application/commandes.js index 061092a..d808b7c 100644 --- a/src/application/commandes.js +++ b/src/application/commandes.js @@ -24,10 +24,12 @@ // retenu quand un placement est retenu ; bloqué se pose de chacun d'eux, et // seul debloquer le lève, vers l'état que le contenu impose (etatDeduit). Une // génération fait passer un brouillon à proposé ; retenir pose retenu ; -// effacer les propositions ramène un plan proposé sans retenu à brouillon. +// effacer les propositions ramène un plan proposé sans retenu à brouillon ; +// garder les meilleures en laisse une au moins, et ne change pas l'état. import { ErreurCsv } from '../csv/erreurs.js'; import { appliquerImport, bilanRemplacement } from '../csv/import.js'; import { appliquerImportObjectifs } from '../csv/objectifs.js'; +import { ErreurConfiguration } from '../moteur/erreurs.js'; import { serialiserCharge } from '../stockage/canonique.js'; import { entreeDeCourse } from '../stockage/courses.js'; import { SCHEMA, clesRangees, creerCharge, etatDeduit, premiereFaute } from '../stockage/document.js'; @@ -38,6 +40,7 @@ import { courseVersFichier, fautes, formeNommee, versFichier } from '../stockage import { COMMANDES_MOBILIER } from './commandes_mobilier.js'; import { COMMANDES_PLACES } from './commandes_places.js'; import { ErreurCommande, refus } from './erreurs.js'; +import { evaluer, nettoyage } from './evaluation.js'; import { libelle } from './libelles.js'; import { avisObjectifs, etiquettesDeLEvenement, etiquettesRangees, recevoirEtiquettes } from './objectifs.js'; import { nomAffiche } from './personnes.js'; @@ -582,6 +585,43 @@ function effacerPropositions(charge) { }; } +/** + * garderMeilleures {n} (§ 5.7) : les n premières propositions du classement + * unique, celles que le tableau montre en tête, restent, et celle d'où vient + * le retenu avec elles ; les autres s'effacent. Le partage est celui de + * nettoyage, sur la table d'evaluer de la charge reçue : la mesure qui + * classe le tableau, refaite ici, puisque la commande ne lit que sa charge. + * Les gardées restent dans l'ordre du fichier ; le compteur ne recule pas, + * et l'état ne change pas, une proposition au moins restant. Rien à + * effacer : SANS_EFFET. Une configuration qui ne se normalise pas ne se + * classe pas : son refus, du code de normaliser, remède nul. n ne s'écrit + * pas dans la charge : le libellé seul le nomme. Le résumé rendu, + * {effacees}, compte les propositions que cette exécution efface : l'annonce + * de l'écran le lit, plutôt que l'écart de deux lectures de la séance entre + * lesquelles une autre écriture de la file peut passer. + */ +function garderMeilleures(charge, { n }) { + exigerModifiable(charge); + let table; + try { + table = evaluer(charge); + } catch (erreur) { + if (erreur instanceof ErreurConfiguration) throw refus(erreur.code, erreur.details); + throw erreur; + } + const { gardees, effacees, origineEnPlus } = nettoyage(table, n); + if (effacees.length === 0) throw refus('SANS_EFFET', { commande: 'garderMeilleures' }); + const restent = new Set(gardees); + const suivante = structuredClone(charge); + suivante.propositions = suivante.propositions.filter(({ id }) => restent.has(id)); + return { + charge: suivante, + libelle: libelle('ENTREE_GARDER_MEILLEURES', { n, effacees: effacees.length, gardee: origineEnPlus }), + avertissements: [], + resume: { effacees: effacees.length }, + }; +} + /** * Un registre réuni de plusieurs : chaque nom une fois, dans l'ordre des * registres reçus, puis de chacun. @@ -627,6 +667,7 @@ export const COMMANDES = reunirCommandes( ['enregistrerGeneration', enregistrerGeneration], ['retenirProposition', retenirProposition], ['effacerPropositions', effacerPropositions], + ['garderMeilleures', garderMeilleures], ]), COMMANDES_MOBILIER, COMMANDES_PLACES, diff --git a/src/application/commandes.test.js b/src/application/commandes.test.js index 9710a67..158791e 100644 --- a/src/application/commandes.test.js +++ b/src/application/commandes.test.js @@ -20,6 +20,7 @@ import { VERSION } from '../version.genere.js'; import { readFileSync } from 'node:fs'; import { chargements } from '../../test/chargements.js'; import { COMMANDES, ErreurCommande, appliquerCommande, chargeNeuve, formeNommeeDe, reunirCommandes } from './commandes.js'; +import { evaluer, nettoyage } from './evaluation.js'; import { COMMANDES_MOBILIER } from './commandes_mobilier.js'; import { COMMANDES_PLACES } from './commandes_places.js'; import { ErreurCommande as ErreurDesErreurs, refus } from './erreurs.js'; @@ -99,7 +100,7 @@ const executer = (nom, charge, args) => COMMANDES.get(nom)(charge, args); const CSV_DEUX = 'nom;prenom;appartenance\nAncolie;Bastien;Club des Merles\nBruyère;Capucine;\n'; describe('commandes : le registre (§ 8.2)', () => { - test("le registre porte les onze commandes d'A1, changerEtiquettes, les dix des places et celles du mobilier, chacune une fonction", () => { + test("le registre porte les onze commandes d'A1, changerEtiquettes, garderMeilleures, les dix des places et celles du mobilier, chacune une fonction", () => { const attendues = [ 'ajouterParticipant', 'changerEtat', @@ -113,6 +114,7 @@ describe('commandes : le registre (§ 8.2)', () => { 'effacerPropositions', 'enregistrerGeneration', 'exclureParticipant', + 'garderMeilleures', 'importerObjectifs', 'importerParticipants', 'modifierParticipant', @@ -125,7 +127,7 @@ describe('commandes : le registre (§ 8.2)', () => { 'retirerTitre', 'supprimerParticipant', ]; - assert.equal(attendues.length, 23); + assert.equal(attendues.length, 24); assert.deepEqual([...COMMANDES.keys()].sort(), [...attendues, ...COMMANDES_MOBILIER.keys()].sort()); for (const commande of COMMANDES.values()) assert.equal(typeof commande, 'function'); }); @@ -771,6 +773,85 @@ describe('commandes : générer, retenir, effacer (§ 5.7, § 8.9)', () => { }); }); +describe('commandes : garder les meilleures propositions (§ 5.7)', () => { + // La charge d'essai, deux générations : cinq propositions, 1 à 5. + function chargeCinq() { + const premiere = executer('enregistrerGeneration', chargeEssai(), generer(chargeEssai(), 3)).charge; + return executer('enregistrerGeneration', premiere, generer(premiere, 2, 11)).charge; + } + + // L'ordre du tableau des propositions : celui de la table d'évaluation. + const ordreDe = (charge) => evaluer(charge).propositions.map(({ id }) => id); + + test("garde les n premières du classement, dans l'ordre du fichier ; le compteur et l'état ne bougent pas ; une entrée qui le dit", () => { + const avant = figer(chargeCinq()); + const ordre = ordreDe(avant); + assert.equal(ordre.length, 5); + const { charge, libelle, avertissements, resume } = executer('garderMeilleures', avant, { n: 2 }); + const gardees = new Set(ordre.slice(0, 2)); + // Le compte des effacées, rendu par la commande même : l'annonce le lit. + assert.deepEqual(resume, { effacees: 3 }); + assert.deepEqual(charge.propositions, avant.propositions.filter(({ id }) => gardees.has(id))); + assert.equal(charge.prochainsIds.proposition, avant.prochainsIds.proposition); + assert.equal(charge.evenement.etat, 'propose'); + assert.equal(charge.retenu, null); + assert.equal(libelle, 'Gardé les 2 meilleures propositions, effacé 3 propositions'); + assert.deepEqual(avertissements, []); + const une = executer('garderMeilleures', avant, { n: 4 }); + assert.deepEqual(une.charge.propositions.map(({ id }) => id).sort(), ordre.slice(0, 4).sort()); + assert.equal(une.libelle, 'Gardé les 4 meilleures propositions, effacé 1 proposition'); + const seule = executer('garderMeilleures', avant, { n: 1 }); + assert.deepEqual(seule.charge.propositions.map(({ id }) => id), [ordre[0]]); + assert.equal(seule.libelle, 'Gardé la meilleure proposition, effacé 4 propositions'); + }); + + test('la proposition du retenu, hors des n premières, reste en plus, et le libellé la nomme ; le plan reste retenu', () => { + const generee = chargeCinq(); + const derniere = ordreDe(generee).at(-1); + const avant = figer(executer('retenirProposition', generee, { id: derniere }).charge); + const ordre = ordreDe(avant); + assert.equal(ordre.at(-1), derniere); + const { charge, libelle, resume } = executer('garderMeilleures', avant, { n: 2 }); + assert.deepEqual(charge.propositions.map(({ id }) => id).sort(), [...ordre.slice(0, 2), derniere].sort()); + assert.deepEqual(resume, { effacees: 2 }); + assert.deepEqual(charge.retenu, avant.retenu); + assert.equal(charge.evenement.etat, 'retenu'); + assert.equal(libelle, `Gardé les 2 meilleures propositions, effacé 2 propositions, gardé aussi la retenue (n° ${derniere})`); + assert.deepEqual(nettoyage(evaluer(avant), 2).effacees.sort(), ordre.slice(2, 4).sort()); + }); + + test("rien à effacer — n au moins le compte, ou la seule au-delà est la retenue : SANS_EFFET", () => { + const avant = figer(chargeCinq()); + refuse(() => executer('garderMeilleures', avant, { n: 5 }), 'SANS_EFFET', { commande: 'garderMeilleures', remede: null }); + refuse(() => executer('garderMeilleures', avant, { n: 50 }), 'SANS_EFFET'); + const derniere = ordreDe(avant).at(-1); + const retenue = figer(executer('retenirProposition', avant, { id: derniere }).charge); + refuse(() => executer('garderMeilleures', retenue, { n: 4 }), 'SANS_EFFET'); + }); + + test("une configuration qui ne se normalise pas ne se classe pas : son refus, remède nul ; un plan bloqué refuse d'abord", () => { + const charge = structuredClone(chargeCinq()); + charge.reservations = [1, 2, 3].map((participant) => ({ participant, table: 1, siege: null, portee: 'tous', tour: null })); + refuse(() => executer('garderMeilleures', figer(charge), { n: 1 }), 'SURRESERVATION'); + assert.throws(() => executer('garderMeilleures', charge, { n: 1 }), (erreur) => erreur.details.remede === null); + const bloquee = figer({ ...charge, evenement: { ...charge.evenement, etat: 'bloque' } }); + refuse(() => executer('garderMeilleures', bloquee, { n: 1 }), 'PLAN_BLOQUE', { remede: { geste: 'debloquer' } }); + }); + + test("n hors des entiers ≥ 1 est une faute du code ; la commande ne rend aucune course", () => { + const avant = figer(chargeCinq()); + for (const n of [0, -2, 1.5, '1', null]) assert.throws(() => executer('garderMeilleures', avant, { n }), TypeError, String(n)); + assert.throws(() => executer('garderMeilleures', avant, {}), TypeError); + assert.equal(Object.hasOwn(executer('garderMeilleures', avant, { n: 1 }), 'courses'), false); + }); + + test("après le nettoyage, aucun identifiant ne revient : la génération suivante continue le compteur", () => { + const gardee = executer('garderMeilleures', chargeCinq(), { n: 1 }).charge; + const suite = executer('enregistrerGeneration', gardee, generer(gardee, 1, 13)).charge; + assert.equal(suite.propositions.at(-1).id, 6); + }); +}); + describe('commandes : la course de la recherche, rendue à côté de la charge (§ 8.9, B31, D53)', () => { // La charge d'essai sur trois tours, sièges attribués, deux tables de // trois places pour quatre personnes : Iris Ombrelle réservée au siège 3 @@ -934,9 +1015,11 @@ describe('commandes : la charge reçue ne change jamais (§ 13.2)', () => { ]; // La charge que reçoit chaque commande : la charge riche, sauf debloquer, - // qui la reçoit bloquée ; convertirEnTous, qui y trouve Théo réservé à la - // table 2 à chaque tour, en portée « tour » ; deplacerDansRetenu, qui y - // trouve l'autre personne du tour 1 retirée dans la réserve. + // qui la reçoit bloquée ; garderMeilleures, qui y trouve deux propositions + // de plus, de quoi effacer au-delà de la première et de la retenue ; + // convertirEnTous, qui y trouve Théo réservé à la table 2 à chaque tour, en + // portée « tour » ; deplacerDansRetenu, qui y trouve l'autre personne du + // tour 1 retirée dans la réserve. function recues(charge) { const { autre } = auTour1(charge); const reservee = structuredClone(charge); @@ -950,6 +1033,7 @@ describe('commandes : la charge reçue ne change jamais (§ 13.2)', () => { ['deplacerDansRetenu', executer('retirerDuTour', charge, { tour: 1, participant: autre }).charge], ['retirerObjectif', executer('poserObjectif', charge, { table: 2, tour: null, etiquettes: 'finance', regle: null }).charge], ['retablirPriorites', executer('changerPriorites', charge, { paliers: PRIORITES_REGLEES }).charge], + ['garderMeilleures', executer('enregistrerGeneration', charge, generer(charge, 2, 5)).charge], ]); } @@ -969,6 +1053,7 @@ describe('commandes : la charge reçue ne change jamais (§ 13.2)', () => { ['enregistrerGeneration', generer(charge, 1, 5)], ['retenirProposition', { id: 2 }], ['effacerPropositions', {}], + ['garderMeilleures', { n: 1 }], ['reserver', { participant: 4, table: 2, siege: null, portee: 'tour', tour: 1 }], ['retirerReservation', { participant: 1, table: 1, portee: 'tous', tour: null }], ['deplacerReservation', { participant: 1, de: { table: 1, portee: 'tous', tour: null }, vers: { table: 2, siege: null } }], @@ -1040,8 +1125,8 @@ describe('commandes : les registres réunis et les erreurs partagées', () => { ); }); - test("COMMANDES réunit les onze commandes d'A1, changerEtiquettes, importerObjectifs et les deux registres de F1", () => { - assert.equal(COMMANDES.size, 13 + COMMANDES_MOBILIER.size + COMMANDES_PLACES.size); + test("COMMANDES réunit les onze commandes d'A1, changerEtiquettes, importerObjectifs, garderMeilleures et les deux registres de F1", () => { + assert.equal(COMMANDES.size, 14 + COMMANDES_MOBILIER.size + COMMANDES_PLACES.size); assert.ok(COMMANDES_MOBILIER instanceof Map); assert.ok(COMMANDES_PLACES instanceof Map); for (const registre of [COMMANDES_MOBILIER, COMMANDES_PLACES]) { diff --git a/src/application/evaluation.js b/src/application/evaluation.js index 5a6a2b6..be61bf6 100644 --- a/src/application/evaluation.js +++ b/src/application/evaluation.js @@ -281,6 +281,36 @@ export function resumeDuMeilleur(charge) { return premiere === null ? null : { id: premiere.id }; } +/** + * Le partage d'un nettoyage des propositions (§ 5.7) : les n premières de + * la table, dans l'ordre où le tableau des propositions les montre — les + * comparables dans l'ordre du classement, puis les autres par identifiant + * —, se gardent, et la proposition d'où vient le retenu avec elles quand + * elle vient après ; les autres s'effacent. La commande garderMeilleures et + * la fenêtre qui la confirme lisent ce partage, chacune sur la table de la + * charge qu'elle tient : aucun ordre ne se refait ailleurs. + * + * @param {TableEvaluation} table + * @param {number} n entier ≥ 1 + * @returns {{gardees: number[], effacees: number[], origineEnPlus: number|null}} + * gardees et effacees : des identifiants, dans l'ordre de la table ; + * origineEnPlus : la proposition du retenu gardée au-delà des n premières, + * null quand elle en est, sans retenu, et quand la liste ne la porte plus + * @throws {TypeError} n qui n'est pas un entier ≥ 1 + */ +export function nettoyage(table, n) { + if (!Number.isSafeInteger(n) || n < 1) throw new TypeError(`nettoyage : entier ≥ 1 attendu, reçu ${JSON.stringify(n)}`); + const ids = table.propositions.map(({ id }) => id); + const origine = table.retenu?.id ?? null; + const origineEnPlus = origine !== null && ids.indexOf(origine) >= n ? origine : null; + const premieres = ids.slice(0, n); + return { + gardees: origineEnPlus === null ? premieres : [...premieres, origineEnPlus], + effacees: ids.slice(n).filter((id) => id !== origineEnPlus), + origineEnPlus, + }; +} + // --- Le détail d'un placement (§ 12, § 12.9, D27, D29, D40, D42) ------------ /** diff --git a/src/application/evaluation.test.js b/src/application/evaluation.test.js index 36394e4..99534fe 100644 --- a/src/application/evaluation.test.js +++ b/src/application/evaluation.test.js @@ -50,6 +50,7 @@ import * as EVALUATION from './evaluation.js'; import { detaillerQualite, evaluer, + nettoyage, premiereDuClassement, rencontresDePersonne, resumeDuMeilleur, @@ -602,6 +603,61 @@ describe('resumeDuMeilleur : la proposition au rang 1 que lit la liste des évé }); }); +describe('nettoyage : les n premières du tableau, et la proposition du retenu (§ 5.7)', () => { + // Les plans écrits à la main, que le classement range 3, 2, 1, 4. + function chargeConflit() { + const charge = structuredClone(chargeDemo('petite-conflit')); + charge.propositions = PLANS_CONFLIT.map(({ tours }, rang) => propositionDe(charge, rang + 1, tours)); + charge.prochainsIds.proposition = 5; + charge.evenement.etat = 'propose'; + return charge; + } + + test("garde les n premières dans l'ordre de la table, efface les suivantes ; sans retenu, rien de plus", () => { + const table = evaluer(figer(chargeConflit())); + assert.deepEqual(table.propositions.map(({ id }) => id), [3, 2, 1, 4]); + assert.deepEqual(nettoyage(table, 2), { gardees: [3, 2], effacees: [1, 4], origineEnPlus: null }); + assert.deepEqual(nettoyage(table, 1), { gardees: [3], effacees: [2, 1, 4], origineEnPlus: null }); + assert.deepEqual(nettoyage(table, 4), { gardees: [3, 2, 1, 4], effacees: [], origineEnPlus: null }); + assert.deepEqual(nettoyage(table, 10), { gardees: [3, 2, 1, 4], effacees: [], origineEnPlus: null }); + }); + + test("la proposition du retenu, après les n premières, est gardée en plus ; parmi elles, rien de plus", () => { + const charge = executer(chargeConflit(), 'retenirProposition', { id: 4 }); + const table = evaluer(figer(charge)); + assert.equal(table.retenu.id, 4); + assert.deepEqual(nettoyage(table, 2), { gardees: [3, 2, 4], effacees: [1], origineEnPlus: 4 }); + assert.deepEqual(nettoyage(table, 3), { gardees: [3, 2, 1, 4], effacees: [], origineEnPlus: 4 }); + const parmi = evaluer(figer(executer(chargeConflit(), 'retenirProposition', { id: 2 }))); + assert.deepEqual(nettoyage(parmi, 2), { gardees: [3, 2], effacees: [1, 4], origineEnPlus: null }); + }); + + test("un retenu dont la proposition n'est plus dans la liste ne garde rien de plus", () => { + const charge = executer(chargeConflit(), 'retenirProposition', { id: 1 }); + charge.propositions = charge.propositions.filter(({ id }) => id !== 1); + const table = evaluer(figer(charge)); + assert.equal(table.retenu.id, 1); + assert.deepEqual(nettoyage(table, 1), { gardees: [3], effacees: [2, 4], origineEnPlus: null }); + }); + + test('les hors classement viennent après les classées, par identifiant, comme au tableau', () => { + let charge = generee(chargeDemo('petite'), { graine: 1, nombre: 2, arret: 200 }); + charge = executer(charge, 'exclureParticipant', { id: 4 }); + charge = generee(charge, { graine: 2, nombre: 2, arret: 200 }); + const table = evaluer(figer(charge)); + const ordre = table.classement.ordre; + assert.deepEqual(nettoyage(table, 2), { gardees: ordre, effacees: [1, 2], origineEnPlus: null }); + assert.deepEqual(nettoyage(table, 3), { gardees: [...ordre, 1], effacees: [2], origineEnPlus: null }); + }); + + test('n hors des entiers ≥ 1 est une faute du code', () => { + const table = evaluer(figer(chargeConflit())); + for (const n of [0, -1, 1.5, '2', null, undefined, Number.NaN, 2 ** 53]) { + assert.throws(() => nettoyage(table, n), TypeError, String(n)); + } + }); +}); + // --- L'ordre des priorités, le mode et les cases (A11, A21) --------------------- exigerVerification(process.env); diff --git a/src/application/libelles.test.js b/src/application/libelles.test.js index 8044fea..73a20e2 100644 --- a/src/application/libelles.test.js +++ b/src/application/libelles.test.js @@ -519,6 +519,7 @@ const EXEMPLES = new Map([ ['ENTREE_ENREGISTRER_GENERATION', { n: 5, premier: 3, dernier: 7 }], ['ENTREE_RETENIR_PROPOSITION', { id: 3 }], ['ENTREE_EFFACER_PROPOSITIONS', { n: 4, gardee: 2 }], + ['ENTREE_GARDER_MEILLEURES', { n: 10, effacees: 37, gardee: 42 }], // Places : refus d'une pose, titres, réservations, retenu ['SIEGE_INEXISTANT', { table: 2, siege: 9, capacite: 8, remede: null }], ['DEUX_PLACES_MEME_TOUR', { participant: 1, tour: 2, autre: { table: 1, siege: null, portee: 'tous', tour: null }, remede: null }], @@ -940,7 +941,7 @@ describe('libellés : les tables par domaine (§ 14.6)', () => { ['seance', ['MODE_LECTURE', 'SUPPORT_ROMPU', 'SANS_EFFET', 'ENTREE_DEFAIRE', 'JALON_CREATION']], ['participants', ['NOM_REQUIS', 'PARTICIPANT_INCONNU', 'RESERVATIONS_SUSPENDUES', 'IMPORT_EN_RETENU', 'ENTREE_AJOUTER_PARTICIPANT']], ['evenements', ['ENTREE_CREATION', 'DATE_INVALIDE', 'AUCUN_RETENU', 'PLAN_NON_BLOQUE', 'ENTREE_DEBLOQUER']], - ['generation', ['PROPOSITION_INCONNUE', 'ENTREE_ENREGISTRER_GENERATION', 'ENTREE_EFFACER_PROPOSITIONS']], + ['generation', ['PROPOSITION_INCONNUE', 'ENTREE_ENREGISTRER_GENERATION', 'ENTREE_EFFACER_PROPOSITIONS', 'ENTREE_GARDER_MEILLEURES']], ['format', ['format.vide', 'format.duree.vingtMinutes']], ]; assert.equal(ATTENDUS.length, 7); diff --git a/src/application/libelles/generation.js b/src/application/libelles/generation.js index 2477b04..9cdf32b 100644 --- a/src/application/libelles/generation.js +++ b/src/application/libelles/generation.js @@ -62,6 +62,15 @@ export const TABLE = new Map([ `Effacé ${compte(n, 'proposition', 'propositions')}` + `${gardee === null || gardee === undefined ? '' : `, gardé la retenue (n° ${gardee})`}`, ], + // n : les premières du classement gardées ; effacees : le nombre + // d'effacées ; gardee : la proposition du retenu, gardée en plus, ou null. + [ + 'ENTREE_GARDER_MEILLEURES', + ({ n, effacees, gardee }) => + `Gardé ${n <= 1 ? 'la meilleure proposition' : `les ${n} meilleures propositions`}, ` + + `effacé ${compte(effacees, 'proposition', 'propositions')}` + + `${gardee === null || gardee === undefined ? '' : `, gardé aussi la retenue (n° ${gardee})`}`, + ], // --- Le contrôleur de génération (§ 5.9, § 5.10, § 15.4) ------------------- ['GENERATION_EN_COURS', () => "Une génération est déjà en cours : l'annuler, ou attendre qu'elle s'achève."], diff --git a/src/application/registre.test.js b/src/application/registre.test.js index 559c22e..0efce28 100644 --- a/src/application/registre.test.js +++ b/src/application/registre.test.js @@ -171,6 +171,19 @@ const RETIRER_UN_LIBRE_DE_LA_TABLE = (idTable) => [ (charge) => ({ tour: 1, participant: libresA(charge, 1, idTable)[0] }), ]; +// Deux propositions de plus sur la charge où le geste s'exécute, sous une +// graine qui n'est pas celle de la charge témoin : de quoi effacer au-delà +// de la première du classement et de la retenue. +const GENERER_DEUX_DE_PLUS = [ + 'enregistrerGeneration', + (charge) => { + const configuration = configurationDepuisCharge(charge); + const { historique } = charge.reglages.generation; + const propositions = rechercher(configuration, { graine: 23, nombre: 2, arret: 200, historique }); + return { propositions, configuration, produitVersion: V }; + }, +]; + /** * Par commande : accepte, refuse, sansEffet, chacun { args, avant?, sur? } * — args, un objet ou une fonction de la charge où l'exemple s'exécute ; @@ -323,6 +336,16 @@ const EXEMPLES = new Map([ sansEffet: { avant: [['effacerPropositions', {}]], args: {} }, }, ], + [ + 'garderMeilleures', + { + accepte: { avant: [GENERER_DEUX_DE_PLUS], args: { n: 1 } }, + refuse: + "garder ne refuse du domaine qu'une configuration qui ne se normalise pas, que la charge témoin ne porte " + + "pas : ses réservations tiennent ; commandes.test.js l'éprouve", + sansEffet: { args: (charge) => ({ n: charge.propositions.length }) }, + }, + ], [ 'creerTable', { diff --git a/src/application/seance.test.js b/src/application/seance.test.js index ad1c7ca..a0def0f 100644 --- a/src/application/seance.test.js +++ b/src/application/seance.test.js @@ -3645,6 +3645,37 @@ describe('séance : la course de la recherche dans son compagnon (§ 5.10, § 8. assert.equal(fs.contenu('documents', COURSES), texte); }); + test("garder les meilleures retire du compagnon les courses des effacées ; une entrée, que défaire rend, compagnon compris ; en lecture, refusé sans rien écrire", async () => { + const fs = creerFichiersSimules(); + await deposerEssai(fs); + const montage = monter({ fs }); + await ouvrirSansEcrire(montage); + const { seance } = montage; + await seance.passerEnEcriture(); + await seance.executer('enregistrerGeneration', generer(seance.charge, 3)); + assert.deepEqual(dansLeCompagnon(fs), [1, 2, 3]); + const entrees = journalDe(fs).entrees.length; + + const rendu = await seance.executer('garderMeilleures', { n: 1 }); + assert.deepEqual(rendu.resume, { effacees: 2 }); + const gardees = seance.charge.propositions.map(({ id }) => id); + assert.equal(gardees.length, 1); + assert.deepEqual(dansLeCompagnon(fs), gardees); + assert.equal(seance.charge.prochainsIds.proposition, 4); + assert.equal(journalDe(fs).entrees.length, entrees + 1); + assert.equal(libellesDe(journalDe(fs)).at(-1), 'Gardé la meilleure proposition, effacé 2 propositions'); + + await seance.defaire(); + assert.deepEqual(seance.charge.propositions.map(({ id }) => id), [1, 2, 3]); + assert.deepEqual(dansLeCompagnon(fs), [1, 2, 3]); + + await seance.passerEnLecture(); + const etat = fs.contenu('documents', `${BASE}${SUFFIXES.etat}`); + await refus(seance.executer('garderMeilleures', { n: 1 }), 'MODE_LECTURE', { remede: { geste: 'modifier' } }); + assert.equal(fs.contenu('documents', `${BASE}${SUFFIXES.etat}`), etat); + assert.deepEqual(dansLeCompagnon(fs), [1, 2, 3]); + }); + // Le compteur des propositions ne recule jamais sous défaire ni revenir ; // il recule quand l'opérateur reprend un état d'avant la génération, ici // son .precedent. La génération suivante, sous la même graine, porte diff --git a/src/interface/libelles/communs.js b/src/interface/libelles/communs.js index fb3a571..614c6dd 100644 --- a/src/interface/libelles/communs.js +++ b/src/interface/libelles/communs.js @@ -72,6 +72,7 @@ export const TABLE = new Map([ ['communs.remede.enregistrerGeneration', 'Enregistrer la génération'], ['communs.remede.retenirProposition', 'Retenir la proposition'], ['communs.remede.effacerPropositions', 'Effacer les propositions'], + ['communs.remede.garderMeilleures', 'Garder les meilleures propositions'], ['communs.remede.creerTable', 'Créer une table'], ['communs.remede.supprimerTable', 'Supprimer la table'], ['communs.remede.deplacerTable', 'Déplacer la table'], diff --git a/src/interface/libelles/propositions.js b/src/interface/libelles/propositions.js index fa0ff48..3784387 100644 --- a/src/interface/libelles/propositions.js +++ b/src/interface/libelles/propositions.js @@ -11,8 +11,15 @@ // chaque valeur qu'un palier applique par sa clé moteur.valeur., // puis l'identifiant croissant, qui départage deux propositions égales sur // tous (§ 5.7, A11). +import { creerFormat } from '../../application/format.js'; import { compte } from '../../application/libelles/aides.js'; +// Un nombre de propositions, mis en forme, et son nom accordé. +const propositions = (n, texte) => `${creerFormat(texte).nombre(n)} ${n <= 1 ? 'proposition' : 'propositions'}`; + +// « la meilleure » pour une, « les N meilleures » au-delà, N mis en forme. +const meilleures = (n, texte) => (n <= 1 ? 'la meilleure' : `les ${creerFormat(texte).nombre(n)} meilleures`); + // Le nom d'un critère du classement, celui de la table du moteur. const critere = (nom, texte) => texte(`moteur.critere.${nom}`); @@ -347,6 +354,36 @@ export const TABLE = new Map([ // de qualité, qui la mesure (§ 12). ['propositions.commande.qualite', 'Qualité'], ['propositions.commande.effacer', 'Effacer les propositions'], + ['propositions.commande.garder', ({ n }, texte) => `Garder ${meilleures(n, texte)}`], + + // --- Garder les meilleures (§ 5.7) --------------------------------------------- + // n : le nombre à garder ; effacees : celles qui s'effacent, comptées par + // nettoyage (evaluation.js) ; proposition : celle du retenu, gardée en plus. + ['propositions.nettoyage.champ', 'Propositions à garder'], + [ + 'propositions.nettoyage.titre', + ({ n, effacees }, texte) => `Effacer ${propositions(effacees, texte)} et garder ${meilleures(n, texte)} ?`, + ], + [ + 'propositions.nettoyage.gardees', + ({ n }, texte) => + n <= 1 + ? 'La première proposition du classement reste.' + : `Les ${creerFormat(texte).nombre(n)} premières propositions du classement restent.`, + ], + [ + 'propositions.nettoyage.origine', + ({ proposition }) => `La proposition n° ${proposition} reste aussi : le placement retenu en vient.`, + ], + [ + 'propositions.nettoyage.defaire', + ({ effacees }, texte) => + effacees <= 1 ? '« Défaire » rend la proposition effacée.' : `« Défaire » rend les ${propositions(effacees, texte)} effacées.`, + ], + [ + 'propositions.nettoyage.annonce', + ({ effacees }, texte) => `${propositions(effacees, texte)} ${effacees <= 1 ? 'effacée' : 'effacées'}.`, + ], // --- Retenir sur un retenu ajusté (§ 2.1) ---------------------------------------- [ diff --git a/src/interface/primitives/Confirmation.svelte b/src/interface/primitives/Confirmation.svelte index 437f293..3e35e79 100644 --- a/src/interface/primitives/Confirmation.svelte +++ b/src/interface/primitives/Confirmation.svelte @@ -2,16 +2,16 @@ License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) --> @@ -213,6 +320,29 @@ {/snippet} +{#snippet commandesListe()} +
+ + + {#if refusGardees !== null} + + {/if} + +

{annonceMontree === null ? '' : t(annonceMontree.cle, annonceMontree.details)}

+
+ {@render effacerListe()} +{/snippet} +

{t('coquille.section.propositions')}

@@ -227,7 +357,7 @@ {/if} {#if vue !== null} - 0 ? effacerListe : null}> + 0 ? commandesListe : null}>
{#each vue.ordre as morceaux, rang (rang)}

@@ -274,6 +404,16 @@ surAnnuler={() => (aConfirmer = null)} /> {/if} + + {#if nettoyageMontre !== null} + t(element.cle, element.details))} + surConfirmer={confirmerNettoyage} + surAnnuler={() => (aNettoyer = null)} + /> + {/if}