├── .gitignore ├── .env.example ├── charts_sales_performance ├── total_sales_over_time.png └── opportunity_stage_distribution.png ├── README.md ├── pipeline_analysis.md ├── final_sales_report.md ├── requirements.txt └── app.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | SALESFORCE_USERNAME= 3 | SALESFORCE_PASSWORD= 4 | SALESFORCE_SECURITY_TOKEN= 5 | SALESFORCE_INSTANCE= 6 | SALESFORCE_INSTANCE_URL= -------------------------------------------------------------------------------- /charts_sales_performance/total_sales_over_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hollaugo/crewai-sales-report-generator/HEAD/charts_sales_performance/total_sales_over_time.png -------------------------------------------------------------------------------- /charts_sales_performance/opportunity_stage_distribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hollaugo/crewai-sales-report-generator/HEAD/charts_sales_performance/opportunity_stage_distribution.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # CrewAI - Salesforce Sales Report Generator 3 | 4 | This application automates the extraction of sales data from Salesforce, analyzes the data, generates visual reports, and compiles a comprehensive sales performance report. It's designed to give insights into the entire sales pipeline, highlighting trends, opportunities, and areas needing attention. 5 | 6 | ## Features 7 | 8 | - **Data Extraction**: Automatically fetches sales opportunities data from Salesforce. 9 | - **Data Analysis**: Analyzes the sales data to identify key metrics and trends. 10 | - **Report Generation**: Generates visual charts and a comprehensive markdown report covering the entire sales pipeline. 11 | 12 | ## Getting Started 13 | 14 | ### Prerequisites 15 | 16 | - Python 3.8 or newer. 17 | - A Salesforce account with access to the Salesforce API. 18 | - An OpenAI API key if leveraging advanced analytics or AI features. 19 | 20 | ### Installation 21 | 22 | 1. Clone this repository to your local machine. 23 | 2. Install the required Python packages: 24 | 25 | ```bash 26 | pip install -r requirements.txt 27 | ``` 28 | 29 | #### Renaming the .env.example file 30 | 31 | 1. Rename the `.env.example` file in the project root directory to `.env`. 32 | 33 | #### Adding the appropriate keys in the .env file 34 | 35 | 1. Open the `.env` file. 36 | 2. Add the necessary keys and their corresponding values for your application. 37 | 38 | ### Running the app with Python 39 | 40 | 41 | ```bash 42 | python app.py 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /pipeline_analysis.md: -------------------------------------------------------------------------------- 1 | # Sales Performance Report 2 | 3 | ## Data Summary 4 | 5 | Opportunities data was fetched from Salesforce and analyzed. The data includes details such as 'Opportunity Name', 'Account Name', 'Amount', 'Stage', and 'Close Date'. The analysis focused on 'Amount', 'Stage', and 'Close Date'. 6 | 7 | ## Key Insights 8 | 9 | Two charts were generated to illustrate key insights: 10 | 11 | 1. **Total Sales Over Time**: This chart provides a visual representation of how total sales have changed over time. The time period covered is from November 2023 to February 2024. 12 | 13 | ![Total Sales Over Time](charts_sales_performance/total_sales_over_time.png) 14 | 15 | 2. **Opportunity Stage Distribution**: This chart shows the distribution of opportunities across different stages. It helps identify which stages have the most opportunities and where there might be bottlenecks in the sales pipeline. 16 | 17 | ![Opportunity Stage Distribution](charts_sales_performance/opportunity_stage_distribution.png) 18 | 19 | ## Limitations 20 | 21 | The charts were generated using a subset of the opportunities data due to constraints in data processing. Therefore, the insights should be considered illustrative rather than comprehensive. With the correct data format ('Amount', 'StageName', and 'CloseDate'), the full dataset can be processed to provide a more comprehensive view of the sales pipeline. 22 | 23 | ## Conclusion 24 | 25 | The analysis provides an initial understanding of the sales pipeline and performance. To make strategic decisions, it is recommended to process the full dataset in the correct format and update the visualizations accordingly. -------------------------------------------------------------------------------- /final_sales_report.md: -------------------------------------------------------------------------------- 1 | # Sales Performance Report 2 | 3 | ## Executive Summary 4 | 5 | This report presents a detailed analysis of the sales data obtained from Salesforce. It includes a comprehensive examination of sales over time and the distribution of opportunities across various stages. 6 | 7 | ## Analysis 8 | 9 | ### Data Summary 10 | 11 | The data analyzed comprises essential details such as 'Opportunity Name', 'Account Name', 'Amount', 'Stage', and 'Close Date', focusing on 'Amount', 'Stage', and 'Close Date'. These factors were chosen to better understand the progression of sales and to identify potential bottlenecks in the sales pipeline. 12 | 13 | ### Key Insights 14 | 15 | Two primary insights were drawn from our data analysis: 16 | 17 | 1. **Total Sales Over Time**: The chart below provides a visual representation of the total sales from November 2023 to February 2024. It allows us to understand the sales trend over this period. 18 | 19 | ![Total Sales Over Time](charts_sales_performance/total_sales_over_time.png) 20 | 21 | 2. **Opportunity Stage Distribution**: This chart shows the number of opportunities at each stage, highlighting potential bottlenecks in the sales pipeline. 22 | 23 | ![Opportunity Stage Distribution](charts_sales_performance/opportunity_stage_distribution.png) 24 | 25 | ### Limitations 26 | 27 | These insights are based on a subset of the available data due to data processing constraints. Therefore, while they offer a useful overview, they may not present a completely comprehensive picture of the sales pipeline. To obtain a more detailed understanding, it would be beneficial to process the full dataset, given the correct data format ('Amount', 'StageName', and 'CloseDate'). 28 | 29 | ## Conclusion and Recommendations 30 | 31 | The current analysis offers an initial understanding of the sales pipeline and performance. However, to make more strategic decisions, it is recommended to process the full dataset with the correct format and update the visualizations accordingly. By doing so, we can gain a more holistic understanding of our sales performance and make more informed decisions moving forward. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.9.3 2 | aiosignal==1.3.1 3 | alembic==1.13.1 4 | annotated-types==0.6.0 5 | anyio==4.3.0 6 | asgiref==3.7.2 7 | attrs==23.2.0 8 | backoff==2.2.1 9 | bcrypt==4.1.2 10 | beautifulsoup4==4.12.3 11 | Brotli==1.1.0 12 | build==1.1.1 13 | cachetools==5.3.3 14 | certifi==2024.2.2 15 | cffi==1.16.0 16 | charset-normalizer==3.3.2 17 | chroma-hnswlib==0.7.3 18 | chromadb==0.4.24 19 | click==8.1.7 20 | coloredlogs==15.0.1 21 | contextlib2==21.6.0 22 | contourpy==1.2.0 23 | crewai==0.22.5 24 | crewai-tools==0.0.15 25 | cryptography==42.0.5 26 | cycler==0.12.1 27 | dataclasses-json==0.6.4 28 | decorator==5.1.1 29 | Deprecated==1.2.14 30 | deprecation==2.1.0 31 | distro==1.9.0 32 | docstring-parser==0.15 33 | embedchain==0.1.97 34 | fastapi==0.110.0 35 | filelock==3.13.1 36 | flatbuffers==24.3.7 37 | fonttools==4.49.0 38 | frozenlist==1.4.1 39 | fsspec==2024.2.0 40 | gitdb==4.0.11 41 | GitPython==3.1.42 42 | google-api-core==2.17.1 43 | google-auth==2.28.2 44 | google-cloud-aiplatform==1.44.0 45 | google-cloud-bigquery==3.19.0 46 | google-cloud-core==2.4.1 47 | google-cloud-resource-manager==1.12.3 48 | google-cloud-storage==2.15.0 49 | google-crc32c==1.5.0 50 | google-resumable-media==2.7.0 51 | googleapis-common-protos==1.63.0 52 | gptcache==0.1.43 53 | grpc-google-iam-v1==0.13.0 54 | grpcio==1.62.1 55 | grpcio-status==1.62.1 56 | h11==0.14.0 57 | httpcore==1.0.4 58 | httptools==0.6.1 59 | httpx==0.27.0 60 | huggingface-hub==0.21.4 61 | humanfriendly==10.0 62 | idna==3.6 63 | importlib-metadata==6.11.0 64 | importlib_resources==6.3.0 65 | iniconfig==2.0.0 66 | instructor==0.5.2 67 | isodate==0.6.1 68 | jsonpatch==1.33 69 | jsonpointer==2.4 70 | kiwisolver==1.4.5 71 | kubernetes==29.0.0 72 | lancedb==0.5.7 73 | langchain==0.1.12 74 | langchain-community==0.0.28 75 | langchain-core==0.1.32 76 | langchain-openai==0.0.5 77 | langchain-text-splitters==0.0.1 78 | langsmith==0.1.26 79 | lxml==5.1.0 80 | Mako==1.3.2 81 | markdown-it-py==3.0.0 82 | MarkupSafe==2.1.5 83 | marshmallow==3.21.1 84 | matplotlib==3.8.3 85 | mdurl==0.1.2 86 | mmh3==4.1.0 87 | monotonic==1.6 88 | more-itertools==10.2.0 89 | mpmath==1.3.0 90 | multidict==6.0.5 91 | mutagen==1.47.0 92 | mypy-extensions==1.0.0 93 | nodeenv==1.8.0 94 | numpy==1.26.4 95 | oauthlib==3.2.2 96 | onnxruntime==1.17.1 97 | openai==1.14.0 98 | opentelemetry-api==1.23.0 99 | opentelemetry-exporter-otlp-proto-common==1.23.0 100 | opentelemetry-exporter-otlp-proto-grpc==1.23.0 101 | opentelemetry-exporter-otlp-proto-http==1.23.0 102 | opentelemetry-instrumentation==0.44b0 103 | opentelemetry-instrumentation-asgi==0.44b0 104 | opentelemetry-instrumentation-fastapi==0.44b0 105 | opentelemetry-proto==1.23.0 106 | opentelemetry-sdk==1.23.0 107 | opentelemetry-semantic-conventions==0.44b0 108 | opentelemetry-util-http==0.44b0 109 | orjson==3.9.15 110 | outcome==1.3.0.post0 111 | overrides==7.7.0 112 | packaging==23.2 113 | pandas==2.2.1 114 | pendulum==3.0.0 115 | pillow==10.2.0 116 | platformdirs==4.2.0 117 | pluggy==1.4.0 118 | posthog==3.5.0 119 | proto-plus==1.23.0 120 | protobuf==4.25.3 121 | pulsar-client==3.4.0 122 | py==1.11.0 123 | pyarrow==15.0.1 124 | pyasn1==0.5.1 125 | pyasn1-modules==0.3.0 126 | pycparser==2.21 127 | pycryptodomex==3.20.0 128 | pydantic==2.6.4 129 | pydantic_core==2.16.3 130 | PyGithub==1.59.1 131 | Pygments==2.17.2 132 | PyJWT==2.8.0 133 | pylance==0.9.18 134 | PyNaCl==1.5.0 135 | pyparsing==3.1.2 136 | pypdf==3.17.4 137 | PyPika==0.48.9 138 | pyproject_hooks==1.0.0 139 | pyright==1.1.354 140 | pysbd==0.3.4 141 | PySocks==1.7.1 142 | pytest==8.1.1 143 | python-dateutil==2.9.0.post0 144 | python-dotenv==1.0.0 145 | pytube==15.0.0 146 | pytz==2024.1 147 | PyYAML==6.0.1 148 | ratelimiter==1.2.0.post0 149 | regex==2023.12.25 150 | requests==2.31.0 151 | requests-file==2.0.0 152 | requests-oauthlib==1.4.0 153 | requests-toolbelt==1.0.0 154 | retry==0.9.2 155 | rich==13.7.1 156 | rsa==4.9 157 | schema==0.7.5 158 | selenium==4.18.1 159 | semver==3.0.2 160 | setuptools==69.2.0 161 | shapely==2.0.3 162 | simple-salesforce==1.12.5 163 | six==1.16.0 164 | smmap==5.0.1 165 | sniffio==1.3.1 166 | sortedcontainers==2.4.0 167 | soupsieve==2.5 168 | SQLAlchemy==2.0.28 169 | starlette==0.36.3 170 | sympy==1.12 171 | tenacity==8.2.3 172 | tiktoken==0.5.2 173 | time-machine==2.14.0 174 | tokenizers==0.15.2 175 | tqdm==4.66.2 176 | trio==0.24.0 177 | trio-websocket==0.11.1 178 | typer==0.9.0 179 | typing-inspect==0.9.0 180 | typing_extensions==4.10.0 181 | tzdata==2024.1 182 | urllib3==2.2.1 183 | uvicorn==0.28.0 184 | uvloop==0.19.0 185 | watchfiles==0.21.0 186 | websocket-client==1.7.0 187 | websockets==12.0 188 | wrapt==1.16.0 189 | wsproto==1.2.0 190 | yarl==1.9.4 191 | youtube-transcript-api==0.6.2 192 | yt-dlp==2023.12.30 193 | zeep==4.2.1 194 | zipp==3.18.1 195 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from crewai import Agent 4 | from crewai_tools import tool 5 | from crewai import Task 6 | from crewai import Crew, Process 7 | from simple_salesforce import Salesforce 8 | import matplotlib.pyplot as plt 9 | import pandas as pd 10 | from typing import List, Dict 11 | 12 | 13 | # Load the environment variables 14 | load_dotenv() 15 | 16 | 17 | #Initialize the Salesforce client 18 | # Replace these with your Salesforce login credentials 19 | SALESFORCE_USERNAME = os.environ["SALESFORCE_USERNAME"] 20 | SALESFORCE_PASSWORD = os.environ["SALESFORCE_PASSWORD"] 21 | SALESFORCE_SECURITY_TOKEN = os.environ["SALESFORCE_SECURITY_TOKEN"] 22 | SALESFORCE_INSTANCE = os.environ["SALESFORCE_INSTANCE"] 23 | SALESFORCE_INSTANCE_URL = os.environ["SALESFORCE_INSTANCE_URL"] 24 | 25 | # Create a Salesforce client 26 | sf = Salesforce(instance=SALESFORCE_INSTANCE, session_id='') 27 | sf = Salesforce(username=SALESFORCE_USERNAME, password=SALESFORCE_PASSWORD, security_token=SALESFORCE_SECURITY_TOKEN) 28 | 29 | 30 | 31 | # Create a Salesforce Tool 32 | @tool("Fetches all Opportunities from Salesforce and returns a summary.") 33 | def fetch_all_opportunities_with_account() -> str: 34 | """ 35 | Fetches all opportunities from Salesforce, including Account information, and returns a summary. 36 | 37 | Returns: 38 | str: A formatted summary of all Opportunities including Account Name. 39 | """ 40 | try: 41 | # Query all opportunities with relevant fields including Account Name 42 | query = "SELECT Id, Name, Amount, StageName, CloseDate, Account.Name FROM Opportunity" 43 | opportunities = sf.query_all(query) 44 | 45 | # Check if any opportunities were found 46 | if opportunities['totalSize'] == 0: 47 | return "No Opportunities found." 48 | 49 | # Initialize summary 50 | summary = "Opportunities with Account Summary:\n\n" 51 | 52 | # Process each opportunity 53 | for opp in opportunities['records']: 54 | account_name = opp['Account']['Name'] if opp.get('Account') else 'N/A' 55 | summary += f"- Opportunity Name: {opp['Name']}, Account Name: {account_name}, Amount: {opp.get('Amount', 'N/A')}, " \ 56 | f"Stage: {opp.get('StageName', 'N/A')}, Close Date: {opp.get('CloseDate', 'N/A')}\n" 57 | 58 | return summary 59 | 60 | except Exception as e: 61 | return f"Error fetching opportunities with account information: {str(e)}" 62 | 63 | 64 | #Tool to create graphs for Sales report 65 | @tool("Generates and saves comprehensive sales performance charts from opportunities data.") 66 | def plot_opportunity_graphs(opportunities: List[Dict]) -> List[str]: 67 | """ 68 | Generates and saves comprehensive sales performance charts from opportunities data. 69 | Intended to reflect overall sales department performance and key observations, including 70 | total sales over time and opportunity stage distribution. 71 | 72 | Parameters: 73 | opportunities (List[Dict]): A list of opportunities where each opportunity is a dictionary 74 | containing at least 'Amount', 'StageName', and 'CloseDate'. 75 | 76 | Returns: 77 | List[str]: Paths to the saved chart images. 78 | """ 79 | # Preparation steps... 80 | cleaned_opportunities = [opp for opp in opportunities if 'CloseDate' in opp and opp['CloseDate']] 81 | df = pd.DataFrame(cleaned_opportunities) 82 | 83 | # Proceed only if there's data to work with 84 | if not df.empty: 85 | df['CloseDate'] = pd.to_datetime(df['CloseDate'], errors='coerce') # Convert CloseDate to datetime, coerce errors 86 | df = df.dropna(subset=['CloseDate']) # Drop rows where CloseDate conversion failed 87 | 88 | chart_paths = [] 89 | base_path = 'charts_sales_performance' 90 | os.makedirs(base_path, exist_ok=True) 91 | 92 | # Chart 1: Total Sales Over Time 93 | fig, ax = plt.subplots(figsize=(10, 6)) 94 | df.resample('M', on='CloseDate')['Amount'].sum().plot(ax=ax) 95 | ax.set_title('Total Sales Over Time') 96 | ax.set_ylabel('Total Sales Amount') 97 | ax.set_xlabel('Month') 98 | chart_path = os.path.join(base_path, 'total_sales_over_time.png') 99 | plt.savefig(chart_path) 100 | plt.close(fig) 101 | chart_paths.append(chart_path) 102 | 103 | # Chart 2: Opportunity Stage Distribution 104 | plt.figure(figsize=(10, 6)) 105 | df['StageName'].value_counts().plot(kind='pie', autopct='%1.1f%%', startangle=90, counterclock=False) 106 | plt.title('Opportunity Stage Distribution') 107 | plt.ylabel('') # Hide the y-label as it's not necessary for pie charts 108 | chart_path_stage_dist = os.path.join(base_path, 'opportunity_stage_distribution.png') 109 | plt.savefig(chart_path_stage_dist) 110 | plt.close() 111 | chart_paths.append(chart_path_stage_dist) 112 | 113 | return chart_paths 114 | else: 115 | return ["No valid opportunities data available for chart generation."] 116 | 117 | 118 | 119 | 120 | #Sales Analyst Agent 121 | sales_analyst = Agent( 122 | role='Sales Data Analyst', 123 | goal='Analyze Salesforce opportunities and visualize data across the entire sales pipeline', 124 | verbose=True, 125 | memory=True, 126 | backstory=( 127 | "Equipped with analytical skills and a knack for visualization, you delve into Salesforce data to " 128 | "draw out key insights across the entire sales pipeline. Through meticulous analysis and chart plotting, " 129 | "you transform raw data into visual stories that highlight overall trends and opportunities, " 130 | "setting the stage for strategic decisions." 131 | ), 132 | tools=[fetch_all_opportunities_with_account, plot_opportunity_graphs], # Tools for fetching and plotting 133 | allow_delegation=True 134 | ) 135 | 136 | #Report Writer Agent 137 | report_writer = Agent( 138 | role='Report Writer', 139 | goal='Compile analysis and charts into a comprehensive sales performance report', 140 | verbose=True, 141 | memory=True, 142 | backstory=( 143 | "With a flair for synthesis and narrative, you adeptly combine analytical insights and visualizations " 144 | "into compelling reports. Your work not only informs but also engages stakeholders, making complex data " 145 | "accessible and actionable for the entire sales department." 146 | ), 147 | tools=[], # No specific tools, but responsible for compiling the final report 148 | allow_delegation=False 149 | ) 150 | 151 | 152 | # Task for sales analyst to perform data analysis and create charts for the entire sales pipeline 153 | analysis_and_charting_task = Task( 154 | description=( 155 | "Extract Salesforce opportunities, analyze the data, and create visualizations that cover the entire sales pipeline. " 156 | "Summarize your findings and include generated charts in a Markdown document, providing a foundation " 157 | "for the comprehensive sales performance report." 158 | ), 159 | expected_output='A Markdown document with analysis and charts covering the entire sales pipeline.', 160 | tools=[fetch_all_opportunities_with_account, plot_opportunity_graphs], 161 | agent=sales_analyst, 162 | output_file='pipeline_analysis.md' # Markdown file for analysis and charts 163 | ) 164 | 165 | # Task for report writer to compile the final comprehensive sales performance report 166 | final_report_task = Task( 167 | description=( 168 | "Using the provided analysis and charts, craft a detailed sales performance report that encompasses the entire sales pipeline. " 169 | "Ensure the report is comprehensive, integrating both textual analysis and visual data representations. " 170 | "Compile the final report into a Markdown document." 171 | ), 172 | expected_output='A comprehensive sales performance report in Markdown format, with embedded charts.', 173 | tools=[], 174 | agent=report_writer, 175 | output_file='final_sales_report.md' # Markdown document for the final report 176 | ) 177 | 178 | # Crew to orchestrate the sales report creation process 179 | sales_crew = Crew( 180 | agents=[sales_analyst, report_writer], 181 | tasks=[analysis_and_charting_task, final_report_task], 182 | process=Process.sequential # Sequential execution ensures analysis and charting precede report writing 183 | ) 184 | 185 | # Initiating the process to create a comprehensive sales report 186 | result = sales_crew.kickoff(inputs={}) 187 | print(result) 188 | --------------------------------------------------------------------------------