Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Payment Credit Dashboard</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: Arial, sans-serif; | |
| background: linear-gradient(135deg, #0f172a, #1e293b); | |
| color: #fff; min-height: 100vh; padding: 20px; | |
| } | |
| .container { max-width: 1400px; margin: 0 auto; } | |
| .title { text-align: center; margin-bottom: 24px; } | |
| .title h1 { color: #38bdf8; margin-bottom: 8px; } | |
| .subtitle { color: #cbd5e1; } | |
| .dashboard { display: grid; grid-template-columns: 1.2fr 0.8fr; gap: 20px; } | |
| .card { | |
| background: rgba(255, 255, 255, 0.06); | |
| border: 1px solid rgba(255, 255, 255, 0.12); | |
| border-radius: 14px; padding: 20px; | |
| backdrop-filter: blur(8px); margin-bottom: 20px; | |
| } | |
| .card h2 { color: #38bdf8; margin-bottom: 16px; font-size: 1.05rem; } | |
| .state-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } | |
| .state-box, .mini-box, .list-box { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 10px; padding: 12px; | |
| } | |
| .label { font-size: 0.75rem; color: #94a3b8; text-transform: uppercase; margin-bottom: 6px; } | |
| .value { font-size: 1rem; font-weight: 700; } | |
| .hero-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 16px; } | |
| .pill { display: inline-block; padding: 6px 10px; border-radius: 999px; font-size: 0.8rem; font-weight: 700; } | |
| .low { background: rgba(34, 197, 94, 0.2); color: #86efac; } | |
| .medium { background: rgba(250, 204, 21, 0.18); color: #fde68a; } | |
| .high { background: rgba(239, 68, 68, 0.18); color: #fca5a5; } | |
| .btn-row { display: flex; flex-wrap: wrap; gap: 10px; } | |
| button { | |
| border: none; border-radius: 10px; padding: 12px 16px; | |
| font-weight: 700; cursor: pointer; transition: 0.2s ease; | |
| } | |
| button:hover { transform: translateY(-1px); } | |
| .primary { background: #38bdf8; color: #082f49; } | |
| .secondary { background: #1e293b; color: #e2e8f0; border: 1px solid rgba(255, 255, 255, 0.15); } | |
| .action-btn { background: #334155; color: #fff; } | |
| .action-btn:hover { background: #475569; } | |
| ul { list-style: none; padding-left: 0; } | |
| li { padding: 8px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.08); } | |
| li:last-child { border-bottom: none; } | |
| .good { color: #86efac; font-weight: 700; } | |
| .bad { color: #fca5a5; font-weight: 700; } | |
| .reward { font-size: 2rem; font-weight: 800; color: #f8fafc; } | |
| .muted { color: #cbd5e1; font-size: 0.95rem; } | |
| .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } | |
| .history-item { | |
| padding: 12px; border-radius: 10px; | |
| background: rgba(255, 255, 255, 0.05); margin-bottom: 10px; | |
| } | |
| .history-top { | |
| display: flex; justify-content: space-between; gap: 10px; | |
| font-weight: 700; margin-bottom: 4px; text-transform: capitalize; | |
| } | |
| .right-col { position: sticky; top: 20px; align-self: start; } | |
| @media (max-width: 980px) { | |
| .dashboard { grid-template-columns: 1fr; } | |
| .hero-row, .state-grid { grid-template-columns: 1fr; } | |
| .right-col { position: static; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="title"> | |
| <h1>Payment Credit Dashboard</h1> | |
| <div class="subtitle">Evaluate multiple actions on the same transaction until you reset.</div> | |
| </div> | |
| <div class="dashboard"> | |
| <div> | |
| <div class="card"> | |
| <h2>Current Transaction</h2> | |
| <div class="hero-row"> | |
| <div class="mini-box"> | |
| <div class="label">Transaction ID</div> | |
| <div class="value mono" id="transaction_id">-</div> | |
| </div> | |
| <div class="mini-box"> | |
| <div class="label">Risk Band</div> | |
| <div id="risk_band_wrap"><span class="pill" id="risk_band">-</span></div> | |
| </div> | |
| <div class="mini-box"> | |
| <div class="label">Recommended Action</div> | |
| <div class="value" id="recommended_action">-</div> | |
| </div> | |
| </div> | |
| <div class="state-grid"> | |
| <div class="state-box"><div class="label">Amount</div><div class="value" id="amount">-</div></div> | |
| <div class="state-box"><div class="label">Credit Score</div><div class="value" id="credit_score">-</div></div> | |
| <div class="state-box"><div class="label">Available Credit</div><div class="value" id="available_credit">-</div></div> | |
| <div class="state-box"><div class="label">Monthly Spend</div><div class="value" id="monthly_spend">-</div></div> | |
| <div class="state-box"><div class="label">Debt To Income</div><div class="value" id="debt_to_income">-</div></div> | |
| <div class="state-box"><div class="label">Payment History</div><div class="value" id="payment_history">-</div></div> | |
| <div class="state-box"><div class="label">Credit Utilization</div><div class="value" id="credit_utilization">-</div></div> | |
| <div class="state-box"><div class="label">Last Payment Date</div><div class="value" id="last_payment_date">-</div></div> | |
| <div class="state-box"><div class="label">Account Age</div><div class="value" id="account_age_months">-</div></div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>Actions</h2> | |
| <div class="btn-row" style="margin-bottom: 12px;"> | |
| <button class="primary" onclick="resetEnvironment()">Reset Environment</button> | |
| </div> | |
| <div class="btn-row"> | |
| <button class="action-btn" onclick="takeAction('approve_card_1')">Approve Card 1</button> | |
| <button class="action-btn" onclick="takeAction('approve_card_2')">Approve Card 2</button> | |
| <button class="action-btn" onclick="takeAction('route_to_debit')">Route to Debit</button> | |
| <button class="action-btn" onclick="takeAction('convert_to_emi')">Convert to EMI</button> | |
| <button class="action-btn" onclick="takeAction('delay_purchase')">Delay Purchase</button> | |
| <button class="action-btn" onclick="takeAction('decline')">Decline</button> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>Reasons</h2> | |
| <ul id="reasons_list"><li class="muted">No evaluation yet.</li></ul> | |
| </div> | |
| <div class="card"> | |
| <h2>Action Leaderboard</h2> | |
| <ul id="leaderboard_list"><li class="muted">No evaluation yet.</li></ul> | |
| </div> | |
| <div class="card"> | |
| <h2>Policy Checks</h2> | |
| <ul id="policy_checks_list"><li class="muted">No evaluation yet.</li></ul> | |
| </div> | |
| </div> | |
| <div class="right-col"> | |
| <div class="card"> | |
| <h2>Last Reward</h2> | |
| <div class="reward" id="last_reward">-</div> | |
| <div class="muted" id="last_action">Action tested: -</div> | |
| </div> | |
| <div class="card"> | |
| <h2>Action History</h2> | |
| <div id="audit_log"><div class="muted">No actions taken yet.</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const API = "http://127.0.0.1:8000"; | |
| const money = (n) => typeof n === "number" ? new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR", maximumFractionDigits: 2 }).format(n) : "-"; | |
| function setText(id, value) { const el = document.getElementById(id); if (el) el.textContent = value ? value : "-"; } | |
| function setRiskBand(band) { | |
| const el = document.getElementById("risk_band"); | |
| el.textContent = band || "-"; | |
| el.className = "pill " + (band || ""); | |
| } | |
| function renderState(state) { | |
| if (!state) return; | |
| setText("transaction_id", state.transaction_id); | |
| setText("amount", money(state.amount)); | |
| setText("credit_score", state.credit_score); | |
| setText("available_credit", money(state.available_credit)); | |
| setText("monthly_spend", money(state.monthly_spend)); | |
| setText("debt_to_income", state.debt_to_income + "%"); | |
| setText("payment_history", state.payment_history + "%"); | |
| setText("credit_utilization", state.credit_utilization + "%"); | |
| setText("last_payment_date", state.last_payment_date); | |
| setText("account_age_months", state.account_age_months + " months"); | |
| } | |
| function renderReasons(reasons) { | |
| reasons = reasons || []; | |
| const box = document.getElementById("reasons_list"); | |
| if (!reasons.length) { box.innerHTML = "<li class=\"muted\">No evaluation yet.</li>"; return; } | |
| box.innerHTML = reasons.map(r => "<li>" + r + "</li>").join(""); | |
| } | |
| function renderLeaderboard(items) { | |
| items = items || []; | |
| const box = document.getElementById("leaderboard_list"); | |
| if (!items.length) { box.innerHTML = "<li class=\"muted\">No evaluation yet.</li>"; return; } | |
| box.innerHTML = items.map((x, i) => "<li><strong>#" + (i + 1) + " " + x.action.replaceAll("_", " ") + "</strong><span style=\"float:right\">" + x.reward.toFixed(4) + "</span></li>").join(""); | |
| } | |
| function renderPolicyChecks(items) { | |
| items = items || []; | |
| const box = document.getElementById("policy_checks_list"); | |
| if (!items.length) { box.innerHTML = "<li class=\"muted\">No evaluation yet.</li>"; return; } | |
| box.innerHTML = items.map(x => "<li><div><strong>" + x.name + "</strong> - <span class=\"" + (x.passed ? "good" : "bad") + "\">" + (x.passed ? "PASS" : "FAIL") + "</span></div><div class=\"muted\">" + x.detail + "</div></li>").join(""); | |
| } | |
| async function loadAuditLog() { | |
| const res = await fetch(API + "/audit-log"); | |
| const data = await res.json(); | |
| const box = document.getElementById("audit_log"); | |
| if (!data.length) { box.innerHTML = "<div class=\"muted\">No actions taken yet.</div>"; return; } | |
| box.innerHTML = data.slice().reverse().map(item => "<div class=\"history-item\"><div class=\"history-top\"><span>" + item.action.replaceAll("_", " ") + "</span><span>" + item.reward.toFixed(4) + "</span></div><div class=\"muted\">" + item.timestamp + " - " + item.transaction_id + "</div><div class=\"muted\">Risk: " + item.risk_band + " - Recommended: " + item.recommended_action.replaceAll("_", " ") + "</div></div>").join(""); | |
| } | |
| async function loadState() { | |
| const res = await fetch(API + "/state"); | |
| const state = await res.json(); | |
| renderState(state); | |
| setRiskBand(""); | |
| setText("recommended_action", "-"); | |
| renderReasons([]); | |
| renderLeaderboard([]); | |
| renderPolicyChecks([]); | |
| setText("last_reward", "-"); | |
| setText("last_action", "Action tested: -"); | |
| await loadAuditLog(); | |
| } | |
| async function resetEnvironment() { | |
| const res = await fetch(API + "/reset", { method: "POST" }); | |
| const state = await res.json(); | |
| renderState(state); | |
| setRiskBand(""); | |
| setText("recommended_action", "-"); | |
| renderReasons([]); | |
| renderLeaderboard([]); | |
| renderPolicyChecks([]); | |
| setText("last_reward", "-"); | |
| setText("last_action", "Action tested: -"); | |
| await loadAuditLog(); | |
| } | |
| async function takeAction(action) { | |
| const res = await fetch(API + "/step", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ action: action }) | |
| }); | |
| const data = await res.json(); | |
| renderState(data.state); | |
| setRiskBand(data.risk_band); | |
| setText("recommended_action", data.recommended_action.replaceAll("_", " ")); | |
| renderReasons(data.reasons || []); | |
| renderLeaderboard(data.leaderboard || []); | |
| renderPolicyChecks(data.policy_checks || []); | |
| setText("last_reward", Number(data.reward).toFixed(4)); | |
| setText("last_action", "Action tested: " + data.action.replaceAll("_", " ")); | |
| await loadAuditLog(); | |
| } | |
| loadState(); | |
| </script> | |
| </body> | |
| </html> |