D Ф m i И i q ц e L Ф y e r commited on
Commit
3d19c67
·
1 Parent(s): 78b2787

Feature: Explainability dashboard + Google Fact Check API

Browse files
Files changed (2) hide show
  1. syscred/.env +15 -0
  2. syscred/static/index.html +363 -2
syscred/.env ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SysCRED Environment Configuration
2
+ # (c) Dominique S. Loyer
3
+
4
+ # === Google Fact Check API ===
5
+ SYSCRED_GOOGLE_API_KEY=AIzaSyBiuY4AxuPgHcrViQJQ6BcKs1wOIqsiz74
6
+
7
+ # === Server Configuration ===
8
+ SYSCRED_PORT=5001
9
+ SYSCRED_HOST=127.0.0.1
10
+
11
+ # === Environment Mode ===
12
+ SYSCRED_ENV=development
13
+
14
+ # === ML Models ===
15
+ SYSCRED_LOAD_ML_MODELS=true
syscred/static/index.html CHANGED
@@ -396,6 +396,143 @@
396
  }
397
  .backend-status.local { color: #22c55e; }
398
  .backend-status.remote { color: #a855f7; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  </style>
400
  </head>
401
 
@@ -436,9 +573,10 @@
436
 
437
  <div class="results" id="results">
438
  <div class="score-card">
439
- <div class="score-label">Score de Crédibilité</div>
440
- <div class="score-value" id="scoreValue">0.00</div>
441
  <div class="credibility-badge" id="credibilityBadge">-</div>
 
442
  </div>
443
 
444
  <div class="summary-box">
@@ -459,6 +597,19 @@
459
  </div>
460
  </div>
461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  <footer>
463
  <p>SysCRED v2.0 - Prototype de recherche doctorale</p>
464
  <p>© Dominique S. Loyer - UQAM | <a href="https://doi.org/10.5281/zenodo.17943226" target="_blank">DOI:
@@ -943,6 +1094,216 @@
943
  analyzeUrl();
944
  }
945
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
946
  </script>
947
  </body>
948
 
 
396
  }
397
  .backend-status.local { color: #22c55e; }
398
  .backend-status.remote { color: #a855f7; }
399
+
400
+ /* === EXPLAINABILITY MODAL === */
401
+ .explainer-modal {
402
+ position: fixed;
403
+ top: 0;
404
+ left: 0;
405
+ width: 100%;
406
+ height: 100%;
407
+ background: rgba(0, 0, 0, 0.8);
408
+ backdrop-filter: blur(10px);
409
+ display: none;
410
+ justify-content: center;
411
+ align-items: center;
412
+ z-index: 1000;
413
+ }
414
+ .explainer-modal.visible { display: flex; }
415
+ .explainer-content {
416
+ background: linear-gradient(135deg, #1a1a3e 0%, #0f0f23 100%);
417
+ border: 1px solid rgba(124, 58, 237, 0.5);
418
+ border-radius: 20px;
419
+ max-width: 600px;
420
+ width: 90%;
421
+ max-height: 80vh;
422
+ overflow-y: auto;
423
+ padding: 2rem;
424
+ animation: slideUp 0.3s ease;
425
+ }
426
+ @keyframes slideUp {
427
+ from { transform: translateY(50px); opacity: 0; }
428
+ to { transform: translateY(0); opacity: 1; }
429
+ }
430
+ .explainer-header {
431
+ display: flex;
432
+ justify-content: space-between;
433
+ align-items: center;
434
+ margin-bottom: 1.5rem;
435
+ border-bottom: 1px solid rgba(255,255,255,0.1);
436
+ padding-bottom: 1rem;
437
+ }
438
+ .explainer-title {
439
+ font-size: 1.5rem;
440
+ font-weight: 700;
441
+ color: #fff;
442
+ }
443
+ .explainer-close {
444
+ background: none;
445
+ border: none;
446
+ color: #8b8ba7;
447
+ font-size: 2rem;
448
+ cursor: pointer;
449
+ padding: 0;
450
+ line-height: 1;
451
+ }
452
+ .explainer-close:hover { color: #fff; transform: none; box-shadow: none; }
453
+ .metric-explain {
454
+ background: rgba(255,255,255,0.03);
455
+ border-radius: 12px;
456
+ padding: 1.25rem;
457
+ margin-bottom: 1rem;
458
+ border-left: 4px solid #7c3aed;
459
+ }
460
+ .metric-explain-header {
461
+ display: flex;
462
+ align-items: center;
463
+ gap: 0.75rem;
464
+ margin-bottom: 0.75rem;
465
+ }
466
+ .metric-explain-icon {
467
+ font-size: 1.5rem;
468
+ }
469
+ .metric-explain-name {
470
+ font-weight: 600;
471
+ color: #fff;
472
+ font-size: 1.1rem;
473
+ }
474
+ .metric-explain-value {
475
+ background: rgba(124, 58, 237, 0.2);
476
+ padding: 0.25rem 0.5rem;
477
+ border-radius: 6px;
478
+ font-weight: 700;
479
+ color: #a855f7;
480
+ margin-left: auto;
481
+ }
482
+ .metric-explain-simple {
483
+ color: #e0e0e0;
484
+ font-size: 1rem;
485
+ line-height: 1.6;
486
+ margin-bottom: 0.5rem;
487
+ }
488
+ .metric-explain-detail {
489
+ color: #8b8ba7;
490
+ font-size: 0.85rem;
491
+ font-style: italic;
492
+ }
493
+ .score-clickable {
494
+ cursor: pointer;
495
+ transition: transform 0.2s ease;
496
+ }
497
+ .score-clickable:hover {
498
+ transform: scale(1.05);
499
+ }
500
+ .help-icon {
501
+ display: inline-block;
502
+ width: 18px;
503
+ height: 18px;
504
+ background: rgba(124, 58, 237, 0.3);
505
+ border-radius: 50%;
506
+ text-align: center;
507
+ line-height: 18px;
508
+ font-size: 12px;
509
+ color: #a855f7;
510
+ cursor: pointer;
511
+ margin-left: 0.5rem;
512
+ vertical-align: middle;
513
+ }
514
+ .help-icon:hover {
515
+ background: rgba(124, 58, 237, 0.6);
516
+ color: #fff;
517
+ }
518
+ .verdict-bar {
519
+ height: 8px;
520
+ background: rgba(255,255,255,0.1);
521
+ border-radius: 4px;
522
+ overflow: hidden;
523
+ margin: 0.5rem 0;
524
+ }
525
+ .verdict-fill {
526
+ height: 100%;
527
+ border-radius: 4px;
528
+ transition: width 0.5s ease;
529
+ }
530
+ .verdict-labels {
531
+ display: flex;
532
+ justify-content: space-between;
533
+ font-size: 0.75rem;
534
+ color: #6b6b8a;
535
+ }
536
  </style>
537
  </head>
538
 
 
573
 
574
  <div class="results" id="results">
575
  <div class="score-card">
576
+ <div class="score-label">Score de Crédibilité <span class="help-icon" onclick="event.stopPropagation(); openExplainer()">?</span></div>
577
+ <div class="score-value score-clickable" id="scoreValue" onclick="openExplainer()" title="Cliquez pour comprendre ce score">0.00</div>
578
  <div class="credibility-badge" id="credibilityBadge">-</div>
579
+ <small style="color: #6b6b8a; margin-top: 0.5rem; display: block;">👆 Cliquez sur le score pour comprendre</small>
580
  </div>
581
 
582
  <div class="summary-box">
 
597
  </div>
598
  </div>
599
 
600
+ <!-- EXPLAINER MODAL -->
601
+ <div class="explainer-modal" id="explainerModal" onclick="if(event.target === this) closeExplainer()">
602
+ <div class="explainer-content">
603
+ <div class="explainer-header">
604
+ <div class="explainer-title">🔍 Comprendre votre score</div>
605
+ <button class="explainer-close" onclick="closeExplainer()">×</button>
606
+ </div>
607
+ <div id="explainerBody">
608
+ <!-- Content filled dynamically -->
609
+ </div>
610
+ </div>
611
+ </div>
612
+
613
  <footer>
614
  <p>SysCRED v2.0 - Prototype de recherche doctorale</p>
615
  <p>© Dominique S. Loyer - UQAM | <a href="https://doi.org/10.5281/zenodo.17943226" target="_blank">DOI:
 
1094
  analyzeUrl();
1095
  }
1096
  });
1097
+
1098
+ // === EXPLAINABILITY DASHBOARD ===
1099
+ let lastAnalysisData = null; // Store last analysis for explainer
1100
+
1101
+ // Store data after analysis
1102
+ const originalDisplayResults = displayResults;
1103
+ displayResults = function(data) {
1104
+ lastAnalysisData = data;
1105
+ originalDisplayResults(data);
1106
+ };
1107
+
1108
+ function openExplainer() {
1109
+ if (!lastAnalysisData) {
1110
+ alert('Analysez d\'abord une URL pour voir les explications.');
1111
+ return;
1112
+ }
1113
+
1114
+ const modal = document.getElementById('explainerModal');
1115
+ const body = document.getElementById('explainerBody');
1116
+
1117
+ const score = lastAnalysisData.scoreCredibilite || 0;
1118
+ const ruleResults = lastAnalysisData.reglesAppliquees || {};
1119
+ const nlpAnalysis = lastAnalysisData.analyseNLP || {};
1120
+ const sourceAnalysis = ruleResults.source_analysis || {};
1121
+
1122
+ // Determine verdict color and message
1123
+ let verdictColor, verdictText, verdictEmoji;
1124
+ if (score >= 0.7) {
1125
+ verdictColor = '#22c55e';
1126
+ verdictText = 'Vous pouvez faire confiance à cette source.';
1127
+ verdictEmoji = '✅';
1128
+ } else if (score >= 0.4) {
1129
+ verdictColor = '#eab308';
1130
+ verdictText = 'Soyez prudent, vérifiez avec d\'autres sources.';
1131
+ verdictEmoji = '⚠️';
1132
+ } else {
1133
+ verdictColor = '#ef4444';
1134
+ verdictText = 'Méfiez-vous, cette source semble peu fiable.';
1135
+ verdictEmoji = '❌';
1136
+ }
1137
+
1138
+ let html = `
1139
+ <!-- Score Global -->
1140
+ <div class="metric-explain" style="border-left-color: ${verdictColor}; background: rgba(${verdictColor === '#22c55e' ? '34,197,94' : verdictColor === '#eab308' ? '234,179,8' : '239,68,68'}, 0.1);">
1141
+ <div class="metric-explain-header">
1142
+ <span class="metric-explain-icon">${verdictEmoji}</span>
1143
+ <span class="metric-explain-name">Score Global</span>
1144
+ <span class="metric-explain-value" style="color: ${verdictColor}; font-size: 1.5rem;">${(score * 100).toFixed(0)}%</span>
1145
+ </div>
1146
+ <div class="metric-explain-simple">
1147
+ <strong>${verdictText}</strong><br><br>
1148
+ C'est comme une note à l'école :<br>
1149
+ • <span style="color:#22c55e">70-100%</span> = Excellent, source fiable<br>
1150
+ • <span style="color:#eab308">40-69%</span> = Moyen, à vérifier<br>
1151
+ • <span style="color:#ef4444">0-39%</span> = Faible, méfiance recommandée
1152
+ </div>
1153
+ <div class="verdict-bar">
1154
+ <div class="verdict-fill" style="width: ${score * 100}%; background: ${verdictColor};"></div>
1155
+ </div>
1156
+ <div class="verdict-labels">
1157
+ <span>⚠️ Pas fiable</span>
1158
+ <span>✅ Très fiable</span>
1159
+ </div>
1160
+ </div>
1161
+ `;
1162
+
1163
+ // Reputation
1164
+ if (sourceAnalysis.reputation) {
1165
+ const rep = sourceAnalysis.reputation;
1166
+ const repEmoji = rep === 'High' ? '🏆' : rep === 'Medium' ? '👍' : rep === 'Low' ? '⚠️' : '❓';
1167
+ const repText = rep === 'High' ? 'Source reconnue et respectée (ex: Le Monde, BBC)'
1168
+ : rep === 'Medium' ? 'Source correcte mais à vérifier'
1169
+ : rep === 'Low' ? 'Source peu fiable ou inconnue'
1170
+ : 'Nous ne connaissons pas cette source';
1171
+
1172
+ html += `
1173
+ <div class="metric-explain">
1174
+ <div class="metric-explain-header">
1175
+ <span class="metric-explain-icon">${repEmoji}</span>
1176
+ <span class="metric-explain-name">Réputation de la Source</span>
1177
+ <span class="metric-explain-value">${rep}</span>
1178
+ </div>
1179
+ <div class="metric-explain-simple">
1180
+ ${repText}
1181
+ </div>
1182
+ <div class="metric-explain-detail">
1183
+ Nous comparons le site à une base de données de médias connus et vérifions s'il est cité par des journalistes.
1184
+ </div>
1185
+ </div>
1186
+ `;
1187
+ }
1188
+
1189
+ // Coherence
1190
+ if (nlpAnalysis.coherence_score !== undefined) {
1191
+ const coh = nlpAnalysis.coherence_score;
1192
+ const cohPercent = (coh * 100).toFixed(0);
1193
+ const cohEmoji = coh >= 0.6 ? '📝' : coh >= 0.4 ? '📄' : '❓';
1194
+
1195
+ html += `
1196
+ <div class="metric-explain">
1197
+ <div class="metric-explain-header">
1198
+ <span class="metric-explain-icon">${cohEmoji}</span>
1199
+ <span class="metric-explain-name">Cohérence du Texte</span>
1200
+ <span class="metric-explain-value">${cohPercent}%</span>
1201
+ </div>
1202
+ <div class="metric-explain-simple">
1203
+ Le texte est-il logique et bien écrit ?<br>
1204
+ Un texte bien structuré avec des phrases claires est généralement plus fiable.
1205
+ </div>
1206
+ <div class="metric-explain-detail">
1207
+ Notre intelligence artificielle analyse si les phrases sont cohérentes entre elles et si le texte suit une logique.
1208
+ </div>
1209
+ </div>
1210
+ `;
1211
+ }
1212
+
1213
+ // PageRank
1214
+ if (lastAnalysisData.pageRankEstimation && lastAnalysisData.pageRankEstimation.estimatedPR) {
1215
+ const pr = lastAnalysisData.pageRankEstimation.estimatedPR;
1216
+ const prEmoji = pr >= 0.4 ? '🌟' : pr >= 0.2 ? '⭐' : '💫';
1217
+
1218
+ html += `
1219
+ <div class="metric-explain">
1220
+ <div class="metric-explain-header">
1221
+ <span class="metric-explain-icon">${prEmoji}</span>
1222
+ <span class="metric-explain-name">PageRank (Popularité)</span>
1223
+ <span class="metric-explain-value">${pr.toFixed(3)}</span>
1224
+ </div>
1225
+ <div class="metric-explain-simple">
1226
+ Plus un site est populaire et cité par d'autres sites importants, plus il est probablement fiable.<br>
1227
+ C'est comme le bouche-à-oreille : si beaucoup de gens recommandent quelqu'un, c'est bon signe.
1228
+ </div>
1229
+ <div class="metric-explain-detail">
1230
+ Nous estimons combien de sites importants font des liens vers cette page (algorithme inspiré de Google).
1231
+ </div>
1232
+ </div>
1233
+ `;
1234
+ }
1235
+
1236
+ // SEO
1237
+ if (lastAnalysisData.seoAnalysis && lastAnalysisData.seoAnalysis.seoScore) {
1238
+ const seo = parseFloat(lastAnalysisData.seoAnalysis.seoScore);
1239
+ const seoEmoji = seo >= 0.7 ? '🔧' : seo >= 0.5 ? '🛠️' : '⚙️';
1240
+
1241
+ html += `
1242
+ <div class="metric-explain">
1243
+ <div class="metric-explain-header">
1244
+ <span class="metric-explain-icon">${seoEmoji}</span>
1245
+ <span class="metric-explain-name">Qualité Technique (SEO)</span>
1246
+ <span class="metric-explain-value">${seo.toFixed(2)}</span>
1247
+ </div>
1248
+ <div class="metric-explain-simple">
1249
+ Le site est-il bien construit ?<br>
1250
+ Un site professionnel bien structuré inspire plus confiance qu'un site mal fait.
1251
+ </div>
1252
+ <div class="metric-explain-detail">
1253
+ Nous vérifions si le site a un titre, une description, des balises correctes, etc.
1254
+ </div>
1255
+ </div>
1256
+ `;
1257
+ }
1258
+
1259
+ // Fact-Checks
1260
+ const factChecks = ruleResults.fact_checking || [];
1261
+ html += `
1262
+ <div class="metric-explain" style="border-left-color: #f472b6;">
1263
+ <div class="metric-explain-header">
1264
+ <span class="metric-explain-icon">🕵️</span>
1265
+ <span class="metric-explain-name">Vérification des Faits</span>
1266
+ <span class="metric-explain-value">${factChecks.length} trouvé(s)</span>
1267
+ </div>
1268
+ <div class="metric-explain-simple">
1269
+ Nous cherchons si des fact-checkers professionnels ont déjà vérifié des informations similaires.<br>
1270
+ ${factChecks.length > 0
1271
+ ? '✅ Des vérifications existent - consultez-les !'
1272
+ : 'Aucune vérification trouvée - cela ne veut pas dire que c\'est faux.'}
1273
+ </div>
1274
+ <div class="metric-explain-detail">
1275
+ Source : Google Fact Check Tools API - vérifie auprès de PolitiFact, Snopes, AFP, etc.
1276
+ </div>
1277
+ </div>
1278
+ `;
1279
+
1280
+ // How it's calculated
1281
+ html += `
1282
+ <div style="margin-top: 1.5rem; padding: 1rem; background: rgba(124, 58, 237, 0.1); border-radius: 12px; border: 1px dashed rgba(124, 58, 237, 0.3);">
1283
+ <strong style="color: #a855f7;">📊 Comment le score est calculé ?</strong>
1284
+ <p style="margin-top: 0.5rem; color: #8b8ba7; font-size: 0.9rem;">
1285
+ Le score combine plusieurs facteurs :<br>
1286
+ • Réputation de la source (22%)<br>
1287
+ • Cohérence du texte (12%)<br>
1288
+ • Qualité technique (15%)<br>
1289
+ • Vérifications de faits (17%)<br>
1290
+ • Âge du domaine et autres (34%)
1291
+ </p>
1292
+ </div>
1293
+ `;
1294
+
1295
+ body.innerHTML = html;
1296
+ modal.classList.add('visible');
1297
+ }
1298
+
1299
+ function closeExplainer() {
1300
+ document.getElementById('explainerModal').classList.remove('visible');
1301
+ }
1302
+
1303
+ // Close modal with Escape key
1304
+ document.addEventListener('keydown', function(e) {
1305
+ if (e.key === 'Escape') closeExplainer();
1306
+ });
1307
  </script>
1308
  </body>
1309