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

✨ Add NER, E-E-A-T modules and enhanced dashboard

Browse files
Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
  # SysCRED Docker Configuration for Hugging Face Spaces
2
- # Full version with PyTorch and Transformers
3
  FROM python:3.10-slim
4
 
5
  WORKDIR /app
@@ -7,19 +7,46 @@ WORKDIR /app
7
  ENV PYTHONDONTWRITEBYTECODE=1
8
  ENV PYTHONUNBUFFERED=1
9
  ENV PYTHONPATH=/app
 
 
 
 
10
  ENV SYSCRED_LOAD_ML_MODELS=true
 
 
 
11
 
12
  # Install system dependencies
13
  RUN apt-get update && apt-get install -y \
14
  build-essential \
15
  && rm -rf /var/lib/apt/lists/*
16
 
17
- # Copy requirements (full version with ML)
18
- COPY requirements.txt /app/requirements.txt
19
 
20
- # Install dependencies (includes PyTorch, Transformers)
21
  RUN pip install --no-cache-dir -r requirements.txt
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  # Copy application code
24
  COPY syscred/ /app/syscred/
25
 
@@ -34,4 +61,5 @@ WORKDIR /app
34
  EXPOSE 7860
35
 
36
  # Run with HF Spaces port (7860)
37
- CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "2", "--timeout", "300", "syscred.backend_app:app"]
 
 
1
  # SysCRED Docker Configuration for Hugging Face Spaces
2
+ # OPTIMIZED version with Distilled Models for faster startup
3
  FROM python:3.10-slim
4
 
5
  WORKDIR /app
 
7
  ENV PYTHONDONTWRITEBYTECODE=1
8
  ENV PYTHONUNBUFFERED=1
9
  ENV PYTHONPATH=/app
10
+
11
+ # ============================================
12
+ # KEY OPTIMIZATION: Use distilled models
13
+ # ============================================
14
  ENV SYSCRED_LOAD_ML_MODELS=true
15
+ ENV SYSCRED_USE_DISTILLED=true
16
+ ENV TRANSFORMERS_CACHE=/app/.cache/huggingface
17
+ ENV HF_HOME=/app/.cache/huggingface
18
 
19
  # Install system dependencies
20
  RUN apt-get update && apt-get install -y \
21
  build-essential \
22
  && rm -rf /var/lib/apt/lists/*
23
 
24
+ # Copy optimized requirements (distilled models, CPU-only torch)
25
+ COPY requirements-distilled.txt /app/requirements.txt
26
 
27
+ # Install dependencies
28
  RUN pip install --no-cache-dir -r requirements.txt
29
 
30
+ # ============================================
31
+ # PRE-DOWNLOAD DISTILLED MODELS (Build Time)
32
+ # This avoids timeout during first request
33
+ # ============================================
34
+ RUN python -c "from transformers import pipeline; \
35
+ pipeline('sentiment-analysis', model='distilbert-base-uncased-finetuned-sst-2-english'); \
36
+ pipeline('ner', model='dslim/bert-base-NER'); \
37
+ print('✓ Distilled models pre-downloaded')"
38
+
39
+ # Download small spaCy models
40
+ RUN pip install spacy && \
41
+ python -m spacy download en_core_web_sm && \
42
+ python -m spacy download fr_core_news_sm && \
43
+ echo '✓ spaCy models downloaded'
44
+
45
+ # Pre-download sentence transformer (small version)
46
+ RUN python -c "from sentence_transformers import SentenceTransformer; \
47
+ SentenceTransformer('all-MiniLM-L6-v2'); \
48
+ print('✓ Sentence transformer pre-downloaded')"
49
+
50
  # Copy application code
51
  COPY syscred/ /app/syscred/
52
 
 
61
  EXPOSE 7860
62
 
63
  # Run with HF Spaces port (7860)
64
+ # Increased workers to 4 for better concurrency, timeout 600s
65
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "1", "--threads", "4", "--timeout", "600", "syscred.backend_app:app"]
syscred/eeat_calculator.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ E-E-A-T Calculator Module - SysCRED
4
+ ====================================
5
+ Google Quality Rater Guidelines implementation.
6
+
7
+ E-E-A-T Scores:
8
+ - Experience: Domain age, content richness
9
+ - Expertise: Technical vocabulary, citations
10
+ - Authority: Estimated PageRank, backlinks
11
+ - Trust: HTTPS, unbiased sentiment
12
+
13
+ (c) Dominique S. Loyer - PhD Thesis Prototype
14
+ """
15
+
16
+ import re
17
+ from typing import Dict, Optional
18
+ from urllib.parse import urlparse
19
+
20
+
21
+ class EEATCalculator:
22
+ """
23
+ Calculate E-E-A-T scores based on Google Quality Rater Guidelines.
24
+ """
25
+
26
+ # Technical terms that indicate expertise
27
+ TECHNICAL_TERMS = {
28
+ 'research', 'study', 'analysis', 'data', 'evidence', 'methodology',
29
+ 'peer-reviewed', 'journal', 'university', 'professor', 'dr.', 'phd',
30
+ 'statistics', 'experiment', 'hypothesis', 'publication', 'citation',
31
+ 'algorithm', 'framework', 'systematic', 'empirical', 'quantitative'
32
+ }
33
+
34
+ # Trusted domains (simplified list)
35
+ TRUSTED_DOMAINS = {
36
+ '.edu', '.gov', '.org', 'reuters.com', 'apnews.com', 'bbc.com',
37
+ 'nature.com', 'science.org', 'who.int', 'un.org', 'wikipedia.org',
38
+ 'lemonde.fr', 'radio-canada.ca', 'uqam.ca', 'umontreal.ca'
39
+ }
40
+
41
+ def __init__(self):
42
+ """Initialize E-E-A-T calculator."""
43
+ pass
44
+
45
+ def calculate(
46
+ self,
47
+ url: Optional[str] = None,
48
+ text: Optional[str] = None,
49
+ sentiment_score: float = 0.5,
50
+ has_citations: bool = False,
51
+ domain_age_years: int = 0
52
+ ) -> Dict:
53
+ """
54
+ Calculate E-E-A-T scores.
55
+
56
+ Args:
57
+ url: Source URL
58
+ text: Content text
59
+ sentiment_score: 0-1 (0.5 = neutral is best for trust)
60
+ has_citations: Whether content has citations
61
+ domain_age_years: Estimated domain age
62
+
63
+ Returns:
64
+ {
65
+ 'experience': 0.75,
66
+ 'expertise': 0.80,
67
+ 'authority': 0.65,
68
+ 'trust': 0.90,
69
+ 'overall': 0.78,
70
+ 'details': {...}
71
+ }
72
+ """
73
+ details = {}
74
+
75
+ # --- EXPERIENCE ---
76
+ experience = 0.5
77
+ if domain_age_years >= 10:
78
+ experience += 0.3
79
+ elif domain_age_years >= 5:
80
+ experience += 0.2
81
+ elif domain_age_years >= 2:
82
+ experience += 0.1
83
+
84
+ if text:
85
+ word_count = len(text.split())
86
+ if word_count >= 1000:
87
+ experience += 0.15
88
+ elif word_count >= 500:
89
+ experience += 0.1
90
+
91
+ experience = min(experience, 1.0)
92
+ details['experience_factors'] = {
93
+ 'domain_age_bonus': domain_age_years >= 2,
94
+ 'content_richness': len(text.split()) if text else 0
95
+ }
96
+
97
+ # --- EXPERTISE ---
98
+ expertise = 0.4
99
+ tech_count = 0
100
+
101
+ if text:
102
+ text_lower = text.lower()
103
+ for term in self.TECHNICAL_TERMS:
104
+ if term in text_lower:
105
+ tech_count += 1
106
+
107
+ if tech_count >= 5:
108
+ expertise += 0.35
109
+ elif tech_count >= 3:
110
+ expertise += 0.25
111
+ elif tech_count >= 1:
112
+ expertise += 0.15
113
+
114
+ if has_citations:
115
+ expertise += 0.2
116
+
117
+ expertise = min(expertise, 1.0)
118
+ details['expertise_factors'] = {
119
+ 'technical_terms_found': tech_count,
120
+ 'has_citations': has_citations
121
+ }
122
+
123
+ # --- AUTHORITY ---
124
+ authority = 0.3
125
+
126
+ if url:
127
+ parsed = urlparse(url)
128
+ domain = parsed.netloc.lower()
129
+
130
+ for trusted in self.TRUSTED_DOMAINS:
131
+ if trusted in domain:
132
+ authority += 0.4
133
+ break
134
+
135
+ if parsed.scheme == 'https':
136
+ authority += 0.1
137
+
138
+ # Check for author indicators in text
139
+ if text:
140
+ author_patterns = [r'by\s+\w+\s+\w+', r'author:', r'written by', r'par\s+\w+']
141
+ for pattern in author_patterns:
142
+ if re.search(pattern, text.lower()):
143
+ authority += 0.15
144
+ break
145
+
146
+ authority = min(authority, 1.0)
147
+ details['authority_factors'] = {
148
+ 'trusted_domain': False,
149
+ 'https': url and urlparse(url).scheme == 'https' if url else False
150
+ }
151
+
152
+ # --- TRUST ---
153
+ trust = 0.5
154
+
155
+ # Neutral sentiment is best (0.5)
156
+ sentiment_deviation = abs(sentiment_score - 0.5)
157
+ if sentiment_deviation < 0.1:
158
+ trust += 0.3 # Very neutral
159
+ elif sentiment_deviation < 0.2:
160
+ trust += 0.2
161
+ elif sentiment_deviation < 0.3:
162
+ trust += 0.1
163
+
164
+ if url and urlparse(url).scheme == 'https':
165
+ trust += 0.15
166
+
167
+ trust = min(trust, 1.0)
168
+ details['trust_factors'] = {
169
+ 'sentiment_neutrality': 1 - sentiment_deviation * 2,
170
+ 'secure_connection': url and 'https' in url if url else False
171
+ }
172
+
173
+ # --- OVERALL ---
174
+ overall = (experience * 0.2 + expertise * 0.3 +
175
+ authority * 0.25 + trust * 0.25)
176
+
177
+ return {
178
+ 'experience': round(experience, 2),
179
+ 'expertise': round(expertise, 2),
180
+ 'authority': round(authority, 2),
181
+ 'trust': round(trust, 2),
182
+ 'overall': round(overall, 2),
183
+ 'details': details
184
+ }
185
+
186
+ def get_explanation(self, scores: Dict) -> str:
187
+ """Generate human-readable explanation of E-E-A-T scores."""
188
+ explanations = []
189
+
190
+ exp = scores.get('experience', 0)
191
+ if exp >= 0.7:
192
+ explanations.append("✅ Expérience: Source établie avec contenu riche")
193
+ elif exp >= 0.5:
194
+ explanations.append("⚠️ Expérience: Source moyennement établie")
195
+ else:
196
+ explanations.append("❌ Expérience: Source nouvelle ou contenu limité")
197
+
198
+ ext = scores.get('expertise', 0)
199
+ if ext >= 0.7:
200
+ explanations.append("✅ Expertise: Vocabulaire technique, citations présentes")
201
+ elif ext >= 0.5:
202
+ explanations.append("⚠️ Expertise: Niveau technique moyen")
203
+ else:
204
+ explanations.append("❌ Expertise: Manque de terminologie spécialisée")
205
+
206
+ auth = scores.get('authority', 0)
207
+ if auth >= 0.7:
208
+ explanations.append("✅ Autorité: Domaine reconnu et fiable")
209
+ elif auth >= 0.5:
210
+ explanations.append("⚠️ Autorité: Niveau d'autorité moyen")
211
+ else:
212
+ explanations.append("❌ Autorité: Source non reconnue")
213
+
214
+ tr = scores.get('trust', 0)
215
+ if tr >= 0.7:
216
+ explanations.append("✅ Confiance: Ton neutre, connexion sécurisée")
217
+ elif tr >= 0.5:
218
+ explanations.append("⚠️ Confiance: Niveau de confiance moyen")
219
+ else:
220
+ explanations.append("❌ Confiance: Ton biaisé ou connexion non sécurisée")
221
+
222
+ return "\n".join(explanations)
223
+
224
+
225
+ # Singleton
226
+ _calculator = None
227
+
228
+ def get_calculator() -> EEATCalculator:
229
+ """Get or create E-E-A-T calculator singleton."""
230
+ global _calculator
231
+ if _calculator is None:
232
+ _calculator = EEATCalculator()
233
+ return _calculator
234
+
235
+
236
+ # --- Testing ---
237
+ if __name__ == "__main__":
238
+ print("=" * 60)
239
+ print("SysCRED E-E-A-T Calculator - Test")
240
+ print("=" * 60)
241
+
242
+ calc = EEATCalculator()
243
+
244
+ test_url = "https://www.nature.com/articles/example"
245
+ test_text = """
246
+ A peer-reviewed study published in the journal Nature found evidence
247
+ that the new methodology significantly improves research outcomes.
248
+ Dr. Smith from Harvard University presented the statistics at the conference.
249
+ """
250
+
251
+ result = calc.calculate(
252
+ url=test_url,
253
+ text=test_text,
254
+ sentiment_score=0.5,
255
+ has_citations=True,
256
+ domain_age_years=15
257
+ )
258
+
259
+ print("\n--- E-E-A-T Scores ---")
260
+ print(f" Experience: {result['experience']:.0%}")
261
+ print(f" Expertise: {result['expertise']:.0%}")
262
+ print(f" Authority: {result['authority']:.0%}")
263
+ print(f" Trust: {result['trust']:.0%}")
264
+ print(f" ─────────────────")
265
+ print(f" OVERALL: {result['overall']:.0%}")
266
+
267
+ print("\n--- Explanation ---")
268
+ print(calc.get_explanation(result))
269
+
270
+ print("\n" + "=" * 60)
syscred/ner_analyzer.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ NER Analyzer Module - SysCRED
4
+ ==============================
5
+ Named Entity Recognition for fact-checking enhancement.
6
+
7
+ Extracts: PERSON, ORG, GPE, DATE, MISC entities
8
+
9
+ (c) Dominique S. Loyer - PhD Thesis Prototype
10
+ """
11
+
12
+ import os
13
+
14
+ # Check for spaCy
15
+ try:
16
+ import spacy
17
+ HAS_SPACY = True
18
+ except ImportError:
19
+ HAS_SPACY = False
20
+ print("[NER] spaCy not installed. NER disabled.")
21
+
22
+
23
+ class NERAnalyzer:
24
+ """
25
+ Named Entity Recognition using spaCy.
26
+
27
+ Supports:
28
+ - French (fr_core_news_md)
29
+ - English (en_core_web_sm)
30
+ """
31
+
32
+ # Entity type mapping with icons
33
+ ENTITY_ICONS = {
34
+ 'PERSON': '👤',
35
+ 'PER': '👤',
36
+ 'ORG': '🏢',
37
+ 'GPE': '📍',
38
+ 'LOC': '📍',
39
+ 'DATE': '📅',
40
+ 'TIME': '🕐',
41
+ 'MONEY': '💰',
42
+ 'MISC': '🏷️',
43
+ 'NORP': '👥',
44
+ 'FAC': '🏛️',
45
+ 'PRODUCT': '📦',
46
+ 'EVENT': '🎉',
47
+ 'WORK_OF_ART': '🎨',
48
+ 'LAW': '⚖️',
49
+ 'LANGUAGE': '🗣️',
50
+ }
51
+
52
+ def __init__(self, language: str = 'en'):
53
+ """
54
+ Initialize NER analyzer.
55
+
56
+ Args:
57
+ language: 'en' or 'fr'
58
+ """
59
+ self.language = language
60
+ self.nlp = None
61
+ self.enabled = False
62
+
63
+ if HAS_SPACY:
64
+ self._load_model()
65
+
66
+ def _load_model(self):
67
+ """Load the appropriate spaCy model."""
68
+ models = {
69
+ 'en': ['en_core_web_sm', 'en_core_web_md'],
70
+ 'fr': ['fr_core_news_md', 'fr_core_news_sm']
71
+ }
72
+
73
+ for model_name in models.get(self.language, models['en']):
74
+ try:
75
+ self.nlp = spacy.load(model_name)
76
+ self.enabled = True
77
+ print(f"[NER] Loaded model: {model_name}")
78
+ break
79
+ except OSError:
80
+ continue
81
+
82
+ if not self.enabled:
83
+ print(f"[NER] No model found for language: {self.language}")
84
+
85
+ def extract_entities(self, text: str) -> dict:
86
+ """
87
+ Extract named entities from text.
88
+
89
+ Returns:
90
+ {
91
+ 'entities': [
92
+ {'text': 'Emmanuel Macron', 'type': 'PERSON', 'icon': '👤'},
93
+ ...
94
+ ],
95
+ 'summary': {
96
+ 'PERSON': ['Emmanuel Macron'],
97
+ 'ORG': ['UQAM', 'Google'],
98
+ ...
99
+ }
100
+ }
101
+ """
102
+ if not self.enabled or not text:
103
+ return {'entities': [], 'summary': {}}
104
+
105
+ doc = self.nlp(text)
106
+
107
+ entities = []
108
+ summary = {}
109
+ seen = set()
110
+
111
+ for ent in doc.ents:
112
+ # Avoid duplicates
113
+ key = (ent.text.lower(), ent.label_)
114
+ if key in seen:
115
+ continue
116
+ seen.add(key)
117
+
118
+ entity = {
119
+ 'text': ent.text,
120
+ 'type': ent.label_,
121
+ 'icon': self.ENTITY_ICONS.get(ent.label_, '🏷️'),
122
+ 'start': ent.start_char,
123
+ 'end': ent.end_char
124
+ }
125
+ entities.append(entity)
126
+
127
+ # Group by type
128
+ if ent.label_ not in summary:
129
+ summary[ent.label_] = []
130
+ summary[ent.label_].append(ent.text)
131
+
132
+ return {
133
+ 'entities': entities,
134
+ 'summary': summary,
135
+ 'count': len(entities)
136
+ }
137
+
138
+ def analyze_for_factcheck(self, text: str) -> dict:
139
+ """
140
+ Analyze text for fact-checking relevance.
141
+
142
+ Returns entities with credibility hints.
143
+ """
144
+ result = self.extract_entities(text)
145
+
146
+ # Add fact-checking hints
147
+ hints = []
148
+
149
+ for ent in result.get('entities', []):
150
+ if ent['type'] in ['PERSON', 'PER']:
151
+ hints.append(f"Verify claims about {ent['text']}")
152
+ elif ent['type'] == 'ORG':
153
+ hints.append(f"Check {ent['text']} official sources")
154
+ elif ent['type'] in ['GPE', 'LOC']:
155
+ hints.append(f"Verify location: {ent['text']}")
156
+ elif ent['type'] == 'DATE':
157
+ hints.append(f"Confirm date: {ent['text']}")
158
+
159
+ result['fact_check_hints'] = hints[:5] # Top 5 hints
160
+ return result
161
+
162
+
163
+ # Singleton instance
164
+ _analyzer = None
165
+
166
+ def get_analyzer(language: str = 'en') -> NERAnalyzer:
167
+ """Get or create the NER analyzer singleton."""
168
+ global _analyzer
169
+ if _analyzer is None:
170
+ _analyzer = NERAnalyzer(language)
171
+ return _analyzer
172
+
173
+
174
+ # --- Testing ---
175
+ if __name__ == "__main__":
176
+ print("=" * 60)
177
+ print("SysCRED NER Analyzer - Test")
178
+ print("=" * 60)
179
+
180
+ analyzer = NERAnalyzer('en')
181
+
182
+ test_text = """
183
+ Emmanuel Macron announced today that France will invest €500 million
184
+ in AI research. The announcement was made at the UQAM in Montreal, Canada
185
+ on February 8, 2026. Google and Microsoft also confirmed their participation.
186
+ """
187
+
188
+ result = analyzer.analyze_for_factcheck(test_text)
189
+
190
+ print("\n--- Entities Found ---")
191
+ for ent in result['entities']:
192
+ print(f" {ent['icon']} {ent['text']} ({ent['type']})")
193
+
194
+ print("\n--- Fact-Check Hints ---")
195
+ for hint in result.get('fact_check_hints', []):
196
+ print(f" • {hint}")
197
+
198
+ print("\n" + "=" * 60)
syscred/static/index.html CHANGED
@@ -32,6 +32,44 @@
32
  min-height: 100vh;
33
  color: #e0e0e0;
34
  padding: 2rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
 
37
  .container {
@@ -140,6 +178,172 @@
140
  }
141
  }
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  .score-card {
144
  background: rgba(255, 255, 255, 0.03);
145
  backdrop-filter: blur(20px);
@@ -586,6 +790,79 @@
586
 
587
  <div class="details-grid" id="detailsGrid"></div>
588
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  <div class="graph-section" style="margin-top: 3rem;">
590
  <div class="summary-title" style="margin-bottom: 2rem; color: #60a5fa;">🕸️ Réseau Neuro-Symbolique
591
  (Ontologie)</div>
@@ -607,6 +884,9 @@
607
  <div id="explainerBody">
608
  <!-- Content filled dynamically -->
609
  </div>
 
 
 
610
  </div>
611
  </div>
612
 
@@ -805,6 +1085,19 @@
805
  </div>
806
  `;
807
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
808
 
809
  // Fact checks
810
  const factChecks = ruleResults.fact_checking || [];
@@ -832,6 +1125,177 @@
832
 
833
  detailsGrid.innerHTML = detailsHTML;
834
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
835
  results.classList.add('visible');
836
 
837
  // Fetch and render graph with slight delay to ensure DOM is ready
 
32
  min-height: 100vh;
33
  color: #e0e0e0;
34
  padding: 2rem;
35
+ position: relative;
36
+ }
37
+
38
+ /* Permanent Blue Glow Border Animation - User requested */
39
+ body::before {
40
+ content: '';
41
+ position: fixed;
42
+ top: 0;
43
+ left: 0;
44
+ right: 0;
45
+ bottom: 0;
46
+ pointer-events: none;
47
+ border: 4px solid transparent;
48
+ border-radius: 0;
49
+ background: linear-gradient(135deg, #0f0f23, #1a1a3e) padding-box,
50
+ linear-gradient(135deg,
51
+ rgba(0, 150, 255, 0.8),
52
+ rgba(100, 200, 255, 0.3),
53
+ rgba(0, 100, 255, 0.6),
54
+ rgba(50, 150, 255, 0.4)
55
+ ) border-box;
56
+ animation: blueGlowPulse 4s ease-in-out infinite;
57
+ z-index: 9999;
58
+ }
59
+
60
+ @keyframes blueGlowPulse {
61
+ 0%, 100% {
62
+ opacity: 0.6;
63
+ filter: blur(0px);
64
+ box-shadow: inset 0 0 60px rgba(0, 150, 255, 0.3),
65
+ 0 0 80px rgba(0, 150, 255, 0.2);
66
+ }
67
+ 50% {
68
+ opacity: 1;
69
+ filter: blur(1px);
70
+ box-shadow: inset 0 0 100px rgba(0, 180, 255, 0.5),
71
+ 0 0 150px rgba(0, 180, 255, 0.4);
72
+ }
73
  }
74
 
75
  .container {
 
178
  }
179
  }
180
 
181
+ /* ========================================
182
+ NER ENTITIES STYLES
183
+ ======================================== */
184
+ .ner-section {
185
+ background: rgba(0, 212, 255, 0.05);
186
+ border: 1px solid rgba(0, 212, 255, 0.2);
187
+ border-radius: 16px;
188
+ padding: 1.5rem;
189
+ margin-bottom: 2rem;
190
+ animation: fadeIn 0.5s ease;
191
+ }
192
+
193
+ .ner-entities {
194
+ display: flex;
195
+ flex-wrap: wrap;
196
+ gap: 0.75rem;
197
+ }
198
+
199
+ .ner-tag {
200
+ display: inline-flex;
201
+ align-items: center;
202
+ gap: 0.5rem;
203
+ padding: 0.5rem 1rem;
204
+ border-radius: 20px;
205
+ font-size: 0.9rem;
206
+ font-weight: 500;
207
+ transition: all 0.3s ease;
208
+ }
209
+
210
+ .ner-tag:hover {
211
+ transform: translateY(-2px);
212
+ box-shadow: 0 4px 15px rgba(0,0,0,0.3);
213
+ }
214
+
215
+ .ner-tag.person { background: linear-gradient(135deg, rgba(239, 68, 68, 0.3), rgba(239, 68, 68, 0.1)); border: 1px solid rgba(239, 68, 68, 0.5); color: #fca5a5; }
216
+ .ner-tag.org { background: linear-gradient(135deg, rgba(59, 130, 246, 0.3), rgba(59, 130, 246, 0.1)); border: 1px solid rgba(59, 130, 246, 0.5); color: #93c5fd; }
217
+ .ner-tag.location { background: linear-gradient(135deg, rgba(16, 185, 129, 0.3), rgba(16, 185, 129, 0.1)); border: 1px solid rgba(16, 185, 129, 0.5); color: #6ee7b7; }
218
+ .ner-tag.date { background: linear-gradient(135deg, rgba(245, 158, 11, 0.3), rgba(245, 158, 11, 0.1)); border: 1px solid rgba(245, 158, 11, 0.5); color: #fcd34d; }
219
+ .ner-tag.misc { background: linear-gradient(135deg, rgba(139, 92, 246, 0.3), rgba(139, 92, 246, 0.1)); border: 1px solid rgba(139, 92, 246, 0.5); color: #c4b5fd; }
220
+
221
+ .ner-tag-icon { font-size: 1.1rem; }
222
+ .ner-tag-text { font-weight: 600; }
223
+ .ner-tag-type { font-size: 0.75rem; opacity: 0.8; }
224
+
225
+ /* ========================================
226
+ E-E-A-T PROGRESS BARS STYLES
227
+ ======================================== */
228
+ .eeat-section {
229
+ background: rgba(16, 185, 129, 0.05);
230
+ border: 1px solid rgba(16, 185, 129, 0.2);
231
+ border-radius: 16px;
232
+ padding: 1.5rem;
233
+ margin-bottom: 2rem;
234
+ animation: fadeIn 0.5s ease;
235
+ }
236
+
237
+ .eeat-bars {
238
+ display: grid;
239
+ gap: 1rem;
240
+ }
241
+
242
+ .eeat-bar-item {
243
+ background: rgba(255,255,255,0.02);
244
+ border-radius: 12px;
245
+ padding: 1rem;
246
+ }
247
+
248
+ .eeat-label {
249
+ display: flex;
250
+ justify-content: space-between;
251
+ align-items: center;
252
+ margin-bottom: 0.75rem;
253
+ font-weight: 500;
254
+ }
255
+
256
+ .eeat-value {
257
+ background: rgba(16, 185, 129, 0.2);
258
+ padding: 0.25rem 0.75rem;
259
+ border-radius: 10px;
260
+ font-weight: 700;
261
+ font-size: 0.9rem;
262
+ color: #10b981;
263
+ }
264
+
265
+ .eeat-bar-container {
266
+ height: 12px;
267
+ background: rgba(255,255,255,0.1);
268
+ border-radius: 6px;
269
+ overflow: hidden;
270
+ }
271
+
272
+ .eeat-bar {
273
+ height: 100%;
274
+ background: linear-gradient(90deg, #10b981, #34d399);
275
+ border-radius: 6px;
276
+ transition: width 1s ease-out;
277
+ }
278
+
279
+ .eeat-bar.expertise { background: linear-gradient(90deg, #3b82f6, #60a5fa); }
280
+ .eeat-bar.authority { background: linear-gradient(90deg, #8b5cf6, #a78bfa); }
281
+ .eeat-bar.trust { background: linear-gradient(90deg, #f59e0b, #fbbf24); }
282
+
283
+ .eeat-overall {
284
+ margin-top: 1.5rem;
285
+ text-align: center;
286
+ padding: 1rem;
287
+ background: linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(16, 185, 129, 0.05));
288
+ border-radius: 12px;
289
+ border: 1px solid rgba(16, 185, 129, 0.3);
290
+ font-size: 1.1rem;
291
+ }
292
+
293
+ .eeat-overall strong {
294
+ color: #10b981;
295
+ font-size: 1.5rem;
296
+ margin-left: 0.5rem;
297
+ }
298
+
299
+ /* ========================================
300
+ WHY THIS RESULT SECTION
301
+ ======================================== */
302
+ .why-section {
303
+ background: rgba(245, 158, 11, 0.05);
304
+ border: 1px solid rgba(245, 158, 11, 0.2);
305
+ border-radius: 16px;
306
+ padding: 1.5rem;
307
+ margin-bottom: 2rem;
308
+ animation: fadeIn 0.5s ease;
309
+ }
310
+
311
+ .why-content {
312
+ display: grid;
313
+ gap: 1rem;
314
+ }
315
+
316
+ .why-item {
317
+ display: flex;
318
+ align-items: flex-start;
319
+ gap: 1rem;
320
+ padding: 1rem;
321
+ background: rgba(255,255,255,0.02);
322
+ border-radius: 10px;
323
+ border-left: 3px solid #f59e0b;
324
+ }
325
+
326
+ .why-icon {
327
+ font-size: 1.5rem;
328
+ min-width: 2rem;
329
+ }
330
+
331
+ .why-text {
332
+ flex: 1;
333
+ }
334
+
335
+ .why-text-title {
336
+ font-weight: 600;
337
+ color: #fbbf24;
338
+ margin-bottom: 0.25rem;
339
+ }
340
+
341
+ .why-text-desc {
342
+ color: #9ca3af;
343
+ font-size: 0.9rem;
344
+ line-height: 1.5;
345
+ }
346
+
347
  .score-card {
348
  background: rgba(255, 255, 255, 0.03);
349
  backdrop-filter: blur(20px);
 
790
 
791
  <div class="details-grid" id="detailsGrid"></div>
792
 
793
+ <!-- NER ENTITIES SECTION -->
794
+ <div class="ner-section" id="nerSection" style="display: none;">
795
+ <div class="summary-title" style="margin-bottom: 1rem; color: #00d4ff;">🏷️ Entités Détectées (NER)</div>
796
+ <div class="ner-entities" id="nerEntities">
797
+ <!-- Filled dynamically -->
798
+ </div>
799
+ </div>
800
+
801
+ <!-- E-E-A-T METRICS SECTION -->
802
+ <div class="eeat-section" id="eeatSection" style="display: none;">
803
+ <div class="summary-title" style="margin-bottom: 1rem; color: #10b981;">
804
+ 📊 Score E-E-A-T
805
+ <span class="help-icon" onclick="event.stopPropagation(); showEEATExplainer()" title="Experience, Expertise, Authority, Trust - Critères Google">?</span>
806
+ </div>
807
+ <p style="color: #8b8ba7; font-size: 0.9rem; margin-bottom: 1rem;">Critères inspirés du système de qualité de Google</p>
808
+
809
+ <div class="eeat-bars">
810
+ <div class="eeat-bar-item">
811
+ <div class="eeat-label">
812
+ <span>🎯 Experience</span>
813
+ <span class="eeat-value" id="eeatExperience">--</span>
814
+ </div>
815
+ <div class="eeat-bar-container">
816
+ <div class="eeat-bar" id="eeatExperienceBar" style="width: 0%"></div>
817
+ </div>
818
+ </div>
819
+
820
+ <div class="eeat-bar-item">
821
+ <div class="eeat-label">
822
+ <span>🧠 Expertise</span>
823
+ <span class="eeat-value" id="eeatExpertise">--</span>
824
+ </div>
825
+ <div class="eeat-bar-container">
826
+ <div class="eeat-bar expertise" id="eeatExpertiseBar" style="width: 0%"></div>
827
+ </div>
828
+ </div>
829
+
830
+ <div class="eeat-bar-item">
831
+ <div class="eeat-label">
832
+ <span>🏛️ Authority</span>
833
+ <span class="eeat-value" id="eeatAuthority">--</span>
834
+ </div>
835
+ <div class="eeat-bar-container">
836
+ <div class="eeat-bar authority" id="eeatAuthorityBar" style="width: 0%"></div>
837
+ </div>
838
+ </div>
839
+
840
+ <div class="eeat-bar-item">
841
+ <div class="eeat-label">
842
+ <span>🛡️ Trust</span>
843
+ <span class="eeat-value" id="eeatTrust">--</span>
844
+ </div>
845
+ <div class="eeat-bar-container">
846
+ <div class="eeat-bar trust" id="eeatTrustBar" style="width: 0%"></div>
847
+ </div>
848
+ </div>
849
+ </div>
850
+
851
+ <div class="eeat-overall" id="eeatOverall">
852
+ Score Global E-E-A-T: <strong>--</strong>
853
+ </div>
854
+ </div>
855
+
856
+ <!-- GOOGLE-STYLE EXPLANATION -->
857
+ <div class="why-section" id="whySection" style="display: none;">
858
+ <div class="summary-title" style="margin-bottom: 1rem; color: #f59e0b;">
859
+ 🔍 Pourquoi ce résultat ?
860
+ </div>
861
+ <div class="why-content" id="whyContent">
862
+ <!-- Filled dynamically with Google-style explanations -->
863
+ </div>
864
+ </div>
865
+
866
  <div class="graph-section" style="margin-top: 3rem;">
867
  <div class="summary-title" style="margin-bottom: 2rem; color: #60a5fa;">🕸️ Réseau Neuro-Symbolique
868
  (Ontologie)</div>
 
884
  <div id="explainerBody">
885
  <!-- Content filled dynamically -->
886
  </div>
887
+ <div style="margin-top: 1.5rem; text-align: center;">
888
+ <button onclick="closeExplainer()" style="background: linear-gradient(135deg, #7c3aed, #a855f7); padding: 0.75rem 2rem; border-radius: 10px; border: none; color: white; font-weight: 600; cursor: pointer;">✓ Fermer</button>
889
+ </div>
890
  </div>
891
  </div>
892
 
 
1085
  </div>
1086
  `;
1087
  }
1088
+
1089
+ // Add Bias Analysis if available
1090
+ if (nlpAnalysis.bias_analysis && nlpAnalysis.bias_analysis.score !== null) {
1091
+ const biasScore = nlpAnalysis.bias_analysis.score;
1092
+ const biasLabel = nlpAnalysis.bias_analysis.label || 'Non analysé';
1093
+ const biasColor = biasScore > 0.5 ? '#ef4444' : biasScore > 0.3 ? '#eab308' : '#22c55e';
1094
+ detailsHTML += `
1095
+ <div class="detail-card">
1096
+ <div class="detail-label">⚖️ Analyse de Biais <span class="help-icon" title="Mesure si le texte contient un langage biaisé ou partisan">?</span></div>
1097
+ <div class="detail-value" style="color: ${biasColor}">${biasLabel} (${(biasScore * 100).toFixed(0)}%)</div>
1098
+ </div>
1099
+ `;
1100
+ }
1101
 
1102
  // Fact checks
1103
  const factChecks = ruleResults.fact_checking || [];
 
1125
 
1126
  detailsGrid.innerHTML = detailsHTML;
1127
 
1128
+ // ========================================
1129
+ // DISPLAY NER ENTITIES
1130
+ // ========================================
1131
+ const nerSection = document.getElementById('nerSection');
1132
+ const nerEntities = document.getElementById('nerEntities');
1133
+ const entities = nlpAnalysis.entities || data.ner_entities || [];
1134
+
1135
+ if (entities && entities.length > 0) {
1136
+ nerSection.style.display = 'block';
1137
+ let nerHTML = '';
1138
+
1139
+ const entityIcons = {
1140
+ 'PERSON': '👤', 'PER': '👤',
1141
+ 'ORG': '🏢', 'ORGANIZATION': '🏢',
1142
+ 'LOC': '📍', 'LOCATION': '📍', 'GPE': '📍',
1143
+ 'DATE': '📅', 'TIME': '🕐',
1144
+ 'MISC': '🏷️', 'PRODUCT': '📦', 'EVENT': '🎭'
1145
+ };
1146
+
1147
+ const entityClasses = {
1148
+ 'PERSON': 'person', 'PER': 'person',
1149
+ 'ORG': 'org', 'ORGANIZATION': 'org',
1150
+ 'LOC': 'location', 'LOCATION': 'location', 'GPE': 'location',
1151
+ 'DATE': 'date', 'TIME': 'date',
1152
+ 'MISC': 'misc', 'PRODUCT': 'misc', 'EVENT': 'misc'
1153
+ };
1154
+
1155
+ entities.forEach(entity => {
1156
+ const text = entity.text || entity.word || entity.entity;
1157
+ const type = (entity.label || entity.entity_group || entity.type || 'MISC').toUpperCase();
1158
+ const icon = entityIcons[type] || '🏷️';
1159
+ const cssClass = entityClasses[type] || 'misc';
1160
+
1161
+ nerHTML += `
1162
+ <span class="ner-tag ${cssClass}">
1163
+ <span class="ner-tag-icon">${icon}</span>
1164
+ <span class="ner-tag-text">${text}</span>
1165
+ <span class="ner-tag-type">${type}</span>
1166
+ </span>
1167
+ `;
1168
+ });
1169
+
1170
+ nerEntities.innerHTML = nerHTML;
1171
+ } else {
1172
+ nerSection.style.display = 'none';
1173
+ }
1174
+
1175
+ // ========================================
1176
+ // DISPLAY E-E-A-T METRICS
1177
+ // ========================================
1178
+ const eeatSection = document.getElementById('eeatSection');
1179
+ const eeatData = data.eeat_score || data.eeatMetrics || null;
1180
+
1181
+ if (eeatData) {
1182
+ eeatSection.style.display = 'block';
1183
+
1184
+ // Experience
1185
+ const experience = eeatData.experience || eeatData.Experience || 0;
1186
+ document.getElementById('eeatExperience').textContent = Math.round(experience * 100) + '%';
1187
+ document.getElementById('eeatExperienceBar').style.width = (experience * 100) + '%';
1188
+
1189
+ // Expertise
1190
+ const expertise = eeatData.expertise || eeatData.Expertise || 0;
1191
+ document.getElementById('eeatExpertise').textContent = Math.round(expertise * 100) + '%';
1192
+ document.getElementById('eeatExpertiseBar').style.width = (expertise * 100) + '%';
1193
+
1194
+ // Authority
1195
+ const authority = eeatData.authority || eeatData.Authority || 0;
1196
+ document.getElementById('eeatAuthority').textContent = Math.round(authority * 100) + '%';
1197
+ document.getElementById('eeatAuthorityBar').style.width = (authority * 100) + '%';
1198
+
1199
+ // Trust
1200
+ const trust = eeatData.trust || eeatData.Trust || 0;
1201
+ document.getElementById('eeatTrust').textContent = Math.round(trust * 100) + '%';
1202
+ document.getElementById('eeatTrustBar').style.width = (trust * 100) + '%';
1203
+
1204
+ // Overall E-E-A-T Score
1205
+ const overall = eeatData.overall || eeatData.Overall ||
1206
+ ((experience * 0.15) + (expertise * 0.25) + (authority * 0.35) + (trust * 0.25));
1207
+ document.getElementById('eeatOverall').innerHTML =
1208
+ `Score Global E-E-A-T: <strong>${Math.round(overall * 100)}%</strong>`;
1209
+ } else {
1210
+ eeatSection.style.display = 'none';
1211
+ }
1212
+
1213
+ // ========================================
1214
+ // DISPLAY "WHY THIS RESULT?" EXPLANATION
1215
+ // ========================================
1216
+ const whySection = document.getElementById('whySection');
1217
+ const whyContent = document.getElementById('whyContent');
1218
+
1219
+ // Generate explanations based on analysis data
1220
+ let whyItems = [];
1221
+
1222
+ // Sentiment-based explanation
1223
+ if (nlpAnalysis.sentiment) {
1224
+ const sentLabel = nlpAnalysis.sentiment.label;
1225
+ const sentScore = (nlpAnalysis.sentiment.score * 100).toFixed(0);
1226
+ whyItems.push({
1227
+ icon: sentLabel === 'POSITIVE' ? '😊' : (sentLabel === 'NEGATIVE' ? '😟' : '😐'),
1228
+ title: `Ton ${sentLabel.toLowerCase()}`,
1229
+ desc: `Le texte utilise un langage ${sentLabel === 'POSITIVE' ? 'positif et optimiste' :
1230
+ sentLabel === 'NEGATIVE' ? 'négatif ou critique' : 'neutre'} (${sentScore}% de confiance).`
1231
+ });
1232
+ }
1233
+
1234
+ // Coherence-based explanation
1235
+ if (nlpAnalysis.coherence_score !== undefined) {
1236
+ const cohScore = (nlpAnalysis.coherence_score * 100).toFixed(0);
1237
+ whyItems.push({
1238
+ icon: nlpAnalysis.coherence_score > 0.7 ? '✓' : (nlpAnalysis.coherence_score > 0.4 ? '⚡' : '✗'),
1239
+ title: `Cohérence ${nlpAnalysis.coherence_score > 0.7 ? 'élevée' : (nlpAnalysis.coherence_score > 0.4 ? 'moyenne' : 'faible')}`,
1240
+ desc: `Le texte présente une cohérence sémantique de ${cohScore}%, indiquant ${
1241
+ nlpAnalysis.coherence_score > 0.7 ? 'un contenu bien structuré et logique' :
1242
+ nlpAnalysis.coherence_score > 0.4 ? 'un contenu partiellement cohérent' :
1243
+ 'des incohérences ou contradictions possibles'}.`
1244
+ });
1245
+ }
1246
+
1247
+ // Source reputation
1248
+ if (sourceAnalysis.reputation) {
1249
+ whyItems.push({
1250
+ icon: sourceAnalysis.reputation === 'High' ? '🏆' : (sourceAnalysis.reputation === 'Low' ? '⚠️' : '📊'),
1251
+ title: `Source ${sourceAnalysis.reputation === 'High' ? 'fiable' :
1252
+ sourceAnalysis.reputation === 'Low' ? 'douteuse' : 'moyenne'}`,
1253
+ desc: `La réputation de cette source est ${sourceAnalysis.reputation === 'High' ?
1254
+ 'reconnue et établie' : sourceAnalysis.reputation === 'Low' ?
1255
+ 'faible ou non vérifiable' : 'modérée'}.`
1256
+ });
1257
+ }
1258
+
1259
+ // Bias analysis
1260
+ if (nlpAnalysis.bias_analysis && nlpAnalysis.bias_analysis.score !== null) {
1261
+ const biasScore = nlpAnalysis.bias_analysis.score;
1262
+ whyItems.push({
1263
+ icon: biasScore > 0.5 ? '⚖️' : '✓',
1264
+ title: biasScore > 0.5 ? 'Biais détecté' : 'Faible biais',
1265
+ desc: `${biasScore > 0.5 ? 'Le texte contient des éléments de langage partisan ou biaisé.' :
1266
+ 'Le texte semble relativement objectif et équilibré.'}`
1267
+ });
1268
+ }
1269
+
1270
+ // PageRank
1271
+ if (data.pageRankEstimation && data.pageRankEstimation.estimatedPR) {
1272
+ const pr = data.pageRankEstimation.estimatedPR;
1273
+ whyItems.push({
1274
+ icon: '📈',
1275
+ title: 'Autorité de la page',
1276
+ desc: `PageRank estimé à ${pr.toFixed(3)}, basé sur l'analyse structurelle du site et des facteurs SEO.`
1277
+ });
1278
+ }
1279
+
1280
+ if (whyItems.length > 0) {
1281
+ whySection.style.display = 'block';
1282
+ let whyHTML = '';
1283
+ whyItems.forEach(item => {
1284
+ whyHTML += `
1285
+ <div class="why-item">
1286
+ <div class="why-icon">${item.icon}</div>
1287
+ <div class="why-text">
1288
+ <div class="why-text-title">${item.title}</div>
1289
+ <div class="why-text-desc">${item.desc}</div>
1290
+ </div>
1291
+ </div>
1292
+ `;
1293
+ });
1294
+ whyContent.innerHTML = whyHTML;
1295
+ } else {
1296
+ whySection.style.display = 'none';
1297
+ }
1298
+
1299
  results.classList.add('visible');
1300
 
1301
  // Fetch and render graph with slight delay to ensure DOM is ready
syscred/verification_system.py CHANGED
@@ -41,6 +41,21 @@ from syscred.graph_rag import GraphRAG # [NEW] GraphRAG
41
  from syscred.trec_retriever import TRECRetriever, Evidence, RetrievalResult # [NEW] TREC Integration
42
  from syscred import config
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  class CredibilityVerificationSystem:
46
  """
 
41
  from syscred.trec_retriever import TRECRetriever, Evidence, RetrievalResult # [NEW] TREC Integration
42
  from syscred import config
43
 
44
+ # [NEW] NER and E-E-A-T modules
45
+ try:
46
+ from syscred.ner_analyzer import NERAnalyzer, get_ner_analyzer
47
+ HAS_NER = True
48
+ except ImportError:
49
+ HAS_NER = False
50
+ print("[SysCRED] Warning: NER module not available")
51
+
52
+ try:
53
+ from syscred.eeat_calculator import EEATCalculator, EEATScore
54
+ HAS_EEAT = True
55
+ except ImportError:
56
+ HAS_EEAT = False
57
+ print("[SysCRED] Warning: E-E-A-T module not available")
58
+
59
 
60
  class CredibilityVerificationSystem:
61
  """