forensix-ai-pro / app.py
Muthukumarank's picture
Add complete multi-page Gradio UI with all visualizations"
078a663 verified
"""
🔬 ForensiX AI — Digital Stratigraphy & Forensic Intelligence Platform
Multi-Page Dashboard with all winning features.
"""
import gradio as gr
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import pandas as pd
import numpy as np
import json, io
from datetime import datetime
from engine import ForensicOrchestrator, DualModeTODEstimator
import networkx as nx
orchestrator = ForensicOrchestrator()
DEMO_REPORT = """AUTOPSY REPORT - CASE #2024-MH-0847
DECEDENT: John Doe (unidentified male), Approximately 35-45 years
DATE OF EXAMINATION: March 15, 2024, 09:00 AM
DATE BODY FOUND: March 14, 2024, 06:30 PM
LOCATION: Abandoned warehouse, Industrial District, Block 7
EXTERNAL: Well-nourished adult male, ~78 kg. Rigor mortis fully developed. Lividity fixed posterior. Body temp (rectal): 28.5°C. Ambient: 18°C.
INJURIES:
1. Blunt force trauma to right temporal region, 4.5x3.2cm, with subdural hematoma
2. Defensive wounds on both forearms - three linear abrasions
3. Contusion on left shoulder, 6x4cm
4. Petechial hemorrhages in conjunctivae bilaterally
5. Ligature mark around neck, 0.5cm width
INTERNAL: Subdural hematoma (right hemisphere, ~80ml). Cerebral edema. Hyoid bone intact. Lungs mild congestion. Heart 340g. Stomach: partially digested meal.
TOXICOLOGY: Blood alcohol 0.04 g/dL. Benzodiazepines detected (trace - diazepam). No illicit substances detected.
CAUSE OF DEATH: Blunt force head trauma with subdural hematoma and asphyxia due to ligature compression of neck.
MANNER OF DEATH: Homicide
NOTES: Skin under fingernails collected for DNA analysis. Foreign fibers recovered (synthetic, blue). TOD estimated 12-18 hours prior to discovery."""
DEMO_EVIDENCE = """timestamp,source,event_type,location_lat,location_lon,details
2024-03-14T00:30:00,CCTV-Cam7,vehicle_detected,19.076,72.878,White sedan entering industrial area
2024-03-14T01:15:00,Mobile-Tower,cell_ping,19.0755,72.878,Victim phone connected tower ID-4421
2024-03-14T01:45:00,CCTV-Cam12,person_detected,19.0762,72.8775,Two individuals walking toward warehouse
2024-03-14T02:00:00,Mobile-Tower,cell_ping,19.0762,72.8774,Victim phone last active ping
2024-03-14T02:15:00,CCTV-Cam12,person_detected,19.0762,72.8775,Single individual leaving warehouse rapidly
2024-03-14T02:20:00,CCTV-Cam7,vehicle_detected,19.076,72.878,White sedan exiting at high speed
2024-03-14T02:30:00,Mobile-Tower,cell_disconnect,19.0762,72.8774,Victim phone disconnected
2024-03-14T06:00:00,CCTV-Cam7,person_detected,19.0758,72.878,Security guard patrol
2024-03-14T18:30:00,Emergency,call_received,19.0762,72.8775,Body discovered by security guard"""
def run_analysis(report, evidence_csv, state):
global orchestrator
orchestrator = ForensicOrchestrator()
if not report.strip(): return {}, None, "⚠️ Provide report text.", state, None, None, ""
evidence_list = []
if evidence_csv.strip():
df = pd.read_csv(io.StringIO(evidence_csv))
evidence_list = df.to_dict('records')
orchestrator.ingest_report(report)
if evidence_list: orchestrator.ingest_evidence(evidence_list)
results = orchestrator.run_analysis(report, evidence_list)
risk_fig = build_risk_fig(results)
graph_fig = build_graph_fig(results)
timeline_fig = build_timeline_fig(results)
summary = build_summary(results)
state = {"results": results}
chain_info = f"🔐 Chain: {'✅ VALID' if results['chain']['valid'] else '❌ BROKEN'} | {results['chain']['blocks']} blocks"
return results, risk_fig, summary, state, graph_fig, timeline_fig, chain_info
def calc_tod(t_rec, t_amb, wt, corr, rigor, lividity, decomp, vk):
est = DualModeTODEstimator()
r = est.estimate(t_rec, t_amb, wt, corr, rigor, lividity, decomp, vk if vk > 0 else None)
fig = go.Figure()
if r.get("cooling_curve"):
fig.add_trace(go.Scatter(x=[p["time"] for p in r["cooling_curve"]], y=[p["temp"] for p in r["cooling_curve"]],
mode='lines', name='Cooling', line=dict(color='#60a5fa', width=3)))
if r.get("pmi_hours"):
fig.add_trace(go.Scatter(x=[r["pmi_hours"]], y=[t_rec], mode='markers', name='Measured',
marker=dict(size=14, color='#ef4444', symbol='x')))
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)
fig.add_hline(y=t_amb, line_dash="dash", line_color="#22c55e")
fig.update_layout(template="plotly_dark", paper_bgcolor="#0a0a0f", plot_bgcolor="#12121a",
title=f"{'Henssge Model' if r.get('phase')=='early' else 'Late-Phase'}", height=380, font=dict(color="#e2e8f0"))
md = f"## ⏱️ PMI: **{r.get('pmi_hours','N/A')} hours** ({r.get('phase','').title()} Phase)\n"
md += f"**Method:** {r.get('method','')}\n**Confidence:** {r.get('confidence','')}\n"
if r.get("lower_bound"): md += f"**95% CI:** {r['lower_bound']}{r['upper_bound']}h"
return r, fig, md
def handle_query(query, state):
if not query.strip(): return "Enter a question."
return orchestrator.query(query)
def load_demo(): return DEMO_REPORT, DEMO_EVIDENCE
def build_risk_fig(results):
rf = [f for a in results.get("agents",[]) for f in a.get("findings",[]) if f.get("category")=="RISK_SCORE"]
if not rf: return go.Figure()
factors = rf[0].get("factors",{})
cats = [k.replace("_"," ").title() for k in factors]; vals = list(factors.values())
fig = make_subplots(rows=1, cols=2, specs=[[{"type":"polar"},{"type":"xy"}]], subplot_titles=("Risk Radar","Scores"))
fig.add_trace(go.Scatterpolar(r=vals+[vals[0]], theta=cats+[cats[0]], fill='toself',
fillcolor='rgba(239,68,68,0.2)', line=dict(color='#ef4444',width=2)), row=1, col=1)
colors = ['#ef4444' if v>=70 else '#f59e0b' if v>=40 else '#22c55e' for v in vals]
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)
fig.update_layout(template="plotly_dark", paper_bgcolor="#0a0a0f", plot_bgcolor="#12121a", height=380,
showlegend=False, font=dict(color="#e2e8f0"), polar=dict(radialaxis=dict(range=[0,100], gridcolor="#1e293b"), bgcolor="#12121a"))
fig.update_xaxes(range=[0,100], gridcolor="#1e293b", row=1, col=2)
return fig
def build_graph_fig(results):
gd = results.get("graph",{}); nodes = gd.get("nodes",[]); edges = gd.get("edges",[])
if not nodes: return go.Figure()
G = nx.DiGraph()
for n in nodes: G.add_node(n["id"])
for e in edges: G.add_edge(e["source"], e["target"])
pos = nx.spring_layout(G, seed=42)
ex, ey = [], []
for e in edges:
if e["source"] in pos and e["target"] in pos:
ex.extend([pos[e["source"]][0], pos[e["target"]][0], None])
ey.extend([pos[e["source"]][1], pos[e["target"]][1], None])
colors_map = {"autopsy":"#ef4444","cctv":"#3b82f6","mobile":"#a855f7","iot":"#22c55e"}
nx_list = [n for n in nodes if n["id"] in pos]
fig = go.Figure()
fig.add_trace(go.Scatter(x=ex, y=ey, mode='lines', line=dict(width=1,color='#475569'), hoverinfo='none'))
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],
mode='markers+text', text=[f'{n["id"][:6]}' for n in nx_list], textposition='top center',
textfont=dict(size=8,color='#94a3b8'), marker=dict(size=14, color=[colors_map.get(n["type"],"#64748b") for n in nx_list])))
fig.update_layout(template="plotly_dark", paper_bgcolor="#0a0a0f", plot_bgcolor="#12121a", title="🔗 Evidence Graph",
height=350, showlegend=False, xaxis=dict(showgrid=False,showticklabels=False), yaxis=dict(showgrid=False,showticklabels=False), font=dict(color="#e2e8f0"))
return fig
def build_timeline_fig(results):
tl = results.get("timeline",[])
if not tl: return go.Figure()
colors = {"autopsy":"#ef4444","cctv":"#3b82f6","mobile":"#a855f7","iot":"#22c55e"}
fig = go.Figure()
for stratum in set(e.get("stratum","") for e in tl):
evs = [e for e in tl if e.get("stratum") == stratum and e.get("timestamp")]
if evs:
fig.add_trace(go.Scatter(x=[e["timestamp"] for e in evs], y=[stratum]*len(evs),
mode='markers+text', name=stratum.upper(), text=[e.get("details","")[:30] for e in evs],
textposition='top center', textfont=dict(size=7), marker=dict(size=11, color=colors.get(stratum,"#64748b"))))
fig.update_layout(template="plotly_dark", paper_bgcolor="#0a0a0f", plot_bgcolor="#12121a",
title="📅 Digital Stratigraphy", height=320, font=dict(color="#e2e8f0"))
return fig
def build_summary(results):
md = f"## 🔬 Analysis Complete — Risk: **{results['risk_score']:.1f}/100** ({results['risk_level']})\n\n"
md += "### 🤖 Multi-Agent Results\n| Agent | Findings | Confidence |\n|-------|----------|------------|\n"
for a in results.get("agents",[]): md += f"| {a['agent'][:28]} | {len(a['findings'])} | {a['confidence']:.0%} |\n"
md += "\n"
anom = results.get("anomalies",[])
if anom:
md += f"### 🚨 Anomalies ({len(anom)})\n"
for a in anom: md += f"- **[{a['severity']}]** {a['description']}\n 💡 {a.get('recommendation','')}\n"
md += "\n"
cross = results.get("cross_case",[])
if cross:
md += "### 🔗 Cross-Case Intelligence\n"
for m in cross: md += f"- **{m['pattern']}** ({m['match_score']}%): {m['description']}\n"
md += "\n"
md += f"### 🔐 Chain of Custody: {'✅ VALID' if results['chain']['valid'] else '❌ BROKEN'}\n"
md += f"### 📊 Stratigraphy: {results['stratigraphy']['total']} items across {len(results['stratigraphy']['strata'])} layers\n"
md += "\n---\n*⚠️ Advisory only — human expert validation required.*"
return md
CSS = """.gradio-container{max-width:1400px!important}"""
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:
state = gr.State({})
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">
<h1 style="font-size:2.4em;margin:0;color:white">🔬 ForensiX AI</h1>
<p style="color:#94a3b8;margin:4px 0;font-size:1.1em">Digital Stratigraphy & Multi-Agent Forensic Intelligence</p>
<p style="color:#64748b;font-size:0.8em">Property Graph • Blockchain CoC • SHAP Explainability • Cross-Case Intel • Dual-Mode TOD • NL Queries</p></div>""")
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>')
with gr.Tabs():
with gr.Tab("🔬 Multi-Agent Analysis"):
gr.Markdown("### FEAT System: 7 AI Agents (Autopsy • Toxicology • Timeline • CCTV • Correlation • Risk • Explainability)")
with gr.Row():
with gr.Column(scale=1):
report_in = gr.Textbox(label="📄 Autopsy Report", lines=10, placeholder="Paste report...")
ev_in = gr.Textbox(label="📱 Digital Evidence CSV", lines=5, placeholder="timestamp,source,event_type,...")
with gr.Row():
run_btn = gr.Button("🚀 Run Full Analysis", variant="primary", scale=2)
demo_btn = gr.Button("📋 Demo", variant="secondary", scale=1)
with gr.Column(scale=2):
summary_out = gr.Markdown()
risk_fig_out = gr.Plot(label="Risk Radar")
with gr.Row():
graph_out = gr.Plot(label="🔗 Evidence Graph")
timeline_out = gr.Plot(label="📅 Stratigraphy Timeline")
chain_out = gr.Textbox(label="🔐 Blockchain Chain of Custody", interactive=False)
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])
demo_btn.click(load_demo, outputs=[report_in, ev_in])
with gr.Tab("⏱️ Dual-Mode TOD"):
gr.Markdown("### Early Phase: Henssge Nomogram | Late Phase: Metabolomic AI")
with gr.Row():
with gr.Column():
tr = gr.Slider(15,37.2,28.5,0.1,label="Rectal Temp °C")
ta = gr.Slider(-10,45,18,0.5,label="Ambient Temp °C")
wt = gr.Slider(20,180,78,1,label="Weight kg")
co = gr.Slider(0.3,1.5,0.9,0.05,label="Corrective Factor")
ri = gr.Radio(["absent","developing","full","resolving"],value="full",label="Rigor")
li = gr.Radio(["absent","developing","present_movable","fixed"],value="fixed",label="Lividity")
de = gr.Radio(["absent","early","bloating","advanced"],value="absent",label="Decomposition")
vk = gr.Slider(0,30,0,0.5,label="Vitreous K+ (0=N/A)")
tod_btn = gr.Button("⏱️ Estimate PMI", variant="primary")
with gr.Column():
tod_json = gr.JSON(label="Results")
tod_fig = gr.Plot(label="Cooling Curve")
tod_md = gr.Markdown()
tod_btn.click(calc_tod, [tr,ta,wt,co,ri,li,de,vk], [tod_json,tod_fig,tod_md])
with gr.Tab("🗣️ NL Investigation Query"):
gr.Markdown("### Ask in Plain English\n*Examples: 'What anomalies?' • 'Show risk score' • 'Timeline' • 'Explain the decision' • 'Chain of custody status'*")
with gr.Row():
q_in = gr.Textbox(label="Question", placeholder="What does the evidence show?", scale=4)
q_btn = gr.Button("🔍", variant="primary", scale=1)
q_out = gr.Markdown()
q_btn.click(handle_query, [q_in, state], q_out)
with gr.Tab("ℹ️ Architecture"):
gr.Markdown("""
## 🏗️ System Architecture — Digital Stratigraphy
**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."*
### ✅ All Implemented Features
| # | Feature | Technology |
|---|---------|-----------|
| 1 | Digital Stratigraphy Engine | Unified event graph across all evidence layers |
| 2 | FEAT Multi-Agent System | 7 specialized AI agents with orchestrator |
| 3 | Property Graph Intelligence | NetworkX with multi-hop reasoning |
| 4 | Dual-Mode TOD | Henssge (early) + Metabolomic AI (late) |
| 5 | Explainable AI (XAI) | SHAP-style factor attribution |
| 6 | Blockchain Chain-of-Custody | SHA-256 immutable evidence ledger |
| 7 | Natural Language Queries | Pattern-matched investigation interface |
| 8 | Smart Evidence Prioritization | AI relevance ranking |
| 9 | Cross-Case Intelligence | Historical pattern matching |
| 10 | Anomaly Detection | Cross-factor inconsistency analysis |
| 11 | Human-in-the-Loop | Advisory-only with legal disclaimers |
| 12 | IoT/Wearable Ready | Extensible strata architecture |
### Multi-Agent Architecture (FEAT)
```
┌────────────────────────────────────────────────┐
│ FORENSIC ORCHESTRATOR │
├─────────┬──────────┬─────────┬────────────────-┤
│ Autopsy │ Timeline │ CCTV │ Toxicology │
│ Agent │ Agent │ Agent │ Agent │
├─────────┴──────────┴─────────┴────────────────-┤
│ Cross-Evidence Correlation Agent │
├────────────────────────────────────────────────-┤
│ Risk Scoring + Anomaly Detection Agent │
├────────────────────────────────────────────────-┤
│ Explainability (SHAP-style) Agent │
└────────────────────────────────────────────────-┘
↓ ↓
Property Graph Blockchain Chain
(NetworkX) (SHA-256)
```
""")
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>')
demo.queue().launch(server_name="0.0.0.0", server_port=7860)