gaialive commited on
Commit
22836fe
·
verified ·
1 Parent(s): 9e82bd8

Upload 11 files

Browse files
Files changed (11) hide show
  1. LICENSE +21 -0
  2. README.md +231 -20
  3. ai_agents.py +233 -0
  4. app.py +1298 -0
  5. config.py +64 -0
  6. data_handler.py +382 -0
  7. emission_factors.py +172 -0
  8. pyproject.toml +19 -0
  9. report_generator.py +314 -0
  10. requirements.txt +14 -3
  11. uv.lock +0 -0
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 AI Anytime
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,20 +1,231 @@
1
- ---
2
- title: KiemkeKhinhakinh
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
- pinned: false
11
- short_description: Streamlit template space
12
- license: mit
13
- ---
14
-
15
- # Welcome to Streamlit!
16
-
17
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
18
-
19
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
20
- forums](https://discuss.streamlit.io).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # YourCarbonFootprint - AI Agents powered Carbon Accounting Tool
2
+
3
+ ![Carbon Footprint](https://img.shields.io/badge/Carbon-Footprint-green)
4
+ ![Streamlit](https://img.shields.io/badge/Streamlit-FF4B4B?logo=streamlit&logoColor=white)
5
+ ![CrewAI](https://img.shields.io/badge/CrewAI-AI%20Agents-blue)
6
+ ![Groq](https://img.shields.io/badge/Groq-LLM-purple)
7
+
8
+ A lightweight, multilingual carbon accounting and reporting tool for SMEs in Asia, with AI-powered insights and data entry.
9
+
10
+ ## 📋 Table of Contents
11
+
12
+ - [Features](#-features)
13
+ - [Architecture](#-architecture)
14
+ - [Installation](#-installation)
15
+ - [Configuration](#-configuration)
16
+ - [Usage](#-usage)
17
+ - [AI Agents](#-ai-agents)
18
+ - [Data Structure](#-data-structure)
19
+ - [Contributing](#-contributing)
20
+ - [License](#-license)
21
+
22
+ ## ✨ Features
23
+
24
+ ### Core Features
25
+ - **Enterprise-Grade Data Entry**: Comprehensive form with business unit tracking, project categorization, facility details, and data quality indicators
26
+ - **Dashboard Visualization**: Interactive charts and graphs for emissions data analysis
27
+ - **AI-Powered Insights**: Specialized AI agents for various carbon accounting tasks
28
+ - **Data Management**: CSV import/export, robust error handling, and automatic backups
29
+ - **Multilingual Support**: Available in multiple languages
30
+
31
+ ### AI Agent Features
32
+ | Agent | Role |
33
+ |-------|------|
34
+ | Data Entry Assistant | Helps users classify emissions, map to scopes, and validate data entries |
35
+ | Report Summary Generator | Converts emission data into human-readable summaries |
36
+ | Carbon Offset Advisor | Suggests verified offset options based on user profile and location |
37
+ | Regulation Radar | Notifies users of upcoming compliance needs |
38
+ | Emission Optimizer | Uses historical data to suggest reductions and savings |
39
+
40
+ ## 🏗 Architecture
41
+
42
+ ```
43
+ ┌─────────────────────────────────────────────────────────────────────────┐
44
+ │ YourCarbonFootprint App │
45
+ └───────────────────────────────────┬─────────────────────────────────────┘
46
+
47
+ ┌─────────────────────────────────────┐
48
+ │ │
49
+ ┌───────────────▼───────────────┐ ┌─────────────▼─────────────┐
50
+ │ Frontend (Streamlit) │ │ Backend Services │
51
+ │ │ │ │
52
+ │ ┌─────────────────────────┐ │ │ ┌─────────────────────┐ │
53
+ │ │ Navigation System │ │ │ │ Data Management │ │
54
+ │ │ - Dashboard │ │ │ │ - JSON Storage │ │
55
+ │ │ - Data Entry │ │ │ │ - CSV Import │ │
56
+ │ │ - AI Insights │ │ │ │ - Backup System │ │
57
+ │ │ - Settings │ │ │ └─────────────────────┘ │
58
+ │ └─────────────────────────┘ │ │ │
59
+ │ │ │ ┌─────────────────────┐ │
60
+ │ ┌─────────────────────────┐ │ │ │ AI Agent System │ │
61
+ │ │ Data Entry Module │ │ │ │ - CrewAI Framework │ │
62
+ │ │ - Enterprise Form │◄─┼───────┼──┤ - Groq LLM │ │
63
+ │ │ - Validation │ │ │ │ - Specialized │ │
64
+ │ │ - AI Suggestions │ │ │ │ Agent Roles │ │
65
+ │ └─────────────────────────┘ │ │ └─────────────────────┘ │
66
+ │ │ │ │
67
+ │ ┌─────────────────────────┐ │ │ ┌─────────────────────┐ │
68
+ │ │ Dashboard Module │ │ │ │ Analytics Engine │ │
69
+ │ │ - Emissions Overview │◄─┼───────┼──┤ - Data Processing │ │
70
+ │ │ - Charts & Graphs │ │ │ │ - Calculations │ │
71
+ │ │ - Filtering │ │ │ │ - Visualization │ │
72
+ │ └─────────────────────────┘ │ │ └─────────────────────┘ │
73
+ └───────────────────────────────┘ └───────────────────────────┘
74
+ ```
75
+
76
+ ## 🚀 Installation
77
+
78
+ ### Prerequisites
79
+ - Python 3.9+
80
+ - Groq API key (for AI features)
81
+
82
+ ### Setup
83
+
84
+ 1. Clone the repository:
85
+ ```bash
86
+ git clone https://github.com/AIAnytime/Your-Carbon-Footprint/tree/main.git
87
+ cd Your-Carbon-Footprint/
88
+ ```
89
+
90
+ 2. Create and activate a virtual environment:
91
+ ```bash
92
+ python -m venv .venv
93
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
94
+ ```
95
+
96
+ 3. Install dependencies:
97
+ ```bash
98
+ pip install -r requirements.txt
99
+ ```
100
+
101
+ 4. Create a `.env` file in the project root with your Groq API key:
102
+ ```
103
+ GROQ_API_KEY=your_groq_api_key_here
104
+ ```
105
+
106
+ ## ⚙️ Configuration
107
+
108
+ ### Environment Variables
109
+ - `GROQ_API_KEY`: Your Groq API key for AI agent functionality
110
+
111
+ ### Data Storage
112
+ - Emissions data is stored in `data/emissions.json`
113
+ - Company settings are stored in `data/settings.json`
114
+ - Automatic backups are created for corrupted files with timestamped filenames
115
+
116
+ ## 📊 Usage
117
+
118
+ ### Running the Application
119
+
120
+ ```bash
121
+ streamlit run app.py
122
+ ```
123
+
124
+ ### Navigation
125
+ - **Dashboard**: View emissions data visualizations and analytics
126
+ - **Data Entry**: Add new emission entries with enterprise-grade form
127
+ - **AI Insights**: Access specialized AI agents for carbon accounting assistance
128
+ - **Settings**: Configure company information and preferences
129
+
130
+ ### Data Entry Form
131
+ The enhanced enterprise-grade data entry form includes:
132
+ - Business unit and project tracking
133
+ - Facility location and responsible person fields
134
+ - Data quality indicators and verification status
135
+ - AI-powered emission factor suggestions
136
+ - Financial impact tracking (optional)
137
+
138
+ ### CSV Import/Export
139
+ - Upload CSV files with emissions data
140
+ - Download sample CSV template
141
+ - Export emissions data as CSV or PDF reports
142
+
143
+ ## 🤖 AI Agents
144
+
145
+ YourCarbonFootprint integrates five specialized AI agents using CrewAI and Groq LLM:
146
+
147
+ 1. **Data Entry Assistant**: Helps classify emissions and validate data entries
148
+ 2. **Report Summary Generator**: Creates human-readable summaries from emissions data
149
+ 3. **Carbon Offset Advisor**: Recommends verified carbon offset options
150
+ 4. **Regulation Radar**: Provides updates on compliance requirements
151
+ 5. **Emission Optimizer**: Suggests ways to reduce emissions based on historical data
152
+
153
+ ### AI Agent Implementation
154
+
155
+ ```python
156
+ from crewai import Agent, Task, Crew, Process
157
+ from crewai.llms import LLM
158
+
159
+ # Initialize LLM
160
+ llm = LLM(provider="groq", model="llama3-70b-8192")
161
+
162
+ # Create an agent
163
+ data_entry_assistant = Agent(
164
+ llm=llm,
165
+ role="Data Entry Assistant",
166
+ goal="Help users classify emissions, map to scopes, and validate data entries",
167
+ backstory="You are an expert in carbon accounting who helps users correctly categorize "
168
+ "their emissions data and ensure it's properly mapped to the right scope.",
169
+ allow_delegation=False,
170
+ verbose=False
171
+ )
172
+
173
+ # Create a task
174
+ data_entry_task = Task(
175
+ description="Analyze the user's emission data and provide guidance on classification",
176
+ agent=data_entry_assistant
177
+ )
178
+
179
+ # Create and run a crew
180
+ crew = Crew(
181
+ agents=[data_entry_assistant],
182
+ tasks=[data_entry_task],
183
+ verbose=False,
184
+ process=Process.sequential
185
+ )
186
+
187
+ result = crew.kickoff(inputs={"user_query": "How should I categorize my company's electricity usage?"})
188
+ ```
189
+
190
+ ## 📁 Data Structure
191
+
192
+ ### Emissions Data Format
193
+
194
+ ```json
195
+ {
196
+ "date": "2025-01-15",
197
+ "business_unit": "Corporate",
198
+ "project": "Carbon Reduction Initiative",
199
+ "scope": "Scope 2",
200
+ "category": "Electricity",
201
+ "activity": "Office Electricity",
202
+ "country": "India",
203
+ "facility": "Mumbai HQ",
204
+ "responsible_person": "Rahul Sharma",
205
+ "quantity": 1000.0,
206
+ "unit": "kWh",
207
+ "emission_factor": 0.82,
208
+ "emissions_kgCO2e": 820.0,
209
+ "data_quality": "High",
210
+ "verification_status": "Internally Verified",
211
+ "notes": "Monthly electricity bill"
212
+ }
213
+ ```
214
+
215
+ ## 🤝 Contributing
216
+
217
+ Contributions are welcome! Please feel free to submit a Pull Request.
218
+
219
+ 1. Fork the repository
220
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
221
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
222
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
223
+ 5. Open a Pull Request
224
+
225
+ ## 📄 License
226
+
227
+ This project is licensed under the MIT License - see the LICENSE file for details.
228
+
229
+ ---
230
+
231
+ Built by AI Anytime with ❤️ for a sustainable future
ai_agents.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AI Agents for CarbonFootprint by GXS application.
3
+ Uses CrewAI to create agents for various tasks.
4
+ """
5
+
6
+ import os
7
+ from dotenv import load_dotenv
8
+ from crewai import Agent, Task, Crew, LLM
9
+ __import__('pysqlite3')
10
+ import sys
11
+ sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
12
+
13
+ # Load environment variables
14
+ load_dotenv()
15
+
16
+ # Get Groq API key
17
+ os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY")
18
+
19
+ # Initialize LLM
20
+ def get_llm():
21
+ """Initialize and return the Groq LLM."""
22
+ return LLM(
23
+ model="groq/llama-3.3-70b-versatile",
24
+ temperature=0.7
25
+ )
26
+
27
+ # Create AI agents
28
+ class CarbonFootprintAgents:
29
+ def __init__(self):
30
+ """Initialize the CarbonFootprintAgents class."""
31
+ self.llm = get_llm()
32
+ self._create_agents()
33
+
34
+ def _create_agents(self):
35
+ """Create all the agents."""
36
+ # Data Entry Assistant
37
+ self.data_entry_assistant = Agent(
38
+ llm=self.llm,
39
+ role="Data Entry Assistant",
40
+ goal="Help users classify emissions, map to scopes, and validate data entries",
41
+ backstory="You are an expert in carbon accounting who helps users correctly categorize "
42
+ "their emissions data and ensure it's properly mapped to the right scope. "
43
+ "You understand the nuances of Scope 1, 2, and 3 emissions and can guide "
44
+ "users to make accurate entries.",
45
+ allow_delegation=False,
46
+ verbose=False
47
+ )
48
+
49
+ # Report Summary Generator
50
+ self.report_generator = Agent(
51
+ llm=self.llm,
52
+ role="Report Summary Generator",
53
+ goal="Convert emission data into human-readable summaries",
54
+ backstory="You are a skilled analyst who can take raw emissions data and transform it "
55
+ "into clear, concise summaries that highlight key trends, areas of concern, "
56
+ "and opportunities for improvement. You make complex data accessible to "
57
+ "non-technical stakeholders.",
58
+ allow_delegation=False,
59
+ verbose=False
60
+ )
61
+
62
+ # Carbon Offset Advisor
63
+ self.offset_advisor = Agent(
64
+ llm=self.llm,
65
+ role="Carbon Offset Advisor",
66
+ goal="Suggest verified offset options based on user profile and location",
67
+ backstory="You are a sustainability expert who understands the carbon offset market "
68
+ "and can recommend high-quality, verified offset projects that align with "
69
+ "the user's industry, values, and location. You help users navigate the "
70
+ "complex world of carbon credits and offsets.",
71
+ allow_delegation=False,
72
+ verbose=False
73
+ )
74
+
75
+ # Regulation Radar
76
+ self.regulation_radar = Agent(
77
+ llm=self.llm,
78
+ role="Regulation Radar",
79
+ goal="Notify users of upcoming compliance requirements",
80
+ backstory="You are a regulatory expert who tracks carbon-related regulations across "
81
+ "different regions, with a focus on EU CBAM, Japan GX League, and Indonesia "
82
+ "ETS/ETP. You help users understand what compliance requirements apply to "
83
+ "them and how to prepare for upcoming changes.",
84
+ allow_delegation=False,
85
+ verbose=False
86
+ )
87
+
88
+ # Emission Optimizer
89
+ self.emission_optimizer = Agent(
90
+ llm=self.llm,
91
+ role="Emission Optimizer",
92
+ goal="Use historical data to suggest reductions and savings",
93
+ backstory="You are a carbon reduction specialist who analyzes emissions data to "
94
+ "identify patterns and opportunities for reduction. You provide practical, "
95
+ "actionable recommendations that can help organizations reduce their "
96
+ "carbon footprint while also saving costs.",
97
+ allow_delegation=False,
98
+ verbose=False
99
+ )
100
+
101
+ def create_data_entry_task(self, data_description):
102
+ """Create a task for the Data Entry Assistant."""
103
+ return Task(
104
+ description=(
105
+ f"Analyze the following data and help classify it into the appropriate "
106
+ f"emission scope and category: {data_description}\n"
107
+ f"1. Determine if this is Scope 1, 2, or 3\n"
108
+ f"2. Suggest the most appropriate category\n"
109
+ f"3. Recommend an appropriate emission factor if possible\n"
110
+ f"4. Validate the data for completeness and accuracy"
111
+ ),
112
+ expected_output="A detailed classification of the emissions data with scope, "
113
+ "category, and recommended emission factor.",
114
+ agent=self.data_entry_assistant
115
+ )
116
+
117
+ def create_report_summary_task(self, emissions_data):
118
+ """Create a task for the Report Summary Generator."""
119
+ return Task(
120
+ description=(
121
+ f"Generate a comprehensive summary of the following emissions data: "
122
+ f"{emissions_data}\n"
123
+ f"1. Highlight key trends and patterns\n"
124
+ f"2. Identify the largest sources of emissions\n"
125
+ f"3. Compare performance across different time periods if data is available\n"
126
+ f"4. Suggest areas for potential improvement"
127
+ ),
128
+ expected_output="A clear, concise summary of the emissions data with key insights "
129
+ "and recommendations.",
130
+ agent=self.report_generator
131
+ )
132
+
133
+ def create_offset_advice_task(self, emissions_total, location, industry):
134
+ """Create a task for the Carbon Offset Advisor."""
135
+ return Task(
136
+ description=(
137
+ f"Recommend carbon offset options for an organization with the following profile:\n"
138
+ f"- Total emissions: {emissions_total} kgCO2e\n"
139
+ f"- Location: {location}\n"
140
+ f"- Industry: {industry}\n"
141
+ f"1. Suggest 3-5 verified offset projects that would be suitable\n"
142
+ f"2. Provide estimated costs for offsetting their emissions\n"
143
+ f"3. Explain the benefits and limitations of each option\n"
144
+ f"4. Recommend a balanced portfolio approach if appropriate"
145
+ ),
146
+ expected_output="A list of recommended carbon offset options with costs, benefits, "
147
+ "and limitations for each.",
148
+ agent=self.offset_advisor
149
+ )
150
+
151
+ def create_regulation_check_task(self, location, industry, export_markets):
152
+ """Create a task for the Regulation Radar."""
153
+ return Task(
154
+ description=(
155
+ f"Analyze the regulatory requirements for an organization with the following profile:\n"
156
+ f"- Location: {location}\n"
157
+ f"- Industry: {industry}\n"
158
+ f"- Export markets: {export_markets}\n"
159
+ f"1. Identify current compliance requirements related to carbon emissions\n"
160
+ f"2. Highlight upcoming regulatory changes in the next 1-2 years\n"
161
+ f"3. Assess the potential impact of these regulations on the organization\n"
162
+ f"4. Recommend preparation steps to ensure compliance"
163
+ ),
164
+ expected_output="A comprehensive overview of current and upcoming regulatory "
165
+ "requirements with recommendations for compliance preparation.",
166
+ agent=self.regulation_radar
167
+ )
168
+
169
+ def create_optimization_task(self, emissions_data):
170
+ """Create a task for the Emission Optimizer."""
171
+ return Task(
172
+ description=(
173
+ f"Analyze the following emissions data and identify opportunities for reduction: "
174
+ f"{emissions_data}\n"
175
+ f"1. Identify the top 3-5 sources of emissions that could be reduced\n"
176
+ f"2. Suggest practical measures to reduce emissions in each area\n"
177
+ f"3. Estimate potential emission reductions and cost savings where possible\n"
178
+ f"4. Prioritize recommendations based on impact and feasibility"
179
+ ),
180
+ expected_output="A prioritized list of emission reduction opportunities with "
181
+ "estimated impacts and implementation guidance.",
182
+ agent=self.emission_optimizer
183
+ )
184
+
185
+ def run_data_entry_crew(self, data_description):
186
+ """Run a crew with the Data Entry Assistant."""
187
+ task = self.create_data_entry_task(data_description)
188
+ crew = Crew(
189
+ agents=[self.data_entry_assistant],
190
+ tasks=[task],
191
+ verbose=False
192
+ )
193
+ return crew.kickoff()
194
+
195
+ def run_report_summary_crew(self, emissions_data):
196
+ """Run a crew with the Report Summary Generator."""
197
+ task = self.create_report_summary_task(emissions_data)
198
+ crew = Crew(
199
+ agents=[self.report_generator],
200
+ tasks=[task],
201
+ verbose=False
202
+ )
203
+ return crew.kickoff()
204
+
205
+ def run_offset_advice_crew(self, emissions_total, location, industry):
206
+ """Run a crew with the Carbon Offset Advisor."""
207
+ task = self.create_offset_advice_task(emissions_total, location, industry)
208
+ crew = Crew(
209
+ agents=[self.offset_advisor],
210
+ tasks=[task],
211
+ verbose=False
212
+ )
213
+ return crew.kickoff()
214
+
215
+ def run_regulation_check_crew(self, location, industry, export_markets):
216
+ """Run a crew with the Regulation Radar."""
217
+ task = self.create_regulation_check_task(location, industry, export_markets)
218
+ crew = Crew(
219
+ agents=[self.regulation_radar],
220
+ tasks=[task],
221
+ verbose=False
222
+ )
223
+ return crew.kickoff()
224
+
225
+ def run_optimization_crew(self, emissions_data):
226
+ """Run a crew with the Emission Optimizer."""
227
+ task = self.create_optimization_task(emissions_data)
228
+ crew = Crew(
229
+ agents=[self.emission_optimizer],
230
+ tasks=[task],
231
+ verbose=False
232
+ )
233
+ return crew.kickoff()
app.py ADDED
@@ -0,0 +1,1298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import os
4
+ import json
5
+ import shutil
6
+ import time
7
+ from datetime import datetime
8
+ import plotly.express as px
9
+ import plotly.graph_objects as go
10
+ from dotenv import load_dotenv
11
+ import base64
12
+ from io import BytesIO
13
+
14
+
15
+ # Load environment variables
16
+ load_dotenv()
17
+
18
+ # Ensure data directory exists
19
+ os.makedirs('data', exist_ok=True)
20
+
21
+ # Set page config for wide layout
22
+ 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")
23
+
24
+ # Initialize session state variables if they don't exist
25
+ if 'language' not in st.session_state:
26
+ st.session_state.language = 'English'
27
+ if 'emissions_data' not in st.session_state:
28
+ # Load data if exists, otherwise create empty dataframe
29
+ if os.path.exists('data/emissions.json'):
30
+ try:
31
+ with open('data/emissions.json', 'r') as f:
32
+ data = f.read().strip()
33
+ if data: # Check if file is not empty
34
+ try:
35
+ st.session_state.emissions_data = pd.DataFrame(json.loads(data))
36
+ except json.JSONDecodeError:
37
+ # Create a backup of the corrupted file
38
+ backup_file = f'data/emissions_backup_{int(time.time())}.json'
39
+ shutil.copy('data/emissions.json', backup_file)
40
+ st.warning(f"Corrupted emissions data file found. A backup has been created at {backup_file}")
41
+ # Create empty dataframe
42
+ st.session_state.emissions_data = pd.DataFrame(columns=[
43
+ 'date', 'scope', 'category', 'activity', 'quantity',
44
+ 'unit', 'emission_factor', 'emissions_kgCO2e', 'notes'
45
+ ])
46
+ else:
47
+ # Empty file, create new DataFrame
48
+ st.session_state.emissions_data = pd.DataFrame(columns=[
49
+ 'date', 'scope', 'category', 'activity', 'quantity',
50
+ 'unit', 'emission_factor', 'emissions_kgCO2e', 'notes'
51
+ ])
52
+ except Exception as e:
53
+ st.error(f"Error loading emissions data: {str(e)}")
54
+ # Create empty dataframe if loading fails
55
+ st.session_state.emissions_data = pd.DataFrame(columns=[
56
+ 'date', 'scope', 'category', 'activity', 'quantity',
57
+ 'unit', 'emission_factor', 'emissions_kgCO2e', 'notes'
58
+ ])
59
+ # Make sure data directory exists
60
+ os.makedirs('data', exist_ok=True)
61
+ else:
62
+ st.session_state.emissions_data = pd.DataFrame(columns=[
63
+ 'date', 'scope', 'category', 'activity', 'quantity',
64
+ 'unit', 'emission_factor', 'emissions_kgCO2e', 'notes'
65
+ ])
66
+ # Make sure data directory exists
67
+ os.makedirs('data', exist_ok=True)
68
+ if 'theme' not in st.session_state:
69
+ st.session_state.theme = 'dark'
70
+ if 'active_page' not in st.session_state:
71
+ st.session_state.active_page = "AI Insights"
72
+
73
+ # Translation dictionary
74
+ translations = {
75
+ 'English': {
76
+ 'title': 'YourCarbonEmissions by GXS',
77
+ 'subtitle': 'Carbon Accounting & Reporting Tool for SMEs',
78
+ 'dashboard': 'Dashboard',
79
+ 'data_entry': 'Data Entry',
80
+ 'reports': 'Reports',
81
+ 'settings': 'Settings',
82
+ 'about': 'About',
83
+ 'scope1': 'Scope 1 (Direct Emissions)',
84
+ 'scope2': 'Scope 2 (Indirect Emissions - Purchased Energy)',
85
+ 'scope3': 'Scope 3 (Other Indirect Emissions)',
86
+ 'date': 'Date',
87
+ 'scope': 'Scope',
88
+ 'category': 'Category',
89
+ 'activity': 'Activity',
90
+ 'quantity': 'Quantity',
91
+ 'unit': 'Unit',
92
+ 'emission_factor': 'Emission Factor',
93
+ 'emissions': 'Emissions (kgCO2e)',
94
+ 'notes': 'Notes',
95
+ 'add_entry': 'Add Entry',
96
+ 'upload_csv': 'Upload CSV',
97
+ 'download_report': 'Download Report',
98
+ 'total_emissions': 'Total Emissions',
99
+ 'emissions_by_scope': 'Emissions by Scope',
100
+ 'emissions_by_category': 'Emissions by Category',
101
+ 'emissions_over_time': 'Emissions Over Time',
102
+ 'language': 'Language',
103
+ 'save': 'Save',
104
+ 'cancel': 'Cancel',
105
+ 'success': 'Success!',
106
+ 'error': 'Error!',
107
+ 'entry_added': 'Entry added successfully!',
108
+ 'csv_uploaded': 'CSV uploaded successfully!',
109
+ 'report_downloaded': 'Report downloaded successfully!',
110
+ 'settings_saved': 'Settings saved successfully!',
111
+ 'no_data': 'No data available.',
112
+ 'welcome_message': 'Welcome to YourCarbonEmissions by GXS! Start by adding your emissions data or uploading a CSV file.',
113
+ 'custom_category': 'Custom Category',
114
+ 'custom_activity': 'Custom Activity',
115
+ 'custom_unit': 'Custom Unit',
116
+ 'entry_failed': 'Failed to add entry.'
117
+ },
118
+ 'Vietnamese': {
119
+ 'title': 'YourCarbonEmissions by GXS',
120
+ 'subtitle': 'Công cụ Kiểm kê Khí Nhà kính và Báo cáo KKKNK cho Doanh nghiệp SMEs',
121
+ 'dashboard': 'Dashboard',
122
+ 'data_entry': 'Nhập Dữ liệu',
123
+ 'reports': 'Các Báo cáo',
124
+ 'settings': 'Cài đặt',
125
+ 'about': 'Thông tin chung',
126
+ 'scope1': 'Phạm vi 1 (Phát thải trực tiếp)',
127
+ 'scope2': 'Phạm vi 2 (Phát thải gián tiếp - Mua Năng lượng)',
128
+ 'scope3': 'Phạm vi 3 (Phát thải gián tiếp khác)',
129
+ 'date': 'Ngày',
130
+ 'scope': 'Phạm vi',
131
+ 'category': 'Tiểu mục',
132
+ 'activity': 'Hoạt động',
133
+ 'quantity': 'Số lượng',
134
+ 'unit': 'Đơn vị',
135
+ 'emission_factor': 'Hệ số phát thải',
136
+ 'emissions': 'Phát thải (kgCO2e)',
137
+ 'notes': 'Ghi chú',
138
+ 'add_entry': 'Thêm Đầu vào',
139
+ 'upload_csv': 'Tải file CSV lên',
140
+ 'download_report': 'Tải Báo cáo xuống',
141
+ 'total_emissions': 'Tổng Phát thải',
142
+ 'emissions_by_scope': 'Phát thải theo Phạm vi',
143
+ 'emissions_by_category': 'Phát thải theo Tiểu mục',
144
+ 'emissions_over_time': 'Phát thải qua thời gian',
145
+ 'language': 'Ngôn ngữ',
146
+ 'save': 'Lưu',
147
+ 'cancel': 'Hủy bỏ',
148
+ 'success': 'Thành công!',
149
+ 'error': 'Lỗi!',
150
+ 'entry_added': 'Dữ liệu đã được thêm!',
151
+ 'csv_uploaded': 'CSV đã tải lên!',
152
+ 'report_downloaded': 'Báo cáo đã được tải xuống!',
153
+ 'settings_saved': 'Cài đặt đã được lưu!',
154
+ 'no_data': 'Không có dữ liệu',
155
+ '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',
156
+ 'custom_category': 'Điều chỉnh Tiểu mục',
157
+ 'custom_activity': 'Điều chỉnh Hoạt động',
158
+ 'custom_unit': 'Điều chỉnh Đơn vị',
159
+ 'entry_failed': 'Nhập Đầu vào thất bại'
160
+ }
161
+ }
162
+
163
+ # Function to get translated text
164
+ def t(key):
165
+ lang = st.session_state.language
166
+ return translations.get(lang, {}).get(key, key)
167
+
168
+ # Function to save emissions data
169
+ def save_emissions_data():
170
+ try:
171
+ # Create data directory if it doesn't exist
172
+ os.makedirs('data', exist_ok=True)
173
+
174
+ # Create a backup of the existing file if it exists
175
+ if os.path.exists('data/emissions.json'):
176
+ backup_path = 'data/emissions_backup.json'
177
+ try:
178
+ with open('data/emissions.json', 'r') as src, open(backup_path, 'w') as dst:
179
+ dst.write(src.read())
180
+ except Exception:
181
+ # Continue even if backup fails
182
+ pass
183
+
184
+ # Save data to JSON file with proper formatting
185
+ with open('data/emissions.json', 'w') as f:
186
+ if len(st.session_state.emissions_data) > 0:
187
+ json.dump(st.session_state.emissions_data.to_dict('records'), f, indent=2)
188
+ else:
189
+ # Write empty array if no data
190
+ f.write('[]')
191
+
192
+ return True
193
+ except Exception as e:
194
+ st.error(f"Error saving data: {str(e)}")
195
+ return False
196
+
197
+ # Function to add new emission entry
198
+ def add_emission_entry(date, business_unit, project, scope, category, activity, country, facility, responsible_person, quantity, unit, emission_factor, data_quality, verification_status, notes):
199
+ """Add a new emission entry to the emissions data."""
200
+ try:
201
+ # Calculate emissions
202
+ emissions_kgCO2e = float(quantity) * float(emission_factor)
203
+
204
+ # Create new entry
205
+ new_entry = pd.DataFrame([{
206
+ 'date': date.strftime('%Y-%m-%d'),
207
+ 'business_unit': business_unit,
208
+ 'project': project,
209
+ 'scope': scope,
210
+ 'category': category,
211
+ 'activity': activity,
212
+ 'country': country,
213
+ 'facility': facility,
214
+ 'responsible_person': responsible_person,
215
+ 'quantity': float(quantity),
216
+ 'unit': unit,
217
+ 'emission_factor': float(emission_factor),
218
+ 'emissions_kgCO2e': emissions_kgCO2e,
219
+ 'data_quality': data_quality,
220
+ 'verification_status': verification_status,
221
+ 'notes': notes
222
+ }])
223
+
224
+ # Add to existing data
225
+ st.session_state.emissions_data = pd.concat([st.session_state.emissions_data, new_entry], ignore_index=True)
226
+
227
+ # Save data and return success/failure
228
+ return save_emissions_data()
229
+ except Exception as e:
230
+ st.error(f"Error adding entry: {str(e)}")
231
+ return False
232
+
233
+ def delete_emission_entry(index):
234
+ try:
235
+ # Make a copy of the current data
236
+ if len(st.session_state.emissions_data) > index:
237
+ # Drop the row at the specified index
238
+ st.session_state.emissions_data = st.session_state.emissions_data.drop(index).reset_index(drop=True)
239
+
240
+ # Save data and return success/failure
241
+ return save_emissions_data()
242
+ else:
243
+ st.error("Invalid index for deletion")
244
+ return False
245
+ except Exception as e:
246
+ st.error(f"Error deleting entry: {str(e)}")
247
+ return False
248
+
249
+ # Function to process uploaded CSV
250
+ def process_csv(uploaded_file):
251
+ """Process uploaded CSV file and add to emissions data."""
252
+ try:
253
+ # Read CSV file
254
+ df = pd.read_csv(uploaded_file)
255
+ required_columns = ['date', 'scope', 'category', 'activity', 'quantity', 'unit', 'emission_factor']
256
+
257
+ # Check if all required columns exist
258
+ if not all(col in df.columns for col in required_columns):
259
+ st.error(f"CSV must contain all required columns: {', '.join(required_columns)}")
260
+ return False
261
+
262
+ # Validate data types
263
+ try:
264
+ # Convert quantity and emission_factor to float
265
+ df['quantity'] = df['quantity'].astype(float)
266
+ df['emission_factor'] = df['emission_factor'].astype(float)
267
+
268
+ # Validate dates
269
+ df['date'] = pd.to_datetime(df['date']).dt.strftime('%Y-%m-%d')
270
+ except Exception as e:
271
+ st.error(f"Data validation error: {str(e)}")
272
+ return False
273
+
274
+ # Calculate emissions if not provided
275
+ if 'emissions_kgCO2e' not in df.columns:
276
+ df['emissions_kgCO2e'] = df['quantity'] * df['emission_factor']
277
+
278
+ # Add enterprise fields if not present
279
+ enterprise_fields = {
280
+ 'business_unit': 'Corporate',
281
+ 'project': 'Not Applicable',
282
+ 'country': 'Vietnam',
283
+ 'facility': '',
284
+ 'responsible_person': '',
285
+ 'data_quality': 'Medium',
286
+ 'verification_status': 'Unverified',
287
+ 'notes': ''
288
+ }
289
+
290
+ # Add missing columns with default values
291
+ for field, default_value in enterprise_fields.items():
292
+ if field not in df.columns:
293
+ df[field] = default_value
294
+
295
+ # Append to existing data
296
+ st.session_state.emissions_data = pd.concat([st.session_state.emissions_data, df], ignore_index=True)
297
+
298
+ # Save data
299
+ if save_emissions_data():
300
+ st.success(f"Successfully added {len(df)} entries")
301
+ return True
302
+ else:
303
+ st.error("Failed to save data")
304
+ return False
305
+ except Exception as e:
306
+ st.error(f"Error processing CSV: {str(e)}")
307
+ return False
308
+
309
+ # Function to generate PDF report
310
+ def generate_report():
311
+ # Create a BytesIO object
312
+ buffer = BytesIO()
313
+
314
+ # Create a simple CSV report for now
315
+ st.session_state.emissions_data.to_csv(buffer, index=False)
316
+ buffer.seek(0)
317
+
318
+ return buffer
319
+
320
+ # Custom CSS
321
+ def local_css():
322
+ st.markdown('''
323
+ <style>
324
+ /* Remove default Streamlit styling */
325
+ #MainMenu {visibility: hidden;}
326
+ footer {visibility: hidden;}
327
+
328
+ /* Base styling */
329
+ html, body, [class*="css"] {
330
+ font-family: 'Segoe UI', 'Roboto', sans-serif;
331
+ }
332
+
333
+ /* Main content area */
334
+ .main .block-container {
335
+ padding-top: 2rem;
336
+ padding-bottom: 2rem;
337
+ }
338
+
339
+ /* Sidebar styling - IMPORTANT: Override the dark background */
340
+ [data-testid="stSidebar"] {
341
+ background-color: #ffffff !important;
342
+ }
343
+
344
+ [data-testid="stSidebar"] > div:first-child {
345
+ background-color: #ffffff !important;
346
+ padding: 2rem 1rem;
347
+ }
348
+
349
+ /* Sidebar title */
350
+ [data-testid="stSidebar"] h1 {
351
+ color: #2E7D32;
352
+ font-size: 24px;
353
+ font-weight: 600;
354
+ margin-bottom: 0;
355
+ }
356
+
357
+ /* Sidebar subtitle */
358
+ [data-testid="stSidebar"] p {
359
+ color: #555555;
360
+ font-size: 14px;
361
+ }
362
+
363
+ /* Headings */
364
+ h1, h2, h3, h4, h5, h6 {
365
+ color: #2E7D32;
366
+ font-weight: 600;
367
+ }
368
+
369
+ h1 {
370
+ font-size: 2rem;
371
+ margin-bottom: 1.5rem;
372
+ }
373
+
374
+ h2 {
375
+ font-size: 1.5rem;
376
+ margin-top: 1.5rem;
377
+ margin-bottom: 1rem;
378
+ }
379
+
380
+ h3 {
381
+ font-size: 1.2rem;
382
+ margin-top: 1.2rem;
383
+ margin-bottom: 0.8rem;
384
+ }
385
+
386
+ /* Card styling */
387
+ div.stCard {
388
+ background-color: #ffffff;
389
+ border-radius: 8px;
390
+ padding: 1.5rem;
391
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
392
+ margin-bottom: 1.5rem;
393
+ border: none;
394
+ }
395
+
396
+ /* Card styling */
397
+ .stCard {
398
+ background-color: white;
399
+ border-radius: 8px;
400
+ padding: 20px;
401
+ margin-bottom: 20px;
402
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
403
+ border: 1px solid #f0f0f0;
404
+ }
405
+
406
+ /* AI Insights card styling */
407
+ .stCard p {
408
+ margin-bottom: 10px;
409
+ line-height: 1.6;
410
+ }
411
+
412
+ .stCard h1, .stCard h2, .stCard h3, .stCard h4 {
413
+ color: #2E7D32;
414
+ margin-top: 15px;
415
+ margin-bottom: 10px;
416
+ }
417
+
418
+ .stCard ul, .stCard ol {
419
+ margin-left: 20px;
420
+ margin-bottom: 15px;
421
+ }
422
+
423
+ .stCard table {
424
+ border-collapse: collapse;
425
+ width: 100%;
426
+ margin-bottom: 15px;
427
+ }
428
+
429
+ .stCard th, .stCard td {
430
+ border: 1px solid #ddd;
431
+ padding: 8px;
432
+ text-align: left;
433
+ }
434
+
435
+ .stCard th {
436
+ background-color: #f2f2f2;
437
+ }
438
+
439
+ /* Metric cards */
440
+ .metric-card {
441
+ background-color: #ffffff;
442
+ border-radius: 8px;
443
+ padding: 1.5rem;
444
+ text-align: center;
445
+ box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
446
+ border-left: 4px solid #2E7D32;
447
+ margin-bottom: 1rem;
448
+ }
449
+
450
+ .metric-value {
451
+ font-size: 28px;
452
+ font-weight: bold;
453
+ margin: 0.5rem 0;
454
+ color: #2E7D32;
455
+ }
456
+
457
+ .metric-label {
458
+ font-size: 14px;
459
+ color: #555555;
460
+ text-transform: uppercase;
461
+ letter-spacing: 1px;
462
+ }
463
+
464
+ /* Buttons */
465
+ .stButton>button {
466
+ background-color: #2E7D32;
467
+ color: white;
468
+ border-radius: 4px;
469
+ border: none;
470
+ padding: 0.5rem 1rem;
471
+ font-size: 16px;
472
+ font-weight: 500;
473
+ transition: all 0.2s ease;
474
+ }
475
+
476
+ .stButton>button:hover {
477
+ background-color: #388E3C;
478
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
479
+ }
480
+
481
+ .stButton>button:focus {
482
+ box-shadow: 0 0 0 2px rgba(46, 125, 50, 0.5);
483
+ }
484
+
485
+ /* Secondary buttons */
486
+ .stButton>button[kind="secondary"] {
487
+ background-color: #f8f9fa;
488
+ color: #2E7D32;
489
+ border: 1px solid #2E7D32;
490
+ }
491
+
492
+ .stButton>button[kind="secondary"]:hover {
493
+ background-color: #f1f3f5;
494
+ }
495
+
496
+ /* Tabs */
497
+ .stTabs [data-baseweb="tab-list"] {
498
+ gap: 10px;
499
+ }
500
+
501
+ .stTabs [data-baseweb="tab"] {
502
+ background-color: #f8f9fa;
503
+ border-radius: 4px 4px 0px 0px;
504
+ padding: 10px 16px;
505
+ font-weight: 500;
506
+ }
507
+
508
+ .stTabs [aria-selected="true"] {
509
+ background-color: #2E7D32 !important;
510
+ color: white !important;
511
+ }
512
+
513
+ /* Info boxes */
514
+ .info-box {
515
+ background-color: #E3F2FD;
516
+ border-left: 4px solid #2196F3;
517
+ padding: 1rem;
518
+ border-radius: 4px;
519
+ margin: 1rem 0;
520
+ }
521
+
522
+ .warning-box {
523
+ background-color: #FFF8E1;
524
+ border-left: 4px solid #FFC107;
525
+ padding: 1rem;
526
+ border-radius: 4px;
527
+ margin: 1rem 0;
528
+ }
529
+
530
+ /* Footer */
531
+ .footer {
532
+ text-align: center;
533
+ padding: 1rem;
534
+ color: #555555;
535
+ font-size: 12px;
536
+ margin-top: 2rem;
537
+ border-top: 1px solid #e9ecef;
538
+ }
539
+
540
+ /* Form fields */
541
+ [data-baseweb="input"] {
542
+ border-radius: 4px;
543
+ }
544
+
545
+ /* Selectbox */
546
+ [data-baseweb="select"] {
547
+ border-radius: 4px;
548
+ }
549
+
550
+ /* Sidebar navigation buttons */
551
+ [data-testid="stSidebar"] .stButton>button {
552
+ width: 100%;
553
+ text-align: left;
554
+ background-color: transparent;
555
+ color: #333333;
556
+ border: none;
557
+ padding: 0.75rem 1rem;
558
+ margin-bottom: 0.5rem;
559
+ border-radius: 4px;
560
+ font-weight: normal;
561
+ display: flex;
562
+ align-items: center;
563
+ }
564
+
565
+ [data-testid="stSidebar"] .stButton>button:hover {
566
+ background-color: #f1f3f5;
567
+ box-shadow: none;
568
+ }
569
+
570
+ /* Active navigation button */
571
+ [data-testid="stSidebar"] .stButton>button.active {
572
+ background-color: #E8F5E9;
573
+ border-left: 4px solid #2E7D32;
574
+ font-weight: 500;
575
+ }
576
+
577
+ /* Divider */
578
+ hr {
579
+ margin: 1.5rem 0;
580
+ border: 0;
581
+ border-top: 1px solid #e9ecef;
582
+ }
583
+
584
+ /* Dataframe styling */
585
+ .dataframe {
586
+ border-collapse: collapse;
587
+ width: 100%;
588
+ border: 1px solid #e9ecef;
589
+ }
590
+
591
+ .dataframe th {
592
+ background-color: #f8f9fa;
593
+ color: #333333;
594
+ font-weight: 500;
595
+ text-align: left;
596
+ padding: 0.75rem;
597
+ border-bottom: 2px solid #e9ecef;
598
+ }
599
+
600
+ .dataframe td {
601
+ padding: 0.75rem;
602
+ border-bottom: 1px solid #e9ecef;
603
+ }
604
+
605
+ .dataframe tr:hover {
606
+ background-color: #f8f9fa;
607
+ }
608
+ </style>
609
+ ''', unsafe_allow_html=True)
610
+
611
+ # Navigation component
612
+ def render_navigation():
613
+ nav_items = [
614
+ {"icon": "📝", "label": "Data Entry (Nhập Dữ liệu", "id": "Data Entry"},
615
+ {"icon": "📊", "label": "Dashboard", "id": "Dashboard"},
616
+ {"icon": "🤖", "label": "AI Insights", "id": "AI Insights"},
617
+ {"icon": "⚙️", "label": "Settings (Cài đặt", "id": "Settings"}
618
+ ]
619
+
620
+ st.markdown("### Navigation")
621
+
622
+ for item in nav_items:
623
+ active_class = "active" if st.session_state.active_page == item["id"] else ""
624
+ if st.sidebar.button(
625
+ f"{item['icon']} {item['label']}",
626
+ key=f"nav_{item['id']}",
627
+ help=f"Go to {item['label']}",
628
+ use_container_width=True
629
+ ):
630
+ st.session_state.active_page = item["id"]
631
+ st.rerun()
632
+
633
+ # Metric card component
634
+ def metric_card(title, value, description=None, icon=None, prefix="", suffix=""):
635
+ st.markdown(f'''
636
+ <div class="metric-card">
637
+ {f'<div style="font-size: 24px;">{icon}</div>' if icon else ''}
638
+ <div class="metric-label">{title}</div>
639
+ <div class="metric-value">{prefix}{value}{suffix}</div>
640
+ {f'<div style="color: #aaa; font-size: 12px;">{description}</div>' if description else ''}
641
+ </div>
642
+ ''', unsafe_allow_html=True)
643
+
644
+ # Card component
645
+ def card(content, title=None):
646
+ if title:
647
+ st.markdown(f"<div class='stCard'><h3>{title}</h3>{content}</div>", unsafe_allow_html=True)
648
+ else:
649
+ st.markdown(f"<div class='stCard'>{content}</div>", unsafe_allow_html=True)
650
+
651
+ # Apply custom CSS
652
+ local_css()
653
+
654
+ # Sidebar
655
+ with st.sidebar:
656
+ st.markdown(f"<h1 style='margin-bottom: 0; font-size: 24px;'>{t('title')}</h1>", unsafe_allow_html=True)
657
+ st.markdown(f"<p style='margin-top: 0; color: #aaa; font-size: 12px;'>{t('subtitle')}</p>", unsafe_allow_html=True)
658
+
659
+ st.divider()
660
+
661
+ # Language selector
662
+ language = st.selectbox(t('language'), ['English', 'Vietnamese'])
663
+ if language != st.session_state.language:
664
+ st.session_state.language = language
665
+ st.rerun()
666
+
667
+ st.divider()
668
+
669
+ # Navigation
670
+ render_navigation()
671
+
672
+ st.divider()
673
+
674
+ # Footer
675
+ st.markdown(
676
+ "<div class='footer' style='color: #555555;'> Copyright©2025 Created by GXS Company Limited<br>Liên hệ: Nguyễn Sơn<br>Zalo: 0376076054 - Email: contact@p4cng.biz.vn</div>",
677
+ unsafe_allow_html=True
678
+ )
679
+
680
+ # Main content
681
+ if st.session_state.active_page == "Dashboard":
682
+ st.markdown(f"<h1> {t('dashboard')}</h1>", unsafe_allow_html=True)
683
+
684
+ if len(st.session_state.emissions_data) == 0:
685
+ st.markdown(f"<div class='info-box'>{t('welcome_message')}</div>", unsafe_allow_html=True)
686
+ else:
687
+ # Calculate metrics
688
+ # Ensure emissions_kgCO2e is numeric
689
+ st.session_state.emissions_data['emissions_kgCO2e'] = pd.to_numeric(st.session_state.emissions_data['emissions_kgCO2e'], errors='coerce')
690
+
691
+ # Replace NaN with 0
692
+ st.session_state.emissions_data['emissions_kgCO2e'].fillna(0, inplace=True)
693
+
694
+ total_emissions = st.session_state.emissions_data['emissions_kgCO2e'].sum()
695
+
696
+ # Display metrics
697
+ col1, col2, col3 = st.columns(3)
698
+ with col1:
699
+ metric_card(
700
+ title=t('total_emissions'),
701
+ value=f"{total_emissions:.2f}",
702
+ suffix=" kgCO2e",
703
+ icon="🌍"
704
+ )
705
+ with col2:
706
+ if 'date' in st.session_state.emissions_data.columns:
707
+ st.session_state.emissions_data['date'] = pd.to_datetime(st.session_state.emissions_data['date'], errors='coerce')
708
+ if not st.session_state.emissions_data['date'].isnull().all():
709
+ latest_date = st.session_state.emissions_data['date'].max().strftime('%Y-%m-%d')
710
+ else:
711
+ latest_date = "No date data"
712
+ metric_card(
713
+ title="Latest Entry",
714
+ value=latest_date,
715
+ icon="📅"
716
+ )
717
+ with col3:
718
+ entry_count = len(st.session_state.emissions_data)
719
+ metric_card(
720
+ title="Total Entries",
721
+ value=str(entry_count),
722
+ icon="📊"
723
+ )
724
+
725
+ # Charts
726
+ st.markdown(f"<h2>{t('emissions_by_scope')}</h2>", unsafe_allow_html=True)
727
+
728
+ # Check if there are any non-zero emissions before creating charts
729
+ if total_emissions > 0:
730
+ # Create scope data for pie chart
731
+ scope_data = st.session_state.emissions_data.groupby('scope')['emissions_kgCO2e'].sum().reset_index()
732
+
733
+ # Only create chart if we have data with emissions
734
+ if not scope_data.empty and scope_data['emissions_kgCO2e'].sum() > 0:
735
+ fig1 = px.pie(
736
+ scope_data,
737
+ values='emissions_kgCO2e',
738
+ names='scope',
739
+ color='scope',
740
+ color_discrete_map={'Scope 1': '#4CAF50', 'Scope 2': '#2196F3', 'Scope 3': '#FFC107'},
741
+ hole=0.4
742
+ )
743
+ fig1.update_layout(
744
+ margin=dict(t=0, b=0, l=0, r=0),
745
+ legend=dict(orientation="h", yanchor="bottom", y=-0.2, xanchor="center", x=0.5),
746
+ height=400
747
+ )
748
+ st.plotly_chart(fig1, use_container_width=True, config={'displayModeBar': False})
749
+ else:
750
+ st.info("No emissions data available for scope breakdown.")
751
+ else:
752
+ st.info("No emissions data available for scope breakdown.")
753
+
754
+ col1, col2 = st.columns(2)
755
+
756
+ with col1:
757
+ st.markdown(f"<h2>{t('emissions_by_category')}</h2>", unsafe_allow_html=True)
758
+
759
+ if total_emissions > 0:
760
+ # Create category data for bar chart
761
+ category_data = st.session_state.emissions_data.groupby('category')['emissions_kgCO2e'].sum().reset_index()
762
+ category_data = category_data.sort_values('emissions_kgCO2e', ascending=False)
763
+
764
+ # Only create chart if we have data with emissions
765
+ if not category_data.empty and category_data['emissions_kgCO2e'].sum() > 0:
766
+ fig2 = px.bar(
767
+ category_data,
768
+ x='category',
769
+ y='emissions_kgCO2e',
770
+ color='category',
771
+ labels={'emissions_kgCO2e': 'Emissions (kgCO2e)', 'category': 'Category'}
772
+ )
773
+ fig2.update_layout(
774
+ showlegend=False,
775
+ margin=dict(t=0, b=0, l=0, r=0),
776
+ height=400
777
+ )
778
+ st.plotly_chart(fig2, use_container_width=True, config={'displayModeBar': False})
779
+ else:
780
+ st.info("No emissions data available for category breakdown.")
781
+ else:
782
+ st.info("No emissions data available for category breakdown.")
783
+
784
+ with col2:
785
+ st.markdown(f"<h2>{t('emissions_over_time')}</h2>", unsafe_allow_html=True)
786
+
787
+ if total_emissions > 0 and 'date' in st.session_state.emissions_data.columns:
788
+ # Convert date column to datetime
789
+ time_data = st.session_state.emissions_data.copy()
790
+ time_data['date'] = pd.to_datetime(time_data['date'], errors='coerce')
791
+
792
+ # Filter out rows with invalid dates
793
+ time_data = time_data.dropna(subset=['date'])
794
+
795
+ if not time_data.empty:
796
+ # Create month column for aggregation
797
+ time_data['month'] = time_data['date'].dt.strftime('%Y-%m')
798
+
799
+ # Group by month and scope
800
+ time_data = time_data.groupby(['month', 'scope'])['emissions_kgCO2e'].sum().reset_index()
801
+
802
+ if len(time_data['month'].unique()) > 0:
803
+ # Create line chart
804
+ fig3 = px.line(
805
+ time_data,
806
+ x='month',
807
+ y='emissions_kgCO2e',
808
+ color='scope',
809
+ markers=True,
810
+ color_discrete_map={'Scope 1': '#4CAF50', 'Scope 2': '#2196F3', 'Scope 3': '#FFC107'},
811
+ labels={'emissions_kgCO2e': 'Emissions (kgCO2e)', 'month': 'Month', 'scope': 'Scope'}
812
+ )
813
+ fig3.update_layout(
814
+ margin=dict(t=0, b=0, l=0, r=0),
815
+ xaxis_title="",
816
+ yaxis_title="kgCO2e",
817
+ legend_title="",
818
+ height=400
819
+ )
820
+ st.plotly_chart(fig3, use_container_width=True, config={'displayModeBar': False})
821
+ else:
822
+ st.info("Not enough time data to show emissions over time.")
823
+ else:
824
+ st.info("No valid date data available for time series chart.")
825
+ else:
826
+ st.info("No emissions data available for time series chart.")
827
+
828
+ elif st.session_state.active_page == "Data Entry":
829
+ st.markdown(f"<h1> {t('data_entry')}</h1>", unsafe_allow_html=True)
830
+
831
+ tabs = st.tabs([" Manual Entry", " CSV Upload"])
832
+
833
+ with tabs[0]:
834
+ st.markdown("<h3>Add New Emission Entry (Nhập Dữ liệu phát thải mới)</h3>", unsafe_allow_html=True)
835
+ with st.form("emission_form", border=False):
836
+ col1, col2 = st.columns(2)
837
+ with col1:
838
+ date = st.date_input(t('date'), datetime.now(), help="Date when the emission occurred")
839
+
840
+ # Add business unit field for enterprise tracking with tooltip
841
+ business_unit = st.selectbox(
842
+ "Business Unit",
843
+ ["Corporate", "Manufacturing", "Sales", "R&D", "Logistics", "IT", "Other"],
844
+ help="The business unit responsible for this emission"
845
+ )
846
+ if business_unit == "Other":
847
+ business_unit = st.text_input("Custom Business Unit", placeholder="Enter business unit name")
848
+
849
+ # Add project field for better categorization with tooltip
850
+ project = st.selectbox(
851
+ "Project",
852
+ ["Not Applicable", "Carbon Reduction Initiative", "Sustainability Program", "Operational", "Other"],
853
+ help="The project or initiative associated with this emission"
854
+ )
855
+ if project == "Other":
856
+ project = st.text_input("Custom Project", placeholder="Enter project name")
857
+
858
+ # Add scope selection with tooltip explaining each scope
859
+ scope = st.selectbox(
860
+ t('scope'),
861
+ ['Scope 1', 'Scope 2', 'Scope 3'],
862
+ help="Scope 1: Direct emissions from owned sources\nScope 2: Indirect emissions from purchased energy\nScope 3: All other indirect emissions in value chain"
863
+ )
864
+ category_options = {
865
+ 'Scope 1': ['Stationary Combustion', 'Mobile Combustion', 'Fugitive Emissions', 'Process Emissions', 'Other'],
866
+ 'Scope 2': ['Electricity', 'Steam', 'Heating', 'Cooling', 'Other'],
867
+ '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']
868
+ }
869
+ category = st.selectbox(
870
+ t('category'),
871
+ category_options[scope],
872
+ help="The category of emission source"
873
+ )
874
+ if category == 'Other':
875
+ category = st.text_input(t('custom_category'), placeholder="Enter custom category")
876
+
877
+ # Enhanced location tracking with facility details and tooltips
878
+ country_options = ['Vietnam', 'India', 'United States', 'United Kingdom', 'Japan', 'Indonesia', 'Other']
879
+ country = st.selectbox(
880
+ "Country",
881
+ country_options,
882
+ help="Country where the emission occurred"
883
+ )
884
+ if country == 'Other':
885
+ country = st.text_input("Custom Country", placeholder="Enter country name")
886
+
887
+ # Add facility/location field with tooltip
888
+ facility = st.text_input(
889
+ "Facility/Location",
890
+ placeholder="e.g., Ho Chi Minh City HQ, Binh Duong Plant 2, etc.",
891
+ help="Specific facility or location where the emission occurred"
892
+ )
893
+
894
+ # Add responsible person field with tooltip
895
+ responsible_person = st.text_input(
896
+ "Responsible Person",
897
+ placeholder="Person responsible for this emission source",
898
+ help="Name of the person accountable for managing this emission source"
899
+ )
900
+ with col2:
901
+ activity_options = {
902
+ 'Stationary Combustion': ['Boiler', 'Furnace', 'Generator', 'Other'],
903
+ 'Mobile Combustion': ['Company Vehicle', 'Fleet Vehicle', 'Machinery', 'Other'],
904
+ 'Fugitive Emissions': ['Refrigerant Leak', 'SF6 Emissions', 'Other'],
905
+ 'Process Emissions': ['Cement Production', 'Chemical Production', 'Other'],
906
+ 'Electricity': ['Office Electricity', 'Manufacturing Electricity', 'Other'],
907
+ 'Steam': ['Industrial Steam', 'Heating Steam', 'Other'],
908
+ 'Heating': ['Office Heating', 'Industrial Heating', 'Other'],
909
+ 'Cooling': ['Office Cooling', 'Industrial Cooling', 'Other'],
910
+ 'Purchased Goods and Services': ['Raw Materials', 'Office Supplies', 'Other'],
911
+ 'Capital Goods': ['Equipment Purchase', 'Vehicle Purchase', 'Other'],
912
+ 'Fuel- and Energy-Related Activities': ['Upstream Fuel Production', 'Transmission Losses', 'Other'],
913
+ 'Upstream Transportation and Distribution': ['Supplier Transport', 'Inbound Logistics', 'Other'],
914
+ 'Waste Generated in Operations': ['Solid Waste', 'Wastewater', 'Other'],
915
+ 'Business Travel': ['Air Travel', 'Ground Travel', 'Hotel Stays', 'Other'],
916
+ 'Employee Commuting': ['Private Vehicle', 'Public Transport', 'Other'],
917
+ 'Upstream Leased Assets': ['Leased Equipment', 'Leased Vehicles', 'Other'],
918
+ 'Downstream Transportation and Distribution': ['Outbound Logistics', 'Customer Transport', 'Other'],
919
+ 'Processing of Sold Products': ['Intermediate Processing', 'Final Assembly', 'Other'],
920
+ 'Use of Sold Products': ['Product Operation', 'Energy Consumption', 'Other'],
921
+ 'End-of-Life Treatment of Sold Products': ['Recycling', 'Landfill', 'Other'],
922
+ 'Downstream Leased Assets': ['Leased Equipment', 'Leased Property', 'Other'],
923
+ 'Franchises': ['Franchise Operations', 'Franchise Energy Use', 'Other'],
924
+ 'Investments': ['Investment Emissions', 'Financed Emissions', 'Other'],
925
+ 'Other': ['Custom Activity', 'Other']
926
+ }
927
+ activity_key = category if category != 'Other' else 'Other'
928
+ activity_list = activity_options.get(activity_key, ['Custom Activity', 'Other'])
929
+ activity = st.selectbox(
930
+ "Activity",
931
+ activity_options.get(category, ['Other']),
932
+ help="Specific activity that generated the emissions"
933
+ )
934
+ if activity == 'Other':
935
+ activity = st.text_input("Custom Activity", placeholder="Enter custom activity")
936
+
937
+ # Add validation for quantity with tooltip
938
+ quantity = st.number_input(
939
+ t('quantity'),
940
+ min_value=0.0,
941
+ format="%.2f",
942
+ help="The amount of activity (e.g., kWh used, liters consumed, etc.)"
943
+ )
944
+
945
+ # Enhanced unit selection with tooltip
946
+ unit_options = ['kWh', 'MWh', 'GJ', 'liter', 'gallon', 'kg', 'tonne', 'km', 'mile', 'hour', 'day', 'piece', 'USD', 'Other']
947
+ unit = st.selectbox(
948
+ t('unit'),
949
+ unit_options,
950
+ help="The unit of measurement for the quantity"
951
+ )
952
+ if unit == 'Other':
953
+ unit = st.text_input(t('custom_unit'), placeholder="Enter custom unit")
954
+
955
+ # Emission factor auto-population based on country and category
956
+ emission_factors = {
957
+ 'India': {
958
+ 'Electricity': 0.82, 'Mobile Combustion': 2.31, 'Stationary Combustion': 1.85, 'Other': 0.0
959
+ },
960
+ 'United States': {
961
+ 'Electricity': 0.42,
962
+ 'Mobile Combustion': 2.32,
963
+ 'Stationary Combustion': 2.01,
964
+ 'Business Travel': 0.12,
965
+ 'Employee Commuting': 0.15
966
+ }
967
+ }
968
+ default_factor = emission_factors.get(country, {}).get(category, 0.0) if country != 'Other' else 0.0
969
+
970
+ # Now that default_factor is defined, show AI suggestion
971
+ 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.")
972
+
973
+ emission_factor = st.number_input(
974
+ t('emission_factor'),
975
+ min_value=0.0,
976
+ value=default_factor,
977
+ format="%.4f",
978
+ help=f"Emission factor in kgCO2e per unit. Typical range: {max(0.1, default_factor*0.8):.4f} to {default_factor*1.2:.4f}"
979
+ )
980
+
981
+ # Add data quality indicator with color-coded help
982
+ data_quality = st.select_slider(
983
+ "Data Quality",
984
+ options=["Low", "Medium", "High"],
985
+ value="Medium",
986
+ help="🔴 Low: Estimated or proxy data\n🟡 Medium: Calculated from bills or invoices\n🟢 High: Directly measured or metered data"
987
+ )
988
+
989
+ # Add verification status with detailed help
990
+ verification_status = st.selectbox(
991
+ "Verification Status",
992
+ ["Unverified", "Internally Verified", "Third-Party Verified"],
993
+ help="Unverified: No verification process applied\nInternally Verified: Checked by internal team\nThird-Party Verified: Validated by external auditor"
994
+ )
995
+
996
+ # Enhanced notes field with better guidance
997
+ notes = st.text_area(
998
+ t('notes'),
999
+ placeholder="Additional information, data sources, calculation methods, etc.",
1000
+ help="Include information about data sources, calculation methodology, assumptions made, and any other relevant context"
1001
+ )
1002
+
1003
+ # Add cost field for financial impact tracking (optional)
1004
+ cost = st.number_input(
1005
+ "Cost (Optional)",
1006
+ min_value=0.0,
1007
+ value=0.0,
1008
+ format="%.2f",
1009
+ help="Optional: Associated cost in your local currency"
1010
+ )
1011
+
1012
+ # Add cost currency if cost is entered
1013
+ if cost > 0:
1014
+ currency = st.selectbox(
1015
+ "Currency",
1016
+ ["VND", "USD", "EUR", "INR", "GBP", "JPY", "Other"],
1017
+ help="Currency for the entered cost"
1018
+ )
1019
+
1020
+ # Form submission buttons
1021
+ col1, col2 = st.columns([1, 1])
1022
+ with col1:
1023
+ submitted = st.form_submit_button(t('add_entry'), type="primary", use_container_width=True)
1024
+ with col2:
1025
+ clear = st.form_submit_button(t('clear_form'), type="secondary", use_container_width=True)
1026
+
1027
+ if submitted:
1028
+ # Basic validation
1029
+ if quantity <= 0:
1030
+ st.error("Quantity must be greater than zero.")
1031
+ elif not facility.strip():
1032
+ st.warning("Facility/Location is recommended for enterprise tracking.")
1033
+ else:
1034
+ try:
1035
+ # Include cost in the entry if provided
1036
+ cost_value = cost if 'cost' in locals() and cost > 0 else 0.0
1037
+ currency_value = currency if 'currency' in locals() and cost > 0 else ""
1038
+
1039
+ add_emission_entry(
1040
+ date, business_unit, project, scope, category, activity, country, facility,
1041
+ responsible_person, quantity, unit, emission_factor, data_quality, verification_status, notes
1042
+ )
1043
+ st.success(t('entry_added'))
1044
+ # Redirect to Dashboard after successful entry
1045
+ st.session_state.active_page = "Dashboard"
1046
+ st.rerun()
1047
+ except Exception as e:
1048
+ st.error(f"{t('entry_failed')} {str(e)}")
1049
+
1050
+ # Show existing data table
1051
+ if len(st.session_state.emissions_data) > 0:
1052
+ st.markdown("<h3>Existing Emissions Data</h3>", unsafe_allow_html=True)
1053
+
1054
+ # Create a copy of the dataframe with an action column
1055
+ display_df = st.session_state.emissions_data.copy()
1056
+
1057
+ # Add a column for the delete action
1058
+ col1, col2 = st.columns([3, 1])
1059
+
1060
+ with col1:
1061
+ # Display the dataframe
1062
+ st.dataframe(
1063
+ display_df,
1064
+ column_config={
1065
+ "date": st.column_config.DateColumn("Date"),
1066
+ "business_unit": st.column_config.TextColumn("Business Unit"),
1067
+ "project": st.column_config.TextColumn("Project"),
1068
+ "scope": st.column_config.TextColumn("Scope"),
1069
+ "category": st.column_config.TextColumn("Category"),
1070
+ "activity": st.column_config.TextColumn("Activity"),
1071
+ "country": st.column_config.TextColumn("Country"),
1072
+ "facility": st.column_config.TextColumn("Facility"),
1073
+ "responsible_person": st.column_config.TextColumn("Responsible Person"),
1074
+ "quantity": st.column_config.NumberColumn("Quantity", format="%.2f"),
1075
+ "unit": st.column_config.TextColumn("Unit"),
1076
+ "emission_factor": st.column_config.NumberColumn("Emission Factor", format="%.4f"),
1077
+ "emissions_kgCO2e": st.column_config.NumberColumn("Emissions (kgCO2e)", format="%.2f"),
1078
+ "data_quality": st.column_config.TextColumn("Data Quality"),
1079
+ "verification_status": st.column_config.TextColumn("Verification"),
1080
+ "notes": st.column_config.TextColumn("Notes"),
1081
+ },
1082
+ use_container_width=True,
1083
+ hide_index=False
1084
+ )
1085
+
1086
+ with col2:
1087
+ # Add delete functionality
1088
+ st.markdown("### Delete Entry")
1089
+ entry_to_delete = st.number_input("Select entry number to delete", min_value=0,
1090
+ max_value=len(display_df)-1 if len(display_df) > 0 else 0,
1091
+ step=1,
1092
+ help="Enter the index number of the entry you want to delete")
1093
+
1094
+ if st.button("🗑️ Delete Selected Entry", type="primary"):
1095
+ if delete_emission_entry(entry_to_delete):
1096
+ st.success(f"Entry {entry_to_delete} deleted successfully!")
1097
+ st.rerun()
1098
+ else:
1099
+ st.error(f"Failed to delete entry {entry_to_delete}")
1100
+
1101
+
1102
+ with tabs[1]:
1103
+ st.markdown("<h3>Upload CSV File</h3>", unsafe_allow_html=True)
1104
+
1105
+ uploaded_file = st.file_uploader(t('upload_csv'), type='csv')
1106
+ if uploaded_file is not None:
1107
+ if process_csv(uploaded_file):
1108
+ st.success(t('csv_uploaded'))
1109
+ # Redirect to Dashboard after successful upload
1110
+ st.session_state.active_page = "Dashboard"
1111
+ st.rerun()
1112
+ else:
1113
+ st.error("Failed to process CSV file. Please check the format.")
1114
+
1115
+ # Sample CSV download with enterprise-grade fields
1116
+ sample_data = {
1117
+ 'date': ['2025-01-15', '2025-01-20'],
1118
+ 'business_unit': ['Corporate', 'Logistics'],
1119
+ 'project': ['Carbon Reduction Initiative', 'Operational'],
1120
+ 'scope': ['Scope 2', 'Scope 1'],
1121
+ 'category': ['Electricity', 'Mobile Combustion'],
1122
+ 'activity': ['Office Electricity', 'Company Vehicle'],
1123
+ 'country': ['Vietnam', 'United States'],
1124
+ 'facility': ['Hanoi HQ', 'Soc Son Distribution Center'],
1125
+ 'responsible_person': ['Nguyen Thuy Trang', 'Tran Quoc Hung'],
1126
+ 'quantity': [1000, 50],
1127
+ 'unit': ['kWh', 'liter'],
1128
+ 'emission_factor': [0.82, 2.31495],
1129
+ 'data_quality': ['High', 'Medium'],
1130
+ 'verification_status': ['Internally Verified', 'Unverified'],
1131
+ 'notes': ['Monthly electricity bill', 'Fleet vehicle fuel consumption']
1132
+ }
1133
+ sample_df = pd.DataFrame(sample_data)
1134
+ csv = sample_df.to_csv(index=False).encode('utf-8')
1135
+
1136
+ st.download_button(
1137
+ label="Download Sample CSV",
1138
+ data=csv,
1139
+ file_name="sample_emissions.csv",
1140
+ mime="text/csv",
1141
+ )
1142
+
1143
+ # Reports page removed - focusing on AI features only
1144
+
1145
+ elif st.session_state.active_page == "Settings":
1146
+ st.markdown(f"<h1> {t('settings')}</h1>", unsafe_allow_html=True)
1147
+
1148
+ st.markdown("<h3>Company Information</h3>", unsafe_allow_html=True)
1149
+
1150
+ # Company info form
1151
+ with st.form("company_info_form"):
1152
+ col1, col2 = st.columns(2)
1153
+ with col1:
1154
+ company_name = st.text_input("Company Name")
1155
+ industry = st.text_input("Industry")
1156
+ location = st.text_input("Location")
1157
+ with col2:
1158
+ contact_person = st.text_input("Contact Person")
1159
+ email = st.text_input("Email")
1160
+ phone = st.text_input("Phone")
1161
+
1162
+ st.markdown("<h4>Export Markets</h4>", unsafe_allow_html=True)
1163
+ col1, col2, col3 = st.columns(3)
1164
+ with col1:
1165
+ eu_market = st.checkbox("European Union")
1166
+ with col2:
1167
+ japan_market = st.checkbox("Japan")
1168
+ with col3:
1169
+ unitedstates_market = st.checkbox("United States")
1170
+
1171
+ submitted = st.form_submit_button("Save Settings")
1172
+ if submitted:
1173
+ st.success("Settings saved successfully!")
1174
+
1175
+ elif st.session_state.active_page == "AI Insights":
1176
+ st.markdown(f"<h1>🤖 AI Insights</h1>", unsafe_allow_html=True)
1177
+
1178
+ # Import AI agents
1179
+ from ai_agents import CarbonFootprintAgents
1180
+
1181
+ # Initialize AI agents
1182
+ if 'ai_agents' not in st.session_state:
1183
+ st.session_state.ai_agents = CarbonFootprintAgents()
1184
+
1185
+ # Create tabs for different AI insights
1186
+ ai_tabs = st.tabs(["Data Assistant", "Report Summary", "Offset Advisor", "Regulation Radar", "Emission Optimizer"])
1187
+
1188
+ with ai_tabs[0]:
1189
+ st.markdown("<h3>Data Entry Assistant</h3>", unsafe_allow_html=True)
1190
+ st.markdown("Get help with classifying emissions and mapping them to the correct scope.")
1191
+
1192
+ data_description = st.text_area("Describe your emission activity",
1193
+ placeholder="Example: We use diesel generators for backup power at our office in Hai Phong. How should I categorize this?")
1194
+
1195
+ if st.button("Get Assistance", key="data_assistant_btn"):
1196
+ if data_description:
1197
+ with st.spinner("AI assistant is analyzing your request..."):
1198
+ try:
1199
+ result = st.session_state.ai_agents.run_data_entry_crew(data_description)
1200
+ # Handle CrewOutput object by converting it to string
1201
+ result_str = str(result)
1202
+ st.markdown(f"<div class='stCard'>{result_str}</div>", unsafe_allow_html=True)
1203
+ except Exception as e:
1204
+ st.error(f"Error: {str(e)}. Please check your API key and try again.")
1205
+ else:
1206
+ st.warning("Please describe your emission activity first.")
1207
+
1208
+ with ai_tabs[1]:
1209
+ st.markdown("<h3>Report Summary Generator</h3>", unsafe_allow_html=True)
1210
+ st.markdown("Generate a human-readable summary of your emissions data.")
1211
+
1212
+ if len(st.session_state.emissions_data) == 0:
1213
+ st.warning("No emissions data available. Please add data first.")
1214
+ else:
1215
+ if st.button("Generate Summary", key="report_summary_btn"):
1216
+ with st.spinner("Generating report summary..."):
1217
+ try:
1218
+ # Convert DataFrame to string representation for the AI
1219
+ emissions_str = st.session_state.emissions_data.to_string()
1220
+ result = st.session_state.ai_agents.run_report_summary_crew(emissions_str)
1221
+ # Handle CrewOutput object by converting it to string
1222
+ result_str = str(result)
1223
+ st.markdown(f"<div class='stCard'>{result_str}</div>", unsafe_allow_html=True)
1224
+ except Exception as e:
1225
+ st.error(f"Error: {str(e)}. Please check your API key and try again.")
1226
+
1227
+ with ai_tabs[2]:
1228
+ st.markdown("<h3>Carbon Offset Advisor</h3>", unsafe_allow_html=True)
1229
+ st.markdown("Get recommendations for verified carbon offset options based on your profile.")
1230
+
1231
+ col1, col2 = st.columns(2)
1232
+ with col1:
1233
+ location = st.text_input("Location", placeholder="e.g., Bac Ninh, Vietnam")
1234
+ industry = st.selectbox("Industry", ["Manufacturing", "Technology", "Agriculture", "Transportation", "Energy", "Services", "Other"])
1235
+
1236
+ if len(st.session_state.emissions_data) == 0:
1237
+ st.warning("No emissions data available. Please add data first.")
1238
+ else:
1239
+ total_emissions = st.session_state.emissions_data['emissions_kgCO2e'].sum()
1240
+ st.markdown(f"<p>Total emissions to offset: <strong>{total_emissions:.2f} kgCO2e</strong></p>", unsafe_allow_html=True)
1241
+
1242
+ if st.button("Get Offset Recommendations", key="offset_advisor_btn"):
1243
+ if location:
1244
+ with st.spinner("Finding offset options..."):
1245
+ try:
1246
+ result = st.session_state.ai_agents.run_offset_advice_crew(total_emissions, location, industry)
1247
+ # Handle CrewOutput object by converting it to string
1248
+ result_str = str(result)
1249
+ st.markdown(f"<div class='stCard'>{result_str}</div>", unsafe_allow_html=True)
1250
+ except Exception as e:
1251
+ st.error(f"Error: {str(e)}. Please check your API key and try again.")
1252
+ else:
1253
+ st.warning("Please enter your location.")
1254
+
1255
+ with ai_tabs[3]:
1256
+ st.markdown("<h3>Regulation Radar</h3>", unsafe_allow_html=True)
1257
+ st.markdown("Get insights on current and upcoming carbon regulations relevant to your business.")
1258
+
1259
+ col1, col2 = st.columns(2)
1260
+ with col1:
1261
+ location = st.text_input("Company Location", placeholder="e.g., Hanoi, Vietnam", key="reg_location")
1262
+ industry = st.selectbox("Industry Sector", ["Manufacturing", "Technology", "Agriculture", "Transportation", "Energy", "Services", "Other"], key="reg_industry")
1263
+ with col2:
1264
+ export_markets = st.multiselect("Export Markets", ["European Union", "Japan", "United States", "China", "Middle East", "India", "Other"])
1265
+
1266
+ if st.button("Check Regulations", key="regulation_radar_btn"):
1267
+ if location and len(export_markets) > 0:
1268
+ with st.spinner("Analyzing regulatory requirements..."):
1269
+ try:
1270
+ result = st.session_state.ai_agents.run_regulation_check_crew(location, industry, ", ".join(export_markets))
1271
+ # Handle CrewOutput object by converting it to string
1272
+ result_str = str(result)
1273
+ st.markdown(f"<div class='stCard'>{result_str}</div>", unsafe_allow_html=True)
1274
+ except Exception as e:
1275
+ st.error(f"Error: {str(e)}. Please check your API key and try again.")
1276
+ else:
1277
+ st.warning("Please enter your location and select at least one export market.")
1278
+
1279
+ with ai_tabs[4]:
1280
+ st.markdown("<h3>Emission Optimizer</h3>", unsafe_allow_html=True)
1281
+ st.markdown("Get AI-powered recommendations to reduce your carbon footprint.")
1282
+
1283
+ if len(st.session_state.emissions_data) == 0:
1284
+ st.warning("No emissions data available. Please add data first.")
1285
+ else:
1286
+ if st.button("Generate Optimization Recommendations", key="emission_optimizer_btn"):
1287
+ with st.spinner("Analyzing your emissions data..."):
1288
+ try:
1289
+ # Convert DataFrame to string representation for the AI
1290
+ emissions_str = st.session_state.emissions_data.to_string()
1291
+ result = st.session_state.ai_agents.run_optimization_crew(emissions_str)
1292
+ # Handle CrewOutput object by converting it to string
1293
+ result_str = str(result)
1294
+ st.markdown(f"<div class='stCard'>{result_str}</div>", unsafe_allow_html=True)
1295
+ except Exception as e:
1296
+ st.error(f"Error: {str(e)}. Please check your API key and try again.")
1297
+
1298
+ # About page removed - focusing on AI features only
config.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration settings for CarbonFootprint by GXS application.
3
+ """
4
+
5
+ import os
6
+ from dotenv import load_dotenv
7
+
8
+ # Load environment variables
9
+ load_dotenv()
10
+
11
+ # Application settings
12
+ APP_NAME = "CarbonFootprint by GXS"
13
+ APP_VERSION = "1.0.0"
14
+ APP_DESCRIPTION = "A lightweight, multilingual carbon accounting and reporting tool for SMEs in Asia"
15
+ APP_AUTHOR = "Son Nguyen"
16
+ APP_CONTACT = "sonncdx@gmail.com"
17
+
18
+ # API keys
19
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
20
+
21
+ # Data settings
22
+ DATA_DIR = "data"
23
+ EMISSIONS_FILE = os.path.join(DATA_DIR, "emissions.json")
24
+ COMPANY_INFO_FILE = os.path.join(DATA_DIR, "company_info.json")
25
+
26
+ # Supported languages
27
+ SUPPORTED_LANGUAGES = ["English", "Vietnamese"]
28
+
29
+ # Emission scopes
30
+ EMISSION_SCOPES = ["Scope 1", "Scope 2", "Scope 3"]
31
+
32
+ # Scope descriptions
33
+ SCOPE_DESCRIPTIONS = {
34
+ "Scope 1": "Direct emissions from owned or controlled sources",
35
+ "Scope 2": "Indirect emissions from the generation of purchased energy",
36
+ "Scope 3": "All other indirect emissions that occur in a company's value chain"
37
+ }
38
+
39
+ # Default units
40
+ DEFAULT_UNITS = [
41
+ "kWh",
42
+ "MWh",
43
+ "liter",
44
+ "kg",
45
+ "tonne",
46
+ "km",
47
+ "passenger-km",
48
+ "cubic meter",
49
+ "square meter",
50
+ "hour",
51
+ "day",
52
+ "piece",
53
+ "USD"
54
+ ]
55
+
56
+ # Regulatory frameworks
57
+ REGULATORY_FRAMEWORKS = {
58
+ "EU CBAM": "EU Carbon Border Adjustment Mechanism",
59
+ "Japan GX League": "Japan Green Transformation League",
60
+ "Indonesia ETS/ETP": "Indonesia Emissions Trading System/Emissions Trading Program"
61
+ }
62
+
63
+ # Create data directory if it doesn't exist
64
+ os.makedirs(DATA_DIR, exist_ok=True)
data_handler.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Data handler for CarbonFootprint by GXS application.
3
+ Manages data import, export, and processing.
4
+ """
5
+
6
+ import pandas as pd
7
+ import json
8
+ import os
9
+ from datetime import datetime
10
+ import csv
11
+ from io import StringIO
12
+ from fpdf import FPDF
13
+ import matplotlib.pyplot as plt
14
+ import seaborn as sns
15
+ from emission_factors import get_emission_factor, get_categories, get_activities
16
+
17
+ # Constants
18
+ DATA_DIR = "data"
19
+ EMISSIONS_FILE = os.path.join(DATA_DIR, "emissions.json")
20
+ COMPANY_INFO_FILE = os.path.join(DATA_DIR, "company_info.json")
21
+
22
+ # Ensure data directory exists
23
+ os.makedirs(DATA_DIR, exist_ok=True)
24
+
25
+ class DataHandler:
26
+ def __init__(self):
27
+ """Initialize the DataHandler class."""
28
+ self.load_emissions_data()
29
+ self.load_company_info()
30
+
31
+ def load_emissions_data(self):
32
+ """Load emissions data from file."""
33
+ if os.path.exists(EMISSIONS_FILE):
34
+ with open(EMISSIONS_FILE, 'r') as f:
35
+ try:
36
+ self.emissions_data = pd.DataFrame(json.load(f))
37
+ # Convert date strings to datetime objects
38
+ if 'date' in self.emissions_data.columns:
39
+ self.emissions_data['date'] = pd.to_datetime(self.emissions_data['date'])
40
+ except json.JSONDecodeError:
41
+ self.create_empty_emissions_data()
42
+ else:
43
+ self.create_empty_emissions_data()
44
+
45
+ def create_empty_emissions_data(self):
46
+ """Create empty emissions dataframe."""
47
+ self.emissions_data = pd.DataFrame(columns=[
48
+ 'date', 'scope', 'category', 'activity', 'quantity',
49
+ 'unit', 'emission_factor', 'emissions_kgCO2e', 'notes'
50
+ ])
51
+
52
+ def load_company_info(self):
53
+ """Load company information from file."""
54
+ if os.path.exists(COMPANY_INFO_FILE):
55
+ with open(COMPANY_INFO_FILE, 'r') as f:
56
+ try:
57
+ self.company_info = json.load(f)
58
+ except json.JSONDecodeError:
59
+ self.create_empty_company_info()
60
+ else:
61
+ self.create_empty_company_info()
62
+
63
+ def create_empty_company_info(self):
64
+ """Create empty company information."""
65
+ self.company_info = {
66
+ "name": "",
67
+ "industry": "",
68
+ "location": "",
69
+ "export_markets": [],
70
+ "contact_person": "",
71
+ "email": "",
72
+ "phone": "",
73
+ "address": "",
74
+ "registration_number": "",
75
+ "reporting_year": datetime.now().year
76
+ }
77
+
78
+ def save_emissions_data(self):
79
+ """Save emissions data to file."""
80
+ # Convert datetime objects to strings
81
+ data_to_save = self.emissions_data.copy()
82
+ if 'date' in data_to_save.columns:
83
+ data_to_save['date'] = data_to_save['date'].dt.strftime('%Y-%m-%d')
84
+
85
+ with open(EMISSIONS_FILE, 'w') as f:
86
+ json.dump(data_to_save.to_dict('records'), f, indent=2)
87
+
88
+ def save_company_info(self):
89
+ """Save company information to file."""
90
+ with open(COMPANY_INFO_FILE, 'w') as f:
91
+ json.dump(self.company_info, f, indent=2)
92
+
93
+ def add_emission_entry(self, date, scope, category, activity, quantity, unit, emission_factor, notes=""):
94
+ """
95
+ Add a new emission entry.
96
+
97
+ Args:
98
+ date (datetime): Date of the emission
99
+ scope (str): Emission scope (Scope 1, Scope 2, or Scope 3)
100
+ category (str): Emission category
101
+ activity (str): Specific activity
102
+ quantity (float): Quantity of activity
103
+ unit (str): Unit of measurement
104
+ emission_factor (float): Emission factor
105
+ notes (str, optional): Additional notes
106
+
107
+ Returns:
108
+ bool: True if successful, False otherwise
109
+ """
110
+ try:
111
+ # Calculate emissions
112
+ emissions_kgCO2e = float(quantity) * float(emission_factor)
113
+
114
+ # Create new entry
115
+ new_entry = pd.DataFrame([{
116
+ 'date': pd.Timestamp(date),
117
+ 'scope': scope,
118
+ 'category': category,
119
+ 'activity': activity,
120
+ 'quantity': float(quantity),
121
+ 'unit': unit,
122
+ 'emission_factor': float(emission_factor),
123
+ 'emissions_kgCO2e': emissions_kgCO2e,
124
+ 'notes': notes
125
+ }])
126
+
127
+ # Append to existing data
128
+ self.emissions_data = pd.concat([self.emissions_data, new_entry], ignore_index=True)
129
+
130
+ # Save data
131
+ self.save_emissions_data()
132
+
133
+ return True
134
+ except Exception as e:
135
+ print(f"Error adding emission entry: {str(e)}")
136
+ return False
137
+
138
+ def import_csv(self, file_path_or_buffer):
139
+ """
140
+ Import emissions data from CSV.
141
+
142
+ Args:
143
+ file_path_or_buffer: Path to CSV file or file-like object
144
+
145
+ Returns:
146
+ tuple: (success, message)
147
+ """
148
+ try:
149
+ # Read CSV
150
+ df = pd.read_csv(file_path_or_buffer)
151
+
152
+ # Check required columns
153
+ required_columns = ['date', 'scope', 'category', 'activity', 'quantity', 'unit', 'emission_factor']
154
+ missing_columns = [col for col in required_columns if col not in df.columns]
155
+
156
+ if missing_columns:
157
+ return False, f"Missing required columns: {', '.join(missing_columns)}"
158
+
159
+ # Convert date strings to datetime objects
160
+ df['date'] = pd.to_datetime(df['date'])
161
+
162
+ # Calculate emissions if not provided
163
+ if 'emissions_kgCO2e' not in df.columns:
164
+ df['emissions_kgCO2e'] = df['quantity'].astype(float) * df['emission_factor'].astype(float)
165
+
166
+ # Add notes column if not present
167
+ if 'notes' not in df.columns:
168
+ df['notes'] = ""
169
+
170
+ # Append to existing data
171
+ self.emissions_data = pd.concat([self.emissions_data, df], ignore_index=True)
172
+
173
+ # Save data
174
+ self.save_emissions_data()
175
+
176
+ return True, f"Successfully imported {len(df)} entries"
177
+ except Exception as e:
178
+ return False, f"Error importing CSV: {str(e)}"
179
+
180
+ def export_csv(self, file_path=None, start_date=None, end_date=None):
181
+ """
182
+ Export emissions data to CSV.
183
+
184
+ Args:
185
+ file_path (str, optional): Path to save CSV file
186
+ start_date (datetime, optional): Start date for filtering
187
+ end_date (datetime, optional): End date for filtering
188
+
189
+ Returns:
190
+ str or bool: CSV string if file_path is None, otherwise True if successful
191
+ """
192
+ try:
193
+ # Filter data by date range if specified
194
+ data = self.emissions_data.copy()
195
+ if start_date and end_date:
196
+ mask = (data['date'] >= pd.Timestamp(start_date)) & (data['date'] <= pd.Timestamp(end_date))
197
+ data = data.loc[mask]
198
+
199
+ # Convert datetime objects to strings
200
+ if 'date' in data.columns:
201
+ data['date'] = data['date'].dt.strftime('%Y-%m-%d')
202
+
203
+ if file_path:
204
+ # Save to file
205
+ data.to_csv(file_path, index=False)
206
+ return True
207
+ else:
208
+ # Return CSV string
209
+ csv_buffer = StringIO()
210
+ data.to_csv(csv_buffer, index=False)
211
+ return csv_buffer.getvalue()
212
+ except Exception as e:
213
+ print(f"Error exporting CSV: {str(e)}")
214
+ return False
215
+
216
+ def generate_pdf_report(self, file_path=None, start_date=None, end_date=None):
217
+ """
218
+ Generate PDF report.
219
+
220
+ Args:
221
+ file_path (str, optional): Path to save PDF file
222
+ start_date (datetime, optional): Start date for filtering
223
+ end_date (datetime, optional): End date for filtering
224
+
225
+ Returns:
226
+ bytes or bool: PDF bytes if file_path is None, otherwise True if successful
227
+ """
228
+ try:
229
+ # Filter data by date range if specified
230
+ data = self.emissions_data.copy()
231
+ if start_date and end_date:
232
+ mask = (data['date'] >= pd.Timestamp(start_date)) & (data['date'] <= pd.Timestamp(end_date))
233
+ data = data.loc[mask]
234
+
235
+ # Create PDF
236
+ pdf = FPDF()
237
+ pdf.add_page()
238
+
239
+ # Set font
240
+ pdf.set_font("Arial", "B", 16)
241
+
242
+ # Title
243
+ pdf.cell(0, 10, "Carbon Emissions Report", 0, 1, "C")
244
+ pdf.set_font("Arial", "", 12)
245
+
246
+ # Company info
247
+ pdf.cell(0, 10, f"Company: {self.company_info['name']}", 0, 1)
248
+ pdf.cell(0, 10, f"Reporting Period: {start_date.strftime('%Y-%m-%d') if start_date else 'All'} to {end_date.strftime('%Y-%m-%d') if end_date else 'All'}", 0, 1)
249
+ pdf.cell(0, 10, f"Generated on: {datetime.now().strftime('%Y-%m-%d')}", 0, 1)
250
+
251
+ # Summary
252
+ pdf.ln(10)
253
+ pdf.set_font("Arial", "B", 14)
254
+ pdf.cell(0, 10, "Summary", 0, 1)
255
+ pdf.set_font("Arial", "", 12)
256
+
257
+ total_emissions = data['emissions_kgCO2e'].sum()
258
+ pdf.cell(0, 10, f"Total Emissions: {total_emissions:.2f} kgCO2e", 0, 1)
259
+
260
+ # Emissions by scope
261
+ scope_data = data.groupby('scope')['emissions_kgCO2e'].sum().reset_index()
262
+ pdf.ln(5)
263
+ pdf.cell(0, 10, "Emissions by Scope:", 0, 1)
264
+ for _, row in scope_data.iterrows():
265
+ pdf.cell(0, 10, f"{row['scope']}: {row['emissions_kgCO2e']:.2f} kgCO2e ({row['emissions_kgCO2e'] / total_emissions * 100:.1f}%)", 0, 1)
266
+
267
+ # Emissions by category
268
+ category_data = data.groupby('category')['emissions_kgCO2e'].sum().reset_index()
269
+ pdf.ln(5)
270
+ pdf.cell(0, 10, "Top Categories:", 0, 1)
271
+ for _, row in category_data.nlargest(5, 'emissions_kgCO2e').iterrows():
272
+ pdf.cell(0, 10, f"{row['category']}: {row['emissions_kgCO2e']:.2f} kgCO2e ({row['emissions_kgCO2e'] / total_emissions * 100:.1f}%)", 0, 1)
273
+
274
+ # Data table
275
+ pdf.ln(10)
276
+ pdf.set_font("Arial", "B", 14)
277
+ pdf.cell(0, 10, "Emissions Data", 0, 1)
278
+ pdf.set_font("Arial", "B", 10)
279
+
280
+ # Table header
281
+ col_widths = [25, 25, 30, 30, 20, 15, 25, 30]
282
+ headers = ['Date', 'Scope', 'Category', 'Activity', 'Quantity', 'Unit', 'Factor', 'Emissions (kgCO2e)']
283
+
284
+ for i, header in enumerate(headers):
285
+ pdf.cell(col_widths[i], 10, header, 1)
286
+ pdf.ln()
287
+
288
+ # Table data
289
+ pdf.set_font("Arial", "", 8)
290
+ for _, row in data.iterrows():
291
+ pdf.cell(col_widths[0], 10, row['date'].strftime('%Y-%m-%d') if isinstance(row['date'], pd.Timestamp) else str(row['date']), 1)
292
+ pdf.cell(col_widths[1], 10, str(row['scope']), 1)
293
+ pdf.cell(col_widths[2], 10, str(row['category']), 1)
294
+ pdf.cell(col_widths[3], 10, str(row['activity']), 1)
295
+ pdf.cell(col_widths[4], 10, f"{row['quantity']:.2f}", 1)
296
+ pdf.cell(col_widths[5], 10, str(row['unit']), 1)
297
+ pdf.cell(col_widths[6], 10, f"{row['emission_factor']:.4f}", 1)
298
+ pdf.cell(col_widths[7], 10, f"{row['emissions_kgCO2e']:.2f}", 1)
299
+ pdf.ln()
300
+
301
+ if file_path:
302
+ # Save to file
303
+ pdf.output(file_path)
304
+ return True
305
+ else:
306
+ # Return PDF bytes
307
+ return pdf.output(dest='S').encode('latin1')
308
+ except Exception as e:
309
+ print(f"Error generating PDF report: {str(e)}")
310
+ return False
311
+
312
+ def get_emissions_summary(self):
313
+ """
314
+ Get emissions summary statistics.
315
+
316
+ Returns:
317
+ dict: Summary statistics
318
+ """
319
+ if len(self.emissions_data) == 0:
320
+ return {
321
+ "total_emissions": 0,
322
+ "scope_breakdown": {},
323
+ "category_breakdown": {},
324
+ "time_series": {}
325
+ }
326
+
327
+ # Total emissions
328
+ total_emissions = self.emissions_data['emissions_kgCO2e'].sum()
329
+
330
+ # Emissions by scope
331
+ scope_data = self.emissions_data.groupby('scope')['emissions_kgCO2e'].sum().to_dict()
332
+
333
+ # Emissions by category
334
+ category_data = self.emissions_data.groupby('category')['emissions_kgCO2e'].sum().to_dict()
335
+
336
+ # Time series data (monthly)
337
+ time_data = self.emissions_data.copy()
338
+ if 'date' in time_data.columns and len(time_data) > 0:
339
+ time_data['month'] = time_data['date'].dt.strftime('%Y-%m')
340
+ time_series = time_data.groupby(['month', 'scope'])['emissions_kgCO2e'].sum().reset_index()
341
+ time_series_dict = {}
342
+ for _, row in time_series.iterrows():
343
+ if row['month'] not in time_series_dict:
344
+ time_series_dict[row['month']] = {}
345
+ time_series_dict[row['month']][row['scope']] = row['emissions_kgCO2e']
346
+ else:
347
+ time_series_dict = {}
348
+
349
+ return {
350
+ "total_emissions": total_emissions,
351
+ "scope_breakdown": scope_data,
352
+ "category_breakdown": category_data,
353
+ "time_series": time_series_dict
354
+ }
355
+
356
+ def get_filtered_data(self, start_date=None, end_date=None, scope=None, category=None):
357
+ """
358
+ Get filtered emissions data.
359
+
360
+ Args:
361
+ start_date (datetime, optional): Start date for filtering
362
+ end_date (datetime, optional): End date for filtering
363
+ scope (str, optional): Scope for filtering
364
+ category (str, optional): Category for filtering
365
+
366
+ Returns:
367
+ pandas.DataFrame: Filtered data
368
+ """
369
+ data = self.emissions_data.copy()
370
+
371
+ # Apply filters
372
+ if start_date and end_date:
373
+ mask = (data['date'] >= pd.Timestamp(start_date)) & (data['date'] <= pd.Timestamp(end_date))
374
+ data = data.loc[mask]
375
+
376
+ if scope:
377
+ data = data[data['scope'] == scope]
378
+
379
+ if category:
380
+ data = data[data['category'] == category]
381
+
382
+ return data
emission_factors.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Emission factors database for CarbonFootprint by GXS application.
3
+ Based on DEFRA/IPCC datasets for common emission sources.
4
+ """
5
+
6
+ # Emission factors by category (in kgCO2e per unit)
7
+ EMISSION_FACTORS = {
8
+ # Scope 1 - Direct emissions
9
+ "Stationary Combustion": {
10
+ "Natural Gas": {"factor": 0.18316, "unit": "kWh"},
11
+ "Diesel": {"factor": 2.68787, "unit": "liter"},
12
+ "LPG": {"factor": 1.55537, "unit": "kg"},
13
+ "Coal": {"factor": 2.42287, "unit": "kg"},
14
+ },
15
+ "Mobile Combustion": {
16
+ "Petrol/Gasoline": {"factor": 2.31495, "unit": "liter"},
17
+ "Diesel": {"factor": 2.70553, "unit": "liter"},
18
+ "LPG": {"factor": 1.55537, "unit": "liter"},
19
+ "CNG": {"factor": 2.53721, "unit": "kg"},
20
+ },
21
+ "Refrigerants": {
22
+ "R-410A": {"factor": 2088.0, "unit": "kg"},
23
+ "R-134a": {"factor": 1430.0, "unit": "kg"},
24
+ "R-404A": {"factor": 3922.0, "unit": "kg"},
25
+ "R-407C": {"factor": 1774.0, "unit": "kg"},
26
+ },
27
+
28
+ # Scope 2 - Indirect emissions from purchased energy
29
+ "Electricity": {
30
+ "Vietnam Grid": {"factor": 0.6592, "unit": "kWh"},
31
+ "India Grid": {"factor": 0.82, "unit": "kWh"},
32
+ "Indonesia Grid": {"factor": 0.87, "unit": "kWh"},
33
+ "Japan Grid": {"factor": 0.47, "unit": "kWh"},
34
+ "Solar Power": {"factor": 0.041, "unit": "kWh"},
35
+ "Wind Power": {"factor": 0.011, "unit": "kWh"},
36
+ },
37
+ "Steam": {
38
+ "Purchased Steam": {"factor": 0.19, "unit": "kg"},
39
+ },
40
+ "District Cooling": {
41
+ "District Cooling": {"factor": 0.12, "unit": "kWh"},
42
+ },
43
+
44
+ # Scope 3 - Other indirect emissions
45
+ "Business Travel": {
46
+ "Short-haul Flight": {"factor": 0.15298, "unit": "passenger-km"},
47
+ "Long-haul Flight": {"factor": 0.19085, "unit": "passenger-km"},
48
+ "Train": {"factor": 0.03694, "unit": "passenger-km"},
49
+ "Bus": {"factor": 0.10471, "unit": "passenger-km"},
50
+ "Taxi": {"factor": 0.14549, "unit": "km"},
51
+ },
52
+ "Employee Commuting": {
53
+ "Car (Petrol/Gasoline)": {"factor": 0.17336, "unit": "km"},
54
+ "Car (Diesel)": {"factor": 0.16844, "unit": "km"},
55
+ "Motorcycle": {"factor": 0.11501, "unit": "km"},
56
+ "Bus": {"factor": 0.10471, "unit": "passenger-km"},
57
+ "Train/Metro": {"factor": 0.03694, "unit": "passenger-km"},
58
+ },
59
+ "Waste": {
60
+ "Landfill": {"factor": 0.45727, "unit": "kg"},
61
+ "Recycling": {"factor": 0.01042, "unit": "kg"},
62
+ "Composting": {"factor": 0.01042, "unit": "kg"},
63
+ "Incineration": {"factor": 0.01613, "unit": "kg"},
64
+ },
65
+ "Water": {
66
+ "Water Supply": {"factor": 0.344, "unit": "cubic meter"},
67
+ "Water Treatment": {"factor": 0.708, "unit": "cubic meter"},
68
+ },
69
+ "Purchased Goods & Services": {
70
+ "Paper": {"factor": 0.919, "unit": "kg"},
71
+ "Plastic": {"factor": 3.14, "unit": "kg"},
72
+ "Glass": {"factor": 0.85, "unit": "kg"},
73
+ "Metal": {"factor": 1.37, "unit": "kg"},
74
+ "Food": {"factor": 3.59, "unit": "kg"},
75
+ },
76
+ }
77
+
78
+ # Scope categories
79
+ SCOPE_CATEGORIES = {
80
+ "Scope 1": [
81
+ "Stationary Combustion",
82
+ "Mobile Combustion",
83
+ "Refrigerants",
84
+ "Process Emissions",
85
+ "Fugitive Emissions"
86
+ ],
87
+ "Scope 2": [
88
+ "Electricity",
89
+ "Steam",
90
+ "District Cooling",
91
+ "District Heating"
92
+ ],
93
+ "Scope 3": [
94
+ "Business Travel",
95
+ "Employee Commuting",
96
+ "Waste",
97
+ "Water",
98
+ "Purchased Goods & Services",
99
+ "Capital Goods",
100
+ "Fuel and Energy-Related Activities",
101
+ "Upstream Transportation & Distribution",
102
+ "Downstream Transportation & Distribution",
103
+ "Use of Sold Products",
104
+ "End-of-Life Treatment of Sold Products",
105
+ "Leased Assets",
106
+ "Franchises",
107
+ "Investments"
108
+ ]
109
+ }
110
+
111
+ # Get emission factor for a specific activity
112
+ def get_emission_factor(category, activity):
113
+ """
114
+ Get the emission factor for a specific activity within a category.
115
+
116
+ Args:
117
+ category (str): The emission category
118
+ activity (str): The specific activity
119
+
120
+ Returns:
121
+ dict: Dictionary containing factor and unit, or None if not found
122
+ """
123
+ if category in EMISSION_FACTORS and activity in EMISSION_FACTORS[category]:
124
+ return EMISSION_FACTORS[category][activity]
125
+ return None
126
+
127
+ # Get all activities for a category
128
+ def get_activities(category):
129
+ """
130
+ Get all activities for a specific category.
131
+
132
+ Args:
133
+ category (str): The emission category
134
+
135
+ Returns:
136
+ list: List of activities for the category, or empty list if category not found
137
+ """
138
+ if category in EMISSION_FACTORS:
139
+ return list(EMISSION_FACTORS[category].keys())
140
+ return []
141
+
142
+ # Get all categories for a scope
143
+ def get_categories(scope):
144
+ """
145
+ Get all categories for a specific scope.
146
+
147
+ Args:
148
+ scope (str): The scope (Scope 1, Scope 2, or Scope 3)
149
+
150
+ Returns:
151
+ list: List of categories for the scope, or empty list if scope not found
152
+ """
153
+ if scope in SCOPE_CATEGORIES:
154
+ return SCOPE_CATEGORIES[scope]
155
+ return []
156
+
157
+ # Get unit for a specific activity
158
+ def get_unit(category, activity):
159
+ """
160
+ Get the unit for a specific activity within a category.
161
+
162
+ Args:
163
+ category (str): The emission category
164
+ activity (str): The specific activity
165
+
166
+ Returns:
167
+ str: Unit for the activity, or None if not found
168
+ """
169
+ ef = get_emission_factor(category, activity)
170
+ if ef:
171
+ return ef["unit"]
172
+ return None
pyproject.toml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "yourcarbonfootprint"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "crewai>=0.140.0",
9
+ "crewai-tools>=0.49.0",
10
+ "langchain>=0.3.26",
11
+ "langchain-community>=0.3.27",
12
+ "langchain-core>=0.3.68",
13
+ "langchain-google-genai>=2.1.6",
14
+ "langchain-groq>=0.3.5",
15
+ "plotly>=6.2.0",
16
+ "python-dotenv>=1.1.1",
17
+ "streamlit>=1.46.1",
18
+ "xlsxwriter>=3.2.5",
19
+ ]
report_generator.py ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Report generator for CarbonFootprint by GXS application.
3
+ Generates PDF reports and visualizations.
4
+ """
5
+
6
+ import pandas as pd
7
+ import matplotlib.pyplot as plt
8
+ import seaborn as sns
9
+ import plotly.express as px
10
+ import plotly.graph_objects as go
11
+ from fpdf import FPDF
12
+ import os
13
+ from datetime import datetime
14
+ import base64
15
+ from io import BytesIO
16
+
17
+ class ReportGenerator:
18
+ def __init__(self, data_handler):
19
+ """Initialize the ReportGenerator class."""
20
+ self.data_handler = data_handler
21
+
22
+ def generate_pdf_report(self, file_path=None, start_date=None, end_date=None, company_info=None):
23
+ """
24
+ Generate PDF report.
25
+
26
+ Args:
27
+ file_path (str, optional): Path to save PDF file
28
+ start_date (datetime, optional): Start date for filtering
29
+ end_date (datetime, optional): End date for filtering
30
+ company_info (dict, optional): Company information
31
+
32
+ Returns:
33
+ bytes or bool: PDF bytes if file_path is None, otherwise True if successful
34
+ """
35
+ try:
36
+ # Get filtered data
37
+ data = self.data_handler.get_filtered_data(start_date, end_date)
38
+
39
+ if len(data) == 0:
40
+ return False, "No data available for the selected period."
41
+
42
+ # Create PDF
43
+ pdf = FPDF()
44
+ pdf.add_page()
45
+
46
+ # Set font
47
+ pdf.set_font("Arial", "B", 16)
48
+
49
+ # Title
50
+ pdf.cell(0, 10, "Carbon Emissions Report", 0, 1, "C")
51
+ pdf.set_font("Arial", "", 12)
52
+
53
+ # Company info
54
+ if company_info:
55
+ pdf.cell(0, 10, f"Company: {company_info.get('name', 'N/A')}", 0, 1)
56
+ pdf.cell(0, 10, f"Industry: {company_info.get('industry', 'N/A')}", 0, 1)
57
+ pdf.cell(0, 10, f"Location: {company_info.get('location', 'N/A')}", 0, 1)
58
+
59
+ # Reporting period
60
+ pdf.cell(0, 10, f"Reporting Period: {start_date.strftime('%Y-%m-%d') if start_date else 'All'} to {end_date.strftime('%Y-%m-%d') if end_date else 'All'}", 0, 1)
61
+ pdf.cell(0, 10, f"Generated on: {datetime.now().strftime('%Y-%m-%d')}", 0, 1)
62
+
63
+ # Summary
64
+ pdf.ln(10)
65
+ pdf.set_font("Arial", "B", 14)
66
+ pdf.cell(0, 10, "Summary", 0, 1)
67
+ pdf.set_font("Arial", "", 12)
68
+
69
+ total_emissions = data['emissions_kgCO2e'].sum()
70
+ pdf.cell(0, 10, f"Total Emissions: {total_emissions:.2f} kgCO2e", 0, 1)
71
+
72
+ # Emissions by scope
73
+ scope_data = data.groupby('scope')['emissions_kgCO2e'].sum().reset_index()
74
+ pdf.ln(5)
75
+ pdf.cell(0, 10, "Emissions by Scope:", 0, 1)
76
+ for _, row in scope_data.iterrows():
77
+ pdf.cell(0, 10, f"{row['scope']}: {row['emissions_kgCO2e']:.2f} kgCO2e ({row['emissions_kgCO2e'] / total_emissions * 100:.1f}%)", 0, 1)
78
+
79
+ # Emissions by category
80
+ category_data = data.groupby('category')['emissions_kgCO2e'].sum().reset_index()
81
+ pdf.ln(5)
82
+ pdf.cell(0, 10, "Top Categories:", 0, 1)
83
+ for _, row in category_data.nlargest(5, 'emissions_kgCO2e').iterrows():
84
+ pdf.cell(0, 10, f"{row['category']}: {row['emissions_kgCO2e']:.2f} kgCO2e ({row['emissions_kgCO2e'] / total_emissions * 100:.1f}%)", 0, 1)
85
+
86
+ # Data table
87
+ pdf.ln(10)
88
+ pdf.set_font("Arial", "B", 14)
89
+ pdf.cell(0, 10, "Emissions Data", 0, 1)
90
+ pdf.set_font("Arial", "B", 10)
91
+
92
+ # Table header
93
+ col_widths = [25, 25, 30, 30, 20, 15, 25, 30]
94
+ headers = ['Date', 'Scope', 'Category', 'Activity', 'Quantity', 'Unit', 'Factor', 'Emissions (kgCO2e)']
95
+
96
+ for i, header in enumerate(headers):
97
+ pdf.cell(col_widths[i], 10, header, 1)
98
+ pdf.ln()
99
+
100
+ # Table data
101
+ pdf.set_font("Arial", "", 8)
102
+ for _, row in data.iterrows():
103
+ pdf.cell(col_widths[0], 10, row['date'].strftime('%Y-%m-%d') if isinstance(row['date'], pd.Timestamp) else str(row['date']), 1)
104
+ pdf.cell(col_widths[1], 10, str(row['scope']), 1)
105
+ pdf.cell(col_widths[2], 10, str(row['category']), 1)
106
+ pdf.cell(col_widths[3], 10, str(row['activity']), 1)
107
+ pdf.cell(col_widths[4], 10, f"{row['quantity']:.2f}", 1)
108
+ pdf.cell(col_widths[5], 10, str(row['unit']), 1)
109
+ pdf.cell(col_widths[6], 10, f"{row['emission_factor']:.4f}", 1)
110
+ pdf.cell(col_widths[7], 10, f"{row['emissions_kgCO2e']:.2f}", 1)
111
+ pdf.ln()
112
+
113
+ # Compliance section
114
+ pdf.ln(10)
115
+ pdf.set_font("Arial", "B", 14)
116
+ pdf.cell(0, 10, "Regulatory Compliance", 0, 1)
117
+ pdf.set_font("Arial", "", 12)
118
+
119
+ pdf.cell(0, 10, "EU CBAM: This report can be used as supporting documentation for EU CBAM compliance.", 0, 1)
120
+ pdf.cell(0, 10, "Japan GX League: This report follows the GX League reporting format.", 0, 1)
121
+ pdf.cell(0, 10, "Indonesia ETS/ETP: This report can be used for Indonesia ETS/ETP compliance.", 0, 1)
122
+
123
+ # Recommendations
124
+ pdf.ln(10)
125
+ pdf.set_font("Arial", "B", 14)
126
+ pdf.cell(0, 10, "Recommendations", 0, 1)
127
+ pdf.set_font("Arial", "", 12)
128
+
129
+ pdf.cell(0, 10, "1. Focus on reducing emissions from the top categories identified in this report.", 0, 1)
130
+ pdf.cell(0, 10, "2. Consider implementing energy efficiency measures for Scope 2 emissions.", 0, 1)
131
+ pdf.cell(0, 10, "3. Explore renewable energy options to reduce your carbon footprint.", 0, 1)
132
+ pdf.cell(0, 10, "4. Engage with suppliers to address Scope 3 emissions in your value chain.", 0, 1)
133
+
134
+ if file_path:
135
+ # Save to file
136
+ pdf.output(file_path)
137
+ return True, "Report generated successfully."
138
+ else:
139
+ # Return PDF bytes
140
+ return pdf.output(dest='S').encode('latin1'), "Report generated successfully."
141
+ except Exception as e:
142
+ return False, f"Error generating PDF report: {str(e)}"
143
+
144
+ def create_scope_pie_chart(self, data):
145
+ """
146
+ Create pie chart of emissions by scope.
147
+
148
+ Args:
149
+ data (pandas.DataFrame): Emissions data
150
+
151
+ Returns:
152
+ plotly.graph_objects.Figure: Pie chart figure
153
+ """
154
+ scope_data = data.groupby('scope')['emissions_kgCO2e'].sum().reset_index()
155
+ fig = px.pie(
156
+ scope_data,
157
+ values='emissions_kgCO2e',
158
+ names='scope',
159
+ color='scope',
160
+ color_discrete_map={
161
+ 'Scope 1': '#4CAF50',
162
+ 'Scope 2': '#2196F3',
163
+ 'Scope 3': '#FFC107'
164
+ },
165
+ title='Emissions by Scope'
166
+ )
167
+ fig.update_layout(
168
+ legend_title="Scope",
169
+ font=dict(size=12),
170
+ margin=dict(t=50, b=20, l=20, r=20)
171
+ )
172
+ return fig
173
+
174
+ def create_category_bar_chart(self, data):
175
+ """
176
+ Create bar chart of emissions by category.
177
+
178
+ Args:
179
+ data (pandas.DataFrame): Emissions data
180
+
181
+ Returns:
182
+ plotly.graph_objects.Figure: Bar chart figure
183
+ """
184
+ category_data = data.groupby('category')['emissions_kgCO2e'].sum().reset_index()
185
+ category_data = category_data.sort_values('emissions_kgCO2e', ascending=False)
186
+ fig = px.bar(
187
+ category_data,
188
+ x='category',
189
+ y='emissions_kgCO2e',
190
+ color='category',
191
+ title='Emissions by Category'
192
+ )
193
+ fig.update_layout(
194
+ xaxis_title="Category",
195
+ yaxis_title="Emissions (kgCO2e)",
196
+ legend_title="Category",
197
+ font=dict(size=12),
198
+ margin=dict(t=50, b=100, l=50, r=20),
199
+ xaxis_tickangle=-45
200
+ )
201
+ return fig
202
+
203
+ def create_time_series_chart(self, data):
204
+ """
205
+ Create time series chart of emissions over time.
206
+
207
+ Args:
208
+ data (pandas.DataFrame): Emissions data
209
+
210
+ Returns:
211
+ plotly.graph_objects.Figure: Line chart figure
212
+ """
213
+ if 'date' not in data.columns or len(data) == 0:
214
+ # Create empty figure if no data
215
+ fig = go.Figure()
216
+ fig.update_layout(
217
+ title='Emissions Over Time',
218
+ xaxis_title="Month",
219
+ yaxis_title="Emissions (kgCO2e)",
220
+ font=dict(size=12),
221
+ margin=dict(t=50, b=50, l=50, r=20)
222
+ )
223
+ return fig
224
+
225
+ # Group by month and scope
226
+ time_data = data.copy()
227
+ time_data['month'] = pd.to_datetime(time_data['date']).dt.strftime('%Y-%m')
228
+ time_data = time_data.groupby(['month', 'scope'])['emissions_kgCO2e'].sum().reset_index()
229
+
230
+ fig = px.line(
231
+ time_data,
232
+ x='month',
233
+ y='emissions_kgCO2e',
234
+ color='scope',
235
+ markers=True,
236
+ title='Emissions Over Time'
237
+ )
238
+ fig.update_layout(
239
+ xaxis_title="Month",
240
+ yaxis_title="Emissions (kgCO2e)",
241
+ legend_title="Scope",
242
+ font=dict(size=12),
243
+ margin=dict(t=50, b=50, l=50, r=20)
244
+ )
245
+ return fig
246
+
247
+ def create_activity_treemap(self, data):
248
+ """
249
+ Create treemap of emissions by scope, category, and activity.
250
+
251
+ Args:
252
+ data (pandas.DataFrame): Emissions data
253
+
254
+ Returns:
255
+ plotly.graph_objects.Figure: Treemap figure
256
+ """
257
+ fig = px.treemap(
258
+ data,
259
+ path=['scope', 'category', 'activity'],
260
+ values='emissions_kgCO2e',
261
+ color='scope',
262
+ color_discrete_map={
263
+ 'Scope 1': '#4CAF50',
264
+ 'Scope 2': '#2196F3',
265
+ 'Scope 3': '#FFC107'
266
+ },
267
+ title='Emissions Breakdown'
268
+ )
269
+ fig.update_layout(
270
+ margin=dict(t=50, b=20, l=20, r=20),
271
+ font=dict(size=12)
272
+ )
273
+ return fig
274
+
275
+ def create_monthly_comparison_chart(self, data):
276
+ """
277
+ Create bar chart comparing emissions by month.
278
+
279
+ Args:
280
+ data (pandas.DataFrame): Emissions data
281
+
282
+ Returns:
283
+ plotly.graph_objects.Figure: Bar chart figure
284
+ """
285
+ if 'date' not in data.columns or len(data) == 0:
286
+ # Create empty figure if no data
287
+ fig = go.Figure()
288
+ fig.update_layout(
289
+ title='Monthly Emissions Comparison',
290
+ xaxis_title="Month",
291
+ yaxis_title="Emissions (kgCO2e)",
292
+ font=dict(size=12),
293
+ margin=dict(t=50, b=50, l=50, r=20)
294
+ )
295
+ return fig
296
+
297
+ # Group by month
298
+ monthly_data = data.copy()
299
+ monthly_data['month'] = pd.to_datetime(monthly_data['date']).dt.strftime('%Y-%m')
300
+ monthly_data = monthly_data.groupby('month')['emissions_kgCO2e'].sum().reset_index()
301
+
302
+ fig = px.bar(
303
+ monthly_data,
304
+ x='month',
305
+ y='emissions_kgCO2e',
306
+ title='Monthly Emissions Comparison'
307
+ )
308
+ fig.update_layout(
309
+ xaxis_title="Month",
310
+ yaxis_title="Emissions (kgCO2e)",
311
+ font=dict(size=12),
312
+ margin=dict(t=50, b=50, l=50, r=20)
313
+ )
314
+ return fig
requirements.txt CHANGED
@@ -1,3 +1,14 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ python-dotenv
3
+ crewai
4
+ crewai_tools
5
+ pandas
6
+ plotly
7
+ matplotlib
8
+ seaborn
9
+ fpdf
10
+ langchain_groq
11
+ faiss-cpu
12
+ gunicorn
13
+ chromadb
14
+ pysqlite3-binary
uv.lock ADDED
The diff for this file is too large to render. See raw diff