Commit ·
2a9745a
1
Parent(s): 02cd7a8
feat: implement PDF export module, workflow pipeline, and web-based dashboard UI
Browse files- .gitignore +61 -43
- README.md +91 -157
- assets/complete_workflow.svg +191 -159
- body_fat_vs_situps.png +3 -0
- broad_jump_vs_situps.png +3 -0
- crew.py +25 -0
- data/sessions/0a66311e308d/cleaned.csv +0 -0
- data/sessions/0a66311e308d/done.txt +0 -1
- data/sessions/0a66311e308d/metadata.json +0 -8
- data/sessions/0a66311e308d/original.csv +0 -0
- data/sessions/0a66311e308d/original_upload.csv +0 -0
- data/sessions/0a66311e308d/results.json +0 -0
- data/sessions/0a66311e308d/stdout.log +0 -0
- data/sessions/14b48eadf665/cleaned.csv +0 -33
- data/sessions/14b48eadf665/done.txt +0 -1
- data/sessions/14b48eadf665/metadata.json +0 -9
- data/sessions/14b48eadf665/original.csv +0 -33
- data/sessions/14b48eadf665/original_upload.csv +0 -33
- data/sessions/14b48eadf665/results.json +0 -3035
- data/sessions/14b48eadf665/stdout.log +0 -0
- data/sessions/a402006b7ddd/cleaned.csv +0 -33
- data/sessions/a402006b7ddd/done.txt +0 -1
- data/sessions/a402006b7ddd/metadata.json +0 -9
- data/sessions/a402006b7ddd/original.csv +0 -33
- data/sessions/a402006b7ddd/original_upload.csv +0 -33
- data/sessions/a402006b7ddd/results.json +0 -0
- data/sessions/a402006b7ddd/stdout.log +0 -0
- data/sessions/c71065e6ffab/cleaned.csv +0 -0
- data/sessions/c71065e6ffab/done.txt +0 -1
- data/sessions/c71065e6ffab/metadata.json +0 -8
- data/sessions/c71065e6ffab/original.csv +0 -0
- data/sessions/c71065e6ffab/original_upload.csv +0 -0
- data/sessions/c71065e6ffab/results.json +0 -0
- data/sessions/c71065e6ffab/stdout.log +0 -0
- grip_force_vs_height.png +3 -0
- grip_force_vs_weight.png +3 -0
- height_vs_weight.png +3 -0
- main.py +251 -8
- requirements.txt +2 -2
- ui/copilot.py +11 -6
- ui/export.py +77 -9
- web/app.js +559 -124
- web/index.html +156 -85
- web/style.css +286 -25
- workflows/pipeline.py +77 -49
.gitignore
CHANGED
|
@@ -1,59 +1,77 @@
|
|
| 1 |
-
#
|
| 2 |
__pycache__/
|
| 3 |
-
*.
|
| 4 |
-
*.
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
.Python
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
pip-log.txt
|
| 11 |
pip-delete-this-directory.txt
|
| 12 |
|
| 13 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
.env
|
| 15 |
.env.local
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
outputs/
|
| 22 |
-
data/cleaned_csv.csv
|
| 23 |
-
# If you want to ignore all uploaded data:
|
| 24 |
-
# data/*
|
| 25 |
-
# !data/.gitkeep
|
| 26 |
|
| 27 |
# IDEs
|
| 28 |
.vscode/
|
| 29 |
.idea/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
# OS
|
| 32 |
.DS_Store
|
| 33 |
Thumbs.db
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
#
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
# Binary Files
|
| 40 |
-
*.png
|
| 41 |
-
*.jpg
|
| 42 |
-
*.jpeg
|
| 43 |
-
*.gif
|
| 44 |
-
*.ico
|
| 45 |
-
*.pdf
|
| 46 |
-
*.zip
|
| 47 |
-
*.tar
|
| 48 |
-
*.gz
|
| 49 |
-
*.7z
|
| 50 |
-
*.exe
|
| 51 |
-
*.dll
|
| 52 |
-
*.so
|
| 53 |
-
*.dylib
|
| 54 |
-
*.class
|
| 55 |
-
*.jar
|
| 56 |
-
*.war
|
| 57 |
-
*.ear
|
| 58 |
*.sqlite
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# Installer logs
|
| 30 |
pip-log.txt
|
| 31 |
pip-delete-this-directory.txt
|
| 32 |
|
| 33 |
+
# Unit test / coverage reports
|
| 34 |
+
htmlcov/
|
| 35 |
+
.tox/
|
| 36 |
+
.nosenv/
|
| 37 |
+
.pytest_cache/
|
| 38 |
+
.mypy_cache/
|
| 39 |
+
.ruff_cache/
|
| 40 |
+
|
| 41 |
+
# Environments
|
| 42 |
.env
|
| 43 |
.env.local
|
| 44 |
+
.env.*
|
| 45 |
+
.venv
|
| 46 |
+
venv/
|
| 47 |
+
ENV/
|
| 48 |
+
env/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
# IDEs
|
| 51 |
.vscode/
|
| 52 |
.idea/
|
| 53 |
+
*.suo
|
| 54 |
+
*.ntvs*
|
| 55 |
+
*.njsproj
|
| 56 |
+
*.sln
|
| 57 |
+
*.sw?
|
| 58 |
|
| 59 |
+
# OS Specific
|
| 60 |
.DS_Store
|
| 61 |
Thumbs.db
|
| 62 |
+
ehthumbs.db
|
| 63 |
+
Desktop.ini
|
| 64 |
|
| 65 |
+
# App-specific runtime databases and uploads
|
| 66 |
+
data/sessions/
|
| 67 |
+
outputs/
|
| 68 |
+
*.db
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
*.sqlite
|
| 70 |
+
|
| 71 |
+
# Ignore all user-uploaded datasets or exports, but keep .gitkeep files if any
|
| 72 |
+
data/*
|
| 73 |
+
!data/.gitkeep
|
| 74 |
+
|
| 75 |
+
# Local agent/tool files and pip cache
|
| 76 |
+
.local/
|
| 77 |
+
.pip_cache/
|
README.md
CHANGED
|
@@ -1,189 +1,123 @@
|
|
| 1 |
-
-
|
| 2 |
-
title: Multi Agent Data Analysis
|
| 3 |
-
emoji: 🤖
|
| 4 |
-
colorFrom: blue
|
| 5 |
-
colorTo: indigo
|
| 6 |
-
sdk: streamlit
|
| 7 |
-
sdk_version: 1.31.0
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
<!-- Enhanced README: hero, badges, tech icons, screenshots -->
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
# Multi Agent Data Analysis with Crew AI
|
| 17 |
|
| 18 |
<p align="center">
|
| 19 |
-
<img src="assets/complete_workflow.svg" alt="Multi
|
| 20 |
</p>
|
| 21 |
|
| 22 |
<p align="center">
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
</p>
|
| 31 |
|
| 32 |
-
> **Data
|
| 33 |
-
|
| 34 |
-
## Quick Links
|
| 35 |
|
| 36 |
-
-
|
| 37 |
-
- **CLI**: `python crew.py`
|
| 38 |
-
- **Agents**: `agents/` — each agent is a factory function that picks up the current LLM config at runtime.
|
| 39 |
-
- **Outputs**: session-isolated PNG visualizations under `outputs/<session_id>/`
|
| 40 |
-
- **Live Demo**: [Hugging Face Space](https://huggingface.co/spaces/sowmiyan-s/Multi-Agent-Data-Analysis-with-CrewAI)
|
| 41 |
-
|
| 42 |
-
## Quick Start
|
| 43 |
-
|
| 44 |
-
### Option 1: Streamlit Web App (Recommended)
|
| 45 |
|
| 46 |
-
|
| 47 |
-
```bash
|
| 48 |
-
streamlit run app.py
|
| 49 |
-
```
|
| 50 |
|
| 51 |
-
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
|
| 58 |
-
##
|
| 59 |
-
|
| 60 |
-
**
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
* Click **Create new Space**.
|
| 65 |
-
* Enter a name (e.g., `multi-agent-data-analyst`).
|
| 66 |
-
* Select **Streamlit** as the SDK.
|
| 67 |
-
* Click **Create Space**.
|
| 68 |
-
|
| 69 |
-
2. **Upload Files**:
|
| 70 |
-
* Clone your new Space locally:
|
| 71 |
-
```bash
|
| 72 |
-
git clone https://huggingface.co/spaces/your-username/multi-agent-data-analyst
|
| 73 |
-
```
|
| 74 |
-
* Copy all files from this project into that directory.
|
| 75 |
-
* Push the files:
|
| 76 |
-
```bash
|
| 77 |
-
git add .
|
| 78 |
-
git commit -m "Initial commit"
|
| 79 |
-
git push
|
| 80 |
-
```
|
| 81 |
-
* *Alternatively, you can upload files directly via the "Files" tab on the Hugging Face website.*
|
| 82 |
-
|
| 83 |
-
3. **Configure Secrets**:
|
| 84 |
-
* Go to the **Settings** tab of your Space.
|
| 85 |
-
* Scroll to **Variables and secrets**.
|
| 86 |
-
* Click **New secret**.
|
| 87 |
-
* Add your API keys (e.g., `GROQ_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`).
|
| 88 |
-
* *Note: You do not need to set `LLM_PROVIDER` here; the user selects it in the app.*
|
| 89 |
-
|
| 90 |
-
4. **Run**:
|
| 91 |
-
* The app will build and launch automatically!
|
| 92 |
-
|
| 93 |
-
## Installation
|
| 94 |
-
|
| 95 |
-
```powershell
|
| 96 |
-
python -m venv .venv
|
| 97 |
-
.\.venv\Scripts\Activate.ps1
|
| 98 |
-
pip install -r requirements.txt
|
| 99 |
-
```
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
3. Run the application.
|
| 104 |
-
|
| 105 |
-
4. Upload your CSV file (Web App) or enter the path (CLI).
|
| 106 |
|
| 107 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
-
-
|
| 110 |
-
- **Executive Dashboard**: Scorecards showing row count, column count, numeric and categorical field totals.
|
| 111 |
-
- **Tabbed Results View**: Five dedicated tabs — Data Preview, Clean Report, Relations Map, Key Insights, Visual Intelligence.
|
| 112 |
-
- **Real-time Agent Logs**: A terminal-style scrollable log widget streams agent output as the pipeline runs.
|
| 113 |
-
- **Structured Insights**: Styled bullet and relation cards for cleaning steps, column relationships, and BI insights.
|
| 114 |
-
- **Agent-Generated Visualizations**: PNG charts produced by the visualizer agent, displayed inline.
|
| 115 |
-
- **PDF & CSV Export**: Download a formatted PDF report or the cleaned dataset as CSV.
|
| 116 |
-
- **Per-session Isolation**: Each browser session writes to its own `data/sessions/<id>/` and `outputs/<id>/` directories.
|
| 117 |
-
- **Content-hashed Caching**: Results are cached by MD5 of the file content — no stale data on re-upload of the same file.
|
| 118 |
|
| 119 |
-
## Project Structure
|
| 120 |
|
| 121 |
```
|
| 122 |
.
|
| 123 |
-
├── agents/ #
|
| 124 |
-
│ ├── cleaner.py # Data Cleaner
|
| 125 |
-
│ ├── relation.py # Relationship Analyst
|
| 126 |
-
│ ├── insights.py #
|
| 127 |
-
│ └── visualizer.py #
|
| 128 |
-
├── config/ #
|
| 129 |
-
│ ├── llm_config.py #
|
| 130 |
│ └── __init__.py
|
| 131 |
-
├──
|
| 132 |
-
│ └──
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
│ └── <session_id>/
|
| 134 |
-
│ ├──
|
| 135 |
-
│
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
├──
|
| 141 |
-
|
| 142 |
-
│ ├��─ components.py # Bullet/relation renderers, StreamlitLogger
|
| 143 |
-
│ └── export.py # PDF report builder (ReportLab)
|
| 144 |
-
├── workflows/ # Pipeline orchestration
|
| 145 |
-
│ └── pipeline.py # Agent + Task factory (make_pipeline)
|
| 146 |
-
├── assets/ # Static assets (SVG badges, diagrams)
|
| 147 |
-
├── app.py # Streamlit entry point
|
| 148 |
-
├── crew.py # Core orchestration (run_crew)
|
| 149 |
-
├── requirements.txt # Python dependencies
|
| 150 |
├── README.md # This file
|
| 151 |
-
├── USAGE.md # Detailed
|
| 152 |
├── CHANGELOG.md # Version history
|
| 153 |
└── LICENSE # MIT License
|
| 154 |
```
|
| 155 |
|
| 156 |
-
## Customization
|
| 157 |
-
|
| 158 |
-
### Agent Configuration
|
| 159 |
-
Modify agent behaviours by editing files in `agents/`:
|
| 160 |
-
- **Cleaner**: Adjust cleaning heuristics in `agents/cleaner.py`.
|
| 161 |
-
- **Insights**: Change analysis focus in `agents/insights.py`.
|
| 162 |
-
- **Relation**: Tune relationship detection in `agents/relation.py`.
|
| 163 |
-
- **Visualizer**: Adjust chart generation prompts in `agents/visualizer.py`.
|
| 164 |
-
|
| 165 |
-
### Pipeline Extension
|
| 166 |
-
Extend analysis capabilities:
|
| 167 |
-
- Add new agent factories under `agents/`
|
| 168 |
-
- Register them in `workflows/pipeline.py`
|
| 169 |
-
|
| 170 |
-
## Contributing
|
| 171 |
-
|
| 172 |
-
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
| 173 |
-
|
| 174 |
-
## License
|
| 175 |
-
|
| 176 |
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
| 177 |
-
|
| 178 |
---
|
| 179 |
|
| 180 |
-
## Support
|
| 181 |
-
|
| 182 |
-
-
|
| 183 |
-
-
|
| 184 |
|
| 185 |
---
|
| 186 |
|
| 187 |
-
*Multi
|
| 188 |
-
*Copyright (c) 2025 Sowmiyan S*
|
| 189 |
*Licensed under the MIT License*
|
|
|
|
| 1 |
+
# Multi-Agent Data Analysis System with CrewAI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
<p align="center">
|
| 4 |
+
<img src="assets/complete_workflow.svg" alt="Multi-Agent Data Analysis System Workflow" width="100%" />
|
| 5 |
</p>
|
| 6 |
|
| 7 |
<p align="center">
|
| 8 |
+
<img src="assets/stars.svg" alt="5-star rating" height="28" />
|
| 9 |
+
|
| 10 |
+
<img src="assets/badge_crewai.svg" alt="crewai" height="28" />
|
| 11 |
+
<img src="assets/badge_pandas.svg" alt="pandas" height="28" />
|
| 12 |
+
<img src="assets/badge_matplotlib.svg" alt="matplotlib" height="28" />
|
| 13 |
+
<img src="assets/badge_seaborn.svg" alt="seaborn" height="28" />
|
| 14 |
+
<img src="assets/badge_ollama.svg" alt="ollama" height="28" />
|
| 15 |
</p>
|
| 16 |
|
| 17 |
+
> **Autonomous Data Intelligence as a Service** | A premium, modular data-analyst pipeline powered by LLM-driven agents. Upload a CSV to initialize a workspace, chat with your dataset in real-time, execute custom schema modifications via natural language, and run a complete multi-agent pipeline to generate structured audits, correlation maps, and executive business summaries.
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
## 🚀 Dual-Engine Workspace
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
Once a project is initialized, the system branches into two distinct, high-impact paths:
|
| 24 |
|
| 25 |
+
### Track A: AI Data Chat (Interactive Exploration)
|
| 26 |
+
- **Natural Language Querying**: Query your dataset directly to get condition-based rows, statistics, or aggregations.
|
| 27 |
+
- **On-the-Fly Data Prep**: Ask the copilot to perform edits in-place, such as `Rename column "Q3_Sales" to "Sales_Q3"` or `Delete column "Notes"`, and watch the live data preview table update dynamically.
|
| 28 |
+
- **Instant Visualizations**: Command the chat bot to create custom charts (e.g. *"plot a neon-purple scatter chart of rating vs cost"*). It writes and runs the matplotlib code in a sandboxed subprocess to output charts inline.
|
| 29 |
|
| 30 |
+
### Track B: Agentic Analysis (CrewAI Pipeline)
|
| 31 |
+
Select and run specific automated tasks through the multi-agent pipeline:
|
| 32 |
+
1. **Data Cleaner (🧹)**: Audits columns, formats values, drops redundant rows, and generates a structured cleaning audit trail.
|
| 33 |
+
2. **Relationship Mapper (🔗)**: Maps numeric and categorical variables, rendering zoomable, interactive **Plotly** correlation charts.
|
| 34 |
+
3. **Business Insights (💡)**: Analyzes statistical summaries and generates McKinsey/BCG consulting cards (Observation ➔ Implication ➔ Strategy) alongside critical risk alerts.
|
| 35 |
+
4. **Visualizer Agent (📈)**: Automatically creates, styles, and saves formatted matplotlib PNG graphs.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
+
## 🛠️ Installation & Setup
|
| 40 |
+
|
| 41 |
+
1. **Clone & Navigate**:
|
| 42 |
+
```bash
|
| 43 |
+
git clone https://github.com/your-username/Multi-Agent-Data-Analysis-System-with-CrewAI.git
|
| 44 |
+
cd Multi-Agent-Data-Analysis-System-with-CrewAI
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
2. **Initialize Environment**:
|
| 48 |
+
```bash
|
| 49 |
+
python -m venv .venv
|
| 50 |
+
# Windows:
|
| 51 |
+
.\.venv\Scripts\Activate.ps1
|
| 52 |
+
# macOS/Linux:
|
| 53 |
+
source .venv/bin/activate
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
3. **Install Dependencies**:
|
| 57 |
+
```bash
|
| 58 |
+
pip install -r requirements.txt
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
4. **Launch Server**:
|
| 62 |
+
```bash
|
| 63 |
+
# Start the FastAPI Web App
|
| 64 |
+
python -m uvicorn main:app --host 127.0.0.1 --port 8000
|
| 65 |
+
```
|
| 66 |
+
*Alternatively, double-click `run_web.bat` (Windows) to boot the server automatically.*
|
| 67 |
+
|
| 68 |
+
5. **Open Browser**:
|
| 69 |
+
Navigate to [http://127.0.0.1:8000](http://127.0.0.1:8000)
|
| 70 |
|
| 71 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
+
## 📂 Project Structure
|
| 74 |
|
| 75 |
```
|
| 76 |
.
|
| 77 |
+
├── agents/ # CrewAI Agent factories
|
| 78 |
+
│ ├── cleaner.py # 🧹 Data Cleaner Agent
|
| 79 |
+
│ ├── relation.py # 🔗 Relationship Analyst Agent
|
| 80 |
+
│ ├── insights.py # 💡 BI McKinsey Insights Agent
|
| 81 |
+
│ └── visualizer.py # 📈 Matplotlib Visualizer Agent
|
| 82 |
+
├── config/ # Platform configuration
|
| 83 |
+
│ ├── llm_config.py # Multi-Provider settings and model catalog
|
| 84 |
│ └── __init__.py
|
| 85 |
+
├── tools/ # Orchestration tools
|
| 86 |
+
│ └── dataset_tools.py # read_head, subprocess sandbox runner, plotly builder
|
| 87 |
+
├── ui/ # Document export services
|
| 88 |
+
│ └── export.py # Formatted PDF Cover & Content builder
|
| 89 |
+
├── workflows/ # Workflow pipelines
|
| 90 |
+
│ └── pipeline.py # Make pipeline orchestration (adaptive cooldown)
|
| 91 |
+
├── web/ # Web Frontend Assets
|
| 92 |
+
│ ├── index.html # Glassmorphic Workspace structure
|
| 93 |
+
│ ├── app.js # Frontend core logic (SSE logs, Chat, API hooks)
|
| 94 |
+
│ └── style.css # Dark Electric-Violet Theme styles
|
| 95 |
+
├── data/ # Dynamic project sessions
|
| 96 |
+
│ └── sessions/ # Concurrency-isolated session directories
|
| 97 |
│ └── <session_id>/
|
| 98 |
+
│ ├── original_upload.csv
|
| 99 |
+
│ ├── cleaned.csv
|
| 100 |
+
│ └── metadata.json
|
| 101 |
+
├── outputs/ # Sandbox generated PNG charts
|
| 102 |
+
│ └── <session_id>/
|
| 103 |
+
├── assets/ # Static icons and complete_workflow.svg
|
| 104 |
+
├── requirements.txt # Python package catalog
|
| 105 |
+
├── main.py # FastAPI backend routing endpoints
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
├── README.md # This file
|
| 107 |
+
├── USAGE.md # Detailed user guide
|
| 108 |
├── CHANGELOG.md # Version history
|
| 109 |
└── LICENSE # MIT License
|
| 110 |
```
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
---
|
| 113 |
|
| 114 |
+
## ⚙️ Provider Gateway Support
|
| 115 |
+
The system integrates a custom gateway supporting **13+ LLM providers** through local configuration or environment variables:
|
| 116 |
+
- **Cloud Gateways**: OpenAI, Anthropic, Google Gemini, NVIDIA NIM, Groq, Mistral, TogetherAI, Cohere, OpenRouter, DeepSeek, Perplexity, HuggingFace.
|
| 117 |
+
- **Local Sandbox**: Ollama (auto-detects local models via the Ollama catalog).
|
| 118 |
|
| 119 |
---
|
| 120 |
|
| 121 |
+
*Multi-Agent Data Analysis System*
|
| 122 |
+
*Copyright (c) 2025 Sowmiyan S*
|
| 123 |
*Licensed under the MIT License*
|
assets/complete_workflow.svg
CHANGED
|
|
|
|
body_fat_vs_situps.png
ADDED
|
Git LFS Details
|
broad_jump_vs_situps.png
ADDED
|
Git LFS Details
|
crew.py
CHANGED
|
@@ -475,12 +475,37 @@ def run_crew(
|
|
| 475 |
do_insights = "insights" in env_tasks
|
| 476 |
do_visualization = "visualization" in env_tasks
|
| 477 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
# ── Build fresh agents + tasks ────────────────────────────────────────────
|
| 479 |
agents, tasks = make_pipeline(
|
| 480 |
session_id,
|
| 481 |
profile=profile,
|
| 482 |
selected_tasks=env_tasks,
|
| 483 |
deep_analysis=deep_analysis,
|
|
|
|
|
|
|
|
|
|
| 484 |
)
|
| 485 |
# tasks = [clean_task, relation_task, insight_task, visualize_task]
|
| 486 |
|
|
|
|
| 475 |
do_insights = "insights" in env_tasks
|
| 476 |
do_visualization = "visualization" in env_tasks
|
| 477 |
|
| 478 |
+
# Load goal, title, and existing tweaked relations if available
|
| 479 |
+
project_goal = ""
|
| 480 |
+
report_title = ""
|
| 481 |
+
existing_relations = ""
|
| 482 |
+
try:
|
| 483 |
+
import json
|
| 484 |
+
meta_path = Path("data/sessions") / session_id / "metadata.json"
|
| 485 |
+
if meta_path.exists():
|
| 486 |
+
with open(meta_path, "r", encoding="utf-8") as f:
|
| 487 |
+
meta = json.load(f)
|
| 488 |
+
project_goal = meta.get("optimized_goal") or meta.get("goal") or ""
|
| 489 |
+
report_title = meta.get("report_title") or ""
|
| 490 |
+
|
| 491 |
+
# Load tweaked relations from results.json
|
| 492 |
+
results_path = Path("data/sessions") / session_id / "results.json"
|
| 493 |
+
if results_path.exists():
|
| 494 |
+
with open(results_path, "r", encoding="utf-8") as f:
|
| 495 |
+
res_data = json.load(f)
|
| 496 |
+
existing_relations = res_data.get("relations") or ""
|
| 497 |
+
except Exception as e:
|
| 498 |
+
print(f"Warning: Could not read metadata or results cache: {e}")
|
| 499 |
+
|
| 500 |
# ── Build fresh agents + tasks ────────────────────────────────────────────
|
| 501 |
agents, tasks = make_pipeline(
|
| 502 |
session_id,
|
| 503 |
profile=profile,
|
| 504 |
selected_tasks=env_tasks,
|
| 505 |
deep_analysis=deep_analysis,
|
| 506 |
+
project_goal=project_goal,
|
| 507 |
+
report_title=report_title,
|
| 508 |
+
existing_relations=existing_relations,
|
| 509 |
)
|
| 510 |
# tasks = [clean_task, relation_task, insight_task, visualize_task]
|
| 511 |
|
data/sessions/0a66311e308d/cleaned.csv
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/0a66311e308d/done.txt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
done
|
|
|
|
|
|
data/sessions/0a66311e308d/metadata.json
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"id": "0a66311e308d",
|
| 3 |
-
"name": "Earthquakes",
|
| 4 |
-
"filename": "earthquakes.csv",
|
| 5 |
-
"size": 2397103,
|
| 6 |
-
"created_at": 1782643882405.215,
|
| 7 |
-
"status": "completed"
|
| 8 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/0a66311e308d/original.csv
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/0a66311e308d/original_upload.csv
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/0a66311e308d/results.json
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/0a66311e308d/stdout.log
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/14b48eadf665/cleaned.csv
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
model,mpg,cyl,hp,drat,wt,qsec,vs,am,gear,carb
|
| 2 |
-
Mazda RX4,21.0,6,110,3.9,2.62,16.46,0,1,4,4
|
| 3 |
-
Mazda RX4 Wag,21.0,6,110,3.9,2.875,17.02,0,1,4,4
|
| 4 |
-
Datsun 710,22.8,4,93,3.85,2.32,18.61,1,1,4,1
|
| 5 |
-
Hornet 4 Drive,21.4,6,110,3.08,3.215,19.44,1,0,3,1
|
| 6 |
-
Hornet Sportabout,18.7,8,175,3.15,3.44,17.02,0,0,3,2
|
| 7 |
-
Valiant,18.1,6,105,2.76,3.46,20.22,1,0,3,1
|
| 8 |
-
Duster 360,14.3,8,245,3.21,3.57,15.84,0,0,3,4
|
| 9 |
-
Merc 240D,24.4,4,62,3.69,3.19,20.0,1,0,4,2
|
| 10 |
-
Merc 230,22.8,4,95,3.92,3.15,22.9,1,0,4,2
|
| 11 |
-
Merc 280,19.2,6,123,3.92,3.44,18.3,1,0,4,4
|
| 12 |
-
Merc 280C,17.8,6,123,3.92,3.44,18.9,1,0,4,4
|
| 13 |
-
Merc 450SE,16.4,8,180,3.07,4.07,17.4,0,0,3,3
|
| 14 |
-
Merc 450SL,17.3,8,180,3.07,3.73,17.6,0,0,3,3
|
| 15 |
-
Merc 450SLC,15.2,8,180,3.07,3.78,18.0,0,0,3,3
|
| 16 |
-
Cadillac Fleetwood,10.4,8,205,2.93,5.25,17.98,0,0,3,4
|
| 17 |
-
Lincoln Continental,10.4,8,215,3.0,5.424,17.82,0,0,3,4
|
| 18 |
-
Chrysler Imperial,14.7,8,230,3.23,5.345,17.42,0,0,3,4
|
| 19 |
-
Fiat 128,32.4,4,66,4.08,2.2,19.47,1,1,4,1
|
| 20 |
-
Honda Civic,30.4,4,52,4.93,1.615,18.52,1,1,4,2
|
| 21 |
-
Toyota Corolla,33.9,4,65,4.22,1.835,19.9,1,1,4,1
|
| 22 |
-
Toyota Corona,21.5,4,97,3.7,2.465,20.01,1,0,3,1
|
| 23 |
-
Dodge Challenger,15.5,8,150,2.76,3.52,16.87,0,0,3,2
|
| 24 |
-
AMC Javelin,15.2,8,150,3.15,3.435,17.3,0,0,3,2
|
| 25 |
-
Camaro Z28,13.3,8,245,3.73,3.84,15.41,0,0,3,4
|
| 26 |
-
Pontiac Firebird,19.2,8,175,3.08,3.845,17.05,0,0,3,2
|
| 27 |
-
Fiat X1-9,27.3,4,66,4.08,1.935,18.9,1,1,4,1
|
| 28 |
-
Porsche 914-2,26.0,4,91,4.43,2.14,16.7,0,1,5,2
|
| 29 |
-
Lotus Europa,30.4,4,113,3.77,1.513,16.9,1,1,5,2
|
| 30 |
-
Ford Pantera L,15.8,8,264,4.22,3.17,14.5,0,1,5,4
|
| 31 |
-
Ferrari Dino,19.7,6,175,3.62,2.77,15.5,0,1,5,6
|
| 32 |
-
Maserati Bora,15.0,8,335,3.54,3.57,14.6,0,1,5,8
|
| 33 |
-
Volvo 142E,21.4,4,109,4.11,2.78,18.6,1,1,4,2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/14b48eadf665/done.txt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
done
|
|
|
|
|
|
data/sessions/14b48eadf665/metadata.json
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"id": "14b48eadf665",
|
| 3 |
-
"name": "Analysis - 6/28/2026",
|
| 4 |
-
"report_title": "Executive Analysis Report",
|
| 5 |
-
"filename": "mtcars.csv",
|
| 6 |
-
"size": 1788,
|
| 7 |
-
"created_at": 1782627889706.4026,
|
| 8 |
-
"status": "completed"
|
| 9 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/14b48eadf665/original.csv
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
model,mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
|
| 2 |
-
Mazda RX4,21.0,6,160.0,110,3.9,2.62,16.46,0,1,4,4
|
| 3 |
-
Mazda RX4 Wag,21.0,6,160.0,110,3.9,2.875,17.02,0,1,4,4
|
| 4 |
-
Datsun 710,22.8,4,108.0,93,3.85,2.32,18.61,1,1,4,1
|
| 5 |
-
Hornet 4 Drive,21.4,6,258.0,110,3.08,3.215,19.44,1,0,3,1
|
| 6 |
-
Hornet Sportabout,18.7,8,360.0,175,3.15,3.44,17.02,0,0,3,2
|
| 7 |
-
Valiant,18.1,6,225.0,105,2.76,3.46,20.22,1,0,3,1
|
| 8 |
-
Duster 360,14.3,8,360.0,245,3.21,3.57,15.84,0,0,3,4
|
| 9 |
-
Merc 240D,24.4,4,146.7,62,3.69,3.19,20.0,1,0,4,2
|
| 10 |
-
Merc 230,22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
|
| 11 |
-
Merc 280,19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
|
| 12 |
-
Merc 280C,17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
|
| 13 |
-
Merc 450SE,16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
|
| 14 |
-
Merc 450SL,17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
|
| 15 |
-
Merc 450SLC,15.2,8,275.8,180,3.07,3.78,18.0,0,0,3,3
|
| 16 |
-
Cadillac Fleetwood,10.4,8,472.0,205,2.93,5.25,17.98,0,0,3,4
|
| 17 |
-
Lincoln Continental,10.4,8,460.0,215,3.0,5.424,17.82,0,0,3,4
|
| 18 |
-
Chrysler Imperial,14.7,8,440.0,230,3.23,5.345,17.42,0,0,3,4
|
| 19 |
-
Fiat 128,32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
|
| 20 |
-
Honda Civic,30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
|
| 21 |
-
Toyota Corolla,33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
|
| 22 |
-
Toyota Corona,21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
|
| 23 |
-
Dodge Challenger,15.5,8,318.0,150,2.76,3.52,16.87,0,0,3,2
|
| 24 |
-
AMC Javelin,15.2,8,304.0,150,3.15,3.435,17.3,0,0,3,2
|
| 25 |
-
Camaro Z28,13.3,8,350.0,245,3.73,3.84,15.41,0,0,3,4
|
| 26 |
-
Pontiac Firebird,19.2,8,400.0,175,3.08,3.845,17.05,0,0,3,2
|
| 27 |
-
Fiat X1-9,27.3,4,79.0,66,4.08,1.935,18.9,1,1,4,1
|
| 28 |
-
Porsche 914-2,26.0,4,120.3,91,4.43,2.14,16.7,0,1,5,2
|
| 29 |
-
Lotus Europa,30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
|
| 30 |
-
Ford Pantera L,15.8,8,351.0,264,4.22,3.17,14.5,0,1,5,4
|
| 31 |
-
Ferrari Dino,19.7,6,145.0,175,3.62,2.77,15.5,0,1,5,6
|
| 32 |
-
Maserati Bora,15.0,8,301.0,335,3.54,3.57,14.6,0,1,5,8
|
| 33 |
-
Volvo 142E,21.4,4,121.0,109,4.11,2.78,18.6,1,1,4,2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/14b48eadf665/original_upload.csv
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
"model","mpg","cyl","disp","hp","drat","wt","qsec","vs","am","gear","carb"
|
| 2 |
-
"Mazda RX4",21,6,160,110,3.9,2.62,16.46,0,1,4,4
|
| 3 |
-
"Mazda RX4 Wag",21,6,160,110,3.9,2.875,17.02,0,1,4,4
|
| 4 |
-
"Datsun 710",22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
|
| 5 |
-
"Hornet 4 Drive",21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
|
| 6 |
-
"Hornet Sportabout",18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
|
| 7 |
-
"Valiant",18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
|
| 8 |
-
"Duster 360",14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
|
| 9 |
-
"Merc 240D",24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
|
| 10 |
-
"Merc 230",22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
|
| 11 |
-
"Merc 280",19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
|
| 12 |
-
"Merc 280C",17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
|
| 13 |
-
"Merc 450SE",16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
|
| 14 |
-
"Merc 450SL",17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
|
| 15 |
-
"Merc 450SLC",15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
|
| 16 |
-
"Cadillac Fleetwood",10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
|
| 17 |
-
"Lincoln Continental",10.4,8,460,215,3,5.424,17.82,0,0,3,4
|
| 18 |
-
"Chrysler Imperial",14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
|
| 19 |
-
"Fiat 128",32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
|
| 20 |
-
"Honda Civic",30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
|
| 21 |
-
"Toyota Corolla",33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
|
| 22 |
-
"Toyota Corona",21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
|
| 23 |
-
"Dodge Challenger",15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
|
| 24 |
-
"AMC Javelin",15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
|
| 25 |
-
"Camaro Z28",13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
|
| 26 |
-
"Pontiac Firebird",19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
|
| 27 |
-
"Fiat X1-9",27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
|
| 28 |
-
"Porsche 914-2",26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
|
| 29 |
-
"Lotus Europa",30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
|
| 30 |
-
"Ford Pantera L",15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
|
| 31 |
-
"Ferrari Dino",19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
|
| 32 |
-
"Maserati Bora",15,8,301,335,3.54,3.57,14.6,0,1,5,8
|
| 33 |
-
"Volvo 142E",21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/14b48eadf665/results.json
DELETED
|
@@ -1,3035 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cleaning_steps": "- Dropped the redundant 'disp' column to prevent multicollinearity issues in analysis.",
|
| 3 |
-
"relations": "- X: cyl | Y: disp | Type: Scatter Plot\n- X: mpg | Y: wt | Type: Scatter Plot\n- X: cyl | Y: mpg | Type: Scatter Plot\n- X: disp | Y: wt | Type: Scatter Plot\n- X: mpg | Y: cyl | Type: Scatter Plot",
|
| 4 |
-
"insights": "1. **Observation**: The dataset contains a strong positive correlation between the number of cylinders (cyl) and the engine displacement (disp), with a correlation coefficient of 0.902.\n **Business Implication**: This suggests that as the number of cylinders increases, the engine displacement also tends to increase, which can impact the overall weight and fuel efficiency of the vehicle.\n **Actionable Strategy**: To optimize engine design and reduce fuel consumption, the company should focus on developing more efficient engine technologies that can achieve higher power outputs with fewer cylinders, such as turbocharging or supercharging.\n\n2. **Observation**: There is a strong negative correlation between the weight (wt) and miles per gallon (mpg), with a correlation coefficient of -0.868.\n **Business Implication**: This indicates that as the weight of the vehicle increases, its fuel efficiency tends to decrease, which can have significant implications for fuel costs and emissions.\n **Actionable Strategy**: To reduce fuel consumption and emissions, the company should prioritize lightweight materials and design techniques in its vehicle development process, such as using aluminum or carbon fiber in body panels and chassis components.\n\n3. **Observation**: The dataset shows a strong positive correlation between the engine displacement (disp) and the weight (wt), with a correlation coefficient of 0.888.\n **Business Implication**: This suggests that as the engine displacement increases, the weight of the vehicle also tends to increase, which can impact its fuel efficiency and overall performance.\n **Actionable Strategy**: To balance engine power and weight, the company should focus on developing more efficient engine technologies that can achieve higher power outputs with fewer cylinders, such as turbocharging or supercharging.\n\n4. **Observation**: There is a strong negative correlation between the number of cylinders (cyl) and the miles per gallon (mpg), with a correlation coefficient of -0.852.\n **Business Implication**: This indicates that as the number of cylinders increases, the fuel efficiency of the vehicle tends to decrease, which can have significant implications for fuel costs and emissions.\n **Actionable Strategy**: To reduce fuel consumption and emissions, the company should prioritize developing more efficient engine technologies that can achieve higher power outputs with fewer cylinders, such as turbocharging or supercharging.\n\n5. **Observation**: The dataset shows a strong negative correlation between the engine displacement (disp) and the miles per gallon (mpg), with a correlation coefficient of -0.848.\n **Business Implication**: This suggests that as the engine displacement increases, the fuel efficiency of the vehicle tends to decrease, which can have significant implications for fuel costs and emissions.\n **Actionable Strategy**: To balance engine power and fuel efficiency, the company should focus on developing more efficient engine technologies that can achieve higher power outputs with fewer cylinders, such as turbocharging or supercharging.",
|
| 5 |
-
"code": "Summary of generated and saved visualization plots:\n- cyl vs disp scatter plot saved as cyl_disp_scatter.png\n- mpg vs wt scatter plot saved as mpg_wt_scatter.png\n- cyl vs mpg scatter plot saved as cyl_mpg_scatter.png\n- disp vs wt scatter plot saved as disp_wt_scatter.png\n- mpg vs cyl scatter plot saved as mpg_cyl_scatter.png\n\n[Auto-Healing Fallback Status]: Generated 4 auto-healing chart(s) in fallback.",
|
| 6 |
-
"output_dir": "outputs\\14b48eadf665",
|
| 7 |
-
"plotly_charts": [
|
| 8 |
-
{
|
| 9 |
-
"title": "mpg vs wt",
|
| 10 |
-
"fig_json": {
|
| 11 |
-
"data": [
|
| 12 |
-
{
|
| 13 |
-
"hovertemplate": "mpg=%{x}<br>wt=%{y}<extra></extra>",
|
| 14 |
-
"legendgroup": "",
|
| 15 |
-
"marker": {
|
| 16 |
-
"color": "#a78bfa",
|
| 17 |
-
"opacity": 0.75,
|
| 18 |
-
"symbol": "circle"
|
| 19 |
-
},
|
| 20 |
-
"mode": "markers",
|
| 21 |
-
"name": "",
|
| 22 |
-
"orientation": "v",
|
| 23 |
-
"showlegend": false,
|
| 24 |
-
"x": {
|
| 25 |
-
"dtype": "f8",
|
| 26 |
-
"bdata": "AAAAAAAANUAAAAAAAAA1QM3MzMzMzDZAZmZmZmZmNUAzMzMzM7MyQJqZmZmZGTJAmpmZmZmZLEBmZmZmZmY4QM3MzMzMzDZAMzMzMzMzM0DNzMzMzMwxQGZmZmZmZjBAzczMzMxMMUBmZmZmZmYuQM3MzMzMzCRAzczMzMzMJEBmZmZmZmYtQDMzMzMzM0BAZmZmZmZmPkAzMzMzM/NAQAAAAAAAgDVAAAAAAAAAL0BmZmZmZmYuQJqZmZmZmSpAMzMzMzMzM0DNzMzMzEw7QAAAAAAAADpAZmZmZmZmPkCamZmZmZkvQDMzMzMzszNAAAAAAAAALkBmZmZmZmY1QA=="
|
| 27 |
-
},
|
| 28 |
-
"xaxis": "x",
|
| 29 |
-
"y": {
|
| 30 |
-
"dtype": "f8",
|
| 31 |
-
"bdata": "9ihcj8L1BEAAAAAAAAAHQI/C9ShcjwJAuB6F61G4CUCF61G4HoULQK5H4XoUrgtAj8L1KFyPDECF61G4HoUJQDMzMzMzMwlAhetRuB6FC0CF61G4HoULQEjhehSuRxBA16NwPQrXDUA9CtejcD0OQAAAAAAAABVAGQRWDi2yFUDhehSuR2EVQJqZmZmZmQFA16NwPQrX+T9cj8L1KFz9P7gehetRuANAKVyPwvUoDEB7FK5H4XoLQLgehetRuA5Aw/UoXI/CDkD2KFyPwvX+Px+F61G4HgFAaJHtfD81+D9cj8L1KFwJQClcj8L1KAZAj8L1KFyPDEA9CtejcD0GQA=="
|
| 32 |
-
},
|
| 33 |
-
"yaxis": "y",
|
| 34 |
-
"type": "scatter"
|
| 35 |
-
}
|
| 36 |
-
],
|
| 37 |
-
"layout": {
|
| 38 |
-
"template": {
|
| 39 |
-
"data": {
|
| 40 |
-
"histogram2dcontour": [
|
| 41 |
-
{
|
| 42 |
-
"type": "histogram2dcontour",
|
| 43 |
-
"colorbar": {
|
| 44 |
-
"outlinewidth": 0,
|
| 45 |
-
"ticks": ""
|
| 46 |
-
},
|
| 47 |
-
"colorscale": [
|
| 48 |
-
[
|
| 49 |
-
0.0,
|
| 50 |
-
"#0d0887"
|
| 51 |
-
],
|
| 52 |
-
[
|
| 53 |
-
0.1111111111111111,
|
| 54 |
-
"#46039f"
|
| 55 |
-
],
|
| 56 |
-
[
|
| 57 |
-
0.2222222222222222,
|
| 58 |
-
"#7201a8"
|
| 59 |
-
],
|
| 60 |
-
[
|
| 61 |
-
0.3333333333333333,
|
| 62 |
-
"#9c179e"
|
| 63 |
-
],
|
| 64 |
-
[
|
| 65 |
-
0.4444444444444444,
|
| 66 |
-
"#bd3786"
|
| 67 |
-
],
|
| 68 |
-
[
|
| 69 |
-
0.5555555555555556,
|
| 70 |
-
"#d8576b"
|
| 71 |
-
],
|
| 72 |
-
[
|
| 73 |
-
0.6666666666666666,
|
| 74 |
-
"#ed7953"
|
| 75 |
-
],
|
| 76 |
-
[
|
| 77 |
-
0.7777777777777778,
|
| 78 |
-
"#fb9f3a"
|
| 79 |
-
],
|
| 80 |
-
[
|
| 81 |
-
0.8888888888888888,
|
| 82 |
-
"#fdca26"
|
| 83 |
-
],
|
| 84 |
-
[
|
| 85 |
-
1.0,
|
| 86 |
-
"#f0f921"
|
| 87 |
-
]
|
| 88 |
-
]
|
| 89 |
-
}
|
| 90 |
-
],
|
| 91 |
-
"choropleth": [
|
| 92 |
-
{
|
| 93 |
-
"type": "choropleth",
|
| 94 |
-
"colorbar": {
|
| 95 |
-
"outlinewidth": 0,
|
| 96 |
-
"ticks": ""
|
| 97 |
-
}
|
| 98 |
-
}
|
| 99 |
-
],
|
| 100 |
-
"histogram2d": [
|
| 101 |
-
{
|
| 102 |
-
"type": "histogram2d",
|
| 103 |
-
"colorbar": {
|
| 104 |
-
"outlinewidth": 0,
|
| 105 |
-
"ticks": ""
|
| 106 |
-
},
|
| 107 |
-
"colorscale": [
|
| 108 |
-
[
|
| 109 |
-
0.0,
|
| 110 |
-
"#0d0887"
|
| 111 |
-
],
|
| 112 |
-
[
|
| 113 |
-
0.1111111111111111,
|
| 114 |
-
"#46039f"
|
| 115 |
-
],
|
| 116 |
-
[
|
| 117 |
-
0.2222222222222222,
|
| 118 |
-
"#7201a8"
|
| 119 |
-
],
|
| 120 |
-
[
|
| 121 |
-
0.3333333333333333,
|
| 122 |
-
"#9c179e"
|
| 123 |
-
],
|
| 124 |
-
[
|
| 125 |
-
0.4444444444444444,
|
| 126 |
-
"#bd3786"
|
| 127 |
-
],
|
| 128 |
-
[
|
| 129 |
-
0.5555555555555556,
|
| 130 |
-
"#d8576b"
|
| 131 |
-
],
|
| 132 |
-
[
|
| 133 |
-
0.6666666666666666,
|
| 134 |
-
"#ed7953"
|
| 135 |
-
],
|
| 136 |
-
[
|
| 137 |
-
0.7777777777777778,
|
| 138 |
-
"#fb9f3a"
|
| 139 |
-
],
|
| 140 |
-
[
|
| 141 |
-
0.8888888888888888,
|
| 142 |
-
"#fdca26"
|
| 143 |
-
],
|
| 144 |
-
[
|
| 145 |
-
1.0,
|
| 146 |
-
"#f0f921"
|
| 147 |
-
]
|
| 148 |
-
]
|
| 149 |
-
}
|
| 150 |
-
],
|
| 151 |
-
"heatmap": [
|
| 152 |
-
{
|
| 153 |
-
"type": "heatmap",
|
| 154 |
-
"colorbar": {
|
| 155 |
-
"outlinewidth": 0,
|
| 156 |
-
"ticks": ""
|
| 157 |
-
},
|
| 158 |
-
"colorscale": [
|
| 159 |
-
[
|
| 160 |
-
0.0,
|
| 161 |
-
"#0d0887"
|
| 162 |
-
],
|
| 163 |
-
[
|
| 164 |
-
0.1111111111111111,
|
| 165 |
-
"#46039f"
|
| 166 |
-
],
|
| 167 |
-
[
|
| 168 |
-
0.2222222222222222,
|
| 169 |
-
"#7201a8"
|
| 170 |
-
],
|
| 171 |
-
[
|
| 172 |
-
0.3333333333333333,
|
| 173 |
-
"#9c179e"
|
| 174 |
-
],
|
| 175 |
-
[
|
| 176 |
-
0.4444444444444444,
|
| 177 |
-
"#bd3786"
|
| 178 |
-
],
|
| 179 |
-
[
|
| 180 |
-
0.5555555555555556,
|
| 181 |
-
"#d8576b"
|
| 182 |
-
],
|
| 183 |
-
[
|
| 184 |
-
0.6666666666666666,
|
| 185 |
-
"#ed7953"
|
| 186 |
-
],
|
| 187 |
-
[
|
| 188 |
-
0.7777777777777778,
|
| 189 |
-
"#fb9f3a"
|
| 190 |
-
],
|
| 191 |
-
[
|
| 192 |
-
0.8888888888888888,
|
| 193 |
-
"#fdca26"
|
| 194 |
-
],
|
| 195 |
-
[
|
| 196 |
-
1.0,
|
| 197 |
-
"#f0f921"
|
| 198 |
-
]
|
| 199 |
-
]
|
| 200 |
-
}
|
| 201 |
-
],
|
| 202 |
-
"contourcarpet": [
|
| 203 |
-
{
|
| 204 |
-
"type": "contourcarpet",
|
| 205 |
-
"colorbar": {
|
| 206 |
-
"outlinewidth": 0,
|
| 207 |
-
"ticks": ""
|
| 208 |
-
}
|
| 209 |
-
}
|
| 210 |
-
],
|
| 211 |
-
"contour": [
|
| 212 |
-
{
|
| 213 |
-
"type": "contour",
|
| 214 |
-
"colorbar": {
|
| 215 |
-
"outlinewidth": 0,
|
| 216 |
-
"ticks": ""
|
| 217 |
-
},
|
| 218 |
-
"colorscale": [
|
| 219 |
-
[
|
| 220 |
-
0.0,
|
| 221 |
-
"#0d0887"
|
| 222 |
-
],
|
| 223 |
-
[
|
| 224 |
-
0.1111111111111111,
|
| 225 |
-
"#46039f"
|
| 226 |
-
],
|
| 227 |
-
[
|
| 228 |
-
0.2222222222222222,
|
| 229 |
-
"#7201a8"
|
| 230 |
-
],
|
| 231 |
-
[
|
| 232 |
-
0.3333333333333333,
|
| 233 |
-
"#9c179e"
|
| 234 |
-
],
|
| 235 |
-
[
|
| 236 |
-
0.4444444444444444,
|
| 237 |
-
"#bd3786"
|
| 238 |
-
],
|
| 239 |
-
[
|
| 240 |
-
0.5555555555555556,
|
| 241 |
-
"#d8576b"
|
| 242 |
-
],
|
| 243 |
-
[
|
| 244 |
-
0.6666666666666666,
|
| 245 |
-
"#ed7953"
|
| 246 |
-
],
|
| 247 |
-
[
|
| 248 |
-
0.7777777777777778,
|
| 249 |
-
"#fb9f3a"
|
| 250 |
-
],
|
| 251 |
-
[
|
| 252 |
-
0.8888888888888888,
|
| 253 |
-
"#fdca26"
|
| 254 |
-
],
|
| 255 |
-
[
|
| 256 |
-
1.0,
|
| 257 |
-
"#f0f921"
|
| 258 |
-
]
|
| 259 |
-
]
|
| 260 |
-
}
|
| 261 |
-
],
|
| 262 |
-
"surface": [
|
| 263 |
-
{
|
| 264 |
-
"type": "surface",
|
| 265 |
-
"colorbar": {
|
| 266 |
-
"outlinewidth": 0,
|
| 267 |
-
"ticks": ""
|
| 268 |
-
},
|
| 269 |
-
"colorscale": [
|
| 270 |
-
[
|
| 271 |
-
0.0,
|
| 272 |
-
"#0d0887"
|
| 273 |
-
],
|
| 274 |
-
[
|
| 275 |
-
0.1111111111111111,
|
| 276 |
-
"#46039f"
|
| 277 |
-
],
|
| 278 |
-
[
|
| 279 |
-
0.2222222222222222,
|
| 280 |
-
"#7201a8"
|
| 281 |
-
],
|
| 282 |
-
[
|
| 283 |
-
0.3333333333333333,
|
| 284 |
-
"#9c179e"
|
| 285 |
-
],
|
| 286 |
-
[
|
| 287 |
-
0.4444444444444444,
|
| 288 |
-
"#bd3786"
|
| 289 |
-
],
|
| 290 |
-
[
|
| 291 |
-
0.5555555555555556,
|
| 292 |
-
"#d8576b"
|
| 293 |
-
],
|
| 294 |
-
[
|
| 295 |
-
0.6666666666666666,
|
| 296 |
-
"#ed7953"
|
| 297 |
-
],
|
| 298 |
-
[
|
| 299 |
-
0.7777777777777778,
|
| 300 |
-
"#fb9f3a"
|
| 301 |
-
],
|
| 302 |
-
[
|
| 303 |
-
0.8888888888888888,
|
| 304 |
-
"#fdca26"
|
| 305 |
-
],
|
| 306 |
-
[
|
| 307 |
-
1.0,
|
| 308 |
-
"#f0f921"
|
| 309 |
-
]
|
| 310 |
-
]
|
| 311 |
-
}
|
| 312 |
-
],
|
| 313 |
-
"mesh3d": [
|
| 314 |
-
{
|
| 315 |
-
"type": "mesh3d",
|
| 316 |
-
"colorbar": {
|
| 317 |
-
"outlinewidth": 0,
|
| 318 |
-
"ticks": ""
|
| 319 |
-
}
|
| 320 |
-
}
|
| 321 |
-
],
|
| 322 |
-
"scatter": [
|
| 323 |
-
{
|
| 324 |
-
"fillpattern": {
|
| 325 |
-
"fillmode": "overlay",
|
| 326 |
-
"size": 10,
|
| 327 |
-
"solidity": 0.2
|
| 328 |
-
},
|
| 329 |
-
"type": "scatter"
|
| 330 |
-
}
|
| 331 |
-
],
|
| 332 |
-
"parcoords": [
|
| 333 |
-
{
|
| 334 |
-
"type": "parcoords",
|
| 335 |
-
"line": {
|
| 336 |
-
"colorbar": {
|
| 337 |
-
"outlinewidth": 0,
|
| 338 |
-
"ticks": ""
|
| 339 |
-
}
|
| 340 |
-
}
|
| 341 |
-
}
|
| 342 |
-
],
|
| 343 |
-
"scatterpolargl": [
|
| 344 |
-
{
|
| 345 |
-
"type": "scatterpolargl",
|
| 346 |
-
"marker": {
|
| 347 |
-
"colorbar": {
|
| 348 |
-
"outlinewidth": 0,
|
| 349 |
-
"ticks": ""
|
| 350 |
-
}
|
| 351 |
-
}
|
| 352 |
-
}
|
| 353 |
-
],
|
| 354 |
-
"bar": [
|
| 355 |
-
{
|
| 356 |
-
"error_x": {
|
| 357 |
-
"color": "#2a3f5f"
|
| 358 |
-
},
|
| 359 |
-
"error_y": {
|
| 360 |
-
"color": "#2a3f5f"
|
| 361 |
-
},
|
| 362 |
-
"marker": {
|
| 363 |
-
"line": {
|
| 364 |
-
"color": "#E5ECF6",
|
| 365 |
-
"width": 0.5
|
| 366 |
-
},
|
| 367 |
-
"pattern": {
|
| 368 |
-
"fillmode": "overlay",
|
| 369 |
-
"size": 10,
|
| 370 |
-
"solidity": 0.2
|
| 371 |
-
}
|
| 372 |
-
},
|
| 373 |
-
"type": "bar"
|
| 374 |
-
}
|
| 375 |
-
],
|
| 376 |
-
"scattergeo": [
|
| 377 |
-
{
|
| 378 |
-
"type": "scattergeo",
|
| 379 |
-
"marker": {
|
| 380 |
-
"colorbar": {
|
| 381 |
-
"outlinewidth": 0,
|
| 382 |
-
"ticks": ""
|
| 383 |
-
}
|
| 384 |
-
}
|
| 385 |
-
}
|
| 386 |
-
],
|
| 387 |
-
"scatterpolar": [
|
| 388 |
-
{
|
| 389 |
-
"type": "scatterpolar",
|
| 390 |
-
"marker": {
|
| 391 |
-
"colorbar": {
|
| 392 |
-
"outlinewidth": 0,
|
| 393 |
-
"ticks": ""
|
| 394 |
-
}
|
| 395 |
-
}
|
| 396 |
-
}
|
| 397 |
-
],
|
| 398 |
-
"histogram": [
|
| 399 |
-
{
|
| 400 |
-
"marker": {
|
| 401 |
-
"pattern": {
|
| 402 |
-
"fillmode": "overlay",
|
| 403 |
-
"size": 10,
|
| 404 |
-
"solidity": 0.2
|
| 405 |
-
}
|
| 406 |
-
},
|
| 407 |
-
"type": "histogram"
|
| 408 |
-
}
|
| 409 |
-
],
|
| 410 |
-
"scattergl": [
|
| 411 |
-
{
|
| 412 |
-
"type": "scattergl",
|
| 413 |
-
"marker": {
|
| 414 |
-
"colorbar": {
|
| 415 |
-
"outlinewidth": 0,
|
| 416 |
-
"ticks": ""
|
| 417 |
-
}
|
| 418 |
-
}
|
| 419 |
-
}
|
| 420 |
-
],
|
| 421 |
-
"scatter3d": [
|
| 422 |
-
{
|
| 423 |
-
"type": "scatter3d",
|
| 424 |
-
"line": {
|
| 425 |
-
"colorbar": {
|
| 426 |
-
"outlinewidth": 0,
|
| 427 |
-
"ticks": ""
|
| 428 |
-
}
|
| 429 |
-
},
|
| 430 |
-
"marker": {
|
| 431 |
-
"colorbar": {
|
| 432 |
-
"outlinewidth": 0,
|
| 433 |
-
"ticks": ""
|
| 434 |
-
}
|
| 435 |
-
}
|
| 436 |
-
}
|
| 437 |
-
],
|
| 438 |
-
"scattermap": [
|
| 439 |
-
{
|
| 440 |
-
"type": "scattermap",
|
| 441 |
-
"marker": {
|
| 442 |
-
"colorbar": {
|
| 443 |
-
"outlinewidth": 0,
|
| 444 |
-
"ticks": ""
|
| 445 |
-
}
|
| 446 |
-
}
|
| 447 |
-
}
|
| 448 |
-
],
|
| 449 |
-
"scattermapbox": [
|
| 450 |
-
{
|
| 451 |
-
"type": "scattermapbox",
|
| 452 |
-
"marker": {
|
| 453 |
-
"colorbar": {
|
| 454 |
-
"outlinewidth": 0,
|
| 455 |
-
"ticks": ""
|
| 456 |
-
}
|
| 457 |
-
}
|
| 458 |
-
}
|
| 459 |
-
],
|
| 460 |
-
"scatterternary": [
|
| 461 |
-
{
|
| 462 |
-
"type": "scatterternary",
|
| 463 |
-
"marker": {
|
| 464 |
-
"colorbar": {
|
| 465 |
-
"outlinewidth": 0,
|
| 466 |
-
"ticks": ""
|
| 467 |
-
}
|
| 468 |
-
}
|
| 469 |
-
}
|
| 470 |
-
],
|
| 471 |
-
"scattercarpet": [
|
| 472 |
-
{
|
| 473 |
-
"type": "scattercarpet",
|
| 474 |
-
"marker": {
|
| 475 |
-
"colorbar": {
|
| 476 |
-
"outlinewidth": 0,
|
| 477 |
-
"ticks": ""
|
| 478 |
-
}
|
| 479 |
-
}
|
| 480 |
-
}
|
| 481 |
-
],
|
| 482 |
-
"carpet": [
|
| 483 |
-
{
|
| 484 |
-
"aaxis": {
|
| 485 |
-
"endlinecolor": "#2a3f5f",
|
| 486 |
-
"gridcolor": "white",
|
| 487 |
-
"linecolor": "white",
|
| 488 |
-
"minorgridcolor": "white",
|
| 489 |
-
"startlinecolor": "#2a3f5f"
|
| 490 |
-
},
|
| 491 |
-
"baxis": {
|
| 492 |
-
"endlinecolor": "#2a3f5f",
|
| 493 |
-
"gridcolor": "white",
|
| 494 |
-
"linecolor": "white",
|
| 495 |
-
"minorgridcolor": "white",
|
| 496 |
-
"startlinecolor": "#2a3f5f"
|
| 497 |
-
},
|
| 498 |
-
"type": "carpet"
|
| 499 |
-
}
|
| 500 |
-
],
|
| 501 |
-
"table": [
|
| 502 |
-
{
|
| 503 |
-
"cells": {
|
| 504 |
-
"fill": {
|
| 505 |
-
"color": "#EBF0F8"
|
| 506 |
-
},
|
| 507 |
-
"line": {
|
| 508 |
-
"color": "white"
|
| 509 |
-
}
|
| 510 |
-
},
|
| 511 |
-
"header": {
|
| 512 |
-
"fill": {
|
| 513 |
-
"color": "#C8D4E3"
|
| 514 |
-
},
|
| 515 |
-
"line": {
|
| 516 |
-
"color": "white"
|
| 517 |
-
}
|
| 518 |
-
},
|
| 519 |
-
"type": "table"
|
| 520 |
-
}
|
| 521 |
-
],
|
| 522 |
-
"barpolar": [
|
| 523 |
-
{
|
| 524 |
-
"marker": {
|
| 525 |
-
"line": {
|
| 526 |
-
"color": "#E5ECF6",
|
| 527 |
-
"width": 0.5
|
| 528 |
-
},
|
| 529 |
-
"pattern": {
|
| 530 |
-
"fillmode": "overlay",
|
| 531 |
-
"size": 10,
|
| 532 |
-
"solidity": 0.2
|
| 533 |
-
}
|
| 534 |
-
},
|
| 535 |
-
"type": "barpolar"
|
| 536 |
-
}
|
| 537 |
-
],
|
| 538 |
-
"pie": [
|
| 539 |
-
{
|
| 540 |
-
"automargin": true,
|
| 541 |
-
"type": "pie"
|
| 542 |
-
}
|
| 543 |
-
]
|
| 544 |
-
},
|
| 545 |
-
"layout": {
|
| 546 |
-
"autotypenumbers": "strict",
|
| 547 |
-
"colorway": [
|
| 548 |
-
"#636efa",
|
| 549 |
-
"#EF553B",
|
| 550 |
-
"#00cc96",
|
| 551 |
-
"#ab63fa",
|
| 552 |
-
"#FFA15A",
|
| 553 |
-
"#19d3f3",
|
| 554 |
-
"#FF6692",
|
| 555 |
-
"#B6E880",
|
| 556 |
-
"#FF97FF",
|
| 557 |
-
"#FECB52"
|
| 558 |
-
],
|
| 559 |
-
"font": {
|
| 560 |
-
"color": "#2a3f5f"
|
| 561 |
-
},
|
| 562 |
-
"hovermode": "closest",
|
| 563 |
-
"hoverlabel": {
|
| 564 |
-
"align": "left"
|
| 565 |
-
},
|
| 566 |
-
"paper_bgcolor": "white",
|
| 567 |
-
"plot_bgcolor": "#E5ECF6",
|
| 568 |
-
"polar": {
|
| 569 |
-
"bgcolor": "#E5ECF6",
|
| 570 |
-
"angularaxis": {
|
| 571 |
-
"gridcolor": "white",
|
| 572 |
-
"linecolor": "white",
|
| 573 |
-
"ticks": ""
|
| 574 |
-
},
|
| 575 |
-
"radialaxis": {
|
| 576 |
-
"gridcolor": "white",
|
| 577 |
-
"linecolor": "white",
|
| 578 |
-
"ticks": ""
|
| 579 |
-
}
|
| 580 |
-
},
|
| 581 |
-
"ternary": {
|
| 582 |
-
"bgcolor": "#E5ECF6",
|
| 583 |
-
"aaxis": {
|
| 584 |
-
"gridcolor": "white",
|
| 585 |
-
"linecolor": "white",
|
| 586 |
-
"ticks": ""
|
| 587 |
-
},
|
| 588 |
-
"baxis": {
|
| 589 |
-
"gridcolor": "white",
|
| 590 |
-
"linecolor": "white",
|
| 591 |
-
"ticks": ""
|
| 592 |
-
},
|
| 593 |
-
"caxis": {
|
| 594 |
-
"gridcolor": "white",
|
| 595 |
-
"linecolor": "white",
|
| 596 |
-
"ticks": ""
|
| 597 |
-
}
|
| 598 |
-
},
|
| 599 |
-
"coloraxis": {
|
| 600 |
-
"colorbar": {
|
| 601 |
-
"outlinewidth": 0,
|
| 602 |
-
"ticks": ""
|
| 603 |
-
}
|
| 604 |
-
},
|
| 605 |
-
"colorscale": {
|
| 606 |
-
"sequential": [
|
| 607 |
-
[
|
| 608 |
-
0.0,
|
| 609 |
-
"#0d0887"
|
| 610 |
-
],
|
| 611 |
-
[
|
| 612 |
-
0.1111111111111111,
|
| 613 |
-
"#46039f"
|
| 614 |
-
],
|
| 615 |
-
[
|
| 616 |
-
0.2222222222222222,
|
| 617 |
-
"#7201a8"
|
| 618 |
-
],
|
| 619 |
-
[
|
| 620 |
-
0.3333333333333333,
|
| 621 |
-
"#9c179e"
|
| 622 |
-
],
|
| 623 |
-
[
|
| 624 |
-
0.4444444444444444,
|
| 625 |
-
"#bd3786"
|
| 626 |
-
],
|
| 627 |
-
[
|
| 628 |
-
0.5555555555555556,
|
| 629 |
-
"#d8576b"
|
| 630 |
-
],
|
| 631 |
-
[
|
| 632 |
-
0.6666666666666666,
|
| 633 |
-
"#ed7953"
|
| 634 |
-
],
|
| 635 |
-
[
|
| 636 |
-
0.7777777777777778,
|
| 637 |
-
"#fb9f3a"
|
| 638 |
-
],
|
| 639 |
-
[
|
| 640 |
-
0.8888888888888888,
|
| 641 |
-
"#fdca26"
|
| 642 |
-
],
|
| 643 |
-
[
|
| 644 |
-
1.0,
|
| 645 |
-
"#f0f921"
|
| 646 |
-
]
|
| 647 |
-
],
|
| 648 |
-
"sequentialminus": [
|
| 649 |
-
[
|
| 650 |
-
0.0,
|
| 651 |
-
"#0d0887"
|
| 652 |
-
],
|
| 653 |
-
[
|
| 654 |
-
0.1111111111111111,
|
| 655 |
-
"#46039f"
|
| 656 |
-
],
|
| 657 |
-
[
|
| 658 |
-
0.2222222222222222,
|
| 659 |
-
"#7201a8"
|
| 660 |
-
],
|
| 661 |
-
[
|
| 662 |
-
0.3333333333333333,
|
| 663 |
-
"#9c179e"
|
| 664 |
-
],
|
| 665 |
-
[
|
| 666 |
-
0.4444444444444444,
|
| 667 |
-
"#bd3786"
|
| 668 |
-
],
|
| 669 |
-
[
|
| 670 |
-
0.5555555555555556,
|
| 671 |
-
"#d8576b"
|
| 672 |
-
],
|
| 673 |
-
[
|
| 674 |
-
0.6666666666666666,
|
| 675 |
-
"#ed7953"
|
| 676 |
-
],
|
| 677 |
-
[
|
| 678 |
-
0.7777777777777778,
|
| 679 |
-
"#fb9f3a"
|
| 680 |
-
],
|
| 681 |
-
[
|
| 682 |
-
0.8888888888888888,
|
| 683 |
-
"#fdca26"
|
| 684 |
-
],
|
| 685 |
-
[
|
| 686 |
-
1.0,
|
| 687 |
-
"#f0f921"
|
| 688 |
-
]
|
| 689 |
-
],
|
| 690 |
-
"diverging": [
|
| 691 |
-
[
|
| 692 |
-
0,
|
| 693 |
-
"#8e0152"
|
| 694 |
-
],
|
| 695 |
-
[
|
| 696 |
-
0.1,
|
| 697 |
-
"#c51b7d"
|
| 698 |
-
],
|
| 699 |
-
[
|
| 700 |
-
0.2,
|
| 701 |
-
"#de77ae"
|
| 702 |
-
],
|
| 703 |
-
[
|
| 704 |
-
0.3,
|
| 705 |
-
"#f1b6da"
|
| 706 |
-
],
|
| 707 |
-
[
|
| 708 |
-
0.4,
|
| 709 |
-
"#fde0ef"
|
| 710 |
-
],
|
| 711 |
-
[
|
| 712 |
-
0.5,
|
| 713 |
-
"#f7f7f7"
|
| 714 |
-
],
|
| 715 |
-
[
|
| 716 |
-
0.6,
|
| 717 |
-
"#e6f5d0"
|
| 718 |
-
],
|
| 719 |
-
[
|
| 720 |
-
0.7,
|
| 721 |
-
"#b8e186"
|
| 722 |
-
],
|
| 723 |
-
[
|
| 724 |
-
0.8,
|
| 725 |
-
"#7fbc41"
|
| 726 |
-
],
|
| 727 |
-
[
|
| 728 |
-
0.9,
|
| 729 |
-
"#4d9221"
|
| 730 |
-
],
|
| 731 |
-
[
|
| 732 |
-
1,
|
| 733 |
-
"#276419"
|
| 734 |
-
]
|
| 735 |
-
]
|
| 736 |
-
},
|
| 737 |
-
"xaxis": {
|
| 738 |
-
"gridcolor": "white",
|
| 739 |
-
"linecolor": "white",
|
| 740 |
-
"ticks": "",
|
| 741 |
-
"title": {
|
| 742 |
-
"standoff": 15
|
| 743 |
-
},
|
| 744 |
-
"zerolinecolor": "white",
|
| 745 |
-
"automargin": true,
|
| 746 |
-
"zerolinewidth": 2
|
| 747 |
-
},
|
| 748 |
-
"yaxis": {
|
| 749 |
-
"gridcolor": "white",
|
| 750 |
-
"linecolor": "white",
|
| 751 |
-
"ticks": "",
|
| 752 |
-
"title": {
|
| 753 |
-
"standoff": 15
|
| 754 |
-
},
|
| 755 |
-
"zerolinecolor": "white",
|
| 756 |
-
"automargin": true,
|
| 757 |
-
"zerolinewidth": 2
|
| 758 |
-
},
|
| 759 |
-
"scene": {
|
| 760 |
-
"xaxis": {
|
| 761 |
-
"backgroundcolor": "#E5ECF6",
|
| 762 |
-
"gridcolor": "white",
|
| 763 |
-
"linecolor": "white",
|
| 764 |
-
"showbackground": true,
|
| 765 |
-
"ticks": "",
|
| 766 |
-
"zerolinecolor": "white",
|
| 767 |
-
"gridwidth": 2
|
| 768 |
-
},
|
| 769 |
-
"yaxis": {
|
| 770 |
-
"backgroundcolor": "#E5ECF6",
|
| 771 |
-
"gridcolor": "white",
|
| 772 |
-
"linecolor": "white",
|
| 773 |
-
"showbackground": true,
|
| 774 |
-
"ticks": "",
|
| 775 |
-
"zerolinecolor": "white",
|
| 776 |
-
"gridwidth": 2
|
| 777 |
-
},
|
| 778 |
-
"zaxis": {
|
| 779 |
-
"backgroundcolor": "#E5ECF6",
|
| 780 |
-
"gridcolor": "white",
|
| 781 |
-
"linecolor": "white",
|
| 782 |
-
"showbackground": true,
|
| 783 |
-
"ticks": "",
|
| 784 |
-
"zerolinecolor": "white",
|
| 785 |
-
"gridwidth": 2
|
| 786 |
-
}
|
| 787 |
-
},
|
| 788 |
-
"shapedefaults": {
|
| 789 |
-
"line": {
|
| 790 |
-
"color": "#2a3f5f"
|
| 791 |
-
}
|
| 792 |
-
},
|
| 793 |
-
"annotationdefaults": {
|
| 794 |
-
"arrowcolor": "#2a3f5f",
|
| 795 |
-
"arrowhead": 0,
|
| 796 |
-
"arrowwidth": 1
|
| 797 |
-
},
|
| 798 |
-
"geo": {
|
| 799 |
-
"bgcolor": "white",
|
| 800 |
-
"landcolor": "#E5ECF6",
|
| 801 |
-
"subunitcolor": "white",
|
| 802 |
-
"showland": true,
|
| 803 |
-
"showlakes": true,
|
| 804 |
-
"lakecolor": "white"
|
| 805 |
-
},
|
| 806 |
-
"title": {
|
| 807 |
-
"x": 0.05
|
| 808 |
-
},
|
| 809 |
-
"mapbox": {
|
| 810 |
-
"style": "light"
|
| 811 |
-
}
|
| 812 |
-
}
|
| 813 |
-
},
|
| 814 |
-
"xaxis": {
|
| 815 |
-
"anchor": "y",
|
| 816 |
-
"domain": [
|
| 817 |
-
0.0,
|
| 818 |
-
1.0
|
| 819 |
-
],
|
| 820 |
-
"title": {
|
| 821 |
-
"text": "mpg",
|
| 822 |
-
"font": {
|
| 823 |
-
"color": "#94a3b8"
|
| 824 |
-
}
|
| 825 |
-
},
|
| 826 |
-
"gridcolor": "rgba(255,255,255,0.07)",
|
| 827 |
-
"zerolinecolor": "rgba(255,255,255,0.1)"
|
| 828 |
-
},
|
| 829 |
-
"yaxis": {
|
| 830 |
-
"anchor": "x",
|
| 831 |
-
"domain": [
|
| 832 |
-
0.0,
|
| 833 |
-
1.0
|
| 834 |
-
],
|
| 835 |
-
"title": {
|
| 836 |
-
"text": "wt",
|
| 837 |
-
"font": {
|
| 838 |
-
"color": "#94a3b8"
|
| 839 |
-
}
|
| 840 |
-
},
|
| 841 |
-
"gridcolor": "rgba(255,255,255,0.07)",
|
| 842 |
-
"zerolinecolor": "rgba(255,255,255,0.1)"
|
| 843 |
-
},
|
| 844 |
-
"legend": {
|
| 845 |
-
"tracegroupgap": 0
|
| 846 |
-
},
|
| 847 |
-
"title": {
|
| 848 |
-
"text": "mpg vs wt",
|
| 849 |
-
"font": {
|
| 850 |
-
"color": "#a78bfa",
|
| 851 |
-
"size": 15
|
| 852 |
-
}
|
| 853 |
-
},
|
| 854 |
-
"font": {
|
| 855 |
-
"color": "#e2e8f0"
|
| 856 |
-
},
|
| 857 |
-
"margin": {
|
| 858 |
-
"l": 50,
|
| 859 |
-
"r": 20,
|
| 860 |
-
"t": 55,
|
| 861 |
-
"b": 50
|
| 862 |
-
},
|
| 863 |
-
"hoverlabel": {
|
| 864 |
-
"font": {
|
| 865 |
-
"color": "#e2e8f0"
|
| 866 |
-
},
|
| 867 |
-
"bgcolor": "rgba(15,23,42,0.95)"
|
| 868 |
-
},
|
| 869 |
-
"paper_bgcolor": "rgba(9,9,11,0.0)",
|
| 870 |
-
"plot_bgcolor": "rgba(15,23,42,0.4)"
|
| 871 |
-
}
|
| 872 |
-
}
|
| 873 |
-
},
|
| 874 |
-
{
|
| 875 |
-
"title": "cyl vs mpg",
|
| 876 |
-
"fig_json": {
|
| 877 |
-
"data": [
|
| 878 |
-
{
|
| 879 |
-
"hovertemplate": "cyl=%{x}<br>mpg=%{y}<extra></extra>",
|
| 880 |
-
"legendgroup": "",
|
| 881 |
-
"marker": {
|
| 882 |
-
"color": "#6366f1",
|
| 883 |
-
"opacity": 0.75,
|
| 884 |
-
"symbol": "circle"
|
| 885 |
-
},
|
| 886 |
-
"mode": "markers",
|
| 887 |
-
"name": "",
|
| 888 |
-
"orientation": "v",
|
| 889 |
-
"showlegend": false,
|
| 890 |
-
"x": {
|
| 891 |
-
"dtype": "i1",
|
| 892 |
-
"bdata": "BgYEBggGCAQEBgYICAgICAgEBAQECAgICAQEBAgGCAQ="
|
| 893 |
-
},
|
| 894 |
-
"xaxis": "x",
|
| 895 |
-
"y": {
|
| 896 |
-
"dtype": "f8",
|
| 897 |
-
"bdata": "AAAAAAAANUAAAAAAAAA1QM3MzMzMzDZAZmZmZmZmNUAzMzMzM7MyQJqZmZmZGTJAmpmZmZmZLEBmZmZmZmY4QM3MzMzMzDZAMzMzMzMzM0DNzMzMzMwxQGZmZmZmZjBAzczMzMxMMUBmZmZmZmYuQM3MzMzMzCRAzczMzMzMJEBmZmZmZmYtQDMzMzMzM0BAZmZmZmZmPkAzMzMzM/NAQAAAAAAAgDVAAAAAAAAAL0BmZmZmZmYuQJqZmZmZmSpAMzMzMzMzM0DNzMzMzEw7QAAAAAAAADpAZmZmZmZmPkCamZmZmZkvQDMzMzMzszNAAAAAAAAALkBmZmZmZmY1QA=="
|
| 898 |
-
},
|
| 899 |
-
"yaxis": "y",
|
| 900 |
-
"type": "scatter"
|
| 901 |
-
}
|
| 902 |
-
],
|
| 903 |
-
"layout": {
|
| 904 |
-
"template": {
|
| 905 |
-
"data": {
|
| 906 |
-
"histogram2dcontour": [
|
| 907 |
-
{
|
| 908 |
-
"type": "histogram2dcontour",
|
| 909 |
-
"colorbar": {
|
| 910 |
-
"outlinewidth": 0,
|
| 911 |
-
"ticks": ""
|
| 912 |
-
},
|
| 913 |
-
"colorscale": [
|
| 914 |
-
[
|
| 915 |
-
0.0,
|
| 916 |
-
"#0d0887"
|
| 917 |
-
],
|
| 918 |
-
[
|
| 919 |
-
0.1111111111111111,
|
| 920 |
-
"#46039f"
|
| 921 |
-
],
|
| 922 |
-
[
|
| 923 |
-
0.2222222222222222,
|
| 924 |
-
"#7201a8"
|
| 925 |
-
],
|
| 926 |
-
[
|
| 927 |
-
0.3333333333333333,
|
| 928 |
-
"#9c179e"
|
| 929 |
-
],
|
| 930 |
-
[
|
| 931 |
-
0.4444444444444444,
|
| 932 |
-
"#bd3786"
|
| 933 |
-
],
|
| 934 |
-
[
|
| 935 |
-
0.5555555555555556,
|
| 936 |
-
"#d8576b"
|
| 937 |
-
],
|
| 938 |
-
[
|
| 939 |
-
0.6666666666666666,
|
| 940 |
-
"#ed7953"
|
| 941 |
-
],
|
| 942 |
-
[
|
| 943 |
-
0.7777777777777778,
|
| 944 |
-
"#fb9f3a"
|
| 945 |
-
],
|
| 946 |
-
[
|
| 947 |
-
0.8888888888888888,
|
| 948 |
-
"#fdca26"
|
| 949 |
-
],
|
| 950 |
-
[
|
| 951 |
-
1.0,
|
| 952 |
-
"#f0f921"
|
| 953 |
-
]
|
| 954 |
-
]
|
| 955 |
-
}
|
| 956 |
-
],
|
| 957 |
-
"choropleth": [
|
| 958 |
-
{
|
| 959 |
-
"type": "choropleth",
|
| 960 |
-
"colorbar": {
|
| 961 |
-
"outlinewidth": 0,
|
| 962 |
-
"ticks": ""
|
| 963 |
-
}
|
| 964 |
-
}
|
| 965 |
-
],
|
| 966 |
-
"histogram2d": [
|
| 967 |
-
{
|
| 968 |
-
"type": "histogram2d",
|
| 969 |
-
"colorbar": {
|
| 970 |
-
"outlinewidth": 0,
|
| 971 |
-
"ticks": ""
|
| 972 |
-
},
|
| 973 |
-
"colorscale": [
|
| 974 |
-
[
|
| 975 |
-
0.0,
|
| 976 |
-
"#0d0887"
|
| 977 |
-
],
|
| 978 |
-
[
|
| 979 |
-
0.1111111111111111,
|
| 980 |
-
"#46039f"
|
| 981 |
-
],
|
| 982 |
-
[
|
| 983 |
-
0.2222222222222222,
|
| 984 |
-
"#7201a8"
|
| 985 |
-
],
|
| 986 |
-
[
|
| 987 |
-
0.3333333333333333,
|
| 988 |
-
"#9c179e"
|
| 989 |
-
],
|
| 990 |
-
[
|
| 991 |
-
0.4444444444444444,
|
| 992 |
-
"#bd3786"
|
| 993 |
-
],
|
| 994 |
-
[
|
| 995 |
-
0.5555555555555556,
|
| 996 |
-
"#d8576b"
|
| 997 |
-
],
|
| 998 |
-
[
|
| 999 |
-
0.6666666666666666,
|
| 1000 |
-
"#ed7953"
|
| 1001 |
-
],
|
| 1002 |
-
[
|
| 1003 |
-
0.7777777777777778,
|
| 1004 |
-
"#fb9f3a"
|
| 1005 |
-
],
|
| 1006 |
-
[
|
| 1007 |
-
0.8888888888888888,
|
| 1008 |
-
"#fdca26"
|
| 1009 |
-
],
|
| 1010 |
-
[
|
| 1011 |
-
1.0,
|
| 1012 |
-
"#f0f921"
|
| 1013 |
-
]
|
| 1014 |
-
]
|
| 1015 |
-
}
|
| 1016 |
-
],
|
| 1017 |
-
"heatmap": [
|
| 1018 |
-
{
|
| 1019 |
-
"type": "heatmap",
|
| 1020 |
-
"colorbar": {
|
| 1021 |
-
"outlinewidth": 0,
|
| 1022 |
-
"ticks": ""
|
| 1023 |
-
},
|
| 1024 |
-
"colorscale": [
|
| 1025 |
-
[
|
| 1026 |
-
0.0,
|
| 1027 |
-
"#0d0887"
|
| 1028 |
-
],
|
| 1029 |
-
[
|
| 1030 |
-
0.1111111111111111,
|
| 1031 |
-
"#46039f"
|
| 1032 |
-
],
|
| 1033 |
-
[
|
| 1034 |
-
0.2222222222222222,
|
| 1035 |
-
"#7201a8"
|
| 1036 |
-
],
|
| 1037 |
-
[
|
| 1038 |
-
0.3333333333333333,
|
| 1039 |
-
"#9c179e"
|
| 1040 |
-
],
|
| 1041 |
-
[
|
| 1042 |
-
0.4444444444444444,
|
| 1043 |
-
"#bd3786"
|
| 1044 |
-
],
|
| 1045 |
-
[
|
| 1046 |
-
0.5555555555555556,
|
| 1047 |
-
"#d8576b"
|
| 1048 |
-
],
|
| 1049 |
-
[
|
| 1050 |
-
0.6666666666666666,
|
| 1051 |
-
"#ed7953"
|
| 1052 |
-
],
|
| 1053 |
-
[
|
| 1054 |
-
0.7777777777777778,
|
| 1055 |
-
"#fb9f3a"
|
| 1056 |
-
],
|
| 1057 |
-
[
|
| 1058 |
-
0.8888888888888888,
|
| 1059 |
-
"#fdca26"
|
| 1060 |
-
],
|
| 1061 |
-
[
|
| 1062 |
-
1.0,
|
| 1063 |
-
"#f0f921"
|
| 1064 |
-
]
|
| 1065 |
-
]
|
| 1066 |
-
}
|
| 1067 |
-
],
|
| 1068 |
-
"contourcarpet": [
|
| 1069 |
-
{
|
| 1070 |
-
"type": "contourcarpet",
|
| 1071 |
-
"colorbar": {
|
| 1072 |
-
"outlinewidth": 0,
|
| 1073 |
-
"ticks": ""
|
| 1074 |
-
}
|
| 1075 |
-
}
|
| 1076 |
-
],
|
| 1077 |
-
"contour": [
|
| 1078 |
-
{
|
| 1079 |
-
"type": "contour",
|
| 1080 |
-
"colorbar": {
|
| 1081 |
-
"outlinewidth": 0,
|
| 1082 |
-
"ticks": ""
|
| 1083 |
-
},
|
| 1084 |
-
"colorscale": [
|
| 1085 |
-
[
|
| 1086 |
-
0.0,
|
| 1087 |
-
"#0d0887"
|
| 1088 |
-
],
|
| 1089 |
-
[
|
| 1090 |
-
0.1111111111111111,
|
| 1091 |
-
"#46039f"
|
| 1092 |
-
],
|
| 1093 |
-
[
|
| 1094 |
-
0.2222222222222222,
|
| 1095 |
-
"#7201a8"
|
| 1096 |
-
],
|
| 1097 |
-
[
|
| 1098 |
-
0.3333333333333333,
|
| 1099 |
-
"#9c179e"
|
| 1100 |
-
],
|
| 1101 |
-
[
|
| 1102 |
-
0.4444444444444444,
|
| 1103 |
-
"#bd3786"
|
| 1104 |
-
],
|
| 1105 |
-
[
|
| 1106 |
-
0.5555555555555556,
|
| 1107 |
-
"#d8576b"
|
| 1108 |
-
],
|
| 1109 |
-
[
|
| 1110 |
-
0.6666666666666666,
|
| 1111 |
-
"#ed7953"
|
| 1112 |
-
],
|
| 1113 |
-
[
|
| 1114 |
-
0.7777777777777778,
|
| 1115 |
-
"#fb9f3a"
|
| 1116 |
-
],
|
| 1117 |
-
[
|
| 1118 |
-
0.8888888888888888,
|
| 1119 |
-
"#fdca26"
|
| 1120 |
-
],
|
| 1121 |
-
[
|
| 1122 |
-
1.0,
|
| 1123 |
-
"#f0f921"
|
| 1124 |
-
]
|
| 1125 |
-
]
|
| 1126 |
-
}
|
| 1127 |
-
],
|
| 1128 |
-
"surface": [
|
| 1129 |
-
{
|
| 1130 |
-
"type": "surface",
|
| 1131 |
-
"colorbar": {
|
| 1132 |
-
"outlinewidth": 0,
|
| 1133 |
-
"ticks": ""
|
| 1134 |
-
},
|
| 1135 |
-
"colorscale": [
|
| 1136 |
-
[
|
| 1137 |
-
0.0,
|
| 1138 |
-
"#0d0887"
|
| 1139 |
-
],
|
| 1140 |
-
[
|
| 1141 |
-
0.1111111111111111,
|
| 1142 |
-
"#46039f"
|
| 1143 |
-
],
|
| 1144 |
-
[
|
| 1145 |
-
0.2222222222222222,
|
| 1146 |
-
"#7201a8"
|
| 1147 |
-
],
|
| 1148 |
-
[
|
| 1149 |
-
0.3333333333333333,
|
| 1150 |
-
"#9c179e"
|
| 1151 |
-
],
|
| 1152 |
-
[
|
| 1153 |
-
0.4444444444444444,
|
| 1154 |
-
"#bd3786"
|
| 1155 |
-
],
|
| 1156 |
-
[
|
| 1157 |
-
0.5555555555555556,
|
| 1158 |
-
"#d8576b"
|
| 1159 |
-
],
|
| 1160 |
-
[
|
| 1161 |
-
0.6666666666666666,
|
| 1162 |
-
"#ed7953"
|
| 1163 |
-
],
|
| 1164 |
-
[
|
| 1165 |
-
0.7777777777777778,
|
| 1166 |
-
"#fb9f3a"
|
| 1167 |
-
],
|
| 1168 |
-
[
|
| 1169 |
-
0.8888888888888888,
|
| 1170 |
-
"#fdca26"
|
| 1171 |
-
],
|
| 1172 |
-
[
|
| 1173 |
-
1.0,
|
| 1174 |
-
"#f0f921"
|
| 1175 |
-
]
|
| 1176 |
-
]
|
| 1177 |
-
}
|
| 1178 |
-
],
|
| 1179 |
-
"mesh3d": [
|
| 1180 |
-
{
|
| 1181 |
-
"type": "mesh3d",
|
| 1182 |
-
"colorbar": {
|
| 1183 |
-
"outlinewidth": 0,
|
| 1184 |
-
"ticks": ""
|
| 1185 |
-
}
|
| 1186 |
-
}
|
| 1187 |
-
],
|
| 1188 |
-
"scatter": [
|
| 1189 |
-
{
|
| 1190 |
-
"fillpattern": {
|
| 1191 |
-
"fillmode": "overlay",
|
| 1192 |
-
"size": 10,
|
| 1193 |
-
"solidity": 0.2
|
| 1194 |
-
},
|
| 1195 |
-
"type": "scatter"
|
| 1196 |
-
}
|
| 1197 |
-
],
|
| 1198 |
-
"parcoords": [
|
| 1199 |
-
{
|
| 1200 |
-
"type": "parcoords",
|
| 1201 |
-
"line": {
|
| 1202 |
-
"colorbar": {
|
| 1203 |
-
"outlinewidth": 0,
|
| 1204 |
-
"ticks": ""
|
| 1205 |
-
}
|
| 1206 |
-
}
|
| 1207 |
-
}
|
| 1208 |
-
],
|
| 1209 |
-
"scatterpolargl": [
|
| 1210 |
-
{
|
| 1211 |
-
"type": "scatterpolargl",
|
| 1212 |
-
"marker": {
|
| 1213 |
-
"colorbar": {
|
| 1214 |
-
"outlinewidth": 0,
|
| 1215 |
-
"ticks": ""
|
| 1216 |
-
}
|
| 1217 |
-
}
|
| 1218 |
-
}
|
| 1219 |
-
],
|
| 1220 |
-
"bar": [
|
| 1221 |
-
{
|
| 1222 |
-
"error_x": {
|
| 1223 |
-
"color": "#2a3f5f"
|
| 1224 |
-
},
|
| 1225 |
-
"error_y": {
|
| 1226 |
-
"color": "#2a3f5f"
|
| 1227 |
-
},
|
| 1228 |
-
"marker": {
|
| 1229 |
-
"line": {
|
| 1230 |
-
"color": "#E5ECF6",
|
| 1231 |
-
"width": 0.5
|
| 1232 |
-
},
|
| 1233 |
-
"pattern": {
|
| 1234 |
-
"fillmode": "overlay",
|
| 1235 |
-
"size": 10,
|
| 1236 |
-
"solidity": 0.2
|
| 1237 |
-
}
|
| 1238 |
-
},
|
| 1239 |
-
"type": "bar"
|
| 1240 |
-
}
|
| 1241 |
-
],
|
| 1242 |
-
"scattergeo": [
|
| 1243 |
-
{
|
| 1244 |
-
"type": "scattergeo",
|
| 1245 |
-
"marker": {
|
| 1246 |
-
"colorbar": {
|
| 1247 |
-
"outlinewidth": 0,
|
| 1248 |
-
"ticks": ""
|
| 1249 |
-
}
|
| 1250 |
-
}
|
| 1251 |
-
}
|
| 1252 |
-
],
|
| 1253 |
-
"scatterpolar": [
|
| 1254 |
-
{
|
| 1255 |
-
"type": "scatterpolar",
|
| 1256 |
-
"marker": {
|
| 1257 |
-
"colorbar": {
|
| 1258 |
-
"outlinewidth": 0,
|
| 1259 |
-
"ticks": ""
|
| 1260 |
-
}
|
| 1261 |
-
}
|
| 1262 |
-
}
|
| 1263 |
-
],
|
| 1264 |
-
"histogram": [
|
| 1265 |
-
{
|
| 1266 |
-
"marker": {
|
| 1267 |
-
"pattern": {
|
| 1268 |
-
"fillmode": "overlay",
|
| 1269 |
-
"size": 10,
|
| 1270 |
-
"solidity": 0.2
|
| 1271 |
-
}
|
| 1272 |
-
},
|
| 1273 |
-
"type": "histogram"
|
| 1274 |
-
}
|
| 1275 |
-
],
|
| 1276 |
-
"scattergl": [
|
| 1277 |
-
{
|
| 1278 |
-
"type": "scattergl",
|
| 1279 |
-
"marker": {
|
| 1280 |
-
"colorbar": {
|
| 1281 |
-
"outlinewidth": 0,
|
| 1282 |
-
"ticks": ""
|
| 1283 |
-
}
|
| 1284 |
-
}
|
| 1285 |
-
}
|
| 1286 |
-
],
|
| 1287 |
-
"scatter3d": [
|
| 1288 |
-
{
|
| 1289 |
-
"type": "scatter3d",
|
| 1290 |
-
"line": {
|
| 1291 |
-
"colorbar": {
|
| 1292 |
-
"outlinewidth": 0,
|
| 1293 |
-
"ticks": ""
|
| 1294 |
-
}
|
| 1295 |
-
},
|
| 1296 |
-
"marker": {
|
| 1297 |
-
"colorbar": {
|
| 1298 |
-
"outlinewidth": 0,
|
| 1299 |
-
"ticks": ""
|
| 1300 |
-
}
|
| 1301 |
-
}
|
| 1302 |
-
}
|
| 1303 |
-
],
|
| 1304 |
-
"scattermap": [
|
| 1305 |
-
{
|
| 1306 |
-
"type": "scattermap",
|
| 1307 |
-
"marker": {
|
| 1308 |
-
"colorbar": {
|
| 1309 |
-
"outlinewidth": 0,
|
| 1310 |
-
"ticks": ""
|
| 1311 |
-
}
|
| 1312 |
-
}
|
| 1313 |
-
}
|
| 1314 |
-
],
|
| 1315 |
-
"scattermapbox": [
|
| 1316 |
-
{
|
| 1317 |
-
"type": "scattermapbox",
|
| 1318 |
-
"marker": {
|
| 1319 |
-
"colorbar": {
|
| 1320 |
-
"outlinewidth": 0,
|
| 1321 |
-
"ticks": ""
|
| 1322 |
-
}
|
| 1323 |
-
}
|
| 1324 |
-
}
|
| 1325 |
-
],
|
| 1326 |
-
"scatterternary": [
|
| 1327 |
-
{
|
| 1328 |
-
"type": "scatterternary",
|
| 1329 |
-
"marker": {
|
| 1330 |
-
"colorbar": {
|
| 1331 |
-
"outlinewidth": 0,
|
| 1332 |
-
"ticks": ""
|
| 1333 |
-
}
|
| 1334 |
-
}
|
| 1335 |
-
}
|
| 1336 |
-
],
|
| 1337 |
-
"scattercarpet": [
|
| 1338 |
-
{
|
| 1339 |
-
"type": "scattercarpet",
|
| 1340 |
-
"marker": {
|
| 1341 |
-
"colorbar": {
|
| 1342 |
-
"outlinewidth": 0,
|
| 1343 |
-
"ticks": ""
|
| 1344 |
-
}
|
| 1345 |
-
}
|
| 1346 |
-
}
|
| 1347 |
-
],
|
| 1348 |
-
"carpet": [
|
| 1349 |
-
{
|
| 1350 |
-
"aaxis": {
|
| 1351 |
-
"endlinecolor": "#2a3f5f",
|
| 1352 |
-
"gridcolor": "white",
|
| 1353 |
-
"linecolor": "white",
|
| 1354 |
-
"minorgridcolor": "white",
|
| 1355 |
-
"startlinecolor": "#2a3f5f"
|
| 1356 |
-
},
|
| 1357 |
-
"baxis": {
|
| 1358 |
-
"endlinecolor": "#2a3f5f",
|
| 1359 |
-
"gridcolor": "white",
|
| 1360 |
-
"linecolor": "white",
|
| 1361 |
-
"minorgridcolor": "white",
|
| 1362 |
-
"startlinecolor": "#2a3f5f"
|
| 1363 |
-
},
|
| 1364 |
-
"type": "carpet"
|
| 1365 |
-
}
|
| 1366 |
-
],
|
| 1367 |
-
"table": [
|
| 1368 |
-
{
|
| 1369 |
-
"cells": {
|
| 1370 |
-
"fill": {
|
| 1371 |
-
"color": "#EBF0F8"
|
| 1372 |
-
},
|
| 1373 |
-
"line": {
|
| 1374 |
-
"color": "white"
|
| 1375 |
-
}
|
| 1376 |
-
},
|
| 1377 |
-
"header": {
|
| 1378 |
-
"fill": {
|
| 1379 |
-
"color": "#C8D4E3"
|
| 1380 |
-
},
|
| 1381 |
-
"line": {
|
| 1382 |
-
"color": "white"
|
| 1383 |
-
}
|
| 1384 |
-
},
|
| 1385 |
-
"type": "table"
|
| 1386 |
-
}
|
| 1387 |
-
],
|
| 1388 |
-
"barpolar": [
|
| 1389 |
-
{
|
| 1390 |
-
"marker": {
|
| 1391 |
-
"line": {
|
| 1392 |
-
"color": "#E5ECF6",
|
| 1393 |
-
"width": 0.5
|
| 1394 |
-
},
|
| 1395 |
-
"pattern": {
|
| 1396 |
-
"fillmode": "overlay",
|
| 1397 |
-
"size": 10,
|
| 1398 |
-
"solidity": 0.2
|
| 1399 |
-
}
|
| 1400 |
-
},
|
| 1401 |
-
"type": "barpolar"
|
| 1402 |
-
}
|
| 1403 |
-
],
|
| 1404 |
-
"pie": [
|
| 1405 |
-
{
|
| 1406 |
-
"automargin": true,
|
| 1407 |
-
"type": "pie"
|
| 1408 |
-
}
|
| 1409 |
-
]
|
| 1410 |
-
},
|
| 1411 |
-
"layout": {
|
| 1412 |
-
"autotypenumbers": "strict",
|
| 1413 |
-
"colorway": [
|
| 1414 |
-
"#636efa",
|
| 1415 |
-
"#EF553B",
|
| 1416 |
-
"#00cc96",
|
| 1417 |
-
"#ab63fa",
|
| 1418 |
-
"#FFA15A",
|
| 1419 |
-
"#19d3f3",
|
| 1420 |
-
"#FF6692",
|
| 1421 |
-
"#B6E880",
|
| 1422 |
-
"#FF97FF",
|
| 1423 |
-
"#FECB52"
|
| 1424 |
-
],
|
| 1425 |
-
"font": {
|
| 1426 |
-
"color": "#2a3f5f"
|
| 1427 |
-
},
|
| 1428 |
-
"hovermode": "closest",
|
| 1429 |
-
"hoverlabel": {
|
| 1430 |
-
"align": "left"
|
| 1431 |
-
},
|
| 1432 |
-
"paper_bgcolor": "white",
|
| 1433 |
-
"plot_bgcolor": "#E5ECF6",
|
| 1434 |
-
"polar": {
|
| 1435 |
-
"bgcolor": "#E5ECF6",
|
| 1436 |
-
"angularaxis": {
|
| 1437 |
-
"gridcolor": "white",
|
| 1438 |
-
"linecolor": "white",
|
| 1439 |
-
"ticks": ""
|
| 1440 |
-
},
|
| 1441 |
-
"radialaxis": {
|
| 1442 |
-
"gridcolor": "white",
|
| 1443 |
-
"linecolor": "white",
|
| 1444 |
-
"ticks": ""
|
| 1445 |
-
}
|
| 1446 |
-
},
|
| 1447 |
-
"ternary": {
|
| 1448 |
-
"bgcolor": "#E5ECF6",
|
| 1449 |
-
"aaxis": {
|
| 1450 |
-
"gridcolor": "white",
|
| 1451 |
-
"linecolor": "white",
|
| 1452 |
-
"ticks": ""
|
| 1453 |
-
},
|
| 1454 |
-
"baxis": {
|
| 1455 |
-
"gridcolor": "white",
|
| 1456 |
-
"linecolor": "white",
|
| 1457 |
-
"ticks": ""
|
| 1458 |
-
},
|
| 1459 |
-
"caxis": {
|
| 1460 |
-
"gridcolor": "white",
|
| 1461 |
-
"linecolor": "white",
|
| 1462 |
-
"ticks": ""
|
| 1463 |
-
}
|
| 1464 |
-
},
|
| 1465 |
-
"coloraxis": {
|
| 1466 |
-
"colorbar": {
|
| 1467 |
-
"outlinewidth": 0,
|
| 1468 |
-
"ticks": ""
|
| 1469 |
-
}
|
| 1470 |
-
},
|
| 1471 |
-
"colorscale": {
|
| 1472 |
-
"sequential": [
|
| 1473 |
-
[
|
| 1474 |
-
0.0,
|
| 1475 |
-
"#0d0887"
|
| 1476 |
-
],
|
| 1477 |
-
[
|
| 1478 |
-
0.1111111111111111,
|
| 1479 |
-
"#46039f"
|
| 1480 |
-
],
|
| 1481 |
-
[
|
| 1482 |
-
0.2222222222222222,
|
| 1483 |
-
"#7201a8"
|
| 1484 |
-
],
|
| 1485 |
-
[
|
| 1486 |
-
0.3333333333333333,
|
| 1487 |
-
"#9c179e"
|
| 1488 |
-
],
|
| 1489 |
-
[
|
| 1490 |
-
0.4444444444444444,
|
| 1491 |
-
"#bd3786"
|
| 1492 |
-
],
|
| 1493 |
-
[
|
| 1494 |
-
0.5555555555555556,
|
| 1495 |
-
"#d8576b"
|
| 1496 |
-
],
|
| 1497 |
-
[
|
| 1498 |
-
0.6666666666666666,
|
| 1499 |
-
"#ed7953"
|
| 1500 |
-
],
|
| 1501 |
-
[
|
| 1502 |
-
0.7777777777777778,
|
| 1503 |
-
"#fb9f3a"
|
| 1504 |
-
],
|
| 1505 |
-
[
|
| 1506 |
-
0.8888888888888888,
|
| 1507 |
-
"#fdca26"
|
| 1508 |
-
],
|
| 1509 |
-
[
|
| 1510 |
-
1.0,
|
| 1511 |
-
"#f0f921"
|
| 1512 |
-
]
|
| 1513 |
-
],
|
| 1514 |
-
"sequentialminus": [
|
| 1515 |
-
[
|
| 1516 |
-
0.0,
|
| 1517 |
-
"#0d0887"
|
| 1518 |
-
],
|
| 1519 |
-
[
|
| 1520 |
-
0.1111111111111111,
|
| 1521 |
-
"#46039f"
|
| 1522 |
-
],
|
| 1523 |
-
[
|
| 1524 |
-
0.2222222222222222,
|
| 1525 |
-
"#7201a8"
|
| 1526 |
-
],
|
| 1527 |
-
[
|
| 1528 |
-
0.3333333333333333,
|
| 1529 |
-
"#9c179e"
|
| 1530 |
-
],
|
| 1531 |
-
[
|
| 1532 |
-
0.4444444444444444,
|
| 1533 |
-
"#bd3786"
|
| 1534 |
-
],
|
| 1535 |
-
[
|
| 1536 |
-
0.5555555555555556,
|
| 1537 |
-
"#d8576b"
|
| 1538 |
-
],
|
| 1539 |
-
[
|
| 1540 |
-
0.6666666666666666,
|
| 1541 |
-
"#ed7953"
|
| 1542 |
-
],
|
| 1543 |
-
[
|
| 1544 |
-
0.7777777777777778,
|
| 1545 |
-
"#fb9f3a"
|
| 1546 |
-
],
|
| 1547 |
-
[
|
| 1548 |
-
0.8888888888888888,
|
| 1549 |
-
"#fdca26"
|
| 1550 |
-
],
|
| 1551 |
-
[
|
| 1552 |
-
1.0,
|
| 1553 |
-
"#f0f921"
|
| 1554 |
-
]
|
| 1555 |
-
],
|
| 1556 |
-
"diverging": [
|
| 1557 |
-
[
|
| 1558 |
-
0,
|
| 1559 |
-
"#8e0152"
|
| 1560 |
-
],
|
| 1561 |
-
[
|
| 1562 |
-
0.1,
|
| 1563 |
-
"#c51b7d"
|
| 1564 |
-
],
|
| 1565 |
-
[
|
| 1566 |
-
0.2,
|
| 1567 |
-
"#de77ae"
|
| 1568 |
-
],
|
| 1569 |
-
[
|
| 1570 |
-
0.3,
|
| 1571 |
-
"#f1b6da"
|
| 1572 |
-
],
|
| 1573 |
-
[
|
| 1574 |
-
0.4,
|
| 1575 |
-
"#fde0ef"
|
| 1576 |
-
],
|
| 1577 |
-
[
|
| 1578 |
-
0.5,
|
| 1579 |
-
"#f7f7f7"
|
| 1580 |
-
],
|
| 1581 |
-
[
|
| 1582 |
-
0.6,
|
| 1583 |
-
"#e6f5d0"
|
| 1584 |
-
],
|
| 1585 |
-
[
|
| 1586 |
-
0.7,
|
| 1587 |
-
"#b8e186"
|
| 1588 |
-
],
|
| 1589 |
-
[
|
| 1590 |
-
0.8,
|
| 1591 |
-
"#7fbc41"
|
| 1592 |
-
],
|
| 1593 |
-
[
|
| 1594 |
-
0.9,
|
| 1595 |
-
"#4d9221"
|
| 1596 |
-
],
|
| 1597 |
-
[
|
| 1598 |
-
1,
|
| 1599 |
-
"#276419"
|
| 1600 |
-
]
|
| 1601 |
-
]
|
| 1602 |
-
},
|
| 1603 |
-
"xaxis": {
|
| 1604 |
-
"gridcolor": "white",
|
| 1605 |
-
"linecolor": "white",
|
| 1606 |
-
"ticks": "",
|
| 1607 |
-
"title": {
|
| 1608 |
-
"standoff": 15
|
| 1609 |
-
},
|
| 1610 |
-
"zerolinecolor": "white",
|
| 1611 |
-
"automargin": true,
|
| 1612 |
-
"zerolinewidth": 2
|
| 1613 |
-
},
|
| 1614 |
-
"yaxis": {
|
| 1615 |
-
"gridcolor": "white",
|
| 1616 |
-
"linecolor": "white",
|
| 1617 |
-
"ticks": "",
|
| 1618 |
-
"title": {
|
| 1619 |
-
"standoff": 15
|
| 1620 |
-
},
|
| 1621 |
-
"zerolinecolor": "white",
|
| 1622 |
-
"automargin": true,
|
| 1623 |
-
"zerolinewidth": 2
|
| 1624 |
-
},
|
| 1625 |
-
"scene": {
|
| 1626 |
-
"xaxis": {
|
| 1627 |
-
"backgroundcolor": "#E5ECF6",
|
| 1628 |
-
"gridcolor": "white",
|
| 1629 |
-
"linecolor": "white",
|
| 1630 |
-
"showbackground": true,
|
| 1631 |
-
"ticks": "",
|
| 1632 |
-
"zerolinecolor": "white",
|
| 1633 |
-
"gridwidth": 2
|
| 1634 |
-
},
|
| 1635 |
-
"yaxis": {
|
| 1636 |
-
"backgroundcolor": "#E5ECF6",
|
| 1637 |
-
"gridcolor": "white",
|
| 1638 |
-
"linecolor": "white",
|
| 1639 |
-
"showbackground": true,
|
| 1640 |
-
"ticks": "",
|
| 1641 |
-
"zerolinecolor": "white",
|
| 1642 |
-
"gridwidth": 2
|
| 1643 |
-
},
|
| 1644 |
-
"zaxis": {
|
| 1645 |
-
"backgroundcolor": "#E5ECF6",
|
| 1646 |
-
"gridcolor": "white",
|
| 1647 |
-
"linecolor": "white",
|
| 1648 |
-
"showbackground": true,
|
| 1649 |
-
"ticks": "",
|
| 1650 |
-
"zerolinecolor": "white",
|
| 1651 |
-
"gridwidth": 2
|
| 1652 |
-
}
|
| 1653 |
-
},
|
| 1654 |
-
"shapedefaults": {
|
| 1655 |
-
"line": {
|
| 1656 |
-
"color": "#2a3f5f"
|
| 1657 |
-
}
|
| 1658 |
-
},
|
| 1659 |
-
"annotationdefaults": {
|
| 1660 |
-
"arrowcolor": "#2a3f5f",
|
| 1661 |
-
"arrowhead": 0,
|
| 1662 |
-
"arrowwidth": 1
|
| 1663 |
-
},
|
| 1664 |
-
"geo": {
|
| 1665 |
-
"bgcolor": "white",
|
| 1666 |
-
"landcolor": "#E5ECF6",
|
| 1667 |
-
"subunitcolor": "white",
|
| 1668 |
-
"showland": true,
|
| 1669 |
-
"showlakes": true,
|
| 1670 |
-
"lakecolor": "white"
|
| 1671 |
-
},
|
| 1672 |
-
"title": {
|
| 1673 |
-
"x": 0.05
|
| 1674 |
-
},
|
| 1675 |
-
"mapbox": {
|
| 1676 |
-
"style": "light"
|
| 1677 |
-
}
|
| 1678 |
-
}
|
| 1679 |
-
},
|
| 1680 |
-
"xaxis": {
|
| 1681 |
-
"anchor": "y",
|
| 1682 |
-
"domain": [
|
| 1683 |
-
0.0,
|
| 1684 |
-
1.0
|
| 1685 |
-
],
|
| 1686 |
-
"title": {
|
| 1687 |
-
"text": "cyl",
|
| 1688 |
-
"font": {
|
| 1689 |
-
"color": "#94a3b8"
|
| 1690 |
-
}
|
| 1691 |
-
},
|
| 1692 |
-
"gridcolor": "rgba(255,255,255,0.07)",
|
| 1693 |
-
"zerolinecolor": "rgba(255,255,255,0.1)"
|
| 1694 |
-
},
|
| 1695 |
-
"yaxis": {
|
| 1696 |
-
"anchor": "x",
|
| 1697 |
-
"domain": [
|
| 1698 |
-
0.0,
|
| 1699 |
-
1.0
|
| 1700 |
-
],
|
| 1701 |
-
"title": {
|
| 1702 |
-
"text": "mpg",
|
| 1703 |
-
"font": {
|
| 1704 |
-
"color": "#94a3b8"
|
| 1705 |
-
}
|
| 1706 |
-
},
|
| 1707 |
-
"gridcolor": "rgba(255,255,255,0.07)",
|
| 1708 |
-
"zerolinecolor": "rgba(255,255,255,0.1)"
|
| 1709 |
-
},
|
| 1710 |
-
"legend": {
|
| 1711 |
-
"tracegroupgap": 0
|
| 1712 |
-
},
|
| 1713 |
-
"title": {
|
| 1714 |
-
"text": "cyl vs mpg",
|
| 1715 |
-
"font": {
|
| 1716 |
-
"color": "#a78bfa",
|
| 1717 |
-
"size": 15
|
| 1718 |
-
}
|
| 1719 |
-
},
|
| 1720 |
-
"font": {
|
| 1721 |
-
"color": "#e2e8f0"
|
| 1722 |
-
},
|
| 1723 |
-
"margin": {
|
| 1724 |
-
"l": 50,
|
| 1725 |
-
"r": 20,
|
| 1726 |
-
"t": 55,
|
| 1727 |
-
"b": 50
|
| 1728 |
-
},
|
| 1729 |
-
"hoverlabel": {
|
| 1730 |
-
"font": {
|
| 1731 |
-
"color": "#e2e8f0"
|
| 1732 |
-
},
|
| 1733 |
-
"bgcolor": "rgba(15,23,42,0.95)"
|
| 1734 |
-
},
|
| 1735 |
-
"paper_bgcolor": "rgba(9,9,11,0.0)",
|
| 1736 |
-
"plot_bgcolor": "rgba(15,23,42,0.4)"
|
| 1737 |
-
}
|
| 1738 |
-
}
|
| 1739 |
-
},
|
| 1740 |
-
{
|
| 1741 |
-
"title": "mpg vs cyl",
|
| 1742 |
-
"fig_json": {
|
| 1743 |
-
"data": [
|
| 1744 |
-
{
|
| 1745 |
-
"hovertemplate": "mpg=%{x}<br>cyl=%{y}<extra></extra>",
|
| 1746 |
-
"legendgroup": "",
|
| 1747 |
-
"marker": {
|
| 1748 |
-
"color": "#22d3ee",
|
| 1749 |
-
"opacity": 0.75,
|
| 1750 |
-
"symbol": "circle"
|
| 1751 |
-
},
|
| 1752 |
-
"mode": "markers",
|
| 1753 |
-
"name": "",
|
| 1754 |
-
"orientation": "v",
|
| 1755 |
-
"showlegend": false,
|
| 1756 |
-
"x": {
|
| 1757 |
-
"dtype": "f8",
|
| 1758 |
-
"bdata": "AAAAAAAANUAAAAAAAAA1QM3MzMzMzDZAZmZmZmZmNUAzMzMzM7MyQJqZmZmZGTJAmpmZmZmZLEBmZmZmZmY4QM3MzMzMzDZAMzMzMzMzM0DNzMzMzMwxQGZmZmZmZjBAzczMzMxMMUBmZmZmZmYuQM3MzMzMzCRAzczMzMzMJEBmZmZmZmYtQDMzMzMzM0BAZmZmZmZmPkAzMzMzM/NAQAAAAAAAgDVAAAAAAAAAL0BmZmZmZmYuQJqZmZmZmSpAMzMzMzMzM0DNzMzMzEw7QAAAAAAAADpAZmZmZmZmPkCamZmZmZkvQDMzMzMzszNAAAAAAAAALkBmZmZmZmY1QA=="
|
| 1759 |
-
},
|
| 1760 |
-
"xaxis": "x",
|
| 1761 |
-
"y": {
|
| 1762 |
-
"dtype": "i1",
|
| 1763 |
-
"bdata": "BgYEBggGCAQEBgYICAgICAgEBAQECAgICAQEBAgGCAQ="
|
| 1764 |
-
},
|
| 1765 |
-
"yaxis": "y",
|
| 1766 |
-
"type": "scatter"
|
| 1767 |
-
}
|
| 1768 |
-
],
|
| 1769 |
-
"layout": {
|
| 1770 |
-
"template": {
|
| 1771 |
-
"data": {
|
| 1772 |
-
"histogram2dcontour": [
|
| 1773 |
-
{
|
| 1774 |
-
"type": "histogram2dcontour",
|
| 1775 |
-
"colorbar": {
|
| 1776 |
-
"outlinewidth": 0,
|
| 1777 |
-
"ticks": ""
|
| 1778 |
-
},
|
| 1779 |
-
"colorscale": [
|
| 1780 |
-
[
|
| 1781 |
-
0.0,
|
| 1782 |
-
"#0d0887"
|
| 1783 |
-
],
|
| 1784 |
-
[
|
| 1785 |
-
0.1111111111111111,
|
| 1786 |
-
"#46039f"
|
| 1787 |
-
],
|
| 1788 |
-
[
|
| 1789 |
-
0.2222222222222222,
|
| 1790 |
-
"#7201a8"
|
| 1791 |
-
],
|
| 1792 |
-
[
|
| 1793 |
-
0.3333333333333333,
|
| 1794 |
-
"#9c179e"
|
| 1795 |
-
],
|
| 1796 |
-
[
|
| 1797 |
-
0.4444444444444444,
|
| 1798 |
-
"#bd3786"
|
| 1799 |
-
],
|
| 1800 |
-
[
|
| 1801 |
-
0.5555555555555556,
|
| 1802 |
-
"#d8576b"
|
| 1803 |
-
],
|
| 1804 |
-
[
|
| 1805 |
-
0.6666666666666666,
|
| 1806 |
-
"#ed7953"
|
| 1807 |
-
],
|
| 1808 |
-
[
|
| 1809 |
-
0.7777777777777778,
|
| 1810 |
-
"#fb9f3a"
|
| 1811 |
-
],
|
| 1812 |
-
[
|
| 1813 |
-
0.8888888888888888,
|
| 1814 |
-
"#fdca26"
|
| 1815 |
-
],
|
| 1816 |
-
[
|
| 1817 |
-
1.0,
|
| 1818 |
-
"#f0f921"
|
| 1819 |
-
]
|
| 1820 |
-
]
|
| 1821 |
-
}
|
| 1822 |
-
],
|
| 1823 |
-
"choropleth": [
|
| 1824 |
-
{
|
| 1825 |
-
"type": "choropleth",
|
| 1826 |
-
"colorbar": {
|
| 1827 |
-
"outlinewidth": 0,
|
| 1828 |
-
"ticks": ""
|
| 1829 |
-
}
|
| 1830 |
-
}
|
| 1831 |
-
],
|
| 1832 |
-
"histogram2d": [
|
| 1833 |
-
{
|
| 1834 |
-
"type": "histogram2d",
|
| 1835 |
-
"colorbar": {
|
| 1836 |
-
"outlinewidth": 0,
|
| 1837 |
-
"ticks": ""
|
| 1838 |
-
},
|
| 1839 |
-
"colorscale": [
|
| 1840 |
-
[
|
| 1841 |
-
0.0,
|
| 1842 |
-
"#0d0887"
|
| 1843 |
-
],
|
| 1844 |
-
[
|
| 1845 |
-
0.1111111111111111,
|
| 1846 |
-
"#46039f"
|
| 1847 |
-
],
|
| 1848 |
-
[
|
| 1849 |
-
0.2222222222222222,
|
| 1850 |
-
"#7201a8"
|
| 1851 |
-
],
|
| 1852 |
-
[
|
| 1853 |
-
0.3333333333333333,
|
| 1854 |
-
"#9c179e"
|
| 1855 |
-
],
|
| 1856 |
-
[
|
| 1857 |
-
0.4444444444444444,
|
| 1858 |
-
"#bd3786"
|
| 1859 |
-
],
|
| 1860 |
-
[
|
| 1861 |
-
0.5555555555555556,
|
| 1862 |
-
"#d8576b"
|
| 1863 |
-
],
|
| 1864 |
-
[
|
| 1865 |
-
0.6666666666666666,
|
| 1866 |
-
"#ed7953"
|
| 1867 |
-
],
|
| 1868 |
-
[
|
| 1869 |
-
0.7777777777777778,
|
| 1870 |
-
"#fb9f3a"
|
| 1871 |
-
],
|
| 1872 |
-
[
|
| 1873 |
-
0.8888888888888888,
|
| 1874 |
-
"#fdca26"
|
| 1875 |
-
],
|
| 1876 |
-
[
|
| 1877 |
-
1.0,
|
| 1878 |
-
"#f0f921"
|
| 1879 |
-
]
|
| 1880 |
-
]
|
| 1881 |
-
}
|
| 1882 |
-
],
|
| 1883 |
-
"heatmap": [
|
| 1884 |
-
{
|
| 1885 |
-
"type": "heatmap",
|
| 1886 |
-
"colorbar": {
|
| 1887 |
-
"outlinewidth": 0,
|
| 1888 |
-
"ticks": ""
|
| 1889 |
-
},
|
| 1890 |
-
"colorscale": [
|
| 1891 |
-
[
|
| 1892 |
-
0.0,
|
| 1893 |
-
"#0d0887"
|
| 1894 |
-
],
|
| 1895 |
-
[
|
| 1896 |
-
0.1111111111111111,
|
| 1897 |
-
"#46039f"
|
| 1898 |
-
],
|
| 1899 |
-
[
|
| 1900 |
-
0.2222222222222222,
|
| 1901 |
-
"#7201a8"
|
| 1902 |
-
],
|
| 1903 |
-
[
|
| 1904 |
-
0.3333333333333333,
|
| 1905 |
-
"#9c179e"
|
| 1906 |
-
],
|
| 1907 |
-
[
|
| 1908 |
-
0.4444444444444444,
|
| 1909 |
-
"#bd3786"
|
| 1910 |
-
],
|
| 1911 |
-
[
|
| 1912 |
-
0.5555555555555556,
|
| 1913 |
-
"#d8576b"
|
| 1914 |
-
],
|
| 1915 |
-
[
|
| 1916 |
-
0.6666666666666666,
|
| 1917 |
-
"#ed7953"
|
| 1918 |
-
],
|
| 1919 |
-
[
|
| 1920 |
-
0.7777777777777778,
|
| 1921 |
-
"#fb9f3a"
|
| 1922 |
-
],
|
| 1923 |
-
[
|
| 1924 |
-
0.8888888888888888,
|
| 1925 |
-
"#fdca26"
|
| 1926 |
-
],
|
| 1927 |
-
[
|
| 1928 |
-
1.0,
|
| 1929 |
-
"#f0f921"
|
| 1930 |
-
]
|
| 1931 |
-
]
|
| 1932 |
-
}
|
| 1933 |
-
],
|
| 1934 |
-
"contourcarpet": [
|
| 1935 |
-
{
|
| 1936 |
-
"type": "contourcarpet",
|
| 1937 |
-
"colorbar": {
|
| 1938 |
-
"outlinewidth": 0,
|
| 1939 |
-
"ticks": ""
|
| 1940 |
-
}
|
| 1941 |
-
}
|
| 1942 |
-
],
|
| 1943 |
-
"contour": [
|
| 1944 |
-
{
|
| 1945 |
-
"type": "contour",
|
| 1946 |
-
"colorbar": {
|
| 1947 |
-
"outlinewidth": 0,
|
| 1948 |
-
"ticks": ""
|
| 1949 |
-
},
|
| 1950 |
-
"colorscale": [
|
| 1951 |
-
[
|
| 1952 |
-
0.0,
|
| 1953 |
-
"#0d0887"
|
| 1954 |
-
],
|
| 1955 |
-
[
|
| 1956 |
-
0.1111111111111111,
|
| 1957 |
-
"#46039f"
|
| 1958 |
-
],
|
| 1959 |
-
[
|
| 1960 |
-
0.2222222222222222,
|
| 1961 |
-
"#7201a8"
|
| 1962 |
-
],
|
| 1963 |
-
[
|
| 1964 |
-
0.3333333333333333,
|
| 1965 |
-
"#9c179e"
|
| 1966 |
-
],
|
| 1967 |
-
[
|
| 1968 |
-
0.4444444444444444,
|
| 1969 |
-
"#bd3786"
|
| 1970 |
-
],
|
| 1971 |
-
[
|
| 1972 |
-
0.5555555555555556,
|
| 1973 |
-
"#d8576b"
|
| 1974 |
-
],
|
| 1975 |
-
[
|
| 1976 |
-
0.6666666666666666,
|
| 1977 |
-
"#ed7953"
|
| 1978 |
-
],
|
| 1979 |
-
[
|
| 1980 |
-
0.7777777777777778,
|
| 1981 |
-
"#fb9f3a"
|
| 1982 |
-
],
|
| 1983 |
-
[
|
| 1984 |
-
0.8888888888888888,
|
| 1985 |
-
"#fdca26"
|
| 1986 |
-
],
|
| 1987 |
-
[
|
| 1988 |
-
1.0,
|
| 1989 |
-
"#f0f921"
|
| 1990 |
-
]
|
| 1991 |
-
]
|
| 1992 |
-
}
|
| 1993 |
-
],
|
| 1994 |
-
"surface": [
|
| 1995 |
-
{
|
| 1996 |
-
"type": "surface",
|
| 1997 |
-
"colorbar": {
|
| 1998 |
-
"outlinewidth": 0,
|
| 1999 |
-
"ticks": ""
|
| 2000 |
-
},
|
| 2001 |
-
"colorscale": [
|
| 2002 |
-
[
|
| 2003 |
-
0.0,
|
| 2004 |
-
"#0d0887"
|
| 2005 |
-
],
|
| 2006 |
-
[
|
| 2007 |
-
0.1111111111111111,
|
| 2008 |
-
"#46039f"
|
| 2009 |
-
],
|
| 2010 |
-
[
|
| 2011 |
-
0.2222222222222222,
|
| 2012 |
-
"#7201a8"
|
| 2013 |
-
],
|
| 2014 |
-
[
|
| 2015 |
-
0.3333333333333333,
|
| 2016 |
-
"#9c179e"
|
| 2017 |
-
],
|
| 2018 |
-
[
|
| 2019 |
-
0.4444444444444444,
|
| 2020 |
-
"#bd3786"
|
| 2021 |
-
],
|
| 2022 |
-
[
|
| 2023 |
-
0.5555555555555556,
|
| 2024 |
-
"#d8576b"
|
| 2025 |
-
],
|
| 2026 |
-
[
|
| 2027 |
-
0.6666666666666666,
|
| 2028 |
-
"#ed7953"
|
| 2029 |
-
],
|
| 2030 |
-
[
|
| 2031 |
-
0.7777777777777778,
|
| 2032 |
-
"#fb9f3a"
|
| 2033 |
-
],
|
| 2034 |
-
[
|
| 2035 |
-
0.8888888888888888,
|
| 2036 |
-
"#fdca26"
|
| 2037 |
-
],
|
| 2038 |
-
[
|
| 2039 |
-
1.0,
|
| 2040 |
-
"#f0f921"
|
| 2041 |
-
]
|
| 2042 |
-
]
|
| 2043 |
-
}
|
| 2044 |
-
],
|
| 2045 |
-
"mesh3d": [
|
| 2046 |
-
{
|
| 2047 |
-
"type": "mesh3d",
|
| 2048 |
-
"colorbar": {
|
| 2049 |
-
"outlinewidth": 0,
|
| 2050 |
-
"ticks": ""
|
| 2051 |
-
}
|
| 2052 |
-
}
|
| 2053 |
-
],
|
| 2054 |
-
"scatter": [
|
| 2055 |
-
{
|
| 2056 |
-
"fillpattern": {
|
| 2057 |
-
"fillmode": "overlay",
|
| 2058 |
-
"size": 10,
|
| 2059 |
-
"solidity": 0.2
|
| 2060 |
-
},
|
| 2061 |
-
"type": "scatter"
|
| 2062 |
-
}
|
| 2063 |
-
],
|
| 2064 |
-
"parcoords": [
|
| 2065 |
-
{
|
| 2066 |
-
"type": "parcoords",
|
| 2067 |
-
"line": {
|
| 2068 |
-
"colorbar": {
|
| 2069 |
-
"outlinewidth": 0,
|
| 2070 |
-
"ticks": ""
|
| 2071 |
-
}
|
| 2072 |
-
}
|
| 2073 |
-
}
|
| 2074 |
-
],
|
| 2075 |
-
"scatterpolargl": [
|
| 2076 |
-
{
|
| 2077 |
-
"type": "scatterpolargl",
|
| 2078 |
-
"marker": {
|
| 2079 |
-
"colorbar": {
|
| 2080 |
-
"outlinewidth": 0,
|
| 2081 |
-
"ticks": ""
|
| 2082 |
-
}
|
| 2083 |
-
}
|
| 2084 |
-
}
|
| 2085 |
-
],
|
| 2086 |
-
"bar": [
|
| 2087 |
-
{
|
| 2088 |
-
"error_x": {
|
| 2089 |
-
"color": "#2a3f5f"
|
| 2090 |
-
},
|
| 2091 |
-
"error_y": {
|
| 2092 |
-
"color": "#2a3f5f"
|
| 2093 |
-
},
|
| 2094 |
-
"marker": {
|
| 2095 |
-
"line": {
|
| 2096 |
-
"color": "#E5ECF6",
|
| 2097 |
-
"width": 0.5
|
| 2098 |
-
},
|
| 2099 |
-
"pattern": {
|
| 2100 |
-
"fillmode": "overlay",
|
| 2101 |
-
"size": 10,
|
| 2102 |
-
"solidity": 0.2
|
| 2103 |
-
}
|
| 2104 |
-
},
|
| 2105 |
-
"type": "bar"
|
| 2106 |
-
}
|
| 2107 |
-
],
|
| 2108 |
-
"scattergeo": [
|
| 2109 |
-
{
|
| 2110 |
-
"type": "scattergeo",
|
| 2111 |
-
"marker": {
|
| 2112 |
-
"colorbar": {
|
| 2113 |
-
"outlinewidth": 0,
|
| 2114 |
-
"ticks": ""
|
| 2115 |
-
}
|
| 2116 |
-
}
|
| 2117 |
-
}
|
| 2118 |
-
],
|
| 2119 |
-
"scatterpolar": [
|
| 2120 |
-
{
|
| 2121 |
-
"type": "scatterpolar",
|
| 2122 |
-
"marker": {
|
| 2123 |
-
"colorbar": {
|
| 2124 |
-
"outlinewidth": 0,
|
| 2125 |
-
"ticks": ""
|
| 2126 |
-
}
|
| 2127 |
-
}
|
| 2128 |
-
}
|
| 2129 |
-
],
|
| 2130 |
-
"histogram": [
|
| 2131 |
-
{
|
| 2132 |
-
"marker": {
|
| 2133 |
-
"pattern": {
|
| 2134 |
-
"fillmode": "overlay",
|
| 2135 |
-
"size": 10,
|
| 2136 |
-
"solidity": 0.2
|
| 2137 |
-
}
|
| 2138 |
-
},
|
| 2139 |
-
"type": "histogram"
|
| 2140 |
-
}
|
| 2141 |
-
],
|
| 2142 |
-
"scattergl": [
|
| 2143 |
-
{
|
| 2144 |
-
"type": "scattergl",
|
| 2145 |
-
"marker": {
|
| 2146 |
-
"colorbar": {
|
| 2147 |
-
"outlinewidth": 0,
|
| 2148 |
-
"ticks": ""
|
| 2149 |
-
}
|
| 2150 |
-
}
|
| 2151 |
-
}
|
| 2152 |
-
],
|
| 2153 |
-
"scatter3d": [
|
| 2154 |
-
{
|
| 2155 |
-
"type": "scatter3d",
|
| 2156 |
-
"line": {
|
| 2157 |
-
"colorbar": {
|
| 2158 |
-
"outlinewidth": 0,
|
| 2159 |
-
"ticks": ""
|
| 2160 |
-
}
|
| 2161 |
-
},
|
| 2162 |
-
"marker": {
|
| 2163 |
-
"colorbar": {
|
| 2164 |
-
"outlinewidth": 0,
|
| 2165 |
-
"ticks": ""
|
| 2166 |
-
}
|
| 2167 |
-
}
|
| 2168 |
-
}
|
| 2169 |
-
],
|
| 2170 |
-
"scattermap": [
|
| 2171 |
-
{
|
| 2172 |
-
"type": "scattermap",
|
| 2173 |
-
"marker": {
|
| 2174 |
-
"colorbar": {
|
| 2175 |
-
"outlinewidth": 0,
|
| 2176 |
-
"ticks": ""
|
| 2177 |
-
}
|
| 2178 |
-
}
|
| 2179 |
-
}
|
| 2180 |
-
],
|
| 2181 |
-
"scattermapbox": [
|
| 2182 |
-
{
|
| 2183 |
-
"type": "scattermapbox",
|
| 2184 |
-
"marker": {
|
| 2185 |
-
"colorbar": {
|
| 2186 |
-
"outlinewidth": 0,
|
| 2187 |
-
"ticks": ""
|
| 2188 |
-
}
|
| 2189 |
-
}
|
| 2190 |
-
}
|
| 2191 |
-
],
|
| 2192 |
-
"scatterternary": [
|
| 2193 |
-
{
|
| 2194 |
-
"type": "scatterternary",
|
| 2195 |
-
"marker": {
|
| 2196 |
-
"colorbar": {
|
| 2197 |
-
"outlinewidth": 0,
|
| 2198 |
-
"ticks": ""
|
| 2199 |
-
}
|
| 2200 |
-
}
|
| 2201 |
-
}
|
| 2202 |
-
],
|
| 2203 |
-
"scattercarpet": [
|
| 2204 |
-
{
|
| 2205 |
-
"type": "scattercarpet",
|
| 2206 |
-
"marker": {
|
| 2207 |
-
"colorbar": {
|
| 2208 |
-
"outlinewidth": 0,
|
| 2209 |
-
"ticks": ""
|
| 2210 |
-
}
|
| 2211 |
-
}
|
| 2212 |
-
}
|
| 2213 |
-
],
|
| 2214 |
-
"carpet": [
|
| 2215 |
-
{
|
| 2216 |
-
"aaxis": {
|
| 2217 |
-
"endlinecolor": "#2a3f5f",
|
| 2218 |
-
"gridcolor": "white",
|
| 2219 |
-
"linecolor": "white",
|
| 2220 |
-
"minorgridcolor": "white",
|
| 2221 |
-
"startlinecolor": "#2a3f5f"
|
| 2222 |
-
},
|
| 2223 |
-
"baxis": {
|
| 2224 |
-
"endlinecolor": "#2a3f5f",
|
| 2225 |
-
"gridcolor": "white",
|
| 2226 |
-
"linecolor": "white",
|
| 2227 |
-
"minorgridcolor": "white",
|
| 2228 |
-
"startlinecolor": "#2a3f5f"
|
| 2229 |
-
},
|
| 2230 |
-
"type": "carpet"
|
| 2231 |
-
}
|
| 2232 |
-
],
|
| 2233 |
-
"table": [
|
| 2234 |
-
{
|
| 2235 |
-
"cells": {
|
| 2236 |
-
"fill": {
|
| 2237 |
-
"color": "#EBF0F8"
|
| 2238 |
-
},
|
| 2239 |
-
"line": {
|
| 2240 |
-
"color": "white"
|
| 2241 |
-
}
|
| 2242 |
-
},
|
| 2243 |
-
"header": {
|
| 2244 |
-
"fill": {
|
| 2245 |
-
"color": "#C8D4E3"
|
| 2246 |
-
},
|
| 2247 |
-
"line": {
|
| 2248 |
-
"color": "white"
|
| 2249 |
-
}
|
| 2250 |
-
},
|
| 2251 |
-
"type": "table"
|
| 2252 |
-
}
|
| 2253 |
-
],
|
| 2254 |
-
"barpolar": [
|
| 2255 |
-
{
|
| 2256 |
-
"marker": {
|
| 2257 |
-
"line": {
|
| 2258 |
-
"color": "#E5ECF6",
|
| 2259 |
-
"width": 0.5
|
| 2260 |
-
},
|
| 2261 |
-
"pattern": {
|
| 2262 |
-
"fillmode": "overlay",
|
| 2263 |
-
"size": 10,
|
| 2264 |
-
"solidity": 0.2
|
| 2265 |
-
}
|
| 2266 |
-
},
|
| 2267 |
-
"type": "barpolar"
|
| 2268 |
-
}
|
| 2269 |
-
],
|
| 2270 |
-
"pie": [
|
| 2271 |
-
{
|
| 2272 |
-
"automargin": true,
|
| 2273 |
-
"type": "pie"
|
| 2274 |
-
}
|
| 2275 |
-
]
|
| 2276 |
-
},
|
| 2277 |
-
"layout": {
|
| 2278 |
-
"autotypenumbers": "strict",
|
| 2279 |
-
"colorway": [
|
| 2280 |
-
"#636efa",
|
| 2281 |
-
"#EF553B",
|
| 2282 |
-
"#00cc96",
|
| 2283 |
-
"#ab63fa",
|
| 2284 |
-
"#FFA15A",
|
| 2285 |
-
"#19d3f3",
|
| 2286 |
-
"#FF6692",
|
| 2287 |
-
"#B6E880",
|
| 2288 |
-
"#FF97FF",
|
| 2289 |
-
"#FECB52"
|
| 2290 |
-
],
|
| 2291 |
-
"font": {
|
| 2292 |
-
"color": "#2a3f5f"
|
| 2293 |
-
},
|
| 2294 |
-
"hovermode": "closest",
|
| 2295 |
-
"hoverlabel": {
|
| 2296 |
-
"align": "left"
|
| 2297 |
-
},
|
| 2298 |
-
"paper_bgcolor": "white",
|
| 2299 |
-
"plot_bgcolor": "#E5ECF6",
|
| 2300 |
-
"polar": {
|
| 2301 |
-
"bgcolor": "#E5ECF6",
|
| 2302 |
-
"angularaxis": {
|
| 2303 |
-
"gridcolor": "white",
|
| 2304 |
-
"linecolor": "white",
|
| 2305 |
-
"ticks": ""
|
| 2306 |
-
},
|
| 2307 |
-
"radialaxis": {
|
| 2308 |
-
"gridcolor": "white",
|
| 2309 |
-
"linecolor": "white",
|
| 2310 |
-
"ticks": ""
|
| 2311 |
-
}
|
| 2312 |
-
},
|
| 2313 |
-
"ternary": {
|
| 2314 |
-
"bgcolor": "#E5ECF6",
|
| 2315 |
-
"aaxis": {
|
| 2316 |
-
"gridcolor": "white",
|
| 2317 |
-
"linecolor": "white",
|
| 2318 |
-
"ticks": ""
|
| 2319 |
-
},
|
| 2320 |
-
"baxis": {
|
| 2321 |
-
"gridcolor": "white",
|
| 2322 |
-
"linecolor": "white",
|
| 2323 |
-
"ticks": ""
|
| 2324 |
-
},
|
| 2325 |
-
"caxis": {
|
| 2326 |
-
"gridcolor": "white",
|
| 2327 |
-
"linecolor": "white",
|
| 2328 |
-
"ticks": ""
|
| 2329 |
-
}
|
| 2330 |
-
},
|
| 2331 |
-
"coloraxis": {
|
| 2332 |
-
"colorbar": {
|
| 2333 |
-
"outlinewidth": 0,
|
| 2334 |
-
"ticks": ""
|
| 2335 |
-
}
|
| 2336 |
-
},
|
| 2337 |
-
"colorscale": {
|
| 2338 |
-
"sequential": [
|
| 2339 |
-
[
|
| 2340 |
-
0.0,
|
| 2341 |
-
"#0d0887"
|
| 2342 |
-
],
|
| 2343 |
-
[
|
| 2344 |
-
0.1111111111111111,
|
| 2345 |
-
"#46039f"
|
| 2346 |
-
],
|
| 2347 |
-
[
|
| 2348 |
-
0.2222222222222222,
|
| 2349 |
-
"#7201a8"
|
| 2350 |
-
],
|
| 2351 |
-
[
|
| 2352 |
-
0.3333333333333333,
|
| 2353 |
-
"#9c179e"
|
| 2354 |
-
],
|
| 2355 |
-
[
|
| 2356 |
-
0.4444444444444444,
|
| 2357 |
-
"#bd3786"
|
| 2358 |
-
],
|
| 2359 |
-
[
|
| 2360 |
-
0.5555555555555556,
|
| 2361 |
-
"#d8576b"
|
| 2362 |
-
],
|
| 2363 |
-
[
|
| 2364 |
-
0.6666666666666666,
|
| 2365 |
-
"#ed7953"
|
| 2366 |
-
],
|
| 2367 |
-
[
|
| 2368 |
-
0.7777777777777778,
|
| 2369 |
-
"#fb9f3a"
|
| 2370 |
-
],
|
| 2371 |
-
[
|
| 2372 |
-
0.8888888888888888,
|
| 2373 |
-
"#fdca26"
|
| 2374 |
-
],
|
| 2375 |
-
[
|
| 2376 |
-
1.0,
|
| 2377 |
-
"#f0f921"
|
| 2378 |
-
]
|
| 2379 |
-
],
|
| 2380 |
-
"sequentialminus": [
|
| 2381 |
-
[
|
| 2382 |
-
0.0,
|
| 2383 |
-
"#0d0887"
|
| 2384 |
-
],
|
| 2385 |
-
[
|
| 2386 |
-
0.1111111111111111,
|
| 2387 |
-
"#46039f"
|
| 2388 |
-
],
|
| 2389 |
-
[
|
| 2390 |
-
0.2222222222222222,
|
| 2391 |
-
"#7201a8"
|
| 2392 |
-
],
|
| 2393 |
-
[
|
| 2394 |
-
0.3333333333333333,
|
| 2395 |
-
"#9c179e"
|
| 2396 |
-
],
|
| 2397 |
-
[
|
| 2398 |
-
0.4444444444444444,
|
| 2399 |
-
"#bd3786"
|
| 2400 |
-
],
|
| 2401 |
-
[
|
| 2402 |
-
0.5555555555555556,
|
| 2403 |
-
"#d8576b"
|
| 2404 |
-
],
|
| 2405 |
-
[
|
| 2406 |
-
0.6666666666666666,
|
| 2407 |
-
"#ed7953"
|
| 2408 |
-
],
|
| 2409 |
-
[
|
| 2410 |
-
0.7777777777777778,
|
| 2411 |
-
"#fb9f3a"
|
| 2412 |
-
],
|
| 2413 |
-
[
|
| 2414 |
-
0.8888888888888888,
|
| 2415 |
-
"#fdca26"
|
| 2416 |
-
],
|
| 2417 |
-
[
|
| 2418 |
-
1.0,
|
| 2419 |
-
"#f0f921"
|
| 2420 |
-
]
|
| 2421 |
-
],
|
| 2422 |
-
"diverging": [
|
| 2423 |
-
[
|
| 2424 |
-
0,
|
| 2425 |
-
"#8e0152"
|
| 2426 |
-
],
|
| 2427 |
-
[
|
| 2428 |
-
0.1,
|
| 2429 |
-
"#c51b7d"
|
| 2430 |
-
],
|
| 2431 |
-
[
|
| 2432 |
-
0.2,
|
| 2433 |
-
"#de77ae"
|
| 2434 |
-
],
|
| 2435 |
-
[
|
| 2436 |
-
0.3,
|
| 2437 |
-
"#f1b6da"
|
| 2438 |
-
],
|
| 2439 |
-
[
|
| 2440 |
-
0.4,
|
| 2441 |
-
"#fde0ef"
|
| 2442 |
-
],
|
| 2443 |
-
[
|
| 2444 |
-
0.5,
|
| 2445 |
-
"#f7f7f7"
|
| 2446 |
-
],
|
| 2447 |
-
[
|
| 2448 |
-
0.6,
|
| 2449 |
-
"#e6f5d0"
|
| 2450 |
-
],
|
| 2451 |
-
[
|
| 2452 |
-
0.7,
|
| 2453 |
-
"#b8e186"
|
| 2454 |
-
],
|
| 2455 |
-
[
|
| 2456 |
-
0.8,
|
| 2457 |
-
"#7fbc41"
|
| 2458 |
-
],
|
| 2459 |
-
[
|
| 2460 |
-
0.9,
|
| 2461 |
-
"#4d9221"
|
| 2462 |
-
],
|
| 2463 |
-
[
|
| 2464 |
-
1,
|
| 2465 |
-
"#276419"
|
| 2466 |
-
]
|
| 2467 |
-
]
|
| 2468 |
-
},
|
| 2469 |
-
"xaxis": {
|
| 2470 |
-
"gridcolor": "white",
|
| 2471 |
-
"linecolor": "white",
|
| 2472 |
-
"ticks": "",
|
| 2473 |
-
"title": {
|
| 2474 |
-
"standoff": 15
|
| 2475 |
-
},
|
| 2476 |
-
"zerolinecolor": "white",
|
| 2477 |
-
"automargin": true,
|
| 2478 |
-
"zerolinewidth": 2
|
| 2479 |
-
},
|
| 2480 |
-
"yaxis": {
|
| 2481 |
-
"gridcolor": "white",
|
| 2482 |
-
"linecolor": "white",
|
| 2483 |
-
"ticks": "",
|
| 2484 |
-
"title": {
|
| 2485 |
-
"standoff": 15
|
| 2486 |
-
},
|
| 2487 |
-
"zerolinecolor": "white",
|
| 2488 |
-
"automargin": true,
|
| 2489 |
-
"zerolinewidth": 2
|
| 2490 |
-
},
|
| 2491 |
-
"scene": {
|
| 2492 |
-
"xaxis": {
|
| 2493 |
-
"backgroundcolor": "#E5ECF6",
|
| 2494 |
-
"gridcolor": "white",
|
| 2495 |
-
"linecolor": "white",
|
| 2496 |
-
"showbackground": true,
|
| 2497 |
-
"ticks": "",
|
| 2498 |
-
"zerolinecolor": "white",
|
| 2499 |
-
"gridwidth": 2
|
| 2500 |
-
},
|
| 2501 |
-
"yaxis": {
|
| 2502 |
-
"backgroundcolor": "#E5ECF6",
|
| 2503 |
-
"gridcolor": "white",
|
| 2504 |
-
"linecolor": "white",
|
| 2505 |
-
"showbackground": true,
|
| 2506 |
-
"ticks": "",
|
| 2507 |
-
"zerolinecolor": "white",
|
| 2508 |
-
"gridwidth": 2
|
| 2509 |
-
},
|
| 2510 |
-
"zaxis": {
|
| 2511 |
-
"backgroundcolor": "#E5ECF6",
|
| 2512 |
-
"gridcolor": "white",
|
| 2513 |
-
"linecolor": "white",
|
| 2514 |
-
"showbackground": true,
|
| 2515 |
-
"ticks": "",
|
| 2516 |
-
"zerolinecolor": "white",
|
| 2517 |
-
"gridwidth": 2
|
| 2518 |
-
}
|
| 2519 |
-
},
|
| 2520 |
-
"shapedefaults": {
|
| 2521 |
-
"line": {
|
| 2522 |
-
"color": "#2a3f5f"
|
| 2523 |
-
}
|
| 2524 |
-
},
|
| 2525 |
-
"annotationdefaults": {
|
| 2526 |
-
"arrowcolor": "#2a3f5f",
|
| 2527 |
-
"arrowhead": 0,
|
| 2528 |
-
"arrowwidth": 1
|
| 2529 |
-
},
|
| 2530 |
-
"geo": {
|
| 2531 |
-
"bgcolor": "white",
|
| 2532 |
-
"landcolor": "#E5ECF6",
|
| 2533 |
-
"subunitcolor": "white",
|
| 2534 |
-
"showland": true,
|
| 2535 |
-
"showlakes": true,
|
| 2536 |
-
"lakecolor": "white"
|
| 2537 |
-
},
|
| 2538 |
-
"title": {
|
| 2539 |
-
"x": 0.05
|
| 2540 |
-
},
|
| 2541 |
-
"mapbox": {
|
| 2542 |
-
"style": "light"
|
| 2543 |
-
}
|
| 2544 |
-
}
|
| 2545 |
-
},
|
| 2546 |
-
"xaxis": {
|
| 2547 |
-
"anchor": "y",
|
| 2548 |
-
"domain": [
|
| 2549 |
-
0.0,
|
| 2550 |
-
1.0
|
| 2551 |
-
],
|
| 2552 |
-
"title": {
|
| 2553 |
-
"text": "mpg",
|
| 2554 |
-
"font": {
|
| 2555 |
-
"color": "#94a3b8"
|
| 2556 |
-
}
|
| 2557 |
-
},
|
| 2558 |
-
"gridcolor": "rgba(255,255,255,0.07)",
|
| 2559 |
-
"zerolinecolor": "rgba(255,255,255,0.1)"
|
| 2560 |
-
},
|
| 2561 |
-
"yaxis": {
|
| 2562 |
-
"anchor": "x",
|
| 2563 |
-
"domain": [
|
| 2564 |
-
0.0,
|
| 2565 |
-
1.0
|
| 2566 |
-
],
|
| 2567 |
-
"title": {
|
| 2568 |
-
"text": "cyl",
|
| 2569 |
-
"font": {
|
| 2570 |
-
"color": "#94a3b8"
|
| 2571 |
-
}
|
| 2572 |
-
},
|
| 2573 |
-
"gridcolor": "rgba(255,255,255,0.07)",
|
| 2574 |
-
"zerolinecolor": "rgba(255,255,255,0.1)"
|
| 2575 |
-
},
|
| 2576 |
-
"legend": {
|
| 2577 |
-
"tracegroupgap": 0
|
| 2578 |
-
},
|
| 2579 |
-
"title": {
|
| 2580 |
-
"text": "mpg vs cyl",
|
| 2581 |
-
"font": {
|
| 2582 |
-
"color": "#a78bfa",
|
| 2583 |
-
"size": 15
|
| 2584 |
-
}
|
| 2585 |
-
},
|
| 2586 |
-
"font": {
|
| 2587 |
-
"color": "#e2e8f0"
|
| 2588 |
-
},
|
| 2589 |
-
"margin": {
|
| 2590 |
-
"l": 50,
|
| 2591 |
-
"r": 20,
|
| 2592 |
-
"t": 55,
|
| 2593 |
-
"b": 50
|
| 2594 |
-
},
|
| 2595 |
-
"hoverlabel": {
|
| 2596 |
-
"font": {
|
| 2597 |
-
"color": "#e2e8f0"
|
| 2598 |
-
},
|
| 2599 |
-
"bgcolor": "rgba(15,23,42,0.95)"
|
| 2600 |
-
},
|
| 2601 |
-
"paper_bgcolor": "rgba(9,9,11,0.0)",
|
| 2602 |
-
"plot_bgcolor": "rgba(15,23,42,0.4)"
|
| 2603 |
-
}
|
| 2604 |
-
}
|
| 2605 |
-
}
|
| 2606 |
-
],
|
| 2607 |
-
"png_charts": [
|
| 2608 |
-
"bar_model_vs_mpg.png",
|
| 2609 |
-
"correlation_matrix.png",
|
| 2610 |
-
"distribution_mpg.png",
|
| 2611 |
-
"scatter_mpg_vs_cyl.png"
|
| 2612 |
-
],
|
| 2613 |
-
"rows_count": 32,
|
| 2614 |
-
"cols_count": 11,
|
| 2615 |
-
"numeric_count": 10,
|
| 2616 |
-
"cat_count": 1,
|
| 2617 |
-
"preview": [
|
| 2618 |
-
{
|
| 2619 |
-
"model": "Mazda RX4",
|
| 2620 |
-
"mpg": 21.0,
|
| 2621 |
-
"cyl": 6,
|
| 2622 |
-
"hp": 110,
|
| 2623 |
-
"drat": 3.9,
|
| 2624 |
-
"wt": 2.62,
|
| 2625 |
-
"qsec": 16.46,
|
| 2626 |
-
"vs": 0,
|
| 2627 |
-
"am": 1,
|
| 2628 |
-
"gear": 4,
|
| 2629 |
-
"carb": 4
|
| 2630 |
-
},
|
| 2631 |
-
{
|
| 2632 |
-
"model": "Mazda RX4 Wag",
|
| 2633 |
-
"mpg": 21.0,
|
| 2634 |
-
"cyl": 6,
|
| 2635 |
-
"hp": 110,
|
| 2636 |
-
"drat": 3.9,
|
| 2637 |
-
"wt": 2.875,
|
| 2638 |
-
"qsec": 17.02,
|
| 2639 |
-
"vs": 0,
|
| 2640 |
-
"am": 1,
|
| 2641 |
-
"gear": 4,
|
| 2642 |
-
"carb": 4
|
| 2643 |
-
},
|
| 2644 |
-
{
|
| 2645 |
-
"model": "Datsun 710",
|
| 2646 |
-
"mpg": 22.8,
|
| 2647 |
-
"cyl": 4,
|
| 2648 |
-
"hp": 93,
|
| 2649 |
-
"drat": 3.85,
|
| 2650 |
-
"wt": 2.32,
|
| 2651 |
-
"qsec": 18.61,
|
| 2652 |
-
"vs": 1,
|
| 2653 |
-
"am": 1,
|
| 2654 |
-
"gear": 4,
|
| 2655 |
-
"carb": 1
|
| 2656 |
-
},
|
| 2657 |
-
{
|
| 2658 |
-
"model": "Hornet 4 Drive",
|
| 2659 |
-
"mpg": 21.4,
|
| 2660 |
-
"cyl": 6,
|
| 2661 |
-
"hp": 110,
|
| 2662 |
-
"drat": 3.08,
|
| 2663 |
-
"wt": 3.215,
|
| 2664 |
-
"qsec": 19.44,
|
| 2665 |
-
"vs": 1,
|
| 2666 |
-
"am": 0,
|
| 2667 |
-
"gear": 3,
|
| 2668 |
-
"carb": 1
|
| 2669 |
-
},
|
| 2670 |
-
{
|
| 2671 |
-
"model": "Hornet Sportabout",
|
| 2672 |
-
"mpg": 18.7,
|
| 2673 |
-
"cyl": 8,
|
| 2674 |
-
"hp": 175,
|
| 2675 |
-
"drat": 3.15,
|
| 2676 |
-
"wt": 3.44,
|
| 2677 |
-
"qsec": 17.02,
|
| 2678 |
-
"vs": 0,
|
| 2679 |
-
"am": 0,
|
| 2680 |
-
"gear": 3,
|
| 2681 |
-
"carb": 2
|
| 2682 |
-
},
|
| 2683 |
-
{
|
| 2684 |
-
"model": "Valiant",
|
| 2685 |
-
"mpg": 18.1,
|
| 2686 |
-
"cyl": 6,
|
| 2687 |
-
"hp": 105,
|
| 2688 |
-
"drat": 2.76,
|
| 2689 |
-
"wt": 3.46,
|
| 2690 |
-
"qsec": 20.22,
|
| 2691 |
-
"vs": 1,
|
| 2692 |
-
"am": 0,
|
| 2693 |
-
"gear": 3,
|
| 2694 |
-
"carb": 1
|
| 2695 |
-
},
|
| 2696 |
-
{
|
| 2697 |
-
"model": "Duster 360",
|
| 2698 |
-
"mpg": 14.3,
|
| 2699 |
-
"cyl": 8,
|
| 2700 |
-
"hp": 245,
|
| 2701 |
-
"drat": 3.21,
|
| 2702 |
-
"wt": 3.57,
|
| 2703 |
-
"qsec": 15.84,
|
| 2704 |
-
"vs": 0,
|
| 2705 |
-
"am": 0,
|
| 2706 |
-
"gear": 3,
|
| 2707 |
-
"carb": 4
|
| 2708 |
-
},
|
| 2709 |
-
{
|
| 2710 |
-
"model": "Merc 240D",
|
| 2711 |
-
"mpg": 24.4,
|
| 2712 |
-
"cyl": 4,
|
| 2713 |
-
"hp": 62,
|
| 2714 |
-
"drat": 3.69,
|
| 2715 |
-
"wt": 3.19,
|
| 2716 |
-
"qsec": 20.0,
|
| 2717 |
-
"vs": 1,
|
| 2718 |
-
"am": 0,
|
| 2719 |
-
"gear": 4,
|
| 2720 |
-
"carb": 2
|
| 2721 |
-
},
|
| 2722 |
-
{
|
| 2723 |
-
"model": "Merc 230",
|
| 2724 |
-
"mpg": 22.8,
|
| 2725 |
-
"cyl": 4,
|
| 2726 |
-
"hp": 95,
|
| 2727 |
-
"drat": 3.92,
|
| 2728 |
-
"wt": 3.15,
|
| 2729 |
-
"qsec": 22.9,
|
| 2730 |
-
"vs": 1,
|
| 2731 |
-
"am": 0,
|
| 2732 |
-
"gear": 4,
|
| 2733 |
-
"carb": 2
|
| 2734 |
-
},
|
| 2735 |
-
{
|
| 2736 |
-
"model": "Merc 280",
|
| 2737 |
-
"mpg": 19.2,
|
| 2738 |
-
"cyl": 6,
|
| 2739 |
-
"hp": 123,
|
| 2740 |
-
"drat": 3.92,
|
| 2741 |
-
"wt": 3.44,
|
| 2742 |
-
"qsec": 18.3,
|
| 2743 |
-
"vs": 1,
|
| 2744 |
-
"am": 0,
|
| 2745 |
-
"gear": 4,
|
| 2746 |
-
"carb": 4
|
| 2747 |
-
},
|
| 2748 |
-
{
|
| 2749 |
-
"model": "Merc 280C",
|
| 2750 |
-
"mpg": 17.8,
|
| 2751 |
-
"cyl": 6,
|
| 2752 |
-
"hp": 123,
|
| 2753 |
-
"drat": 3.92,
|
| 2754 |
-
"wt": 3.44,
|
| 2755 |
-
"qsec": 18.9,
|
| 2756 |
-
"vs": 1,
|
| 2757 |
-
"am": 0,
|
| 2758 |
-
"gear": 4,
|
| 2759 |
-
"carb": 4
|
| 2760 |
-
},
|
| 2761 |
-
{
|
| 2762 |
-
"model": "Merc 450SE",
|
| 2763 |
-
"mpg": 16.4,
|
| 2764 |
-
"cyl": 8,
|
| 2765 |
-
"hp": 180,
|
| 2766 |
-
"drat": 3.07,
|
| 2767 |
-
"wt": 4.07,
|
| 2768 |
-
"qsec": 17.4,
|
| 2769 |
-
"vs": 0,
|
| 2770 |
-
"am": 0,
|
| 2771 |
-
"gear": 3,
|
| 2772 |
-
"carb": 3
|
| 2773 |
-
},
|
| 2774 |
-
{
|
| 2775 |
-
"model": "Merc 450SL",
|
| 2776 |
-
"mpg": 17.3,
|
| 2777 |
-
"cyl": 8,
|
| 2778 |
-
"hp": 180,
|
| 2779 |
-
"drat": 3.07,
|
| 2780 |
-
"wt": 3.73,
|
| 2781 |
-
"qsec": 17.6,
|
| 2782 |
-
"vs": 0,
|
| 2783 |
-
"am": 0,
|
| 2784 |
-
"gear": 3,
|
| 2785 |
-
"carb": 3
|
| 2786 |
-
},
|
| 2787 |
-
{
|
| 2788 |
-
"model": "Merc 450SLC",
|
| 2789 |
-
"mpg": 15.2,
|
| 2790 |
-
"cyl": 8,
|
| 2791 |
-
"hp": 180,
|
| 2792 |
-
"drat": 3.07,
|
| 2793 |
-
"wt": 3.78,
|
| 2794 |
-
"qsec": 18.0,
|
| 2795 |
-
"vs": 0,
|
| 2796 |
-
"am": 0,
|
| 2797 |
-
"gear": 3,
|
| 2798 |
-
"carb": 3
|
| 2799 |
-
},
|
| 2800 |
-
{
|
| 2801 |
-
"model": "Cadillac Fleetwood",
|
| 2802 |
-
"mpg": 10.4,
|
| 2803 |
-
"cyl": 8,
|
| 2804 |
-
"hp": 205,
|
| 2805 |
-
"drat": 2.93,
|
| 2806 |
-
"wt": 5.25,
|
| 2807 |
-
"qsec": 17.98,
|
| 2808 |
-
"vs": 0,
|
| 2809 |
-
"am": 0,
|
| 2810 |
-
"gear": 3,
|
| 2811 |
-
"carb": 4
|
| 2812 |
-
},
|
| 2813 |
-
{
|
| 2814 |
-
"model": "Lincoln Continental",
|
| 2815 |
-
"mpg": 10.4,
|
| 2816 |
-
"cyl": 8,
|
| 2817 |
-
"hp": 215,
|
| 2818 |
-
"drat": 3.0,
|
| 2819 |
-
"wt": 5.424,
|
| 2820 |
-
"qsec": 17.82,
|
| 2821 |
-
"vs": 0,
|
| 2822 |
-
"am": 0,
|
| 2823 |
-
"gear": 3,
|
| 2824 |
-
"carb": 4
|
| 2825 |
-
},
|
| 2826 |
-
{
|
| 2827 |
-
"model": "Chrysler Imperial",
|
| 2828 |
-
"mpg": 14.7,
|
| 2829 |
-
"cyl": 8,
|
| 2830 |
-
"hp": 230,
|
| 2831 |
-
"drat": 3.23,
|
| 2832 |
-
"wt": 5.345,
|
| 2833 |
-
"qsec": 17.42,
|
| 2834 |
-
"vs": 0,
|
| 2835 |
-
"am": 0,
|
| 2836 |
-
"gear": 3,
|
| 2837 |
-
"carb": 4
|
| 2838 |
-
},
|
| 2839 |
-
{
|
| 2840 |
-
"model": "Fiat 128",
|
| 2841 |
-
"mpg": 32.4,
|
| 2842 |
-
"cyl": 4,
|
| 2843 |
-
"hp": 66,
|
| 2844 |
-
"drat": 4.08,
|
| 2845 |
-
"wt": 2.2,
|
| 2846 |
-
"qsec": 19.47,
|
| 2847 |
-
"vs": 1,
|
| 2848 |
-
"am": 1,
|
| 2849 |
-
"gear": 4,
|
| 2850 |
-
"carb": 1
|
| 2851 |
-
},
|
| 2852 |
-
{
|
| 2853 |
-
"model": "Honda Civic",
|
| 2854 |
-
"mpg": 30.4,
|
| 2855 |
-
"cyl": 4,
|
| 2856 |
-
"hp": 52,
|
| 2857 |
-
"drat": 4.93,
|
| 2858 |
-
"wt": 1.615,
|
| 2859 |
-
"qsec": 18.52,
|
| 2860 |
-
"vs": 1,
|
| 2861 |
-
"am": 1,
|
| 2862 |
-
"gear": 4,
|
| 2863 |
-
"carb": 2
|
| 2864 |
-
},
|
| 2865 |
-
{
|
| 2866 |
-
"model": "Toyota Corolla",
|
| 2867 |
-
"mpg": 33.9,
|
| 2868 |
-
"cyl": 4,
|
| 2869 |
-
"hp": 65,
|
| 2870 |
-
"drat": 4.22,
|
| 2871 |
-
"wt": 1.835,
|
| 2872 |
-
"qsec": 19.9,
|
| 2873 |
-
"vs": 1,
|
| 2874 |
-
"am": 1,
|
| 2875 |
-
"gear": 4,
|
| 2876 |
-
"carb": 1
|
| 2877 |
-
},
|
| 2878 |
-
{
|
| 2879 |
-
"model": "Toyota Corona",
|
| 2880 |
-
"mpg": 21.5,
|
| 2881 |
-
"cyl": 4,
|
| 2882 |
-
"hp": 97,
|
| 2883 |
-
"drat": 3.7,
|
| 2884 |
-
"wt": 2.465,
|
| 2885 |
-
"qsec": 20.01,
|
| 2886 |
-
"vs": 1,
|
| 2887 |
-
"am": 0,
|
| 2888 |
-
"gear": 3,
|
| 2889 |
-
"carb": 1
|
| 2890 |
-
},
|
| 2891 |
-
{
|
| 2892 |
-
"model": "Dodge Challenger",
|
| 2893 |
-
"mpg": 15.5,
|
| 2894 |
-
"cyl": 8,
|
| 2895 |
-
"hp": 150,
|
| 2896 |
-
"drat": 2.76,
|
| 2897 |
-
"wt": 3.52,
|
| 2898 |
-
"qsec": 16.87,
|
| 2899 |
-
"vs": 0,
|
| 2900 |
-
"am": 0,
|
| 2901 |
-
"gear": 3,
|
| 2902 |
-
"carb": 2
|
| 2903 |
-
},
|
| 2904 |
-
{
|
| 2905 |
-
"model": "AMC Javelin",
|
| 2906 |
-
"mpg": 15.2,
|
| 2907 |
-
"cyl": 8,
|
| 2908 |
-
"hp": 150,
|
| 2909 |
-
"drat": 3.15,
|
| 2910 |
-
"wt": 3.435,
|
| 2911 |
-
"qsec": 17.3,
|
| 2912 |
-
"vs": 0,
|
| 2913 |
-
"am": 0,
|
| 2914 |
-
"gear": 3,
|
| 2915 |
-
"carb": 2
|
| 2916 |
-
},
|
| 2917 |
-
{
|
| 2918 |
-
"model": "Camaro Z28",
|
| 2919 |
-
"mpg": 13.3,
|
| 2920 |
-
"cyl": 8,
|
| 2921 |
-
"hp": 245,
|
| 2922 |
-
"drat": 3.73,
|
| 2923 |
-
"wt": 3.84,
|
| 2924 |
-
"qsec": 15.41,
|
| 2925 |
-
"vs": 0,
|
| 2926 |
-
"am": 0,
|
| 2927 |
-
"gear": 3,
|
| 2928 |
-
"carb": 4
|
| 2929 |
-
},
|
| 2930 |
-
{
|
| 2931 |
-
"model": "Pontiac Firebird",
|
| 2932 |
-
"mpg": 19.2,
|
| 2933 |
-
"cyl": 8,
|
| 2934 |
-
"hp": 175,
|
| 2935 |
-
"drat": 3.08,
|
| 2936 |
-
"wt": 3.845,
|
| 2937 |
-
"qsec": 17.05,
|
| 2938 |
-
"vs": 0,
|
| 2939 |
-
"am": 0,
|
| 2940 |
-
"gear": 3,
|
| 2941 |
-
"carb": 2
|
| 2942 |
-
},
|
| 2943 |
-
{
|
| 2944 |
-
"model": "Fiat X1-9",
|
| 2945 |
-
"mpg": 27.3,
|
| 2946 |
-
"cyl": 4,
|
| 2947 |
-
"hp": 66,
|
| 2948 |
-
"drat": 4.08,
|
| 2949 |
-
"wt": 1.935,
|
| 2950 |
-
"qsec": 18.9,
|
| 2951 |
-
"vs": 1,
|
| 2952 |
-
"am": 1,
|
| 2953 |
-
"gear": 4,
|
| 2954 |
-
"carb": 1
|
| 2955 |
-
},
|
| 2956 |
-
{
|
| 2957 |
-
"model": "Porsche 914-2",
|
| 2958 |
-
"mpg": 26.0,
|
| 2959 |
-
"cyl": 4,
|
| 2960 |
-
"hp": 91,
|
| 2961 |
-
"drat": 4.43,
|
| 2962 |
-
"wt": 2.14,
|
| 2963 |
-
"qsec": 16.7,
|
| 2964 |
-
"vs": 0,
|
| 2965 |
-
"am": 1,
|
| 2966 |
-
"gear": 5,
|
| 2967 |
-
"carb": 2
|
| 2968 |
-
},
|
| 2969 |
-
{
|
| 2970 |
-
"model": "Lotus Europa",
|
| 2971 |
-
"mpg": 30.4,
|
| 2972 |
-
"cyl": 4,
|
| 2973 |
-
"hp": 113,
|
| 2974 |
-
"drat": 3.77,
|
| 2975 |
-
"wt": 1.513,
|
| 2976 |
-
"qsec": 16.9,
|
| 2977 |
-
"vs": 1,
|
| 2978 |
-
"am": 1,
|
| 2979 |
-
"gear": 5,
|
| 2980 |
-
"carb": 2
|
| 2981 |
-
},
|
| 2982 |
-
{
|
| 2983 |
-
"model": "Ford Pantera L",
|
| 2984 |
-
"mpg": 15.8,
|
| 2985 |
-
"cyl": 8,
|
| 2986 |
-
"hp": 264,
|
| 2987 |
-
"drat": 4.22,
|
| 2988 |
-
"wt": 3.17,
|
| 2989 |
-
"qsec": 14.5,
|
| 2990 |
-
"vs": 0,
|
| 2991 |
-
"am": 1,
|
| 2992 |
-
"gear": 5,
|
| 2993 |
-
"carb": 4
|
| 2994 |
-
},
|
| 2995 |
-
{
|
| 2996 |
-
"model": "Ferrari Dino",
|
| 2997 |
-
"mpg": 19.7,
|
| 2998 |
-
"cyl": 6,
|
| 2999 |
-
"hp": 175,
|
| 3000 |
-
"drat": 3.62,
|
| 3001 |
-
"wt": 2.77,
|
| 3002 |
-
"qsec": 15.5,
|
| 3003 |
-
"vs": 0,
|
| 3004 |
-
"am": 1,
|
| 3005 |
-
"gear": 5,
|
| 3006 |
-
"carb": 6
|
| 3007 |
-
},
|
| 3008 |
-
{
|
| 3009 |
-
"model": "Maserati Bora",
|
| 3010 |
-
"mpg": 15.0,
|
| 3011 |
-
"cyl": 8,
|
| 3012 |
-
"hp": 335,
|
| 3013 |
-
"drat": 3.54,
|
| 3014 |
-
"wt": 3.57,
|
| 3015 |
-
"qsec": 14.6,
|
| 3016 |
-
"vs": 0,
|
| 3017 |
-
"am": 1,
|
| 3018 |
-
"gear": 5,
|
| 3019 |
-
"carb": 8
|
| 3020 |
-
},
|
| 3021 |
-
{
|
| 3022 |
-
"model": "Volvo 142E",
|
| 3023 |
-
"mpg": 21.4,
|
| 3024 |
-
"cyl": 4,
|
| 3025 |
-
"hp": 109,
|
| 3026 |
-
"drat": 4.11,
|
| 3027 |
-
"wt": 2.78,
|
| 3028 |
-
"qsec": 18.6,
|
| 3029 |
-
"vs": 1,
|
| 3030 |
-
"am": 1,
|
| 3031 |
-
"gear": 4,
|
| 3032 |
-
"carb": 2
|
| 3033 |
-
}
|
| 3034 |
-
]
|
| 3035 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/14b48eadf665/stdout.log
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/a402006b7ddd/cleaned.csv
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
model,mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
|
| 2 |
-
Mazda RX4,21.0,6,160.0,110,3.9,2.62,16.46,0,1,4,4
|
| 3 |
-
Mazda RX4 Wag,21.0,6,160.0,110,3.9,2.875,17.02,0,1,4,4
|
| 4 |
-
Datsun 710,22.8,4,108.0,93,3.85,2.32,18.61,1,1,4,1
|
| 5 |
-
Hornet 4 Drive,21.4,6,258.0,110,3.08,3.215,19.44,1,0,3,1
|
| 6 |
-
Hornet Sportabout,18.7,8,360.0,175,3.15,3.44,17.02,0,0,3,2
|
| 7 |
-
Valiant,18.1,6,225.0,105,2.76,3.46,20.22,1,0,3,1
|
| 8 |
-
Duster 360,14.3,8,360.0,245,3.21,3.57,15.84,0,0,3,4
|
| 9 |
-
Merc 240D,24.4,4,146.7,62,3.69,3.19,20.0,1,0,4,2
|
| 10 |
-
Merc 230,22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
|
| 11 |
-
Merc 280,19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
|
| 12 |
-
Merc 280C,17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
|
| 13 |
-
Merc 450SE,16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
|
| 14 |
-
Merc 450SL,17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
|
| 15 |
-
Merc 450SLC,15.2,8,275.8,180,3.07,3.78,18.0,0,0,3,3
|
| 16 |
-
Cadillac Fleetwood,10.4,8,472.0,205,2.93,5.25,17.98,0,0,3,4
|
| 17 |
-
Lincoln Continental,10.4,8,460.0,215,3.0,5.424,17.82,0,0,3,4
|
| 18 |
-
Chrysler Imperial,14.7,8,440.0,230,3.23,5.345,17.42,0,0,3,4
|
| 19 |
-
Fiat 128,32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
|
| 20 |
-
Honda Civic,30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
|
| 21 |
-
Toyota Corolla,33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
|
| 22 |
-
Toyota Corona,21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
|
| 23 |
-
Dodge Challenger,15.5,8,318.0,150,2.76,3.52,16.87,0,0,3,2
|
| 24 |
-
AMC Javelin,15.2,8,304.0,150,3.15,3.435,17.3,0,0,3,2
|
| 25 |
-
Camaro Z28,13.3,8,350.0,245,3.73,3.84,15.41,0,0,3,4
|
| 26 |
-
Pontiac Firebird,19.2,8,400.0,175,3.08,3.845,17.05,0,0,3,2
|
| 27 |
-
Fiat X1-9,27.3,4,79.0,66,4.08,1.935,18.9,1,1,4,1
|
| 28 |
-
Porsche 914-2,26.0,4,120.3,91,4.43,2.14,16.7,0,1,5,2
|
| 29 |
-
Lotus Europa,30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
|
| 30 |
-
Ford Pantera L,15.8,8,351.0,264,4.22,3.17,14.5,0,1,5,4
|
| 31 |
-
Ferrari Dino,19.7,6,145.0,175,3.62,2.77,15.5,0,1,5,6
|
| 32 |
-
Maserati Bora,15.0,8,301.0,335,3.54,3.57,14.6,0,1,5,8
|
| 33 |
-
Volvo 142E,21.4,4,121.0,109,4.11,2.78,18.6,1,1,4,2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/a402006b7ddd/done.txt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
done
|
|
|
|
|
|
data/sessions/a402006b7ddd/metadata.json
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"id": "a402006b7ddd",
|
| 3 |
-
"name": "Analysis - 6/28/2026",
|
| 4 |
-
"report_title": "Executive Analysis Report 3",
|
| 5 |
-
"filename": "mtcars.csv",
|
| 6 |
-
"size": 1788,
|
| 7 |
-
"created_at": 1782631164433.6091,
|
| 8 |
-
"status": "completed"
|
| 9 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/a402006b7ddd/original.csv
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
model,mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
|
| 2 |
-
Mazda RX4,21.0,6,160.0,110,3.9,2.62,16.46,0,1,4,4
|
| 3 |
-
Mazda RX4 Wag,21.0,6,160.0,110,3.9,2.875,17.02,0,1,4,4
|
| 4 |
-
Datsun 710,22.8,4,108.0,93,3.85,2.32,18.61,1,1,4,1
|
| 5 |
-
Hornet 4 Drive,21.4,6,258.0,110,3.08,3.215,19.44,1,0,3,1
|
| 6 |
-
Hornet Sportabout,18.7,8,360.0,175,3.15,3.44,17.02,0,0,3,2
|
| 7 |
-
Valiant,18.1,6,225.0,105,2.76,3.46,20.22,1,0,3,1
|
| 8 |
-
Duster 360,14.3,8,360.0,245,3.21,3.57,15.84,0,0,3,4
|
| 9 |
-
Merc 240D,24.4,4,146.7,62,3.69,3.19,20.0,1,0,4,2
|
| 10 |
-
Merc 230,22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
|
| 11 |
-
Merc 280,19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
|
| 12 |
-
Merc 280C,17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
|
| 13 |
-
Merc 450SE,16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
|
| 14 |
-
Merc 450SL,17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
|
| 15 |
-
Merc 450SLC,15.2,8,275.8,180,3.07,3.78,18.0,0,0,3,3
|
| 16 |
-
Cadillac Fleetwood,10.4,8,472.0,205,2.93,5.25,17.98,0,0,3,4
|
| 17 |
-
Lincoln Continental,10.4,8,460.0,215,3.0,5.424,17.82,0,0,3,4
|
| 18 |
-
Chrysler Imperial,14.7,8,440.0,230,3.23,5.345,17.42,0,0,3,4
|
| 19 |
-
Fiat 128,32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
|
| 20 |
-
Honda Civic,30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
|
| 21 |
-
Toyota Corolla,33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
|
| 22 |
-
Toyota Corona,21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
|
| 23 |
-
Dodge Challenger,15.5,8,318.0,150,2.76,3.52,16.87,0,0,3,2
|
| 24 |
-
AMC Javelin,15.2,8,304.0,150,3.15,3.435,17.3,0,0,3,2
|
| 25 |
-
Camaro Z28,13.3,8,350.0,245,3.73,3.84,15.41,0,0,3,4
|
| 26 |
-
Pontiac Firebird,19.2,8,400.0,175,3.08,3.845,17.05,0,0,3,2
|
| 27 |
-
Fiat X1-9,27.3,4,79.0,66,4.08,1.935,18.9,1,1,4,1
|
| 28 |
-
Porsche 914-2,26.0,4,120.3,91,4.43,2.14,16.7,0,1,5,2
|
| 29 |
-
Lotus Europa,30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
|
| 30 |
-
Ford Pantera L,15.8,8,351.0,264,4.22,3.17,14.5,0,1,5,4
|
| 31 |
-
Ferrari Dino,19.7,6,145.0,175,3.62,2.77,15.5,0,1,5,6
|
| 32 |
-
Maserati Bora,15.0,8,301.0,335,3.54,3.57,14.6,0,1,5,8
|
| 33 |
-
Volvo 142E,21.4,4,121.0,109,4.11,2.78,18.6,1,1,4,2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/a402006b7ddd/original_upload.csv
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
"model","mpg","cyl","disp","hp","drat","wt","qsec","vs","am","gear","carb"
|
| 2 |
-
"Mazda RX4",21,6,160,110,3.9,2.62,16.46,0,1,4,4
|
| 3 |
-
"Mazda RX4 Wag",21,6,160,110,3.9,2.875,17.02,0,1,4,4
|
| 4 |
-
"Datsun 710",22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
|
| 5 |
-
"Hornet 4 Drive",21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
|
| 6 |
-
"Hornet Sportabout",18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
|
| 7 |
-
"Valiant",18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
|
| 8 |
-
"Duster 360",14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
|
| 9 |
-
"Merc 240D",24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
|
| 10 |
-
"Merc 230",22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
|
| 11 |
-
"Merc 280",19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
|
| 12 |
-
"Merc 280C",17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
|
| 13 |
-
"Merc 450SE",16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
|
| 14 |
-
"Merc 450SL",17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
|
| 15 |
-
"Merc 450SLC",15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
|
| 16 |
-
"Cadillac Fleetwood",10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
|
| 17 |
-
"Lincoln Continental",10.4,8,460,215,3,5.424,17.82,0,0,3,4
|
| 18 |
-
"Chrysler Imperial",14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
|
| 19 |
-
"Fiat 128",32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
|
| 20 |
-
"Honda Civic",30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
|
| 21 |
-
"Toyota Corolla",33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
|
| 22 |
-
"Toyota Corona",21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
|
| 23 |
-
"Dodge Challenger",15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
|
| 24 |
-
"AMC Javelin",15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
|
| 25 |
-
"Camaro Z28",13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
|
| 26 |
-
"Pontiac Firebird",19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
|
| 27 |
-
"Fiat X1-9",27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
|
| 28 |
-
"Porsche 914-2",26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
|
| 29 |
-
"Lotus Europa",30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
|
| 30 |
-
"Ford Pantera L",15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
|
| 31 |
-
"Ferrari Dino",19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
|
| 32 |
-
"Maserati Bora",15,8,301,335,3.54,3.57,14.6,0,1,5,8
|
| 33 |
-
"Volvo 142E",21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/a402006b7ddd/results.json
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/a402006b7ddd/stdout.log
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/c71065e6ffab/cleaned.csv
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/c71065e6ffab/done.txt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
done
|
|
|
|
|
|
data/sessions/c71065e6ffab/metadata.json
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"id": "c71065e6ffab",
|
| 3 |
-
"name": "Credit Card",
|
| 4 |
-
"filename": "credit card.csv",
|
| 5 |
-
"size": 211884,
|
| 6 |
-
"created_at": 1782643254770.404,
|
| 7 |
-
"status": "completed"
|
| 8 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/sessions/c71065e6ffab/original.csv
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/c71065e6ffab/original_upload.csv
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/c71065e6ffab/results.json
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sessions/c71065e6ffab/stdout.log
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
grip_force_vs_height.png
ADDED
|
Git LFS Details
|
grip_force_vs_weight.png
ADDED
|
Git LFS Details
|
height_vs_weight.png
ADDED
|
Git LFS Details
|
main.py
CHANGED
|
@@ -10,12 +10,21 @@ for streaming real-time analysis logs.
|
|
| 10 |
|
| 11 |
import os
|
| 12 |
import sys
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
import json
|
| 14 |
import re
|
| 15 |
import uuid
|
| 16 |
import asyncio
|
| 17 |
import shutil
|
| 18 |
import time
|
|
|
|
|
|
|
| 19 |
from pathlib import Path
|
| 20 |
from typing import Optional
|
| 21 |
|
|
@@ -230,9 +239,13 @@ def get_project_metadata(project_id: str) -> dict:
|
|
| 230 |
output_dir = OUTPUTS_DIR / project_id
|
| 231 |
thumbnail = None
|
| 232 |
if output_dir.exists() and output_dir.is_dir():
|
| 233 |
-
png_charts = sorted(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
if png_charts:
|
| 235 |
-
thumbnail = f"/api/charts/{project_id}/{png_charts[0]}"
|
| 236 |
|
| 237 |
meta = {
|
| 238 |
"id": project_id,
|
|
@@ -253,6 +266,32 @@ def parse_bool(value: Optional[str]) -> bool:
|
|
| 253 |
return bool(value and str(value).strip().lower() not in {"false", "0", "off", "no", ""})
|
| 254 |
|
| 255 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
# ---------------------------------------------------------------------------
|
| 257 |
# Background Task Pipeline
|
| 258 |
# ---------------------------------------------------------------------------
|
|
@@ -284,14 +323,24 @@ def run_crew_in_background(
|
|
| 284 |
if api_key:
|
| 285 |
os.environ[env_key_name] = api_key
|
| 286 |
|
| 287 |
-
# Save or update the report title in project metadata
|
| 288 |
try:
|
| 289 |
meta = get_project_metadata(session_id)
|
| 290 |
if report_title:
|
| 291 |
meta["report_title"] = report_title.strip()
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
session_dir = SESSIONS_DIR / session_id
|
| 297 |
log_path = session_dir / "stdout.log"
|
|
@@ -371,6 +420,8 @@ def run_crew_in_background(
|
|
| 371 |
try:
|
| 372 |
meta = get_project_metadata(session_id)
|
| 373 |
meta["status"] = "completed"
|
|
|
|
|
|
|
| 374 |
save_project_metadata(session_id, meta)
|
| 375 |
except Exception:
|
| 376 |
pass
|
|
@@ -568,10 +619,14 @@ async def get_results(session_id: str):
|
|
| 568 |
"""Retrieves cached JSON results containing stats, insights, and charts."""
|
| 569 |
results_path = SESSIONS_DIR / session_id / "results.json"
|
| 570 |
if not results_path.exists():
|
| 571 |
-
|
| 572 |
|
| 573 |
with open(results_path, "r", encoding="utf-8") as f:
|
| 574 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
|
| 576 |
|
| 577 |
@app.post("/api/copilot")
|
|
@@ -632,6 +687,7 @@ async def get_pdf_report(session_id: str, report_title: Optional[str] = None):
|
|
| 632 |
|
| 633 |
meta = get_project_metadata(session_id)
|
| 634 |
title = report_title.strip() if report_title else meta.get("report_title", meta.get("name", "Analysis Report"))
|
|
|
|
| 635 |
|
| 636 |
# Format result structure for reportlab builder
|
| 637 |
df = read_csv_robust(cleaned_csv)
|
|
@@ -643,6 +699,7 @@ async def get_pdf_report(session_id: str, report_title: Optional[str] = None):
|
|
| 643 |
"code": data.get("code", ""),
|
| 644 |
"output_dir": str(OUTPUTS_DIR / session_id),
|
| 645 |
"report_title": title,
|
|
|
|
| 646 |
}
|
| 647 |
|
| 648 |
try:
|
|
@@ -722,6 +779,7 @@ async def list_projects():
|
|
| 722 |
async def create_project(
|
| 723 |
name: str = Form(...),
|
| 724 |
report_title: str = Form(""),
|
|
|
|
| 725 |
file: UploadFile = File(...)
|
| 726 |
):
|
| 727 |
"""Creates a new project context and uploads the dataset CSV."""
|
|
@@ -742,6 +800,8 @@ async def create_project(
|
|
| 742 |
"id": project_id,
|
| 743 |
"name": name.strip(),
|
| 744 |
"report_title": report_title.strip() or f"{name.strip()} Executive Analysis",
|
|
|
|
|
|
|
| 745 |
"filename": file.filename,
|
| 746 |
"size": file_path.stat().st_size,
|
| 747 |
"created_at": time.time() * 1000,
|
|
@@ -764,6 +824,32 @@ async def rename_project(project_id: str, name: str = Form(...)):
|
|
| 764 |
|
| 765 |
return meta
|
| 766 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 767 |
@app.delete("/api/projects/{project_id}")
|
| 768 |
async def delete_project(project_id: str):
|
| 769 |
"""Deletes all session files, artifacts, and outputs of a project."""
|
|
@@ -780,6 +866,163 @@ async def delete_project(project_id: str):
|
|
| 780 |
return {"status": "deleted", "id": project_id}
|
| 781 |
|
| 782 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 783 |
@app.get("/api/projects/{project_id}/download-csv")
|
| 784 |
async def download_project_csv(project_id: str):
|
| 785 |
"""Downloads the cleaned dataset CSV for the specified project."""
|
|
|
|
| 10 |
|
| 11 |
import os
|
| 12 |
import sys
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
sys.stdout.reconfigure(encoding='utf-8')
|
| 16 |
+
sys.stderr.reconfigure(encoding='utf-8')
|
| 17 |
+
except Exception:
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
import json
|
| 21 |
import re
|
| 22 |
import uuid
|
| 23 |
import asyncio
|
| 24 |
import shutil
|
| 25 |
import time
|
| 26 |
+
import zipfile
|
| 27 |
+
from io import BytesIO
|
| 28 |
from pathlib import Path
|
| 29 |
from typing import Optional
|
| 30 |
|
|
|
|
| 239 |
output_dir = OUTPUTS_DIR / project_id
|
| 240 |
thumbnail = None
|
| 241 |
if output_dir.exists() and output_dir.is_dir():
|
| 242 |
+
png_charts = sorted(
|
| 243 |
+
[f for f in output_dir.glob("*.png")],
|
| 244 |
+
key=lambda x: x.stat().st_mtime,
|
| 245 |
+
reverse=True
|
| 246 |
+
)
|
| 247 |
if png_charts:
|
| 248 |
+
thumbnail = f"/api/charts/{project_id}/{png_charts[0].name}"
|
| 249 |
|
| 250 |
meta = {
|
| 251 |
"id": project_id,
|
|
|
|
| 266 |
return bool(value and str(value).strip().lower() not in {"false", "0", "off", "no", ""})
|
| 267 |
|
| 268 |
|
| 269 |
+
def optimize_goal_grammar(goal: str, provider: str, model: str, api_key: str, env_key_name: str) -> str:
|
| 270 |
+
"""Uses the runtime-configured LLM to optimize the grammar of the project goal."""
|
| 271 |
+
if not goal.strip():
|
| 272 |
+
return ""
|
| 273 |
+
try:
|
| 274 |
+
from config.llm_config import apply_runtime_llm_settings, get_llm_params
|
| 275 |
+
from crewai import LLM
|
| 276 |
+
|
| 277 |
+
apply_runtime_llm_settings(provider, model, api_key or "", env_key_name)
|
| 278 |
+
params = get_llm_params()
|
| 279 |
+
llm = LLM(**params)
|
| 280 |
+
|
| 281 |
+
prompt = (
|
| 282 |
+
"You are a professional editor. Improve the grammar, phrasing, and professional tone "
|
| 283 |
+
"of the following data analysis goal. Keep it concise (1-2 sentences). "
|
| 284 |
+
"Return ONLY the corrected goal text, without any introductory text, quotes, or metadata.\n\n"
|
| 285 |
+
f"Goal: {goal.strip()}"
|
| 286 |
+
)
|
| 287 |
+
response = llm.call([{"role": "user", "content": prompt}])
|
| 288 |
+
result = response if isinstance(response, str) else str(response)
|
| 289 |
+
return result.strip().strip('"').strip("'")
|
| 290 |
+
except Exception as e:
|
| 291 |
+
print(f"Grammar optimization failed: {e}")
|
| 292 |
+
return goal.strip()
|
| 293 |
+
|
| 294 |
+
|
| 295 |
# ---------------------------------------------------------------------------
|
| 296 |
# Background Task Pipeline
|
| 297 |
# ---------------------------------------------------------------------------
|
|
|
|
| 323 |
if api_key:
|
| 324 |
os.environ[env_key_name] = api_key
|
| 325 |
|
| 326 |
+
# Save or update the report title and goal in project metadata
|
| 327 |
try:
|
| 328 |
meta = get_project_metadata(session_id)
|
| 329 |
if report_title:
|
| 330 |
meta["report_title"] = report_title.strip()
|
| 331 |
+
|
| 332 |
+
user_goal = meta.get("goal", "")
|
| 333 |
+
if user_goal.strip():
|
| 334 |
+
print("📝 Optimizing goal grammar...")
|
| 335 |
+
opt_goal = optimize_goal_grammar(user_goal, provider, model, api_key, env_key_name)
|
| 336 |
+
meta["optimized_goal"] = opt_goal
|
| 337 |
+
print(f"✨ Optimized goal: {opt_goal}")
|
| 338 |
+
else:
|
| 339 |
+
meta["optimized_goal"] = ""
|
| 340 |
+
|
| 341 |
+
save_project_metadata(session_id, meta)
|
| 342 |
+
except Exception as e:
|
| 343 |
+
print(f"Error handling metadata goal/title: {e}")
|
| 344 |
|
| 345 |
session_dir = SESSIONS_DIR / session_id
|
| 346 |
log_path = session_dir / "stdout.log"
|
|
|
|
| 420 |
try:
|
| 421 |
meta = get_project_metadata(session_id)
|
| 422 |
meta["status"] = "completed"
|
| 423 |
+
if png_charts_list:
|
| 424 |
+
meta["thumbnail"] = f"/api/charts/{session_id}/{png_charts_list[0]}"
|
| 425 |
save_project_metadata(session_id, meta)
|
| 426 |
except Exception:
|
| 427 |
pass
|
|
|
|
| 619 |
"""Retrieves cached JSON results containing stats, insights, and charts."""
|
| 620 |
results_path = SESSIONS_DIR / session_id / "results.json"
|
| 621 |
if not results_path.exists():
|
| 622 |
+
return {"ready": False, "status": "pending"}
|
| 623 |
|
| 624 |
with open(results_path, "r", encoding="utf-8") as f:
|
| 625 |
+
data = json.load(f)
|
| 626 |
+
if "error" in data:
|
| 627 |
+
return data
|
| 628 |
+
data["ready"] = True
|
| 629 |
+
return data
|
| 630 |
|
| 631 |
|
| 632 |
@app.post("/api/copilot")
|
|
|
|
| 687 |
|
| 688 |
meta = get_project_metadata(session_id)
|
| 689 |
title = report_title.strip() if report_title else meta.get("report_title", meta.get("name", "Analysis Report"))
|
| 690 |
+
goal = meta.get("optimized_goal") or meta.get("goal") or ""
|
| 691 |
|
| 692 |
# Format result structure for reportlab builder
|
| 693 |
df = read_csv_robust(cleaned_csv)
|
|
|
|
| 699 |
"code": data.get("code", ""),
|
| 700 |
"output_dir": str(OUTPUTS_DIR / session_id),
|
| 701 |
"report_title": title,
|
| 702 |
+
"goal": goal,
|
| 703 |
}
|
| 704 |
|
| 705 |
try:
|
|
|
|
| 779 |
async def create_project(
|
| 780 |
name: str = Form(...),
|
| 781 |
report_title: str = Form(""),
|
| 782 |
+
goal: str = Form(""),
|
| 783 |
file: UploadFile = File(...)
|
| 784 |
):
|
| 785 |
"""Creates a new project context and uploads the dataset CSV."""
|
|
|
|
| 800 |
"id": project_id,
|
| 801 |
"name": name.strip(),
|
| 802 |
"report_title": report_title.strip() or f"{name.strip()} Executive Analysis",
|
| 803 |
+
"goal": goal.strip(),
|
| 804 |
+
"optimized_goal": "",
|
| 805 |
"filename": file.filename,
|
| 806 |
"size": file_path.stat().st_size,
|
| 807 |
"created_at": time.time() * 1000,
|
|
|
|
| 824 |
|
| 825 |
return meta
|
| 826 |
|
| 827 |
+
|
| 828 |
+
@app.post("/api/projects/{project_id}/tweak-relations")
|
| 829 |
+
async def tweak_relations(project_id: str, relations_text: str = Form(...)):
|
| 830 |
+
"""Saves tweaked relationships back to the results cache."""
|
| 831 |
+
session_dir = SESSIONS_DIR / project_id
|
| 832 |
+
if not session_dir.exists():
|
| 833 |
+
raise HTTPException(status_code=404, detail="Project not found")
|
| 834 |
+
|
| 835 |
+
results_path = session_dir / "results.json"
|
| 836 |
+
|
| 837 |
+
# Ensure results.json structure is present even if not analysed yet
|
| 838 |
+
res_data = {}
|
| 839 |
+
if results_path.exists():
|
| 840 |
+
try:
|
| 841 |
+
with open(results_path, "r", encoding="utf-8") as f:
|
| 842 |
+
res_data = json.load(f)
|
| 843 |
+
except Exception:
|
| 844 |
+
pass
|
| 845 |
+
|
| 846 |
+
res_data["relations"] = relations_text.strip()
|
| 847 |
+
|
| 848 |
+
with open(results_path, "w", encoding="utf-8") as f:
|
| 849 |
+
json.dump(res_data, f, indent=2)
|
| 850 |
+
|
| 851 |
+
return {"status": "success", "relations": res_data["relations"]}
|
| 852 |
+
|
| 853 |
@app.delete("/api/projects/{project_id}")
|
| 854 |
async def delete_project(project_id: str):
|
| 855 |
"""Deletes all session files, artifacts, and outputs of a project."""
|
|
|
|
| 866 |
return {"status": "deleted", "id": project_id}
|
| 867 |
|
| 868 |
|
| 869 |
+
@app.get("/api/projects/{project_id}/export-zip")
|
| 870 |
+
async def export_project_zip(project_id: str):
|
| 871 |
+
"""Exports the entire project (metadata, data files, results, and generated charts) as a ZIP file."""
|
| 872 |
+
session_dir = SESSIONS_DIR / project_id
|
| 873 |
+
output_dir = OUTPUTS_DIR / project_id
|
| 874 |
+
if not session_dir.exists():
|
| 875 |
+
raise HTTPException(status_code=404, detail="Project not found")
|
| 876 |
+
|
| 877 |
+
zip_buffer = BytesIO()
|
| 878 |
+
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
| 879 |
+
# Zip session files
|
| 880 |
+
for root, dirs, files in os.walk(session_dir):
|
| 881 |
+
for file in files:
|
| 882 |
+
file_path = Path(root) / file
|
| 883 |
+
arcname = Path("session") / file_path.relative_to(session_dir)
|
| 884 |
+
zip_file.write(file_path, arcname=arcname)
|
| 885 |
+
# Zip output files (charts)
|
| 886 |
+
if output_dir.exists():
|
| 887 |
+
for root, dirs, files in os.walk(output_dir):
|
| 888 |
+
for file in files:
|
| 889 |
+
file_path = Path(root) / file
|
| 890 |
+
arcname = Path("outputs") / file_path.relative_to(output_dir)
|
| 891 |
+
zip_file.write(file_path, arcname=arcname)
|
| 892 |
+
|
| 893 |
+
zip_buffer.seek(0)
|
| 894 |
+
meta = get_project_metadata(project_id)
|
| 895 |
+
safe_name = re.sub(r"[^a-zA-Z0-9_-]", "_", meta.get("name", "project").lower())
|
| 896 |
+
filename = f"{safe_name}_{project_id}.zip"
|
| 897 |
+
return StreamingResponse(
|
| 898 |
+
BytesIO_iterator(zip_buffer.getvalue()),
|
| 899 |
+
media_type="application/x-zip-compressed",
|
| 900 |
+
headers={"Content-Disposition": f"attachment; filename={filename}"}
|
| 901 |
+
)
|
| 902 |
+
|
| 903 |
+
|
| 904 |
+
@app.post("/api/projects/import-zip")
|
| 905 |
+
async def import_project_zip(file: UploadFile = File(...)):
|
| 906 |
+
"""Imports a project from a ZIP file and registers it in the system."""
|
| 907 |
+
zip_contents = await file.read()
|
| 908 |
+
zip_buffer = BytesIO(zip_contents)
|
| 909 |
+
|
| 910 |
+
project_id = uuid.uuid4().hex[:12]
|
| 911 |
+
temp_dir = DATA_DIR / "temp_import" / project_id
|
| 912 |
+
temp_dir.mkdir(parents=True, exist_ok=True)
|
| 913 |
+
|
| 914 |
+
target_project_id = project_id
|
| 915 |
+
session_dir = None
|
| 916 |
+
output_dir = None
|
| 917 |
+
try:
|
| 918 |
+
with zipfile.ZipFile(zip_buffer, "r") as zip_file:
|
| 919 |
+
zip_file.extractall(temp_dir)
|
| 920 |
+
|
| 921 |
+
# Verify metadata.json exists
|
| 922 |
+
meta_file = temp_dir / "session" / "metadata.json"
|
| 923 |
+
if not meta_file.exists():
|
| 924 |
+
raise HTTPException(status_code=400, detail="Invalid zip format: missing metadata.json")
|
| 925 |
+
|
| 926 |
+
with open(meta_file, "r", encoding="utf-8") as f:
|
| 927 |
+
meta = json.load(f)
|
| 928 |
+
|
| 929 |
+
orig_project_id = meta.get("id")
|
| 930 |
+
if orig_project_id:
|
| 931 |
+
target_project_id = orig_project_id
|
| 932 |
+
|
| 933 |
+
# Check if project conflicts. If so, generate new ID
|
| 934 |
+
session_dir = SESSIONS_DIR / target_project_id
|
| 935 |
+
if session_dir.exists():
|
| 936 |
+
target_project_id = uuid.uuid4().hex[:12]
|
| 937 |
+
session_dir = SESSIONS_DIR / target_project_id
|
| 938 |
+
meta["id"] = target_project_id
|
| 939 |
+
meta["name"] = f"{meta.get('name', 'Imported')} (Copy)"
|
| 940 |
+
|
| 941 |
+
output_dir = OUTPUTS_DIR / target_project_id
|
| 942 |
+
session_dir.mkdir(parents=True, exist_ok=True)
|
| 943 |
+
|
| 944 |
+
# Copy session files
|
| 945 |
+
for item in (temp_dir / "session").iterdir():
|
| 946 |
+
if item.is_file():
|
| 947 |
+
shutil.copy2(item, session_dir / item.name)
|
| 948 |
+
|
| 949 |
+
# Copy outputs
|
| 950 |
+
if (temp_dir / "outputs").exists():
|
| 951 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 952 |
+
for item in (temp_dir / "outputs").iterdir():
|
| 953 |
+
if item.is_file():
|
| 954 |
+
shutil.copy2(item, output_dir / item.name)
|
| 955 |
+
|
| 956 |
+
# Update metadata.json
|
| 957 |
+
meta["id"] = target_project_id
|
| 958 |
+
if meta.get("thumbnail"):
|
| 959 |
+
# Update thumbnail link with new project ID
|
| 960 |
+
thumb_parts = meta["thumbnail"].split("/")
|
| 961 |
+
if len(thumb_parts) >= 5:
|
| 962 |
+
thumb_parts[3] = target_project_id
|
| 963 |
+
meta["thumbnail"] = "/".join(thumb_parts)
|
| 964 |
+
|
| 965 |
+
with open(session_dir / "metadata.json", "w", encoding="utf-8") as f:
|
| 966 |
+
json.dump(meta, f, indent=2)
|
| 967 |
+
|
| 968 |
+
return meta
|
| 969 |
+
except Exception as e:
|
| 970 |
+
if session_dir and session_dir.exists():
|
| 971 |
+
shutil.rmtree(session_dir, ignore_errors=True)
|
| 972 |
+
if output_dir and output_dir.exists():
|
| 973 |
+
shutil.rmtree(output_dir, ignore_errors=True)
|
| 974 |
+
raise HTTPException(status_code=400, detail=f"Import failed: {str(e)}")
|
| 975 |
+
finally:
|
| 976 |
+
shutil.rmtree(temp_dir, ignore_errors=True)
|
| 977 |
+
|
| 978 |
+
|
| 979 |
+
|
| 980 |
+
@app.get("/api/projects/{project_id}/preview")
|
| 981 |
+
async def get_dynamic_preview(project_id: str):
|
| 982 |
+
"""Dynamically reads the latest state of the CSV and returns a 100-row preview, column names, shapes, and types."""
|
| 983 |
+
session_dir = SESSIONS_DIR / project_id
|
| 984 |
+
if not session_dir.exists():
|
| 985 |
+
raise HTTPException(status_code=404, detail="Project not found")
|
| 986 |
+
|
| 987 |
+
cleaned_csv = session_dir / "cleaned.csv"
|
| 988 |
+
original_csv = session_dir / "original_upload.csv"
|
| 989 |
+
csv_path = cleaned_csv if cleaned_csv.exists() else original_csv
|
| 990 |
+
|
| 991 |
+
if not csv_path.exists():
|
| 992 |
+
raise HTTPException(status_code=404, detail="CSV not found.")
|
| 993 |
+
|
| 994 |
+
try:
|
| 995 |
+
df = read_csv_robust(str(csv_path))
|
| 996 |
+
rows_count, cols_count = df.shape
|
| 997 |
+
preview = df.head(100).fillna("").to_dict(orient="records")
|
| 998 |
+
col_types = {col: str(dtype) for col, dtype in df.dtypes.items()}
|
| 999 |
+
columns = list(df.columns)
|
| 1000 |
+
|
| 1001 |
+
# Update cache in results.json if it exists
|
| 1002 |
+
results_path = session_dir / "results.json"
|
| 1003 |
+
if results_path.exists():
|
| 1004 |
+
try:
|
| 1005 |
+
with open(results_path, "r", encoding="utf-8") as f:
|
| 1006 |
+
res_data = json.load(f)
|
| 1007 |
+
res_data["preview"] = preview
|
| 1008 |
+
res_data["rows_count"] = rows_count
|
| 1009 |
+
res_data["cols_count"] = cols_count
|
| 1010 |
+
with open(results_path, "w", encoding="utf-8") as f:
|
| 1011 |
+
json.dump(res_data, f, indent=2)
|
| 1012 |
+
except Exception:
|
| 1013 |
+
pass
|
| 1014 |
+
|
| 1015 |
+
return {
|
| 1016 |
+
"columns": columns,
|
| 1017 |
+
"col_types": col_types,
|
| 1018 |
+
"rows_count": rows_count,
|
| 1019 |
+
"cols_count": cols_count,
|
| 1020 |
+
"preview": preview
|
| 1021 |
+
}
|
| 1022 |
+
except Exception as e:
|
| 1023 |
+
raise HTTPException(status_code=500, detail=f"Failed to load preview: {str(e)}")
|
| 1024 |
+
|
| 1025 |
+
|
| 1026 |
@app.get("/api/projects/{project_id}/download-csv")
|
| 1027 |
async def download_project_csv(project_id: str):
|
| 1028 |
"""Downloads the cleaned dataset CSV for the specified project."""
|
requirements.txt
CHANGED
|
@@ -25,8 +25,8 @@ Pillow
|
|
| 25 |
reportlab
|
| 26 |
|
| 27 |
# ── Pinned for Windows compatibility ─────────────────────────────────────────
|
| 28 |
-
# regex
|
| 29 |
-
regex
|
| 30 |
|
| 31 |
# NOTE: streamlit has been removed — the app now runs on FastAPI + vanilla web UI
|
| 32 |
# NOTE: tiktoken removed — requires Rust; not needed
|
|
|
|
| 25 |
reportlab
|
| 26 |
|
| 27 |
# ── Pinned for Windows compatibility ─────────────────────────────────────────
|
| 28 |
+
# regex has pre-built Windows wheels for Python 3.13
|
| 29 |
+
regex
|
| 30 |
|
| 31 |
# NOTE: streamlit has been removed — the app now runs on FastAPI + vanilla web UI
|
| 32 |
# NOTE: tiktoken removed — requires Rust; not needed
|
ui/copilot.py
CHANGED
|
@@ -113,7 +113,7 @@ def run_copilot_query(query: str, csv_path: str, output_dir_str: str) -> dict:
|
|
| 113 |
# 4. Build LLM prompt with full column context
|
| 114 |
prompt = textwrap.dedent(f"""
|
| 115 |
You are an expert AI Data Analyst. You have access to a CSV dataset at:
|
| 116 |
-
FILE_PATH = '{csv_path}'
|
| 117 |
|
| 118 |
=== DATASET SCHEMA ===
|
| 119 |
{column_context}
|
|
@@ -123,12 +123,17 @@ def run_copilot_query(query: str, csv_path: str, output_dir_str: str) -> dict:
|
|
| 123 |
|
| 124 |
INSTRUCTIONS:
|
| 125 |
1. Read the dataset: df = pd.read_csv(FILE_PATH)
|
| 126 |
-
2. Use ONLY the column names listed
|
| 127 |
-
3. Perform any required analysis, aggregation, or
|
| 128 |
-
4. Print a clear, formatted answer to stdout.
|
| 129 |
-
5. If the query asks
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
- Call `import matplotlib; matplotlib.use('Agg')` BEFORE importing pyplot.
|
| 131 |
-
- Generate a professional chart
|
|
|
|
| 132 |
- Call `plt.tight_layout()` then `plt.savefig(...)` then `plt.close()`.
|
| 133 |
|
| 134 |
Return ONLY valid Python code inside a ```python ... ``` block.
|
|
|
|
| 113 |
# 4. Build LLM prompt with full column context
|
| 114 |
prompt = textwrap.dedent(f"""
|
| 115 |
You are an expert AI Data Analyst. You have access to a CSV dataset at:
|
| 116 |
+
FILE_PATH = '{Path(csv_path).as_posix()}'
|
| 117 |
|
| 118 |
=== DATASET SCHEMA ===
|
| 119 |
{column_context}
|
|
|
|
| 123 |
|
| 124 |
INSTRUCTIONS:
|
| 125 |
1. Read the dataset: df = pd.read_csv(FILE_PATH)
|
| 126 |
+
2. Use ONLY the column names listed in the dataset schema (exact spelling, case-sensitive).
|
| 127 |
+
3. Perform any required analysis, aggregation, computation, or modifications.
|
| 128 |
+
4. Print a clear, formatted answer to stdout detailing the results or actions taken.
|
| 129 |
+
5. If the query asks to modify, clean, fix, rename, delete columns, drop rows, replace missing values, or update values in the dataset:
|
| 130 |
+
- Perform the operation on the DataFrame `df`.
|
| 131 |
+
- Save the modified DataFrame back to the CSV file at the end of the script: `df.to_csv(FILE_PATH, index=False)`.
|
| 132 |
+
- Print a confirmation message to stdout explaining exactly what dataset modifications were made.
|
| 133 |
+
6. If the query asks for a chart/plot/graph:
|
| 134 |
- Call `import matplotlib; matplotlib.use('Agg')` BEFORE importing pyplot.
|
| 135 |
+
- Generate a professional chart. Apply any specific styles, colors, layouts, grids, or palettes requested by the user. If they specify custom colors/configurations (e.g. "red-blue line chart" or "neon green theme"), make sure to use those styles/colors in your matplotlib/seaborn code.
|
| 136 |
+
- Save the chart to: '{plot_path.as_posix()}'
|
| 137 |
- Call `plt.tight_layout()` then `plt.savefig(...)` then `plt.close()`.
|
| 138 |
|
| 139 |
Return ONLY valid Python code inside a ```python ... ``` block.
|
ui/export.py
CHANGED
|
@@ -249,15 +249,20 @@ def export_pdf(result: dict, filename: str = "") -> bytes:
|
|
| 249 |
textColor=text_color, leftIndent=14, firstLineIndent=-10, spaceAfter=7)
|
| 250 |
|
| 251 |
# ── Cover page ────────────────────────────────────────────────────────────
|
| 252 |
-
|
| 253 |
-
|
|
|
|
| 254 |
|
| 255 |
cover_content = [
|
| 256 |
-
Paragraph("<b>
|
| 257 |
Paragraph("Autonomous Business Intelligence & Strategy Recommendations", subtitle_style),
|
| 258 |
-
Paragraph(f"Dataset: <b>{
|
| 259 |
-
Paragraph(f"Generated: {timestamp}", meta_style),
|
| 260 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
cover_table = Table([[cover_content]], colWidths=[504])
|
| 262 |
cover_table.setStyle(TableStyle([
|
| 263 |
("LINELEFT", (0, 0), (0, -1), 5, secondary_color),
|
|
@@ -268,6 +273,35 @@ def export_pdf(result: dict, filename: str = "") -> bytes:
|
|
| 268 |
story.append(cover_table)
|
| 269 |
story.append(Spacer(1, 14))
|
| 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
# ── Dataset Summary callout ───────────────────────────────────────────────
|
| 272 |
df = result.get("dataframe")
|
| 273 |
if df is not None and isinstance(df, pd.DataFrame):
|
|
@@ -298,7 +332,7 @@ def export_pdf(result: dict, filename: str = "") -> bytes:
|
|
| 298 |
story.append(Spacer(1, 12))
|
| 299 |
|
| 300 |
# ── Data Insights (min/max/mean/median + top categories) ─────────────
|
| 301 |
-
story.append(Paragraph("📊 Data Insights", h1_style))
|
| 302 |
story.append(Paragraph(
|
| 303 |
"Per-column statistical summary of the cleaned dataset.",
|
| 304 |
body_style,
|
|
@@ -320,6 +354,12 @@ def export_pdf(result: dict, filename: str = "") -> bytes:
|
|
| 320 |
story.append(Paragraph(f"✔ {_md_to_html(line)}", bullet_style))
|
| 321 |
story.append(Spacer(1, 10))
|
| 322 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
# ── Relations ─────────────────────────────────────────────────────────────
|
| 324 |
relations_text = result.get("relations", "").strip()
|
| 325 |
if relations_text:
|
|
@@ -331,10 +371,9 @@ def export_pdf(result: dict, filename: str = "") -> bytes:
|
|
| 331 |
story.append(Spacer(1, 10))
|
| 332 |
|
| 333 |
# ── Strategic Insights (McKinsey cards) ───────────────────────────────────
|
| 334 |
-
|
| 335 |
-
if insights_text:
|
| 336 |
story.append(Paragraph("💡 Strategic Business Insights", h1_style))
|
| 337 |
-
insight_items = re.split(r"\d+\.\s+",
|
| 338 |
insight_count = 0
|
| 339 |
|
| 340 |
for item in insight_items:
|
|
@@ -384,6 +423,35 @@ def export_pdf(result: dict, filename: str = "") -> bytes:
|
|
| 384 |
]))
|
| 385 |
story.append(KeepTogether([lbl_para, card_table, Spacer(1, 8)]))
|
| 386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
# ── Visualizations ───────────────────────────────────────────────────────
|
| 388 |
output_dir = result.get("output_dir", Path("outputs"))
|
| 389 |
png_files = list(Path(output_dir).glob("*.png"))
|
|
|
|
| 249 |
textColor=text_color, leftIndent=14, firstLineIndent=-10, spaceAfter=7)
|
| 250 |
|
| 251 |
# ── Cover page ────────────────────────────────────────────────────────────
|
| 252 |
+
report_title = result.get("report_title") or filename or "Data Analysis Report"
|
| 253 |
+
report_goal = result.get("goal") or ""
|
| 254 |
+
timestamp = datetime.now().strftime("%B %d, %Y at %I:%M %p")
|
| 255 |
|
| 256 |
cover_content = [
|
| 257 |
+
Paragraph(f"<b>{report_title.upper()}</b>", title_style),
|
| 258 |
Paragraph("Autonomous Business Intelligence & Strategy Recommendations", subtitle_style),
|
| 259 |
+
Paragraph(f"Dataset: <b>{filename or 'dataset.csv'}</b>", meta_style),
|
| 260 |
+
Paragraph(f"Generated: {timestamp} (Live Analysis)", meta_style),
|
| 261 |
]
|
| 262 |
+
if report_goal:
|
| 263 |
+
cover_content.append(Spacer(1, 6))
|
| 264 |
+
cover_content.append(Paragraph(f"<b>Project Goal / Objective:</b> {report_goal}", ParagraphStyle("GoalStyle", parent=body_style, fontName="Helvetica", fontSize=9.5, textColor=muted_color)))
|
| 265 |
+
|
| 266 |
cover_table = Table([[cover_content]], colWidths=[504])
|
| 267 |
cover_table.setStyle(TableStyle([
|
| 268 |
("LINELEFT", (0, 0), (0, -1), 5, secondary_color),
|
|
|
|
| 273 |
story.append(cover_table)
|
| 274 |
story.append(Spacer(1, 14))
|
| 275 |
|
| 276 |
+
# Parse structured sections from insights text
|
| 277 |
+
raw_insights = result.get("insights", "").strip()
|
| 278 |
+
objectives_text = ""
|
| 279 |
+
stats_text = ""
|
| 280 |
+
strategic_text = ""
|
| 281 |
+
warnings_text = ""
|
| 282 |
+
|
| 283 |
+
if raw_insights:
|
| 284 |
+
sections = re.split(r"###\s+", raw_insights)
|
| 285 |
+
for sec in sections:
|
| 286 |
+
lines = sec.split("\n")
|
| 287 |
+
if not lines or not lines[0].strip():
|
| 288 |
+
continue
|
| 289 |
+
header = lines[0].strip().lower()
|
| 290 |
+
content = "\n".join(lines[1:]).strip()
|
| 291 |
+
|
| 292 |
+
if "objective" in header or "goal" in header:
|
| 293 |
+
objectives_text = content
|
| 294 |
+
elif "stat" in header:
|
| 295 |
+
stats_text = content
|
| 296 |
+
elif "insight" in header:
|
| 297 |
+
strategic_text = content
|
| 298 |
+
elif "warning" in header or "alert" in header:
|
| 299 |
+
warnings_text = content
|
| 300 |
+
|
| 301 |
+
# Fallback if no sections parsed
|
| 302 |
+
if not strategic_text and not objectives_text:
|
| 303 |
+
strategic_text = raw_insights
|
| 304 |
+
|
| 305 |
# ── Dataset Summary callout ───────────────────────────────────────────────
|
| 306 |
df = result.get("dataframe")
|
| 307 |
if df is not None and isinstance(df, pd.DataFrame):
|
|
|
|
| 332 |
story.append(Spacer(1, 12))
|
| 333 |
|
| 334 |
# ── Data Insights (min/max/mean/median + top categories) ─────────────
|
| 335 |
+
story.append(Paragraph("📊 Data Insights Summary", h1_style))
|
| 336 |
story.append(Paragraph(
|
| 337 |
"Per-column statistical summary of the cleaned dataset.",
|
| 338 |
body_style,
|
|
|
|
| 354 |
story.append(Paragraph(f"✔ {_md_to_html(line)}", bullet_style))
|
| 355 |
story.append(Spacer(1, 10))
|
| 356 |
|
| 357 |
+
# ── Objectives Section (if parsed) ────────────────────────────────────────
|
| 358 |
+
if objectives_text:
|
| 359 |
+
story.append(Paragraph("🎯 Project Objectives & Scope", h1_style))
|
| 360 |
+
story.append(Paragraph(objectives_text.replace("\n", "<br/>"), body_style))
|
| 361 |
+
story.append(Spacer(1, 10))
|
| 362 |
+
|
| 363 |
# ── Relations ─────────────────────────────────────────────────────────────
|
| 364 |
relations_text = result.get("relations", "").strip()
|
| 365 |
if relations_text:
|
|
|
|
| 371 |
story.append(Spacer(1, 10))
|
| 372 |
|
| 373 |
# ── Strategic Insights (McKinsey cards) ───────────────────────────────────
|
| 374 |
+
if strategic_text:
|
|
|
|
| 375 |
story.append(Paragraph("💡 Strategic Business Insights", h1_style))
|
| 376 |
+
insight_items = re.split(r"\d+\.\s+", strategic_text)
|
| 377 |
insight_count = 0
|
| 378 |
|
| 379 |
for item in insight_items:
|
|
|
|
| 423 |
]))
|
| 424 |
story.append(KeepTogether([lbl_para, card_table, Spacer(1, 8)]))
|
| 425 |
|
| 426 |
+
# ── Warnings & Alerts ─────────────────────────────────────────────────────
|
| 427 |
+
if warnings_text and not "no warnings" in warnings_text.lower() and not "none" in warnings_text.lower():
|
| 428 |
+
story.append(Paragraph("⚠️ Business Risks & Critical Alerts", h1_style))
|
| 429 |
+
warning_content = [
|
| 430 |
+
Paragraph(warnings_text.replace("\n", "<br/>"), ParagraphStyle("WarnStyle", parent=body_style, fontSize=9.5, textColor=colors.HexColor("#991b1b")))
|
| 431 |
+
]
|
| 432 |
+
warning_table = Table([[warning_content]], colWidths=[504])
|
| 433 |
+
warning_table.setStyle(TableStyle([
|
| 434 |
+
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#fef2f2")),
|
| 435 |
+
("LINELEFT", (0, 0), (0, -1), 4, colors.HexColor("#f43f5e")),
|
| 436 |
+
("LEFTPADDING", (0, 0), (-1, -1), 12),
|
| 437 |
+
("RIGHTPADDING", (0, 0), (-1, -1), 12),
|
| 438 |
+
("TOPPADDING", (0, 0), (-1, -1), 8),
|
| 439 |
+
("BOTTOMPADDING", (0, 0), (-1, -1), 8),
|
| 440 |
+
("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#fee2e2")),
|
| 441 |
+
]))
|
| 442 |
+
story.append(KeepTogether([warning_table, Spacer(1, 10)]))
|
| 443 |
+
|
| 444 |
+
# ── Conclusion ────────────────────────────────────────────────────────────
|
| 445 |
+
story.append(Paragraph("🏁 Executive Conclusion & Next Steps", h1_style))
|
| 446 |
+
conclusion_text = (
|
| 447 |
+
"The automated data pipeline has successfully validated, cleaned, and evaluated the dataset "
|
| 448 |
+
"under the specified project guidelines. To maximize return on these insights, management is advised "
|
| 449 |
+
"to prioritize the Actionable Strategy recommendations outlined in the insights section, address the warnings "
|
| 450 |
+
"disclosed, and leverage the visual intelligence charts for stakeholder presentations."
|
| 451 |
+
)
|
| 452 |
+
story.append(Paragraph(conclusion_text, body_style))
|
| 453 |
+
story.append(Spacer(1, 10))
|
| 454 |
+
|
| 455 |
# ── Visualizations ───────────────────────────────────────────────────────
|
| 456 |
output_dir = result.get("output_dir", Path("outputs"))
|
| 457 |
png_files = list(Path(output_dir).glob("*.png"))
|
web/app.js
CHANGED
|
@@ -17,6 +17,8 @@
|
|
| 17 |
|
| 18 |
'use strict';
|
| 19 |
|
|
|
|
|
|
|
| 20 |
// ────────────────────────────────────────────────────────────────────────────
|
| 21 |
// Model catalogue (mirrors the sidebar in the old Streamlit app)
|
| 22 |
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -220,6 +222,30 @@ const els = {
|
|
| 220 |
customDialogConfirmBtn: $('customDialogConfirmBtn'),
|
| 221 |
closeCustomDialogBtn: $('closeCustomDialogBtn'),
|
| 222 |
stagePovPanel: $('stagePovPanel'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
};
|
| 224 |
|
| 225 |
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -827,7 +853,7 @@ function renderProjectsList() {
|
|
| 827 |
<span class="db-project-status-badge ${p.status}">${p.status}</span>
|
| 828 |
</div>
|
| 829 |
<div class="db-project-preview-wrap">
|
| 830 |
-
<img src="${p.thumbnail ||
|
| 831 |
<div class="db-project-preview-overlay">
|
| 832 |
<div class="db-project-folder-icon ${p.status}">
|
| 833 |
<svg viewBox="0 0 24 24" class="folder-svg" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -860,6 +886,51 @@ function renderProjectsList() {
|
|
| 860 |
}
|
| 861 |
}
|
| 862 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
async function switchToProject(p) {
|
| 864 |
state.activeProject = p;
|
| 865 |
renderProjectsList();
|
|
@@ -867,14 +938,30 @@ async function switchToProject(p) {
|
|
| 867 |
setStatus('● Loading…', 'running');
|
| 868 |
|
| 869 |
if (p.status === 'completed') {
|
|
|
|
|
|
|
|
|
|
| 870 |
await loadResults(p.id);
|
| 871 |
} else if (p.status === 'running') {
|
|
|
|
|
|
|
|
|
|
| 872 |
showScreen('running');
|
| 873 |
els.runningTitle.textContent = `Analysing "${p.name}"…`;
|
| 874 |
setStatus('● Running', 'running');
|
| 875 |
startSSEStream(p.id);
|
| 876 |
} else {
|
| 877 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
setStatus('● Idle', 'idle');
|
| 879 |
}
|
| 880 |
}
|
|
@@ -909,6 +996,49 @@ function resetWizardState() {
|
|
| 909 |
if (els.uploadedFileActions) els.uploadedFileActions.classList.add('hidden');
|
| 910 |
}
|
| 911 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 912 |
// Wire wizard events
|
| 913 |
if (els.startWizardCard) {
|
| 914 |
els.startWizardCard.addEventListener('click', () => {
|
|
@@ -934,11 +1064,14 @@ if (els.wizardNextBtn) {
|
|
| 934 |
return;
|
| 935 |
}
|
| 936 |
state.newProjectName = name;
|
|
|
|
|
|
|
|
|
|
| 937 |
if (els.wizardUploadTitle) {
|
| 938 |
els.wizardUploadTitle.innerHTML = `Upload CSV for <strong>${escHtml(name)}</strong>`;
|
| 939 |
}
|
| 940 |
if (els.reportTitle) {
|
| 941 |
-
els.reportTitle.value = `${name} Executive Analysis`;
|
| 942 |
}
|
| 943 |
if (els.wizardStep1) els.wizardStep1.classList.remove('active');
|
| 944 |
if (els.wizardStep2) els.wizardStep2.classList.add('active');
|
|
@@ -1010,6 +1143,8 @@ async function handleFileSelected(file) {
|
|
| 1010 |
// Create project immediately via projects endpoint
|
| 1011 |
const fd = new FormData();
|
| 1012 |
fd.append('name', state.newProjectName || file.name.replace(/\.csv$/i, ''));
|
|
|
|
|
|
|
| 1013 |
fd.append('file', file);
|
| 1014 |
|
| 1015 |
try {
|
|
@@ -1021,6 +1156,14 @@ async function handleFileSelected(file) {
|
|
| 1021 |
els.uploadedFileMeta.classList.remove('hidden');
|
| 1022 |
els.uploadedFileActions.classList.remove('hidden');
|
| 1023 |
toast(`Project created: ${state.newProjectName || data.name}`, 'success');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1024 |
} catch (e) {
|
| 1025 |
toast('Project creation failed: ' + e.message, 'error');
|
| 1026 |
}
|
|
@@ -1311,19 +1454,21 @@ function renderStagePov(stage) {
|
|
| 1311 |
</div>
|
| 1312 |
<div class="pov-content">
|
| 1313 |
<svg class="pov-chords-svg" viewBox="0 0 160 100">
|
| 1314 |
-
<
|
| 1315 |
-
|
| 1316 |
-
|
| 1317 |
-
|
| 1318 |
-
|
| 1319 |
-
|
| 1320 |
-
|
| 1321 |
-
|
| 1322 |
-
|
| 1323 |
-
|
| 1324 |
-
|
| 1325 |
-
|
| 1326 |
-
|
|
|
|
|
|
|
| 1327 |
</svg>
|
| 1328 |
</div>`;
|
| 1329 |
|
|
@@ -1489,6 +1634,10 @@ async function loadResults(sessionId) {
|
|
| 1489 |
if (!res.ok) throw new Error('Results not ready');
|
| 1490 |
const data = await res.json();
|
| 1491 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1492 |
if (data.error) {
|
| 1493 |
setStatus('● Error', 'error');
|
| 1494 |
toast('Analysis failed: ' + data.error, 'error');
|
|
@@ -1496,6 +1645,8 @@ async function loadResults(sessionId) {
|
|
| 1496 |
return;
|
| 1497 |
}
|
| 1498 |
|
|
|
|
|
|
|
| 1499 |
state.results = data;
|
| 1500 |
|
| 1501 |
// Extract column info from preview
|
|
@@ -1509,6 +1660,11 @@ async function loadResults(sessionId) {
|
|
| 1509 |
setStatus('● Complete', 'complete');
|
| 1510 |
|
| 1511 |
renderDashboard(data, sessionId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1512 |
showScreen('results');
|
| 1513 |
} catch (e) {
|
| 1514 |
// Results not ready yet, retry
|
|
@@ -1526,7 +1682,17 @@ function renderDashboard(data, sessionId) {
|
|
| 1526 |
renderVizCode(data.code || '');
|
| 1527 |
setupExport(sessionId);
|
| 1528 |
resetChat();
|
| 1529 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1530 |
}
|
| 1531 |
|
| 1532 |
// ── Stats row ────────────────────────────────────────────────────────────────
|
|
@@ -1566,20 +1732,22 @@ function renderPreview(rows) {
|
|
| 1566 |
}
|
| 1567 |
|
| 1568 |
// Toggle preview minimize
|
| 1569 |
-
els.togglePreviewBtn
|
| 1570 |
-
|
| 1571 |
-
|
| 1572 |
-
|
| 1573 |
-
|
| 1574 |
-
|
| 1575 |
-
|
| 1576 |
-
|
| 1577 |
-
|
| 1578 |
-
|
| 1579 |
-
|
| 1580 |
-
|
| 1581 |
-
|
| 1582 |
-
}
|
|
|
|
|
|
|
| 1583 |
|
| 1584 |
// ── Cleaning ─────────────────────────────────────────────────────────────────
|
| 1585 |
function renderCleaning(text) {
|
|
@@ -1596,66 +1764,180 @@ function renderCleaning(text) {
|
|
| 1596 |
).join('');
|
| 1597 |
}
|
| 1598 |
|
|
|
|
| 1599 |
// ── Relations ─────────────────────────────────────────────────────────────────
|
| 1600 |
function renderRelations(text) {
|
| 1601 |
const lines = text.split('\n').map(l => l.trim()).filter(Boolean);
|
| 1602 |
-
|
| 1603 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1604 |
return;
|
| 1605 |
}
|
| 1606 |
|
| 1607 |
-
|
| 1608 |
-
|
| 1609 |
-
const
|
| 1610 |
-
|
| 1611 |
-
|
| 1612 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1613 |
|
| 1614 |
-
|
| 1615 |
-
|
| 1616 |
-
|
| 1617 |
-
|
| 1618 |
-
|
| 1619 |
-
|
| 1620 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1621 |
}
|
| 1622 |
|
| 1623 |
-
|
| 1624 |
-
const chartTypeIcon = { scatter: '⬥', bar: '▬', line: '∿', box: '☐', histogram: '▤', hist: '▤', heatmap: '▦' };
|
| 1625 |
|
| 1626 |
-
els.
|
| 1627 |
-
const
|
| 1628 |
-
|
| 1629 |
-
|
| 1630 |
-
|
|
|
|
|
|
|
| 1631 |
|
| 1632 |
-
if (
|
| 1633 |
-
|
| 1634 |
-
|
| 1635 |
-
|
| 1636 |
-
|
| 1637 |
-
|
| 1638 |
-
|
| 1639 |
-
|
| 1640 |
|
| 1641 |
-
|
| 1642 |
-
|
| 1643 |
-
|
| 1644 |
-
|
| 1645 |
-
|
| 1646 |
-
|
| 1647 |
-
|
| 1648 |
-
|
| 1649 |
-
|
| 1650 |
-
|
| 1651 |
-
|
| 1652 |
-
|
| 1653 |
-
|
| 1654 |
-
|
| 1655 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1656 |
}
|
| 1657 |
-
|
| 1658 |
-
|
|
|
|
| 1659 |
}
|
| 1660 |
|
| 1661 |
// ── Insights Markdown Formatter ──────────────────────────────────────────────
|
|
@@ -1685,64 +1967,157 @@ function formatInlineMarkdown(content) {
|
|
| 1685 |
// ── Insights ─────────────────────────────────────────────────────────────────
|
| 1686 |
function renderInsights(text) {
|
| 1687 |
if (!text || !text.trim() || text.toLowerCase().includes('skipped')) {
|
| 1688 |
-
els.insightsContent.innerHTML = '<p style="color:var(--text-muted)">No insights generated.</p>';
|
| 1689 |
return;
|
| 1690 |
}
|
| 1691 |
|
| 1692 |
-
//
|
| 1693 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1694 |
|
| 1695 |
-
//
|
| 1696 |
-
|
| 1697 |
-
|
| 1698 |
-
items = numberedSplit.map(s => s.replace(/^\s*(?:\*{0,2}\d+[.):]\*{0,2}|#{1,3})\s*/, '').trim()).filter(Boolean);
|
| 1699 |
-
} else {
|
| 1700 |
-
// Fallback: split on double newlines
|
| 1701 |
-
items = text.split(/\n{2,}/).map(s => s.trim()).filter(Boolean);
|
| 1702 |
}
|
| 1703 |
|
| 1704 |
-
|
| 1705 |
-
|
| 1706 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1707 |
}
|
| 1708 |
|
| 1709 |
-
|
| 1710 |
-
|
| 1711 |
-
|
| 1712 |
-
|
| 1713 |
-
|
| 1714 |
-
|
| 1715 |
-
|
| 1716 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1717 |
|
| 1718 |
-
|
| 1719 |
-
|
| 1720 |
-
|
| 1721 |
-
|
| 1722 |
-
|
| 1723 |
-
|
| 1724 |
-
|
| 1725 |
-
|
| 1726 |
-
|
| 1727 |
-
|
| 1728 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1729 |
}
|
| 1730 |
-
|
| 1731 |
-
|
| 1732 |
-
|
| 1733 |
-
|
| 1734 |
-
|
| 1735 |
-
|
| 1736 |
-
|
| 1737 |
-
|
| 1738 |
-
|
| 1739 |
-
return `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1740 |
}).join('');
|
|
|
|
| 1741 |
|
| 1742 |
-
|
| 1743 |
-
|
| 1744 |
-
|
| 1745 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1746 |
}).join('');
|
| 1747 |
}
|
| 1748 |
|
|
@@ -1835,6 +2210,11 @@ function setupExport(sessionId) {
|
|
| 1835 |
els.exportPdfBtn.onclick = () => {
|
| 1836 |
window.location = `/api/export-pdf?session_id=${sessionId}`;
|
| 1837 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1838 |
els.downloadCsvBtn.onclick = () => {
|
| 1839 |
window.location = `/api/projects/${sessionId}/download-csv`;
|
| 1840 |
};
|
|
@@ -1954,6 +2334,12 @@ async function sendChat() {
|
|
| 1954 |
const data = await res.json();
|
| 1955 |
const text = data.text && data.text.trim() ? data.text : 'No response returned.';
|
| 1956 |
appendChatMsg('assistant', text, data.plot_url || null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1957 |
} catch (e) {
|
| 1958 |
removeTypingIndicator();
|
| 1959 |
appendChatMsg('assistant', '⚠ Network error: ' + e.message);
|
|
@@ -2056,6 +2442,55 @@ function escHtml(str) {
|
|
| 2056 |
showScreen('landing');
|
| 2057 |
setStatus('● Idle', 'idle');
|
| 2058 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2059 |
// Check if a running session exists on page load
|
| 2060 |
// (handles F5 refresh during analysis)
|
| 2061 |
setTimeout(async () => {
|
|
|
|
| 17 |
|
| 18 |
'use strict';
|
| 19 |
|
| 20 |
+
const DEFAULT_THUMBNAIL_SVG = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMjAiIGhlaWdodD0iMTgwIiB2aWV3Qm94PSIwIDAgMzIwIDE4MCI+PHJlY3Qgd2lkdGg9IjMyMCIgaGVpZ2h0PSIxODAiIGZpbGw9IiMxODE4MWIiLz48Y2lyY2xlIGN4PSIxNjAiIGN5PSI5MCIgcj0iNDAiIGZpbGw9IiM3YzNhZWQiIGZpbGwtb3BhY2l0eT0iMC4xIi8+PHBhdGggZD0iTTE0MCAxMDAgTDE2MCA4MCBMMTgwIDEwMCIgc3Ryb2tlPSIjN2MzYWVkIiBzdHJva2Utd2lkdGg9IjMiIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0xMzAgMTE1IEwxOTAgMTE1IiBzdHJva2U9IiMyMmQzZWUiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHRleHQgeD0iMTYwIiB5PSIxNTAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBmaWxsPSIjYTFhMWFhIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5BR0VOVElDIExPR1M8L3RleHQ+PC9zdmc+';
|
| 21 |
+
|
| 22 |
// ────────────────────────────────────────────────────────────────────────────
|
| 23 |
// Model catalogue (mirrors the sidebar in the old Streamlit app)
|
| 24 |
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
| 222 |
customDialogConfirmBtn: $('customDialogConfirmBtn'),
|
| 223 |
closeCustomDialogBtn: $('closeCustomDialogBtn'),
|
| 224 |
stagePovPanel: $('stagePovPanel'),
|
| 225 |
+
wizardReportTitle: $('wizardReportTitle'),
|
| 226 |
+
wizardProjectGoal: $('wizardProjectGoal'),
|
| 227 |
+
importProjectCard: $('importProjectCard'),
|
| 228 |
+
importZipFileInput: $('importZipFileInput'),
|
| 229 |
+
exportZipBtn: $('exportZipBtn'),
|
| 230 |
+
btnSectionChat: $('btnSectionChat'),
|
| 231 |
+
btnSectionAgentic: $('btnSectionAgentic'),
|
| 232 |
+
areaChat: $('areaChat'),
|
| 233 |
+
areaAgentic: $('areaAgentic'),
|
| 234 |
+
btnRenameColQuick: $('btnRenameColQuick'),
|
| 235 |
+
btnDeleteColQuick: $('btnDeleteColQuick'),
|
| 236 |
+
chatPreviewDims: $('chatPreviewDims'),
|
| 237 |
+
relationModal: $('relationModal'),
|
| 238 |
+
relationModalXSelect: $('relationModalXSelect'),
|
| 239 |
+
relationModalYSelect: $('relationModalYSelect'),
|
| 240 |
+
relationModalTypeSelect: $('relationModalTypeSelect'),
|
| 241 |
+
relationModalDetails: $('relationModalDetails'),
|
| 242 |
+
relationModalConfirmBtn: $('relationModalConfirmBtn'),
|
| 243 |
+
relationModalCancelBtn: $('relationModalCancelBtn'),
|
| 244 |
+
closeRelationModalBtn: $('closeRelationModalBtn'),
|
| 245 |
+
agenticPlaceholder: $('agenticPlaceholder'),
|
| 246 |
+
agenticTabsBar: $('agenticTabsBar'),
|
| 247 |
+
agenticTabPanels: $('agenticTabPanels'),
|
| 248 |
+
btnRunAgenticPipeline: $('btnRunAgenticPipeline'),
|
| 249 |
};
|
| 250 |
|
| 251 |
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
| 853 |
<span class="db-project-status-badge ${p.status}">${p.status}</span>
|
| 854 |
</div>
|
| 855 |
<div class="db-project-preview-wrap">
|
| 856 |
+
<img src="${p.thumbnail || DEFAULT_THUMBNAIL_SVG}" class="db-project-preview-img" alt="Project Preview" onerror="this.onerror=null; this.src=DEFAULT_THUMBNAIL_SVG;" />
|
| 857 |
<div class="db-project-preview-overlay">
|
| 858 |
<div class="db-project-folder-icon ${p.status}">
|
| 859 |
<svg viewBox="0 0 24 24" class="folder-svg" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
| 886 |
}
|
| 887 |
}
|
| 888 |
|
| 889 |
+
async function refreshPreviewData(sessionId) {
|
| 890 |
+
try {
|
| 891 |
+
const res = await fetch(`/api/projects/${sessionId}/preview`);
|
| 892 |
+
if (res.ok) {
|
| 893 |
+
const data = await res.json();
|
| 894 |
+
state.columns = data.columns || [];
|
| 895 |
+
state.colTypes = data.col_types || {};
|
| 896 |
+
renderPreview(data.preview || []);
|
| 897 |
+
if (els.chatPreviewDims) {
|
| 898 |
+
els.chatPreviewDims.textContent = `(${data.rows_count?.toLocaleString() || 0} rows × ${data.cols_count || 0} columns)`;
|
| 899 |
+
}
|
| 900 |
+
}
|
| 901 |
+
} catch (e) {
|
| 902 |
+
console.error('Failed to load dynamic preview:', e);
|
| 903 |
+
}
|
| 904 |
+
}
|
| 905 |
+
|
| 906 |
+
function switchSection(sec) {
|
| 907 |
+
if (sec === 'chat') {
|
| 908 |
+
els.btnSectionChat.classList.add('active');
|
| 909 |
+
els.btnSectionAgentic.classList.remove('active');
|
| 910 |
+
els.areaChat.classList.remove('hidden');
|
| 911 |
+
els.areaAgentic.classList.add('hidden');
|
| 912 |
+
|
| 913 |
+
els.btnSectionChat.style.background = 'var(--violet)';
|
| 914 |
+
els.btnSectionChat.style.color = '#fff';
|
| 915 |
+
els.btnSectionAgentic.style.background = 'transparent';
|
| 916 |
+
els.btnSectionAgentic.style.color = 'var(--text-secondary)';
|
| 917 |
+
} else {
|
| 918 |
+
els.btnSectionChat.classList.remove('active');
|
| 919 |
+
els.btnSectionAgentic.classList.add('active');
|
| 920 |
+
els.areaChat.classList.add('hidden');
|
| 921 |
+
els.areaAgentic.classList.remove('hidden');
|
| 922 |
+
|
| 923 |
+
els.btnSectionAgentic.style.background = 'var(--violet)';
|
| 924 |
+
els.btnSectionAgentic.style.color = '#fff';
|
| 925 |
+
els.btnSectionChat.style.background = 'transparent';
|
| 926 |
+
els.btnSectionChat.style.color = 'var(--text-secondary)';
|
| 927 |
+
}
|
| 928 |
+
// Resize Plotly charts when switching to agentic area
|
| 929 |
+
if (sec === 'agentic') {
|
| 930 |
+
setTimeout(() => Plotly.Plots?.resize?.(), 100);
|
| 931 |
+
}
|
| 932 |
+
}
|
| 933 |
+
|
| 934 |
async function switchToProject(p) {
|
| 935 |
state.activeProject = p;
|
| 936 |
renderProjectsList();
|
|
|
|
| 938 |
setStatus('● Loading…', 'running');
|
| 939 |
|
| 940 |
if (p.status === 'completed') {
|
| 941 |
+
if (els.agenticPlaceholder) els.agenticPlaceholder.classList.add('hidden');
|
| 942 |
+
if (els.agenticTabsBar) els.agenticTabsBar.classList.remove('hidden');
|
| 943 |
+
if (els.agenticTabPanels) els.agenticTabPanels.classList.remove('hidden');
|
| 944 |
await loadResults(p.id);
|
| 945 |
} else if (p.status === 'running') {
|
| 946 |
+
if (els.agenticPlaceholder) els.agenticPlaceholder.classList.add('hidden');
|
| 947 |
+
if (els.agenticTabsBar) els.agenticTabsBar.classList.remove('hidden');
|
| 948 |
+
if (els.agenticTabPanels) els.agenticTabPanels.classList.remove('hidden');
|
| 949 |
showScreen('running');
|
| 950 |
els.runningTitle.textContent = `Analysing "${p.name}"…`;
|
| 951 |
setStatus('● Running', 'running');
|
| 952 |
startSSEStream(p.id);
|
| 953 |
} else {
|
| 954 |
+
// Idle state
|
| 955 |
+
if (els.agenticPlaceholder) els.agenticPlaceholder.classList.remove('hidden');
|
| 956 |
+
if (els.agenticTabsBar) els.agenticTabsBar.classList.add('hidden');
|
| 957 |
+
if (els.agenticTabPanels) els.agenticTabPanels.classList.add('hidden');
|
| 958 |
+
|
| 959 |
+
state.results = null;
|
| 960 |
+
await refreshPreviewData(p.id);
|
| 961 |
+
setupExport(p.id);
|
| 962 |
+
resetChat();
|
| 963 |
+
switchSection('chat');
|
| 964 |
+
showScreen('results');
|
| 965 |
setStatus('● Idle', 'idle');
|
| 966 |
}
|
| 967 |
}
|
|
|
|
| 996 |
if (els.uploadedFileActions) els.uploadedFileActions.classList.add('hidden');
|
| 997 |
}
|
| 998 |
|
| 999 |
+
// Import Project ZIP flow
|
| 1000 |
+
if (els.importProjectCard && els.importZipFileInput) {
|
| 1001 |
+
els.importProjectCard.addEventListener('click', () => {
|
| 1002 |
+
els.importZipFileInput.click();
|
| 1003 |
+
});
|
| 1004 |
+
|
| 1005 |
+
els.importZipFileInput.addEventListener('change', async () => {
|
| 1006 |
+
const file = els.importZipFileInput.files[0];
|
| 1007 |
+
if (!file) return;
|
| 1008 |
+
if (!file.name.endsWith('.zip')) {
|
| 1009 |
+
toast('Only ZIP files are supported.', 'error');
|
| 1010 |
+
return;
|
| 1011 |
+
}
|
| 1012 |
+
|
| 1013 |
+
setStatus('● Importing…', 'running');
|
| 1014 |
+
const fd = new FormData();
|
| 1015 |
+
fd.append('file', file);
|
| 1016 |
+
|
| 1017 |
+
try {
|
| 1018 |
+
const res = await fetch('/api/projects/import-zip', { method: 'POST', body: fd });
|
| 1019 |
+
if (!res.ok) throw new Error((await res.json()).detail || 'Import failed');
|
| 1020 |
+
const data = await res.json();
|
| 1021 |
+
toast(`Project imported successfully: ${data.name}`, 'success');
|
| 1022 |
+
|
| 1023 |
+
// Reset input
|
| 1024 |
+
els.importZipFileInput.value = '';
|
| 1025 |
+
|
| 1026 |
+
// Reload projects and open the imported project
|
| 1027 |
+
await loadProjects();
|
| 1028 |
+
const importedProj = state.projects.find(p => p.id === data.id);
|
| 1029 |
+
if (importedProj) {
|
| 1030 |
+
switchToProject(importedProj);
|
| 1031 |
+
} else {
|
| 1032 |
+
goToDashboardHome();
|
| 1033 |
+
}
|
| 1034 |
+
} catch (e) {
|
| 1035 |
+
toast('Import failed: ' + e.message, 'error');
|
| 1036 |
+
setStatus('● Idle', 'idle');
|
| 1037 |
+
els.importZipFileInput.value = '';
|
| 1038 |
+
}
|
| 1039 |
+
});
|
| 1040 |
+
}
|
| 1041 |
+
|
| 1042 |
// Wire wizard events
|
| 1043 |
if (els.startWizardCard) {
|
| 1044 |
els.startWizardCard.addEventListener('click', () => {
|
|
|
|
| 1064 |
return;
|
| 1065 |
}
|
| 1066 |
state.newProjectName = name;
|
| 1067 |
+
state.newProjectReportTitle = els.wizardReportTitle ? els.wizardReportTitle.value.trim() : '';
|
| 1068 |
+
state.newProjectGoal = els.wizardProjectGoal ? els.wizardProjectGoal.value.trim() : '';
|
| 1069 |
+
|
| 1070 |
if (els.wizardUploadTitle) {
|
| 1071 |
els.wizardUploadTitle.innerHTML = `Upload CSV for <strong>${escHtml(name)}</strong>`;
|
| 1072 |
}
|
| 1073 |
if (els.reportTitle) {
|
| 1074 |
+
els.reportTitle.value = state.newProjectReportTitle || `${name} Executive Analysis`;
|
| 1075 |
}
|
| 1076 |
if (els.wizardStep1) els.wizardStep1.classList.remove('active');
|
| 1077 |
if (els.wizardStep2) els.wizardStep2.classList.add('active');
|
|
|
|
| 1143 |
// Create project immediately via projects endpoint
|
| 1144 |
const fd = new FormData();
|
| 1145 |
fd.append('name', state.newProjectName || file.name.replace(/\.csv$/i, ''));
|
| 1146 |
+
fd.append('report_title', state.newProjectReportTitle || '');
|
| 1147 |
+
fd.append('goal', state.newProjectGoal || '');
|
| 1148 |
fd.append('file', file);
|
| 1149 |
|
| 1150 |
try {
|
|
|
|
| 1156 |
els.uploadedFileMeta.classList.remove('hidden');
|
| 1157 |
els.uploadedFileActions.classList.remove('hidden');
|
| 1158 |
toast(`Project created: ${state.newProjectName || data.name}`, 'success');
|
| 1159 |
+
|
| 1160 |
+
// Auto-enter project workspace
|
| 1161 |
+
resetWizardState();
|
| 1162 |
+
await loadProjects();
|
| 1163 |
+
const newProj = state.projects.find(p => p.id === data.id);
|
| 1164 |
+
if (newProj) {
|
| 1165 |
+
switchToProject(newProj);
|
| 1166 |
+
}
|
| 1167 |
} catch (e) {
|
| 1168 |
toast('Project creation failed: ' + e.message, 'error');
|
| 1169 |
}
|
|
|
|
| 1454 |
</div>
|
| 1455 |
<div class="pov-content">
|
| 1456 |
<svg class="pov-chords-svg" viewBox="0 0 160 100">
|
| 1457 |
+
<g class="pov-chords-rotate">
|
| 1458 |
+
<circle cx="80" cy="50" r="30" class="pov-chord-line" stroke-dasharray="2,2"></circle>
|
| 1459 |
+
<circle cx="80" cy="50" r="35" class="pov-chord-line active"></circle>
|
| 1460 |
+
|
| 1461 |
+
<circle cx="50" cy="30" r="4" class="pov-node"></circle>
|
| 1462 |
+
<circle cx="110" cy="30" r="4" class="pov-node"></circle>
|
| 1463 |
+
<circle cx="50" cy="70" r="4" class="pov-node"></circle>
|
| 1464 |
+
<circle cx="110" cy="70" r="4" class="pov-node"></circle>
|
| 1465 |
+
<circle cx="80" cy="15" r="4" class="pov-node"></circle>
|
| 1466 |
+
<circle cx="80" cy="85" r="4" class="pov-node"></circle>
|
| 1467 |
+
|
| 1468 |
+
<path d="M50 30 L110 70" class="pov-chord-line active" stroke-dasharray="none"></path>
|
| 1469 |
+
<path d="M110 30 L50 70" class="pov-chord-line" stroke-dasharray="4,4"></path>
|
| 1470 |
+
<path d="M80 15 L80 85" class="pov-chord-line active" stroke-dasharray="none"></path>
|
| 1471 |
+
</g>
|
| 1472 |
</svg>
|
| 1473 |
</div>`;
|
| 1474 |
|
|
|
|
| 1634 |
if (!res.ok) throw new Error('Results not ready');
|
| 1635 |
const data = await res.json();
|
| 1636 |
|
| 1637 |
+
if (data.ready === false) {
|
| 1638 |
+
throw new Error('Results pending');
|
| 1639 |
+
}
|
| 1640 |
+
|
| 1641 |
if (data.error) {
|
| 1642 |
setStatus('● Error', 'error');
|
| 1643 |
toast('Analysis failed: ' + data.error, 'error');
|
|
|
|
| 1645 |
return;
|
| 1646 |
}
|
| 1647 |
|
| 1648 |
+
const wasRunning = els.runningScreen.classList.contains('active');
|
| 1649 |
+
|
| 1650 |
state.results = data;
|
| 1651 |
|
| 1652 |
// Extract column info from preview
|
|
|
|
| 1660 |
setStatus('● Complete', 'complete');
|
| 1661 |
|
| 1662 |
renderDashboard(data, sessionId);
|
| 1663 |
+
|
| 1664 |
+
if (wasRunning) {
|
| 1665 |
+
switchSection('agentic');
|
| 1666 |
+
}
|
| 1667 |
+
|
| 1668 |
showScreen('results');
|
| 1669 |
} catch (e) {
|
| 1670 |
// Results not ready yet, retry
|
|
|
|
| 1682 |
renderVizCode(data.code || '');
|
| 1683 |
setupExport(sessionId);
|
| 1684 |
resetChat();
|
| 1685 |
+
|
| 1686 |
+
// Set dimensions text in Chat Preview
|
| 1687 |
+
if (els.chatPreviewDims) {
|
| 1688 |
+
els.chatPreviewDims.textContent = `(${data.rows_count?.toLocaleString() || 0} rows × ${data.cols_count || 0} columns)`;
|
| 1689 |
+
}
|
| 1690 |
+
|
| 1691 |
+
// Default to AI Chat section
|
| 1692 |
+
switchSection('chat');
|
| 1693 |
+
|
| 1694 |
+
// Activate first tab inside agentic area
|
| 1695 |
+
activateTab('cleaning');
|
| 1696 |
}
|
| 1697 |
|
| 1698 |
// ── Stats row ────────────────────────────────────────────────────────────────
|
|
|
|
| 1732 |
}
|
| 1733 |
|
| 1734 |
// Toggle preview minimize
|
| 1735 |
+
if (els.togglePreviewBtn) {
|
| 1736 |
+
els.togglePreviewBtn.addEventListener('click', () => {
|
| 1737 |
+
state.previewMinimized = !state.previewMinimized;
|
| 1738 |
+
if (state.previewMinimized) {
|
| 1739 |
+
els.previewTableWrap.innerHTML = `
|
| 1740 |
+
<div class="preview-minimized">
|
| 1741 |
+
Preview minimized — ${state.results?.rows_count?.toLocaleString() ?? '?'} rows × ${state.columns.length} columns.
|
| 1742 |
+
</div>`;
|
| 1743 |
+
els.togglePreviewBtn.textContent = '🔼 Expand Preview';
|
| 1744 |
+
} else {
|
| 1745 |
+
els.previewTableWrap.innerHTML = '<div id="previewTable" class="data-table-container"></div>';
|
| 1746 |
+
renderPreview(state.results?.preview || []);
|
| 1747 |
+
els.togglePreviewBtn.textContent = '🔽 Minimize';
|
| 1748 |
+
}
|
| 1749 |
+
});
|
| 1750 |
+
}
|
| 1751 |
|
| 1752 |
// ── Cleaning ─────────────────────────────────────────────────────────────────
|
| 1753 |
function renderCleaning(text) {
|
|
|
|
| 1764 |
).join('');
|
| 1765 |
}
|
| 1766 |
|
| 1767 |
+
// ── Relations ─────────────────────────────────────────────────────────────────
|
| 1768 |
// ── Relations ─────────────────────────────────────────────────────────────────
|
| 1769 |
function renderRelations(text) {
|
| 1770 |
const lines = text.split('\n').map(l => l.trim()).filter(Boolean);
|
| 1771 |
+
|
| 1772 |
+
// Parse lines into structured state.relationsList
|
| 1773 |
+
state.relationsList = [];
|
| 1774 |
+
lines.forEach(line => {
|
| 1775 |
+
const xMatch = line.match(/X:\s*([^|]+)/i);
|
| 1776 |
+
const yMatch = line.match(/Y:\s*([^|]+)/i);
|
| 1777 |
+
const typeMatch = line.match(/Type:\s*([^|]+)/i);
|
| 1778 |
+
const detailsMatch = line.match(/Details:\s*(.+)/i);
|
| 1779 |
+
|
| 1780 |
+
if (xMatch && yMatch) {
|
| 1781 |
+
state.relationsList.push({
|
| 1782 |
+
xCol: xMatch[1].trim(),
|
| 1783 |
+
yCol: yMatch[1].trim(),
|
| 1784 |
+
typ: typeMatch ? typeMatch[1].trim() : 'Correlation',
|
| 1785 |
+
details: detailsMatch ? detailsMatch[1].trim() : 'Key relationship identified by analyst.'
|
| 1786 |
+
});
|
| 1787 |
+
}
|
| 1788 |
+
});
|
| 1789 |
+
|
| 1790 |
+
renderRelationsListUI();
|
| 1791 |
+
}
|
| 1792 |
+
|
| 1793 |
+
function renderRelationsListUI() {
|
| 1794 |
+
const list = state.relationsList || [];
|
| 1795 |
+
if (!list.length) {
|
| 1796 |
+
els.relationsContent.innerHTML = `
|
| 1797 |
+
<div style="text-align: center; padding: 24px; color: var(--text-secondary); background: var(--bg-card); border-radius: var(--r-md); border: 1px dashed var(--border-mid); width: 100%;">
|
| 1798 |
+
<p style="margin-bottom: 8px;">No schema relationships mapped yet.</p>
|
| 1799 |
+
<button id="btnAddRelation" class="btn-primary btn-sm" style="font-weight: 600;">➕ Add Custom Relation</button>
|
| 1800 |
+
</div>`;
|
| 1801 |
+
|
| 1802 |
+
const btn = document.getElementById('btnAddRelation');
|
| 1803 |
+
if (btn) btn.onclick = () => showRelationModal(null);
|
| 1804 |
return;
|
| 1805 |
}
|
| 1806 |
|
| 1807 |
+
let html = '<div class="relation-mapper-container">';
|
| 1808 |
+
list.forEach((rel, index) => {
|
| 1809 |
+
const xType = state.colTypes[rel.xCol] || 'Numeric';
|
| 1810 |
+
const yType = state.colTypes[rel.yCol] || 'Numeric';
|
| 1811 |
+
|
| 1812 |
+
html += `
|
| 1813 |
+
<div class="relation-card" style="position: relative; width: 100%;">
|
| 1814 |
+
<!-- Tweak Actions Toolbar -->
|
| 1815 |
+
<div style="position: absolute; top: 12px; right: 12px; display: flex; gap: 6px; z-index: 5;">
|
| 1816 |
+
<button class="btn-secondary btn-sm btn-rel-edit" data-idx="${index}" style="padding: 2px 6px; font-size: 0.72rem; border-color: rgba(34,211,238,0.2); color: var(--cyan);">✏️ Edit</button>
|
| 1817 |
+
<button class="btn-secondary btn-sm btn-rel-del" data-idx="${index}" style="padding: 2px 6px; font-size: 0.72rem; border-color: rgba(244,63,94,0.2); color: var(--rose);">🗑️ Delete</button>
|
| 1818 |
+
</div>
|
| 1819 |
+
|
| 1820 |
+
<div class="relation-node">
|
| 1821 |
+
<span class="relation-node-type">${escHtml(xType)}</span>
|
| 1822 |
+
<strong>${escHtml(rel.xCol)}</strong>
|
| 1823 |
+
</div>
|
| 1824 |
+
|
| 1825 |
+
<div class="relation-connector">
|
| 1826 |
+
<span style="font-size: 0.8rem; font-weight: 600; color: var(--cyan); margin-bottom: 4px;">${escHtml(rel.typ)}</span>
|
| 1827 |
+
<div class="relation-line"></div>
|
| 1828 |
+
<span class="relation-info">${escHtml(rel.details)}</span>
|
| 1829 |
+
</div>
|
| 1830 |
+
|
| 1831 |
+
<div class="relation-node" style="border-color: rgba(34, 211, 238, 0.2);">
|
| 1832 |
+
<span class="relation-node-type" style="color: var(--cyan);">${escHtml(yType)}</span>
|
| 1833 |
+
<strong>${escHtml(rel.yCol)}</strong>
|
| 1834 |
+
</div>
|
| 1835 |
+
</div>
|
| 1836 |
+
`;
|
| 1837 |
+
});
|
| 1838 |
+
|
| 1839 |
+
// Footer Actions
|
| 1840 |
+
html += `
|
| 1841 |
+
<div style="display: flex; gap: 12px; margin-top: 16px; justify-content: flex-end; width: 100%;">
|
| 1842 |
+
<button id="btnAddRelation" class="btn-secondary" style="font-weight: 600; font-size: 0.85rem;">➕ Add Custom Relation</button>
|
| 1843 |
+
<button id="btnSaveRelations" class="btn-primary" style="font-weight: 600; font-size: 0.85rem;">💾 Save Schema Tweaks</button>
|
| 1844 |
+
</div>
|
| 1845 |
+
`;
|
| 1846 |
+
html += '</div>';
|
| 1847 |
+
|
| 1848 |
+
els.relationsContent.innerHTML = html;
|
| 1849 |
+
|
| 1850 |
+
// Wire buttons
|
| 1851 |
+
els.relationsContent.querySelectorAll('.btn-rel-edit').forEach(b => {
|
| 1852 |
+
b.onclick = () => showRelationModal(parseInt(b.dataset.idx, 10));
|
| 1853 |
+
});
|
| 1854 |
+
els.relationsContent.querySelectorAll('.btn-rel-del').forEach(b => {
|
| 1855 |
+
b.onclick = () => deleteRelationAt(parseInt(b.dataset.idx, 10));
|
| 1856 |
+
});
|
| 1857 |
+
|
| 1858 |
+
document.getElementById('btnAddRelation').onclick = () => showRelationModal(null);
|
| 1859 |
+
document.getElementById('btnSaveRelations').onclick = () => saveTweakedRelations();
|
| 1860 |
+
}
|
| 1861 |
|
| 1862 |
+
function showRelationModal(index) {
|
| 1863 |
+
const cols = state.columns || [];
|
| 1864 |
+
els.relationModalXSelect.innerHTML = cols.map(c => `<option value="${escHtml(c)}">${escHtml(c)}</option>`).join('');
|
| 1865 |
+
els.relationModalYSelect.innerHTML = cols.map(c => `<option value="${escHtml(c)}">${escHtml(c)}</option>`).join('');
|
| 1866 |
+
|
| 1867 |
+
if (index !== null && state.relationsList[index]) {
|
| 1868 |
+
const rel = state.relationsList[index];
|
| 1869 |
+
els.relationModalTitle.textContent = '✏️ Edit Relationship Schema';
|
| 1870 |
+
els.relationModalXSelect.value = rel.xCol;
|
| 1871 |
+
els.relationModalYSelect.value = rel.yCol;
|
| 1872 |
+
els.relationModalTypeSelect.value = rel.typ;
|
| 1873 |
+
els.relationModalDetails.value = rel.details;
|
| 1874 |
+
} else {
|
| 1875 |
+
els.relationModalTitle.textContent = '➕ Add Custom Relationship';
|
| 1876 |
+
if (cols.length > 1) {
|
| 1877 |
+
els.relationModalXSelect.selectedIndex = 0;
|
| 1878 |
+
els.relationModalYSelect.selectedIndex = 1;
|
| 1879 |
+
}
|
| 1880 |
+
els.relationModalTypeSelect.value = 'Scatter Plot';
|
| 1881 |
+
els.relationModalDetails.value = '';
|
| 1882 |
}
|
| 1883 |
|
| 1884 |
+
els.relationModal.classList.remove('hidden');
|
|
|
|
| 1885 |
|
| 1886 |
+
els.relationModalConfirmBtn.onclick = () => {
|
| 1887 |
+
const relObj = {
|
| 1888 |
+
xCol: els.relationModalXSelect.value,
|
| 1889 |
+
yCol: els.relationModalYSelect.value,
|
| 1890 |
+
typ: els.relationModalTypeSelect.value,
|
| 1891 |
+
details: els.relationModalDetails.value.trim() || 'Custom correlation defined by data scientist.'
|
| 1892 |
+
};
|
| 1893 |
|
| 1894 |
+
if (index !== null) {
|
| 1895 |
+
state.relationsList[index] = relObj;
|
| 1896 |
+
} else {
|
| 1897 |
+
state.relationsList.push(relObj);
|
| 1898 |
+
}
|
| 1899 |
+
els.relationModal.classList.add('hidden');
|
| 1900 |
+
renderRelationsListUI();
|
| 1901 |
+
};
|
| 1902 |
|
| 1903 |
+
els.relationModalCancelBtn.onclick = () => els.relationModal.classList.add('hidden');
|
| 1904 |
+
els.closeRelationModalBtn.onclick = () => els.relationModal.classList.add('hidden');
|
| 1905 |
+
}
|
| 1906 |
+
|
| 1907 |
+
function deleteRelationAt(index) {
|
| 1908 |
+
state.relationsList.splice(index, 1);
|
| 1909 |
+
renderRelationsListUI();
|
| 1910 |
+
toast('Relationship removed from schema configuration.', 'info');
|
| 1911 |
+
}
|
| 1912 |
+
|
| 1913 |
+
async function saveTweakedRelations() {
|
| 1914 |
+
const sessionId = state.activeProject?.id || state.uploadedSession;
|
| 1915 |
+
if (!sessionId) { toast('No active project context.', 'error'); return; }
|
| 1916 |
+
|
| 1917 |
+
const textLines = state.relationsList.map(r =>
|
| 1918 |
+
`- X: ${r.xCol} | Y: ${r.yCol} | Type: ${r.typ} | Details: ${r.details}`
|
| 1919 |
+
).join('\n');
|
| 1920 |
+
|
| 1921 |
+
try {
|
| 1922 |
+
const fd = new FormData();
|
| 1923 |
+
fd.append('relations_text', textLines);
|
| 1924 |
+
|
| 1925 |
+
const res = await fetch(`/api/projects/${sessionId}/tweak-relations`, {
|
| 1926 |
+
method: 'POST',
|
| 1927 |
+
body: fd
|
| 1928 |
+
});
|
| 1929 |
+
|
| 1930 |
+
if (res.ok) {
|
| 1931 |
+
toast('Schema relationships committed successfully!', 'success');
|
| 1932 |
+
if (state.results) {
|
| 1933 |
+
state.results.relations = textLines;
|
| 1934 |
+
}
|
| 1935 |
+
} else {
|
| 1936 |
+
toast('Failed to save schema tweaks.', 'error');
|
| 1937 |
}
|
| 1938 |
+
} catch (e) {
|
| 1939 |
+
toast('Error saving schema tweaks: ' + e.message, 'error');
|
| 1940 |
+
}
|
| 1941 |
}
|
| 1942 |
|
| 1943 |
// ── Insights Markdown Formatter ──────────────────────────────────────────────
|
|
|
|
| 1967 |
// ── Insights ─────────────────────────────────────────────────────────────────
|
| 1968 |
function renderInsights(text) {
|
| 1969 |
if (!text || !text.trim() || text.toLowerCase().includes('skipped')) {
|
| 1970 |
+
els.insightsContent.innerHTML = '<p style="color:var(--text-muted)">No business insights generated.</p>';
|
| 1971 |
return;
|
| 1972 |
}
|
| 1973 |
|
| 1974 |
+
// Parse sections
|
| 1975 |
+
let objectivesText = "";
|
| 1976 |
+
let statsText = "";
|
| 1977 |
+
let strategicText = "";
|
| 1978 |
+
let warningsText = "";
|
| 1979 |
+
|
| 1980 |
+
const sections = text.split(/###\s+/);
|
| 1981 |
+
sections.forEach(sec => {
|
| 1982 |
+
const lines = sec.split('\n');
|
| 1983 |
+
if (lines.length === 0) return;
|
| 1984 |
+
const header = lines[0].trim().toLowerCase();
|
| 1985 |
+
const content = lines.slice(1).join('\n').trim();
|
| 1986 |
+
|
| 1987 |
+
if (header.includes('objective') || header.includes('goal')) {
|
| 1988 |
+
objectivesText = content;
|
| 1989 |
+
} else if (header.includes('stat')) {
|
| 1990 |
+
statsText = content;
|
| 1991 |
+
} else if (header.includes('insight')) {
|
| 1992 |
+
strategicText = content;
|
| 1993 |
+
} else if (header.includes('warning') || header.includes('alert')) {
|
| 1994 |
+
warningsText = content;
|
| 1995 |
+
}
|
| 1996 |
+
});
|
| 1997 |
|
| 1998 |
+
// If parsing didn't find specific sections, fallback to parsing as a single block
|
| 1999 |
+
if (!strategicText && !objectivesText) {
|
| 2000 |
+
strategicText = text;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2001 |
}
|
| 2002 |
|
| 2003 |
+
let html = "";
|
| 2004 |
+
|
| 2005 |
+
// 1. Objectives & Goals Banner
|
| 2006 |
+
if (objectivesText) {
|
| 2007 |
+
html += `
|
| 2008 |
+
<div class="insight-header-card">
|
| 2009 |
+
<div class="insight-header-title">🎯 Primary Objectives & Goals</div>
|
| 2010 |
+
<div class="insight-header-text">${formatInsightsSubsections(objectivesText)}</div>
|
| 2011 |
+
</div>
|
| 2012 |
+
`;
|
| 2013 |
}
|
| 2014 |
|
| 2015 |
+
// 2. Dataset Statistics Grid
|
| 2016 |
+
if (statsText) {
|
| 2017 |
+
const statLines = statsText.split('\n').map(l => l.trim()).filter(l => l.startsWith('-') || l.startsWith('*'));
|
| 2018 |
+
if (statLines.length > 0) {
|
| 2019 |
+
html += `<div class="insight-stats-grid">`;
|
| 2020 |
+
statLines.forEach(line => {
|
| 2021 |
+
const cleanLine = line.replace(/^[-*•]\s*/, '');
|
| 2022 |
+
const colIndex = cleanLine.indexOf(':');
|
| 2023 |
+
if (colIndex > -1) {
|
| 2024 |
+
const key = cleanLine.slice(0, colIndex).trim();
|
| 2025 |
+
const val = cleanLine.slice(colIndex + 1).trim();
|
| 2026 |
+
html += `
|
| 2027 |
+
<div class="insight-stat-card">
|
| 2028 |
+
<div class="insight-stat-val">${escHtml(val)}</div>
|
| 2029 |
+
<div class="insight-stat-lbl">${escHtml(key)}</div>
|
| 2030 |
+
</div>
|
| 2031 |
+
`;
|
| 2032 |
+
}
|
| 2033 |
+
});
|
| 2034 |
+
html += `</div>`;
|
| 2035 |
+
} else {
|
| 2036 |
+
html += `
|
| 2037 |
+
<div class="card" style="padding: 16px; margin-bottom: 20px; border-color: var(--border-mid);">
|
| 2038 |
+
<h4 style="margin-bottom: 8px; color: var(--cyan);">📊 Dataset Metrics</h4>
|
| 2039 |
+
<div style="font-size: 0.9rem; line-height: 1.5;">${formatInsightsSubsections(statsText)}</div>
|
| 2040 |
+
</div>
|
| 2041 |
+
`;
|
| 2042 |
+
}
|
| 2043 |
+
}
|
| 2044 |
+
|
| 2045 |
+
// 3. Strategic Insights List
|
| 2046 |
+
if (strategicText) {
|
| 2047 |
+
html += `<h4 style="margin-bottom: 12px; color: var(--violet-light); font-size: 1.05rem;">💡 Strategic Business Insights</h4>`;
|
| 2048 |
+
const numberedSplit = strategicText.split(/\n(?=\s*(?:\*{0,2}\d+[.):]\*{0,2}|#{1,3}\s))/);
|
| 2049 |
+
let items = [];
|
| 2050 |
+
if (numberedSplit.length > 1) {
|
| 2051 |
+
items = numberedSplit.map(s => s.replace(/^\s*(?:\*{0,2}\d+[.):]\*{0,2}|#{1,3})\s*/, '').trim()).filter(Boolean);
|
| 2052 |
+
} else {
|
| 2053 |
+
items = strategicText.split(/\n{2,}/).map(s => s.trim()).filter(Boolean);
|
| 2054 |
+
}
|
| 2055 |
|
| 2056 |
+
const LABEL_CFG = [
|
| 2057 |
+
{ re: /^(?:observation|finding|pattern)[s]?[:]/i, cls: 'obs', icon: '🔍', label: 'Observation' },
|
| 2058 |
+
{ re: /^(?:business\s+)?implication[s]?[:]/i, cls: 'impl', icon: '💼', label: 'Implication' },
|
| 2059 |
+
{ re: /^(?:actionable\s+)?strateg(?:y|ies)[:]/i, cls: 'strat', icon: '🚀', label: 'Strategy' },
|
| 2060 |
+
{ re: /^(?:recommendation|action|next\s+step)[s]?[:]/i, cls: 'strat', icon: '✅', label: 'Recommendation' },
|
| 2061 |
+
{ re: /^(?:risk|concern|warning)[s]?[:]/i, cls: 'impl', icon: '⚠️', label: 'Risk' },
|
| 2062 |
+
{ re: /^(?:kpi|metric|measure)[s]?[:]/i, cls: 'obs', icon: '📊', label: 'Metric' },
|
| 2063 |
+
];
|
| 2064 |
+
|
| 2065 |
+
html += items.map((item, i) => {
|
| 2066 |
+
const parts = item.split('\n').map(p => p.trim()).filter(Boolean);
|
| 2067 |
+
const sectionHtml = parts.map(p => {
|
| 2068 |
+
for (const cfg of LABEL_CFG) {
|
| 2069 |
+
if (cfg.re.test(p)) {
|
| 2070 |
+
const body = p.replace(cfg.re, '').trim();
|
| 2071 |
+
return `<div class="insight-section">
|
| 2072 |
+
<div class="insight-section-label ${cfg.cls}">${cfg.icon} ${cfg.label}</div>
|
| 2073 |
+
<div class="insight-section-text">${formatInlineMarkdown(body || p)}</div>
|
| 2074 |
+
</div>`;
|
| 2075 |
+
}
|
| 2076 |
}
|
| 2077 |
+
if (/^[-*•]/.test(p)) {
|
| 2078 |
+
return `<div class="insight-bullet">${formatInlineMarkdown(p)}</div>`;
|
| 2079 |
+
}
|
| 2080 |
+
if (/^#{1,3}\s/.test(p)) {
|
| 2081 |
+
return `<div class="insight-sub-heading">${escHtml(p.replace(/^#+\s*/, ''))}</div>`;
|
| 2082 |
+
}
|
| 2083 |
+
return `<div class="insight-section-text">${formatInlineMarkdown(p)}</div>`;
|
| 2084 |
+
}).join('');
|
| 2085 |
+
|
| 2086 |
+
return `
|
| 2087 |
+
<div class="insight-card">
|
| 2088 |
+
<div class="insight-num-pill">${i + 1}</div>
|
| 2089 |
+
<div class="insight-body">${sectionHtml}</div>
|
| 2090 |
+
</div>
|
| 2091 |
+
`;
|
| 2092 |
}).join('');
|
| 2093 |
+
}
|
| 2094 |
|
| 2095 |
+
// 4. Warnings & Alerts
|
| 2096 |
+
if (warningsText && !warningsText.toLowerCase().includes('no warnings') && !warningsText.toLowerCase().includes('none')) {
|
| 2097 |
+
html += `
|
| 2098 |
+
<div class="insight-warning-card">
|
| 2099 |
+
<div class="insight-warning-card-icon">⚠️</div>
|
| 2100 |
+
<div class="insight-warning-card-body">
|
| 2101 |
+
<div class="insight-warning-card-title">Business Risks & Data Alerts</div>
|
| 2102 |
+
<div class="insight-warning-card-text">${formatInsightsSubsections(warningsText)}</div>
|
| 2103 |
+
</div>
|
| 2104 |
+
</div>
|
| 2105 |
+
`;
|
| 2106 |
+
}
|
| 2107 |
+
|
| 2108 |
+
els.insightsContent.innerHTML = html;
|
| 2109 |
+
}
|
| 2110 |
+
|
| 2111 |
+
function formatInsightsSubsections(text) {
|
| 2112 |
+
return text.split('\n').map(line => {
|
| 2113 |
+
const trimmed = line.trim();
|
| 2114 |
+
if (trimmed.startsWith('-') || trimmed.startsWith('*')) {
|
| 2115 |
+
return `<div style="margin-left: 12px; margin-top: 4px; display: flex; gap: 6px;">
|
| 2116 |
+
<span>•</span>
|
| 2117 |
+
<span>${formatInlineMarkdown(trimmed.replace(/^[-*•]\s*/, ''))}</span>
|
| 2118 |
+
</div>`;
|
| 2119 |
+
}
|
| 2120 |
+
return `<p style="margin-top: 4px; margin-bottom: 4px;">${formatInlineMarkdown(trimmed)}</p>`;
|
| 2121 |
}).join('');
|
| 2122 |
}
|
| 2123 |
|
|
|
|
| 2210 |
els.exportPdfBtn.onclick = () => {
|
| 2211 |
window.location = `/api/export-pdf?session_id=${sessionId}`;
|
| 2212 |
};
|
| 2213 |
+
if (els.exportZipBtn) {
|
| 2214 |
+
els.exportZipBtn.onclick = () => {
|
| 2215 |
+
window.location = `/api/projects/${sessionId}/export-zip`;
|
| 2216 |
+
};
|
| 2217 |
+
}
|
| 2218 |
els.downloadCsvBtn.onclick = () => {
|
| 2219 |
window.location = `/api/projects/${sessionId}/download-csv`;
|
| 2220 |
};
|
|
|
|
| 2334 |
const data = await res.json();
|
| 2335 |
const text = data.text && data.text.trim() ? data.text : 'No response returned.';
|
| 2336 |
appendChatMsg('assistant', text, data.plot_url || null);
|
| 2337 |
+
|
| 2338 |
+
// Reload dynamic preview if this query modified the dataset
|
| 2339 |
+
const qLower = query.toLowerCase();
|
| 2340 |
+
if (qLower.includes('delete') || qLower.includes('rename') || qLower.includes('replace') || qLower.includes('drop') || qLower.includes('fix') || qLower.includes('clean') || qLower.includes('modify') || qLower.includes('update')) {
|
| 2341 |
+
await refreshPreviewData(sessionId);
|
| 2342 |
+
}
|
| 2343 |
} catch (e) {
|
| 2344 |
removeTypingIndicator();
|
| 2345 |
appendChatMsg('assistant', '⚠ Network error: ' + e.message);
|
|
|
|
| 2442 |
showScreen('landing');
|
| 2443 |
setStatus('● Idle', 'idle');
|
| 2444 |
|
| 2445 |
+
// Wire section switcher
|
| 2446 |
+
if (els.btnSectionChat) els.btnSectionChat.addEventListener('click', () => switchSection('chat'));
|
| 2447 |
+
if (els.btnSectionAgentic) els.btnSectionAgentic.addEventListener('click', () => switchSection('agentic'));
|
| 2448 |
+
|
| 2449 |
+
// Wire quick action buttons
|
| 2450 |
+
if (els.btnRenameColQuick) {
|
| 2451 |
+
els.btnRenameColQuick.addEventListener('click', async () => {
|
| 2452 |
+
const oldName = await customPrompt('Select or enter the column you want to rename:', '', 'e.g. Q3_Sales', 'Rename Column');
|
| 2453 |
+
if (!oldName) return;
|
| 2454 |
+
if (!state.columns.includes(oldName)) {
|
| 2455 |
+
toast(`Column "${oldName}" not found in dataset.`, 'error');
|
| 2456 |
+
return;
|
| 2457 |
+
}
|
| 2458 |
+
const newName = await customPrompt(`Enter the new name for column "${oldName}":`, '', 'e.g. Sales_Q3', 'Rename Column');
|
| 2459 |
+
if (!newName) return;
|
| 2460 |
+
|
| 2461 |
+
// Command the copilot
|
| 2462 |
+
els.chatInput.value = `Rename column \`${oldName}\` to \`${newName}\` in the dataset`;
|
| 2463 |
+
sendChat();
|
| 2464 |
+
});
|
| 2465 |
+
}
|
| 2466 |
+
|
| 2467 |
+
if (els.btnDeleteColQuick) {
|
| 2468 |
+
els.btnDeleteColQuick.addEventListener('click', async () => {
|
| 2469 |
+
const colName = await customPrompt('Enter the name of the column you want to delete:', '', 'e.g. Unwanted_Col', 'Delete Column');
|
| 2470 |
+
if (!colName) return;
|
| 2471 |
+
if (!state.columns.includes(colName)) {
|
| 2472 |
+
toast(`Column "${colName}" not found in dataset.`, 'error');
|
| 2473 |
+
return;
|
| 2474 |
+
}
|
| 2475 |
+
const confirmed = await customConfirm(`Are you sure you want to permanently delete column "${colName}"?`, 'Delete Column');
|
| 2476 |
+
if (!confirmed) return;
|
| 2477 |
+
|
| 2478 |
+
// Command the copilot
|
| 2479 |
+
els.chatInput.value = `Delete column \`${colName}\` from the dataset`;
|
| 2480 |
+
sendChat();
|
| 2481 |
+
});
|
| 2482 |
+
}
|
| 2483 |
+
|
| 2484 |
+
// Wire agentic pipeline launch button
|
| 2485 |
+
if (els.btnRunAgenticPipeline) {
|
| 2486 |
+
els.btnRunAgenticPipeline.addEventListener('click', () => {
|
| 2487 |
+
state.uploadedSession = state.activeProject?.id;
|
| 2488 |
+
state.uploadedFile = { name: state.activeProject?.filename || 'dataset.csv' };
|
| 2489 |
+
if (!checkApiKeySet()) return;
|
| 2490 |
+
openConfigModal();
|
| 2491 |
+
});
|
| 2492 |
+
}
|
| 2493 |
+
|
| 2494 |
// Check if a running session exists on page load
|
| 2495 |
// (handles F5 refresh during analysis)
|
| 2496 |
setTimeout(async () => {
|
web/index.html
CHANGED
|
@@ -83,11 +83,16 @@
|
|
| 83 |
<!-- Wizard Container -->
|
| 84 |
<div class="wizard-container" id="newProjectWizard">
|
| 85 |
<!-- Step 0: Create Project Button Card -->
|
| 86 |
-
<div class="wizard-step active" id="wizardStep0" style="width: 100%; display: flex; justify-content: center;">
|
| 87 |
<div class="create-project-card" id="startWizardCard">
|
| 88 |
<div class="create-project-plus">+</div>
|
| 89 |
<div class="create-project-text">Create New Project</div>
|
| 90 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
</div>
|
| 92 |
|
| 93 |
<!-- Step 1: Project Name -->
|
|
@@ -95,9 +100,20 @@
|
|
| 95 |
<button id="wizardBack1Btn" class="btn-back">← Back</button>
|
| 96 |
<div class="wizard-icon">✨</div>
|
| 97 |
<h3 class="wizard-step-title">Create New Project</h3>
|
| 98 |
-
<p class="wizard-step-desc">Enter a name
|
| 99 |
-
<div
|
| 100 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
</div>
|
| 102 |
<button id="wizardNextBtn" class="btn-primary btn-lg mt-20" style="min-width: 200px; margin-top: 20px;">Continue to Upload →</button>
|
| 103 |
</div>
|
|
@@ -279,113 +295,168 @@
|
|
| 279 |
<section id="resultsScreen" class="screen">
|
| 280 |
|
| 281 |
<!-- Stat row -->
|
| 282 |
-
<div class="stats-row" id="statsRow"></div>
|
| 283 |
-
|
| 284 |
-
<!-- Tabs -->
|
| 285 |
-
<div class="tabs-bar">
|
| 286 |
-
<button class="tab-btn active" data-tab="preview">🔍 Data Preview</button>
|
| 287 |
-
<button class="tab-btn" data-tab="cleaning">🧹 Cleaning Report</button>
|
| 288 |
-
<button class="tab-btn" data-tab="relations">🔗 Relations</button>
|
| 289 |
-
<button class="tab-btn" data-tab="insights">💡 Insights</button>
|
| 290 |
-
<button class="tab-btn" data-tab="charts">📈 Visualizations</button>
|
| 291 |
-
<button class="tab-btn" data-tab="chat">💬 AI Copilot</button>
|
| 292 |
-
</div>
|
| 293 |
|
| 294 |
-
<!--
|
| 295 |
-
<div class="
|
|
|
|
|
|
|
|
|
|
| 296 |
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
</div>
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
</div>
|
| 306 |
-
</div>
|
| 307 |
|
| 308 |
-
<!-- CLEANING REPORT -->
|
| 309 |
-
<div class="tab-panel" id="panel-cleaning">
|
| 310 |
-
<h3 class="panel-title">🧹 Data Cleaning Audit Trail</h3>
|
| 311 |
-
<div id="cleaningContent" class="cleaning-list"></div>
|
| 312 |
</div>
|
|
|
|
| 313 |
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
</div>
|
| 319 |
|
| 320 |
-
<!--
|
| 321 |
-
<div class="
|
| 322 |
-
<
|
| 323 |
-
<
|
|
|
|
|
|
|
| 324 |
</div>
|
| 325 |
|
| 326 |
-
<!--
|
| 327 |
-
<div class="tab-
|
| 328 |
-
<
|
| 329 |
-
|
| 330 |
-
|
|
|
|
|
|
|
| 331 |
|
| 332 |
-
<
|
| 333 |
-
|
| 334 |
-
<
|
|
|
|
| 335 |
</div>
|
| 336 |
|
| 337 |
-
<!--
|
| 338 |
-
<
|
| 339 |
-
<
|
| 340 |
-
<
|
| 341 |
-
</
|
| 342 |
-
</div>
|
| 343 |
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
<div class="
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
<
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
<div class="chat-input-row">
|
| 357 |
-
<textarea
|
| 358 |
-
id="chatInput"
|
| 359 |
-
class="chat-textarea"
|
| 360 |
-
rows="2"
|
| 361 |
-
placeholder="Ask about your data… Type / to insert a column name"
|
| 362 |
-
></textarea>
|
| 363 |
-
<button id="sendChatBtn" class="btn-primary btn-send">Send ↑</button>
|
| 364 |
-
</div>
|
| 365 |
-
<div class="chat-hints">
|
| 366 |
-
<span class="chat-hint-chip" data-query="Show summary statistics for all numeric columns">📊 Summary Stats</span>
|
| 367 |
-
<span class="chat-hint-chip" data-query="Plot a histogram of the first numeric column">📉 Histogram</span>
|
| 368 |
-
<span class="chat-hint-chip" data-query="Which columns have the most missing values?">❓ Missing Data</span>
|
| 369 |
-
<span class="chat-hint-chip" data-query="Show top 10 rows sorted by the first numeric column descending">🔝 Top 10 Rows</span>
|
| 370 |
-
</div>
|
| 371 |
-
</div>
|
| 372 |
-
</div>
|
| 373 |
</div>
|
| 374 |
</div>
|
|
|
|
| 375 |
|
| 376 |
-
<
|
| 377 |
-
|
| 378 |
-
<!-- Export bar -->
|
| 379 |
-
<div class="export-bar">
|
| 380 |
<button id="exportPdfBtn" class="btn-primary">📄 Export PDF Report</button>
|
|
|
|
| 381 |
<button id="downloadCsvBtn" class="btn-secondary">📥 Download Cleaned CSV</button>
|
| 382 |
-
<button id="reRunBtn" class="btn-ghost">🔄 Re-run Analysis</button>
|
| 383 |
</div>
|
| 384 |
|
| 385 |
</section><!-- /results screen -->
|
| 386 |
|
| 387 |
</main><!-- /main-area -->
|
| 388 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
<!-- API Warning Modal -->
|
| 390 |
<div id="apiWarningModal" class="modal-overlay hidden">
|
| 391 |
<div class="modal-box" style="max-width: 420px; text-align: center; padding: 30px;">
|
|
|
|
| 83 |
<!-- Wizard Container -->
|
| 84 |
<div class="wizard-container" id="newProjectWizard">
|
| 85 |
<!-- Step 0: Create Project Button Card -->
|
| 86 |
+
<div class="wizard-step active" id="wizardStep0" style="width: 100%; display: flex; flex-direction: row !important; justify-content: center; gap: 20px; flex-wrap: wrap;">
|
| 87 |
<div class="create-project-card" id="startWizardCard">
|
| 88 |
<div class="create-project-plus">+</div>
|
| 89 |
<div class="create-project-text">Create New Project</div>
|
| 90 |
</div>
|
| 91 |
+
<div class="create-project-card" id="importProjectCard" style="background: rgba(16,185,129,0.02); border-color: rgba(16,185,129,0.2);">
|
| 92 |
+
<div class="create-project-plus" style="color: #10b981;">↓</div>
|
| 93 |
+
<div class="create-project-text" style="color: var(--text-primary);">Import Project (.zip)</div>
|
| 94 |
+
<input type="file" id="importZipFileInput" accept=".zip" style="display: none;" />
|
| 95 |
+
</div>
|
| 96 |
</div>
|
| 97 |
|
| 98 |
<!-- Step 1: Project Name -->
|
|
|
|
| 100 |
<button id="wizardBack1Btn" class="btn-back">← Back</button>
|
| 101 |
<div class="wizard-icon">✨</div>
|
| 102 |
<h3 class="wizard-step-title">Create New Project</h3>
|
| 103 |
+
<p class="wizard-step-desc">Enter a name, and optionally a title and goal for your project.</p>
|
| 104 |
+
<div style="width: 100%; max-width: 440px; margin: 16px auto 0; text-align: left; display: flex; flex-direction: column; gap: 12px;">
|
| 105 |
+
<div class="field-group">
|
| 106 |
+
<label class="field-label" style="font-weight: 600; margin-bottom: 4px;">Project Name</label>
|
| 107 |
+
<input type="text" id="wizardProjectName" class="field-input" placeholder="e.g. Sales Analysis Q3" style="font-size: 0.95rem; padding: 10px 12px; width: 100%; box-sizing: border-box;" />
|
| 108 |
+
</div>
|
| 109 |
+
<div class="field-group">
|
| 110 |
+
<label class="field-label" style="font-weight: 600; margin-bottom: 4px;">Report Title (Optional)</label>
|
| 111 |
+
<input type="text" id="wizardReportTitle" class="field-input" placeholder="e.g. Q3 Sales Executive Analysis" style="font-size: 0.95rem; padding: 10px 12px; width: 100%; box-sizing: border-box;" />
|
| 112 |
+
</div>
|
| 113 |
+
<div class="field-group">
|
| 114 |
+
<label class="field-label" style="font-weight: 600; margin-bottom: 4px;">Project Goal / Objective (Optional)</label>
|
| 115 |
+
<textarea id="wizardProjectGoal" class="field-input" rows="3" placeholder="e.g. Identify top 10% highest-margin customer segments and explain why spend exceeded budget." style="font-size: 0.95rem; padding: 10px 12px; font-family: inherit; resize: none; width: 100%; box-sizing: border-box;"></textarea>
|
| 116 |
+
</div>
|
| 117 |
</div>
|
| 118 |
<button id="wizardNextBtn" class="btn-primary btn-lg mt-20" style="min-width: 200px; margin-top: 20px;">Continue to Upload →</button>
|
| 119 |
</div>
|
|
|
|
| 295 |
<section id="resultsScreen" class="screen">
|
| 296 |
|
| 297 |
<!-- Stat row -->
|
| 298 |
+
<div class="stats-row" id="statsRow" style="margin-bottom: 20px;"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
+
<!-- Main Sections Switch -->
|
| 301 |
+
<div class="sections-switch" style="display: flex; gap: 8px; justify-content: center; margin-bottom: 24px; padding: 4px; background: var(--bg-elevated); border: 1px solid var(--border-mid); border-radius: 30px; max-width: 440px; margin-left: auto; margin-right: auto; position: relative;">
|
| 302 |
+
<button class="section-switch-btn active" id="btnSectionChat" style="flex: 1; padding: 10px 20px; border: none; background: var(--violet-dark); color: #fff; font-weight: 600; border-radius: 26px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px;">💬 AI Data Chat</button>
|
| 303 |
+
<button class="section-switch-btn" id="btnSectionAgentic" style="flex: 1; padding: 10px 20px; border: none; background: transparent; color: var(--text-secondary); font-weight: 600; border-radius: 26px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px;">🤖 Agentic Analysis</button>
|
| 304 |
+
</div>
|
| 305 |
|
| 306 |
+
<!-- ───────────────── SECTION 1: AI DATA CHAT ───────────────── -->
|
| 307 |
+
<div id="areaChat" class="section-area active">
|
| 308 |
+
<div style="display: flex; flex-direction: column; gap: 20px;">
|
| 309 |
+
|
| 310 |
+
<!-- Table Preview Area -->
|
| 311 |
+
<div class="card" style="padding: 18px; background: var(--bg-elevated); border: 1px solid var(--border-mid);">
|
| 312 |
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; flex-wrap: wrap; gap: 8px;">
|
| 313 |
+
<h3 style="margin: 0; font-size: 1.1rem; display: flex; align-items: center; gap: 8px;">🔍 Dataset Explorer <span id="chatPreviewDims" style="font-size: 0.75rem; font-weight: normal; color: var(--text-secondary);"></span></h3>
|
| 314 |
+
<div style="display: flex; gap: 8px;">
|
| 315 |
+
<button id="btnRenameColQuick" class="btn-secondary btn-sm" style="font-size: 0.75rem; display: flex; align-items: center; gap: 4px;">✏️ Rename Column</button>
|
| 316 |
+
<button id="btnDeleteColQuick" class="btn-secondary btn-sm" style="font-size: 0.75rem; border-color: rgba(239, 68, 68, 0.2); color: #ef4444; display: flex; align-items: center; gap: 4px;">🗑️ Delete Column</button>
|
| 317 |
+
</div>
|
| 318 |
+
</div>
|
| 319 |
+
<div id="previewTableWrap" class="table-wrap" style="max-height: 250px; overflow-y: auto;">
|
| 320 |
+
<div id="previewTable" class="data-table-container"></div>
|
| 321 |
+
</div>
|
| 322 |
</div>
|
| 323 |
+
|
| 324 |
+
<!-- Chat Container -->
|
| 325 |
+
<div class="card" style="padding: 20px; background: var(--bg-elevated); border: 1px solid var(--border-mid); display: flex; flex-direction: column; min-height: 480px;">
|
| 326 |
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; border-bottom: 1px solid var(--border-low); padding-bottom: 12px;">
|
| 327 |
+
<h3 style="margin: 0; font-size: 1.1rem; display: flex; align-items: center; gap: 8px;">💬 AI Copilot & Data Interaction</h3>
|
| 328 |
+
<button id="clearChatBtn" class="btn-secondary btn-sm">Clear Conversation</button>
|
| 329 |
+
</div>
|
| 330 |
+
|
| 331 |
+
<div id="chatMessages" class="chat-messages" style="flex: 1; min-height: 260px; max-height: 420px; overflow-y: auto; padding-right: 4px; display: flex; flex-direction: column; gap: 16px; margin-bottom: 16px;">
|
| 332 |
+
<!-- messages populate here -->
|
| 333 |
+
</div>
|
| 334 |
+
|
| 335 |
+
<div class="chat-input-area" style="position: relative;">
|
| 336 |
+
<div id="colPickerDropdown" class="col-picker-dropdown hidden" style="position: absolute; bottom: 100%; left: 0; right: 0; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--r-md); z-index: 10; max-height: 160px; overflow-y: auto; padding: 4px;"></div>
|
| 337 |
+
<div class="chat-input-row" style="display: flex; gap: 8px; align-items: flex-end;">
|
| 338 |
+
<textarea id="chatInput" class="chat-textarea" rows="2" placeholder="Ask questions, request visual charts, or modify columns (type / to select a column)..." style="flex: 1; background: var(--bg-card); color: var(--text-primary); border: 1px solid var(--border-mid); border-radius: var(--r-md); padding: 10px 14px; font-size: 0.9rem; font-family: inherit; resize: none;"></textarea>
|
| 339 |
+
<button id="sendChatBtn" class="btn-primary btn-send" style="padding: 12px 20px; border-radius: var(--r-md); font-weight: 600;">Send ↑</button>
|
| 340 |
+
</div>
|
| 341 |
+
<div class="chat-hints" style="display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; font-size: 0.75rem; color: var(--text-secondary);">
|
| 342 |
+
<span class="chat-hint-chip" data-query="Show summary statistics for all numeric columns" style="background: var(--bg-card); border: 1px solid var(--border-low); padding: 4px 10px; border-radius: 12px; cursor: pointer; transition: all 0.2s;">📊 Summary Stats</span>
|
| 343 |
+
<span class="chat-hint-chip" data-query="Plot a distribution histogram of the first column with custom color config" style="background: var(--bg-card); border: 1px solid var(--border-low); padding: 4px 10px; border-radius: 12px; cursor: pointer; transition: all 0.2s;">📉 Custom Histogram</span>
|
| 344 |
+
<span class="chat-hint-chip" data-query="Rename column..." style="background: var(--bg-card); border: 1px solid var(--border-low); padding: 4px 10px; border-radius: 12px; cursor: pointer; transition: all 0.2s;">✏️ Rename Column</span>
|
| 345 |
+
<span class="chat-hint-chip" data-query="Delete column..." style="background: var(--bg-card); border: 1px solid var(--border-low); padding: 4px 10px; border-radius: 12px; cursor: pointer; transition: all 0.2s;">🗑️ Delete Column</span>
|
| 346 |
+
</div>
|
| 347 |
+
</div>
|
| 348 |
</div>
|
|
|
|
| 349 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
</div>
|
| 351 |
+
</div>
|
| 352 |
|
| 353 |
+
<!-- ───────────────── SECTION 2: AGENTIC ANALYSIS ───────────────── -->
|
| 354 |
+
<div id="areaAgentic" class="section-area hidden">
|
| 355 |
+
<!-- Run Analysis Placeholder -->
|
| 356 |
+
<div id="agenticPlaceholder" class="hidden" style="text-align: center; padding: 60px 20px; background: rgba(24, 24, 27, 0.4); border: 1px dashed var(--border-mid); border-radius: var(--r-lg); max-width: 580px; margin: 40px auto; display: flex; flex-direction: column; align-items: center; gap: 16px; backdrop-filter: blur(12px);">
|
| 357 |
+
<div style="font-size: 3rem; animation: pulse 2s infinite;">🤖</div>
|
| 358 |
+
<h3 style="margin: 0; font-size: 1.2rem; font-weight: 700; color: #fff;">Automated Pipeline Analysis</h3>
|
| 359 |
+
<p style="color: var(--text-secondary); line-height: 1.5; margin: 0; font-size: 0.9rem;">
|
| 360 |
+
Trigger autonomous AI agents to perform data cleaning, schema relationship mapping, and surface consulting-grade insights.
|
| 361 |
+
</p>
|
| 362 |
+
<button id="btnRunAgenticPipeline" class="btn-primary btn-lg" style="margin-top: 10px; font-weight: 600;">▶ Configure & Run Analysis</button>
|
| 363 |
</div>
|
| 364 |
|
| 365 |
+
<!-- Tabs -->
|
| 366 |
+
<div class="tabs-bar" id="agenticTabsBar" style="margin-bottom: 16px;">
|
| 367 |
+
<button class="tab-btn active" data-tab="cleaning">🧹 Cleaning Report</button>
|
| 368 |
+
<button class="tab-btn" data-tab="relations">🔗 Relation Mapper</button>
|
| 369 |
+
<button class="tab-btn" data-tab="insights">💡 Business Insights</button>
|
| 370 |
+
<button class="tab-btn" data-tab="charts">📈 Visualizations</button>
|
| 371 |
</div>
|
| 372 |
|
| 373 |
+
<!-- Tab panels -->
|
| 374 |
+
<div class="tab-panels" id="agenticTabPanels">
|
| 375 |
+
<!-- CLEANING REPORT -->
|
| 376 |
+
<div class="tab-panel active" id="panel-cleaning">
|
| 377 |
+
<h3 class="panel-title">🧹 Data Cleaning Audit Trail</h3>
|
| 378 |
+
<div id="cleaningContent" class="cleaning-list"></div>
|
| 379 |
+
</div>
|
| 380 |
|
| 381 |
+
<!-- RELATIONS -->
|
| 382 |
+
<div class="tab-panel" id="panel-relations">
|
| 383 |
+
<h3 class="panel-title">🔗 Relationship Mapping</h3>
|
| 384 |
+
<div id="relationsContent" class="relations-grid"></div>
|
| 385 |
</div>
|
| 386 |
|
| 387 |
+
<!-- INSIGHTS -->
|
| 388 |
+
<div class="tab-panel" id="panel-insights">
|
| 389 |
+
<h3 class="panel-title">💡 Strategic Business Insights</h3>
|
| 390 |
+
<div id="insightsContent" class="insights-list"></div>
|
| 391 |
+
</div>
|
|
|
|
| 392 |
|
| 393 |
+
<!-- CHARTS -->
|
| 394 |
+
<div class="tab-panel" id="panel-charts">
|
| 395 |
+
<h3 class="panel-title">📈 Visual Intelligence</h3>
|
| 396 |
+
<div id="plotlyChartsWrap" class="charts-grid"></div>
|
| 397 |
+
<div id="pngChartsWrap" class="png-charts-wrap hidden">
|
| 398 |
+
<h4 class="sub-title">🖼 Agent-Generated Charts</h4>
|
| 399 |
+
<div id="pngCharts" class="png-grid"></div>
|
| 400 |
+
</div>
|
| 401 |
+
<details class="viz-code-details hidden" id="vizCodeDetails">
|
| 402 |
+
<summary>⚙️ Visualization Architecture Code</summary>
|
| 403 |
+
<pre id="vizCodeBlock" class="code-block"></pre>
|
| 404 |
+
</details>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
</div>
|
| 406 |
</div>
|
| 407 |
+
</div>
|
| 408 |
|
| 409 |
+
<!-- Export/action bar -->
|
| 410 |
+
<div class="export-bar" style="margin-top: 24px;">
|
|
|
|
|
|
|
| 411 |
<button id="exportPdfBtn" class="btn-primary">📄 Export PDF Report</button>
|
| 412 |
+
<button id="exportZipBtn" class="btn-secondary" style="background: rgba(124,58,237,0.06); border-color: var(--violet-light); color: var(--violet-light); font-weight: 600;">📦 Export Project (.zip)</button>
|
| 413 |
<button id="downloadCsvBtn" class="btn-secondary">📥 Download Cleaned CSV</button>
|
| 414 |
+
<button id="reRunBtn" class="btn-ghost">🔄 Re-run Agentic Analysis</button>
|
| 415 |
</div>
|
| 416 |
|
| 417 |
</section><!-- /results screen -->
|
| 418 |
|
| 419 |
</main><!-- /main-area -->
|
| 420 |
|
| 421 |
+
<!-- Schema Relation Modal -->
|
| 422 |
+
<div id="relationModal" class="modal-overlay hidden">
|
| 423 |
+
<div class="modal-box" style="max-width: 480px; padding: 24px; border: 1px solid rgba(34, 211, 238, 0.3); background: rgba(18, 18, 22, 0.98); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border-radius: var(--r-md);">
|
| 424 |
+
<div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
| 425 |
+
<h2 id="relationModalTitle" style="margin: 0; font-size: 1.15rem; font-weight: 700; color: #fff;">Tweak Relationship</h2>
|
| 426 |
+
<button id="closeRelationModalBtn" style="background: transparent; border: none; font-size: 1.25rem; cursor: pointer; color: var(--text-secondary); width: auto; padding: 0;">✕</button>
|
| 427 |
+
</div>
|
| 428 |
+
<div class="modal-body" style="display: flex; flex-direction: column; gap: 14px; text-align: left;">
|
| 429 |
+
<div class="field-group" style="display: flex; flex-direction: column; gap: 4px;">
|
| 430 |
+
<label class="field-label" style="font-weight: 600; color: var(--text-secondary); font-size: 0.82rem;">X Variable (Source Column)</label>
|
| 431 |
+
<select id="relationModalXSelect" class="field-input" style="width:100%; box-sizing:border-box; background:var(--bg-card); color:var(--text-primary); border:1px solid var(--border-mid); border-radius:var(--r-sm); padding:8px;"></select>
|
| 432 |
+
</div>
|
| 433 |
+
<div class="field-group" style="display: flex; flex-direction: column; gap: 4px;">
|
| 434 |
+
<label class="field-label" style="font-weight: 600; color: var(--text-secondary); font-size: 0.82rem;">Y Variable (Target Column)</label>
|
| 435 |
+
<select id="relationModalYSelect" class="field-input" style="width:100%; box-sizing:border-box; background:var(--bg-card); color:var(--text-primary); border:1px solid var(--border-mid); border-radius:var(--r-sm); padding:8px;"></select>
|
| 436 |
+
</div>
|
| 437 |
+
<div class="field-group" style="display: flex; flex-direction: column; gap: 4px;">
|
| 438 |
+
<label class="field-label" style="font-weight: 600; color: var(--text-secondary); font-size: 0.82rem;">Visual Plot Recommendation</label>
|
| 439 |
+
<select id="relationModalTypeSelect" class="field-input" style="width:100%; box-sizing:border-box; background:var(--bg-card); color:var(--text-primary); border:1px solid var(--border-mid); border-radius:var(--r-sm); padding:8px;">
|
| 440 |
+
<option value="Scatter Plot">Scatter Plot</option>
|
| 441 |
+
<option value="Bar Chart">Bar Chart</option>
|
| 442 |
+
<option value="Line Chart">Line Chart</option>
|
| 443 |
+
<option value="Box Plot">Box Plot</option>
|
| 444 |
+
<option value="Histogram">Histogram</option>
|
| 445 |
+
<option value="Heatmap">Heatmap</option>
|
| 446 |
+
</select>
|
| 447 |
+
</div>
|
| 448 |
+
<div class="field-group" style="display: flex; flex-direction: column; gap: 4px;">
|
| 449 |
+
<label class="field-label" style="font-weight: 600; color: var(--text-secondary); font-size: 0.82rem;">Correlation & Statistical Details</label>
|
| 450 |
+
<textarea id="relationModalDetails" class="field-input" rows="3" placeholder="Explain the relationship correlation or domain relevance..." style="width:100%; box-sizing:border-box; font-family:inherit; resize:none; padding:8px; background:var(--bg-card); color:var(--text-primary); border:1px solid var(--border-mid); border-radius:var(--r-sm);"></textarea>
|
| 451 |
+
</div>
|
| 452 |
+
</div>
|
| 453 |
+
<div class="modal-footer" style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px;">
|
| 454 |
+
<button id="relationModalCancelBtn" class="btn-secondary" style="padding: 8px 16px;">Cancel</button>
|
| 455 |
+
<button id="relationModalConfirmBtn" class="btn-primary" style="padding: 8px 16px;">Apply Changes</button>
|
| 456 |
+
</div>
|
| 457 |
+
</div>
|
| 458 |
+
</div>
|
| 459 |
+
|
| 460 |
<!-- API Warning Modal -->
|
| 461 |
<div id="apiWarningModal" class="modal-overlay hidden">
|
| 462 |
<div class="modal-box" style="max-width: 420px; text-align: center; padding: 30px;">
|
web/style.css
CHANGED
|
@@ -576,45 +576,77 @@ html, body {
|
|
| 576 |
display: flex;
|
| 577 |
align-items: center;
|
| 578 |
gap: 0;
|
| 579 |
-
background:
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
|
|
|
|
|
|
| 583 |
overflow-x: auto;
|
|
|
|
| 584 |
}
|
| 585 |
.stage-item {
|
| 586 |
display: flex;
|
| 587 |
flex-direction: column;
|
| 588 |
align-items: center;
|
| 589 |
-
gap:
|
| 590 |
flex: 1;
|
| 591 |
position: relative;
|
| 592 |
}
|
| 593 |
.stage-item:not(:last-child)::after {
|
| 594 |
content: '';
|
| 595 |
position: absolute;
|
| 596 |
-
top:
|
| 597 |
-
left: calc(50% +
|
| 598 |
-
right: calc(-50% +
|
| 599 |
-
height:
|
| 600 |
-
background:
|
| 601 |
z-index: 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 602 |
}
|
| 603 |
-
.stage-item.done:not(:last-child)::after { background: var(--emerald); }
|
| 604 |
-
.stage-item.active:not(:last-child)::after { background: linear-gradient(to right, var(--violet-light), var(--border-mid)); }
|
| 605 |
.stage-dot {
|
| 606 |
-
width:
|
|
|
|
| 607 |
border-radius: 50%;
|
| 608 |
-
background: var(--bg-
|
| 609 |
-
border:
|
| 610 |
z-index: 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 611 |
transition: all 0.3s;
|
| 612 |
}
|
| 613 |
-
.stage-item.done
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
.stage-item.active .stage-label {
|
|
|
|
|
|
|
|
|
|
| 618 |
/* ── Stage POV Panel ────────────────────────────────── */
|
| 619 |
.stage-pov-panel {
|
| 620 |
background: rgba(22, 22, 25, 0.45);
|
|
@@ -719,11 +751,19 @@ html, body {
|
|
| 719 |
height: 100px;
|
| 720 |
overflow: visible;
|
| 721 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 722 |
.pov-node {
|
| 723 |
fill: rgba(15, 15, 18, 0.9);
|
| 724 |
stroke: var(--violet-light);
|
| 725 |
stroke-width: 1.5px;
|
| 726 |
transition: all 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
}
|
| 728 |
.pov-node:hover {
|
| 729 |
stroke: var(--cyan);
|
|
@@ -893,6 +933,18 @@ html, body {
|
|
| 893 |
0% { height: 20%; }
|
| 894 |
100% { height: 85%; }
|
| 895 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 896 |
|
| 897 |
.log-panel {
|
| 898 |
background: var(--bg-surface);
|
|
@@ -1812,16 +1864,16 @@ html, body {
|
|
| 1812 |
justify-content: center;
|
| 1813 |
border: 1px dashed rgba(124, 58, 237, 0.35);
|
| 1814 |
border-radius: var(--r-xl);
|
| 1815 |
-
padding:
|
| 1816 |
cursor: pointer;
|
| 1817 |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 1818 |
background: rgba(18, 18, 22, 0.4);
|
| 1819 |
backdrop-filter: blur(8px);
|
| 1820 |
-webkit-backdrop-filter: blur(8px);
|
| 1821 |
-
min-height:
|
| 1822 |
-
width:
|
| 1823 |
-
max-width:
|
| 1824 |
-
margin: 0
|
| 1825 |
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2);
|
| 1826 |
}
|
| 1827 |
.create-project-card:hover {
|
|
@@ -1911,4 +1963,213 @@ html, body {
|
|
| 1911 |
}
|
| 1912 |
|
| 1913 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1914 |
|
|
|
|
| 576 |
display: flex;
|
| 577 |
align-items: center;
|
| 578 |
gap: 0;
|
| 579 |
+
background: rgba(30, 30, 35, 0.65);
|
| 580 |
+
backdrop-filter: blur(12px);
|
| 581 |
+
-webkit-backdrop-filter: blur(12px);
|
| 582 |
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
| 583 |
+
border-radius: var(--r-xl);
|
| 584 |
+
padding: 20px 24px;
|
| 585 |
overflow-x: auto;
|
| 586 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), inset 0 1px 1px rgba(255, 255, 255, 0.05);
|
| 587 |
}
|
| 588 |
.stage-item {
|
| 589 |
display: flex;
|
| 590 |
flex-direction: column;
|
| 591 |
align-items: center;
|
| 592 |
+
gap: 8px;
|
| 593 |
flex: 1;
|
| 594 |
position: relative;
|
| 595 |
}
|
| 596 |
.stage-item:not(:last-child)::after {
|
| 597 |
content: '';
|
| 598 |
position: absolute;
|
| 599 |
+
top: 9px;
|
| 600 |
+
left: calc(50% + 15px);
|
| 601 |
+
right: calc(-50% + 15px);
|
| 602 |
+
height: 3px;
|
| 603 |
+
background: rgba(255, 255, 255, 0.05);
|
| 604 |
z-index: 0;
|
| 605 |
+
border-radius: 99px;
|
| 606 |
+
transition: all 0.4s ease;
|
| 607 |
+
}
|
| 608 |
+
.stage-item.done:not(:last-child)::after {
|
| 609 |
+
background: var(--emerald);
|
| 610 |
+
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
|
| 611 |
+
}
|
| 612 |
+
.stage-item.active:not(:last-child)::after {
|
| 613 |
+
background: linear-gradient(90deg, var(--violet-light) 0%, rgba(255, 255, 255, 0.05) 100%);
|
| 614 |
}
|
|
|
|
|
|
|
| 615 |
.stage-dot {
|
| 616 |
+
width: 20px;
|
| 617 |
+
height: 20px;
|
| 618 |
border-radius: 50%;
|
| 619 |
+
background: var(--bg-surface);
|
| 620 |
+
border: 2.5px solid var(--border-mid);
|
| 621 |
z-index: 1;
|
| 622 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 623 |
+
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.2);
|
| 624 |
+
}
|
| 625 |
+
.stage-item.done .stage-dot {
|
| 626 |
+
background: var(--emerald);
|
| 627 |
+
border-color: var(--emerald);
|
| 628 |
+
box-shadow: 0 0 14px rgba(16, 185, 129, 0.4);
|
| 629 |
+
}
|
| 630 |
+
.stage-item.active .stage-dot {
|
| 631 |
+
background: var(--violet-light);
|
| 632 |
+
border-color: var(--violet-light);
|
| 633 |
+
animation: stageDotActivePulse 1.4s infinite alternate;
|
| 634 |
+
}
|
| 635 |
+
.stage-label {
|
| 636 |
+
font-size: 0.75rem;
|
| 637 |
+
color: var(--text-muted);
|
| 638 |
+
font-weight: 600;
|
| 639 |
+
letter-spacing: 0.02em;
|
| 640 |
transition: all 0.3s;
|
| 641 |
}
|
| 642 |
+
.stage-item.done .stage-label {
|
| 643 |
+
color: var(--emerald);
|
| 644 |
+
text-shadow: 0 0 8px rgba(16, 185, 129, 0.2);
|
| 645 |
+
}
|
| 646 |
+
.stage-item.active .stage-label {
|
| 647 |
+
color: var(--violet-light);
|
| 648 |
+
text-shadow: 0 0 8px rgba(167, 139, 250, 0.3);
|
| 649 |
+
}
|
| 650 |
/* ── Stage POV Panel ────────────────────────────────── */
|
| 651 |
.stage-pov-panel {
|
| 652 |
background: rgba(22, 22, 25, 0.45);
|
|
|
|
| 751 |
height: 100px;
|
| 752 |
overflow: visible;
|
| 753 |
}
|
| 754 |
+
.pov-chords-rotate {
|
| 755 |
+
transform-origin: 80px 50px;
|
| 756 |
+
animation: rotateChords 25s linear infinite;
|
| 757 |
+
}
|
| 758 |
.pov-node {
|
| 759 |
fill: rgba(15, 15, 18, 0.9);
|
| 760 |
stroke: var(--violet-light);
|
| 761 |
stroke-width: 1.5px;
|
| 762 |
transition: all 0.3s;
|
| 763 |
+
animation: nodePulse 2.4s ease-in-out infinite alternate;
|
| 764 |
+
}
|
| 765 |
+
.pov-node:nth-child(even) {
|
| 766 |
+
animation-delay: 1.2s;
|
| 767 |
}
|
| 768 |
.pov-node:hover {
|
| 769 |
stroke: var(--cyan);
|
|
|
|
| 933 |
0% { height: 20%; }
|
| 934 |
100% { height: 85%; }
|
| 935 |
}
|
| 936 |
+
@keyframes stageDotActivePulse {
|
| 937 |
+
0% { transform: scale(0.95); box-shadow: 0 0 6px var(--violet-light); }
|
| 938 |
+
100% { transform: scale(1.15); box-shadow: 0 0 16px var(--violet-light), 0 0 4px var(--violet-light); }
|
| 939 |
+
}
|
| 940 |
+
@keyframes rotateChords {
|
| 941 |
+
from { transform: rotate(0deg); }
|
| 942 |
+
to { transform: rotate(360deg); }
|
| 943 |
+
}
|
| 944 |
+
@keyframes nodePulse {
|
| 945 |
+
0% { r: 3.5px; fill: rgba(124, 58, 237, 0.5); filter: drop-shadow(0 0 1px var(--violet-light)); }
|
| 946 |
+
100% { r: 5.5px; fill: var(--cyan); filter: drop-shadow(0 0 5px var(--cyan)); }
|
| 947 |
+
}
|
| 948 |
|
| 949 |
.log-panel {
|
| 950 |
background: var(--bg-surface);
|
|
|
|
| 1864 |
justify-content: center;
|
| 1865 |
border: 1px dashed rgba(124, 58, 237, 0.35);
|
| 1866 |
border-radius: var(--r-xl);
|
| 1867 |
+
padding: 30px 24px;
|
| 1868 |
cursor: pointer;
|
| 1869 |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 1870 |
background: rgba(18, 18, 22, 0.4);
|
| 1871 |
backdrop-filter: blur(8px);
|
| 1872 |
-webkit-backdrop-filter: blur(8px);
|
| 1873 |
+
min-height: 180px;
|
| 1874 |
+
width: 260px;
|
| 1875 |
+
max-width: 280px;
|
| 1876 |
+
margin: 0;
|
| 1877 |
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2);
|
| 1878 |
}
|
| 1879 |
.create-project-card:hover {
|
|
|
|
| 1963 |
}
|
| 1964 |
|
| 1965 |
|
| 1966 |
+
/* ── Custom Platform Enhancements ──��──────────────────── */
|
| 1967 |
+
.hidden {
|
| 1968 |
+
display: none !important;
|
| 1969 |
+
}
|
| 1970 |
+
|
| 1971 |
+
.section-area {
|
| 1972 |
+
display: block;
|
| 1973 |
+
}
|
| 1974 |
+
.section-area.hidden {
|
| 1975 |
+
display: none !important;
|
| 1976 |
+
}
|
| 1977 |
+
|
| 1978 |
+
.sections-switch {
|
| 1979 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
|
| 1980 |
+
}
|
| 1981 |
+
|
| 1982 |
+
.section-switch-btn {
|
| 1983 |
+
font-family: var(--font-body);
|
| 1984 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 1985 |
+
}
|
| 1986 |
+
|
| 1987 |
+
.section-switch-btn.active {
|
| 1988 |
+
background: var(--violet) !important;
|
| 1989 |
+
color: #ffffff !important;
|
| 1990 |
+
box-shadow: 0 0 16px rgba(124, 58, 237, 0.4);
|
| 1991 |
+
}
|
| 1992 |
+
|
| 1993 |
+
/* Relation Schema Mapper Styling */
|
| 1994 |
+
.relation-mapper-container {
|
| 1995 |
+
display: flex;
|
| 1996 |
+
flex-direction: column;
|
| 1997 |
+
gap: 16px;
|
| 1998 |
+
width: 100%;
|
| 1999 |
+
}
|
| 2000 |
+
|
| 2001 |
+
.relation-card {
|
| 2002 |
+
background: var(--bg-card);
|
| 2003 |
+
border: 1px solid var(--border-mid);
|
| 2004 |
+
border-radius: var(--r-md);
|
| 2005 |
+
padding: 16px;
|
| 2006 |
+
display: flex;
|
| 2007 |
+
align-items: center;
|
| 2008 |
+
gap: 16px;
|
| 2009 |
+
transition: all 0.25s ease;
|
| 2010 |
+
position: relative;
|
| 2011 |
+
}
|
| 2012 |
+
|
| 2013 |
+
.relation-card:hover {
|
| 2014 |
+
transform: translateY(-2px);
|
| 2015 |
+
border-color: var(--violet-light);
|
| 2016 |
+
box-shadow: 0 4px 20px rgba(124, 58, 237, 0.15);
|
| 2017 |
+
}
|
| 2018 |
+
|
| 2019 |
+
.relation-node {
|
| 2020 |
+
background: rgba(255, 255, 255, 0.03);
|
| 2021 |
+
border: 1px solid var(--border-low);
|
| 2022 |
+
padding: 10px 14px;
|
| 2023 |
+
border-radius: var(--r-sm);
|
| 2024 |
+
font-family: var(--font-mono);
|
| 2025 |
+
font-size: 0.88rem;
|
| 2026 |
+
color: var(--text-primary);
|
| 2027 |
+
display: flex;
|
| 2028 |
+
flex-direction: column;
|
| 2029 |
+
gap: 2px;
|
| 2030 |
+
min-width: 150px;
|
| 2031 |
+
text-align: center;
|
| 2032 |
+
}
|
| 2033 |
+
|
| 2034 |
+
.relation-node-type {
|
| 2035 |
+
font-size: 0.7rem;
|
| 2036 |
+
color: var(--text-secondary);
|
| 2037 |
+
text-transform: uppercase;
|
| 2038 |
+
}
|
| 2039 |
+
|
| 2040 |
+
.relation-connector {
|
| 2041 |
+
flex: 1;
|
| 2042 |
+
display: flex;
|
| 2043 |
+
flex-direction: column;
|
| 2044 |
+
align-items: center;
|
| 2045 |
+
justify-content: center;
|
| 2046 |
+
position: relative;
|
| 2047 |
+
min-width: 120px;
|
| 2048 |
+
}
|
| 2049 |
+
|
| 2050 |
+
.relation-line {
|
| 2051 |
+
height: 2px;
|
| 2052 |
+
background: linear-gradient(90deg, var(--violet) 0%, var(--cyan) 100%);
|
| 2053 |
+
width: 100%;
|
| 2054 |
+
border-radius: 1px;
|
| 2055 |
+
position: relative;
|
| 2056 |
+
}
|
| 2057 |
+
|
| 2058 |
+
.relation-line::after {
|
| 2059 |
+
content: '';
|
| 2060 |
+
position: absolute;
|
| 2061 |
+
right: 0;
|
| 2062 |
+
top: -4px;
|
| 2063 |
+
width: 0;
|
| 2064 |
+
height: 0;
|
| 2065 |
+
border-top: 5px solid transparent;
|
| 2066 |
+
border-bottom: 5px solid transparent;
|
| 2067 |
+
border-left: 8px solid var(--cyan);
|
| 2068 |
+
}
|
| 2069 |
+
|
| 2070 |
+
.relation-info {
|
| 2071 |
+
font-size: 0.76rem;
|
| 2072 |
+
color: var(--text-secondary);
|
| 2073 |
+
margin-top: 6px;
|
| 2074 |
+
text-align: center;
|
| 2075 |
+
line-height: 1.4;
|
| 2076 |
+
padding: 0 8px;
|
| 2077 |
+
}
|
| 2078 |
+
|
| 2079 |
+
/* Structured Insights Styling */
|
| 2080 |
+
.insight-header-card {
|
| 2081 |
+
background: linear-gradient(135deg, rgba(124, 58, 237, 0.08) 0%, rgba(34, 211, 238, 0.04) 100%);
|
| 2082 |
+
border: 1px solid rgba(124, 58, 237, 0.2);
|
| 2083 |
+
border-radius: var(--r-lg);
|
| 2084 |
+
padding: 20px;
|
| 2085 |
+
margin-bottom: 24px;
|
| 2086 |
+
}
|
| 2087 |
+
|
| 2088 |
+
.insight-header-title {
|
| 2089 |
+
font-size: 1.1rem;
|
| 2090 |
+
font-weight: 700;
|
| 2091 |
+
color: var(--violet-light);
|
| 2092 |
+
margin-bottom: 8px;
|
| 2093 |
+
display: flex;
|
| 2094 |
+
align-items: center;
|
| 2095 |
+
gap: 8px;
|
| 2096 |
+
}
|
| 2097 |
+
|
| 2098 |
+
.insight-header-text {
|
| 2099 |
+
font-size: 0.95rem;
|
| 2100 |
+
line-height: 1.6;
|
| 2101 |
+
color: var(--text-primary);
|
| 2102 |
+
}
|
| 2103 |
+
|
| 2104 |
+
.insight-stats-grid {
|
| 2105 |
+
display: grid;
|
| 2106 |
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
| 2107 |
+
gap: 16px;
|
| 2108 |
+
margin-bottom: 24px;
|
| 2109 |
+
}
|
| 2110 |
+
|
| 2111 |
+
.insight-stat-card {
|
| 2112 |
+
background: var(--bg-card);
|
| 2113 |
+
border: 1px solid var(--border-mid);
|
| 2114 |
+
border-radius: var(--r-md);
|
| 2115 |
+
padding: 16px;
|
| 2116 |
+
text-align: center;
|
| 2117 |
+
transition: all 0.25s ease;
|
| 2118 |
+
}
|
| 2119 |
+
|
| 2120 |
+
.insight-stat-card:hover {
|
| 2121 |
+
border-color: var(--cyan);
|
| 2122 |
+
box-shadow: 0 4px 15px rgba(34, 211, 238, 0.1);
|
| 2123 |
+
}
|
| 2124 |
+
|
| 2125 |
+
.insight-stat-val {
|
| 2126 |
+
font-family: var(--font-mono);
|
| 2127 |
+
font-size: 1.6rem;
|
| 2128 |
+
font-weight: 700;
|
| 2129 |
+
color: var(--cyan);
|
| 2130 |
+
margin-bottom: 4px;
|
| 2131 |
+
}
|
| 2132 |
+
|
| 2133 |
+
.insight-stat-lbl {
|
| 2134 |
+
font-size: 0.8rem;
|
| 2135 |
+
color: var(--text-secondary);
|
| 2136 |
+
font-weight: 600;
|
| 2137 |
+
}
|
| 2138 |
+
|
| 2139 |
+
.insight-warning-card {
|
| 2140 |
+
background: rgba(244, 63, 94, 0.03);
|
| 2141 |
+
border: 1px solid rgba(244, 63, 94, 0.25);
|
| 2142 |
+
border-radius: var(--r-md);
|
| 2143 |
+
padding: 16px;
|
| 2144 |
+
margin-top: 24px;
|
| 2145 |
+
display: flex;
|
| 2146 |
+
gap: 12px;
|
| 2147 |
+
align-items: flex-start;
|
| 2148 |
+
}
|
| 2149 |
+
|
| 2150 |
+
.insight-warning-card-icon {
|
| 2151 |
+
font-size: 1.4rem;
|
| 2152 |
+
color: var(--rose);
|
| 2153 |
+
line-height: 1;
|
| 2154 |
+
}
|
| 2155 |
+
|
| 2156 |
+
.insight-warning-card-body {
|
| 2157 |
+
display: flex;
|
| 2158 |
+
flex-direction: column;
|
| 2159 |
+
gap: 6px;
|
| 2160 |
+
}
|
| 2161 |
+
|
| 2162 |
+
.insight-warning-card-title {
|
| 2163 |
+
font-size: 0.95rem;
|
| 2164 |
+
font-weight: 700;
|
| 2165 |
+
color: #ff8b9e;
|
| 2166 |
+
}
|
| 2167 |
+
|
| 2168 |
+
.insight-warning-card-text {
|
| 2169 |
+
font-size: 0.88rem;
|
| 2170 |
+
line-height: 1.5;
|
| 2171 |
+
color: var(--text-secondary);
|
| 2172 |
+
}
|
| 2173 |
+
|
| 2174 |
+
|
| 2175 |
|
workflows/pipeline.py
CHANGED
|
@@ -73,23 +73,21 @@ def make_cooldown_callback(min_sleep: int = 5):
|
|
| 73 |
# Pipeline factory
|
| 74 |
# ---------------------------------------------------------------------------
|
| 75 |
|
| 76 |
-
def make_pipeline(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
"""
|
| 78 |
Build and return (agents, tasks) for a single analysis run.
|
| 79 |
|
| 80 |
Every call creates fresh agent instances (picks up the latest LLM config)
|
| 81 |
and embeds the session-specific CSV and output paths + dataset profile
|
| 82 |
into each task description.
|
| 83 |
-
|
| 84 |
-
Parameters
|
| 85 |
-
----------
|
| 86 |
-
session_id : Short unique string used to isolate per-user data.
|
| 87 |
-
profile : Pre-computed dataset profile string from build_dataset_profile().
|
| 88 |
-
|
| 89 |
-
Returns
|
| 90 |
-
-------
|
| 91 |
-
agents : list[Agent]
|
| 92 |
-
tasks : list[Task]
|
| 93 |
"""
|
| 94 |
csv_path = f"data/sessions/{session_id}/cleaned.csv"
|
| 95 |
output_dir = f"outputs/{session_id}"
|
|
@@ -114,8 +112,12 @@ def make_pipeline(session_id: str, profile: str = "", selected_tasks: Optional[l
|
|
| 114 |
visualizer_agent = make_visualizer_agent()
|
| 115 |
|
| 116 |
deep_prompt = "\n\nIf deep analysis mode is enabled, provide richer reasoning, deeper causal exploration, and more detailed business implications for each recommendation." if deep_analysis else ""
|
|
|
|
|
|
|
|
|
|
| 117 |
cleaning_prompt = (
|
| 118 |
f"The dataset working copy is at '{csv_path}'. "
|
|
|
|
| 119 |
"Identify data quality issues from the profile below, then write and run "
|
| 120 |
"Python cleaning code using 'Clean Dataset with Python Code' to fix them. "
|
| 121 |
"Explain the business rationale of each cleaning step in the final report."
|
|
@@ -133,59 +135,85 @@ def make_pipeline(session_id: str, profile: str = "", selected_tasks: Optional[l
|
|
| 133 |
callback=cb,
|
| 134 |
)
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
relation_task = Task(
|
| 137 |
agent=relation_agent,
|
| 138 |
-
description=
|
| 139 |
-
f"Using the dataset profile below, identify 5 key relationships among the "
|
| 140 |
-
f"columns of '{csv_path}' that carry significant business meaning. "
|
| 141 |
-
"Format the output STRICTLY as:\n"
|
| 142 |
-
"- X: [Column1] | Y: [Column2] | Type: [PlotType]\n"
|
| 143 |
-
"Output NOTHING else."
|
| 144 |
-
f"{profile_block}"
|
| 145 |
-
),
|
| 146 |
expected_output=(
|
| 147 |
"Strictly formatted list of relationships. Example:\n"
|
| 148 |
-
"- X: Age | Y: Income | Type: Scatter Plot"
|
| 149 |
),
|
| 150 |
callback=cb,
|
| 151 |
)
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
insight_task = Task(
|
| 154 |
agent=insights_agent,
|
| 155 |
-
description=
|
| 156 |
-
|
| 157 |
-
"generate 5 key business insights. Each insight MUST strictly use this structure:\n"
|
| 158 |
-
"1. **Observation**: [Describe the exact trend, anomaly, or correlation from the data]\n"
|
| 159 |
-
" **Business Implication**: [Explain how this impacts profitability, risk, operation, or customers]\n"
|
| 160 |
-
" **Actionable Strategy**: [Outline a concrete recommendation that the organization should implement immediately]\n"
|
| 161 |
-
"Provide highly context-specific, professional management-consultant level insights. Avoid generic fillers."
|
| 162 |
-
f"{profile_block}"
|
| 163 |
-
),
|
| 164 |
-
expected_output="Plain-text numbered list of 5 business insights in the mandated structure.",
|
| 165 |
callback=cb,
|
| 166 |
)
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
visualize_task = Task(
|
| 169 |
agent=visualizer_agent,
|
| 170 |
-
description=
|
| 171 |
-
"Examine the columns and data types from the profile below. Using your AI reasoning, select "
|
| 172 |
-
"the 3-4 most insightful relationships, trends, or distributions that characterize this specific dataset.\n"
|
| 173 |
-
"Then, write and execute Python plotting code using 'Execute Visualization Code'.\n\n"
|
| 174 |
-
"ENVIRONMENT NOTE:\n"
|
| 175 |
-
"- The pandas DataFrame is pre-loaded as `df` in your execution environment.\n"
|
| 176 |
-
"- Pre-defined variable `OUTPUT_DIR` contains the target output folder path.\n"
|
| 177 |
-
"- A helper function `save_chart(filename_string)` is available to save the current figure.\n"
|
| 178 |
-
"- Matplotlib and Seaborn are pre-imported. Do NOT load CSVs or create folders yourself!\n\n"
|
| 179 |
-
"CODE REQUIREMENTS:\n"
|
| 180 |
-
"- Set style theme: 'sns.set_theme(style=\"whitegrid\", palette=\"muted\")'\n"
|
| 181 |
-
"- Use high-end palette hex colors (e.g. `#6366f1` for Indigo, `#06b6d4` for Teal, `#ec4899` for Pink, `#10b981` for Emerald).\n"
|
| 182 |
-
"- Set figure size to `(10, 6)` or `(12, 6)`.\n"
|
| 183 |
-
"- Set clear, descriptive titles and wrap long titles: 'plt.title(textwrap.fill(title, 40))'.\n"
|
| 184 |
-
"- Apply 'sns.despine(left=True, bottom=True)' to remove borders.\n"
|
| 185 |
-
"- Save each plot with: `save_chart('chart_name')`.\n"
|
| 186 |
-
"- Call `plt.close()` immediately after each save to clear the state."
|
| 187 |
-
f"{profile_block}"
|
| 188 |
-
),
|
| 189 |
expected_output="Summary of the 3-4 custom visualization plots generated and saved.",
|
| 190 |
callback=cb,
|
| 191 |
)
|
|
|
|
| 73 |
# Pipeline factory
|
| 74 |
# ---------------------------------------------------------------------------
|
| 75 |
|
| 76 |
+
def make_pipeline(
|
| 77 |
+
session_id: str,
|
| 78 |
+
profile: str = "",
|
| 79 |
+
selected_tasks: Optional[list[str]] = None,
|
| 80 |
+
deep_analysis: bool = False,
|
| 81 |
+
project_goal: str = "",
|
| 82 |
+
report_title: str = "",
|
| 83 |
+
existing_relations: str = "",
|
| 84 |
+
) -> tuple[list, list]:
|
| 85 |
"""
|
| 86 |
Build and return (agents, tasks) for a single analysis run.
|
| 87 |
|
| 88 |
Every call creates fresh agent instances (picks up the latest LLM config)
|
| 89 |
and embeds the session-specific CSV and output paths + dataset profile
|
| 90 |
into each task description.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
"""
|
| 92 |
csv_path = f"data/sessions/{session_id}/cleaned.csv"
|
| 93 |
output_dir = f"outputs/{session_id}"
|
|
|
|
| 112 |
visualizer_agent = make_visualizer_agent()
|
| 113 |
|
| 114 |
deep_prompt = "\n\nIf deep analysis mode is enabled, provide richer reasoning, deeper causal exploration, and more detailed business implications for each recommendation." if deep_analysis else ""
|
| 115 |
+
|
| 116 |
+
goal_context = f"\nThe user has set the following goal for this project: '{project_goal}'." if project_goal else ""
|
| 117 |
+
|
| 118 |
cleaning_prompt = (
|
| 119 |
f"The dataset working copy is at '{csv_path}'. "
|
| 120 |
+
f"{goal_context} "
|
| 121 |
"Identify data quality issues from the profile below, then write and run "
|
| 122 |
"Python cleaning code using 'Clean Dataset with Python Code' to fix them. "
|
| 123 |
"Explain the business rationale of each cleaning step in the final report."
|
|
|
|
| 135 |
callback=cb,
|
| 136 |
)
|
| 137 |
|
| 138 |
+
relation_prompt = (
|
| 139 |
+
f"First, examine the 5 sample rows and column details in the dataset profile of '{csv_path}'. "
|
| 140 |
+
"Identify the data type of each column (e.g. Unique ID, Categorical, Continuous Numeric, Timestamp, Key Column). "
|
| 141 |
+
f"Then, identify 5 key column relationships with high business relevance, focusing on correlations or connections that align with the user's project goal: '{project_goal}'. "
|
| 142 |
+
"Format the output STRICTLY as:\n"
|
| 143 |
+
"- X: [Column1] | Y: [Column2] | Type: [PlotType] | Details: [Relationship details, data type mapping of both columns, and business relevance]\n"
|
| 144 |
+
"Output NOTHING else."
|
| 145 |
+
f"{profile_block}"
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
relation_task = Task(
|
| 149 |
agent=relation_agent,
|
| 150 |
+
description=relation_prompt,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
expected_output=(
|
| 152 |
"Strictly formatted list of relationships. Example:\n"
|
| 153 |
+
"- X: Age | Y: Income | Type: Scatter Plot | Details: Age (Continuous Numeric) vs Income (Continuous Numeric). Displays moderate positive correlation, key for targeting demographics."
|
| 154 |
),
|
| 155 |
callback=cb,
|
| 156 |
)
|
| 157 |
|
| 158 |
+
relations_context = f"\n\n--- TWEAKED SCHEMA & VERIFIED RELATIONSHIPS (use these verified columns & relationships to guide your analysis) ---\n{existing_relations}\n---" if existing_relations else ""
|
| 159 |
+
|
| 160 |
+
goal_prompt = f"Align all insights and strategies directly to address the project goal: '{project_goal}'." if project_goal else ""
|
| 161 |
+
insight_prompt = (
|
| 162 |
+
"Using the dataset profile and identified relationships provided below, "
|
| 163 |
+
"generate a structured report. "
|
| 164 |
+
f"{goal_prompt}\n"
|
| 165 |
+
"Format the report using markdown headers EXACTLY as follows:\n\n"
|
| 166 |
+
"### Objectives & Goals\n"
|
| 167 |
+
"[Describe the primary objective and business goals of this analysis based on the project goal and dataset profile]\n\n"
|
| 168 |
+
"### Dataset Statistics\n"
|
| 169 |
+
"- Total rows: [row count]\n"
|
| 170 |
+
"- Total columns: [col count]\n"
|
| 171 |
+
"- Numeric columns: [list numeric columns and their min/max values]\n"
|
| 172 |
+
"- Categorical columns: [list categorical columns]\n\n"
|
| 173 |
+
"### Strategic Insights\n"
|
| 174 |
+
"Generate 5 key business insights. Each insight MUST strictly use this structure:\n"
|
| 175 |
+
"1. **Observation**: [Describe the exact trend, anomaly, or correlation from the data]\n"
|
| 176 |
+
" **Business Implication**: [Explain how this impacts profitability, risk, operation, or customers]\n"
|
| 177 |
+
" **Actionable Strategy**: [Outline a concrete recommendation that the organization should implement immediately]\n\n"
|
| 178 |
+
"### Warnings & Alerts\n"
|
| 179 |
+
"[List any warning or alert (e.g. data quality issues, outlier presence, class imbalance, missing values, declining trends) that a data scientist or business user should be aware of]"
|
| 180 |
+
f"{relations_context}"
|
| 181 |
+
f"{profile_block}"
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
insight_task = Task(
|
| 185 |
agent=insights_agent,
|
| 186 |
+
description=insight_prompt,
|
| 187 |
+
expected_output="A structured report in markdown format containing Objectives & Goals, Dataset Statistics, Strategic Insights, and Warnings & Alerts.",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
callback=cb,
|
| 189 |
)
|
| 190 |
|
| 191 |
+
viz_goal_prompt = f"\nFocus visualizations on answering or addressing the project goal: '{project_goal}'." if project_goal else ""
|
| 192 |
+
visualize_prompt = (
|
| 193 |
+
"Examine the columns and data types from the profile below. Using your AI reasoning, select "
|
| 194 |
+
"the 3-4 most insightful relationships, trends, or distributions that characterize this specific dataset.\n"
|
| 195 |
+
f"{viz_goal_prompt}\n"
|
| 196 |
+
"Then, write and execute Python plotting code using 'Execute Visualization Code'.\n\n"
|
| 197 |
+
"ENVIRONMENT NOTE:\n"
|
| 198 |
+
"- The pandas DataFrame is pre-loaded as `df` in your execution environment.\n"
|
| 199 |
+
"- Pre-defined variable `OUTPUT_DIR` contains the target output folder path.\n"
|
| 200 |
+
"- A helper function `save_chart(filename_string)` is available to save the current figure.\n"
|
| 201 |
+
"- Matplotlib and Seaborn are pre-imported. Do NOT load CSVs or create folders yourself!\n\n"
|
| 202 |
+
"CODE REQUIREMENTS:\n"
|
| 203 |
+
"- Set style theme: 'sns.set_theme(style=\"whitegrid\", palette=\"muted\")'\n"
|
| 204 |
+
"- Use high-end palette hex colors (e.g. `#6366f1` for Indigo, `#06b6d4` for Teal, `#ec4899` for Pink, `#10b981` for Emerald).\n"
|
| 205 |
+
"- Set figure size to `(10, 6)` or `(12, 6)`.\n"
|
| 206 |
+
"- Set clear, descriptive titles and wrap long titles: 'plt.title(textwrap.fill(title, 40))'.\n"
|
| 207 |
+
"- Apply 'sns.despine(left=True, bottom=True)' to remove borders.\n"
|
| 208 |
+
"- Save each plot with: `save_chart('chart_name')`.\n"
|
| 209 |
+
"- Call `plt.close()` immediately after each save to clear the state."
|
| 210 |
+
f"{relations_context}"
|
| 211 |
+
f"{profile_block}"
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
visualize_task = Task(
|
| 215 |
agent=visualizer_agent,
|
| 216 |
+
description=visualize_prompt,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
expected_output="Summary of the 3-4 custom visualization plots generated and saved.",
|
| 218 |
callback=cb,
|
| 219 |
)
|