@roomi-fields/grillhouse 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/METACADRE.md +1 -1
- package/agents/explorateur.md +4 -2
- package/agents/questionneur.md +1 -1
- package/bin/grillhouse.mjs +3 -58
- package/cadre/templates/projet/CLAUDE.md +5 -1
- package/cadre/templates/projet/docs/agents/issue-tracker.md +4 -4
- package/charte.md +23 -6
- package/docs/ARCHITECTURE.md +127 -1
- package/hooks/register.tsx +3 -13
- package/package.json +13 -1
- package/scripts/bilan.mjs +134 -88
- package/scripts/circuit.mjs +1547 -2248
- package/scripts/composants-affectes.mjs +27 -108
- package/scripts/consommateurs-arbitres.mjs +15 -21
- package/scripts/consommateurs.d.ts +8 -0
- package/scripts/consommateurs.mjs +5 -1
- package/scripts/dette.d.ts +23 -0
- package/scripts/dette.mjs +76 -13
- package/scripts/droits.mjs +38 -44
- package/scripts/elements-du-lot.mjs +246 -0
- package/scripts/enveloppe/enveloppe.mjs +105 -232
- package/scripts/enveloppe/lancer.sh +79 -49
- package/scripts/enveloppe/paquets-du-plan.mjs +49 -0
- package/scripts/etapes.mjs +148 -213
- package/scripts/gardes-du-cadre.mjs +8 -6
- package/scripts/grillhouse-maj.mjs +31 -9
- package/scripts/index-interfaces.mjs +21 -1
- package/scripts/integration/integrer.mjs +2 -2
- package/scripts/interfaces-contre-code.mjs +25 -17
- package/scripts/migrations/0.23.0-config-vitest.mjs +26 -23
- package/scripts/migrations/0.23.2-copies-du-cadre.mjs +201 -0
- package/scripts/migrations/0.24.0-circuit-simplifie.mjs +231 -0
- package/scripts/migrations.mjs +41 -6
- package/scripts/paquets.d.ts +5 -0
- package/scripts/paquets.mjs +3 -0
- package/scripts/references-par-les-types.mjs +55 -36
- package/scripts/registre.mjs +7 -15
- package/scripts/resultats.mjs +283 -142
- package/scripts/resumes.mjs +1 -1
- package/scripts/session-start.sh +1 -2
- package/scripts/sortie.mjs +1 -1
- package/scripts/sources.mjs +28 -0
- package/scripts/sous-commandes.mjs +62 -0
- package/scripts/tableau.mjs +85 -191
- package/scripts/tests.mjs +19 -17
- package/scripts/verifier.mjs +5 -4
- package/scripts/verrou-de-machine.mjs +131 -24
- package/scripts/verrous/verrou.mjs +66 -5
- package/skills/developpeur/SKILL.md +60 -50
- package/skills/explorateur/SKILL.md +90 -64
- package/skills/grill/SKILL.md +1 -1
- package/skills/mesure/SKILL.md +2 -1
- package/skills/pitmaster/SKILL.md +132 -118
- package/skills/pitmaster/references/consigne-agent.md +54 -148
- package/skills/questionneur/SKILL.md +18 -18
- package/skills/relecteur/SKILL.md +69 -23
- package/skills/testwriter/SKILL.md +46 -35
- package/types/index.d.ts +1 -3
- package/agents/referent-technique.md +0 -9
- package/agents/regulateur.md +0 -11
- package/scripts/enveloppe/composant.mjs +0 -11
- package/scripts/etat-du-circuit.mjs +0 -104
- package/skills/referent-technique/SKILL.md +0 -145
- package/skills/regulateur/SKILL.md +0 -220
package/scripts/bilan.mjs
CHANGED
|
@@ -16,24 +16,26 @@
|
|
|
16
16
|
// `circuit` of the steps the circuit leads itself; the results the circuit keeps,
|
|
17
17
|
// `.claude/worktrees/<ticket>.fiches.jsonl`, each with its time: the launch of each session the
|
|
18
18
|
// circuit opens or resumes (`lancement`: its role and its session id, the register line's
|
|
19
|
-
// `agent`), the sheet of each session (`{ role,
|
|
19
|
+
// `agent`), the sheet of each session (`{ role, fiche }`), the outcome of each proof
|
|
20
20
|
// (`resultat`: `ok`, its counts, `vide`) and of each check (`resultat`: `issue` vert, rouge or a-repasser, `finale`), each stop
|
|
21
21
|
// (`arret`, its name, `finale`), each developpeur resumed who changed nothing (`inchange`), the
|
|
22
|
-
// end of
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
22
|
+
// end of the questioning of the plan (`questionnement`: its `objet`, the questions the
|
|
23
|
+
// questionneur asked, `questions`, whether an answer changed the plan, `change`, its `issue`,
|
|
24
|
+
// AUCUNE, QUESTIONS or DÉSACCORD, and the questions left open, `ouvertes`), each end of a run on
|
|
25
|
+
// a stop (`montee`: its code, its stop and who must answer its question, `qui`); the circuit's
|
|
26
|
+
// state, `.claude/worktrees/<ticket>.circuit.json`, for where the circuit ended; the night the
|
|
27
|
+
// project declares in its package.json (`grillhouse.plageDeNuit`). Every agent declares
|
|
28
|
+
// nothing: the bilan measures.
|
|
29
|
+
// - The steps and the roles are those of scripts/etapes.mjs. Only the circuit's roles count, the
|
|
30
|
+
// explorateur of the plan among them. Only the sessions the circuit launched count in
|
|
29
31
|
// the window, the categories, the tokens and the cost; a session of a circuit's role on the
|
|
30
32
|
// ticket the circuit did not launch, the supervisor's, counts apart (`seances.horsCircuit`: its
|
|
31
33
|
// number, by role, its cost).
|
|
32
34
|
// - The window: from the first turn of a session or a step to the last result of the circuit or
|
|
33
35
|
// the last turn; its length is the wall time.
|
|
34
36
|
// - Each turn of a session and each step finds its result: a step, the last outcome or stop of
|
|
35
|
-
// its step
|
|
36
|
-
// turn, before the next turn of any other
|
|
37
|
+
// its step kept during it, before the next step of its name; a session, the first sheet of its
|
|
38
|
+
// role kept after its turn, before the next turn of any other. Every step is one for the whole ticket. A step `controles` whose
|
|
37
39
|
// outcome or stop is the final pass's (`finale`) is the final pass.
|
|
38
40
|
// - The categories, each instant once:
|
|
39
41
|
// - travail resté au lot: a session that delivered, the reviewer's included;
|
|
@@ -42,26 +44,28 @@
|
|
|
42
44
|
// proof finds the lot of the proof before it (the same counts of reds, greens of the lot,
|
|
43
45
|
// broken files and greens of HEAD), after a refused proof, or no file of the lot (`vide`:
|
|
44
46
|
// reds that exist already or of a build alone, no green of the lot); that proof; checks that
|
|
45
|
-
// play the same red again
|
|
46
|
-
// before (the final pass:
|
|
47
|
-
// - a session of the
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
47
|
+
// play the same red again with no delivery of the testwriter or the developpeur since the
|
|
48
|
+
// checks of their kind before (the final pass: since the final pass before);
|
|
49
|
+
// - questionnement: a session of the questionneur (kept: a look that did not write the plan, as
|
|
50
|
+
// a review);
|
|
51
|
+
// - attente du superviseur, attente du responsable, nuit: as the lead time of DORA separates the
|
|
52
|
+
// wait for review from the work, each wait runs from an end of a run on a stop (`montee`) to
|
|
53
|
+
// the first turn of a session or step after it, or the next result of the circuit, the first
|
|
54
|
+
// of the two; it is the wait of who must answer its question (`qui`, the owner or the
|
|
55
|
+
// supervisor, as the circuit writes it), but its part in the night: the hours of `grillhouse.plageDeNuit` in the
|
|
56
|
+
// project's package.json (`{ debut, fin }`, in local time), from 23 h to 8 h without it;
|
|
55
57
|
// - étapes automatiques: the other steps the circuit leads, but the final passes;
|
|
56
58
|
// - passes finales: the final passes, but those without effect or replayed;
|
|
57
59
|
// - rejeu d'étapes déjà vertes: the same, on a lot unchanged since green: checks green again,
|
|
58
60
|
// a testwriter run again on a proven lot, its proof;
|
|
59
61
|
// - entre deux étapes: the rest of the window.
|
|
60
62
|
// What is lost is the wall time out of the work, the questioning, the automatic steps and the
|
|
61
|
-
// final passes.
|
|
63
|
+
// final passes. The text gives the wall time and the wall time out of the night spent waiting
|
|
64
|
+
// (« hors nuit d'attente »: a night at work stays in it), the time lost and the time lost out of
|
|
65
|
+
// the waits.
|
|
62
66
|
// - The bilan counts the sessions by role, the steps by their titles, the stops by kind (`arret`),
|
|
63
|
-
// the ends on a stop (the waits), the
|
|
64
|
-
//
|
|
67
|
+
// the ends on a stop (the waits), the questions of the plan asked, the plans they changed and the
|
|
68
|
+
// questions left open, the tokens, the cache reads and the cost (a session without a cost
|
|
65
69
|
// counts 0); a session's tokens go to its turns by their time.
|
|
66
70
|
// - It is kept in the main tree, `.claude/worktrees/<ticket>.bilan.json`, and compares itself with
|
|
67
71
|
// the latest bilan kept for another ticket of the project, or with nothing: no figure of
|
|
@@ -72,14 +76,13 @@ import os from 'node:os';
|
|
|
72
76
|
import path from 'node:path';
|
|
73
77
|
import { pathToFileURL } from 'node:url';
|
|
74
78
|
import {
|
|
75
|
-
|
|
79
|
+
AGENTS_DU_LOT,
|
|
76
80
|
etapeDuCircuit,
|
|
77
|
-
etapeDuRole,
|
|
78
|
-
juge,
|
|
79
81
|
nomDuRole,
|
|
80
82
|
QUESTIONNEUR,
|
|
81
|
-
|
|
83
|
+
RESPONSABLE,
|
|
82
84
|
SUITE,
|
|
85
|
+
SUPERVISEUR,
|
|
83
86
|
} from './etapes.mjs';
|
|
84
87
|
|
|
85
88
|
// The role of a register line that a step of the circuit writes, and of its results.
|
|
@@ -93,7 +96,9 @@ const CATEGORIES = [
|
|
|
93
96
|
['travail', 'travail resté au lot'],
|
|
94
97
|
['sansEffet', 'relance sans effet'],
|
|
95
98
|
['questionnement', 'questionnement'],
|
|
96
|
-
[
|
|
99
|
+
[SUPERVISEUR, 'attente du superviseur'],
|
|
100
|
+
[RESPONSABLE, 'attente du responsable'],
|
|
101
|
+
['nuit', 'nuit'],
|
|
97
102
|
['automatique', 'étapes automatiques'],
|
|
98
103
|
['finales', 'passes finales'],
|
|
99
104
|
['rejeu', "rejeu d'étapes déjà vertes"],
|
|
@@ -101,9 +106,50 @@ const CATEGORIES = [
|
|
|
101
106
|
];
|
|
102
107
|
// The categories of the time kept: the rest is lost.
|
|
103
108
|
const GARDE = new Set(['travail', 'automatique', 'finales', 'questionnement']);
|
|
104
|
-
// The
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
// The categories of the waits: the rest of the time lost is lost out of them.
|
|
110
|
+
const ATTENTES = [SUPERVISEUR, RESPONSABLE, 'nuit'];
|
|
111
|
+
// The night when the project declares none: from 23 h to 8 h, in local time.
|
|
112
|
+
const NUIT = Object.freeze({ debut: 23, fin: 8 });
|
|
113
|
+
|
|
114
|
+
// The night the project at <racine> declares in its package.json (`grillhouse.plageDeNuit`, `{
|
|
115
|
+
// debut, fin }`, hours from 0 to 23 in local time, distinct; a night that crosses midnight has its
|
|
116
|
+
// start after its end), else NUIT.
|
|
117
|
+
export function plageDeNuit(racine) {
|
|
118
|
+
let p = null;
|
|
119
|
+
try {
|
|
120
|
+
p = JSON.parse(readFileSync(path.join(racine, 'package.json'), 'utf8'))?.grillhouse
|
|
121
|
+
?.plageDeNuit;
|
|
122
|
+
} catch {
|
|
123
|
+
p = null;
|
|
124
|
+
}
|
|
125
|
+
const heure = h => Number.isInteger(h) && h >= 0 && h <= 23;
|
|
126
|
+
return heure(p?.debut) && heure(p?.fin) && p.debut !== p.fin
|
|
127
|
+
? { debut: p.debut, fin: p.fin }
|
|
128
|
+
: NUIT;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// The parts of the interval [d, f] in the night <nuit>, each night from its start hour of a day to
|
|
132
|
+
// its end hour, of the next day when it crosses midnight, in local time.
|
|
133
|
+
function nuits([d, f], nuit) {
|
|
134
|
+
const out = [];
|
|
135
|
+
const jour = new Date(d);
|
|
136
|
+
jour.setHours(0, 0, 0, 0);
|
|
137
|
+
jour.setDate(jour.getDate() - 1);
|
|
138
|
+
while (jour.getTime() < f) {
|
|
139
|
+
const a = new Date(jour);
|
|
140
|
+
a.setHours(nuit.debut);
|
|
141
|
+
const b = new Date(jour);
|
|
142
|
+
if (nuit.fin < nuit.debut) b.setDate(b.getDate() + 1);
|
|
143
|
+
b.setHours(nuit.fin);
|
|
144
|
+
const x = Math.max(a.getTime(), d);
|
|
145
|
+
const y = Math.min(b.getTime(), f);
|
|
146
|
+
if (y > x) out.push([x, y]);
|
|
147
|
+
jour.setDate(jour.getDate() + 1);
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
// The roles of the circuit, by the steps they work at.
|
|
152
|
+
const ROLES = new Set(SUITE.flatMap(e => e.qui));
|
|
107
153
|
|
|
108
154
|
// The results the circuit kept, `{ t, …entrée }`, in their order; an entry without a time counts
|
|
109
155
|
// nothing.
|
|
@@ -139,8 +185,16 @@ const commun = ([d, f], l) =>
|
|
|
139
185
|
longueur(l.map(([a, b]) => [Math.max(a, d), Math.min(b, f)]).filter(([a, b]) => b > a));
|
|
140
186
|
|
|
141
187
|
// The bilan of <ticket> from the register's lines <lignes> (of its project), the results the
|
|
142
|
-
// circuit kept <entrees> (`<ticket>.fiches.jsonl`)
|
|
143
|
-
|
|
188
|
+
// circuit kept <entrees> (`<ticket>.fiches.jsonl`), the circuit's state <etat> (or null) and the
|
|
189
|
+
// project's night <nuit>.
|
|
190
|
+
export function bilan({
|
|
191
|
+
ticket,
|
|
192
|
+
lignes,
|
|
193
|
+
entrees = [],
|
|
194
|
+
etat = null,
|
|
195
|
+
nuit = NUIT,
|
|
196
|
+
maintenant = Date.now(),
|
|
197
|
+
}) {
|
|
144
198
|
// The sessions the circuit launched, by their id.
|
|
145
199
|
const lancees = new Set(
|
|
146
200
|
entrees.filter(e => e.role === CIRCUIT && e.lancement).map(e => e.lancement.seance)
|
|
@@ -163,7 +217,6 @@ export function bilan({ ticket, lignes, entrees = [], etat = null, maintenant =
|
|
|
163
217
|
f,
|
|
164
218
|
role: l.role,
|
|
165
219
|
etape: l.role === CIRCUIT ? l.etape : null,
|
|
166
|
-
composant: l.role === CIRCUIT ? l.composant : null,
|
|
167
220
|
}))
|
|
168
221
|
)
|
|
169
222
|
.filter(s => s.f > s.d)
|
|
@@ -177,21 +230,23 @@ export function bilan({ ticket, lignes, entrees = [], etat = null, maintenant =
|
|
|
177
230
|
const duCircuit = gardes.filter(e => e.role === CIRCUIT);
|
|
178
231
|
for (const s of segments) {
|
|
179
232
|
if (s.role === CIRCUIT) {
|
|
233
|
+
// The next step of the same name: its result is not this one's, even within the gap.
|
|
234
|
+
const apres =
|
|
235
|
+
segments.find(x => x.role === CIRCUIT && x.etape === s.etape && x.d > s.d)?.d ?? Infinity;
|
|
180
236
|
s.entree =
|
|
181
237
|
duCircuit.findLast(
|
|
182
238
|
e =>
|
|
183
239
|
(e.resultat || e.arret) &&
|
|
184
240
|
e.etape === s.etape &&
|
|
185
|
-
e.composant === s.composant &&
|
|
186
241
|
e.t >= s.d - JEU &&
|
|
187
|
-
e.t <= s.f + JEU
|
|
242
|
+
e.t <= s.f + JEU &&
|
|
243
|
+
e.t <= apres
|
|
188
244
|
) ?? null;
|
|
189
245
|
s.finale = s.etape === 'controles' && Boolean(s.entree?.resultat?.finale ?? s.entree?.finale);
|
|
190
246
|
} else {
|
|
191
247
|
s.suivant = segments.find(x => x.d >= s.f && x.l !== s.l)?.d ?? Infinity;
|
|
192
248
|
s.entree =
|
|
193
249
|
gardes.find(e => e.role === s.role && e.t >= s.f - JEU && e.t <= s.suivant + JEU) ?? null;
|
|
194
|
-
s.composant = s.entree?.composant ?? null;
|
|
195
250
|
}
|
|
196
251
|
}
|
|
197
252
|
const etapes = segments.filter(s => s.role === CIRCUIT);
|
|
@@ -199,7 +254,7 @@ export function bilan({ ticket, lignes, entrees = [], etat = null, maintenant =
|
|
|
199
254
|
for (const p of preuves) p.preuve = p.entree?.resultat ?? null;
|
|
200
255
|
// A proof and the category it gives the testwriter before it: the lot unchanged since the proof
|
|
201
256
|
// before, or none.
|
|
202
|
-
const avantLa = p => preuves.findLast(x => x.
|
|
257
|
+
const avantLa = p => preuves.findLast(x => x.f <= p.d);
|
|
203
258
|
const parLaPreuve = p => {
|
|
204
259
|
const avant = avantLa(p);
|
|
205
260
|
if (!p.preuve) return null;
|
|
@@ -214,42 +269,31 @@ export function bilan({ ticket, lignes, entrees = [], etat = null, maintenant =
|
|
|
214
269
|
duCircuit.some(
|
|
215
270
|
e =>
|
|
216
271
|
e.etape === 'developpeur' &&
|
|
217
|
-
e.composant === s.composant &&
|
|
218
272
|
(e.arret === 'sans-livraison' || e.inchange) &&
|
|
219
273
|
e.t >= s.f - JEU &&
|
|
220
274
|
e.t <= s.suivant + JEU
|
|
221
275
|
);
|
|
222
276
|
|
|
223
277
|
// The sessions first: the checks read what they delivered.
|
|
224
|
-
for (const s of segments.filter(x => x.role !== CIRCUIT
|
|
225
|
-
if (
|
|
278
|
+
for (const s of segments.filter(x => x.role !== CIRCUIT)) {
|
|
279
|
+
if (s.role === QUESTIONNEUR) s.categorie = 'questionnement';
|
|
226
280
|
else if (s.role === 'developpeur') {
|
|
227
281
|
s.categorie = sansLivraison(s) ? 'sansEffet' : 'travail';
|
|
228
282
|
} else if (s.role === 'testwriter') {
|
|
229
|
-
const p = preuves.find(x => x.
|
|
283
|
+
const p = preuves.find(x => x.d >= s.f - JEU);
|
|
230
284
|
s.categorie = p ? (parLaPreuve(p) ?? (p.preuve?.vide ? 'sansEffet' : 'travail')) : 'travail';
|
|
231
285
|
} else s.categorie = 'travail';
|
|
232
286
|
}
|
|
233
|
-
// A session that changed
|
|
234
|
-
const livre = s =>
|
|
235
|
-
s.role !== CIRCUIT && s.categorie === 'travail' && etapeDuRole(s.role)?.parComposant;
|
|
287
|
+
// A session that changed the lot: the testwriter's or the developpeur's, that delivered.
|
|
288
|
+
const livre = s => AGENTS_DU_LOT.includes(s.role) && s.categorie === 'travail';
|
|
236
289
|
for (const s of etapes) {
|
|
237
290
|
if (s.etape === 'preuve') {
|
|
238
291
|
s.categorie = parLaPreuve(s) ?? 'automatique';
|
|
239
292
|
} else if (s.etape === 'controles') {
|
|
240
293
|
const memes = x => x.etape === 'controles' && x.finale === s.finale;
|
|
241
|
-
const avant = etapes.findLast(
|
|
242
|
-
x => memes(x) && x.f <= s.d && (s.finale || x.composant === s.composant)
|
|
243
|
-
);
|
|
294
|
+
const avant = etapes.findLast(x => memes(x) && x.f <= s.d);
|
|
244
295
|
const change =
|
|
245
|
-
!avant ||
|
|
246
|
-
segments.some(
|
|
247
|
-
x =>
|
|
248
|
-
livre(x) &&
|
|
249
|
-
x.d >= avant.f - JEU &&
|
|
250
|
-
x.f <= s.d + JEU &&
|
|
251
|
-
(s.finale || x.composant === s.composant)
|
|
252
|
-
);
|
|
296
|
+
!avant || segments.some(x => livre(x) && x.d >= avant.f - JEU && x.f <= s.d + JEU);
|
|
253
297
|
const i = s.entree ? issue(s.entree) : 'rouge';
|
|
254
298
|
s.categorie =
|
|
255
299
|
change || !avant.entree || issue(avant.entree) !== i
|
|
@@ -262,32 +306,27 @@ export function bilan({ ticket, lignes, entrees = [], etat = null, maintenant =
|
|
|
262
306
|
} else s.categorie = 'automatique';
|
|
263
307
|
}
|
|
264
308
|
|
|
265
|
-
// The
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const suite = segments.find(x => x.d >= s.f - JEU && !arbitrage(x));
|
|
272
|
-
const coupe = suite && montees.some(t => t >= s.f - JEU && t <= suite.d);
|
|
273
|
-
s.categorie =
|
|
274
|
-
suite && !coupe && (suite.categorie === 'sansEffet' || suite.categorie === 'rejeu')
|
|
275
|
-
? 'sansEffet'
|
|
276
|
-
: 'travail';
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// The waits for the supervisor: from each end of a run on a stop to the answer, the run after.
|
|
280
|
-
const attentes = [];
|
|
281
|
-
for (const t of montees) {
|
|
282
|
-
const tour = segments.find(s => s.d > t)?.d ?? Infinity;
|
|
283
|
-
const suivant = gardes.find(e => e.t > t)?.t ?? Infinity;
|
|
309
|
+
// The waits: from each end of a run on a stop to the answer, the run after, each for who must
|
|
310
|
+
// answer its question (`qui`, the owner or the supervisor).
|
|
311
|
+
const lesAttentes = [];
|
|
312
|
+
for (const e of duCircuit.filter(x => x.montee)) {
|
|
313
|
+
const tour = segments.find(s => s.d > e.t)?.d ?? Infinity;
|
|
314
|
+
const suivant = gardes.find(x => x.t > e.t)?.t ?? Infinity;
|
|
284
315
|
const f = Math.min(tour, suivant, fin);
|
|
285
|
-
|
|
316
|
+
const { qui } = e.montee;
|
|
317
|
+
if (f > e.t) lesAttentes.push({ i: [e.t, f], qui, nuit: nuits([e.t, f], nuit) });
|
|
286
318
|
}
|
|
319
|
+
const attentes = lesAttentes.map(a => a.i);
|
|
287
320
|
|
|
288
321
|
const vide = () => ({ ms: 0, n: 0, jetons: 0, cache: 0, cout: 0 });
|
|
289
322
|
const categories = Object.fromEntries(CATEGORIES.map(([k]) => [k, vide()]));
|
|
290
|
-
|
|
323
|
+
for (const qui of [SUPERVISEUR, RESPONSABLE]) {
|
|
324
|
+
const l = lesAttentes.filter(a => a.qui === qui);
|
|
325
|
+
const ms = l.reduce((m, a) => m + a.i[1] - a.i[0] - longueur(a.nuit), 0);
|
|
326
|
+
categories[qui] = { ...vide(), ms, n: l.length };
|
|
327
|
+
}
|
|
328
|
+
const laNuit = lesAttentes.filter(a => a.nuit.length);
|
|
329
|
+
categories.nuit = { ...vide(), ms: longueur(laNuit.flatMap(a => a.nuit)), n: laNuit.length };
|
|
291
330
|
const fenetre = s => [Math.max(s.d, debut), Math.min(s.f, fin)];
|
|
292
331
|
for (const s of segments) {
|
|
293
332
|
const [d, f] = fenetre(s);
|
|
@@ -318,7 +357,7 @@ export function bilan({ ticket, lignes, entrees = [], etat = null, maintenant =
|
|
|
318
357
|
// The sessions <l> by role, in the order of the steps.
|
|
319
358
|
const parRoleDe = l => {
|
|
320
359
|
const out = {};
|
|
321
|
-
for (const role of new Set(
|
|
360
|
+
for (const role of new Set(SUITE.flatMap(e => e.qui))) {
|
|
322
361
|
const n = l.filter(x => x.role === role).length;
|
|
323
362
|
if (n) out[role] = n;
|
|
324
363
|
}
|
|
@@ -332,22 +371,29 @@ export function bilan({ ticket, lignes, entrees = [], etat = null, maintenant =
|
|
|
332
371
|
}
|
|
333
372
|
const arrets = {};
|
|
334
373
|
for (const e of duCircuit.filter(x => x.arret)) arrets[e.arret] = (arrets[e.arret] ?? 0) + 1;
|
|
335
|
-
// The ends of the questionings:
|
|
374
|
+
// The ends of the questionings of the plan: the questions asked, the plans an answer changed, the
|
|
375
|
+
// questions left open.
|
|
336
376
|
const fins = duCircuit.map(e => e.questionnement).filter(Boolean);
|
|
337
377
|
const questionnement = {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
378
|
+
questions: fins.reduce((m, q) => m + (q.questions ?? 0), 0),
|
|
379
|
+
changes: fins.filter(q => q.change).length,
|
|
380
|
+
ouvertes: fins.reduce((m, q) => m + (q.ouvertes ?? 0), 0),
|
|
341
381
|
};
|
|
342
382
|
const somme = k => sessions.reduce((m, l) => m + (l[k] ?? 0), 0);
|
|
383
|
+
const perdu = CATEGORIES.filter(([k]) => !GARDE.has(k)).reduce(
|
|
384
|
+
(m, [k]) => m + categories[k].ms,
|
|
385
|
+
0
|
|
386
|
+
);
|
|
343
387
|
return {
|
|
344
388
|
ticket,
|
|
345
389
|
calcule: new Date(maintenant).toISOString(),
|
|
346
390
|
debut: mur ? new Date(debut).toISOString() : null,
|
|
347
391
|
fin: mur ? new Date(fin).toISOString() : null,
|
|
348
392
|
mur,
|
|
393
|
+
murHorsNuit: mur - categories.nuit.ms,
|
|
349
394
|
statut: statutDe(etat),
|
|
350
|
-
perdu
|
|
395
|
+
perdu,
|
|
396
|
+
perduHorsAttentes: perdu - ATTENTES.reduce((m, k) => m + categories[k].ms, 0),
|
|
351
397
|
categories,
|
|
352
398
|
seances: {
|
|
353
399
|
total: sessions.length,
|
|
@@ -395,17 +441,17 @@ export function texteDuBilan(b, avant) {
|
|
|
395
441
|
const horsCircuit = h.total
|
|
396
442
|
? ` ; ${h.total} séance${h.total > 1 ? 's' : ''} hors circuit, lancée${h.total > 1 ? 's' : ''} par le superviseur (${lesRoles(h.parRole)}), ${dollars(h.cout)} $`
|
|
397
443
|
: '';
|
|
398
|
-
const tete = `${b.statut} en ${minutes(b.mur)} min ; ${minutes(b.perdu)} min perdues (${pourcent(b.perdu, b.mur)} %) ; ${b.seances.total} séances${roles ? ` (${roles})` : ''}${horsCircuit} ; ${dollars(b.cout)} $ ; ${millions(b.jetons)} M jetons, ${millions(b.cache)} M lus du cache.`;
|
|
444
|
+
const tete = `${b.statut} en ${minutes(b.mur)} min, ${minutes(b.murHorsNuit)} min hors nuit d'attente ; ${minutes(b.perdu)} min perdues (${pourcent(b.perdu, b.mur)} %), ${minutes(b.perduHorsAttentes)} min hors attentes ; ${b.seances.total} séances${roles ? ` (${roles})` : ''}${horsCircuit} ; ${dollars(b.cout)} $ ; ${millions(b.jetons)} M jetons, ${millions(b.cache)} M lus du cache.`;
|
|
399
445
|
const paye = new Set(['travail', 'sansEffet', 'questionnement']);
|
|
400
|
-
const q = b.questionnement
|
|
446
|
+
const q = b.questionnement;
|
|
401
447
|
const pluriel = (n, mot) => `${n} ${mot}${n > 1 ? 's' : ''}`;
|
|
402
448
|
const lignes = CATEGORIES.map(([k, nom]) => {
|
|
403
449
|
const c = b.categories[k];
|
|
404
450
|
const plus =
|
|
405
|
-
k ===
|
|
451
|
+
(k === SUPERVISEUR || k === RESPONSABLE) && c.n
|
|
406
452
|
? ` (${pluriel(c.n, 'montée')})`
|
|
407
453
|
: k === 'questionnement'
|
|
408
|
-
? `, ${dollars(c.cout)} $ (${pluriel(q.
|
|
454
|
+
? `, ${dollars(c.cout)} $ (${pluriel(q.questions, 'question')}, ${pluriel(q.changes, 'plan')} ${q.changes > 1 ? 'changés' : 'changé'}, ${pluriel(q.ouvertes, 'question')} ${q.ouvertes > 1 ? 'ouvertes' : 'ouverte'})`
|
|
409
455
|
: paye.has(k)
|
|
410
456
|
? `, ${dollars(c.cout)} $`
|
|
411
457
|
: '';
|
|
@@ -494,7 +540,7 @@ function calculer(racine, ticket) {
|
|
|
494
540
|
} catch {
|
|
495
541
|
etat = null;
|
|
496
542
|
}
|
|
497
|
-
return bilan({ ticket, lignes, entrees, etat });
|
|
543
|
+
return bilan({ ticket, lignes, entrees, etat, nuit: plageDeNuit(racine) });
|
|
498
544
|
}
|
|
499
545
|
|
|
500
546
|
// The bilan of <ticket> computed, kept in the main tree <racine>, and its text, compared with the
|