Spaces:
Sleeping
Sleeping
Upload 6 files
Browse files- .gitattributes +1 -0
- README.md +75 -0
- amit_kedia_dataset.csv +3 -0
- app.py +644 -0
- fraud_detection_final_model.pkl +3 -0
- requirements.txt +6 -0
- tfidf_vectorizer.pkl +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
amit_kedia_dataset.csv filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: SEC Fraud Detector
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: streamlit
|
| 7 |
+
sdk_version: 1.32.0
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# SEC Fraud Detector
|
| 14 |
+
|
| 15 |
+
NLP-based fraud detection on SEC financial filings.
|
| 16 |
+
Built as a data science bootcamp capstone project.
|
| 17 |
+
|
| 18 |
+
## What it does
|
| 19 |
+
|
| 20 |
+
Paste any financial statement excerpt (10-K, 8-K, MD&A section) and get an instant fraud risk assessment. The model classifies the text as **Low**, **Moderate**, or **High** risk based on linguistic patterns learned from confirmed fraud cases.
|
| 21 |
+
|
| 22 |
+
## Model
|
| 23 |
+
|
| 24 |
+
| Component | Details |
|
| 25 |
+
|---|---|
|
| 26 |
+
| **Architecture** | Logistic Regression |
|
| 27 |
+
| **Embedding** | Hybrid TF-IDF (500 features) + SEC-BERT (768 dims) |
|
| 28 |
+
| **SEC-BERT model** | `nlpaueb/sec-bert-base` β frozen, no fine-tuning |
|
| 29 |
+
| **Dataset** | AmitKedia SEC Filings β 170 samples (85 fraud / 85 non-fraud) |
|
| 30 |
+
| **Validation** | Stratified 5-fold cross-validation |
|
| 31 |
+
| **Test F1** | 0.895 |
|
| 32 |
+
| **Recall (fraud)** | 1.000 β zero missed fraud cases on test set |
|
| 33 |
+
| **ROC-AUC** | 0.983 |
|
| 34 |
+
| **CV F1** | 0.856 Β± 0.056 |
|
| 35 |
+
|
| 36 |
+
## Project pipeline
|
| 37 |
+
|
| 38 |
+
```
|
| 39 |
+
NB01 β EDA
|
| 40 |
+
ββ Class distribution, text length, vocabulary analysis, word clouds
|
| 41 |
+
|
| 42 |
+
NB02 β Embedding Comparison
|
| 43 |
+
ββ TF-IDF vs Frozen FinBERT vs Frozen SEC-BERT
|
| 44 |
+
vs Hybrid TF-IDF + SEC-BERT vs Isolation Forest (unsupervised)
|
| 45 |
+
ββ Winner: Hybrid TF-IDF + SEC-BERT (CV F1 = 0.845, Recall = 0.929)
|
| 46 |
+
|
| 47 |
+
NB03 β Classifier Comparison on Hybrid features
|
| 48 |
+
ββ Logistic Regression vs Linear SVM vs Random Forest
|
| 49 |
+
vs Gradient Boosting vs Naive Bayes
|
| 50 |
+
ββ Winner: Logistic Regression (Recall = 1.0, gap = 0.0004)
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## Why Hybrid TF-IDF + SEC-BERT
|
| 54 |
+
|
| 55 |
+
- **TF-IDF** captures fraud-specific lexical patterns: *restatement, going concern, material weakness, irregularities*
|
| 56 |
+
- **SEC-BERT** (`nlpaueb/sec-bert-base`) was pre-trained on actual SEC 10-K and 10-Q filings β the exact document type being classified
|
| 57 |
+
- Combining both gives the model lexical and semantic signals simultaneously
|
| 58 |
+
- At 170 samples, neither embedding alone generalises as well as their combination
|
| 59 |
+
|
| 60 |
+
## Dataset
|
| 61 |
+
|
| 62 |
+
[AmitKedia Financial Statement Fraud Dataset](https://www.kaggle.com/datasets/amitkedia/financial-statement-fraud-data) β 170 SEC filings, balanced 85 fraud / 85 non-fraud, sourced from SEC-EDGAR and AAER enforcement releases.
|
| 63 |
+
|
| 64 |
+
## Scope & limitations
|
| 65 |
+
|
| 66 |
+
> β οΈ This tool classifies filing **text** based on linguistic patterns learned from 170 SEC filings. It does not perform forensic accounting analysis or evaluate structured financial ratios. Results are probabilistic and should not replace professional audit judgment. Built for research and educational purposes only.
|
| 67 |
+
|
| 68 |
+
## Tech stack
|
| 69 |
+
|
| 70 |
+
`Python` Β· `Streamlit` Β· `scikit-learn` Β· `HuggingFace Transformers` Β· `PyTorch` Β· `SEC-BERT`
|
| 71 |
+
|
| 72 |
+
## Author
|
| 73 |
+
|
| 74 |
+
**Lamia Rahmani** β Data Science Bootcamp Capstone, 2026
|
| 75 |
+
**Bootcamp Coach:** Sara Latreche
|
amit_kedia_dataset.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9c261b2ca9c208f90b6decda3ca32eae4706421586b6d455edd7bbc60c48a7cd
|
| 3 |
+
size 217918716
|
app.py
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pickle
|
| 3 |
+
import joblib
|
| 4 |
+
import torch
|
| 5 |
+
import numpy as np
|
| 6 |
+
import re
|
| 7 |
+
import os
|
| 8 |
+
from transformers import AutoTokenizer, AutoModel
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
|
| 11 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 12 |
+
# Page config
|
| 13 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 14 |
+
st.set_page_config(
|
| 15 |
+
page_title="SEC Fraud Detector",
|
| 16 |
+
page_icon="π",
|
| 17 |
+
layout="wide",
|
| 18 |
+
initial_sidebar_state="collapsed"
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 22 |
+
# CSS
|
| 23 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 24 |
+
st.markdown("""
|
| 25 |
+
<style>
|
| 26 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
| 27 |
+
|
| 28 |
+
/* Reset */
|
| 29 |
+
[data-testid="stAppViewContainer"] { background: #0a0e17; }
|
| 30 |
+
[data-testid="stHeader"] { background: transparent; }
|
| 31 |
+
[data-testid="stSidebar"] { background: #0d1220; border-right: 1px solid #1e2a3a; }
|
| 32 |
+
html, body, [class*="css"] { font-family: 'Inter', sans-serif; }
|
| 33 |
+
|
| 34 |
+
/* Hide default streamlit chrome */
|
| 35 |
+
#MainMenu, footer, header { visibility: hidden; }
|
| 36 |
+
[data-testid="stDecoration"] { display: none; }
|
| 37 |
+
|
| 38 |
+
/* Page shell */
|
| 39 |
+
.main .block-container {
|
| 40 |
+
padding: 0 !important;
|
| 41 |
+
max-width: 100% !important;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/* ββ NAV BAR ββ */
|
| 45 |
+
.nav-bar {
|
| 46 |
+
display: flex;
|
| 47 |
+
align-items: center;
|
| 48 |
+
justify-content: space-between;
|
| 49 |
+
padding: 18px 48px;
|
| 50 |
+
border-bottom: 1px solid #1e2a3a;
|
| 51 |
+
background: #0a0e17;
|
| 52 |
+
position: sticky;
|
| 53 |
+
top: 0;
|
| 54 |
+
z-index: 100;
|
| 55 |
+
}
|
| 56 |
+
.nav-logo {
|
| 57 |
+
display: flex;
|
| 58 |
+
align-items: center;
|
| 59 |
+
gap: 10px;
|
| 60 |
+
font-size: 15px;
|
| 61 |
+
font-weight: 600;
|
| 62 |
+
color: #f1f5f9;
|
| 63 |
+
letter-spacing: -0.3px;
|
| 64 |
+
}
|
| 65 |
+
.nav-logo-dot {
|
| 66 |
+
width: 8px; height: 8px;
|
| 67 |
+
border-radius: 50%;
|
| 68 |
+
background: #3b82f6;
|
| 69 |
+
box-shadow: 0 0 8px #3b82f680;
|
| 70 |
+
}
|
| 71 |
+
.nav-badge {
|
| 72 |
+
font-size: 11px;
|
| 73 |
+
font-weight: 500;
|
| 74 |
+
color: #64748b;
|
| 75 |
+
background: #1e2a3a;
|
| 76 |
+
padding: 4px 10px;
|
| 77 |
+
border-radius: 20px;
|
| 78 |
+
letter-spacing: 0.5px;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/* ββ HERO ββ */
|
| 82 |
+
.hero {
|
| 83 |
+
padding: 64px 48px 48px;
|
| 84 |
+
max-width: 1100px;
|
| 85 |
+
margin: 0 auto;
|
| 86 |
+
}
|
| 87 |
+
.hero-eyebrow {
|
| 88 |
+
font-size: 11px;
|
| 89 |
+
font-weight: 600;
|
| 90 |
+
letter-spacing: 2px;
|
| 91 |
+
text-transform: uppercase;
|
| 92 |
+
color: #3b82f6;
|
| 93 |
+
margin-bottom: 16px;
|
| 94 |
+
}
|
| 95 |
+
.hero-title {
|
| 96 |
+
font-size: 42px;
|
| 97 |
+
font-weight: 700;
|
| 98 |
+
color: #f1f5f9;
|
| 99 |
+
line-height: 1.15;
|
| 100 |
+
letter-spacing: -1.2px;
|
| 101 |
+
margin-bottom: 16px;
|
| 102 |
+
}
|
| 103 |
+
.hero-title span {
|
| 104 |
+
color: #3b82f6;
|
| 105 |
+
}
|
| 106 |
+
.hero-desc {
|
| 107 |
+
font-size: 15px;
|
| 108 |
+
color: #64748b;
|
| 109 |
+
line-height: 1.7;
|
| 110 |
+
max-width: 560px;
|
| 111 |
+
margin-bottom: 40px;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
/* ββ STAT STRIP ββ */
|
| 115 |
+
.stat-strip {
|
| 116 |
+
display: grid;
|
| 117 |
+
grid-template-columns: repeat(4, 1fr);
|
| 118 |
+
gap: 1px;
|
| 119 |
+
background: #1e2a3a;
|
| 120 |
+
border: 1px solid #1e2a3a;
|
| 121 |
+
border-radius: 12px;
|
| 122 |
+
overflow: hidden;
|
| 123 |
+
margin-bottom: 48px;
|
| 124 |
+
}
|
| 125 |
+
.stat-cell {
|
| 126 |
+
background: #0d1220;
|
| 127 |
+
padding: 20px 24px;
|
| 128 |
+
}
|
| 129 |
+
.stat-val {
|
| 130 |
+
font-family: 'JetBrains Mono', monospace;
|
| 131 |
+
font-size: 24px;
|
| 132 |
+
font-weight: 500;
|
| 133 |
+
color: #f1f5f9;
|
| 134 |
+
letter-spacing: -0.5px;
|
| 135 |
+
}
|
| 136 |
+
.stat-lbl {
|
| 137 |
+
font-size: 11px;
|
| 138 |
+
color: #475569;
|
| 139 |
+
text-transform: uppercase;
|
| 140 |
+
letter-spacing: 1px;
|
| 141 |
+
margin-top: 4px;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
/* ββ MAIN GRID ββ */
|
| 145 |
+
.main-grid {
|
| 146 |
+
display: grid;
|
| 147 |
+
grid-template-columns: 1.4fr 1fr;
|
| 148 |
+
gap: 20px;
|
| 149 |
+
padding: 0 48px 48px;
|
| 150 |
+
max-width: 1100px;
|
| 151 |
+
margin: 0 auto;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
/* ββ CARD ββ */
|
| 155 |
+
.card {
|
| 156 |
+
background: #0d1220;
|
| 157 |
+
border: 1px solid #1e2a3a;
|
| 158 |
+
border-radius: 14px;
|
| 159 |
+
padding: 28px;
|
| 160 |
+
}
|
| 161 |
+
.card-title {
|
| 162 |
+
font-size: 12px;
|
| 163 |
+
font-weight: 600;
|
| 164 |
+
letter-spacing: 1.5px;
|
| 165 |
+
text-transform: uppercase;
|
| 166 |
+
color: #475569;
|
| 167 |
+
margin-bottom: 20px;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
/* ββ TEXTAREA OVERRIDE ββ */
|
| 171 |
+
textarea {
|
| 172 |
+
background: #070b12 !important;
|
| 173 |
+
border: 1px solid #1e2a3a !important;
|
| 174 |
+
border-radius: 10px !important;
|
| 175 |
+
color: #cbd5e1 !important;
|
| 176 |
+
font-family: 'JetBrains Mono', monospace !important;
|
| 177 |
+
font-size: 13px !important;
|
| 178 |
+
line-height: 1.7 !important;
|
| 179 |
+
resize: none !important;
|
| 180 |
+
}
|
| 181 |
+
textarea:focus {
|
| 182 |
+
border-color: #3b82f6 !important;
|
| 183 |
+
box-shadow: 0 0 0 2px #3b82f620 !important;
|
| 184 |
+
}
|
| 185 |
+
textarea::placeholder { color: #334155 !important; }
|
| 186 |
+
|
| 187 |
+
/* ββ BUTTON ββ */
|
| 188 |
+
[data-testid="stButton"] > button {
|
| 189 |
+
background: #3b82f6 !important;
|
| 190 |
+
color: white !important;
|
| 191 |
+
border: none !important;
|
| 192 |
+
border-radius: 8px !important;
|
| 193 |
+
padding: 12px 28px !important;
|
| 194 |
+
font-size: 13px !important;
|
| 195 |
+
font-weight: 600 !important;
|
| 196 |
+
letter-spacing: 0.3px !important;
|
| 197 |
+
width: 100% !important;
|
| 198 |
+
margin-top: 12px !important;
|
| 199 |
+
cursor: pointer !important;
|
| 200 |
+
transition: background 0.15s !important;
|
| 201 |
+
}
|
| 202 |
+
[data-testid="stButton"] > button:hover {
|
| 203 |
+
background: #2563eb !important;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
/* ββ RESULT PANEL ββ */
|
| 207 |
+
.result-risk-badge {
|
| 208 |
+
display: inline-block;
|
| 209 |
+
padding: 6px 16px;
|
| 210 |
+
border-radius: 20px;
|
| 211 |
+
font-size: 12px;
|
| 212 |
+
font-weight: 700;
|
| 213 |
+
letter-spacing: 1px;
|
| 214 |
+
text-transform: uppercase;
|
| 215 |
+
margin-bottom: 24px;
|
| 216 |
+
}
|
| 217 |
+
.badge-low { background: #052e16; color: #4ade80; border: 1px solid #166534; }
|
| 218 |
+
.badge-mod { background: #431407; color: #fb923c; border: 1px solid #9a3412; }
|
| 219 |
+
.badge-high { background: #3b0764; color: #e879f9; border: 1px solid #7e22ce; }
|
| 220 |
+
|
| 221 |
+
.prob-number {
|
| 222 |
+
font-family: 'JetBrains Mono', monospace;
|
| 223 |
+
font-size: 56px;
|
| 224 |
+
font-weight: 500;
|
| 225 |
+
line-height: 1;
|
| 226 |
+
letter-spacing: -2px;
|
| 227 |
+
margin-bottom: 8px;
|
| 228 |
+
}
|
| 229 |
+
.prob-low { color: #4ade80; }
|
| 230 |
+
.prob-mod { color: #fb923c; }
|
| 231 |
+
.prob-high { color: #e879f9; }
|
| 232 |
+
|
| 233 |
+
.prob-bar-track {
|
| 234 |
+
height: 4px;
|
| 235 |
+
background: #1e2a3a;
|
| 236 |
+
border-radius: 4px;
|
| 237 |
+
margin: 20px 0 28px;
|
| 238 |
+
overflow: hidden;
|
| 239 |
+
}
|
| 240 |
+
.prob-bar-fill {
|
| 241 |
+
height: 100%;
|
| 242 |
+
border-radius: 4px;
|
| 243 |
+
transition: width 0.6s ease;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.metric-row {
|
| 247 |
+
display: flex;
|
| 248 |
+
justify-content: space-between;
|
| 249 |
+
align-items: center;
|
| 250 |
+
padding: 10px 0;
|
| 251 |
+
border-bottom: 1px solid #1e2a3a;
|
| 252 |
+
}
|
| 253 |
+
.metric-row:last-child { border-bottom: none; }
|
| 254 |
+
.metric-key {
|
| 255 |
+
font-size: 12px;
|
| 256 |
+
color: #475569;
|
| 257 |
+
font-family: 'JetBrains Mono', monospace;
|
| 258 |
+
}
|
| 259 |
+
.metric-val {
|
| 260 |
+
font-size: 12px;
|
| 261 |
+
color: #94a3b8;
|
| 262 |
+
font-weight: 500;
|
| 263 |
+
}
|
| 264 |
+
.metric-val-accent {
|
| 265 |
+
font-size: 12px;
|
| 266 |
+
color: #3b82f6;
|
| 267 |
+
font-weight: 600;
|
| 268 |
+
font-family: 'JetBrains Mono', monospace;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
/* ββ KEYWORD PANEL ββ */
|
| 272 |
+
.kw-list {
|
| 273 |
+
display: flex;
|
| 274 |
+
flex-wrap: wrap;
|
| 275 |
+
gap: 8px;
|
| 276 |
+
margin-top: 8px;
|
| 277 |
+
}
|
| 278 |
+
.kw-chip {
|
| 279 |
+
font-family: 'JetBrains Mono', monospace;
|
| 280 |
+
font-size: 11px;
|
| 281 |
+
padding: 4px 10px;
|
| 282 |
+
border-radius: 6px;
|
| 283 |
+
background: #0a1628;
|
| 284 |
+
border: 1px solid #1e3a5f;
|
| 285 |
+
color: #60a5fa;
|
| 286 |
+
}
|
| 287 |
+
.kw-chip.hit {
|
| 288 |
+
background: #1e1245;
|
| 289 |
+
border-color: #7c3aed;
|
| 290 |
+
color: #c084fc;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
/* ββ MODEL INFO ββ */
|
| 294 |
+
.info-row {
|
| 295 |
+
display: flex;
|
| 296 |
+
justify-content: space-between;
|
| 297 |
+
align-items: flex-start;
|
| 298 |
+
padding: 10px 0;
|
| 299 |
+
border-bottom: 1px solid #1e2a3a;
|
| 300 |
+
gap: 16px;
|
| 301 |
+
}
|
| 302 |
+
.info-row:last-child { border-bottom: none; }
|
| 303 |
+
.info-key {
|
| 304 |
+
font-size: 11px;
|
| 305 |
+
color: #475569;
|
| 306 |
+
text-transform: uppercase;
|
| 307 |
+
letter-spacing: 0.8px;
|
| 308 |
+
white-space: nowrap;
|
| 309 |
+
padding-top: 1px;
|
| 310 |
+
}
|
| 311 |
+
.info-val {
|
| 312 |
+
font-size: 12px;
|
| 313 |
+
color: #94a3b8;
|
| 314 |
+
text-align: right;
|
| 315 |
+
line-height: 1.5;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
/* ββ NOTICE ββ */
|
| 319 |
+
.notice {
|
| 320 |
+
margin: 0 48px 32px;
|
| 321 |
+
max-width: 1060px;
|
| 322 |
+
background: #0d1628;
|
| 323 |
+
border: 1px solid #1e3a5f;
|
| 324 |
+
border-radius: 10px;
|
| 325 |
+
padding: 14px 20px;
|
| 326 |
+
font-size: 12px;
|
| 327 |
+
color: #475569;
|
| 328 |
+
line-height: 1.6;
|
| 329 |
+
}
|
| 330 |
+
.notice span { color: #3b82f6; font-weight: 500; }
|
| 331 |
+
|
| 332 |
+
/* Alert overrides */
|
| 333 |
+
[data-testid="stAlert"] {
|
| 334 |
+
background: transparent !important;
|
| 335 |
+
border: none !important;
|
| 336 |
+
padding: 0 !important;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
/* Spinner */
|
| 340 |
+
[data-testid="stSpinner"] p { color: #475569 !important; }
|
| 341 |
+
</style>
|
| 342 |
+
""", unsafe_allow_html=True)
|
| 343 |
+
|
| 344 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 345 |
+
# Load model
|
| 346 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 347 |
+
@st.cache_resource(show_spinner=False)
|
| 348 |
+
def load_artifacts():
|
| 349 |
+
model = joblib.load("fraud_detection_final_model.pkl")
|
| 350 |
+
tfidf = joblib.load("tfidf_vectorizer.pkl")
|
| 351 |
+
sec_tok = AutoTokenizer.from_pretrained("nlpaueb/sec-bert-base")
|
| 352 |
+
sec_mdl = AutoModel.from_pretrained("nlpaueb/sec-bert-base")
|
| 353 |
+
sec_mdl.eval()
|
| 354 |
+
for p in sec_mdl.parameters():
|
| 355 |
+
p.requires_grad = False
|
| 356 |
+
return model, tfidf, sec_tok, sec_mdl
|
| 357 |
+
|
| 358 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 359 |
+
# Helpers
|
| 360 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 361 |
+
FRAUD_KEYWORDS = [
|
| 362 |
+
"restatement", "going concern", "material weakness",
|
| 363 |
+
"internal control", "misstatement", "irregularities",
|
| 364 |
+
"investigation", "SEC inquiry", "revenue recognition",
|
| 365 |
+
"write-off", "impairment", "earnings manipulation",
|
| 366 |
+
"off-balance sheet", "undisclosed", "overstated"
|
| 367 |
+
]
|
| 368 |
+
|
| 369 |
+
def clean_text(text):
|
| 370 |
+
text = str(text)
|
| 371 |
+
text = re.sub(r"<.*?>", " ", text)
|
| 372 |
+
text = re.sub(r"[^a-zA-Z\s]", " ", text)
|
| 373 |
+
return re.sub(r"\s+", " ", text).lower().strip()
|
| 374 |
+
|
| 375 |
+
def get_sec_bert_embedding(text, tokenizer, model, device="cpu"):
|
| 376 |
+
inputs = tokenizer(
|
| 377 |
+
[text], padding=True, truncation=True,
|
| 378 |
+
max_length=512, return_tensors="pt"
|
| 379 |
+
).to(device)
|
| 380 |
+
with torch.no_grad():
|
| 381 |
+
out = model(**inputs)
|
| 382 |
+
return out.last_hidden_state[:, 0, :].cpu().numpy()
|
| 383 |
+
|
| 384 |
+
def build_hybrid(text, tfidf_vec, sec_tok, sec_mdl):
|
| 385 |
+
cleaned = clean_text(text)
|
| 386 |
+
tfidf_feat = tfidf_vec.transform([cleaned]).toarray()
|
| 387 |
+
norm_tfidf = tfidf_feat / (np.linalg.norm(tfidf_feat) + 1e-9)
|
| 388 |
+
sec_emb = get_sec_bert_embedding(cleaned, sec_tok, sec_mdl)
|
| 389 |
+
norm_sec = sec_emb / (np.linalg.norm(sec_emb) + 1e-9)
|
| 390 |
+
return np.hstack([norm_tfidf, norm_sec])
|
| 391 |
+
|
| 392 |
+
def detect_keywords(text):
|
| 393 |
+
text_lower = text.lower()
|
| 394 |
+
return [kw for kw in FRAUD_KEYWORDS if kw in text_lower]
|
| 395 |
+
|
| 396 |
+
def risk_tier(prob):
|
| 397 |
+
if prob < 0.40:
|
| 398 |
+
return "Low Risk", "low", "badge-low", "prob-low", "#4ade80"
|
| 399 |
+
elif prob < 0.75:
|
| 400 |
+
return "Moderate Risk", "mod", "badge-mod", "prob-mod", "#fb923c"
|
| 401 |
+
else:
|
| 402 |
+
return "High Risk", "high", "badge-high", "prob-high", "#e879f9"
|
| 403 |
+
|
| 404 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 405 |
+
# NAV BAR
|
| 406 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 407 |
+
st.markdown("""
|
| 408 |
+
<div class="nav-bar">
|
| 409 |
+
<div class="nav-logo">
|
| 410 |
+
<div class="nav-logo-dot"></div>
|
| 411 |
+
SEC Fraud Detector
|
| 412 |
+
</div>
|
| 413 |
+
<div class="nav-badge">RESEARCH Β· BOOTCAMP PROJECT</div>
|
| 414 |
+
</div>
|
| 415 |
+
""", unsafe_allow_html=True)
|
| 416 |
+
|
| 417 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 418 |
+
# HERO
|
| 419 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 420 |
+
st.markdown("""
|
| 421 |
+
<div class="hero">
|
| 422 |
+
<div class="hero-eyebrow">NLP Β· Financial Statement Analysis</div>
|
| 423 |
+
<div class="hero-title">Detect fraud signals<br>in <span>SEC filings</span>.</div>
|
| 424 |
+
<div class="hero-desc">
|
| 425 |
+
Paste any financial statement excerpt β 10-K, 8-K, MD&A section β
|
| 426 |
+
and get an instant risk assessment powered by a hybrid
|
| 427 |
+
TF-IDF + SEC-BERT model trained on confirmed fraud cases.
|
| 428 |
+
</div>
|
| 429 |
+
<div class="stat-strip">
|
| 430 |
+
<div class="stat-cell">
|
| 431 |
+
<div class="stat-val">0.895</div>
|
| 432 |
+
<div class="stat-lbl">Test F1 Score</div>
|
| 433 |
+
</div>
|
| 434 |
+
<div class="stat-cell">
|
| 435 |
+
<div class="stat-val">1.000</div>
|
| 436 |
+
<div class="stat-lbl">Recall (fraud)</div>
|
| 437 |
+
</div>
|
| 438 |
+
<div class="stat-cell">
|
| 439 |
+
<div class="stat-val">0.983</div>
|
| 440 |
+
<div class="stat-lbl">ROC-AUC</div>
|
| 441 |
+
</div>
|
| 442 |
+
<div class="stat-cell">
|
| 443 |
+
<div class="stat-val">170</div>
|
| 444 |
+
<div class="stat-lbl">Training samples</div>
|
| 445 |
+
</div>
|
| 446 |
+
</div>
|
| 447 |
+
</div>
|
| 448 |
+
""", unsafe_allow_html=True)
|
| 449 |
+
|
| 450 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 451 |
+
# MAIN LAYOUT β use st.columns to keep streamlit widgets working
|
| 452 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 453 |
+
pad_l, col_input, gap, col_result, pad_r = st.columns([0.05, 1.2, 0.04, 0.85, 0.05])
|
| 454 |
+
|
| 455 |
+
# βββ INPUT COLUMN βββββββββββββββββββββββββββ
|
| 456 |
+
with col_input:
|
| 457 |
+
st.markdown('<div class="card-title">FILING TEXT INPUT</div>', unsafe_allow_html=True)
|
| 458 |
+
|
| 459 |
+
sample_texts = {
|
| 460 |
+
"β paste your own text β": "",
|
| 461 |
+
"Sample: Fraud (going concern + restatement)":
|
| 462 |
+
"The Company has identified material weaknesses in its internal control over financial reporting. "
|
| 463 |
+
"Management has concluded that the previously issued financial statements contained misstatements "
|
| 464 |
+
"and will require restatement. There is substantial doubt about the Company's ability to continue "
|
| 465 |
+
"as a going concern. An SEC inquiry into revenue recognition practices is currently ongoing.",
|
| 466 |
+
"Sample: Non-fraud (clean filing)":
|
| 467 |
+
"Net revenues for the fiscal year ended December 31 were 4.2 billion dollars, "
|
| 468 |
+
"representing an increase of 8 percent compared to the prior year. "
|
| 469 |
+
"The increase was primarily driven by strong performance in our core product segments. "
|
| 470 |
+
"Operating income improved to 890 million dollars. "
|
| 471 |
+
"The Board of Directors approved a quarterly dividend of 0.42 dollars per share."
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
selected = st.selectbox("Load a sample or paste your own:", list(sample_texts.keys()),
|
| 475 |
+
label_visibility="collapsed")
|
| 476 |
+
default_text = sample_texts[selected]
|
| 477 |
+
|
| 478 |
+
user_input = st.text_area(
|
| 479 |
+
"Filing text",
|
| 480 |
+
value=default_text,
|
| 481 |
+
height=240,
|
| 482 |
+
placeholder="Paste SEC filing text here β MD&A, 10-K, 8-K, footnotes...",
|
| 483 |
+
label_visibility="collapsed"
|
| 484 |
+
)
|
| 485 |
+
|
| 486 |
+
analyze = st.button("Run Fraud Assessment β")
|
| 487 |
+
|
| 488 |
+
# Keyword scanner (always visible)
|
| 489 |
+
if user_input.strip():
|
| 490 |
+
hits = detect_keywords(user_input)
|
| 491 |
+
st.markdown('<div style="margin-top:20px">', unsafe_allow_html=True)
|
| 492 |
+
st.markdown('<div class="card-title" style="margin-bottom:10px">KEYWORD SCAN</div>',
|
| 493 |
+
unsafe_allow_html=True)
|
| 494 |
+
chips = ""
|
| 495 |
+
for kw in FRAUD_KEYWORDS:
|
| 496 |
+
cls = "kw-chip hit" if kw in hits else "kw-chip"
|
| 497 |
+
chips += f'<span class="{cls}">{kw}</span>'
|
| 498 |
+
st.markdown(f'<div class="kw-list">{chips}</div>', unsafe_allow_html=True)
|
| 499 |
+
if hits:
|
| 500 |
+
st.markdown(
|
| 501 |
+
f'<div style="margin-top:10px;font-size:12px;color:#c084fc">'
|
| 502 |
+
f'β {len(hits)} fraud-associated term{"s" if len(hits)>1 else ""} detected</div>',
|
| 503 |
+
unsafe_allow_html=True)
|
| 504 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 505 |
+
|
| 506 |
+
# βββ RESULT COLUMN ββββββββββββββββββββββββββ
|
| 507 |
+
with col_result:
|
| 508 |
+
st.markdown('<div class="card-title">RISK ASSESSMENT</div>', unsafe_allow_html=True)
|
| 509 |
+
|
| 510 |
+
if not analyze and not user_input.strip():
|
| 511 |
+
st.markdown("""
|
| 512 |
+
<div style="padding:40px 0;text-align:center;color:#1e3a5f">
|
| 513 |
+
<div style="font-size:40px;margin-bottom:12px">π</div>
|
| 514 |
+
<div style="font-size:13px;color:#334155;line-height:1.7">
|
| 515 |
+
Enter filing text and click<br><strong style="color:#3b82f6">Run Fraud Assessment</strong>
|
| 516 |
+
</div>
|
| 517 |
+
</div>
|
| 518 |
+
""", unsafe_allow_html=True)
|
| 519 |
+
|
| 520 |
+
elif analyze:
|
| 521 |
+
if not user_input.strip():
|
| 522 |
+
st.markdown('<div style="color:#fb923c;font-size:13px;padding:12px 0">β Please enter some filing text first.</div>',
|
| 523 |
+
unsafe_allow_html=True)
|
| 524 |
+
else:
|
| 525 |
+
with st.spinner("Running hybrid model inference..."):
|
| 526 |
+
try:
|
| 527 |
+
clf, tfidf_vec, sec_tok, sec_mdl = load_artifacts()
|
| 528 |
+
X = build_hybrid(user_input, tfidf_vec, sec_tok, sec_mdl)
|
| 529 |
+
prob = float(clf.predict_proba(X)[0][1])
|
| 530 |
+
label, tier, badge_cls, prob_cls, bar_color = risk_tier(prob)
|
| 531 |
+
hits = detect_keywords(user_input)
|
| 532 |
+
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 533 |
+
|
| 534 |
+
st.markdown(f'<div class="result-risk-badge {badge_cls}">{label}</div>',
|
| 535 |
+
unsafe_allow_html=True)
|
| 536 |
+
|
| 537 |
+
st.markdown(
|
| 538 |
+
f'<div class="prob-number {prob_cls}">{prob:.1%}</div>'
|
| 539 |
+
f'<div style="font-size:12px;color:#475569">fraud probability</div>',
|
| 540 |
+
unsafe_allow_html=True
|
| 541 |
+
)
|
| 542 |
+
|
| 543 |
+
fill_w = int(prob * 100)
|
| 544 |
+
st.markdown(
|
| 545 |
+
f'<div class="prob-bar-track">'
|
| 546 |
+
f'<div class="prob-bar-fill" style="width:{fill_w}%;background:{bar_color}"></div>'
|
| 547 |
+
f'</div>',
|
| 548 |
+
unsafe_allow_html=True
|
| 549 |
+
)
|
| 550 |
+
|
| 551 |
+
# Metrics
|
| 552 |
+
verdict = "Fraudulent pattern detected" if prob >= 0.70 else "No fraud pattern detected"
|
| 553 |
+
st.markdown(f"""
|
| 554 |
+
<div class="metric-row">
|
| 555 |
+
<span class="metric-key">verdict</span>
|
| 556 |
+
<span class="metric-val">{verdict}</span>
|
| 557 |
+
</div>
|
| 558 |
+
<div class="metric-row">
|
| 559 |
+
<span class="metric-key">fraud_prob</span>
|
| 560 |
+
<span class="metric-val-accent">{prob:.4f}</span>
|
| 561 |
+
</div>
|
| 562 |
+
<div class="metric-row">
|
| 563 |
+
<span class="metric-key">keywords_hit</span>
|
| 564 |
+
<span class="metric-val">{len(hits)} / {len(FRAUD_KEYWORDS)}</span>
|
| 565 |
+
</div>
|
| 566 |
+
<div class="metric-row">
|
| 567 |
+
<span class="metric-key">threshold</span>
|
| 568 |
+
<span class="metric-val">0.70</span>
|
| 569 |
+
</div>
|
| 570 |
+
<div class="metric-row">
|
| 571 |
+
<span class="metric-key">timestamp</span>
|
| 572 |
+
<span class="metric-val">{ts}</span>
|
| 573 |
+
</div>
|
| 574 |
+
""", unsafe_allow_html=True)
|
| 575 |
+
|
| 576 |
+
# Contextual guidance
|
| 577 |
+
guidance = {
|
| 578 |
+
"low": ("β", "#4ade80", "Low lexical fraud risk. Filing language is consistent with non-fraudulent SEC disclosures."),
|
| 579 |
+
"mod": ("!", "#fb923c", "Moderate risk signals detected. One or more linguistic patterns align with fraud-adjacent language. Manual review recommended."),
|
| 580 |
+
"high": ("β ", "#e879f9", "High-confidence fraud indicators detected. Filing language strongly matches confirmed fraud cases in the training corpus. Escalate for review."),
|
| 581 |
+
}
|
| 582 |
+
icon, color, msg = guidance[tier]
|
| 583 |
+
st.markdown(
|
| 584 |
+
f'<div style="margin-top:20px;padding:14px 16px;border-radius:8px;'
|
| 585 |
+
f'background:{color}10;border:1px solid {color}30;'
|
| 586 |
+
f'font-size:12px;color:{color};line-height:1.6">'
|
| 587 |
+
f'<strong>{icon} {label}:</strong> {msg}</div>',
|
| 588 |
+
unsafe_allow_html=True
|
| 589 |
+
)
|
| 590 |
+
|
| 591 |
+
except FileNotFoundError as e:
|
| 592 |
+
st.markdown(f"""
|
| 593 |
+
<div style="padding:20px;background:#1a0a0a;border:1px solid #7f1d1d;
|
| 594 |
+
border-radius:10px;font-size:13px;color:#fca5a5;line-height:1.7">
|
| 595 |
+
<strong>Model files not found.</strong><br>
|
| 596 |
+
Make sure these files are in the same directory as app.py:<br>
|
| 597 |
+
<code style="color:#f87171">fraud_detection_final_model.pkl</code><br>
|
| 598 |
+
<code style="color:#f87171">tfidf_vectorizer.pkl</code>
|
| 599 |
+
</div>
|
| 600 |
+
""", unsafe_allow_html=True)
|
| 601 |
+
|
| 602 |
+
# Model info card β always shown
|
| 603 |
+
st.markdown("""
|
| 604 |
+
<div style="margin-top:28px">
|
| 605 |
+
<div class="card-title">MODEL DETAILS</div>
|
| 606 |
+
<div class="info-row">
|
| 607 |
+
<span class="info-key">Architecture</span>
|
| 608 |
+
<span class="info-val">Logistic Regression</span>
|
| 609 |
+
</div>
|
| 610 |
+
<div class="info-row">
|
| 611 |
+
<span class="info-key">Embedding</span>
|
| 612 |
+
<span class="info-val">Hybrid TF-IDF (500) + SEC-BERT (768)</span>
|
| 613 |
+
</div>
|
| 614 |
+
<div class="info-row">
|
| 615 |
+
<span class="info-key">SEC-BERT</span>
|
| 616 |
+
<span class="info-val">nlpaueb/sec-bert-base Β· frozen</span>
|
| 617 |
+
</div>
|
| 618 |
+
<div class="info-row">
|
| 619 |
+
<span class="info-key">Dataset</span>
|
| 620 |
+
<span class="info-val">AmitKedia Β· 170 SEC filings<br>85 fraud / 85 non-fraud</span>
|
| 621 |
+
</div>
|
| 622 |
+
<div class="info-row">
|
| 623 |
+
<span class="info-key">Validation</span>
|
| 624 |
+
<span class="info-val">Stratified 5-fold CV</span>
|
| 625 |
+
</div>
|
| 626 |
+
<div class="info-row">
|
| 627 |
+
<span class="info-key">CV F1</span>
|
| 628 |
+
<span class="info-val">0.856 Β± 0.056</span>
|
| 629 |
+
</div>
|
| 630 |
+
</div>
|
| 631 |
+
""", unsafe_allow_html=True)
|
| 632 |
+
|
| 633 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 634 |
+
# NOTICE
|
| 635 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 636 |
+
st.markdown("""
|
| 637 |
+
<div class="notice">
|
| 638 |
+
<span>β Research scope:</span>
|
| 639 |
+
This tool classifies filing text based on linguistic patterns learned from 170 SEC filings.
|
| 640 |
+
It does not perform forensic accounting analysis or evaluate structured financial data.
|
| 641 |
+
Results are probabilistic and should not replace professional audit judgment.
|
| 642 |
+
Built as a data science bootcamp capstone project.
|
| 643 |
+
</div>
|
| 644 |
+
""", unsafe_allow_html=True)
|
fraud_detection_final_model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cae083729eabfd57d72fcbb152411ccf631d15e211e2fa7ec442ef40b012381c
|
| 3 |
+
size 11023
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit
|
| 2 |
+
scikit-learn
|
| 3 |
+
transformers
|
| 4 |
+
torch
|
| 5 |
+
numpy
|
| 6 |
+
pandas
|
tfidf_vectorizer.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dca5de5761fcc3f57b99245ee9e1c529c5a6e951bbe97bd0769311859a7d7501
|
| 3 |
+
size 20212
|