Muthukumarank commited on
Commit
078a663
·
verified ·
1 Parent(s): 9ea9272

Add complete multi-page Gradio UI with all visualizations"

Browse files
Files changed (1) hide show
  1. app.py +271 -0
app.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 🔬 ForensiX AI — Digital Stratigraphy & Forensic Intelligence Platform
3
+ Multi-Page Dashboard with all winning features.
4
+ """
5
+ import gradio as gr
6
+ import plotly.graph_objects as go
7
+ import plotly.express as px
8
+ from plotly.subplots import make_subplots
9
+ import pandas as pd
10
+ import numpy as np
11
+ import json, io
12
+ from datetime import datetime
13
+ from engine import ForensicOrchestrator, DualModeTODEstimator
14
+ import networkx as nx
15
+
16
+ orchestrator = ForensicOrchestrator()
17
+
18
+ DEMO_REPORT = """AUTOPSY REPORT - CASE #2024-MH-0847
19
+ DECEDENT: John Doe (unidentified male), Approximately 35-45 years
20
+ DATE OF EXAMINATION: March 15, 2024, 09:00 AM
21
+ DATE BODY FOUND: March 14, 2024, 06:30 PM
22
+ LOCATION: Abandoned warehouse, Industrial District, Block 7
23
+
24
+ EXTERNAL: Well-nourished adult male, ~78 kg. Rigor mortis fully developed. Lividity fixed posterior. Body temp (rectal): 28.5°C. Ambient: 18°C.
25
+
26
+ INJURIES:
27
+ 1. Blunt force trauma to right temporal region, 4.5x3.2cm, with subdural hematoma
28
+ 2. Defensive wounds on both forearms - three linear abrasions
29
+ 3. Contusion on left shoulder, 6x4cm
30
+ 4. Petechial hemorrhages in conjunctivae bilaterally
31
+ 5. Ligature mark around neck, 0.5cm width
32
+
33
+ INTERNAL: Subdural hematoma (right hemisphere, ~80ml). Cerebral edema. Hyoid bone intact. Lungs mild congestion. Heart 340g. Stomach: partially digested meal.
34
+
35
+ TOXICOLOGY: Blood alcohol 0.04 g/dL. Benzodiazepines detected (trace - diazepam). No illicit substances detected.
36
+
37
+ CAUSE OF DEATH: Blunt force head trauma with subdural hematoma and asphyxia due to ligature compression of neck.
38
+ MANNER OF DEATH: Homicide
39
+
40
+ NOTES: Skin under fingernails collected for DNA analysis. Foreign fibers recovered (synthetic, blue). TOD estimated 12-18 hours prior to discovery."""
41
+
42
+ DEMO_EVIDENCE = """timestamp,source,event_type,location_lat,location_lon,details
43
+ 2024-03-14T00:30:00,CCTV-Cam7,vehicle_detected,19.076,72.878,White sedan entering industrial area
44
+ 2024-03-14T01:15:00,Mobile-Tower,cell_ping,19.0755,72.878,Victim phone connected tower ID-4421
45
+ 2024-03-14T01:45:00,CCTV-Cam12,person_detected,19.0762,72.8775,Two individuals walking toward warehouse
46
+ 2024-03-14T02:00:00,Mobile-Tower,cell_ping,19.0762,72.8774,Victim phone last active ping
47
+ 2024-03-14T02:15:00,CCTV-Cam12,person_detected,19.0762,72.8775,Single individual leaving warehouse rapidly
48
+ 2024-03-14T02:20:00,CCTV-Cam7,vehicle_detected,19.076,72.878,White sedan exiting at high speed
49
+ 2024-03-14T02:30:00,Mobile-Tower,cell_disconnect,19.0762,72.8774,Victim phone disconnected
50
+ 2024-03-14T06:00:00,CCTV-Cam7,person_detected,19.0758,72.878,Security guard patrol
51
+ 2024-03-14T18:30:00,Emergency,call_received,19.0762,72.8775,Body discovered by security guard"""
52
+
53
+ def run_analysis(report, evidence_csv, state):
54
+ global orchestrator
55
+ orchestrator = ForensicOrchestrator()
56
+ if not report.strip(): return {}, None, "⚠️ Provide report text.", state, None, None, ""
57
+ evidence_list = []
58
+ if evidence_csv.strip():
59
+ df = pd.read_csv(io.StringIO(evidence_csv))
60
+ evidence_list = df.to_dict('records')
61
+ orchestrator.ingest_report(report)
62
+ if evidence_list: orchestrator.ingest_evidence(evidence_list)
63
+ results = orchestrator.run_analysis(report, evidence_list)
64
+ risk_fig = build_risk_fig(results)
65
+ graph_fig = build_graph_fig(results)
66
+ timeline_fig = build_timeline_fig(results)
67
+ summary = build_summary(results)
68
+ state = {"results": results}
69
+ chain_info = f"🔐 Chain: {'✅ VALID' if results['chain']['valid'] else '❌ BROKEN'} | {results['chain']['blocks']} blocks"
70
+ return results, risk_fig, summary, state, graph_fig, timeline_fig, chain_info
71
+
72
+ def calc_tod(t_rec, t_amb, wt, corr, rigor, lividity, decomp, vk):
73
+ est = DualModeTODEstimator()
74
+ r = est.estimate(t_rec, t_amb, wt, corr, rigor, lividity, decomp, vk if vk > 0 else None)
75
+ fig = go.Figure()
76
+ if r.get("cooling_curve"):
77
+ fig.add_trace(go.Scatter(x=[p["time"] for p in r["cooling_curve"]], y=[p["temp"] for p in r["cooling_curve"]],
78
+ mode='lines', name='Cooling', line=dict(color='#60a5fa', width=3)))
79
+ if r.get("pmi_hours"):
80
+ fig.add_trace(go.Scatter(x=[r["pmi_hours"]], y=[t_rec], mode='markers', name='Measured',
81
+ marker=dict(size=14, color='#ef4444', symbol='x')))
82
+ fig.add_vrect(x0=max(0,r["pmi_hours"]-2.8), x1=r["pmi_hours"]+2.8, fillcolor="rgba(239,68,68,0.1)", line_width=0)
83
+ fig.add_hline(y=t_amb, line_dash="dash", line_color="#22c55e")
84
+ fig.update_layout(template="plotly_dark", paper_bgcolor="#0a0a0f", plot_bgcolor="#12121a",
85
+ title=f"{'Henssge Model' if r.get('phase')=='early' else 'Late-Phase'}", height=380, font=dict(color="#e2e8f0"))
86
+ md = f"## ⏱️ PMI: **{r.get('pmi_hours','N/A')} hours** ({r.get('phase','').title()} Phase)\n"
87
+ md += f"**Method:** {r.get('method','')}\n**Confidence:** {r.get('confidence','')}\n"
88
+ if r.get("lower_bound"): md += f"**95% CI:** {r['lower_bound']}–{r['upper_bound']}h"
89
+ return r, fig, md
90
+
91
+ def handle_query(query, state):
92
+ if not query.strip(): return "Enter a question."
93
+ return orchestrator.query(query)
94
+
95
+ def load_demo(): return DEMO_REPORT, DEMO_EVIDENCE
96
+
97
+ def build_risk_fig(results):
98
+ rf = [f for a in results.get("agents",[]) for f in a.get("findings",[]) if f.get("category")=="RISK_SCORE"]
99
+ if not rf: return go.Figure()
100
+ factors = rf[0].get("factors",{})
101
+ cats = [k.replace("_"," ").title() for k in factors]; vals = list(factors.values())
102
+ fig = make_subplots(rows=1, cols=2, specs=[[{"type":"polar"},{"type":"xy"}]], subplot_titles=("Risk Radar","Scores"))
103
+ fig.add_trace(go.Scatterpolar(r=vals+[vals[0]], theta=cats+[cats[0]], fill='toself',
104
+ fillcolor='rgba(239,68,68,0.2)', line=dict(color='#ef4444',width=2)), row=1, col=1)
105
+ colors = ['#ef4444' if v>=70 else '#f59e0b' if v>=40 else '#22c55e' for v in vals]
106
+ fig.add_trace(go.Bar(x=vals, y=cats, orientation='h', marker_color=colors, text=[f'{v:.0f}' for v in vals], textposition='inside'), row=1, col=2)
107
+ fig.update_layout(template="plotly_dark", paper_bgcolor="#0a0a0f", plot_bgcolor="#12121a", height=380,
108
+ showlegend=False, font=dict(color="#e2e8f0"), polar=dict(radialaxis=dict(range=[0,100], gridcolor="#1e293b"), bgcolor="#12121a"))
109
+ fig.update_xaxes(range=[0,100], gridcolor="#1e293b", row=1, col=2)
110
+ return fig
111
+
112
+ def build_graph_fig(results):
113
+ gd = results.get("graph",{}); nodes = gd.get("nodes",[]); edges = gd.get("edges",[])
114
+ if not nodes: return go.Figure()
115
+ G = nx.DiGraph()
116
+ for n in nodes: G.add_node(n["id"])
117
+ for e in edges: G.add_edge(e["source"], e["target"])
118
+ pos = nx.spring_layout(G, seed=42)
119
+ ex, ey = [], []
120
+ for e in edges:
121
+ if e["source"] in pos and e["target"] in pos:
122
+ ex.extend([pos[e["source"]][0], pos[e["target"]][0], None])
123
+ ey.extend([pos[e["source"]][1], pos[e["target"]][1], None])
124
+ colors_map = {"autopsy":"#ef4444","cctv":"#3b82f6","mobile":"#a855f7","iot":"#22c55e"}
125
+ nx_list = [n for n in nodes if n["id"] in pos]
126
+ fig = go.Figure()
127
+ fig.add_trace(go.Scatter(x=ex, y=ey, mode='lines', line=dict(width=1,color='#475569'), hoverinfo='none'))
128
+ fig.add_trace(go.Scatter(x=[pos[n["id"]][0] for n in nx_list], y=[pos[n["id"]][1] for n in nx_list],
129
+ mode='markers+text', text=[f'{n["id"][:6]}' for n in nx_list], textposition='top center',
130
+ textfont=dict(size=8,color='#94a3b8'), marker=dict(size=14, color=[colors_map.get(n["type"],"#64748b") for n in nx_list])))
131
+ fig.update_layout(template="plotly_dark", paper_bgcolor="#0a0a0f", plot_bgcolor="#12121a", title="🔗 Evidence Graph",
132
+ height=350, showlegend=False, xaxis=dict(showgrid=False,showticklabels=False), yaxis=dict(showgrid=False,showticklabels=False), font=dict(color="#e2e8f0"))
133
+ return fig
134
+
135
+ def build_timeline_fig(results):
136
+ tl = results.get("timeline",[])
137
+ if not tl: return go.Figure()
138
+ colors = {"autopsy":"#ef4444","cctv":"#3b82f6","mobile":"#a855f7","iot":"#22c55e"}
139
+ fig = go.Figure()
140
+ for stratum in set(e.get("stratum","") for e in tl):
141
+ evs = [e for e in tl if e.get("stratum") == stratum and e.get("timestamp")]
142
+ if evs:
143
+ fig.add_trace(go.Scatter(x=[e["timestamp"] for e in evs], y=[stratum]*len(evs),
144
+ mode='markers+text', name=stratum.upper(), text=[e.get("details","")[:30] for e in evs],
145
+ textposition='top center', textfont=dict(size=7), marker=dict(size=11, color=colors.get(stratum,"#64748b"))))
146
+ fig.update_layout(template="plotly_dark", paper_bgcolor="#0a0a0f", plot_bgcolor="#12121a",
147
+ title="📅 Digital Stratigraphy", height=320, font=dict(color="#e2e8f0"))
148
+ return fig
149
+
150
+ def build_summary(results):
151
+ md = f"## 🔬 Analysis Complete — Risk: **{results['risk_score']:.1f}/100** ({results['risk_level']})\n\n"
152
+ md += "### 🤖 Multi-Agent Results\n| Agent | Findings | Confidence |\n|-------|----------|------------|\n"
153
+ for a in results.get("agents",[]): md += f"| {a['agent'][:28]} | {len(a['findings'])} | {a['confidence']:.0%} |\n"
154
+ md += "\n"
155
+ anom = results.get("anomalies",[])
156
+ if anom:
157
+ md += f"### 🚨 Anomalies ({len(anom)})\n"
158
+ for a in anom: md += f"- **[{a['severity']}]** {a['description']}\n 💡 {a.get('recommendation','')}\n"
159
+ md += "\n"
160
+ cross = results.get("cross_case",[])
161
+ if cross:
162
+ md += "### 🔗 Cross-Case Intelligence\n"
163
+ for m in cross: md += f"- **{m['pattern']}** ({m['match_score']}%): {m['description']}\n"
164
+ md += "\n"
165
+ md += f"### 🔐 Chain of Custody: {'✅ VALID' if results['chain']['valid'] else '❌ BROKEN'}\n"
166
+ md += f"### 📊 Stratigraphy: {results['stratigraphy']['total']} items across {len(results['stratigraphy']['strata'])} layers\n"
167
+ md += "\n---\n*⚠️ Advisory only — human expert validation required.*"
168
+ return md
169
+
170
+ CSS = """.gradio-container{max-width:1400px!important}"""
171
+
172
+ with gr.Blocks(title="ForensiX AI — Digital Stratigraphy Platform", theme=gr.themes.Soft(primary_hue="red", secondary_hue="blue", neutral_hue="slate"), css=CSS) as demo:
173
+ state = gr.State({})
174
+ gr.HTML("""<div style="text-align:center;padding:24px;background:linear-gradient(135deg,#0f0f1a,#1a0a0a);border-radius:16px;border:1px solid rgba(239,68,68,0.2);margin-bottom:12px">
175
+ <h1 style="font-size:2.4em;margin:0;color:white">🔬 ForensiX AI</h1>
176
+ <p style="color:#94a3b8;margin:4px 0;font-size:1.1em">Digital Stratigraphy & Multi-Agent Forensic Intelligence</p>
177
+ <p style="color:#64748b;font-size:0.8em">Property Graph • Blockchain CoC • SHAP Explainability • Cross-Case Intel • Dual-Mode TOD • NL Queries</p></div>""")
178
+ gr.HTML('<p style="background:rgba(239,68,68,0.05);border:1px solid rgba(239,68,68,0.2);border-radius:8px;padding:8px 14px;font-size:0.82em;color:#f59e0b">⚠️ <b>ADVISORY ONLY</b> — Not a replacement for forensic experts. Human-in-the-loop validation required.</p>')
179
+
180
+ with gr.Tabs():
181
+ with gr.Tab("🔬 Multi-Agent Analysis"):
182
+ gr.Markdown("### FEAT System: 7 AI Agents (Autopsy • Toxicology • Timeline • CCTV • Correlation • Risk • Explainability)")
183
+ with gr.Row():
184
+ with gr.Column(scale=1):
185
+ report_in = gr.Textbox(label="📄 Autopsy Report", lines=10, placeholder="Paste report...")
186
+ ev_in = gr.Textbox(label="📱 Digital Evidence CSV", lines=5, placeholder="timestamp,source,event_type,...")
187
+ with gr.Row():
188
+ run_btn = gr.Button("🚀 Run Full Analysis", variant="primary", scale=2)
189
+ demo_btn = gr.Button("📋 Demo", variant="secondary", scale=1)
190
+ with gr.Column(scale=2):
191
+ summary_out = gr.Markdown()
192
+ risk_fig_out = gr.Plot(label="Risk Radar")
193
+ with gr.Row():
194
+ graph_out = gr.Plot(label="🔗 Evidence Graph")
195
+ timeline_out = gr.Plot(label="📅 Stratigraphy Timeline")
196
+ chain_out = gr.Textbox(label="🔐 Blockchain Chain of Custody", interactive=False)
197
+ run_btn.click(run_analysis, [report_in, ev_in, state], [gr.JSON(visible=False), risk_fig_out, summary_out, state, graph_out, timeline_out, chain_out])
198
+ demo_btn.click(load_demo, outputs=[report_in, ev_in])
199
+
200
+ with gr.Tab("⏱️ Dual-Mode TOD"):
201
+ gr.Markdown("### Early Phase: Henssge Nomogram | Late Phase: Metabolomic AI")
202
+ with gr.Row():
203
+ with gr.Column():
204
+ tr = gr.Slider(15,37.2,28.5,0.1,label="Rectal Temp °C")
205
+ ta = gr.Slider(-10,45,18,0.5,label="Ambient Temp °C")
206
+ wt = gr.Slider(20,180,78,1,label="Weight kg")
207
+ co = gr.Slider(0.3,1.5,0.9,0.05,label="Corrective Factor")
208
+ ri = gr.Radio(["absent","developing","full","resolving"],value="full",label="Rigor")
209
+ li = gr.Radio(["absent","developing","present_movable","fixed"],value="fixed",label="Lividity")
210
+ de = gr.Radio(["absent","early","bloating","advanced"],value="absent",label="Decomposition")
211
+ vk = gr.Slider(0,30,0,0.5,label="Vitreous K+ (0=N/A)")
212
+ tod_btn = gr.Button("⏱️ Estimate PMI", variant="primary")
213
+ with gr.Column():
214
+ tod_json = gr.JSON(label="Results")
215
+ tod_fig = gr.Plot(label="Cooling Curve")
216
+ tod_md = gr.Markdown()
217
+ tod_btn.click(calc_tod, [tr,ta,wt,co,ri,li,de,vk], [tod_json,tod_fig,tod_md])
218
+
219
+ with gr.Tab("🗣️ NL Investigation Query"):
220
+ gr.Markdown("### Ask in Plain English\n*Examples: 'What anomalies?' • 'Show risk score' • 'Timeline' • 'Explain the decision' • 'Chain of custody status'*")
221
+ with gr.Row():
222
+ q_in = gr.Textbox(label="Question", placeholder="What does the evidence show?", scale=4)
223
+ q_btn = gr.Button("🔍", variant="primary", scale=1)
224
+ q_out = gr.Markdown()
225
+ q_btn.click(handle_query, [q_in, state], q_out)
226
+
227
+ with gr.Tab("ℹ️ Architecture"):
228
+ gr.Markdown("""
229
+ ## 🏗️ System Architecture — Digital Stratigraphy
230
+
231
+ **Core Innovation:** *"Unified multimodal forensic intelligence that semantically correlates autopsy findings, digital evidence, CCTV metadata, IoT telemetry, and behavioral anomalies into a legally explainable investigative graph using AI-powered reasoning."*
232
+
233
+ ### ✅ All Implemented Features
234
+ | # | Feature | Technology |
235
+ |---|---------|-----------|
236
+ | 1 | Digital Stratigraphy Engine | Unified event graph across all evidence layers |
237
+ | 2 | FEAT Multi-Agent System | 7 specialized AI agents with orchestrator |
238
+ | 3 | Property Graph Intelligence | NetworkX with multi-hop reasoning |
239
+ | 4 | Dual-Mode TOD | Henssge (early) + Metabolomic AI (late) |
240
+ | 5 | Explainable AI (XAI) | SHAP-style factor attribution |
241
+ | 6 | Blockchain Chain-of-Custody | SHA-256 immutable evidence ledger |
242
+ | 7 | Natural Language Queries | Pattern-matched investigation interface |
243
+ | 8 | Smart Evidence Prioritization | AI relevance ranking |
244
+ | 9 | Cross-Case Intelligence | Historical pattern matching |
245
+ | 10 | Anomaly Detection | Cross-factor inconsistency analysis |
246
+ | 11 | Human-in-the-Loop | Advisory-only with legal disclaimers |
247
+ | 12 | IoT/Wearable Ready | Extensible strata architecture |
248
+
249
+ ### Multi-Agent Architecture (FEAT)
250
+ ```
251
+ ┌────────────────────────────────────────────────┐
252
+ │ FORENSIC ORCHESTRATOR │
253
+ ├─────────┬──────────┬─────────┬────────────────-┤
254
+ │ Autopsy │ Timeline │ CCTV │ Toxicology │
255
+ │ Agent │ Agent │ Agent │ Agent │
256
+ ├─────────┴──────────┴─────────┴────────────────-┤
257
+ │ Cross-Evidence Correlation Agent │
258
+ ├────────────────────────────────────────────────-┤
259
+ │ Risk Scoring + Anomaly Detection Agent │
260
+ ├────────────────────────────────────────────────-┤
261
+ │ Explainability (SHAP-style) Agent │
262
+ └────────────────────────────────────────────────-┘
263
+ ↓ ↓
264
+ Property Graph Blockchain Chain
265
+ (NetworkX) (SHA-256)
266
+ ```
267
+ """)
268
+
269
+ gr.HTML('<p style="text-align:center;color:#64748b;font-size:0.75em;border-top:1px solid #1e293b;padding:12px;margin-top:12px">🔬 ForensiX AI v2.0 — Digital Stratigraphy Platform • Advisory Only • All features fully implemented</p>')
270
+
271
+ demo.queue().launch(server_name="0.0.0.0", server_port=7860)