Spaces:
Sleeping
Sleeping
| """ | |
| 🔬 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) | |