""" đŦ 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("""
Digital Stratigraphy & Multi-Agent Forensic Intelligence
Property Graph âĸ Blockchain CoC âĸ SHAP Explainability âĸ Cross-Case Intel âĸ Dual-Mode TOD âĸ NL Queries
â ī¸ ADVISORY ONLY â Not a replacement for forensic experts. Human-in-the-loop validation required.
') 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('đŦ ForensiX AI v2.0 â Digital Stratigraphy Platform âĸ Advisory Only âĸ All features fully implemented
') demo.queue().launch(server_name="0.0.0.0", server_port=7860)