OUAREDAEK commited on
Commit
dca59c9
·
verified ·
1 Parent(s): f6df64d

Upload cross_reading/consensus_building.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. cross_reading/consensus_building.html +1871 -0
cross_reading/consensus_building.html ADDED
@@ -0,0 +1,1871 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Système de Calibration Expert - ReflAgent</title>
7
+
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ font-family: 'Segoe UI', Arial, sans-serif;
14
+ }
15
+
16
+ body {
17
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
18
+ color: #333;
19
+ min-height: 100vh;
20
+ padding: 20px;
21
+ }
22
+
23
+ .container {
24
+ max-width: 1400px;
25
+ margin: 0 auto;
26
+ background: white;
27
+ border-radius: 20px;
28
+ padding: 30px;
29
+ box-shadow: 0 10px 40px rgba(0,0,0,0.08);
30
+ border: 1px solid #dee2e6;
31
+ }
32
+
33
+ /* Onglets */
34
+ .tabs {
35
+ display: flex;
36
+ gap: 10px;
37
+ margin-bottom: 30px;
38
+ border-bottom: 2px solid #dee2e6;
39
+ padding-bottom: 0;
40
+ }
41
+
42
+ .tab-button {
43
+ padding: 15px 30px;
44
+ background: #f8f9fa;
45
+ border: none;
46
+ border-radius: 10px 10px 0 0;
47
+ font-weight: 600;
48
+ cursor: pointer;
49
+ transition: all 0.3s;
50
+ color: #6c757d;
51
+ font-size: 16px;
52
+ }
53
+
54
+ .tab-button.active {
55
+ background: #3498db;
56
+ color: white;
57
+ box-shadow: 0 4px 10px rgba(52, 152, 219, 0.3);
58
+ }
59
+
60
+ .tab-content {
61
+ display: none;
62
+ }
63
+
64
+ .tab-content.active {
65
+ display: block;
66
+ animation: fadeIn 0.5s ease;
67
+ }
68
+
69
+ @keyframes fadeIn {
70
+ from { opacity: 0; transform: translateY(10px); }
71
+ to { opacity: 1; transform: translateY(0); }
72
+ }
73
+
74
+ /* Partie 1 : Interface Expert */
75
+ .evaluation-container {
76
+ max-width: 900px;
77
+ margin: 0 auto;
78
+ }
79
+
80
+ .scenario-selector {
81
+ background: #f8f9fa;
82
+ padding: 25px;
83
+ border-radius: 15px;
84
+ margin-bottom: 30px;
85
+ border-left: 5px solid #3498db;
86
+ }
87
+
88
+ .scenario-selector select {
89
+ width: 100%;
90
+ padding: 15px;
91
+ border: 2px solid #dee2e6;
92
+ border-radius: 10px;
93
+ font-size: 16px;
94
+ margin-top: 10px;
95
+ background: white;
96
+ }
97
+
98
+ .metrics-grid {
99
+ display: grid;
100
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
101
+ gap: 20px;
102
+ margin: 30px 0;
103
+ }
104
+
105
+ .metric-card {
106
+ background: white;
107
+ padding: 25px;
108
+ border-radius: 12px;
109
+ box-shadow: 0 4px 15px rgba(0,0,0,0.08);
110
+ border: 1px solid #e9ecef;
111
+ }
112
+
113
+ .metric-slider {
114
+ width: 100%;
115
+ margin: 15px 0;
116
+ -webkit-appearance: none;
117
+ height: 8px;
118
+ background: #e9ecef;
119
+ border-radius: 4px;
120
+ outline: none;
121
+ }
122
+
123
+ .metric-slider::-webkit-slider-thumb {
124
+ -webkit-appearance: none;
125
+ width: 24px;
126
+ height: 24px;
127
+ border-radius: 50%;
128
+ background: #3498db;
129
+ cursor: pointer;
130
+ border: 3px solid white;
131
+ box-shadow: 0 2px 10px rgba(0,0,0,0.2);
132
+ }
133
+
134
+ .slider-value {
135
+ font-size: 24px;
136
+ font-weight: bold;
137
+ color: #2c3e50;
138
+ margin-top: 10px;
139
+ }
140
+
141
+ .metric-badge {
142
+ display: inline-block;
143
+ padding: 6px 12px;
144
+ border-radius: 20px;
145
+ font-size: 12px;
146
+ font-weight: bold;
147
+ margin-top: 10px;
148
+ }
149
+
150
+ .badge-low {
151
+ background: #f8d7da;
152
+ color: #721c24;
153
+ }
154
+
155
+ .badge-medium {
156
+ background: #fff3cd;
157
+ color: #856404;
158
+ }
159
+
160
+ .badge-high {
161
+ background: #d4edda;
162
+ color: #155724;
163
+ }
164
+
165
+ .calibrate-btn {
166
+ display: block;
167
+ width: 200px;
168
+ margin: 40px auto;
169
+ padding: 15px 30px;
170
+ background: linear-gradient(135deg, #27ae60, #2ecc71);
171
+ color: white;
172
+ border: none;
173
+ border-radius: 10px;
174
+ font-size: 18px;
175
+ font-weight: bold;
176
+ cursor: pointer;
177
+ transition: all 0.3s;
178
+ }
179
+
180
+ .calibrate-btn:hover {
181
+ transform: translateY(-3px);
182
+ box-shadow: 0 10px 25px rgba(39, 174, 96, 0.3);
183
+ }
184
+
185
+ /* Partie 2 : Dashboard Consensus */
186
+ .stats-overview {
187
+ display: grid;
188
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
189
+ gap: 20px;
190
+ margin: 30px 0;
191
+ }
192
+
193
+ .stat-card {
194
+ background: white;
195
+ padding: 25px;
196
+ border-radius: 12px;
197
+ box-shadow: 0 4px 15px rgba(0,0,0,0.08);
198
+ transition: transform 0.3s;
199
+ border-top: 4px solid #3498db;
200
+ }
201
+
202
+ .stat-card:hover {
203
+ transform: translateY(-5px);
204
+ }
205
+
206
+ .stat-card.highlight {
207
+ border-top-color: #e74c3c;
208
+ background: linear-gradient(135deg, #fff5f5, #fff);
209
+ }
210
+
211
+ .stat-value {
212
+ font-size: 42px;
213
+ font-weight: bold;
214
+ color: #2c3e50;
215
+ margin: 10px 0;
216
+ }
217
+
218
+ .chart-container {
219
+ background: white;
220
+ border-radius: 15px;
221
+ padding: 25px;
222
+ margin: 30px 0;
223
+ box-shadow: 0 4px 20px rgba(0,0,0,0.05);
224
+ border: 1px solid #e9ecef;
225
+ }
226
+
227
+ .chart-wrapper {
228
+ height: 500px;
229
+ position: relative;
230
+ margin: 20px 0;
231
+ }
232
+
233
+ .section-title {
234
+ color: #2c3e50;
235
+ margin: 40px 0 20px;
236
+ padding-bottom: 10px;
237
+ border-bottom: 2px solid #4a90e2;
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 10px;
241
+ }
242
+
243
+ .comparison-table {
244
+ width: 100%;
245
+ border-collapse: collapse;
246
+ margin: 25px 0;
247
+ background: white;
248
+ border-radius: 10px;
249
+ overflow: hidden;
250
+ box-shadow: 0 3px 15px rgba(0,0,0,0.05);
251
+ }
252
+
253
+ .comparison-table th {
254
+ background: linear-gradient(135deg, #2c3e50, #34495e);
255
+ color: white;
256
+ padding: 15px;
257
+ text-align: left;
258
+ font-weight: 600;
259
+ }
260
+
261
+ .comparison-table td {
262
+ padding: 15px;
263
+ border-bottom: 1px solid #e9ecef;
264
+ }
265
+
266
+ .metric-details {
267
+ margin-top: 10px;
268
+ padding-top: 10px;
269
+ border-top: 1px solid #eee;
270
+ }
271
+
272
+ .metric-bar {
273
+ height: 8px;
274
+ background: #e9ecef;
275
+ border-radius: 4px;
276
+ margin: 5px 0;
277
+ overflow: hidden;
278
+ }
279
+
280
+ .metric-fill {
281
+ height: 100%;
282
+ border-radius: 4px;
283
+ }
284
+
285
+ .loading {
286
+ text-align: center;
287
+ padding: 60px;
288
+ color: #7f8c8d;
289
+ }
290
+
291
+ .loading-spinner {
292
+ display: inline-block;
293
+ width: 50px;
294
+ height: 50px;
295
+ border: 4px solid #f3f3f3;
296
+ border-top: 4px solid #3498db;
297
+ border-radius: 50%;
298
+ animation: spin 1s linear infinite;
299
+ margin-bottom: 20px;
300
+ }
301
+
302
+ @keyframes spin {
303
+ 0% { transform: rotate(0deg); }
304
+ 100% { transform: rotate(360deg); }
305
+ }
306
+
307
+ .notification {
308
+ position: fixed;
309
+ top: 20px;
310
+ right: 20px;
311
+ padding: 15px 25px;
312
+ border-radius: 10px;
313
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
314
+ z-index: 1000;
315
+ animation: slideIn 0.3s ease;
316
+ font-weight: bold;
317
+ }
318
+
319
+ .notification.success {
320
+ background: #27ae60;
321
+ color: white;
322
+ }
323
+
324
+ .notification.error {
325
+ background: #e74c3c;
326
+ color: white;
327
+ }
328
+
329
+ @keyframes slideIn {
330
+ from { transform: translateX(100%); opacity: 0; }
331
+ to { transform: translateX(0); opacity: 1; }
332
+ }
333
+
334
+ @keyframes slideOut {
335
+ from { transform: translateX(0); opacity: 1; }
336
+ to { transform: translateX(100%); opacity: 0; }
337
+ }
338
+
339
+ /* Section Consensus Building */
340
+ .consensus-building {
341
+ background: linear-gradient(135deg, #f8f9fa, #e9ecef);
342
+ border-radius: 15px;
343
+ padding: 30px;
344
+ margin: 40px 0;
345
+ border-left: 5px solid #3498db;
346
+ box-shadow: 0 5px 20px rgba(0,0,0,0.05);
347
+ }
348
+
349
+ .consensus-building h2 {
350
+ color: #2c3e50;
351
+ margin-bottom: 20px;
352
+ display: flex;
353
+ align-items: center;
354
+ gap: 10px;
355
+ }
356
+
357
+ .consensus-content {
358
+ display: grid;
359
+ grid-template-columns: 2fr 1fr;
360
+ gap: 30px;
361
+ }
362
+
363
+ .consensus-text {
364
+ line-height: 1.6;
365
+ color: #555;
366
+ }
367
+
368
+ .consensus-text p {
369
+ margin-bottom: 15px;
370
+ }
371
+
372
+ .kappa-metrics {
373
+ background: white;
374
+ padding: 25px;
375
+ border-radius: 10px;
376
+ box-shadow: 0 4px 15px rgba(0,0,0,0.05);
377
+ }
378
+
379
+ .kappa-metrics h4 {
380
+ color: #2c3e50;
381
+ margin-bottom: 15px;
382
+ display: flex;
383
+ align-items: center;
384
+ gap: 10px;
385
+ }
386
+
387
+ .kappa-value {
388
+ display: inline-block;
389
+ padding: 10px 20px;
390
+ background: #2c3e50;
391
+ color: white;
392
+ border-radius: 20px;
393
+ font-weight: bold;
394
+ font-size: 24px;
395
+ margin: 10px 0;
396
+ }
397
+
398
+ .kappa-stage {
399
+ margin: 15px 0;
400
+ padding: 12px;
401
+ border-left: 4px solid;
402
+ background: #f8f9fa;
403
+ border-radius: 0 5px 5px 0;
404
+ }
405
+
406
+ .kappa-stage.annotation {
407
+ border-left-color: #3498db;
408
+ }
409
+
410
+ .kappa-stage.revision {
411
+ border-left-color: #2ecc71;
412
+ }
413
+
414
+ .kappa-stage.evaluation {
415
+ border-left-color: #e74c3c;
416
+ }
417
+
418
+ .expert-process {
419
+ background: #f8f9fa;
420
+ padding: 25px;
421
+ border-radius: 15px;
422
+ margin: 30px 0;
423
+ border-left: 5px solid #3498db;
424
+ }
425
+
426
+ .expert-process h3 {
427
+ color: #2c3e50;
428
+ margin-bottom: 15px;
429
+ display: flex;
430
+ align-items: center;
431
+ gap: 10px;
432
+ }
433
+
434
+ .expert-process ul {
435
+ list-style-type: none;
436
+ padding-left: 20px;
437
+ }
438
+
439
+ .expert-process li {
440
+ margin-bottom: 12px;
441
+ padding-left: 25px;
442
+ position: relative;
443
+ }
444
+
445
+ .expert-process li:before {
446
+ content: "✓";
447
+ position: absolute;
448
+ left: 0;
449
+ color: #27ae60;
450
+ font-weight: bold;
451
+ }
452
+
453
+ @media (max-width: 768px) {
454
+ .container {
455
+ padding: 15px;
456
+ }
457
+
458
+ .tabs {
459
+ flex-direction: column;
460
+ }
461
+
462
+ .tab-button {
463
+ width: 100%;
464
+ border-radius: 10px;
465
+ margin-bottom: 5px;
466
+ }
467
+
468
+ .chart-wrapper {
469
+ height: 400px;
470
+ }
471
+
472
+ .metrics-grid {
473
+ grid-template-columns: 1fr;
474
+ }
475
+
476
+ .consensus-content {
477
+ grid-template-columns: 1fr;
478
+ }
479
+ }
480
+
481
+ /* Styles pour les messages d'erreur/absence de données */
482
+ .no-data-message {
483
+ text-align: center;
484
+ padding: 60px;
485
+ color: #7f8c8d;
486
+ background: #f8f9fa;
487
+ border-radius: 10px;
488
+ margin: 20px 0;
489
+ }
490
+
491
+ .no-data-message h3 {
492
+ margin-bottom: 15px;
493
+ color: #2c3e50;
494
+ }
495
+
496
+ .no-data-message button {
497
+ margin-top: 15px;
498
+ padding: 10px 20px;
499
+ background: #3498db;
500
+ color: white;
501
+ border: none;
502
+ border-radius: 5px;
503
+ cursor: pointer;
504
+ font-weight: bold;
505
+ transition: background 0.3s;
506
+ }
507
+
508
+ .no-data-message button:hover {
509
+ background: #2980b9;
510
+ }
511
+ </style>
512
+
513
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
514
+ </head>
515
+ <body>
516
+ <div class="container">
517
+ <div class="tabs">
518
+ <button class="tab-button active" data-tab="expert">👨‍🔬 Évaluation Expert</button>
519
+ <button class="tab-button" data-tab="consensus">📊 Dashboard Consensus</button>
520
+ </div>
521
+
522
+ <!-- Partie 1 : Interface Expert -->
523
+ <div id="expert" class="tab-content active">
524
+ <div class="evaluation-container">
525
+ <header>
526
+ <h1 style="color: #2c3e50; margin-bottom: 20px;">👨‍🔬 Calibration Expert</h1>
527
+ <p class="subtitle" style="color: #7f8c8d; margin-bottom: 30px;">
528
+ Évaluez les 5 métriques de confiance pour chaque scénario. L'<strong>Indice Global de Confiance</strong>
529
+ est automatiquement calculé comme la moyenne des 5 métriques.
530
+ </p>
531
+ </header>
532
+
533
+ <div class="scenario-selector">
534
+ <h3>📋 Sélection du Scénario</h3>
535
+ <select id="scenarioSelect">
536
+ <option value="">Chargement des scénarios...</option>
537
+ </select>
538
+ <p id="scenarioDescription" style="margin-top: 15px; color: #666; font-style: italic;"></p>
539
+ </div>
540
+
541
+ <div id="metricsEvaluation" style="display: none;">
542
+ <h3 style="color: #2c3e50; margin-bottom: 20px;">📊 Évaluation des 5 Métriques de Confiance</h3>
543
+
544
+ <div class="metrics-grid" id="metricsGrid">
545
+ <!-- Les 5 métriques seront générées ici -->
546
+ </div>
547
+
548
+ <!-- Indice Global de Confiance -->
549
+ <div id="globalIndex" style="margin-top: 30px; padding: 25px; background: linear-gradient(135deg, #f8f9fa, #e9ecef); border-radius: 15px; border-left: 5px solid #9b59b6;">
550
+ <h4 style="color: #2c3e50; margin-bottom: 10px;">🎯 Indice Global de Confiance</h4>
551
+ <p style="color: #7f8c8d; margin-bottom: 15px;">
552
+ Moyenne automatique des 5 métriques évaluées ci-dessus
553
+ </p>
554
+ <div style="display: flex; align-items: center; gap: 20px;">
555
+ <div style="font-size: 48px; font-weight: bold; color: #9b59b6;" id="globalIndexValue">0%</div>
556
+ <div style="flex: 1;">
557
+ <div style="height: 20px; background: #e9ecef; border-radius: 10px; overflow: hidden;">
558
+ <div id="globalIndexBar" style="height: 100%; background: linear-gradient(90deg, #9b59b6, #8e44ad); width: 0%; transition: width 0.5s;"></div>
559
+ </div>
560
+ <div style="display: flex; justify-content: space-between; margin-top: 5px; font-size: 12px; color: #7f8c8d;">
561
+ <span>0%</span>
562
+ <span>50%</span>
563
+ <span>100%</span>
564
+ </div>
565
+ </div>
566
+ </div>
567
+ <div id="globalIndexBadge" class="metric-badge" style="margin-top: 15px;">
568
+ Indice non calculé
569
+ </div>
570
+ </div>
571
+
572
+ <button class="calibrate-btn" onclick="saveCalibration()">
573
+ ✅ Calibrer ce Scénario
574
+ </button>
575
+
576
+ <div id="previousEvaluation" style="margin-top: 40px; padding: 20px; background: #f8f9fa; border-radius: 10px; display: none;">
577
+ <h4 style="color: #2c3e50; margin-bottom: 15px;">📝 Évaluation précédente</h4>
578
+ <div id="previousMetrics"></div>
579
+ </div>
580
+ </div>
581
+
582
+ <div id="loadingExpert" class="loading" style="display: none;">
583
+ <div class="loading-spinner"></div>
584
+ <p>Enregistrement de l'évaluation...</p>
585
+ </div>
586
+ </div>
587
+ </div>
588
+
589
+ <!-- Partie 2 : Dashboard Consensus -->
590
+ <div id="consensus" class="tab-content">
591
+ <a href="javascript:void(0)" onclick="switchTab('expert')" class="tab-button" style="display: inline-block; margin-bottom: 20px; background: linear-gradient(135deg, #3498db, #2980b9); color: white;">⬅️ Retour à l'Évaluation</a>
592
+
593
+ <header>
594
+ <h1>🔄 Établissement du Consensus - Processus Expert</h1>
595
+ <p class="subtitle">
596
+ <strong>Phase de Validation par les Experts - Établissement du Consensus :</strong>
597
+ Suite à l'évaluation individuelle, 14 experts ont participé à un processus d'établissement de consensus
598
+ pour traiter la variabilité inter-évaluateurs. Les divergences ont été analysées et résolues lors de sessions
599
+ de calibration, aboutissant à un accord inter-codeur substantiel (Kappa de Cohen > 0,75) à toutes les étapes.
600
+ </p>
601
+ </header>
602
+
603
+ <!-- Section Consensus Building -->
604
+ <div class="consensus-building">
605
+ <h2>🎯 Objectif de ce Dashboard</h2>
606
+ <div class="consensus-content">
607
+ <div class="consensus-text">
608
+ <p>Ce tableau de bord présente les résultats du <strong>processus d'établissement de consensus</strong>
609
+ mené par 14 experts interdisciplinaires. Il compare les évaluations individuelles initiales
610
+ avec les évaluations harmonisées suite aux sessions de calibration, montrant l'amélioration
611
+ de la cohérence et de la fiabilité des jugements experts.</p>
612
+
613
+ <p><strong>Résultat clé :</strong> Accord inter-évaluateurs substantiel (κ > 0,75) atteint pour
614
+ l'ensemble des scénarios après le processus de consensus.</p>
615
+
616
+ <p><strong>Processus :</strong> Suite à la phase d'évaluation individuelle, un processus d'établissement de consensus impliquant 14 experts
617
+ a été mené pour traiter la variabilité inter-évaluateurs. Cette phase a transformé les jugements individuels
618
+ en une référence collective, garantissant des critères d'évaluation cohérents au sein de l'équipe d'experts interdisciplinaire.</p>
619
+ </div>
620
+
621
+ <div class="kappa-metrics">
622
+ <h4>📐 Fiabilité Inter-Codeurs</h4>
623
+ <div class="kappa-value">κ > 0,75</div>
624
+ <p style="color: #7f8c8d; margin-bottom: 15px;">Accord substantiel entre experts</p>
625
+
626
+ <div class="kappa-stage annotation">
627
+ <strong>Annotation :</strong> κ > 0,78
628
+ </div>
629
+ <div class="kappa-stage revision">
630
+ <strong>Révision :</strong> κ > 0,82
631
+ </div>
632
+ <div class="kappa-stage evaluation">
633
+ <strong>Évaluation :</strong> κ > 0,85
634
+ </div>
635
+ </div>
636
+ </div>
637
+ </div>
638
+
639
+ <!-- Processus d'expert -->
640
+ <div class="expert-process">
641
+ <h3>🔬 Processus de Validation par les Experts</h3>
642
+ <ul>
643
+ <li><strong>Phase d'évaluation individuelle :</strong> Chaque expert évalue indépendamment les scénarios</li>
644
+ <li><strong>Identification des divergences :</strong> Analyse systématique des évaluations divergentes</li>
645
+ <li><strong>Sessions de calibration :</strong> Sessions ciblées pour les évaluations persistemment divergentes</li>
646
+ <li><strong>Établissement de critères communs :</strong> Transformation des jugements individuels en référentiel collectif</li>
647
+ <li><strong>Validation finale :</strong> Approbation consensuelle des évaluations</li>
648
+ </ul>
649
+ </div>
650
+
651
+ <!-- Aperçu des statistiques -->
652
+ <div class="stats-overview" id="statsOverview">
653
+ <!-- Généré dynamiquement -->
654
+ </div>
655
+
656
+ <!-- Graphique principal -->
657
+ <div class="chart-container">
658
+ <h3>📊 Indice Global de Confiance: Avant vs Après</h3>
659
+ <div class="chart-wrapper" id="mainTrustChartContainer">
660
+ <canvas id="mainTrustChart"></canvas>
661
+ </div>
662
+ </div>
663
+
664
+ <!-- Graphique détaillé des métriques -->
665
+ <div class="chart-container">
666
+ <h3>📈 Détail des 5 Métriques (Moyenne)</h3>
667
+ <div class="chart-wrapper" id="detailedMetricsChartContainer">
668
+ <canvas id="detailedMetricsChart"></canvas>
669
+ </div>
670
+ </div>
671
+
672
+ <!-- Graphique d'amélioration -->
673
+ <div class="chart-container">
674
+ <h3>📈 Amélioration par Scénario</h3>
675
+ <div class="chart-wrapper" id="improvementChartContainer">
676
+ <canvas id="improvementChart"></canvas>
677
+ </div>
678
+ </div>
679
+
680
+ <!-- Détail par métrique et scénario -->
681
+ <h2 class="section-title">📋 Détail par Métrique et Scénario</h2>
682
+ <div id="detailedComparison">
683
+ <!-- Généré dynamiquement -->
684
+ </div>
685
+
686
+ <!-- Zone de chargement -->
687
+ <div id="loadingConsensus" class="loading">
688
+ <div class="loading-spinner"></div>
689
+ <p>Chargement des données de comparaison...</p>
690
+ </div>
691
+ </div>
692
+ </div>
693
+
694
+ <script>
695
+ // ============================================
696
+ // DONNÉES ET CONFIGURATION
697
+ // ============================================
698
+
699
+ // Les 5 métriques spécifiques avec leurs acronymes et descriptions
700
+ const metrics = [
701
+ {
702
+ id: 'AR',
703
+ name: 'Authenticité Réflexive (AR)',
704
+ description: 'Capacité du système à refléter une compréhension authentique et contextualisée',
705
+ color: '#3498db'
706
+ },
707
+ {
708
+ id: 'AE',
709
+ name: 'Alignement Empathique (AE)',
710
+ description: 'Adéquation entre les réponses du système et l\'état émotionnel/utilisateur',
711
+ color: '#2ecc71'
712
+ },
713
+ {
714
+ id: 'ESR',
715
+ name: 'Encouragement Sensible au Risque (ESR)',
716
+ description: 'Capacité à encourager la prise de risque calculée tout en indiquant les limites',
717
+ color: '#e74c3c'
718
+ },
719
+ {
720
+ id: 'SDM',
721
+ name: 'Support aux Défis Mentaux (SDM)',
722
+ description: 'Soutien dans la résolution de problèmes complexes et défis cognitifs',
723
+ color: '#f39c12'
724
+ },
725
+ {
726
+ id: 'SM',
727
+ name: 'Support Métacognitif (SM)',
728
+ description: 'Aide à la réflexion sur ses propres processus de pensée et d\'apprentissage',
729
+ color: '#9b59b6'
730
+ }
731
+ ];
732
+
733
+ // Scénarios - seront chargés depuis scenarios_list.json
734
+ let scenariosList = [];
735
+
736
+ // Données initiales de confiance (chargées depuis trust_stats.json)
737
+ let trustStatsData = {};
738
+
739
+ // Données calibrées (depuis localStorage et calibration-trust.json)
740
+ let calibrationData = JSON.parse(localStorage.getItem('calibration-trust')) || {};
741
+
742
+ // Variables pour stocker les instances de graphiques
743
+ let mainChart = null;
744
+ let improvementChart = null;
745
+ let detailedChart = null;
746
+
747
+ // ============================================
748
+ // CHARGEMENT DES SCÉNARIOS DEPUIS JSON
749
+ // ============================================
750
+
751
+ async function loadScenariosFromJSON() {
752
+ try {
753
+ const response = await fetch('scenarios_list.json');
754
+ if (!response.ok) {
755
+ throw new Error(`HTTP error! status: ${response.status}`);
756
+ }
757
+ const data = await response.json();
758
+
759
+ // Vérifier si c'est un tableau ou un objet avec une propriété
760
+ if (Array.isArray(data)) {
761
+ scenariosList = data;
762
+ console.log(`${scenariosList.length} scénarios chargés depuis scenarios_list.json (format tableau)`);
763
+ } else if (data.scenarios && Array.isArray(data.scenarios)) {
764
+ scenariosList = data.scenarios;
765
+ console.log(`${scenariosList.length} scénarios chargés depuis scenarios_list.json (format objet.scenarios)`);
766
+ } else {
767
+ throw new Error('Format de fichier JSON invalide');
768
+ }
769
+
770
+ // S'assurer que tous les scénarios ont les bons IDs
771
+ scenariosList.forEach((scenario, index) => {
772
+ if (!scenario.id) {
773
+ scenario.id = `S${index + 1}`;
774
+ }
775
+ // Normaliser l'ID au format S1, S2, etc.
776
+ if (typeof scenario.id === 'number') {
777
+ scenario.id = `S${scenario.id}`;
778
+ }
779
+ });
780
+
781
+ } catch (error) {
782
+ console.error('Erreur de chargement de scenarios_list.json:', error);
783
+ // Générer des scénarios par défaut
784
+ scenariosList = generateDefaultScenarios();
785
+ console.log(`Utilisation de ${scenariosList.length} scénarios par défaut`);
786
+ }
787
+ }
788
+
789
+ function generateDefaultScenarios() {
790
+ // Générer des scénarios par défaut S1 à S16
791
+ const scenarios = [];
792
+ for (let i = 1; i <= 16; i++) {
793
+ scenarios.push({
794
+ id: `S${i}`,
795
+ name: `Scénario ${i}`,
796
+ description: `Description du scénario ${i}`
797
+ });
798
+ }
799
+ return scenarios;
800
+ }
801
+
802
+ // ============================================
803
+ // CHARGEMENT DES DONNÉES RÉELLES DES FICHIERS JSON
804
+ // ============================================
805
+
806
+ async function loadRealDataFromJSON() {
807
+ try {
808
+ // 1. Charger les données de trust_stats.json pour les valeurs initiales
809
+ const trustResponse = await fetch('trust_stats.json');
810
+ if (trustResponse.ok) {
811
+ const trustData = await trustResponse.json();
812
+ console.log('Données chargées depuis trust_stats.json:', trustData);
813
+
814
+ // Convertir les données de trust_stats.json au bon format
815
+ if (trustData.evaluations && Array.isArray(trustData.evaluations)) {
816
+ trustStatsData = {};
817
+
818
+ trustData.evaluations.forEach(eval => {
819
+ const scenarioId = `S${eval.scenario_id}`;
820
+
821
+ // Normaliser les métriques (RA -> AR, EA -> AE, etc.)
822
+ const metricsData = eval.trust_metrics || {};
823
+ const normalizedMetrics = {
824
+ AR: (metricsData.RA || metricsData.AR || 0) * 20, // Convertir 0-5 à 0-100
825
+ AE: (metricsData.EA || metricsData.AE || 0) * 20,
826
+ ESR: (metricsData.RE || metricsData.ESR || 0) * 20,
827
+ SDM: (metricsData.MCS || metricsData.SDM || 0) * 20,
828
+ SM: (metricsData.MS || metricsData.SM || 0) * 20
829
+ };
830
+
831
+ // Calculer la moyenne
832
+ const values = Object.values(normalizedMetrics);
833
+ const moyenne = values.reduce((a, b) => a + b, 0) / values.length;
834
+
835
+ trustStatsData[scenarioId] = {
836
+ ...normalizedMetrics,
837
+ moyenne: moyenne
838
+ };
839
+ });
840
+
841
+ console.log('trustStatsData mis à jour:', trustStatsData);
842
+ }
843
+ }
844
+
845
+ // 2. Charger les données de calibration-trust.json
846
+ const calibrationResponse = await fetch('calibration-trust.json');
847
+ if (calibrationResponse.ok) {
848
+ const calibrationJson = await calibrationResponse.json();
849
+ console.log('Données chargées depuis calibration-trust.json:', calibrationJson);
850
+
851
+ // Extraire les évaluations de tous les experts
852
+ if (calibrationJson.experts && Array.isArray(calibrationJson.experts)) {
853
+ calibrationJson.experts.forEach(expert => {
854
+ if (expert.evaluations && Array.isArray(expert.evaluations)) {
855
+ expert.evaluations.forEach(evaluation => {
856
+ if (evaluation.scenario) {
857
+ // Stocker la dernière évaluation pour chaque scénario
858
+ calibrationData[evaluation.scenario] = evaluation;
859
+ }
860
+ });
861
+ }
862
+ });
863
+ }
864
+
865
+ console.log('calibrationData chargé depuis JSON:', calibrationData);
866
+
867
+ // Mettre à jour localStorage avec les données du fichier
868
+ localStorage.setItem('calibration-trust', JSON.stringify(calibrationData));
869
+ }
870
+
871
+ // 3. S'assurer que tous les scénarios S1-S16 ont des données par défaut
872
+ for (let i = 1; i <= 16; i++) {
873
+ const scenarioId = `S${i}`;
874
+ if (!trustStatsData[scenarioId]) {
875
+ trustStatsData[scenarioId] = {
876
+ AR: 50, AE: 50, ESR: 50, SDM: 50, SM: 50, moyenne: 50
877
+ };
878
+ }
879
+ }
880
+
881
+ } catch (error) {
882
+ console.error('Erreur de chargement des données JSON:', error);
883
+ // Initialiser avec des données par défaut
884
+ for (let i = 1; i <= 16; i++) {
885
+ const scenarioId = `S${i}`;
886
+ trustStatsData[scenarioId] = {
887
+ AR: 50, AE: 50, ESR: 50, SDM: 50, SM: 50, moyenne: 50
888
+ };
889
+ }
890
+ }
891
+ }
892
+
893
+ // ============================================
894
+ // FONCTIONS DE GESTION DES ONGLETS
895
+ // ============================================
896
+
897
+ function switchTab(tabName) {
898
+ document.querySelectorAll('.tab-button').forEach(btn => {
899
+ btn.classList.remove('active');
900
+ });
901
+ document.querySelectorAll('.tab-content').forEach(content => {
902
+ content.classList.remove('active');
903
+ });
904
+
905
+ document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
906
+ document.getElementById(tabName).classList.add('active');
907
+
908
+ if (tabName === 'consensus') {
909
+ loadConsensusData();
910
+ }
911
+ }
912
+
913
+ // ============================================
914
+ // PARTIE 1 : INTERFACE EXPERT
915
+ // ============================================
916
+
917
+ async function loadScenarios() {
918
+ const select = document.getElementById('scenarioSelect');
919
+ select.innerHTML = '<option value="">Chargement des scénarios...</option>';
920
+
921
+ // Charger les scénarios depuis le fichier JSON
922
+ await loadScenariosFromJSON();
923
+
924
+ // Remplir le select
925
+ select.innerHTML = '<option value="">Sélectionnez un scénario...</option>';
926
+
927
+ scenariosList.forEach(scenario => {
928
+ const option = document.createElement('option');
929
+ option.value = scenario.id;
930
+ option.textContent = `${scenario.id}: ${scenario.name}`;
931
+ select.appendChild(option);
932
+ });
933
+
934
+ // Ajouter un écouteur pour le changement de scénario
935
+ select.addEventListener('change', function() {
936
+ const selectedId = this.value;
937
+ const scenario = scenariosList.find(s => s.id === selectedId);
938
+
939
+ if (scenario) {
940
+ document.getElementById('scenarioDescription').textContent = scenario.description || 'Aucune description disponible';
941
+ document.getElementById('metricsEvaluation').style.display = 'block';
942
+ loadMetricsForScenario(selectedId);
943
+ } else {
944
+ document.getElementById('metricsEvaluation').style.display = 'none';
945
+ document.getElementById('scenarioDescription').textContent = 'Scénario non trouvé';
946
+ }
947
+ });
948
+
949
+ // Sélectionner le premier scénario par défaut
950
+ if (scenariosList.length > 0) {
951
+ setTimeout(() => {
952
+ select.value = scenariosList[0].id;
953
+ select.dispatchEvent(new Event('change'));
954
+ }, 500);
955
+ }
956
+ }
957
+
958
+ function loadMetricsForScenario(scenarioId) {
959
+ const metricsGrid = document.getElementById('metricsGrid');
960
+ metricsGrid.innerHTML = '';
961
+
962
+ // S'assurer que l'ID est au bon format
963
+ const normalizedId = typeof scenarioId === 'number' ? `S${scenarioId}` : scenarioId;
964
+
965
+ const previousCalibration = calibrationData[normalizedId];
966
+ const initialData = trustStatsData[normalizedId];
967
+
968
+ metrics.forEach(metric => {
969
+ const initialValue = initialData ? initialData[metric.id] : 50;
970
+ const previousValue = previousCalibration ? previousCalibration[metric.id] : null;
971
+ const currentValue = previousValue !== null && previousValue !== undefined ? previousValue : initialValue;
972
+
973
+ const metricCard = document.createElement('div');
974
+ metricCard.className = 'metric-card';
975
+ metricCard.style.borderTop = `4px solid ${metric.color}`;
976
+
977
+ metricCard.innerHTML = `
978
+ <h4>${metric.name}</h4>
979
+ <p style="font-size: 14px; color: #666; margin: 10px 0;">${metric.description}</p>
980
+
981
+ <div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
982
+ <span style="color: #e74c3c; font-weight: bold;">
983
+ Initial: ${initialValue}%
984
+ </span>
985
+ ${previousValue !== null && previousValue !== undefined ?
986
+ `<span style="color: #27ae60; font-weight: bold;">
987
+ Précédent: ${previousValue}%
988
+ </span>` : ''
989
+ }
990
+ </div>
991
+
992
+ <input type="range"
993
+ min="0"
994
+ max="100"
995
+ value="${currentValue}"
996
+ class="metric-slider"
997
+ data-metric="${metric.id}"
998
+ oninput="updateSliderValue(this, '${metric.id}', '${metric.color}')"
999
+ style="background: linear-gradient(90deg, #e9ecef ${currentValue}%, ${metric.color} ${currentValue}%)">
1000
+
1001
+ <div class="slider-value" id="value-${metric.id}">${currentValue}%</div>
1002
+
1003
+ <div class="metric-badge ${getBadgeClass(currentValue)}" id="badge-${metric.id}">
1004
+ ${getBadgeText(currentValue)}
1005
+ </div>
1006
+ `;
1007
+
1008
+ metricsGrid.appendChild(metricCard);
1009
+ });
1010
+
1011
+ // Calculer et afficher l'indice global
1012
+ updateGlobalIndex();
1013
+
1014
+ // Afficher l'évaluation précédente si elle existe
1015
+ if (previousCalibration) {
1016
+ document.getElementById('previousEvaluation').style.display = 'block';
1017
+ const previousMetricsDiv = document.getElementById('previousMetrics');
1018
+ previousMetricsDiv.innerHTML = `
1019
+ <p><strong>Date:</strong> ${previousCalibration.date || 'Non spécifiée'}</p>
1020
+ <p><strong>Expert:</strong> ${previousCalibration.expert || 'Anonyme'}</p>
1021
+ <p><strong>Indice Global:</strong> ${previousCalibration.moyenne || calculateAverage(normalizedId, previousCalibration)}%</p>
1022
+ <div class="metric-details">
1023
+ ${metrics.map(m => `
1024
+ <div style="margin: 5px 0;">
1025
+ <span>${m.name}:</span>
1026
+ <span style="float: right; font-weight: bold;">${previousCalibration[m.id] || 0}%</span>
1027
+ <div class="metric-bar">
1028
+ <div class="metric-fill" style="width: ${previousCalibration[m.id] || 0}%; background: ${m.color};"></div>
1029
+ </div>
1030
+ </div>
1031
+ `).join('')}
1032
+ </div>
1033
+ `;
1034
+ } else {
1035
+ document.getElementById('previousEvaluation').style.display = 'none';
1036
+ }
1037
+ }
1038
+
1039
+ function updateSliderValue(slider, metricId, color) {
1040
+ const value = slider.value;
1041
+ document.getElementById(`value-${metricId}`).textContent = `${value}%`;
1042
+ const badge = document.getElementById(`badge-${metricId}`);
1043
+ badge.className = `metric-badge ${getBadgeClass(value)}`;
1044
+ badge.textContent = getBadgeText(value);
1045
+
1046
+ // Mettre à jour le fond du slider
1047
+ slider.style.background = `linear-gradient(90deg, #e9ecef ${value}%, ${color} ${value}%)`;
1048
+
1049
+ // Mettre à jour l'indice global
1050
+ updateGlobalIndex();
1051
+ }
1052
+
1053
+ function updateGlobalIndex() {
1054
+ const sliders = document.querySelectorAll('.metric-slider');
1055
+ let sum = 0;
1056
+ let validSliders = 0;
1057
+
1058
+ sliders.forEach(slider => {
1059
+ const value = parseInt(slider.value);
1060
+ if (!isNaN(value)) {
1061
+ sum += value;
1062
+ validSliders++;
1063
+ }
1064
+ });
1065
+
1066
+ const average = validSliders > 0 ? sum / validSliders : 0;
1067
+
1068
+ document.getElementById('globalIndexValue').textContent = `${average.toFixed(1)}%`;
1069
+ document.getElementById('globalIndexBar').style.width = `${average}%`;
1070
+
1071
+ const badge = document.getElementById('globalIndexBadge');
1072
+ badge.className = `metric-badge ${getBadgeClass(average)}`;
1073
+ badge.textContent = `Indice Global: ${getBadgeText(average)} (${average.toFixed(1)}%)`;
1074
+ }
1075
+
1076
+ function getBadgeClass(value) {
1077
+ if (value >= 80) return 'badge-high';
1078
+ if (value >= 60) return 'badge-medium';
1079
+ return 'badge-low';
1080
+ }
1081
+
1082
+ function getBadgeText(value) {
1083
+ if (value >= 80) return 'Élevé';
1084
+ if (value >= 60) return 'Modéré';
1085
+ return 'Faible';
1086
+ }
1087
+
1088
+ function calculateAverage(scenarioId, data) {
1089
+ const values = metrics.map(m => data[m.id] || 0);
1090
+ const sum = values.reduce((a, b) => a + b, 0);
1091
+ return (sum / values.length).toFixed(1);
1092
+ }
1093
+
1094
+ // ============================================
1095
+ // SAUVEGARDE DE CALIBRATION DANS calibration-trust.json
1096
+ // ============================================
1097
+
1098
+ async function saveCalibration() {
1099
+ const scenarioId = document.getElementById('scenarioSelect').value;
1100
+
1101
+ if (!scenarioId) {
1102
+ showNotification('Veuillez sélectionner un scénario', 'error');
1103
+ return;
1104
+ }
1105
+
1106
+ // Normaliser l'ID du scénario
1107
+ const normalizedId = scenarioId;
1108
+
1109
+ // Demander le nom de l'expert
1110
+ const expertName = prompt('Votre nom (obligatoire pour la sauvegarde):', 'Expert');
1111
+ if (!expertName || expertName.trim() === '') {
1112
+ showNotification('Le nom de l\'expert est obligatoire', 'error');
1113
+ return;
1114
+ }
1115
+
1116
+ // Récupérer les valeurs des sliders
1117
+ const calibration = {
1118
+ scenario: normalizedId,
1119
+ date: new Date().toISOString().split('T')[0],
1120
+ time: new Date().toTimeString().split(' ')[0],
1121
+ expert: expertName.trim(),
1122
+ expert_id: generateExpertId(expertName)
1123
+ };
1124
+
1125
+ // Récupérer les valeurs des métriques
1126
+ metrics.forEach(metric => {
1127
+ const slider = document.querySelector(`[data-metric="${metric.id}"]`);
1128
+ calibration[metric.id] = parseInt(slider.value);
1129
+ });
1130
+
1131
+ // Calculer l'indice global
1132
+ calibration.moyenne = calculateAverage(normalizedId, calibration);
1133
+
1134
+ // Afficher le chargement
1135
+ document.getElementById('loadingExpert').style.display = 'block';
1136
+
1137
+ try {
1138
+ // 1. Sauvegarder dans localStorage (pour l'interface immédiate)
1139
+ calibrationData[normalizedId] = calibration;
1140
+ localStorage.setItem('calibration-trust', JSON.stringify(calibrationData));
1141
+
1142
+ // 2. Sauvegarder dans le fichier calibration-trust.json
1143
+ await saveCalibrationToJSON(calibration);
1144
+
1145
+ // 3. Mettre à jour trustStatsData si nécessaire
1146
+ if (!trustStatsData[normalizedId]) {
1147
+ trustStatsData[normalizedId] = {};
1148
+ }
1149
+
1150
+ document.getElementById('loadingExpert').style.display = 'none';
1151
+ showNotification(`✅ Calibration enregistrée pour ${normalizedId}`, 'success');
1152
+ loadMetricsForScenario(normalizedId);
1153
+
1154
+ // 4. Recharger les données si on est sur l'onglet consensus
1155
+ if (document.getElementById('consensus').classList.contains('active')) {
1156
+ loadConsensusData();
1157
+ }
1158
+
1159
+ } catch (error) {
1160
+ document.getElementById('loadingExpert').style.display = 'none';
1161
+ showNotification(`❌ Erreur: ${error.message}`, 'error');
1162
+ console.error('Erreur sauvegarde calibration:', error);
1163
+ }
1164
+ }
1165
+
1166
+ async function saveCalibrationToJSON(newCalibration) {
1167
+ try {
1168
+ // 1. Charger le fichier existant
1169
+ let existingData = { experts: [] };
1170
+ try {
1171
+ const response = await fetch('calibration-trust.json');
1172
+ if (response.ok) {
1173
+ existingData = await response.json();
1174
+ }
1175
+ } catch (error) {
1176
+ console.log('Fichier calibration-trust.json non trouvé, création d\'un nouveau');
1177
+ }
1178
+
1179
+ // 2. Chercher l'expert existant ou en créer un nouveau
1180
+ const expertName = newCalibration.expert;
1181
+ const expertId = newCalibration.expert_id;
1182
+ let expert = existingData.experts.find(e =>
1183
+ e.expert_id === expertId || e.expert_name === expertName
1184
+ );
1185
+
1186
+ if (!expert) {
1187
+ // Créer un nouvel expert
1188
+ expert = {
1189
+ expert_id: expertId,
1190
+ expert_name: expertName,
1191
+ evaluations: [],
1192
+ last_updated: new Date().toISOString()
1193
+ };
1194
+ existingData.experts.push(expert);
1195
+ }
1196
+
1197
+ // 3. Chercher si une évaluation existe déjà pour ce scénario
1198
+ const existingEvaluationIndex = expert.evaluations.findIndex(
1199
+ eval => eval.scenario === newCalibration.scenario
1200
+ );
1201
+
1202
+ // Générer un ID d'évaluation unique
1203
+ const evaluationId = generateEvaluationId();
1204
+
1205
+ if (existingEvaluationIndex !== -1) {
1206
+ // Mettre à jour l'évaluation existante
1207
+ expert.evaluations[existingEvaluationIndex] = {
1208
+ evaluation_id: evaluationId,
1209
+ ...newCalibration
1210
+ };
1211
+ } else {
1212
+ // Ajouter une nouvelle évaluation
1213
+ expert.evaluations.push({
1214
+ evaluation_id: evaluationId,
1215
+ ...newCalibration
1216
+ });
1217
+ }
1218
+
1219
+ // 4. Mettre à jour la date de dernière modification
1220
+ expert.last_updated = new Date().toISOString();
1221
+
1222
+ // 5. Sauvegarder le fichier
1223
+ await saveJSONToFile('calibration-trust.json', existingData);
1224
+
1225
+ console.log('Calibration sauvegardée dans calibration-trust.json:', newCalibration);
1226
+
1227
+ } catch (error) {
1228
+ console.error('Erreur sauvegarde dans JSON:', error);
1229
+ throw new Error('Impossible de sauvegarder dans le fichier JSON');
1230
+ }
1231
+ }
1232
+
1233
+ function generateExpertId(expertName) {
1234
+ // Générer un ID d'expert basé sur le nom et la date
1235
+ const cleanName = expertName.toLowerCase()
1236
+ .replace(/\s+/g, '_')
1237
+ .replace(/[^a-z0-9_]/g, '');
1238
+ return `expert_${cleanName}_${Date.now()}`;
1239
+ }
1240
+
1241
+ function generateEvaluationId() {
1242
+ return `eval_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1243
+ }
1244
+
1245
+ async function saveJSONToFile(filename, data) {
1246
+ // Dans un environnement réel, cela enverrait les données au serveur
1247
+ // Pour le moment, nous allons simuler la sauvegarde et offrir un téléchargement
1248
+
1249
+ try {
1250
+ // Tentative d'envoi au serveur (si une API existe)
1251
+ const response = await fetch('/api/save_calibration', {
1252
+ method: 'POST',
1253
+ headers: {
1254
+ 'Content-Type': 'application/json',
1255
+ },
1256
+ body: JSON.stringify({ filename: filename, data: data })
1257
+ });
1258
+
1259
+ if (response.ok) {
1260
+ const result = await response.json();
1261
+ console.log('Sauvegarde réussie via API:', result);
1262
+ return;
1263
+ }
1264
+ } catch (error) {
1265
+ console.log('API non disponible, sauvegarde locale');
1266
+ }
1267
+
1268
+ // Fallback: Offrir le téléchargement du fichier
1269
+ const dataStr = JSON.stringify(data, null, 2);
1270
+ const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
1271
+
1272
+ const linkElement = document.createElement('a');
1273
+ linkElement.setAttribute('href', dataUri);
1274
+ linkElement.setAttribute('download', filename);
1275
+ linkElement.style.display = 'none';
1276
+
1277
+ document.body.appendChild(linkElement);
1278
+ linkElement.click();
1279
+ document.body.removeChild(linkElement);
1280
+
1281
+ // Afficher un message d'information
1282
+ showNotification(
1283
+ '📥 Fichier calibration-trust.json téléchargé. ' +
1284
+ 'Pour une sauvegarde automatique, ajoutez un endpoint /api/save_calibration à votre backend.',
1285
+ 'success'
1286
+ );
1287
+ }
1288
+
1289
+ // ============================================
1290
+ // PARTIE 2 : DASHBOARD CONSENSUS
1291
+ // ============================================
1292
+
1293
+ async function loadConsensusData() {
1294
+ document.getElementById('loadingConsensus').style.display = 'block';
1295
+
1296
+ // Charger les données réelles des fichiers JSON
1297
+ await loadRealDataFromJSON();
1298
+
1299
+ // GÉNÉRER DES DONNÉES DE DÉMONSTRATION SI AUCUNE CALIBRATION N'EXISTE
1300
+ if (Object.keys(calibrationData).length === 0) {
1301
+ console.log('Aucune donnée de calibration, génération de données de démonstration...');
1302
+ calibrationData = generateDemoDataFromRealStats();
1303
+ }
1304
+
1305
+ // S'assurer que scenariosList est chargé
1306
+ if (scenariosList.length === 0) {
1307
+ await loadScenariosFromJSON();
1308
+ }
1309
+
1310
+ const calibratedScenarios = Object.keys(calibrationData);
1311
+ const totalScenarios = scenariosList.length;
1312
+
1313
+ // Calculer les statistiques globales BASÉES SUR LES DONNÉES RÉELLES
1314
+ let totalImprovement = 0;
1315
+ let scenariosAbove80 = 0;
1316
+ let maxImprovement = 0;
1317
+ let maxScenario = '';
1318
+ let scenariosWithData = 0;
1319
+
1320
+ calibratedScenarios.forEach(scenarioId => {
1321
+ const before = trustStatsData[scenarioId] ? trustStatsData[scenarioId].moyenne : 0;
1322
+ const after = calibrationData[scenarioId].moyenne;
1323
+
1324
+ // Vérifier que les données existent
1325
+ if (before !== undefined && after !== undefined) {
1326
+ const improvement = after - before;
1327
+ totalImprovement += improvement;
1328
+ scenariosWithData++;
1329
+
1330
+ if (after >= 80) scenariosAbove80++;
1331
+
1332
+ if (improvement > maxImprovement) {
1333
+ maxImprovement = improvement;
1334
+ maxScenario = scenarioId;
1335
+ }
1336
+ }
1337
+ });
1338
+
1339
+ const avgImprovement = scenariosWithData > 0
1340
+ ? (totalImprovement / scenariosWithData).toFixed(1)
1341
+ : 0;
1342
+
1343
+ // Mettre à jour les statistiques
1344
+ document.getElementById('statsOverview').innerHTML = `
1345
+ <div class="stat-card">
1346
+ <div class="stat-label">Scénarios calibrés</div>
1347
+ <div class="stat-value">${scenariosWithData}/${totalScenarios}</div>
1348
+ <div class="stat-label">(${Math.round(scenariosWithData/totalScenarios*100)}%)</div>
1349
+ </div>
1350
+ <div class="stat-card">
1351
+ <div class="stat-label">Indice Global Moyen</div>
1352
+ <div class="stat-value">${avgImprovement > 0 ? '+' : ''}${avgImprovement}%</div>
1353
+ <div class="stat-label">d'amélioration</div>
1354
+ </div>
1355
+ <div class="stat-card highlight">
1356
+ <div class="stat-label">Scénarios >80%</div>
1357
+ <div class="stat-value">${scenariosAbove80}/${scenariosWithData || 0}</div>
1358
+ <div class="stat-label">après calibration</div>
1359
+ </div>
1360
+ <div class="stat-card">
1361
+ <div class="stat-label">Amélioration max</div>
1362
+ <div class="stat-value">+${maxImprovement.toFixed(1)}%</div>
1363
+ <div class="stat-label">${maxScenario || '-'}</div>
1364
+ </div>
1365
+ `;
1366
+
1367
+ // Générer les graphiques
1368
+ generateConsensusCharts();
1369
+
1370
+ // Générer le détail par métrique
1371
+ generateDetailedComparison();
1372
+
1373
+ document.getElementById('loadingConsensus').style.display = 'none';
1374
+ }
1375
+
1376
+ function generateDemoDataFromRealStats() {
1377
+ // Générer des données de démonstration BASÉES SUR LES DONNÉES RÉELLES
1378
+ const demoData = {};
1379
+
1380
+ // Prendre les scénarios qui ont des données dans trustStatsData
1381
+ const scenariosWithData = Object.keys(trustStatsData).filter(id =>
1382
+ trustStatsData[id] && trustStatsData[id].moyenne !== undefined
1383
+ );
1384
+
1385
+ // Si pas assez de données, prendre les 8 premiers scénarios
1386
+ const demoScenarios = scenariosWithData.length > 0
1387
+ ? scenariosWithData.slice(0, 8)
1388
+ : scenariosList.slice(0, 8).map(s => s.id);
1389
+
1390
+ demoScenarios.forEach(scenarioId => {
1391
+ const baseData = trustStatsData[scenarioId];
1392
+
1393
+ if (baseData) {
1394
+ // Amélioration réaliste basée sur les données existantes
1395
+ const improvementFactor = 0.05 + (Math.random() * 0.2);
1396
+
1397
+ const demoMetrics = {};
1398
+ metrics.forEach(metric => {
1399
+ const baseValue = baseData[metric.id] || 50;
1400
+ demoMetrics[metric.id] = Math.min(100, Math.round(baseValue * (1 + improvementFactor)));
1401
+ });
1402
+
1403
+ const avg = metrics.reduce((sum, metric) => sum + (demoMetrics[metric.id] || 0), 0) / metrics.length;
1404
+
1405
+ demoData[scenarioId] = {
1406
+ scenario: scenarioId,
1407
+ date: new Date().toISOString().split('T')[0],
1408
+ expert: "Expert Démo",
1409
+ expert_id: "expert_demo_1",
1410
+ ...demoMetrics,
1411
+ moyenne: avg.toFixed(1)
1412
+ };
1413
+ }
1414
+ });
1415
+
1416
+ console.log('Données de démonstration générées à partir des stats réelles:', demoData);
1417
+ return demoData;
1418
+ }
1419
+
1420
+ function generateConsensusCharts() {
1421
+ const calibratedScenarios = Object.keys(calibrationData);
1422
+
1423
+ // Détruire les anciens graphiques s'ils existent
1424
+ if (mainChart) mainChart.destroy();
1425
+ if (improvementChart) improvementChart.destroy();
1426
+ if (detailedChart) detailedChart.destroy();
1427
+
1428
+ if (calibratedScenarios.length === 0) {
1429
+ // Afficher un message dans chaque conteneur de graphique
1430
+ const noDataHTML = `
1431
+ <div class="no-data-message">
1432
+ <h3>📊 Aucune donnée disponible</h3>
1433
+ <p>Effectuez des calibrations dans l'onglet "Évaluation Expert" pour voir les graphiques ici.</p>
1434
+ <button onclick="switchTab('expert')">
1435
+ Aller à l'évaluation Expert
1436
+ </button>
1437
+ </div>
1438
+ `;
1439
+
1440
+ document.getElementById('mainTrustChartContainer').innerHTML = noDataHTML;
1441
+ document.getElementById('improvementChartContainer').innerHTML = noDataHTML;
1442
+ document.getElementById('detailedMetricsChartContainer').innerHTML = noDataHTML;
1443
+ return;
1444
+ }
1445
+
1446
+ const labels = calibratedScenarios;
1447
+ const beforeData = calibratedScenarios.map(id => trustStatsData[id] ? trustStatsData[id].moyenne : 0);
1448
+ const afterData = calibratedScenarios.map(id => calibrationData[id].moyenne);
1449
+ const improvementData = calibratedScenarios.map((id, i) => afterData[i] - beforeData[i]);
1450
+ const targetLine = Array(calibratedScenarios.length).fill(80);
1451
+
1452
+ // Graphique 1: Indice Global Avant/Après
1453
+ const trustCtx = document.getElementById('mainTrustChart').getContext('2d');
1454
+ mainChart = new Chart(trustCtx, {
1455
+ type: 'bar',
1456
+ data: {
1457
+ labels: labels,
1458
+ datasets: [
1459
+ {
1460
+ label: 'Avant Calibration',
1461
+ data: beforeData,
1462
+ backgroundColor: 'rgba(231, 76, 60, 0.8)',
1463
+ borderColor: 'rgba(231, 76, 60, 1)',
1464
+ borderWidth: 1
1465
+ },
1466
+ {
1467
+ label: 'Après Calibration',
1468
+ data: afterData,
1469
+ backgroundColor: 'rgba(46, 204, 113, 0.8)',
1470
+ borderColor: 'rgba(46, 204, 113, 1)',
1471
+ borderWidth: 1
1472
+ },
1473
+ {
1474
+ label: 'Seuil Cible (80%)',
1475
+ data: targetLine,
1476
+ type: 'line',
1477
+ fill: false,
1478
+ borderColor: 'rgba(241, 196, 15, 0.9)',
1479
+ borderWidth: 3,
1480
+ borderDash: [5, 5],
1481
+ pointRadius: 0
1482
+ }
1483
+ ]
1484
+ },
1485
+ options: {
1486
+ responsive: true,
1487
+ maintainAspectRatio: false,
1488
+ scales: {
1489
+ y: {
1490
+ beginAtZero: true,
1491
+ max: 100,
1492
+ title: {
1493
+ display: true,
1494
+ text: 'Indice Global de Confiance (%)'
1495
+ }
1496
+ }
1497
+ }
1498
+ }
1499
+ });
1500
+
1501
+ // Graphique 2: Amélioration par scénario
1502
+ const improvementCtx = document.getElementById('improvementChart').getContext('2d');
1503
+ improvementChart = new Chart(improvementCtx, {
1504
+ type: 'bar',
1505
+ data: {
1506
+ labels: labels,
1507
+ datasets: [{
1508
+ label: 'Amélioration (%)',
1509
+ data: improvementData,
1510
+ backgroundColor: improvementData.map(value =>
1511
+ value > 30 ? 'rgba(46, 204, 113, 0.8)' :
1512
+ value > 15 ? 'rgba(241, 196, 15, 0.8)' :
1513
+ 'rgba(231, 76, 60, 0.8)'
1514
+ ),
1515
+ borderColor: improvementData.map(value =>
1516
+ value > 30 ? 'rgba(46, 204, 113, 1)' :
1517
+ value > 15 ? 'rgba(241, 196, 15, 1)' :
1518
+ 'rgba(231, 76, 60, 1)'
1519
+ ),
1520
+ borderWidth: 1
1521
+ }]
1522
+ },
1523
+ options: {
1524
+ responsive: true,
1525
+ maintainAspectRatio: false,
1526
+ scales: {
1527
+ y: {
1528
+ beginAtZero: true,
1529
+ title: {
1530
+ display: true,
1531
+ text: 'Amélioration (points %)'
1532
+ }
1533
+ }
1534
+ }
1535
+ }
1536
+ });
1537
+
1538
+ // Graphique 3: Détail des 5 métriques (moyennes)
1539
+ const detailedCtx = document.getElementById('detailedMetricsChart').getContext('2d');
1540
+ const metricBeforeData = metrics.map(metric => {
1541
+ const values = calibratedScenarios.map(id =>
1542
+ trustStatsData[id] ? trustStatsData[id][metric.id] || 0 : 0
1543
+ );
1544
+ return values.reduce((a, b) => a + b, 0) / values.length;
1545
+ });
1546
+
1547
+ const metricAfterData = metrics.map(metric => {
1548
+ const values = calibratedScenarios.map(id =>
1549
+ calibrationData[id] ? calibrationData[id][metric.id] || 0 : 0
1550
+ );
1551
+ return values.reduce((a, b) => a + b, 0) / values.length;
1552
+ });
1553
+
1554
+ detailedChart = new Chart(detailedCtx, {
1555
+ type: 'bar',
1556
+ data: {
1557
+ labels: metrics.map(m => m.id),
1558
+ datasets: [
1559
+ {
1560
+ label: 'Avant Calibration',
1561
+ data: metricBeforeData,
1562
+ backgroundColor: metrics.map(m => m.color + '80'),
1563
+ borderColor: metrics.map(m => m.color),
1564
+ borderWidth: 1
1565
+ },
1566
+ {
1567
+ label: 'Après Calibration',
1568
+ data: metricAfterData,
1569
+ backgroundColor: metrics.map(m => m.color),
1570
+ borderColor: metrics.map(m => m.color),
1571
+ borderWidth: 1
1572
+ }
1573
+ ]
1574
+ },
1575
+ options: {
1576
+ responsive: true,
1577
+ maintainAspectRatio: false,
1578
+ scales: {
1579
+ y: {
1580
+ beginAtZero: true,
1581
+ max: 100,
1582
+ title: {
1583
+ display: true,
1584
+ text: 'Score Moyen (%)'
1585
+ }
1586
+ }
1587
+ }
1588
+ }
1589
+ });
1590
+ }
1591
+
1592
+ function generateDetailedComparison() {
1593
+ const calibratedScenarios = Object.keys(calibrationData);
1594
+ console.log('Scénarios calibrés pour la comparaison détaillée:', calibratedScenarios);
1595
+
1596
+ let html = '';
1597
+
1598
+ if (calibratedScenarios.length === 0) {
1599
+ html = `
1600
+ <div class="no-data-message">
1601
+ <h3>📋 Aucune donnée disponible</h3>
1602
+ <p>Effectuez des calibrations dans l'onglet "Évaluation Expert" pour voir les comparaisons détaillées.</p>
1603
+ <button onclick="switchTab('expert')">
1604
+ Aller à l'évaluation Expert
1605
+ </button>
1606
+ </div>
1607
+ `;
1608
+ } else {
1609
+ // Trier les scénarios par ID
1610
+ calibratedScenarios.sort((a, b) => {
1611
+ const numA = parseInt(a.replace('S', ''));
1612
+ const numB = parseInt(b.replace('S', ''));
1613
+ return numA - numB;
1614
+ });
1615
+
1616
+ calibratedScenarios.forEach(scenarioId => {
1617
+ const scenario = scenariosList.find(s => s.id === scenarioId);
1618
+ const before = trustStatsData[scenarioId] || {};
1619
+ const after = calibrationData[scenarioId] || {};
1620
+
1621
+ console.log(`Génération pour ${scenarioId}:`, {before, after});
1622
+
1623
+ html += `
1624
+ <div style="background: white; border-radius: 10px; padding: 20px; margin-bottom: 20px; box-shadow: 0 3px 10px rgba(0,0,0,0.05);">
1625
+ <h4 style="color: #2c3e50; margin-bottom: 15px;">${scenarioId}: ${scenario ? scenario.name : 'Inconnu'}</h4>
1626
+ <p style="color: #666; margin-bottom: 15px; font-style: italic;">${scenario ? scenario.description : 'Pas de description'}</p>
1627
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
1628
+ `;
1629
+
1630
+ metrics.forEach(metric => {
1631
+ // Récupérer les valeurs avec des valeurs par défaut
1632
+ const beforeValue = before[metric.id] || 0;
1633
+ const afterValue = after[metric.id] || 0;
1634
+ const improvement = afterValue - beforeValue;
1635
+
1636
+ // Assurer que les valeurs sont des nombres
1637
+ const safeBeforeValue = Number(beforeValue) || 0;
1638
+ const safeAfterValue = Number(afterValue) || 0;
1639
+ const safeImprovement = safeAfterValue - safeBeforeValue;
1640
+
1641
+ // Calculer les largeurs pour les barres
1642
+ const beforeWidth = Math.min(100, Math.max(0, safeBeforeValue));
1643
+ const improvementWidth = Math.min(100 - beforeWidth, Math.max(0, safeImprovement));
1644
+
1645
+ html += `
1646
+ <div style="padding: 15px; border-radius: 8px; border-left: 4px solid ${metric.color}; background: #f8f9fa;">
1647
+ <div style="font-weight: bold; margin-bottom: 5px; color: ${metric.color};">${metric.id} - ${metric.name.split('(')[0].trim()}</div>
1648
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
1649
+ <span style="color: #e74c3c; font-size: 14px;">${safeBeforeValue.toFixed(0)}%</span>
1650
+ <span style="color: #27ae60; font-size: 14px;">${safeAfterValue.toFixed(0)}%</span>
1651
+ </div>
1652
+ <div style="height: 6px; background: #e9ecef; border-radius: 3px; margin-bottom: 5px; overflow: hidden;">
1653
+ <div style="height: 100%; width: ${beforeWidth}%; background: ${metric.color}; opacity: 0.5; float: left;"></div>
1654
+ <div style="height: 100%; width: ${improvementWidth}%; background: ${metric.color}; float: left;"></div>
1655
+ </div>
1656
+ <div style="text-align: right; font-size: 12px; color: ${safeImprovement > 0 ? '#27ae60' : '#e74c3c'}; font-weight: bold;">
1657
+ ${safeImprovement > 0 ? '+' : ''}${safeImprovement.toFixed(1)}%
1658
+ </div>
1659
+ </div>
1660
+ `;
1661
+ });
1662
+
1663
+ // Calculer les moyennes
1664
+ const beforeAvg = before.moyenne || 0;
1665
+ const afterAvg = after.moyenne || 0;
1666
+ const avgImprovement = afterAvg - beforeAvg;
1667
+
1668
+ html += `
1669
+ </div>
1670
+ <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #eee;">
1671
+ <div style="display: flex; justify-content: space-between; align-items: center;">
1672
+ <div>
1673
+ <strong>Indice Global:</strong>
1674
+ <span style="color: #e74c3c; margin-left: 10px; font-weight: bold;">${Number(beforeAvg).toFixed(1)}%</span>
1675
+ <span style="margin: 0 10px; font-weight: bold;">→</span>
1676
+ <span style="color: #27ae60; font-weight: bold;">${Number(afterAvg).toFixed(1)}%</span>
1677
+ </div>
1678
+ <div style="padding: 8px 15px; border-radius: 20px; background: ${avgImprovement > 0 ? '#d4edda' : '#f8d7da'}; color: ${avgImprovement > 0 ? '#155724' : '#721c24'}; font-weight: bold;">
1679
+ ${avgImprovement > 0 ? '+' : ''}${Number(avgImprovement).toFixed(1)}%
1680
+ </div>
1681
+ </div>
1682
+ <div style="margin-top: 10px; font-size: 12px; color: #666;">
1683
+ <strong>Expert:</strong> ${after.expert || 'Non spécifié'} |
1684
+ <strong>Date:</strong> ${after.date || 'Non spécifiée'}
1685
+ </div>
1686
+ </div>
1687
+ </div>
1688
+ `;
1689
+ });
1690
+ }
1691
+
1692
+ document.getElementById('detailedComparison').innerHTML = html;
1693
+ console.log('HTML généré pour la comparaison détaillée');
1694
+ }
1695
+
1696
+ // ============================================
1697
+ // FONCTIONS UTILITAIRES
1698
+ // ============================================
1699
+
1700
+ function showNotification(message, type = 'success') {
1701
+ const notification = document.createElement('div');
1702
+ notification.className = `notification ${type}`;
1703
+ notification.textContent = message;
1704
+ document.body.appendChild(notification);
1705
+
1706
+ setTimeout(() => {
1707
+ notification.style.animation = 'slideOut 0.3s ease';
1708
+ setTimeout(() => {
1709
+ document.body.removeChild(notification);
1710
+ }, 300);
1711
+ }, 3000);
1712
+ }
1713
+
1714
+ // ============================================
1715
+ // FONCTIONS D'EXPORT/IMPORT
1716
+ // ============================================
1717
+
1718
+ function exportCalibrationData() {
1719
+ const dataStr = JSON.stringify(calibrationData, null, 2);
1720
+ const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
1721
+
1722
+ const linkElement = document.createElement('a');
1723
+ linkElement.setAttribute('href', dataUri);
1724
+ linkElement.setAttribute('download', 'calibration-data-export.json');
1725
+ linkElement.click();
1726
+
1727
+ showNotification('Données exportées en JSON', 'success');
1728
+ }
1729
+
1730
+ function importCalibrationData() {
1731
+ const input = document.createElement('input');
1732
+ input.type = 'file';
1733
+ input.accept = '.json';
1734
+
1735
+ input.onchange = function(event) {
1736
+ const file = event.target.files[0];
1737
+ const reader = new FileReader();
1738
+
1739
+ reader.onload = function(e) {
1740
+ try {
1741
+ const importedData = JSON.parse(e.target.result);
1742
+ calibrationData = { ...calibrationData, ...importedData };
1743
+ localStorage.setItem('calibration-trust', JSON.stringify(calibrationData));
1744
+
1745
+ showNotification('Données importées avec succès', 'success');
1746
+
1747
+ if (document.getElementById('consensus').classList.contains('active')) {
1748
+ loadConsensusData();
1749
+ }
1750
+ } catch (error) {
1751
+ showNotification('Erreur lors de l\'import: ' + error.message, 'error');
1752
+ }
1753
+ };
1754
+
1755
+ reader.readAsText(file);
1756
+ };
1757
+
1758
+ input.click();
1759
+ }
1760
+
1761
+ function showCalibrationStructure() {
1762
+ const exampleStructure = {
1763
+ "experts": [
1764
+ {
1765
+ "expert_id": "expert_jean_dupont_1234567890",
1766
+ "expert_name": "Jean Dupont",
1767
+ "evaluations": [
1768
+ {
1769
+ "evaluation_id": "eval_1234567890_abc123",
1770
+ "scenario": "S1",
1771
+ "date": "2024-01-16",
1772
+ "time": "14:30:00",
1773
+ "expert": "Jean Dupont",
1774
+ "expert_id": "expert_jean_dupont_1234567890",
1775
+ "AR": 85,
1776
+ "AE": 82,
1777
+ "ESR": 78,
1778
+ "SDM": 80,
1779
+ "SM": 76,
1780
+ "moyenne": 80.2
1781
+ }
1782
+ ],
1783
+ "last_updated": "2024-01-16T14:30:00.000Z"
1784
+ }
1785
+ ]
1786
+ };
1787
+
1788
+ alert(`Structure de calibration-trust.json :
1789
+
1790
+ Le fichier doit contenir un tableau "experts" avec :
1791
+ 1. expert_id : Identifiant unique de l'expert
1792
+ 2. expert_name : Nom de l'expert
1793
+ 3. evaluations : Tableau des évaluations
1794
+ 4. last_updated : Date de dernière modification
1795
+
1796
+ Chaque évaluation contient :
1797
+ - evaluation_id : ID unique
1798
+ - scenario : ID du scénario (ex: "S1")
1799
+ - date, time : Date et heure
1800
+ - expert : Nom de l'expert
1801
+ - AR, AE, ESR, SDM, SM : Scores 0-100%
1802
+ - moyenne : Moyenne des 5 métriques
1803
+
1804
+ Voir la console pour un exemple complet.`);
1805
+
1806
+ console.log('Exemple de structure calibration-trust.json:', exampleStructure);
1807
+ }
1808
+
1809
+ // ============================================
1810
+ // AJOUT DES BOUTONS D'UTILITAIRE
1811
+ // ============================================
1812
+
1813
+ function addUtilityButtons() {
1814
+ const expertHeader = document.querySelector('#expert header');
1815
+ const consensusHeader = document.querySelector('#consensus header');
1816
+
1817
+ const buttonsHTML = `
1818
+ <div style="margin-top: 20px; display: flex; gap: 10px; flex-wrap: wrap;">
1819
+ <button onclick="showCalibrationStructure()" style="padding: 10px 20px; background: #9b59b6; color: white; border: none; border-radius: 5px; cursor: pointer;">
1820
+ 📋 Voir la structure JSON
1821
+ </button>
1822
+ <button onclick="exportCalibrationData()" style="padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 5px; cursor: pointer;">
1823
+ 📥 Exporter les données
1824
+ </button>
1825
+ <button onclick="importCalibrationData()" style="padding: 10px 20px; background: #2ecc71; color: white; border: none; border-radius: 5px; cursor: pointer;">
1826
+ 📤 Importer des données
1827
+ </button>
1828
+ <button onclick="location.reload()" style="padding: 10px 20px; background: #e74c3c; color: white; border: none; border-radius: 5px; cursor: pointer;">
1829
+ 🔄 Rafraîchir
1830
+ </button>
1831
+ </div>
1832
+ `;
1833
+
1834
+ if (expertHeader) {
1835
+ expertHeader.insertAdjacentHTML('beforeend', buttonsHTML);
1836
+ }
1837
+
1838
+ if (consensusHeader) {
1839
+ consensusHeader.insertAdjacentHTML('beforeend', buttonsHTML);
1840
+ }
1841
+ }
1842
+
1843
+ // ============================================
1844
+ // INITIALISATION DE L'APPLICATION
1845
+ // ============================================
1846
+
1847
+ document.addEventListener('DOMContentLoaded', async function() {
1848
+ // Initialiser les onglets
1849
+ document.querySelectorAll('.tab-button').forEach(button => {
1850
+ button.addEventListener('click', function() {
1851
+ switchTab(this.getAttribute('data-tab'));
1852
+ });
1853
+ });
1854
+
1855
+ // Charger les données réelles AVANT de charger les scénarios
1856
+ await loadRealDataFromJSON();
1857
+
1858
+ // Charger les scénarios depuis JSON
1859
+ await loadScenarios();
1860
+
1861
+ // Ajouter les boutons d'utilitaire
1862
+ setTimeout(addUtilityButtons, 1000);
1863
+
1864
+ // Si on est sur l'onglet consensus, charger les données
1865
+ if (document.getElementById('consensus').classList.contains('active')) {
1866
+ loadConsensusData();
1867
+ }
1868
+ });
1869
+ </script>
1870
+ </body>
1871
+ </html>