PCAgentinAI / pc_insurance_multiagent_v4.html
ITNovaML's picture
Fix: PB pipeline visibility + Quote Comparison step 6
d9d0398
Raw
History Blame Contribute Delete
261 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multi-Agent Pipeline - Policy Issuance</title>
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Syne:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root {
--bg:#040c18; --panel:#070f1e; --panel2:#0b1628; --border:#0d2240; --border2:#122d4f;
--neon:#00d4ff; --neon2:#00ff9d; --neon3:#ff6b35; --neon4:#a855f7; --neon5:#f59e0b;
--text:#c8e0f4; --dim:#3a6080; --dim2:#4a7090;
--success:#00ff9d; --warn:#f59e0b; --danger:#ff4d4d; --info:#00d4ff;
--agent-kyc:#00d4ff; --agent-prop:#ff6b35; --agent-uw:#f59e0b; --agent-price:#00ff9d; --agent-issue:#818cf8;
}
*{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);font-family:'Syne',sans-serif;color:var(--text);min-height:100vh;overflow-x:hidden}
body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(ellipse 80% 60% at 50% -10%,rgba(0,212,255,0.07),transparent),linear-gradient(rgba(0,212,255,0.025) 1px,transparent 1px),linear-gradient(90deg,rgba(0,212,255,0.025) 1px,transparent 1px);background-size:100% 100%,48px 48px,48px 48px;pointer-events:none;z-index:0}
.container{max-width:1280px;margin:0 auto;padding:20px 24px;position:relative;z-index:1}
/* ── HEADER ── */
header{display:flex;align-items:flex-start;justify-content:space-between;padding:18px 0 22px;gap:20px;flex-wrap:wrap}
.hd-left .eyebrow{font-family:'Share Tech Mono',monospace;font-size:10px;letter-spacing:4px;color:var(--neon);text-transform:uppercase;margin-bottom:8px;opacity:0;animation:fadeUp .5s ease .1s forwards}
.hd-left h1{font-size:clamp(22px,3.5vw,40px);font-weight:800;letter-spacing:-1px;line-height:1.1;opacity:0;animation:fadeUp .6s ease .25s forwards}
.hd-left h1 em{color:var(--neon);font-style:normal}
.hd-left .sub{margin-top:7px;font-size:12px;color:var(--dim2);opacity:0;animation:fadeUp .6s ease .4s forwards;font-family:'Share Tech Mono',monospace}
.hd-right{display:flex;flex-direction:column;align-items:flex-end;gap:8px;opacity:0;animation:fadeUp .6s ease .5s forwards}
.db-status{display:flex;align-items:center;gap:8px;font-family:'Share Tech Mono',monospace;font-size:10px;padding:6px 14px;border-radius:20px;border:1px solid var(--border2);background:var(--panel)}
.db-dot{width:7px;height:7px;border-radius:50%;background:var(--dim);flex-shrink:0;transition:all .3s}
.db-dot.live{background:var(--success);box-shadow:0 0 8px var(--success);animation:pulse 2s ease infinite}
.db-dot.err{background:var(--danger);box-shadow:0 0 8px var(--danger)}
.hd-pills{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}
.hpill{font-family:'Share Tech Mono',monospace;font-size:9px;padding:3px 9px;border-radius:12px;border:1px solid var(--border2);color:var(--dim2)}
/* ── TABS ── */
.tabs{display:flex;gap:4px;margin-bottom:18px;opacity:0;animation:fadeUp .6s ease .55s forwards}
.tab{padding:7px 18px;border-radius:6px;font-family:'Share Tech Mono',monospace;font-size:11px;cursor:pointer;border:1px solid var(--border2);background:transparent;color:var(--dim2);transition:all .2s;text-transform:uppercase;letter-spacing:1px}
.tab.active{background:rgba(0,212,255,.1);border-color:var(--neon);color:var(--neon)}
.tab:hover:not(.active){border-color:var(--dim);color:var(--text)}
/* ── QUEUE SECTION ── */
.queue-section{opacity:0;animation:fadeUp .6s ease .65s forwards;margin-bottom:18px}
.queue-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:wrap;gap:8px}
.qh-left{display:flex;align-items:baseline;gap:12px}
.queue-title{font-family:'Share Tech Mono',monospace;font-size:10px;letter-spacing:3px;color:var(--neon);text-transform:uppercase}
.queue-sub{font-size:11px;color:var(--dim2)}
.qh-right{display:flex;gap:8px;align-items:center}
.reload-btn{padding:5px 12px;border-radius:5px;font-family:'Share Tech Mono',monospace;font-size:10px;cursor:pointer;border:1px solid var(--neon);background:transparent;color:var(--neon);transition:all .2s;text-transform:uppercase}
.reload-btn:hover{background:rgba(0,212,255,.1)}
.reload-btn:disabled{opacity:.35;cursor:not-allowed}
.batch-info{font-family:'Share Tech Mono',monospace;font-size:10px;color:var(--dim2)}
.batch-info span{color:var(--neon5)}
.policy-queue{display:grid;grid-template-columns:repeat(5,1fr);gap:10px}
.pcb{background:var(--panel);border:2px solid var(--border);border-radius:12px;padding:12px 11px;cursor:pointer;transition:all .22s;text-align:left;color:var(--text);font-family:'Syne',sans-serif;position:relative;overflow:hidden;display:flex;flex-direction:column;gap:2px}
.pcb:hover:not(:disabled){border-color:var(--dim);transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.5)}
.pcb.selected{border-color:var(--neon);background:rgba(0,212,255,.05);box-shadow:0 0 20px rgba(0,212,255,.15)}
.pcb.rpass{border-color:rgba(0,255,157,.35);background:rgba(0,255,157,.03)}
.pcb.rfail{border-color:rgba(255,77,77,.35);background:rgba(255,77,77,.03)}
.pcb.live-sub{border-color:rgba(245,158,11,.4);background:rgba(245,158,11,.03)}
.pcb.live-sub.selected{border-color:var(--neon5);box-shadow:0 0 20px rgba(245,158,11,.2)}
.pcb-top{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:3px}
.pcb-id{font-family:'Share Tech Mono',monospace;font-size:9px;color:var(--dim2)}
.pcb-result{font-size:13px}
.pcb-source{font-family:'Share Tech Mono',monospace;font-size:8px;padding:1px 5px;border-radius:3px;background:rgba(245,158,11,.15);color:var(--neon5);border:1px solid rgba(245,158,11,.3)}
.pcb-name{font-size:12px;font-weight:700;margin-bottom:1px}
.pcb-loc{font-size:10px;color:var(--dim2);margin-bottom:6px}
.pcb-tags{display:flex;gap:4px;flex-wrap:wrap}
.pct{font-family:'Share Tech Mono',monospace;font-size:8px;padding:2px 5px;border-radius:3px;border:1px solid}
.pct-cov{color:var(--neon);border-color:rgba(0,212,255,.3);background:rgba(0,212,255,.08)}
.pct-lim{color:var(--warn);border-color:rgba(245,158,11,.3);background:rgba(245,158,11,.08)}
.pcb-sel-dot{display:none;position:absolute;bottom:8px;right:9px;width:6px;height:6px;border-radius:50%;background:var(--neon);box-shadow:0 0 8px var(--neon);animation:pdot 1.5s ease infinite}
.pcb.selected .pcb-sel-dot{display:block}
.queue-empty{grid-column:1/-1;text-align:center;padding:40px;color:var(--dim2);font-family:'Share Tech Mono',monospace;font-size:12px}
/* ── CONTROLS ── */
.controls{display:flex;gap:10px;align-items:center;margin-bottom:16px;flex-wrap:wrap;opacity:0;animation:fadeUp .6s ease .75s forwards}
.btn{padding:9px 20px;border-radius:6px;font-family:'Share Tech Mono',monospace;font-size:11px;cursor:pointer;border:1px solid;transition:all .2s;text-transform:uppercase;letter-spacing:1px;display:flex;align-items:center;gap:6px}
.btn-primary{background:var(--neon);color:#000;border-color:var(--neon);box-shadow:0 0 18px rgba(0,212,255,.3);font-weight:700}
.btn-primary:hover:not(:disabled){box-shadow:0 0 28px rgba(0,212,255,.6);transform:translateY(-1px)}
.btn-primary:disabled{opacity:.35;cursor:not-allowed}
.btn-live{background:var(--neon5);color:#000;border-color:var(--neon5);box-shadow:0 0 18px rgba(245,158,11,.3);font-weight:700}
.btn-live:hover:not(:disabled){box-shadow:0 0 28px rgba(245,158,11,.5);transform:translateY(-1px)}
.btn-live:disabled{opacity:.35;cursor:not-allowed}
.btn-sec{background:transparent;color:var(--neon);border-color:var(--neon)}
.btn-sec:hover{background:rgba(0,212,255,.1)}
.sel-info{font-family:'Share Tech Mono',monospace;font-size:11px;color:var(--dim2);margin-left:auto}
.sel-info span{color:var(--neon)}
.live-badge{display:inline-flex;align-items:center;gap:5px;font-family:'Share Tech Mono',monospace;font-size:9px;padding:3px 8px;border-radius:12px;background:rgba(245,158,11,.12);border:1px solid rgba(245,158,11,.3);color:var(--neon5)}
.live-badge::before{content:'';width:5px;height:5px;border-radius:50%;background:var(--neon5);box-shadow:0 0 6px var(--neon5);animation:pulse 1.5s ease infinite}
/* ── PROGRESS ── */
.prog-wrap{background:var(--panel);border:1px solid var(--border);border-radius:4px;height:4px;margin-bottom:20px;overflow:hidden}
.prog{height:100%;background:linear-gradient(90deg,var(--neon),var(--neon2));width:0%;transition:width .4s;box-shadow:0 0 10px var(--neon)}
.prog.fail{background:linear-gradient(90deg,var(--danger),#ff8c00)!important;box-shadow:0 0 10px var(--danger)!important}
/* ── TIMELINE ── */
.tl{display:flex;justify-content:space-between;margin-bottom:20px;position:relative;opacity:0;animation:fadeUp .6s ease .9s forwards}
.tl::before{content:'';position:absolute;top:19px;left:38px;right:38px;height:2px;background:var(--border)}
.tl-prog{position:absolute;top:19px;left:38px;height:2px;background:linear-gradient(90deg,var(--neon),var(--neon2));box-shadow:0 0 8px var(--neon);z-index:1;width:0%;transition:width .5s}
.tls{display:flex;flex-direction:column;align-items:center;gap:6px;z-index:2;flex:1}
.tld{width:38px;height:38px;border-radius:50%;border:2px solid var(--border);background:var(--panel);display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .4s}
.tls.active .tld{border-color:var(--neon);box-shadow:0 0 15px rgba(0,212,255,.5);animation:pglow 1.5s ease infinite}
.tls.done .tld{border-color:var(--success);background:rgba(0,255,157,.1);box-shadow:0 0 10px rgba(0,255,157,.3)}
.tls.failed .tld{border-color:var(--danger)!important;background:rgba(255,77,77,.1)!important;box-shadow:0 0 14px rgba(255,77,77,.5)!important;animation:none!important}
.tll{font-size:9px;font-family:'Share Tech Mono',monospace;color:var(--dim2);text-align:center;max-width:70px;line-height:1.3;transition:color .3s}
.tls.active .tll{color:var(--neon)}
.tls.done .tll{color:var(--success)}
.tls.failed .tll{color:var(--danger)!important}
/* ── MAIN GRID ── */
.main-grid{display:grid;grid-template-columns:215px 1fr;gap:16px;align-items:start}
.agents-sidebar{display:flex;flex-direction:column;gap:7px;position:sticky;top:20px}
.ac{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:10px 12px;transition:all .3s;position:relative;overflow:hidden}
.ac::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;border-radius:2px 0 0 2px;background:var(--agent-color,var(--border));transition:all .3s}
.ac.active{border-color:var(--agent-color,var(--neon));background:color-mix(in srgb,var(--agent-color) 8%,var(--panel));box-shadow:0 0 20px color-mix(in srgb,var(--agent-color) 20%,transparent)}
.ac.active::before{width:4px;box-shadow:0 0 12px var(--agent-color,var(--neon))}
.ac.done{opacity:.65}.ac.done::before{background:var(--success)}
.ac.rejected{opacity:.65}.ac.rejected::before{background:var(--danger)!important}
.ai{font-size:17px;margin-bottom:3px}
.an{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px}
.ar{font-size:9px;color:var(--dim2);margin-top:1px;font-family:'Share Tech Mono',monospace}
.astat{font-size:9px;font-family:'Share Tech Mono',monospace;margin-top:4px;height:13px}
.astat.processing{color:var(--warn)}.astat.complete{color:var(--success)}.astat.idle{color:var(--dim2)}
/* ── LIVE AGENT BADGE ── */
.agent-live-tag{display:inline-flex;align-items:center;gap:4px;font-family:'Share Tech Mono',monospace;font-size:8px;padding:1px 6px;border-radius:8px;background:rgba(245,158,11,.15);border:1px solid rgba(245,158,11,.35);color:var(--neon5);margin-left:6px;vertical-align:middle}
/* ── CHAT ── */
.chat-area{display:flex;flex-direction:column;gap:12px;min-height:400px}
.ph{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:10px 15px;display:flex;align-items:center;gap:12px;opacity:0;transition:opacity .4s}
.ph.v{opacity:1}
.pbadge{font-family:'Share Tech Mono',monospace;font-size:9px;padding:3px 9px;border-radius:16px;background:rgba(0,212,255,.1);border:1px solid var(--neon);color:var(--neon);white-space:nowrap}
.pbadge.live{background:rgba(245,158,11,.12);border-color:var(--neon5);color:var(--neon5)}
.ptitle{font-size:12px;font-weight:700}
.pdesc{font-size:10px;color:var(--dim2)}
.msg{display:flex;gap:9px;opacity:0;transform:translateY(8px);transition:all .4s;align-items:flex-start}
.msg.v{opacity:1;transform:translateY(0)}
.msg.r{flex-direction:row-reverse}
.mav{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;border:1px solid}
.mbody{flex:1;max-width:88%}
.mfrom{font-size:10px;font-family:'Share Tech Mono',monospace;color:var(--dim2);margin-bottom:3px;display:flex;align-items:center;gap:7px}
.msg.r .mfrom{justify-content:flex-end}
.mtime{font-size:9px;color:rgba(74,112,144,.5)}
.mbub{border-radius:10px;padding:9px 12px;font-size:12px;line-height:1.6;border:1px solid}
.mtag{font-family:'Share Tech Mono',monospace;font-size:9px;padding:2px 6px;border-radius:3px;margin-right:5px;background:rgba(0,0,0,.3)}
.cb{font-family:'Share Tech Mono',monospace;font-size:10px;background:rgba(0,0,0,.45);border-radius:5px;padding:8px 11px;margin-top:7px;border:1px solid rgba(255,255,255,.05);line-height:1.75;white-space:pre;overflow-x:auto}
.cb .k{color:var(--neon)}.cb .vs{color:var(--neon2)}.cb .vn{color:var(--warn)}.cb .vp{color:var(--success)}.cb .vf{color:var(--danger)}.cb .vw{color:var(--warn)}.cb .p{color:var(--dim2)}
.s3b{font-family:'Share Tech Mono',monospace;font-size:9px;padding:2px 7px;border-radius:3px;background:rgba(255,107,53,.1);color:var(--neon3);border:1px solid rgba(255,107,53,.25);display:inline-block;margin-top:4px}
.model-badge{font-family:'Share Tech Mono',monospace;font-size:8px;padding:1px 6px;border-radius:3px;background:rgba(0,255,157,.1);color:var(--success);border:1px solid rgba(0,255,157,.25);display:inline-block;margin-top:4px;margin-left:4px}
.ti{display:none;gap:9px;align-items:flex-start}
.ti.v{display:flex}
.tdots{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:9px 14px;display:flex;gap:5px;align-items:center}
.tdots span{width:5px;height:5px;background:var(--dim);border-radius:50%;animation:bnc 1.2s ease infinite}
.tdots span:nth-child(2){animation-delay:.2s}.tdots span:nth-child(3){animation-delay:.4s}
.ob{background:var(--panel);border:1px solid rgba(168,85,247,.3);border-radius:9px;padding:8px 12px;display:flex;align-items:center;gap:9px;opacity:0;transition:opacity .4s}
.ob.v{opacity:1}
.obicon{font-size:15px}
.obt{color:var(--neon4);font-family:'Share Tech Mono',monospace;font-size:9px}
.oba{color:var(--text);font-size:11px}
/* ── LIVE CALL STATUS ── */
.live-call-box{background:rgba(245,158,11,.04);border:1px solid rgba(245,158,11,.25);border-radius:9px;padding:10px 14px;display:flex;align-items:center;gap:10px;opacity:0;transition:opacity .4s}
.live-call-box.v{opacity:1}
.lcb-icon{font-size:18px}
.lcb-title{font-family:'Share Tech Mono',monospace;font-size:9px;color:var(--neon5);text-transform:uppercase;letter-spacing:1px;margin-bottom:2px}
.lcb-text{font-size:11px;color:var(--text)}
.lcb-spinner{width:14px;height:14px;border:2px solid rgba(245,158,11,.3);border-top-color:var(--neon5);border-radius:50%;animation:spin 1s linear infinite;flex-shrink:0;margin-left:auto}
/* ── CHIPS ── */
.chip{display:inline-flex;align-items:center;padding:3px 8px;border-radius:16px;font-family:'Share Tech Mono',monospace;font-size:9px;font-weight:600}
.chip.pass{background:rgba(0,255,157,.1);color:var(--success);border:1px solid rgba(0,255,157,.25)}
.chip.fail{background:rgba(255,77,77,.1);color:var(--danger);border:1px solid rgba(255,77,77,.25)}
.chip.warn{background:rgba(245,158,11,.1);color:var(--warn);border:1px solid rgba(245,158,11,.25)}
.chip.info{background:rgba(0,212,255,.1);color:var(--neon);border:1px solid rgba(0,212,255,.25)}
.chip.purple{background:rgba(168,85,247,.1);color:var(--neon4);border:1px solid rgba(168,85,247,.25)}
.chip.amber{background:rgba(245,158,11,.12);color:var(--neon5);border:1px solid rgba(245,158,11,.3)}
/* ── RESULT CARDS ── */
.rc{border-radius:14px;padding:18px;opacity:0;transform:translateY(16px);transition:all .6s}
.rc.v{opacity:1;transform:translateY(0)}
.rc.app{background:linear-gradient(135deg,#071020,#041508);border:1px solid var(--neon2);box-shadow:0 0 40px rgba(0,255,157,.1)}
.rc.rej{background:linear-gradient(135deg,#180a0a,#0c0404);border:1px solid var(--danger);box-shadow:0 0 40px rgba(255,77,77,.1)}
.rchead{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:14px;padding-bottom:12px}
.rc.app .rchead{border-bottom:1px solid rgba(0,255,157,.12)}
.rc.rej .rchead{border-bottom:1px solid rgba(255,77,77,.18)}
.rcidl{font-size:9px;font-family:'Share Tech Mono',monospace;color:var(--dim2);margin-bottom:3px}
.rcid{font-family:'Share Tech Mono',monospace;font-size:17px;font-weight:700}
.rc.app .rcid{color:var(--neon2)}.rc.rej .rcid{color:var(--danger)}
.rcbadge{font-family:'Share Tech Mono',monospace;font-size:10px;padding:3px 10px;border-radius:16px}
.rc.app .rcbadge{color:var(--success);border:1px solid var(--success)}
.rc.rej .rcbadge{color:var(--danger);border:1px solid var(--danger)}
.rcgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}
.rcs{text-align:center;padding:10px;background:rgba(0,0,0,.3);border-radius:7px;border:1px solid var(--border)}
.rc.rej .rcs{border-color:rgba(255,77,77,.12)}
.rcs .rl{font-size:8px;color:var(--dim2);text-transform:uppercase;letter-spacing:1px;margin-bottom:3px;font-family:'Share Tech Mono',monospace}
.rcs .rv{font-size:14px;font-weight:700;color:var(--text)}
.rv.g{color:var(--success)}.rv.r{color:var(--danger)}
.rreasons{background:rgba(255,77,77,.05);border:1px solid rgba(255,77,77,.18);border-radius:7px;padding:12px 14px;margin-top:12px}
.rrt{font-family:'Share Tech Mono',monospace;font-size:9px;color:var(--danger);letter-spacing:1px;text-transform:uppercase;margin-bottom:8px}
.rri{display:flex;align-items:flex-start;gap:7px;margin-bottom:6px;font-size:11.5px;color:var(--text);line-height:1.5}
.rri::before{content:'βœ—';color:var(--danger);flex-shrink:0;font-weight:700;margin-top:1px}
/* ── LIVE RESULT INDICATOR ── */
.live-result-tag{display:inline-flex;align-items:center;gap:5px;font-family:'Share Tech Mono',monospace;font-size:8px;padding:2px 8px;border-radius:10px;background:rgba(245,158,11,.15);border:1px solid rgba(245,158,11,.35);color:var(--neon5);margin-top:4px}
/* ── API CONFIG PANEL ── */
.cfg-panel{background:var(--panel);border:1px solid var(--border2);border-radius:10px;padding:16px;margin-bottom:16px;display:none}
.cfg-panel.open{display:block}
.cfg-title{font-family:'Share Tech Mono',monospace;font-size:10px;color:var(--neon);text-transform:uppercase;letter-spacing:2px;margin-bottom:14px}
.cfg-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.cfg-field{display:flex;flex-direction:column;gap:5px}
.cfg-label{font-family:'Share Tech Mono',monospace;font-size:9px;color:var(--dim2);text-transform:uppercase;letter-spacing:1px}
.cfg-input{background:rgba(0,0,0,.4);border:1px solid var(--border2);border-radius:5px;padding:7px 10px;font-family:'Share Tech Mono',monospace;font-size:11px;color:var(--text);outline:none;transition:border-color .2s}
.cfg-input:focus{border-color:var(--neon)}
.cfg-actions{display:flex;gap:8px;margin-top:14px}
/* ── ANIMATIONS ── */
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
@keyframes bnc{0%,80%,100%{transform:translateY(0)}40%{transform:translateY(-5px)}}
@keyframes pglow{0%,100%{box-shadow:0 0 15px rgba(0,212,255,.5)}50%{box-shadow:0 0 25px rgba(0,212,255,.9)}}
@keyframes pdot{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(1.3)}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
@keyframes spin{to{transform:rotate(360deg)}}
@media(max-width:900px){.policy-queue{grid-template-columns:repeat(3,1fr)}.cfg-grid{grid-template-columns:1fr 1fr}}
@media(max-width:600px){.policy-queue{grid-template-columns:repeat(2,1fr)}.main-grid{grid-template-columns:1fr}.tl{display:none}}
/* ── AUDIT MODAL ─────────────────────────────────────────── */
.audit-modal-overlay {
position:fixed;inset:0;z-index:2000;
background:rgba(0,0,0,0.82);backdrop-filter:blur(8px);
display:flex;align-items:center;justify-content:center;
opacity:0;pointer-events:none;transition:opacity 0.25s;
}
.audit-modal-overlay.open{opacity:1;pointer-events:all;}
.audit-modal {
background:#0d1626;border:1px solid rgba(244,114,182,0.3);
width:min(720px,95vw);max-height:88vh;overflow-y:auto;
padding:0;position:relative;
transform:translateY(20px) scale(0.97);transition:transform 0.25s;
scrollbar-width:thin;scrollbar-color:rgba(244,114,182,.3) transparent;
}
.audit-modal-overlay.open .audit-modal{transform:translateY(0) scale(1);}
.audit-modal-header {
background:linear-gradient(135deg,rgba(244,114,182,.15),rgba(168,85,247,.1));
border-bottom:1px solid rgba(244,114,182,.2);
padding:1.2rem 1.5rem;display:flex;align-items:center;justify-content:space-between;
position:sticky;top:0;z-index:1;
}
.audit-modal-title{font-family:'Share Tech Mono',monospace;font-size:14px;color:#f472b6;font-weight:700;letter-spacing:.1em;}
.audit-modal-sub{font-size:10px;color:rgba(255,255,255,.5);margin-top:2px;letter-spacing:.05em;}
.audit-modal-close {
background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);
color:rgba(255,255,255,.7);cursor:pointer;width:28px;height:28px;
display:flex;align-items:center;justify-content:center;font-size:14px;
transition:background .2s;
}
.audit-modal-close:hover{background:rgba(255,77,77,.2);color:#ff4d4d;}
.audit-modal-body{padding:1.5rem;}
/* Summary box */
.audit-summary-box {
background:rgba(244,114,182,.07);border-left:3px solid #f472b6;
padding:.9rem 1.1rem;margin-bottom:1.2rem;
font-size:12px;color:rgba(255,255,255,.85);line-height:1.7;
font-family:'Share Tech Mono',monospace;
}
.audit-summary-label{font-size:9px;letter-spacing:.15em;color:#f472b6;text-transform:uppercase;margin-bottom:.4rem;}
/* Score bar */
.audit-score-row{display:flex;align-items:center;gap:.8rem;margin-bottom:1.2rem;}
.audit-score-label{font-size:9px;letter-spacing:.12em;color:rgba(255,255,255,.5);text-transform:uppercase;white-space:nowrap;}
.audit-score-track{flex:1;height:6px;background:rgba(255,255,255,.08);border-radius:3px;overflow:hidden;}
.audit-score-fill{height:100%;border-radius:3px;transition:width 1s ease;}
.audit-score-val{font-family:'Share Tech Mono',monospace;font-size:13px;font-weight:700;width:40px;text-align:right;}
/* Factor cards */
.audit-factors-title{font-size:9px;letter-spacing:.15em;color:rgba(255,255,255,.4);text-transform:uppercase;margin-bottom:.6rem;}
.audit-factor {
background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.07);
padding:.75rem 1rem;margin-bottom:.5rem;
}
.audit-factor-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.3rem;}
.audit-factor-agent{font-size:11px;color:rgba(255,255,255,.9);font-family:'Share Tech Mono',monospace;font-weight:700;}
.audit-factor-outcome{font-size:9px;font-weight:700;letter-spacing:.1em;padding:.2rem .6rem;border:1px solid;}
.audit-factor-factor{font-size:9px;color:rgba(255,255,255,.4);letter-spacing:.08em;text-transform:uppercase;margin-bottom:.3rem;}
.audit-factor-detail{font-size:11px;color:rgba(255,255,255,.65);line-height:1.5;font-family:'Share Tech Mono',monospace;}
/* Decline reasons */
.audit-decline-box{background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.2);padding:.9rem 1.1rem;margin-bottom:1.2rem;}
.audit-decline-title{font-size:9px;letter-spacing:.15em;color:#ef4444;text-transform:uppercase;margin-bottom:.5rem;}
.audit-decline-item{font-size:11px;color:#fca5a5;line-height:1.6;margin-bottom:.3rem;font-family:'Share Tech Mono',monospace;}
/* Export button */
.audit-export-btn {
display:flex;align-items:center;gap:.5rem;
background:linear-gradient(135deg,rgba(0,212,255,.15),rgba(0,212,255,.08));
border:1px solid rgba(0,212,255,.35);color:#00d4ff;
padding:.6rem 1.2rem;font-family:'Share Tech Mono',monospace;
font-size:11px;letter-spacing:.08em;cursor:pointer;
transition:background .2s,box-shadow .2s;width:100%;justify-content:center;margin-top:1rem;
}
.audit-export-btn:hover{background:rgba(0,212,255,.2);box-shadow:0 0 20px rgba(0,212,255,.2);}
/* View Audit button on result card */
.btn-view-audit {
display:inline-flex;align-items:center;gap:.4rem;margin-top:10px;
background:rgba(244,114,182,.12);border:1px solid rgba(244,114,182,.35);
color:#f472b6;padding:.45rem 1rem;font-family:'Share Tech Mono',monospace;
font-size:10px;letter-spacing:.08em;cursor:pointer;transition:background .2s;
}
.btn-view-audit:hover{background:rgba(244,114,182,.22);}
/* ── CLAIMSENSE ── */
.claims-section{display:none;animation:fadeUp .4s ease forwards}
.claims-section.active{display:block}
.claims-header{font-family:'Share Tech Mono',monospace;font-size:10px;letter-spacing:3px;color:#fb923c;text-transform:uppercase;margin-bottom:14px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}
.claim-queue{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;margin-bottom:18px}
.claim-card{background:rgba(255,255,255,.03);border:1px solid rgba(251,146,60,.2);padding:12px;cursor:pointer;transition:border-color .2s}
.claim-card:hover,.claim-card.selected{border-color:#fb923c;background:rgba(251,146,60,.06)}
.claim-card-id{font-family:'Share Tech Mono',monospace;font-size:9px;color:#fb923c;margin-bottom:4px}
.claim-card-name{font-size:13px;font-weight:600;color:var(--text);margin-bottom:4px}
.claim-card-meta{font-size:10px;color:var(--dim2);margin-bottom:6px}
.claim-card-chips{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:6px}
.claim-chip{font-size:9px;padding:2px 6px;border:1px solid rgba(255,255,255,.1);color:var(--dim2)}
.claim-chip.fire{border-color:rgba(239,68,68,.4);color:#fca5a5}
.claim-chip.theft{border-color:rgba(251,146,60,.4);color:#fdba74}
.claim-chip.water{border-color:rgba(59,130,246,.4);color:#93c5fd}
.claim-chip.wind{border-color:rgba(99,102,241,.4);color:#c7d2fe}
.claim-chip.flood{border-color:rgba(59,130,246,.4);color:#93c5fd}
.claim-chip.liability{border-color:rgba(168,85,247,.4);color:#d8b4fe}
.claim-chip.auto_settle{border-color:rgba(34,197,94,.4);color:#86efac}
.claim-chip.adjuster_review{border-color:rgba(251,191,36,.4);color:#fde68a}
.claim-chip.siu_referral{border-color:rgba(239,68,68,.4);color:#fca5a5}
.claim-chip.deny{border-color:rgba(107,114,128,.4);color:#9ca3af}
.fnol-form{background:rgba(255,255,255,.03);border:1px solid rgba(251,146,60,.2);padding:20px;margin-bottom:16px}
.fnol-title{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:2px;color:#fb923c;text-transform:uppercase;margin-bottom:14px}
.fnol-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.fnol-field{display:flex;flex-direction:column;gap:4px}
.fnol-field.full{grid-column:1/-1}
.fnol-label{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:1px;color:var(--dim2);text-transform:uppercase}
.fnol-input{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);color:var(--text);padding:7px 10px;font-family:'Share Tech Mono',monospace;font-size:11px;outline:none;transition:border-color .2s}
.fnol-input:focus{border-color:#fb923c}
.fnol-select{background:rgba(20,25,40,1);border:1px solid rgba(255,255,255,.1);color:var(--text);padding:7px 10px;font-family:'Share Tech Mono',monospace;font-size:11px;outline:none}
.fnol-select:focus{border-color:#fb923c}
.fnol-actions{display:flex;gap:8px;margin-top:14px;flex-wrap:wrap;align-items:center}
.triage-card{border:1px solid;padding:16px;margin-top:12px}
.triage-card.auto_settle{border-color:rgba(34,197,94,.4);background:rgba(34,197,94,.05)}
.triage-card.adjuster_review{border-color:rgba(251,191,36,.3);background:rgba(251,191,36,.04)}
.triage-card.siu_referral{border-color:rgba(239,68,68,.35);background:rgba(239,68,68,.05)}
.triage-card.deny{border-color:rgba(107,114,128,.3);background:rgba(107,114,128,.04)}
.triage-metrics{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px;margin-bottom:10px}
.triage-metric{background:rgba(255,255,255,.03);padding:8px 10px;border:1px solid rgba(255,255,255,.06)}
.triage-metric-label{font-family:'Share Tech Mono',monospace;font-size:8px;letter-spacing:1px;color:var(--dim2);text-transform:uppercase;margin-bottom:3px}
.triage-metric-val{font-size:14px;font-weight:700;color:var(--text)}
.claim-steps{display:flex;gap:0;margin-bottom:16px;overflow:hidden;border:1px solid rgba(255,255,255,.07)}
.claim-step{flex:1;padding:8px 6px;text-align:center;border-right:1px solid rgba(255,255,255,.07);transition:background .3s}
.claim-step:last-child{border-right:none}
.claim-step.idle{background:transparent}
.claim-step.running{background:rgba(251,146,60,.12)}
.claim-step.done{background:rgba(34,197,94,.07)}
.claim-step.failed{background:rgba(239,68,68,.07)}
.claim-step-icon{font-size:14px;margin-bottom:2px}
.claim-step-label{font-family:'Share Tech Mono',monospace;font-size:8px;letter-spacing:.5px;color:var(--dim2)}
.claim-step.done .claim-step-label{color:#86efac}
.claim-step.failed .claim-step-label{color:#fca5a5}
.claim-step.running .claim-step-label{color:#fb923c}
/* ── RISKRADAR ── */
.radar-section{display:none;animation:fadeUp .4s ease forwards}
.radar-section.active{display:block}
.radar-header{font-family:'Share Tech Mono',monospace;font-size:10px;letter-spacing:3px;color:#38bdf8;text-transform:uppercase;margin-bottom:14px}
.radar-layout{display:grid;grid-template-columns:340px 1fr;gap:16px;min-height:520px}
@media(max-width:900px){.radar-layout{grid-template-columns:1fr}}
.radar-panel{display:flex;flex-direction:column;gap:12px}
.radar-form{background:rgba(255,255,255,.03);border:1px solid rgba(56,189,248,.2);padding:16px}
.radar-form-title{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:2px;color:#38bdf8;text-transform:uppercase;margin-bottom:12px}
.radar-field{display:flex;flex-direction:column;gap:4px;margin-bottom:8px}
.radar-label{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:1px;color:var(--dim2);text-transform:uppercase}
.radar-input{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);color:var(--text);padding:7px 10px;font-family:'Share Tech Mono',monospace;font-size:11px;outline:none;width:100%;transition:border-color .2s}
.radar-input:focus{border-color:#38bdf8}
.radar-select{background:rgba(20,25,40,1);border:1px solid rgba(255,255,255,.1);color:var(--text);padding:7px 10px;font-family:'Share Tech Mono',monospace;font-size:11px;outline:none;width:100%}
.radar-grid2{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.radar-btn{background:rgba(56,189,248,.12);border:1px solid rgba(56,189,248,.5);color:#38bdf8;padding:9px 16px;font-family:'Share Tech Mono',monospace;font-size:10px;letter-spacing:.1em;cursor:pointer;width:100%;transition:background .2s}
.radar-btn:hover{background:rgba(56,189,248,.2)}
.radar-btn:disabled{opacity:.4;cursor:default}
/* Peril score cards */
.peril-cards{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.peril-card{border:1px solid rgba(255,255,255,.07);padding:12px;position:relative;overflow:hidden}
.peril-card-icon{font-size:20px;margin-bottom:4px}
.peril-card-label{font-family:'Share Tech Mono',monospace;font-size:8px;letter-spacing:2px;color:var(--dim2);text-transform:uppercase;margin-bottom:4px}
.peril-card-score{font-size:28px;font-weight:700;font-family:'Share Tech Mono',monospace;margin-bottom:2px}
.peril-card-band{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:1px;padding:2px 7px;border:1px solid;display:inline-block}
.peril-bar{height:3px;background:rgba(255,255,255,.06);margin-top:8px}
.peril-bar-fill{height:100%;transition:width 1.2s ease}
.band-critical{color:#ef4444;border-color:rgba(239,68,68,.5)}
.band-high{color:#f97316;border-color:rgba(249,115,22,.5)}
.band-medium{color:#fbbf24;border-color:rgba(251,191,36,.5)}
.band-low{color:#34d399;border-color:rgba(52,211,153,.5)}
.overall-card{border:1px solid rgba(56,189,248,.25);padding:14px;background:rgba(56,189,248,.04)}
.overall-score-big{font-size:42px;font-weight:700;font-family:'Share Tech Mono',monospace;color:#38bdf8}
.radar-narrative{font-size:11px;color:var(--dim);line-height:1.7;padding:10px;background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.06);margin-top:8px}
/* Map */
.radar-map-wrap{border:1px solid rgba(56,189,248,.2);position:relative;min-height:480px}
#radarMap{width:100%;height:480px;background:#0d1117}
.map-legend{position:absolute;bottom:12px;right:12px;background:rgba(10,14,26,.92);border:1px solid rgba(255,255,255,.12);padding:10px 12px;z-index:1000;font-family:'Share Tech Mono',monospace;font-size:9px}
.map-legend-title{color:rgba(255,255,255,.5);letter-spacing:2px;text-transform:uppercase;margin-bottom:6px}
.legend-row{display:flex;align-items:center;gap:6px;margin-bottom:3px}
.legend-dot{width:10px;height:10px;border-radius:50%}
.legend-label{color:rgba(255,255,255,.6)}
.peril-tabs{display:flex;gap:4px;margin-bottom:8px}
.peril-tab{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:.08em;padding:4px 10px;border:1px solid rgba(255,255,255,.1);color:var(--dim2);cursor:pointer;transition:all .2s}
.peril-tab.active{border-color:#38bdf8;color:#38bdf8;background:rgba(56,189,248,.08)}
.radar-history{margin-top:16px}
.radar-history-title{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:2px;color:#38bdf8;text-transform:uppercase;margin-bottom:8px}
.history-row{display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05);cursor:pointer;transition:background .15s}
.history-row:hover{background:rgba(56,189,248,.04)}
.history-addr{font-size:11px;color:var(--text);flex:1}
.history-chip{font-family:'Share Tech Mono',monospace;font-size:9px;padding:1px 6px;border:1px solid}
/* ── POLICYBRIDGE ── */
.pb-section{display:none;animation:fadeUp .4s ease forwards}
.pb-section.active{display:block}
.pb-layout{display:grid;grid-template-columns:220px 1fr 240px;min-height:calc(100vh - 54px)}
@media(max-width:900px){.pb-layout{grid-template-columns:1fr}}
.pb-nav{background:rgba(255,255,255,.02);border-right:1px solid var(--border);padding:16px 12px;position:sticky;top:0;height:calc(100vh - 54px);overflow-y:auto}
.pb-nav-title{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:2px;color:#818cf8;text-transform:uppercase;margin-bottom:10px}
.pb-nav-item{display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;color:var(--dim);margin-bottom:2px;transition:all .2s;border:1px solid transparent}
.pb-nav-item:hover{background:rgba(129,140,248,.06);color:var(--text)}
.pb-nav-item.active{background:rgba(129,140,248,.1);border-color:rgba(129,140,248,.3);color:#818cf8}
.pb-nav-item.done{color:#10b981}
.pb-nav-num{width:20px;height:20px;min-width:20px;border-radius:50%;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex-shrink:0}
.pb-nav-item.active .pb-nav-num{background:#818cf8;border-color:#818cf8;color:#fff}
.pb-nav-item.done .pb-nav-num{background:#10b981;border-color:#10b981;color:#fff}
.pb-main{padding:22px 26px;overflow-y:auto}
.pb-panel{background:rgba(255,255,255,.02);border-left:1px solid var(--border);padding:16px 14px;position:sticky;top:0;height:calc(100vh - 54px);overflow-y:auto}
.pb-step{display:none;animation:fadeUp .25s ease}
.pb-step.active{display:block}
.pb-step-hdr{display:flex;align-items:center;gap:12px;margin-bottom:20px;padding-bottom:14px;border-bottom:1px solid var(--border)}
.pb-step-icon{width:38px;height:38px;border-radius:9px;background:rgba(129,140,248,.1);border:1px solid rgba(129,140,248,.3);display:flex;align-items:center;justify-content:center;font-size:18px}
.pb-step-title{font-size:18px;font-weight:700;color:var(--text)}
.pb-step-sub{font-size:11px;color:var(--dim)}
.pb-card{background:rgba(255,255,255,.025);border:1px solid var(--border);border-radius:10px;padding:16px 18px;margin-bottom:14px}
.pb-card-title{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:2px;color:#818cf8;text-transform:uppercase;margin-bottom:12px;display:flex;align-items:center;gap:6px}
.pb-card-title::before{content:'';width:3px;height:12px;background:#818cf8;border-radius:2px;display:inline-block}
.pb-fg{display:grid;gap:12px}
.pb-fg2{grid-template-columns:1fr 1fr}
.pb-fg3{grid-template-columns:1fr 1fr 1fr}
.pb-cs2{grid-column:span 2}
.pb-field{display:flex;flex-direction:column;gap:4px}
.pb-label{font-size:11px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:4px}
.pb-label .req{color:var(--danger)}
.pb-input{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.12);color:var(--text);padding:7px 10px;font-family:'Share Tech Mono',monospace;font-size:11px;border-radius:6px;outline:none;width:100%;transition:border-color .2s}
.pb-input:focus{border-color:#818cf8}
.pb-select{background:#0a0e1a;border:1px solid rgba(255,255,255,.12);color:var(--text);padding:7px 10px;font-family:'Share Tech Mono',monospace;font-size:11px;border-radius:6px;outline:none;width:100%}
.pb-btn-row{display:flex;gap:8px;align-items:center;margin-top:20px;padding-top:14px;border-top:1px solid var(--border)}
.pb-cov-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:14px}
.pb-cov-card{border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:10px;cursor:pointer;transition:all .2s;background:rgba(255,255,255,.02);position:relative}
.pb-cov-card:hover,.pb-cov-card.sel{border-color:#818cf8;background:rgba(129,140,248,.08)}
.pb-cov-card input{position:absolute;opacity:0}
.pb-cov-code{font-family:'Share Tech Mono',monospace;font-size:13px;font-weight:500;color:#818cf8;margin-bottom:2px}
.pb-cov-name{font-size:10px;color:var(--text);font-weight:600}
.pb-cov-desc{font-size:9px;color:var(--dim);margin-top:2px}
.pb-chk-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.pb-chk{display:flex;align-items:center;gap:7px;padding:6px 9px;border:1px solid rgba(255,255,255,.08);border-radius:6px;cursor:pointer;font-size:11px;color:var(--dim);transition:all .2s;background:rgba(255,255,255,.02)}
.pb-chk:hover{border-color:#818cf8;color:#818cf8}
.pb-chk.on{border-color:#818cf8;background:rgba(129,140,248,.08);color:#818cf8;font-weight:600}
.pb-step-bar{display:flex;gap:0;align-items:center;margin-bottom:20px;padding:12px 16px;background:rgba(255,255,255,.02);border:1px solid var(--border);border-radius:8px}
.pb-sb-wrap{display:flex;flex-direction:column;align-items:center;gap:3px;flex:1}
.pb-sb-bub{width:24px;height:24px;border-radius:50%;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;color:var(--dim);transition:all .25s;flex-shrink:0}
.pb-sb-bub.active{background:#818cf8;border-color:#818cf8;color:#fff}
.pb-sb-bub.done{background:#10b981;border-color:#10b981;color:#fff}
.pb-sb-name{font-size:8px;color:var(--dim);white-space:nowrap;font-family:'Share Tech Mono',monospace}
.pb-sb-wrap.active .pb-sb-name{color:#818cf8}
.pb-sb-wrap.done .pb-sb-name{color:#10b981}
.pb-sb-line{flex:1;height:1px;background:rgba(255,255,255,.08);margin:0 4px}
.pb-sb-line.done{background:#10b981}
.pb-panel-card{background:rgba(255,255,255,.03);border:1px solid var(--border);border-radius:8px;padding:10px 12px;margin-bottom:10px}
.pb-panel-title{font-family:'Share Tech Mono',monospace;font-size:8px;letter-spacing:1.5px;color:#818cf8;text-transform:uppercase;margin-bottom:7px}
.pb-prow{display:flex;justify-content:space-between;gap:6px;font-size:10px;margin-bottom:3px}
.pb-pk{color:var(--dim)}
.pb-pv{color:var(--text);font-weight:500;text-align:right;font-family:'Share Tech Mono',monospace;font-size:10px}
.mga-overlay{position:fixed;inset:0;background:rgba(0,0,0,.75);backdrop-filter:blur(6px);z-index:3000;display:flex;align-items:center;justify-content:center}
.mga-modal{background:#0d1626;border:1px solid rgba(129,140,248,.3);border-radius:12px;width:min(640px,95vw);max-height:90vh;overflow-y:auto;padding:0}
.mga-header{background:linear-gradient(135deg,rgba(129,140,248,.15),rgba(99,102,241,.08));border-bottom:1px solid rgba(129,140,248,.2);padding:1.2rem 1.5rem;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:1}
.mga-title{font-family:'Share Tech Mono',monospace;font-size:13px;color:#818cf8;font-weight:700;letter-spacing:.1em}
.mga-close{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);color:rgba(255,255,255,.6);cursor:pointer;width:26px;height:26px;display:flex;align-items:center;justify-content:center;font-size:13px;border-radius:4px}
.mga-step{display:none}
.mga-step.active{display:block;animation:fadeUp .25s ease}
.mga-search-result{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:7px;padding:10px 12px;cursor:pointer;margin-bottom:6px;transition:all .2s}
.mga-search-result:hover,.mga-search-result.sel{border-color:#818cf8;background:rgba(129,140,248,.08)}
.mga-result-name{font-size:13px;font-weight:600;color:var(--text);margin-bottom:2px}
.mga-result-meta{font-size:10px;color:var(--dim);font-family:'Share Tech Mono',monospace}
.mga-code-badge{font-family:'Share Tech Mono',monospace;font-size:14px;font-weight:700;color:#818cf8;background:rgba(129,140,248,.1);border:1px solid rgba(129,140,248,.3);padding:8px 16px;border-radius:6px;text-align:center;letter-spacing:.1em;margin:10px 0}
.mga-spinner{display:inline-block;width:16px;height:16px;border:2px solid rgba(129,140,248,.3);border-top-color:#818cf8;border-radius:50%;animation:spin .8s linear infinite;vertical-align:middle;margin-right:6px}
.mga-progress{display:flex;gap:0;margin-bottom:16px}
.mga-prog-step{flex:1;text-align:center;padding:6px 4px;font-family:'Share Tech Mono',monospace;font-size:8px;letter-spacing:.05em;color:var(--dim);border-bottom:2px solid rgba(255,255,255,.08);transition:all .2s}
.mga-prog-step.active{color:#818cf8;border-color:#818cf8}
.mga-prog-step.done{color:#10b981;border-color:#10b981}
.mga-confirm-row{display:flex;justify-content:space-between;font-size:11px;padding:5px 0;border-bottom:1px solid rgba(255,255,255,.05)}
.mga-ck{color:var(--dim)}
.mga-cv{color:var(--text);font-weight:500;font-family:'Share Tech Mono',monospace;font-size:10px}
/* ── QUOTE COMPARISON ── */
.quote-section{margin-top:20px}
.quote-header-bar{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;flex-wrap:wrap;gap:8px}
.quote-title{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:2px;color:#818cf8;text-transform:uppercase}
.quote-market-avg{font-family:'Share Tech Mono',monospace;font-size:10px;color:var(--dim2)}
.quote-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px;margin-bottom:16px}
.quote-card{border:1px solid rgba(255,255,255,.1);border-radius:10px;padding:14px;position:relative;transition:all .2s;cursor:pointer;background:rgba(255,255,255,.02)}
.quote-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.4)}
.quote-card.our-quote{border-color:rgba(129,140,248,.5);background:rgba(129,140,248,.06)}
.quote-card.selected-quote{border-color:#10b981;background:rgba(16,185,129,.06)}
.quote-badge{position:absolute;top:-8px;left:12px;font-family:'Share Tech Mono',monospace;font-size:8px;font-weight:700;padding:2px 8px;border-radius:10px;letter-spacing:.08em}
.qb-recommended{background:#818cf8;color:#fff}
.qb-cheapest{background:#10b981;color:#fff}
.qb-established{background:#f59e0b;color:#000}
.quote-carrier{font-size:13px;font-weight:700;color:var(--text);margin-bottom:2px;margin-top:4px}
.quote-am-best{font-family:'Share Tech Mono',monospace;font-size:9px;color:var(--dim2);margin-bottom:8px}
.quote-premium-big{font-size:28px;font-weight:800;font-family:'Share Tech Mono',monospace;margin-bottom:2px}
.quote-monthly{font-size:11px;color:var(--dim2);margin-bottom:8px}
.quote-saving{font-size:10px;padding:2px 8px;border-radius:10px;display:inline-block;margin-bottom:8px;font-family:'Share Tech Mono',monospace;font-weight:600}
.qs-positive{background:rgba(16,185,129,.15);color:#10b981;border:1px solid rgba(16,185,129,.3)}
.qs-negative{background:rgba(239,68,68,.1);color:#ef4444;border:1px solid rgba(239,68,68,.25)}
.quote-features{font-size:10px;color:var(--dim);line-height:1.6;margin-bottom:8px}
.quote-score-bar{height:4px;background:rgba(255,255,255,.08);border-radius:2px;margin-bottom:10px;overflow:hidden}
.quote-score-fill{height:100%;border-radius:2px;transition:width .8s ease}
.quote-select-btn{width:100%;padding:6px;border-radius:6px;font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:.08em;cursor:pointer;transition:all .2s;border:1px solid rgba(255,255,255,.15);background:transparent;color:var(--text)}
.quote-select-btn:hover{background:rgba(255,255,255,.06)}
.quote-card.selected-quote .quote-select-btn{background:#10b981;border-color:#10b981;color:#fff}
.quote-narrative{background:rgba(129,140,248,.06);border:1px solid rgba(129,140,248,.2);border-left:3px solid #818cf8;padding:12px 14px;font-size:11px;color:var(--text);line-height:1.7;margin-bottom:14px;font-family:'Share Tech Mono',monospace}
.quote-proceed-bar{display:flex;gap:10px;align-items:center;padding:12px 14px;background:rgba(16,185,129,.06);border:1px solid rgba(16,185,129,.2);border-radius:8px}
.qpb-info{flex:1;font-size:11px;color:var(--text)}
.qpb-carrier{font-weight:700;color:#10b981}
.qpb-premium{font-family:'Share Tech Mono',monospace;font-size:13px;color:#818cf8;font-weight:700}
</style>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
</head>
<body>
<div class="container">
<!-- HEADER -->
<header>
<div class="hd-left">
<div class="eyebrow">β–Ά Live Inference Β· Policy Issuance Multi-Agent System</div>
<h1>P&amp;C Insurance <em>Multi-Agent</em> Pipeline</h1>
<p class="sub">Bronze β†’ Silver β†’ Gold Β· 5 Agents Β· Real-Time Inference Β· Flask API</p>
</div>
<div class="hd-right">
<div class="db-status">
<div class="db-dot" id="dbDot"></div>
<span id="dbStatusText">Connecting...</span>
</div>
<!-- <div class="hd-pills">
<span class="hpill">agent1_kyc_classifier.pkl</span>
<span class="hpill">agent2_property_risk.pkl</span>
<span class="hpill">agent3_underwriting.pkl</span>
<span class="hpill">agent4_pricing.pkl</span>
</div> -->
<div> <button class="btn btn-sec" style="font-size:9px;padding:5px 12px" onclick="toggleConfig()">βš™ API Config</button> </div>
</div>
</header>
<!-- API CONFIG -->
<div class="cfg-panel" id="cfgPanel">
<div class="cfg-title">πŸ”§ Flask API Settings (HuggingFace β†’ Clever Cloud)</div>
<div class="cfg-grid" style="grid-template-columns:1fr">
<div class="cfg-field"><label class="cfg-label">API Base URL</label><input class="cfg-input" id="cfgApi" value="https://itnovaml-pcagentinai.hf.space" placeholder="https://itnovaml-pcagentinai.hf.space" style="width:100%"></div>
</div>
<div style="font-family:'Share Tech Mono',monospace;font-size:9px;color:var(--dim2);margin-top:6px;padding:6px 8px;background:rgba(0,212,255,0.05);border:1px solid rgba(0,212,255,0.15);border-radius:4px">
DB: btvbbpqhvnttzvptguj3-mysql.services.clever-cloud.com Β· bronze_* tables Β· Clever Cloud MySQL
</div>
<div class="cfg-actions">
<button class="btn btn-primary" onclick="testConnection()">β–Ά Test Connection</button>
<button class="btn btn-sec" onclick="toggleConfig()">βœ• Close</button>
<span id="cfgTestResult" style="font-family:'Share Tech Mono',monospace;font-size:10px;color:var(--dim2);margin-left:8px;align-self:center"></span>
</div>
</div>
<!-- TABS -->
<div class="tabs">
<!-- <button class="tab active" onclick="switchTab('queue')">πŸ“‹ Submission Queue</button> -->
<button class="tab" onclick="switchTab('live')">⚑ Live Submissions</button>
<button class="tab" onclick="switchTab('demo')">🎭 Demo Scenarios</button>
<button class="tab" id="claimsTab" onclick="switchTab('claims')" style="border-color:rgba(251,146,60,.5);color:#fb923c">πŸ”΄ ClaimSense</button>
<button class="tab" id="radarTab" onclick="switchTab('radar')" style="border-color:rgba(56,189,248,.5);color:#38bdf8">🌐 RiskRadar</button>
<button class="tab" id="pbTab" onclick="switchTab('policybridge')" style="border-color:rgba(99,102,241,.5);color:#818cf8">πŸ“‹ PolicyBridge</button>
</div>
<!-- QUEUE SECTION -->
<div class="queue-section">
<div class="queue-header">
<div class="qh-left">
<span class="queue-title" id="queueTitle">πŸ“‹ Recent Policy Submissions</span>
<span class="queue-sub" id="queueSub">β€” Loaded from Bronze layer Β· Click any submission to process</span>
</div>
<div class="qh-right">
<span class="batch-info" id="batchInfo">Showing <span id="batchCount">0</span> submissions</span>
<button class="reload-btn" id="reloadBtn" onclick="loadSubmissions()">β†Ί Refresh</button>
</div>
</div>
<div class="policy-queue" id="policyQueue">
<div class="queue-empty">Loading submissions from Bronze layer...</div>
</div>
</div>
<!-- CONTROLS -->
<div class="controls">
<button class="btn btn-primary" id="startBtn" onclick="startSimulation()" disabled>β–Ά Run Simulation</button>
<button class="btn btn-live" id="liveBtn" onclick="runLiveAgents()" disabled style="display:none">⚑ Run Live Agents</button>
<button class="btn btn-sec" onclick="resetDemo()">β†Ί Reset</button>
<button class="btn btn-sec" id="speedBtn" onclick="toggleSpeed()">⚑ Speed: Normal</button>
<div class="sel-info" id="selInfo">No submission selected</div>
</div>
<!-- PROGRESS -->
<div class="prog-wrap"><div class="prog" id="prog"></div></div>
<!-- TIMELINE -->
<div class="tl">
<div class="tl-prog" id="tlProg"></div>
<div class="tls" id="tl-0"><div class="tld">πŸ“₯</div><div class="tll">Submission</div></div>
<div class="tls" id="tl-1"><div class="tld">πŸͺͺ</div><div class="tll">Document &amp; Identity</div></div>
<div class="tls" id="tl-2"><div class="tld">🏠</div><div class="tll">Property Risk</div></div>
<div class="tls" id="tl-3"><div class="tld">πŸ“‹</div><div class="tll">Underwriting</div></div>
<div class="tls" id="tl-4"><div class="tld">πŸ’²</div><div class="tll">ML Pricing</div></div>
<div class="tls" id="tl-5"><div class="tld">πŸ“„</div><div class="tll">Issuance</div></div>
<div class="tls" id="tl-6"><div class="tld">πŸ”</div><div class="tll">Audit</div></div>
</div>
<!-- MAIN GRID -->
<div class="main-grid">
<div class="agents-sidebar">
<div class="ac" id="ac-orch" style="--agent-color:#a855f7"><div class="ai">🧠</div><div class="an">Orchestrator</div><div class="ar">AWS Step Functions</div><div class="astat idle" id="as-orch">● STANDBY</div></div>
<div class="ac" id="ac-kyc" style="--agent-color:#00d4ff"><div class="ai">πŸͺͺ</div><div class="an">Doc Agent<span class="agent-live-tag" id="tag-kyc" style="display:none">LIVE</span></div><div class="ar">XGBoost Β· Identity</div><div class="astat idle" id="as-kyc">● IDLE</div></div>
<div class="ac" id="ac-prop" style="--agent-color:#ff6b35"><div class="ai">🏠</div><div class="an">Property Agent<span class="agent-live-tag" id="tag-prop" style="display:none">LIVE</span></div><div class="ar">XGBoost Β· Geo-Risk</div><div class="astat idle" id="as-prop">● IDLE</div></div>
<div class="ac" id="ac-uw" style="--agent-color:#f59e0b"><div class="ai">πŸ“‹</div><div class="an">UW Agent<span class="agent-live-tag" id="tag-uw" style="display:none">LIVE</span></div><div class="ar">XGBoost Β· Rules Engine</div><div class="astat idle" id="as-uw">● IDLE</div></div>
<div class="ac" id="ac-price" style="--agent-color:#00ff9d"><div class="ai">πŸ€–</div><div class="an">ML Pricing<span class="agent-live-tag" id="tag-price" style="display:none">LIVE</span></div><div class="ar">XGBoost + GLM Ensemble</div><div class="astat idle" id="as-price">● IDLE</div></div>
<div class="ac" id="ac-issue" style="--agent-color:#818cf8"><div class="ai">πŸ“„</div><div class="an">Issuance Agent</div><div class="ar">Policy Binding</div><div class="astat idle" id="as-issue">● IDLE</div></div>
<div class="ac" id="ac-audit" style="--agent-color:#f472b6"><div class="ai">πŸ”</div><div class="an">Audit Agent<span class="agent-live-tag" id="tag-audit" style="display:none">LIVE</span></div><div class="ar">AXIOM Β· Decision Trail</div><div class="astat idle" id="as-audit">● IDLE</div></div>
</div>
<div class="chat-area" id="chatArea">
<div style="text-align:center;padding:70px 20px;color:var(--dim2)">
<div style="font-size:42px;margin-bottom:12px;opacity:.2">πŸ“‹</div>
<div style="font-family:'Share Tech Mono',monospace;font-size:13px">Select a submission from the queue above</div>
<div style="font-size:11px;margin-top:6px;opacity:.6">Live submissions pull from Bronze MySQL Β· Demo scenarios use static data</div>
</div>
</div>
</div>
<!-- CLAIMSENSE SECTION -->
<div class="claims-section" id="claimsSection">
<div class="fnol-form" id="fnolForm">
<div class="fnol-title">πŸ”΄ Submit New FNOL β€” First Notice of Loss</div>
<div class="fnol-grid">
<div class="fnol-field"><label class="fnol-label">Policy Number *</label><input class="fnol-input" id="fnol_policy" placeholder="PC-2025-00612TX" /></div>
<div class="fnol-field"><label class="fnol-label">Claimant Name *</label><input class="fnol-input" id="fnol_name" placeholder="John Smith" /></div>
<div class="fnol-field"><label class="fnol-label">Incident Date *</label><input class="fnol-input" id="fnol_date" type="date" /></div>
<div class="fnol-field"><label class="fnol-label">Incident Type *</label>
<select class="fnol-select" id="fnol_type">
<option value="FIRE">Fire</option><option value="WATER">Water / Burst Pipe</option>
<option value="WIND">Wind / Storm</option><option value="HAIL">Hail</option>
<option value="THEFT">Theft / Burglary</option><option value="VANDALISM">Vandalism</option>
<option value="LIABILITY">Liability / Injury</option><option value="FLOOD">Flood</option>
<option value="STRUCTURAL">Structural Damage</option><option value="VEHICLE_IMPACT">Vehicle Impact</option>
<option value="OTHER">Other</option>
</select>
</div>
<div class="fnol-field"><label class="fnol-label">Estimated Damage ($)</label><input class="fnol-input" id="fnol_damage" type="number" placeholder="25000" /></div>
<div class="fnol-field"><label class="fnol-label">Coverage Type</label>
<select class="fnol-select" id="fnol_coverage">
<option value="HO-3">HO-3 (Standard Homeowners)</option><option value="HO-5">HO-5 (Comprehensive)</option>
<option value="HO-4">HO-4 (Renters)</option><option value="HO-6">HO-6 (Condo)</option><option value="DP-3">DP-3 (Dwelling)</option>
</select>
</div>
<div class="fnol-field full"><label class="fnol-label">Incident Description</label><input class="fnol-input" id="fnol_desc" placeholder="Brief description of what happened..." /></div>
<div class="fnol-field"><label class="fnol-label">Has Police Report?</label><select class="fnol-select" id="fnol_police"><option value="0">No</option><option value="1">Yes</option></select></div>
<div class="fnol-field"><label class="fnol-label">Photos Submitted?</label><select class="fnol-select" id="fnol_photos"><option value="0">No</option><option value="1">Yes</option></select></div>
</div>
<div class="fnol-actions">
<button class="btn btn-live" id="submitFnolBtn" onclick="submitAndRunClaim()" style="border-color:#fb923c;background:#fb923c;color:#0a0e1a;font-weight:600">πŸ”΄ Submit FNOL &amp; Run Triage</button>
<button class="btn" onclick="loadRecentClaims()" style="font-size:11px">β†Ί Load Recent Claims</button>
<span id="fnolStatus" style="font-size:11px;color:var(--dim2)"></span>
</div>
</div>
<div class="claim-steps" id="claimSteps">
<div class="claim-step idle" id="cstep1"><div class="claim-step-icon">πŸ“‹</div><div class="claim-step-label">FNOL Intake</div></div>
<div class="claim-step idle" id="cstep2"><div class="claim-step-icon">πŸ›‘</div><div class="claim-step-label">Coverage</div></div>
<div class="claim-step idle" id="cstep3"><div class="claim-step-icon">πŸ”</div><div class="claim-step-label">Fraud Signal</div></div>
<div class="claim-step idle" id="cstep4"><div class="claim-step-icon">βš–</div><div class="claim-step-label">Severity</div></div>
<div class="claim-step idle" id="cstep5"><div class="claim-step-icon">🚦</div><div class="claim-step-label">Triage</div></div>
</div>
<div id="triageResult" style="display:none"></div>
<div class="claims-header">
<span>πŸ“‹ Recent Claims</span>
<div style="display:flex;gap:8px;align-items:center">
<span id="claimCount" style="font-size:10px;color:var(--dim2)">0 claims</span>
<button class="reload-btn" onclick="loadRecentClaims()">β†Ί Refresh</button>
</div>
</div>
<div class="claim-queue" id="claimQueue">
<div style="color:var(--dim2);font-size:11px;font-family:'Share Tech Mono',monospace">Submit a claim or click Refresh to load recent claims.</div>
</div>
</div>
<!-- RISKRADAR SECTION -->
<div class="radar-section" id="radarSection">
<div class="radar-header">🌐 RiskRadar β€” Geo Risk Intelligence Β· Address-Level Peril Scoring</div>
<div class="radar-layout">
<!-- Left panel: form + scores -->
<div class="radar-panel">
<div class="radar-form">
<div class="radar-form-title">Property Lookup</div>
<div class="radar-field"><label class="radar-label">Street Address</label><input class="radar-input" id="rr_address" placeholder="4821 Elm Ridge Drive" /></div>
<div class="radar-grid2">
<div class="radar-field"><label class="radar-label">State *</label>
<select class="radar-select" id="rr_state">
<option value="TX">TX β€” Texas</option><option value="FL">FL β€” Florida</option>
<option value="CA">CA β€” California</option><option value="NY">NY β€” New York</option>
<option value="LA">LA β€” Louisiana</option><option value="NC">NC β€” N. Carolina</option>
<option value="SC">SC β€” S. Carolina</option><option value="GA">GA β€” Georgia</option>
<option value="AL">AL β€” Alabama</option><option value="MS">MS β€” Mississippi</option>
<option value="OK">OK β€” Oklahoma</option><option value="KS">KS β€” Kansas</option>
<option value="CO">CO β€” Colorado</option><option value="AZ">AZ β€” Arizona</option>
<option value="NM">NM β€” New Mexico</option><option value="NV">NV β€” Nevada</option>
<option value="OR">OR β€” Oregon</option><option value="WA">WA β€” Washington</option>
<option value="IL">IL β€” Illinois</option><option value="OH">OH β€” Ohio</option>
<option value="VA">VA β€” Virginia</option><option value="MD">MD β€” Maryland</option>
<option value="NJ">NJ β€” New Jersey</option><option value="PA">PA β€” Pennsylvania</option>
<option value="TN">TN β€” Tennessee</option><option value="MO">MO β€” Missouri</option>
<option value="AR">AR β€” Arkansas</option><option value="UT">UT β€” Utah</option>
<option value="ID">ID β€” Idaho</option><option value="MT">MT β€” Montana</option>
<option value="WY">WY β€” Wyoming</option><option value="AK">AK β€” Alaska</option>
<option value="HI">HI β€” Hawaii</option><option value="MA">MA β€” Massachusetts</option>
<option value="CT">CT β€” Connecticut</option><option value="MI">MI β€” Michigan</option>
<option value="WI">WI β€” Wisconsin</option><option value="MN">MN β€” Minnesota</option>
<option value="IA">IA β€” Iowa</option><option value="NE">NE β€” Nebraska</option>
<option value="KY">KY β€” Kentucky</option><option value="IN">IN β€” Indiana</option>
<option value="DC">DC β€” Washington DC</option>
</select>
</div>
<div class="radar-field"><label class="radar-label">ZIP Code *</label><input class="radar-input" id="rr_zip" placeholder="77001" maxlength="5"/></div>
</div>
<div class="radar-grid2">
<div class="radar-field"><label class="radar-label">Year Built</label><input class="radar-input" id="rr_year" type="number" placeholder="2005" /></div>
<div class="radar-field"><label class="radar-label">Roof Year</label><input class="radar-input" id="rr_roof" type="number" placeholder="2018" /></div>
</div>
<div class="radar-grid2">
<div class="radar-field"><label class="radar-label">Construction</label>
<select class="radar-select" id="rr_constr">
<option value="Frame">Frame</option><option value="Masonry">Masonry</option>
<option value="Concrete">Concrete</option><option value="Steel">Steel Frame</option>
</select>
</div>
<div class="radar-field"><label class="radar-label">Stories</label>
<select class="radar-select" id="rr_stories"><option value="1">1</option><option value="2" selected>2</option><option value="3">3</option><option value="4">4+</option></select>
</div>
</div>
<button class="radar-btn" id="rrScoreBtn" onclick="runRiskRadar()">⚑ SCORE PROPERTY RISK</button>
<div id="rrStatus" style="font-size:10px;color:var(--dim2);font-family:Share Tech Mono,monospace;margin-top:6px;text-align:center"></div>
</div>
<!-- Overall score card -->
<div class="overall-card" id="rrOverall" style="display:none">
<div style="display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:6px">
<div>
<div style="font-family:'Share Tech Mono',monospace;font-size:8px;letter-spacing:2px;color:rgba(56,189,248,.7);text-transform:uppercase;margin-bottom:4px">Overall Risk Score</div>
<div class="overall-score-big" id="rrOverallScore">β€”</div>
<div class="peril-card-band" id="rrOverallBand" style="margin-top:4px">β€”</div>
</div>
<div style="text-align:right">
<button onclick="exportRiskReport()" style="background:transparent;border:1px solid rgba(56,189,248,.35);color:#38bdf8;padding:5px 10px;font-family:Share Tech Mono,monospace;font-size:8px;letter-spacing:.1em;cursor:pointer;margin-bottom:4px">⬇ EXPORT</button>
<div id="rrMethod" style="font-size:9px;color:var(--dim2);font-family:Share Tech Mono,monospace"></div>
</div>
</div>
<div class="radar-narrative" id="rrNarrative"></div>
</div>
<!-- 4 peril score cards -->
<div class="peril-cards" id="rrPerilCards" style="display:none">
<div class="peril-card" id="pc_wind">
<div class="peril-card-icon">πŸŒͺ</div>
<div class="peril-card-label">Wind</div>
<div class="peril-card-score" id="sc_wind">β€”</div>
<div class="peril-card-band" id="bd_wind">β€”</div>
<div class="peril-bar"><div class="peril-bar-fill" id="bar_wind" style="width:0%"></div></div>
</div>
<div class="peril-card" id="pc_flood">
<div class="peril-card-icon">πŸ’§</div>
<div class="peril-card-label">Flood</div>
<div class="peril-card-score" id="sc_flood">β€”</div>
<div class="peril-card-band" id="bd_flood">β€”</div>
<div class="peril-bar"><div class="peril-bar-fill" id="bar_flood" style="width:0%"></div></div>
</div>
<div class="peril-card" id="pc_fire">
<div class="peril-card-icon">πŸ”₯</div>
<div class="peril-card-label">Fire</div>
<div class="peril-card-score" id="sc_fire">β€”</div>
<div class="peril-card-band" id="bd_fire">β€”</div>
<div class="peril-bar"><div class="peril-bar-fill" id="bar_fire" style="width:0%"></div></div>
</div>
<div class="peril-card" id="pc_quake">
<div class="peril-card-icon">⚑</div>
<div class="peril-card-label">Earthquake</div>
<div class="peril-card-score" id="sc_quake">β€”</div>
<div class="peril-card-band" id="bd_quake">β€”</div>
<div class="peril-bar"><div class="peril-bar-fill" id="bar_quake" style="width:0%"></div></div>
</div>
</div>
<!-- Lookup history -->
<div class="radar-history">
<div class="radar-history-title">Recent Lookups</div>
<div id="rrHistory"><div style="font-size:10px;color:var(--dim2);font-family:Share Tech Mono,monospace">No recent lookups.</div></div>
</div>
</div>
<!-- Right panel: map -->
<div>
<div class="peril-tabs" id="perilTabs">
<div class="peril-tab active" onclick="setPerilLayer('wind',this)">πŸŒͺ Wind</div>
<div class="peril-tab" onclick="setPerilLayer('flood',this)">πŸ’§ Flood</div>
<div class="peril-tab" onclick="setPerilLayer('fire',this)">πŸ”₯ Fire</div>
<div class="peril-tab" onclick="setPerilLayer('quake',this)">⚑ Quake</div>
<div class="peril-tab" onclick="setPerilLayer('overall',this)">πŸ“Š Overall</div>
</div>
<div class="radar-map-wrap">
<div id="radarMap"></div>
<div class="map-legend">
<div class="map-legend-title">Risk Level</div>
<div class="legend-row"><div class="legend-dot" style="background:#ef4444"></div><div class="legend-label">Critical (75-100)</div></div>
<div class="legend-row"><div class="legend-dot" style="background:#f97316"></div><div class="legend-label">High (55-74)</div></div>
<div class="legend-row"><div class="legend-dot" style="background:#fbbf24"></div><div class="legend-label">Medium (35-54)</div></div>
<div class="legend-row"><div class="legend-dot" style="background:#34d399"></div><div class="legend-label">Low (0-34)</div></div>
</div>
</div>
</div>
</div>
</div>
<!-- POLICYBRIDGE SECTION -->
<div class="pb-section" id="pbSection">
<div class="pb-layout">
<div class="pb-nav">
<div class="pb-nav-title">Form Steps</div>
<div class="pb-nav-item active" id="pb-snav-1" onclick="pbGoStep(1)"><div class="pb-nav-num" id="pb-sn-1">1</div>Submitter</div>
<div class="pb-nav-item" id="pb-snav-2" onclick="pbGoStep(2)"><div class="pb-nav-num" id="pb-sn-2">2</div>Insured</div>
<div class="pb-nav-item" id="pb-snav-3" onclick="pbGoStep(3)"><div class="pb-nav-num" id="pb-sn-3">3</div>Property</div>
<div class="pb-nav-item" id="pb-snav-4" onclick="pbGoStep(4)"><div class="pb-nav-num" id="pb-sn-4">4</div>Coverage</div>
<div class="pb-nav-item" id="pb-snav-5" onclick="pbGoStep(5)"><div class="pb-nav-num" id="pb-sn-5">5</div>Review</div>
<div class="pb-nav-item" id="pb-snav-6" onclick="pbGoStep(6)"><div class="pb-nav-num" id="pb-sn-6">6</div>Quotes</div>
<div style="margin-top:18px;padding:10px 12px;background:rgba(129,140,248,.06);border:1px solid rgba(129,140,248,.2);border-radius:8px">
<div style="font-family:'Share Tech Mono',monospace;font-size:8px;letter-spacing:1.5px;color:#818cf8;text-transform:uppercase;margin-bottom:6px">MGA Portal</div>
<div id="pb-mga-status" style="font-size:10px;color:var(--dim);margin-bottom:8px">No MGA onboarded</div>
<button onclick="openMgaOnboarding()" style="width:100%;background:rgba(129,140,248,.1);border:1px solid rgba(129,140,248,.3);color:#818cf8;padding:6px;font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:.1em;cursor:pointer;border-radius:5px">+ MGA ONBOARDING</button>
</div>
<div style="margin-top:12px;padding:10px 12px;background:rgba(255,255,255,.02);border:1px solid var(--border);border-radius:8px">
<div style="font-family:'Share Tech Mono',monospace;font-size:8px;letter-spacing:1.5px;color:var(--dim2);text-transform:uppercase;margin-bottom:6px">Bronze Tables</div>
<div style="font-size:9px;color:var(--dim);line-height:1.9;font-family:'Share Tech Mono',monospace">
&#8627; bronze_brokers<br>&#8627; bronze_insureds<br>&#8627; bronze_properties<br>&#8627; bronze_submissions<br>&#8627; bronze_pipeline_agent_log
</div>
</div>
</div>
<div class="pb-main">
<div class="pb-step-bar">
<div class="pb-sb-wrap active" id="pb-sbw-1"><div class="pb-sb-bub active" id="pb-sbb-1">1</div><div class="pb-sb-name">Submitter</div></div>
<div class="pb-sb-line" id="pb-sbl-1"></div>
<div class="pb-sb-wrap" id="pb-sbw-2"><div class="pb-sb-bub" id="pb-sbb-2">2</div><div class="pb-sb-name">Insured</div></div>
<div class="pb-sb-line" id="pb-sbl-2"></div>
<div class="pb-sb-wrap" id="pb-sbw-3"><div class="pb-sb-bub" id="pb-sbb-3">3</div><div class="pb-sb-name">Property</div></div>
<div class="pb-sb-line" id="pb-sbl-3"></div>
<div class="pb-sb-wrap" id="pb-sbw-4"><div class="pb-sb-bub" id="pb-sbb-4">4</div><div class="pb-sb-name">Coverage</div></div>
<div class="pb-sb-line" id="pb-sbl-4"></div>
<div class="pb-sb-wrap" id="pb-sbw-5"><div class="pb-sb-bub" id="pb-sbb-5">5</div><div class="pb-sb-name">Submit</div></div>
<div class="pb-sb-line" id="pb-sbl-5"></div>
<div class="pb-sb-wrap" id="pb-sbw-6"><div class="pb-sb-bub" id="pb-sbb-6">6</div><div class="pb-sb-name">Quotes</div></div>
</div>
<!-- STEP 1 -->
<div class="pb-step active" id="pb-sec-1">
<div class="pb-step-hdr"><div class="pb-step-icon">&#127970;</div><div><div class="pb-step-title">Submitter Information</div><div class="pb-step-sub">Broker or MGA details</div></div></div>
<div class="pb-card"><div class="pb-card-title">Organization</div>
<div class="pb-fg pb-fg2">
<div class="pb-field pb-cs2"><label class="pb-label">Organization Name <span class="req">*</span></label><input class="pb-input" id="pb_broker_name" placeholder="Acme Insurance Brokers LLC"></div>
<div class="pb-field"><label class="pb-label">Broker Code <span class="req">*</span></label><input class="pb-input" id="pb_broker_code" placeholder="ACM-TX-0041" maxlength="15"></div>
<div class="pb-field"><label class="pb-label">Contact Email <span class="req">*</span></label><input class="pb-input" id="pb_broker_email" type="email" placeholder="submissions@broker.com"></div>
<div class="pb-field"><label class="pb-label">Licensed State <span class="req">*</span></label><select class="pb-select" id="pb_broker_state"><option value="">-- State --</option></select></div>
<div class="pb-field"><label class="pb-label">Line of Business <span class="req">*</span></label><select class="pb-select" id="pb_lob"><option value="">-- Select --</option><option>Personal Lines</option><option>Commercial Lines</option><option>Farm &amp; Ranch</option></select></div>
<div class="pb-field"><label class="pb-label">Submission Date <span class="req">*</span></label><input class="pb-input" id="pb_sub_date" type="date"></div>
</div>
</div>
<div class="pb-btn-row"><span style="font-size:11px;color:var(--dim)">Step 1 of 5</span><button class="btn btn-live" style="margin-left:auto;border-color:#818cf8;color:#818cf8;background:rgba(129,140,248,.1)" onclick="pbNextStep(1)">Next: Insured &#8594;</button></div>
</div>
<!-- STEP 2 -->
<div class="pb-step" id="pb-sec-2">
<div class="pb-step-hdr"><div class="pb-step-icon">&#128100;</div><div><div class="pb-step-title">Insured Information</div><div class="pb-step-sub">PII details</div></div></div>
<div class="pb-card"><div class="pb-card-title">Personal Details</div>
<div class="pb-fg pb-fg2">
<div class="pb-field"><label class="pb-label">First Name <span class="req">*</span></label><input class="pb-input" id="pb_ins_first" placeholder="Sarah"></div>
<div class="pb-field"><label class="pb-label">Last Name <span class="req">*</span></label><input class="pb-input" id="pb_ins_last" placeholder="Mitchell"></div>
<div class="pb-field"><label class="pb-label">Date of Birth <span class="req">*</span></label><input class="pb-input" id="pb_ins_dob" type="date"></div>
<div class="pb-field"><label class="pb-label">Credit Score <span class="req">*</span></label><input class="pb-input" id="pb_credit" type="number" placeholder="720" min="300" max="850"></div>
<div class="pb-field"><label class="pb-label">Email <span class="req">*</span></label><input class="pb-input" id="pb_ins_email" type="email" placeholder="sarah@email.com"></div>
<div class="pb-field"><label class="pb-label">Phone</label><input class="pb-input" id="pb_ins_phone" placeholder="(512) 555-0192"></div>
<div class="pb-field pb-cs2"><label class="pb-label">Street Address <span class="req">*</span></label><input class="pb-input" id="pb_ins_street" placeholder="4821 Elm Ridge Drive"></div>
<div class="pb-field"><label class="pb-label">City <span class="req">*</span></label><input class="pb-input" id="pb_ins_city" placeholder="Austin"></div>
<div class="pb-field"><label class="pb-label">State <span class="req">*</span></label><select class="pb-select" id="pb_ins_state"><option value="">-- State --</option></select></div>
<div class="pb-field"><label class="pb-label">ZIP <span class="req">*</span></label><input class="pb-input" id="pb_ins_zip" placeholder="78701" maxlength="5"></div>
</div>
</div>
<div class="pb-btn-row"><button class="btn btn-sec" onclick="pbGoStep(1)">&#8592; Back</button><button class="btn btn-live" style="margin-left:auto;border-color:#818cf8;color:#818cf8;background:rgba(129,140,248,.1)" onclick="pbNextStep(2)">Next: Property &#8594;</button></div>
</div>
<!-- STEP 3 -->
<div class="pb-step" id="pb-sec-3">
<div class="pb-step-hdr"><div class="pb-step-icon">&#127968;</div><div><div class="pb-step-title">Property Information</div><div class="pb-step-sub">Risk location details</div></div></div>
<div class="pb-card"><div class="pb-card-title">Property Address</div>
<div class="pb-fg pb-fg2">
<div class="pb-field pb-cs2"><label class="pb-label">Street <span class="req">*</span></label><input class="pb-input" id="pb_prop_street" placeholder="4821 Elm Ridge Drive"></div>
<div class="pb-field"><label class="pb-label">City <span class="req">*</span></label><input class="pb-input" id="pb_prop_city" placeholder="Austin"></div>
<div class="pb-field"><label class="pb-label">State <span class="req">*</span></label><select class="pb-select" id="pb_prop_state"><option value="">-- State --</option></select></div>
<div class="pb-field"><label class="pb-label">ZIP <span class="req">*</span></label><input class="pb-input" id="pb_prop_zip" placeholder="78701" maxlength="5"></div>
</div>
</div>
<div class="pb-card"><div class="pb-card-title">Construction</div>
<div class="pb-fg pb-fg3">
<div class="pb-field"><label class="pb-label">Property Type <span class="req">*</span></label><select class="pb-select" id="pb_prop_type"><option value="">-- Select --</option><option>Single Family</option><option>Condo</option><option>Multi-Family</option><option>Mobile Home</option></select></div>
<div class="pb-field"><label class="pb-label">Year Built <span class="req">*</span></label><input class="pb-input" id="pb_year_built" type="number" placeholder="2005" min="1800"></div>
<div class="pb-field"><label class="pb-label">Square Footage <span class="req">*</span></label><input class="pb-input" id="pb_sqft" type="number" placeholder="2400" min="100"></div>
<div class="pb-field"><label class="pb-label">Construction <span class="req">*</span></label><select class="pb-select" id="pb_construction"><option value="">-- Select --</option><option>Frame</option><option>Masonry</option><option>Masonry Veneer</option><option>Fire Resistive</option></select></div>
<div class="pb-field"><label class="pb-label">Roof Type <span class="req">*</span></label><select class="pb-select" id="pb_roof_type"><option value="">-- Select --</option><option>Asphalt Shingle</option><option>Metal</option><option>Tile</option><option>Flat</option><option>Wood Shake</option></select></div>
<div class="pb-field"><label class="pb-label">Roof Year <span class="req">*</span></label><input class="pb-input" id="pb_roof_year" type="number" placeholder="2018" min="1950"></div>
<div class="pb-field"><label class="pb-label">Stories</label><select class="pb-select" id="pb_stories"><option value="1">1</option><option value="2" selected>2</option><option value="3">3</option><option value="4+">4+</option></select></div>
<div class="pb-field"><label class="pb-label">Occupancy</label><select class="pb-select" id="pb_occupancy"><option>Owner Occupied</option><option>Tenant Occupied</option><option>Seasonal</option></select></div>
<div class="pb-field"><label class="pb-label">Units</label><input class="pb-input" id="pb_units" type="number" value="1" min="1"></div>
</div>
</div>
<div class="pb-card"><div class="pb-card-title">Liability Indicators</div>
<div class="pb-chk-grid">
<label class="pb-chk" onclick="pbToggleChk(this,'pb_pool')"><input type="checkbox" id="pb_pool"> &#127946; Swimming Pool</label>
<label class="pb-chk" onclick="pbToggleChk(this,'pb_tramp')"><input type="checkbox" id="pb_tramp"> &#129336; Trampoline</label>
<label class="pb-chk" onclick="pbToggleChk(this,'pb_dog')"><input type="checkbox" id="pb_dog"> &#128021; Aggressive Dog</label>
<label class="pb-chk" onclick="pbToggleChk(this,'pb_solar')"><input type="checkbox" id="pb_solar"> &#9728; Solar Panels</label>
</div>
</div>
<div class="pb-btn-row"><button class="btn btn-sec" onclick="pbGoStep(2)">&#8592; Back</button><button class="btn btn-live" style="margin-left:auto;border-color:#818cf8;color:#818cf8;background:rgba(129,140,248,.1)" onclick="pbNextStep(3)">Next: Coverage &#8594;</button></div>
</div>
<!-- STEP 4 -->
<div class="pb-step" id="pb-sec-4">
<div class="pb-step-hdr"><div class="pb-step-icon">&#128737;</div><div><div class="pb-step-title">Coverage Request</div><div class="pb-step-sub">Limits and endorsements</div></div></div>
<div class="pb-card"><div class="pb-card-title">Coverage Type</div>
<div class="pb-cov-grid">
<div class="pb-cov-card sel" data-code="HO-3" onclick="pbSelCov(this)"><input type="radio" name="pb_cov" value="HO-3" checked><div class="pb-cov-code">HO-3</div><div class="pb-cov-name">Special Form</div><div class="pb-cov-desc">Most common homeowners</div></div>
<div class="pb-cov-card" data-code="HO-5" onclick="pbSelCov(this)"><input type="radio" name="pb_cov" value="HO-5"><div class="pb-cov-code">HO-5</div><div class="pb-cov-name">Comprehensive</div><div class="pb-cov-desc">Open perils on all</div></div>
<div class="pb-cov-card" data-code="HO-4" onclick="pbSelCov(this)"><input type="radio" name="pb_cov" value="HO-4"><div class="pb-cov-code">HO-4</div><div class="pb-cov-name">Renters</div><div class="pb-cov-desc">Contents &amp; liability</div></div>
<div class="pb-cov-card" data-code="HO-6" onclick="pbSelCov(this)"><input type="radio" name="pb_cov" value="HO-6"><div class="pb-cov-code">HO-6</div><div class="pb-cov-name">Condo</div><div class="pb-cov-desc">Walls-in coverage</div></div>
<div class="pb-cov-card" data-code="DP-3" onclick="pbSelCov(this)"><input type="radio" name="pb_cov" value="DP-3"><div class="pb-cov-code">DP-3</div><div class="pb-cov-name">Dwelling Special</div><div class="pb-cov-desc">Landlord open perils</div></div>
<div class="pb-cov-card" data-code="WC-3" onclick="pbSelCov(this)"><input type="radio" name="pb_cov" value="WC-3"><div class="pb-cov-code">WC-3</div><div class="pb-cov-name">Workers Comp</div><div class="pb-cov-desc">Employer liability</div></div>
</div>
</div>
<div class="pb-card"><div class="pb-card-title">Limits &amp; Dates</div>
<div class="pb-fg pb-fg2">
<div class="pb-field"><label class="pb-label">Coverage Limit ($) <span class="req">*</span></label><input class="pb-input" id="pb_cov_limit" type="number" placeholder="450000" min="50000"></div>
<div class="pb-field"><label class="pb-label">Deductible <span class="req">*</span></label><select class="pb-select" id="pb_deductible"><option value="">-- Select --</option><option value="500">$500</option><option value="1000">$1,000</option><option value="2500" selected>$2,500</option><option value="5000">$5,000</option><option value="10000">$10,000</option></select></div>
<div class="pb-field"><label class="pb-label">Effective Date <span class="req">*</span></label><input class="pb-input" id="pb_eff_date" type="date"></div>
<div class="pb-field"><label class="pb-label">Liability Limit</label><select class="pb-select" id="pb_liab"><option value="100000">$100,000</option><option value="300000" selected>$300,000</option><option value="500000">$500,000</option></select></div>
</div>
</div>
<div class="pb-card"><div class="pb-card-title">Endorsements</div>
<div class="pb-chk-grid">
<label class="pb-chk" onclick="pbToggleChk(this,'pb_end_water')"><input type="checkbox" id="pb_end_water"> &#128167; Water Backup</label>
<label class="pb-chk" onclick="pbToggleChk(this,'pb_end_quake')"><input type="checkbox" id="pb_end_quake"> &#127755; Earthquake</label>
<label class="pb-chk" onclick="pbToggleChk(this,'pb_end_flood')"><input type="checkbox" id="pb_end_flood"> &#127754; Flood (NFIP)</label>
<label class="pb-chk" onclick="pbToggleChk(this,'pb_end_id')"><input type="checkbox" id="pb_end_id"> &#128272; Identity Theft</label>
</div>
</div>
<div class="pb-card"><div class="pb-card-title">Notes</div>
<textarea class="pb-input" id="pb_notes" rows="3" placeholder="Special circumstances, prior losses..." style="resize:vertical;min-height:64px"></textarea>
</div>
<div class="pb-btn-row"><button class="btn btn-sec" onclick="pbGoStep(3)">&#8592; Back</button><button class="btn btn-live" style="margin-left:auto;border-color:#818cf8;color:#818cf8;background:rgba(129,140,248,.1)" onclick="pbNextStep(4)">Review &#8594;</button></div>
</div>
<!-- STEP 5 -->
<div class="pb-step" id="pb-sec-5">
<div class="pb-step-hdr"><div class="pb-step-icon">&#128203;</div><div><div class="pb-step-title">Review &amp; Submit</div><div class="pb-step-sub">Confirm before Bronze layer commit</div></div></div>
<div id="pb-review-content"></div>
<div class="pb-btn-row"><button class="btn btn-sec" onclick="pbGoStep(4)">&#8592; Back</button><button class="btn btn-live" style="margin-left:auto;border-color:#818cf8;color:#818cf8;background:rgba(129,140,248,.1)" onclick="pbGoToQuotes()">&#128200; Get Quote Comparison &#8594;</button></div>
</div>
<!-- STEP 6: QUOTE COMPARISON -->
<div class="pb-step" id="pb-sec-6">
<div class="pb-step-hdr"><div class="pb-step-icon">&#128200;</div><div><div class="pb-step-title">Quote Comparison</div><div class="pb-step-sub">AI-powered market analysis &#183; Choose your best option</div></div></div>
<div id="pb-quote-loading" style="text-align:center;padding:30px;display:none">
<div class="mga-spinner" style="width:24px;height:24px;margin-bottom:10px"></div>
<div style="font-family:'Share Tech Mono',monospace;font-size:11px;color:#818cf8">Analysing market rates&#8230;</div>
</div>
<div id="pb-quote-content"></div>
<div class="pb-btn-row" id="pb-quote-btn-row" style="display:none">
<button class="btn btn-sec" onclick="pbGoStep(5)">&#8592; Back</button>
<button class="btn btn-primary" style="margin-left:auto" onclick="pbDoSubmit()">&#9989; Proceed with Selected Quote</button>
</div>
</div>
<!-- STEP 7: SUCCESS -->
<div class="pb-step" id="pb-sec-7">
<div style="text-align:center;padding:40px 20px">
<div style="width:60px;height:60px;border-radius:50%;background:rgba(16,185,129,.1);border:2px solid #10b981;display:flex;align-items:center;justify-content:center;font-size:28px;margin:0 auto 16px">&#10003;</div>
<div style="font-size:22px;font-weight:700;color:var(--text);margin-bottom:6px">Submission Accepted</div>
<div style="font-size:12px;color:var(--dim);margin-bottom:20px">Committed to Bronze layer &#183; Pipeline agents queued</div>
<div id="pb-suc-detail" style="background:rgba(255,255,255,.02);border:1px solid var(--border);border-radius:8px;padding:16px;display:inline-block;min-width:320px;text-align:left;margin-bottom:20px"></div>
<div style="display:flex;gap:8px;justify-content:center;flex-wrap:wrap">
<button class="btn btn-primary" onclick="pbReset()">+ New Submission</button>
<button class="btn btn-sec" onclick="switchTab('live');loadSubmissions()">View in Pipeline &#8594;</button>
</div>
</div>
</div>
</div>
<!-- Right panel -->
<div class="pb-panel">
<div class="pb-panel-card" style="border-color:rgba(129,140,248,.2)">
<div class="pb-panel-title" style="color:#818cf8">Submission ID</div>
<div style="font-family:'Share Tech Mono',monospace;font-size:12px;color:#818cf8" id="pb-live-subid">SUB-2025---</div>
</div>
<div class="pb-panel-card"><div class="pb-panel-title">Submitter</div>
<div class="pb-prow"><span class="pb-pk">Name</span><span class="pb-pv" id="pb-lv-bname">--</span></div>
<div class="pb-prow"><span class="pb-pk">Code</span><span class="pb-pv" id="pb-lv-bcode">--</span></div>
<div class="pb-prow"><span class="pb-pk">State</span><span class="pb-pv" id="pb-lv-bstate">--</span></div>
</div>
<div class="pb-panel-card"><div class="pb-panel-title">Insured</div>
<div class="pb-prow"><span class="pb-pk">Name</span><span class="pb-pv" id="pb-lv-iname">--</span></div>
<div class="pb-prow"><span class="pb-pk">DOB</span><span class="pb-pv" id="pb-lv-idob">--</span></div>
<div class="pb-prow"><span class="pb-pk">Credit</span><span class="pb-pv" id="pb-lv-icredit">--</span></div>
</div>
<div class="pb-panel-card"><div class="pb-panel-title">Property</div>
<div class="pb-prow"><span class="pb-pk">Location</span><span class="pb-pv" id="pb-lv-ploc">--</span></div>
<div class="pb-prow"><span class="pb-pk">Type</span><span class="pb-pv" id="pb-lv-ptype">--</span></div>
<div class="pb-prow"><span class="pb-pk">Built</span><span class="pb-pv" id="pb-lv-pbuilt">--</span></div>
</div>
<div class="pb-panel-card"><div class="pb-panel-title">Coverage</div>
<div class="pb-prow"><span class="pb-pk">Type</span><span class="pb-pv" id="pb-lv-ctype">HO-3</span></div>
<div class="pb-prow"><span class="pb-pk">Limit</span><span class="pb-pv" id="pb-lv-climit">--</span></div>
<div class="pb-prow"><span class="pb-pk">Ded.</span><span class="pb-pv" id="pb-lv-cded">--</span></div>
</div>
<div class="pb-panel-card" id="pb-mga-panel" style="display:none;border-color:rgba(129,140,248,.2)">
<div class="pb-panel-title" style="color:#818cf8">MGA Onboarded</div>
<div class="pb-prow"><span class="pb-pk">Name</span><span class="pb-pv" id="pb-mga-name">--</span></div>
<div class="pb-prow"><span class="pb-pk">Code</span><span class="pb-pv" id="pb-mga-code">--</span></div>
<div class="pb-prow"><span class="pb-pk">State</span><span class="pb-pv" id="pb-mga-state">--</span></div>
<div class="pb-prow"><span class="pb-pk">License</span><span class="pb-pv" id="pb-mga-license">--</span></div>
</div>
</div>
</div>
</div>
<!-- MGA ONBOARDING MODAL -->
<div class="mga-overlay" id="mgaOverlay" style="display:none" onclick="if(event.target===this)closeMgaOnboarding()">
<div class="mga-modal">
<div class="mga-header">
<div class="mga-title">&#127974; MGA ONBOARDING</div>
<button class="mga-close" onclick="closeMgaOnboarding()">&#x2715;</button>
</div>
<div style="padding:1.25rem 1.5rem">
<div class="mga-progress">
<div class="mga-prog-step active" id="mga-ps-1">1 - Identity</div>
<div class="mga-prog-step" id="mga-ps-2">2 - Verify</div>
<div class="mga-prog-step" id="mga-ps-3">3 - Details</div>
<div class="mga-prog-step" id="mga-ps-4">4 - Confirm</div>
</div>
<div class="mga-step active" id="mga-s1">
<div style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:4px">Welcome to PolicySense</div>
<div style="font-size:11px;color:var(--dim);margin-bottom:16px">Enter your email and company name to begin MGA onboarding.</div>
<div class="pb-fg" style="gap:10px;margin-bottom:14px">
<div class="pb-field"><label class="pb-label">Work Email <span class="req">*</span></label><input class="pb-input" id="mga_email" type="email" placeholder="john@acmemga.com"></div>
<div class="pb-field"><label class="pb-label">Company Name <span class="req">*</span></label><input class="pb-input" id="mga_company" placeholder="Acme Managing General Agency"></div>
</div>
<button onclick="mgaSearchCompany()" class="btn btn-primary" style="width:100%;justify-content:center">Search Company &#8594;</button>
</div>
<div class="mga-step" id="mga-s2">
<div style="font-size:12px;color:var(--dim);margin-bottom:10px">Select your company from the results below:</div>
<div id="mga-results-list" style="margin-bottom:14px"></div>
<div style="font-size:10px;color:var(--dim);margin-bottom:10px">Not listed? <span style="color:#818cf8;cursor:pointer" onclick="mgaManualEntry()">Enter manually &#8594;</span></div>
<div style="display:flex;gap:8px">
<button onclick="mgaGoStep(1)" class="btn btn-sec" style="flex:1">&#8592; Back</button>
<button onclick="mgaConfirmSelection()" class="btn btn-primary" style="flex:2;justify-content:center">Confirm Selection &#8594;</button>
</div>
</div>
<div class="mga-step" id="mga-s3">
<div style="font-size:12px;color:var(--dim);margin-bottom:12px">Verifying details<span id="mga-searching-dots">...</span></div>
<div class="pb-fg pb-fg2" style="gap:10px;margin-bottom:12px">
<div class="pb-field"><label class="pb-label">Full Legal Name <span class="req">*</span></label><input class="pb-input" id="mga_full_name" placeholder="Acme MGA LLC"></div>
<div class="pb-field"><label class="pb-label">License Number</label><input class="pb-input" id="mga_license" placeholder="MGA-TX-123456"></div>
<div class="pb-field"><label class="pb-label">State <span class="req">*</span></label><select class="pb-select" id="mga_state"><option value="">-- State --</option></select></div>
<div class="pb-field"><label class="pb-label">City <span class="req">*</span></label><input class="pb-input" id="mga_city" placeholder="Austin"></div>
<div class="pb-field"><label class="pb-label">Phone</label><input class="pb-input" id="mga_phone" placeholder="(512) 555-0100"></div>
<div class="pb-field"><label class="pb-label">Lines of Business</label><input class="pb-input" id="mga_lob" placeholder="Personal Lines, Commercial"></div>
</div>
<div style="display:flex;gap:8px">
<button onclick="mgaGoStep(2)" class="btn btn-sec" style="flex:1">&#8592; Back</button>
<button onclick="mgaGoStep(4)" class="btn btn-primary" style="flex:2;justify-content:center">Generate Code &#8594;</button>
</div>
</div>
<div class="mga-step" id="mga-s4">
<div style="font-size:13px;font-weight:600;color:var(--text);margin-bottom:12px">Confirm MGA Profile</div>
<div id="mga-confirm-details" style="margin-bottom:14px;background:rgba(255,255,255,.02);border:1px solid var(--border);border-radius:7px;padding:12px"></div>
<div style="font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:1.5px;color:var(--dim);text-transform:uppercase;margin-bottom:6px">Master Broker Code</div>
<div class="mga-code-badge" id="mga-code-display">MGA---</div>
<div style="font-size:10px;color:var(--dim);margin-bottom:14px">Unique identifier for all PolicySense submissions.</div>
<div style="display:flex;gap:8px">
<button onclick="mgaGoStep(3)" class="btn btn-sec" style="flex:1">&#8592; Edit</button>
<button onclick="mgaComplete()" class="btn btn-primary" style="flex:2;justify-content:center">&#10003; Complete Onboarding</button>
</div>
</div>
</div>
</div>
</div>
<!-- CLAIMSENSE AUDIT MODAL -->
<div class="audit-modal-overlay" id="claimAuditOverlay" style="display:none" onclick="if(event.target===this)closeClaimAuditModal()">
<div class="audit-modal" id="claimAuditModal">
<div class="audit-modal-header">
<div>
<div class="audit-modal-title">πŸ”΄ AXIOM β€” ClaimSense Triage Audit Trail</div>
<div class="audit-modal-sub" id="claimAuditSub">Agent 11 Β· gold_claim_audit_log Β· Full Triage Analysis</div>
</div>
<button class="audit-modal-close" onclick="closeClaimAuditModal()">βœ•</button>
</div>
<div class="audit-modal-body" id="claimAuditBody">
<div style="text-align:center;padding:2rem;color:rgba(255,255,255,.3);font-family:'Share Tech Mono',monospace;font-size:12px;">Loading claim audit data...</div>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// ══════════════════════════════════════════════════════════════════
// CONFIG & STATE
// ══════════════════════════════════════════════════════════════════
let API_BASE = 'https://itnovaml-pcagentinai.hf.space';
let speed = 1;
let running = false;
let timeouts = [];
let selectedId = null;
let selectedSrc = null; // 'demo' | 'live'
let currentTab = 'queue';
let allPolicies = []; // demo + live merged
let liveResult = null; // result from live agent run
// ── DEMO POLICIES (original 5 from v3) ──────────────────────────
const DEMO_POLICIES = [
{
id:'SUB-2025-00491', name:'Sarah Mitchell', loc:'Austin, TX', cov:'HO-3', lim:'$450K', icon:'πŸ‘©',
outcome:'approve', failAt:null, broker:'Acme Brokers', addr:'4821 Oakwood Dr, Austin TX',
kyc:{score:741, ofac:'CLEAR', fraud:'NONE'},
prop:{score:28, band:'LOW', fire:12, flood:5, wind:18, liab:8, rcv:387000},
uw:{lim:450000, ded:2500, end:'["Identity Theft Protection"]'},
pr:{prem:2847, band:'LOW', ci:'[2561, 3133]', shap:'{"credit":-0.34,"roof_age":-0.28,"wind":+0.19}'},
pol:{num:'PC-2025-00491TX', eff:'2025-06-15'},
note:'Excellent credit Β· newer roof Β· low peril scores', failReasons:[], _src:'demo'
},
{
id:'SUB-2025-00587', name:'Marcus Vega', loc:'Malibu, CA', cov:'HO-3', lim:'$1.2M', icon:'πŸ§”',
outcome:'reject', failAt:'kyc', broker:'Delta Insurance Brokers', addr:'7 Sunset Blvd, Malibu CA',
kyc:{score:498, ofac:'PARTIAL_MATCH', fraud:'2_ANOMALIES'},
prop:null, uw:null, pr:null, pol:null,
note:'OFAC hit Β· low credit Β· fraud signals',
failReasons:['OFAC-SDN-001 β€” Partial match on OFAC Specially Designated Nationals list.','CRED-SCORE-002 β€” Credit score 498 below carrier minimum of 550.','FRAUD-ANOM-003 β€” Fraud model detected 2 prior claim irregularities in 18 months.'],
_src:'demo'
},
{
id:'SUB-2025-00612', name:'Priya Nair', loc:'Houston, TX', cov:'HO-5', lim:'$680K', icon:'πŸ‘©β€πŸ’Ό',
outcome:'approve', failAt:null, broker:'Horizon Risk Partners', addr:'1820 Cypress Creek Rd, Houston TX',
kyc:{score:798, ofac:'CLEAR', fraud:'NONE'},
prop:{score:41, band:'MEDIUM', fire:22, flood:38, wind:31, liab:6, rcv:612000},
uw:{lim:680000, ded:5000, end:'["Flood Endorsement Zone AE","Sewer Backup"]'},
pr:{prem:5214, band:'MEDIUM', ci:'[4690, 5738]', shap:'{"flood_zone":+0.41,"prop_age":+0.22,"credit":-0.29}'},
pol:{num:'PC-2025-00612TX', eff:'2025-06-18'},
note:'Flood zone AE Β· medium risk Β· endorsements applied', failReasons:[], _src:'demo'
},
{
id:'SUB-2025-00703', name:'Derek Hoffmann', loc:'Fort Lauderdale, FL', cov:'WC-3', lim:'$900K', icon:'πŸ§‘β€πŸ’»',
outcome:'reject', failAt:'property', broker:'SunCoast Insurance Group', addr:'88 Ocean Drive, Fort Lauderdale FL',
kyc:{score:672, ofac:'CLEAR', fraud:'NONE'},
prop:{score:91, band:'DECLINED', fire:44, flood:88, wind:95, liab:72, rcv:843000},
uw:null, pr:null, pol:null,
note:'Extreme coastal exposure Β· hurricane zone Β· VE flood',
failReasons:['PROP-WIND-001 β€” Coastal wind risk score 95/100 exceeds max of 70.','PROP-FLOOD-002 β€” FEMA Zone VE flood score 88/100 outside carrier appetite.','PROP-LIAB-003 β€” Liability score 72: dock, boat, short-term rental flagged.'],
_src:'demo'
},
{
id:'SUB-2025-00781', name:'Amara Okonkwo', loc:'Phoenix, AZ', cov:'WC-3', lim:'$320K', icon:'πŸ‘©πŸΎ',
outcome:'approve', failAt:null, broker:'Desert Sun Brokers', addr:'3304 Camelback Rd, Phoenix AZ',
kyc:{score:762, ofac:'CLEAR', fraud:'NONE'},
prop:{score:35, band:'LOW', fire:28, flood:4, wind:22, liab:10, rcv:294000},
uw:{lim:320000, ded:2000, end:'["Wildfire Smoke Damage Rider"]'},
pr:{prem:2103, band:'LOW', ci:'[1892, 2314]', shap:'{"credit":-0.31,"wildfire_prox":+0.25,"prop_age":-0.18}'},
pol:{num:'PC-2025-00781AZ', eff:'2025-06-22'},
note:'Desert property Β· minor wildfire flag Β· resolved with rider', failReasons:[], _src:'demo'
}
];
const PERSON_ICONS = ['πŸ‘©','πŸ§”','πŸ‘©β€πŸ’Ό','πŸ§‘β€πŸ’»','πŸ‘©πŸΎ','πŸ‘¨','πŸ‘©πŸ»','πŸ§‘','πŸ‘¨πŸ½','πŸ‘©πŸΏ','πŸ§‘β€πŸ¦±','πŸ‘¨πŸ»'];
const C = {
orch: {c:'#a855f7',bg:'rgba(168,85,247,0.08)',b:'rgba(168,85,247,0.25)'},
kyc: {c:'#00d4ff',bg:'rgba(0,212,255,0.08)', b:'rgba(0,212,255,0.25)'},
prop: {c:'#ff6b35',bg:'rgba(255,107,53,0.08)',b:'rgba(255,107,53,0.25)'},
uw: {c:'#f59e0b',bg:'rgba(245,158,11,0.08)',b:'rgba(245,158,11,0.25)'},
price:{c:'#00ff9d',bg:'rgba(0,255,157,0.08)', b:'rgba(0,255,157,0.25)'},
issue:{c:'#818cf8',bg:'rgba(129,140,248,0.08)',b:'rgba(129,140,248,0.25)'},
broker:{c:'#f472b6',bg:'rgba(244,114,182,0.08)',b:'rgba(244,114,182,0.25)'}
};
const ICONS = {orch:'🧠',kyc:'πŸͺͺ',prop:'🏠',uw:'πŸ“‹',price:'πŸ€–',issue:'πŸ“„',broker:'🏒'};
// ══════════════════════════════════════════════════════════════════
// API + DB STATUS
// ══════════════════════════════════════════════════════════════════
async function checkApiStatus() {
const dot = document.getElementById('dbDot');
const txt = document.getElementById('dbStatusText');
try {
const r = await fetch(`${API_BASE}/status`, {method:'GET', signal:AbortSignal.timeout(3000)});
if (r.ok) {
dot.className='db-dot live';
const d = await r.json();
txt.textContent = `βœ“ Clever Cloud Β· ${d.submissions_count||'?'} submissions Β· ${d.db_host||''}`.replace('btvbbpqhvnttzvptguj3-mysql.services.clever-cloud.com','CC MySQL');
return true;
}
} catch(e){}
dot.className='db-dot err';
txt.textContent = 'API Offline β€” Demo Mode';
return false;
}
async function testConnection() {
document.getElementById('cfgTestResult').textContent = '⏳ Testing...';
const base = document.getElementById('cfgApi').value.trim();
if (base) API_BASE = base;
const ok = await checkApiStatus();
document.getElementById('cfgTestResult').textContent = ok ? 'βœ“ Connected!' : 'βœ— Could not connect';
document.getElementById('cfgTestResult').style.color = ok ? 'var(--success)' : 'var(--danger)';
if (ok) {
// Auto-load live submissions on successful connect
currentTab = 'live';
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab')[1].classList.add('active');
await loadSubmissions();
}
}
// ══════════════════════════════════════════════════════════════════
// LOAD SUBMISSIONS FROM BRONZE (via Flask API)
// ══════════════════════════════════════════════════════════════════
async function loadSubmissions() {
const reloadBtn = document.getElementById('reloadBtn');
reloadBtn.disabled = true;
reloadBtn.textContent = '⏳ Loading...';
document.getElementById('policyQueue').innerHTML = '<div class="queue-empty">Fetching from Bronze layer...</div>';
let livePolicies = [];
try {
const r = await fetch(`${API_BASE}/submissions/recent?limit=10`, {signal:AbortSignal.timeout(4000)});
const data = await r.json();
if (r.ok && data.submissions) {
livePolicies = data.submissions.map(normalizeLiveSub);
console.log(`[loadSubmissions] Loaded ${livePolicies.length} live submissions`);
} else {
console.warn(`[loadSubmissions] API error:`, data.error || data);
}
} catch(e) {
console.warn('[loadSubmissions] Fetch failed:', e.message);
// API not available β€” use demo data only
}
// Merge: live submissions first, then demo
const tab = currentTab;
if (tab === 'live') {
allPolicies = livePolicies.length ? livePolicies : [];
} else if (tab === 'demo') {
allPolicies = DEMO_POLICIES;
} else {
// queue: show live submissions from DB first, append demos for any gaps
allPolicies = livePolicies.length
? [...livePolicies, ...DEMO_POLICIES.filter(d => !livePolicies.find(l => l.id === d.id))]
: DEMO_POLICIES;
}
document.getElementById('batchCount').textContent = allPolicies.length;
renderQueue();
reloadBtn.disabled = false;
reloadBtn.textContent = 'β†Ί Refresh';
checkApiStatus();
}
function normalizeLiveSub(raw) {
// Confirmed schema from /debug/tables:
// raw_payload.insured β†’ full_name, credit_score, kyc_status, dob
// raw_payload.property β†’ city, state, state_code, street, year_built, prop_risk_score, risk_band
// raw_payload.policy_request β†’ coverage_type, limit, deductible
// raw_payload.agent_results β†’ kyc_status, risk_band, final_outcome, premium, policy_number
// DB columns (top-level in raw): submission_id, coverage_type_code, requested_coverage_limit,
// final_outcome, pipeline_status, halt_reason, broker_code, broker_name
const payload = raw.raw_payload || {};
const insured = payload.insured || {};
const prop = payload.property || {};
const policy = payload.policy_request || {};
const agents = payload.agent_results || {};
// Name from DB-merged insured.full_name
const fullName = insured.full_name || `Applicant ${(raw.submission_id||'').slice(-5)}`;
// Location β€” confirmed: prop.city, prop.state (set from prop.state_code by API)
const city = prop.city || '?';
const state = prop.state || prop.state_code || '';
const loc = state ? `${city}, ${state}` : city;
const addr = prop.street ? `${prop.street}, ${city} ${state}`.trim() : loc;
// Coverage
const covCode = raw.coverage_type_code || policy.coverage_type || 'HO-3';
const limNum = parseFloat(raw.requested_coverage_limit || policy.limit || 300000);
const limStr = limNum >= 1000000
? `$${(limNum/1000000).toFixed(1)}M`
: `$${Math.round(limNum/1000)}K`;
// Pipeline outcome
const pipeStatus = raw.pipeline_status || '';
const outcome = raw.final_outcome === 'APPROVED' ? 'approve'
: raw.final_outcome === 'DECLINED' ? 'reject'
: agents.final_outcome === 'APPROVED' ? 'approve'
: agents.final_outcome === 'DECLINED' ? 'reject'
: null;
const failAt = pipeStatus === 'KYC_DECLINED' ? 'kyc'
: pipeStatus === 'PROP_DECLINED' ? 'property'
: agents.kyc_status === 'KYC_FAIL' ? 'kyc'
: null;
// KYC β€” credit_score lives in raw_payload.insured.credit_score (confirmed)
const credit = insured.credit_score || 0;
const ofac = (agents.kyc_status === 'KYC_PASS' || insured.kyc_status === 'KYC_PASS')
? 'CLEAR' : 'CLEAR';
// Property risk if already processed
const riskScore = prop.prop_risk_score;
const riskBand = prop.risk_band || agents.risk_band;
const propObj = riskScore != null ? {
score: riskScore, band: riskBand || 'MEDIUM',
fire: Math.round(riskScore * 0.55), flood: Math.round(riskScore * 0.50),
wind: Math.round(riskScore * 0.60), liab: Math.round(riskScore * 0.35),
rcv: Math.round(limNum * 0.88)
} : null;
// If already issued β€” populate result fields
const prem = agents.premium;
const polNum = agents.policy_number;
const prObj = prem != null ? { prem, band: riskBand||'MEDIUM', ci:'[0,0]', shap:'{}' } : null;
const uwObj = outcome === 'approve' ? { lim: limNum, ded: raw.requested_deductible||1000, end:'[]' } : null;
const polObj = polNum ? { num: polNum, eff: (raw.submitted_at||'').slice(0,10) } : null;
const submDate = raw.submitted_at ? new Date(raw.submitted_at).toLocaleDateString() : '';
return {
id: raw.submission_id,
name: fullName,
loc, cov: covCode, lim: limStr,
icon: PERSON_ICONS[Math.abs(hashStr(raw.submission_id||'x')) % PERSON_ICONS.length],
outcome, failAt,
broker: raw.broker_name || raw.broker_code || 'PolicyBridge Broker',
addr,
kyc: { score: credit, ofac, fraud: 'NONE' },
prop: propObj,
uw: uwObj,
pr: prObj,
pol: polObj,
note: `Bronze Β· ${pipeStatus||'RECEIVED'} Β· ${submDate}`,
failReasons: raw.halt_reason ? [raw.halt_reason] : [],
_src: 'live',
_raw: raw
};
}
function hashStr(s) {
let h = 0;
for (let i = 0; i < s.length; i++) h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
return h;
}
// ══════════════════════════════════════════════════════════════════
// RENDER QUEUE
// ══════════════════════════════════════════════════════════════════
function renderQueue() {
const wrap = document.getElementById('policyQueue');
wrap.innerHTML = '';
if (!allPolicies.length) {
const isLiveTab = currentTab === 'live';
wrap.innerHTML = `<div class="queue-empty">
${isLiveTab
? '⚠ No submissions returned from API.<br><small style="color:var(--dim2);margin-top:6px;display:block">Check browser Console (F12) for the exact error.<br>API is online β€” the SQL query may be failing. Check Flask terminal for logs.</small>'
: 'No submissions found. Check API connection or switch to Demo tab.'}
</div>`;
return;
}
allPolicies.forEach(p => {
const b = document.createElement('button');
b.id = 'pcb-'+p.id;
const isLive = p._src === 'live';
let cls = 'pcb';
if (p._result === 'approve') cls += ' rpass';
else if (p._result === 'reject') cls += ' rfail';
if (isLive) cls += ' live-sub';
if (selectedId === p.id) cls += ' selected';
b.className = cls;
b.onclick = () => selectPolicy(p.id);
const icon = p._result === 'approve' ? 'βœ…' : p._result === 'reject' ? '❌' : isLive ? '⏳' : '⏳';
b.innerHTML = `
<div class="pcb-top">
<div class="pcb-id">${p.id.replace('SUB-2024-','#').replace('SUB-2025-','#')}</div>
<div style="display:flex;gap:4px;align-items:center">
${isLive ? '<span class="pcb-source">LIVE</span>' : ''}
<div class="pcb-result">${icon}</div>
</div>
</div>
<div class="pcb-name">${p.icon} ${p.name}</div>
<div class="pcb-loc">πŸ“ ${p.loc}</div>
<div class="pcb-tags"><span class="pct pct-cov">${p.cov}</span><span class="pct pct-lim">${p.lim}</span></div>
<div class="pcb-sel-dot"></div>`;
wrap.appendChild(b);
});
}
// ══════════════════════════════════════════════════════════════════
// SELECT POLICY
// ══════════════════════════════════════════════════════════════════
function selectPolicy(id) {
if (running) return;
selectedId = id;
liveResult = null;
const p = allPolicies.find(x => x.id === id);
selectedSrc = p._src;
renderQueue();
document.getElementById('selInfo').innerHTML = `Selected: <span>${p.icon} ${p.name}</span>` +
(p._src==='live' ? ' <span class="live-badge">LIVE</span>' : '');
// Show appropriate run button
const isLive = p._src === 'live';
document.getElementById('startBtn').style.display = isLive ? 'none' : 'flex';
document.getElementById('liveBtn').style.display = isLive ? 'flex' : 'none';
document.getElementById('startBtn').disabled = isLive;
document.getElementById('liveBtn').disabled = !isLive;
// Show agent LIVE tags
['kyc','prop','uw','price'].forEach(a => {
document.getElementById(`tag-${a}`).style.display = isLive ? 'inline-flex' : 'none';
});
resetChat(p);
}
function resetChat(p) {
const isLive = p._src === 'live';
document.getElementById('chatArea').innerHTML = `
<div style="text-align:center;padding:50px 20px;color:var(--dim2)">
<div style="font-size:36px;margin-bottom:8px">${p.icon}</div>
<div style="font-size:14px;font-weight:700;color:var(--text)">${p.name}</div>
<div style="font-size:11px;margin-top:3px">πŸ“ ${p.addr}</div>
<div style="margin-top:10px;display:flex;gap:7px;justify-content:center;flex-wrap:wrap">
<span class="chip info">${p.cov}</span>
<span class="chip warn">Limit: ${p.lim}</span>
${isLive
? '<span class="chip amber">⚑ LIVE SUBMISSION β€” Agents not yet run</span>'
: `<span class="chip ${p.outcome==='reject'?'fail':'pass'}">${p.outcome==='reject'?'⚠ High Risk':'βœ“ Within Appetite'}</span>`}
</div>
<div style="font-size:11px;margin-top:8px;font-family:\'Share Tech Mono\',monospace;color:var(--dim2)">${p.note}</div>
<div style="font-size:12px;margin-top:12px;color:${isLive?'var(--neon5)':'var(--neon)'};opacity:0.8">
${isLive
? '⚑ Press <strong>Run Live Agents</strong> to call your trained XGBoost models in real-time'
: 'Press β–Ά Run Simulation to process through all agents'}
</div>
</div>`;
}
// ══════════════════════════════════════════════════════════════════
// TAB SWITCHING
// ══════════════════════════════════════════════════════════════════
// switchTab is defined below β€” handles all tabs including policybridge, radar, claims
// This stub ensures backward compat for any early calls
function _switchTabOld(tab) {
currentTab = tab;
document.querySelectorAll('.tab').forEach((el,i) => {
el.classList.toggle('active', ['queue','live','demo'][i] === tab);
});
const titles = {queue:'Recent Policy Submissions', live:'Live Submissions', demo:'Demo Scenarios'};
const subs = {queue:'Loaded from Bronze layer', live:'Real-time from Bronze MySQL', demo:'Static test cases'};
const qTitle = document.getElementById('queueTitle');
const qSub = document.getElementById('queueSub');
if (qTitle && titles[tab]) qTitle.textContent = titles[tab];
if (qSub && subs[tab]) qSub.textContent = subs[tab];
}
// ══════════════════════════════════════════════════════════════════
// LIVE AGENT EXECUTION (calls Flask API β†’ trained models)
// ══════════════════════════════════════════════════════════════════
async function runLiveAgents() {
if (running || !selectedId) return;
const p = allPolicies.find(x => x.id === selectedId);
if (!p || p._src !== 'live') return;
running = true;
document.getElementById('liveBtn').disabled = true;
liveResult = null;
const chat = document.getElementById('chatArea');
chat.innerHTML = '';
// Reset UI
document.getElementById('prog').style.width = '0%';
document.getElementById('prog').classList.remove('fail');
for (let i=0;i<=6;i++){const e=document.getElementById('tl-'+i);e.classList.remove('active','done','failed')}
document.getElementById('tlProg').style.width = '0%';
resetAgents();
setStat('orch','processing','● ACTIVE');
document.getElementById('ac-orch').classList.add('active');
addPhase('STEP 1 · BRONZE LAYER', '⚑ Live Submission Processing', `${p.cov} from Bronze MySQL · Invoking trained agents`, 0);
addOrch(`πŸ“₯ Submission ${p.id} loaded from Bronze layer. Running full 6-agent pipeline with trained XGBoost models + AXIOM audit...`);
updateProg(12);
try {
// Step 1: Call Flask API β†’ Agent 1 (KYC)
addPhase('STEP 2 Β· AGENT 1', 'πŸͺͺ KYC Agent β€” Live XGBoost Inference', 'agent1_kyc_classifier.pkl Β· Credit score Β· OFAC Β· Fraud signals', 1);
activateAgent('kyc');
addLiveCallBox('agent1_ssn_kyc.py', `Running KYC classifier for ${p.id}...`);
await sleep(600/speed);
const kycResp = await callAgent('kyc', p.id);
removeLiveCallBox();
updateProg(28);
if (!kycResp.ok) throw new Error('API call failed');
const kycData = await kycResp.json();
window._kycData = kycData;
addAgentMsg('kyc', 'πŸͺͺ', 'KYC Agent', kycData.status === 'KYC_PASS' ? 'RESULT' : '🚨 ALERT',
kycData.status === 'KYC_PASS'
? `Identity validated. Credit: ${kycData.credit_score}. OFAC: ${kycData.ofac_result}. Fraud: ${kycData.fraud_signals}. All KYC checks passed.`
: `β›” ${kycData.decline_reason}`,
`<span class="k">"kyc_status"</span><span class="p">:</span> <span class="${kycData.status==='KYC_PASS'?'vp':'vf'}">${kycData.status}</span><span class="p">,</span>\n<span class="k">"credit_score"</span><span class="p">:</span> <span class="vn">${kycData.credit_score}</span><span class="p">,</span>\n<span class="k">"ml_probability"</span><span class="p">:</span> <span class="vn">${(kycData.ml_kyc_probability||0).toFixed(3)}</span><span class="p">,</span>\n<span class="k">"ofac_result"</span><span class="p">:</span> <span class="vs">"${kycData.ofac_result}"</span>`,
`s3://pcins-silver/kyc_decisions/${p.id}_kyc.json`, true);
if (kycData.status !== 'KYC_PASS') {
await finishDeclined(p, 'kyc', kycData.decline_reason, [kycData.decline_reason]);
liveResult = kycData;
return;
}
addOrch(`KYC_PASS confirmed. ML probability: ${(kycData.ml_kyc_probability||0).toFixed(3)}. Routing to Property Risk Agent.`);
await sleep(400/speed);
// Step 2: Agent 2 (Property Risk)
addPhase('STEP 3 Β· AGENT 2', '🏠 Property Risk Agent β€” Live XGBoost Inference', 'agent2_property_risk.pkl Β· Peril scores Β· Geo-spatial analysis', 2);
activateAgent('prop');
addLiveCallBox('agent2_property_risk.py', `Scoring peril risks for ${p.addr}...`);
await sleep(700/speed);
const propResp = await callAgent('property', p.id);
removeLiveCallBox();
updateProg(46);
const propData = await propResp.json();
window._propData = propData;
const perils = propData.peril_scores || {};
addAgentMsg('prop', '🏠', 'Property Risk Agent', propData.status==='RISK_ACCEPTABLE'?'RESULT':'🚨 DECLINE',
propData.status==='RISK_ACCEPTABLE'
? `Risk scored. Overall: ${perils.overall_risk}/100 β€” Band: ${propData.risk_band}. All perils within carrier appetite.`
: `β›” ${propData.decline_reason}`,
`<span class="k">"status"</span><span class="p">:</span> <span class="${propData.status==='RISK_ACCEPTABLE'?'vp':'vf'}">${propData.status}</span><span class="p">,</span>\n<span class="k">"risk_band"</span><span class="p">:</span> <span class="vp">${propData.risk_band||'N/A'}</span><span class="p">,</span>\n<span class="k">"peril_scores"</span><span class="p">:</span> <span class="p">{</span><span class="k">"wind"</span><span class="p">:</span><span class="vn">${perils.wind_score||0}</span><span class="p">,</span><span class="k">"flood"</span><span class="p">:</span><span class="vn">${perils.flood_score||0}</span><span class="p">,</span><span class="k">"fire"</span><span class="p">:</span><span class="vn">${perils.fire_score||0}</span><span class="p">}</span><span class="p">,</span>\n<span class="k">"acceptability_prob"</span><span class="p">:</span> <span class="vn">${(propData.risk_acceptability_prob||0).toFixed(3)}</span>`,
`s3://pcins-silver/property_risk/${p.id}_property.json`, true);
if (propData.status === 'RISK_DECLINED') {
await finishDeclined(p, 'property', propData.decline_reason, [propData.decline_reason]);
liveResult = propData;
return;
}
addOrch(`Property risk ACCEPTABLE. Band: ${propData.risk_band}. Overall score: ${perils.overall_risk}/100. Routing to UW Agent.`);
await sleep(400/speed);
// Step 3: Agent 3 (Underwriting)
addPhase('STEP 4 Β· AGENT 3', 'πŸ“‹ Underwriting Agent β€” Live XGBoost + Rules Engine', 'agent3_underwriting.pkl Β· Carrier guidelines Β· State compliance', 3);
activateAgent('uw');
addLiveCallBox('agent3_underwriting.py', `Applying underwriting guidelines for ${p.id}...`);
await sleep(600/speed);
const uwResp = await callAgent('underwriting', p.id);
removeLiveCallBox();
updateProg(63);
const uwData = await uwResp.json();
window._uwData = uwData;
addAgentMsg('uw', 'πŸ“‹', 'Underwriting Agent', uwData.status==='UW_APPROVED'?'DECISION':'🚨 DECLINE',
uwData.status==='UW_APPROVED'
? `UW guidelines applied. Coverage ${uwData.coverage_type} β€” APPROVED. ELR: ${uwData.expected_loss_ratio}. Compliance clear.`
: `β›” ${uwData.primary_decline_reason}`,
`<span class="k">"uw_decision"</span><span class="p">:</span> <span class="${uwData.status==='UW_APPROVED'?'vp':'vf'}">${uwData.status}</span><span class="p">,</span>\n<span class="k">"uw_probability"</span><span class="p">:</span> <span class="vn">${(uwData.uw_approval_probability||0).toFixed(3)}</span><span class="p">,</span>\n<span class="k">"coverage_type"</span><span class="p">:</span> <span class="vs">"${uwData.coverage_type||'?'}"</span><span class="p">,</span>\n<span class="k">"reinsurance_required"</span><span class="p">:</span> <span class="${uwData.reinsurance_required?'vw':'vp'}">${uwData.reinsurance_required}</span>`,
`s3://pcins-silver/uw_decisions/${p.id}_uw.json`, true);
if (uwData.status === 'UW_DECLINED') {
await finishDeclined(p, 'uw', uwData.primary_decline_reason, uwData.all_rule_violations||[uwData.primary_decline_reason]);
liveResult = uwData;
return;
}
addOrch(`UW APPROVED. Probability: ${(uwData.uw_approval_probability||0).toFixed(3)}. Routing to ML Pricing Agent.`);
await sleep(400/speed);
// Step 4: Agent 4 (Pricing)
addPhase('STEP 5 Β· AGENT 4', 'πŸ€– ML Pricing Agent β€” XGBoost + GLM Ensemble', 'agent4_pricing.pkl Β· Actuarial formula Β· 95% confidence interval', 4);
activateAgent('price');
addLiveCallBox('agent4_pricing.py', `Running XGBoost + GLM ensemble pricing for ${p.id}...`);
await sleep(800/speed);
const pricResp = await callAgent('pricing', p.id);
removeLiveCallBox();
updateProg(80);
const pricData = await pricResp.json();
window._pricData = pricData;
const ci = pricData.confidence_interval || {};
const brkdn = pricData.premium_breakdown || {};
addAgentMsg('price', 'πŸ€–', 'ML Pricing Agent', 'INFERENCE',
`SageMaker endpoint invoked. XGBoost + GLM ensemble complete. Annual premium: $${(pricData.final_premium||0).toLocaleString()}. Actuarial base: $${(pricData.actuarial_premium||0).toLocaleString()}.`,
`<span class="k">"final_premium"</span><span class="p">:</span> <span class="vn">${pricData.final_premium||0}</span><span class="p">,</span>\n<span class="k">"monthly_premium"</span><span class="p">:</span> <span class="vn">${pricData.monthly_premium||0}</span><span class="p">,</span>\n<span class="k">"confidence_95"</span><span class="p">:</span> <span class="vs">[$${(ci.lo_95||0).toLocaleString()}, $${(ci.hi_95||0).toLocaleString()}]</span><span class="p">,</span>\n<span class="k">"credit_modifier"</span><span class="p">:</span> <span class="vn">${brkdn.credit_modifier||1}</span><span class="p">,</span>\n<span class="k">"risk_modifier"</span><span class="p">:</span> <span class="vn">${brkdn.risk_modifier||1}</span>`,
`s3://pcins-silver/premium_predictions/${p.id}_pricing.json`, true);
await sleep(400/speed);
// Step 5: Agent 5 (Issuance)
addPhase('STEP 6 Β· AGENT 5', 'πŸ“„ Policy Issuance β€” Binding & Delivery', 'Deterministic Β· Policy number Β· Document generation Β· Gold Layer ETL', 5);
activateAgent('issue');
addLiveCallBox('agent5_issuance_orchestrator.py', `Binding policy and generating documents for ${p.id}...`);
await sleep(700/speed);
const issResp = await callAgent('issuance', p.id);
removeLiveCallBox();
updateProg(95);
const issData = await issResp.json();
window._issData = issData;
const polNum = issData.policy_number || 'PC-LIVE-'+p.id.slice(-5);
const polDets = issData.policy_details || {};
const effDate = polDets.effective_date || new Date().toISOString().slice(0,10);
addAgentMsg('issue', 'πŸ“„', 'Issuance Agent', 'BINDING',
`Policy bound. ${polNum} issued. Documents generated. Delivered via SES. SNS lifecycle event published. Gold Layer ETL triggered.`,
`<span class="k">"policy_number"</span><span class="p">:</span> <span class="vs">"${polNum}"</span><span class="p">,</span>\n<span class="k">"effective_date"</span><span class="p">:</span> <span class="vs">"${effDate}"</span><span class="p">,</span>\n<span class="k">"annual_premium"</span><span class="p">:</span> <span class="vn">${polDets.annual_premium||pricData.final_premium||0}</span><span class="p">,</span>\n<span class="k">"status"</span><span class="p">:</span> <span class="vp">ISSUED</span>`,
`s3://pcins-silver/issued_policies/${polNum}/`, true);
addOrch(`Pipeline complete. Policy ${polNum} issued. Gold Layer updated. Broker notified.`);
await sleep(500/speed);
updateProg(97);
// Step 6: Agent 6 (Audit β€” AXIOM)
// Build local fallback first β€” guarantees modal/export always have data
var localApproveAudit = {
submission_id: p.id,
decision: 'APPROVED',
overall_summary: `The application from ${p.name} for ${p.loc} has been approved. The property was assessed as ${propData.risk_band||'LOW'} risk. KYC and compliance screening passed. Underwriting approved at ${((uwData.uw_approval_probability||0.82)*100).toFixed(0)}% confidence. Annual premium of $${(pricData.final_premium||0).toLocaleString()} calculated. Policy ${polNum} issued.`,
decline_reasons: [],
decision_factors: [
{agent:'Document Validation Agent', factor:'Identity & Compliance Check', outcome:'PASS',
detail:`Credit score: ${kycData.credit_score||0}. OFAC: ${kycData.ofac_result||'CLEAR'}. Fraud signals: ${kycData.fraud_signals||0}. KYC probability: ${((kycData.ml_kyc_probability||0.85)*100).toFixed(1)}%.`},
{agent:'Property Risk Agent', factor:'Peril Risk Assessment', outcome:'PASS',
detail:`Risk band: ${propData.risk_band||'LOW'}. Wind: ${(propData.peril_scores||{}).wind_score||0} | Flood: ${(propData.peril_scores||{}).flood_score||0} | Fire: ${(propData.peril_scores||{}).fire_score||0}. Overall: ${(propData.peril_scores||{}).overall_risk||0}/100.`},
{agent:'Underwriting Agent', factor:'AI Underwriting Decision', outcome:'APPROVED',
detail:`Decision: UW_APPROVED. Approval probability: ${((uwData.uw_approval_probability||0.82)*100).toFixed(1)}%. Expected loss ratio: ${(uwData.expected_loss_ratio||0.58).toFixed(2)}. Coverage: ${uwData.coverage_type||'HO-3'}.`},
{agent:'Pricing Agent', factor:'Actuarial Premium Calculation', outcome:'CALCULATED',
detail:`Annual premium: $${(pricData.final_premium||0).toLocaleString()} ($${(pricData.monthly_premium||0).toLocaleString()}/mo). 95% CI: $${(ci.lo_95||0).toLocaleString()} – $${(ci.hi_95||0).toLocaleString()}.`},
{agent:'Issuance Agent', factor:'Policy Issuance', outcome:'ISSUED',
detail:`Policy: ${polNum}. Effective: ${effDate}. Documents: declarations_page.pdf, policy_contract.pdf.`}
],
risk_band: propData.risk_band || 'LOW',
final_premium: pricData.final_premium || 0,
policy_number: polNum,
audit_score: 85,
audit_log_id: 'N/A',
audited_at: new Date().toISOString(),
_source: 'local_approved'
};
window._currentAuditData = localApproveAudit;
window._currentPipelineData = p;
// Mark all agents done FIRST β€” UI responsive before audit API call
['kyc','prop','uw','price'].forEach(a => { document.getElementById(`ac-${a}`).classList.remove('active'); document.getElementById(`ac-${a}`).classList.add('done'); setStat(a,'complete','βœ“ COMPLETE'); });
document.getElementById('ac-issue').classList.remove('active'); document.getElementById('ac-issue').classList.add('done'); setStat('issue','complete','βœ“ ISSUED');
document.getElementById('ac-orch').classList.remove('active'); setStat('orch','complete','βœ“ COMPLETE');
for (let i=0;i<=6;i++){document.getElementById('tl-'+i).classList.remove('active');document.getElementById('tl-'+i).classList.add('done')}
document.getElementById('tlProg').style.width='calc(100% - 76px)';
// ── AGENT 6: AUDIT (runs synchronously in pipeline) ─────────
addPhase('STEP 7 Β· AGENT 6', 'πŸ” Audit Agent β€” AXIOM Decision Trail', 'agent6_audit.py Β· Plain-English Summary Β· gold_audit_log', undefined);
activateAgent('audit');
addLiveCallBox('agent6_audit.py', `Generating AXIOM audit trail for ${p.id}...`);
await sleep(500/speed);
try {
const _acA = new AbortController();
const _tA = setTimeout(()=>_acA.abort(), 10000);
const auditResp = await fetch(`${API_BASE}/agent/audit`, {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({submission_id: p.id}),
signal: _acA.signal
});
clearTimeout(_tA);
removeLiveCallBox();
const apiAudit = await auditResp.json().catch(()=>({}));
if (apiAudit && apiAudit.overall_summary && apiAudit.overall_summary.length > 20) {
window._currentAuditData = Object.assign({}, localApproveAudit, {
overall_summary: apiAudit.overall_summary,
decision_factors: (apiAudit.decision_factors||[]).length > 0 ? apiAudit.decision_factors : localApproveAudit.decision_factors,
audit_score: apiAudit.audit_score || 85,
audit_log_id: apiAudit.audit_log_id || 'N/A',
audited_at: apiAudit.audited_at || new Date().toISOString(),
});
}
} catch(auditErr) {
removeLiveCallBox();
console.warn('[AUDIT] Using local fallback:', auditErr.message);
}
const fa = window._currentAuditData;
addAgentMsg('audit', 'πŸ”', 'Audit Agent (AXIOM)', 'AUDIT COMPLETE',
fa.overall_summary,
`<span class="k">"decision"</span><span class="p">:</span> <span class="vp">APPROVED</span><span class="p">,</span>\n<span class="k">"audit_score"</span><span class="p">:</span> <span class="vn">${fa.audit_score}</span><span class="p">,</span>\n<span class="k">"audit_log_id"</span><span class="p">:</span> <span class="vn">${fa.audit_log_id}</span><span class="p">,</span>\n<span class="k">"factors"</span><span class="p">:</span> <span class="vn">${fa.decision_factors.length}</span>`,
`gold_audit_log Β· submission_id=${p.id}`, true);
addOrch(`Audit complete. APPROVED. Score: ${fa.audit_score}/100. Log id: ${fa.audit_log_id}.`);
await sleep(300/speed);
updateProg(100);
document.getElementById('ac-audit').classList.remove('active');
document.getElementById('ac-audit').classList.add('done');
setStat('audit','complete','βœ“ AUDITED');
['kyc','prop','uw','price'].forEach(a=>{document.getElementById(`ac-${a}`).classList.remove('active');document.getElementById(`ac-${a}`).classList.add('done');setStat(a,'complete','βœ“ COMPLETE');});
document.getElementById('ac-issue').classList.remove('active');document.getElementById('ac-issue').classList.add('done');setStat('issue','complete','βœ“ ISSUED');
document.getElementById('ac-orch').classList.remove('active');setStat('orch','complete','βœ“ COMPLETE');
for(let i=0;i<=6;i++){document.getElementById('tl-'+i).classList.remove('active');document.getElementById('tl-'+i).classList.add('done');}
document.getElementById('tlProg').style.width='calc(100% - 76px)';
liveResult = issData;
p._result = 'approve';
renderQueue();
// Live result card
const e = document.createElement('div'); e.className='rc app';
e.innerHTML=`
<div class="rchead">
<div>
<div class="rcidl">POLICY NUMBER</div>
<div class="rcid">${polNum}</div>
<div style="font-size:10px;color:var(--dim2);margin-top:2px;font-family:'Share Tech Mono',monospace">${p.name} Β· ${p.loc}</div>
<div class="live-result-tag">⚑ LIVE AGENT RESULT · XGBoost Inference</div>
</div>
<div style="text-align:right">
<div class="rcbadge">βœ“ ISSUED</div>
<div style="font-size:9px;color:var(--dim2);font-family:'Share Tech Mono',monospace;margin-top:4px">All 6 agents passed βœ“ Audited</div>
<div style="font-size:9px;color:var(--dim2);font-family:'Share Tech Mono',monospace;margin-top:2px">${new Date().toLocaleString()}</div>
</div>
</div>
<div class="rcgrid">
<div class="rcs"><div class="rl">Coverage</div><div class="rv" style="font-size:12px">${p.cov}</div></div>
<div class="rcs"><div class="rl">Limit</div><div class="rv">${p.lim}</div></div>
<div class="rcs"><div class="rl">Annual Premium</div><div class="rv g">$${(pricData.final_premium||0).toLocaleString()}</div></div>
<div class="rcs"><div class="rl">Monthly</div><div class="rv g">$${(pricData.monthly_premium||0).toLocaleString()}</div></div>
<div class="rcs"><div class="rl">Risk Band</div><div class="rv g">${propData.risk_band||'LOW'}</div></div>
<div class="rcs"><div class="rl">Effective Date</div><div class="rv" style="font-size:11px">${effDate}</div></div>
</div>
<div style="margin-top:12px;display:flex;gap:5px;flex-wrap:wrap">
<span class="chip pass">βœ“ KYC PASS</span>
<span class="chip pass">βœ“ PROPERTY ${propData.risk_band||'OK'}</span>
<span class="chip pass">βœ“ UW APPROVED</span>
<span class="chip pass">βœ“ ML PRICING LIVE</span>
<span class="chip amber">⚑ XGBoost Inference</span>
<span class="chip info">πŸ“¦ S3 Docs</span>
<span class="chip purple">πŸ“Š Gold Layer Updated</span>
<span class="chip" style="background:rgba(244,114,182,.15);color:#f472b6;border:1px solid rgba(244,114,182,.3)">πŸ” AXIOM Audited</span>
</div>
<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;">
<button class="btn-view-audit" onclick="openAuditModal(window._currentAuditData||{},window._currentPipelineData||{})">πŸ” View Audit Trail</button>
<button class="btn-view-audit" style="color:#00d4ff;border-color:rgba(0,212,255,.35);background:rgba(0,212,255,.08);" onclick="exportFullAnalysis(window._currentAuditData||{},window._currentPipelineData||{})">⬇ Export Full Analysis</button>
</div>`;
document.getElementById('chatArea').appendChild(e);
setTimeout(()=>e.classList.add('v'),50);
liveResult = issData;
p._result = 'approve';
renderQueue();
} catch(err) {
removeLiveCallBox();
console.error('Live agent error:', err);
const errBox = document.createElement('div');
errBox.style.cssText='background:rgba(255,77,77,.08);border:1px solid rgba(255,77,77,.3);border-radius:9px;padding:16px;font-family:Share Tech Mono,monospace;font-size:11px;color:var(--danger)';
errBox.innerHTML=`<div style="font-size:13px;margin-bottom:8px">β›” API Connection Error</div>
<div style="color:var(--text);font-size:11px;margin-bottom:10px">${err.message}</div>
<div style="color:var(--dim2);font-size:10px">Make sure the Flask API is running:<br><br>
<span style="color:var(--neon)">cd D:\\Personal\\Usecase\\Python\\agents</span><br>
<span style="color:var(--neon)">python api_server.py</span><br><br>
Then click β†Ί Reset and try again.</div>`;
document.getElementById('chatArea').appendChild(errBox);
} finally {
// Always unlock UI in case something unexpected happened
running = false;
document.getElementById('liveBtn').disabled = false;
}
}
async function callAgent(agentName, submissionId) {
return fetch(`${API_BASE}/agent/${agentName}`, {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({submission_id: submissionId}),
signal: AbortSignal.timeout(15000)
});
}
function sleep(ms) { return new Promise(r => timeouts.push(setTimeout(r, ms))); }
// ══════════════════════════════════════════════════════════════════
// CHAT HELPERS
// ══════════════════════════════════════════════════════════════════
function addPhase(badge, title, desc, tl, isLive=false) {
const e = document.createElement('div'); e.className='ph';
e.innerHTML=`<span class="pbadge${isLive?' live':''}">${badge}</span><div><div class="ptitle">${title}</div><div class="pdesc">${desc}</div></div>`;
document.getElementById('chatArea').appendChild(e);
setTimeout(()=>e.classList.add('v'),50);
if (tl!==undefined) tlSet(tl);
}
function addOrch(text) {
const e = document.createElement('div'); e.className='ob';
e.innerHTML=`<span class="obicon">🧠</span><div><div class="obt">ORCHESTRATOR · STEP FUNCTIONS</div><div class="oba">${text}</div></div>`;
document.getElementById('chatArea').appendChild(e);
setTimeout(()=>e.classList.add('v'),50);
}
function addLiveCallBox(script, text) {
const e = document.createElement('div'); e.className='live-call-box'; e.id='liveCallBox';
e.innerHTML=`<span class="lcb-icon">⚑</span><div style="flex:1"><div class="lcb-title">Calling Trained Model · ${script}</div><div class="lcb-text">${text}</div></div><div class="lcb-spinner"></div>`;
document.getElementById('chatArea').appendChild(e);
setTimeout(()=>e.classList.add('v'),50);
e.scrollIntoView({behavior:'smooth',block:'nearest'});
}
function removeLiveCallBox() {
const e = document.getElementById('liveCallBox');
if (e) e.remove();
}
function addAgentMsg(from, icon, name, tag, text, code, s3, isLive=false) {
const c = C[from] || C.orch;
const t = new Date();
const ts = t.getHours().toString().padStart(2,'0')+':'+t.getMinutes().toString().padStart(2,'0')+':'+t.getSeconds().toString().padStart(2,'0');
const e = document.createElement('div'); e.className='msg';
e.innerHTML=`
<div class="mav" style="background:${c.bg};border-color:${c.b};color:${c.c}">${icon}</div>
<div class="mbody">
<div class="mfrom"><strong style="color:${c.c}">${name}</strong><span class="mtime">${ts}</span></div>
<div class="mbub" style="background:${c.bg};border-color:${c.b}">
<span class="mtag" style="color:${c.c};border:1px solid ${c.b}">${tag}</span>${text}
${isLive?`<span class="model-badge">⚑ LIVE INFERENCE</span>`:''}
${code?`<div class="cb">{ ${code} }</div>`:''}
${s3?`<div class="s3b">πŸ“¦ ${s3}</div>`:''}
</div>
</div>`;
document.getElementById('chatArea').appendChild(e);
setTimeout(()=>e.classList.add('v'),50);
e.scrollIntoView({behavior:'smooth',block:'nearest'});
}
async function finishDeclined(p, agent, reason, reasons) {
document.getElementById(`ac-${agent}`).classList.remove('active');
document.getElementById(`ac-${agent}`).classList.add('rejected');
setStat(agent,'complete','βœ— DECLINED');
document.getElementById('ac-orch').classList.remove('active');
setStat('orch','complete','β›” HALTED');
const tlMap = {kyc:1, property:2, uw:3};
const tlIdx = tlMap[agent] || 1;
document.getElementById(`tl-${tlIdx}`).classList.remove('active');
document.getElementById(`tl-${tlIdx}`).classList.add('failed');
document.getElementById('prog').style.width = `${tlIdx/5*100}%`;
document.getElementById('prog').classList.add('fail');
// ── Run Audit Agent even on decline ───────────────────────────
// Build a local fallback audit object from what we already know
// This guarantees the modal always has correct data even if API returns empty
var localAuditData = {
submission_id: p.id,
decision: 'DECLINED',
overall_summary: `The application from ${p.name} for ${p.loc} has been declined at the ${agent.toUpperCase()} stage. ${reasons.join(' ')}`,
decline_reasons: reasons,
decision_factors: [],
risk_band: 'UNKNOWN',
final_premium: null,
policy_number: null,
audit_score: 0,
audit_log_id: 'N/A',
audited_at: new Date().toISOString(),
_source: 'local_fallback'
};
var auditData = localAuditData;
try {
addPhase('STEP Β· AGENT 6', 'πŸ” Audit Agent β€” AXIOM Decision Trail', 'agent6_audit.py Β· Decline analysis Β· gold_audit_log', undefined);
activateAgent('audit');
addLiveCallBox('agent6_audit.py', `Generating decline audit trail for ${p.id}...`);
const auditResp = await callAgent('audit', p.id);
removeLiveCallBox();
const apiAudit = await auditResp.json().catch(() => ({}));
// Merge API response with local fallback β€” API wins where it has real data
auditData = {
submission_id: p.id,
decision: apiAudit.decision || 'DECLINED',
overall_summary: (apiAudit.overall_summary && apiAudit.overall_summary !== 'Audit complete.')
? apiAudit.overall_summary
: localAuditData.overall_summary,
decline_reasons: (apiAudit.decline_reasons && apiAudit.decline_reasons.length > 0)
? apiAudit.decline_reasons
: reasons,
decision_factors: (apiAudit.decision_factors && apiAudit.decision_factors.length > 0)
? apiAudit.decision_factors
: localAuditData.decision_factors,
risk_band: apiAudit.risk_band || 'UNKNOWN',
final_premium: apiAudit.final_premium || null,
policy_number: apiAudit.policy_number || null,
audit_score: apiAudit.audit_score || 0,
audit_log_id: apiAudit.audit_log_id || 'N/A',
audited_at: apiAudit.audited_at || new Date().toISOString(),
halted_at: agent.toUpperCase(),
};
// Force decision to DECLINED β€” it was declined, regardless of what API says
auditData.decision = 'DECLINED';
// If decline_reasons still empty, use what we know
if (!auditData.decline_reasons || auditData.decline_reasons.length === 0) {
auditData.decline_reasons = reasons;
}
} catch(auditErr) {
removeLiveCallBox();
console.warn('[AUDIT] API call failed, using local data:', auditErr);
// auditData already set to localAuditData above
}
window._currentAuditData = auditData;
window._currentPipelineData = p;
const auditScore = auditData.audit_score || 0;
const auditLogId = auditData.audit_log_id || 'N/A';
const auditSummary = auditData.overall_summary;
addAgentMsg('audit', 'πŸ”', 'Audit Agent (AXIOM)', 'DECLINE AUDIT',
auditSummary,
`<span class="k">"decision"</span><span class="p">:</span> <span class="vf">DECLINED</span><span class="p">,</span>\n<span class="k">"audit_score"</span><span class="p">:</span> <span class="vn">${auditScore}</span><span class="p">,</span>\n<span class="k">"audit_log_id"</span><span class="p">:</span> <span class="vn">${auditLogId}</span><span class="p">,</span>\n<span class="k">"halted_at"</span><span class="p">:</span> <span class="vs">"${agent.toUpperCase()}"</span><span class="p">,</span>\n<span class="k">"decline_reasons"</span><span class="p">:</span> <span class="vn">${auditData.decline_reasons.length}</span>`,
`gold_audit_log Β· submission_id=${p.id}`, true);
addOrch(`Decline audit complete. Halted at ${agent.toUpperCase()}. ${auditData.decline_reasons.length} decline reason(s) recorded. Audit id=${auditLogId}.`);
document.getElementById('ac-audit').classList.remove('active');
document.getElementById('ac-audit').classList.add('done');
setStat('audit','complete','βœ“ AUDITED');
// ── Declined result card ───────────────────────────────────────
const e = document.createElement('div'); e.className='rc rej';
e.innerHTML=`
<div class="rchead">
<div>
<div class="rcidl">SUBMISSION ID</div>
<div class="rcid">${p.id}</div>
<div style="font-size:10px;color:var(--dim2);margin-top:2px;font-family:'Share Tech Mono',monospace">${p.name} Β· ${p.loc}</div>
<div class="live-result-tag">⚑ LIVE AGENT RESULT</div>
</div>
<div style="text-align:right">
<div class="rcbadge">βœ— DECLINED</div>
<div style="font-size:9px;color:var(--dim2);font-family:'Share Tech Mono',monospace;margin-top:4px">Halted at ${agent.toUpperCase()}</div>
<div style="font-size:9px;color:#f472b6;font-family:'Share Tech Mono',monospace;margin-top:2px">πŸ” AXIOM Audited</div>
</div>
</div>
<div class="rreasons"><div class="rrt">⚠ Decline Reasons</div>${reasons.map(r=>`<div class="rri">${r}</div>`).join('')}</div>
<div style="margin-top:11px;display:flex;gap:5px;flex-wrap:wrap">
<span class="chip fail">βœ— DECLINED</span>
<span class="chip amber">⚑ XGBoost Decision</span>
<span class="chip warn">πŸ“‹ Human Review Queued</span>
<span class="chip info">πŸ“§ Adverse Action β†’ SES</span>
<span class="chip purple">πŸ“Š Decline β†’ Gold Layer</span>
<span class="chip" style="background:rgba(244,114,182,.15);color:#f472b6;border:1px solid rgba(244,114,182,.3)">πŸ” AXIOM Audited</span>
</div>
<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;">
<button class="btn-view-audit" onclick="openAuditModal(window._currentAuditData||{},window._currentPipelineData||{})">πŸ” View Decline Audit Trail</button>
<button class="btn-view-audit" style="color:#00d4ff;border-color:rgba(0,212,255,.35);background:rgba(0,212,255,.08);" onclick="exportFullAnalysis(window._currentAuditData||{},window._currentPipelineData||{})">⬇ Export Full Analysis</button>
</div>`;
document.getElementById('chatArea').appendChild(e);
setTimeout(()=>e.classList.add('v'),50);
p._result = 'reject';
renderQueue();
running = false;
document.getElementById('liveBtn').disabled = false;
}
function updateProg(pct) {
document.getElementById('prog').style.width = pct+'%';
}
// ══════════════════════════════════════════════════════════════════
// SIMULATION (demo mode β€” same script engine as v3)
// ══════════════════════════════════════════════════════════════════
function startSimulation() {
if (running || !selectedId) return;
running = true;
document.getElementById('startBtn').disabled = true;
const p = allPolicies.find(x => x.id === selectedId);
document.getElementById('chatArea').innerHTML = '';
const prog = document.getElementById('prog');
prog.style.width='0%';
p.outcome==='reject' ? prog.classList.add('fail') : prog.classList.remove('fail');
for(let i=0;i<=5;i++){const e=document.getElementById('tl-'+i);e.classList.remove('active','done','failed')}
document.getElementById('tlProg').style.width='0%';
resetAgents();
setStat('orch','processing','● ACTIVE');
document.getElementById('ac-orch').classList.add('active');
const sc = buildScript(p);
runScript(sc, p, 0);
}
function runScript(sc, p, i) {
if (i>=sc.length){ running=false; document.getElementById('startBtn').disabled=false; p._result=p.outcome; renderQueue(); return; }
const s=sc[i];
const D={phase:500,msg:950,typ:700,orch:450,act:80,result:600};
const delay=(D[s.t]||600)/speed;
timeouts.push(setTimeout(()=>{ execStep(s,p); document.getElementById('prog').style.width=Math.round(i/(sc.length-1)*100)+'%'; runScript(sc,p,i+1); }, delay));
}
function execStep(s, p) {
const chat = document.getElementById('chatArea');
if (s.t==='phase') {
const e=document.createElement('div');e.className='ph';
e.innerHTML=`<span class="pbadge">${s.badge}</span><div><div class="ptitle">${s.title}</div><div class="pdesc">${s.desc}</div></div>`;
chat.appendChild(e);setTimeout(()=>e.classList.add('v'),50);
if(s.tl!==undefined)tlSet(s.tl);
} else if (s.t==='act'){activateAgent(s.agent);}
else if (s.t==='orch'){
const e=document.createElement('div');e.className='ob';
e.innerHTML=`<span class="obicon">🧠</span><div><div class="obt">ORCHESTRATOR · STEP FUNCTIONS</div><div class="oba">${s.text}</div></div>`;
chat.appendChild(e);setTimeout(()=>e.classList.add('v'),50);
} else if (s.t==='typ'){
const c=agentColor(s.agent);
const e=document.createElement('div');e.className='ti v';
e.innerHTML=`<div class="mav" style="background:${c.bg};border-color:${c.b};color:${c.c}">${ICONS[s.agent]||'πŸ€–'}</div><div class="tdots"><span></span><span></span><span></span></div>`;
chat.appendChild(e);setTimeout(()=>{if(e.parentNode)e.remove();},900/speed);
} else if (s.t==='msg'){
const c=agentColor(s.from);
const t=new Date();const ts=t.getHours().toString().padStart(2,'0')+':'+t.getMinutes().toString().padStart(2,'0')+':'+t.getSeconds().toString().padStart(2,'0');
const e=document.createElement('div');e.className='msg'+(s.right?' r':'');
e.innerHTML=`
<div class="mav" style="background:${c.bg};border-color:${c.b};color:${c.c}">${s.icon}</div>
<div class="mbody">
<div class="mfrom"><strong style="color:${c.c}">${s.name}</strong><span class="mtime">${ts}</span></div>
<div class="mbub" style="background:${c.bg};border-color:${c.b}">
<span class="mtag" style="color:${c.c};border:1px solid ${c.b}">${s.tag}</span>${s.text}
${s.code?`<div class="cb">{ ${s.code} }</div>`:''}
${s.s3?`<div class="s3b">πŸ“¦ ${s.s3}</div>`:''}
</div>
</div>`;
chat.appendChild(e);setTimeout(()=>e.classList.add('v'),50);
} else if (s.t==='result'){
if(s.outcome==='approve'){
document.getElementById('ac-issue').classList.remove('active');document.getElementById('ac-issue').classList.add('done');setStat('issue','complete','βœ“ ISSUED');
document.getElementById('ac-orch').classList.remove('active');setStat('orch','complete','βœ“ COMPLETE');
for(let i=0;i<=5;i++){document.getElementById('tl-'+i).classList.remove('active');document.getElementById('tl-'+i).classList.add('done')}
document.getElementById('prog').style.width='100%';document.getElementById('tlProg').style.width='calc(100% - 76px)';
// Build sim audit data from pre-recorded submission data
var simAuditApprove = {
submission_id: p.id,
decision: 'APPROVED',
overall_summary: 'The application from ' + p.name + ' for ' + p.loc + ' has been approved. The property was assessed as ' + p.prop.band + ' risk. Identity and compliance screening passed. The underwriting model approved the risk and a final annual premium of $' + p.pr.prem.toLocaleString() + ' has been calculated. Policy ' + p.pol.num + ' has been issued.',
decline_reasons: [],
decision_factors: [
{agent:'Document Validation Agent', factor:'Identity & Compliance Check', outcome:'PASS', detail:'Credit score: ' + p.kyc.score + '. OFAC: ' + p.kyc.ofac + '. Fraud signals: ' + p.kyc.fraud + '.'},
{agent:'Property Risk Agent', factor:'Peril Risk Assessment', outcome:'PASS', detail:'Risk band: ' + p.prop.band + '. Wind: ' + p.prop.wind + ' | Flood: ' + p.prop.flood + ' | Overall: ' + p.prop.score + '/100.'},
{agent:'Underwriting Agent', factor:'AI Underwriting Decision', outcome:'APPROVED', detail:'UW approved. Coverage: ' + p.cov + '. Limit: $' + (p.uw.lim/1000).toFixed(0) + 'K.'},
{agent:'Pricing Agent', factor:'Actuarial Premium Calculation', outcome:'CALCULATED', detail:'Annual premium: $' + p.pr.prem.toLocaleString() + '. Monthly: $' + Math.round(p.pr.prem/12).toLocaleString() + '. 95% CI: [$' + p.pr.lo.toLocaleString() + ' – $' + p.pr.hi.toLocaleString() + '].'},
{agent:'Issuance Agent', factor:'Policy Issuance', outcome:'ISSUED', detail:'Policy: ' + p.pol.num + '. Effective: ' + p.pol.eff + '. Documents generated.'}
],
risk_band: p.prop.band,
final_premium: p.pr.prem,
policy_number: p.pol.num,
audit_score: 85,
audit_log_id: 'SIM-' + p.id,
audited_at: new Date().toISOString(),
_source: 'simulation'
};
// Store sim data for export
window._currentAuditData = simAuditApprove;
window._currentPipelineData = p;
window._kycData = {status:'KYC_PASS', credit_score:p.kyc.score, ofac_result:p.kyc.ofac, fraud_signals:p.kyc.fraud, ml_kyc_probability:0.92};
window._propData = {status:'RISK_ACCEPTABLE', risk_band:p.prop.band, peril_scores:{wind_score:p.prop.wind, flood_score:p.prop.flood, fire_score:p.prop.fire||0, overall_risk:p.prop.score}, risk_acceptability_prob:0.85};
window._uwData = {status:'UW_APPROVED', coverage_type:p.cov, uw_approval_probability:0.88, expected_loss_ratio:0.56, reinsurance_required:false, all_rule_violations:[]};
window._pricData = {final_premium:p.pr.prem, monthly_premium:Math.round(p.pr.prem/12), actuarial_premium:p.pr.prem, confidence_interval:{lo_95:p.pr.lo, hi_95:p.pr.hi}, premium_breakdown:{credit_modifier:1.0,risk_modifier:1.0,age_modifier:1.0}};
window._issData = {policy_number:p.pol.num, documents_generated:['declarations_page.pdf','policy_contract.pdf'], notifications_sent:{email:true}, policy_details:{effective_date:p.pol.eff, annual_premium:p.pr.prem, coverage_type:p.cov, status:'ISSUED'}};
const e=document.createElement('div');e.className='rc app';
e.innerHTML=`
<div class="rchead">
<div><div class="rcidl">POLICY NUMBER</div><div class="rcid">${p.pol.num}</div><div style="font-size:10px;color:var(--dim2);margin-top:2px;font-family:'Share Tech Mono',monospace">${p.name} Β· ${p.loc}</div></div>
<div style="text-align:right"><div class="rcbadge">βœ“ ISSUED</div><div style="font-size:9px;color:var(--dim2);font-family:'Share Tech Mono',monospace;margin-top:4px">All 5 agents passed Β· Simulated</div></div>
</div>
<div class="rcgrid">
<div class="rcs"><div class="rl">Coverage</div><div class="rv" style="font-size:12px">${p.cov} Homeowners</div></div>
<div class="rcs"><div class="rl">Approved Limit</div><div class="rv">$${(p.uw.lim/1000).toFixed(0)}K</div></div>
<div class="rcs"><div class="rl">Annual Premium</div><div class="rv g">$${p.pr.prem.toLocaleString()}</div></div>
<div class="rcs"><div class="rl">Risk Band</div><div class="rv g">${p.prop.band}</div></div>
<div class="rcs"><div class="rl">Credit Score</div><div class="rv">${p.kyc.score}</div></div>
<div class="rcs"><div class="rl">Effective Date</div><div class="rv" style="font-size:11px">${p.pol.eff}</div></div>
</div>
<div style="margin-top:11px;display:flex;gap:5px;flex-wrap:wrap">
<span class="chip pass">βœ“ KYC PASS</span><span class="chip pass">βœ“ PROPERTY ${p.prop.band}</span><span class="chip pass">βœ“ UW APPROVED</span>
<span class="chip pass">βœ“ ML PRICING</span><span class="chip info">πŸ“¦ S3 Docs</span><span class="chip info">πŸ“§ SES</span><span class="chip purple">πŸ“Š Gold Layer</span>
<span class="chip" style="background:rgba(244,114,182,.15);color:#f472b6;border:1px solid rgba(244,114,182,.3)">πŸ” AXIOM Audited</span>
</div>
<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;">
<button class="btn-view-audit" onclick="openAuditModal(window._currentAuditData||{},window._currentPipelineData||{})">πŸ” View Audit Trail</button>
<button class="btn-view-audit" style="color:#00d4ff;border-color:rgba(0,212,255,.35);background:rgba(0,212,255,.08);" onclick="exportFullAnalysis(window._currentAuditData||{},window._currentPipelineData||{})">⬇ Export Full Analysis</button>
</div>`;
chat.appendChild(e);setTimeout(()=>e.classList.add('v'),50);
} else {
document.getElementById('ac-orch').classList.remove('active');setStat('orch','complete','β›” HALTED');
if(p.failAt==='kyc'){
document.getElementById('ac-kyc').classList.remove('active');document.getElementById('ac-kyc').classList.add('rejected');setStat('kyc','complete','βœ— KYC_FAIL');
['prop','uw','price','issue'].forEach(a=>setStat(a,'idle','β€” SKIPPED'));
document.getElementById('tl-0').classList.add('done');
document.getElementById('tl-1').classList.remove('active');document.getElementById('tl-1').classList.add('failed');
document.getElementById('prog').style.width='22%';
} else {
document.getElementById('ac-kyc').classList.remove('active');document.getElementById('ac-kyc').classList.add('done');setStat('kyc','complete','βœ“ COMPLETE');
document.getElementById('ac-prop').classList.remove('active');document.getElementById('ac-prop').classList.add('rejected');setStat('prop','complete','βœ— DECLINED');
['uw','price','issue'].forEach(a=>setStat(a,'idle','β€” SKIPPED'));
['tl-0','tl-1'].forEach(id=>{document.getElementById(id).classList.remove('active');document.getElementById(id).classList.add('done')});
document.getElementById('tl-2').classList.remove('active');document.getElementById('tl-2').classList.add('failed');
document.getElementById('prog').style.width='42%';
}
// Build sim audit data for declined submission
var haltedAt = p.failAt === 'kyc' ? 'KYC' : 'PROPERTY';
var simAuditDecline = {
submission_id: p.id,
decision: 'DECLINED',
overall_summary: 'The application from ' + p.name + ' for ' + p.loc + ' has been declined at the ' + haltedAt + ' stage. ' + (p.failReasons||[]).join(' '),
decline_reasons: p.failReasons || [],
decision_factors: p.failAt === 'kyc' ? [
{agent:'Document Validation Agent', factor:'Identity & Compliance Check', outcome:'FAIL', detail:'Credit score: ' + p.kyc.score + '. OFAC: ' + p.kyc.ofac + '. KYC failed β€” submission halted.'}
] : [
{agent:'Document Validation Agent', factor:'Identity & Compliance Check', outcome:'PASS', detail:'Credit score: ' + p.kyc.score + '. OFAC: ' + p.kyc.ofac + '. KYC passed.'},
{agent:'Property Risk Agent', factor:'Peril Risk Assessment', outcome:'DECLINE', detail:'Risk band: DECLINED. Wind: ' + p.prop.wind + ' | Flood: ' + p.prop.flood + ' β€” exceeded maximum thresholds.'}
],
risk_band: p.failAt === 'kyc' ? 'UNKNOWN' : 'DECLINED',
final_premium: null,
policy_number: null,
audit_score: 0,
audit_log_id: 'SIM-' + p.id,
audited_at: new Date().toISOString(),
halted_at: haltedAt,
_source: 'simulation'
};
window._currentAuditData = simAuditDecline;
window._currentPipelineData = p;
window._kycData = {status: p.failAt==='kyc' ? 'KYC_FAIL' : 'KYC_PASS', credit_score:p.kyc.score, ofac_result:p.kyc.ofac, fraud_signals:p.kyc.fraud||0};
window._propData = p.failAt!=='kyc' ? {status:'RISK_DECLINED', risk_band:'DECLINED', peril_scores:{wind_score:p.prop.wind, flood_score:p.prop.flood, overall_risk:p.prop.score||90}, decline_reason:(p.failReasons||[])[0]||''} : {};
const e=document.createElement('div');e.className='rc rej';
e.innerHTML=`
<div class="rchead">
<div><div class="rcidl">SUBMISSION ID</div><div class="rcid">${p.id}</div><div style="font-size:10px;color:var(--dim2);margin-top:2px;font-family:'Share Tech Mono',monospace">${p.name} Β· ${p.loc}</div></div>
<div style="text-align:right"><div class="rcbadge">βœ— DECLINED</div><div style="font-size:9px;color:var(--dim2);font-family:'Share Tech Mono',monospace;margin-top:4px">Halted at ${p.failAt==='kyc'?'Step 2':'Step 3'}</div><div style="font-size:9px;color:#f472b6;font-family:'Share Tech Mono',monospace;margin-top:2px">πŸ” AXIOM Audited</div></div>
</div>
<div class="rreasons"><div class="rrt">⚠ Decline Reasons</div>${(p.failReasons||[]).map(r=>`<div class="rri">${r}</div>`).join('')}</div>
<div style="margin-top:11px;display:flex;gap:5px;flex-wrap:wrap">
<span class="chip fail">βœ— DECLINED</span>
<span class="chip warn">πŸ“‹ Human Review Queued</span><span class="chip info">πŸ“§ SES Adverse Action</span><span class="chip purple">πŸ“Š Gold Layer</span>
<span class="chip" style="background:rgba(244,114,182,.15);color:#f472b6;border:1px solid rgba(244,114,182,.3)">πŸ” AXIOM Audited</span>
</div>
<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;">
<button class="btn-view-audit" onclick="openAuditModal(window._currentAuditData||{},window._currentPipelineData||{})">πŸ” View Decline Audit Trail</button>
<button class="btn-view-audit" style="color:#00d4ff;border-color:rgba(0,212,255,.35);background:rgba(0,212,255,.08);" onclick="exportFullAnalysis(window._currentAuditData||{},window._currentPipelineData||{})">⬇ Export Decline Report</button>
</div>`;
chat.appendChild(e);setTimeout(()=>e.classList.add('v'),50);
}
}
setTimeout(()=>{chat.lastElementChild?.scrollIntoView({behavior:'smooth',block:'nearest'})},80);
}
// ── buildScript (same as v3) ───────────────────────────────────
function buildScript(p) {
const sc=[];
const limNum=parseInt(p.lim.replace(/[^0-9.]/g,''))*(p.lim.includes('M')?1000000:1000);
sc.push({t:'phase',badge:'STEP 1 Β· BRONZE LAYER',title:'Policy Submission Received',desc:`${p.cov} submission via API Gateway β†’ S3 Bronze`,tl:0});
sc.push({t:'msg',from:'broker',icon:'🏒',name:p.broker,right:true,tag:'API POST',text:`Submitting new ${p.cov} policy for ${p.name} β€” ${p.addr}.`,code:`<span class="k">"submission_id"</span><span class="p">:</span> <span class="vs">"${p.id}"</span><span class="p">,</span>\n<span class="k">"insured"</span><span class="p">:</span> <span class="vs">"${p.name}"</span><span class="p">,</span>\n<span class="k">"coverage"</span><span class="p">:</span> <span class="vs">"${p.cov}"</span><span class="p">,</span>\n<span class="k">"requested_limit"</span><span class="p">:</span> <span class="vn">${limNum}</span>`});
sc.push({t:'orch',text:`πŸ“₯ Stored β†’ s3://pcins-bronze/submissions/2025/${p.id}.json | Step Functions triggered...`});
sc.push({t:'phase',badge:'STEP 2 Β· SILVER LAYER',title:'Agent 1 β€” KYC & Identity Validation',desc:'OFAC screening Β· Credit bureau pull Β· Fraud anomaly detection',tl:1});
sc.push({t:'act',agent:'kyc'});
sc.push({t:'msg',from:'orch',icon:'🧠',name:'Orchestrator',tag:'INVOKE',text:`KYC Agent β€” validate identity for ${p.id}. Run OFAC, credit pull, fraud detection.`,s3:`s3://pcins-bronze/submissions/${p.id}.json`});
sc.push({t:'typ',agent:'kyc'});
if(p.failAt==='kyc'){
sc.push({t:'msg',from:'kyc',icon:'πŸͺͺ',name:'KYC Agent',tag:'🚨 ALERT',text:`β›” OFAC PARTIAL_MATCH. Credit ${p.kyc.score} below minimum 550. Fraud signals detected.`,code:`<span class="k">"kyc_status"</span><span class="p">:</span> <span class="vf">KYC_FAIL</span><span class="p">,</span>\n<span class="k">"credit_score"</span><span class="p">:</span> <span class="vn">${p.kyc.score}</span><span class="p">,</span>\n<span class="k">"ofac"</span><span class="p">:</span> <span class="vf">${p.kyc.ofac}</span>`,s3:`s3://pcins-silver/kyc_decisions/${p.id}_decline.json`});
sc.push({t:'msg',from:'orch',icon:'🧠',name:'Orchestrator',tag:'β›” HALT',text:`KYC_FAIL. Pipeline terminated at Step 2. Agents 2–5 will NOT be invoked.`});
sc.push({t:'result',outcome:'reject'});return sc;
}
sc.push({t:'msg',from:'kyc',icon:'πŸͺͺ',name:'KYC Agent',tag:'RESULT',text:`Identity validated. OFAC: ${p.kyc.ofac}. Credit ${p.kyc.score} β€” above threshold. Fraud: ${p.kyc.fraud}. All checks passed.`,code:`<span class="k">"kyc_status"</span><span class="p">:</span> <span class="vp">KYC_PASS</span><span class="p">,</span>\n<span class="k">"credit_score"</span><span class="p">:</span> <span class="vn">${p.kyc.score}</span><span class="p">,</span>\n<span class="k">"ofac"</span><span class="p">:</span> <span class="vp">${p.kyc.ofac}</span>`,s3:`s3://pcins-silver/kyc_decisions/${p.id}_kyc.json`});
sc.push({t:'msg',from:'orch',icon:'🧠',name:'Orchestrator',tag:'ROUTE',text:'KYC_PASS. Routing to Property Risk Agent.'});
sc.push({t:'phase',badge:'STEP 3 Β· SILVER LAYER',title:'Agent 2 β€” Property Risk Assessment',desc:'Geo-spatial Β· FEMA zones Β· Satellite imagery Β· Peril scoring',tl:2});
sc.push({t:'act',agent:'prop'});
sc.push({t:'msg',from:'orch',icon:'🧠',name:'Orchestrator',tag:'INVOKE',text:`Property Agent β€” assess risk for ${p.addr}.`,s3:`s3://pcins-silver/kyc_decisions/${p.id}_kyc.json`});
sc.push({t:'typ',agent:'prop'});
if(p.failAt==='property'){
sc.push({t:'msg',from:'prop',icon:'🏠',name:'Property Risk Agent',tag:'🚨 DECLINE',text:`β›” Wind ${p.prop.wind}/100 > max 70. Flood ${p.prop.flood}/100 > max 75. Overall risk DECLINED.`,code:`<span class="k">"risk_band"</span><span class="p">:</span> <span class="vf">DECLINED</span><span class="p">,</span>\n<span class="k">"wind"</span><span class="p">:</span> <span class="vf">${p.prop.wind}</span><span class="p">,</span>\n<span class="k">"flood"</span><span class="p">:</span> <span class="vf">${p.prop.flood}</span>`,s3:`s3://pcins-silver/property_risk/${p.id}_property.json`});
sc.push({t:'msg',from:'orch',icon:'🧠',name:'Orchestrator',tag:'β›” HALT',text:'Property DECLINE. Pipeline terminated at Step 3.'});
sc.push({t:'result',outcome:'reject'});return sc;
}
sc.push({t:'msg',from:'prop',icon:'🏠',name:'Property Risk Agent',tag:'RESULT',text:`Score ${p.prop.score}/100 β€” Band ${p.prop.band}. RCV $${(p.prop.rcv/1000).toFixed(0)}K. All perils within appetite.`,code:`<span class="k">"risk_band"</span><span class="p">:</span> <span class="vp">${p.prop.band}</span><span class="p">,</span>\n<span class="k">"wind"</span><span class="p">:</span> <span class="vn">${p.prop.wind}</span><span class="p">,</span>\n<span class="k">"flood"</span><span class="p">:</span> <span class="vn">${p.prop.flood}</span><span class="p">,</span>\n<span class="k">"overall"</span><span class="p">:</span> <span class="vn">${p.prop.score}</span>`,s3:`s3://pcins-silver/property_risk/${p.id}_property.json`});
sc.push({t:'phase',badge:'STEP 4 Β· SILVER LAYER',title:'Agent 3 β€” Underwriting Validation',desc:'Carrier guidelines Β· Coverage eligibility Β· State compliance Β· Reinsurance',tl:3});
sc.push({t:'act',agent:'uw'});
sc.push({t:'msg',from:'orch',icon:'🧠',name:'Orchestrator',tag:'INVOKE',text:`UW Agent β€” consolidate KYC and Property for ${p.id}.`,s3:`s3://pcins-silver/property_risk/${p.id}_property.json`});
sc.push({t:'typ',agent:'uw'});
sc.push({t:'msg',from:'uw',icon:'πŸ“‹',name:'Underwriting Agent',tag:'DECISION',text:`${p.cov} eligibility confirmed. Endorsements applied. Compliance clear.`,code:`<span class="k">"uw_decision"</span><span class="p">:</span> <span class="vp">ISSUE_POLICY</span><span class="p">,</span>\n<span class="k">"limit"</span><span class="p">:</span> <span class="vn">${p.uw.lim}</span><span class="p">,</span>\n<span class="k">"deductible"</span><span class="p">:</span> <span class="vn">${p.uw.ded}</span>`,s3:`s3://pcins-silver/uw_decisions/${p.id}_uw.json`});
sc.push({t:'phase',badge:'STEP 5 Β· SILVER LAYER',title:'Agent 4 β€” ML Premium Pricing',desc:'XGBoost + GLM ensemble Β· SHAP explainability Β· 95% CI',tl:4});
sc.push({t:'act',agent:'price'});
sc.push({t:'msg',from:'orch',icon:'🧠',name:'Orchestrator',tag:'INVOKE',text:`ML Pricing Agent β€” generate premium for ${p.id}. XGBoost/GLM ensemble + SHAP.`});
sc.push({t:'typ',agent:'price'});
sc.push({t:'msg',from:'price',icon:'πŸ€–',name:'ML Pricing Agent',tag:'INFERENCE',text:`Ensemble inference complete. Premium $${p.pr.prem.toLocaleString()}. CI: ${p.pr.ci}.`,code:`<span class="k">"premium"</span><span class="p">:</span> <span class="vn">${p.pr.prem}</span><span class="p">,</span>\n<span class="k">"band"</span><span class="p">:</span> <span class="vp">${p.pr.band}</span><span class="p">,</span>\n<span class="k">"ci_95"</span><span class="p">:</span> <span class="vs">${p.pr.ci}</span><span class="p">,</span>\n<span class="k">"shap"</span><span class="p">:</span> <span class="vs">${p.pr.shap}</span>`,s3:`s3://pcins-silver/premium_predictions/${p.id}_pricing.json`});
sc.push({t:'phase',badge:'STEP 6 Β· SILVER β†’ GOLD',title:'Agent 5 β€” Policy Issuance & Delivery',desc:'Bind coverage Β· Generate documents Β· SES delivery Β· SNS events',tl:5});
sc.push({t:'act',agent:'issue'});
sc.push({t:'msg',from:'orch',icon:'🧠',name:'Orchestrator',tag:'INVOKE',text:'All 5 agents PASSED. Invoking Issuance Agent.'});
sc.push({t:'typ',agent:'issue'});
sc.push({t:'msg',from:'issue',icon:'πŸ“„',name:'Issuance Agent',tag:'BINDING',text:`Policy bound. Documents generated. SES delivery confirmed. Gold Layer ETL triggered.`,code:`<span class="k">"policy_number"</span><span class="p">:</span> <span class="vs">"${p.pol.num}"</span><span class="p">,</span>\n<span class="k">"effective_date"</span><span class="p">:</span> <span class="vs">"${p.pol.eff}"</span><span class="p">,</span>\n<span class="k">"status"</span><span class="p">:</span> <span class="vp">ISSUED</span>`,s3:`s3://pcins-silver/issued_policies/${p.pol.num}/`});
sc.push({t:'msg',from:'orch',icon:'🧠',name:'Orchestrator',tag:'COMPLETE',text:`Pipeline complete. ${p.pol.num} issued. Gold Layer updated.`});
sc.push({t:'msg',from:'broker',icon:'🏒',name:p.broker,right:true,tag:'RECEIVED',text:`βœ… Policy documents received! ${p.name} is now covered under ${p.pol.num}.`});
sc.push({t:'result',outcome:'approve'});
return sc;
}
// ══════════════════════════════════════════════════════════════════
// HELPERS
// ══════════════════════════════════════════════════════════════════
function tlSet(step) {
for(let i=0;i<step;i++){const e=document.getElementById('tl-'+i);e.classList.remove('active');e.classList.add('done')}
const cur=document.getElementById('tl-'+step);cur.classList.remove('done','failed');cur.classList.add('active');
const pct=step===0?0:(step/6)*100;
document.getElementById('tlProg').style.width=`calc(${pct}% - ${pct>0?38:0}px)`;
}
function activateAgent(agent) {
const map={kyc:'ac-kyc',prop:'ac-prop',uw:'ac-uw',price:'ac-price',issue:'ac-issue',audit:'ac-audit'};
Object.values(map).forEach(id=>{
const el=document.getElementById(id);
if(el&&el!==document.getElementById(map[agent])&&el.classList.contains('active')){el.classList.remove('active');el.classList.add('done');setStat(id.replace('ac-',''),'complete','βœ“ COMPLETE')}
});
document.getElementById(map[agent])?.classList.add('active');
setStat(agent,'processing','βš™ PROCESSING');
}
function agentColor(key){const m={orch:'orch',orchestrator:'orch',kyc:'kyc',prop:'prop',property:'prop',uw:'uw',price:'price',pricing:'price',issue:'issue',issuance:'issue',broker:'broker'};return C[m[key]]||C.orch}
function setStat(agent,cls,text){const map={orch:'as-orch',kyc:'as-kyc',prop:'as-prop',uw:'as-uw',price:'as-price',issue:'as-issue',audit:'as-audit'};const el=document.getElementById(map[agent]);if(el){el.className='astat '+cls;el.textContent=text}}
function resetAgents(){[['ac-orch','as-orch','● STANDBY'],['ac-kyc','as-kyc','● IDLE'],['ac-prop','as-prop','● IDLE'],['ac-uw','as-uw','● IDLE'],['ac-price','as-price','● IDLE'],['ac-issue','as-issue','● IDLE'],['ac-audit','as-audit','● IDLE']].forEach(([cid,sid,txt])=>{const c=document.getElementById(cid);if(c)c.classList.remove('active','done','rejected');const s=document.getElementById(sid);if(s){s.className='astat idle';s.textContent=txt}})}
function resetDemo(){timeouts.forEach(t=>clearTimeout(t));timeouts=[];running=false;liveResult=null;document.getElementById('startBtn').disabled=!selectedId||selectedSrc==='live';document.getElementById('liveBtn').disabled=!selectedId||selectedSrc!=='live';document.getElementById('prog').style.width='0%';document.getElementById('prog').classList.remove('fail');document.getElementById('tlProg').style.width='0%';for(let i=0;i<=6;i++){const e=document.getElementById('tl-'+i);e.classList.remove('active','done','failed')}resetAgents();if(selectedId){const p=allPolicies.find(x=>x.id===selectedId);if(p)resetChat(p)}else document.getElementById('chatArea').innerHTML='<div style="text-align:center;padding:70px 20px;color:var(--dim2)"><div style="font-size:42px;margin-bottom:12px;opacity:.2">πŸ“‹</div><div style="font-family:\'Share Tech Mono\',monospace;font-size:13px">Select a submission from the queue above</div></div>'}
function toggleSpeed(){speed=speed===1?3:1;document.getElementById('speedBtn').textContent=speed===1?'⚑ Speed: Normal':'⚑ Speed: Fast'}
function toggleConfig(){document.getElementById('cfgPanel').classList.toggle('open')}
// ══════════════════════════════════════════════════════════════════
// INIT
// ══════════════════════════════════════════════════════════════════
checkApiStatus();
loadSubmissions();
// ── AXIOM AUDIT MODAL ─────────────────────────────────────────
var _lastAuditData = null;
var _lastPipelineData = null;
function openAuditModal(auditData, pipelineData) {
_lastAuditData = auditData;
_lastPipelineData = pipelineData;
var overlay = document.getElementById('auditModalOverlay');
var body = document.getElementById('auditModalBody');
var sub = document.getElementById('auditModalSub');
var decision = auditData.decision || 'APPROVED';
var score = auditData.audit_score || 0;
var summary = auditData.overall_summary || 'No summary available.';
var factors = auditData.decision_factors || [];
var declines = auditData.decline_reasons || [];
var logId = auditData.audit_log_id || 'N/A';
var subId = auditData.submission_id || (pipelineData && pipelineData.id) || '';
sub.textContent = 'Submission: ' + subId + ' Β· Audit Log ID: ' + logId + ' Β· ' + new Date().toLocaleString();
var scoreColor = score >= 70 ? '#00e5a0' : score >= 40 ? '#f59e0b' : '#ef4444';
var decColor = decision === 'APPROVED' ? '#00e5a0' : '#ef4444';
var html = '';
// Decision badge + halted at
var haltedAt = auditData.halted_at ? ' Β· Halted at ' + auditData.halted_at : '';
html += '<div style="display:flex;align-items:center;gap:.8rem;margin-bottom:1rem;flex-wrap:wrap;">';
html += '<div style="font-size:9px;letter-spacing:.15em;color:rgba(255,255,255,.4);text-transform:uppercase;">Final Decision</div>';
html += '<div style="font-size:11px;font-weight:700;letter-spacing:.12em;padding:.25rem .8rem;border:1px solid ' + decColor + ';color:' + decColor + ';font-family:Share Tech Mono,monospace;">' + decision + '</div>';
if (haltedAt) html += '<div style="font-size:10px;color:rgba(255,255,255,.4);font-family:Share Tech Mono,monospace;">' + haltedAt + '</div>';
html += '</div>';
// Summary
html += '<div class="audit-summary-box"><div class="audit-summary-label">Plain-English Decision Summary</div>' + summary + '</div>';
// Score bar
html += '<div class="audit-score-row">';
html += '<div class="audit-score-label">Audit Confidence Score</div>';
html += '<div class="audit-score-track"><div class="audit-score-fill" id="auditScoreFill" style="width:0%;background:' + scoreColor + '"></div></div>';
html += '<div class="audit-score-val" style="color:' + scoreColor + '">' + score + '%</div>';
html += '</div>';
// Decline reasons
if (declines.length > 0) {
html += '<div class="audit-decline-box"><div class="audit-decline-title">β›” Decline Reasons</div>';
declines.forEach(function(r, i) {
html += '<div class="audit-decline-item">' + (i+1) + '. ' + r + '</div>';
});
html += '</div>';
}
// Agent factors
html += '<div class="audit-factors-title">Agent Decision Factors (' + factors.length + ' checks)</div>';
var outcomeColors = {
'PASS':'#00e5a0','APPROVED':'#00e5a0','ISSUED':'#00e5a0','CALCULATED':'#00d4ff',
'FAIL':'#ef4444','DECLINED':'#ef4444','DECLINE':'#ef4444',
'REFER':'#f59e0b','NOT ISSUED':'#f59e0b'
};
factors.forEach(function(f) {
var oc = outcomeColors[f.outcome] || '#7a9ab8';
html += '<div class="audit-factor">';
html += '<div class="audit-factor-header">';
html += '<div class="audit-factor-agent">' + f.agent + '</div>';
html += '<div class="audit-factor-outcome" style="border-color:' + oc + ';color:' + oc + '">' + f.outcome + '</div>';
html += '</div>';
html += '<div class="audit-factor-factor">' + f.factor + '</div>';
html += '<div class="audit-factor-detail">' + f.detail + '</div>';
html += '</div>';
});
// Export button
html += '<button class="audit-export-btn" onclick="exportFullAnalysis(_lastAuditData||{},_lastPipelineData||{})">⬇ Export Full Analysis to Excel</button>';
body.innerHTML = html;
overlay.classList.add('open');
// Animate score bar
setTimeout(function() {
var fill = document.getElementById('auditScoreFill');
if (fill) fill.style.width = score + '%';
}, 100);
}
function closeAuditModal() {
document.getElementById('auditModalOverlay').classList.remove('open');
}
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeAuditModal();
});
// ── FULL ANALYSIS EXCEL EXPORT ────────────────────────────────
function exportAuditToExcel() {
exportFullAnalysis(_lastAuditData || {}, _lastPipelineData || {});
}
function exportFullAnalysis(audit, pipeline) {
audit = audit || _lastAuditData || {};
pipeline = pipeline || _lastPipelineData || {};
var factors = audit.decision_factors || [];
var declines = audit.decline_reasons || [];
var kyc = window._kycData || {};
var prop = window._propData || {};
var uw = window._uwData || {};
var pric = window._pricData || {};
var iss = window._issData || {};
var perils = prop.peril_scores || {};
var ci = pric.confidence_interval || {};
var brkdn = pric.premium_breakdown || {};
var rows = [];
var sep = ['─────────────────────────────────────────────────────────────'];
// ── COVER ──────────────────────────────────────────────────
rows.push(['POLICYBRIDGE β€” COMPLETE PIPELINE ANALYSIS REPORT']);
rows.push(['Generated', new Date().toLocaleString()]);
rows.push(['Submission ID', audit.submission_id || pipeline.id || '']);
rows.push(['Insured Name', pipeline.name || '']);
rows.push(['Location', pipeline.loc || '']);
rows.push(['Coverage Type', pipeline.cov || '']);
rows.push(['Coverage Limit', pipeline.lim || '']);
rows.push(['Final Decision', audit.decision || '']);
rows.push(['Policy Number', audit.decision === 'DECLINED' ? 'N/A β€” Not Issued' : (audit.policy_number || iss.policy_number || '')]);
rows.push(['Audit Log ID', audit.audit_log_id || '']);
rows.push(['Audit Confidence Score', (audit.audit_score || 0) + '%']);
rows.push(['Report Timestamp', new Date().toISOString()]);
rows.push([]);
// ── DECISION SUMMARY ────────────────────────────────────────
rows.push(sep);
rows.push(['SECTION 1: DECISION SUMMARY']);
rows.push(sep);
rows.push([audit.overall_summary || '']);
rows.push([]);
if (declines.length > 0) {
rows.push(['DECLINE REASONS']);
declines.forEach(function(r, i) { rows.push([(i+1) + '. ' + r]); });
rows.push([]);
}
// ── AGENT 1: DOCUMENT VALIDATION ────────────────────────────
rows.push(sep);
rows.push(['SECTION 2: AGENT 1 β€” DOCUMENT VALIDATION (AURA/KYC)']);
rows.push(sep);
rows.push(['Field', 'Value']);
rows.push(['KYC Status', kyc.status || '']);
rows.push(['Credit Score', kyc.credit_score || '']);
rows.push(['OFAC Result', kyc.ofac_result || '']);
rows.push(['Fraud Signals', kyc.fraud_signals || 0]);
rows.push(['ML KYC Probability', kyc.ml_kyc_probability ? (kyc.ml_kyc_probability * 100).toFixed(1) + '%' : '']);
rows.push(['Decline Reason', kyc.decline_reason || 'None']);
rows.push([]);
// ── AGENT 2: PROPERTY RISK ───────────────────────────────────
rows.push(sep);
rows.push(['SECTION 3: AGENT 2 β€” PROPERTY RISK ASSESSMENT']);
rows.push(sep);
rows.push(['Field', 'Value']);
rows.push(['Status', prop.status || '']);
rows.push(['Risk Band', prop.risk_band || '']);
rows.push(['Wind Score', perils.wind_score || 0]);
rows.push(['Flood Score', perils.flood_score || 0]);
rows.push(['Fire Score', perils.fire_score || 0]);
rows.push(['Overall Risk Score', perils.overall_risk || 0]);
rows.push(['Risk Acceptability Probability', prop.risk_acceptability_prob ? (prop.risk_acceptability_prob * 100).toFixed(1) + '%' : '']);
rows.push(['Decline Reason', prop.decline_reason || 'None']);
rows.push([]);
// ── AGENT 3: UNDERWRITING ────────────────────────────────────
rows.push(sep);
rows.push(['SECTION 4: AGENT 3 β€” UNDERWRITING DECISION']);
rows.push(sep);
rows.push(['Field', 'Value']);
rows.push(['UW Decision', uw.status || '']);
rows.push(['Coverage Type', uw.coverage_type || '']);
rows.push(['Approval Probability', uw.uw_approval_probability ? (uw.uw_approval_probability * 100).toFixed(1) + '%' : '']);
rows.push(['Expected Loss Ratio', uw.expected_loss_ratio || '']);
rows.push(['Reinsurance Required', uw.reinsurance_required ? 'Yes' : 'No']);
rows.push(['Primary Decline Reason', uw.primary_decline_reason || 'None']);
rows.push(['Rule Violations', (uw.all_rule_violations || []).join('; ') || 'None']);
rows.push([]);
// ── AGENT 4: PRICING ─────────────────────────────────────────
rows.push(sep);
rows.push(['SECTION 5: AGENT 4 β€” PREMIUM PRICING']);
rows.push(sep);
rows.push(['Field', 'Value']);
rows.push(['Final Annual Premium', pric.final_premium ? '$' + Number(pric.final_premium).toLocaleString() : '']);
rows.push(['Monthly Premium', pric.monthly_premium ? '$' + Number(pric.monthly_premium).toLocaleString() : '']);
rows.push(['Actuarial Premium', pric.actuarial_premium ? '$' + Number(pric.actuarial_premium).toLocaleString() : '']);
rows.push(['95% CI Low', ci.lo_95 ? '$' + Number(ci.lo_95).toLocaleString() : '']);
rows.push(['95% CI High', ci.hi_95 ? '$' + Number(ci.hi_95).toLocaleString() : '']);
rows.push(['Credit Modifier', brkdn.credit_modifier || 1.0]);
rows.push(['Risk Modifier', brkdn.risk_modifier || 1.0]);
rows.push(['Age Modifier', brkdn.age_modifier || 1.0]);
rows.push([]);
// ── AGENT 5: ISSUANCE ────────────────────────────────────────
rows.push(sep);
rows.push(['SECTION 6: AGENT 5 β€” POLICY ISSUANCE']);
rows.push(sep);
var isDeclined = (audit.decision === 'DECLINED');
if (isDeclined) {
rows.push(['Status', 'NOT ISSUED β€” Pipeline halted before issuance']);
rows.push(['Policy Number', 'N/A β€” Policy was not issued']);
rows.push(['Reason', (audit.decline_reasons || []).join('; ') || 'Declined by prior agent']);
rows.push(['Documents Generated', 'None β€” policy was not issued']);
} else {
var polDets = iss.policy_details || {};
rows.push(['Field', 'Value']);
rows.push(['Policy Number', iss.policy_number || audit.policy_number || 'N/A']);
rows.push(['Status', 'ISSUED']);
rows.push(['Coverage Type', polDets.coverage_type || '']);
rows.push(['Effective Date', polDets.effective_date || '']);
rows.push(['Expiration Date', polDets.expiration_date || '']);
rows.push(['Annual Premium', polDets.annual_premium ? '$' + Number(polDets.annual_premium).toLocaleString() : (audit.final_premium ? '$' + Number(audit.final_premium).toLocaleString() : '')]);
rows.push(['Documents Generated', (iss.documents_generated || []).join(', ') || '']);
rows.push(['Email Notification', (iss.notifications_sent || {}).email ? 'Sent' : 'Pending']);
}
rows.push([]);
// ── AGENT 6: AUDIT TRAIL ─────────────────────────────────────
rows.push(sep);
rows.push(['SECTION 7: AGENT 6 β€” AXIOM AUDIT TRAIL']);
rows.push(sep);
rows.push(['Field', 'Value']);
rows.push(['Final Decision', audit.decision || '']);
rows.push(['Policy Issued', audit.decision === 'DECLINED' ? 'NO β€” Not Issued' : 'YES']);
rows.push(['Halted At Agent', audit.halted_at || (audit.decision === 'DECLINED' ? 'See decline reasons' : 'N/A β€” all agents passed')]);
rows.push(['Audit Confidence Score', (audit.audit_score || 0) + '%']);
rows.push(['Risk Band', audit.risk_band || '']);
rows.push(['Audit Log ID', audit.audit_log_id || '']);
rows.push(['Audited At', audit.audited_at || '']);
rows.push([]);
rows.push(['Plain-English Summary']);
rows.push([audit.overall_summary || '']);
rows.push([]);
// Agent decision factors table
if (factors.length > 0) {
rows.push(['AGENT DECISION FACTORS BREAKDOWN']);
rows.push(['Agent', 'Factor Checked', 'Outcome', 'Detail']);
factors.forEach(function(f) {
rows.push([f.agent || '', f.factor || '', f.outcome || '', f.detail || '']);
});
rows.push([]);
}
// ── Convert to CSV ───────────────────────────────────────────
function escCell(v) {
var s = String(v == null ? '' : v).replace(/"/g, '""');
return (s.indexOf(',') >= 0 || s.indexOf('"') >= 0 || s.indexOf('\n') >= 0) ? '"' + s + '"' : s;
}
var csv = rows.map(function(r) {
return Array.isArray(r) ? r.map(escCell).join(',') : escCell(r);
}).join('\n');
var blob = new Blob(['\uFEFF' + csv], {type:'text/csv;charset=utf-8;'});
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'PolicyBridge_Analysis_' + (audit.submission_id || pipeline.id || 'report') + '_' + new Date().toISOString().slice(0,10) + '.csv';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// ═══════════════════════════════════════════════════════════
// CLAIMSENSE JS
// ═══════════════════════════════════════════════════════════
var claimRunning = false;
var _lastTriageData = null;
var _lastClaimAuditData = null;
function switchTab(tab) {
var queueSection = document.querySelector('.queue-section');
var mainGrid = document.querySelector('.main-grid');
var controls = document.querySelector('.controls');
var timeline = document.querySelector('.tl');
var claimsSec = document.getElementById('claimsSection');
var radarSec = document.getElementById('radarSection');
var pbSec = document.getElementById('pbSection');
if (radarSec) { radarSec.style.display='none'; radarSec.classList.remove('active'); }
if (claimsSec) { claimsSec.style.display='none'; claimsSec.classList.remove('active'); }
if (pbSec) { pbSec.style.display='none'; pbSec.classList.remove('active'); }
if (tab === 'policybridge') {
if (queueSection) queueSection.style.display = 'none';
if (mainGrid) mainGrid.style.display = 'none';
if (controls) controls.style.display = 'none';
if (timeline) timeline.style.display = 'none';
pbSec.classList.add('active'); pbSec.style.display = 'block';
pbInit();
document.querySelectorAll('.tab').forEach(function(t){ t.classList.remove('active'); });
if (event && event.target) event.target.classList.add('active');
return;
}
if (tab === 'radar') {
if (queueSection) queueSection.style.display = 'none';
if (mainGrid) mainGrid.style.display = 'none';
if (controls) controls.style.display = 'none';
if (timeline) timeline.style.display = 'none';
radarSec.classList.add('active'); radarSec.style.display = 'block';
setTimeout(function(){
if (!_rrMap) _initMap();
if (_rrMap) _rrMap.invalidateSize();
loadRRHistory();
}, 100);
document.querySelectorAll('.tab').forEach(function(t){ t.classList.remove('active'); });
if (event && event.target) event.target.classList.add('active');
return;
}
if (tab === 'claims') {
if (queueSection) queueSection.style.display = 'none';
if (mainGrid) mainGrid.style.display = 'none';
if (controls) controls.style.display = 'none';
if (timeline) timeline.style.display = 'none';
claimsSec.classList.add('active');
claimsSec.style.display = 'block';
loadRecentClaims();
} else {
if (queueSection) queueSection.style.display = '';
if (mainGrid) mainGrid.style.display = '';
if (controls) controls.style.display = '';
if (timeline) timeline.style.display = '';
if (typeof selectedSrc !== 'undefined') selectedSrc = tab === 'live' ? 'live' : 'demo';
loadSubmissions();
var startBtn = document.getElementById('startBtn');
var liveBtn = document.getElementById('liveBtn');
if (startBtn) startBtn.style.display = tab === 'demo' ? '' : 'none';
if (liveBtn) liveBtn.style.display = tab === 'live' ? '' : 'none';
}
document.querySelectorAll('.tab').forEach(function(t){ t.classList.remove('active'); });
if (event && event.target) event.target.classList.add('active');
}
function _setFnolStatus(msg, color) {
var el = document.getElementById('fnolStatus');
if (el) { el.textContent = msg; el.style.color = color || 'var(--dim2)'; }
}
async function submitAndRunClaim() {
if (claimRunning) return;
var policy = document.getElementById('fnol_policy').value.trim();
var name = document.getElementById('fnol_name').value.trim();
var iDate = document.getElementById('fnol_date').value;
var iType = document.getElementById('fnol_type').value;
if (!policy || !name || !iDate) {
_setFnolStatus('⚠ Policy number, claimant name and incident date are required.', '#fca5a5');
return;
}
// Duplicate check
try {
var _acd = new AbortController(); var _td = setTimeout(function(){_acd.abort()},6000);
var dupResp = await fetch(API_BASE + '/claims/recent?limit=50', {signal:_acd.signal});
clearTimeout(_td);
var dupData = await dupResp.json();
var existing = (dupData.claims || []).find(function(c) {
return c.policy_number === policy && c.incident_type === iType && c.incident_date === iDate;
});
if (existing) {
_setFnolStatus('⚠ Duplicate: ' + existing.claim_id + ' already filed for this policy / date / type.', '#fca5a5');
return;
}
} catch(e) { /* allow if check fails */ }
claimRunning = true;
document.getElementById('submitFnolBtn').disabled = true;
_claimStepsReset();
document.getElementById('triageResult').style.display = 'none';
var claimId = 'CLM-' + new Date().getFullYear() + '-' + String(Math.floor(Math.random()*90000)+10000);
var payload = {
claim_id: claimId, policy_number: policy, claimant_name: name,
incident_date: iDate, incident_type: iType,
coverage_type_code: document.getElementById('fnol_coverage').value,
estimated_damage: parseFloat(document.getElementById('fnol_damage').value) || 0,
incident_description: document.getElementById('fnol_desc').value.trim(),
has_police_report: document.getElementById('fnol_police').value === '1',
has_photos: document.getElementById('fnol_photos').value === '1',
};
_setFnolStatus('Submitting FNOL...', '#fb923c');
try {
var _ac0 = new AbortController(); var _t0 = setTimeout(function(){_ac0.abort()},10000);
var subResp = await fetch(API_BASE + '/claim/submit', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify(payload), signal: _ac0.signal
});
clearTimeout(_t0);
if (!subResp.ok) throw new Error('Submit failed: ' + subResp.status);
var steps = ['fnol','coverage','fraud','severity','triage'];
var labels = ['FNOL Intake','Coverage','Fraud Signal','Severity','Triage'];
var results = {};
for (var i=0; i<steps.length; i++) {
_claimStepSet(i+1, 'running');
_setFnolStatus('Agent ' + (i+1) + '/5 β€” ' + labels[i] + '...', '#fb923c');
var _ac = new AbortController(); var _t = setTimeout(function(){_ac.abort()},12000);
var r = await fetch(API_BASE + '/claim/agent/' + steps[i], {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({claim_id: claimId}), signal: _ac.signal
});
clearTimeout(_t);
results[steps[i]] = await r.json().catch(function(){return {};});
_claimStepSet(i+1, 'done');
if (steps[i]==='fnol' && results['fnol'].status==='FNOL_INVALID') {
for (var j=i+2;j<=5;j++) _claimStepSet(j,'failed');
_renderTriageResult(claimId,'DENY',results,payload); break;
}
if (steps[i]==='coverage' && results['coverage'].coverage_status==='EXCLUDED') {
for (var j=i+2;j<=5;j++) _claimStepSet(j,'failed');
_renderTriageResult(claimId,'DENY',results,payload); break;
}
await new Promise(function(res){setTimeout(res,300)});
}
if (results['triage']) _renderTriageResult(claimId, results['triage'].triage_outcome, results, payload);
_setFnolStatus('Triage complete β€” ' + claimId, '#86efac');
await loadRecentClaims();
} catch(err) {
_setFnolStatus('Error: ' + err.message, '#fca5a5');
console.error('[CLAIMSENSE]', err);
} finally {
claimRunning = false;
document.getElementById('submitFnolBtn').disabled = false;
}
}
function _renderTriageResult(claimId, outcome, results, payload) {
var triage = results['triage'] || {};
var fraud = results['fraud'] || {};
var severity = results['severity'] || {};
var coverage = results['coverage'] || {};
var fnol = results['fnol'] || {};
_lastTriageData = {claimId, outcome, triage, fraud, severity, coverage, fnol, payload};
var outcomeColor = {AUTO_SETTLE:'#86efac',ADJUSTER_REVIEW:'#fde68a',SIU_REFERRAL:'#fca5a5',DENY:'#9ca3af'}[outcome]||'#fff';
var html = '<div class="triage-card ' + (outcome||'').toLowerCase() + '">';
html += '<div style="font-size:11px;font-weight:700;letter-spacing:.1em;padding:4px 12px;border:1px solid ' + outcomeColor + ';color:' + outcomeColor + ';display:inline-block;margin-bottom:8px;font-family:Share Tech Mono,monospace;">' + (outcome||'UNKNOWN') + '</div>';
html += '<div style="font-size:12px;color:var(--dim);line-height:1.7;margin-bottom:10px;">' + (triage.overall_summary||'Triage complete.') + '</div>';
html += '<div class="triage-metrics">';
var fraudScore = fraud.fraud_score||0;
html += '<div class="triage-metric"><div class="triage-metric-label">Fraud Score</div><div class="triage-metric-val" style="color:' + (fraudScore>=70?'#fca5a5':fraudScore>=40?'#fde68a':'#86efac') + '">' + fraudScore + '/100</div></div>';
html += '<div class="triage-metric"><div class="triage-metric-label">Severity</div><div class="triage-metric-val">' + (severity.severity_band||'β€”') + '</div></div>';
html += '<div class="triage-metric"><div class="triage-metric-label">Reserve Est.</div><div class="triage-metric-val" style="color:#fb923c">$' + ((severity.reserve_estimate||0).toLocaleString()) + '</div></div>';
html += '<div class="triage-metric"><div class="triage-metric-label">Coverage</div><div class="triage-metric-val" style="color:' + (coverage.coverage_status==='COVERED'?'#86efac':'#fca5a5') + '">' + (coverage.coverage_status||'β€”') + '</div></div>';
html += '<div class="triage-metric"><div class="triage-metric-label">Priority</div><div class="triage-metric-val">' + (triage.triage_priority||'β€”') + '</div></div>';
html += '<div class="triage-metric"><div class="triage-metric-label">Queue</div><div class="triage-metric-val" style="font-size:10px">' + (triage.adjuster_queue||'β€”').replace(/_/g,' ') + '</div></div>';
html += '</div>';
var flags = fraud.fraud_flags||[];
if (flags.length>0) {
html += '<div style="margin-bottom:10px"><div style="font-family:Share Tech Mono,monospace;font-size:8px;letter-spacing:2px;color:#fca5a5;margin-bottom:5px">⚠ FRAUD SIGNALS</div>';
flags.forEach(function(f){ html += '<div style="font-size:11px;color:#fca5a5;margin-bottom:2px">β€’ ' + f + '</div>'; });
html += '</div>';
}
html += '<div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap">';
html += '<button class="btn" style="font-size:10px;border-color:rgba(0,229,160,.4);color:#00e5a0" onclick="openClaimAuditFromResult()">πŸ” View Audit Trail</button>';
html += '<button class="btn" style="font-size:10px;border-color:rgba(251,146,60,.4);color:#fb923c" onclick="exportClaimAuditReport()">⬇ Export Report</button>';
html += '</div>';
html += '</div>';
var el = document.getElementById('triageResult');
el.innerHTML = html; el.style.display = 'block';
el.scrollIntoView({behavior:'smooth',block:'nearest'});
}
function openClaimAuditFromResult() {
if (!_lastTriageData) return;
var d = _lastTriageData;
openClaimAuditModal({
claim_id: d.claimId,
triage_outcome: d.outcome,
audit_score: d.triage.audit_score || 0,
overall_summary: d.triage.overall_summary || '',
decision_factors: d.triage.decision_factors || [],
fraud_score: (d.fraud||{}).fraud_score || 0,
fraud_band: (d.fraud||{}).fraud_band || 'LOW',
fraud_flags: (d.fraud||{}).fraud_flags || [],
severity_band: (d.severity||{}).severity_band || '',
reserve_estimate: (d.severity||{}).reserve_estimate || 0,
reserve_note: (d.severity||{}).reserve_note || '',
coverage_status: (d.coverage||{}).coverage_status || '',
coverage_type: (d.coverage||{}).coverage_type || '',
applicable_limit: (d.coverage||{}).applicable_limit || 0,
deductible: (d.coverage||{}).deductible || 0,
net_payable_est: (d.coverage||{}).net_payable_est || 0,
days_to_report: (d.fnol||{}).days_to_report || 0,
completeness_score:(d.fnol||{}).completeness_score || 0,
fnol_status: (d.fnol||{}).status || '',
incident_type: d.payload.incident_type || '',
incident_date: d.payload.incident_date || '',
claimant_name: d.payload.claimant_name || '',
policy_number: d.payload.policy_number || '',
audit_log_id: d.triage.audit_log_id || 'N/A',
});
}
async function loadAndOpenClaimAudit(claimId) {
try {
var _ac = new AbortController(); var _t = setTimeout(function(){_ac.abort()},8000);
var r = await fetch(API_BASE + '/claim/audit/' + claimId, {signal:_ac.signal});
clearTimeout(_t);
if (!r.ok) { alert('No audit record for ' + claimId + '. Run the pipeline first.'); return; }
var a = await r.json();
openClaimAuditModal({
claim_id: claimId,
triage_outcome: a.triage_outcome,
audit_score: a.audit_score || 0,
overall_summary: a.overall_summary || '',
decision_factors: a.decision_factors || (a.decision_factors_json ? JSON.parse(a.decision_factors_json) : []),
fraud_flags: a.fraud_flags || (a.fraud_flags_json ? JSON.parse(a.fraud_flags_json) : []),
fraud_score: a.fraud_score || 0,
fraud_band: a.fraud_score>=70?'HIGH':a.fraud_score>=40?'MEDIUM':'LOW',
severity_band: a.severity_band || '',
reserve_estimate: a.reserve_estimate || 0,
coverage_status: a.coverage_status || '',
triage_priority: a.triage_priority || '',
adjuster_queue: a.assigned_adjuster_queue || '',
policy_number: a.policy_number || '',
audit_log_id: a.id || 'N/A',
});
} catch(err) { alert('Could not load audit: ' + err.message); }
}
function openClaimAuditModal(d) {
_lastClaimAuditData = d;
var overlay = document.getElementById('claimAuditOverlay');
var body = document.getElementById('claimAuditBody');
var sub = document.getElementById('claimAuditSub');
var outcome = d.triage_outcome || 'UNKNOWN';
var score = d.audit_score || 0;
var fraud = d.fraud_score || 0;
var reserve = d.reserve_estimate || 0;
var factors = d.decision_factors || [];
var flags = d.fraud_flags || [];
sub.textContent = 'Claim: ' + (d.claim_id||'') + ' Β· Audit Log: ' + (d.audit_log_id||'N/A') + ' Β· ' + new Date().toLocaleString();
var oc = {AUTO_SETTLE:'#00e5a0',ADJUSTER_REVIEW:'#f59e0b',SIU_REFERRAL:'#ef4444',DENY:'#888888'}[outcome]||'#fff';
var sc = score>=70?'#00e5a0':score>=40?'#f59e0b':'#ef4444';
var fc = fraud>=70?'#ef4444':fraud>=40?'#f59e0b':'#00e5a0';
var html = '';
html += '<div style="display:flex;align-items:center;gap:.8rem;margin-bottom:1rem;flex-wrap:wrap;">';
html += '<div style="font-size:9px;letter-spacing:.15em;color:rgba(255,255,255,.4);text-transform:uppercase;">Triage Decision</div>';
html += '<div style="font-size:11px;font-weight:700;letter-spacing:.12em;padding:.25rem .8rem;border:1px solid ' + oc + ';color:' + oc + ';font-family:Share Tech Mono,monospace;">' + outcome + '</div>';
html += '<div style="font-size:10px;color:rgba(255,255,255,.4);font-family:Share Tech Mono,monospace;">Priority: ' + (d.triage_priority||'β€”') + '</div>';
html += '</div>';
html += '<div class="audit-summary-box"><div class="audit-summary-label">Plain-English Triage Summary</div>' + (d.overall_summary||'No summary available.') + '</div>';
html += '<div class="audit-score-row"><div class="audit-score-label">Triage Confidence Score</div>';
html += '<div class="audit-score-track"><div class="audit-score-fill" id="claimScoreFill" style="width:0%;background:' + sc + '"></div></div>';
html += '<div class="audit-score-val" style="color:' + sc + '">' + score + '%</div></div>';
// Metrics grid
html += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:8px;margin:1rem 0;">';
[{l:'Fraud Score',v:fraud+'/100',c:fc},{l:'Severity',v:d.severity_band||'β€”',c:'#fff'},
{l:'Reserve Est.',v:'$'+Number(reserve).toLocaleString(),c:'#fb923c'},
{l:'Coverage',v:d.coverage_status||'β€”',c:d.coverage_status==='COVERED'?'#00e5a0':'#ef4444'},
{l:'Priority',v:d.triage_priority||'β€”',c:'#fff'},
{l:'Queue',v:(d.adjuster_queue||'β€”').replace(/_/g,' '),c:'#00d4ff'}
].forEach(function(m){
html += '<div style="background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.07);padding:8px 10px;">';
html += '<div style="font-family:Share Tech Mono,monospace;font-size:8px;letter-spacing:1px;color:rgba(255,255,255,.4);text-transform:uppercase;margin-bottom:3px;">' + m.l + '</div>';
html += '<div style="font-size:13px;font-weight:700;color:' + m.c + ';font-family:Share Tech Mono,monospace;">' + m.v + '</div></div>';
});
html += '</div>';
if (flags.length>0) {
html += '<div class="audit-decline-box"><div class="audit-decline-title">⚠ Fraud Signal Flags</div>';
flags.forEach(function(f,i){ html += '<div class="audit-decline-item">'+(i+1)+'. '+f+'</div>'; });
html += '</div>';
}
var agentColors = {
'PASS':'#00e5a0','COVERED':'#00e5a0','FNOL_ACCEPTED':'#00e5a0','LOW':'#00e5a0','AUTO_SETTLE':'#00e5a0','MINOR':'#00e5a0',
'MEDIUM':'#f59e0b','PARTIAL':'#f59e0b','ADJUSTER_REVIEW':'#f59e0b','MODERATE':'#f59e0b','MAJOR':'#f59e0b',
'HIGH':'#ef4444','SIU_REFERRAL':'#ef4444','EXCLUDED':'#ef4444','FNOL_INVALID':'#ef4444','DENY':'#ef4444','CATASTROPHIC':'#ef4444',
'SCORED':'#00d4ff','CALCULATED':'#00d4ff',
};
html += '<div class="audit-factors-title">Agent Decision Factors (' + factors.length + ' checks)</div>';
factors.forEach(function(f){
var fc2 = agentColors[f.outcome]||'#888';
html += '<div class="audit-factor-card"><div class="audit-factor-header">';
html += '<span class="audit-factor-agent">'+(f.agent||'')+'</span>';
html += '<span class="audit-factor-badge" style="border-color:'+fc2+';color:'+fc2+'">'+(f.outcome||'')+'</span></div>';
html += '<div class="audit-factor-label">'+(f.factor||'')+'</div>';
html += '<div class="audit-factor-detail">'+(f.detail||'')+'</div></div>';
});
html += '<div style="margin-top:1.2rem;padding-top:1rem;border-top:1px solid rgba(255,255,255,.08);">';
html += '<button onclick="exportClaimAuditReport()" style="background:transparent;border:1px solid rgba(251,146,60,.5);color:#fb923c;padding:7px 16px;font-family:Share Tech Mono,monospace;font-size:10px;letter-spacing:.1em;cursor:pointer;">⬇ EXPORT FULL TRIAGE REPORT (CSV)</button>';
html += '</div>';
body.innerHTML = html;
overlay.style.display = 'flex';
document.addEventListener('keydown', _claimEscHandler);
setTimeout(function(){
var fill = document.getElementById('claimScoreFill');
if (fill) fill.style.cssText += ';width:'+score+'%;transition:width 1s ease;';
}, 80);
}
function _claimEscHandler(e){ if(e.key==='Escape') closeClaimAuditModal(); }
function closeClaimAuditModal(){
document.getElementById('claimAuditOverlay').style.display='none';
document.removeEventListener('keydown',_claimEscHandler);
}
function exportClaimAuditReport() {
var d = _lastClaimAuditData; if (!d) return;
var outcome = d.triage_outcome||'';
var factors = d.decision_factors||[];
var flags = d.fraud_flags||[];
var sep = ['──────────────────────────────────────'];
var rows = [];
rows.push(['CLAIMSENSE β€” AXIOM TRIAGE AUDIT REPORT']);
rows.push(['Generated', new Date().toLocaleString()]);
rows.push(['Claim ID', d.claim_id||'']); rows.push(['Policy Number', d.policy_number||'']);
rows.push(['Triage Outcome', outcome]); rows.push(['Priority', d.triage_priority||'']);
rows.push(['Audit Confidence', (d.audit_score||0)+'%']); rows.push([]);
rows.push(sep);
rows.push(['SECTION 1: FNOL INTAKE']); rows.push(sep);
rows.push(['Claimant Name', d.claimant_name||'']); rows.push(['Incident Type', d.incident_type||'']);
rows.push(['Incident Date', d.incident_date||'']); rows.push(['Days to Report', d.days_to_report||'']);
rows.push(['FNOL Status', d.fnol_status||'']); rows.push(['Completeness', (d.completeness_score||'')+'%']); rows.push([]);
rows.push(sep);
rows.push(['SECTION 2: COVERAGE VERIFICATION']); rows.push(sep);
rows.push(['Coverage Status', d.coverage_status||'']); rows.push(['Coverage Type', d.coverage_type||'']);
rows.push(['Applicable Limit', d.applicable_limit?'$'+Number(d.applicable_limit).toLocaleString():'']);
rows.push(['Deductible', d.deductible?'$'+Number(d.deductible).toLocaleString():'']);
rows.push(['Net Payable Est.', d.net_payable_est?'$'+Number(d.net_payable_est).toLocaleString():'']); rows.push([]);
rows.push(sep);
rows.push(['SECTION 3: FRAUD SIGNAL DETECTION']); rows.push(sep);
rows.push(['Fraud Score', (d.fraud_score||0)+'/100']); rows.push(['Fraud Band', d.fraud_band||'']);
if (flags.length>0) rows.push(['Fraud Flags', flags.join(' | ')]); rows.push([]);
rows.push(sep);
rows.push(['SECTION 4: SEVERITY & RESERVE']); rows.push(sep);
rows.push(['Severity Band', d.severity_band||'']);
rows.push(['Reserve Estimate', d.reserve_estimate?'$'+Number(d.reserve_estimate).toLocaleString():'']);
rows.push(['Reserve Note', d.reserve_note||'']); rows.push([]);
rows.push(sep);
rows.push(['SECTION 5: TRIAGE & ROUTING']); rows.push(sep);
rows.push(['Triage Outcome', outcome]); rows.push(['Priority', d.triage_priority||'']);
rows.push(['Adjuster Queue', (d.adjuster_queue||'').replace(/_/g,' ')]);
rows.push(['Audit Log ID', d.audit_log_id||'N/A']); rows.push([]);
rows.push(sep);
rows.push(['SECTION 6: AGENT DECISION FACTORS']); rows.push(sep);
rows.push(['Agent','Factor','Outcome','Detail']);
factors.forEach(function(f){ rows.push([f.agent||'',f.factor||'',f.outcome||'',f.detail||'']); });
rows.push([]); rows.push(sep);
rows.push(['SECTION 7: PLAIN-ENGLISH SUMMARY']); rows.push(sep);
rows.push([d.overall_summary||'']);
function esc(v){ var s=String(v==null?'':v).replace(/"/g,'""'); return(s.indexOf(',')>=0||s.indexOf('"')>=0||s.indexOf('\n')>=0)?'"'+s+'"':s; }
var csv = rows.map(function(r){ return Array.isArray(r)?r.map(esc).join(','):esc(r); }).join('\n');
var blob = new Blob(['\uFEFF'+csv],{type:'text/csv;charset=utf-8;'});
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href=url; a.download='ClaimSense_Audit_'+(d.claim_id||'')+'_'+new Date().toISOString().slice(0,10)+'.csv';
document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
}
async function loadRecentClaims() {
try {
var _ac = new AbortController(); var _t = setTimeout(function(){_ac.abort()},8000);
var r = await fetch(API_BASE + '/claims/recent?limit=15', {signal:_ac.signal});
clearTimeout(_t);
var data = await r.json();
var claims = data.claims||[];
document.getElementById('claimCount').textContent = claims.length + ' claims';
var qEl = document.getElementById('claimQueue');
if (claims.length===0) { qEl.innerHTML='<div style="color:var(--dim2);font-size:11px;font-family:Share Tech Mono,monospace">No claims yet. Submit a FNOL above.</div>'; return; }
qEl.innerHTML = claims.map(function(c){
var cc = (c.incident_type||'other').toLowerCase();
var oc = c.triage_outcome ? '<span class="claim-chip '+(c.triage_outcome.toLowerCase())+'">'+(c.triage_outcome.replace(/_/g,' '))+'</span>' : '';
var ab = c.triage_outcome
? '<button onclick="event.stopPropagation();loadAndOpenClaimAudit(\'' + c.claim_id + '\')" style="margin-top:6px;width:100%;background:transparent;border:1px solid rgba(0,229,160,.25);color:#00e5a0;padding:4px;font-family:Share Tech Mono,monospace;font-size:8px;letter-spacing:.08em;cursor:pointer;">πŸ” VIEW AUDIT TRAIL</button>'
: '<div style="margin-top:6px;font-size:9px;color:var(--dim2);font-family:Share Tech Mono,monospace;text-align:center">Pipeline not yet run</div>';
return '<div class="claim-card" onclick="loadClaimDetail(\'' + c.claim_id + '\')">'
+ '<div class="claim-card-id">'+c.claim_id+'</div>'
+ '<div class="claim-card-name">'+(c.claimant_name||'β€”')+'</div>'
+ '<div class="claim-card-meta">'+(c.incident_type||'')+' Β· '+(c.incident_date||'')+'</div>'
+ '<div class="claim-card-chips"><span class="claim-chip '+cc+'">'+(c.incident_type||'OTHER')+'</span>'
+ (c.fraud_score!=null?'<span class="claim-chip">Fraud: '+c.fraud_score+'</span>':'')
+ (c.severity_band?'<span class="claim-chip">'+c.severity_band+'</span>':'')
+ oc + '</div>' + ab + '</div>';
}).join('');
} catch(err) {
console.warn('[CLAIMS] loadRecentClaims:', err.message);
document.getElementById('claimQueue').innerHTML='<div style="color:#fca5a5;font-size:11px;font-family:Share Tech Mono,monospace">API offline β€” submit a claim to test.</div>';
}
}
async function loadClaimDetail(claimId) {
document.querySelectorAll('.claim-card').forEach(function(c){c.classList.remove('selected')});
if (event && event.currentTarget) event.currentTarget.classList.add('selected');
_setFnolStatus('Selected: ' + claimId, 'var(--dim2)');
}
// ═══════════════════════════════════════════════════════════
// RISKRADAR JS
// ═══════════════════════════════════════════════════════════
var _rrMap = null;
var _rrHeatLayer = null;
var _rrMarker = null;
var _rrData = null;
var _rrPeril = 'wind';
var _rrRunning = false;
function _initMap() {
if (_rrMap) return;
_rrMap = L.map('radarMap', {zoomControl:true, attributionControl:true})
.setView([37.8, -96], 4);
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution:'&copy; OpenStreetMap &copy; CARTO',
maxZoom:18
}).addTo(_rrMap);
}
function _bandColor(score) {
if (score >= 75) return '#ef4444';
if (score >= 55) return '#f97316';
if (score >= 35) return '#fbbf24';
return '#34d399';
}
function _renderHeatmap(points, peril) {
if (!_rrMap) return;
if (_rrHeatLayer) { _rrMap.removeLayer(_rrHeatLayer); _rrHeatLayer = null; }
if (_rrMarker) { _rrMap.removeLayer(_rrMarker); _rrMarker = null; }
var subject = points.find(function(p){ return p.is_subject; });
var others = points.filter(function(p){ return !p.is_subject; });
// Draw surrounding points as circles
var layerGroup = L.layerGroup();
others.forEach(function(pt) {
var score = pt[peril] !== undefined ? pt[peril] : pt.overall;
var color = _bandColor(score);
var radius = 30 + score * 0.6;
L.circle([pt.lat, pt.lng], {
radius: radius * 18,
color: color,
fillColor: color,
fillOpacity: 0.28,
weight: 0.5,
opacity: 0.6,
}).addTo(layerGroup);
});
layerGroup.addTo(_rrMap);
_rrHeatLayer = layerGroup;
// Subject property marker
if (subject) {
var score = subject[peril] !== undefined ? subject[peril] : subject.overall;
var color = _bandColor(score);
var icon = L.divIcon({
className: '',
html: '<div style="width:20px;height:20px;border-radius:50%;background:' + color + ';border:2px solid #fff;box-shadow:0 0 8px ' + color + '"></div>',
iconSize: [20,20], iconAnchor:[10,10]
});
_rrMarker = L.marker([subject.lat, subject.lng], {icon:icon}).addTo(_rrMap);
var band = score>=75?'CRITICAL':score>=55?'HIGH':score>=35?'MEDIUM':'LOW';
_rrMarker.bindPopup(
'<div style="font-family:Share Tech Mono,monospace;font-size:11px;color:#fff;background:#0d1117;padding:8px 12px;border:1px solid ' + color + '">' +
'<div style="color:' + color + ';font-weight:700;letter-spacing:.1em;margin-bottom:4px">' + peril.toUpperCase() + ' RISK: ' + score + '/100</div>' +
'<div style="color:rgba(255,255,255,.6)">Band: ' + band + '</div>' +
'<div style="color:rgba(255,255,255,.6)">Subject Property</div></div>',
{className:'rr-popup'}
).openPopup();
_rrMap.setView([subject.lat, subject.lng], 12);
}
}
function setPerilLayer(peril, tabEl) {
_rrPeril = peril;
document.querySelectorAll('.peril-tab').forEach(function(t){ t.classList.remove('active'); });
if (tabEl) tabEl.classList.add('active');
if (_rrData) _renderHeatmap(_rrData.heatmap_points, peril);
// Update card highlights
['wind','flood','fire','quake'].forEach(function(p){
var card = document.getElementById('pc_'+p);
if (card) card.style.borderColor = (p===peril||peril==='overall') ? 'rgba(56,189,248,.4)' : 'rgba(255,255,255,.07)';
});
}
function _setRRStatus(msg, color) {
var el = document.getElementById('rrStatus');
if (el) { el.textContent=msg; el.style.color=color||'var(--dim2)'; }
}
function _bandClass(band) {
return 'band-' + (band||'low').toLowerCase();
}
function _perilBarColor(score) {
if (score>=75) return '#ef4444';
if (score>=55) return '#f97316';
if (score>=35) return '#fbbf24';
return '#34d399';
}
function _updateScoreCards(result) {
var scores = result.scores;
var bands = result.bands;
['wind','flood','fire','quake'].forEach(function(p){
var sc = scores[p]; var bd = (bands[p]||'LOW').toUpperCase();
document.getElementById('sc_'+p).textContent = sc;
document.getElementById('sc_'+p).style.color = _perilBarColor(sc);
var bdEl = document.getElementById('bd_'+p);
bdEl.textContent = bd;
bdEl.className = 'peril-card-band ' + _bandClass(bd);
setTimeout(function(){
document.getElementById('bar_'+p).style.cssText =
'width:'+sc+'%;background:'+_perilBarColor(sc)+';transition:width 1.2s ease';
}, 100);
});
var os = result.overall_score;
var ob = (result.overall_band||'LOW').toUpperCase();
document.getElementById('rrOverallScore').textContent = os;
var obEl = document.getElementById('rrOverallBand');
obEl.textContent = ob; obEl.className = 'peril-card-band ' + _bandClass(ob);
document.getElementById('rrOverallScore').style.color = _perilBarColor(os);
document.getElementById('rrNarrative').textContent = result.narrative || '';
document.getElementById('rrMethod').textContent = 'Model: ' + (result._method||'rules');
document.getElementById('rrOverall').style.display = 'block';
document.getElementById('rrPerilCards').style.display = 'grid';
}
async function runRiskRadar() {
if (_rrRunning) return;
var state = document.getElementById('rr_state').value;
var zip = document.getElementById('rr_zip').value.trim();
if (!zip || zip.length < 5) { _setRRStatus('⚠ Enter a 5-digit ZIP code.','#fca5a5'); return; }
_rrRunning = true;
document.getElementById('rrScoreBtn').disabled = true;
_setRRStatus('Scoring peril risk...','#38bdf8');
if (!_rrMap) _initMap();
var payload = {
address: document.getElementById('rr_address').value.trim(),
state_code: state,
zip: zip,
year_built: parseInt(document.getElementById('rr_year').value)||2000,
roof_year: parseInt(document.getElementById('rr_roof').value)||2015,
construction_type: document.getElementById('rr_constr').value,
num_stories: parseInt(document.getElementById('rr_stories').value)||2,
};
try {
var _ac = new AbortController(); var _t = setTimeout(function(){_ac.abort()},12000);
var r = await fetch(API_BASE + '/riskradar/score', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify(payload), signal: _ac.signal
});
clearTimeout(_t);
if (!r.ok) throw new Error('Score failed: ' + r.status);
var result = await r.json();
_rrData = result;
_updateScoreCards(result);
_renderHeatmap(result.heatmap_points, _rrPeril);
_setRRStatus('βœ“ Risk scored β€” ' + (result.address || zip + ', ' + state), '#34d399');
loadRRHistory();
} catch(err) {
_setRRStatus('Error: ' + err.message, '#fca5a5');
console.error('[RISKRADAR]', err);
} finally {
_rrRunning = false;
document.getElementById('rrScoreBtn').disabled = false;
}
}
async function loadRRHistory() {
try {
var _ac = new AbortController(); var _t = setTimeout(function(){_ac.abort()},6000);
var r = await fetch(API_BASE + '/riskradar/history?limit=8', {signal:_ac.signal});
clearTimeout(_t);
var data = await r.json();
var lookups = data.lookups||[];
var el = document.getElementById('rrHistory');
if (lookups.length===0) { el.innerHTML='<div style="font-size:10px;color:var(--dim2);font-family:Share Tech Mono,monospace">No lookups yet.</div>'; return; }
el.innerHTML = lookups.map(function(l){
var band = l.overall_band||'LOW';
var color = {CRITICAL:'#ef4444',HIGH:'#f97316',MEDIUM:'#fbbf24',LOW:'#34d399'}[band]||'#888';
return '<div class="history-row" onclick="reuseHistoryLookup('+JSON.stringify(l)+')">'
+ '<div class="history-addr">' + (l.address||l.zip_code+', '+l.state_code) + '</div>'
+ '<span class="history-chip" style="color:'+color+';border-color:'+color+'">'+band+'</span>'
+ '<span style="font-family:Share Tech Mono,monospace;font-size:9px;color:var(--dim2)">'+(l.overall_score||0)+'</span>'
+ '</div>';
}).join('');
} catch(e) { /* silent */ }
}
function reuseHistoryLookup(l) {
if (l.state_code) document.getElementById('rr_state').value = l.state_code;
if (l.zip_code) document.getElementById('rr_zip').value = l.zip_code;
if (l.address) document.getElementById('rr_address').value = l.address;
runRiskRadar();
}
function exportRiskReport() {
var d = _rrData; if (!d) return;
var scores = d.scores||{}; var bands = d.bands||{};
var rows = [];
rows.push(['RISKRADAR β€” GEO RISK INTELLIGENCE REPORT']);
rows.push(['Generated', new Date().toLocaleString()]);
rows.push(['Address', d.address||'']); rows.push(['State', d.state_code]);
rows.push(['ZIP', d.zip_code]); rows.push(['Lat/Lng', d.latitude+' / '+d.longitude]);
rows.push([]); rows.push(['─────────────────────────────────']);
rows.push(['PERIL SCORES']); rows.push(['Peril','Score','Band']);
rows.push(['Wind', scores.wind||0, (bands.wind||'').toUpperCase()]);
rows.push(['Flood', scores.flood||0, (bands.flood||'').toUpperCase()]);
rows.push(['Fire', scores.fire||0, (bands.fire||'').toUpperCase()]);
rows.push(['Earthquake', scores.quake||0, (bands.quake||'').toUpperCase()]);
rows.push([]); rows.push(['Overall Score', d.overall_score, d.overall_band]);
rows.push([]); rows.push(['NARRATIVE']); rows.push([d.narrative||'']);
function esc(v){var s=String(v==null?'':v).replace(/"/g,'""');return(s.indexOf(',')>=0||s.indexOf('"')>=0)?'"'+s+'"':s;}
var csv = rows.map(function(r){return Array.isArray(r)?r.map(esc).join(','):esc(r);}).join('\n');
var blob = new Blob(['\uFEFF'+csv],{type:'text/csv;charset=utf-8;'});
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href=url; a.download='RiskRadar_'+d.zip_code+'_'+d.state_code+'_'+new Date().toISOString().slice(0,10)+'.csv';
document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
}
// =========================================================
// POLICYBRIDGE + MGA JS
// =========================================================
var PB = { step:1, coverage:'HO-3', subId:'', json:null, mgaData:null };
var MGA = { step:1, email:'', company:'', selected:null, data:{}, _matches:[] };
var MGA_DB = [
{name:'Acme Managing General Agency',state:'TX',city:'Austin',license:'MGA-TX-48291',lob:'Personal Lines, Commercial'},
{name:'Acme MGA Holdings LLC',state:'CA',city:'Los Angeles',license:'MGA-CA-77032',lob:'Commercial Lines'},
{name:'Pacific Bridge MGA',state:'FL',city:'Miami',license:'MGA-FL-19283',lob:'Personal Lines'},
{name:'Meridian Underwriting Group',state:'NY',city:'New York',license:'MGA-NY-66104',lob:'Specialty'},
{name:'Summit Risk Partners',state:'CO',city:'Denver',license:'MGA-CO-33215',lob:'Personal Lines, Farm'},
];
var _pbInitDone = false;
function pbInit() {
if (_pbInitDone) return;
_pbInitDone = true;
var states = ['AL','AK','AZ','AR','CA','CO','CT','DE','FL','GA','HI','ID','IL','IN','IA',
'KS','KY','LA','ME','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY',
'NC','ND','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY','DC'];
['pb_broker_state','pb_ins_state','pb_prop_state'].forEach(function(id){
var el = document.getElementById(id);
if (!el) return;
if (el.options.length > 1) return;
states.forEach(function(s){ var o=document.createElement('option'); o.value=s; o.textContent=s; el.appendChild(o); });
});
PB.subId = 'SUB-'+new Date().getFullYear()+'-'+String(Math.floor(10000+Math.random()*89999));
var pbLiveSubId = document.getElementById('pb-live-subid');
if (pbLiveSubId) pbLiveSubId.textContent = PB.subId;
var today = new Date().toISOString().split('T')[0];
var tmr = new Date(); tmr.setDate(tmr.getDate()+1);
var pbDate = document.getElementById('pb_sub_date');
var pbEff = document.getElementById('pb_eff_date');
if (pbDate) pbDate.value = today;
if (pbEff) pbEff.value = tmr.toISOString().split('T')[0];
setInterval(pbSyncPanel, 1000);
}
function pbGoStep(n) {
document.querySelectorAll('.pb-step').forEach(function(s){ s.classList.remove('active'); });
var sec = document.getElementById('pb-sec-'+n);
if (sec) sec.classList.add('active');
for (var i=1; i<=5; i++) {
var nav=document.getElementById('pb-snav-'+i);
var bub=document.getElementById('pb-sbb-'+i);
var wrap=document.getElementById('pb-sbw-'+i);
var line=document.getElementById('pb-sbl-'+i);
if (!nav) continue;
nav.classList.remove('active','done'); bub.classList.remove('active','done'); wrap.classList.remove('active','done');
if (i<n){ nav.classList.add('done'); bub.classList.add('done'); bub.textContent='v'; wrap.classList.add('done'); if(line)line.classList.add('done'); }
else if (i===n){ nav.classList.add('active'); bub.classList.add('active'); bub.textContent=i; wrap.classList.add('active'); if(line)line.classList.remove('done'); }
else { bub.textContent=i; if(line)line.classList.remove('done'); }
}
PB.step = n;
if (n===5) pbBuildReview();
var main = document.querySelector('.pb-main');
if (main) main.scrollTop = 0;
}
function pbNextStep(n) {
if (!pbValidateStep(n)) return;
pbGoStep(n+1);
}
function pbValidateStep(n) {
function req(id){ var el=document.getElementById(id); return !!(el && el.value && el.value.trim()); }
var ok = true;
if (n===1) ok = req('pb_broker_name') && req('pb_broker_code') && req('pb_broker_email') && req('pb_broker_state') && req('pb_lob');
if (n===2) ok = req('pb_ins_first') && req('pb_ins_last') && req('pb_ins_dob') && req('pb_ins_email') && req('pb_ins_street') && req('pb_ins_city') && req('pb_ins_state') && req('pb_ins_zip') && req('pb_credit');
if (n===3) ok = req('pb_prop_street') && req('pb_prop_city') && req('pb_prop_state') && req('pb_prop_zip') && req('pb_prop_type') && req('pb_year_built') && req('pb_sqft') && req('pb_construction') && req('pb_roof_type') && req('pb_roof_year');
if (n===4) ok = req('pb_cov_limit') && req('pb_deductible') && req('pb_eff_date');
if (!ok) alert('Please fill in all required fields before continuing.');
return ok;
}
function pbToggleChk(lbl, id) {
var c = document.getElementById(id); c.checked=!c.checked; lbl.classList.toggle('on', c.checked);
}
function pbSelCov(card) {
document.querySelectorAll('.pb-cov-card').forEach(function(c){ c.classList.remove('sel'); c.querySelector('input').checked=false; });
card.classList.add('sel'); card.querySelector('input').checked=true; PB.coverage=card.dataset.code;
}
function pbSyncPanel() {
function g(id){ var el=document.getElementById(id); return el?el.value.trim():''; }
var bn=g('pb_broker_name'); if(bn){ var e=document.getElementById('pb-lv-bname'); if(e) e.textContent=bn; }
var bc=g('pb_broker_code'); if(bc){ var e=document.getElementById('pb-lv-bcode'); if(e) e.textContent=bc; }
var bs=g('pb_broker_state'); if(bs){ var e=document.getElementById('pb-lv-bstate'); if(e) e.textContent=bs; }
var fn=g('pb_ins_first'), ln=g('pb_ins_last');
if(fn||ln){ var e=document.getElementById('pb-lv-iname'); if(e) e.textContent=[fn,ln].filter(Boolean).join(' '); }
var dob=g('pb_ins_dob'); if(dob){ var e=document.getElementById('pb-lv-idob'); if(e) e.textContent=dob; }
var cr=g('pb_credit'); if(cr){ var e=document.getElementById('pb-lv-icredit'); if(e) e.textContent=cr; }
var pc=g('pb_prop_city'), ps=g('pb_prop_state');
if(pc||ps){ var e=document.getElementById('pb-lv-ploc'); if(e) e.textContent=[pc,ps].filter(Boolean).join(', '); }
var pt=g('pb_prop_type'); if(pt){ var e=document.getElementById('pb-lv-ptype'); if(e) e.textContent=pt; }
var yb=g('pb_year_built'); if(yb){ var e=document.getElementById('pb-lv-pbuilt'); if(e) e.textContent=yb; }
var ctEl=document.getElementById('pb-lv-ctype'); if(ctEl) ctEl.textContent=PB.coverage;
var cl=g('pb_cov_limit'); if(cl){ var e=document.getElementById('pb-lv-climit'); if(e) e.textContent='$'+parseInt(cl).toLocaleString(); }
var dd=g('pb_deductible'); if(dd){ var e=document.getElementById('pb-lv-cded'); if(e) e.textContent='$'+parseInt(dd).toLocaleString(); }
}
function pbBuildReview() {
function g(id){ var el=document.getElementById(id); return el?el.value.trim():''; }
var rows=[
['Submission ID',PB.subId],['Broker',g('pb_broker_name')],['Broker Code',g('pb_broker_code')],
['State',g('pb_broker_state')],['LOB',g('pb_lob')],
['Insured',g('pb_ins_first')+' '+g('pb_ins_last')],['DOB',g('pb_ins_dob')],['Credit',g('pb_credit')],
['Property',g('pb_prop_street')+', '+g('pb_prop_city')+', '+g('pb_prop_state')+' '+g('pb_prop_zip')],
['Type',g('pb_prop_type')],['Built',g('pb_year_built')],['Construction',g('pb_construction')],
['Roof',g('pb_roof_type')+' ('+g('pb_roof_year')+')'],
['Coverage',PB.coverage],['Limit','$'+parseInt(g('pb_cov_limit')||0).toLocaleString()],
['Deductible','$'+parseInt(g('pb_deductible')||0).toLocaleString()],['Effective',g('pb_eff_date')],
];
var html='<div class="pb-card"><div class="pb-card-title">Submission Summary</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:4px 16px;">';
rows.forEach(function(r){
html+='<div style="font-size:10px;color:var(--dim);padding:4px 0;border-bottom:1px solid rgba(255,255,255,.04)">'+r[0]+'</div>';
html+='<div style="font-size:11px;color:var(--text);font-weight:500;padding:4px 0;border-bottom:1px solid rgba(255,255,255,.04);font-family:Share Tech Mono,monospace">'+r[1]+'</div>';
});
html+='</div></div>';
var rc=document.getElementById('pb-review-content'); if(rc) rc.innerHTML=html;
}
async function pbDoSubmit() {
function g(id){ var el=document.getElementById(id); return el?el.value.trim():''; }
var payload={
submission_id:PB.subId,
broker:{broker_code:g('pb_broker_code'),broker_name:g('pb_broker_name'),contact_email:g('pb_broker_email'),state_code:g('pb_broker_state')},
insured:{full_name:g('pb_ins_first')+' '+g('pb_ins_last'),dob:g('pb_ins_dob'),email:g('pb_ins_email'),phone:g('pb_ins_phone'),
credit_score:parseInt(g('pb_credit'))||650,kyc_status:'CLEAR',ofac_result:'CLEAR',fraud_signals:0,
street:g('pb_ins_street'),city:g('pb_ins_city'),state:g('pb_ins_state'),zip:g('pb_ins_zip')},
property:{street:g('pb_prop_street'),city:g('pb_prop_city'),state_code:g('pb_prop_state'),zip:g('pb_prop_zip'),
year_built:parseInt(g('pb_year_built'))||2000,square_footage:parseInt(g('pb_sqft'))||2000,
construction_type:g('pb_construction'),roof_type:g('pb_roof_type'),roof_year:parseInt(g('pb_roof_year'))||2015,
num_stories:parseInt(g('pb_stories'))||2,property_type:g('pb_prop_type'),occupancy:g('pb_occupancy'),num_units:parseInt(g('pb_units'))||1},
policy_request:{coverage_type:PB.coverage,requested_coverage_limit:parseInt(g('pb_cov_limit'))||300000,
requested_deductible:parseInt(g('pb_deductible'))||2500,effective_date:g('pb_eff_date'),notes:g('pb_notes')}
};
if (PB.mgaData) payload.mga = PB.mgaData;
try {
var _ac=new AbortController(); var _t=setTimeout(function(){_ac.abort()},12000);
var resp=await fetch(API_BASE+'/policybridge/submit',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload),signal:_ac.signal});
clearTimeout(_t);
var result=await resp.json().catch(function(){return {};});
console.log('[PB] Submit result:', result);
} catch(err) { console.warn('[PB] API submit failed:', err.message); }
pbShowSuccess(payload);
}
function pbShowSuccess(payload) {
pbGoStep(6);
var rows=[['Submission ID',PB.subId],['Insured',payload.insured.full_name],['Coverage',payload.policy_request.coverage_type],
['Limit','$'+parseInt(payload.policy_request.requested_coverage_limit).toLocaleString()],
['Effective',payload.policy_request.effective_date],['Broker',payload.broker.broker_name],['Status','RECEIVED -> Pipeline queued']];
var html='';
rows.forEach(function(r){
html+='<div style="display:flex;justify-content:space-between;font-size:11px;padding:4px 0;border-bottom:1px solid rgba(255,255,255,.05)">';
html+='<span style="color:var(--dim)">'+r[0]+'</span><span style="color:var(--text);font-weight:500;font-family:Share Tech Mono,monospace;font-size:10px">'+r[1]+'</span></div>';
});
var sd=document.getElementById('pb-suc-detail'); if(sd) sd.innerHTML=html;
}
function pbReset() {
PB.step=1; PB.coverage='HO-3'; _pbInitDone=false;
PB.subId='SUB-'+new Date().getFullYear()+'-'+String(Math.floor(10000+Math.random()*89999));
var pbLiveSubId=document.getElementById('pb-live-subid'); if(pbLiveSubId) pbLiveSubId.textContent=PB.subId;
document.querySelectorAll('.pb-input, .pb-select').forEach(function(el){ if(el.type!=='radio'&&el.type!=='checkbox') el.value=''; });
pbGoStep(1);
}
function openMgaOnboarding() {
var overlay=document.getElementById('mgaOverlay'); if(overlay) overlay.style.display='flex';
mgaGoStep(1);
var sel=document.getElementById('mga_state');
if (sel && sel.options.length<=1) {
var states=['AL','AK','AZ','AR','CA','CO','CT','DE','FL','GA','HI','ID','IL','IN','IA','KS','KY','LA','ME','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY','DC'];
states.forEach(function(s){ var o=document.createElement('option'); o.value=s; o.textContent=s; sel.appendChild(o); });
}
document.addEventListener('keydown', _mgaEscHandler);
}
function _mgaEscHandler(e){ if(e.key==='Escape') closeMgaOnboarding(); }
function closeMgaOnboarding(){ var o=document.getElementById('mgaOverlay'); if(o) o.style.display='none'; document.removeEventListener('keydown',_mgaEscHandler); }
function mgaGoStep(n) {
MGA.step=n;
document.querySelectorAll('.mga-step').forEach(function(s){ s.classList.remove('active'); });
var step=document.getElementById('mga-s'+n); if(step) step.classList.add('active');
for(var i=1;i<=4;i++){ var el=document.getElementById('mga-ps-'+i); if(!el) continue; el.classList.remove('active','done'); if(i<n) el.classList.add('done'); else if(i===n) el.classList.add('active'); }
if (n===4) mgaBuildConfirm();
}
async function mgaSearchCompany() {
var email=document.getElementById('mga_email').value.trim();
var company=document.getElementById('mga_company').value.trim();
if (!email||!company){ alert('Please enter both email and company name.'); return; }
MGA.email=email; MGA.company=company;
mgaGoStep(2);
var list=document.getElementById('mga-results-list');
list.innerHTML='<div style="color:var(--dim);font-size:11px;padding:8px"><span class="mga-spinner"></span>Searching...</div>';
await new Promise(function(res){ setTimeout(res,1200); });
var term=company.toLowerCase();
var matches=MGA_DB.filter(function(m){ return m.name.toLowerCase().includes(term.split(' ')[0]) || term.includes(m.name.toLowerCase().split(' ')[0]); });
if (matches.length===0) matches=MGA_DB.slice(0,2);
if (matches.length<2) matches=matches.concat(MGA_DB.slice(0,2-matches.length));
MGA._matches=matches;
list.innerHTML=matches.map(function(m,i){
return '<div class="mga-search-result" onclick="mgaSelectResult('+i+',this)"><div class="mga-result-name">'+m.name+'</div><div class="mga-result-meta">'+m.state+' - '+m.city+' - License: '+m.license+'</div></div>';
}).join('');
}
function mgaSelectResult(idx,el){ document.querySelectorAll('.mga-search-result').forEach(function(r){r.classList.remove('sel');}); el.classList.add('sel'); MGA.selectedIdx=idx; MGA.selected=MGA._matches[idx]; }
function mgaConfirmSelection() {
if (MGA.selected===undefined){ alert('Please select a company.'); return; }
var m=MGA.selected;
document.getElementById('mga_full_name').value=m.name;
document.getElementById('mga_license').value=m.license;
document.getElementById('mga_state').value=m.state;
document.getElementById('mga_city').value=m.city;
document.getElementById('mga_lob').value=m.lob;
mgaGoStep(3);
var dots=document.getElementById('mga-searching-dots'); var i=0;
var iv=setInterval(function(){ if(dots) dots.textContent=['...',' ..','.. ','...'][i++%4]; },400);
setTimeout(function(){ clearInterval(iv); if(dots) dots.textContent=' - Verified'; },2000);
}
function mgaManualEntry(){ MGA.selected={name:MGA.company,state:'',city:'',license:'',lob:''}; mgaGoStep(3); }
function mgaBuildConfirm() {
function g(id){ var el=document.getElementById(id); return el?el.value.trim():''; }
var name=g('mga_full_name'), license=g('mga_license')||'Not found', state=g('mga_state'), city=g('mga_city'), lob=g('mga_lob');
var code='MGA-'+(state||'XX')+'-'+Math.floor(10000+Math.random()*89999);
var bd=document.getElementById('mga-code-display'); if(bd) bd.textContent=code;
var rows=[['Company',name],['License',license],['State',state],['City',city],['LOB',lob],['Email',MGA.email]];
var html=rows.map(function(r){ return '<div class="mga-confirm-row"><span class="mga-ck">'+r[0]+'</span><span class="mga-cv">'+r[1]+'</span></div>'; }).join('');
var cd=document.getElementById('mga-confirm-details'); if(cd) cd.innerHTML=html;
MGA.data={company_name:name,license:license,state:state,city:city,lob:lob,email:MGA.email,master_broker_code:code};
}
function mgaComplete() {
PB.mgaData=MGA.data;
var s=document.getElementById('pb-mga-status'); if(s) s.textContent=MGA.data.company_name;
var p=document.getElementById('pb-mga-panel'); if(p) p.style.display='block';
var nm=document.getElementById('pb-mga-name'); if(nm) nm.textContent=MGA.data.company_name;
var cd=document.getElementById('pb-mga-code'); if(cd) cd.textContent=MGA.data.master_broker_code;
var st=document.getElementById('pb-mga-state'); if(st) st.textContent=MGA.data.state;
var li=document.getElementById('pb-mga-license'); if(li) li.textContent=MGA.data.license;
var bnEl=document.getElementById('pb_broker_name'); if(bnEl&&!bnEl.value) bnEl.value=MGA.data.company_name;
var bcEl=document.getElementById('pb_broker_code'); if(bcEl&&!bcEl.value) bcEl.value=MGA.data.master_broker_code;
var bsEl=document.getElementById('pb_broker_state'); if(bsEl&&!bsEl.value) bsEl.value=MGA.data.state;
var beEl=document.getElementById('pb_broker_email'); if(beEl&&!beEl.value) beEl.value=MGA.data.email;
fetch(API_BASE+'/mga/onboard',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({master_broker_code:MGA.data.master_broker_code,company_name:MGA.data.company_name,
email:MGA.data.email,license_number:MGA.data.license,state_code:MGA.data.state,city:MGA.data.city,
lines_of_business:MGA.data.lob,verified_by:'WEB_SEARCH'})
}).then(function(r){return r.json();}).then(function(d){console.log('[MGA] Saved:',d.master_broker_code);}).catch(function(e){console.warn('[MGA] Save failed:',e.message);});
closeMgaOnboarding();
var banner=document.createElement('div');
banner.style.cssText='position:fixed;top:64px;right:20px;background:#10b981;color:#fff;padding:10px 16px;border-radius:8px;font-size:12px;font-weight:500;z-index:9999;box-shadow:0 4px 20px rgba(16,185,129,.3)';
banner.textContent='MGA onboarded: '+MGA.data.master_broker_code;
document.body.appendChild(banner);
setTimeout(function(){if(banner.parentNode)document.body.removeChild(banner);},3000);
}
// =========================================================
// QUOTE COMPARISON JS
// =========================================================
var _pbSelectedQuote = null;
var _pbQuoteData = null;
function pbGoToQuotes() {
pbBuildReview();
pbGoStep(6);
loadPBQuotes();
}
async function loadPBQuotes() {
var loading = document.getElementById('pb-quote-loading');
var content = document.getElementById('pb-quote-content');
var btnRow = document.getElementById('pb-quote-btn-row');
if (loading) loading.style.display = 'block';
if (content) content.innerHTML = '';
if (btnRow) btnRow.style.display = 'none';
function g(id){ var el=document.getElementById(id); return el?el.value.trim():''; }
var payload = {
submission_id: PB.subId,
property: {
state_code: g('pb_prop_state'), zip: g('pb_prop_zip'),
year_built: parseInt(g('pb_year_built'))||2000,
square_footage: parseInt(g('pb_sqft'))||2000,
construction_type: g('pb_construction'),
roof_type: g('pb_roof_type'),
roof_year: parseInt(g('pb_roof_year'))||2015,
},
insured: { credit_score: parseInt(g('pb_credit'))||680 },
policy_request: {
coverage_type: PB.coverage,
requested_coverage_limit: parseInt(g('pb_cov_limit'))||300000,
requested_deductible: parseInt(g('pb_deductible'))||2500,
}
};
try {
var _ac = new AbortController(); var _t = setTimeout(function(){_ac.abort()},15000);
var r = await fetch(API_BASE + '/quote/compare', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify(payload), signal: _ac.signal
});
clearTimeout(_t);
var data = await r.json();
_pbQuoteData = data;
if (loading) loading.style.display = 'none';
renderQuoteComparison(data);
if (btnRow) btnRow.style.display = 'flex';
} catch(err) {
if (loading) loading.style.display = 'none';
if (content) content.innerHTML = '<div style="color:#ef4444;font-size:12px;padding:16px">Could not load quotes: ' + err.message + '</div>';
console.error('[QUOTE]', err);
}
}
function renderQuoteComparison(data) {
var content = document.getElementById('pb-quote-content');
if (!content) return;
var quotes = data.quotes || [];
if (!quotes.length) { content.innerHTML='<div style="color:var(--dim2)">No quotes available.</div>'; return; }
var marketAvg = data.market_avg || 0;
var html = '';
// AI narrative
html += '<div class="quote-narrative">';
html += '<div style="font-size:8px;letter-spacing:2px;color:#818cf8;text-transform:uppercase;margin-bottom:4px">AI Recommendation</div>';
html += data.recommendation || '';
html += '</div>';
// Market avg bar
html += '<div class="quote-header-bar">';
html += '<div class="quote-title">&#128200; 5 Competitive Quotes</div>';
html += '<div class="quote-market-avg">Market avg: <span style="color:var(--warn);font-weight:700">$' + marketAvg.toLocaleString() + '/yr</span></div>';
html += '</div>';
// Quote cards
html += '<div class="quote-grid">';
quotes.forEach(function(q, i) {
var isOurs = q.tier === 'our_quote';
var isBest = i === 0;
var cardClass = 'quote-card' + (isOurs ? ' our-quote' : '');
var scoreColor = q.score >= 85 ? '#818cf8' : q.score >= 75 ? '#10b981' : q.score >= 65 ? '#f59e0b' : '#ef4444';
var savingClass = q.savings_vs_market >= 0 ? 'qs-positive' : 'qs-negative';
var savingText = q.savings_vs_market >= 0
? 'Save $' + Math.abs(q.savings_vs_market).toLocaleString() + '/yr vs avg'
: '$' + Math.abs(q.savings_vs_market).toLocaleString() + '/yr above avg';
html += '<div class="' + cardClass + '" id="qcard-' + i + '" onclick="selectQuote(' + i + ')">';
// Badge
if (isOurs) html += '<div class="quote-badge qb-recommended">&#11088; RECOMMENDED</div>';
else if (isBest) html += '<div class="quote-badge qb-cheapest">LOWEST PRICE</div>';
else if (i === 1) html += '<div class="quote-badge qb-established">ESTABLISHED</div>';
html += '<div class="quote-carrier" style="color:' + q.color + '">' + q.carrier + '</div>';
html += '<div class="quote-am-best">AM Best: ' + q.am_best + ' &#183; ' + (q.market_info||'') + '</div>';
html += '<div class="quote-premium-big" style="color:' + q.color + '">$' + (q.annual||0).toLocaleString() + '</div>';
html += '<div class="quote-monthly">$' + (q.monthly||0).toLocaleString() + '/month</div>';
html += '<div class="quote-saving ' + savingClass + '">' + savingText + '</div>';
// Score bar
html += '<div class="quote-score-bar"><div class="quote-score-fill" style="width:' + (q.score||0) + '%;background:' + scoreColor + '"></div></div>';
// Features
html += '<div class="quote-features">';
(q.features||[]).slice(0,3).forEach(function(f){ html += '&#10003; ' + f + '<br>'; });
html += '</div>';
// Pros
if ((q.pros||[]).length) {
html += '<div style="font-size:9px;color:#10b981;margin-bottom:4px">';
q.pros.slice(0,2).forEach(function(p){ html += '+ ' + p + '<br>'; });
html += '</div>';
}
// Cons
if ((q.cons||[]).length) {
html += '<div style="font-size:9px;color:#ef4444;margin-bottom:8px">';
q.cons.slice(0,1).forEach(function(c){ html += '- ' + c + '<br>'; });
html += '</div>';
}
html += '<button class="quote-select-btn" id="qbtn-' + i + '">' + (isOurs ? '&#9989; Select This Quote' : 'Select Quote') + '</button>';
html += '</div>';
});
html += '</div>';
// Proceed bar (hidden until selection)
html += '<div class="quote-proceed-bar" id="pb-proceed-bar" style="display:none">';
html += '<div class="qpb-info">Selected: <span class="qpb-carrier" id="qpb-carrier">--</span> &#183; <span class="qpb-premium" id="qpb-premium">--</span></div>';
html += '<div style="font-size:10px;color:var(--dim2);font-family:Share Tech Mono,monospace" id="qpb-saving"></div>';
html += '</div>';
content.innerHTML = html;
// Auto-select our quote
var ourIdx = quotes.findIndex(function(q){ return q.tier === 'our_quote'; });
if (ourIdx >= 0) selectQuote(ourIdx);
}
function selectQuote(idx) {
if (!_pbQuoteData) return;
var quotes = _pbQuoteData.quotes || [];
var q = quotes[idx];
if (!q) return;
_pbSelectedQuote = q;
// Update card styles
document.querySelectorAll('.quote-card').forEach(function(c){ c.classList.remove('selected-quote'); });
document.querySelectorAll('.quote-select-btn').forEach(function(b){ b.textContent = 'Select Quote'; });
var card = document.getElementById('qcard-' + idx);
var btn = document.getElementById('qbtn-' + idx);
if (card) card.classList.add('selected-quote');
if (btn) btn.textContent = '&#10003; Selected';
// Update proceed bar
var bar = document.getElementById('pb-proceed-bar');
if (bar) bar.style.display = 'flex';
var carrier = document.getElementById('qpb-carrier');
var premium = document.getElementById('qpb-premium');
var saving = document.getElementById('qpb-saving');
if (carrier) carrier.textContent = q.carrier;
if (premium) premium.textContent = '$' + q.annual.toLocaleString() + '/yr ($' + q.monthly.toLocaleString() + '/mo)';
if (saving && q.savings_vs_market > 0)
saving.textContent = 'Saving $' + q.savings_vs_market.toLocaleString() + '/yr vs market average';
else if (saving)
saving.textContent = '';
}
// Override pbDoSubmit to attach selected quote
var _pbDoSubmitOrig = pbDoSubmit;
pbDoSubmit = async function() {
function g(id){ var el=document.getElementById(id); return el?el.value.trim():''; }
var payload = {
submission_id: PB.subId,
broker:{ broker_code:g('pb_broker_code'), broker_name:g('pb_broker_name'),
contact_email:g('pb_broker_email'), state_code:g('pb_broker_state') },
insured:{ full_name:g('pb_ins_first')+' '+g('pb_ins_last'), dob:g('pb_ins_dob'),
email:g('pb_ins_email'), phone:g('pb_ins_phone'), credit_score:parseInt(g('pb_credit'))||650,
kyc_status:'CLEAR', ofac_result:'CLEAR', fraud_signals:0,
street:g('pb_ins_street'), city:g('pb_ins_city'), state:g('pb_ins_state'), zip:g('pb_ins_zip') },
property:{ street:g('pb_prop_street'), city:g('pb_prop_city'), state_code:g('pb_prop_state'),
zip:g('pb_prop_zip'), year_built:parseInt(g('pb_year_built'))||2000,
square_footage:parseInt(g('pb_sqft'))||2000, construction_type:g('pb_construction'),
roof_type:g('pb_roof_type'), roof_year:parseInt(g('pb_roof_year'))||2015,
num_stories:parseInt(g('pb_stories'))||2, property_type:g('pb_prop_type'),
occupancy:g('pb_occupancy'), num_units:parseInt(g('pb_units'))||1 },
policy_request:{ coverage_type:PB.coverage,
requested_coverage_limit:parseInt(g('pb_cov_limit'))||300000,
requested_deductible:parseInt(g('pb_deductible'))||2500,
effective_date:g('pb_eff_date'), notes:g('pb_notes') },
selected_quote: _pbSelectedQuote || null,
quote_comparison: _pbQuoteData ? { market_avg: _pbQuoteData.market_avg, our_premium: _pbQuoteData.our_premium } : null,
};
if (PB.mgaData) payload.mga = PB.mgaData;
try {
var _ac=new AbortController(); var _t=setTimeout(function(){_ac.abort()},12000);
var resp=await fetch(API_BASE+'/policybridge/submit',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload),signal:_ac.signal});
clearTimeout(_t);
var result=await resp.json().catch(function(){return{};});
console.log('[PB] Submit result:', result.message || result.error);
} catch(err){ console.warn('[PB] API submit failed:',err.message); }
pbShowSuccess(payload);
};
// Update pbGoStep to handle step 6 in nav
var _pbGoStepOrig = pbGoStep;
pbGoStep = function(n) {
document.querySelectorAll('.pb-step').forEach(function(s){ s.classList.remove('active'); });
var sec=document.getElementById('pb-sec-'+n); if(sec) sec.classList.add('active');
for(var i=1;i<=6;i++){
var nav=document.getElementById('pb-snav-'+i); var bub=document.getElementById('pb-sbb-'+i);
var wrap=document.getElementById('pb-sbw-'+i); var line=document.getElementById('pb-sbl-'+i);
if(!nav) continue;
nav.classList.remove('active','done'); bub.classList.remove('active','done'); wrap.classList.remove('active','done');
if(i<n){ nav.classList.add('done'); bub.classList.add('done'); bub.textContent='v'; wrap.classList.add('done'); if(line) line.classList.add('done'); }
else if(i===n){ nav.classList.add('active'); bub.classList.add('active'); bub.textContent=i; wrap.classList.add('active'); if(line) line.classList.remove('done'); }
else{ bub.textContent=i; if(line) line.classList.remove('done'); }
}
PB.step=n;
if(n===5) pbBuildReview();
var main=document.querySelector('.pb-main'); if(main) main.scrollTop=0;
};
// Also update pbShowSuccess to go to step 7
function pbShowSuccess(payload) {
pbGoStep(7);
var q = _pbSelectedQuote;
var rows=[
['Submission ID',PB.subId],
['Insured',payload.insured.full_name],
['Coverage',payload.policy_request.coverage_type],
['Limit','$'+parseInt(payload.policy_request.requested_coverage_limit).toLocaleString()],
['Effective',payload.policy_request.effective_date],
['Broker',payload.broker.broker_name],
['Selected Quote', q ? q.carrier + ' @ $' + q.annual.toLocaleString() + '/yr' : 'PolicySense Direct'],
['Status','RECEIVED -> Pipeline queued'],
];
var html='';
rows.forEach(function(r){
html+='<div style="display:flex;justify-content:space-between;font-size:11px;padding:4px 0;border-bottom:1px solid rgba(255,255,255,.05)">';
html+='<span style="color:var(--dim)">'+r[0]+'</span><span style="color:var(--text);font-weight:500;font-family:Share Tech Mono,monospace;font-size:10px">'+r[1]+'</span></div>';
});
var sd=document.getElementById('pb-suc-detail'); if(sd) sd.innerHTML=html;
}
</script>
<!-- ═══════════════════════════════════════════════════════════════
FLASK API SERVER β€” save as api_server.py in your agents/ folder
Run with: python api_server.py
═══════════════════════════════════════════════════════════════
from flask import Flask, jsonify, request
from flask_cors import CORS
import json, sys, os
sys.path.insert(0, os.path.dirname(__file__))
from agent1_ssn_kyc import run_kyc_agent
from agent2_property_risk import run_property_risk_agent
from agent3_underwriting import run_underwriting_agent
from agent4_pricing import run_pricing_agent
from agent5_issuance_orchestrator import run_issuance_agent
import mysql.connector
from urllib.parse import quote_plus as qp
from sqlalchemy import create_engine
import pandas as pd
app = Flask(__name__)
CORS(app)
DB = dict(host='localhost', port=3306, user='root', password='root@123', database='bronze')
def get_conn():
pwd = qp(DB['password'])
eng = create_engine(f"mysql+mysqlconnector://{DB['user']}:{pwd}@{DB['host']}:{DB['port']}/{DB['database']}")
return eng.connect()
# In-memory pipeline context per submission
pipeline_ctx = {}
@app.route('/status')
def status():
try:
conn = get_conn()
df = pd.read_sql("SELECT COUNT(*) as c FROM bronze.submissions", conn)
conn.close()
return jsonify({'status':'ok', 'submissions_count': int(df.iloc[0]['c'])})
except Exception as e:
return jsonify({'status':'error','error':str(e)}), 500
@app.route('/submissions/recent')
def recent_submissions():
limit = request.args.get('limit', 10, type=int)
try:
conn = get_conn()
df = pd.read_sql(f"""
SELECT s.submission_id, s.coverage_type_code, s.requested_coverage_limit,
s.requested_deductible, s.final_outcome, s.pipeline_status,
s.halt_reason, s.submitted_at, s.raw_payload,
b.broker_code
FROM bronze.submissions s
LEFT JOIN bronze.brokers b ON s.broker_id = b.broker_id
ORDER BY s.submitted_at DESC LIMIT {limit}
""", conn)
conn.close()
subs = []
for _, row in df.iterrows():
d = row.to_dict()
d['raw_payload'] = json.loads(d['raw_payload']) if d['raw_payload'] else {}
d['submitted_at'] = str(d['submitted_at'])
subs.append(d)
return jsonify({'submissions': subs})
except Exception as e:
return jsonify({'error': str(e)}), 500
def _load_submission(sub_id):
conn = get_conn()
df = pd.read_sql(f"SELECT raw_payload FROM bronze.submissions WHERE submission_id='{sub_id}' LIMIT 1", conn)
conn.close()
if df.empty: return None
return json.loads(df.iloc[0]['raw_payload'])
@app.route('/agent/kyc', methods=['POST'])
def agent_kyc():
sub_id = request.json.get('submission_id')
sub = _load_submission(sub_id)
if not sub: return jsonify({'error':'Not found'}), 404
result = run_kyc_agent(sub)
pipeline_ctx[sub_id] = {'kyc': result, 'submission': sub}
return jsonify(result)
@app.route('/agent/property', methods=['POST'])
def agent_property():
sub_id = request.json.get('submission_id')
ctx = pipeline_ctx.get(sub_id, {})
sub = ctx.get('submission') or _load_submission(sub_id)
kyc = ctx.get('kyc', {'status':'KYC_PASS'})
result = run_property_risk_agent(kyc, sub)
pipeline_ctx.setdefault(sub_id, {}).update({'property': result, 'submission': sub})
return jsonify(result)
@app.route('/agent/underwriting', methods=['POST'])
def agent_underwriting():
sub_id = request.json.get('submission_id')
ctx = pipeline_ctx.get(sub_id, {})
sub = ctx.get('submission') or _load_submission(sub_id)
prop = ctx.get('property', {'status':'RISK_ACCEPTABLE'})
result = run_underwriting_agent(prop, sub)
pipeline_ctx.setdefault(sub_id, {}).update({'underwriting': result})
return jsonify(result)
@app.route('/agent/pricing', methods=['POST'])
def agent_pricing():
sub_id = request.json.get('submission_id')
ctx = pipeline_ctx.get(sub_id, {})
sub = ctx.get('submission') or _load_submission(sub_id)
uw = ctx.get('underwriting', {'status':'UW_APPROVED'})
prop = ctx.get('property', {})
result = run_pricing_agent(uw, prop, sub)
pipeline_ctx.setdefault(sub_id, {}).update({'pricing': result})
return jsonify(result)
@app.route('/agent/issuance', methods=['POST'])
def agent_issuance():
sub_id = request.json.get('submission_id')
ctx = pipeline_ctx.get(sub_id, {})
sub = ctx.get('submission') or _load_submission(sub_id)
result = run_issuance_agent(
ctx.get('pricing', {'status':'PRICED', 'final_premium':0}),
ctx.get('underwriting', {}),
ctx.get('property', {}),
sub
)
pipeline_ctx.pop(sub_id, None) # clear context after issuance
return jsonify(result)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
-->
<!-- ── AXIOM AUDIT MODAL ──────────────────────────────────────── -->
<div class="audit-modal-overlay" id="auditModalOverlay" onclick="if(event.target===this)closeAuditModal()">
<div class="audit-modal" id="auditModal">
<div class="audit-modal-header">
<div>
<div class="audit-modal-title">πŸ” AXIOM β€” Audit Decision Trail</div>
<div class="audit-modal-sub" id="auditModalSub">Agent 6 Β· gold_audit_log Β· Full Pipeline Analysis</div>
</div>
<button class="audit-modal-close" onclick="closeAuditModal()">βœ•</button>
</div>
<div class="audit-modal-body" id="auditModalBody">
<div style="text-align:center;padding:2rem;color:rgba(255,255,255,.3);font-family:'Share Tech Mono',monospace;font-size:12px;">Loading audit data...</div>
</div>
</div>
</div>
</body>
</html>