Spaces:
Running
Running
Commit ·
747e613
1
Parent(s): 06c3703
fix(encoding): strip non-ASCII chars from all modified files
Browse filesAll 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 +6 -6
- api/models.py +1 -1
- api/routes/graph.py +1 -1
- api/routes/search.py +3 -3
- frontend/index.html +22 -22
- frontend/js/app.js +35 -35
- frontend/js/evidence_panel.js +19 -19
- frontend/js/graph.js +1 -1
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
|
| 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
|
| 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
|
| 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
|
| 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','')}
|
| 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
|
| 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
|
| 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"
|
| 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
|
| 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__
|
| 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
|
| 7 |
<meta name="theme-color" content="#0A0F2E">
|
| 8 |
-
<title>BharatGraph
|
| 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
|
| 54 |
-
<option value="hi">HI
|
| 55 |
-
<option value="ta">TA
|
| 56 |
-
<option value="te">TE
|
| 57 |
-
<option value="kn">KN
|
| 58 |
-
<option value="ml">ML
|
| 59 |
-
<option value="mr">MR
|
| 60 |
-
<option value="bn">BN
|
| 61 |
-
<option value="gu">GU
|
| 62 |
-
<option value="pa">PA
|
| 63 |
-
<option value="or">OR
|
| 64 |
-
<option value="as">AS
|
| 65 |
-
<option value="ur">UR
|
| 66 |
-
<option value="sd">SD
|
| 67 |
-
<option value="kok">KOK
|
| 68 |
-
<option value="mai">MAI
|
| 69 |
-
<option value="ne">NE
|
| 70 |
-
<option value="doi">DOI
|
| 71 |
-
<option value="sa">SA
|
| 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
|
| 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
|
| 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 || "
|
| 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
|
| 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">
|
| 251 |
-
<option value="hi">
|
| 252 |
-
<option value="ta">
|
| 253 |
-
<option value="te">
|
| 254 |
-
<option value="kn">
|
| 255 |
-
<option value="ml">
|
| 256 |
-
<option value="mr">
|
| 257 |
-
<option value="bn">
|
| 258 |
-
<option value="gu">
|
| 259 |
-
<option value="pa">
|
| 260 |
-
<option value="or">
|
| 261 |
-
<option value="as">
|
| 262 |
-
<option value="ur">
|
| 263 |
-
<option value="kok">
|
| 264 |
-
<option value="mai">
|
| 265 |
-
<option value="mni">
|
| 266 |
-
<option value="sat">
|
| 267 |
-
<option value="ks">
|
| 268 |
-
<option value="ne">
|
| 269 |
-
<option value="doi">
|
| 270 |
-
<option value="sa">
|
| 271 |
-
<option value="sd">
|
| 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
|
| 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 |
-
|
| 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 |
-
|
| 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">
|
| 765 |
-
).join("").replace(/<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 |
-
//
|
| 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
|
| 919 |
-
// BUG-25 FIX: message said "Start: uvicorn"
|
| 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
|
| 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">
|
| 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">
|
| 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"?"
|
| 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 |
-
|
| 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)">
|
| 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)">
|
| 245 |
-
${e.date ? `<span style="font-size:10px;color:var(--text-muted)">
|
| 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">
|
| 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 |
-
|
| 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
|
| 310 |
-
Pattern Detection
|
| 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
|
| 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
|
| 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
|
| 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 |
|