import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
from io import StringIO, BytesIO
import base64
import json
from dataclasses import dataclass, asdict
from typing import Optional, Dict, List
import hashlib
# Configuration
@dataclass
class Config:
PAGE_TITLE = "ESG Compliance Intelligence"
PAGE_ICON = "🌱"
REQUIRED_COLUMNS = ['timestamp', 'ph_level', 'wastewater_lmin', 'co2_emission_kg', 'energy_kwh', 'location', 'status']
PH_RANGE = (6.0, 8.5)
WASTEWATER_LIMIT = 60.0
COMPLIANCE_THRESHOLD = 80
# Data Models
@dataclass
class ComplianceReport:
report_id: str
timestamp: str
location: str
ph_level: float
wastewater_volume: float
co2_total: float
energy_consumption: float
compliance_status: str
generated_by: str = "ESG Compliance Engine"
@classmethod
def from_dataframe_row(cls, row):
return cls(
report_id=f"PSA-ENV-{datetime.now().strftime('%Y%m%d%H%M')}",
timestamp=datetime.now().isoformat(),
location=row['location'],
ph_level=float(row['ph_level']),
wastewater_volume=float(row['wastewater_lmin']),
co2_total=float(row['co2_emission_kg']),
energy_consumption=float(row['energy_kwh']),
compliance_status=row['status'].upper()
)
# Data Validation
class DataValidator:
@staticmethod
def validate_csv_structure(df: pd.DataFrame) -> None:
missing_cols = [col for col in Config.REQUIRED_COLUMNS if col not in df.columns]
if missing_cols:
raise ValueError(f"Missing required columns: {', '.join(missing_cols)}")
@staticmethod
def validate_data_types(df: pd.DataFrame) -> pd.DataFrame:
try:
df['timestamp'] = pd.to_datetime(df['timestamp'])
df['ph_level'] = pd.to_numeric(df['ph_level'])
df['wastewater_lmin'] = pd.to_numeric(df['wastewater_lmin'])
df['co2_emission_kg'] = pd.to_numeric(df['co2_emission_kg'])
df['energy_kwh'] = pd.to_numeric(df['energy_kwh'])
return df
except Exception as e:
raise ValueError(f"Data type conversion failed: {str(e)}")
@staticmethod
def validate_ranges(df: pd.DataFrame) -> None:
if not df['ph_level'].between(0, 14).all():
raise ValueError("pH values must be between 0 and 14")
if (df[['wastewater_lmin', 'co2_emission_kg', 'energy_kwh']] < 0).any().any():
raise ValueError("Environmental measurements cannot be negative")
# Data Management
class DataManager:
@staticmethod
def get_sample_data() -> str:
return """timestamp,ph_level,wastewater_lmin,co2_emission_kg,energy_kwh,chemical_usage_kg,location,status
2024-08-27 14:30,7.4,48.5,14.2,92,2.3,Statoil Platform Alpha,compliant
2024-08-27 14:25,7.2,45.1,12.8,88,2.1,Statoil Platform Alpha,compliant
2024-08-27 14:20,7.6,52.3,15.7,95,2.5,Statoil Platform Alpha,warning
2024-08-27 14:15,7.8,55.2,16.1,98,2.8,Statoil Platform Alpha,violation
2024-08-27 14:10,7.3,47.8,13.5,89,2.2,Statoil Platform Alpha,compliant"""
@staticmethod
@st.cache_data
def load_and_validate_data(uploaded_file) -> pd.DataFrame:
try:
if uploaded_file:
df = pd.read_csv(uploaded_file)
else:
df = pd.read_csv(StringIO(DataManager.get_sample_data()))
DataValidator.validate_csv_structure(df)
df = DataValidator.validate_data_types(df)
DataValidator.validate_ranges(df)
return df
except Exception as e:
st.error(f"Data loading error: {str(e)}")
return pd.DataFrame()
@staticmethod
def get_carbon_footprint_data() -> pd.DataFrame:
return pd.DataFrame({
'source': ['Fuel Consumption', 'Electricity', 'Chemicals', 'Transport'],
'co2_kg': [145.2, 67.8, 89.1, 19.6],
'percentage': [45, 21, 28, 6]
})
# UI Components
class UIComponents:
@staticmethod
def apply_styling():
st.markdown("""
""", unsafe_allow_html=True)
@staticmethod
def render_header():
st.markdown("""
🌱 ESG Compliance Intelligence
Norwegian Petroleum Safety Authority Real-time Monitoring
""", unsafe_allow_html=True)
@staticmethod
def render_alert(status: str, location: str):
alerts = {
'violation': ('alert-danger', '🚨', 'CRITICAL ALERT', 'Environmental violation detected'),
'warning': ('alert-warning', '⚠️', 'WARNING', 'Parameters approaching limits'),
'compliant': ('alert-success', '✅', 'COMPLIANT', 'All systems within regulations')
}
css_class, icon, title, message = alerts.get(status, alerts['compliant'])
st.markdown(f"""
{icon} {title}: {message} at {location}
""", unsafe_allow_html=True)
# Visualization
class ChartGenerator:
@staticmethod
def create_environmental_trend(df: pd.DataFrame):
fig = px.line(df, x='timestamp', y=['ph_level', 'wastewater_lmin'],
title="Environmental Parameters Over Time")
fig.update_layout(height=400, showlegend=True)
return fig
@staticmethod
def create_carbon_breakdown(carbon_df: pd.DataFrame):
fig = px.bar(carbon_df, x='source', y='co2_kg',
title="CO₂ Emissions by Source",
color='co2_kg', color_continuous_scale='Greens')
fig.update_layout(height=400)
return fig
@staticmethod
def create_compliance_trend(df: pd.DataFrame):
compliance_map = {'compliant': 100, 'warning': 70, 'violation': 30}
df['compliance_score'] = df['status'].map(compliance_map)
fig = px.area(df, x='timestamp', y='compliance_score',
title="Compliance Score Trend",
color_discrete_sequence=['#059669'])
fig.add_hline(y=Config.COMPLIANCE_THRESHOLD, line_dash="dash",
line_color="red", annotation_text="PSA Threshold")
fig.update_layout(height=300)
return fig
# Report Generation
class ReportGenerator:
@staticmethod
def generate_html_report(report: ComplianceReport) -> str:
return f"""
PSA Environmental Compliance Report
Summary
Generated: {report.timestamp}
Location: {report.location}
Status: {report.compliance_status}
Environmental Measurements
| Parameter | Value | Unit | Status |
| pH Level | {report.ph_level:.1f} | pH | ✓ Monitored |
| Wastewater | {report.wastewater_volume:.1f} | L/min | ✓ Monitored |
| CO₂ Emissions | {report.co2_total:.1f} | kg | ✓ Tracked |
| Energy | {report.energy_consumption:.1f} | kWh | ✓ Tracked |
Certification
Report generated by {report.generated_by}
Hash: {hashlib.md5(str(report.report_id).encode()).hexdigest()[:8]}
"""
@staticmethod
def create_excel_export(df: pd.DataFrame, carbon_df: pd.DataFrame) -> bytes:
output = BytesIO()
with pd.ExcelWriter(output, engine='openpyxl') as writer:
df.to_excel(writer, sheet_name='Environmental_Data', index=False)
carbon_df.to_excel(writer, sheet_name='Carbon_Footprint', index=False)
summary = pd.DataFrame({
'Metric': ['Latest pH', 'Latest CO₂', 'Latest Energy', 'Status'],
'Value': [df.iloc[-1]['ph_level'], df.iloc[-1]['co2_emission_kg'],
df.iloc[-1]['energy_kwh'], df.iloc[-1]['status'].upper()]
})
summary.to_excel(writer, sheet_name='Summary', index=False)
return output.getvalue()
# Main Application
class ESGDashboard:
def __init__(self):
st.set_page_config(
page_title=Config.PAGE_TITLE,
page_icon=Config.PAGE_ICON,
layout="wide"
)
UIComponents.apply_styling()
def render_sidebar(self) -> Optional[pd.DataFrame]:
with st.sidebar:
st.header("⚙️ Controls")
uploaded_file = st.file_uploader("Upload CSV Data", type=['csv'])
if st.button("🔄 Refresh Data"):
st.cache_data.clear()
st.rerun()
st.markdown("---")
st.markdown("**Required CSV Format:**")
st.code("timestamp,ph_level,wastewater_lmin,co2_emission_kg,energy_kwh,location,status")
return uploaded_file
def render_metrics(self, df: pd.DataFrame):
latest = df.iloc[-1]
col1, col2, col3, col4 = st.columns(4)
status_icons = {"compliant": "✅", "warning": "⚠️", "violation": "🚨"}
with col1:
st.metric("Status", f"{status_icons[latest['status']]} {latest['status'].upper()}")
with col2:
ph_delta = latest['ph_level'] - 7.0
st.metric("pH Level", f"{latest['ph_level']:.1f}", f"{ph_delta:+.1f}")
with col3:
st.metric("CO₂ Emissions", f"{latest['co2_emission_kg']:.1f} kg", "-2.3 kg")
with col4:
st.metric("Energy Usage", f"{latest['energy_kwh']:.0f} kWh", "+5.2 kWh")
def render_charts(self, df: pd.DataFrame, carbon_df: pd.DataFrame):
col1, col2 = st.columns(2)
with col1:
st.markdown('', unsafe_allow_html=True)
st.subheader("💧 Environmental Monitoring")
fig_env = ChartGenerator.create_environmental_trend(df)
st.plotly_chart(fig_env, use_container_width=True)
st.markdown('
', unsafe_allow_html=True)
with col2:
st.markdown('', unsafe_allow_html=True)
st.subheader("📈 Carbon Footprint")
fig_carbon = ChartGenerator.create_carbon_breakdown(carbon_df)
st.plotly_chart(fig_carbon, use_container_width=True)
st.markdown('
', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.subheader("📊 Compliance Trend")
fig_compliance = ChartGenerator.create_compliance_trend(df)
st.plotly_chart(fig_compliance, use_container_width=True)
st.markdown('
', unsafe_allow_html=True)
def render_export_section(self, df: pd.DataFrame, carbon_df: pd.DataFrame):
st.markdown('', unsafe_allow_html=True)
st.subheader("⬇️ Export Reports")
col1, col2, col3 = st.columns(3)
with col1:
if st.button("📄 Generate PDF Report", type="primary"):
report = ComplianceReport.from_dataframe_row(df.iloc[-1])
html_content = ReportGenerator.generate_html_report(report)
st.download_button(
label="⬇️ Download Report",
data=html_content,
file_name=f"PSA_Report_{datetime.now().strftime('%Y%m%d_%H%M')}.html",
mime="text/html"
)
st.success("✅ Report ready for download!")
with col2:
if st.button("📊 Export Excel"):
excel_data = ReportGenerator.create_excel_export(df, carbon_df)
st.download_button(
label="⬇️ Download Excel",
data=excel_data,
file_name=f"ESG_Data_{datetime.now().strftime('%Y%m%d_%H%M')}.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
st.success("✅ Excel file ready!")
with col3:
if st.button("📤 Submit to PSA"):
submission_id = f"PSA-SUB-{datetime.now().strftime('%Y%m%d%H%M')}"
st.success(f"✅ Submitted!\nID: {submission_id}")
st.markdown('
', unsafe_allow_html=True)
def run(self):
UIComponents.render_header()
uploaded_file = self.render_sidebar()
# Load and validate data
df = DataManager.load_and_validate_data(uploaded_file)
if df.empty:
st.stop()
carbon_df = DataManager.get_carbon_footprint_data()
# Render alert
latest_status = df.iloc[-1]['status']
latest_location = df.iloc[-1]['location']
UIComponents.render_alert(latest_status, latest_location)
# Render dashboard components
self.render_metrics(df)
self.render_charts(df, carbon_df)
self.render_export_section(df, carbon_df)
# Application Entry Point
def main():
dashboard = ESGDashboard()
dashboard.run()
if __name__ == "__main__":
main()