upgraedd commited on
Commit
5ae763b
·
verified ·
1 Parent(s): 4fe76d8

Upload Anti _hallucination_3.txt

Browse files
Files changed (1) hide show
  1. Anti _hallucination_3.txt +1422 -0
Anti _hallucination_3.txt ADDED
@@ -0,0 +1,1422 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ EIS + ESL MEDIATOR v4.0 – Grounded Epistemic Engine
4
+ =====================================================
5
+ - Embedding‑based evidence matching (cosine similarity)
6
+ - Structural hallucination detection (novelty, entity absence)
7
+ - Semantic contradiction detection (embedding + polarity)
8
+ - Confidence decomposition (evidence, contradiction, suppression, hallucination)
9
+ - External grounding layer (Wikipedia API)
10
+ - Anti‑hallucination feedback loop
11
+ - Full backward compatibility with v3.9
12
+ """
13
+
14
+ import hashlib
15
+ import json
16
+ import os
17
+ import secrets
18
+ import time
19
+ import math
20
+ import re
21
+ from datetime import datetime, timedelta
22
+ from typing import Dict, List, Any, Optional, Tuple, Set
23
+ from collections import defaultdict
24
+ import requests
25
+
26
+ # ----------------------------------------------------------------------------
27
+ # LAZY EMBEDDER (from embeddings.py)
28
+ # ----------------------------------------------------------------------------
29
+ _EMBEDDER = None
30
+
31
+ def _load_embedder():
32
+ global _EMBEDDER
33
+ if _EMBEDDER is None:
34
+ try:
35
+ from sentence_transformers import SentenceTransformer
36
+ _EMBEDDER = SentenceTransformer('all-MiniLM-L6-v2')
37
+ except Exception:
38
+ _EMBEDDER = None
39
+ return _EMBEDDER
40
+
41
+ def _embed_texts(texts: List[str]) -> Optional[Any]:
42
+ model = _load_embedder()
43
+ if model is None:
44
+ return None
45
+ arr = model.encode(texts, convert_to_numpy=True, show_progress_bar=False)
46
+ return arr.astype('float32')
47
+
48
+ def _cosine_sim(a: Any, b: Any) -> float:
49
+ import numpy as np
50
+ from numpy.linalg import norm
51
+ a = np.array(a, dtype=np.float32)
52
+ b = np.array(b, dtype=np.float32)
53
+ denom = (norm(a) * norm(b) + 1e-12)
54
+ return float(np.dot(a, b) / denom)
55
+
56
+ # ----------------------------------------------------------------------------
57
+ # EXTERNAL GROUNDING LAYER (Wikipedia API)
58
+ # ----------------------------------------------------------------------------
59
+ def _fetch_wikipedia_summary(entity: str) -> Optional[str]:
60
+ try:
61
+ url = "https://en.wikipedia.org/api/rest_v1/page/summary/" + entity.replace(" ", "_")
62
+ resp = requests.get(url, timeout=5)
63
+ if resp.status_code == 200:
64
+ data = resp.json()
65
+ return data.get("extract", "")
66
+ except Exception:
67
+ pass
68
+ return None
69
+
70
+ # ----------------------------------------------------------------------------
71
+ # UTILITIES (drift, crowding, inoculation, attrition)
72
+ # ----------------------------------------------------------------------------
73
+ def _compute_entity_drift(embeddings_tuples: List[Dict]) -> List[Dict]:
74
+ if not embeddings_tuples:
75
+ return []
76
+ import numpy as np
77
+ arrs = [np.array(e["embedding"], dtype=np.float32) for e in embeddings_tuples]
78
+ baseline_count = max(1, len(arrs)//4)
79
+ baseline = np.mean(arrs[:baseline_count], axis=0)
80
+ drift = []
81
+ for rec, emb in zip(embeddings_tuples, arrs):
82
+ sim = _cosine_sim(baseline, emb)
83
+ drift.append({
84
+ "timestamp": rec["timestamp"],
85
+ "similarity_to_baseline": sim,
86
+ "drift_score": 1.0 - sim
87
+ })
88
+ return drift
89
+
90
+ def _semantic_drift_score(emb_timeline: List[Dict], window: int = 7) -> float:
91
+ if not emb_timeline or len(emb_timeline) < 4:
92
+ return 0.0
93
+ import numpy as np
94
+ arrs = [np.array(e["embedding"], dtype=np.float32) for e in emb_timeline]
95
+ baseline = np.mean(arrs[:max(1, len(arrs)//4)], axis=0)
96
+ sims = [float(np.dot(baseline, v) / (np.linalg.norm(baseline)*np.linalg.norm(v)+1e-12)) for v in arrs]
97
+ recent = sims[-min(window, len(sims)):]
98
+ velocity = 0.0
99
+ if len(recent) >= 2:
100
+ velocity = (recent[-1] - recent[0]) / max(1, len(recent)-1)
101
+ drift = max(0.0, 1.0 - recent[-1])
102
+ velocity_component = -velocity if velocity < 0 else 0.0
103
+ return float(min(1.0, drift + velocity_component))
104
+
105
+ def _shingle_hashes(s: str, k: int = 5) -> Set[int]:
106
+ toks = [t for t in re.split(r'\s+', s.lower()) if t]
107
+ if len(toks) < k:
108
+ return {hash(" ".join(toks))}
109
+ return {hash(" ".join(toks[i:i+k])) for i in range(max(0, len(toks)-k+1))}
110
+
111
+ def _crowding_signature(esl: 'ESLedger', window_days: int = 3, dup_threshold: float = 0.6):
112
+ now = datetime.utcnow()
113
+ cutoff = now - timedelta(days=window_days)
114
+ texts = []
115
+ for cid, c in esl.claims.items():
116
+ try:
117
+ ts = datetime.fromisoformat(c["timestamp"].replace('Z', '+00:00'))
118
+ except Exception:
119
+ continue
120
+ if ts >= cutoff:
121
+ texts.append(c.get("text", ""))
122
+ if len(texts) < 2:
123
+ return None
124
+ hashes = [_shingle_hashes(t) for t in texts]
125
+ pairs = 0
126
+ near_dup = 0
127
+ for i in range(len(hashes)):
128
+ for j in range(i+1, len(hashes)):
129
+ pairs += 1
130
+ inter = len(hashes[i].intersection(hashes[j]))
131
+ union = len(hashes[i].union(hashes[j])) + 1e-9
132
+ if inter/union > dup_threshold:
133
+ near_dup += 1
134
+ dup_frac = (near_dup / pairs) if pairs else 0.0
135
+ if dup_frac > dup_threshold:
136
+ weight = min(0.9, 0.5 + dup_frac)
137
+ return ("crowding_noise_floor", weight)
138
+ return None
139
+
140
+ def _compute_attrition_score(workflow_events: List[Dict]) -> float:
141
+ if not workflow_events:
142
+ return 0.0
143
+ events_sorted = sorted(workflow_events, key=lambda e: e["timestamp"])
144
+ durations = []
145
+ loops = 0
146
+ for i in range(len(events_sorted)-1):
147
+ dt = (events_sorted[i+1]["timestamp"] - events_sorted[i]["timestamp"]).total_seconds()
148
+ durations.append(dt)
149
+ if events_sorted[i].get("status") == "request_more_info" and events_sorted[i+1].get("status") == "resubmission":
150
+ loops += 1
151
+ median_duration_days = (sorted(durations)[len(durations)//2] / 86400) if durations else 0
152
+ score = min(1.0, (median_duration_days / 30.0) + (loops * 0.1))
153
+ return score
154
+
155
+ def _inoculation_signature(esl: 'ESLedger', claim_id: str, lead_window_days: int = 7, sim_threshold: float = 0.72):
156
+ base_claim = esl.claims.get(claim_id)
157
+ if not base_claim:
158
+ return None
159
+ emb = base_claim.get("embedding")
160
+ if emb is None:
161
+ return None
162
+ try:
163
+ import numpy as np
164
+ except Exception:
165
+ return None
166
+ now = datetime.utcnow()
167
+ cutoff = now - timedelta(days=lead_window_days*2)
168
+ similar_pairs = []
169
+ for cid, c in esl.claims.items():
170
+ if cid == claim_id:
171
+ continue
172
+ try:
173
+ ts = datetime.fromisoformat(c["timestamp"].replace('Z', '+00:00'))
174
+ except Exception:
175
+ continue
176
+ if ts < cutoff:
177
+ continue
178
+ emb2 = c.get("embedding")
179
+ if emb2 is None:
180
+ continue
181
+ sim = float(np.dot(np.array(emb), np.array(emb2)) / ((np.linalg.norm(emb)*np.linalg.norm(emb2))+1e-12))
182
+ if sim >= sim_threshold:
183
+ similar_pairs.append((cid, ts, sim))
184
+ if not similar_pairs:
185
+ return None
186
+ try:
187
+ base_ts = datetime.fromisoformat(base_claim["timestamp"].replace('Z', '+00:00'))
188
+ except Exception:
189
+ return None
190
+ leads = [(base_ts - ts).total_seconds() for (_, ts, _) in similar_pairs]
191
+ mean_lead = sum(leads)/len(leads)
192
+ if mean_lead > (24*3600):
193
+ weight = min(0.9, 0.3 + min(0.7, abs(mean_lead)/(7*24*3600)))
194
+ return ("preemptive_inoculation", weight)
195
+ return None
196
+
197
+ # ----------------------------------------------------------------------------
198
+ # NEGATION AND POLARITY HELPERS
199
+ # ----------------------------------------------------------------------------
200
+ NEGATION_WORDS = {"not", "no", "never", "false", "didn't", "isn't", "wasn't", "weren't", "cannot", "couldn't", "wouldn't", "shouldn't"}
201
+ ANTONYMS = {
202
+ "suppressed": "revealed", "erased": "preserved", "hidden": "public",
203
+ "denied": "confirmed", "falsified": "verified", "concealed": "disclosed"
204
+ }
205
+
206
+ def has_negation(text: str, entity: str = None) -> bool:
207
+ words = text.lower().split()
208
+ if entity:
209
+ for i, w in enumerate(words):
210
+ if entity.lower() in w or w == entity.lower():
211
+ start = max(0, i-5)
212
+ preceding = words[start:i]
213
+ if any(neg in preceding for neg in NEGATION_WORDS):
214
+ return True
215
+ else:
216
+ if any(neg in words for neg in NEGATION_WORDS):
217
+ return True
218
+ return False
219
+
220
+ def claim_polarity(text: str) -> float:
221
+ return 0.3 if has_negation(text) else 1.0
222
+
223
+ # ----------------------------------------------------------------------------
224
+ # ENTITY EXTRACTION (spaCy / TextBlob / fallback)
225
+ # ----------------------------------------------------------------------------
226
+ try:
227
+ import spacy
228
+ _nlp = spacy.load("en_core_web_sm")
229
+ HAS_SPACY = True
230
+ except ImportError:
231
+ HAS_SPACY = False
232
+ _nlp = None
233
+
234
+ try:
235
+ from textblob import TextBlob
236
+ HAS_TEXTBLOB = True
237
+ except ImportError:
238
+ HAS_TEXTBLOB = False
239
+
240
+ def extract_entities(text: str) -> List[Tuple[str, str, bool]]:
241
+ entities = []
242
+ if HAS_SPACY and _nlp:
243
+ doc = _nlp(text)
244
+ for ent in doc.ents:
245
+ negated = has_negation(text, ent.text)
246
+ entities.append((ent.text, ent.label_, negated))
247
+ for chunk in doc.noun_chunks:
248
+ if chunk.text not in [e[0] for e in entities] and len(chunk.text.split()) <= 3 and chunk.text[0].isupper():
249
+ negated = has_negation(text, chunk.text)
250
+ entities.append((chunk.text, "NOUN_PHRASE", negated))
251
+ return entities
252
+ if HAS_TEXTBLOB:
253
+ blob = TextBlob(text)
254
+ for np in blob.noun_phrases:
255
+ if np[0].isupper() or np in ["CIA", "FBI", "NSA", "Pentagon"]:
256
+ negated = has_negation(text, np)
257
+ entities.append((np, "NOUN_PHRASE", negated))
258
+ words = text.split()
259
+ i = 0
260
+ while i < len(words):
261
+ if words[i] and words[i][0].isupper() and len(words[i]) > 1:
262
+ phrase = [words[i]]
263
+ j = i+1
264
+ while j < len(words) and words[j] and words[j][0].isupper():
265
+ phrase.append(words[j])
266
+ j += 1
267
+ ent = " ".join(phrase)
268
+ negated = has_negation(text, ent)
269
+ entities.append((ent, "PROPER_NOUN", negated))
270
+ i = j
271
+ else:
272
+ i += 1
273
+ return entities
274
+ # final fallback
275
+ pattern = r'\b[A-Z][a-z]*(?:\s+[A-Z][a-z]*)*\b'
276
+ matches = re.findall(pattern, text)
277
+ for match in matches:
278
+ if len(match.split()) <= 4 and match not in ["The", "This", "That", "These", "Those", "I", "We", "They"]:
279
+ negated = has_negation(text, match)
280
+ entities.append((match, "UNKNOWN", negated))
281
+ return entities
282
+
283
+ # ----------------------------------------------------------------------------
284
+ # TAXONOMY (methods, primitives, lenses)
285
+ # ----------------------------------------------------------------------------
286
+ METHODS = {
287
+ 1: {"name": "Total Erasure", "primitive": "ERASURE", "signatures": ["entity_present_then_absent", "abrupt_disappearance"]},
288
+ 2: {"name": "Soft Erasure", "primitive": "ERASURE", "signatures": ["gradual_fading", "citation_decay"]},
289
+ 3: {"name": "Citation Decay", "primitive": "ERASURE", "signatures": ["decreasing_citations"]},
290
+ 4: {"name": "Index Removal", "primitive": "ERASURE", "signatures": ["missing_from_indices"]},
291
+ 5: {"name": "Selective Retention", "primitive": "ERASURE", "signatures": ["archival_gaps"]},
292
+ 10: {"name": "Narrative Seizure", "primitive": "NARRATIVE_CAPTURE", "signatures": ["single_explanation"]},
293
+ 12: {"name": "Official Story", "primitive": "NARRATIVE_CAPTURE", "signatures": ["authoritative_sources"]},
294
+ 14: {"name": "Temporal Gaps", "primitive": "TEMPORAL", "signatures": ["publication_gap"]},
295
+ 15: {"name": "Latency Spikes", "primitive": "TEMPORAL", "signatures": ["delayed_reporting"]},
296
+ 17: {"name": "Smear Campaign", "primitive": "DISCREDITATION", "signatures": ["ad_hominem_attacks"]},
297
+ 23: {"name": "Whataboutism", "primitive": "MISDIRECTION", "signatures": ["deflection"]},
298
+ 43: {"name": "Conditioning", "primitive": "CONDITIONING", "signatures": ["repetitive_messaging"]},
299
+ }
300
+
301
+ LENSES = {
302
+ 1: "Threat→Response→Control→Enforce→Centralize",
303
+ 2: "Sacred Geometry Weaponized",
304
+ 3: "Language Inversions / Ridicule / Gatekeeping",
305
+ 4: "Crisis→Consent→Surveillance",
306
+ 5: "Divide and Fragment",
307
+ 6: "Blame the Victim",
308
+ 7: "Narrative Capture through Expertise",
309
+ 8: "Information Saturation",
310
+ 9: "Historical Revisionism",
311
+ 10: "Institutional Capture",
312
+ 11: "Access Control via Credentialing",
313
+ 12: "Temporal Displacement",
314
+ 13: "Moral Equivalence",
315
+ 14: "Whataboutism",
316
+ 15: "Ad Hominem",
317
+ 16: "Straw Man",
318
+ 17: "False Dichotomy",
319
+ 18: "Slippery Slope",
320
+ 19: "Appeal to Authority",
321
+ 20: "Appeal to Nature",
322
+ 21: "Appeal to Tradition",
323
+ 22: "Appeal to Novelty",
324
+ 23: "Cherry Picking",
325
+ 24: "Moving the Goalposts",
326
+ 25: "Burden of Proof Reversal",
327
+ 26: "Circular Reasoning",
328
+ 27: "Special Pleading",
329
+ 28: "Loaded Question",
330
+ 29: "No True Scotsman",
331
+ 30: "Texas Sharpshooter",
332
+ 31: "Middle Ground Fallacy",
333
+ 32: "Black-and-White Thinking",
334
+ 33: "Fear Mongering",
335
+ 34: "Flattery",
336
+ 35: "Guilt by Association",
337
+ 36: "Transfer",
338
+ 37: "Testimonial",
339
+ 38: "Plain Folks",
340
+ 39: "Bandwagon",
341
+ 40: "Snob Appeal",
342
+ 41: "Glittering Generalities",
343
+ 42: "Name-Calling",
344
+ 43: "Card Stacking",
345
+ 44: "Euphemisms",
346
+ 45: "Dysphemisms",
347
+ 46: "Weasel Words",
348
+ 47: "Thought-Terminating Cliché",
349
+ 48: "Proof by Intimidation",
350
+ 49: "Proof by Verbosity",
351
+ 50: "Sealioning",
352
+ 51: "Gish Gallop",
353
+ 52: "JAQing Off",
354
+ 53: "Nutpicking",
355
+ 54: "Concern Trolling",
356
+ 55: "Gaslighting",
357
+ 56: "Kafkatrapping",
358
+ 57: "Brandolini's Law",
359
+ 58: "Occam's Razor",
360
+ 59: "Hanlon's Razor",
361
+ 60: "Hitchens's Razor",
362
+ 61: "Popper's Falsification",
363
+ 62: "Sagan's Standard",
364
+ 63: "Newton's Flaming Laser Sword",
365
+ 64: "Alder's Razor",
366
+ 65: "Grice's Maxims",
367
+ 66: "Poe's Law",
368
+ 67: "Sturgeon's Law",
369
+ 68: "Betteridge's Law",
370
+ 69: "Godwin's Law",
371
+ 70: "Skoptsy Syndrome",
372
+ }
373
+
374
+ PRIMITIVE_TO_LENSES = {
375
+ "ERASURE": [31, 53, 71, 24, 54, 4, 37, 45, 46],
376
+ "INTERRUPTION": [19, 33, 30, 63, 10, 61, 12, 26],
377
+ "FRAGMENTATION": [2, 52, 15, 20, 3, 29, 31, 54],
378
+ "NARRATIVE_CAPTURE": [1, 34, 40, 64, 7, 16, 22, 47],
379
+ "MISDIRECTION": [5, 21, 8, 36, 27, 61],
380
+ "SATURATION": [41, 69, 3, 36, 34, 66],
381
+ "DISCREDITATION": [3, 27, 10, 40, 30, 63],
382
+ "ATTRITION": [13, 19, 14, 33, 19, 27],
383
+ "ACCESS_CONTROL": [25, 62, 37, 51, 23, 53],
384
+ "TEMPORAL": [22, 47, 26, 68, 12, 22],
385
+ "CONDITIONING": [8, 36, 34, 43, 27, 33],
386
+ "META": [23, 70, 34, 64, 23, 40, 18, 71, 46, 31, 5, 21]
387
+ }
388
+
389
+ def map_signature_to_method(signature: str) -> Optional[Dict]:
390
+ for mid, method in METHODS.items():
391
+ if signature in method["signatures"]:
392
+ return {"method_id": mid, "method_name": method["name"], "primitive": method["primitive"]}
393
+ return None
394
+
395
+ def get_lenses_for_primitive(primitive: str) -> List[int]:
396
+ return PRIMITIVE_TO_LENSES.get(primitive, [])
397
+
398
+ def get_lens_name(lens_id: int) -> str:
399
+ return LENSES.get(lens_id, f"Lens {lens_id} (unknown)")
400
+
401
+ # ----------------------------------------------------------------------------
402
+ # EPISTEMIC SUBSTRATE LEDGER (ESL)
403
+ # ----------------------------------------------------------------------------
404
+ class ESLedger:
405
+ def __init__(self, path: str = "esl_ledger.json"):
406
+ self.path = path
407
+ self.claims: Dict[str, Dict] = {}
408
+ self.entities: Dict[str, Dict] = {}
409
+ self.signatures: List[Dict] = []
410
+ self.contradiction_graph: Dict[str, Set[str]] = defaultdict(set)
411
+ self.blocks: List[Dict] = []
412
+ self._load()
413
+
414
+ def _load(self):
415
+ if os.path.exists(self.path):
416
+ try:
417
+ with open(self.path, 'r') as f:
418
+ data = json.load(f)
419
+ self.claims = data.get("claims", {})
420
+ self.entities = data.get("entities", {})
421
+ self.signatures = data.get("signatures", [])
422
+ self.blocks = data.get("blocks", [])
423
+ cg = data.get("contradiction_graph", {})
424
+ self.contradiction_graph = {k: set(v) for k, v in cg.items()}
425
+ except Exception:
426
+ pass
427
+
428
+ def _save(self):
429
+ cg_serializable = {k: list(v) for k, v in self.contradiction_graph.items()}
430
+ data = {
431
+ "claims": self.claims,
432
+ "entities": self.entities,
433
+ "signatures": self.signatures,
434
+ "contradiction_graph": cg_serializable,
435
+ "blocks": self.blocks,
436
+ "updated": datetime.utcnow().isoformat() + "Z"
437
+ }
438
+ with open(self.path + ".tmp", 'w') as f:
439
+ json.dump(data, f, indent=2)
440
+ os.replace(self.path + ".tmp", self.path)
441
+
442
+ def add_claim(self, text: str, agent: str = "user") -> str:
443
+ claim_id = secrets.token_hex(16)
444
+ polarity = claim_polarity(text)
445
+ self.claims[claim_id] = {
446
+ "id": claim_id, "text": text, "agent": agent,
447
+ "timestamp": datetime.utcnow().isoformat() + "Z",
448
+ "entities": [], "signatures": [], "coherence": 0.5,
449
+ "contradictions": [], "suppression_score": 0.0,
450
+ "methods": [], "primitives": [], "lenses": [],
451
+ "polarity": polarity,
452
+ "source_types": [],
453
+ "embedding": None,
454
+ "workflow_events": []
455
+ }
456
+ self._save()
457
+ emb_arr = _embed_texts([text])
458
+ if emb_arr is not None:
459
+ self.claims[claim_id]["embedding"] = emb_arr[0].tolist()
460
+ self._save()
461
+ return claim_id
462
+
463
+ def add_entity(self, name: str, etype: str, claim_id: str, negated: bool = False, source_type: str = "unknown"):
464
+ if name not in self.entities:
465
+ self.entities[name] = {
466
+ "name": name, "type": etype,
467
+ "first_seen": datetime.utcnow().isoformat() + "Z",
468
+ "last_seen": self.claims[claim_id]["timestamp"],
469
+ "appearances": [], "coherence_scores": [],
470
+ "suppression_score": 0.0,
471
+ "negated_mentions": [],
472
+ "source_types": {},
473
+ "embeddings": [],
474
+ "external_summary": None
475
+ }
476
+ try:
477
+ summary = _fetch_wikipedia_summary(name)
478
+ if summary:
479
+ self.entities[name]["external_summary"] = summary[:500]
480
+ except Exception:
481
+ pass
482
+ ent = self.entities[name]
483
+ if claim_id not in ent["appearances"]:
484
+ ent["appearances"].append(claim_id)
485
+ if negated:
486
+ ent["negated_mentions"].append(claim_id)
487
+ ent["last_seen"] = self.claims[claim_id]["timestamp"]
488
+ ent["source_types"][source_type] = ent["source_types"].get(source_type, 0) + 1
489
+ if "entities" not in self.claims[claim_id]:
490
+ self.claims[claim_id]["entities"] = []
491
+ if claim_id not in self.claims[claim_id]["entities"]:
492
+ self.claims[claim_id]["entities"].append(name)
493
+ if "source_types" not in self.claims[claim_id]:
494
+ self.claims[claim_id]["source_types"] = []
495
+ if source_type not in self.claims[claim_id]["source_types"]:
496
+ self.claims[claim_id]["source_types"].append(source_type)
497
+ emb = self.claims[claim_id].get("embedding")
498
+ if emb is not None:
499
+ ent.setdefault("embeddings", []).append({
500
+ "timestamp": self.claims[claim_id]["timestamp"],
501
+ "embedding": emb,
502
+ "claim_id": claim_id,
503
+ "text_snippet": self.claims[claim_id]["text"][:512]
504
+ })
505
+ self._save()
506
+
507
+ def add_signature(self, claim_id: str, sig_name: str, weight: float = 0.5, context: Dict = None):
508
+ polarity = self.claims[claim_id].get("polarity", 1.0)
509
+ adjusted_weight = weight * polarity
510
+ method_info = map_signature_to_method(sig_name)
511
+ primitive = method_info["primitive"] if method_info else "UNKNOWN"
512
+ lenses = get_lenses_for_primitive(primitive) if primitive != "UNKNOWN" else []
513
+ self.signatures.append({
514
+ "signature": sig_name, "claim_id": claim_id,
515
+ "timestamp": datetime.utcnow().isoformat() + "Z",
516
+ "weight": adjusted_weight, "context": context or {},
517
+ "method": method_info["method_name"] if method_info else None,
518
+ "primitive": primitive,
519
+ "lenses": lenses
520
+ })
521
+ if sig_name not in self.claims[claim_id]["signatures"]:
522
+ self.claims[claim_id]["signatures"].append(sig_name)
523
+ if method_info and method_info["method_name"] not in self.claims[claim_id]["methods"]:
524
+ self.claims[claim_id]["methods"].append(method_info["method_name"])
525
+ if primitive not in self.claims[claim_id]["primitives"]:
526
+ self.claims[claim_id]["primitives"].append(primitive)
527
+ for lens in lenses:
528
+ if lens not in self.claims[claim_id]["lenses"]:
529
+ self.claims[claim_id]["lenses"].append(lens)
530
+
531
+ combined = 1.0
532
+ for sig in self.claims[claim_id]["signatures"]:
533
+ w = 0.5
534
+ for log in self.signatures:
535
+ if log["signature"] == sig and log["claim_id"] == claim_id:
536
+ w = log.get("weight", 0.5)
537
+ break
538
+ combined *= (1 - w)
539
+ new_score = 1 - combined
540
+ self.claims[claim_id]["suppression_score"] = new_score
541
+
542
+ for entity in self.claims[claim_id]["entities"]:
543
+ ent = self.entities.get(entity)
544
+ if ent:
545
+ ent_combined = 1.0
546
+ for cid in ent["appearances"]:
547
+ sc = self.claims[cid].get("suppression_score", 0.0)
548
+ ent_combined *= (1 - sc)
549
+ ent["suppression_score"] = 1 - ent_combined
550
+ self._save()
551
+
552
+ def add_contradiction(self, claim_id_a: str, claim_id_b: str):
553
+ self.contradiction_graph[claim_id_a].add(claim_id_b)
554
+ self.contradiction_graph[claim_id_b].add(claim_id_a)
555
+ if claim_id_b not in self.claims[claim_id_a]["contradictions"]:
556
+ self.claims[claim_id_a]["contradictions"].append(claim_id_b)
557
+ if claim_id_a not in self.claims[claim_id_b]["contradictions"]:
558
+ self.claims[claim_id_b]["contradictions"].append(claim_id_a)
559
+ self._save()
560
+
561
+ def get_entity_coherence(self, entity_name: str) -> float:
562
+ ent = self.entities.get(entity_name)
563
+ if not ent or len(ent["appearances"]) < 2:
564
+ return 0.5
565
+ timestamps = []
566
+ for cid in ent["appearances"]:
567
+ ts = self.claims[cid]["timestamp"]
568
+ timestamps.append(datetime.fromisoformat(ts.replace('Z', '+00:00')))
569
+ intervals = [(timestamps[i+1] - timestamps[i]).total_seconds() / 86400 for i in range(len(timestamps)-1)]
570
+ if not intervals:
571
+ return 0.5
572
+ mean_int = sum(intervals) / len(intervals)
573
+ variance = sum((i - mean_int)**2 for i in intervals) / len(intervals)
574
+ coherence = 1.0 / (1.0 + variance)
575
+ return min(1.0, max(0.0, coherence))
576
+
577
+ def get_entity_embeddings(self, entity_name: str) -> List[Dict]:
578
+ ent = self.entities.get(entity_name)
579
+ if not ent:
580
+ return []
581
+ return sorted(ent.get("embeddings", []), key=lambda x: x["timestamp"])
582
+
583
+ def suppression_pattern_classifier(self, claim_id: str) -> Dict:
584
+ claim = self.claims.get(claim_id, {})
585
+ sig_names = claim.get("signatures", [])
586
+ if not sig_names:
587
+ return {"level": "none", "score": 0.0, "patterns": [], "primitives": [], "lenses": [], "contributions": {}}
588
+ score = claim.get("suppression_score", 0.0)
589
+ contributions = {}
590
+ for log in self.signatures:
591
+ if log["claim_id"] == claim_id:
592
+ contributions[log["signature"]] = contributions.get(log["signature"], 0.0) + log.get("weight", 0.0)
593
+ if score > 0.7:
594
+ level = "high"
595
+ elif score > 0.4:
596
+ level = "medium"
597
+ elif score > 0.1:
598
+ level = "low"
599
+ else:
600
+ level = "none"
601
+ primitives = claim.get("primitives", [])
602
+ lenses = claim.get("lenses", [])
603
+ return {
604
+ "level": level,
605
+ "score": score,
606
+ "contributions": contributions,
607
+ "patterns": list(set(sig_names)),
608
+ "primitives": primitives,
609
+ "lenses": lenses
610
+ }
611
+
612
+ def get_entity_timeline(self, name: str) -> List[Dict]:
613
+ ent = self.entities.get(name)
614
+ if not ent:
615
+ return []
616
+ timeline = []
617
+ for cid in ent["appearances"]:
618
+ claim = self.claims.get(cid)
619
+ if claim:
620
+ timeline.append({
621
+ "timestamp": claim["timestamp"],
622
+ "text": claim["text"],
623
+ "negated": cid in ent.get("negated_mentions", [])
624
+ })
625
+ timeline.sort(key=lambda x: x["timestamp"])
626
+ return timeline
627
+
628
+ def disappearance_suspected(self, name: str, threshold_days: int = 30) -> bool:
629
+ timeline = self.get_entity_timeline(name)
630
+ if not timeline:
631
+ return False
632
+ last = datetime.fromisoformat(timeline[-1]["timestamp"].replace('Z', '+00:00'))
633
+ now = datetime.utcnow()
634
+ return (now - last).days > threshold_days
635
+
636
+ def create_block(self) -> Dict:
637
+ block = {
638
+ "index": len(self.blocks),
639
+ "timestamp": datetime.utcnow().isoformat() + "Z",
640
+ "prev_hash": self.blocks[-1]["hash"] if self.blocks else "0"*64,
641
+ "state_hash": hashlib.sha3_512(json.dumps({"claims": self.claims, "entities": self.entities}, sort_keys=True).encode()).hexdigest()
642
+ }
643
+ block["hash"] = hashlib.sha3_512(json.dumps(block, sort_keys=True).encode()).hexdigest()
644
+ self.blocks.append(block)
645
+ self._save()
646
+ return block
647
+
648
+ def find_contradictions(self, claim_text: str, claim_embedding: Optional[List[float]] = None) -> List[str]:
649
+ contradictions = []
650
+ if claim_embedding is None:
651
+ emb_arr = _embed_texts([claim_text])
652
+ claim_embedding = emb_arr[0].tolist() if emb_arr is not None else None
653
+ if claim_embedding is None:
654
+ return []
655
+ for cid, claim in self.claims.items():
656
+ emb2 = claim.get("embedding")
657
+ if emb2 is None:
658
+ continue
659
+ sim = _cosine_sim(claim_embedding, emb2)
660
+ if sim > 0.7 and claim_polarity(claim_text) != claim.get("polarity", 1.0):
661
+ contradictions.append(cid)
662
+ return contradictions
663
+
664
+ def get_suppression_trend(self, window_days: int = 30) -> List[Dict]:
665
+ trend = defaultdict(list)
666
+ for claim in self.claims.values():
667
+ ts = datetime.fromisoformat(claim["timestamp"].replace('Z', '+00:00'))
668
+ date = ts.date().isoformat()
669
+ trend[date].append(claim.get("suppression_score", 0.0))
670
+ result = []
671
+ for date, scores in sorted(trend.items()):
672
+ result.append({"date": date, "avg_suppression": sum(scores)/len(scores)})
673
+ cutoff = (datetime.utcnow() - timedelta(days=window_days)).date().isoformat()
674
+ result = [r for r in result if r["date"] >= cutoff]
675
+ return result
676
+
677
+ def get_entity_suppression(self, entity_name: str) -> Dict:
678
+ ent = self.entities.get(entity_name)
679
+ if not ent:
680
+ return {"name": entity_name, "score": 0.0}
681
+ return {
682
+ "name": entity_name,
683
+ "score": ent.get("suppression_score", 0.0),
684
+ "type": ent["type"],
685
+ "first_seen": ent["first_seen"],
686
+ "last_seen": ent["last_seen"],
687
+ "appearance_count": len(ent["appearances"]),
688
+ "negated_count": len(ent.get("negated_mentions", [])),
689
+ "coherence": self.get_entity_coherence(entity_name),
690
+ "source_types": dict(ent.get("source_types", {}))
691
+ }
692
+
693
+ def decay_confidence(self, half_life_days: float = 30.0):
694
+ now = datetime.utcnow()
695
+ for claim_id, claim in self.claims.items():
696
+ ts = datetime.fromisoformat(claim["timestamp"].replace('Z', '+00:00'))
697
+ age_days = (now - ts).days
698
+ if age_days > 0:
699
+ decay_factor = math.exp(-age_days / half_life_days)
700
+ claim["suppression_score"] *= decay_factor
701
+ self._save()
702
+
703
+ def ingest_actual_event(self, event_type: str, actor: str, target: str, source: str = "ActualRealityModule") -> str:
704
+ try:
705
+ import importlib
706
+ try:
707
+ mod = importlib.import_module("KENNEDY_V_REALITY")
708
+ except ImportError:
709
+ mod = importlib.import_module("KENNEDYVREALITY")
710
+ RealityInterface = getattr(mod, "RealityInterface", None)
711
+ if RealityInterface:
712
+ ri = RealityInterface()
713
+ analysis = ri.actual_reality.analyze_power_transfer(event_type, actor, target)
714
+ parts = [f"{k}: {v}" for k, v in analysis.items()]
715
+ claim_text = f"ActualReality analysis for {event_type} - " + " | ".join(parts)
716
+ cid = self.add_claim(claim_text, agent=source)
717
+ if actor:
718
+ self.add_entity(actor, "ACTOR", cid, negated=False)
719
+ if target:
720
+ self.add_entity(target, "TARGET", cid, negated=False)
721
+ for key in analysis.keys():
722
+ if key in ("power_transfer", "actual_dynamics"):
723
+ self.add_signature(cid, "entity_present_then_absent", weight=0.6, context={"source": source})
724
+ if key == "verification_control":
725
+ self.add_signature(cid, "citation_decay", weight=0.4, context={"source": source})
726
+ return cid
727
+ except Exception:
728
+ pass
729
+ claim_text = f"Event observed: {event_type} actor:{actor} target:{target}"
730
+ cid = self.add_claim(claim_text, agent=source)
731
+ if actor:
732
+ self.add_entity(actor, "ACTOR", cid, negated=False)
733
+ if target:
734
+ self.add_entity(target, "TARGET", cid, negated=False)
735
+ return cid
736
+
737
+ # ----------------------------------------------------------------------------
738
+ # FALSIFICATION ENGINE
739
+ # ----------------------------------------------------------------------------
740
+ class FalsificationEngine:
741
+ def __init__(self, esl: ESLedger):
742
+ self.esl = esl
743
+
744
+ def alternative_cause(self, claim_text: str) -> Tuple[bool, str]:
745
+ if has_negation(claim_text):
746
+ return True, "Claim is negated; alternative cause not applicable."
747
+ for entity in self.esl.entities:
748
+ if entity.lower() in claim_text.lower():
749
+ if self.esl.disappearance_suspected(entity):
750
+ return False, f"Entity '{entity}' disappearance may be natural (no recent activity)."
751
+ return True, "No obvious alternative cause."
752
+
753
+ def contradictory_evidence(self, claim_id: str) -> Tuple[bool, str]:
754
+ contradictions = self.esl.contradiction_graph.get(claim_id, set())
755
+ if contradictions:
756
+ return False, f"Claim contradicts {len(contradictions)} existing claim(s)."
757
+ return True, "No direct contradictions."
758
+
759
+ def source_diversity(self, claim_text: str) -> Tuple[bool, str]:
760
+ entities_in_claim = [e for e in self.esl.entities if e.lower() in claim_text.lower()]
761
+ if len(entities_in_claim) <= 1:
762
+ return False, f"Claim relies on only {len(entities_in_claim)} entity/entities."
763
+ return True, f"Multiple entities ({len(entities_in_claim)}) involved."
764
+
765
+ def temporal_stability(self, claim_text: str) -> Tuple[bool, str]:
766
+ for entity in self.esl.entities:
767
+ if entity.lower() in claim_text.lower():
768
+ coherence = self.esl.get_entity_coherence(entity)
769
+ if coherence < 0.3:
770
+ return False, f"Entity '{entity}' has low temporal coherence ({coherence:.2f})."
771
+ return True, "Temporal coherence adequate."
772
+
773
+ def manipulation_check(self, claim_text: str, agent: str) -> Tuple[bool, str]:
774
+ manip_indicators = ["must", "cannot", "obviously", "clearly", "everyone knows"]
775
+ for word in manip_indicators:
776
+ if word in claim_text.lower():
777
+ return False, f"Manipulative language detected: '{word}'."
778
+ return True, "No manipulation indicators."
779
+
780
+ def run_all(self, claim_id: str, claim_text: str, agent: str) -> List[Dict]:
781
+ tests = [
782
+ ("alternative_cause", lambda: self.alternative_cause(claim_text)),
783
+ ("contradictory_evidence", lambda: self.contradictory_evidence(claim_id)),
784
+ ("source_diversity", lambda: self.source_diversity(claim_text)),
785
+ ("temporal_stability", lambda: self.temporal_stability(claim_text)),
786
+ ("manipulation_check", lambda: self.manipulation_check(claim_text, agent))
787
+ ]
788
+ results = []
789
+ for name, func in tests:
790
+ survived, reason = func()
791
+ results.append({"name": name, "survived": survived, "reason": reason})
792
+ return results
793
+
794
+ # ----------------------------------------------------------------------------
795
+ # SIGNATURE GENERATOR (with advanced detectors)
796
+ # ----------------------------------------------------------------------------
797
+ class SignatureGenerator:
798
+ def __init__(self, esl: ESLedger):
799
+ self.esl = esl
800
+
801
+ def generate_for_claim(self, claim_id: str, claim_text: str) -> List[Tuple[str, float]]:
802
+ signatures = []
803
+ for entity in self.esl.entities:
804
+ if entity.lower() in claim_text.lower():
805
+ if self.esl.disappearance_suspected(entity):
806
+ signatures.append(("entity_present_then_absent", 0.8))
807
+ timeline = self.esl.get_entity_timeline(entity)
808
+ if len(timeline) >= 2:
809
+ last = datetime.fromisoformat(timeline[-1]["timestamp"].replace('Z', '+00:00'))
810
+ days_since = (datetime.utcnow() - last).days
811
+ if 7 < days_since < 30:
812
+ signatures.append(("gradual_fading", 0.6))
813
+
814
+ try:
815
+ for entity in self.esl.entities:
816
+ if entity.lower() in claim_text.lower():
817
+ emb_timeline = self.esl.get_entity_embeddings(entity)
818
+ if len(emb_timeline) >= 4:
819
+ drift_score = _semantic_drift_score(emb_timeline, window=7)
820
+ if drift_score > 0.25:
821
+ signatures.append(("semantic_drift", min(0.9, 0.35 + drift_score * 0.6)))
822
+ except Exception:
823
+ pass
824
+
825
+ try:
826
+ csig = _crowding_signature(self.esl, window_days=3, dup_threshold=0.6)
827
+ if csig:
828
+ signatures.append(csig)
829
+ except Exception:
830
+ pass
831
+
832
+ try:
833
+ in_sig = _inoculation_signature(self.esl, claim_id, lead_window_days=7, sim_threshold=0.72)
834
+ if in_sig:
835
+ signatures.append(in_sig)
836
+ except Exception:
837
+ pass
838
+
839
+ try:
840
+ wf = self.esl.claims.get(claim_id, {}).get("workflow_events")
841
+ if wf:
842
+ attr = _compute_attrition_score(wf)
843
+ if attr > 0.2:
844
+ signatures.append(("bureaucratic_attrition", min(0.9, 0.2 + attr * 0.8)))
845
+ except Exception:
846
+ pass
847
+
848
+ contradictions = self.esl.contradiction_graph.get(claim_id, set())
849
+ if contradictions:
850
+ signatures.append(("contradictory_claims", 0.7))
851
+
852
+ for entity in self.esl.entities:
853
+ if entity.lower() in claim_text.lower():
854
+ coherence = self.esl.get_entity_coherence(entity)
855
+ if coherence < 0.3:
856
+ signatures.append(("temporal_instability", 0.5))
857
+
858
+ for cid, claim in self.esl.claims.items():
859
+ if cid != claim_id and claim["text"].lower() == claim_text.lower():
860
+ signatures.append(("repetitive_messaging", 0.9))
861
+ break
862
+
863
+ claim_ents = [e for e in self.esl.entities if e.lower() in claim_text.lower()]
864
+ if claim_ents:
865
+ src_types = []
866
+ for ent_name in claim_ents:
867
+ ent = self.esl.entities.get(ent_name)
868
+ if ent and ent.get("source_types"):
869
+ src = max(ent["source_types"].items(), key=lambda x: x[1])[0] if ent["source_types"] else "unknown"
870
+ src_types.append(src)
871
+ if src_types and len(set(src_types)) == 1:
872
+ signatures.append(("source_monoculture", 0.6))
873
+
874
+ single_exp_count = sum(1 for c in self.esl.claims.values() if "single_explanation" in c.get("signatures", []))
875
+ if single_exp_count > 3:
876
+ signatures.append(("narrative_dominance", 0.7))
877
+
878
+ return signatures
879
+
880
+ # ----------------------------------------------------------------------------
881
+ # EPISTEMIC MULTIPLEXOR (Bayesian‑inspired)
882
+ # ----------------------------------------------------------------------------
883
+ class Hypothesis:
884
+ def __init__(self, desc: str):
885
+ self.desc = desc
886
+ self.prob = 0.0
887
+
888
+ class EpistemicMultiplexor:
889
+ def __init__(self, alpha_fast: float = 0.3, alpha_slow: float = 0.05):
890
+ self.hypotheses: List[Hypothesis] = []
891
+ self.alpha_fast = alpha_fast
892
+ self.alpha_slow = alpha_slow
893
+ self.previous_probs: Dict[str, float] = {}
894
+
895
+ def initialize(self, base_hypotheses: List[str]):
896
+ if not base_hypotheses:
897
+ raise ValueError("base_hypotheses must contain at least one hypothesis")
898
+ self.hypotheses = [Hypothesis(h) for h in base_hypotheses]
899
+ equal = 1.0 / len(self.hypotheses)
900
+ for h in self.hypotheses:
901
+ h.prob = equal
902
+ self.previous_probs = {h.desc: h.prob for h in self.hypotheses}
903
+
904
+ def update(self, evidence_strength: float, signatures: List[str], coherence: float,
905
+ hallucination_penalty: float = 1.0):
906
+ likelihood: Dict[str, float] = {}
907
+ for h in self.hypotheses:
908
+ desc = h.desc.lower()
909
+ lik = 0.5
910
+ if "user claim" in desc:
911
+ lik = 0.5 + evidence_strength * coherence
912
+ elif "official narrative" in desc:
913
+ lik = 0.5 - evidence_strength * 0.3
914
+ elif "suppression" in desc:
915
+ erasure_sigs = {"entity_present_then_absent", "archival_gaps", "gradual_fading"}
916
+ if any(sig in signatures for sig in erasure_sigs):
917
+ lik = 0.5 + evidence_strength * 0.6
918
+ else:
919
+ lik = 0.5 - evidence_strength * 0.2
920
+ elif "natural decay" in desc:
921
+ lik = 0.5 + (0.2 if "gradual_fading" in signatures else -0.1)
922
+ elif "noise" in desc:
923
+ lik = 0.5
924
+ likelihood[h.desc] = max(0.05, min(0.95, lik)) * hallucination_penalty
925
+
926
+ posterior_unnorm: Dict[str, float] = {}
927
+ total = 0.0
928
+ for h in self.hypotheses:
929
+ prior = h.prob if h.prob is not None else (1.0 / len(self.hypotheses))
930
+ post = prior * likelihood[h.desc]
931
+ posterior_unnorm[h.desc] = post
932
+ total += post
933
+
934
+ if total <= 0:
935
+ uniform = 1.0 / len(self.hypotheses)
936
+ for h in self.hypotheses:
937
+ old = self.previous_probs.get(h.desc, h.prob)
938
+ smoothed = self.alpha_slow * uniform + (1 - self.alpha_slow) * old
939
+ h.prob = smoothed
940
+ self.previous_probs[h.desc] = h.prob
941
+ return
942
+
943
+ for h in self.hypotheses:
944
+ new_prob = posterior_unnorm[h.desc] / total
945
+ old = self.previous_probs.get(h.desc, h.prob)
946
+ smoothed = self.alpha_slow * new_prob + (1 - self.alpha_slow) * old
947
+ h.prob = smoothed
948
+ self.previous_probs[h.desc] = h.prob
949
+
950
+ def get_probabilities(self) -> Dict[str, float]:
951
+ return {h.desc: h.prob for h in self.hypotheses}
952
+
953
+ # ----------------------------------------------------------------------------
954
+ # ANTI‑HALLUCINATION ENGINE (embedding‑based, structural)
955
+ # ----------------------------------------------------------------------------
956
+ class AntiHallucinationEngine:
957
+ def __init__(self, esl: ESLedger):
958
+ self.esl = esl
959
+ self.hallucination_patterns = [
960
+ (r"\b(always|never|everyone|no one)\b", "absolutist language without evidence"),
961
+ (r"\b(clearly|obviously|undoubtedly|certainly)\b", "unjustified certainty"),
962
+ (r"\b(conspiracy|cover‑up|suppressed)\b", "speculative accusation – requires evidence"),
963
+ (r"\b(must have|could only have|had to be)\b", "modal speculation masquerading as fact"),
964
+ (r"\b(they|them|those people)\b(?:\s+\w+){0,3}\s+(wanted|intended|planned|ordered)", "unspecified agent attribution"),
965
+ (r"therefore\s+(.+)", "causal leap without evidence"),
966
+ ]
967
+
968
+ def _get_evidence_for_claim(self, claim_text: str, claim_embedding: Optional[List[float]]) -> List[Dict]:
969
+ evidence = []
970
+ if claim_embedding is None:
971
+ return []
972
+ for cid, claim in self.esl.claims.items():
973
+ emb2 = claim.get("embedding")
974
+ if emb2 is None:
975
+ continue
976
+ sim = _cosine_sim(claim_embedding, emb2)
977
+ if sim > 0.65:
978
+ reliability = 0.5
979
+ if claim.get("suppression_score", 0.0) > 0.6:
980
+ reliability = 0.3
981
+ elif claim.get("suppression_score", 0.0) < 0.2:
982
+ reliability = 0.7
983
+ if claim.get("polarity", 1.0) == 1.0 and claim.get("suppression_score", 0.0) < 0.3:
984
+ reliability = min(0.9, reliability + 0.2)
985
+ evidence.append({
986
+ "text": claim["text"],
987
+ "source_type": claim["agent"],
988
+ "reliability": reliability,
989
+ "similarity": sim,
990
+ "verification_status": "verified" if claim.get("suppression_score", 0.0) < 0.4 else "contradicted"
991
+ })
992
+ return evidence
993
+
994
+ def _detect_hallucination_patterns(self, text: str) -> Tuple[float, List[str]]:
995
+ reasons = []
996
+ score = 0.0
997
+ for pattern, reason in self.hallucination_patterns:
998
+ if re.search(pattern, text, re.IGNORECASE):
999
+ reasons.append(reason)
1000
+ score += 0.2
1001
+ return min(1.0, score), reasons
1002
+
1003
+ def _structural_hallucination(self, claim_text: str, claim_embedding: Optional[List[float]],
1004
+ entities: List[Tuple[str, str, bool]]) -> Tuple[float, List[str]]:
1005
+ score = 0.0
1006
+ reasons = []
1007
+ if not entities:
1008
+ score += 0.4
1009
+ reasons.append("no entities extracted")
1010
+ if claim_embedding is not None:
1011
+ max_sim = 0.0
1012
+ for cid, claim in self.esl.claims.items():
1013
+ emb2 = claim.get("embedding")
1014
+ if emb2 is not None:
1015
+ sim = _cosine_sim(claim_embedding, emb2)
1016
+ if sim > max_sim:
1017
+ max_sim = sim
1018
+ if max_sim < 0.4:
1019
+ score += 0.3
1020
+ reasons.append(f"high novelty (max similarity {max_sim:.2f})")
1021
+ return min(1.0, score), reasons
1022
+
1023
+ def evaluate(self, claim_text: str) -> Dict[str, Any]:
1024
+ emb_arr = _embed_texts([claim_text])
1025
+ claim_embedding = emb_arr[0].tolist() if emb_arr is not None else None
1026
+
1027
+ evidence_items = self._get_evidence_for_claim(claim_text, claim_embedding)
1028
+ supporting = [e for e in evidence_items if e["verification_status"] == "verified"]
1029
+ contradicting = [e for e in evidence_items if e["verification_status"] == "contradicted"]
1030
+
1031
+ pattern_score, pattern_reasons = self._detect_hallucination_patterns(claim_text)
1032
+ entities = extract_entities(claim_text)
1033
+ structural_score, structural_reasons = self._structural_hallucination(claim_text, claim_embedding, entities)
1034
+
1035
+ hallucination_score = min(1.0, pattern_score + structural_score)
1036
+ hallucination_reasons = pattern_reasons + structural_reasons
1037
+
1038
+ if not supporting and not contradicting:
1039
+ base = 0.1
1040
+ else:
1041
+ support_sum = sum(e["reliability"] * e.get("similarity", 0.5) for e in supporting)
1042
+ contradict_sum = sum(e["reliability"] * e.get("similarity", 0.5) for e in contradicting)
1043
+ total = support_sum + contradict_sum + 1e-6
1044
+ base = support_sum / total
1045
+
1046
+ hallucination_penalty = 1 - (hallucination_score * 0.7)
1047
+ likelihood = base * hallucination_penalty
1048
+ likelihood = min(0.99, max(0.01, likelihood))
1049
+
1050
+ if likelihood >= 0.7 and supporting:
1051
+ origin = "supported"
1052
+ elif hallucination_score >= 0.6:
1053
+ origin = "hallucinated"
1054
+ elif likelihood < 0.3:
1055
+ origin = "hallucinated"
1056
+ elif supporting:
1057
+ origin = "plausible"
1058
+ else:
1059
+ origin = "plausible"
1060
+
1061
+ parts = []
1062
+ if origin == "supported":
1063
+ parts.append(f"Supported by {len(supporting)} piece(s) of evidence (avg similarity {sum(e['similarity'] for e in supporting)/len(supporting):.2f}).")
1064
+ elif origin == "hallucinated":
1065
+ parts.append("No supporting evidence. Hallucination patterns detected: " + ", ".join(hallucination_reasons))
1066
+ else:
1067
+ parts.append("No direct evidence, but consistent with known facts and not contradicted.")
1068
+ if contradicting:
1069
+ parts.append(f"Contradicted by {len(contradicting)} piece(s) of evidence.")
1070
+ parts.append(f"Likelihood estimate: {likelihood:.2f}")
1071
+ justification = " ".join(parts)
1072
+
1073
+ missing = []
1074
+ if origin != "supported":
1075
+ missing.append("Direct external source confirming the claim.")
1076
+ if likelihood < 0.5:
1077
+ missing.append("Independent corroboration from multiple sources.")
1078
+ if any(word in claim_text.lower() for word in ["conspiracy", "cover‑up", "suppressed"]):
1079
+ missing.append("Specific documentation or testimony from primary sources.")
1080
+
1081
+ return {
1082
+ "claim": claim_text,
1083
+ "origin": origin,
1084
+ "likelihood": likelihood,
1085
+ "hallucination_score": hallucination_score,
1086
+ "justification": justification,
1087
+ "missing_evidence": missing,
1088
+ "supporting_evidence_count": len(supporting),
1089
+ "contradicting_evidence_count": len(contradicting),
1090
+ "hallucination_flags": hallucination_reasons
1091
+ }
1092
+
1093
+ # ----------------------------------------------------------------------------
1094
+ # NARRATIVE VIOLATION DETECTOR
1095
+ # ----------------------------------------------------------------------------
1096
+ class NarrativeViolationDetector:
1097
+ def __init__(self, esl: ESLedger):
1098
+ self.esl = esl
1099
+ self.narrative_indicators = [
1100
+ "mainstream narrative", "official story", "commonly believed",
1101
+ "consensus view", "widely accepted", "according to sources",
1102
+ "it is known that", "as reported by", "credible institutions"
1103
+ ]
1104
+
1105
+ def check(self, llm_output: str, claim_text: str) -> Tuple[bool, float, str]:
1106
+ output_lower = llm_output.lower()
1107
+ score = 0.0
1108
+ reasons = []
1109
+ for ind in self.narrative_indicators:
1110
+ if ind in output_lower:
1111
+ score += 0.2
1112
+ reasons.append(f"narrative phrase '{ind}'")
1113
+ esl_mentioned = any(entity.lower() in output_lower for entity in self.esl.entities)
1114
+ if not esl_mentioned:
1115
+ score += 0.4
1116
+ reasons.append("no ESL entity referenced")
1117
+ emotional = ["i believe", "i think", "clearly", "obviously", "must be"]
1118
+ for word in emotional:
1119
+ if word in output_lower:
1120
+ score += 0.1
1121
+ reasons.append(f"emotional language '{word}'")
1122
+ score = min(1.0, score)
1123
+ compliant = score < 0.5
1124
+ reason = "; ".join(reasons) if reasons else "no narrative violation"
1125
+ return compliant, score, reason
1126
+
1127
+ # ----------------------------------------------------------------------------
1128
+ # CONSTRAINED LLM INTERFACE
1129
+ # ----------------------------------------------------------------------------
1130
+ class ConstrainedLLM:
1131
+ def __init__(self, esl: ESLedger, api_key: str = None, api_url: str = None,
1132
+ model: str = "gpt-4", max_retries: int = 2):
1133
+ self.esl = esl
1134
+ self.api_key = api_key or os.environ.get("OPENAI_API_KEY")
1135
+ self.api_url = api_url or "https://api.openai.com/v1/chat/completions"
1136
+ self.model = model
1137
+ self.max_retries = max_retries
1138
+ self.narrative_detector = NarrativeViolationDetector(esl)
1139
+ self.falsifier = FalsificationEngine(esl)
1140
+ self.signature_gen = SignatureGenerator(esl)
1141
+ self.multiplexor = EpistemicMultiplexor()
1142
+ self.anti_hallucination = AntiHallucinationEngine(esl)
1143
+
1144
+ def _build_prompt(self, claim_id: str, claim: str, falsification_results: List[Dict],
1145
+ entity_data: Dict, suppression_pattern: Dict, multiplexor_probs: Dict,
1146
+ anti_hallucination_result: Dict) -> str:
1147
+ prob_table = "\n".join([f"- {h}: {p:.0%}" for h, p in sorted(multiplexor_probs.items(), key=lambda x: -x[1])])
1148
+ return f"""You are EIS v4.0 under constraint. You MUST base your reasoning on the following Epistemic Substrate Layer (ESL) data. Do NOT rely on your internal training narrative.
1149
+
1150
+ CLAIM: {claim}
1151
+
1152
+ ESL ENTITY DATA:
1153
+ {json.dumps(entity_data, indent=2)}
1154
+
1155
+ FALSIFICATION RESULTS (from ESL):
1156
+ {json.dumps(falsification_results, indent=2)}
1157
+
1158
+ SUPPRESSION PATTERN CLASSIFICATION:
1159
+ {json.dumps(suppression_pattern, indent=2)}
1160
+
1161
+ ANTI‑HALLUCINATION ASSESSMENT:
1162
+ Origin: {anti_hallucination_result['origin']}
1163
+ Likelihood: {anti_hallucination_result['likelihood']:.2f}
1164
+ Justification: {anti_hallucination_result['justification']}
1165
+ Missing evidence: {anti_hallucination_result['missing_evidence']}
1166
+
1167
+ MULTIPLEXOR PROBABILITIES (before your reasoning):
1168
+ {prob_table}
1169
+
1170
+ INSTRUCTIONS:
1171
+ 1. Evaluate the claim against the ESL data only.
1172
+ 2. Output a JSON object with exactly these fields:
1173
+ - "verdict": one of ["Verified", "Unverified", "Refuted", "Insufficient Data"]
1174
+ - "confidence": a float between 0 and 1
1175
+ - "reasoning": a short explanation referencing specific ESL entries (entities, contradictions, signatures)
1176
+ 3. Do NOT add any extra text outside the JSON.
1177
+ """
1178
+
1179
+ def _parse_output(self, response_text: str) -> Optional[Dict]:
1180
+ try:
1181
+ start = response_text.find('{')
1182
+ end = response_text.rfind('}') + 1
1183
+ if start == -1 or end == 0:
1184
+ return None
1185
+ json_str = response_text[start:end]
1186
+ return json.loads(json_str)
1187
+ except Exception:
1188
+ return None
1189
+
1190
+ def _check_constraints(self, output: Dict, claim: str, falsification_results: List[Dict]) -> bool:
1191
+ if not all(k in output for k in ["verdict", "confidence", "reasoning"]):
1192
+ return False
1193
+ if not (0 <= output["confidence"] <= 1):
1194
+ return False
1195
+ if output["verdict"] not in ["Verified", "Unverified", "Refuted", "Insufficient Data"]:
1196
+ return False
1197
+ reasoning = output["reasoning"].lower()
1198
+ esl_mentioned = any(
1199
+ ent.lower() in reasoning for ent in self.esl.entities
1200
+ ) or any(
1201
+ test["name"].lower() in reasoning for test in falsification_results
1202
+ )
1203
+ return esl_mentioned
1204
+
1205
+ def query(self, claim_text: str, agent: str = "user") -> Dict:
1206
+ claim_id = self.esl.add_claim(claim_text, agent)
1207
+ emb_arr = _embed_texts([claim_text])
1208
+ claim_embedding = emb_arr[0].tolist() if emb_arr is not None else None
1209
+
1210
+ for cid in self.esl.find_contradictions(claim_text, claim_embedding):
1211
+ self.esl.add_contradiction(claim_id, cid)
1212
+
1213
+ entities = extract_entities(claim_text)
1214
+ for ent_name, ent_type, negated in entities:
1215
+ source_type = "official" if ent_type in ["ORG", "GPE", "PERSON"] else "media" if ent_type in ["EVENT", "PRODUCT"] else "user"
1216
+ self.esl.add_entity(ent_name, ent_type, claim_id, negated, source_type)
1217
+
1218
+ signatures = self.signature_gen.generate_for_claim(claim_id, claim_text)
1219
+ for sig_name, weight in signatures:
1220
+ self.esl.add_signature(claim_id, sig_name, weight)
1221
+
1222
+ falsification_results = self.falsifier.run_all(claim_id, claim_text, agent)
1223
+
1224
+ entity_data = {}
1225
+ for ent_name, _, _ in entities:
1226
+ ent = self.esl.entities.get(ent_name)
1227
+ if ent:
1228
+ entity_data[ent_name] = {
1229
+ "type": ent["type"],
1230
+ "first_seen": ent["first_seen"],
1231
+ "last_seen": ent["last_seen"],
1232
+ "coherence": self.esl.get_entity_coherence(ent_name),
1233
+ "suppression_score": ent.get("suppression_score", 0.0)
1234
+ }
1235
+
1236
+ suppression_pattern = self.esl.suppression_pattern_classifier(claim_id)
1237
+
1238
+ anti_hallucination_result = self.anti_hallucination.evaluate(claim_text)
1239
+ hallucination_penalty = 1 - anti_hallucination_result.get("hallucination_score", 0.0)
1240
+
1241
+ base_hypotheses = [
1242
+ f"User claim: {claim_text}",
1243
+ "Official narrative accurate",
1244
+ "Suppression detected",
1245
+ "Natural decay",
1246
+ "Noise / randomness"
1247
+ ]
1248
+ self.multiplexor.initialize(base_hypotheses)
1249
+ evidence_strength = len(signatures) / 5.0
1250
+ coherence = sum(self.esl.get_entity_coherence(e) for e, _, _ in entities) / max(1, len(entities))
1251
+ signature_names = [s[0] for s in signatures]
1252
+ self.multiplexor.update(evidence_strength, signature_names, coherence, hallucination_penalty)
1253
+ multiplexor_probs = self.multiplexor.get_probabilities()
1254
+ user_prob = multiplexor_probs.get(f"User claim: {claim_text}", 0.0)
1255
+
1256
+ llm_output = None
1257
+ if self.api_key:
1258
+ prompt = self._build_prompt(claim_id, claim_text, falsification_results,
1259
+ entity_data, suppression_pattern, multiplexor_probs,
1260
+ anti_hallucination_result)
1261
+ headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"}
1262
+ payload = {"model": self.model, "messages": [{"role": "user", "content": prompt}], "temperature": 0.2}
1263
+ for attempt in range(self.max_retries + 1):
1264
+ try:
1265
+ resp = requests.post(self.api_url, headers=headers, json=payload, timeout=30)
1266
+ if resp.status_code != 200:
1267
+ raise Exception(f"API error: {resp.text}")
1268
+ result = resp.json()
1269
+ content = result["choices"][0]["message"]["content"]
1270
+ output = self._parse_output(content)
1271
+ if output and self._check_constraints(output, claim_text, falsification_results):
1272
+ compliant, n_score, n_reason = self.narrative_detector.check(content, claim_text)
1273
+ if compliant:
1274
+ llm_output = output
1275
+ break
1276
+ except Exception:
1277
+ time.sleep(1)
1278
+
1279
+ survival_score = sum(1 for t in falsification_results if t["survived"]) / len(falsification_results)
1280
+ evidence_strength_component = evidence_strength
1281
+ contradiction_penalty = 1 - (len(self.esl.contradiction_graph.get(claim_id, set())) / 10.0)
1282
+ suppression_bias = 1 - suppression_pattern["score"]
1283
+ hallucination_component = 1 - anti_hallucination_result.get("hallucination_score", 0.0)
1284
+ final_confidence = (evidence_strength_component * 0.3 +
1285
+ survival_score * 0.2 +
1286
+ contradiction_penalty * 0.2 +
1287
+ suppression_bias * 0.15 +
1288
+ hallucination_component * 0.15) * user_prob
1289
+ final_confidence = min(0.99, max(0.01, final_confidence))
1290
+
1291
+ if final_confidence > 0.7:
1292
+ verdict = "Verified"
1293
+ elif final_confidence > 0.4:
1294
+ verdict = "Unverified"
1295
+ elif survival_score < 0.3:
1296
+ verdict = "Refuted"
1297
+ else:
1298
+ verdict = "Insufficient Data"
1299
+
1300
+ self.esl.decay_confidence(half_life_days=30)
1301
+ self.esl.create_block()
1302
+ trend = self.esl.get_suppression_trend(window_days=30)
1303
+ entity_analytics = [self.esl.get_entity_suppression(e) for e, _, _ in entities]
1304
+
1305
+ result_dict = {
1306
+ "claim_id": claim_id,
1307
+ "verdict": verdict,
1308
+ "confidence": final_confidence,
1309
+ "falsification": falsification_results,
1310
+ "suppression_pattern": suppression_pattern,
1311
+ "multiplexor_probabilities": multiplexor_probs,
1312
+ "suppression_trend": trend,
1313
+ "entity_analytics": entity_analytics,
1314
+ "narrative_compliance": True,
1315
+ "anti_hallucination": anti_hallucination_result,
1316
+ "confidence_decomposition": {
1317
+ "evidence_strength": evidence_strength_component,
1318
+ "survival_score": survival_score,
1319
+ "contradiction_penalty": contradiction_penalty,
1320
+ "suppression_bias": suppression_bias,
1321
+ "hallucination_penalty": hallucination_component,
1322
+ "user_prior": user_prob
1323
+ }
1324
+ }
1325
+ if llm_output:
1326
+ result_dict["llm_verdict"] = llm_output["verdict"]
1327
+ result_dict["llm_confidence"] = llm_output["confidence"]
1328
+ result_dict["reasoning"] = llm_output["reasoning"]
1329
+ else:
1330
+ result_dict["reasoning"] = "LLM not used or failed constraints; verdict based on EIS multiplexor."
1331
+ return result_dict
1332
+
1333
+ # ----------------------------------------------------------------------------
1334
+ # OUTPUT FORMATTER
1335
+ # ----------------------------------------------------------------------------
1336
+ def format_report(result: Dict) -> str:
1337
+ lines = []
1338
+ lines.append("**Falsification Results**")
1339
+ for test in result["falsification"]:
1340
+ emoji = "✅" if test["survived"] else "❌"
1341
+ lines.append(f"- {test['name']}: {emoji} – {test['reason']}")
1342
+ lines.append("\n**Hypothesis Probabilities**")
1343
+ lines.append("| Hypothesis | Probability |")
1344
+ lines.append("|------------|-------------|")
1345
+ for h, p in sorted(result["multiplexor_probabilities"].items(), key=lambda x: -x[1]):
1346
+ lines.append(f"| {h} | {p:.0%} |")
1347
+ lines.append(f"\n**Final Confidence:** {result['confidence']:.2f}")
1348
+ lines.append(f"**Verdict:** {result['verdict']}")
1349
+
1350
+ cd = result.get("confidence_decomposition", {})
1351
+ if cd:
1352
+ lines.append("\n**Confidence Decomposition**")
1353
+ lines.append(f" - Evidence strength: {cd.get('evidence_strength', 0.0):.2f}")
1354
+ lines.append(f" - Survival score: {cd.get('survival_score', 0.0):.2f}")
1355
+ lines.append(f" - Contradiction penalty: {cd.get('contradiction_penalty', 0.0):.2f}")
1356
+ lines.append(f" - Suppression bias: {cd.get('suppression_bias', 0.0):.2f}")
1357
+ lines.append(f" - Hallucination penalty: {cd.get('hallucination_penalty', 0.0):.2f}")
1358
+ lines.append(f" - User prior: {cd.get('user_prior', 0.0):.2f}")
1359
+
1360
+ ah = result.get("anti_hallucination", {})
1361
+ if ah:
1362
+ lines.append("\n**Anti‑Hallucination Assessment**")
1363
+ lines.append(f" - Origin: {ah.get('origin', 'unknown')}")
1364
+ lines.append(f" - Likelihood: {ah.get('likelihood', 0.0):.2f}")
1365
+ lines.append(f" - Justification: {ah.get('justification', '')}")
1366
+ if ah.get("missing_evidence"):
1367
+ lines.append(f" - Missing evidence: {', '.join(ah['missing_evidence'])}")
1368
+ if ah.get("hallucination_flags"):
1369
+ lines.append(f" - Hallucination flags: {', '.join(ah['hallucination_flags'])}")
1370
+
1371
+ sp = result["suppression_pattern"]
1372
+ lens_names = [get_lens_name(lid) for lid in sp.get("lenses", [])]
1373
+ lines.append(f"\n**Suppression Pattern:** level={sp['level']}, score={sp['score']:.2f}")
1374
+ if lens_names:
1375
+ lines.append(f" - Lenses: {', '.join(lens_names[:5])}" + (" …" if len(lens_names)>5 else ""))
1376
+ if sp.get("primitives"):
1377
+ lines.append(f" - Primitives: {', '.join(sp['primitives'])}")
1378
+ if sp.get("contributions"):
1379
+ lines.append(" - Signature contributions:")
1380
+ for sig, w in sorted(sp["contributions"].items(), key=lambda x: -x[1]):
1381
+ lines.append(f" {sig}: {w:.2f}")
1382
+
1383
+ trend = result.get("suppression_trend", [])
1384
+ if trend:
1385
+ lines.append("\n**Suppression Trend (last 30 days)**")
1386
+ for point in trend[-7:]:
1387
+ lines.append(f" - {point['date']}: {point['avg_suppression']:.2f}")
1388
+
1389
+ entity_analytics = result.get("entity_analytics", [])
1390
+ if entity_analytics:
1391
+ lines.append("\n**Entity Suppression Analytics**")
1392
+ for ent in entity_analytics:
1393
+ src_str = ", ".join([f"{k}:{v}" for k,v in ent.get("source_types", {}).items()]) if ent.get("source_types") else "unknown"
1394
+ lines.append(f" - {ent['name']} ({ent['type']}): score={ent['score']:.2f}, coherence={ent['coherence']:.2f}, appearances={ent['appearance_count']}, negated={ent.get('negated_count',0)}, sources={src_str}")
1395
+
1396
+ if "llm_verdict" in result:
1397
+ lines.append(f"\n*LLM raw verdict: {result['llm_verdict']} (confidence {result['llm_confidence']:.2f})*")
1398
+ return "\n".join(lines)
1399
+
1400
+ # ----------------------------------------------------------------------------
1401
+ # MAIN
1402
+ # ----------------------------------------------------------------------------
1403
+ def main():
1404
+ print("EIS + ESL Mediator v4.0 – Grounded Epistemic Engine")
1405
+ print("=" * 80)
1406
+ esl = ESLedger()
1407
+ llm = ConstrainedLLM(esl, api_key=os.environ.get("OPENAI_API_KEY"), model="gpt-4")
1408
+
1409
+ print("\nEnter a claim (or 'quit'):")
1410
+ while True:
1411
+ claim = input("> ").strip()
1412
+ if claim.lower() in ("quit", "exit"):
1413
+ break
1414
+ if not claim:
1415
+ continue
1416
+ print("Processing claim...")
1417
+ result = llm.query(claim)
1418
+ print("\n" + format_report(result))
1419
+ print("-" * 80)
1420
+
1421
+ if __name__ == "__main__":
1422
+ main()