|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
import plotly.express as px |
|
|
import requests |
|
|
from io import StringIO |
|
|
from datetime import datetime |
|
|
|
|
|
|
|
|
st.set_page_config( |
|
|
page_title="Terror Finance & Maritime Watch", |
|
|
page_icon="π‘οΈ", |
|
|
layout="wide", |
|
|
initial_sidebar_state="collapsed" |
|
|
) |
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<style> |
|
|
.main { |
|
|
padding: 0rem 1rem; |
|
|
} |
|
|
.stTabs [data-baseweb="tab-list"] { |
|
|
gap: 24px; |
|
|
} |
|
|
.stTabs [data-baseweb="tab"] { |
|
|
padding-left: 20px; |
|
|
padding-right: 20px; |
|
|
background-color: transparent; |
|
|
border: none; |
|
|
color: #666; |
|
|
font-weight: 500; |
|
|
} |
|
|
.stTabs [aria-selected="true"] { |
|
|
background-color: transparent; |
|
|
color: #E53E3E; |
|
|
border-bottom: 2px solid #E53E3E; |
|
|
} |
|
|
|
|
|
/* Metric Cards */ |
|
|
.metric-card { |
|
|
background: white; |
|
|
padding: 24px; |
|
|
border-radius: 12px; |
|
|
border: 1px solid #E2E8F0; |
|
|
text-align: center; |
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1); |
|
|
} |
|
|
.metric-number { |
|
|
font-size: 2.5rem; |
|
|
font-weight: 700; |
|
|
margin: 8px 0; |
|
|
color: #1A202C !important; |
|
|
} |
|
|
.metric-label { |
|
|
color: #4A5568 !important; |
|
|
font-size: 0.875rem; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.5px; |
|
|
} |
|
|
|
|
|
/* Entity Cards */ |
|
|
.entity-card { |
|
|
background: white; |
|
|
padding: 24px; |
|
|
border-radius: 12px; |
|
|
border: 1px solid #E2E8F0; |
|
|
margin-bottom: 16px; |
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1); |
|
|
} |
|
|
.entity-card h3, |
|
|
.entity-card p, |
|
|
.entity-card span, |
|
|
.entity-card div { |
|
|
color: #1A202C !important; |
|
|
} |
|
|
|
|
|
/* Tags */ |
|
|
.tag { |
|
|
display: inline-block; |
|
|
padding: 4px 12px; |
|
|
background-color: #EBF8FF; |
|
|
color: #2B6CB0; |
|
|
border-radius: 16px; |
|
|
font-size: 0.875rem; |
|
|
margin-right: 8px; |
|
|
margin-bottom: 8px; |
|
|
} |
|
|
.tag-danger { |
|
|
background-color: #FED7D7; |
|
|
color: #C53030; |
|
|
} |
|
|
.tag-warning { |
|
|
background-color: #FEFCBF; |
|
|
color: #975A16; |
|
|
} |
|
|
|
|
|
/* Headers */ |
|
|
h1 { |
|
|
color: #1A202C; |
|
|
font-weight: 700; |
|
|
} |
|
|
h2 { |
|
|
color: #2D3748; |
|
|
font-weight: 600; |
|
|
margin-top: 2rem; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
|
|
|
/* Status indicators */ |
|
|
.status-active { |
|
|
color: #48BB78; |
|
|
font-weight: bold; |
|
|
} |
|
|
.status-inactive { |
|
|
color: #E53E3E; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
/* Pariente AI Branding */ |
|
|
.pariente-ai { |
|
|
color: #4A5568; |
|
|
font-size: 0.875rem; |
|
|
font-style: italic; |
|
|
text-align: right; |
|
|
} |
|
|
.pariente-ai-footer { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
font-weight: bold; |
|
|
} |
|
|
</style> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
def extract_imo_from_info(info_text): |
|
|
"""Extract IMO number from Information field""" |
|
|
import re |
|
|
if not info_text or pd.isna(info_text): |
|
|
return "N/A" |
|
|
|
|
|
|
|
|
imo_match = re.search(r'IMO-?\s*(\d{7})', str(info_text), re.IGNORECASE) |
|
|
if imo_match: |
|
|
return imo_match.group(1) |
|
|
return "N/A" |
|
|
|
|
|
def extract_mmsi_from_info(info_text): |
|
|
"""Extract MMSI from Information field""" |
|
|
import re |
|
|
if not info_text or pd.isna(info_text): |
|
|
return "N/A" |
|
|
|
|
|
|
|
|
mmsi_match = re.search(r'MMSI-?\s*(\d{9})', str(info_text), re.IGNORECASE) |
|
|
if mmsi_match: |
|
|
return mmsi_match.group(1) |
|
|
return "N/A" |
|
|
|
|
|
def extract_call_sign_from_info(info_text): |
|
|
"""Extract Call Sign from Information field""" |
|
|
import re |
|
|
if not info_text or pd.isna(info_text): |
|
|
return "N/A" |
|
|
|
|
|
|
|
|
call_match = re.search(r'Call Sign-?\s*([A-Z0-9]+)', str(info_text), re.IGNORECASE) |
|
|
if call_match: |
|
|
return call_match.group(1) |
|
|
return "N/A" |
|
|
|
|
|
def extract_owner_from_info(info_text): |
|
|
"""Extract owner information from Information field""" |
|
|
import re |
|
|
if not info_text or pd.isna(info_text): |
|
|
return "N/A" |
|
|
|
|
|
|
|
|
owner_match = re.search(r'Registered owner-?\s*([^,]+)', str(info_text), re.IGNORECASE) |
|
|
if owner_match: |
|
|
return owner_match.group(1).strip() |
|
|
|
|
|
manager_match = re.search(r'Commercial manager-?\s*([^,]+)', str(info_text), re.IGNORECASE) |
|
|
if manager_match: |
|
|
return manager_match.group(1).strip() |
|
|
|
|
|
return "N/A" |
|
|
def safe_get(row, key, default="N/A"): |
|
|
try: |
|
|
|
|
|
if key == 'Name': |
|
|
|
|
|
if 'Name' in row.index and pd.notna(row['Name']) and str(row['Name']).strip(): |
|
|
return str(row['Name']).strip() |
|
|
|
|
|
|
|
|
last_name = row.get('Last_Name', '') if 'Last_Name' in row.index else '' |
|
|
first_name = row.get('First_Name', '') if 'First_Name' in row.index else '' |
|
|
|
|
|
|
|
|
last_name = str(last_name).strip() if pd.notna(last_name) else '' |
|
|
first_name = str(first_name).strip() if pd.notna(first_name) else '' |
|
|
|
|
|
|
|
|
if last_name and first_name: |
|
|
return f"{last_name} {first_name}" |
|
|
elif first_name: |
|
|
return first_name |
|
|
elif last_name: |
|
|
return last_name |
|
|
else: |
|
|
dc_id = row.get('DC_ID', 'Unknown') |
|
|
return f"Entity {dc_id}" |
|
|
|
|
|
|
|
|
if key in row.index: |
|
|
val = row[key] |
|
|
else: |
|
|
|
|
|
alternatives = { |
|
|
'DC_ID': ['ID', 'Entity_ID'], |
|
|
'Countries': ['Country', 'Location'], |
|
|
'Companies': ['Company', 'Business'], |
|
|
'Phone#': ['Phone', 'Telephone', 'Contact_Phone'], |
|
|
'Linked To': ['Organization', 'Terror_Organization', 'Group'], |
|
|
'IMO': ['IMO_Number', 'IMO_No', 'International_Maritime_Organization'], |
|
|
'Flag': ['Flag_State', 'Flag_Country'], |
|
|
'DWT': ['Deadweight', 'Dead_Weight_Tonnage'], |
|
|
'Built_Year': ['Year_Built', 'Construction_Year', 'DOB'], |
|
|
'Status': ['Vessel_Status', 'Ship_Status', 'AIS_Status'], |
|
|
'Insurance': ['Insurer', 'Insurance_Company'] |
|
|
} |
|
|
|
|
|
val = None |
|
|
if key in alternatives: |
|
|
for alt_key in alternatives[key]: |
|
|
if alt_key in row.index: |
|
|
val = row[alt_key] |
|
|
break |
|
|
|
|
|
if val is None: |
|
|
return default |
|
|
|
|
|
|
|
|
if pd.isna(val) or str(val).strip() == '' or str(val).lower() in ['nan', 'none', 'null']: |
|
|
return default |
|
|
|
|
|
return str(val).strip() |
|
|
except (KeyError, TypeError, AttributeError): |
|
|
return default |
|
|
|
|
|
|
|
|
@st.cache_data |
|
|
def load_clean_data(): |
|
|
"""Load pre-processed clean datasets""" |
|
|
base_url = "https://huggingface.co/spaces/Malaji71/list/resolve/main/" |
|
|
|
|
|
datasets = {} |
|
|
files_to_load = { |
|
|
'individuals': 'individuals_clean.csv', |
|
|
'companies': 'companies_clean.csv', |
|
|
'vessels': 'vessels_clean.csv' |
|
|
} |
|
|
|
|
|
for category, filename in files_to_load.items(): |
|
|
try: |
|
|
url = base_url + filename |
|
|
response = requests.get(url) |
|
|
response.raise_for_status() |
|
|
|
|
|
|
|
|
df = pd.read_csv(StringIO(response.text)) |
|
|
|
|
|
|
|
|
for col in df.columns: |
|
|
if df[col].dtype == 'object': |
|
|
df[col] = df[col].fillna('').astype(str) |
|
|
|
|
|
datasets[category] = df |
|
|
|
|
|
except Exception as e: |
|
|
st.error(f"Error loading {filename}: {str(e)}") |
|
|
|
|
|
datasets[category] = pd.DataFrame() |
|
|
|
|
|
return datasets |
|
|
|
|
|
|
|
|
def create_sample_data(): |
|
|
"""Create sample data if CSV files are not available""" |
|
|
individuals = pd.DataFrame({ |
|
|
'Name': ['Sample Individual 1', 'Sample Individual 2'], |
|
|
'DC_ID': [1, 2], |
|
|
'Position': ['Financier', 'Operator'], |
|
|
'Countries': ['Lebanon', 'Syria'], |
|
|
'Email': ['sample1@email.com', 'sample2@email.com'], |
|
|
'Phone#': ['+961-xxx-xxxx', '+963-xxx-xxxx'], |
|
|
'Companies': ['Sample Corp', 'Test LLC'], |
|
|
'Linked To': ['Hamas', 'Hezbollah'], |
|
|
'Auto_Category': ['Individual', 'Individual'] |
|
|
}) |
|
|
|
|
|
companies = pd.DataFrame({ |
|
|
'Name': ['Sample Company 1', 'Sample Company 2'], |
|
|
'DC_ID': [101, 102], |
|
|
'Sub_Category': ['Shell Company', 'Front Company'], |
|
|
'Countries': ['Panama', 'Cyprus'], |
|
|
'Owner': ['Unknown', 'Sample Person'], |
|
|
'Key_Individuals': ['Person A', 'Person B'], |
|
|
'Linked To': ['Hamas', 'Hezbollah'], |
|
|
'Auto_Category': ['Company', 'Company'] |
|
|
}) |
|
|
|
|
|
vessels = pd.DataFrame({ |
|
|
'Name': ['Sample Vessel 1', 'Sample Vessel 2'], |
|
|
'DC_ID': [201, 202], |
|
|
'IMO': ['1234567', '7654321'], |
|
|
'Flag': ['Panama', 'Liberia'], |
|
|
'Owner': ['Sample Maritime LLC', 'Ocean Holdings'], |
|
|
'Status': ['AIS Off', 'Active'], |
|
|
'Insurance': ['Unknown', 'Lloyd\'s'], |
|
|
'Auto_Category': ['Vessel', 'Vessel'] |
|
|
}) |
|
|
|
|
|
return { |
|
|
'individuals': individuals, |
|
|
'companies': companies, |
|
|
'vessels': vessels |
|
|
} |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([6, 1, 1]) |
|
|
with col1: |
|
|
st.markdown("# π‘οΈ Terror Finance & Maritime Watch") |
|
|
st.markdown("*Powered by Pariente AI - Advanced Intelligence Analytics*") |
|
|
with col2: |
|
|
theme_toggle = st.checkbox("π", key="theme") |
|
|
with col3: |
|
|
st.markdown('<div class="pariente-ai">π€ <span class="pariente-ai-footer">Pariente AI</span><br><em>Intelligence Platform</em></div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
try: |
|
|
data = load_clean_data() |
|
|
|
|
|
if all(len(df) == 0 for df in data.values()): |
|
|
st.warning("β οΈ Using sample data - CSV files not found. Please upload the processed CSV files.") |
|
|
data = create_sample_data() |
|
|
except Exception as e: |
|
|
st.error(f"Error loading data: {str(e)}") |
|
|
data = create_sample_data() |
|
|
|
|
|
|
|
|
tab1, tab2, tab3, tab4, tab5 = st.tabs(["π₯ Individuals", "π’ Companies", "π’ Vessels", "π Summary", "π Data Reports"]) |
|
|
|
|
|
|
|
|
with tab4: |
|
|
st.markdown("## Key Statistics") |
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
with col1: |
|
|
st.markdown(f""" |
|
|
<div class="metric-card"> |
|
|
<div class="metric-number" style="color: #E53E3E;">{len(data['individuals'])}</div> |
|
|
<div class="metric-label">Individuals Tracked</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with col2: |
|
|
st.markdown(f""" |
|
|
<div class="metric-card"> |
|
|
<div class="metric-number" style="color: #3182CE;">{len(data['companies'])}</div> |
|
|
<div class="metric-label">Companies Monitored</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with col3: |
|
|
st.markdown(f""" |
|
|
<div class="metric-card"> |
|
|
<div class="metric-number" style="color: #F6AD55;">{len(data['vessels'])}</div> |
|
|
<div class="metric-label">Vessels Documented</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
with col4: |
|
|
|
|
|
try: |
|
|
all_countries = [] |
|
|
for df in data.values(): |
|
|
if 'Countries' in df.columns: |
|
|
countries = df['Countries'].dropna().astype(str) |
|
|
for country_list in countries: |
|
|
if ',' in country_list: |
|
|
all_countries.extend([c.strip() for c in country_list.split(',') if c.strip()]) |
|
|
else: |
|
|
all_countries.append(country_list.strip()) |
|
|
unique_countries = len(set([c for c in all_countries if c and c.lower() != 'nan'])) |
|
|
except: |
|
|
unique_countries = 0 |
|
|
|
|
|
st.markdown(f""" |
|
|
<div class="metric-card"> |
|
|
<div class="metric-number" style="color: #48BB78;">{unique_countries}</div> |
|
|
<div class="metric-label">Countries Involved</div> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.markdown("## Data Visualization") |
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
|
|
with col1: |
|
|
st.markdown("### Organizations") |
|
|
try: |
|
|
if len(data['individuals']) > 0 and 'Linked To' in data['individuals'].columns: |
|
|
org_data = data['individuals']['Linked To'].value_counts().reset_index() |
|
|
org_data.columns = ['Organization', 'Count'] |
|
|
if not org_data.empty: |
|
|
fig = px.pie(org_data, names='Organization', values='Count') |
|
|
fig.update_layout(showlegend=True, height=300) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
else: |
|
|
st.write("No organization data available") |
|
|
else: |
|
|
st.write("No organization data available") |
|
|
except Exception as e: |
|
|
st.write("Error creating organization chart") |
|
|
|
|
|
with col2: |
|
|
st.markdown("### Entity Types") |
|
|
type_data = pd.DataFrame({ |
|
|
'Type': ['Individuals', 'Companies', 'Vessels'], |
|
|
'Count': [len(data['individuals']), len(data['companies']), len(data['vessels'])] |
|
|
}) |
|
|
fig = px.bar(type_data, x='Type', y='Count', color_discrete_sequence=['#3182CE']) |
|
|
fig.update_layout(showlegend=False, height=300) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with col3: |
|
|
st.markdown("### Top Countries") |
|
|
try: |
|
|
if unique_countries > 0: |
|
|
country_counts = {} |
|
|
for df in data.values(): |
|
|
if 'Countries' in df.columns: |
|
|
countries = df['Countries'].dropna().astype(str) |
|
|
for country_list in countries: |
|
|
if ',' in country_list: |
|
|
for country in country_list.split(','): |
|
|
country = country.strip() |
|
|
if country and country.lower() != 'nan': |
|
|
country_counts[country] = country_counts.get(country, 0) + 1 |
|
|
else: |
|
|
country = country_list.strip() |
|
|
if country and country.lower() != 'nan': |
|
|
country_counts[country] = country_counts.get(country, 0) + 1 |
|
|
|
|
|
if country_counts: |
|
|
country_data = pd.DataFrame(list(country_counts.items()), columns=['Country', 'Count']) |
|
|
country_data = country_data.sort_values('Count', ascending=False).head(10) |
|
|
fig = px.pie(country_data, names='Country', values='Count') |
|
|
fig.update_layout(showlegend=True, height=300) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
else: |
|
|
st.write("No country data available") |
|
|
else: |
|
|
st.write("No country data available") |
|
|
except Exception as e: |
|
|
st.write("Error creating country chart") |
|
|
|
|
|
|
|
|
with tab1: |
|
|
st.markdown("## Individuals Database") |
|
|
|
|
|
|
|
|
col1, col2 = st.columns([6, 1]) |
|
|
with col2: |
|
|
if st.button("π₯ Export", key="export_individuals"): |
|
|
csv_data = data['individuals'].to_csv(index=False) |
|
|
st.download_button( |
|
|
label="Download CSV", |
|
|
data=csv_data, |
|
|
file_name=f"individuals_{datetime.now().strftime('%Y%m%d')}.csv", |
|
|
mime="text/csv" |
|
|
) |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([3, 2, 2]) |
|
|
|
|
|
with col1: |
|
|
name_filter = st.text_input("Search by name", placeholder="Enter name...", key="search_individuals") |
|
|
|
|
|
with col2: |
|
|
country_options = ["All Countries"] |
|
|
if len(data['individuals']) > 0 and 'Countries' in data['individuals'].columns: |
|
|
all_countries = set() |
|
|
for country_list in data['individuals']['Countries'].dropna().astype(str): |
|
|
if ',' in country_list: |
|
|
all_countries.update([c.strip() for c in country_list.split(',') if c.strip() and c.lower() != 'nan']) |
|
|
else: |
|
|
if country_list.strip() and country_list.lower() != 'nan': |
|
|
all_countries.add(country_list.strip()) |
|
|
country_options += sorted(list(all_countries)) |
|
|
country_filter = st.selectbox("Country", country_options, key="country_individuals") |
|
|
|
|
|
with col3: |
|
|
org_options = ["All Organizations"] |
|
|
if len(data['individuals']) > 0 and 'Linked To' in data['individuals'].columns: |
|
|
orgs = data['individuals']['Linked To'].dropna().astype(str) |
|
|
org_options += sorted(orgs.unique().tolist()) |
|
|
org_filter = st.selectbox("Organization", org_options, key="org_individuals") |
|
|
|
|
|
|
|
|
filtered_individuals = data['individuals'].copy() |
|
|
|
|
|
if name_filter: |
|
|
filtered_individuals = filtered_individuals[ |
|
|
filtered_individuals['Name'].str.contains(name_filter, case=False, na=False) |
|
|
] |
|
|
|
|
|
if country_filter != "All Countries": |
|
|
filtered_individuals = filtered_individuals[ |
|
|
filtered_individuals['Countries'].str.contains(country_filter, case=False, na=False) |
|
|
] |
|
|
|
|
|
if org_filter != "All Organizations": |
|
|
filtered_individuals = filtered_individuals[ |
|
|
filtered_individuals['Linked To'] == org_filter |
|
|
] |
|
|
|
|
|
|
|
|
for _, person in filtered_individuals.iterrows(): |
|
|
org_value = safe_get(person, 'Linked To') |
|
|
org_color = "tag-danger" if "Hamas" in org_value else "tag-warning" |
|
|
|
|
|
st.markdown(f""" |
|
|
<div class="entity-card"> |
|
|
<h3>{safe_get(person, 'Name')}</h3> |
|
|
<p><strong>ID:</strong> {safe_get(person, 'DC_ID')}</p> |
|
|
<p><strong>Position:</strong> {safe_get(person, 'Position')}</p> |
|
|
<p><strong>Countries:</strong> {safe_get(person, 'Countries')}</p> |
|
|
<p><strong>Email:</strong> {safe_get(person, 'Email')}</p> |
|
|
<p><strong>Phone:</strong> {safe_get(person, 'Phone#')}</p> |
|
|
<p><strong>Companies:</strong> {safe_get(person, 'Companies')}</p> |
|
|
<span class="tag {org_color}">{org_value}</span> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
with tab2: |
|
|
st.markdown("## Companies Database") |
|
|
|
|
|
|
|
|
col1, col2 = st.columns([6, 1]) |
|
|
with col2: |
|
|
if st.button("π₯ Export", key="export_companies"): |
|
|
csv_data = data['companies'].to_csv(index=False) |
|
|
st.download_button( |
|
|
label="Download CSV", |
|
|
data=csv_data, |
|
|
file_name=f"companies_{datetime.now().strftime('%Y%m%d')}.csv", |
|
|
mime="text/csv" |
|
|
) |
|
|
|
|
|
|
|
|
name_filter = st.text_input("Search companies", placeholder="Enter company name...", key="search_companies") |
|
|
|
|
|
|
|
|
filtered_companies = data['companies'].copy() |
|
|
if name_filter: |
|
|
filtered_companies = filtered_companies[ |
|
|
filtered_companies['Name'].str.contains(name_filter, case=False, na=False) |
|
|
] |
|
|
|
|
|
|
|
|
for _, company in filtered_companies.iterrows(): |
|
|
org_value = safe_get(company, 'Linked To') |
|
|
org_color = "tag-danger" if "Hamas" in org_value else "tag-warning" |
|
|
|
|
|
st.markdown(f""" |
|
|
<div class="entity-card"> |
|
|
<h3>{safe_get(company, 'Name')}</h3> |
|
|
<p><strong>ID:</strong> {safe_get(company, 'DC_ID')}</p> |
|
|
<p><strong>Type:</strong> {safe_get(company, 'Sub_Category')}</p> |
|
|
<p><strong>Countries:</strong> {safe_get(company, 'Countries')}</p> |
|
|
<p><strong>Owner:</strong> {safe_get(company, 'Owner')}</p> |
|
|
<p><strong>Key Individuals:</strong> {safe_get(company, 'Key_Individuals')}</p> |
|
|
<span class="tag {org_color}">{org_value}</span> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
with tab3: |
|
|
st.markdown("## Maritime Vessels Database") |
|
|
|
|
|
|
|
|
col1, col2 = st.columns([6, 1]) |
|
|
with col2: |
|
|
if st.button("π₯ Export", key="export_vessels"): |
|
|
csv_data = data['vessels'].to_csv(index=False) |
|
|
st.download_button( |
|
|
label="Download CSV", |
|
|
data=csv_data, |
|
|
file_name=f"vessels_{datetime.now().strftime('%Y%m%d')}.csv", |
|
|
mime="text/csv" |
|
|
) |
|
|
|
|
|
|
|
|
col1, col2, col3, col4 = st.columns([3, 2, 2, 2]) |
|
|
|
|
|
with col1: |
|
|
name_filter = st.text_input("Search vessels", placeholder="Enter vessel name or IMO...", key="search_vessels") |
|
|
|
|
|
with col2: |
|
|
flag_options = ["All Flags"] |
|
|
if len(data['vessels']) > 0 and 'Flag' in data['vessels'].columns: |
|
|
flags = data['vessels']['Flag'].dropna().astype(str).unique() |
|
|
flag_options += sorted([f for f in flags if f and f.lower() != 'nan']) |
|
|
flag_filter = st.selectbox("Flag State", flag_options, key="flag_vessels") |
|
|
|
|
|
with col3: |
|
|
status_options = ["All Status"] |
|
|
if len(data['vessels']) > 0 and 'Status' in data['vessels'].columns: |
|
|
statuses = data['vessels']['Status'].dropna().astype(str).unique() |
|
|
status_options += sorted([s for s in statuses if s and s.lower() != 'nan']) |
|
|
status_filter = st.selectbox("Status", status_options, key="status_vessels") |
|
|
|
|
|
with col4: |
|
|
org_options = ["All Organizations"] |
|
|
if len(data['vessels']) > 0 and 'Linked To' in data['vessels'].columns: |
|
|
orgs = data['vessels']['Linked To'].dropna().astype(str).unique() |
|
|
org_options += sorted([o for o in orgs if o and o.lower() != 'nan']) |
|
|
org_filter = st.selectbox("Organization", org_options, key="org_vessels") |
|
|
|
|
|
|
|
|
filtered_vessels = data['vessels'].copy() |
|
|
|
|
|
if name_filter: |
|
|
|
|
|
mask = ( |
|
|
filtered_vessels['Name'].str.contains(name_filter, case=False, na=False) | |
|
|
filtered_vessels.get('IMO', pd.Series()).astype(str).str.contains(name_filter, case=False, na=False) | |
|
|
filtered_vessels.get('Owner', pd.Series()).astype(str).str.contains(name_filter, case=False, na=False) | |
|
|
filtered_vessels.get('Information', pd.Series()).astype(str).str.contains(name_filter, case=False, na=False) |
|
|
) |
|
|
filtered_vessels = filtered_vessels[mask] |
|
|
|
|
|
if flag_filter != "All Flags": |
|
|
filtered_vessels = filtered_vessels[ |
|
|
filtered_vessels['Flag'].str.contains(flag_filter, case=False, na=False) |
|
|
] |
|
|
|
|
|
if status_filter != "All Status": |
|
|
filtered_vessels = filtered_vessels[ |
|
|
filtered_vessels['Status'] == status_filter |
|
|
] |
|
|
|
|
|
if org_filter != "All Organizations": |
|
|
filtered_vessels = filtered_vessels[ |
|
|
filtered_vessels['Linked To'] == org_filter |
|
|
] |
|
|
|
|
|
|
|
|
for _, vessel in filtered_vessels.iterrows(): |
|
|
status_value = safe_get(vessel, 'Status') |
|
|
status_class = "status-inactive" if status_value == 'AIS Off' else "status-active" |
|
|
|
|
|
|
|
|
imo = safe_get(vessel, 'IMO') |
|
|
flag = safe_get(vessel, 'Flag') |
|
|
vessel_type = safe_get(vessel, 'Sub_Category') |
|
|
owner = safe_get(vessel, 'Owner') |
|
|
built_year = safe_get(vessel, 'Built_Year', safe_get(vessel, 'DOB')) |
|
|
dwt = safe_get(vessel, 'DWT') |
|
|
|
|
|
st.markdown(f""" |
|
|
<div class="entity-card"> |
|
|
<h3>{safe_get(vessel, 'Name')}</h3> |
|
|
<p><strong>DC_ID:</strong> {safe_get(vessel, 'DC_ID')}</p> |
|
|
<p><strong>IMO Number:</strong> <span style="color: #2B6CB0; font-weight: bold;">{imo}</span></p> |
|
|
<p><strong>Vessel Type:</strong> {vessel_type}</p> |
|
|
<p><strong>Flag State:</strong> {flag}</p> |
|
|
<p><strong>Owner:</strong> {owner}</p> |
|
|
<p><strong>Built Year:</strong> {built_year}</p> |
|
|
<p><strong>DWT:</strong> {dwt}</p> |
|
|
<p><strong>Status:</strong> <span class="{status_class}">{status_value}</span></p> |
|
|
<p><strong>Insurance:</strong> {safe_get(vessel, 'Insurance')}</p> |
|
|
<p><strong>Countries:</strong> {safe_get(vessel, 'Countries')}</p> |
|
|
<p><strong>Information:</strong> {safe_get(vessel, 'Information')}</p> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
with tab5: |
|
|
st.markdown("## π Data Analysis Reports") |
|
|
|
|
|
|
|
|
try: |
|
|
|
|
|
analysis_url = "https://huggingface.co/spaces/Malaji71/list/resolve/main/complete_analysis.json" |
|
|
analysis_response = requests.get(analysis_url) |
|
|
if analysis_response.status_code == 200: |
|
|
analysis_data = analysis_response.json() |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
st.markdown("### π Data Quality Metrics") |
|
|
if 'complete_analysis' in analysis_data: |
|
|
quality_metrics = analysis_data['complete_analysis'].get('quality_metrics', {}) |
|
|
if 'completeness' in quality_metrics: |
|
|
completeness_df = pd.DataFrame( |
|
|
list(quality_metrics['completeness'].items()), |
|
|
columns=['Column', 'Completeness %'] |
|
|
).sort_values('Completeness %', ascending=False) |
|
|
st.dataframe(completeness_df, use_container_width=True) |
|
|
|
|
|
if 'issues' in quality_metrics and quality_metrics['issues']: |
|
|
st.markdown("### β οΈ Data Issues") |
|
|
for issue in quality_metrics['issues']: |
|
|
st.warning(issue) |
|
|
|
|
|
with col2: |
|
|
st.markdown("### π― Categorization Analysis") |
|
|
if 'complete_analysis' in analysis_data: |
|
|
cat_analysis = analysis_data['complete_analysis'].get('categorization_analysis', {}) |
|
|
if 'content_based_categorization' in cat_analysis: |
|
|
cat_counts = cat_analysis['content_based_categorization'] |
|
|
|
|
|
for category, count in cat_counts.items(): |
|
|
st.metric(category.title(), count) |
|
|
|
|
|
st.markdown("### π Processing Statistics") |
|
|
processing_stats = { |
|
|
'Total Rows Processed': analysis_data.get('complete_analysis', {}).get('total_rows', 0), |
|
|
'Total Columns Analyzed': analysis_data.get('complete_analysis', {}).get('total_columns', 0), |
|
|
'Analysis Timestamp': analysis_data.get('complete_analysis', {}).get('timestamp', 'Unknown') |
|
|
} |
|
|
|
|
|
for stat, value in processing_stats.items(): |
|
|
st.write(f"**{stat}**: {value}") |
|
|
|
|
|
else: |
|
|
st.info("π Complete analysis data not available. Upload complete_analysis.json to see detailed reports.") |
|
|
|
|
|
except Exception as e: |
|
|
st.info("π Analysis reports will be available when you upload the JSON files.") |
|
|
|
|
|
|
|
|
try: |
|
|
|
|
|
summary_url = "https://huggingface.co/spaces/Malaji71/list/resolve/main/executive_summary.txt" |
|
|
summary_response = requests.get(summary_url) |
|
|
if summary_response.status_code == 200: |
|
|
st.markdown("### π Executive Summary") |
|
|
st.text(summary_response.text) |
|
|
except: |
|
|
pass |
|
|
|
|
|
try: |
|
|
|
|
|
report_url = "https://huggingface.co/spaces/Malaji71/list/resolve/main/analysis_report.txt" |
|
|
report_response = requests.get(report_url) |
|
|
if report_response.status_code == 200: |
|
|
st.markdown("### π Full Analysis Report") |
|
|
with st.expander("View Complete Report"): |
|
|
st.text(report_response.text) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
st.markdown("### π₯ Download Analysis Files") |
|
|
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
|
|
|
|
with col1: |
|
|
try: |
|
|
|
|
|
analysis_url = "https://huggingface.co/spaces/Malaji71/list/resolve/main/complete_analysis.json" |
|
|
analysis_response = requests.get(analysis_url) |
|
|
if analysis_response.status_code == 200: |
|
|
st.download_button( |
|
|
label="π Analysis JSON", |
|
|
data=analysis_response.content, |
|
|
file_name=f"complete_analysis_{datetime.now().strftime('%Y%m%d')}.json", |
|
|
mime="application/json", |
|
|
help="Complete analysis data in JSON format" |
|
|
) |
|
|
else: |
|
|
st.button("π Analysis JSON", disabled=True, help="File not available") |
|
|
except: |
|
|
st.button("π Analysis JSON", disabled=True, help="File not available") |
|
|
|
|
|
with col2: |
|
|
try: |
|
|
|
|
|
report_url = "https://huggingface.co/spaces/Malaji71/list/resolve/main/analysis_report.txt" |
|
|
report_response = requests.get(report_url) |
|
|
if report_response.status_code == 200: |
|
|
st.download_button( |
|
|
label="π Full Report", |
|
|
data=report_response.text.encode('utf-8'), |
|
|
file_name=f"analysis_report_{datetime.now().strftime('%Y%m%d')}.txt", |
|
|
mime="text/plain", |
|
|
help="Detailed human-readable report" |
|
|
) |
|
|
else: |
|
|
st.button("π Full Report", disabled=True, help="File not available") |
|
|
except: |
|
|
st.button("π Full Report", disabled=True, help="File not available") |
|
|
|
|
|
with col3: |
|
|
try: |
|
|
|
|
|
summary_url = "https://huggingface.co/spaces/Malaji71/list/resolve/main/executive_summary.txt" |
|
|
summary_response = requests.get(summary_url) |
|
|
if summary_response.status_code == 200: |
|
|
st.download_button( |
|
|
label="π Executive Summary", |
|
|
data=summary_response.text.encode('utf-8'), |
|
|
file_name=f"executive_summary_{datetime.now().strftime('%Y%m%d')}.txt", |
|
|
mime="text/plain", |
|
|
help="Executive summary" |
|
|
) |
|
|
else: |
|
|
st.button("π Executive Summary", disabled=True, help="File not available") |
|
|
except: |
|
|
st.button("π Executive Summary", disabled=True, help="File not available") |
|
|
|
|
|
with col4: |
|
|
try: |
|
|
|
|
|
uncertain_url = "https://huggingface.co/spaces/Malaji71/list/resolve/main/uncertain_entities.csv" |
|
|
uncertain_response = requests.get(uncertain_url) |
|
|
if uncertain_response.status_code == 200: |
|
|
st.download_button( |
|
|
label="β Uncertain Entities", |
|
|
data=uncertain_response.content, |
|
|
file_name=f"uncertain_entities_{datetime.now().strftime('%Y%m%d')}.csv", |
|
|
mime="text/csv", |
|
|
help="Entities requiring manual review" |
|
|
) |
|
|
else: |
|
|
st.button("β Uncertain Entities", disabled=True, help="File not available") |
|
|
except: |
|
|
st.button("β Uncertain Entities", disabled=True, help="File not available") |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
st.markdown("### π¦ Bulk Download") |
|
|
|
|
|
if st.button("π₯ Download All Analysis Files", use_container_width=True): |
|
|
try: |
|
|
|
|
|
import zipfile |
|
|
from io import BytesIO |
|
|
|
|
|
zip_buffer = BytesIO() |
|
|
|
|
|
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: |
|
|
|
|
|
files_to_add = [ |
|
|
("complete_analysis.json", "application/json"), |
|
|
("analysis_report.txt", "text/plain"), |
|
|
("executive_summary.txt", "text/plain"), |
|
|
("uncertain_entities.csv", "text/csv") |
|
|
] |
|
|
|
|
|
for filename, mime_type in files_to_add: |
|
|
try: |
|
|
file_url = f"https://huggingface.co/spaces/Malaji71/list/resolve/main/{filename}" |
|
|
response = requests.get(file_url) |
|
|
if response.status_code == 200: |
|
|
zip_file.writestr(filename, response.content) |
|
|
except: |
|
|
|
|
|
zip_file.writestr(f"{filename}.missing", f"File {filename} not available") |
|
|
|
|
|
st.download_button( |
|
|
label="π¦ Download ZIP Package", |
|
|
data=zip_buffer.getvalue(), |
|
|
file_name=f"terror_finance_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip", |
|
|
mime="application/zip", |
|
|
help="Download all analysis files in a single ZIP package" |
|
|
) |
|
|
except Exception as e: |
|
|
st.error(f"Error creating ZIP file: {str(e)}") |
|
|
|
|
|
st.markdown("---") |
|
|
st.markdown("**π Analysis Package Contents:**") |
|
|
st.markdown(""" |
|
|
- `complete_analysis.json` - Complete analysis data in JSON format |
|
|
- `analysis_report.txt` - Detailed human-readable report |
|
|
- `executive_summary.txt` - Executive summary |
|
|
- `uncertain_entities.csv` - Entities requiring manual review |
|
|
|
|
|
**π€ Generated by Pariente AI - Advanced Intelligence Analytics** |
|
|
""") |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
col1, col2 = st.columns([3, 1]) |
|
|
with col1: |
|
|
st.markdown("π‘οΈ **Terror Finance & Maritime Watch** - Monitoring entities involved in terror financing and maritime sanctions evasion") |
|
|
st.markdown(f"π Data processed: {len(data['individuals'])} individuals, {len(data['companies'])} companies, {len(data['vessels'])} vessels") |
|
|
st.markdown(f"π Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") |
|
|
with col2: |
|
|
st.markdown("**Powered by**") |
|
|
st.markdown("π€ **Pariente AI**") |
|
|
st.markdown("*Advanced Intelligence Analytics*") |