fraud_detection_api_1 / frontend /fraud_detection_frontend.html
cindyy287's picture
Upload 23 files
c2fb337 verified
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fraud Detection System</title>
<!-- PWA: manifest & theme -->
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#3498db">
<link rel="icon" href="/icons/icon.svg" sizes="192x192">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root {
--primary-color: #2c3e50;
--secondary-color: #3498db;
--success-color: #27ae60;
--danger-color: #e74c3c;
--warning-color: #f39c12;
--info-color: #17a2b8;
--light-bg: #f8f9fa;
--dark-text: #2c3e50;
--border-radius: 10px;
--box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa;
color: var(--dark-text);
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Header Styles */
header {
background-color: white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 1000;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
}
.logo {
display: flex;
align-items: center;
gap: 15px;
}
.logo-icon {
background-color: var(--primary-color);
color: white;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.logo-text h1 {
font-size: 1.8rem;
color: var(--primary-color);
}
.logo-text p {
font-size: 0.9rem;
color: #666;
}
nav ul {
display: flex;
list-style: none;
gap: 25px;
}
nav a {
text-decoration: none;
color: var(--dark-text);
font-weight: 500;
padding: 8px 15px;
border-radius: var(--border-radius);
transition: var(--transition);
}
nav a:hover, nav a.active {
background-color: var(--primary-color);
color: white;
}
.user-section {
display: flex;
align-items: center;
gap: 20px;
}
.user-info {
display: flex;
align-items: center;
gap: 10px;
}
.user-avatar {
width: 40px;
height: 40px;
background-color: var(--secondary-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.logout-btn {
background-color: var(--danger-color);
color: white;
border: none;
padding: 8px 20px;
border-radius: var(--border-radius);
cursor: pointer;
font-weight: 500;
transition: var(--transition);
}
.logout-btn:hover {
background-color: #c0392b;
transform: translateY(-2px);
}
/* Main Content */
.main-content {
display: flex;
flex-direction: column;
gap: 30px;
margin-top: 30px;
}
/* Dashboard Cards */
.dashboard-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 25px;
}
.card {
background-color: white;
border-radius: var(--border-radius);
padding: 25px;
box-shadow: var(--box-shadow);
transition: var(--transition);
border-top: 4px solid transparent;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
}
.card.fraud {
border-top-color: var(--danger-color);
}
.card.safe {
border-top-color: var(--success-color);
}
.card.warning {
border-top-color: var(--warning-color);
}
.card.info {
border-top-color: var(--info-color);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.card-icon {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.8rem;
color: white;
}
.fraud .card-icon {
background-color: var(--danger-color);
}
.safe .card-icon {
background-color: var(--success-color);
}
.warning .card-icon {
background-color: var(--warning-color);
}
.info .card-icon {
background-color: var(--info-color);
}
.card-value {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 5px;
}
.card-label {
color: #666;
font-size: 1rem;
}
/* Section Header */
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.section-title {
font-size: 1.8rem;
color: var(--primary-color);
display: flex;
align-items: center;
gap: 15px;
}
.section-title i {
color: var(--secondary-color);
}
/* Prediction Form */
.prediction-form-container {
background-color: white;
border-radius: var(--border-radius);
padding: 30px;
box-shadow: var(--box-shadow);
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 25px;
margin-bottom: 25px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--primary-color);
}
.form-control {
width: 100%;
padding: 14px 20px;
border: 2px solid #e0e0e0;
border-radius: var(--border-radius);
font-size: 1rem;
transition: var(--transition);
background-color: #f9f9f9;
}
.form-control:focus {
outline: none;
border-color: var(--secondary-color);
background-color: white;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
.form-note {
font-size: 0.9rem;
color: #666;
margin-top: 5px;
font-style: italic;
}
.btn {
padding: 14px 30px;
border: none;
border-radius: var(--border-radius);
cursor: pointer;
font-weight: 600;
font-size: 1rem;
transition: var(--transition);
display: inline-flex;
align-items: center;
gap: 10px;
}
.btn-primary {
background-color: var(--secondary-color);
color: white;
}
.btn-primary:hover {
background-color: #2980b9;
transform: translateY(-2px);
}
.btn-success {
background-color: var(--success-color);
color: white;
}
.btn-success:hover {
background-color: #219653;
}
.btn-danger {
background-color: var(--danger-color);
color: white;
}
.btn-danger:hover {
background-color: #c0392b;
}
.btn-block {
display: block;
width: 100%;
}
/* Results Section */
.results-container {
background-color: white;
border-radius: var(--border-radius);
padding: 30px;
box-shadow: var(--box-shadow);
margin-top: 20px;
display: none;
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 2px solid #f0f0f0;
}
.results-title {
font-size: 1.6rem;
color: var(--primary-color);
}
.prediction-badge {
padding: 10px 25px;
border-radius: 50px;
font-weight: 700;
font-size: 1.1rem;
text-transform: uppercase;
letter-spacing: 1px;
}
.badge-fraud {
background-color: rgba(231, 76, 60, 0.1);
color: var(--danger-color);
border: 2px solid var(--danger-color);
}
.badge-safe {
background-color: rgba(39, 174, 96, 0.1);
color: var(--success-color);
border: 2px solid var(--success-color);
}
.prediction-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 25px;
margin-bottom: 30px;
}
.detail-box {
background-color: #f8f9fa;
padding: 20px;
border-radius: var(--border-radius);
border-left: 4px solid var(--secondary-color);
}
.detail-label {
font-size: 0.9rem;
color: #666;
margin-bottom: 5px;
}
.detail-value {
font-size: 1.3rem;
font-weight: 700;
color: var(--primary-color);
}
.probability-container {
margin: 30px 0;
}
.probability-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.probability-bar {
height: 25px;
background-color: #ecf0f1;
border-radius: 12px;
overflow: hidden;
margin-bottom: 15px;
}
.probability-fill {
height: 100%;
border-radius: 12px;
transition: width 1s ease;
}
.fraud-probability {
background: linear-gradient(90deg, #e74c3c, #c0392b);
}
.safe-probability {
background: linear-gradient(90deg, #27ae60, #219653);
}
.probability-text {
display: flex;
justify-content: space-between;
font-weight: 600;
color: var(--primary-color);
}
.feedback-section {
margin-top: 35px;
padding-top: 25px;
border-top: 2px solid #f0f0f0;
}
.feedback-title {
font-size: 1.2rem;
margin-bottom: 15px;
color: var(--primary-color);
}
.feedback-buttons {
display: flex;
gap: 15px;
}
.feedback-btn {
flex: 1;
padding: 15px;
border-radius: var(--border-radius);
background-color: #f8f9fa;
border: 2px solid #ddd;
cursor: pointer;
transition: var(--transition);
font-weight: 600;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.feedback-btn:hover {
background-color: #e9ecef;
}
.feedback-btn.active {
background-color: var(--secondary-color);
color: white;
border-color: var(--secondary-color);
}
/* Charts Section */
.charts-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 30px;
margin-bottom: 30px;
}
.chart-card {
background-color: white;
border-radius: var(--border-radius);
padding: 25px;
box-shadow: var(--box-shadow);
}
.chart-title {
font-size: 1.3rem;
margin-bottom: 20px;
color: var(--primary-color);
display: flex;
align-items: center;
gap: 10px;
}
.chart-wrapper {
position: relative;
height: 300px;
}
/* Transactions Table */
.transactions-container {
background-color: white;
border-radius: var(--border-radius);
padding: 30px;
box-shadow: var(--box-shadow);
margin-bottom: 30px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.table-actions {
display: flex;
gap: 15px;
}
.table-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
thead {
background-color: #f8f9fa;
}
th {
padding: 16px 20px;
text-align: left;
font-weight: 600;
color: var(--primary-color);
border-bottom: 2px solid #eee;
}
td {
padding: 16px 20px;
border-bottom: 1px solid #eee;
}
tr:hover {
background-color: #f9f9f9;
}
.status-badge {
padding: 6px 15px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
text-align: center;
display: inline-block;
}
.status-fraud {
background-color: rgba(231, 76, 60, 0.1);
color: var(--danger-color);
border: 1px solid rgba(231, 76, 60, 0.3);
}
.status-safe {
background-color: rgba(39, 174, 96, 0.1);
color: var(--success-color);
border: 1px solid rgba(39, 174, 96, 0.3);
}
/* Feature Importance */
.feature-importance-container {
background-color: white;
border-radius: var(--border-radius);
padding: 30px;
box-shadow: var(--box-shadow);
margin-bottom: 30px;
}
.feature-list {
margin-top: 25px;
}
.feature-item {
display: flex;
align-items: center;
margin-bottom: 15px;
padding: 15px;
background-color: #f8f9fa;
border-radius: var(--border-radius);
border-left: 4px solid var(--secondary-color);
}
.feature-rank {
background-color: var(--primary-color);
color: white;
width: 35px;
height: 35px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 15px;
}
.feature-name {
flex-grow: 1;
font-weight: 600;
}
.feature-importance {
font-weight: 700;
color: var(--primary-color);
}
/* Notification System */
.notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 2000;
max-width: 400px;
}
.notification {
background-color: white;
border-radius: var(--border-radius);
padding: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 15px;
transform: translateX(120%);
transition: transform 0.5s ease;
border-left: 5px solid var(--secondary-color);
}
.notification.show {
transform: translateX(0);
}
.notification.warning {
border-left-color: var(--warning-color);
}
.notification.danger {
border-left-color: var(--danger-color);
}
.notification.success {
border-left-color: var(--success-color);
}
.notification-icon {
font-size: 1.8rem;
}
.notification.warning .notification-icon {
color: var(--warning-color);
}
.notification.danger .notification-icon {
color: var(--danger-color);
}
.notification.success .notification-icon {
color: var(--success-color);
}
.notification-content h4 {
margin-bottom: 5px;
color: var(--primary-color);
}
.notification-content p {
color: #666;
font-size: 0.9rem;
}
/* Login Page */
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 80vh;
}
.login-card {
background-color: white;
border-radius: var(--border-radius);
padding: 40px;
box-shadow: var(--box-shadow);
width: 100%;
max-width: 450px;
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-header h2 {
color: var(--primary-color);
margin-bottom: 10px;
}
.login-header p {
color: #666;
}
/* Responsive Design */
@media (max-width: 1200px) {
.charts-container {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 20px;
}
nav ul {
flex-wrap: wrap;
justify-content: center;
gap: 15px;
}
.form-row {
grid-template-columns: 1fr;
}
.dashboard-cards {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.charts-container {
grid-template-columns: 1fr;
}
.feedback-buttons {
flex-direction: column;
}
.table-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.table-actions {
width: 100%;
justify-content: space-between;
}
}
</style>
</head>
<body>
<!-- Notification System -->
<div class="notification-container" id="notificationContainer"></div>
<!-- Header Section -->
<header>
<div class="container header-content">
<div class="logo">
<div class="logo-icon">
<i class="fas fa-shield-alt"></i>
</div>
<div class="logo-text">
<h1>Fraud Detection AI</h1>
<p>Real-time Transaction Monitoring System</p>
</div>
</div>
<nav>
<ul>
<li><a href="#" class="nav-link active" data-page="dashboard"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
<li><a href="#" class="nav-link" data-page="predict"><i class="fas fa-search"></i> Fraud Prediction</a></li>
<li><a href="#" class="nav-link" data-page="transactions"><i class="fas fa-history"></i> Transactions</a></li>
<li><a href="#" class="nav-link" data-page="features"><i class="fas fa-chart-bar"></i> Feature Analysis</a></li>
</ul>
</nav>
<div class="user-section">
<div class="user-info">
<div class="user-avatar">AD</div>
<div>
<div class="user-name">Admin User</div>
<div class="user-role" style="font-size: 0.85rem; color: #666;">Administrator</div>
</div>
</div>
<!-- Backend URL control: paste your ngrok backend HTTPS URL here and click Set -->
<div style="display:flex; align-items:center; gap:8px; margin-right:12px;">
<input id="backendUrlInput" class="form-control" placeholder="Backend URL (https://...)" style="width:320px; padding:8px 12px; font-size:0.9rem;" />
<button id="setBackendUrlBtn" class="btn" style="background:#444; color:white; padding:8px 12px; border-radius:8px;">Set</button>
</div>
<button class="logout-btn" id="logoutBtn"><i class="fas fa-sign-out-alt"></i> Logout</button>
</div>
</div>
</header>
<!-- Main Content Container -->
<div class="container">
<!-- Dashboard Page -->
<div id="dashboard-page" class="page active">
<div class="main-content">
<!-- Dashboard Stats -->
<div class="dashboard-cards">
<div class="card fraud">
<div class="card-header">
<div>
<div class="card-value" id="totalFraud">0</div>
<div class="card-label">Fraudulent Transactions</div>
</div>
<div class="card-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
</div>
<div class="card-trend" style="color: var(--danger-color); font-weight: 600;">
<i class="fas fa-arrow-up"></i> 12% from last month
</div>
</div>
<div class="card safe">
<div class="card-header">
<div>
<div class="card-value" id="totalSafe">0</div>
<div class="card-label">Legitimate Transactions</div>
</div>
<div class="card-icon">
<i class="fas fa-check-circle"></i>
</div>
</div>
<div class="card-trend" style="color: var(--success-color); font-weight: 600;">
<i class="fas fa-arrow-up"></i> 8% from last month
</div>
</div>
<div class="card warning">
<div class="card-header">
<div>
<div class="card-value" id="totalTransactions">0</div>
<div class="card-label">Total Transactions</div>
</div>
<div class="card-icon">
<i class="fas fa-exchange-alt"></i>
</div>
</div>
<div class="card-trend" style="color: var(--warning-color); font-weight: 600;">
<i class="fas fa-arrow-up"></i> 15% from last month
</div>
</div>
<div class="card info">
<div class="card-header">
<div>
<div class="card-value" id="accuracyRate">0%</div>
<div class="card-label">Model Accuracy</div>
</div>
<div class="card-icon">
<i class="fas fa-brain"></i>
</div>
</div>
<div class="card-trend" style="color: var(--info-color); font-weight: 600;">
<i class="fas fa-arrow-up"></i> 3% improvement
</div>
</div>
</div>
<!-- Charts Section -->
<div class="section-header">
<h2 class="section-title"><i class="fas fa-chart-line"></i> Fraud Analytics Overview</h2>
</div>
<div class="charts-container">
<div class="chart-card">
<h3 class="chart-title"><i class="fas fa-chart-pie"></i> Fraud Distribution</h3>
<div class="chart-wrapper">
<canvas id="fraudDistributionChart"></canvas>
</div>
</div>
<div class="chart-card">
<h3 class="chart-title"><i class="fas fa-chart-bar"></i> Fraud by Location</h3>
<div class="chart-wrapper">
<canvas id="locationFraudChart"></canvas>
</div>
</div>
</div>
<!-- Recent Fraud Alerts -->
<div class="section-header">
<h2 class="section-title"><i class="fas fa-bell"></i> Recent Fraud Alerts</h2>
<button class="btn btn-primary" id="viewAllAlerts">
<i class="fas fa-eye"></i> View All
</button>
</div>
<div class="transactions-container">
<table id="recentAlertsTable">
<thead>
<tr>
<th>Transaction ID</th>
<th>Date & Time</th>
<th>Amount ($)</th>
<th>Location</th>
<th>Merchant</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody id="recentAlertsBody">
<!-- Alerts will be loaded here -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Fraud Prediction Page -->
<div id="predict-page" class="page">
<div class="section-header">
<h2 class="section-title"><i class="fas fa-search"></i> Fraud Detection Prediction</h2>
<div class="model-info" style="background-color: #e8f4fc; padding: 10px 20px; border-radius: var(--border-radius);">
<span style="font-weight: 600; color: var(--secondary-color);">
<i class="fas fa-robot"></i> Model: Ensemble (Random Forest + XGBoost)
</span>
</div>
</div>
<div class="prediction-form-container">
<form id="predictionForm">
<div class="form-row">
<div class="form-group">
<label for="transactionAmount">Transaction Amount ($)</label>
<input type="number" id="transactionAmount" class="form-control" placeholder="Enter transaction amount" step="0.01" min="0" required>
<div class="form-note">Based on dataset: Amount range from $4.30 to $4189.27</div>
</div>
<div class="form-group">
<label for="transactionLocation">Transaction Location</label>
<select id="transactionLocation" class="form-control" required>
<option value="">Select Location</option>
<option value="San Antonio">San Antonio</option>
<option value="Dallas">Dallas</option>
<option value="New York">New York</option>
<option value="Philadelphia">Philadelphia</option>
<option value="Phoenix">Phoenix</option>
<option value="Utah">Utah</option>
<option value="Maryland">Maryland</option>
<option value="New Mexico">New Mexico</option>
<option value="South Dakota">South Dakota</option>
<option value="Montana">Montana</option>
<option value="Luar Negeri">International</option>
</select>
<div class="form-note">Locations from the fraud dataset</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="merchantName">Merchant Name</label>
<input type="text" id="merchantName" class="form-control" placeholder="Enter merchant name">
</div>
<div class="form-group">
<label for="transactionCategory">Transaction Category</label>
<select id="transactionCategory" class="form-control">
<option value="retail">Retail</option>
<option value="travel">Travel</option>
<option value="food">Food & Dining</option>
<option value="entertainment">Entertainment</option>
<option value="digital">Digital Products</option>
<option value="other">Other</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="customerEmail">Customer Email Domain</label>
<select id="customerEmail" class="form-control">
<option value="gmail.com">gmail.com</option>
<option value="yahoo.com">yahoo.com</option>
<option value="outlook.com">outlook.com</option>
<option value="company.com">company.com</option>
<option value="other">Other Domain</option>
</select>
<div class="form-note">From dataset: customerEmail is an important feature</div>
</div>
<div class="form-group">
<label for="customerDevice">Customer Device</label>
<select id="customerDevice" class="form-control">
<option value="mobile">Mobile</option>
<option value="desktop">Desktop</option>
<option value="tablet">Tablet</option>
<option value="unknown">Unknown</option>
</select>
<div class="form-note">From dataset: customerDevice is used for fraud detection</div>
</div>
</div>
<div class="form-group">
<label for="transactionType">Transaction Type</label>
<select id="transactionType" class="form-control">
<option value="online">Online Purchase</option>
<option value="pos">Point of Sale</option>
<option value="atm">ATM Withdrawal</option>
<option value="transfer">Bank Transfer</option>
</select>
</div>
<button type="submit" class="btn btn-primary btn-block" id="predictButton">
<i class="fas fa-brain"></i> Analyze Transaction for Fraud
</button>
</form>
</div>
<!-- Results Container -->
<div class="results-container" id="resultsContainer">
<div class="results-header">
<h3 class="results-title">Prediction Results</h3>
<div class="prediction-badge" id="predictionBadge">Safe</div>
</div>
<div class="prediction-details">
<div class="detail-box">
<div class="detail-label">Transaction Amount</div>
<div class="detail-value" id="resultAmount">$0.00</div>
</div>
<div class="detail-box">
<div class="detail-label">Location</div>
<div class="detail-value" id="resultLocation">Unknown</div>
</div>
<div class="detail-box">
<div class="detail-label">Merchant</div>
<div class="detail-value" id="resultMerchant">Unknown</div>
</div>
<div class="detail-box">
<div class="detail-label">Prediction Confidence</div>
<div class="detail-value" id="resultConfidence">0%</div>
</div>
</div>
<div class="probability-container">
<div class="probability-header">
<span>Fraud Probability</span>
<span id="probabilityValue">0%</span>
</div>
<div class="probability-bar">
<div class="probability-fill" id="probabilityFill" style="width: 0%;"></div>
</div>
<div class="probability-text">
<span>Legitimate Transaction</span>
<span>Fraudulent Transaction</span>
</div>
</div>
<div class="feedback-section">
<h4 class="feedback-title">Is this prediction accurate?</h4>
<p style="margin-bottom: 20px; color: #666;">Your feedback helps improve the AI model</p>
<div class="feedback-buttons">
<button class="feedback-btn" id="feedbackAccurate">
<i class="fas fa-check-circle"></i>
<span>Accurate Prediction</span>
</button>
<button class="feedback-btn" id="feedbackInaccurate">
<i class="fas fa-times-circle"></i>
<span>Inaccurate Prediction</span>
</button>
</div>
</div>
</div>
</div>
<!-- Transactions History Page -->
<div id="transactions-page" class="page">
<div class="section-header">
<h2 class="section-title"><i class="fas fa-history"></i> Transaction History</h2>
<div class="table-actions">
<button class="btn btn-primary" id="refreshTransactions">
<i class="fas fa-sync-alt"></i> Refresh
</button>
<button class="btn btn-success" id="exportTransactions">
<i class="fas fa-file-export"></i> Export CSV
</button>
</div>
</div>
<div class="transactions-container">
<div class="filters" style="margin-bottom: 20px; display: flex; gap: 15px; flex-wrap: wrap;">
<select id="filterStatus" class="form-control" style="width: auto;">
<option value="">All Status</option>
<option value="fraud">Fraud</option>
<option value="safe">Safe</option>
</select>
<input type="date" id="filterDate" class="form-control" style="width: auto;">
<input type="text" id="searchTransaction" class="form-control" placeholder="Search transactions..." style="flex-grow: 1;">
</div>
<table id="transactionsTable">
<thead>
<tr>
<th>ID</th>
<th>Date & Time</th>
<th>Amount</th>
<th>Location</th>
<th>Merchant</th>
<th>Category</th>
<th>Status</th>
<th>Confidence</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="transactionsBody">
<!-- Transactions will be loaded here -->
</tbody>
</table>
<div class="table-controls">
<div class="table-info" id="tableInfo">Showing 0 transactions</div>
<div class="pagination">
<button class="btn" id="prevPage" disabled><i class="fas fa-chevron-left"></i> Previous</button>
<span style="margin: 0 15px;" id="pageInfo">Page 1 of 1</span>
<button class="btn" id="nextPage" disabled>Next <i class="fas fa-chevron-right"></i></button>
</div>
</div>
</div>
</div>
<!-- Feature Importance Page -->
<div id="features-page" class="page">
<div class="section-header">
<h2 class="section-title"><i class="fas fa-chart-bar"></i> Feature Importance Analysis</h2>
<div class="model-info" style="background-color: #e8f4fc; padding: 10px 20px; border-radius: var(--border-radius);">
<span style="font-weight: 600; color: var(--secondary-color);">
<i class="fas fa-project-diagram"></i> Based on Random Forest Feature Importance
</span>
</div>
</div>
<div class="feature-importance-container">
<h3 style="margin-bottom: 20px; color: var(--primary-color);">Top 10 Most Important Features for Fraud Detection</h3>
<div class="chart-wrapper">
<canvas id="featureImportanceChart"></canvas>
</div>
<div class="feature-list" id="featureList">
<!-- Feature importance list will be loaded here -->
</div>
</div>
<div class="charts-container">
<div class="chart-card">
<h3 class="chart-title"><i class="fas fa-money-bill-wave"></i> Amount Distribution</h3>
<div class="chart-wrapper">
<canvas id="amountDistributionChart"></canvas>
</div>
<div style="margin-top: 15px; font-size: 0.9rem; color: #666;">
<p>Dataset statistics: Amount ranges from $4.30 to $4189.27</p>
<p>Average fraud amount: $1,245.67 | Average legitimate amount: $256.43</p>
</div>
</div>
<div class="chart-card">
<h3 class="chart-title"><i class="fas fa-map-marker-alt"></i> Fraud by Location Heatmap</h3>
<div class="chart-wrapper">
<canvas id="locationHeatmapChart"></canvas>
</div>
<div style="margin-top: 15px; font-size: 0.9rem; color: #666;">
<p>Top 5 fraud locations: New York, Dallas, Phoenix, San Antonio, Philadelphia</p>
</div>
</div>
</div>
</div>
</div>
<script>
// Base URL for Flask API - can be overridden at runtime via the header input.
// NOTE: backend exposes `/predict` at the server root, not under /api
let API_BASE_URL = localStorage.getItem('API_BASE_URL') || 'http://localhost:5000';
// State management
let currentUser = {
id: 1,
name: "Admin User",
email: "admin@frauddetection.com"
};
let transactions = [];
let currentPage = 1;
const transactionsPerPage = 10;
// DOM Elements
const pages = {
dashboard: document.getElementById('dashboard-page'),
predict: document.getElementById('predict-page'),
transactions: document.getElementById('transactions-page'),
features: document.getElementById('features-page')
};
const navLinks = document.querySelectorAll('.nav-link');
const logoutBtn = document.getElementById('logoutBtn');
const predictionForm = document.getElementById('predictionForm');
const predictButton = document.getElementById('predictButton');
const resultsContainer = document.getElementById('resultsContainer');
const probabilityFill = document.getElementById('probabilityFill');
const probabilityValue = document.getElementById('probabilityValue');
const predictionBadge = document.getElementById('predictionBadge');
const feedbackAccurate = document.getElementById('feedbackAccurate');
const feedbackInaccurate = document.getElementById('feedbackInaccurate');
const refreshTransactionsBtn = document.getElementById('refreshTransactions');
const exportTransactionsBtn = document.getElementById('exportTransactions');
const filterStatus = document.getElementById('filterStatus');
const filterDate = document.getElementById('filterDate');
const searchTransaction = document.getElementById('searchTransaction');
const transactionsBody = document.getElementById('transactionsBody');
const prevPageBtn = document.getElementById('prevPage');
const nextPageBtn = document.getElementById('nextPage');
const pageInfo = document.getElementById('pageInfo');
const tableInfo = document.getElementById('tableInfo');
// Chart instances
let fraudDistributionChart, locationFraudChart, featureImportanceChart, amountDistributionChart, locationHeatmapChart;
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
initializeEventListeners();
loadDashboardData();
initializeCharts();
loadSampleTransactions();
loadFeatureImportance();
// Simulate user login
simulateLogin();
// Populate backend URL input from saved value
const backendUrlInput = document.getElementById('backendUrlInput');
const setBackendUrlBtn = document.getElementById('setBackendUrlBtn');
if (backendUrlInput) backendUrlInput.value = localStorage.getItem('API_BASE_URL') || API_BASE_URL;
if (setBackendUrlBtn) {
setBackendUrlBtn.addEventListener('click', function() {
const val = (backendUrlInput && backendUrlInput.value || '').trim();
if (!val) {
localStorage.removeItem('API_BASE_URL');
API_BASE_URL = 'http://localhost:5000';
showNotification('warning', 'Backend URL cleared — using localhost');
return;
}
try {
new URL(val);
} catch (e) {
showNotification('danger', 'Invalid URL');
return;
}
localStorage.setItem('API_BASE_URL', val);
API_BASE_URL = val;
showNotification('success', 'Backend URL saved');
});
}
// Register service worker for PWA (if available)
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('ServiceWorker registered', reg.scope))
.catch(err => console.warn('ServiceWorker registration failed', err));
}
});
function initializeEventListeners() {
// Navigation
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const page = this.getAttribute('data-page');
switchPage(page);
});
});
// Logout
logoutBtn.addEventListener('click', function() {
showNotification('success', 'Logged out successfully');
setTimeout(() => {
window.location.reload();
}, 1500);
});
// Prediction form
predictionForm.addEventListener('submit', function(e) {
e.preventDefault();
predictFraud();
});
// Feedback buttons
feedbackAccurate.addEventListener('click', function() {
submitFeedback(true);
this.classList.add('active');
feedbackInaccurate.classList.remove('active');
});
feedbackInaccurate.addEventListener('click', function() {
submitFeedback(false);
this.classList.add('active');
feedbackAccurate.classList.remove('active');
});
// Transactions
refreshTransactionsBtn.addEventListener('click', loadSampleTransactions);
exportTransactionsBtn.addEventListener('click', exportTransactionsToCSV);
filterStatus.addEventListener('change', filterTransactions);
filterDate.addEventListener('change', filterTransactions);
searchTransaction.addEventListener('input', filterTransactions);
prevPageBtn.addEventListener('click', () => changePage(-1));
nextPageBtn.addEventListener('click', () => changePage(1));
}
function switchPage(pageName) {
// Hide all pages
Object.values(pages).forEach(page => {
page.classList.remove('active');
});
// Show selected page
pages[pageName].classList.add('active');
// Update active nav link
navLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('data-page') === pageName) {
link.classList.add('active');
}
});
// Load data for specific pages
if (pageName === 'dashboard') {
updateDashboardStats();
} else if (pageName === 'transactions') {
renderTransactionsTable();
}
}
// Simulate login - in real app, this would be an API call
function simulateLogin() {
showNotification('success', `Welcome back, ${currentUser.name}!`);
}
// Load dashboard data
function loadDashboardData() {
// In a real app, this would be an API call
updateDashboardStats();
}
function updateDashboardStats() {
// Simulate API response
const stats = {
totalFraud: Math.floor(Math.random() * 500) + 120,
totalSafe: Math.floor(Math.random() * 10000) + 8500,
totalTransactions: Math.floor(Math.random() * 10500) + 9000,
accuracyRate: (Math.random() * 10 + 90).toFixed(1) + '%'
};
document.getElementById('totalFraud').textContent = stats.totalFraud;
document.getElementById('totalSafe').textContent = stats.totalSafe;
document.getElementById('totalTransactions').textContent = stats.totalTransactions;
document.getElementById('accuracyRate').textContent = stats.accuracyRate;
}
// Initialize charts
function initializeCharts() {
// Fraud Distribution Chart
const fraudDistributionCtx = document.getElementById('fraudDistributionChart').getContext('2d');
fraudDistributionChart = new Chart(fraudDistributionCtx, {
type: 'doughnut',
data: {
labels: ['Legitimate', 'Fraudulent'],
datasets: [{
data: [92, 8],
backgroundColor: [
'rgba(39, 174, 96, 0.8)',
'rgba(231, 76, 60, 0.8)'
],
borderColor: [
'rgba(39, 174, 96, 1)',
'rgba(231, 76, 60, 1)'
],
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
},
tooltip: {
callbacks: {
label: function(context) {
return `${context.label}: ${context.raw}%`;
}
}
}
}
}
});
// Location Fraud Chart
const locationFraudCtx = document.getElementById('locationFraudChart').getContext('2d');
locationFraudChart = new Chart(locationFraudCtx, {
type: 'bar',
data: {
labels: ['New York', 'Dallas', 'Phoenix', 'San Antonio', 'Philadelphia', 'Utah', 'Maryland'],
datasets: [
{
label: 'Fraudulent',
data: [45, 38, 32, 28, 24, 15, 12],
backgroundColor: 'rgba(231, 76, 60, 0.8)',
borderColor: 'rgba(231, 76, 60, 1)',
borderWidth: 1
},
{
label: 'Legitimate',
data: [455, 412, 398, 322, 376, 285, 288],
backgroundColor: 'rgba(39, 174, 96, 0.8)',
borderColor: 'rgba(39, 174, 96, 1)',
borderWidth: 1
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return value;
}
}
}
},
plugins: {
legend: {
position: 'top'
}
}
}
});
}
// Load feature importance data
function loadFeatureImportance() {
// Feature importance data based on the provided dataset columns
const featureImportanceData = [
{ feature: 'transactionAmount', importance: 0.28 },
{ feature: 'customerDevice', importance: 0.18 },
{ feature: 'location', importance: 0.15 },
{ feature: 'customerEmail', importance: 0.12 },
{ feature: 'No_Transactions', importance: 0.08 },
{ feature: 'merchant', importance: 0.07 },
{ feature: 'category', importance: 0.05 },
{ feature: 'customerIPAddress', importance: 0.04 },
{ feature: 'TransactionType', importance: 0.02 },
{ feature: 'customerBillingAddress', importance: 0.01 }
];
// Render feature importance list
const featureList = document.getElementById('featureList');
featureList.innerHTML = '';
featureImportanceData.forEach((item, index) => {
const featureItem = document.createElement('div');
featureItem.className = 'feature-item';
featureItem.innerHTML = `
<div class="feature-rank">${index + 1}</div>
<div class="feature-name">${formatFeatureName(item.feature)}</div>
<div class="feature-importance">${(item.importance * 100).toFixed(1)}%</div>
`;
featureList.appendChild(featureItem);
});
// Create feature importance chart
const featureImportanceCtx = document.getElementById('featureImportanceChart').getContext('2d');
featureImportanceChart = new Chart(featureImportanceCtx, {
type: 'horizontalBar',
data: {
labels: featureImportanceData.map(item => formatFeatureName(item.feature)),
datasets: [{
label: 'Importance',
data: featureImportanceData.map(item => item.importance * 100),
backgroundColor: 'rgba(52, 152, 219, 0.8)',
borderColor: 'rgba(52, 152, 219, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y',
scales: {
x: {
beginAtZero: true,
ticks: {
callback: function(value) {
return value + '%';
}
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return `Importance: ${context.raw.toFixed(1)}%`;
}
}
}
}
}
});
// Create amount distribution chart
const amountDistributionCtx = document.getElementById('amountDistributionChart').getContext('2d');
amountDistributionChart = new Chart(amountDistributionCtx, {
type: 'scatter',
data: {
datasets: [
{
label: 'Legitimate Transactions',
data: generateRandomAmountData(200, 4, 500, false),
backgroundColor: 'rgba(39, 174, 96, 0.6)',
borderColor: 'rgba(39, 174, 96, 1)',
pointRadius: 5
},
{
label: 'Fraudulent Transactions',
data: generateRandomAmountData(50, 300, 4200, true),
backgroundColor: 'rgba(231, 76, 60, 0.6)',
borderColor: 'rgba(231, 76, 60, 1)',
pointRadius: 6
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'linear',
position: 'bottom',
title: {
display: true,
text: 'Transaction Amount ($)'
}
},
y: {
title: {
display: true,
text: 'Frequency'
},
ticks: {
display: false
}
}
},
plugins: {
legend: {
position: 'top'
}
}
}
});
// Create location heatmap chart
const locationHeatmapCtx = document.getElementById('locationHeatmapChart').getContext('2d');
locationHeatmapChart = new Chart(locationHeatmapCtx, {
type: 'radar',
data: {
labels: ['New York', 'Dallas', 'Phoenix', 'San Antonio', 'Philadelphia', 'Utah', 'Maryland'],
datasets: [
{
label: 'Fraud Risk Level',
data: [85, 78, 72, 65, 58, 42, 38],
backgroundColor: 'rgba(231, 76, 60, 0.2)',
borderColor: 'rgba(231, 76, 60, 1)',
pointBackgroundColor: 'rgba(231, 76, 60, 1)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgba(231, 76, 60, 1)'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
r: {
angleLines: {
display: true
},
suggestedMin: 0,
suggestedMax: 100
}
},
plugins: {
legend: {
display: false
}
}
}
});
}
function formatFeatureName(feature) {
// Format feature names for display
const nameMap = {
'transactionAmount': 'Transaction Amount',
'customerDevice': 'Customer Device',
'location': 'Location',
'customerEmail': 'Customer Email',
'No_Transactions': 'Number of Transactions',
'merchant': 'Merchant',
'category': 'Category',
'customerIPAddress': 'Customer IP Address',
'TransactionType': 'Transaction Type',
'customerBillingAddress': 'Billing Address'
};
return nameMap[feature] || feature.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
}
function generateRandomAmountData(count, min, max, isFraud) {
const data = [];
for (let i = 0; i < count; i++) {
const amount = Math.random() * (max - min) + min;
const y = isFraud ? Math.random() * 0.5 + 0.5 : Math.random() * 0.5;
data.push({ x: amount, y: y });
}
return data;
}
// Load sample transactions
function loadSampleTransactions() {
// Generate sample transactions based on the dataset
const locations = ['New York', 'Dallas', 'Phoenix', 'San Antonio', 'Philadelphia', 'Utah', 'Maryland', 'New Mexico', 'South Dakota', 'Montana'];
const merchants = ['Amazon', 'Walmart', 'Target', 'Best Buy', 'Apple', 'Starbucks', 'Uber', 'Airbnb', 'Netflix', 'Spotify'];
const categories = ['retail', 'travel', 'food', 'entertainment', 'digital'];
transactions = [];
for (let i = 1; i <= 50; i++) {
const amount = (Math.random() * 4000 + 4.3).toFixed(2);
const location = locations[Math.floor(Math.random() * locations.length)];
const isFraud = Math.random() < 0.08; // 8% chance of fraud
const confidence = (Math.random() * 30 + 70).toFixed(1); // 70-100% confidence
const date = new Date();
date.setDate(date.getDate() - Math.floor(Math.random() * 30));
transactions.push({
id: `TRX${10000 + i}`,
date: date.toISOString(),
amount: parseFloat(amount),
location: location,
merchant: merchants[Math.floor(Math.random() * merchants.length)],
category: categories[Math.floor(Math.random() * categories.length)],
isFraud: isFraud,
confidence: parseFloat(confidence),
status: isFraud ? 'fraud' : 'safe'
});
}
// Sort by date (newest first)
transactions.sort((a, b) => new Date(b.date) - new Date(a.date));
renderTransactionsTable();
showNotification('success', 'Transactions loaded successfully');
}
function renderTransactionsTable() {
// Filter transactions
let filteredTransactions = [...transactions];
// Apply status filter
if (filterStatus.value) {
filteredTransactions = filteredTransactions.filter(t => t.status === filterStatus.value);
}
// Apply date filter
if (filterDate.value) {
const filterDateObj = new Date(filterDate.value);
filteredTransactions = filteredTransactions.filter(t => {
const transactionDate = new Date(t.date);
return transactionDate.toDateString() === filterDateObj.toDateString();
});
}
// Apply search filter
if (searchTransaction.value) {
const searchTerm = searchTransaction.value.toLowerCase();
filteredTransactions = filteredTransactions.filter(t =>
t.id.toLowerCase().includes(searchTerm) ||
t.merchant.toLowerCase().includes(searchTerm) ||
t.location.toLowerCase().includes(searchTerm)
);
}
// Calculate pagination
const totalPages = Math.ceil(filteredTransactions.length / transactionsPerPage);
const startIndex = (currentPage - 1) * transactionsPerPage;
const endIndex = Math.min(startIndex + transactionsPerPage, filteredTransactions.length);
const pageTransactions = filteredTransactions.slice(startIndex, endIndex);
// Update table
transactionsBody.innerHTML = '';
if (pageTransactions.length === 0) {
const row = document.createElement('tr');
row.innerHTML = `
<td colspan="9" style="text-align: center; padding: 40px; color: #666;">
<i class="fas fa-search" style="font-size: 2rem; margin-bottom: 15px; display: block;"></i>
No transactions found matching your criteria
</td>
`;
transactionsBody.appendChild(row);
} else {
pageTransactions.forEach(transaction => {
const row = document.createElement('tr');
const date = new Date(transaction.date);
const formattedDate = date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
row.innerHTML = `
<td>${transaction.id}</td>
<td>${formattedDate}</td>
<td>$${transaction.amount.toFixed(2)}</td>
<td>${transaction.location}</td>
<td>${transaction.merchant}</td>
<td>${transaction.category}</td>
<td>
<span class="status-badge ${transaction.isFraud ? 'status-fraud' : 'status-safe'}">
${transaction.isFraud ? 'FRAUD' : 'SAFE'}
</span>
</td>
<td>${transaction.confidence}%</td>
<td>
<button class="btn" style="padding: 5px 10px; font-size: 0.85rem;" onclick="viewTransaction('${transaction.id}')">
<i class="fas fa-eye"></i> View
</button>
</td>
`;
transactionsBody.appendChild(row);
});
}
// Update pagination controls
prevPageBtn.disabled = currentPage <= 1;
nextPageBtn.disabled = currentPage >= totalPages;
pageInfo.textContent = `Page ${currentPage} of ${totalPages || 1}`;
tableInfo.textContent = `Showing ${startIndex + 1}-${endIndex} of ${filteredTransactions.length} transactions`;
}
function changePage(direction) {
const filteredTransactions = getFilteredTransactions();
const totalPages = Math.ceil(filteredTransactions.length / transactionsPerPage);
currentPage += direction;
if (currentPage < 1) currentPage = 1;
if (currentPage > totalPages) currentPage = totalPages;
renderTransactionsTable();
}
function getFilteredTransactions() {
let filteredTransactions = [...transactions];
if (filterStatus.value) {
filteredTransactions = filteredTransactions.filter(t => t.status === filterStatus.value);
}
if (filterDate.value) {
const filterDateObj = new Date(filterDate.value);
filteredTransactions = filteredTransactions.filter(t => {
const transactionDate = new Date(t.date);
return transactionDate.toDateString() === filterDateObj.toDateString();
});
}
if (searchTransaction.value) {
const searchTerm = searchTransaction.value.toLowerCase();
filteredTransactions = filteredTransactions.filter(t =>
t.id.toLowerCase().includes(searchTerm) ||
t.merchant.toLowerCase().includes(searchTerm) ||
t.location.toLowerCase().includes(searchTerm)
);
}
return filteredTransactions;
}
function filterTransactions() {
currentPage = 1;
renderTransactionsTable();
}
// Predict fraud - simulate API call to Flask backend
function predictFraud() {
const amount = parseFloat(document.getElementById('transactionAmount').value);
const location = document.getElementById('transactionLocation').value;
const merchant = document.getElementById('merchantName').value || 'Unknown Merchant';
const category = document.getElementById('transactionCategory').value;
const email = document.getElementById('customerEmail').value;
const device = document.getElementById('customerDevice').value;
const type = document.getElementById('transactionType').value;
// Show loading state
predictButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Analyzing...';
predictButton.disabled = true;
// Build a payload that matches the preprocessor expected feature names.
// The preprocessor expects a fixed set of features (see preprocessor.feature_names_in_).
// We'll provide best-effort mappings from the form and sensible defaults for missing values.
const now = new Date();
const payload = {
merchant_name: merchant || '',
avg_amount_per_transaction: amount || 0,
day_of_week: now.getDay(),
amount_deviation_from_location_mean: 0,
transaction_category: category || '',
customer_no_transactions: 0,
customer_lat: null,
transaction_type: type || '',
customer_place_name: null,
merchant_id: null,
location: location || '',
customer_job: null,
age: null,
merchant_long: null,
amount_per_city_pop: 0,
customer_long: null,
distance_customer_merchant: 0,
transactions_per_customer_ratio: 0,
customer_city_population: 0,
merchant_lat: null,
customer_no_payments: 0,
customer_no_orders: 0,
payments_per_order_ratio: 0,
hour_of_day: now.getHours(),
amount: amount || 0,
customer_zip_code: null,
mean_amount_by_location: 0,
fraud_rate_by_location: 0,
customer_gender: null
};
// Real network call to backend
fetch(`${API_BASE_URL}/predict`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
.then(async response => {
if (!response.ok) {
// Try to read JSON error if available
let errText = `${response.status} ${response.statusText}`;
try {
const errBody = await response.json();
if (errBody && errBody.error) errText = errBody.error;
} catch (e) {}
throw new Error(errText);
}
return response.json();
})
.then(data => {
// Normalize different shapes (legacy, mock, or production)
displayPredictionResults(data, amount, location, merchant);
})
.catch(error => {
console.error('Prediction API error:', error);
showNotification('danger', `Prediction service error: ${error.message}`);
// Fallback to local simulation so UI remains usable offline
const isFraud = simulateFraudPrediction(amount, location);
const confidence = (Math.random() * 20 + 80).toFixed(1);
const fraudProbability = isFraud ? (Math.random() * 30 + 70).toFixed(1) : (Math.random() * 20).toFixed(1);
const fallbackResponse = {
prediction: isFraud ? 'fraud' : 'safe',
confidence: parseFloat(confidence),
fraud_probability: parseFloat(fraudProbability)
};
displayPredictionResults(fallbackResponse, amount, location, merchant);
})
.finally(() => {
predictButton.innerHTML = '<i class="fas fa-brain"></i> Analyze Transaction for Fraud';
predictButton.disabled = false;
});
// Normalize and adapt various API response shapes to UI-friendly values
function normalizePredictionResponse(data) {
// Determine prediction
const isFraud = (
data.fraud === 1 ||
data.fraud_prediction === 1 ||
data.prediction === 'fraud' ||
data.predicted === 'fraud' ||
data.prediction === 1
);
// Determine probability (as 0..1)
let probability = null;
if (typeof data.probability === 'number') probability = data.probability;
else if (typeof data.fraud_probability === 'number') probability = (data.fraud_probability > 1 ? data.fraud_probability / 100 : data.fraud_probability);
else if (typeof data.confidence === 'number') probability = (data.confidence > 1 ? data.confidence / 100 : data.confidence);
// Fallback heuristic
if (probability === null) probability = isFraud ? 0.85 : 0.12;
return {
prediction: isFraud ? 'fraud' : 'safe',
probability: Math.max(0, Math.min(1, probability)),
confidence: data.confidence ?? Math.round(probability * 100)
};
}
function simulateFraudPrediction(amount, location) {
// Simple simulation logic based on dataset patterns
let fraudScore = 0;
// Amount-based risk
if (amount > 3000) fraudScore += 40;
else if (amount > 1000) fraudScore += 20;
else if (amount < 10) fraudScore += 15;
// Location-based risk (from dataset patterns)
const highRiskLocations = ['New York', 'Dallas', 'Phoenix'];
const mediumRiskLocations = ['San Antonio', 'Philadelphia'];
if (highRiskLocations.includes(location)) fraudScore += 35;
else if (mediumRiskLocations.includes(location)) fraudScore += 20;
else if (location === 'Luar Negeri') fraudScore += 50;
// Random factor
fraudScore += Math.random() * 30;
return fraudScore > 60; // Threshold for fraud
}
function displayPredictionResults(data, amount, location, merchant) {
// Update UI with results (support multiple API response shapes)
document.getElementById('resultAmount').textContent = `$${amount.toFixed(2)}`;
document.getElementById('resultLocation').textContent = location;
document.getElementById('resultMerchant').textContent = merchant;
const norm = normalizePredictionResponse(data);
// Update prediction badge
predictionBadge.textContent = norm.prediction === 'fraud' ? 'FRAUD' : 'SAFE';
predictionBadge.className = `prediction-badge ${norm.prediction === 'fraud' ? 'badge-fraud' : 'badge-safe'}`;
// Update probability bar: use percentage
const pct = Math.round(norm.probability * 100);
probabilityFill.className = `probability-fill ${norm.prediction === 'fraud' ? 'fraud-probability' : 'safe-probability'}`;
probabilityFill.style.width = `${pct}%`;
probabilityValue.textContent = `${pct}%`;
document.getElementById('resultConfidence').textContent = `${norm.confidence}%`;
// Show results container
resultsContainer.style.display = 'block';
// Reset feedback buttons
feedbackAccurate.classList.remove('active');
feedbackInaccurate.classList.remove('active');
// Add to transaction history
const isFraud = norm.prediction === 'fraud';
const fraudProbability = Math.round(norm.probability * 100);
const newTransaction = {
id: `TRX${10000 + transactions.length + 1}`,
date: new Date().toISOString(),
amount: amount,
location: location,
merchant: merchant,
category: document.getElementById('transactionCategory').value,
isFraud: isFraud,
confidence: norm.confidence,
status: isFraud ? 'fraud' : 'safe'
};
transactions.unshift(newTransaction);
// Show notification
if (isFraud) {
showNotification('danger', `Fraud detected! Probability: ${fraudProbability}%`, true);
} else {
showNotification('success', `Transaction appears legitimate. Fraud probability: ${fraudProbability}%`);
}
// Scroll to results
resultsContainer.scrollIntoView({ behavior: 'smooth' });
}
function submitFeedback(isAccurate) {
// In a real app, this would send feedback to the Flask API
// fetch(`${API_BASE_URL}/feedback`, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// transaction_id: transactions[0].id,
// prediction_accurate: isAccurate,
// user_id: currentUser.id
// })
// })
showNotification('success', `Thank you for your feedback! Model accuracy will be improved.`);
// Reset form after feedback
setTimeout(() => {
predictionForm.reset();
resultsContainer.style.display = 'none';
}, 2000);
}
function exportTransactionsToCSV() {
// Create CSV content
const headers = ['ID', 'Date', 'Amount', 'Location', 'Merchant', 'Category', 'Status', 'Confidence'];
const csvContent = [
headers.join(','),
...transactions.map(t => [
t.id,
new Date(t.date).toLocaleDateString(),
t.amount,
t.location,
t.merchant,
t.category,
t.status.toUpperCase(),
t.confidence
].join(','))
].join('\n');
// Create download link
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `fraud_transactions_${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showNotification('success', 'Transactions exported successfully');
}
function viewTransaction(transactionId) {
const transaction = transactions.find(t => t.id === transactionId);
if (transaction) {
// Switch to prediction page and populate form
switchPage('predict');
// Populate form with transaction data
document.getElementById('transactionAmount').value = transaction.amount;
document.getElementById('transactionLocation').value = transaction.location;
document.getElementById('merchantName').value = transaction.merchant;
document.getElementById('transactionCategory').value = transaction.category;
// Trigger prediction
setTimeout(() => {
predictButton.click();
}, 500);
}
}
// Notification system
function showNotification(type, message, isImportant = false) {
const container = document.getElementById('notificationContainer');
const notification = document.createElement('div');
notification.className = `notification ${type}`;
const icons = {
success: 'fa-check-circle',
warning: 'fa-exclamation-triangle',
danger: 'fa-times-circle',
info: 'fa-info-circle'
};
notification.innerHTML = `
<div class="notification-icon">
<i class="fas ${icons[type] || 'fa-info-circle'}"></i>
</div>
<div class="notification-content">
<h4>${type.charAt(0).toUpperCase() + type.slice(1)}</h4>
<p>${message}</p>
</div>
`;
container.appendChild(notification);
// Trigger animation
setTimeout(() => {
notification.classList.add('show');
}, 10);
// Auto-remove notification
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 500);
}, isImportant ? 8000 : 5000);
}
</script>
</body>
</html>