import streamlit as st
import pandas as pd
import os
import json
import shutil
import time
from datetime import datetime
import plotly.express as px
import plotly.graph_objects as go
from dotenv import load_dotenv
import base64
from io import BytesIO
# Load environment variables
load_dotenv()
# Ensure data directory exists
os.makedirs('data', exist_ok=True)
# Set page config for wide layout
st.set_page_config(page_title="YourCarbonEmissions by GXS - Công cụ Kiểm kê Khí Nhà kính và Báo cáo KKKNK cho Doanh nghiệp SMEs", page_icon="🌍", layout="wide")
# Initialize session state variables if they don't exist
if 'language' not in st.session_state:
st.session_state.language = 'English'
if 'emissions_data' not in st.session_state:
# Load data if exists, otherwise create empty dataframe
if os.path.exists('data/emissions.json'):
try:
with open('data/emissions.json', 'r') as f:
data = f.read().strip()
if data: # Check if file is not empty
try:
st.session_state.emissions_data = pd.DataFrame(json.loads(data))
except json.JSONDecodeError:
# Create a backup of the corrupted file
backup_file = f'data/emissions_backup_{int(time.time())}.json'
shutil.copy('data/emissions.json', backup_file)
st.warning(f"Corrupted emissions data file found. A backup has been created at {backup_file}")
# Create empty dataframe
st.session_state.emissions_data = pd.DataFrame(columns=[
'date', 'scope', 'category', 'activity', 'quantity',
'unit', 'emission_factor', 'emissions_kgCO2e', 'notes'
])
else:
# Empty file, create new DataFrame
st.session_state.emissions_data = pd.DataFrame(columns=[
'date', 'scope', 'category', 'activity', 'quantity',
'unit', 'emission_factor', 'emissions_kgCO2e', 'notes'
])
except Exception as e:
st.error(f"Error loading emissions data: {str(e)}")
# Create empty dataframe if loading fails
st.session_state.emissions_data = pd.DataFrame(columns=[
'date', 'scope', 'category', 'activity', 'quantity',
'unit', 'emission_factor', 'emissions_kgCO2e', 'notes'
])
# Make sure data directory exists
os.makedirs('data', exist_ok=True)
else:
st.session_state.emissions_data = pd.DataFrame(columns=[
'date', 'scope', 'category', 'activity', 'quantity',
'unit', 'emission_factor', 'emissions_kgCO2e', 'notes'
])
# Make sure data directory exists
os.makedirs('data', exist_ok=True)
if 'theme' not in st.session_state:
st.session_state.theme = 'dark'
if 'active_page' not in st.session_state:
st.session_state.active_page = "AI Insights"
# Translation dictionary
translations = {
'English': {
'title': 'YourCarbonEmissions by GXS',
'subtitle': 'Carbon Accounting & Reporting Tool for SMEs',
'dashboard': 'Dashboard',
'data_entry': 'Data Entry',
'reports': 'Reports',
'settings': 'Settings',
'about': 'About',
'scope1': 'Scope 1 (Direct Emissions)',
'scope2': 'Scope 2 (Indirect Emissions - Purchased Energy)',
'scope3': 'Scope 3 (Other Indirect Emissions)',
'date': 'Date',
'scope': 'Scope',
'category': 'Category',
'activity': 'Activity',
'quantity': 'Quantity',
'unit': 'Unit',
'emission_factor': 'Emission Factor',
'emissions': 'Emissions (kgCO2e)',
'notes': 'Notes',
'add_entry': 'Add Entry',
'upload_csv': 'Upload CSV',
'download_report': 'Download Report',
'total_emissions': 'Total Emissions',
'emissions_by_scope': 'Emissions by Scope',
'emissions_by_category': 'Emissions by Category',
'emissions_over_time': 'Emissions Over Time',
'language': 'Language',
'save': 'Save',
'cancel': 'Cancel',
'success': 'Success!',
'error': 'Error!',
'entry_added': 'Entry added successfully!',
'csv_uploaded': 'CSV uploaded successfully!',
'report_downloaded': 'Report downloaded successfully!',
'settings_saved': 'Settings saved successfully!',
'no_data': 'No data available.',
'welcome_message': 'Welcome to YourCarbonEmissions by GXS! Start by adding your emissions data or uploading a CSV file.',
'custom_category': 'Custom Category',
'custom_activity': 'Custom Activity',
'custom_unit': 'Custom Unit',
'entry_failed': 'Failed to add entry.'
},
'Vietnamese': {
'title': 'YourCarbonEmissions by GXS',
'subtitle': 'Công cụ Kiểm kê Khí Nhà kính và Báo cáo KKKNK cho Doanh nghiệp SMEs',
'dashboard': 'Dashboard',
'data_entry': 'Nhập Dữ liệu',
'reports': 'Các Báo cáo',
'settings': 'Cài đặt',
'about': 'Thông tin chung',
'scope1': 'Phạm vi 1 (Phát thải trực tiếp)',
'scope2': 'Phạm vi 2 (Phát thải gián tiếp - Mua Năng lượng)',
'scope3': 'Phạm vi 3 (Phát thải gián tiếp khác)',
'date': 'Ngày',
'scope': 'Phạm vi',
'category': 'Tiểu mục',
'activity': 'Hoạt động',
'quantity': 'Số lượng',
'unit': 'Đơn vị',
'emission_factor': 'Hệ số phát thải',
'emissions': 'Phát thải (kgCO2e)',
'notes': 'Ghi chú',
'add_entry': 'Thêm Đầu vào',
'upload_csv': 'Tải file CSV lên',
'download_report': 'Tải Báo cáo xuống',
'total_emissions': 'Tổng Phát thải',
'emissions_by_scope': 'Phát thải theo Phạm vi',
'emissions_by_category': 'Phát thải theo Tiểu mục',
'emissions_over_time': 'Phát thải qua thời gian',
'language': 'Ngôn ngữ',
'save': 'Lưu',
'cancel': 'Hủy bỏ',
'success': 'Thành công!',
'error': 'Lỗi!',
'entry_added': 'Dữ liệu đã được thêm!',
'csv_uploaded': 'CSV đã tải lên!',
'report_downloaded': 'Báo cáo đã được tải xuống!',
'settings_saved': 'Cài đặt đã được lưu!',
'no_data': 'Không có dữ liệu',
'welcome_message': 'Chào mừng Bạn đến YourCarbonEmissions by GXS! Bắt đầu bằng nhập dữ liệu phát thải của bạn hoặc tải file CSV lên',
'custom_category': 'Điều chỉnh Tiểu mục',
'custom_activity': 'Điều chỉnh Hoạt động',
'custom_unit': 'Điều chỉnh Đơn vị',
'entry_failed': 'Nhập Đầu vào thất bại'
}
}
# Function to get translated text
def t(key):
lang = st.session_state.language
return translations.get(lang, {}).get(key, key)
# Function to save emissions data
def save_emissions_data():
try:
# Create data directory if it doesn't exist
os.makedirs('data', exist_ok=True)
# Create a backup of the existing file if it exists
if os.path.exists('data/emissions.json'):
backup_path = 'data/emissions_backup.json'
try:
with open('data/emissions.json', 'r') as src, open(backup_path, 'w') as dst:
dst.write(src.read())
except Exception:
# Continue even if backup fails
pass
# Save data to JSON file with proper formatting
with open('data/emissions.json', 'w') as f:
if len(st.session_state.emissions_data) > 0:
json.dump(st.session_state.emissions_data.to_dict('records'), f, indent=2)
else:
# Write empty array if no data
f.write('[]')
return True
except Exception as e:
st.error(f"Error saving data: {str(e)}")
return False
# Function to add new emission entry
def add_emission_entry(date, business_unit, project, scope, category, activity, country, facility, responsible_person, quantity, unit, emission_factor, data_quality, verification_status, notes):
"""Add a new emission entry to the emissions data."""
try:
# Calculate emissions
emissions_kgCO2e = float(quantity) * float(emission_factor)
# Create new entry
new_entry = pd.DataFrame([{
'date': date.strftime('%Y-%m-%d'),
'business_unit': business_unit,
'project': project,
'scope': scope,
'category': category,
'activity': activity,
'country': country,
'facility': facility,
'responsible_person': responsible_person,
'quantity': float(quantity),
'unit': unit,
'emission_factor': float(emission_factor),
'emissions_kgCO2e': emissions_kgCO2e,
'data_quality': data_quality,
'verification_status': verification_status,
'notes': notes
}])
# Add to existing data
st.session_state.emissions_data = pd.concat([st.session_state.emissions_data, new_entry], ignore_index=True)
# Save data and return success/failure
return save_emissions_data()
except Exception as e:
st.error(f"Error adding entry: {str(e)}")
return False
def delete_emission_entry(index):
try:
# Make a copy of the current data
if len(st.session_state.emissions_data) > index:
# Drop the row at the specified index
st.session_state.emissions_data = st.session_state.emissions_data.drop(index).reset_index(drop=True)
# Save data and return success/failure
return save_emissions_data()
else:
st.error("Invalid index for deletion")
return False
except Exception as e:
st.error(f"Error deleting entry: {str(e)}")
return False
# Function to process uploaded CSV
def process_csv(uploaded_file):
"""Process uploaded CSV file and add to emissions data."""
try:
# Read CSV file
df = pd.read_csv(uploaded_file)
required_columns = ['date', 'scope', 'category', 'activity', 'quantity', 'unit', 'emission_factor']
# Check if all required columns exist
if not all(col in df.columns for col in required_columns):
st.error(f"CSV must contain all required columns: {', '.join(required_columns)}")
return False
# Validate data types
try:
# Convert quantity and emission_factor to float
df['quantity'] = df['quantity'].astype(float)
df['emission_factor'] = df['emission_factor'].astype(float)
# Validate dates
df['date'] = pd.to_datetime(df['date']).dt.strftime('%Y-%m-%d')
except Exception as e:
st.error(f"Data validation error: {str(e)}")
return False
# Calculate emissions if not provided
if 'emissions_kgCO2e' not in df.columns:
df['emissions_kgCO2e'] = df['quantity'] * df['emission_factor']
# Add enterprise fields if not present
enterprise_fields = {
'business_unit': 'Corporate',
'project': 'Not Applicable',
'country': 'Vietnam',
'facility': '',
'responsible_person': '',
'data_quality': 'Medium',
'verification_status': 'Unverified',
'notes': ''
}
# Add missing columns with default values
for field, default_value in enterprise_fields.items():
if field not in df.columns:
df[field] = default_value
# Append to existing data
st.session_state.emissions_data = pd.concat([st.session_state.emissions_data, df], ignore_index=True)
# Save data
if save_emissions_data():
st.success(f"Successfully added {len(df)} entries")
return True
else:
st.error("Failed to save data")
return False
except Exception as e:
st.error(f"Error processing CSV: {str(e)}")
return False
# Function to generate PDF report
def generate_report():
# Create a BytesIO object
buffer = BytesIO()
# Create a simple CSV report for now
st.session_state.emissions_data.to_csv(buffer, index=False)
buffer.seek(0)
return buffer
# Custom CSS
def local_css():
st.markdown('''
''', unsafe_allow_html=True)
# Navigation component
def render_navigation():
nav_items = [
{"icon": "📝", "label": "Data Entry (Nhập Dữ liệu", "id": "Data Entry"},
{"icon": "📊", "label": "Dashboard", "id": "Dashboard"},
{"icon": "🤖", "label": "AI Insights", "id": "AI Insights"},
{"icon": "⚙️", "label": "Settings (Cài đặt", "id": "Settings"}
]
st.markdown("### Navigation")
for item in nav_items:
active_class = "active" if st.session_state.active_page == item["id"] else ""
if st.sidebar.button(
f"{item['icon']} {item['label']}",
key=f"nav_{item['id']}",
help=f"Go to {item['label']}",
use_container_width=True
):
st.session_state.active_page = item["id"]
st.rerun()
# Metric card component
def metric_card(title, value, description=None, icon=None, prefix="", suffix=""):
st.markdown(f'''
{f'
{icon}
' if icon else ''}
{title}
{prefix}{value}{suffix}
{f'
{description}
' if description else ''}
''', unsafe_allow_html=True)
# Card component
def card(content, title=None):
if title:
st.markdown(f"{title}
{content}", unsafe_allow_html=True)
else:
st.markdown(f"{content}
", unsafe_allow_html=True)
# Apply custom CSS
local_css()
# Sidebar
with st.sidebar:
st.markdown(f"{t('title')}
", unsafe_allow_html=True)
st.markdown(f"{t('subtitle')}
", unsafe_allow_html=True)
st.divider()
# Language selector
language = st.selectbox(t('language'), ['English', 'Vietnamese'])
if language != st.session_state.language:
st.session_state.language = language
st.rerun()
st.divider()
# Navigation
render_navigation()
st.divider()
# Footer
st.markdown(
"",
unsafe_allow_html=True
)
# Main content
if st.session_state.active_page == "Dashboard":
st.markdown(f" {t('dashboard')}
", unsafe_allow_html=True)
if len(st.session_state.emissions_data) == 0:
st.markdown(f"{t('welcome_message')}
", unsafe_allow_html=True)
else:
# Calculate metrics
# Ensure emissions_kgCO2e is numeric
st.session_state.emissions_data['emissions_kgCO2e'] = pd.to_numeric(st.session_state.emissions_data['emissions_kgCO2e'], errors='coerce')
# Replace NaN with 0
st.session_state.emissions_data['emissions_kgCO2e'].fillna(0, inplace=True)
total_emissions = st.session_state.emissions_data['emissions_kgCO2e'].sum()
# Display metrics
col1, col2, col3 = st.columns(3)
with col1:
metric_card(
title=t('total_emissions'),
value=f"{total_emissions:.2f}",
suffix=" kgCO2e",
icon="🌍"
)
with col2:
if 'date' in st.session_state.emissions_data.columns:
st.session_state.emissions_data['date'] = pd.to_datetime(st.session_state.emissions_data['date'], errors='coerce')
if not st.session_state.emissions_data['date'].isnull().all():
latest_date = st.session_state.emissions_data['date'].max().strftime('%Y-%m-%d')
else:
latest_date = "No date data"
metric_card(
title="Latest Entry",
value=latest_date,
icon="📅"
)
with col3:
entry_count = len(st.session_state.emissions_data)
metric_card(
title="Total Entries",
value=str(entry_count),
icon="📊"
)
# Charts
st.markdown(f"{t('emissions_by_scope')}
", unsafe_allow_html=True)
# Check if there are any non-zero emissions before creating charts
if total_emissions > 0:
# Create scope data for pie chart
scope_data = st.session_state.emissions_data.groupby('scope')['emissions_kgCO2e'].sum().reset_index()
# Only create chart if we have data with emissions
if not scope_data.empty and scope_data['emissions_kgCO2e'].sum() > 0:
fig1 = px.pie(
scope_data,
values='emissions_kgCO2e',
names='scope',
color='scope',
color_discrete_map={'Scope 1': '#4CAF50', 'Scope 2': '#2196F3', 'Scope 3': '#FFC107'},
hole=0.4
)
fig1.update_layout(
margin=dict(t=0, b=0, l=0, r=0),
legend=dict(orientation="h", yanchor="bottom", y=-0.2, xanchor="center", x=0.5),
height=400
)
st.plotly_chart(fig1, use_container_width=True, config={'displayModeBar': False})
else:
st.info("No emissions data available for scope breakdown.")
else:
st.info("No emissions data available for scope breakdown.")
col1, col2 = st.columns(2)
with col1:
st.markdown(f"{t('emissions_by_category')}
", unsafe_allow_html=True)
if total_emissions > 0:
# Create category data for bar chart
category_data = st.session_state.emissions_data.groupby('category')['emissions_kgCO2e'].sum().reset_index()
category_data = category_data.sort_values('emissions_kgCO2e', ascending=False)
# Only create chart if we have data with emissions
if not category_data.empty and category_data['emissions_kgCO2e'].sum() > 0:
fig2 = px.bar(
category_data,
x='category',
y='emissions_kgCO2e',
color='category',
labels={'emissions_kgCO2e': 'Emissions (kgCO2e)', 'category': 'Category'}
)
fig2.update_layout(
showlegend=False,
margin=dict(t=0, b=0, l=0, r=0),
height=400
)
st.plotly_chart(fig2, use_container_width=True, config={'displayModeBar': False})
else:
st.info("No emissions data available for category breakdown.")
else:
st.info("No emissions data available for category breakdown.")
with col2:
st.markdown(f"{t('emissions_over_time')}
", unsafe_allow_html=True)
if total_emissions > 0 and 'date' in st.session_state.emissions_data.columns:
# Convert date column to datetime
time_data = st.session_state.emissions_data.copy()
time_data['date'] = pd.to_datetime(time_data['date'], errors='coerce')
# Filter out rows with invalid dates
time_data = time_data.dropna(subset=['date'])
if not time_data.empty:
# Create month column for aggregation
time_data['month'] = time_data['date'].dt.strftime('%Y-%m')
# Group by month and scope
time_data = time_data.groupby(['month', 'scope'])['emissions_kgCO2e'].sum().reset_index()
if len(time_data['month'].unique()) > 0:
# Create line chart
fig3 = px.line(
time_data,
x='month',
y='emissions_kgCO2e',
color='scope',
markers=True,
color_discrete_map={'Scope 1': '#4CAF50', 'Scope 2': '#2196F3', 'Scope 3': '#FFC107'},
labels={'emissions_kgCO2e': 'Emissions (kgCO2e)', 'month': 'Month', 'scope': 'Scope'}
)
fig3.update_layout(
margin=dict(t=0, b=0, l=0, r=0),
xaxis_title="",
yaxis_title="kgCO2e",
legend_title="",
height=400
)
st.plotly_chart(fig3, use_container_width=True, config={'displayModeBar': False})
else:
st.info("Not enough time data to show emissions over time.")
else:
st.info("No valid date data available for time series chart.")
else:
st.info("No emissions data available for time series chart.")
elif st.session_state.active_page == "Data Entry":
st.markdown(f" {t('data_entry')}
", unsafe_allow_html=True)
tabs = st.tabs([" Manual Entry", " CSV Upload"])
with tabs[0]:
st.markdown("Add New Emission Entry (Nhập Dữ liệu phát thải mới)
", unsafe_allow_html=True)
with st.form("emission_form", border=False):
col1, col2 = st.columns(2)
with col1:
date = st.date_input(t('date'), datetime.now(), help="Date when the emission occurred")
# Add business unit field for enterprise tracking with tooltip
business_unit = st.selectbox(
"Business Unit",
["Corporate", "Manufacturing", "Sales", "R&D", "Logistics", "IT", "Other"],
help="The business unit responsible for this emission"
)
if business_unit == "Other":
business_unit = st.text_input("Custom Business Unit", placeholder="Enter business unit name")
# Add project field for better categorization with tooltip
project = st.selectbox(
"Project",
["Not Applicable", "Carbon Reduction Initiative", "Sustainability Program", "Operational", "Other"],
help="The project or initiative associated with this emission"
)
if project == "Other":
project = st.text_input("Custom Project", placeholder="Enter project name")
# Add scope selection with tooltip explaining each scope
scope = st.selectbox(
t('scope'),
['Scope 1', 'Scope 2', 'Scope 3'],
help="Scope 1: Direct emissions from owned sources\nScope 2: Indirect emissions from purchased energy\nScope 3: All other indirect emissions in value chain"
)
category_options = {
'Scope 1': ['Stationary Combustion', 'Mobile Combustion', 'Fugitive Emissions', 'Process Emissions', 'Other'],
'Scope 2': ['Electricity', 'Steam', 'Heating', 'Cooling', 'Other'],
'Scope 3': ['Purchased Goods and Services', 'Capital Goods', 'Fuel- and Energy-Related Activities', 'Upstream Transportation and Distribution', 'Waste Generated in Operations', 'Business Travel', 'Employee Commuting', 'Upstream Leased Assets', 'Downstream Transportation and Distribution', 'Processing of Sold Products', 'Use of Sold Products', 'End-of-Life Treatment of Sold Products', 'Downstream Leased Assets', 'Franchises', 'Investments', 'Other']
}
category = st.selectbox(
t('category'),
category_options[scope],
help="The category of emission source"
)
if category == 'Other':
category = st.text_input(t('custom_category'), placeholder="Enter custom category")
# Enhanced location tracking with facility details and tooltips
country_options = ['Vietnam', 'India', 'United States', 'United Kingdom', 'Japan', 'Indonesia', 'Other']
country = st.selectbox(
"Country",
country_options,
help="Country where the emission occurred"
)
if country == 'Other':
country = st.text_input("Custom Country", placeholder="Enter country name")
# Add facility/location field with tooltip
facility = st.text_input(
"Facility/Location",
placeholder="e.g., Ho Chi Minh City HQ, Binh Duong Plant 2, etc.",
help="Specific facility or location where the emission occurred"
)
# Add responsible person field with tooltip
responsible_person = st.text_input(
"Responsible Person",
placeholder="Person responsible for this emission source",
help="Name of the person accountable for managing this emission source"
)
with col2:
activity_options = {
'Stationary Combustion': ['Boiler', 'Furnace', 'Generator', 'Other'],
'Mobile Combustion': ['Company Vehicle', 'Fleet Vehicle', 'Machinery', 'Other'],
'Fugitive Emissions': ['Refrigerant Leak', 'SF6 Emissions', 'Other'],
'Process Emissions': ['Cement Production', 'Chemical Production', 'Other'],
'Electricity': ['Office Electricity', 'Manufacturing Electricity', 'Other'],
'Steam': ['Industrial Steam', 'Heating Steam', 'Other'],
'Heating': ['Office Heating', 'Industrial Heating', 'Other'],
'Cooling': ['Office Cooling', 'Industrial Cooling', 'Other'],
'Purchased Goods and Services': ['Raw Materials', 'Office Supplies', 'Other'],
'Capital Goods': ['Equipment Purchase', 'Vehicle Purchase', 'Other'],
'Fuel- and Energy-Related Activities': ['Upstream Fuel Production', 'Transmission Losses', 'Other'],
'Upstream Transportation and Distribution': ['Supplier Transport', 'Inbound Logistics', 'Other'],
'Waste Generated in Operations': ['Solid Waste', 'Wastewater', 'Other'],
'Business Travel': ['Air Travel', 'Ground Travel', 'Hotel Stays', 'Other'],
'Employee Commuting': ['Private Vehicle', 'Public Transport', 'Other'],
'Upstream Leased Assets': ['Leased Equipment', 'Leased Vehicles', 'Other'],
'Downstream Transportation and Distribution': ['Outbound Logistics', 'Customer Transport', 'Other'],
'Processing of Sold Products': ['Intermediate Processing', 'Final Assembly', 'Other'],
'Use of Sold Products': ['Product Operation', 'Energy Consumption', 'Other'],
'End-of-Life Treatment of Sold Products': ['Recycling', 'Landfill', 'Other'],
'Downstream Leased Assets': ['Leased Equipment', 'Leased Property', 'Other'],
'Franchises': ['Franchise Operations', 'Franchise Energy Use', 'Other'],
'Investments': ['Investment Emissions', 'Financed Emissions', 'Other'],
'Other': ['Custom Activity', 'Other']
}
activity_key = category if category != 'Other' else 'Other'
activity_list = activity_options.get(activity_key, ['Custom Activity', 'Other'])
activity = st.selectbox(
"Activity",
activity_options.get(category, ['Other']),
help="Specific activity that generated the emissions"
)
if activity == 'Other':
activity = st.text_input("Custom Activity", placeholder="Enter custom activity")
# Add validation for quantity with tooltip
quantity = st.number_input(
t('quantity'),
min_value=0.0,
format="%.2f",
help="The amount of activity (e.g., kWh used, liters consumed, etc.)"
)
# Enhanced unit selection with tooltip
unit_options = ['kWh', 'MWh', 'GJ', 'liter', 'gallon', 'kg', 'tonne', 'km', 'mile', 'hour', 'day', 'piece', 'USD', 'Other']
unit = st.selectbox(
t('unit'),
unit_options,
help="The unit of measurement for the quantity"
)
if unit == 'Other':
unit = st.text_input(t('custom_unit'), placeholder="Enter custom unit")
# Emission factor auto-population based on country and category
emission_factors = {
'India': {
'Electricity': 0.82, 'Mobile Combustion': 2.31, 'Stationary Combustion': 1.85, 'Other': 0.0
},
'United States': {
'Electricity': 0.42,
'Mobile Combustion': 2.32,
'Stationary Combustion': 2.01,
'Business Travel': 0.12,
'Employee Commuting': 0.15
}
}
default_factor = emission_factors.get(country, {}).get(category, 0.0) if country != 'Other' else 0.0
# Now that default_factor is defined, show AI suggestion
st.info(f"💡 AI Suggestion: Based on your selections, a typical emission factor for {category} in {country} would be around {default_factor:.4f} kgCO2e per unit.")
emission_factor = st.number_input(
t('emission_factor'),
min_value=0.0,
value=default_factor,
format="%.4f",
help=f"Emission factor in kgCO2e per unit. Typical range: {max(0.1, default_factor*0.8):.4f} to {default_factor*1.2:.4f}"
)
# Add data quality indicator with color-coded help
data_quality = st.select_slider(
"Data Quality",
options=["Low", "Medium", "High"],
value="Medium",
help="🔴 Low: Estimated or proxy data\n🟡 Medium: Calculated from bills or invoices\n🟢 High: Directly measured or metered data"
)
# Add verification status with detailed help
verification_status = st.selectbox(
"Verification Status",
["Unverified", "Internally Verified", "Third-Party Verified"],
help="Unverified: No verification process applied\nInternally Verified: Checked by internal team\nThird-Party Verified: Validated by external auditor"
)
# Enhanced notes field with better guidance
notes = st.text_area(
t('notes'),
placeholder="Additional information, data sources, calculation methods, etc.",
help="Include information about data sources, calculation methodology, assumptions made, and any other relevant context"
)
# Add cost field for financial impact tracking (optional)
cost = st.number_input(
"Cost (Optional)",
min_value=0.0,
value=0.0,
format="%.2f",
help="Optional: Associated cost in your local currency"
)
# Add cost currency if cost is entered
if cost > 0:
currency = st.selectbox(
"Currency",
["VND", "USD", "EUR", "INR", "GBP", "JPY", "Other"],
help="Currency for the entered cost"
)
# Form submission buttons
col1, col2 = st.columns([1, 1])
with col1:
submitted = st.form_submit_button(t('add_entry'), type="primary", use_container_width=True)
with col2:
clear = st.form_submit_button(t('clear_form'), type="secondary", use_container_width=True)
if submitted:
# Basic validation
if quantity <= 0:
st.error("Quantity must be greater than zero.")
elif not facility.strip():
st.warning("Facility/Location is recommended for enterprise tracking.")
else:
try:
# Include cost in the entry if provided
cost_value = cost if 'cost' in locals() and cost > 0 else 0.0
currency_value = currency if 'currency' in locals() and cost > 0 else ""
add_emission_entry(
date, business_unit, project, scope, category, activity, country, facility,
responsible_person, quantity, unit, emission_factor, data_quality, verification_status, notes
)
st.success(t('entry_added'))
# Redirect to Dashboard after successful entry
st.session_state.active_page = "Dashboard"
st.rerun()
except Exception as e:
st.error(f"{t('entry_failed')} {str(e)}")
# Show existing data table
if len(st.session_state.emissions_data) > 0:
st.markdown("Existing Emissions Data
", unsafe_allow_html=True)
# Create a copy of the dataframe with an action column
display_df = st.session_state.emissions_data.copy()
# Add a column for the delete action
col1, col2 = st.columns([3, 1])
with col1:
# Display the dataframe
st.dataframe(
display_df,
column_config={
"date": st.column_config.DateColumn("Date"),
"business_unit": st.column_config.TextColumn("Business Unit"),
"project": st.column_config.TextColumn("Project"),
"scope": st.column_config.TextColumn("Scope"),
"category": st.column_config.TextColumn("Category"),
"activity": st.column_config.TextColumn("Activity"),
"country": st.column_config.TextColumn("Country"),
"facility": st.column_config.TextColumn("Facility"),
"responsible_person": st.column_config.TextColumn("Responsible Person"),
"quantity": st.column_config.NumberColumn("Quantity", format="%.2f"),
"unit": st.column_config.TextColumn("Unit"),
"emission_factor": st.column_config.NumberColumn("Emission Factor", format="%.4f"),
"emissions_kgCO2e": st.column_config.NumberColumn("Emissions (kgCO2e)", format="%.2f"),
"data_quality": st.column_config.TextColumn("Data Quality"),
"verification_status": st.column_config.TextColumn("Verification"),
"notes": st.column_config.TextColumn("Notes"),
},
use_container_width=True,
hide_index=False
)
with col2:
# Add delete functionality
st.markdown("### Delete Entry")
entry_to_delete = st.number_input("Select entry number to delete", min_value=0,
max_value=len(display_df)-1 if len(display_df) > 0 else 0,
step=1,
help="Enter the index number of the entry you want to delete")
if st.button("🗑️ Delete Selected Entry", type="primary"):
if delete_emission_entry(entry_to_delete):
st.success(f"Entry {entry_to_delete} deleted successfully!")
st.rerun()
else:
st.error(f"Failed to delete entry {entry_to_delete}")
with tabs[1]:
st.markdown("Upload CSV File
", unsafe_allow_html=True)
uploaded_file = st.file_uploader(t('upload_csv'), type='csv')
if uploaded_file is not None:
if process_csv(uploaded_file):
st.success(t('csv_uploaded'))
# Redirect to Dashboard after successful upload
st.session_state.active_page = "Dashboard"
st.rerun()
else:
st.error("Failed to process CSV file. Please check the format.")
# Sample CSV download with enterprise-grade fields
sample_data = {
'date': ['2025-01-15', '2025-01-20'],
'business_unit': ['Corporate', 'Logistics'],
'project': ['Carbon Reduction Initiative', 'Operational'],
'scope': ['Scope 2', 'Scope 1'],
'category': ['Electricity', 'Mobile Combustion'],
'activity': ['Office Electricity', 'Company Vehicle'],
'country': ['Vietnam', 'United States'],
'facility': ['Hanoi HQ', 'Soc Son Distribution Center'],
'responsible_person': ['Nguyen Thuy Trang', 'Tran Quoc Hung'],
'quantity': [1000, 50],
'unit': ['kWh', 'liter'],
'emission_factor': [0.82, 2.31495],
'data_quality': ['High', 'Medium'],
'verification_status': ['Internally Verified', 'Unverified'],
'notes': ['Monthly electricity bill', 'Fleet vehicle fuel consumption']
}
sample_df = pd.DataFrame(sample_data)
csv = sample_df.to_csv(index=False).encode('utf-8')
st.download_button(
label="Download Sample CSV",
data=csv,
file_name="sample_emissions.csv",
mime="text/csv",
)
# Reports page removed - focusing on AI features only
elif st.session_state.active_page == "Settings":
st.markdown(f" {t('settings')}
", unsafe_allow_html=True)
st.markdown("Company Information
", unsafe_allow_html=True)
# Company info form
with st.form("company_info_form"):
col1, col2 = st.columns(2)
with col1:
company_name = st.text_input("Company Name")
industry = st.text_input("Industry")
location = st.text_input("Location")
with col2:
contact_person = st.text_input("Contact Person")
email = st.text_input("Email")
phone = st.text_input("Phone")
st.markdown("Export Markets
", unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
with col1:
eu_market = st.checkbox("European Union")
with col2:
japan_market = st.checkbox("Japan")
with col3:
unitedstates_market = st.checkbox("United States")
submitted = st.form_submit_button("Save Settings")
if submitted:
st.success("Settings saved successfully!")
elif st.session_state.active_page == "AI Insights":
st.markdown(f"🤖 AI Insights
", unsafe_allow_html=True)
# Import AI agents
from ai_agents import CarbonFootprintAgents
# Initialize AI agents
if 'ai_agents' not in st.session_state:
st.session_state.ai_agents = CarbonFootprintAgents()
# Create tabs for different AI insights
ai_tabs = st.tabs(["Data Assistant", "Report Summary", "Offset Advisor", "Regulation Radar", "Emission Optimizer"])
with ai_tabs[0]:
st.markdown("Data Entry Assistant
", unsafe_allow_html=True)
st.markdown("Get help with classifying emissions and mapping them to the correct scope.")
data_description = st.text_area("Describe your emission activity",
placeholder="Example: We use diesel generators for backup power at our office in Hai Phong. How should I categorize this?")
if st.button("Get Assistance", key="data_assistant_btn"):
if data_description:
with st.spinner("AI assistant is analyzing your request..."):
try:
result = st.session_state.ai_agents.run_data_entry_crew(data_description)
# Handle CrewOutput object by converting it to string
result_str = str(result)
st.markdown(f"{result_str}
", unsafe_allow_html=True)
except Exception as e:
st.error(f"Error: {str(e)}. Please check your API key and try again.")
else:
st.warning("Please describe your emission activity first.")
with ai_tabs[1]:
st.markdown("Report Summary Generator
", unsafe_allow_html=True)
st.markdown("Generate a human-readable summary of your emissions data.")
if len(st.session_state.emissions_data) == 0:
st.warning("No emissions data available. Please add data first.")
else:
if st.button("Generate Summary", key="report_summary_btn"):
with st.spinner("Generating report summary..."):
try:
# Convert DataFrame to string representation for the AI
emissions_str = st.session_state.emissions_data.to_string()
result = st.session_state.ai_agents.run_report_summary_crew(emissions_str)
# Handle CrewOutput object by converting it to string
result_str = str(result)
st.markdown(f"{result_str}
", unsafe_allow_html=True)
except Exception as e:
st.error(f"Error: {str(e)}. Please check your API key and try again.")
with ai_tabs[2]:
st.markdown("Carbon Offset Advisor
", unsafe_allow_html=True)
st.markdown("Get recommendations for verified carbon offset options based on your profile.")
col1, col2 = st.columns(2)
with col1:
location = st.text_input("Location", placeholder="e.g., Bac Ninh, Vietnam")
industry = st.selectbox("Industry", ["Manufacturing", "Technology", "Agriculture", "Transportation", "Energy", "Services", "Other"])
if len(st.session_state.emissions_data) == 0:
st.warning("No emissions data available. Please add data first.")
else:
total_emissions = st.session_state.emissions_data['emissions_kgCO2e'].sum()
st.markdown(f"Total emissions to offset: {total_emissions:.2f} kgCO2e
", unsafe_allow_html=True)
if st.button("Get Offset Recommendations", key="offset_advisor_btn"):
if location:
with st.spinner("Finding offset options..."):
try:
result = st.session_state.ai_agents.run_offset_advice_crew(total_emissions, location, industry)
# Handle CrewOutput object by converting it to string
result_str = str(result)
st.markdown(f"{result_str}
", unsafe_allow_html=True)
except Exception as e:
st.error(f"Error: {str(e)}. Please check your API key and try again.")
else:
st.warning("Please enter your location.")
with ai_tabs[3]:
st.markdown("Regulation Radar
", unsafe_allow_html=True)
st.markdown("Get insights on current and upcoming carbon regulations relevant to your business.")
col1, col2 = st.columns(2)
with col1:
location = st.text_input("Company Location", placeholder="e.g., Hanoi, Vietnam", key="reg_location")
industry = st.selectbox("Industry Sector", ["Manufacturing", "Technology", "Agriculture", "Transportation", "Energy", "Services", "Other"], key="reg_industry")
with col2:
export_markets = st.multiselect("Export Markets", ["European Union", "Japan", "United States", "China", "Middle East", "India", "Other"])
if st.button("Check Regulations", key="regulation_radar_btn"):
if location and len(export_markets) > 0:
with st.spinner("Analyzing regulatory requirements..."):
try:
result = st.session_state.ai_agents.run_regulation_check_crew(location, industry, ", ".join(export_markets))
# Handle CrewOutput object by converting it to string
result_str = str(result)
st.markdown(f"{result_str}
", unsafe_allow_html=True)
except Exception as e:
st.error(f"Error: {str(e)}. Please check your API key and try again.")
else:
st.warning("Please enter your location and select at least one export market.")
with ai_tabs[4]:
st.markdown("Emission Optimizer
", unsafe_allow_html=True)
st.markdown("Get AI-powered recommendations to reduce your carbon footprint.")
if len(st.session_state.emissions_data) == 0:
st.warning("No emissions data available. Please add data first.")
else:
if st.button("Generate Optimization Recommendations", key="emission_optimizer_btn"):
with st.spinner("Analyzing your emissions data..."):
try:
# Convert DataFrame to string representation for the AI
emissions_str = st.session_state.emissions_data.to_string()
result = st.session_state.ai_agents.run_optimization_crew(emissions_str)
# Handle CrewOutput object by converting it to string
result_str = str(result)
st.markdown(f"{result_str}
", unsafe_allow_html=True)
except Exception as e:
st.error(f"Error: {str(e)}. Please check your API key and try again.")
# About page removed - focusing on AI features only