[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
// Le modèle du tableau des propositions (§ 5.4, § 5.5, § 5.7, § 9, § 12.1,
|
|
|
|
|
// § 12.6, § 12.9), pur : SectionPropositions.svelte et
|
|
|
|
|
// TableauPropositions.svelte le câblent.
|
|
|
|
|
//
|
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
|
|
|
// Le tableau lit la table d'évaluation d'evaluer (evaluation.js), la seule mesure : chaque
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
// cellule porte la valeur que la table rend, telle quelle, et sa clé de mise
|
|
|
|
|
// en forme ; aucune arithmétique ne s'y fait, aucun plafond ne s'y recompose.
|
|
|
|
|
// Des rencontres, il ne porte que le minimum, sur les trois populations du
|
|
|
|
|
// § 5.4 — tous, mobiles, ancrés —, en tête des colonnes de mesure ; la moyenne
|
|
|
|
|
// n'y entre pas. Le plafond réalisé, individuel, se lit par le manque, dont
|
|
|
|
|
// les cellules sont celles de troisChiffres. Les imposés se disent à part, sur
|
|
|
|
|
// chaque ligne, jamais ajoutés aux choisis.
|
|
|
|
|
//
|
|
|
|
|
// Chaque minimum affiché désigne la personne la plus mal servie (§ 12.1) :
|
|
|
|
|
// le moins de rencontres, d'appartenances vues, de diversité. La redondance
|
|
|
|
|
// r(p) va dans l'autre sens — plus elle est grande, pire est le sort —, et
|
|
|
|
|
// son minimum désignerait la personne la mieux servie : elle se lit par sa
|
|
|
|
|
// somme Σ r(p), la grandeur que la recherche et le classement lisent.
|
|
|
|
|
//
|
|
|
|
|
// Une cellule vaut { colonne, valeur, cle, decimales } : cle null, la valeur
|
|
|
|
|
// est un nombre que format.nombre écrit avec decimales chiffres après la
|
|
|
|
|
// virgule ; sinon le texte est t(cle) — « — » pour une population vide,
|
|
|
|
|
// « inconnu » pour une grandeur que l'a priori inconnu ne permet pas, le
|
|
|
|
|
// certificat en mots.
|
|
|
|
|
import { evaluer } from '../../application/evaluation.js';
|
|
|
|
|
import { nomAffiche } from '../../application/personnes.js';
|
|
|
|
|
|
|
|
|
|
const VIDE = 'format.vide';
|
|
|
|
|
const INCONNU = 'propositions.valeur.inconnu';
|
|
|
|
|
|
|
|
|
|
// Chaque colonne de mesure et la lecture de sa valeur dans une ligne
|
|
|
|
|
// comparable de la table d'évaluation, dans l'ordre d'affichage. La Map ne
|
|
|
|
|
// sert qu'à retrouver la lecture d'une colonne ; COLONNES fixe l'ordre.
|
|
|
|
|
const LECTURES = new Map([
|
|
|
|
|
['rencontresTous', (l) => l.mesures.aggRencontres.tous.min],
|
|
|
|
|
['rencontresMobiles', (l) => l.mesures.aggRencontres.mobiles.min],
|
|
|
|
|
['rencontresAncres', (l) => l.mesures.aggRencontres.ancres.min],
|
|
|
|
|
['collisionsCumulees', (l) => l.mesures.collisionsCumulees],
|
|
|
|
|
['pairesDistinctes', (l) => l.mesures.pairesDistinctes],
|
|
|
|
|
['excedentCollisions', (l) => l.mesures.excedentCollisions],
|
|
|
|
|
['rencontresRepetees', (l) => l.mesures.rencontresRepetees.choisies],
|
|
|
|
|
['maxRencontresPaire', (l) => l.mesures.maxRencontresPaire],
|
|
|
|
|
['retoursChoisis', (l) => l.mesures.totalRetoursChoisis],
|
|
|
|
|
['appartenancesVues', (l) => l.mesures.aggAppartenancesVues.tous.min],
|
|
|
|
|
['totalRedondance', (l) => l.mesures.totalRedondance],
|
|
|
|
|
['diversite', (l) => l.mesures.aggDiversite.tous.min],
|
|
|
|
|
['manqueMax', (l) => l.troisChiffres.manqueMax.tous],
|
|
|
|
|
['effectifManque', (l) => l.troisChiffres.effectifManque.tous],
|
|
|
|
|
['ecartItineraireMax', (l) => l.troisChiffres.ecartItineraireMax?.tous ?? null],
|
|
|
|
|
['ecartAuPlafondAPriori', (l) => l.ecartAuPlafondAPrioriMax],
|
|
|
|
|
['certificat', (l) => l.minimumAtteint],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Les colonnes dont la valeur n'existe pas quand le plafond a priori est
|
|
|
|
|
// inconnu : elles s'écrivent « inconnu », jamais « — » (§ 12.6).
|
|
|
|
|
const SELON_A_PRIORI = new Set(['ecartItineraireMax', 'ecartAuPlafondAPriori']);
|
|
|
|
|
|
|
|
|
|
// Le taux de diversité, un rapport, se lit au centième ; les autres colonnes
|
|
|
|
|
// sont des comptes.
|
|
|
|
|
const DECIMALES = new Map([['diversite', 2]]);
|
|
|
|
|
|
[IMP] proposals, generation, badges, preview: cards and grouped headers
The proposal table had a dozen measure columns with no grouping. Five
group headers now gather neighbouring measures without changing any
column text; numbers align right in tabular figures, and the kept
placement row wears the Retenu tint and a « retenue » pill. Generation
settings and progress, badges and the paper choice become cards, each
paper a distinct choice; the preview toolbar groups its commands. The
printed paper is unchanged. Checked: node 172, browser 162.
--- FR ---
[IMP] propositions, génération, badges, aperçu : cartes, intitulés
Le tableau des propositions alignait une douzaine de mesures sans
groupe. Cinq intitulés de groupe réunissent les mesures voisines sans
changer le texte d'aucune colonne ; les nombres s'alignent à droite en
chiffres tabulaires, la ligne du retenu porte la teinte Retenu et une
pastille « retenue ». Réglages et avancement, badges et papier
deviennent des cartes, chaque papier un choix distinct ; la barre de
l'aperçu groupe ses commandes. Le papier imprimé ne change pas.
Vérifié : node 172, navigateur 162.
Assisted-by: Claude Opus 5.5
2026-10-11 00:58:07 -04:00
|
|
|
// Les colonnes dont les cellules sont du texte : toutes les autres portent
|
|
|
|
|
// un nombre, qui s'aligne à droite en chiffres tabulaires.
|
|
|
|
|
const TEXTUELLES = new Set(['proposition', 'certificat', 'imposes', 'commandes']);
|
|
|
|
|
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
/** Les colonnes du tableau, dans l'ordre d'affichage : le rang et
|
|
|
|
|
* l'identifiant, puis les mesures — les trois minimums des rencontres en
|
[ADD] propositions: explanation tooltips on criteria and column headers
The propositions table names its measures with the glossary's terms, which
an organiser cannot be expected to know. Each ranking criterion of the
order sentence and each column header now shows a short explanation on
hover or keyboard focus: what the number counts and whether lower or
higher is better, written from the spec and the engine. The tooltip is a
reusable primitive, read through aria-describedby, kept in the window,
never over its word, closed by Escape; the rendering guards measure it.
Checked: 2882 node, 592 browser tests, from the index alone.
--- FR ---
[ADD] propositions : infobulles d'explication des critères et colonnes
Le tableau des propositions nomme ses mesures par les termes du glossaire,
qu'un organisateur n'a pas à connaître. Chaque critère de la phrase de
l'ordre et chaque en-tête de colonne montre une courte explication au
survol ou au focus : ce que compte le nombre, et s'il vaut mieux bas ou
haut, écrite d'après le spec et le moteur. L'infobulle est une primitive
réutilisable, lue par aria-describedby, gardée dans la fenêtre, jamais sur
son mot, fermée par Échap ; les gardes du rendu la mesurent.
Vérifié : 2882 node, 592 navigateur, depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-08 04:56:52 -04:00
|
|
|
* tête —, le certificat, les imposés et les commandes. Chacune porte son nom,
|
[IMP] proposals, generation, badges, preview: cards and grouped headers
The proposal table had a dozen measure columns with no grouping. Five
group headers now gather neighbouring measures without changing any
column text; numbers align right in tabular figures, and the kept
placement row wears the Retenu tint and a « retenue » pill. Generation
settings and progress, badges and the paper choice become cards, each
paper a distinct choice; the preview toolbar groups its commands. The
printed paper is unchanged. Checked: node 172, browser 162.
--- FR ---
[IMP] propositions, génération, badges, aperçu : cartes, intitulés
Le tableau des propositions alignait une douzaine de mesures sans
groupe. Cinq intitulés de groupe réunissent les mesures voisines sans
changer le texte d'aucune colonne ; les nombres s'alignent à droite en
chiffres tabulaires, la ligne du retenu porte la teinte Retenu et une
pastille « retenue ». Réglages et avancement, badges et papier
deviennent des cartes, chaque papier un choix distinct ; la barre de
l'aperçu groupe ses commandes. Le papier imprimé ne change pas.
Vérifié : node 172, navigateur 162.
Assisted-by: Claude Opus 5.5
2026-10-11 00:58:07 -04:00
|
|
|
* la clé de son en-tête, qui nomme son unité, celle de son explication,
|
|
|
|
|
* le texte de l'infobulle de son en-tête et du critère qui la désigne, et
|
|
|
|
|
* nombre, vrai quand ses cellules portent un nombre. */
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
export const COLONNES = Object.freeze(
|
|
|
|
|
['rang', 'proposition', ...LECTURES.keys(), 'imposes', 'commandes'].map((nom) =>
|
[IMP] proposals, generation, badges, preview: cards and grouped headers
The proposal table had a dozen measure columns with no grouping. Five
group headers now gather neighbouring measures without changing any
column text; numbers align right in tabular figures, and the kept
placement row wears the Retenu tint and a « retenue » pill. Generation
settings and progress, badges and the paper choice become cards, each
paper a distinct choice; the preview toolbar groups its commands. The
printed paper is unchanged. Checked: node 172, browser 162.
--- FR ---
[IMP] propositions, génération, badges, aperçu : cartes, intitulés
Le tableau des propositions alignait une douzaine de mesures sans
groupe. Cinq intitulés de groupe réunissent les mesures voisines sans
changer le texte d'aucune colonne ; les nombres s'alignent à droite en
chiffres tabulaires, la ligne du retenu porte la teinte Retenu et une
pastille « retenue ». Réglages et avancement, badges et papier
deviennent des cartes, chaque papier un choix distinct ; la barre de
l'aperçu groupe ses commandes. Le papier imprimé ne change pas.
Vérifié : node 172, navigateur 162.
Assisted-by: Claude Opus 5.5
2026-10-11 00:58:07 -04:00
|
|
|
Object.freeze({ nom, cle: `propositions.colonne.${nom}`, explication: `propositions.explication.${nom}`, nombre: !TEXTUELLES.has(nom) }),
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
[IMP] proposals, generation, badges, preview: cards and grouped headers
The proposal table had a dozen measure columns with no grouping. Five
group headers now gather neighbouring measures without changing any
column text; numbers align right in tabular figures, and the kept
placement row wears the Retenu tint and a « retenue » pill. Generation
settings and progress, badges and the paper choice become cards, each
paper a distinct choice; the preview toolbar groups its commands. The
printed paper is unchanged. Checked: node 172, browser 162.
--- FR ---
[IMP] propositions, génération, badges, aperçu : cartes, intitulés
Le tableau des propositions alignait une douzaine de mesures sans
groupe. Cinq intitulés de groupe réunissent les mesures voisines sans
changer le texte d'aucune colonne ; les nombres s'alignent à droite en
chiffres tabulaires, la ligne du retenu porte la teinte Retenu et une
pastille « retenue ». Réglages et avancement, badges et papier
deviennent des cartes, chaque papier un choix distinct ; la barre de
l'aperçu groupe ses commandes. Le papier imprimé ne change pas.
Vérifié : node 172, navigateur 162.
Assisted-by: Claude Opus 5.5
2026-10-11 00:58:07 -04:00
|
|
|
// Les groupes d'en-têtes : chacun nomme des colonnes de mesure voisines dans
|
|
|
|
|
// COLONNES, sous la clé propositions.groupe.<nom>.
|
|
|
|
|
const GROUPES = [
|
|
|
|
|
['rencontres', ['rencontresTous', 'rencontresMobiles', 'rencontresAncres']],
|
|
|
|
|
['collisions', ['collisionsCumulees', 'pairesDistinctes', 'excedentCollisions']],
|
|
|
|
|
['repetitions', ['rencontresRepetees', 'maxRencontresPaire', 'retoursChoisis']],
|
|
|
|
|
['diversite', ['appartenancesVues', 'totalRedondance', 'diversite']],
|
|
|
|
|
['manque', ['manqueMax', 'effectifManque', 'ecartItineraireMax', 'ecartAuPlafondAPriori']],
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Les segments de la rangée des groupes, dans l'ordre de COLONNES : un
|
|
|
|
|
// groupe couvre ses colonnes d'un seul segment, une colonne hors groupe
|
|
|
|
|
// forme le sien, sans clé. Un groupe dont les colonnes ne se suivent pas
|
|
|
|
|
// dans COLONNES lève au chargement du module.
|
|
|
|
|
function segmenter() {
|
|
|
|
|
const noms = COLONNES.map(({ nom }) => nom);
|
|
|
|
|
const segments = [];
|
|
|
|
|
for (let rang = 0; rang < noms.length; ) {
|
|
|
|
|
const groupe = GROUPES.find(([, colonnes]) => colonnes[0] === noms[rang]);
|
|
|
|
|
if (groupe === undefined) {
|
|
|
|
|
if (GROUPES.some(([, colonnes]) => colonnes.includes(noms[rang]))) throw new Error(`colonne groupée hors de son groupe : ${noms[rang]}`);
|
|
|
|
|
segments.push({ cle: null, colonnes: [noms[rang]] });
|
|
|
|
|
rang += 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const [nom, colonnes] = groupe;
|
|
|
|
|
colonnes.forEach((colonne, decalage) => {
|
|
|
|
|
if (noms[rang + decalage] !== colonne) throw new Error(`groupe ${nom} : colonnes non voisines`);
|
|
|
|
|
});
|
|
|
|
|
segments.push({ cle: `propositions.groupe.${nom}`, colonnes });
|
|
|
|
|
rang += colonnes.length;
|
|
|
|
|
}
|
|
|
|
|
return segments.map(({ cle, colonnes }) => Object.freeze({ cle, etendue: colonnes.length, colonnes: Object.freeze([...colonnes]) }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** La rangée des groupes d'en-têtes, au-dessus de celle des colonnes :
|
|
|
|
|
* { cle, etendue, colonnes } par segment, dans l'ordre de COLONNES — cle,
|
|
|
|
|
* la clé du libellé du groupe, null pour une colonne hors groupe ;
|
|
|
|
|
* etendue, le nombre de colonnes couvertes ; colonnes, leurs noms. Les
|
|
|
|
|
* segments couvrent chaque colonne une fois. */
|
|
|
|
|
export const GROUPES_COLONNES = Object.freeze(segmenter());
|
|
|
|
|
|
[ADD] propositions: explanation tooltips on criteria and column headers
The propositions table names its measures with the glossary's terms, which
an organiser cannot be expected to know. Each ranking criterion of the
order sentence and each column header now shows a short explanation on
hover or keyboard focus: what the number counts and whether lower or
higher is better, written from the spec and the engine. The tooltip is a
reusable primitive, read through aria-describedby, kept in the window,
never over its word, closed by Escape; the rendering guards measure it.
Checked: 2882 node, 592 browser tests, from the index alone.
--- FR ---
[ADD] propositions : infobulles d'explication des critères et colonnes
Le tableau des propositions nomme ses mesures par les termes du glossaire,
qu'un organisateur n'a pas à connaître. Chaque critère de la phrase de
l'ordre et chaque en-tête de colonne montre une courte explication au
survol ou au focus : ce que compte le nombre, et s'il vaut mieux bas ou
haut, écrite d'après le spec et le moteur. L'infobulle est une primitive
réutilisable, lue par aria-describedby, gardée dans la fenêtre, jamais sur
son mot, fermée par Échap ; les gardes du rendu la mesurent.
Vérifié : 2882 node, 592 navigateur, depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-08 04:56:52 -04:00
|
|
|
/**
|
|
|
|
|
* La clé de l'explication d'une colonne : le texte de l'infobulle de son
|
|
|
|
|
* en-tête, et celui du nom de critère qui la désigne dans la phrase de
|
|
|
|
|
* l'ordre (colonneDuCritere) — un critère et sa colonne partagent la même.
|
|
|
|
|
* Lève TypeError pour un nom qui n'est pas une colonne de COLONNES.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} nom le nom d'une colonne
|
|
|
|
|
* @returns {string}
|
|
|
|
|
*/
|
|
|
|
|
export function explicationDeColonne(nom) {
|
|
|
|
|
const colonne = COLONNES.find((candidate) => candidate.nom === nom);
|
|
|
|
|
if (colonne === undefined) throw new TypeError(`colonne inconnue : ${nom}`);
|
|
|
|
|
return colonne.explication;
|
|
|
|
|
}
|
|
|
|
|
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
// Une cellule de nombre : null s'écrit « — ».
|
|
|
|
|
const nombre = (colonne, valeur) => ({
|
|
|
|
|
colonne,
|
|
|
|
|
valeur,
|
|
|
|
|
cle: valeur === null ? VIDE : null,
|
|
|
|
|
decimales: DECIMALES.get(colonne) ?? 0,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Le certificat en mots (§ 5.5) : « minimum atteint » quand minimumAtteint
|
|
|
|
|
// est vrai ; « atteignabilité inconnue » quand il est faux, ou null faute de
|
|
|
|
|
// plafond a priori ; « — » sur une population vide.
|
|
|
|
|
function certificat(ligne) {
|
|
|
|
|
const valeur = ligne.minimumAtteint;
|
|
|
|
|
let cle = 'propositions.certificat.inconnue';
|
|
|
|
|
if (valeur === true) cle = 'propositions.certificat.atteint';
|
|
|
|
|
else if (ligne.mesures.rencontres.length === 0) cle = VIDE;
|
|
|
|
|
return { colonne: 'certificat', valeur, cle, decimales: 0 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Les cellules de mesure d'une ligne comparable, dans l'ordre de LECTURES.
|
|
|
|
|
// L'a priori est inconnu quand troisChiffres ne porte pas d'écart
|
|
|
|
|
// d'itinéraire du tout.
|
|
|
|
|
function cellules(ligne) {
|
|
|
|
|
const inconnu = ligne.troisChiffres.ecartItineraireMax === null;
|
|
|
|
|
return [...LECTURES].map(([colonne, lire]) => {
|
|
|
|
|
if (colonne === 'certificat') return certificat(ligne);
|
|
|
|
|
if (inconnu && SELON_A_PRIORI.has(colonne)) return { colonne, valeur: null, cle: INCONNU, decimales: 0 };
|
|
|
|
|
return nombre(colonne, lire(ligne));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ce que les réservations imposent, énoncé à part (§ 5.4) : les retours et
|
|
|
|
|
// les ancrages qui les imposent, les mêmes pour toute ligne ; les rencontres
|
|
|
|
|
// répétées imposées, propres au plan.
|
|
|
|
|
const imposes = (ligne, { retours, ancrages }) => [
|
|
|
|
|
{ cle: 'propositions.imposes.retours', details: { retours, ancrages } },
|
|
|
|
|
{ cle: 'propositions.imposes.repetees', details: { repetees: ligne.mesures.rencontresRepetees.imposees } },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Un code et ses détails : une raison de dérive, une faute, une violation.
|
|
|
|
|
const codeDe = (raison) => ({ cle: raison.code, details: raison });
|
|
|
|
|
|
|
|
|
|
// Une raison de dérive : son code, ses détails, et la personne qu'elle nomme,
|
|
|
|
|
// null quand elle n'en nomme aucune.
|
|
|
|
|
const raisonDe = (raison) => ({ ...codeDe(raison), participant: raison.participant ?? null });
|
|
|
|
|
|
|
|
|
|
// Les colonnes que couvre la cellule des raisons d'une ligne hors
|
|
|
|
|
// classement : celles des mesures et celle des imposés.
|
|
|
|
|
const ETENDUE_RAISONS = LECTURES.size + 1;
|
|
|
|
|
|
|
|
|
|
// La ligne d'un placement mesuré, ou celle d'un placement hors classement.
|
|
|
|
|
// rang : la place dans le classement, à partir de 1 ; null hors classement
|
|
|
|
|
// et pour le retenu.
|
|
|
|
|
function ligneDe(ligne, table, idRetenu, rang = null) {
|
|
|
|
|
const retenue = ligne.id === idRetenu;
|
|
|
|
|
if (ligne.statut !== 'comparable') {
|
|
|
|
|
return {
|
|
|
|
|
id: ligne.id,
|
|
|
|
|
rang: null,
|
|
|
|
|
retenue,
|
|
|
|
|
comparable: false,
|
|
|
|
|
statut: `propositions.statut.${ligne.statut}`,
|
|
|
|
|
raisons: ligne.raisons.map(raisonDe),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
id: ligne.id,
|
|
|
|
|
rang,
|
|
|
|
|
retenue,
|
|
|
|
|
comparable: true,
|
|
|
|
|
cellules: cellules(ligne),
|
|
|
|
|
imposes: imposes(ligne, table.imposes),
|
|
|
|
|
violations: ligne.violations.map(codeDe),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
[ADD] propositions: a ranking criterion highlights its table column
The order sentence names the ranking criteria, but nothing tied a name to
the column holding its value. Each criterion name, and « l'identifiant
croissant », now highlights its column when hovered or focused: the header
and every cell get heavier side borders and the header an underline, so
text contrast is unchanged and the cue is not colour alone. The mapping
from criterion to column lives in the propositions model, and a criterion
without a column fails there. The sentence still reads word for word.
Checked: 2780 node, 554 browser tests, from the index alone.
--- FR ---
[ADD] propositions : un critère du classement surligne sa colonne
La phrase de l'ordre nomme les critères, mais rien ne reliait un nom à la
colonne qui porte sa valeur. Chaque nom de critère, et « l'identifiant
croissant », surligne maintenant sa colonne au survol ou au focus : l'en-
tête et chaque cellule prennent des bords latéraux plus épais et l'en-tête
un soulignement ; le contraste du texte ne change pas, le signe ne tient
pas qu'à la couleur. Le lien critère-colonne vit dans le modèle, et un
critère sans colonne y lève. La phrase se lit mot pour mot comme avant.
Vérifié : 2780 node, 554 navigateur, depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-08 03:39:25 -04:00
|
|
|
// La colonne que désigne chaque critère du classement (§ 5.7) : celle qui
|
|
|
|
|
// porte la grandeur qu'il compare. La Map ne sert qu'à retrouver la colonne
|
|
|
|
|
// d'un critère ; la phrase suit l'ordre que le classement rend.
|
|
|
|
|
const COLONNE_DU_CRITERE = new Map([
|
|
|
|
|
['ecartAuPlafondAPrioriMax', 'ecartAuPlafondAPriori'],
|
|
|
|
|
['excedentCollisions', 'excedentCollisions'],
|
|
|
|
|
['collisionsCumulees', 'collisionsCumulees'],
|
|
|
|
|
['rencontresRepetees', 'rencontresRepetees'],
|
|
|
|
|
['redondance', 'totalRedondance'],
|
|
|
|
|
['identifiant', 'proposition'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* La colonne du tableau que désigne un critère du classement, ou
|
|
|
|
|
* 'identifiant', le dernier départage, qui désigne la colonne des
|
|
|
|
|
* propositions. Lève TypeError pour un critère sans colonne : un critère
|
|
|
|
|
* ajouté au classement doit dire laquelle il compare.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} critere un nom de CRITERES (moteur), ou 'identifiant'
|
|
|
|
|
* @returns {string} le nom d'une colonne de COLONNES
|
|
|
|
|
*/
|
|
|
|
|
export function colonneDuCritere(critere) {
|
|
|
|
|
const colonne = COLONNE_DU_CRITERE.get(critere);
|
|
|
|
|
if (colonne === undefined) throw new TypeError(`critère sans colonne : ${critere}`);
|
|
|
|
|
return colonne;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Un morceau de phrase : un texte de liaison, ou un nom qui désigne une
|
|
|
|
|
// colonne.
|
|
|
|
|
const liaison = (cle, details = {}) => ({ cle, details, critere: null, colonne: null });
|
|
|
|
|
const designant = (critere, cle) => ({ cle, details: {}, critere, colonne: colonneDuCritere(critere) });
|
|
|
|
|
|
|
|
|
|
// La phrase des critères appliqués, morceau par morceau : le début, chaque
|
|
|
|
|
// critère par son nom moteur.critere.<nom>, puis l'identifiant croissant,
|
|
|
|
|
// séparés, et la fin. Bout à bout, les morceaux traduits composent le texte
|
|
|
|
|
// de propositions.ordre.criteres.
|
|
|
|
|
function phraseDesCriteres(criteresAppliques) {
|
|
|
|
|
const noms = [
|
|
|
|
|
...criteresAppliques.map((critere) => designant(critere, `moteur.critere.${critere}`)),
|
|
|
|
|
designant('identifiant', 'propositions.ordre.identifiant'),
|
|
|
|
|
];
|
|
|
|
|
return [
|
|
|
|
|
liaison('propositions.ordre.debut'),
|
|
|
|
|
...noms.flatMap((nom, rang) => (rang === 0 ? [nom] : [liaison('propositions.ordre.separateur'), nom])),
|
|
|
|
|
liaison('propositions.ordre.fin'),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Les phrases de l'ordre (§ 5.7), chacune une liste de morceaux : le critère
|
|
|
|
|
// sauté et celui appliqué à sa place quand le plafond a priori manque, puis
|
|
|
|
|
// les critères appliqués.
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
function ordre({ criteresAppliques, critereSaute }) {
|
|
|
|
|
const phrases = [];
|
|
|
|
|
if (critereSaute !== null) {
|
[ADD] propositions: a ranking criterion highlights its table column
The order sentence names the ranking criteria, but nothing tied a name to
the column holding its value. Each criterion name, and « l'identifiant
croissant », now highlights its column when hovered or focused: the header
and every cell get heavier side borders and the header an underline, so
text contrast is unchanged and the cue is not colour alone. The mapping
from criterion to column lives in the propositions model, and a criterion
without a column fails there. The sentence still reads word for word.
Checked: 2780 node, 554 browser tests, from the index alone.
--- FR ---
[ADD] propositions : un critère du classement surligne sa colonne
La phrase de l'ordre nomme les critères, mais rien ne reliait un nom à la
colonne qui porte sa valeur. Chaque nom de critère, et « l'identifiant
croissant », surligne maintenant sa colonne au survol ou au focus : l'en-
tête et chaque cellule prennent des bords latéraux plus épais et l'en-tête
un soulignement ; le contraste du texte ne change pas, le signe ne tient
pas qu'à la couleur. Le lien critère-colonne vit dans le modèle, et un
critère sans colonne y lève. La phrase se lit mot pour mot comme avant.
Vérifié : 2780 node, 554 navigateur, depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-08 03:39:25 -04:00
|
|
|
phrases.push([liaison('propositions.ordre.saute', { saute: critereSaute, applique: criteresAppliques[0] })]);
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
}
|
[ADD] propositions: a ranking criterion highlights its table column
The order sentence names the ranking criteria, but nothing tied a name to
the column holding its value. Each criterion name, and « l'identifiant
croissant », now highlights its column when hovered or focused: the header
and every cell get heavier side borders and the header an underline, so
text contrast is unchanged and the cue is not colour alone. The mapping
from criterion to column lives in the propositions model, and a criterion
without a column fails there. The sentence still reads word for word.
Checked: 2780 node, 554 browser tests, from the index alone.
--- FR ---
[ADD] propositions : un critère du classement surligne sa colonne
La phrase de l'ordre nomme les critères, mais rien ne reliait un nom à la
colonne qui porte sa valeur. Chaque nom de critère, et « l'identifiant
croissant », surligne maintenant sa colonne au survol ou au focus : l'en-
tête et chaque cellule prennent des bords latéraux plus épais et l'en-tête
un soulignement ; le contraste du texte ne change pas, le signe ne tient
pas qu'à la couleur. Le lien critère-colonne vit dans le modèle, et un
critère sans colonne y lève. La phrase se lit mot pour mot comme avant.
Vérifié : 2780 node, 554 navigateur, depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-08 03:39:25 -04:00
|
|
|
phrases.push(phraseDesCriteres(criteresAppliques));
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
return phrases;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
|
|
|
* Le tableau d'une table d'évaluation (evaluer, evaluation.js).
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
*
|
|
|
|
|
* @param {import('../../application/evaluation.js').TableEvaluation} tableEvaluation
|
|
|
|
|
* @param {{ajuste?: boolean}} [options] ajuste : le retenu ne porte plus le
|
|
|
|
|
* placement de sa proposition d'origine (retenuAjuste) ; il choisit le
|
|
|
|
|
* libellé de la ligne du retenu
|
[ADD] propositions: a ranking criterion highlights its table column
The order sentence names the ranking criteria, but nothing tied a name to
the column holding its value. Each criterion name, and « l'identifiant
croissant », now highlights its column when hovered or focused: the header
and every cell get heavier side borders and the header an underline, so
text contrast is unchanged and the cue is not colour alone. The mapping
from criterion to column lives in the propositions model, and a criterion
without a column fails there. The sentence still reads word for word.
Checked: 2780 node, 554 browser tests, from the index alone.
--- FR ---
[ADD] propositions : un critère du classement surligne sa colonne
La phrase de l'ordre nomme les critères, mais rien ne reliait un nom à la
colonne qui porte sa valeur. Chaque nom de critère, et « l'identifiant
croissant », surligne maintenant sa colonne au survol ou au focus : l'en-
tête et chaque cellule prennent des bords latéraux plus épais et l'en-tête
un soulignement ; le contraste du texte ne change pas, le signe ne tient
pas qu'à la couleur. Le lien critère-colonne vit dans le modèle, et un
critère sans colonne y lève. La phrase se lit mot pour mot comme avant.
Vérifié : 2780 node, 554 navigateur, depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-08 03:39:25 -04:00
|
|
|
* @returns {{ordre: Array<Array<{cle: string, details: Object, critere: string|null,
|
|
|
|
|
* colonne: string|null}>>, colonnes: typeof COLONNES,
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
* etendueRaisons: number, lignes: Object[], horsClassement: Object[], retenu: Object|null}}
|
[ADD] propositions: a ranking criterion highlights its table column
The order sentence names the ranking criteria, but nothing tied a name to
the column holding its value. Each criterion name, and « l'identifiant
croissant », now highlights its column when hovered or focused: the header
and every cell get heavier side borders and the header an underline, so
text contrast is unchanged and the cue is not colour alone. The mapping
from criterion to column lives in the propositions model, and a criterion
without a column fails there. The sentence still reads word for word.
Checked: 2780 node, 554 browser tests, from the index alone.
--- FR ---
[ADD] propositions : un critère du classement surligne sa colonne
La phrase de l'ordre nomme les critères, mais rien ne reliait un nom à la
colonne qui porte sa valeur. Chaque nom de critère, et « l'identifiant
croissant », surligne maintenant sa colonne au survol ou au focus : l'en-
tête et chaque cellule prennent des bords latéraux plus épais et l'en-tête
un soulignement ; le contraste du texte ne change pas, le signe ne tient
pas qu'à la couleur. Le lien critère-colonne vit dans le modèle, et un
critère sans colonne y lève. La phrase se lit mot pour mot comme avant.
Vérifié : 2780 node, 554 navigateur, depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-08 03:39:25 -04:00
|
|
|
* ordre : les phrases de l'ordre, chacune une liste de morceaux que t
|
|
|
|
|
* traduit et que l'affichage met bout à bout ; un morceau qui nomme un
|
|
|
|
|
* critère, ou l'identifiant croissant, porte critere et la colonne qu'il
|
|
|
|
|
* désigne, null pour un texte de liaison ; lignes : les comparables dans
|
|
|
|
|
* l'ordre du classement, chacune { id, rang, retenue, comparable: true,
|
|
|
|
|
* cellules, imposes, violations } ;
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
* horsClassement : les autres par identifiant, { id, rang: null, retenue,
|
|
|
|
|
* comparable: false, statut, raisons } — statut la clé de « non comparable »,
|
|
|
|
|
* raisons leurs codes, chacune { cle, details, participant }, participant
|
|
|
|
|
* l'identifiant de la personne en cause ou null ; etendueRaisons : le nombre
|
|
|
|
|
* de colonnes que couvre la cellule des raisons ; retenu : la ligne du
|
|
|
|
|
* retenu sous l'une ou l'autre forme, plus libelle { cle, details } — « tel
|
|
|
|
|
* que » ou « ajusté » —, null sans retenu. retenue marque la proposition
|
|
|
|
|
* d'origine du retenu.
|
|
|
|
|
*/
|
|
|
|
|
export function tableau(tableEvaluation, { ajuste = false } = {}) {
|
|
|
|
|
const idRetenu = tableEvaluation.retenu?.id ?? null;
|
|
|
|
|
// Les comparables ouvrent la liste, dans l'ordre du classement : leur
|
|
|
|
|
// place y est leur rang.
|
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
|
|
|
const toutes = tableEvaluation.propositions.map((ligne, place) => ligneDe(ligne, tableEvaluation, idRetenu, place + 1));
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
return {
|
|
|
|
|
ordre: ordre(tableEvaluation.classement),
|
|
|
|
|
colonnes: COLONNES,
|
|
|
|
|
etendueRaisons: ETENDUE_RAISONS,
|
|
|
|
|
lignes: toutes.filter(({ comparable }) => comparable),
|
|
|
|
|
horsClassement: toutes.filter(({ comparable }) => !comparable),
|
|
|
|
|
retenu: tableEvaluation.retenu === null ? null : ligneDuRetenu(tableEvaluation, idRetenu, ajuste),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// La ligne du retenu, et son libellé : « tel que » la proposition d'origine,
|
|
|
|
|
// ou « ajusté » depuis elle.
|
|
|
|
|
function ligneDuRetenu(tableEvaluation, idRetenu, ajuste) {
|
|
|
|
|
const cle = ajuste ? 'propositions.retenu.ajuste' : 'propositions.retenu.origine';
|
|
|
|
|
return { ...ligneDe(tableEvaluation.retenu, tableEvaluation, idRetenu), libelle: { cle, details: { proposition: idRetenu } } };
|
|
|
|
|
}
|
|
|
|
|
|
[FIX] interface: ghost labels, keyboard, valid HTML, closed data-* list
The drag ghost names what releasing at the point changes, floor included.
Escape on a seat returns to its table, a refused Tab stays put, the legend
toggle exposes aria-expanded, and the header markup is valid HTML. Every
data-* attribute the interface sets belongs to one closed list that a guard
reads. The tree guards follow helpers a test imports, and the reporter says
the whole series stays watched under coverage on a small machine.
Checked: every new test red first; 3001 node, 635 browser and 63 long tests
green from the index alone.
--- FR ---
[FIX] interface : fantôme nommé, clavier, HTML valide, data-* fermés
Le fantôme du glisser nomme ce que relâcher au point modifie, sol compris.
Échap sur un siège remonte à sa table, un Tab refusé reste en place, la
bascule de la légende expose aria-expanded, et la tête est du HTML valide.
Chaque attribut data-* que l'interface pose appartient à une liste fermée
qu'une garde lit. Les gardes de l'arbre suivent les aides qu'une épreuve
importe, et le rapporteur dit la série entière surveillée sous couverture.
Vérifié : chaque épreuve rouge d'abord ; 3001 node, 635 navigateur et 63
longues vertes depuis l'index seul.
Assisted-by: Claude Opus 5.5
2026-10-09 04:01:57 -04:00
|
|
|
/**
|
|
|
|
|
* La colonne que la phrase de l'ordre surligne (§ 5.7), de deux états
|
|
|
|
|
* tenus à part : celle du nom survolé, sinon celle du nom qui a le focus,
|
|
|
|
|
* sinon null. Quitter l'un rend l'autre : un survol qui passe ne retire pas
|
|
|
|
|
* le surlignage du focus.
|
|
|
|
|
*
|
|
|
|
|
* @param {string|null} survolee la colonne du nom sous le pointeur
|
|
|
|
|
* @param {string|null} focalisee la colonne du nom qui a le focus
|
|
|
|
|
* @returns {string|null}
|
|
|
|
|
*/
|
|
|
|
|
export function surlignee(survolee, focalisee) {
|
|
|
|
|
return survolee ?? focalisee;
|
|
|
|
|
}
|
|
|
|
|
|
[ADD] interface: propositions — ranked table, retain, clear
The operator chooses among the generated plans. The ranked table orders
propositions by the criteria of § 5.4 and shows, for encounters, the
minimum only — the mean is the same for every proposition at full tables
— with redundancy read through its sum. A proposition is retained or the
list cleared through named commands; clearing spares the retained one,
and a proposition that broke is shown apart, not ranked.
Checked: 2400 node and 406 browser tests.
--- FR ---
[ADD] interface : propositions — tableau classé, retenir, effacer
L'opérateur choisit parmi les plans générés. Le tableau classé ordonne les
propositions selon les critères du § 5.4 et ne montre, pour les
rencontres, que le minimum — la moyenne est la même pour toute
proposition à tables pleines —, la redondance se lisant par sa somme. Une
proposition se retient, ou la liste s'efface, par des commandes nommées ;
effacer épargne la retenue, et une proposition abîmée se montre à part,
hors classement.
Vérifié : 2400 node et 406 navigateur.
Assisted-by: Claude Opus 5.5
2026-10-07 09:10:39 -04:00
|
|
|
/**
|
|
|
|
|
* Le texte d'une cellule : t(cle) quand elle porte une clé, sinon son nombre
|
|
|
|
|
* mis en forme.
|
|
|
|
|
*
|
|
|
|
|
* @param {{valeur: number|boolean|null, cle: string|null, decimales: number}} cellule
|
|
|
|
|
* @param {{t: Function, format: {nombre: Function}}} outils
|
|
|
|
|
* @returns {string}
|
|
|
|
|
*/
|
|
|
|
|
export function texteCellule({ valeur, cle, decimales }, { t, format }) {
|
|
|
|
|
return cle === null ? format.nombre(valeur, { decimales }) : t(cle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Égalité en profondeur de deux valeurs du fichier d'état : nombres,
|
|
|
|
|
// chaînes, booléens, null, listes et objets simples.
|
|
|
|
|
function egaux(a, b) {
|
|
|
|
|
if (a === b) return true;
|
|
|
|
|
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
|
|
|
|
|
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
|
|
|
const clesA = Object.keys(a);
|
|
|
|
|
if (clesA.length !== Object.keys(b).length) return false;
|
|
|
|
|
return clesA.every((cle) => Object.hasOwn(b, cle) && egaux(a[cle], b[cle]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ce que le retenu copie de sa proposition d'origine, et que retenirProposition
|
|
|
|
|
// remplacerait.
|
|
|
|
|
const CHAMPS_DU_PLACEMENT = ['siegesAttribues', 'tables', 'capacites', 'tours', 'participants', 'placement'];
|
|
|
|
|
|
|
|
|
|
/** Vrai quand un retenu existe et ne porte plus le placement de sa proposition d'origine :
|
|
|
|
|
* tables, capacités, tours, participants ou placement diffèrent, en profondeur, de ceux
|
|
|
|
|
* de la proposition retenu.proposition, ou celle-ci n'est plus dans la liste. */
|
|
|
|
|
export function retenuAjuste(charge) {
|
|
|
|
|
const { retenu } = charge;
|
|
|
|
|
if (retenu === null) return false;
|
|
|
|
|
const origine = charge.propositions.find(({ id }) => id === retenu.proposition);
|
|
|
|
|
if (origine === undefined) return true;
|
|
|
|
|
return !CHAMPS_DU_PLACEMENT.every((champ) => egaux(retenu[champ], origine[champ]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* La fenêtre à confirmer avant de retenir la proposition id (§ 2.1) : retenir
|
|
|
|
|
* remplace le retenu, et détruit ses ajustements quand il en porte. Elle
|
|
|
|
|
* s'ouvre quand la séance accepterait le geste — etat.refus null — et que le
|
|
|
|
|
* retenu est ajusté, y compris pour re-retenir sa proposition d'origine ; un
|
|
|
|
|
* geste que la séance refuserait s'exécute sans fenêtre, et son refus
|
|
|
|
|
* s'affiche.
|
|
|
|
|
*
|
|
|
|
|
* @param {{refus: Object|null, charge: Object|null}} etat l'état observable de la séance
|
|
|
|
|
* @param {number} id la proposition à retenir
|
|
|
|
|
* @returns {null|{id: number, titre: {cle: string, details: Object},
|
|
|
|
|
* elements: Array<{cle: string, details: Object}>}} null sans fenêtre ;
|
|
|
|
|
* sinon la proposition demandée, le titre, puis les éléments qui nomment le
|
|
|
|
|
* retenu ajusté et la proposition qui le remplace
|
|
|
|
|
*/
|
|
|
|
|
export function demandeConfirmation({ refus, charge }, id) {
|
|
|
|
|
if (refus !== null || charge === null || !retenuAjuste(charge)) return null;
|
|
|
|
|
return {
|
|
|
|
|
id,
|
|
|
|
|
titre: { cle: 'propositions.confirmation.titre', details: { proposition: id } },
|
|
|
|
|
elements: [
|
|
|
|
|
{ cle: 'propositions.confirmation.retenuAjuste', details: { proposition: charge.retenu.proposition } },
|
|
|
|
|
{ cle: 'propositions.confirmation.remplacant', details: { proposition: id } },
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* La table d'évaluation d'une charge, ou ce qui l'empêche : null sans
|
|
|
|
|
* proposition ni retenu, rien n'étant à mesurer ; le refus de normaliser —
|
|
|
|
|
* une erreur qui porte un code — quand la configuration ne se tient pas.
|
|
|
|
|
* Toute autre erreur remonte.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} charge
|
|
|
|
|
* @returns {{table: Object|null, refus: null|{code: string, details: Object}}}
|
|
|
|
|
*/
|
|
|
|
|
export function evaluation(charge) {
|
|
|
|
|
if (charge.propositions.length === 0 && charge.retenu === null) return { table: null, refus: null };
|
|
|
|
|
try {
|
|
|
|
|
return { table: evaluer(charge), refus: null };
|
|
|
|
|
} catch (erreur) {
|
|
|
|
|
if (typeof erreur?.code !== 'string') throw erreur;
|
|
|
|
|
return { table: null, refus: { code: erreur.code, details: erreur.details ?? {} } };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Le nom affiché de la personne id de la charge, exclue comprise ; null
|
|
|
|
|
* pour une personne que la charge n'a plus, et pour id null. */
|
|
|
|
|
export function nomDe(charge, id) {
|
|
|
|
|
const personne = charge.participants.find((candidate) => candidate.id === id);
|
|
|
|
|
return personne === undefined ? null : nomAffiche(personne);
|
|
|
|
|
}
|