[ADD] demo: delivered event files, example CSVs, payload fingerprint

The four demonstrations ship as event files written once by a script,
the source of truth of § 15.5: a test replays the generator and compares
SHA-256 fingerprints of the payload alone, naming the paths that differ,
so a new build version never forces a regeneration. The affiliation
profile is read from the delivered file. The two demonstration CSVs are
the export of those files; the edge-case CSV is written by hand, and
its windows-1252 variant derived. Version and forged-name guards skip
these generated files, as § 18.5 and § 15.6 allow.

Checked: node and long series; the script writes only its seven files.

--- FR ---

[ADD] démo : fichiers d'événement livrés, CSV d'exemple, empreinte

Les quatre démonstrations se livrent en fichiers d'événement qu'un
script écrit une fois, source de vérité du § 15.5 : une épreuve rejoue
le générateur et compare les empreintes SHA-256 de la seule charge, en
nommant les chemins qui diffèrent ; une nouvelle version n'impose donc
aucune régénération. Le profil d'appartenances se lit dans le fichier
livré. Les deux CSV de démonstration sont l'export de ces fichiers ;
celui des cas limites s'écrit à la main, sa variante windows-1252 en
dérive. Les gardes de version et de noms forgés passent ces fichiers.

Vérifié : séries node et longue ; le script n'écrit que ses sept fichiers.

Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
This commit is contained in:
Mathieu Benoit 2026-10-06 17:03:15 -04:00
parent 201259331d
commit 2bca543e7e
18 changed files with 2493 additions and 19 deletions

2
.gitattributes vendored
View file

@ -1,3 +1,5 @@
* text=auto eol=lf
*.cmd text eol=crlf
test/fixtures/csv/** -text
src/demo/livrees/*.gtt.json -text
exemples/*.csv -text

View file

@ -0,0 +1,16 @@
Nom;Prénom;Organisation;Courriel;Rôle;Exclu;Notes;Téléphone
Lanterneau;Basile;Chœur de l’Anse;basile.lanterneau@exemple.test;animation;;;555-0101
Pommerel;Capucine;Société des Hérons;capucine.pommerel@exemple.test;;;;555-0102
Marbrelle;Gaspard;Cercle des Saules;;animation;;;555-0103
Sablonnet;Ysaline;Chœur de l’Anse;;;;;555-0104
Givrecœur;Anselme;;anselme.givrecoeur@exemple.test;;;;555-0105
Tourterel;Héloïse;Cercle des Saules;;;oui;S’est désistée la veille;555-0106
Fenouillet;Côme;Société des Hérons;;;;"végétarien; sans noix, ni arachides";555-0107
Coudraie;Apolline;Chœur de l’Anse;;;;"Arrive après le premier tour
repart avant le dessert";555-0108
Lanterneau;Basile;Chœur de l’Anse;;;;;555-0109
Brunœil;;Cercle des Saules;brunoeil@exemple.test;;;;555-0110
Ormelin;Faustine;Societe des herons;;;;;555-0111
;Clotilde;Cercle des Saules;clotilde@exemple.test;;;;555-0112
Ardoisier;Edgar;Société des Hérons;;;;;555-0113
Cressonnier;Bertille;Cercle des Saules;;;;;555-0114
1 Nom Prénom Organisation Courriel Rôle Exclu Notes Téléphone
2 Lanterneau Basile Chœur de l’Anse basile.lanterneau@exemple.test animation 555-0101
3 Pommerel Capucine Société des Hérons capucine.pommerel@exemple.test 555-0102
4 Marbrelle Gaspard Cercle des Saules animation 555-0103
5 Sablonnet Ysaline Chœur de l’Anse 555-0104
6 Givrecœur Anselme anselme.givrecoeur@exemple.test 555-0105
7 Tourterel Héloïse Cercle des Saules oui S’est désistée la veille 555-0106
8 Fenouillet Côme Société des Hérons végétarien; sans noix, ni arachides 555-0107
9 Coudraie Apolline Chœur de l’Anse Arrive après le premier tour repart avant le dessert 555-0108
10 Lanterneau Basile Chœur de l’Anse 555-0109
11 Brunœil Cercle des Saules brunoeil@exemple.test 555-0110
12 Ormelin Faustine Societe des herons 555-0111
13 Clotilde Cercle des Saules clotilde@exemple.test 555-0112
14 Ardoisier Edgar Société des Hérons 555-0113
15 Cressonnier Bertille Cercle des Saules 555-0114

View file

@ -0,0 +1,16 @@
Nom,Prénom,Organisation,Courriel,Rôle,Exclu,Notes,Téléphone
Lanterneau,Basile,Chœur de l’Anse,basile.lanterneau@exemple.test,animation,,,555-0101
Pommerel,Capucine,Société des Hérons,capucine.pommerel@exemple.test,,,,555-0102
Marbrelle,Gaspard,Cercle des Saules,,animation,,,555-0103
Sablonnet,Ysaline,Chœur de l’Anse,,,,,555-0104
Givrecœur,Anselme,,anselme.givrecoeur@exemple.test,,,,555-0105
Tourterel,Héloïse,Cercle des Saules,,,oui,S’est désistée la veille,555-0106
Fenouillet,Côme,Société des Hérons,,,,"végétarien; sans noix, ni arachides",555-0107
Coudraie,Apolline,Chœur de l’Anse,,,,"Arrive après le premier tour
repart avant le dessert",555-0108
Lanterneau,Basile,Chœur de l’Anse,,,,,555-0109
Brunœil,,Cercle des Saules,brunoeil@exemple.test,,,,555-0110
Ormelin,Faustine,Societe des herons,,,,,555-0111
,Clotilde,Cercle des Saules,clotilde@exemple.test,,,,555-0112
Ardoisier,Edgar,Société des Hérons,,,,,555-0113
Cressonnier,Bertille,Cercle des Saules,,,,,555-0114
1 Nom Prénom Organisation Courriel Rôle Exclu Notes Téléphone
2 Lanterneau Basile Chœur de l’Anse basile.lanterneau@exemple.test animation 555-0101
3 Pommerel Capucine Société des Hérons capucine.pommerel@exemple.test 555-0102
4 Marbrelle Gaspard Cercle des Saules animation 555-0103
5 Sablonnet Ysaline Chœur de l’Anse 555-0104
6 Givrecœur Anselme anselme.givrecoeur@exemple.test 555-0105
7 Tourterel Héloïse Cercle des Saules oui S’est désistée la veille 555-0106
8 Fenouillet Côme Société des Hérons végétarien; sans noix, ni arachides 555-0107
9 Coudraie Apolline Chœur de l’Anse Arrive après le premier tour repart avant le dessert 555-0108
10 Lanterneau Basile Chœur de l’Anse 555-0109
11 Brunœil Cercle des Saules brunoeil@exemple.test 555-0110
12 Ormelin Faustine Societe des herons 555-0111
13 Clotilde Cercle des Saules clotilde@exemple.test 555-0112
14 Ardoisier Edgar Société des Hérons 555-0113
15 Cressonnier Bertille Cercle des Saules 555-0114

View file

@ -0,0 +1,261 @@
nom;prenom;appartenance;courriel;titre_pressenti;exclu;notes
Mirvault;Isabelle;Comptoir Gravelune;;;non;
Neskière;Youssef;Verrerie Vaubrenne;;animation;non;
Abrevande;Félix;Imprimerie Yzorne;;;non;
Fralvançon;Hugo;Brasserie Wassorel;;;non;
Fralveux;Manon;Menuiserie Estrolle;;;non;
Luskotte;Catherine;Fonderie Estrolle;;;non;
WAMMERWOLM-MAWWEMMEL;Zoé;Menuiserie Ulvardes;;;non;
Neskichon;Louise;Ateliers Rossolde;;animation;non;
Urvesson;Catherine;Fabrique Ulvardes;;;non;
Hulvivet;Karim;Fonderie Xandrelle;;;non;
Selveron;Caroline;Ateliers Cassorgue;;;non;
Korvault;Catherine;Tannerie Nervanche;;;non;
Fralvotte;Caroline;Fonderie Ivoranche;;;non;
Jusvesson;Geneviève;Tannerie Belvarre;;;non;
Abrevumet;Paul;Ateliers Aubrenc;;;non;
Fralvault;Léa;Fonderie Sorbagne;;;non;
Obrivet;Fatima;Fabrique Sorbagne;;;non;
Voskeval;Philippe;Menuiserie Estrolle;;animation;non;
Dulveval;Zoé;Menuiserie Quimvaux;;;non;
Korveux;Paul;Filature Olbrecq;;;non;
Luskière;Julie;Fabrique Sorbagne;;;non;
Clovande;Émilie;Ateliers Falvigne;;;non;
Hulvande;Paul;Verrerie Vaubrenne;;;non;
Fralvesson;Hugo;Filature Wassorel;;;non;
Urvière;Isabelle;Brasserie Belvarre;;;non;
Rozvotte;Thomas;Verrerie Ivoranche;;;non;
Bralmumet;Isabelle;Verrerie Aubrenc;;;non;
Dulvouze;Catherine;Ateliers Rossolde;;;non;
Voskichon;Marc;Menuiserie Estrolle;;;non;
Mirvivet;Simon;Ateliers Ivoranche;;;non;
Clovault;Louise;Imprimerie Yzorne;;;non;
Voskeux;Lucie;Coopérative Morvalle;;;non;
Eskignac;Amina;Brasserie Xandrelle;;;non;
Korvouze;Fatima;Ateliers Tavrenne;;animation;non;
Selvoubre;Luc;Brasserie Sorbagne;;;non;
Fralvichon;Étienne;Papeterie Ulvardes;;;non;
Voskivet;Philippe;Ateliers Tavrenne;;;non;
Quilmançon;Louise;Menuiserie Estrolle;;;non;
Luskande;Sophie;Fabrique Morvalle;;;non;
Abreveron;Pierre;Ateliers Rossolde;;;non;
Neskeux;Philippe;Comptoir Xandrelle;;;non;
Neskançon;Amina;Fabrique Ulvardes;;;non;
Tolvande;Léa;Ateliers Rossolde;;;non;
Quilmeron;Antoine;Fabrique Kalvenne;;;non;
Clovumet;Guillaume;Verrerie Pravette;;;non;
Eskeux;Michel;Papeterie Wassorel;;;non;
Hulvault;Louise;Coopérative Jolvane;;;non;
Hulveron;Catherine;Coopérative Rossolde;;animation;non;
Ivrivet;François;Menuiserie Sorbagne;;animation;non;
Eskumet;Samuel;Verrerie Ivoranche;;;non;
Fralvivet;Claire;Imprimerie Yzorne;;;non;
Mirveux;Léa;Menuiserie Wassorel;;;non;
Gorzesson;Karim;Papeterie Ulvardes;;;non;
Fralvumet;Jean;Ateliers Falvigne;;animation;non;
Urvançon;Manon;Ateliers Tavrenne;;animation;non;
Quilmande;Sylvie;Imprimerie Yzorne;;;non;
Gorzichon;François;Ateliers Estrolle;;;non;
Pruvesson;Amina;Fonderie Sorbagne;;;non;
Quilmière;Lucie;Fabrique Ivoranche;;;non;
Korvesson;Fatima;Ateliers Rossolde;;;non;
Selvesson;Nicolas;Imprimerie Yzorne;;;non;
Selvivet;Louis;Menuiserie Hostrenne;;animation;non;
Neskesson;Louise;Tannerie Belvarre;;;non;
Tolvesson;Lucie;Filature Zelvaine;;;non;
Quilmesson;Olivier;Comptoir Gravelune;;;non;
Luskivet;Guillaume;Brasserie Lavorneau;;;non;
Neskault;Gabriel;Menuiserie Tavrenne;;;non;
Urvumet;Antoine;Menuiserie Hostrenne;;;non;
Jusvançon;Olivier;Brasserie Morvalle;;;non;
Dulvançon;Simon;Menuiserie Ivoranche;;;non;
Gorzard;Chloé;Fabrique Sorbagne;;;non;
Pruveux;Gabriel;Papeterie Estrolle;;;non;
Clovière;Manon;Ateliers Ivoranche;;;non;
Obrouze;François;Tannerie Olbrecq;;animation;non;
Tolveval;Karim;Tannerie Belvarre;;;non;
Dulvumet;Geneviève;Menuiserie Quimvaux;;animation;non;
Bérêçàuvïlle-Dœûmôntëÿ;Hugo;Ateliers Falvigne;;;non;
Gorzeron;Claire;Menuiserie Hostrenne;;;non;
Luskumet;Hélène;Brasserie Xandrelle;;;non;
Fralvande;Louis;Ateliers Rossolde;;;non;
Selvault;Émilie;Fabrique Sorbagne;;;non;
Neskumet;Léa;Verrerie Aubrenc;;;non;
Korvichon;Isabelle;Menuiserie Ulvardes;;;non;
Obrande;Michel;Menuiserie Estrolle;;;non;
Luskeval;Isabelle;Brasserie Wassorel;;;non;
Tolvoubre;Fatima;Papeterie Ulvardes;;;non;
Gorzière;Zoé;Menuiserie Olbrecq;;;non;
Korvignac;Sylvie;Fabrique Ulvardes;;;non;
Obreval;Antoine;Ateliers Cassorgue;;;non;
Korveron;Michel;Fonderie Sorbagne;;animation;non;
Tolvumet;Camille;Fonderie Estrolle;;;non;
Dulvesson;Marie;Ateliers Cassorgue;;;non;
Jusvard;Youssef;Tannerie Aubrenc;;animation;non;
Tolvignac;Guillaume;Menuiserie Sorbagne;;;non;
Mirvotte;Gabriel;Tannerie Belvarre;;;non;
Urvard;Marie;Filature Wassorel;;animation;non;
Bralmesson;Marie;Coopérative Dravenne;;animation;non;
Neskignac;Paul;Brasserie Sorbagne;;;non;
Bralmeux;Samuel;Ateliers Rossolde;;animation;non;
Ivrotte;Raphaël;Coopérative Rossolde;;;non;
Luskouze;Marc;Ateliers Rossolde;;;non;
Jusvotte;Sylvie;Brasserie Belvarre;;;non;
Bralmoubre;Véronique;Fonderie Xandrelle;;;non;
Selvard;Pierre;Verrerie Vaubrenne;;;non;
Hulveux;Marie;Coopérative Rossolde;;;non;
Pruvotte;Sophie;Imprimerie Yzorne;;animation;non;
Fralveval;Karim;Coopérative Gravelune;;;non;
Gorzignac;Philippe;Imprimerie Falvigne;;;non;
Selvouze;Étienne;Ateliers Tavrenne;;;non;
Hulvichon;Louis;Ateliers Falvigne;;;non;
Urvoubre;Sylvie;Verrerie Vaubrenne;;;non;
Pruveval;Pierre;Comptoir Xandrelle;;;non;
Hulvotte;Guillaume;Verrerie Vaubrenne;;;non;
Abrevignac;Geneviève;Brasserie Morvalle;;;non;
Abrevault;Gabriel;Ateliers Rossolde;;animation;non;
Bralmignac;Amina;Fonderie Estrolle;;;non;
Bralmançon;Olivier;Comptoir Lavorneau;;;non;
Luskoubre;Marc;Verrerie Vaubrenne;;;non;
Abrevoubre;Geneviève;Menuiserie Gravelune;;;non;
Korvoubre;Raphaël;Verrerie Vaubrenne;;;non;
Mirvard;Pierre;Verrerie Ivoranche;;;non;
Jusvumet;Camille;Fonderie Estrolle;;;non;
Cloveval;Samuel;Menuiserie Ulvardes;;;non;
Selvière;Antoine;Coopérative Dravenne;;;non;
Jusvouze;Véronique;Verrerie Vaubrenne;;;non;
Neskivet;Paul;Comptoir Jolvane;;;non;
Dulvande;Samuel;Verrerie Pravette;;;non;
Bralmault;Antoine;Fabrique Sorbagne;;animation;non;
Abrevière;Émilie;Imprimerie Yzorne;;animation;non;
Selveval;Lucie;Filature Zelvaine;;;non;
Tolvichon;Olivier;Fonderie Xandrelle;;;non;
Hulvard;Fatima;Ateliers Estrolle;;;non;
Ivreux;Alexandre;Tannerie Aubrenc;;;non;
Korveval;Félix;Tannerie Nervanche;;;non;
Urvichon;Karim;Tannerie Nervanche;;;non;
Dulvichon;Émilie;Menuiserie Ulvardes;;;non;
Tolvouze;Luc;Fabrique Ivoranche;;;non;
Jusvivet;Claire;Imprimerie Yzorne;;animation;non;
Quilmault;Amina;Coopérative Jolvane;;;non;
Ivrouze;François;Fonderie Aubrenc;;;non;
Gorzumet;Hélène;Fonderie Aubrenc;;;non;
Ivrault;Sophie;Coopérative Morvalle;;;non;
Bralmière;Louise;Tannerie Quimvaux;;;non;
Korvande;Nicolas;Comptoir Sorbagne;;;non;
Ivrumet;Geneviève;Coopérative Jolvane;;;non;
Obresson;Geneviève;Coopérative Gravelune;;;non;
Obrançon;Guillaume;Papeterie Hostrenne;;;non;
Urvignac;Fatima;Ateliers Aubrenc;;;non;
Bralmotte;Claire;Ateliers Tavrenne;;;non;
Hulvignac;Félix;Imprimerie Hostrenne;;;non;
Clovotte;François;Coopérative Rossolde;;;non;
Dulvoubre;Philippe;Brasserie Belvarre;;;non;
Rozveron;Samuel;Fonderie Sorbagne;;;non;
Hulvoubre;Pierre;Menuiserie Olbrecq;;;non;
Urvouze;Émilie;Comptoir Gravelune;;;non;
Korvançon;Félix;Fonderie Ivoranche;;;non;
Neskande;Émilie;Coopérative Gravelune;;;non;
Mirvumet;Chantal;Menuiserie Gravelune;;;non;
Korvotte;Alexandre;Menuiserie Nervanche;;;non;
Eskeval;Zoé;Tannerie Nervanche;;;non;
Neskard;Camille;Menuiserie Gravelune;;animation;non;
Tolveron;Manon;Brasserie Belvarre;;animation;non;
Voskoubre;Nathalie;Fonderie Kalvenne;;;non;
Pruvière;Sophie;Ateliers Rossolde;;;non;
Obrichon;Paul;Ateliers Rossolde;;;non;
Abrevançon;Fatima;Tannerie Nervanche;;animation;non;
Eskière;Nathalie;Fonderie Xandrelle;;;non;
Ivreron;Caroline;Imprimerie Falvigne;;;non;
Neskoubre;Véronique;Fonderie Estrolle;;;non;
Urvivet;Anne;Fonderie Kalvenne;;;non;
Hulvançon;Olivier;Menuiserie Estrolle;;;non;
Clovançon;Karim;Fabrique Sorbagne;;;non;
Quilmotte;Zoé;Ateliers Aubrenc;;;non;
Eskouze;Félix;Filature Zelvaine;;;non;
Rozvichon;Amina;Verrerie Olbrecq;;;non;
Fralvière;Simon;Comptoir Sorbagne;;;non;
Vandergrolcquesmirtavelle-Ostrovaubelindigues;Marc;Tannerie Aubrenc;;animation;non;
Hulvière;Pierre;Papeterie Hostrenne;;;non;
Jusvault;Raphaël;Fabrique Sorbagne;;;non;
Dulvivet;Guillaume;Filature Olbrecq;;animation;non;
Fralvoubre;Pierre;Imprimerie Falvigne;;;non;
Selveux;Hélène;Brasserie Lavorneau;;;non;
Rozvançon;Caroline;Brasserie Belvarre;;;non;
Korvière;Olivier;Ateliers Falvigne;;;non;
Ivrichon;Nicolas;Fabrique Sorbagne;;;non;
Korvumet;Félix;Imprimerie Yzorne;;;non;
Voskande;Félix;Ateliers Falvigne;;animation;non;
Selvumet;Guillaume;Papeterie Estrolle;;;non;
Urvotte;Michel;Verrerie Ivoranche;;;non;
Eskault;Émilie;Coopérative Jolvane;;;non;
Bralmeval;Hélène;Brasserie Dravenne;;;non;
Hulvesson;Guillaume;Comptoir Jolvane;;;non;
Jusvoubre;Caroline;Fabrique Sorbagne;;;non;
Luskeux;Félix;Tannerie Belvarre;;;non;
Pruvouze;Antoine;Imprimerie Yzorne;;;non;
Quilmignac;Julie;Menuiserie Jolvane;;;non;
Tolvançon;Étienne;Menuiserie Estrolle;;animation;non;
Voskumet;Hugo;Ateliers Tavrenne;;;non;
Quilmumet;Manon;Fonderie Sorbagne;;;non;
Voskouze;Marc;Fonderie Estrolle;;;non;
Abrevard;Samuel;Filature Dravenne;;;non;
Luskichon;Guillaume;Brasserie Belvarre;;animation;non;
Clovesson;Manon;Fabrique Sorbagne;;;non;
Hulvumet;Simon;Ateliers Ivoranche;;;non;
Voskeron;Lucie;Verrerie Ivoranche;;;non;
Selvignac;Thomas;Brasserie Sorbagne;;animation;non;
Neskeron;Jean;Filature Zelvaine;;;non;
Pruvivet;Manon;Menuiserie Aubrenc;;;non;
Clovard;Sophie;Brasserie Belvarre;;;non;
Gorzeval;Étienne;Papeterie Ulvardes;;;non;
Ivroubre;Philippe;Ateliers Tavrenne;;animation;non;
Luskault;Gabriel;Verrerie Gravelune;;;non;
Korvard;Émilie;Verrerie Vaubrenne;;;non;
Quilmichon;Thomas;Fonderie Estrolle;;;non;
Eskançon;Alexandre;Menuiserie Hostrenne;;;non;
Luskançon;Émilie;Ateliers Tavrenne;;;non;
Dulvault;Marc;Verrerie Ivoranche;;;non;
Rozveval;Hélène;Comptoir Ivoranche;;;non;
Pruvançon;Julie;Imprimerie Yzorne;;;non;
Mirveron;Louis;Filature Wassorel;;;non;
Eskard;François;Fonderie Xandrelle;;;non;
Bralmouze;Hugo;Tannerie Falvigne;;animation;non;
Ivrançon;Louis;Coopérative Morvalle;;;non;
Clovivet;Karim;Fonderie Kalvenne;;;non;
Rozvande;Léa;Verrerie Olbrecq;;;non;
Hulvouze;Amina;Coopérative Dravenne;;;non;
Eskesson;Étienne;Menuiserie Ulvardes;;;non;
Abreveval;Philippe;Ateliers Falvigne;;;non;
Quilmeux;Émilie;Ateliers Falvigne;;;non;
Ivreval;Gabriel;Verrerie Vaubrenne;;animation;non;
Voskotte;Simon;Fabrique Ivoranche;;;non;
Obrignac;Chantal;Imprimerie Yzorne;;;non;
Eskotte;Guillaume;Verrerie Nervanche;;;non;
Obrard;Léa;Comptoir Sorbagne;;;non;
Gorzivet;Guillaume;Fonderie Estrolle;;;non;
Mirvançon;Geneviève;Brasserie Belvarre;;;non;
Selvotte;Julie;Comptoir Xandrelle;;;non;
Mirvesson;Amina;Brasserie Sorbagne;;;non;
Luskignac;Isabelle;Papeterie Estrolle;;;non;
Obrumet;Chloé;Comptoir Yzorne;;;non;
Abrevesson;Anne;Fonderie Estrolle;;;non;
Rozvumet;Fatima;Menuiserie Hostrenne;;;non;
Mirvoubre;Guillaume;Comptoir Sorbagne;;;non;
Rozvoubre;Véronique;Fonderie Xandrelle;;;non;
Gorzotte;Claire;Menuiserie Olbrecq;;;non;
Ivrière;Antoine;Fabrique Sorbagne;;;non;
Selvichon;Isabelle;Tannerie Nervanche;;;non;
Urvault;Isabelle;Comptoir Yzorne;;;non;
Voskesson;Julie;Brasserie Belvarre;;;non;
Abrevotte;Chantal;Ateliers Ivoranche;;;non;
Neskotte;Amina;Fabrique Kalvenne;;;non;
Voskançon;Étienne;Menuiserie Olbrecq;;;non;
Fralveron;Zoé;Menuiserie Sorbagne;;;non;
Rozvouze;Marc;Ateliers Estrolle;;;non;
Abreveux;Sylvie;Comptoir Jolvane;;;non;
Pruvignac;Karim;Menuiserie Estrolle;;;non;
Mirveval;Zoé;Fabrique Kalvenne;;;non;
Fralvignac;Philippe;Menuiserie Estrolle;;;non;
Gorzeux;Gabriel;Menuiserie Ulvardes;;;non;
Urveval;Jean;Menuiserie Quimvaux;;;non;
1 nom prenom appartenance courriel titre_pressenti exclu notes
2 Mirvault Isabelle Comptoir Gravelune non
3 Neskière Youssef Verrerie Vaubrenne animation non
4 Abrevande Félix Imprimerie Yzorne non
5 Fralvançon Hugo Brasserie Wassorel non
6 Fralveux Manon Menuiserie Estrolle non
7 Luskotte Catherine Fonderie Estrolle non
8 WAMMERWOLM-MAWWEMMEL Zoé Menuiserie Ulvardes non
9 Neskichon Louise Ateliers Rossolde animation non
10 Urvesson Catherine Fabrique Ulvardes non
11 Hulvivet Karim Fonderie Xandrelle non
12 Selveron Caroline Ateliers Cassorgue non
13 Korvault Catherine Tannerie Nervanche non
14 Fralvotte Caroline Fonderie Ivoranche non
15 Jusvesson Geneviève Tannerie Belvarre non
16 Abrevumet Paul Ateliers Aubrenc non
17 Fralvault Léa Fonderie Sorbagne non
18 Obrivet Fatima Fabrique Sorbagne non
19 Voskeval Philippe Menuiserie Estrolle animation non
20 Dulveval Zoé Menuiserie Quimvaux non
21 Korveux Paul Filature Olbrecq non
22 Luskière Julie Fabrique Sorbagne non
23 Clovande Émilie Ateliers Falvigne non
24 Hulvande Paul Verrerie Vaubrenne non
25 Fralvesson Hugo Filature Wassorel non
26 Urvière Isabelle Brasserie Belvarre non
27 Rozvotte Thomas Verrerie Ivoranche non
28 Bralmumet Isabelle Verrerie Aubrenc non
29 Dulvouze Catherine Ateliers Rossolde non
30 Voskichon Marc Menuiserie Estrolle non
31 Mirvivet Simon Ateliers Ivoranche non
32 Clovault Louise Imprimerie Yzorne non
33 Voskeux Lucie Coopérative Morvalle non
34 Eskignac Amina Brasserie Xandrelle non
35 Korvouze Fatima Ateliers Tavrenne animation non
36 Selvoubre Luc Brasserie Sorbagne non
37 Fralvichon Étienne Papeterie Ulvardes non
38 Voskivet Philippe Ateliers Tavrenne non
39 Quilmançon Louise Menuiserie Estrolle non
40 Luskande Sophie Fabrique Morvalle non
41 Abreveron Pierre Ateliers Rossolde non
42 Neskeux Philippe Comptoir Xandrelle non
43 Neskançon Amina Fabrique Ulvardes non
44 Tolvande Léa Ateliers Rossolde non
45 Quilmeron Antoine Fabrique Kalvenne non
46 Clovumet Guillaume Verrerie Pravette non
47 Eskeux Michel Papeterie Wassorel non
48 Hulvault Louise Coopérative Jolvane non
49 Hulveron Catherine Coopérative Rossolde animation non
50 Ivrivet François Menuiserie Sorbagne animation non
51 Eskumet Samuel Verrerie Ivoranche non
52 Fralvivet Claire Imprimerie Yzorne non
53 Mirveux Léa Menuiserie Wassorel non
54 Gorzesson Karim Papeterie Ulvardes non
55 Fralvumet Jean Ateliers Falvigne animation non
56 Urvançon Manon Ateliers Tavrenne animation non
57 Quilmande Sylvie Imprimerie Yzorne non
58 Gorzichon François Ateliers Estrolle non
59 Pruvesson Amina Fonderie Sorbagne non
60 Quilmière Lucie Fabrique Ivoranche non
61 Korvesson Fatima Ateliers Rossolde non
62 Selvesson Nicolas Imprimerie Yzorne non
63 Selvivet Louis Menuiserie Hostrenne animation non
64 Neskesson Louise Tannerie Belvarre non
65 Tolvesson Lucie Filature Zelvaine non
66 Quilmesson Olivier Comptoir Gravelune non
67 Luskivet Guillaume Brasserie Lavorneau non
68 Neskault Gabriel Menuiserie Tavrenne non
69 Urvumet Antoine Menuiserie Hostrenne non
70 Jusvançon Olivier Brasserie Morvalle non
71 Dulvançon Simon Menuiserie Ivoranche non
72 Gorzard Chloé Fabrique Sorbagne non
73 Pruveux Gabriel Papeterie Estrolle non
74 Clovière Manon Ateliers Ivoranche non
75 Obrouze François Tannerie Olbrecq animation non
76 Tolveval Karim Tannerie Belvarre non
77 Dulvumet Geneviève Menuiserie Quimvaux animation non
78 Bérêçàuvïlle-Dœûmôntëÿ Hugo Ateliers Falvigne non
79 Gorzeron Claire Menuiserie Hostrenne non
80 Luskumet Hélène Brasserie Xandrelle non
81 Fralvande Louis Ateliers Rossolde non
82 Selvault Émilie Fabrique Sorbagne non
83 Neskumet Léa Verrerie Aubrenc non
84 Korvichon Isabelle Menuiserie Ulvardes non
85 Obrande Michel Menuiserie Estrolle non
86 Luskeval Isabelle Brasserie Wassorel non
87 Tolvoubre Fatima Papeterie Ulvardes non
88 Gorzière Zoé Menuiserie Olbrecq non
89 Korvignac Sylvie Fabrique Ulvardes non
90 Obreval Antoine Ateliers Cassorgue non
91 Korveron Michel Fonderie Sorbagne animation non
92 Tolvumet Camille Fonderie Estrolle non
93 Dulvesson Marie Ateliers Cassorgue non
94 Jusvard Youssef Tannerie Aubrenc animation non
95 Tolvignac Guillaume Menuiserie Sorbagne non
96 Mirvotte Gabriel Tannerie Belvarre non
97 Urvard Marie Filature Wassorel animation non
98 Bralmesson Marie Coopérative Dravenne animation non
99 Neskignac Paul Brasserie Sorbagne non
100 Bralmeux Samuel Ateliers Rossolde animation non
101 Ivrotte Raphaël Coopérative Rossolde non
102 Luskouze Marc Ateliers Rossolde non
103 Jusvotte Sylvie Brasserie Belvarre non
104 Bralmoubre Véronique Fonderie Xandrelle non
105 Selvard Pierre Verrerie Vaubrenne non
106 Hulveux Marie Coopérative Rossolde non
107 Pruvotte Sophie Imprimerie Yzorne animation non
108 Fralveval Karim Coopérative Gravelune non
109 Gorzignac Philippe Imprimerie Falvigne non
110 Selvouze Étienne Ateliers Tavrenne non
111 Hulvichon Louis Ateliers Falvigne non
112 Urvoubre Sylvie Verrerie Vaubrenne non
113 Pruveval Pierre Comptoir Xandrelle non
114 Hulvotte Guillaume Verrerie Vaubrenne non
115 Abrevignac Geneviève Brasserie Morvalle non
116 Abrevault Gabriel Ateliers Rossolde animation non
117 Bralmignac Amina Fonderie Estrolle non
118 Bralmançon Olivier Comptoir Lavorneau non
119 Luskoubre Marc Verrerie Vaubrenne non
120 Abrevoubre Geneviève Menuiserie Gravelune non
121 Korvoubre Raphaël Verrerie Vaubrenne non
122 Mirvard Pierre Verrerie Ivoranche non
123 Jusvumet Camille Fonderie Estrolle non
124 Cloveval Samuel Menuiserie Ulvardes non
125 Selvière Antoine Coopérative Dravenne non
126 Jusvouze Véronique Verrerie Vaubrenne non
127 Neskivet Paul Comptoir Jolvane non
128 Dulvande Samuel Verrerie Pravette non
129 Bralmault Antoine Fabrique Sorbagne animation non
130 Abrevière Émilie Imprimerie Yzorne animation non
131 Selveval Lucie Filature Zelvaine non
132 Tolvichon Olivier Fonderie Xandrelle non
133 Hulvard Fatima Ateliers Estrolle non
134 Ivreux Alexandre Tannerie Aubrenc non
135 Korveval Félix Tannerie Nervanche non
136 Urvichon Karim Tannerie Nervanche non
137 Dulvichon Émilie Menuiserie Ulvardes non
138 Tolvouze Luc Fabrique Ivoranche non
139 Jusvivet Claire Imprimerie Yzorne animation non
140 Quilmault Amina Coopérative Jolvane non
141 Ivrouze François Fonderie Aubrenc non
142 Gorzumet Hélène Fonderie Aubrenc non
143 Ivrault Sophie Coopérative Morvalle non
144 Bralmière Louise Tannerie Quimvaux non
145 Korvande Nicolas Comptoir Sorbagne non
146 Ivrumet Geneviève Coopérative Jolvane non
147 Obresson Geneviève Coopérative Gravelune non
148 Obrançon Guillaume Papeterie Hostrenne non
149 Urvignac Fatima Ateliers Aubrenc non
150 Bralmotte Claire Ateliers Tavrenne non
151 Hulvignac Félix Imprimerie Hostrenne non
152 Clovotte François Coopérative Rossolde non
153 Dulvoubre Philippe Brasserie Belvarre non
154 Rozveron Samuel Fonderie Sorbagne non
155 Hulvoubre Pierre Menuiserie Olbrecq non
156 Urvouze Émilie Comptoir Gravelune non
157 Korvançon Félix Fonderie Ivoranche non
158 Neskande Émilie Coopérative Gravelune non
159 Mirvumet Chantal Menuiserie Gravelune non
160 Korvotte Alexandre Menuiserie Nervanche non
161 Eskeval Zoé Tannerie Nervanche non
162 Neskard Camille Menuiserie Gravelune animation non
163 Tolveron Manon Brasserie Belvarre animation non
164 Voskoubre Nathalie Fonderie Kalvenne non
165 Pruvière Sophie Ateliers Rossolde non
166 Obrichon Paul Ateliers Rossolde non
167 Abrevançon Fatima Tannerie Nervanche animation non
168 Eskière Nathalie Fonderie Xandrelle non
169 Ivreron Caroline Imprimerie Falvigne non
170 Neskoubre Véronique Fonderie Estrolle non
171 Urvivet Anne Fonderie Kalvenne non
172 Hulvançon Olivier Menuiserie Estrolle non
173 Clovançon Karim Fabrique Sorbagne non
174 Quilmotte Zoé Ateliers Aubrenc non
175 Eskouze Félix Filature Zelvaine non
176 Rozvichon Amina Verrerie Olbrecq non
177 Fralvière Simon Comptoir Sorbagne non
178 Vandergrolcquesmirtavelle-Ostrovaubelindigues Marc Tannerie Aubrenc animation non
179 Hulvière Pierre Papeterie Hostrenne non
180 Jusvault Raphaël Fabrique Sorbagne non
181 Dulvivet Guillaume Filature Olbrecq animation non
182 Fralvoubre Pierre Imprimerie Falvigne non
183 Selveux Hélène Brasserie Lavorneau non
184 Rozvançon Caroline Brasserie Belvarre non
185 Korvière Olivier Ateliers Falvigne non
186 Ivrichon Nicolas Fabrique Sorbagne non
187 Korvumet Félix Imprimerie Yzorne non
188 Voskande Félix Ateliers Falvigne animation non
189 Selvumet Guillaume Papeterie Estrolle non
190 Urvotte Michel Verrerie Ivoranche non
191 Eskault Émilie Coopérative Jolvane non
192 Bralmeval Hélène Brasserie Dravenne non
193 Hulvesson Guillaume Comptoir Jolvane non
194 Jusvoubre Caroline Fabrique Sorbagne non
195 Luskeux Félix Tannerie Belvarre non
196 Pruvouze Antoine Imprimerie Yzorne non
197 Quilmignac Julie Menuiserie Jolvane non
198 Tolvançon Étienne Menuiserie Estrolle animation non
199 Voskumet Hugo Ateliers Tavrenne non
200 Quilmumet Manon Fonderie Sorbagne non
201 Voskouze Marc Fonderie Estrolle non
202 Abrevard Samuel Filature Dravenne non
203 Luskichon Guillaume Brasserie Belvarre animation non
204 Clovesson Manon Fabrique Sorbagne non
205 Hulvumet Simon Ateliers Ivoranche non
206 Voskeron Lucie Verrerie Ivoranche non
207 Selvignac Thomas Brasserie Sorbagne animation non
208 Neskeron Jean Filature Zelvaine non
209 Pruvivet Manon Menuiserie Aubrenc non
210 Clovard Sophie Brasserie Belvarre non
211 Gorzeval Étienne Papeterie Ulvardes non
212 Ivroubre Philippe Ateliers Tavrenne animation non
213 Luskault Gabriel Verrerie Gravelune non
214 Korvard Émilie Verrerie Vaubrenne non
215 Quilmichon Thomas Fonderie Estrolle non
216 Eskançon Alexandre Menuiserie Hostrenne non
217 Luskançon Émilie Ateliers Tavrenne non
218 Dulvault Marc Verrerie Ivoranche non
219 Rozveval Hélène Comptoir Ivoranche non
220 Pruvançon Julie Imprimerie Yzorne non
221 Mirveron Louis Filature Wassorel non
222 Eskard François Fonderie Xandrelle non
223 Bralmouze Hugo Tannerie Falvigne animation non
224 Ivrançon Louis Coopérative Morvalle non
225 Clovivet Karim Fonderie Kalvenne non
226 Rozvande Léa Verrerie Olbrecq non
227 Hulvouze Amina Coopérative Dravenne non
228 Eskesson Étienne Menuiserie Ulvardes non
229 Abreveval Philippe Ateliers Falvigne non
230 Quilmeux Émilie Ateliers Falvigne non
231 Ivreval Gabriel Verrerie Vaubrenne animation non
232 Voskotte Simon Fabrique Ivoranche non
233 Obrignac Chantal Imprimerie Yzorne non
234 Eskotte Guillaume Verrerie Nervanche non
235 Obrard Léa Comptoir Sorbagne non
236 Gorzivet Guillaume Fonderie Estrolle non
237 Mirvançon Geneviève Brasserie Belvarre non
238 Selvotte Julie Comptoir Xandrelle non
239 Mirvesson Amina Brasserie Sorbagne non
240 Luskignac Isabelle Papeterie Estrolle non
241 Obrumet Chloé Comptoir Yzorne non
242 Abrevesson Anne Fonderie Estrolle non
243 Rozvumet Fatima Menuiserie Hostrenne non
244 Mirvoubre Guillaume Comptoir Sorbagne non
245 Rozvoubre Véronique Fonderie Xandrelle non
246 Gorzotte Claire Menuiserie Olbrecq non
247 Ivrière Antoine Fabrique Sorbagne non
248 Selvichon Isabelle Tannerie Nervanche non
249 Urvault Isabelle Comptoir Yzorne non
250 Voskesson Julie Brasserie Belvarre non
251 Abrevotte Chantal Ateliers Ivoranche non
252 Neskotte Amina Fabrique Kalvenne non
253 Voskançon Étienne Menuiserie Olbrecq non
254 Fralveron Zoé Menuiserie Sorbagne non
255 Rozvouze Marc Ateliers Estrolle non
256 Abreveux Sylvie Comptoir Jolvane non
257 Pruvignac Karim Menuiserie Estrolle non
258 Mirveval Zoé Fabrique Kalvenne non
259 Fralvignac Philippe Menuiserie Estrolle non
260 Gorzeux Gabriel Menuiserie Ulvardes non
261 Urveval Jean Menuiserie Quimvaux non

View file

@ -0,0 +1,13 @@
nom;prenom;appartenance;courriel;titre_pressenti;exclu;notes
Bralmumet;Caroline;Ateliers Jolvane;;;non;
Neskault;Hugo;Tannerie Cassorgue;;;non;
Ivrotte;Youssef;Verrerie Ulvardes;;;non;
Selvière;Louis;Tannerie Cassorgue;;;non;
Selvançon;Youssef;Ateliers Jolvane;;;non;
Quilmoubre;Amina;Verrerie Ulvardes;;;non;
Voskotte;Julie;Verrerie Ulvardes;;;non;
Neskivet;Paul;Ateliers Jolvane;;;non;
Bralmignac;Michel;Tannerie Cassorgue;;;non;
Tolveval;Zoé;Verrerie Ulvardes;;;non;
Urvard;Luc;Tannerie Cassorgue;;;non;
Mirveval;Véronique;Ateliers Jolvane;;;non;
1 nom prenom appartenance courriel titre_pressenti exclu notes
2 Bralmumet Caroline Ateliers Jolvane non
3 Neskault Hugo Tannerie Cassorgue non
4 Ivrotte Youssef Verrerie Ulvardes non
5 Selvière Louis Tannerie Cassorgue non
6 Selvançon Youssef Ateliers Jolvane non
7 Quilmoubre Amina Verrerie Ulvardes non
8 Voskotte Julie Verrerie Ulvardes non
9 Neskivet Paul Ateliers Jolvane non
10 Bralmignac Michel Tannerie Cassorgue non
11 Tolveval Zoé Verrerie Ulvardes non
12 Urvard Luc Tannerie Cassorgue non
13 Mirveval Véronique Ateliers Jolvane non

View file

@ -0,0 +1,351 @@
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Les fichiers livrés que le catalogue des démonstrations détermine (§ 10.3,
// § 15.5) : le fichier d'état de chaque démonstration, sous
// src/demo/livrees/ ; l'export CSV de la liste des participants de la petite
// et de la grande, et la variante windows-1252 du CSV des cas limites, sous
// exemples/. Le CSV des cas limites s'écrit à la main : le script le lit et
// ne l'écrit jamais.
//
// node scripts/engendrer_livrables.js [destination]
//
// La destination, la racine du projet par défaut, reçoit les sept fichiers
// sous leurs chemins relatifs, et rien n'est écrit ailleurs ; un chemin
// relatif se lit depuis le répertoire courant. Tout se calcule avant la
// première écriture. Le fichier livré est la source de vérité (§ 15.5,
// point 5) : src/demo/livrees.long.test.js rejoue ce script et compare
// l'empreinte de chaque charge à celle du fichier livré. Lancer le script
// est donc le geste conscient qui change une démonstration : il imprime,
// pour chaque fichier, s'il est créé, inchangé ou réécrit, et l'empreinte de
// la charge de chaque fichier d'état. Ni horloge ni tirage n'y entrent.
//
// Une démonstration livrée est la configuration de son entrée du catalogue,
// mise en charge (§ 4, § 8.8) :
// - l'événement demo:<clé>, au nom du catalogue, sans date, en brouillon et
// sans filiation ;
// - les sièges par défaut : la capacité la plus fréquente des tables, la
// plus grande à égalité ; une table d'une autre capacité porte la sienne
// en surcharge (§ 6.1) ;
// - les tables rondes, rangées en grille dans l'ordre de la configuration,
// rangée par rangée, leurs centres espacés de 300 cm ; la grille a la plus
// petite largeur dont le carré contient toutes les tables, et seule sa
// dernière rangée est incomplète ;
// - les contraintes de la configuration, les sièges non attribués, la
// génération par défaut de la recherche ;
// - chaque réservation, celle d'un animateur, au siège 1 de sa table pour
// tous les tours, ce siège titré « animation », et le titre pressenti
// « animation » porté par l'animateur ;
// - aucune proposition : le placement n'est pas figé (§ 15.5, point 6).
// Son fichier d'état s'écrit à la révision 1, sans journal, sous la version
// de la construction qui écrit (§ 18.6).
import { createHash } from 'node:crypto';
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { decoder } from '../src/csv/encodage.js';
import { SEPARATEUR, ecrireCsv, exporterParticipants } from '../src/csv/export.js';
import { decouper } from '../src/csv/lecture.js';
import { CATALOGUE } from '../src/demo/catalogue.js';
import { serialiser, serialiserCharge } from '../src/stockage/canonique.js';
import { analyser, creerCharge } from '../src/stockage/document.js';
import { VERSION } from '../src/version.genere.js';
const RACINE = resolve(fileURLToPath(new URL('..', import.meta.url)));
// Chemins des livrables, relatifs à la racine, séparés par « / ».
const DOSSIER_LIVREES = 'src/demo/livrees';
const CAS_LIMITES = 'exemples/participants_cas_limites.csv';
const CAS_LIMITES_CP1252 = 'exemples/participants_cas_limites_cp1252.csv';
const csvDeDemonstration = (cle) => `exemples/participants_demo_${cle}.csv`;
// Démonstrations dont la liste des participants est livrée en CSV, dans
// l'ordre du § 10.3.
const DEMONSTRATIONS_EN_CSV = ['petite', 'grande'];
// Séparateur de la variante windows-1252 (§ 10.3).
const VIRGULE = ',';
// Écart entre les centres de deux tables voisines de la grille, en cm.
const ECART_CM = 300;
// Le siège de l'animateur, et le titre qu'il porte.
const SIEGE_ANIMATEUR = 1;
const ANIMATION = 'animation';
const USAGE = 'usage : node scripts/engendrer_livrables.js [destination]';
// Capacité la plus fréquente parmi les tables, la plus grande à égalité ; le
// résultat ne dépend pas de l'ordre des tables.
function capaciteParDefaut(tables) {
const effectifs = new Map();
for (const { capacite } of tables) effectifs.set(capacite, (effectifs.get(capacite) ?? 0) + 1);
let defaut = null;
for (const [capacite, effectif] of effectifs) {
const effectifDuDefaut = effectifs.get(defaut) ?? 0;
if (effectif > effectifDuDefaut || (effectif === effectifDuDefaut && capacite > defaut)) defaut = capacite;
}
return defaut;
}
// Largeur de la grille de nombre tables : la plus petite largeur c telle
// que c × c ≥ nombre.
function colonnesDe(nombre) {
let colonnes = 1;
while (colonnes * colonnes < nombre) colonnes += 1;
return colonnes;
}
// Plus grand identifiant d'une liste d'enregistrements ; 0 pour une liste
// vide.
const plusGrandId = (enregistrements) => enregistrements.reduce((plus, { id }) => Math.max(plus, id), 0);
// Animateur de chaque table réservée, par identifiant de table, dans l'ordre
// des réservations. Lève quand une réservation ne vaut pas pour tous les
// tours, ou qu'une table en reçoit deux : le siège 1 ne porte qu'un
// animateur, et le script n'écrit que ce que le catalogue produit.
function animateursDe(reservations, cle) {
const animateurs = new Map();
for (const { participant, table, portee } of reservations) {
if (portee !== 'tous' || animateurs.has(table)) {
throw new Error(
`démonstration ${cle} : la table ${table} reçoit au plus un animateur, réservé pour tous les tours`,
);
}
animateurs.set(table, participant);
}
return animateurs;
}
/**
* Charge de la démonstration d'une entrée du catalogue, telle que l'en-tête
* du module la décrit. La configuration est construite une fois ; la charge
* rendue est faite d'objets neufs.
*
* @param {{cle: string, nom: string, construire: () => import('../src/moteur/types.js').Configuration}} entree
* @returns {import('../src/stockage/types.js').Charge}
* @throws {Error} quand une réservation ne vaut pas pour tous les tours, ou
* qu'une table en reçoit deux
*/
export function chargeDeDemonstration({ cle, nom, construire }) {
const { participants, tables, tours, reservations, contraintes } = construire();
const siegesParDefaut = capaciteParDefaut(tables);
const neuve = creerCharge({ id: `demo:${cle}`, nom, date: null, siegesParDefaut, tours });
const animateurs = animateursDe(reservations, cle);
const reserves = new Set(animateurs.values());
const colonnes = colonnesDe(tables.length);
const { separerAppartenances, nouveauxVoisins, nouvelleTable, varierAppartenances } = contraintes;
return {
...neuve,
reglages: { ...neuve.reglages, separerAppartenances, nouveauxVoisins, nouvelleTable, varierAppartenances },
prochainsIds: { participant: plusGrandId(participants) + 1, table: plusGrandId(tables) + 1, proposition: 1 },
participants: participants.map((participant) => ({
id: participant.id,
nom: participant.nom,
prenom: participant.prenom ?? null,
appartenance: participant.appartenance,
courriel: null,
titrePressenti: reserves.has(participant.id) ? ANIMATION : null,
notes: null,
exclu: participant.exclu === true,
})),
tables: tables.map(({ id, numero, capacite }, rang) => ({
id,
numero,
sieges: capacite === siegesParDefaut ? null : capacite,
forme: 'ronde',
position: { x: (rang % colonnes) * ECART_CM, y: Math.floor(rang / colonnes) * ECART_CM },
})),
reservations: [...animateurs].map(([table, participant]) => ({
participant,
table,
siege: SIEGE_ANIMATEUR,
portee: 'tous',
tour: null,
})),
titres: [...animateurs.keys()].map((table) => ({ table, siege: SIEGE_ANIMATEUR, libelle: ANIMATION })),
};
}
/**
* Texte du fichier d'état livré d'une entrée du catalogue : sa charge, en
* forme canonique, à la révision 1, sous la version de la construction qui
* écrit.
*
* @param {{cle: string, nom: string, construire: Function}} entree
* @param {{produitVersion?: string}} [options] version affichée de la
* construction, VERSION.affichee par défaut
* @returns {string}
*/
export function texteDeDemonstration(entree, { produitVersion = VERSION.affichee } = {}) {
return serialiser(chargeDeDemonstration(entree), { revision: 1, produitVersion });
}
/**
* Empreinte d'une charge (§ 15.5, point 5) : SHA-256 de son texte canonique
* seul, en 64 chiffres hexadécimaux minuscules. L'en-tête n'y entre pas : la
* version de la construction qui écrit ne la change pas.
*
* @param {import('../src/stockage/types.js').Charge} charge
* @returns {string}
*/
export function empreinte(charge) {
return createHash('sha256').update(serialiserCharge(charge)).digest('hex');
}
// Octet windows-1252 de chaque caractère que ce codage représente, lu dans
// le décodeur windows-1252 lui-même, octet par octet : l'encodage en est
// l'inverse exact. 00 à 7F donnent l'ASCII, A0 à FF les mêmes points de
// code, 80 à 9F les signes propres à windows-1252, dont « € », « œ » et
// l'apostrophe typographique.
const DECODEUR_1252 = new TextDecoder('windows-1252');
const OCTET_1252 = new Map(
Array.from({ length: 256 }, (_, octet) => [DECODEUR_1252.decode(Uint8Array.of(octet)), octet]),
);
/**
* Octets windows-1252 d'un texte, un par caractère, sans marque.
*
* @param {string} texte
* @returns {Uint8Array}
* @throws {RangeError} pour un caractère que windows-1252 ne représente pas,
* nommé par son point de code et son rang dans le texte, en unités UTF-16
*/
export function enWindows1252(texte) {
const octets = [];
let rang = 0;
for (const caractere of texte) {
const octet = OCTET_1252.get(caractere);
if (octet === undefined) {
const point = caractere.codePointAt(0).toString(16).toUpperCase().padStart(4, '0');
throw new RangeError(`U+${point}, au rang ${rang} : caractère absent de windows-1252`);
}
octets.push(octet);
rang += caractere.length;
}
return Uint8Array.from(octets);
}
/**
* Variante windows-1252 du CSV des cas limites (§ 10.3) : ses
* enregistrements, découpés sous « ; », réécrits sous « , » par l'écriture
* unique des CSV, puis encodés en windows-1252, sans marque. Chaque champ
* garde son texte ; un champ qui porte la virgule, un guillemet ou une fin
* de ligne s'écrit cité.
*
* @param {Uint8Array} octets le fichier écrit à la main, que decoder lit
* @returns {Uint8Array}
* @throws {Error} quand un guillemet reste ouvert à la fin du fichier
*/
export function varianteWindows1252(octets) {
const { enregistrements, guillemetOuvert } = decouper(decoder(octets).texte, SEPARATEUR);
if (guillemetOuvert) throw new Error(`${CAS_LIMITES} : un guillemet reste ouvert à la fin du fichier`);
// ecrireCsv écrit de l'UTF-8 avec marque, que le décodeur UTF-8 retire.
return enWindows1252(new TextDecoder().decode(ecrireCsv(enregistrements, VIRGULE)));
}
/**
* Les sept livrables, calculés sans rien écrire : { chemin, octets,
* empreinte } ; le chemin est relatif à la racine, séparé par « / » ;
* l'empreinte, celle de la charge d'un fichier d'état, null pour un CSV. Dans
* l'ordre : le fichier d'état de chaque démonstration, dans l'ordre du
* catalogue ; l'export de la liste des participants de la petite, puis de la
* grande, chacun lu dans le texte du fichier d'état de sa démonstration ; la
* variante windows-1252 du CSV des cas limites.
*
* @param {{casLimites: Uint8Array, produitVersion?: string}} options
* casLimites : les octets du CSV des cas limites, écrit à la main
* @returns {Array<{chemin: string, octets: Uint8Array, empreinte: string|null}>}
*/
export function livrables({ casLimites, produitVersion = VERSION.affichee }) {
const etats = CATALOGUE.map((entree) => {
const texte = texteDeDemonstration(entree, { produitVersion });
return { cle: entree.cle, texte, charge: analyser(texte).charge };
});
const fichiers = etats.map(({ cle, texte, charge }) => ({
chemin: `${DOSSIER_LIVREES}/${cle}.gtt.json`,
octets: new TextEncoder().encode(texte),
empreinte: empreinte(charge),
}));
for (const cle of DEMONSTRATIONS_EN_CSV) {
const etat = etats.find((candidat) => candidat.cle === cle);
if (etat === undefined) throw new Error(`aucune démonstration ${cle} au catalogue`);
fichiers.push({
chemin: csvDeDemonstration(cle),
octets: exporterParticipants(etat.charge.participants),
empreinte: null,
});
}
fichiers.push({ chemin: CAS_LIMITES_CP1252, octets: varianteWindows1252(casLimites), empreinte: null });
return fichiers;
}
// Octets d'un fichier, ou null quand il n'existe pas.
function lireSiPresent(chemin) {
try {
return readFileSync(chemin);
} catch (erreur) {
if (erreur.code === 'ENOENT') return null;
throw erreur;
}
}
/**
* Écrit les sept livrables sous destination, après les avoir tous calculés,
* et rien ailleurs. Le CSV des cas limites se lit sous source. Rend chaque
* livrable avec son état : « créé » quand le fichier n'existait pas,
* « inchangé » quand il portait déjà ces octets, « réécrit » sinon.
*
* @param {string} destination racine qui reçoit les fichiers
* @param {{source?: string, produitVersion?: string}} [options] source : la
* racine du projet par défaut
* @returns {Array<{chemin: string, octets: Uint8Array, empreinte: string|null, etat: string}>}
*/
export function ecrireLivrables(destination, { source = RACINE, produitVersion } = {}) {
const casLimites = readFileSync(join(source, ...CAS_LIMITES.split('/')));
return livrables({ casLimites, produitVersion }).map((fichier) => {
const cible = join(destination, ...fichier.chemin.split('/'));
const avant = lireSiPresent(cible);
mkdirSync(dirname(cible), { recursive: true });
writeFileSync(cible, fichier.octets);
const etat = avant === null ? 'créé' : Buffer.compare(avant, fichier.octets) === 0 ? 'inchangé' : 'réécrit';
return { ...fichier, etat };
});
}
// Ligne de commande : une destination au plus, la racine par défaut. Sort à
// 0 après l'écriture, à 1 quand un livrable ne se calcule ou ne s'écrit pas,
// à 2 sur un usage fautif.
function executer(arguments_) {
if (arguments_.length > 1 || arguments_.some((argument) => argument.startsWith('-'))) {
console.error(USAGE);
return 2;
}
const destination = arguments_.length === 1 ? resolve(arguments_[0]) : RACINE;
let ecrits;
try {
ecrits = ecrireLivrables(destination);
} catch (erreur) {
console.error(`Livrables : ${erreur.message}`);
return 1;
}
console.log(`Livrables écrits sous ${destination}, construction ${VERSION.affichee} :`);
for (const { chemin, octets, empreinte: hachage, etat } of ecrits) {
const suite = hachage === null ? '' : `, empreinte de la charge ${hachage}`;
console.log(` ${etat.padEnd(9)}${chemin} (${octets.length} octets${suite})`);
}
return 0;
}
// Vrai quand ce fichier est le script que Node a lancé, et non un module
// importé.
function estLanceDirectement() {
if (process.argv[1] === undefined) return false;
try {
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
} catch {
return false;
}
}
if (estLanceDirectement()) process.exitCode = executer(process.argv.slice(2));

View file

@ -0,0 +1,491 @@
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Épreuves du script qui engendre les fichiers livrés (§ 10.3, § 15.5) : la
// charge de chaque démonstration — la configuration du catalogue,
// l'événement demo:<clé>, la salle en grille, les animateurs au siège 1 —,
// le fichier d'état qu'il en écrit, la variante windows-1252 du CSV des cas
// limites, et la ligne de commande, qui écrit dans le dossier donné et nulle
// part ailleurs, et réécrit les mêmes octets quand on la relance. « Nulle
// part ailleurs » s'éprouve sur une copie du projet, photographiée avant et
// après chaque lancement : tout fichier écrit hors des sept livrables s'y
// voit, le CSV des cas limites, que le script lit sans l'écrire, comme les
// autres. Le contenu des fichiers livrés eux-mêmes s'éprouve à côté d'eux,
// dans src/demo/livrees.test.js et src/demo/livrees.long.test.js.
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { cpSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, utimesSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, relative, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { exporterParticipants } from '../src/csv/export.js';
import { decouper } from '../src/csv/lecture.js';
import { CATALOGUE } from '../src/demo/catalogue.js';
import { serialiser } from '../src/stockage/canonique.js';
import { GENERATION_PAR_DEFAUT, analyser, configurationDepuisCharge } from '../src/stockage/document.js';
import { VERSION } from '../src/version.genere.js';
import { describe, test } from '../test/lanceur.js';
import { versionVoisine } from '../test/version_voisine.js';
import {
chargeDeDemonstration,
enWindows1252,
livrables,
texteDeDemonstration,
varianteWindows1252,
} from './engendrer_livrables.js';
const RACINE = fileURLToPath(new URL('..', import.meta.url));
const SCRIPT = fileURLToPath(new URL('./engendrer_livrables.js', import.meta.url));
const CAS_LIMITES = join(RACINE, 'exemples', 'participants_cas_limites.csv');
// Les sept livrables que le script écrit, chemins relatifs à la racine, dans
// l'ordre où il les écrit : un fichier d'état par démonstration du
// catalogue, les deux CSV de démonstration, puis la variante windows-1252 du
// CSV des cas limites (§ 10.3).
const LIVRABLES = [
...CATALOGUE.map(({ cle }) => `src/demo/livrees/${cle}.gtt.json`),
'exemples/participants_demo_petite.csv',
'exemples/participants_demo_grande.csv',
'exemples/participants_cas_limites_cp1252.csv',
];
// Ce que le script lit, chemins relatifs à la racine du projet : package.json,
// qui déclare les modules ES, le script, le code de src/ qu'il importe et les
// CSV de exemples/, celui des cas limites compris, qu'il lit sans l'écrire.
const COPIES = ['package.json', 'scripts/engendrer_livrables.js', 'src', 'exemples'];
// Date de modification, en secondes, de chaque fichier copié : un instant
// ancien. Toute écriture ultérieure la déplace, d'octets identiques comprise,
// quelle que soit la finesse de l'horloge du système de fichiers.
const DATE_ANCIENNE = 1_000_000_000;
const MARQUE = '\u{FEFF}';
// Entrée du catalogue de clé donnée.
const entreeDe = (cle) => CATALOGUE.find((entree) => entree.cle === cle);
// Entrée d'essai : la petite démonstration, sa configuration retouchée par
// retoucher, qui reçoit une copie et la rend.
const entreeRetouchee = (retoucher) => ({
cle: 'essai',
nom: 'Essai',
construire: () => retoucher(entreeDe('petite').construire()),
});
const parTable = (a, b) => a.table - b.table;
// Dossier temporaire neuf, passé à faire, puis effacé ; rend ce que rend
// faire.
function avecDossier(faire) {
const dossier = mkdtempSync(join(tmpdir(), 'livrables-'));
try {
return faire(dossier);
} finally {
rmSync(dossier, { recursive: true, force: true });
}
}
// Fichiers d'un dossier, à toute profondeur, en chemins relatifs séparés par
// « / », triés.
const fichiersSous = (dossier) =>
readdirSync(dossier, { recursive: true, withFileTypes: true })
.filter((entree) => entree.isFile())
.map((entree) => relative(dossier, join(entree.parentPath, entree.name)).split(sep).join('/'))
.sort();
// Instantané d'un dossier : la taille et la date de modification, en
// nanosecondes, de chaque fichier à toute profondeur, sous son chemin relatif
// séparé par « / ».
const instantane = (dossier) =>
new Map(
fichiersSous(dossier).map((chemin) => {
const { size, mtimeNs } = statSync(join(dossier, chemin), { bigint: true });
return [chemin, `${size} octets, modifié à ${mtimeNs} ns`];
}),
);
// Chemins que deux instantanés distinguent, triés : un fichier créé, retiré,
// ou dont la taille ou la date de modification change. Un fichier réécrit avec
// les mêmes octets en fait partie, sa date de modification avance.
const differences = (avant, apres) =>
[...new Set([...avant.keys(), ...apres.keys()])].filter((chemin) => avant.get(chemin) !== apres.get(chemin)).sort();
// Lance le script de chemin donné depuis le répertoire cwd ; rend son statut
// et ses sorties.
const lancerScript = (script, cwd, ...arguments_) =>
spawnSync(process.execPath, [script, ...arguments_], { cwd, encoding: 'utf8' });
// Lance le script du projet depuis le répertoire cwd.
const lancer = (cwd, ...arguments_) => lancerScript(SCRIPT, cwd, ...arguments_);
// Dossier temporaire neuf qui porte, sous projet/, une copie de ce que le
// script lit, chaque fichier daté de DATE_ANCIENNE. Passe à faire ce dossier
// et lancerCopie, qui lance depuis lui le script copié ; efface le dossier ;
// rend ce que rend faire. Le script y est à la racine de son arbre, comme
// dans le projet : un fichier qu'il écrit hors de la destination tombe dans
// ce dossier, copie du projet ou répertoire courant, jamais dans le projet.
function avecProjetCopie(faire) {
return avecDossier((dossier) => {
for (const chemin of COPIES) {
cpSync(join(RACINE, chemin), join(dossier, 'projet', chemin), { recursive: true });
}
for (const chemin of fichiersSous(dossier)) utimesSync(join(dossier, chemin), DATE_ANCIENNE, DATE_ANCIENNE);
const copie = join(dossier, 'projet', 'scripts', 'engendrer_livrables.js');
return faire(dossier, (...arguments_) => lancerScript(copie, dossier, ...arguments_));
});
}
const echapper = (texte) => texte.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
describe("engendrer_livrables : la charge d'une démonstration (§ 15)", () => {
test('porte la configuration du catalogue : participants, capacités, tours, réservations, contraintes', () => {
for (const entree of CATALOGUE) {
const configuration = entree.construire();
const charge = chargeDeDemonstration(entree);
const lue = configurationDepuisCharge(charge);
assert.deepEqual(
{ ...lue, reservations: [...lue.reservations].sort(parTable) },
{
...configuration,
participants: configuration.participants.map(({ id, nom, appartenance }) => ({
id,
nom,
appartenance,
exclu: false,
})),
reservations: [...configuration.reservations].sort(parTable),
},
entree.cle,
);
assert.deepEqual(
charge.participants.map(({ prenom }) => prenom),
configuration.participants.map(({ prenom }) => prenom),
entree.cle,
);
}
});
test("l'événement demo:<clé> : nom du catalogue, sans date, en brouillon, sans filiation, les tours et le défaut de sièges de la configuration", () => {
// Sièges par défaut des quatre démonstrations (§ 15.1, § 15.3).
const defauts = new Map([['grande', 8], ['grande-sans-exception', 8], ['petite', 3], ['petite-conflit', 3]]);
assert.deepEqual(CATALOGUE.map(({ cle }) => cle).sort(), [...defauts.keys()].sort());
for (const entree of CATALOGUE) {
assert.deepEqual(
chargeDeDemonstration(entree).evenement,
{
id: `demo:${entree.cle}`,
nom: entree.nom,
date: null,
siegesParDefaut: defauts.get(entree.cle),
tours: entree.construire().tours,
unite: 'cm',
etat: 'brouillon',
filiation: null,
},
entree.cle,
);
}
});
test('les réglages : les contraintes de la configuration, une à une, les sièges non attribués, la génération par défaut', () => {
for (const entree of CATALOGUE) {
assert.deepEqual(
chargeDeDemonstration(entree).reglages,
{ ...entree.construire().contraintes, attribuerSieges: false, generation: { ...GENERATION_PAR_DEFAUT } },
entree.cle,
);
}
const contraintes = {
separerAppartenances: false,
nouveauxVoisins: true,
nouvelleTable: false,
varierAppartenances: true,
};
const entree = entreeRetouchee((configuration) => ({ ...configuration, contraintes }));
assert.deepEqual(chargeDeDemonstration(entree).reglages, {
...contraintes,
attribuerSieges: false,
generation: { ...GENERATION_PAR_DEFAUT },
});
});
test('les tables : rondes, en grille rangée par rangée, centres à 300 cm, la dernière rangée seule incomplète', () => {
assert.deepEqual(chargeDeDemonstration(entreeDe('petite')).tables, [
{ id: 1, numero: 1, sieges: null, forme: 'ronde', position: { x: 0, y: 0 } },
{ id: 2, numero: 2, sieges: null, forme: 'ronde', position: { x: 300, y: 0 } },
{ id: 3, numero: 3, sieges: null, forme: 'ronde', position: { x: 0, y: 300 } },
{ id: 4, numero: 4, sieges: null, forme: 'ronde', position: { x: 300, y: 300 } },
]);
// 33 tables : six colonnes, cinq rangées pleines, puis trois tables.
const { tables } = chargeDeDemonstration(entreeDe('grande'));
const positions = tables.map(({ position: { x, y } }) => [x, y]);
assert.deepEqual(positions.slice(0, 7), [[0, 0], [300, 0], [600, 0], [900, 0], [1200, 0], [1500, 0], [0, 300]]);
assert.deepEqual(positions.slice(-4), [[1500, 1200], [0, 1500], [300, 1500], [600, 1500]]);
assert.equal(new Set(positions.map(String)).size, 33);
assert.ok(tables.every(({ forme }) => forme === 'ronde'));
assert.deepEqual(tables.map(({ id, numero }) => [id, numero]), positions.map((_, rang) => [rang + 1, rang + 1]));
});
test("les sièges : le défaut est la capacité la plus fréquente, la plus grande à égalité ; toute autre capacité surcharge sa table (§ 6.1)", () => {
const surcharges = (charge) =>
charge.tables.filter(({ sieges }) => sieges !== null).map(({ numero, sieges }) => [numero, sieges]);
assert.deepEqual(surcharges(chargeDeDemonstration(entreeDe('grande'))), [[30, 7], [31, 7], [32, 7], [33, 7]]);
for (const cle of ['grande-sans-exception', 'petite', 'petite-conflit']) {
assert.deepEqual(surcharges(chargeDeDemonstration(entreeDe(cle))), [], cle);
}
const capacites = [6, 9, 6, 9];
const egalite = chargeDeDemonstration(
entreeRetouchee((configuration) => ({
...configuration,
tables: configuration.tables.map((table, rang) => ({ ...table, capacite: capacites[rang] })),
})),
);
assert.equal(egalite.evenement.siegesParDefaut, 9);
assert.deepEqual(surcharges(egalite), [[1, 6], [3, 6]]);
});
test("grandes : chaque animateur réservé au siège 1 de sa table pour tous les tours, ce siège titré « animation », le titre pressenti porté par lui seul ; petites : ni réservation ni titre", () => {
for (const cle of ['grande', 'grande-sans-exception']) {
const entree = entreeDe(cle);
const animateurs = entree.construire().reservations;
const charge = chargeDeDemonstration(entree);
assert.equal(animateurs.length, 33, cle);
assert.deepEqual(
[...charge.reservations].sort(parTable),
animateurs
.map(({ participant, table }) => ({ participant, table, siege: 1, portee: 'tous', tour: null }))
.sort(parTable),
cle,
);
assert.deepEqual(
[...charge.titres].sort(parTable),
charge.tables.map(({ id }) => ({ table: id, siege: 1, libelle: 'animation' })),
cle,
);
assert.deepEqual(
charge.participants
.filter(({ titrePressenti }) => titrePressenti !== null)
.map(({ id, titrePressenti }) => [id, titrePressenti]),
animateurs.map(({ participant }) => [participant, 'animation']).sort((a, b) => a[0] - b[0]),
cle,
);
}
for (const cle of ['petite', 'petite-conflit']) {
const charge = chargeDeDemonstration(entreeDe(cle));
assert.deepEqual([charge.reservations, charge.titres], [[], []], cle);
assert.ok(charge.participants.every(({ titrePressenti }) => titrePressenti === null), cle);
}
});
test("ni proposition ni retenu, personne d'exclu, ni courriel ni notes ; les compteurs dépassent chaque identifiant", () => {
for (const entree of CATALOGUE) {
const charge = chargeDeDemonstration(entree);
assert.deepEqual([charge.propositions, charge.retenu], [[], null], entree.cle);
assert.ok(
charge.participants.every(({ exclu, courriel, notes }) => exclu === false && courriel === null && notes === null),
entree.cle,
);
const plusGrand = (liste) => Math.max(...liste.map(({ id }) => id));
assert.deepEqual(
charge.prochainsIds,
{ participant: plusGrand(charge.participants) + 1, table: plusGrand(charge.tables) + 1, proposition: 1 },
entree.cle,
);
}
});
test("refuse une configuration qu'il ne sait pas écrire : deux réservations à une table, une réservation d'un seul tour", () => {
const avecReservations = (reservations) =>
entreeRetouchee((configuration) => ({ ...configuration, reservations }));
assert.throws(
() =>
chargeDeDemonstration(
avecReservations([
{ participant: 1, table: 2, portee: 'tous' },
{ participant: 5, table: 2, portee: 'tous' },
]),
),
/essai.*table 2/,
);
assert.throws(
() => chargeDeDemonstration(avecReservations([{ participant: 1, table: 3, portee: 'tour', tour: 2 }])),
/essai.*table 3/,
);
});
});
describe("engendrer_livrables : le fichier d'état écrit (§ 8.8, § 18.6)", () => {
test('révision 1, la version de la construction qui écrit, la forme canonique ; UTF-8 sans marque, des LF seules, une fin de ligne finale', () => {
for (const entree of CATALOGUE) {
const texte = texteDeDemonstration(entree);
const lu = analyser(texte);
assert.deepEqual(
[lu.entete.format, lu.entete.produitVersion, lu.entete.revision],
[1, VERSION.affichee, 1],
entree.cle,
);
assert.equal(serialiser(lu.charge, lu.entete), texte, entree.cle);
assert.ok(!texte.startsWith(MARQUE) && !texte.includes('\r'), entree.cle);
assert.ok(texte.endsWith('}\n') && !texte.endsWith('\n\n'), entree.cle);
}
});
test("une autre construction n'écrit qu'un autre en-tête : la charge est la même", () => {
const voisine = versionVoisine(VERSION.affichee);
for (const entree of CATALOGUE) {
const ici = analyser(texteDeDemonstration(entree));
const ailleurs = analyser(texteDeDemonstration(entree, { produitVersion: voisine }));
assert.equal(ailleurs.entete.produitVersion, voisine, entree.cle);
assert.deepEqual(ailleurs.charge, ici.charge, entree.cle);
}
});
});
describe('engendrer_livrables : la variante windows-1252 (§ 10.3)', () => {
test('enWindows1252 : un octet par caractère, les signes propres à windows-1252 compris', () => {
// Octets de la table publiée de windows-1252.
assert.deepEqual(
[...enWindows1252('aé€\u{2019}œŸ\u{A0}ÿ')],
[0x61, 0xe9, 0x80, 0x92, 0x9c, 0x9f, 0xa0, 0xff],
);
const decodeur = new TextDecoder('windows-1252');
for (let octet = 0; octet < 256; octet += 1) {
assert.deepEqual([...enWindows1252(decodeur.decode(Uint8Array.of(octet)))], [octet], `octet ${octet}`);
}
});
test('enWindows1252 refuse un caractère que windows-1252 ne porte pas, et nomme son point de code et son rang', () => {
assert.throws(
() => enWindows1252('noté ✓'),
(erreur) => erreur instanceof RangeError && /U\+2713/.test(erreur.message) && /rang 5/.test(erreur.message),
);
assert.throws(() => enWindows1252('\u{1D11E}'), /U\+1D11E/);
});
test('varianteWindows1252 : les mêmes enregistrements, séparés par des virgules, en windows-1252 sans marque', () => {
const source = [
'Nom;Prénom;Notes',
'Lanterneau;Basile;"chœur; l\u{2019}Anse, nord"',
'Coudraie;Apolline;"deux\nlignes"',
'Brunœil;;sans prénom',
];
const octets = new TextEncoder().encode(`${MARQUE}${source.join('\r\n')}\r\n`);
const variante = varianteWindows1252(octets);
assert.equal(
new TextDecoder('windows-1252').decode(variante),
[
'Nom,Prénom,Notes',
'Lanterneau,Basile,"chœur; l\u{2019}Anse, nord"',
'Coudraie,Apolline,"deux\nlignes"',
'Brunœil,,sans prénom',
'',
].join('\r\n'),
);
assert.deepEqual([...variante.subarray(0, 3)], [...enWindows1252('Nom')]);
assert.deepEqual(
decouper(new TextDecoder('windows-1252').decode(variante), ',').enregistrements,
decouper(new TextDecoder().decode(octets), ';').enregistrements,
);
});
test('varianteWindows1252 refuse un fichier dont un guillemet reste ouvert', () => {
const octets = new TextEncoder().encode(`${MARQUE}Nom;Notes\r\nLanterneau;"sans fin\r\n`);
assert.throws(() => varianteWindows1252(octets), /guillemet/);
});
});
describe('engendrer_livrables : les livrables (§ 10.3, § 15.5)', () => {
test("les sept fichiers, dans l'ordre ; les CSV de démonstration exportent les fichiers d'état écrits, la variante dérive des cas limites", () => {
const casLimites = readFileSync(CAS_LIMITES);
const fichiers = livrables({ casLimites });
assert.deepEqual(
fichiers.map(({ chemin }) => chemin),
LIVRABLES,
);
const octetsDe = new Map(fichiers.map(({ chemin, octets }) => [chemin, octets]));
for (const entree of CATALOGUE) {
assert.equal(
new TextDecoder().decode(octetsDe.get(`src/demo/livrees/${entree.cle}.gtt.json`)),
texteDeDemonstration(entree),
entree.cle,
);
}
for (const cle of ['petite', 'grande']) {
const etat = new TextDecoder().decode(octetsDe.get(`src/demo/livrees/${cle}.gtt.json`));
assert.deepEqual(
octetsDe.get(`exemples/participants_demo_${cle}.csv`),
exporterParticipants(analyser(etat).charge.participants),
cle,
);
}
assert.deepEqual(octetsDe.get('exemples/participants_cas_limites_cp1252.csv'), varianteWindows1252(casLimites));
});
});
describe('engendrer_livrables : la ligne de commande', () => {
test('écrit les sept livrables sous le dossier donné, relatif au répertoire courant, et rien ailleurs, au premier lancement comme au suivant', () =>
avecProjetCopie((dossier, lancerCopie) => {
// Le dossier entier, copie du projet comprise, est photographié avant et
// après chaque lancement : seuls les sept livrables, sous sortie/, y
// apparaissent. Le CSV des cas limites écrit à la main, tout autre
// fichier de la copie et le répertoire courant doivent y rester tels
// quels.
const sept = LIVRABLES.map((chemin) => `sortie/${chemin}`).sort();
const avant = instantane(dossier);
assert.ok(avant.has('projet/exemples/participants_cas_limites.csv'), 'la copie porte le CSV des cas limites');
const premier = lancerCopie('sortie');
assert.equal(premier.status, 0, premier.stderr);
const apres = instantane(dossier);
assert.deepEqual(differences(avant, apres), sept, "le premier lancement n'écrit pas exactement les sept livrables");
const attendus = livrables({ casLimites: readFileSync(CAS_LIMITES) });
for (const { chemin, octets } of attendus) {
assert.deepEqual(new Uint8Array(readFileSync(join(dossier, 'sortie', chemin))), octets, chemin);
assert.match(premier.stdout, new RegExp(`^ *créé +${echapper(chemin)} `, 'm'), chemin);
}
// Relancé, le script ne touche rien hors des sept livrables, qu'il
// réécrit sans en changer un octet : leurs dates peuvent avancer, leurs
// octets non.
const second = lancerCopie('sortie');
assert.equal(second.status, 0, second.stderr);
assert.deepEqual(
differences(apres, instantane(dossier)).filter((chemin) => !sept.includes(chemin)),
[],
'le lancement suivant écrit autre chose que les sept livrables',
);
for (const { chemin, octets } of attendus) {
assert.deepEqual(new Uint8Array(readFileSync(join(dossier, 'sortie', chemin))), octets, chemin);
}
}));
test('relancé, il réécrit les mêmes octets, et le dit', () =>
avecDossier((dossier) => {
const premier = lancer(dossier, '.');
assert.equal(premier.status, 0, premier.stderr);
const octets = LIVRABLES.map((chemin) => readFileSync(join(dossier, chemin)));
const second = lancer(dossier, dossier);
assert.equal(second.status, 0, second.stderr);
LIVRABLES.forEach((chemin, rang) => {
assert.deepEqual(readFileSync(join(dossier, chemin)), octets[rang], chemin);
assert.match(second.stdout, new RegExp(`^ *inchangé +${echapper(chemin)} `, 'm'), chemin);
});
}));
test("une destination qui ne s'écrit pas sort en 1 en nommant la cause", () =>
avecDossier((dossier) => {
writeFileSync(join(dossier, 'occupe'), 'un fichier, pas un dossier\n');
const resultat = lancer(dossier, 'occupe');
assert.equal(resultat.status, 1, resultat.stdout);
assert.match(resultat.stderr, /^Livrables : .*occupe/m);
assert.deepEqual(fichiersSous(dossier), ['occupe']);
}));
test("un argument de trop, ou une option, sort en 2 en rappelant l'usage, sans rien écrire", () =>
avecDossier((dossier) => {
for (const arguments_ of [['a', 'b'], ['--aide']]) {
const resultat = lancer(dossier, ...arguments_);
assert.equal(resultat.status, 2, arguments_.join(' '));
assert.match(resultat.stderr, /usage : node scripts\/engendrer_livrables\.js \[destination\]/);
}
assert.deepEqual(fichiersSous(dossier), []);
}));
});

View file

@ -299,7 +299,14 @@ const LIGNES_OBLIGATOIRES = [
// contrôlent ;
// - les documents (*.md), dont le point 7 contrôle les noms cités ;
// - les épreuves de la dérivation, qui citent des versions par nécessité ;
// - les données d'épreuve, sous test/fixtures/.
// - les données d'épreuve, sous test/fixtures/ ;
// - les démonstrations livrées, sous src/demo/livrees/, données engendrées
// dont l'en-tête porte la version de la construction qui les a écrites
// (§ 18.5, § 18.6) : une livraison suivante ne les réécrit pas (§ 15.5,
// point 5).
// Un répertoire s'exclut par son chemin exact, et rien n'est lu sous lui ;
// un fichier dont le nom commence comme le sien, src/demo/livrees.js, reste
// balayé.
// package.json reste balayé : la forme technique y est à sa place, la forme
// affichée non.
const ARBRES_BALAYES = ['src', 'scripts', 'electron', 'test'];
@ -310,7 +317,7 @@ const FICHIERS_EXCLUS = new Set([
'CHANGELOG.md',
'scripts/version.test.js',
]);
const REPERTOIRES_EXCLUS = new Set(['test/fixtures']);
const REPERTOIRES_EXCLUS = new Set(['test/fixtures', 'src/demo/livrees']);
const DOCUMENT = /\.md$/i;
const ARBRE_DES_SOURCES = 'src/';

View file

@ -629,12 +629,21 @@ describe('version : contrôle du § 18.5, un défaut à la fois', () => {
assert.deepEqual(controler(racine, { dateCivile: DATE }), []);
}));
test('point 6 : seul le répertoire des démonstrations livrées est exclu, pas un fichier qui porte son nom', () =>
surArbre((racine) => {
ecrire(racine, 'src/demo/livrees.js', "export const v = '2026.10.05.01';\n");
ecrire(racine, 'src/demo/livrees/sous/grande.gtt.json', '{"produitVersion":"2026.10.05.01"}\n');
const echecs = exigerEchecs(racine, [6], [/^6\. src\/demo\/livrees\.js, ligne 1 : « 2026\.10\.05\.01 », forme affichée/]);
assert.equal(echecs.length, 1, echecs.join('\n'));
}));
test('point 6 : données, guides, dépendances et sorties restent hors du périmètre', () =>
surArbre((racine) => {
const versions = `${VERSION} ${TECHNIQUE} ${TECHNIQUE}.0\n`;
for (const chemin of [
'scripts/version.test.js',
'test/fixtures/demonstration.json',
'src/demo/livrees/grande.gtt.json',
'GUIDE-USAGE.md',
'docs/notes.md',
'node_modules/paquet/index.js',

View file

@ -3,8 +3,12 @@
// Garde des noms forgés (§ 14.9, § 15.6) : aucun patronyme forgé complet, nom
// long compris, ni aucune racine d'organisation n'apparaît en mot entier dans
// un fichier texte du projet hors de src/demo/noms.js, qui les définit. Les
// données d'épreuve de test/fixtures/ sont balayées comme le reste.
// un fichier texte du projet hors de src/demo/noms.js, qui les définit, et
// des fichiers que scripts/engendrer_livrables.js engendre et qui les portent
// par construction : les démonstrations livrées, sous src/demo/livrees/, et
// les deux CSV de démonstration, sous exemples/. Le CSV des cas limites,
// écrit à la main, sa variante windows-1252 et les données d'épreuve de
// test/fixtures/ sont balayés comme le reste.
//
// Un mot entier est bordé, de chaque côté, par autre chose qu'une lettre, une
// marque combinante ou un chiffre : le trait de soulignement et le trait
@ -16,11 +20,13 @@
// Chaque fichier de l'arbre exerce une règle : une sonde de test/fixtures/
// porte chaque nom cherché ; d'autres placent un nom au-delà du premier Mio
// du texte, en NFD, après un octet nul, dans une entrée cachée, dans un
// noms.js qui n'est pas celui des listes, ou sous un répertoire exclu ; trois
// derniers se lisent malgré un nom voisin d'une exclusion : src/distances/,
// distinctes.js et un fichier .git. L'épreuve affirme les fichiers lus, le
// compte exact des trouvailles, et que la garde de l'épreuve du projet
// désigne exactement les fichiers placés sous un répertoire exclu.
// noms.js qui n'est pas celui des listes, dans un fichier engendré, ou sous
// un répertoire exclu ; les derniers se lisent malgré un nom voisin d'une
// exclusion : src/distances/, distinctes.js, un fichier .git, un module
// src/demo/livrees.js, le CSV des cas limites et sa variante. L'épreuve
// affirme les fichiers lus, le compte exact des trouvailles, et que la garde
// de l'épreuve du projet désigne exactement les fichiers placés sous un
// répertoire exclu.
//
// L'épreuve établit l'absence dans ce projet, rien sur le monde réel : aucune
// vérification locale ne prouve qu'un nom forgé ne désigne nulle part une
@ -28,7 +34,7 @@
import assert from 'node:assert/strict';
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, relative } from 'node:path';
import { dirname, join, relative, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, test } from '../../test/lanceur.js';
import { NOMS_LONGS, RACINES, patronymes } from './noms.js';
@ -36,6 +42,20 @@ import { NOMS_LONGS, RACINES, patronymes } from './noms.js';
const RACINE = fileURLToPath(new URL('../..', import.meta.url));
const LISTES = join('src', 'demo', 'noms.js');
// Fichiers qui portent les noms forgés par construction, que
// scripts/engendrer_livrables.js écrit (§ 10.3, § 15.6) : tout fichier sous
// src/demo/livrees/, et les deux CSV de démonstration.
const DEMONSTRATIONS_LIVREES = join('src', 'demo', 'livrees') + sep;
const CSV_DE_DEMONSTRATION = new Set([
join('exemples', 'participants_demo_petite.csv'),
join('exemples', 'participants_demo_grande.csv'),
]);
// Vrai pour les listes du générateur et pour un fichier que
// scripts/engendrer_livrables.js en engendre.
const porteLesNomsParConstruction = (chemin) =>
chemin === LISTES || chemin.startsWith(DEMONSTRATIONS_LIVREES) || CSV_DE_DEMONSTRATION.has(chemin);
// Répertoires hors balayage, à toute profondeur : .git, les dépendances, les
// caches des outils, et les sorties de construction, qui portent une copie
// des listes mises en paquet (www, dist et chaque dist-…, plateformes de
@ -87,17 +107,18 @@ function motifDes(noms) {
// Occurrences du motif dans un texte, normalisé en NFC.
const occurrences = (motif, texte) => [...texte.normalize('NFC').matchAll(motif)].map(([mot]) => mot);
// Balaie l'arbre de racine : chaque fichier texte hors des répertoires exclus
// et des listes du générateur se lit en entier, décodé en UTF-8, et chaque
// nom cherché s'y relève. Rend les chemins examinés, relatifs à racine et
// triés, et les trouvailles { chemin, mot }, fichier après fichier, dans
// l'ordre du texte.
// Balaie l'arbre de racine : chaque fichier texte hors des répertoires
// exclus, des listes du générateur et des fichiers que
// scripts/engendrer_livrables.js en engendre se lit en entier, décodé en
// UTF-8, et chaque nom cherché s'y relève. Rend les chemins examinés,
// relatifs à racine et triés, et les trouvailles { chemin, mot }, fichier
// après fichier, dans l'ordre du texte.
function balayer(racine) {
const motif = motifDes(nomsCherches());
const examines = [];
const trouvailles = [];
for (const chemin of fichiers(racine)) {
if (chemin === LISTES) continue;
if (porteLesNomsParConstruction(chemin)) continue;
const octets = readFileSync(join(racine, chemin));
if (estBinaire(octets)) continue;
examines.push(chemin);
@ -178,12 +199,22 @@ describe('noms forgés : balayage du projet (§ 15.6)', () => {
[join('src', 'distances', 'a.js'), patronyme, [patronyme]],
[join('src', 'distinctes.js'), 'paires distinctes\n', []],
[join('sous', '.git'), 'gitdir: ../.git/worktrees/sous\n', []],
// Les voisins des fichiers engendrés se lisent : un module dont le nom
// commence comme leur répertoire, le CSV des cas limites, écrit à la
// main, et sa variante windows-1252, que le script dérive de lui.
[join('src', 'demo', 'livrees.js'), patronyme, [patronyme]],
[join('exemples', 'participants_cas_limites.csv'), patronyme, [patronyme]],
[join('exemples', 'participants_cas_limites_cp1252.csv'), patronyme, [patronyme]],
];
// [chemin, contenu] : les fichiers que le balayage saute — un octet nul
// dans les 8000 premiers, les listes du générateur.
// dans les 8000 premiers, les listes du générateur, les fichiers que le
// script en engendre.
const sautes = [
['nul-dedans.dat', nulA(7999, patronyme)],
[LISTES, patronyme],
[join('src', 'demo', 'livrees', 'grande.gtt.json'), patronyme],
[join('exemples', 'participants_demo_petite.csv'), patronyme],
[join('exemples', 'participants_demo_grande.csv'), patronyme],
];
// Un fichier sous chaque répertoire exclu, à la racine et plus profond ;
// chacun porte un nom forgé.
@ -224,7 +255,7 @@ describe('noms forgés : balayage du projet (§ 15.6)', () => {
assert.deepEqual(arbre.map(([chemin]) => chemin).filter(sousUnRepertoireExclu), exclus);
});
test('aucun patronyme forgé ni aucune racine hors de src/demo/noms.js', () => {
test('aucun patronyme forgé ni aucune racine hors de src/demo/noms.js et des fichiers engendrés', () => {
assert.ok(nomsCherches().length > 0, 'aucun nom forgé à chercher');
const { examines, trouvailles } = balayer(RACINE);
assert.ok(examines.length > 0, 'balayage vide');

57
src/demo/livrees.js Normal file
View file

@ -0,0 +1,57 @@
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Les démonstrations livrées (§ 15, § 15.5) : le texte du fichier d'état de
// chaque démonstration du catalogue, tel que scripts/engendrer_livrables.js
// l'a écrit sous livrees/. Le fichier livré est la source de vérité (§ 15.5,
// point 5) : montrer une démonstration lit ce fichier, sans rejouer le
// générateur. Charger une démonstration crée un événement neuf depuis ce
// texte (§ 15) ; le texte, une chaîne, ne se modifie pas, et la
// démonstration d'origine non plus.
//
// Chaque fichier s'importe en module JSON, syntaxe du langage que Node et
// Vite lisent l'un comme l'autre : le module se charge sous Node seul, ce
// qu'exige le repli « node --test » du § 14.8, et un fichier absent casse
// la construction. Un module JSON rend le document, non ses octets : le
// sérialiseur canonique le réécrit sous son propre en-tête. Chaque fichier
// livré est en forme canonique, UTF-8 sans marque, fin de ligne LF ; le
// texte rendu est donc le sien, octet pour octet, ce que livrees.test.js
// exige sous Vitest comme sous Node seul.
//
// Chaque texte décrit l'événement demo:<clé>, à la révision 1, en brouillon,
// sans proposition.
import { serialiser } from '../stockage/canonique.js';
import grande from './livrees/grande.gtt.json' with { type: 'json' };
import grandeSansException from './livrees/grande-sans-exception.gtt.json' with { type: 'json' };
import petite from './livrees/petite.gtt.json' with { type: 'json' };
import petiteConflit from './livrees/petite-conflit.gtt.json' with { type: 'json' };
// Texte canonique d'un fichier d'état lu en module JSON : sa charge, sous la
// révision et la version de son en-tête ; format et comptes se recalculent.
const texteDe = ({ entete, charge }) => serialiser(charge, entete);
/**
* Les quatre démonstrations livrées, dans l'ordre du catalogue
* (catalogue.js) : { cle, texte }, figées ; cle est celle de l'entrée du
* catalogue, texte le fichier d'état livré.
* @type {ReadonlyArray<Readonly<{cle: string, texte: string}>>}
*/
export const LIVREES = Object.freeze([
Object.freeze({ cle: 'grande', texte: texteDe(grande) }),
Object.freeze({ cle: 'grande-sans-exception', texte: texteDe(grandeSansException) }),
Object.freeze({ cle: 'petite', texte: texteDe(petite) }),
Object.freeze({ cle: 'petite-conflit', texte: texteDe(petiteConflit) }),
]);
/**
* Texte livré de la démonstration de clé donnée.
*
* @param {string} cle la clé d'une entrée du catalogue
* @returns {string}
* @throws {RangeError} pour une clé qu'aucune démonstration livrée ne porte
*/
export function texteLivre(cle) {
const livree = LIVREES.find((candidate) => candidate.cle === cle);
if (livree === undefined) throw new RangeError(`aucune démonstration livrée de clé ${JSON.stringify(cle)}`);
return livree.texte;
}

View file

@ -0,0 +1,166 @@
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Épreuves longues des démonstrations livrées (§ 14.4) : l'empreinte de la
// charge de chaque fichier livré contre celle que rejoue le script (§ 15.5,
// point 5), le profil d'appartenances lu dans le fichier (§ 15.2), et
// l'aller-retour sur chacun, grande démonstration comprise (§ 8.9). Le
// fichier livré est la source de vérité : un écart d'empreinte se nomme par
// les chemins de la charge qui diffèrent, et se résout en engendrant de
// nouveau, geste conscient, jamais en corrigeant l'épreuve.
import assert from 'node:assert/strict';
import { empreinte, texteDeDemonstration } from '../../scripts/engendrer_livrables.js';
import { describe, test } from '../../test/lanceur.js';
import { versionVoisine } from '../../test/version_voisine.js';
import { serialiser } from '../stockage/canonique.js';
import { difference } from '../stockage/correctifs.js';
import { analyser } from '../stockage/document.js';
import { examiner } from '../stockage/placements.js';
import { VERSION } from '../version.genere.js';
import { CATALOGUE } from './catalogue.js';
import { LIVREES, texteLivre } from './livrees.js';
const EMPREINTE = /^[0-9a-f]{64}$/;
// Chemin d'une opération de correctif, lisible : les clés jointes par des
// points, un rang entre crochets, un enregistrement par [id=n].
const cheminLisible = (chemin) =>
chemin
.map((etape) => {
if (typeof etape === 'string') return `.${etape}`;
return typeof etape === 'number' ? `[${etape}]` : `[id=${etape.id}]`;
})
.join('')
.replace(/^\./, '');
// Affirme que la charge rejouée a l'empreinte de la charge livrée ; à
// l'échec, nomme chaque chemin où elles diffèrent, tel que le correctif de
// l'une à l'autre le pose ou le retire (§ 15.5, point 5).
function memeEmpreinte(livree, rejouee, cle) {
if (empreinte(rejouee) === empreinte(livree)) return;
const chemins = difference(livree, rejouee).map(({ op, chemin }) => `${op} ${cheminLisible(chemin)}`);
assert.fail(
`${cle} : la charge que rejoue le script diffère du fichier livré : ${chemins.join(' ; ')}. ` +
'Engendrer de nouveau est un geste conscient : node scripts/engendrer_livrables.js',
);
}
// Profil d'appartenances d'une liste de participants (§ 15.2) : pour chaque
// taille de groupe, le nombre de groupes de cette taille, tailles
// croissantes ; les personnes sans appartenance ne forment pas de groupe.
function profil(participants) {
const tailles = new Map();
for (const { appartenance } of participants) {
if (appartenance !== null) tailles.set(appartenance, (tailles.get(appartenance) ?? 0) + 1);
}
const groupesParTaille = new Map();
for (const taille of tailles.values()) groupesParTaille.set(taille, (groupesParTaille.get(taille) ?? 0) + 1);
return [...groupesParTaille].sort((a, b) => a[0] - b[0]);
}
// Les fichiers livrés, tous ; lève quand il n'y en a aucun (§ 14.2).
function balayer() {
assert.ok(LIVREES.length > 0, 'aucun fichier livré examiné');
return LIVREES;
}
// Aller-retour d'un fichier livré (§ 8.9) : charger — analyser, puis
// contrôler les placements, qui n'écartent ni ne signalent rien —,
// enregistrer sous une autre construction et à la révision suivante,
// recharger. Rend la charge chargée, la charge rechargée, et le texte que
// l'enregistrement écrirait sous l'en-tête lu.
function allerRetour(texte) {
const lu = analyser(texte);
const { charge, fautives, derives } = examiner(lu.charge);
assert.deepEqual([fautives, derives], [[], []]);
const enregistre = serialiser(charge, {
revision: lu.entete.revision + 1,
produitVersion: versionVoisine(VERSION.affichee),
});
const relu = analyser(enregistre);
assert.notEqual(relu.entete.revision, lu.entete.revision);
return { charge, relue: relu.charge, sousLEnteteLu: serialiser(charge, lu.entete) };
}
describe('démonstrations livrées : empreinte (§ 15.5, point 5)', () => {
test("pour chaque entrée du catalogue, la charge que rejoue le script a l'empreinte du fichier livré", () => {
assert.ok(CATALOGUE.length > 0, 'catalogue vide');
for (const entree of CATALOGUE) {
const livree = analyser(texteLivre(entree.cle)).charge;
const rejouee = analyser(texteDeDemonstration(entree)).charge;
memeEmpreinte(livree, rejouee, entree.cle);
}
});
test("un changement de produitVersion seul ne change pas l'empreinte : elle porte sur la charge, l'en-tête exclu", () => {
const voisine = versionVoisine(VERSION.affichee);
for (const entree of CATALOGUE) {
const texteIci = texteDeDemonstration(entree);
const texteAilleurs = texteDeDemonstration(entree, { produitVersion: voisine });
assert.notEqual(texteAilleurs, texteIci, entree.cle);
const ailleurs = analyser(texteAilleurs);
assert.equal(ailleurs.entete.produitVersion, voisine, entree.cle);
assert.equal(empreinte(ailleurs.charge), empreinte(analyser(texteIci).charge), entree.cle);
memeEmpreinte(analyser(texteLivre(entree.cle)).charge, ailleurs.charge, entree.cle);
}
});
test("l'empreinte : 64 chiffres hexadécimaux, une par charge — les quatre démonstrations en ont quatre", () => {
const empreintes = balayer().map(({ texte }) => empreinte(analyser(texte).charge));
assert.ok(empreintes.every((valeur) => EMPREINTE.test(valeur)), empreintes.join(' '));
assert.equal(new Set(empreintes).size, empreintes.length);
});
test("l'échec nomme les chemins qui diffèrent, jamais seulement deux empreintes", () => {
const livree = analyser(texteLivre('petite')).charge;
const rejouee = structuredClone(livree);
rejouee.participants.find(({ id }) => id === 7).nom = 'Lanterneau';
rejouee.tables.find(({ id }) => id === 3).position.x += 1;
assert.notEqual(empreinte(rejouee), empreinte(livree));
assert.throws(
() => memeEmpreinte(livree, rejouee, 'petite'),
(erreur) => {
assert.match(erreur.message, /^petite : /);
assert.match(erreur.message, /poser participants\[id=7\]\.nom ; poser tables\[id=3\]\.position\.x\./);
assert.doesNotMatch(erreur.message, /[0-9a-f]{64}/);
return true;
},
);
});
});
describe("démonstrations livrées : profil d'appartenances (§ 15.2)", () => {
test("les tailles d'appartenance lues dans chaque fichier livré égalent celles du générateur", () => {
for (const entree of CATALOGUE) {
const attendu = profil(analyser(texteLivre(entree.cle)).charge.participants);
assert.ok(attendu.length > 0, entree.cle);
assert.deepEqual(profil(entree.construire().participants), attendu, entree.cle);
}
});
test('le profil compte les groupes par taille, sans les personnes sans appartenance', () => {
const personne = (id, appartenance) => ({ id, appartenance });
assert.deepEqual(
profil([
personne(1, 'Chœur de l\u{2019}Anse'),
personne(2, 'Cercle des Saules'),
personne(3, 'Chœur de l\u{2019}Anse'),
personne(4, null),
personne(5, 'Société des Hérons'),
]),
[[1, 2], [2, 1]],
);
});
});
describe('démonstrations livrées : aller-retour sur chacune, grande comprise (§ 8.9)', () => {
test("charger, enregistrer, recharger rend la même charge ; le texte livré est celui qu'écrit le sérialiseur", () => {
const examines = balayer();
for (const { cle, texte } of examines) {
const { charge, relue, sousLEnteteLu } = allerRetour(texte);
assert.deepEqual(relue, charge, cle);
assert.equal(sousLEnteteLu, texte, cle);
}
assert.ok(examines.some(({ cle }) => cle === 'grande'), 'la grande démonstration non examinée');
});
});

233
src/demo/livrees.test.js Normal file
View file

@ -0,0 +1,233 @@
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Épreuves des démonstrations livrées et des CSV d'exemple, série surveillée
// (§ 8.9, § 10.3, § 14.4, § 14.8, § 15) : un fichier par entrée du
// catalogue, que LIVREES porte octet pour octet, sous Vitest comme sous Node
// seul ; chacun une démonstration neuve, en forme canonique ;
// l'aller-retour sur la petite démonstration ; les deux CSV de
// démonstration, l'export exact des fichiers livrés ; les deux CSV des cas
// limites, la même lecture en deux encodages, où chaque cas du § 10.3 se
// voit, et des noms inventés hors des listes du générateur. L'empreinte, le
// profil et l'aller-retour sur la grande sont dans livrees.long.test.js.
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { readdirSync, readFileSync } from 'node:fs';
import { varianteWindows1252 } from '../../scripts/engendrer_livrables.js';
import { describe, test } from '../../test/lanceur.js';
import { versionVoisine } from '../../test/version_voisine.js';
import { apercevoir } from '../csv/apercu.js';
import { decoder } from '../csv/encodage.js';
import { exporterParticipants } from '../csv/export.js';
import { serialiser } from '../stockage/canonique.js';
import { analyser } from '../stockage/document.js';
import { examiner } from '../stockage/placements.js';
import { VERSION } from '../version.genere.js';
import { CATALOGUE } from './catalogue.js';
import { LIVREES, texteLivre } from './livrees.js';
import { NOMS_LONGS, PRENOMS, RACINES, TETES, patronymes } from './noms.js';
const DOSSIER_LIVREES = new URL('./livrees/', import.meta.url);
const EXEMPLES = new URL('../../exemples/', import.meta.url);
// Les démonstrations dont la série surveillée fait l'aller-retour ; la série
// longue le fait sur toutes (§ 14.4).
const PETITES = new Set(['petite', 'petite-conflit']);
const MARQUE_UTF8 = [0xef, 0xbb, 0xbf];
const lireLivre = (cle) => readFileSync(new URL(`${cle}.gtt.json`, DOSSIER_LIVREES));
const lireExemple = (nom) => readFileSync(new URL(nom, EXEMPLES));
// Texte d'un fichier livré, décodé en UTF-8 strict : un octet invalide lève.
const texteDuFichier = (cle) => new TextDecoder('utf-8', { fatal: true }).decode(lireLivre(cle));
// Programme que Node exécute seul, hors de Vite et de Vitest : il importe
// livrees.js et écrit LIVREES en JSON sur sa sortie standard.
const SOUS_NODE_SEUL = [
`const { LIVREES } = await import(${JSON.stringify(new URL('./livrees.js', import.meta.url).href)});`,
'process.stdout.write(JSON.stringify(LIVREES));',
].join('\n');
// Les fichiers livrés que retient garder ; lève quand il n'en reste aucun :
// un balayage qui n'examine rien ne prouve rien (§ 14.2).
function balayer(garder) {
const examines = LIVREES.filter(({ cle }) => garder(cle));
assert.ok(examines.length > 0, 'aucun fichier livré examiné');
return examines;
}
// Aller-retour d'un fichier livré (§ 8.9) : charger — analyser, puis
// contrôler les placements, qui n'écartent ni ne signalent rien —,
// enregistrer sous une autre construction et à la révision suivante,
// recharger. Rend la charge chargée, la charge rechargée, et le texte que
// l'enregistrement écrirait sous l'en-tête lu.
function allerRetour(texte) {
const lu = analyser(texte);
const { charge, fautives, derives } = examiner(lu.charge);
assert.deepEqual([fautives, derives], [[], []]);
const enregistre = serialiser(charge, {
revision: lu.entete.revision + 1,
produitVersion: versionVoisine(VERSION.affichee),
});
const relu = analyser(enregistre);
assert.notEqual(relu.entete.revision, lu.entete.revision);
return { charge, relue: relu.charge, sousLEnteteLu: serialiser(charge, lu.entete) };
}
// Rang du premier octet où deux suites diffèrent, longueur comprise, ou -1.
function premierEcart(a, b) {
const longueur = Math.min(a.length, b.length);
for (let rang = 0; rang < longueur; rang += 1) if (a[rang] !== b[rang]) return rang;
return a.length === b.length ? -1 : longueur;
}
// Les deux lectures du CSV des cas limites : celle du fichier écrit à la
// main et celle de sa variante windows-1252.
function lecturesDesCasLimites() {
return [
['participants_cas_limites.csv', decoder(lireExemple('participants_cas_limites.csv'))],
['participants_cas_limites_cp1252.csv', decoder(lireExemple('participants_cas_limites_cp1252.csv'))],
].map(([nom, { texte, encodage }]) => ({ nom, encodage, apercu: apercevoir(texte) }));
}
describe('démonstrations livrées : les fichiers (§ 15.5, point 5)', () => {
test("un fichier par entrée du catalogue, nommé <clé>.gtt.json, et rien d'autre : ni journal ni proposition", () => {
const fichiers = readdirSync(DOSSIER_LIVREES).sort();
assert.ok(fichiers.length > 0, 'aucun fichier livré');
assert.deepEqual(fichiers, CATALOGUE.map(({ cle }) => `${cle}.gtt.json`).sort());
});
test("LIVREES porte le texte de chaque fichier, octet pour octet, dans l'ordre du catalogue, figé", () => {
assert.deepEqual(
LIVREES.map(({ cle }) => cle),
CATALOGUE.map(({ cle }) => cle),
);
for (const livree of LIVREES) {
assert.equal(
livree.texte,
texteDuFichier(livree.cle),
`${livree.cle} : LIVREES ne porte pas le texte du fichier ; un fichier hors de la forme canonique s'y réécrit autrement`,
);
assert.ok(Object.isFrozen(livree), livree.cle);
}
assert.ok(Object.isFrozen(LIVREES));
});
test('Node seul, sans Vite ni Vitest, importe livrees.js et y lit le texte de chaque fichier : le repli « node --test » le charge (§ 14.8)', () => {
const resultat = spawnSync(process.execPath, ['--input-type=module', '--eval', SOUS_NODE_SEUL], {
encoding: 'utf8',
});
assert.equal(resultat.status, 0, resultat.stderr);
const lues = JSON.parse(resultat.stdout);
assert.deepEqual(
lues.map(({ cle }) => cle),
CATALOGUE.map(({ cle }) => cle),
);
for (const { cle, texte } of lues) {
assert.equal(texte, texteDuFichier(cle), `${cle} : le texte lu sous Node seul diffère du fichier`);
}
});
test("texteLivre rend le texte d'une clé, et refuse une clé qu'aucune démonstration ne porte", () => {
for (const { cle, texte } of LIVREES) assert.equal(texteLivre(cle), texte, cle);
assert.throws(() => texteLivre('moyenne'), (erreur) => erreur instanceof RangeError && /moyenne/.test(erreur.message));
});
test('chaque fichier : UTF-8 sans marque, des LF seules, une seule fin de ligne finale', () => {
for (const { cle } of LIVREES) {
const octets = lireLivre(cle);
assert.notDeepEqual([...octets.subarray(0, 3)], MARQUE_UTF8, cle);
const texte = new TextDecoder('utf-8', { fatal: true }).decode(octets);
assert.ok(!texte.includes('\r'), cle);
assert.ok(texte.endsWith('}\n') && !texte.endsWith('\n\n'), cle);
}
});
test("chaque fichier est une démonstration neuve : identifiant demo:<clé>, nom du catalogue, révision 1, brouillon, sans filiation, ni proposition ni retenu", () => {
for (const { cle, texte } of LIVREES) {
const { entete, charge } = analyser(texte);
assert.deepEqual(
[entete.revision, charge.evenement.id, charge.evenement.nom, charge.evenement.etat, charge.evenement.filiation],
[1, `demo:${cle}`, CATALOGUE.find((entree) => entree.cle === cle).nom, 'brouillon', null],
cle,
);
assert.deepEqual([charge.propositions, charge.retenu], [[], null], cle);
}
});
});
describe('démonstrations livrées : aller-retour sur la petite (§ 8.9, § 14.4)', () => {
test("charger, enregistrer, recharger rend la même charge ; le texte livré est celui qu'écrit le sérialiseur", () => {
for (const { cle, texte } of balayer((cle) => PETITES.has(cle))) {
const { charge, relue, sousLEnteteLu } = allerRetour(texte);
assert.deepEqual(relue, charge, cle);
assert.equal(sousLEnteteLu, texte, cle);
}
});
test("le balayage refuse de passer quand il n'examine aucun fichier (§ 14.2)", () => {
assert.throws(() => balayer(() => false), /aucun fichier livré examiné/);
});
});
describe("CSV d'exemple : les deux de démonstration (§ 10.3)", () => {
test("participants_demo_petite.csv et participants_demo_grande.csv égalent, octet pour octet, l'export des fichiers livrés", () => {
for (const cle of ['petite', 'grande']) {
const lus = lireExemple(`participants_demo_${cle}.csv`);
const attendus = exporterParticipants(analyser(texteLivre(cle)).charge.participants);
assert.equal(premierEcart(lus, attendus), -1, `participants_demo_${cle}.csv : premier octet qui diffère`);
}
});
});
describe("CSV d'exemple : les cas limites (§ 10.3)", () => {
test('UTF-8 avec marque et « ; », puis windows-1252 et « , » : la même lecture, séparateur à part', () => {
assert.deepEqual([...lireExemple('participants_cas_limites.csv').subarray(0, 3)], MARQUE_UTF8);
const [utf8, windows1252] = lecturesDesCasLimites();
assert.deepEqual(
[utf8.encodage, utf8.apercu.separateur, windows1252.encodage, windows1252.apercu.separateur],
['utf-8-bom', ';', 'windows-1252', ','],
);
assert.equal(utf8.apercu.refusGlobal, null);
assert.deepEqual({ ...windows1252.apercu, separateur: ';' }, utf8.apercu);
});
test('la variante windows-1252 est celle que le script dérive du fichier écrit à la main', () => {
const lus = lireExemple('participants_cas_limites_cp1252.csv');
const attendus = varianteWindows1252(lireExemple('participants_cas_limites.csv'));
assert.equal(premierEcart(lus, attendus), -1, 'participants_cas_limites_cp1252.csv : premier octet qui diffère');
});
test('chaque cas du § 10.3 se voit dans les deux fichiers', () => {
for (const { nom, apercu } of lecturesDesCasLimites()) {
const champs = apercu.lignes.map((ligne) => ligne.champs);
const bruts = apercu.lignes.flatMap(({ brut }) => brut);
const cas = [
['une colonne non reconnue', apercu.association.nonReconnues.length > 0],
['une personne sans appartenance', champs.some(({ appartenance }) => appartenance === null)],
['une exclusion', champs.some(({ exclu }) => exclu === true)],
// Un champ qui porte le séparateur n'a pu se lire entier que cité.
['un champ cité contenant le séparateur', bruts.some((champ) => champ.includes(apercu.separateur))],
['un champ cité sur deux lignes', bruts.some((champ) => champ.includes('\n'))],
['un doublon', apercu.doublons.length > 0],
['une personne sans prénom', champs.some(({ prenom }) => prenom === null)],
['deux orthographes fondues', apercu.fusions.some(({ orthographes }) => orthographes.length >= 2)],
['une ligne refusée faute de nom', apercu.refusees.some(({ code }) => code === 'NOM_ABSENT')],
];
assert.deepEqual(cas.filter(([, vu]) => !vu).map(([libelle]) => libelle), [], nom);
}
});
test('noms, organisations et courriels inventés : aucun nom des listes du générateur, chaque courriel sous .test', () => {
const { texte } = decoder(lireExemple('participants_cas_limites.csv'));
// Chaque nom des listes, en mot entier, sans égard à la casse, en NFC.
const noms = [...PRENOMS, ...patronymes(), ...NOMS_LONGS, ...TETES, ...RACINES]
.map((nom) => nom.normalize('NFC').replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
const motif = new RegExp(`(?<![\\p{L}\\p{M}\\p{N}])(?:${noms.join('|')})(?![\\p{L}\\p{M}\\p{N}])`, 'giu');
assert.deepEqual([...texte.normalize('NFC').matchAll(motif)].map(([mot]) => mot), []);
const courriels = texte.match(/[^\s;,"]+@[^\s;,"]+/g) ?? [];
assert.ok(courriels.length > 0, 'aucun courriel');
assert.deepEqual(courriels.filter((courriel) => !courriel.endsWith('.test')), []);
});
});

View file

@ -0,0 +1,377 @@
{
"entete": {"format":1,"produitVersion":"2026.10.05.01","revision":1,"comptes":{"participants":260,"tables":33,"reservations":33,"titres":33,"propositions":0,"retenu":0}},
"charge": {
"evenement": {"id":"demo:grande-sans-exception","nom":"Grande démonstration sans exception","date":null,"siegesParDefaut":8,"tours":4,"unite":"cm","etat":"brouillon","filiation":null},
"reglages": {"separerAppartenances":true,"nouveauxVoisins":true,"nouvelleTable":true,"varierAppartenances":true,"attribuerSieges":false,"generation":{"nombre":5,"arret":200000,"historique":1000}},
"prochainsIds": {"participant":261,"table":34,"proposition":1},
"participants": [
{"id":1,"nom":"Mirvault","prenom":"Isabelle","appartenance":"Comptoir Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":2,"nom":"Neskière","prenom":"Youssef","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":3,"nom":"Abrevande","prenom":"Félix","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":4,"nom":"Fralvançon","prenom":"Hugo","appartenance":"Brasserie Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":5,"nom":"Fralveux","prenom":"Manon","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":6,"nom":"Luskotte","prenom":"Catherine","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":7,"nom":"WAMMERWOLM-MAWWEMMEL","prenom":"Zoé","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":8,"nom":"Neskichon","prenom":"Louise","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":9,"nom":"Urvesson","prenom":"Catherine","appartenance":"Fabrique Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":10,"nom":"Hulvivet","prenom":"Karim","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":11,"nom":"Selveron","prenom":"Caroline","appartenance":"Ateliers Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":12,"nom":"Korvault","prenom":"Catherine","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":13,"nom":"Fralvotte","prenom":"Caroline","appartenance":"Fonderie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":14,"nom":"Jusvesson","prenom":"Geneviève","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":15,"nom":"Abrevumet","prenom":"Paul","appartenance":"Ateliers Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":16,"nom":"Fralvault","prenom":"Léa","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":17,"nom":"Obrivet","prenom":"Fatima","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":18,"nom":"Voskeval","prenom":"Philippe","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":19,"nom":"Dulveval","prenom":"Zoé","appartenance":"Menuiserie Quimvaux","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":20,"nom":"Korveux","prenom":"Paul","appartenance":"Filature Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":21,"nom":"Luskière","prenom":"Julie","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":22,"nom":"Clovande","prenom":"Émilie","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":23,"nom":"Hulvande","prenom":"Paul","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":24,"nom":"Fralvesson","prenom":"Hugo","appartenance":"Filature Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":25,"nom":"Urvière","prenom":"Isabelle","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":26,"nom":"Rozvotte","prenom":"Thomas","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":27,"nom":"Bralmumet","prenom":"Isabelle","appartenance":"Verrerie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":28,"nom":"Dulvouze","prenom":"Catherine","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":29,"nom":"Voskichon","prenom":"Marc","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":30,"nom":"Mirvivet","prenom":"Simon","appartenance":"Ateliers Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":31,"nom":"Clovault","prenom":"Louise","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":32,"nom":"Voskeux","prenom":"Lucie","appartenance":"Coopérative Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":33,"nom":"Eskignac","prenom":"Amina","appartenance":"Brasserie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":34,"nom":"Korvouze","prenom":"Fatima","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":35,"nom":"Selvoubre","prenom":"Luc","appartenance":"Brasserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":36,"nom":"Fralvichon","prenom":"Étienne","appartenance":"Papeterie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":37,"nom":"Voskivet","prenom":"Philippe","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":38,"nom":"Quilmançon","prenom":"Louise","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":39,"nom":"Luskande","prenom":"Sophie","appartenance":"Fabrique Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":40,"nom":"Abreveron","prenom":"Pierre","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":41,"nom":"Neskeux","prenom":"Philippe","appartenance":"Comptoir Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":42,"nom":"Neskançon","prenom":"Amina","appartenance":"Fabrique Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":43,"nom":"Tolvande","prenom":"Léa","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":44,"nom":"Quilmeron","prenom":"Antoine","appartenance":"Fabrique Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":45,"nom":"Clovumet","prenom":"Guillaume","appartenance":"Verrerie Pravette","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":46,"nom":"Eskeux","prenom":"Michel","appartenance":"Papeterie Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":47,"nom":"Hulvault","prenom":"Louise","appartenance":"Coopérative Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":48,"nom":"Hulveron","prenom":"Catherine","appartenance":"Coopérative Rossolde","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":49,"nom":"Ivrivet","prenom":"François","appartenance":"Menuiserie Sorbagne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":50,"nom":"Eskumet","prenom":"Samuel","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":51,"nom":"Fralvivet","prenom":"Claire","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":52,"nom":"Mirveux","prenom":"Léa","appartenance":"Menuiserie Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":53,"nom":"Gorzesson","prenom":"Karim","appartenance":"Papeterie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":54,"nom":"Fralvumet","prenom":"Jean","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":55,"nom":"Urvançon","prenom":"Manon","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":56,"nom":"Quilmande","prenom":"Sylvie","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":57,"nom":"Gorzichon","prenom":"François","appartenance":"Ateliers Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":58,"nom":"Pruvesson","prenom":"Amina","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":59,"nom":"Quilmière","prenom":"Lucie","appartenance":"Fabrique Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":60,"nom":"Korvesson","prenom":"Fatima","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":61,"nom":"Selvesson","prenom":"Nicolas","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":62,"nom":"Selvivet","prenom":"Louis","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":63,"nom":"Neskesson","prenom":"Louise","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":64,"nom":"Tolvesson","prenom":"Lucie","appartenance":"Filature Zelvaine","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":65,"nom":"Quilmesson","prenom":"Olivier","appartenance":"Comptoir Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":66,"nom":"Luskivet","prenom":"Guillaume","appartenance":"Brasserie Lavorneau","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":67,"nom":"Neskault","prenom":"Gabriel","appartenance":"Menuiserie Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":68,"nom":"Urvumet","prenom":"Antoine","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":69,"nom":"Jusvançon","prenom":"Olivier","appartenance":"Brasserie Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":70,"nom":"Dulvançon","prenom":"Simon","appartenance":"Menuiserie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":71,"nom":"Gorzard","prenom":"Chloé","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":72,"nom":"Pruveux","prenom":"Gabriel","appartenance":"Papeterie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":73,"nom":"Clovière","prenom":"Manon","appartenance":"Ateliers Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":74,"nom":"Obrouze","prenom":"François","appartenance":"Tannerie Olbrecq","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":75,"nom":"Tolveval","prenom":"Karim","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":76,"nom":"Dulvumet","prenom":"Geneviève","appartenance":"Menuiserie Quimvaux","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":77,"nom":"Bérêçàuvïlle-Dœûmôntëÿ","prenom":"Hugo","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":78,"nom":"Gorzeron","prenom":"Claire","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":79,"nom":"Luskumet","prenom":"Hélène","appartenance":"Brasserie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":80,"nom":"Fralvande","prenom":"Louis","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":81,"nom":"Selvault","prenom":"Émilie","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":82,"nom":"Neskumet","prenom":"Léa","appartenance":"Verrerie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":83,"nom":"Korvichon","prenom":"Isabelle","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":84,"nom":"Obrande","prenom":"Michel","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":85,"nom":"Luskeval","prenom":"Isabelle","appartenance":"Brasserie Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":86,"nom":"Tolvoubre","prenom":"Fatima","appartenance":"Papeterie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":87,"nom":"Gorzière","prenom":"Zoé","appartenance":"Menuiserie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":88,"nom":"Korvignac","prenom":"Sylvie","appartenance":"Fabrique Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":89,"nom":"Obreval","prenom":"Antoine","appartenance":"Ateliers Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":90,"nom":"Korveron","prenom":"Michel","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":91,"nom":"Tolvumet","prenom":"Camille","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":92,"nom":"Dulvesson","prenom":"Marie","appartenance":"Ateliers Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":93,"nom":"Jusvard","prenom":"Youssef","appartenance":"Tannerie Aubrenc","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":94,"nom":"Tolvignac","prenom":"Guillaume","appartenance":"Menuiserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":95,"nom":"Mirvotte","prenom":"Gabriel","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":96,"nom":"Urvard","prenom":"Marie","appartenance":"Filature Wassorel","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":97,"nom":"Bralmesson","prenom":"Marie","appartenance":"Coopérative Dravenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":98,"nom":"Neskignac","prenom":"Paul","appartenance":"Brasserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":99,"nom":"Bralmeux","prenom":"Samuel","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":100,"nom":"Ivrotte","prenom":"Raphaël","appartenance":"Coopérative Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":101,"nom":"Luskouze","prenom":"Marc","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":102,"nom":"Jusvotte","prenom":"Sylvie","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":103,"nom":"Bralmoubre","prenom":"Véronique","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":104,"nom":"Selvard","prenom":"Pierre","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":105,"nom":"Hulveux","prenom":"Marie","appartenance":"Coopérative Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":106,"nom":"Pruvotte","prenom":"Sophie","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":107,"nom":"Fralveval","prenom":"Karim","appartenance":"Coopérative Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":108,"nom":"Gorzignac","prenom":"Philippe","appartenance":"Imprimerie Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":109,"nom":"Selvouze","prenom":"Étienne","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":110,"nom":"Hulvichon","prenom":"Louis","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":111,"nom":"Urvoubre","prenom":"Sylvie","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":112,"nom":"Pruveval","prenom":"Pierre","appartenance":"Comptoir Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":113,"nom":"Hulvotte","prenom":"Guillaume","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":114,"nom":"Abrevignac","prenom":"Geneviève","appartenance":"Brasserie Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":115,"nom":"Abrevault","prenom":"Gabriel","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":116,"nom":"Bralmignac","prenom":"Amina","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":117,"nom":"Bralmançon","prenom":"Olivier","appartenance":"Comptoir Lavorneau","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":118,"nom":"Luskoubre","prenom":"Marc","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":119,"nom":"Abrevoubre","prenom":"Geneviève","appartenance":"Menuiserie Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":120,"nom":"Korvoubre","prenom":"Raphaël","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":121,"nom":"Mirvard","prenom":"Pierre","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":122,"nom":"Jusvumet","prenom":"Camille","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":123,"nom":"Cloveval","prenom":"Samuel","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":124,"nom":"Selvière","prenom":"Antoine","appartenance":"Coopérative Dravenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":125,"nom":"Jusvouze","prenom":"Véronique","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":126,"nom":"Neskivet","prenom":"Paul","appartenance":"Comptoir Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":127,"nom":"Dulvande","prenom":"Samuel","appartenance":"Verrerie Pravette","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":128,"nom":"Bralmault","prenom":"Antoine","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":129,"nom":"Abrevière","prenom":"Émilie","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":130,"nom":"Selveval","prenom":"Lucie","appartenance":"Filature Zelvaine","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":131,"nom":"Tolvichon","prenom":"Olivier","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":132,"nom":"Hulvard","prenom":"Fatima","appartenance":"Ateliers Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":133,"nom":"Ivreux","prenom":"Alexandre","appartenance":"Tannerie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":134,"nom":"Korveval","prenom":"Félix","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":135,"nom":"Urvichon","prenom":"Karim","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":136,"nom":"Dulvichon","prenom":"Émilie","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":137,"nom":"Tolvouze","prenom":"Luc","appartenance":"Fabrique Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":138,"nom":"Jusvivet","prenom":"Claire","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":139,"nom":"Quilmault","prenom":"Amina","appartenance":"Coopérative Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":140,"nom":"Ivrouze","prenom":"François","appartenance":"Fonderie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":141,"nom":"Gorzumet","prenom":"Hélène","appartenance":"Fonderie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":142,"nom":"Ivrault","prenom":"Sophie","appartenance":"Coopérative Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":143,"nom":"Bralmière","prenom":"Louise","appartenance":"Tannerie Quimvaux","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":144,"nom":"Korvande","prenom":"Nicolas","appartenance":"Comptoir Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":145,"nom":"Ivrumet","prenom":"Geneviève","appartenance":"Coopérative Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":146,"nom":"Obresson","prenom":"Geneviève","appartenance":"Coopérative Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":147,"nom":"Obrançon","prenom":"Guillaume","appartenance":"Papeterie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":148,"nom":"Urvignac","prenom":"Fatima","appartenance":"Ateliers Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":149,"nom":"Bralmotte","prenom":"Claire","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":150,"nom":"Hulvignac","prenom":"Félix","appartenance":"Imprimerie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":151,"nom":"Clovotte","prenom":"François","appartenance":"Coopérative Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":152,"nom":"Dulvoubre","prenom":"Philippe","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":153,"nom":"Rozveron","prenom":"Samuel","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":154,"nom":"Hulvoubre","prenom":"Pierre","appartenance":"Menuiserie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":155,"nom":"Urvouze","prenom":"Émilie","appartenance":"Comptoir Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":156,"nom":"Korvançon","prenom":"Félix","appartenance":"Fonderie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":157,"nom":"Neskande","prenom":"Émilie","appartenance":"Coopérative Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":158,"nom":"Mirvumet","prenom":"Chantal","appartenance":"Menuiserie Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":159,"nom":"Korvotte","prenom":"Alexandre","appartenance":"Menuiserie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":160,"nom":"Eskeval","prenom":"Zoé","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":161,"nom":"Neskard","prenom":"Camille","appartenance":"Menuiserie Gravelune","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":162,"nom":"Tolveron","prenom":"Manon","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":163,"nom":"Voskoubre","prenom":"Nathalie","appartenance":"Fonderie Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":164,"nom":"Pruvière","prenom":"Sophie","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":165,"nom":"Obrichon","prenom":"Paul","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":166,"nom":"Abrevançon","prenom":"Fatima","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":167,"nom":"Eskière","prenom":"Nathalie","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":168,"nom":"Ivreron","prenom":"Caroline","appartenance":"Imprimerie Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":169,"nom":"Neskoubre","prenom":"Véronique","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":170,"nom":"Urvivet","prenom":"Anne","appartenance":"Fonderie Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":171,"nom":"Hulvançon","prenom":"Olivier","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":172,"nom":"Clovançon","prenom":"Karim","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":173,"nom":"Quilmotte","prenom":"Zoé","appartenance":"Ateliers Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":174,"nom":"Eskouze","prenom":"Félix","appartenance":"Filature Zelvaine","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":175,"nom":"Rozvichon","prenom":"Amina","appartenance":"Verrerie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":176,"nom":"Fralvière","prenom":"Simon","appartenance":"Comptoir Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":177,"nom":"Vandergrolcquesmirtavelle-Ostrovaubelindigues","prenom":"Marc","appartenance":"Tannerie Aubrenc","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":178,"nom":"Hulvière","prenom":"Pierre","appartenance":"Papeterie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":179,"nom":"Jusvault","prenom":"Raphaël","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":180,"nom":"Dulvivet","prenom":"Guillaume","appartenance":"Filature Olbrecq","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":181,"nom":"Fralvoubre","prenom":"Pierre","appartenance":"Imprimerie Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":182,"nom":"Selveux","prenom":"Hélène","appartenance":"Brasserie Lavorneau","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":183,"nom":"Rozvançon","prenom":"Caroline","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":184,"nom":"Korvière","prenom":"Olivier","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":185,"nom":"Ivrichon","prenom":"Nicolas","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":186,"nom":"Korvumet","prenom":"Félix","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":187,"nom":"Voskande","prenom":"Félix","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":188,"nom":"Selvumet","prenom":"Guillaume","appartenance":"Papeterie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":189,"nom":"Urvotte","prenom":"Michel","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":190,"nom":"Eskault","prenom":"Émilie","appartenance":"Coopérative Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":191,"nom":"Bralmeval","prenom":"Hélène","appartenance":"Brasserie Dravenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":192,"nom":"Hulvesson","prenom":"Guillaume","appartenance":"Comptoir Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":193,"nom":"Jusvoubre","prenom":"Caroline","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":194,"nom":"Luskeux","prenom":"Félix","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":195,"nom":"Pruvouze","prenom":"Antoine","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":196,"nom":"Quilmignac","prenom":"Julie","appartenance":"Menuiserie Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":197,"nom":"Tolvançon","prenom":"Étienne","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":198,"nom":"Voskumet","prenom":"Hugo","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":199,"nom":"Quilmumet","prenom":"Manon","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":200,"nom":"Voskouze","prenom":"Marc","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":201,"nom":"Abrevard","prenom":"Samuel","appartenance":"Filature Dravenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":202,"nom":"Luskichon","prenom":"Guillaume","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":203,"nom":"Clovesson","prenom":"Manon","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":204,"nom":"Hulvumet","prenom":"Simon","appartenance":"Ateliers Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":205,"nom":"Voskeron","prenom":"Lucie","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":206,"nom":"Selvignac","prenom":"Thomas","appartenance":"Brasserie Sorbagne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":207,"nom":"Neskeron","prenom":"Jean","appartenance":"Filature Zelvaine","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":208,"nom":"Pruvivet","prenom":"Manon","appartenance":"Menuiserie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":209,"nom":"Clovard","prenom":"Sophie","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":210,"nom":"Gorzeval","prenom":"Étienne","appartenance":"Papeterie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":211,"nom":"Ivroubre","prenom":"Philippe","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":212,"nom":"Luskault","prenom":"Gabriel","appartenance":"Verrerie Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":213,"nom":"Korvard","prenom":"Émilie","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":214,"nom":"Quilmichon","prenom":"Thomas","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":215,"nom":"Eskançon","prenom":"Alexandre","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":216,"nom":"Luskançon","prenom":"Émilie","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":217,"nom":"Dulvault","prenom":"Marc","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":218,"nom":"Rozveval","prenom":"Hélène","appartenance":"Comptoir Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":219,"nom":"Pruvançon","prenom":"Julie","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":220,"nom":"Mirveron","prenom":"Louis","appartenance":"Filature Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":221,"nom":"Eskard","prenom":"François","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":222,"nom":"Bralmouze","prenom":"Hugo","appartenance":"Tannerie Falvigne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":223,"nom":"Ivrançon","prenom":"Louis","appartenance":"Coopérative Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":224,"nom":"Clovivet","prenom":"Karim","appartenance":"Fonderie Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":225,"nom":"Rozvande","prenom":"Léa","appartenance":"Verrerie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":226,"nom":"Hulvouze","prenom":"Amina","appartenance":"Coopérative Dravenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":227,"nom":"Eskesson","prenom":"Étienne","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":228,"nom":"Abreveval","prenom":"Philippe","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":229,"nom":"Quilmeux","prenom":"Émilie","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":230,"nom":"Ivreval","prenom":"Gabriel","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":231,"nom":"Voskotte","prenom":"Simon","appartenance":"Fabrique Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":232,"nom":"Obrignac","prenom":"Chantal","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":233,"nom":"Eskotte","prenom":"Guillaume","appartenance":"Verrerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":234,"nom":"Obrard","prenom":"Léa","appartenance":"Comptoir Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":235,"nom":"Gorzivet","prenom":"Guillaume","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":236,"nom":"Mirvançon","prenom":"Geneviève","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":237,"nom":"Selvotte","prenom":"Julie","appartenance":"Comptoir Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":238,"nom":"Mirvesson","prenom":"Amina","appartenance":"Brasserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":239,"nom":"Luskignac","prenom":"Isabelle","appartenance":"Papeterie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":240,"nom":"Obrumet","prenom":"Chloé","appartenance":"Comptoir Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":241,"nom":"Abrevesson","prenom":"Anne","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":242,"nom":"Rozvumet","prenom":"Fatima","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":243,"nom":"Mirvoubre","prenom":"Guillaume","appartenance":"Comptoir Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":244,"nom":"Rozvoubre","prenom":"Véronique","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":245,"nom":"Gorzotte","prenom":"Claire","appartenance":"Menuiserie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":246,"nom":"Ivrière","prenom":"Antoine","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":247,"nom":"Selvichon","prenom":"Isabelle","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":248,"nom":"Urvault","prenom":"Isabelle","appartenance":"Comptoir Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":249,"nom":"Voskesson","prenom":"Julie","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":250,"nom":"Abrevotte","prenom":"Chantal","appartenance":"Ateliers Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":251,"nom":"Neskotte","prenom":"Amina","appartenance":"Fabrique Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":252,"nom":"Voskançon","prenom":"Étienne","appartenance":"Menuiserie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":253,"nom":"Fralveron","prenom":"Zoé","appartenance":"Menuiserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":254,"nom":"Rozvouze","prenom":"Marc","appartenance":"Ateliers Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":255,"nom":"Abreveux","prenom":"Sylvie","appartenance":"Comptoir Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":256,"nom":"Pruvignac","prenom":"Karim","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":257,"nom":"Mirveval","prenom":"Zoé","appartenance":"Fabrique Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":258,"nom":"Fralvignac","prenom":"Philippe","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":259,"nom":"Gorzeux","prenom":"Gabriel","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":260,"nom":"Urveval","prenom":"Jean","appartenance":"Menuiserie Quimvaux","courriel":null,"titrePressenti":null,"notes":null,"exclu":false}
],
"tables": [
{"id":1,"numero":1,"sieges":null,"forme":"ronde","position":{"x":0,"y":0}},
{"id":2,"numero":2,"sieges":null,"forme":"ronde","position":{"x":300,"y":0}},
{"id":3,"numero":3,"sieges":null,"forme":"ronde","position":{"x":600,"y":0}},
{"id":4,"numero":4,"sieges":null,"forme":"ronde","position":{"x":900,"y":0}},
{"id":5,"numero":5,"sieges":null,"forme":"ronde","position":{"x":1200,"y":0}},
{"id":6,"numero":6,"sieges":null,"forme":"ronde","position":{"x":1500,"y":0}},
{"id":7,"numero":7,"sieges":null,"forme":"ronde","position":{"x":0,"y":300}},
{"id":8,"numero":8,"sieges":null,"forme":"ronde","position":{"x":300,"y":300}},
{"id":9,"numero":9,"sieges":null,"forme":"ronde","position":{"x":600,"y":300}},
{"id":10,"numero":10,"sieges":null,"forme":"ronde","position":{"x":900,"y":300}},
{"id":11,"numero":11,"sieges":null,"forme":"ronde","position":{"x":1200,"y":300}},
{"id":12,"numero":12,"sieges":null,"forme":"ronde","position":{"x":1500,"y":300}},
{"id":13,"numero":13,"sieges":null,"forme":"ronde","position":{"x":0,"y":600}},
{"id":14,"numero":14,"sieges":null,"forme":"ronde","position":{"x":300,"y":600}},
{"id":15,"numero":15,"sieges":null,"forme":"ronde","position":{"x":600,"y":600}},
{"id":16,"numero":16,"sieges":null,"forme":"ronde","position":{"x":900,"y":600}},
{"id":17,"numero":17,"sieges":null,"forme":"ronde","position":{"x":1200,"y":600}},
{"id":18,"numero":18,"sieges":null,"forme":"ronde","position":{"x":1500,"y":600}},
{"id":19,"numero":19,"sieges":null,"forme":"ronde","position":{"x":0,"y":900}},
{"id":20,"numero":20,"sieges":null,"forme":"ronde","position":{"x":300,"y":900}},
{"id":21,"numero":21,"sieges":null,"forme":"ronde","position":{"x":600,"y":900}},
{"id":22,"numero":22,"sieges":null,"forme":"ronde","position":{"x":900,"y":900}},
{"id":23,"numero":23,"sieges":null,"forme":"ronde","position":{"x":1200,"y":900}},
{"id":24,"numero":24,"sieges":null,"forme":"ronde","position":{"x":1500,"y":900}},
{"id":25,"numero":25,"sieges":null,"forme":"ronde","position":{"x":0,"y":1200}},
{"id":26,"numero":26,"sieges":null,"forme":"ronde","position":{"x":300,"y":1200}},
{"id":27,"numero":27,"sieges":null,"forme":"ronde","position":{"x":600,"y":1200}},
{"id":28,"numero":28,"sieges":null,"forme":"ronde","position":{"x":900,"y":1200}},
{"id":29,"numero":29,"sieges":null,"forme":"ronde","position":{"x":1200,"y":1200}},
{"id":30,"numero":30,"sieges":null,"forme":"ronde","position":{"x":1500,"y":1200}},
{"id":31,"numero":31,"sieges":null,"forme":"ronde","position":{"x":0,"y":1500}},
{"id":32,"numero":32,"sieges":null,"forme":"ronde","position":{"x":300,"y":1500}},
{"id":33,"numero":33,"sieges":null,"forme":"ronde","position":{"x":600,"y":1500}}
],
"reservations": [
{"participant":2,"table":17,"siege":1,"portee":"tous","tour":null},
{"participant":8,"table":22,"siege":1,"portee":"tous","tour":null},
{"participant":18,"table":6,"siege":1,"portee":"tous","tour":null},
{"participant":34,"table":3,"siege":1,"portee":"tous","tour":null},
{"participant":48,"table":15,"siege":1,"portee":"tous","tour":null},
{"participant":49,"table":32,"siege":1,"portee":"tous","tour":null},
{"participant":54,"table":26,"siege":1,"portee":"tous","tour":null},
{"participant":55,"table":1,"siege":1,"portee":"tous","tour":null},
{"participant":62,"table":25,"siege":1,"portee":"tous","tour":null},
{"participant":74,"table":31,"siege":1,"portee":"tous","tour":null},
{"participant":76,"table":30,"siege":1,"portee":"tous","tour":null},
{"participant":90,"table":8,"siege":1,"portee":"tous","tour":null},
{"participant":93,"table":21,"siege":1,"portee":"tous","tour":null},
{"participant":96,"table":14,"siege":1,"portee":"tous","tour":null},
{"participant":97,"table":27,"siege":1,"portee":"tous","tour":null},
{"participant":99,"table":33,"siege":1,"portee":"tous","tour":null},
{"participant":106,"table":12,"siege":1,"portee":"tous","tour":null},
{"participant":115,"table":24,"siege":1,"portee":"tous","tour":null},
{"participant":128,"table":4,"siege":1,"portee":"tous","tour":null},
{"participant":129,"table":29,"siege":1,"portee":"tous","tour":null},
{"participant":138,"table":20,"siege":1,"portee":"tous","tour":null},
{"participant":161,"table":16,"siege":1,"portee":"tous","tour":null},
{"participant":162,"table":5,"siege":1,"portee":"tous","tour":null},
{"participant":166,"table":13,"siege":1,"portee":"tous","tour":null},
{"participant":177,"table":10,"siege":1,"portee":"tous","tour":null},
{"participant":180,"table":18,"siege":1,"portee":"tous","tour":null},
{"participant":187,"table":7,"siege":1,"portee":"tous","tour":null},
{"participant":197,"table":11,"siege":1,"portee":"tous","tour":null},
{"participant":202,"table":2,"siege":1,"portee":"tous","tour":null},
{"participant":206,"table":28,"siege":1,"portee":"tous","tour":null},
{"participant":211,"table":19,"siege":1,"portee":"tous","tour":null},
{"participant":222,"table":23,"siege":1,"portee":"tous","tour":null},
{"participant":230,"table":9,"siege":1,"portee":"tous","tour":null}
],
"titres": [
{"table":1,"siege":1,"libelle":"animation"},
{"table":2,"siege":1,"libelle":"animation"},
{"table":3,"siege":1,"libelle":"animation"},
{"table":4,"siege":1,"libelle":"animation"},
{"table":5,"siege":1,"libelle":"animation"},
{"table":6,"siege":1,"libelle":"animation"},
{"table":7,"siege":1,"libelle":"animation"},
{"table":8,"siege":1,"libelle":"animation"},
{"table":9,"siege":1,"libelle":"animation"},
{"table":10,"siege":1,"libelle":"animation"},
{"table":11,"siege":1,"libelle":"animation"},
{"table":12,"siege":1,"libelle":"animation"},
{"table":13,"siege":1,"libelle":"animation"},
{"table":14,"siege":1,"libelle":"animation"},
{"table":15,"siege":1,"libelle":"animation"},
{"table":16,"siege":1,"libelle":"animation"},
{"table":17,"siege":1,"libelle":"animation"},
{"table":18,"siege":1,"libelle":"animation"},
{"table":19,"siege":1,"libelle":"animation"},
{"table":20,"siege":1,"libelle":"animation"},
{"table":21,"siege":1,"libelle":"animation"},
{"table":22,"siege":1,"libelle":"animation"},
{"table":23,"siege":1,"libelle":"animation"},
{"table":24,"siege":1,"libelle":"animation"},
{"table":25,"siege":1,"libelle":"animation"},
{"table":26,"siege":1,"libelle":"animation"},
{"table":27,"siege":1,"libelle":"animation"},
{"table":28,"siege":1,"libelle":"animation"},
{"table":29,"siege":1,"libelle":"animation"},
{"table":30,"siege":1,"libelle":"animation"},
{"table":31,"siege":1,"libelle":"animation"},
{"table":32,"siege":1,"libelle":"animation"},
{"table":33,"siege":1,"libelle":"animation"}
],
"propositions": [],
"retenu": null
}
}

View file

@ -0,0 +1,377 @@
{
"entete": {"format":1,"produitVersion":"2026.10.05.01","revision":1,"comptes":{"participants":260,"tables":33,"reservations":33,"titres":33,"propositions":0,"retenu":0}},
"charge": {
"evenement": {"id":"demo:grande","nom":"Grande démonstration","date":null,"siegesParDefaut":8,"tours":4,"unite":"cm","etat":"brouillon","filiation":null},
"reglages": {"separerAppartenances":true,"nouveauxVoisins":true,"nouvelleTable":true,"varierAppartenances":true,"attribuerSieges":false,"generation":{"nombre":5,"arret":200000,"historique":1000}},
"prochainsIds": {"participant":261,"table":34,"proposition":1},
"participants": [
{"id":1,"nom":"Mirvault","prenom":"Isabelle","appartenance":"Comptoir Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":2,"nom":"Neskière","prenom":"Youssef","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":3,"nom":"Abrevande","prenom":"Félix","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":4,"nom":"Fralvançon","prenom":"Hugo","appartenance":"Brasserie Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":5,"nom":"Fralveux","prenom":"Manon","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":6,"nom":"Luskotte","prenom":"Catherine","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":7,"nom":"WAMMERWOLM-MAWWEMMEL","prenom":"Zoé","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":8,"nom":"Neskichon","prenom":"Louise","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":9,"nom":"Urvesson","prenom":"Catherine","appartenance":"Fabrique Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":10,"nom":"Hulvivet","prenom":"Karim","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":11,"nom":"Selveron","prenom":"Caroline","appartenance":"Ateliers Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":12,"nom":"Korvault","prenom":"Catherine","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":13,"nom":"Fralvotte","prenom":"Caroline","appartenance":"Fonderie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":14,"nom":"Jusvesson","prenom":"Geneviève","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":15,"nom":"Abrevumet","prenom":"Paul","appartenance":"Ateliers Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":16,"nom":"Fralvault","prenom":"Léa","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":17,"nom":"Obrivet","prenom":"Fatima","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":18,"nom":"Voskeval","prenom":"Philippe","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":19,"nom":"Dulveval","prenom":"Zoé","appartenance":"Menuiserie Quimvaux","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":20,"nom":"Korveux","prenom":"Paul","appartenance":"Filature Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":21,"nom":"Luskière","prenom":"Julie","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":22,"nom":"Clovande","prenom":"Émilie","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":23,"nom":"Hulvande","prenom":"Paul","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":24,"nom":"Fralvesson","prenom":"Hugo","appartenance":"Filature Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":25,"nom":"Urvière","prenom":"Isabelle","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":26,"nom":"Rozvotte","prenom":"Thomas","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":27,"nom":"Bralmumet","prenom":"Isabelle","appartenance":"Verrerie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":28,"nom":"Dulvouze","prenom":"Catherine","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":29,"nom":"Voskichon","prenom":"Marc","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":30,"nom":"Mirvivet","prenom":"Simon","appartenance":"Ateliers Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":31,"nom":"Clovault","prenom":"Louise","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":32,"nom":"Voskeux","prenom":"Lucie","appartenance":"Coopérative Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":33,"nom":"Eskignac","prenom":"Amina","appartenance":"Brasserie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":34,"nom":"Korvouze","prenom":"Fatima","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":35,"nom":"Selvoubre","prenom":"Luc","appartenance":"Brasserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":36,"nom":"Fralvichon","prenom":"Étienne","appartenance":"Papeterie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":37,"nom":"Voskivet","prenom":"Philippe","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":38,"nom":"Quilmançon","prenom":"Louise","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":39,"nom":"Luskande","prenom":"Sophie","appartenance":"Fabrique Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":40,"nom":"Abreveron","prenom":"Pierre","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":41,"nom":"Neskeux","prenom":"Philippe","appartenance":"Comptoir Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":42,"nom":"Neskançon","prenom":"Amina","appartenance":"Fabrique Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":43,"nom":"Tolvande","prenom":"Léa","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":44,"nom":"Quilmeron","prenom":"Antoine","appartenance":"Fabrique Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":45,"nom":"Clovumet","prenom":"Guillaume","appartenance":"Verrerie Pravette","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":46,"nom":"Eskeux","prenom":"Michel","appartenance":"Papeterie Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":47,"nom":"Hulvault","prenom":"Louise","appartenance":"Coopérative Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":48,"nom":"Hulveron","prenom":"Catherine","appartenance":"Coopérative Rossolde","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":49,"nom":"Ivrivet","prenom":"François","appartenance":"Menuiserie Sorbagne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":50,"nom":"Eskumet","prenom":"Samuel","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":51,"nom":"Fralvivet","prenom":"Claire","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":52,"nom":"Mirveux","prenom":"Léa","appartenance":"Menuiserie Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":53,"nom":"Gorzesson","prenom":"Karim","appartenance":"Papeterie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":54,"nom":"Fralvumet","prenom":"Jean","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":55,"nom":"Urvançon","prenom":"Manon","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":56,"nom":"Quilmande","prenom":"Sylvie","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":57,"nom":"Gorzichon","prenom":"François","appartenance":"Ateliers Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":58,"nom":"Pruvesson","prenom":"Amina","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":59,"nom":"Quilmière","prenom":"Lucie","appartenance":"Fabrique Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":60,"nom":"Korvesson","prenom":"Fatima","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":61,"nom":"Selvesson","prenom":"Nicolas","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":62,"nom":"Selvivet","prenom":"Louis","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":63,"nom":"Neskesson","prenom":"Louise","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":64,"nom":"Tolvesson","prenom":"Lucie","appartenance":"Filature Zelvaine","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":65,"nom":"Quilmesson","prenom":"Olivier","appartenance":"Comptoir Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":66,"nom":"Luskivet","prenom":"Guillaume","appartenance":"Brasserie Lavorneau","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":67,"nom":"Neskault","prenom":"Gabriel","appartenance":"Menuiserie Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":68,"nom":"Urvumet","prenom":"Antoine","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":69,"nom":"Jusvançon","prenom":"Olivier","appartenance":"Brasserie Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":70,"nom":"Dulvançon","prenom":"Simon","appartenance":"Menuiserie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":71,"nom":"Gorzard","prenom":"Chloé","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":72,"nom":"Pruveux","prenom":"Gabriel","appartenance":"Papeterie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":73,"nom":"Clovière","prenom":"Manon","appartenance":"Ateliers Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":74,"nom":"Obrouze","prenom":"François","appartenance":"Tannerie Olbrecq","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":75,"nom":"Tolveval","prenom":"Karim","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":76,"nom":"Dulvumet","prenom":"Geneviève","appartenance":"Menuiserie Quimvaux","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":77,"nom":"Bérêçàuvïlle-Dœûmôntëÿ","prenom":"Hugo","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":78,"nom":"Gorzeron","prenom":"Claire","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":79,"nom":"Luskumet","prenom":"Hélène","appartenance":"Brasserie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":80,"nom":"Fralvande","prenom":"Louis","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":81,"nom":"Selvault","prenom":"Émilie","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":82,"nom":"Neskumet","prenom":"Léa","appartenance":"Verrerie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":83,"nom":"Korvichon","prenom":"Isabelle","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":84,"nom":"Obrande","prenom":"Michel","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":85,"nom":"Luskeval","prenom":"Isabelle","appartenance":"Brasserie Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":86,"nom":"Tolvoubre","prenom":"Fatima","appartenance":"Papeterie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":87,"nom":"Gorzière","prenom":"Zoé","appartenance":"Menuiserie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":88,"nom":"Korvignac","prenom":"Sylvie","appartenance":"Fabrique Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":89,"nom":"Obreval","prenom":"Antoine","appartenance":"Ateliers Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":90,"nom":"Korveron","prenom":"Michel","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":91,"nom":"Tolvumet","prenom":"Camille","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":92,"nom":"Dulvesson","prenom":"Marie","appartenance":"Ateliers Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":93,"nom":"Jusvard","prenom":"Youssef","appartenance":"Tannerie Aubrenc","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":94,"nom":"Tolvignac","prenom":"Guillaume","appartenance":"Menuiserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":95,"nom":"Mirvotte","prenom":"Gabriel","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":96,"nom":"Urvard","prenom":"Marie","appartenance":"Filature Wassorel","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":97,"nom":"Bralmesson","prenom":"Marie","appartenance":"Coopérative Dravenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":98,"nom":"Neskignac","prenom":"Paul","appartenance":"Brasserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":99,"nom":"Bralmeux","prenom":"Samuel","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":100,"nom":"Ivrotte","prenom":"Raphaël","appartenance":"Coopérative Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":101,"nom":"Luskouze","prenom":"Marc","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":102,"nom":"Jusvotte","prenom":"Sylvie","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":103,"nom":"Bralmoubre","prenom":"Véronique","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":104,"nom":"Selvard","prenom":"Pierre","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":105,"nom":"Hulveux","prenom":"Marie","appartenance":"Coopérative Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":106,"nom":"Pruvotte","prenom":"Sophie","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":107,"nom":"Fralveval","prenom":"Karim","appartenance":"Coopérative Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":108,"nom":"Gorzignac","prenom":"Philippe","appartenance":"Imprimerie Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":109,"nom":"Selvouze","prenom":"Étienne","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":110,"nom":"Hulvichon","prenom":"Louis","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":111,"nom":"Urvoubre","prenom":"Sylvie","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":112,"nom":"Pruveval","prenom":"Pierre","appartenance":"Comptoir Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":113,"nom":"Hulvotte","prenom":"Guillaume","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":114,"nom":"Abrevignac","prenom":"Geneviève","appartenance":"Brasserie Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":115,"nom":"Abrevault","prenom":"Gabriel","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":116,"nom":"Bralmignac","prenom":"Amina","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":117,"nom":"Bralmançon","prenom":"Olivier","appartenance":"Comptoir Lavorneau","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":118,"nom":"Luskoubre","prenom":"Marc","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":119,"nom":"Abrevoubre","prenom":"Geneviève","appartenance":"Menuiserie Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":120,"nom":"Korvoubre","prenom":"Raphaël","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":121,"nom":"Mirvard","prenom":"Pierre","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":122,"nom":"Jusvumet","prenom":"Camille","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":123,"nom":"Cloveval","prenom":"Samuel","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":124,"nom":"Selvière","prenom":"Antoine","appartenance":"Coopérative Dravenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":125,"nom":"Jusvouze","prenom":"Véronique","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":126,"nom":"Neskivet","prenom":"Paul","appartenance":"Comptoir Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":127,"nom":"Dulvande","prenom":"Samuel","appartenance":"Verrerie Pravette","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":128,"nom":"Bralmault","prenom":"Antoine","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":129,"nom":"Abrevière","prenom":"Émilie","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":130,"nom":"Selveval","prenom":"Lucie","appartenance":"Filature Zelvaine","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":131,"nom":"Tolvichon","prenom":"Olivier","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":132,"nom":"Hulvard","prenom":"Fatima","appartenance":"Ateliers Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":133,"nom":"Ivreux","prenom":"Alexandre","appartenance":"Tannerie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":134,"nom":"Korveval","prenom":"Félix","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":135,"nom":"Urvichon","prenom":"Karim","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":136,"nom":"Dulvichon","prenom":"Émilie","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":137,"nom":"Tolvouze","prenom":"Luc","appartenance":"Fabrique Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":138,"nom":"Jusvivet","prenom":"Claire","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":139,"nom":"Quilmault","prenom":"Amina","appartenance":"Coopérative Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":140,"nom":"Ivrouze","prenom":"François","appartenance":"Fonderie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":141,"nom":"Gorzumet","prenom":"Hélène","appartenance":"Fonderie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":142,"nom":"Ivrault","prenom":"Sophie","appartenance":"Coopérative Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":143,"nom":"Bralmière","prenom":"Louise","appartenance":"Tannerie Quimvaux","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":144,"nom":"Korvande","prenom":"Nicolas","appartenance":"Comptoir Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":145,"nom":"Ivrumet","prenom":"Geneviève","appartenance":"Coopérative Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":146,"nom":"Obresson","prenom":"Geneviève","appartenance":"Coopérative Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":147,"nom":"Obrançon","prenom":"Guillaume","appartenance":"Papeterie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":148,"nom":"Urvignac","prenom":"Fatima","appartenance":"Ateliers Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":149,"nom":"Bralmotte","prenom":"Claire","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":150,"nom":"Hulvignac","prenom":"Félix","appartenance":"Imprimerie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":151,"nom":"Clovotte","prenom":"François","appartenance":"Coopérative Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":152,"nom":"Dulvoubre","prenom":"Philippe","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":153,"nom":"Rozveron","prenom":"Samuel","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":154,"nom":"Hulvoubre","prenom":"Pierre","appartenance":"Menuiserie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":155,"nom":"Urvouze","prenom":"Émilie","appartenance":"Comptoir Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":156,"nom":"Korvançon","prenom":"Félix","appartenance":"Fonderie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":157,"nom":"Neskande","prenom":"Émilie","appartenance":"Coopérative Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":158,"nom":"Mirvumet","prenom":"Chantal","appartenance":"Menuiserie Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":159,"nom":"Korvotte","prenom":"Alexandre","appartenance":"Menuiserie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":160,"nom":"Eskeval","prenom":"Zoé","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":161,"nom":"Neskard","prenom":"Camille","appartenance":"Menuiserie Gravelune","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":162,"nom":"Tolveron","prenom":"Manon","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":163,"nom":"Voskoubre","prenom":"Nathalie","appartenance":"Fonderie Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":164,"nom":"Pruvière","prenom":"Sophie","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":165,"nom":"Obrichon","prenom":"Paul","appartenance":"Ateliers Rossolde","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":166,"nom":"Abrevançon","prenom":"Fatima","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":167,"nom":"Eskière","prenom":"Nathalie","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":168,"nom":"Ivreron","prenom":"Caroline","appartenance":"Imprimerie Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":169,"nom":"Neskoubre","prenom":"Véronique","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":170,"nom":"Urvivet","prenom":"Anne","appartenance":"Fonderie Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":171,"nom":"Hulvançon","prenom":"Olivier","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":172,"nom":"Clovançon","prenom":"Karim","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":173,"nom":"Quilmotte","prenom":"Zoé","appartenance":"Ateliers Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":174,"nom":"Eskouze","prenom":"Félix","appartenance":"Filature Zelvaine","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":175,"nom":"Rozvichon","prenom":"Amina","appartenance":"Verrerie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":176,"nom":"Fralvière","prenom":"Simon","appartenance":"Comptoir Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":177,"nom":"Vandergrolcquesmirtavelle-Ostrovaubelindigues","prenom":"Marc","appartenance":"Tannerie Aubrenc","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":178,"nom":"Hulvière","prenom":"Pierre","appartenance":"Papeterie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":179,"nom":"Jusvault","prenom":"Raphaël","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":180,"nom":"Dulvivet","prenom":"Guillaume","appartenance":"Filature Olbrecq","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":181,"nom":"Fralvoubre","prenom":"Pierre","appartenance":"Imprimerie Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":182,"nom":"Selveux","prenom":"Hélène","appartenance":"Brasserie Lavorneau","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":183,"nom":"Rozvançon","prenom":"Caroline","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":184,"nom":"Korvière","prenom":"Olivier","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":185,"nom":"Ivrichon","prenom":"Nicolas","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":186,"nom":"Korvumet","prenom":"Félix","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":187,"nom":"Voskande","prenom":"Félix","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":188,"nom":"Selvumet","prenom":"Guillaume","appartenance":"Papeterie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":189,"nom":"Urvotte","prenom":"Michel","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":190,"nom":"Eskault","prenom":"Émilie","appartenance":"Coopérative Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":191,"nom":"Bralmeval","prenom":"Hélène","appartenance":"Brasserie Dravenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":192,"nom":"Hulvesson","prenom":"Guillaume","appartenance":"Comptoir Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":193,"nom":"Jusvoubre","prenom":"Caroline","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":194,"nom":"Luskeux","prenom":"Félix","appartenance":"Tannerie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":195,"nom":"Pruvouze","prenom":"Antoine","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":196,"nom":"Quilmignac","prenom":"Julie","appartenance":"Menuiserie Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":197,"nom":"Tolvançon","prenom":"Étienne","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":198,"nom":"Voskumet","prenom":"Hugo","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":199,"nom":"Quilmumet","prenom":"Manon","appartenance":"Fonderie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":200,"nom":"Voskouze","prenom":"Marc","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":201,"nom":"Abrevard","prenom":"Samuel","appartenance":"Filature Dravenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":202,"nom":"Luskichon","prenom":"Guillaume","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":203,"nom":"Clovesson","prenom":"Manon","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":204,"nom":"Hulvumet","prenom":"Simon","appartenance":"Ateliers Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":205,"nom":"Voskeron","prenom":"Lucie","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":206,"nom":"Selvignac","prenom":"Thomas","appartenance":"Brasserie Sorbagne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":207,"nom":"Neskeron","prenom":"Jean","appartenance":"Filature Zelvaine","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":208,"nom":"Pruvivet","prenom":"Manon","appartenance":"Menuiserie Aubrenc","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":209,"nom":"Clovard","prenom":"Sophie","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":210,"nom":"Gorzeval","prenom":"Étienne","appartenance":"Papeterie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":211,"nom":"Ivroubre","prenom":"Philippe","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":212,"nom":"Luskault","prenom":"Gabriel","appartenance":"Verrerie Gravelune","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":213,"nom":"Korvard","prenom":"Émilie","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":214,"nom":"Quilmichon","prenom":"Thomas","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":215,"nom":"Eskançon","prenom":"Alexandre","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":216,"nom":"Luskançon","prenom":"Émilie","appartenance":"Ateliers Tavrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":217,"nom":"Dulvault","prenom":"Marc","appartenance":"Verrerie Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":218,"nom":"Rozveval","prenom":"Hélène","appartenance":"Comptoir Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":219,"nom":"Pruvançon","prenom":"Julie","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":220,"nom":"Mirveron","prenom":"Louis","appartenance":"Filature Wassorel","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":221,"nom":"Eskard","prenom":"François","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":222,"nom":"Bralmouze","prenom":"Hugo","appartenance":"Tannerie Falvigne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":223,"nom":"Ivrançon","prenom":"Louis","appartenance":"Coopérative Morvalle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":224,"nom":"Clovivet","prenom":"Karim","appartenance":"Fonderie Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":225,"nom":"Rozvande","prenom":"Léa","appartenance":"Verrerie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":226,"nom":"Hulvouze","prenom":"Amina","appartenance":"Coopérative Dravenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":227,"nom":"Eskesson","prenom":"Étienne","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":228,"nom":"Abreveval","prenom":"Philippe","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":229,"nom":"Quilmeux","prenom":"Émilie","appartenance":"Ateliers Falvigne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":230,"nom":"Ivreval","prenom":"Gabriel","appartenance":"Verrerie Vaubrenne","courriel":null,"titrePressenti":"animation","notes":null,"exclu":false},
{"id":231,"nom":"Voskotte","prenom":"Simon","appartenance":"Fabrique Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":232,"nom":"Obrignac","prenom":"Chantal","appartenance":"Imprimerie Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":233,"nom":"Eskotte","prenom":"Guillaume","appartenance":"Verrerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":234,"nom":"Obrard","prenom":"Léa","appartenance":"Comptoir Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":235,"nom":"Gorzivet","prenom":"Guillaume","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":236,"nom":"Mirvançon","prenom":"Geneviève","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":237,"nom":"Selvotte","prenom":"Julie","appartenance":"Comptoir Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":238,"nom":"Mirvesson","prenom":"Amina","appartenance":"Brasserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":239,"nom":"Luskignac","prenom":"Isabelle","appartenance":"Papeterie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":240,"nom":"Obrumet","prenom":"Chloé","appartenance":"Comptoir Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":241,"nom":"Abrevesson","prenom":"Anne","appartenance":"Fonderie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":242,"nom":"Rozvumet","prenom":"Fatima","appartenance":"Menuiserie Hostrenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":243,"nom":"Mirvoubre","prenom":"Guillaume","appartenance":"Comptoir Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":244,"nom":"Rozvoubre","prenom":"Véronique","appartenance":"Fonderie Xandrelle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":245,"nom":"Gorzotte","prenom":"Claire","appartenance":"Menuiserie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":246,"nom":"Ivrière","prenom":"Antoine","appartenance":"Fabrique Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":247,"nom":"Selvichon","prenom":"Isabelle","appartenance":"Tannerie Nervanche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":248,"nom":"Urvault","prenom":"Isabelle","appartenance":"Comptoir Yzorne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":249,"nom":"Voskesson","prenom":"Julie","appartenance":"Brasserie Belvarre","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":250,"nom":"Abrevotte","prenom":"Chantal","appartenance":"Ateliers Ivoranche","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":251,"nom":"Neskotte","prenom":"Amina","appartenance":"Fabrique Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":252,"nom":"Voskançon","prenom":"Étienne","appartenance":"Menuiserie Olbrecq","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":253,"nom":"Fralveron","prenom":"Zoé","appartenance":"Menuiserie Sorbagne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":254,"nom":"Rozvouze","prenom":"Marc","appartenance":"Ateliers Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":255,"nom":"Abreveux","prenom":"Sylvie","appartenance":"Comptoir Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":256,"nom":"Pruvignac","prenom":"Karim","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":257,"nom":"Mirveval","prenom":"Zoé","appartenance":"Fabrique Kalvenne","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":258,"nom":"Fralvignac","prenom":"Philippe","appartenance":"Menuiserie Estrolle","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":259,"nom":"Gorzeux","prenom":"Gabriel","appartenance":"Menuiserie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":260,"nom":"Urveval","prenom":"Jean","appartenance":"Menuiserie Quimvaux","courriel":null,"titrePressenti":null,"notes":null,"exclu":false}
],
"tables": [
{"id":1,"numero":1,"sieges":null,"forme":"ronde","position":{"x":0,"y":0}},
{"id":2,"numero":2,"sieges":null,"forme":"ronde","position":{"x":300,"y":0}},
{"id":3,"numero":3,"sieges":null,"forme":"ronde","position":{"x":600,"y":0}},
{"id":4,"numero":4,"sieges":null,"forme":"ronde","position":{"x":900,"y":0}},
{"id":5,"numero":5,"sieges":null,"forme":"ronde","position":{"x":1200,"y":0}},
{"id":6,"numero":6,"sieges":null,"forme":"ronde","position":{"x":1500,"y":0}},
{"id":7,"numero":7,"sieges":null,"forme":"ronde","position":{"x":0,"y":300}},
{"id":8,"numero":8,"sieges":null,"forme":"ronde","position":{"x":300,"y":300}},
{"id":9,"numero":9,"sieges":null,"forme":"ronde","position":{"x":600,"y":300}},
{"id":10,"numero":10,"sieges":null,"forme":"ronde","position":{"x":900,"y":300}},
{"id":11,"numero":11,"sieges":null,"forme":"ronde","position":{"x":1200,"y":300}},
{"id":12,"numero":12,"sieges":null,"forme":"ronde","position":{"x":1500,"y":300}},
{"id":13,"numero":13,"sieges":null,"forme":"ronde","position":{"x":0,"y":600}},
{"id":14,"numero":14,"sieges":null,"forme":"ronde","position":{"x":300,"y":600}},
{"id":15,"numero":15,"sieges":null,"forme":"ronde","position":{"x":600,"y":600}},
{"id":16,"numero":16,"sieges":null,"forme":"ronde","position":{"x":900,"y":600}},
{"id":17,"numero":17,"sieges":null,"forme":"ronde","position":{"x":1200,"y":600}},
{"id":18,"numero":18,"sieges":null,"forme":"ronde","position":{"x":1500,"y":600}},
{"id":19,"numero":19,"sieges":null,"forme":"ronde","position":{"x":0,"y":900}},
{"id":20,"numero":20,"sieges":null,"forme":"ronde","position":{"x":300,"y":900}},
{"id":21,"numero":21,"sieges":null,"forme":"ronde","position":{"x":600,"y":900}},
{"id":22,"numero":22,"sieges":null,"forme":"ronde","position":{"x":900,"y":900}},
{"id":23,"numero":23,"sieges":null,"forme":"ronde","position":{"x":1200,"y":900}},
{"id":24,"numero":24,"sieges":null,"forme":"ronde","position":{"x":1500,"y":900}},
{"id":25,"numero":25,"sieges":null,"forme":"ronde","position":{"x":0,"y":1200}},
{"id":26,"numero":26,"sieges":null,"forme":"ronde","position":{"x":300,"y":1200}},
{"id":27,"numero":27,"sieges":null,"forme":"ronde","position":{"x":600,"y":1200}},
{"id":28,"numero":28,"sieges":null,"forme":"ronde","position":{"x":900,"y":1200}},
{"id":29,"numero":29,"sieges":null,"forme":"ronde","position":{"x":1200,"y":1200}},
{"id":30,"numero":30,"sieges":7,"forme":"ronde","position":{"x":1500,"y":1200}},
{"id":31,"numero":31,"sieges":7,"forme":"ronde","position":{"x":0,"y":1500}},
{"id":32,"numero":32,"sieges":7,"forme":"ronde","position":{"x":300,"y":1500}},
{"id":33,"numero":33,"sieges":7,"forme":"ronde","position":{"x":600,"y":1500}}
],
"reservations": [
{"participant":2,"table":17,"siege":1,"portee":"tous","tour":null},
{"participant":8,"table":22,"siege":1,"portee":"tous","tour":null},
{"participant":18,"table":6,"siege":1,"portee":"tous","tour":null},
{"participant":34,"table":3,"siege":1,"portee":"tous","tour":null},
{"participant":48,"table":15,"siege":1,"portee":"tous","tour":null},
{"participant":49,"table":32,"siege":1,"portee":"tous","tour":null},
{"participant":54,"table":26,"siege":1,"portee":"tous","tour":null},
{"participant":55,"table":1,"siege":1,"portee":"tous","tour":null},
{"participant":62,"table":25,"siege":1,"portee":"tous","tour":null},
{"participant":74,"table":31,"siege":1,"portee":"tous","tour":null},
{"participant":76,"table":30,"siege":1,"portee":"tous","tour":null},
{"participant":90,"table":8,"siege":1,"portee":"tous","tour":null},
{"participant":93,"table":21,"siege":1,"portee":"tous","tour":null},
{"participant":96,"table":14,"siege":1,"portee":"tous","tour":null},
{"participant":97,"table":27,"siege":1,"portee":"tous","tour":null},
{"participant":99,"table":33,"siege":1,"portee":"tous","tour":null},
{"participant":106,"table":12,"siege":1,"portee":"tous","tour":null},
{"participant":115,"table":24,"siege":1,"portee":"tous","tour":null},
{"participant":128,"table":4,"siege":1,"portee":"tous","tour":null},
{"participant":129,"table":29,"siege":1,"portee":"tous","tour":null},
{"participant":138,"table":20,"siege":1,"portee":"tous","tour":null},
{"participant":161,"table":16,"siege":1,"portee":"tous","tour":null},
{"participant":162,"table":5,"siege":1,"portee":"tous","tour":null},
{"participant":166,"table":13,"siege":1,"portee":"tous","tour":null},
{"participant":177,"table":10,"siege":1,"portee":"tous","tour":null},
{"participant":180,"table":18,"siege":1,"portee":"tous","tour":null},
{"participant":187,"table":7,"siege":1,"portee":"tous","tour":null},
{"participant":197,"table":11,"siege":1,"portee":"tous","tour":null},
{"participant":202,"table":2,"siege":1,"portee":"tous","tour":null},
{"participant":206,"table":28,"siege":1,"portee":"tous","tour":null},
{"participant":211,"table":19,"siege":1,"portee":"tous","tour":null},
{"participant":222,"table":23,"siege":1,"portee":"tous","tour":null},
{"participant":230,"table":9,"siege":1,"portee":"tous","tour":null}
],
"titres": [
{"table":1,"siege":1,"libelle":"animation"},
{"table":2,"siege":1,"libelle":"animation"},
{"table":3,"siege":1,"libelle":"animation"},
{"table":4,"siege":1,"libelle":"animation"},
{"table":5,"siege":1,"libelle":"animation"},
{"table":6,"siege":1,"libelle":"animation"},
{"table":7,"siege":1,"libelle":"animation"},
{"table":8,"siege":1,"libelle":"animation"},
{"table":9,"siege":1,"libelle":"animation"},
{"table":10,"siege":1,"libelle":"animation"},
{"table":11,"siege":1,"libelle":"animation"},
{"table":12,"siege":1,"libelle":"animation"},
{"table":13,"siege":1,"libelle":"animation"},
{"table":14,"siege":1,"libelle":"animation"},
{"table":15,"siege":1,"libelle":"animation"},
{"table":16,"siege":1,"libelle":"animation"},
{"table":17,"siege":1,"libelle":"animation"},
{"table":18,"siege":1,"libelle":"animation"},
{"table":19,"siege":1,"libelle":"animation"},
{"table":20,"siege":1,"libelle":"animation"},
{"table":21,"siege":1,"libelle":"animation"},
{"table":22,"siege":1,"libelle":"animation"},
{"table":23,"siege":1,"libelle":"animation"},
{"table":24,"siege":1,"libelle":"animation"},
{"table":25,"siege":1,"libelle":"animation"},
{"table":26,"siege":1,"libelle":"animation"},
{"table":27,"siege":1,"libelle":"animation"},
{"table":28,"siege":1,"libelle":"animation"},
{"table":29,"siege":1,"libelle":"animation"},
{"table":30,"siege":1,"libelle":"animation"},
{"table":31,"siege":1,"libelle":"animation"},
{"table":32,"siege":1,"libelle":"animation"},
{"table":33,"siege":1,"libelle":"animation"}
],
"propositions": [],
"retenu": null
}
}

View file

@ -0,0 +1,32 @@
{
"entete": {"format":1,"produitVersion":"2026.10.05.01","revision":1,"comptes":{"participants":12,"tables":4,"reservations":0,"titres":0,"propositions":0,"retenu":0}},
"charge": {
"evenement": {"id":"demo:petite-conflit","nom":"Petite démonstration, conflit inévitable","date":null,"siegesParDefaut":3,"tours":4,"unite":"cm","etat":"brouillon","filiation":null},
"reglages": {"separerAppartenances":true,"nouveauxVoisins":true,"nouvelleTable":true,"varierAppartenances":true,"attribuerSieges":false,"generation":{"nombre":5,"arret":200000,"historique":1000}},
"prochainsIds": {"participant":13,"table":5,"proposition":1},
"participants": [
{"id":1,"nom":"Bralmumet","prenom":"Caroline","appartenance":"Ateliers Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":2,"nom":"Neskault","prenom":"Hugo","appartenance":"Tannerie Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":3,"nom":"Ivrotte","prenom":"Youssef","appartenance":"Verrerie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":4,"nom":"Selvière","prenom":"Louis","appartenance":"Tannerie Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":5,"nom":"Selvançon","prenom":"Youssef","appartenance":"Ateliers Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":6,"nom":"Quilmoubre","prenom":"Amina","appartenance":"Verrerie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":7,"nom":"Voskotte","prenom":"Julie","appartenance":"Verrerie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":8,"nom":"Neskivet","prenom":"Paul","appartenance":"Ateliers Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":9,"nom":"Bralmignac","prenom":"Michel","appartenance":"Tannerie Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":10,"nom":"Tolveval","prenom":"Zoé","appartenance":"Ateliers Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":11,"nom":"Urvard","prenom":"Luc","appartenance":"Tannerie Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":12,"nom":"Mirveval","prenom":"Véronique","appartenance":"Ateliers Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false}
],
"tables": [
{"id":1,"numero":1,"sieges":null,"forme":"ronde","position":{"x":0,"y":0}},
{"id":2,"numero":2,"sieges":null,"forme":"ronde","position":{"x":300,"y":0}},
{"id":3,"numero":3,"sieges":null,"forme":"ronde","position":{"x":0,"y":300}},
{"id":4,"numero":4,"sieges":null,"forme":"ronde","position":{"x":300,"y":300}}
],
"reservations": [],
"titres": [],
"propositions": [],
"retenu": null
}
}

View file

@ -0,0 +1,32 @@
{
"entete": {"format":1,"produitVersion":"2026.10.05.01","revision":1,"comptes":{"participants":12,"tables":4,"reservations":0,"titres":0,"propositions":0,"retenu":0}},
"charge": {
"evenement": {"id":"demo:petite","nom":"Petite démonstration","date":null,"siegesParDefaut":3,"tours":4,"unite":"cm","etat":"brouillon","filiation":null},
"reglages": {"separerAppartenances":true,"nouveauxVoisins":true,"nouvelleTable":true,"varierAppartenances":true,"attribuerSieges":false,"generation":{"nombre":5,"arret":200000,"historique":1000}},
"prochainsIds": {"participant":13,"table":5,"proposition":1},
"participants": [
{"id":1,"nom":"Bralmumet","prenom":"Caroline","appartenance":"Ateliers Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":2,"nom":"Neskault","prenom":"Hugo","appartenance":"Tannerie Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":3,"nom":"Ivrotte","prenom":"Youssef","appartenance":"Verrerie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":4,"nom":"Selvière","prenom":"Louis","appartenance":"Tannerie Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":5,"nom":"Selvançon","prenom":"Youssef","appartenance":"Ateliers Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":6,"nom":"Quilmoubre","prenom":"Amina","appartenance":"Verrerie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":7,"nom":"Voskotte","prenom":"Julie","appartenance":"Verrerie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":8,"nom":"Neskivet","prenom":"Paul","appartenance":"Ateliers Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":9,"nom":"Bralmignac","prenom":"Michel","appartenance":"Tannerie Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":10,"nom":"Tolveval","prenom":"Zoé","appartenance":"Verrerie Ulvardes","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":11,"nom":"Urvard","prenom":"Luc","appartenance":"Tannerie Cassorgue","courriel":null,"titrePressenti":null,"notes":null,"exclu":false},
{"id":12,"nom":"Mirveval","prenom":"Véronique","appartenance":"Ateliers Jolvane","courriel":null,"titrePressenti":null,"notes":null,"exclu":false}
],
"tables": [
{"id":1,"numero":1,"sieges":null,"forme":"ronde","position":{"x":0,"y":0}},
{"id":2,"numero":2,"sieges":null,"forme":"ronde","position":{"x":300,"y":0}},
{"id":3,"numero":3,"sieges":null,"forme":"ronde","position":{"x":0,"y":300}},
{"id":4,"numero":4,"sieges":null,"forme":"ronde","position":{"x":300,"y":300}}
],
"reservations": [],
"titres": [],
"propositions": [],
"retenu": null
}
}

View file

@ -8,7 +8,10 @@
// un patronyme distinct, chaque appartenance un libellé distinct, sans boucle
// de rattrapage. Les fragments sont forgés pour ne désigner personne ;
// catalogue.long.test.js refuse qu'un patronyme complet ou une racine
// apparaisse ailleurs dans le projet.
// apparaisse ailleurs dans le projet, hors des fichiers que
// scripts/engendrer_livrables.js engendre de ces listes et qui les portent
// par construction : les démonstrations livrées et les deux CSV de
// démonstration (§ 15.6).
//
// L'ordre de chaque liste fait partie du contrat des tirages (§ 15.5,
// point 3) : un tirage désigne un rang, et changer un ordre change les