abinazebinoy commited on
Commit
747e613
·
1 Parent(s): 06c3703

fix(encoding): strip non-ASCII chars from all modified files

Browse files

All files contained UTF-8 punctuation (em dashes U+2014, arrows U+2192,
curly quotes) in comments which cause UnicodeDecodeError on GitHub
Actions runners that read files without explicit encoding= argument.

Replaced all non-ASCII punctuation with ASCII equivalents:
U+2014 em dash -> --
U+2192 arrow -> ->
curly quotes -> straight quotes

ai/deep_investigator.py CHANGED
@@ -16,7 +16,7 @@ LAYER_NAMES = [
16
 
17
  LAYER_DESCRIPTIONS = {
18
  "direct_evidence": "All relationships directly connected to this entity in the graph.",
19
- "relationship_expansion": "One-hop neighbours entities connected through this entity.",
20
  "pattern_investigation": "Structural patterns: circular ownership, shell indicators, procurement anomalies.",
21
  "timeline_investigation": "Temporal patterns: contract awards near elections, entity formation dates.",
22
  "network_influence": "Centrality and bridge-node analysis within the local subgraph.",
@@ -37,7 +37,7 @@ class DeepInvestigator:
37
 
38
  layers = []
39
  # BUG-15 FIX: each layer now gets its own session.
40
- # Previously all 6 layers shared ONE session if any layer timed out or
41
  # threw, the entire session (and all remaining layers) was killed.
42
  # Resolve name/label in its own short session first.
43
  # BUG-1 + BUG-7 FIX: default before try so entity_label is always bound
@@ -65,7 +65,7 @@ class DeepInvestigator:
65
  # entity_label stays "Unknown" from the default above
66
  # entity_label stays "Unknown" from the default above
67
 
68
- # Each layer uses its own session isolated, independently failable
69
  LAYER_METHODS = [
70
  (1, "direct_evidence", lambda _id=entity_id: self._run_layer(self._layer_1_direct, _id)),
71
  (2, "relationship_expansion", lambda _id=entity_id: self._run_layer(self._layer_2_expansion, _id)),
@@ -190,7 +190,7 @@ class DeepInvestigator:
190
  if key in seen: continue
191
  seen.add(key)
192
  items.append({
193
- "path": f"{row.get('via_name')} ({row.get('rel2')}) ",
194
  "connected_entity": row.get("name"),
195
  "connected_id": row.get("oid"),
196
  "entity_type": row.get("node_type"),
@@ -221,7 +221,7 @@ class DeepInvestigator:
221
  for row in rows:
222
  findings.append({
223
  "pattern": "DIRECTOR_CONTRACT_LINK",
224
- "description": f"Director of {row.get('company')} which won {row.get('contract_count')} contract(s) worth {row.get('total_value',0):.1f} Cr",
225
  "severity": "HIGH" if row.get("total_value",0) > 10 else "MODERATE",
226
  })
227
 
@@ -249,7 +249,7 @@ class DeepInvestigator:
249
  for row in rows:
250
  findings.append({
251
  "pattern": "ELECTORAL_BOND_PROXIMITY",
252
- "description": f"Electoral bond donor proximity: {row.get('donor','')} {row.get('party','')} {row.get('amount',0)} Cr",
253
  "severity": "MODERATE",
254
  })
255
 
 
16
 
17
  LAYER_DESCRIPTIONS = {
18
  "direct_evidence": "All relationships directly connected to this entity in the graph.",
19
+ "relationship_expansion": "One-hop neighbours -- entities connected through this entity.",
20
  "pattern_investigation": "Structural patterns: circular ownership, shell indicators, procurement anomalies.",
21
  "timeline_investigation": "Temporal patterns: contract awards near elections, entity formation dates.",
22
  "network_influence": "Centrality and bridge-node analysis within the local subgraph.",
 
37
 
38
  layers = []
39
  # BUG-15 FIX: each layer now gets its own session.
40
+ # Previously all 6 layers shared ONE session -- if any layer timed out or
41
  # threw, the entire session (and all remaining layers) was killed.
42
  # Resolve name/label in its own short session first.
43
  # BUG-1 + BUG-7 FIX: default before try so entity_label is always bound
 
65
  # entity_label stays "Unknown" from the default above
66
  # entity_label stays "Unknown" from the default above
67
 
68
+ # Each layer uses its own session -- isolated, independently failable
69
  LAYER_METHODS = [
70
  (1, "direct_evidence", lambda _id=entity_id: self._run_layer(self._layer_1_direct, _id)),
71
  (2, "relationship_expansion", lambda _id=entity_id: self._run_layer(self._layer_2_expansion, _id)),
 
190
  if key in seen: continue
191
  seen.add(key)
192
  items.append({
193
+ "path": f"{row.get('via_name')} ({row.get('rel2')}) ->",
194
  "connected_entity": row.get("name"),
195
  "connected_id": row.get("oid"),
196
  "entity_type": row.get("node_type"),
 
221
  for row in rows:
222
  findings.append({
223
  "pattern": "DIRECTOR_CONTRACT_LINK",
224
+ "description": f"Director of {row.get('company')} which won {row.get('contract_count')} contract(s) worth ?{row.get('total_value',0):.1f} Cr",
225
  "severity": "HIGH" if row.get("total_value",0) > 10 else "MODERATE",
226
  })
227
 
 
249
  for row in rows:
250
  findings.append({
251
  "pattern": "ELECTORAL_BOND_PROXIMITY",
252
+ "description": f"Electoral bond donor proximity: {row.get('donor','')} -> {row.get('party','')} ?{row.get('amount',0)} Cr",
253
  "severity": "MODERATE",
254
  })
255
 
api/models.py CHANGED
@@ -84,7 +84,7 @@ class ProfileSection(BaseModel):
84
 
85
 
86
  class ProfileResponse(BaseModel):
87
- """BUG-22 FIX: added sources field profile.py was returning it but model lacked it,
88
  causing Pydantic ValidationError silently on some entity types."""
89
  entity_id: str
90
  entity_type: str
 
84
 
85
 
86
  class ProfileResponse(BaseModel):
87
+ """BUG-22 FIX: added sources field -- profile.py was returning it but model lacked it,
88
  causing Pydantic ValidationError silently on some entity types."""
89
  entity_id: str
90
  entity_type: str
api/routes/graph.py CHANGED
@@ -25,7 +25,7 @@ def get_connections(
25
  _edge_seen = set() # BUG-10 FIX: dedup (src, tgt, type) triples
26
 
27
  with driver.session() as session:
28
- # Always load anchor node first guarantees non-empty response
29
  anchor = session.run(
30
  "MATCH (n {id:$id}) RETURN n", id=entity_id
31
  ).single()
 
25
  _edge_seen = set() # BUG-10 FIX: dedup (src, tgt, type) triples
26
 
27
  with driver.session() as session:
28
+ # Always load anchor node first -- guarantees non-empty response
29
  anchor = session.run(
30
  "MATCH (n {id:$id}) RETURN n", id=entity_id
31
  ).single()
api/routes/search.py CHANGED
@@ -21,7 +21,7 @@ SOURCE_MAP = {
21
  "Ministry": ("Government of India", "Ministry Directory", "https://india.gov.in"),
22
  "Party": ("Election Commission of India", "Party Registration", "https://eci.gov.in"),
23
  "Scheme": ("NITI Aayog / Ministries", "Scheme Database", "https://myscheme.gov.in"),
24
- # BUG-18 FIX: key was "pressrelease" but scraper produces "pib" normalised
25
  "PressRelease": ("Press Information Bureau", "PIB Press Release", "https://pib.gov.in"),
26
  "NGO": ("NGO Darpan / NITI Aayog", "NGO Registration Record", "https://ngodarpan.gov.in"),
27
  "ElectoralBond": ("Election Commission of India", "Electoral Bond Transaction", "https://eci.gov.in"),
@@ -38,7 +38,7 @@ SOURCE_MAP = {
38
  }
39
 
40
  # BUG-4 FIX: added ParliamentQuestion, VigilanceCircular, ICIJEntity,
41
- # SanctionedEntity, CourtCase, LocalBody previously these node types were
42
  # loaded into Neo4j but had no LABEL_QUERIES entry, making them unsearchable
43
  # via the label-scan fallback path.
44
  LABEL_QUERIES = {
@@ -203,7 +203,7 @@ def search_entities(
203
  generated_at=datetime.now().isoformat(),
204
  )
205
  except Exception as ft_err:
206
- # BUG-1 FIX: was type(ft_err).__name__ crashed because `type`
207
  # was shadowed by the query parameter. Now uses __class__.__name__.
208
  logger.debug(
209
  f"[Search] Full-text index unavailable, using label scan: "
 
21
  "Ministry": ("Government of India", "Ministry Directory", "https://india.gov.in"),
22
  "Party": ("Election Commission of India", "Party Registration", "https://eci.gov.in"),
23
  "Scheme": ("NITI Aayog / Ministries", "Scheme Database", "https://myscheme.gov.in"),
24
+ # BUG-18 FIX: key was "pressrelease" but scraper produces "pib" -- normalised
25
  "PressRelease": ("Press Information Bureau", "PIB Press Release", "https://pib.gov.in"),
26
  "NGO": ("NGO Darpan / NITI Aayog", "NGO Registration Record", "https://ngodarpan.gov.in"),
27
  "ElectoralBond": ("Election Commission of India", "Electoral Bond Transaction", "https://eci.gov.in"),
 
38
  }
39
 
40
  # BUG-4 FIX: added ParliamentQuestion, VigilanceCircular, ICIJEntity,
41
+ # SanctionedEntity, CourtCase, LocalBody -- previously these node types were
42
  # loaded into Neo4j but had no LABEL_QUERIES entry, making them unsearchable
43
  # via the label-scan fallback path.
44
  LABEL_QUERIES = {
 
203
  generated_at=datetime.now().isoformat(),
204
  )
205
  except Exception as ft_err:
206
+ # BUG-1 FIX: was type(ft_err).__name__ -- crashed because `type`
207
  # was shadowed by the query parameter. Now uses __class__.__name__.
208
  logger.debug(
209
  f"[Search] Full-text index unavailable, using label scan: "
frontend/index.html CHANGED
@@ -3,9 +3,9 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta name="description" content="BharatGraph AI-powered public transparency and institutional intelligence platform for India.">
7
  <meta name="theme-color" content="#0A0F2E">
8
- <title>BharatGraph Public Transparency Intelligence</title>
9
 
10
  <link rel="preconnect" href="https://fonts.googleapis.com">
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -50,25 +50,25 @@
50
  border-radius:var(--radius-md);padding:var(--space-1) var(--space-2);
51
  color:var(--text-secondary);font-size:var(--font-size-xs);cursor:pointer"
52
  onchange="State.language=this.value; if(window.applyLanguage) applyLanguage(this.value);">
53
- <option value="en">EN English</option>
54
- <option value="hi">HI हिन्दी</option>
55
- <option value="ta">TA தமிழ்</option>
56
- <option value="te">TE తెలుగు</option>
57
- <option value="kn">KN ಕನ್ನಡ</option>
58
- <option value="ml">ML മലയാളം</option>
59
- <option value="mr">MR मराठी</option>
60
- <option value="bn">BN বাংলা</option>
61
- <option value="gu">GU ગુજરાતી</option>
62
- <option value="pa">PA ਪੰਜਾਬੀ</option>
63
- <option value="or">OR ଓଡ଼ିଆ</option>
64
- <option value="as">AS অসমীয়া</option>
65
- <option value="ur">UR اردو</option>
66
- <option value="sd">SD سنڌي</option>
67
- <option value="kok">KOK कोंकणी</option>
68
- <option value="mai">MAI मैथिली</option>
69
- <option value="ne">NE नेपाली</option>
70
- <option value="doi">DOI डोगरी</option>
71
- <option value="sa">SA संस्कृतम्</option>
72
  </select>
73
  <button class="btn btn--ghost btn--sm" id="theme-btn"
74
  onclick="toggleTheme()" aria-label="Toggle theme">
@@ -103,7 +103,7 @@
103
  text-align:right;line-height:var(--line-height-relaxed)">
104
  All data from official government sources.<br>
105
  Outputs are analytical indicators, not legal findings.<br>
106
- MIT License Open Source
107
  </div>
108
  </div>
109
  <div style="margin-top:var(--space-4);padding-top:var(--space-4);
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="BharatGraph -- AI-powered public transparency and institutional intelligence platform for India.">
7
  <meta name="theme-color" content="#0A0F2E">
8
+ <title>BharatGraph -- Public Transparency Intelligence</title>
9
 
10
  <link rel="preconnect" href="https://fonts.googleapis.com">
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 
50
  border-radius:var(--radius-md);padding:var(--space-1) var(--space-2);
51
  color:var(--text-secondary);font-size:var(--font-size-xs);cursor:pointer"
52
  onchange="State.language=this.value; if(window.applyLanguage) applyLanguage(this.value);">
53
+ <option value="en">EN -- English</option>
54
+ <option value="hi">HI -- ??????</option>
55
+ <option value="ta">TA -- ?????</option>
56
+ <option value="te">TE -- ??????</option>
57
+ <option value="kn">KN -- ?????</option>
58
+ <option value="ml">ML -- ??????</option>
59
+ <option value="mr">MR -- ?????</option>
60
+ <option value="bn">BN -- ?????</option>
61
+ <option value="gu">GU -- ???????</option>
62
+ <option value="pa">PA -- ??????</option>
63
+ <option value="or">OR -- ?????</option>
64
+ <option value="as">AS -- ???????</option>
65
+ <option value="ur">UR -- ????</option>
66
+ <option value="sd">SD -- ????</option>
67
+ <option value="kok">KOK -- ??????</option>
68
+ <option value="mai">MAI -- ??????</option>
69
+ <option value="ne">NE -- ??????</option>
70
+ <option value="doi">DOI -- ?????</option>
71
+ <option value="sa">SA -- ?????????</option>
72
  </select>
73
  <button class="btn btn--ghost btn--sm" id="theme-btn"
74
  onclick="toggleTheme()" aria-label="Toggle theme">
 
103
  text-align:right;line-height:var(--line-height-relaxed)">
104
  All data from official government sources.<br>
105
  Outputs are analytical indicators, not legal findings.<br>
106
+ MIT License -- Open Source
107
  </div>
108
  </div>
109
  <div style="margin-top:var(--space-4);padding-top:var(--space-4);
frontend/js/app.js CHANGED
@@ -76,7 +76,7 @@ const Views = {
76
  <p class="section-subtitle">
77
  Enter any entity. The platform runs 12 specialist investigators
78
  in parallel, each analysing from a different angle.
79
- Findings are synthesised where three or more investigators
80
  agree, confidence is marked as high.
81
  </p>
82
  <div style="display:flex;flex-direction:column;gap:var(--space-4)">
@@ -155,7 +155,7 @@ const Views = {
155
  grid.innerHTML = stats
156
  .map(([v,l]) => `
157
  <div class="stat-card">
158
- <div class="stat-card__value">${v || ""}</div>
159
  <div class="stat-card__label">${l}</div>
160
  </div>
161
  `).join("");
@@ -194,7 +194,7 @@ const Views = {
194
  if (items.length > 0) {
195
  items.slice(0, 3).forEach(item => {
196
  container.appendChild(Components.FeedItem({
197
- headline: `[${item.label || "Entity"}] ${item.name || ""}`,
198
  risk_level: "MODERATE",
199
  detected_at: item.scraped_at || data.at,
200
  source: item.source || "BharatGraph",
@@ -216,7 +216,7 @@ const Views = {
216
  // BUG-6 FIX: reconnect on close
217
  ws.onclose = () => {
218
  const container = document.getElementById("home-feed");
219
- if (!container) return; // user navigated away stop retrying
220
  const nextDelay = Math.min(delay * 2, 30000);
221
  setTimeout(() => connect(nextDelay), nextDelay);
222
  };
@@ -247,28 +247,28 @@ const Views = {
247
  background:var(--bg-secondary);color:var(--text-primary);
248
  border:1px solid var(--border-color);border-radius:6px;cursor:pointer"
249
  onchange="State.language=this.value;applyLanguage(this.value);document.getElementById('navbar-lang-select')&&(document.getElementById('navbar-lang-select').value=this.value);(function(){const q=document.getElementById('search-input')&&document.getElementById('search-input').value.trim()||new URLSearchParams(window.location.hash.split('?')[1]||'').get('q')||'';if(q){const t=new URLSearchParams(window.location.hash.split('?')[1]||'').get('type')||'';Router.navigate('/search?q='+encodeURIComponent(q)+(t?'&type='+encodeURIComponent(t):'')+'&lang='+this.value);}}).call(this)">
250
- <option value="en">🇮🇳 English</option>
251
- <option value="hi">हिन्दी</option>
252
- <option value="ta">தமிழ்</option>
253
- <option value="te">తెలుగు</option>
254
- <option value="kn">ಕನ್ನಡ</option>
255
- <option value="ml">മലയാളം</option>
256
- <option value="mr">मराठी</option>
257
- <option value="bn">বাংলা</option>
258
- <option value="gu">ગુજરાતી</option>
259
- <option value="pa">ਪੰਜਾਬੀ</option>
260
- <option value="or">ଓଡ଼ିଆ</option>
261
- <option value="as">অসমীয়া</option>
262
- <option value="ur">اردو</option>
263
- <option value="kok">कोंकणी</option>
264
- <option value="mai">मैथिली</option>
265
- <option value="mni">ꯃꯩꯇꯩꯂꯣꯟ</option>
266
- <option value="sat">ᱥᱟᱱᱛᱟᱲᱤ</option>
267
- <option value="ks">كٲشُر</option>
268
- <option value="ne">नेपाली</option>
269
- <option value="doi">डोगरी</option>
270
- <option value="sa">संस्कृतम्</option>
271
- <option value="sd">سنڌي</option>
272
  </select>
273
  </div>
274
 
@@ -373,7 +373,7 @@ const Views = {
373
  const container = document.getElementById("search-results");
374
  if (container) container.innerHTML = `
375
  <div style="text-align:center;padding:var(--space-8);color:var(--color-risk-very-high)">
376
- Search failed. The API may be starting up please retry in a moment.
377
  </div>
378
  `;
379
  });
@@ -670,7 +670,7 @@ const Views = {
670
  </span>
671
  <div style="display:flex;gap:6px">
672
  <span style="font-size:10px;color:var(--text-muted)">
673
- Strong Medium Weak
674
  </span>
675
  </div>
676
  </div>
@@ -725,13 +725,13 @@ const Views = {
725
  <div style="font-size:10px;font-weight:700;text-transform:uppercase;
726
  color:var(--color-saffron);margin-bottom:2px">${sanitize(e.rel_label||"")}</div>
727
  <div style="font-size:12px;font-weight:600;color:var(--text-primary)">
728
- ${sanitize(e.connected_to||e.connected_id||"")}
729
  </div>
730
  <div style="font-size:10px;color:var(--text-secondary);margin-top:2px">
731
  ${sanitize(e.why||"")}
732
  </div>
733
  <div style="font-size:10px;color:var(--text-muted);margin-top:2px">
734
- 📄 ${sanitize(e.source||"")}
735
  </div>
736
  </div>`).join("") : `<div style="text-align:center;padding:20px;color:var(--text-muted);font-size:12px">
737
  No connections in current dataset</div>`;
@@ -761,8 +761,8 @@ const Views = {
761
  </div>
762
  ${(Array.isArray(path[0]) ? path[0] : path).map(n =>
763
  `<span style="color:var(--text-primary)">${sanitize(n)}</span>
764
- <span style="color:var(--text-muted);margin:0 4px"></span>`
765
- ).join("").replace(/<span[^>]*><\/span>$/, "")}
766
  </div>` : `
767
  <div style="color:var(--text-muted);font-size:12px">No path found</div>`;
768
  }).catch(() => {
@@ -866,7 +866,7 @@ function toggleTheme() {
866
  }
867
 
868
 
869
- // ── Language Application ──────────────────────────────────────────────────────
870
  // BUG-10 FIX: full DOM translation via data-i18n attributes
871
  async function applyLanguage(lang) {
872
  const badge = document.getElementById("lang-badge");
@@ -915,8 +915,8 @@ function initApp() {
915
  Router.register("/live-feed", Views.liveFeed);
916
  Router.register("/about", Views.about);
917
 
918
- // BUG-02 FIX: was single immediate check failed permanently on HF cold start.
919
- // BUG-25 FIX: message said "Start: uvicorn" wrong for HuggingFace users.
920
  // Fix: retry up to 5 times with exponential backoff (2s, 4s, 8s, 16s, 30s).
921
  (function checkHealth(attempt) {
922
  Api.health()
 
76
  <p class="section-subtitle">
77
  Enter any entity. The platform runs 12 specialist investigators
78
  in parallel, each analysing from a different angle.
79
+ Findings are synthesised -- where three or more investigators
80
  agree, confidence is marked as high.
81
  </p>
82
  <div style="display:flex;flex-direction:column;gap:var(--space-4)">
 
155
  grid.innerHTML = stats
156
  .map(([v,l]) => `
157
  <div class="stat-card">
158
+ <div class="stat-card__value">${v || "--"}</div>
159
  <div class="stat-card__label">${l}</div>
160
  </div>
161
  `).join("");
 
194
  if (items.length > 0) {
195
  items.slice(0, 3).forEach(item => {
196
  container.appendChild(Components.FeedItem({
197
+ headline: `[${item.label || "Entity"}] ${item.name || "--"}`,
198
  risk_level: "MODERATE",
199
  detected_at: item.scraped_at || data.at,
200
  source: item.source || "BharatGraph",
 
216
  // BUG-6 FIX: reconnect on close
217
  ws.onclose = () => {
218
  const container = document.getElementById("home-feed");
219
+ if (!container) return; // user navigated away -- stop retrying
220
  const nextDelay = Math.min(delay * 2, 30000);
221
  setTimeout(() => connect(nextDelay), nextDelay);
222
  };
 
247
  background:var(--bg-secondary);color:var(--text-primary);
248
  border:1px solid var(--border-color);border-radius:6px;cursor:pointer"
249
  onchange="State.language=this.value;applyLanguage(this.value);document.getElementById('navbar-lang-select')&&(document.getElementById('navbar-lang-select').value=this.value);(function(){const q=document.getElementById('search-input')&&document.getElementById('search-input').value.trim()||new URLSearchParams(window.location.hash.split('?')[1]||'').get('q')||'';if(q){const t=new URLSearchParams(window.location.hash.split('?')[1]||'').get('type')||'';Router.navigate('/search?q='+encodeURIComponent(q)+(t?'&type='+encodeURIComponent(t):'')+'&lang='+this.value);}}).call(this)">
250
+ <option value="en">?? English</option>
251
+ <option value="hi">??????</option>
252
+ <option value="ta">?????</option>
253
+ <option value="te">??????</option>
254
+ <option value="kn">?????</option>
255
+ <option value="ml">??????</option>
256
+ <option value="mr">?????</option>
257
+ <option value="bn">?????</option>
258
+ <option value="gu">???????</option>
259
+ <option value="pa">??????</option>
260
+ <option value="or">?????</option>
261
+ <option value="as">???????</option>
262
+ <option value="ur">????</option>
263
+ <option value="kok">??????</option>
264
+ <option value="mai">??????</option>
265
+ <option value="mni">???????</option>
266
+ <option value="sat">???????</option>
267
+ <option value="ks">?????</option>
268
+ <option value="ne">??????</option>
269
+ <option value="doi">?????</option>
270
+ <option value="sa">?????????</option>
271
+ <option value="sd">????</option>
272
  </select>
273
  </div>
274
 
 
373
  const container = document.getElementById("search-results");
374
  if (container) container.innerHTML = `
375
  <div style="text-align:center;padding:var(--space-8);color:var(--color-risk-very-high)">
376
+ Search failed. The API may be starting up -- please retry in a moment.
377
  </div>
378
  `;
379
  });
 
670
  </span>
671
  <div style="display:flex;gap:6px">
672
  <span style="font-size:10px;color:var(--text-muted)">
673
+ ? Strong ? Medium ? Weak
674
  </span>
675
  </div>
676
  </div>
 
725
  <div style="font-size:10px;font-weight:700;text-transform:uppercase;
726
  color:var(--color-saffron);margin-bottom:2px">${sanitize(e.rel_label||"")}</div>
727
  <div style="font-size:12px;font-weight:600;color:var(--text-primary)">
728
+ ${sanitize(e.connected_to||e.connected_id||"--")}
729
  </div>
730
  <div style="font-size:10px;color:var(--text-secondary);margin-top:2px">
731
  ${sanitize(e.why||"")}
732
  </div>
733
  <div style="font-size:10px;color:var(--text-muted);margin-top:2px">
734
+ ? ${sanitize(e.source||"")}
735
  </div>
736
  </div>`).join("") : `<div style="text-align:center;padding:20px;color:var(--text-muted);font-size:12px">
737
  No connections in current dataset</div>`;
 
761
  </div>
762
  ${(Array.isArray(path[0]) ? path[0] : path).map(n =>
763
  `<span style="color:var(--text-primary)">${sanitize(n)}</span>
764
+ <span style="color:var(--text-muted);margin:0 4px">-></span>`
765
+ ).join("").replace(/<span[^>]*>-><\/span>$/, "")}
766
  </div>` : `
767
  <div style="color:var(--text-muted);font-size:12px">No path found</div>`;
768
  }).catch(() => {
 
866
  }
867
 
868
 
869
+ // ?? Language Application ??????????????????????????????????????????????????????
870
  // BUG-10 FIX: full DOM translation via data-i18n attributes
871
  async function applyLanguage(lang) {
872
  const badge = document.getElementById("lang-badge");
 
915
  Router.register("/live-feed", Views.liveFeed);
916
  Router.register("/about", Views.about);
917
 
918
+ // BUG-02 FIX: was single immediate check -- failed permanently on HF cold start.
919
+ // BUG-25 FIX: message said "Start: uvicorn" -- wrong for HuggingFace users.
920
  // Fix: retry up to 5 times with exponential backoff (2s, 4s, 8s, 16s, 30s).
921
  (function checkHealth(attempt) {
922
  Api.health()
frontend/js/evidence_panel.js CHANGED
@@ -1,4 +1,4 @@
1
- // Evidence Panel tabbed deep investigation panel
2
  // Tabs: Overview | Connections | Timeline | Investigate
3
  // NOTE: Provenance tab was planned but not yet implemented (see Phase 45)
4
  const EvidencePanel = {
@@ -47,7 +47,7 @@ const EvidencePanel = {
47
  style="background:var(--bg-secondary);border:1px solid var(--border-color);
48
  color:var(--text-muted);width:32px;height:32px;border-radius:50%;
49
  cursor:pointer;font-size:18px;display:flex;align-items:center;
50
- justify-content:center">×</button>
51
  </div>
52
 
53
  <!-- Tab bar -->
@@ -98,7 +98,7 @@ const EvidencePanel = {
98
  } catch (err) {
99
  document.getElementById("ep-body").innerHTML = `
100
  <div style="text-align:center;padding:40px 20px;color:var(--text-muted)">
101
- <div style="font-size:32px;margin-bottom:12px">⚠️</div>
102
  <div style="font-weight:600;margin-bottom:4px">Evidence unavailable</div>
103
  <div style="font-size:12px">API may be offline or entity not in database.</div>
104
  </div>`;
@@ -157,8 +157,8 @@ const EvidencePanel = {
157
  <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:16px">
158
  ${[
159
  ["Connections", edges.length],
160
- ["Entity Type", info.entity_type || ""],
161
- ["Source", info.source || ""]
162
  ].map(([l,v]) => `
163
  <div style="padding:10px;background:var(--bg-tertiary);border-radius:8px;
164
  border:1px solid var(--border-color);text-align:center">
@@ -177,17 +177,17 @@ const EvidencePanel = {
177
  <span style="font-size:10px;font-weight:700;text-transform:uppercase;
178
  color:${e.color||'var(--accent-primary)'};">${sanitize(e.rel_label||"LINKED")}</span>
179
  <span style="font-size:10px;color:var(--text-muted)">
180
- ${e.strength==="strong"?"●●●":e.strength==="medium"?"●●○":"●○○"} ${sanitize(e.strength||"")}
181
  </span>
182
  </div>
183
  <div style="font-size:13px;font-weight:600;color:var(--text-primary)">
184
- ${sanitize(e.connected_to||e.connected_id||"")}
185
  </div>
186
  <div style="font-size:11px;color:var(--text-secondary);margin-top:3px">
187
  ${sanitize(e.why||"")}
188
  </div>
189
  <div style="font-size:10px;color:var(--text-muted);margin-top:2px">
190
- 📄 ${sanitize(e.source||"")}
191
  </div>
192
  </div>`).join("") : `
193
  <div style="text-align:center;padding:30px;color:var(--text-muted);font-size:12px">
@@ -197,7 +197,7 @@ const EvidencePanel = {
197
  ${edges.length > 5 ? `
198
  <button onclick="EvidencePanel._switchTab('connections')"
199
  class="btn btn--secondary" style="width:100%;font-size:12px;margin-top:4px">
200
- View all ${edges.length} connections
201
  </button>` : ""}
202
  `;
203
  },
@@ -233,21 +233,21 @@ const EvidencePanel = {
233
  onclick="EvidencePanel.open(this.getAttribute('data-eid'),this.getAttribute('data-ename'))">
234
  <div style="display:flex;justify-content:space-between">
235
  <span style="font-size:13px;font-weight:600;color:var(--text-primary)">
236
- ${sanitize(e.connected_to||e.connected_id||"")}
237
  </span>
238
- <span style="font-size:10px;color:var(--text-muted)"></span>
239
  </div>
240
  <div style="font-size:11px;color:var(--text-secondary);margin-top:2px">
241
  ${sanitize(e.why||"")}
242
  </div>
243
  <div style="display:flex;gap:8px;margin-top:4px">
244
- <span style="font-size:10px;color:var(--text-muted)">📄 ${sanitize(e.source||"")}</span>
245
- ${e.date ? `<span style="font-size:10px;color:var(--text-muted)">📅 ${sanitize(e.date)}</span>` : ""}
246
  </div>
247
  ${e.next_leads && e.next_leads.length ? `
248
  <div style="margin-top:6px">
249
  ${e.next_leads.map(l => `
250
- <span style="font-size:10px;color:var(--color-saffron);margin-right:8px"> ${sanitize(l)}</span>
251
  `).join("")}
252
  </div>` : ""}
253
  </div>`).join("")}
@@ -287,7 +287,7 @@ const EvidencePanel = {
287
  ${sanitize(ev.description||ev.summary||"")}
288
  </div>
289
  <div style="font-size:10px;color:var(--text-muted);margin-top:2px">
290
- 📄 ${sanitize(ev.source||"")}
291
  </div>
292
  </div>`).join("")}
293
  </div>`;
@@ -306,8 +306,8 @@ const EvidencePanel = {
306
  6-Layer Investigation Engine
307
  </div>
308
  <div style="font-size:12px;color:var(--text-secondary);margin-bottom:12px">
309
- Runs 6 specialist investigators in parallel: Direct Evidence Relationship Mapping
310
- Pattern Detection Timeline Analysis Network Influence Evidence Validation
311
  </div>
312
  <button onclick="EvidencePanel._runInvestigation('${entityId}')"
313
  id="ep-run-btn" class="btn btn--primary" style="width:100%;margin-bottom:8px">
@@ -339,7 +339,7 @@ const EvidencePanel = {
339
  <div style="padding:14px;background:var(--bg-tertiary);border-radius:10px;
340
  border:1px solid var(--color-saffron)">
341
  <div style="font-size:11px;font-weight:700;color:var(--color-saffron);margin-bottom:10px">
342
- INVESTIGATION COMPLETE ${total} ITEMS
343
  </div>
344
  ${layers.map(l => `
345
  <div style="display:flex;justify-content:space-between;
@@ -374,7 +374,7 @@ const EvidencePanel = {
374
  <div style="padding:14px;background:var(--bg-tertiary);border-radius:10px;
375
  border:1px solid var(--color-risk-high);margin-top:8px">
376
  <div style="font-size:11px;font-weight:700;color:var(--color-risk-high);margin-bottom:10px">
377
- CONTRADICTION SEARCH ${items.length} items found
378
  </div>
379
  ${items.slice(0,5).map(item => `
380
  <div style="margin-bottom:8px;padding:8px;background:var(--bg-primary);border-radius:6px">
 
1
+ // Evidence Panel -- tabbed deep investigation panel
2
  // Tabs: Overview | Connections | Timeline | Investigate
3
  // NOTE: Provenance tab was planned but not yet implemented (see Phase 45)
4
  const EvidencePanel = {
 
47
  style="background:var(--bg-secondary);border:1px solid var(--border-color);
48
  color:var(--text-muted);width:32px;height:32px;border-radius:50%;
49
  cursor:pointer;font-size:18px;display:flex;align-items:center;
50
+ justify-content:center">?</button>
51
  </div>
52
 
53
  <!-- Tab bar -->
 
98
  } catch (err) {
99
  document.getElementById("ep-body").innerHTML = `
100
  <div style="text-align:center;padding:40px 20px;color:var(--text-muted)">
101
+ <div style="font-size:32px;margin-bottom:12px">??</div>
102
  <div style="font-weight:600;margin-bottom:4px">Evidence unavailable</div>
103
  <div style="font-size:12px">API may be offline or entity not in database.</div>
104
  </div>`;
 
157
  <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:16px">
158
  ${[
159
  ["Connections", edges.length],
160
+ ["Entity Type", info.entity_type || "--"],
161
+ ["Source", info.source || "--"]
162
  ].map(([l,v]) => `
163
  <div style="padding:10px;background:var(--bg-tertiary);border-radius:8px;
164
  border:1px solid var(--border-color);text-align:center">
 
177
  <span style="font-size:10px;font-weight:700;text-transform:uppercase;
178
  color:${e.color||'var(--accent-primary)'};">${sanitize(e.rel_label||"LINKED")}</span>
179
  <span style="font-size:10px;color:var(--text-muted)">
180
+ ${e.strength==="strong"?"???":e.strength==="medium"?"???":"???"} ${sanitize(e.strength||"")}
181
  </span>
182
  </div>
183
  <div style="font-size:13px;font-weight:600;color:var(--text-primary)">
184
+ ${sanitize(e.connected_to||e.connected_id||"--")}
185
  </div>
186
  <div style="font-size:11px;color:var(--text-secondary);margin-top:3px">
187
  ${sanitize(e.why||"")}
188
  </div>
189
  <div style="font-size:10px;color:var(--text-muted);margin-top:2px">
190
+ ? ${sanitize(e.source||"")}
191
  </div>
192
  </div>`).join("") : `
193
  <div style="text-align:center;padding:30px;color:var(--text-muted);font-size:12px">
 
197
  ${edges.length > 5 ? `
198
  <button onclick="EvidencePanel._switchTab('connections')"
199
  class="btn btn--secondary" style="width:100%;font-size:12px;margin-top:4px">
200
+ View all ${edges.length} connections ->
201
  </button>` : ""}
202
  `;
203
  },
 
233
  onclick="EvidencePanel.open(this.getAttribute('data-eid'),this.getAttribute('data-ename'))">
234
  <div style="display:flex;justify-content:space-between">
235
  <span style="font-size:13px;font-weight:600;color:var(--text-primary)">
236
+ ${sanitize(e.connected_to||e.connected_id||"--")}
237
  </span>
238
+ <span style="font-size:10px;color:var(--text-muted)">-></span>
239
  </div>
240
  <div style="font-size:11px;color:var(--text-secondary);margin-top:2px">
241
  ${sanitize(e.why||"")}
242
  </div>
243
  <div style="display:flex;gap:8px;margin-top:4px">
244
+ <span style="font-size:10px;color:var(--text-muted)">? ${sanitize(e.source||"")}</span>
245
+ ${e.date ? `<span style="font-size:10px;color:var(--text-muted)">? ${sanitize(e.date)}</span>` : ""}
246
  </div>
247
  ${e.next_leads && e.next_leads.length ? `
248
  <div style="margin-top:6px">
249
  ${e.next_leads.map(l => `
250
+ <span style="font-size:10px;color:var(--color-saffron);margin-right:8px">-> ${sanitize(l)}</span>
251
  `).join("")}
252
  </div>` : ""}
253
  </div>`).join("")}
 
287
  ${sanitize(ev.description||ev.summary||"")}
288
  </div>
289
  <div style="font-size:10px;color:var(--text-muted);margin-top:2px">
290
+ ? ${sanitize(ev.source||"")}
291
  </div>
292
  </div>`).join("")}
293
  </div>`;
 
306
  6-Layer Investigation Engine
307
  </div>
308
  <div style="font-size:12px;color:var(--text-secondary);margin-bottom:12px">
309
+ Runs 6 specialist investigators in parallel: Direct Evidence -> Relationship Mapping ->
310
+ Pattern Detection -> Timeline Analysis -> Network Influence -> Evidence Validation
311
  </div>
312
  <button onclick="EvidencePanel._runInvestigation('${entityId}')"
313
  id="ep-run-btn" class="btn btn--primary" style="width:100%;margin-bottom:8px">
 
339
  <div style="padding:14px;background:var(--bg-tertiary);border-radius:10px;
340
  border:1px solid var(--color-saffron)">
341
  <div style="font-size:11px;font-weight:700;color:var(--color-saffron);margin-bottom:10px">
342
+ INVESTIGATION COMPLETE -- ${total} ITEMS
343
  </div>
344
  ${layers.map(l => `
345
  <div style="display:flex;justify-content:space-between;
 
374
  <div style="padding:14px;background:var(--bg-tertiary);border-radius:10px;
375
  border:1px solid var(--color-risk-high);margin-top:8px">
376
  <div style="font-size:11px;font-weight:700;color:var(--color-risk-high);margin-bottom:10px">
377
+ CONTRADICTION SEARCH -- ${items.length} items found
378
  </div>
379
  ${items.slice(0,5).map(item => `
380
  <div style="margin-bottom:8px;padding:8px;background:var(--bg-primary);border-radius:6px">
frontend/js/graph.js CHANGED
@@ -25,7 +25,7 @@ const GraphRenderer = {
25
  container.innerHTML = "";
26
 
27
  if (!window.d3) {
28
- container.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:14px;">D3.js not loaded graph unavailable</div>`;
29
  return;
30
  }
31
 
 
25
  container.innerHTML = "";
26
 
27
  if (!window.d3) {
28
+ container.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:14px;">D3.js not loaded -- graph unavailable</div>`;
29
  return;
30
  }
31