AML_Shield / modules /visualizations.py
AJAY KASU
Hotfix: Correct BytezChatModel params and fix missing imports for plotly/pandas
022f94e
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
# Theme settings
BG_COLOR = "#0f1117"
FONT_COLOR = "white"
COLOR_HIGH = "#e63946"
COLOR_MED = "#f4a261"
COLOR_LOW = "#2a9d8f"
COLOR_MAP = {
"High": COLOR_HIGH,
"Medium": COLOR_MED,
"Low": COLOR_LOW
}
def apply_theme(fig):
fig.update_layout(
paper_bgcolor=BG_COLOR,
plot_bgcolor=BG_COLOR,
font_color=FONT_COLOR,
margin=dict(l=40, r=40, t=40, b=40)
)
return fig
def risk_distribution_chart(df):
counts = df['risk_level'].value_counts().reset_index()
counts.columns = ['risk_level', 'count']
fig = px.pie(counts, values='count', names='risk_level', color='risk_level',
color_discrete_map=COLOR_MAP, hole=0.4)
fig.update_traces(textposition='inside', textinfo='percent+label')
return apply_theme(fig)
def flagged_transactions_timeline(df):
flagged_df = df[df['is_flagged'] == 1].copy()
if flagged_df.empty:
return px.line(title="No Flagged Transactions")
flagged_df['date'] = flagged_df['timestamp'].dt.date
daily_counts = flagged_df.groupby('date').size().reset_index(name='count')
fig = px.line(daily_counts, x='date', y='count', markers=True)
fig.update_traces(line_color=COLOR_HIGH)
return apply_theme(fig)
def amount_vs_risk_scatter(df):
# size needs to be positive
df['size'] = df['transaction_velocity'].clip(lower=1)
# create a rule string for hover
df['rule_str'] = df['rule_flags'].apply(lambda x: ", ".join(x) if isinstance(x, list) and x else "None")
fig = px.scatter(df, x='amount', y='risk_score', color='risk_level',
size='size', hover_data=['transaction_id', 'rule_str'],
color_discrete_map=COLOR_MAP, log_x=True)
return apply_theme(fig)
def transaction_type_breakdown(df):
grouped = df.groupby(['transaction_type', 'is_flagged']).size().reset_index(name='count')
grouped['Status'] = grouped['is_flagged'].map({1: 'Flagged', 0: 'Clean'})
fig = px.bar(grouped, x='transaction_type', y='count', color='Status', barmode='group',
color_discrete_map={'Flagged': COLOR_HIGH, 'Clean': COLOR_LOW})
return apply_theme(fig)
def top_flagged_customers_chart(df):
flagged = df[df['is_flagged'] == 1]
if flagged.empty:
return px.bar(title="No Flagged Customers")
cust_stats = flagged.groupby('customer_id').agg(
flagged_count=('transaction_id', 'count'),
avg_risk=('risk_score', 'mean')
).reset_index().sort_values('flagged_count', ascending=False).head(10)
fig = px.bar(cust_stats, x='flagged_count', y='customer_id', orientation='h',
color='avg_risk', color_continuous_scale='Reds')
fig.update_layout(yaxis={'categoryorder':'total ascending'})
return apply_theme(fig)
def kyc_tier_distribution(profile_df):
counts = profile_df['kyc_tier'].value_counts().reset_index()
counts.columns = ['kyc_tier', 'count']
fig = px.pie(counts, values='count', names='kyc_tier', color='kyc_tier',
color_discrete_map=COLOR_MAP, hole=0.5)
return apply_theme(fig)
def rule_trigger_frequency(df):
all_rules = []
for rules in df['rule_flags']:
if isinstance(rules, list):
all_rules.extend(rules)
if not all_rules:
return px.bar(title="No Rules Triggered")
counts = pd.Series(all_rules).value_counts().reset_index()
counts.columns = ['Rule', 'Count']
fig = px.bar(counts, x='Count', y='Rule', orientation='h', color_discrete_sequence=[COLOR_MED])
fig.update_layout(yaxis={'categoryorder':'total ascending'})
return apply_theme(fig)