diff --git a/.coverage b/.coverage index 5b66007..bf8dae6 100644 Binary files a/.coverage and b/.coverage differ diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 4ece4bd..77e3f07 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -80,4 +80,4 @@ Related to # - [ ] I have searched existing issues/PRs for similar feature requests - [ ] I have provided a clear use case for this feature - [ ] I have considered how this fits with PlotSense's mission -- [ ] I have provided example usage code +- [ ] I have provided example usage code. diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 03c4271..b43238b 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -27,8 +27,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install pytest pytest-cov pytest-mock flake8 + pip install -e ".[test]" + pip install flake8 - name: Lint with flake8 run: | @@ -68,8 +68,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install pytest pytest-mock + pip install -e ".[test]" - name: Run unit tests only (fast) run: | diff --git a/.gitignore b/.gitignore index d611e12..5617b89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,48 @@ +# Virtual environments /venv +/.venv +venv/ +env/ +ENV/ + +# Environment variables .env *.env -/__pycache__ -/.venv -*.egg-info + +# Python cache +__pycache__/ +*.py[cod] +*$py.class +*.so + +# Distribution / packaging +*.egg-info/ *.egg -*.pyc -*.pyo -*.pyd +build/ +dist/ +wheels/ +*.whl + +# Testing +.pytest_cache/ +.coverage +coverage.xml +htmlcov/ +.tox/ +test_logs/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Database *.db +*.sqlite +*.sqlite3 diff --git a/plotsense.egg-info/PKG-INFO b/plotsense.egg-info/PKG-INFO index d4194bf..ab892f6 100644 --- a/plotsense.egg-info/PKG-INFO +++ b/plotsense.egg-info/PKG-INFO @@ -1,10 +1,11 @@ Metadata-Version: 2.4 Name: plotsense -Version: 0.1.0 +Version: 0.1.3 Summary: An intelligent plotting package with suggestions and explanations -Author-email: Christian Chimezie -License: MIT -Project-URL: Homepage, https://github.com/christianchimezie/PlotSenseAI +Home-page: https://github.com/christianchimezie/PlotSenseAI +Author: Christian Chimezie, Toluwaleke Ogidan, Grace Farayola, Amaka Iduwe, Nelson Ogbeide, Onyekachukwu Ojumah, Olamilekan Ajao +Author-email: chimeziechristiancc@gmail.com, gbemilekeogidan@gmail.com, gracefarayola@gmail.com, nwaamaka_iduwe@yahoo.com, Ogbeide331@gmail.com, Onyekaojumah22@gmail.com, olamilekan011@gmail.com +License: Apache License 2.0 Project-URL: Documentation, https://github.com/christianchimezie/PlotSenseAI/blob/main/README.md Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Science/Research @@ -21,19 +22,53 @@ Requires-Python: >=3.8 Description-Content-Type: text/markdown License-File: LICENCE License-File: NOTICE -Requires-Dist: matplotlib>=3.0 +Requires-Dist: matplotlib>=3.8.0 Requires-Dist: seaborn>=0.11 Requires-Dist: pandas>=1.0 Requires-Dist: numpy>=1.18 +Requires-Dist: python-dotenv +Requires-Dist: groq +Requires-Dist: requests +Requires-Dist: Pillow>=9.0.0 +Provides-Extra: dev +Requires-Dist: pytest>=7.0; extra == "dev" +Requires-Dist: pytest-cov>=4.0; extra == "dev" +Requires-Dist: pytest-mock>=3.10; extra == "dev" +Requires-Dist: flake8>=6.0; extra == "dev" +Requires-Dist: autopep8>=2.0; extra == "dev" +Provides-Extra: test +Requires-Dist: pytest>=7.0; extra == "test" +Requires-Dist: pytest-cov>=4.0; extra == "test" +Requires-Dist: pytest-mock>=3.10; extra == "test" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: license Dynamic: license-file +Dynamic: project-url +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary # 🌟 PlotSense: AI-Powered Data Visualization Assistant +[![Python Tests](https://github.com/PlotSenseAI/PlotSenseAI/actions/workflows/python-tests.yml/badge.svg)](https://github.com/PlotSenseAI/PlotSenseAI/actions/workflows/python-tests.yml) +[![Frontend Tests](https://github.com/PlotSenseAI/PlotSenseAI/actions/workflows/frontend-tests.yml/badge.svg)](https://github.com/PlotSenseAI/PlotSenseAI/actions/workflows/frontend-tests.yml) +[![codecov](https://codecov.io/gh/PlotSenseAI/PlotSenseAI/branch/main/graph/badge.svg)](https://codecov.io/gh/PlotSenseAI/PlotSenseAI) +[![Python Version](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Code style: flake8](https://img.shields.io/badge/code%20style-flake8-black.svg)](https://flake8.pycqa.org/) + ## πŸ“Œ Overview **PlotSense** is an AI-powered assistant that helps data professionals and analysts make smarter, faster, and more explainable data visualizations. Whether you're exploring a new dataset or building dashboards, PlotSense simplifies the process with: - βœ… Smart Visualization Suggestions - Recommends the best plots based on your data structure and relationships. +- πŸ“Š Visualization Plot - Generates suggested plot with ease. - 🧠 Natural Language Explanations – Automatically explains charts in plain English. - πŸ”— Seamless Integration – Works out of the box with pandas, matplotlib, and seaborn. @@ -51,7 +86,7 @@ pip install plotsense ```bash import plotsense as ps -from plotsense import recommender, generate_plot, explainer, +from plotsense import recommender, plotgen, explainer ``` ### πŸ” Authenticate with Groq API: Get your free API key from Groq Cloud https://console.groq.com/home @@ -77,24 +112,55 @@ import pandas as pd df = pd.read_csv("data.csv") # Get AI-recommended visualizations -suggestions = ps.recommender(df) # default number of suggestions is 5 +suggestions = recommender(df) # default number of suggestions is 5 print(suggestions) ``` ### πŸ“Š Sample Output: -![alt text](suggestions_table.png) +![alt text](image.png) πŸŽ›οΈ Want more suggestions? ``` bash -suggestions = ps.recommender(df, n=10) +suggestions = recommender(df, n=10) +``` + +### πŸ“ˆ 2. One-Click Plot Generation +Generate recommended charts instantly using .iloc + +```bash +plot1 = plotgen(df, suggestions.iloc[0]) # This will plot a bar chart with variables 'survived', 'pclass' +plot2 = plotgen(df, suggestions.iloc[1]) # This will plot a bar chart with variables 'survived', 'sex' +plot3 = plotgen(df, suggestions.iloc[2]) # This will plot a histogram with variable 'age' ``` -### 🧾 2. AI-Powered Plot Explanation +or Generate recommended charts instantly using three argurments + +```bash +plot1 = plotgen(df, 0, suggestions) # This will plot a bar chart with variables 'survived', 'pclass' +plot2 = plotgen(df, 1, suggestions) # This will plot a bar chart with variables 'survived', 'sex' +plot3 = plotgen(df, 2, suggestions) # This will plot a histogram with variable 'age' +``` +πŸŽ›οΈ Want more control? + +``` bash +plot1 = plotgen(df, suggestions.iloc[0], x='pclass', y='survived') +``` +Supported Plots +- scatter +- bar +- barh +- histogram +- boxplot +- violinplot +- pie +- hexbin + +### 🧾 3. AI-Powered Plot Explanation Turn your visualizations into stories with natural language insights: ``` bash -explanation = ps.explainer(plot1) +explanation = explainer(plot1) print(explanation) ``` @@ -103,7 +169,7 @@ print(explanation) - Custom Prompts: You can provide your own prompt to guide the explanation ``` bash -explanation = refine_plot_explanation( +explanation = explainer( fig, prompt="Explain the key trends in this sales data visualization" ) @@ -111,33 +177,126 @@ explanation = refine_plot_explanation( - Multiple Refinement Iterations: Increase the number of refinement cycles for more polished explanations: ```bash -explanation = refine_plot_explanation(fig, iterations=3) # Default is 2 +explanation = explainer(fig, max_iterations=3) # Default is 2 +``` + +## πŸ”„ Combined Workflow: Suggest β†’ Plot β†’ Explain +``` bash +suggestions = recommender(df) +plot = plotgen(df, suggestions.iloc[0]) +insight = explainer(plot) +``` + +## πŸ§ͺ Testing + +PlotSenseAI has comprehensive test coverage for both Python and frontend code. + +### Quick Test Commands + +**Python Tests:** +```bash +# Run all tests +pytest + +# Run with coverage +pytest --cov=plotsense --cov-report=html + +# Run fast tests only (skip slow tests) +pytest -m "not slow" +``` + +**Frontend Tests:** +```bash +cd web + +# Run all tests +npm test + +# Run with coverage +npm run test:coverage + +# Run in watch mode +npm test -- --watch +``` + +### Test Infrastructure + +- **Backend**: pytest with 950+ lines of tests covering: + - Unit tests for individual functions + - Integration tests for component interaction + - End-to-end workflow tests + - Mock external API calls (Groq) + +- **Frontend**: Vitest + React Testing Library + - Component tests for UI elements + - User interaction testing + - Accessibility testing + +- **CI/CD**: GitHub Actions runs tests automatically on every push and PR +- **Pre-commit Hooks**: Automated linting and quick tests before commits + +For detailed testing documentation, see [TESTING.md](TESTING.md). + +### Setting Up Development Environment + +```bash +# Install Python dependencies +pip install -e . +pip install pytest pytest-cov pytest-mock + +# Install frontend dependencies +cd web && npm install + +# Install pre-commit hooks (optional but recommended) +pip install pre-commit +pre-commit install ``` ## 🀝 Contributing -We welcome contributions! + +We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. + +### Quick Start for Contributors + +1. **Fork and clone** the repository +2. **Set up development environment:** + ```bash + pip install -e . + pip install pytest pytest-cov pytest-mock pre-commit + pre-commit install + ``` +3. **Create a feature branch:** `git checkout -b feature/your-feature` +4. **Make changes and add tests** +5. **Run tests:** `pytest` and `cd web && npm test` +6. **Submit a Pull Request** ### Branching Strategy -- main β†’ The stable production-ready version of PlotSense. -- dev β†’ Active development -- feature/ β†’ Branches for specific features (e.g., feature/ai-visualization-suggestions). +- `main` β†’ Production-ready version +- `dev` β†’ Active development +- `feature/` β†’ New features +- `fix/` β†’ Bug fixes ### πŸ’‘ How to Help -- 🐞 **Bug Reports** β†’ GitHub Issues -- πŸ’‘ **Suggest features** β†’ Open a discussion -- πŸš€ **Submit PRs** β†’ Fork β†’ Branch β†’ Test β†’ Pull Request +- 🐞 **Bug Reports** β†’ [Open an issue](https://github.com/PlotSenseAI/PlotSenseAI/issues/new?template=bug_report.md) +- πŸ’‘ **Feature Requests** β†’ [Request a feature](https://github.com/PlotSenseAI/PlotSenseAI/issues/new?template=feature_request.md) +- ❓ **Questions** β†’ [Ask a question](https://github.com/PlotSenseAI/PlotSenseAI/issues/new?template=question.md) +- πŸš€ **Submit PRs** β†’ See [CONTRIBUTING.md](CONTRIBUTING.md) ### πŸ“… Roadmap - More model integrations - Automated insight highlighting - Jupyter widget support +- Features/target analysis +- More supported plots +- PlotSense web interface +- PlotSense customised notebook template ### πŸ“₯ Install or Update ``` bash pip install --upgrade plotsense # Get the latest features! ``` ## πŸ›‘ License -MIT License (Open Source) +Apache License 2.0 ## πŸ” API & Privacy Notes - Your API key is securely held in memory for your current Python session. @@ -146,3 +305,6 @@ MIT License (Open Source) Let your data speakβ€”with clarity, power, and PlotSense. πŸ“Šβœ¨ + +## Your Feedback +[Feedback Form](https://forms.gle/QEjipzHiMagpAQU99) diff --git a/plotsense.egg-info/SOURCES.txt b/plotsense.egg-info/SOURCES.txt index babe714..ad6ed30 100644 --- a/plotsense.egg-info/SOURCES.txt +++ b/plotsense.egg-info/SOURCES.txt @@ -1,5 +1,8 @@ +LICENCE +NOTICE README.md pyproject.toml +setup.cfg setup.py plotsense/__init__.py plotsense.egg-info/PKG-INFO @@ -11,5 +14,10 @@ plotsense/explanations/__init__.py plotsense/explanations/explanations.py plotsense/plot_generator/__init__.py plotsense/plot_generator/generator.py -plotsense/visual_suggestion/suggestions.py plotsense/visual_suggestion/__init__.py +plotsense/visual_suggestion/suggestions.py +test/__init__.py +test/conftest.py +test/test_explanations.py +test/test_plotgen.py +test/test_suggestions.py diff --git a/plotsense.egg-info/requires.txt b/plotsense.egg-info/requires.txt index 6fbc6f2..dca5248 100644 --- a/plotsense.egg-info/requires.txt +++ b/plotsense.egg-info/requires.txt @@ -1,4 +1,20 @@ -matplotlib>=3.0 +matplotlib>=3.8.0 seaborn>=0.11 pandas>=1.0 numpy>=1.18 +python-dotenv +groq +requests +Pillow>=9.0.0 + +[dev] +pytest>=7.0 +pytest-cov>=4.0 +pytest-mock>=3.10 +flake8>=6.0 +autopep8>=2.0 + +[test] +pytest>=7.0 +pytest-cov>=4.0 +pytest-mock>=3.10 diff --git a/plotsense.egg-info/top_level.txt b/plotsense.egg-info/top_level.txt index 100c7e8..f5522c3 100644 --- a/plotsense.egg-info/top_level.txt +++ b/plotsense.egg-info/top_level.txt @@ -1 +1,2 @@ plotsense +test diff --git a/plotsense/visual_suggestion/suggestions.py b/plotsense/visual_suggestion/suggestions.py index dcb9743..f59e9e3 100644 --- a/plotsense/visual_suggestion/suggestions.py +++ b/plotsense/visual_suggestion/suggestions.py @@ -84,8 +84,7 @@ def _validate_keys(self): link = service_links.get( service, f"the {service.upper()} website") message = ( - f"Enter { - service.upper()} API key (get it at {link}): ") + f"Enter {service.upper()} API key (get it at {link}): ") self.api_keys[service] = builtins.input( message).strip() if not self.api_keys[service]: @@ -207,8 +206,7 @@ def recommend_visualizations(self, if len(ensemble_results) < n: if self.debug: print( - f"\n[DEBUG] Only got { - len(ensemble_results)} recommendations, trying to supplement") + f"\n[DEBUG] Only got {len(ensemble_results)} recommendations, trying to supplement") return self._supplement_recommendations(ensemble_results, n) if self.debug: @@ -254,15 +252,13 @@ def _supplement_recommendations( if self.debug: print( - f"\n[DEBUG] Supplemented with { - len(new_recs)} new recommendations") + f"\n[DEBUG] Supplemented with {len(new_recs)} new recommendations") return combined.head(target) except Exception as e: if self.debug: print( - f"\n[WARNING] Couldn't supplement recommendations: { - str(e)}") + f"\n[WARNING] Couldn't supplement recommendations: {str(e)}") return existing.head(target) # Return what we have def _get_all_recommendations(self) -> Dict[str, List[Dict]]: @@ -298,12 +294,10 @@ def _get_all_recommendations(self) -> Dict[str, List[Dict]]: all_recommendations[model] = result if self.debug: print( - f"\n[DEBUG] Got { - len(result)} recommendations from {model}") + f"\n[DEBUG] Got {len(result)} recommendations from {model}") except Exception as e: warnings.warn( - f"Failed to get recommendations from {model}: { - str(e)}") + f"Failed to get recommendations from {model}: {str(e)}") if self.debug: print(f"\n[ERROR] Failed to process {model}: {str(e)}") @@ -327,8 +321,7 @@ def _get_model_recommendations(self, warnings.warn(f"Error processing model {model}: {str(e)}") if self.debug: print( - f"\n[ERROR] Failed to parse response from {model}: { - str(e)}") + f"\n[ERROR] Failed to parse response from {model}: {str(e)}") return [] def _apply_ensemble_scoring(self, @@ -438,11 +431,8 @@ def _describe_dataframe(self) -> str: # Add stats for numerical/datetime if col_type == "numerical": desc.append( - f" Stats: min={ - self.df[col].min()}, max={ - self.df[col].max()}, " f"mean={ - self.df[col].mean():.2f}, missing={ - self.df[col].isna().sum()}") + f" Stats: min={self.df[col].min()}, max={self.df[col].max()}, " + f"mean={self.df[col].mean():.2f}, missing={self.df[col].isna().sum()}") elif col_type == "datetime": desc.append( f" Range: {self.df[col].min()} to {self.df[col].max()}, " diff --git a/pytest.ini b/pytest.ini index c7e184a..9ef3569 100644 --- a/pytest.ini +++ b/pytest.ini @@ -26,12 +26,10 @@ addopts = --strict-config # Show warnings -W default - # Coverage options (when --cov is used) - --cov-report=term-missing:skip-covered - --cov-report=html - --cov-branch # Performance --durations=10 + # Note: Coverage options removed from default addopts + # To run with coverage: pytest --cov=plotsense --cov-report=term-missing --cov-report=html --cov-branch # Markers for organizing tests markers = diff --git a/setup.py b/setup.py index 600ee5c..abb5997 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,16 @@ from setuptools import setup, find_packages +# Read README with explicit UTF-8 encoding +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + setup( name="plotsense", version="0.1.3", author="Christian Chimezie, Toluwaleke Ogidan, Grace Farayola, Amaka Iduwe, Nelson Ogbeide, Onyekachukwu Ojumah, Olamilekan Ajao", author_email="chimeziechristiancc@gmail.com, gbemilekeogidan@gmail.com, gracefarayola@gmail.com, nwaamaka_iduwe@yahoo.com, Ogbeide331@gmail.com, Onyekaojumah22@gmail.com, olamilekan011@gmail.com", description="An intelligent plotting package with suggestions and explanations", - long_description=open("README.md").read(), + long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/christianchimezie/PlotSenseAI", project_urls={ @@ -35,6 +39,21 @@ "python-dotenv", "groq", "requests", + "Pillow>=9.0.0", ], + extras_require={ + "dev": [ + "pytest>=7.0", + "pytest-cov>=4.0", + "pytest-mock>=3.10", + "flake8>=6.0", + "autopep8>=2.0", + ], + "test": [ + "pytest>=7.0", + "pytest-cov>=4.0", + "pytest-mock>=3.10", + ], + }, license="Apache License 2.0", ) diff --git a/test/conftest.py b/test/conftest.py index 7f76bbd..f58d458 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -29,6 +29,7 @@ # Session-level fixtures (shared across all tests) # ============================================================================ + @pytest.fixture(scope="session") def test_api_key(): """Provide a test API key for testing.""" diff --git a/test_logs/pytest.log b/test_logs/pytest.log index d46fbdc..4ad4ead 100644 --- a/test_logs/pytest.log +++ b/test_logs/pytest.log @@ -390,12 +390,12 @@ DEBUG matplotlib.font_manager:font_manager.py:1483 findfont: score(FontEntry( DEBUG matplotlib.font_manager:font_manager.py:1483 findfont: score(FontEntry(fname='C:\\Windows\\Fonts\\ntailu.ttf', name='Microsoft New Tai Lue', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')) = 10.05 DEBUG matplotlib.font_manager:font_manager.py:1483 findfont: score(FontEntry(fname='C:\\Windows\\Fonts\\BOD_BI.TTF', name='Bodoni MT', style='italic', variant='normal', weight=700, stretch='normal', size='scalable')) = 11.335 DEBUG matplotlib.font_manager:font_manager.py:1515 findfont: Matching sans\-serif:style=normal:variant=normal:weight=normal:stretch=normal:size=10.0 to DejaVu Sans ('C:\\Users\\chime\\PlotSense_Recloned\\PlotSenseAI\\venv\\Lib\\site-packages\\matplotlib\\mpl-data\\fonts\\ttf\\DejaVuSans.ttf') with score of 0.050000. -DEBUG matplotlib.colorbar:colorbar.py:867 locator: -DEBUG matplotlib.colorbar:colorbar.py:867 locator: -DEBUG matplotlib.colorbar:colorbar.py:867 locator: -DEBUG matplotlib.colorbar:colorbar.py:867 locator: -DEBUG matplotlib.colorbar:colorbar.py:867 locator: -DEBUG matplotlib.colorbar:colorbar.py:867 locator: +DEBUG matplotlib.colorbar:colorbar.py:867 locator: +DEBUG matplotlib.colorbar:colorbar.py:867 locator: +DEBUG matplotlib.colorbar:colorbar.py:867 locator: +DEBUG matplotlib.colorbar:colorbar.py:867 locator: +DEBUG matplotlib.colorbar:colorbar.py:867 locator: +DEBUG matplotlib.colorbar:colorbar.py:867 locator: INFO matplotlib.category:category.py:224 Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting. INFO matplotlib.category:category.py:224 Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting. INFO matplotlib.category:category.py:224 Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting. @@ -406,29 +406,23 @@ DEBUG groq._base_client:_base_client.py:950 Sending HTTP Request: POST https: DEBUG httpcore.connection:_trace.py:47 connect_tcp.started host='api.groq.com' port=443 local_address=None timeout=30 socket_options=None DEBUG groq._base_client:_base_client.py:950 Sending HTTP Request: POST https://api.groq.com/openai/v1/chat/completions DEBUG httpcore.connection:_trace.py:47 connect_tcp.started host='api.groq.com' port=443 local_address=None timeout=30 socket_options=None -DEBUG httpcore.connection:_trace.py:47 connect_tcp.complete return_value= -DEBUG httpcore.connection:_trace.py:47 connect_tcp.complete return_value= -DEBUG httpcore.connection:_trace.py:47 start_tls.started ssl_context= server_hostname='api.groq.com' timeout=30 -DEBUG httpcore.connection:_trace.py:47 start_tls.started ssl_context= server_hostname='api.groq.com' timeout=30 -DEBUG httpcore.connection:_trace.py:47 start_tls.complete return_value= +DEBUG httpcore.connection:_trace.py:47 connect_tcp.complete return_value= +DEBUG httpcore.connection:_trace.py:47 start_tls.started ssl_context= server_hostname='api.groq.com' timeout=30 +DEBUG httpcore.connection:_trace.py:47 connect_tcp.complete return_value= +DEBUG httpcore.connection:_trace.py:47 start_tls.started ssl_context= server_hostname='api.groq.com' timeout=30 +DEBUG httpcore.connection:_trace.py:47 start_tls.complete return_value= DEBUG httpcore.http11:_trace.py:47 send_request_headers.started request= DEBUG httpcore.http11:_trace.py:47 send_request_headers.complete DEBUG httpcore.http11:_trace.py:47 send_request_body.started request= DEBUG httpcore.http11:_trace.py:47 send_request_body.complete DEBUG httpcore.http11:_trace.py:47 receive_response_headers.started request= -DEBUG httpcore.connection:_trace.py:47 start_tls.complete return_value= -DEBUG httpcore.http11:_trace.py:47 send_request_headers.started request= -DEBUG httpcore.http11:_trace.py:47 send_request_headers.complete -DEBUG httpcore.http11:_trace.py:47 send_request_body.started request= -DEBUG httpcore.http11:_trace.py:47 send_request_body.complete -DEBUG httpcore.http11:_trace.py:47 receive_response_headers.started request= -DEBUG httpcore.http11:_trace.py:47 receive_response_headers.complete return_value=(b'HTTP/1.1', 401, b'Unauthorized', [(b'Date', b'Sat, 25 Oct 2025 01:58:34 GMT'), (b'Content-Type', b'application/json'), (b'Content-Length', b'96'), (b'Connection', b'keep-alive'), (b'Cache-Control', b'private, max-age=0, no-store, no-cache, must-revalidate'), (b'Server', b'cloudflare'), (b'vary', b'Origin'), (b'x-groq-region', b'fra'), (b'x-request-id', b'req_01k8chdhqmemcvjdwk67cnbtdz'), (b'via', b'1.1 google'), (b'cf-cache-status', b'DYNAMIC'), (b'Set-Cookie', b'__cf_bm=b66yrI.gqNCW4fVikPD0rkECgQXWLutBciOi5gao1Ts-1761357514.4726043-1.0.1.1-y4WZvDohmunPNzTp1k7tD1dfKYid_OHB1IwcBjTZx_fKEjxO435ggm_aiVjheo99GTJXgUXvKeJMHCmDap2pQUzdBnAtFyUc7_82xEzqSwlRy68GYA3l9yASp9kVqApO; HttpOnly; Secure; Path=/; Expires=Sat, 25 Oct 2025 02:28:34 GMT'), (b'CF-RAY', b'993e1c1178692b0b-LHR'), (b'alt-svc', b'h3=":443"; ma=86400')]) +DEBUG httpcore.http11:_trace.py:47 receive_response_headers.complete return_value=(b'HTTP/1.1', 401, b'Unauthorized', [(b'Date', b'Thu, 13 Nov 2025 17:37:03 GMT'), (b'Content-Type', b'application/json'), (b'Content-Length', b'96'), (b'Connection', b'keep-alive'), (b'Cache-Control', b'private, max-age=0, no-store, no-cache, must-revalidate'), (b'Server', b'cloudflare'), (b'vary', b'Origin'), (b'x-groq-region', b'fra'), (b'x-request-id', b'req_01k9z4nkjde4st09n44hm8tjp8'), (b'via', b'1.1 google'), (b'cf-cache-status', b'DYNAMIC'), (b'Set-Cookie', b'__cf_bm=LVIaRobBN1PJhPWpI_ThvnipI4NTlMKRzTAG5nq1.0A-1763055423.0405674-1.0.1.1-Konb9eRsioMJnK67NHofpA6A7sjR9MgG62u.Tb42zkBDIu1nKbs3.JE3dod.mQWgjy6vB9FdPE0BFHzYIDfbUmFfESqT4aiTP25hsePNe8MKs4Z_f7L1sZ2J5rf1j_5Q; HttpOnly; Secure; Path=/; Domain=groq.com; Expires=Thu, 13 Nov 2025 18:07:03 GMT'), (b'CF-RAY', b'99e008e9f9e3593c-LHR'), (b'alt-svc', b'h3=":443"; ma=86400')]) INFO httpx:_client.py:1025 HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 401 Unauthorized" DEBUG httpcore.http11:_trace.py:47 receive_response_body.started request= DEBUG httpcore.http11:_trace.py:47 receive_response_body.complete DEBUG httpcore.http11:_trace.py:47 response_closed.started DEBUG httpcore.http11:_trace.py:47 response_closed.complete -DEBUG groq._base_client:_base_client.py:989 HTTP Response: POST https://api.groq.com/openai/v1/chat/completions "401 Unauthorized" Headers({'date': 'Sat, 25 Oct 2025 01:58:34 GMT', 'content-type': 'application/json', 'content-length': '96', 'connection': 'keep-alive', 'cache-control': 'private, max-age=0, no-store, no-cache, must-revalidate', 'server': 'cloudflare', 'vary': 'Origin', 'x-groq-region': 'fra', 'x-request-id': 'req_01k8chdhqmemcvjdwk67cnbtdz', 'via': '1.1 google', 'cf-cache-status': 'DYNAMIC', 'set-cookie': '__cf_bm=b66yrI.gqNCW4fVikPD0rkECgQXWLutBciOi5gao1Ts-1761357514.4726043-1.0.1.1-y4WZvDohmunPNzTp1k7tD1dfKYid_OHB1IwcBjTZx_fKEjxO435ggm_aiVjheo99GTJXgUXvKeJMHCmDap2pQUzdBnAtFyUc7_82xEzqSwlRy68GYA3l9yASp9kVqApO; HttpOnly; Secure; Path=/; Expires=Sat, 25 Oct 2025 02:28:34 GMT', 'cf-ray': '993e1c1178692b0b-LHR', 'alt-svc': 'h3=":443"; ma=86400'}) +DEBUG groq._base_client:_base_client.py:989 HTTP Response: POST https://api.groq.com/openai/v1/chat/completions "401 Unauthorized" Headers({'date': 'Thu, 13 Nov 2025 17:37:03 GMT', 'content-type': 'application/json', 'content-length': '96', 'connection': 'keep-alive', 'cache-control': 'private, max-age=0, no-store, no-cache, must-revalidate', 'server': 'cloudflare', 'vary': 'Origin', 'x-groq-region': 'fra', 'x-request-id': 'req_01k9z4nkjde4st09n44hm8tjp8', 'via': '1.1 google', 'cf-cache-status': 'DYNAMIC', 'set-cookie': '__cf_bm=LVIaRobBN1PJhPWpI_ThvnipI4NTlMKRzTAG5nq1.0A-1763055423.0405674-1.0.1.1-Konb9eRsioMJnK67NHofpA6A7sjR9MgG62u.Tb42zkBDIu1nKbs3.JE3dod.mQWgjy6vB9FdPE0BFHzYIDfbUmFfESqT4aiTP25hsePNe8MKs4Z_f7L1sZ2J5rf1j_5Q; HttpOnly; Secure; Path=/; Domain=groq.com; Expires=Thu, 13 Nov 2025 18:07:03 GMT', 'cf-ray': '99e008e9f9e3593c-LHR', 'alt-svc': 'h3=":443"; ma=86400'}) DEBUG groq._base_client:_base_client.py:1001 Encountered httpx.HTTPStatusError Traceback (most recent call last): File "C:\Users\chime\PlotSense_Recloned\PlotSenseAI\venv\Lib\site-packages\groq\_base_client.py", line 999, in _request @@ -438,15 +432,21 @@ Traceback (most recent call last): raise HTTPStatusError(message, request=request, response=self) httpx.HTTPStatusError: Client error '401 Unauthorized' for url 'https://api.groq.com/openai/v1/chat/completions' For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 -DEBUG httpcore.http11:_trace.py:47 receive_response_headers.complete return_value=(b'HTTP/1.1', 401, b'Unauthorized', [(b'Date', b'Sat, 25 Oct 2025 01:58:34 GMT'), (b'Content-Type', b'application/json'), (b'Content-Length', b'96'), (b'Connection', b'keep-alive'), (b'Cache-Control', b'private, max-age=0, no-store, no-cache, must-revalidate'), (b'Server', b'cloudflare'), (b'vary', b'Origin'), (b'x-groq-region', b'fra'), (b'x-request-id', b'req_01k8chdhqqegbbxh76yfk4amrz'), (b'via', b'1.1 google'), (b'cf-cache-status', b'DYNAMIC'), (b'Set-Cookie', b'__cf_bm=gHUAY446hZoYVG2FXMFy.LGFC.Kw.PzDsVyWZCjk2lc-1761357514.4735234-1.0.1.1-erpRAJ.ifk8LuHGOXz75CyX46PP8No38mImVeIGq67SVIf5oaTrFxtN8.X8NLJ99Dzm324LgWreSHVn8h5YEX93qUDHaClcavAAVRqKSH7Is3uvRdZHqROKn0sktB93b; HttpOnly; Secure; Path=/; Expires=Sat, 25 Oct 2025 02:28:34 GMT'), (b'CF-RAY', b'993e1c117d2848b9-LHR'), (b'alt-svc', b'h3=":443"; ma=86400')]) DEBUG groq._base_client:_base_client.py:741 Not retrying -INFO httpx:_client.py:1025 HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 401 Unauthorized" DEBUG groq._base_client:_base_client.py:1019 Re-raising status error +DEBUG httpcore.connection:_trace.py:47 start_tls.complete return_value= +DEBUG httpcore.http11:_trace.py:47 send_request_headers.started request= +DEBUG httpcore.http11:_trace.py:47 send_request_headers.complete +DEBUG httpcore.http11:_trace.py:47 send_request_body.started request= +DEBUG httpcore.http11:_trace.py:47 send_request_body.complete +DEBUG httpcore.http11:_trace.py:47 receive_response_headers.started request= +DEBUG httpcore.http11:_trace.py:47 receive_response_headers.complete return_value=(b'HTTP/1.1', 401, b'Unauthorized', [(b'Date', b'Thu, 13 Nov 2025 17:37:03 GMT'), (b'Content-Type', b'application/json'), (b'Content-Length', b'96'), (b'Connection', b'keep-alive'), (b'Cache-Control', b'private, max-age=0, no-store, no-cache, must-revalidate'), (b'Server', b'cloudflare'), (b'vary', b'Origin'), (b'x-groq-region', b'fra'), (b'x-request-id', b'req_01k9z4nkscfq8anrv8baxgrwm0'), (b'via', b'1.1 google'), (b'cf-cache-status', b'DYNAMIC'), (b'Set-Cookie', b'__cf_bm=pp8dX29TH8wbgOphZua71qlVOIQHnCiDD.R81mnj9Zw-1763055423.2502484-1.0.1.1-Lv7NTiG06vhIzlPmWQtKR2.fAfxBsdrvTcJtmQilI29xavsa7hqAHAvgRNTSuC5x2S17.HDx6yyl_pC3l5ZjoHuZrp4JcN1E30YHcyKbo.fW9Y5YLKqtto7BGqQoujl6; HttpOnly; Secure; Path=/; Domain=groq.com; Expires=Thu, 13 Nov 2025 18:07:03 GMT'), (b'CF-RAY', b'99e008eb4aa7942b-LHR'), (b'alt-svc', b'h3=":443"; ma=86400')]) +INFO httpx:_client.py:1025 HTTP Request: POST https://api.groq.com/openai/v1/chat/completions "HTTP/1.1 401 Unauthorized" DEBUG httpcore.http11:_trace.py:47 receive_response_body.started request= DEBUG httpcore.http11:_trace.py:47 receive_response_body.complete DEBUG httpcore.http11:_trace.py:47 response_closed.started DEBUG httpcore.http11:_trace.py:47 response_closed.complete -DEBUG groq._base_client:_base_client.py:989 HTTP Response: POST https://api.groq.com/openai/v1/chat/completions "401 Unauthorized" Headers({'date': 'Sat, 25 Oct 2025 01:58:34 GMT', 'content-type': 'application/json', 'content-length': '96', 'connection': 'keep-alive', 'cache-control': 'private, max-age=0, no-store, no-cache, must-revalidate', 'server': 'cloudflare', 'vary': 'Origin', 'x-groq-region': 'fra', 'x-request-id': 'req_01k8chdhqqegbbxh76yfk4amrz', 'via': '1.1 google', 'cf-cache-status': 'DYNAMIC', 'set-cookie': '__cf_bm=gHUAY446hZoYVG2FXMFy.LGFC.Kw.PzDsVyWZCjk2lc-1761357514.4735234-1.0.1.1-erpRAJ.ifk8LuHGOXz75CyX46PP8No38mImVeIGq67SVIf5oaTrFxtN8.X8NLJ99Dzm324LgWreSHVn8h5YEX93qUDHaClcavAAVRqKSH7Is3uvRdZHqROKn0sktB93b; HttpOnly; Secure; Path=/; Expires=Sat, 25 Oct 2025 02:28:34 GMT', 'cf-ray': '993e1c117d2848b9-LHR', 'alt-svc': 'h3=":443"; ma=86400'}) +DEBUG groq._base_client:_base_client.py:989 HTTP Response: POST https://api.groq.com/openai/v1/chat/completions "401 Unauthorized" Headers({'date': 'Thu, 13 Nov 2025 17:37:03 GMT', 'content-type': 'application/json', 'content-length': '96', 'connection': 'keep-alive', 'cache-control': 'private, max-age=0, no-store, no-cache, must-revalidate', 'server': 'cloudflare', 'vary': 'Origin', 'x-groq-region': 'fra', 'x-request-id': 'req_01k9z4nkscfq8anrv8baxgrwm0', 'via': '1.1 google', 'cf-cache-status': 'DYNAMIC', 'set-cookie': '__cf_bm=pp8dX29TH8wbgOphZua71qlVOIQHnCiDD.R81mnj9Zw-1763055423.2502484-1.0.1.1-Lv7NTiG06vhIzlPmWQtKR2.fAfxBsdrvTcJtmQilI29xavsa7hqAHAvgRNTSuC5x2S17.HDx6yyl_pC3l5ZjoHuZrp4JcN1E30YHcyKbo.fW9Y5YLKqtto7BGqQoujl6; HttpOnly; Secure; Path=/; Domain=groq.com; Expires=Thu, 13 Nov 2025 18:07:03 GMT', 'cf-ray': '99e008eb4aa7942b-LHR', 'alt-svc': 'h3=":443"; ma=86400'}) DEBUG groq._base_client:_base_client.py:1001 Encountered httpx.HTTPStatusError Traceback (most recent call last): File "C:\Users\chime\PlotSense_Recloned\PlotSenseAI\venv\Lib\site-packages\groq\_base_client.py", line 999, in _request