File size: 8,056 Bytes
58a7d45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# ============================================================
# app.py β€” Integration snippets for Agent 6 (AXIOM Audit Agent)
# Add these to your existing app.py
# ============================================================

# ── 1. Import at top of app.py ───────────────────────────────
from agent6_audit import run_audit_agent, get_audit_for_submission


# ── 2. Call AXIOM at end of pipeline (after NOVA) ───────────
# In your existing pipeline route (wherever NOVA runs), add:

def run_full_pipeline(submission_id: int) -> dict:
    """
    Example: call this at the end of your existing pipeline function.
    Replace with your actual pipeline call.
    """
    # ... your existing agent calls (AURA, TERRA, KARMA, AURUM, NOVA) ...

    # βœ… ADD THIS at the very end, after NOVA issues/declines:
    audit_result = run_audit_agent(submission_id)

    return {
        # ... your existing pipeline result dict ...
        "audit": audit_result,
    }


# ── 3. Flask API route β€” GET audit for a submission ──────────
from flask import jsonify, request

@app.route("/audit/<int:submission_id>", methods=["GET"])
def get_audit(submission_id):
    """
    Returns the audit record for a given submission.
    Called by the UI audit panel.
    GET /audit/123
    """
    record = get_audit_for_submission(submission_id)
    if not record:
        # Run it on demand if not yet generated
        record = run_audit_agent(submission_id)
    return jsonify(record)


@app.route("/audit/run/<int:submission_id>", methods=["POST"])
def trigger_audit(submission_id):
    """
    Manually trigger audit for a submission (useful for backfill).
    POST /audit/run/123
    """
    result = run_audit_agent(submission_id)
    return jsonify(result)


# ============================================================
# HTML AUDIT PANEL β€” Add this to your existing policy detail
# page or results screen in your HTML/Jinja templates
# ============================================================
AUDIT_PANEL_HTML = """
<!-- ── AXIOM AUDIT PANEL ─────────────────────────────────── -->
<div id="auditPanel" style="
  background: #0a1628;
  border: 1px solid rgba(0,212,255,0.2);
  border-radius: 4px;
  padding: 1.5rem 2rem;
  margin-top: 2rem;
  font-family: 'DM Mono', monospace;
">
  <div style="display:flex; align-items:center; gap:0.8rem; margin-bottom:1.2rem;">
    <span style="font-size:1.2rem;">πŸ”</span>
    <div>
      <div style="font-weight:700; font-size:0.9rem; color:#f0f6ff; letter-spacing:0.05em;">
        AXIOM β€” Audit Agent
      </div>
      <div style="font-size:0.65rem; color:#7a9ab8; letter-spacing:0.1em; text-transform:uppercase;">
        Agent 6 Β· Decision Audit Β· Gold Layer
      </div>
    </div>
    <div id="auditDecisionBadge" style="
      margin-left:auto;
      font-size:0.7rem;
      font-weight:700;
      letter-spacing:0.12em;
      text-transform:uppercase;
      padding:0.3rem 0.9rem;
      border:1px solid;
      border-radius:2px;
    ">Loading...</div>
  </div>

  <!-- Overall summary -->
  <div id="auditSummaryText" style="
    font-size:0.82rem;
    color:#a8c0d8;
    line-height:1.7;
    background:rgba(255,255,255,0.03);
    border-left:3px solid #00d4ff;
    padding:0.8rem 1rem;
    margin-bottom:1.2rem;
  ">Loading audit summary...</div>

  <!-- Audit score -->
  <div style="display:flex; align-items:center; gap:0.8rem; margin-bottom:1.2rem;">
    <div style="font-size:0.65rem; color:#7a9ab8; text-transform:uppercase; letter-spacing:0.1em;">
      Audit Confidence Score
    </div>
    <div style="flex:1; height:6px; background:rgba(255,255,255,0.08); border-radius:3px; overflow:hidden;">
      <div id="auditScoreBar" style="height:100%; width:0%; background:#00d4ff; transition:width 1s ease; border-radius:3px;"></div>
    </div>
    <div id="auditScoreVal" style="font-size:0.75rem; font-weight:700; color:#00d4ff; width:36px;">0%</div>
  </div>

  <!-- Decision factors table -->
  <div style="font-size:0.65rem; color:#7a9ab8; text-transform:uppercase; letter-spacing:0.1em; margin-bottom:0.6rem;">
    Agent Decision Factors
  </div>
  <div id="auditFactors" style="display:flex; flex-direction:column; gap:0.5rem;"></div>

  <!-- Decline reasons (only shown if declined) -->
  <div id="auditDeclineReasons" style="display:none; margin-top:1rem;">
    <div style="font-size:0.65rem; color:#ef4444; text-transform:uppercase; letter-spacing:0.1em; margin-bottom:0.5rem;">
      Decline Reasons
    </div>
    <div id="auditDeclineList" style="font-size:0.8rem; color:#fca5a5; line-height:1.7;"></div>
  </div>
</div>

<script>
// Load audit panel for a given submission ID
async function loadAuditPanel(submissionId) {
  try {
    const resp = await fetch('/audit/' + submissionId);
    const data = await resp.json();
    renderAuditPanel(data);
  } catch(e) {
    document.getElementById('auditSummaryText').textContent = 'Audit data unavailable.';
  }
}

function renderAuditPanel(data) {
  const decision = data.decision || data.decision;
  const isApproved = decision === 'APPROVED';

  // Badge
  const badge = document.getElementById('auditDecisionBadge');
  badge.textContent = decision;
  badge.style.borderColor = isApproved ? '#00e5a0' : '#ef4444';
  badge.style.color        = isApproved ? '#00e5a0' : '#ef4444';

  // Summary
  document.getElementById('auditSummaryText').textContent = data.overall_summary || '';

  // Score bar
  const score = data.audit_score || 0;
  document.getElementById('auditScoreBar').style.width = score + '%';
  document.getElementById('auditScoreBar').style.background = score > 70 ? '#00e5a0' : score > 40 ? '#f59e0b' : '#ef4444';
  document.getElementById('auditScoreVal').textContent = score + '%';

  // Factors
  const factors = data.decision_factors || [];
  const container = document.getElementById('auditFactors');
  container.innerHTML = '';
  factors.forEach(function(f) {
    const outcomeColor = {
      'PASS': '#00e5a0', 'APPROVED': '#00e5a0', 'ISSUED': '#00e5a0', 'CALCULATED': '#00d4ff',
      'FAIL': '#ef4444', 'DECLINED': '#ef4444', 'DECLINE': '#ef4444',
      'REFER': '#f59e0b', 'NOT ISSUED': '#f59e0b'
    }[f.outcome] || '#7a9ab8';

    const row = document.createElement('div');
    row.style.cssText = 'background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07);padding:0.6rem 0.9rem;border-radius:2px;';
    row.innerHTML = `
      <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.3rem;">
        <span style="font-size:0.7rem;color:#f0f6ff;font-weight:600;">${f.agent}</span>
        <span style="font-size:0.65rem;font-weight:700;color:${outcomeColor};letter-spacing:0.1em;">${f.outcome}</span>
      </div>
      <div style="font-size:0.65rem;color:#7a9ab8;margin-bottom:0.2rem;text-transform:uppercase;letter-spacing:0.08em;">${f.factor}</div>
      <div style="font-size:0.72rem;color:#a8c0d8;line-height:1.5;">${f.detail}</div>
    `;
    container.appendChild(row);
  });

  // Decline reasons
  const reasons = data.decline_reasons || [];
  if (!isApproved && reasons.length > 0) {
    const reasonsDiv = document.getElementById('auditDeclineReasons');
    reasonsDiv.style.display = 'block';
    const list = document.getElementById('auditDeclineList');
    list.innerHTML = reasons.map(function(r, i) {
      return `<div style="margin-bottom:0.4rem;"><span style="color:#ef4444;margin-right:0.4rem;">${i+1}.</span>${r}</div>`;
    }).join('');
  }
}

// Auto-load if submission ID is in the page
document.addEventListener('DOMContentLoaded', function() {
  // Replace with however you pass submission_id to your page
  // e.g. from a data attribute: <div id="auditPanel" data-submission-id="123">
  const panel = document.getElementById('auditPanel');
  const sid = panel && panel.dataset.submissionId;
  if (sid) loadAuditPanel(sid);
});
</script>
"""