Spaces:
Running
Running
Add complete multi-page Gradio UI with all visualizations"
Browse files
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)
|