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

Norwegian Petroleum Safety Authority

Environmental Compliance Report

Report ID: {report.report_id}

Summary

Generated: {report.timestamp}

Location: {report.location}

Status: {report.compliance_status}

Environmental Measurements

ParameterValueUnitStatus
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()