muterornament commited on
Commit
2a9745a
·
1 Parent(s): 02cd7a8

feat: implement PDF export module, workflow pipeline, and web-based dashboard UI

Browse files
Files changed (45) hide show
  1. .gitignore +61 -43
  2. README.md +91 -157
  3. assets/complete_workflow.svg +191 -159
  4. body_fat_vs_situps.png +3 -0
  5. broad_jump_vs_situps.png +3 -0
  6. crew.py +25 -0
  7. data/sessions/0a66311e308d/cleaned.csv +0 -0
  8. data/sessions/0a66311e308d/done.txt +0 -1
  9. data/sessions/0a66311e308d/metadata.json +0 -8
  10. data/sessions/0a66311e308d/original.csv +0 -0
  11. data/sessions/0a66311e308d/original_upload.csv +0 -0
  12. data/sessions/0a66311e308d/results.json +0 -0
  13. data/sessions/0a66311e308d/stdout.log +0 -0
  14. data/sessions/14b48eadf665/cleaned.csv +0 -33
  15. data/sessions/14b48eadf665/done.txt +0 -1
  16. data/sessions/14b48eadf665/metadata.json +0 -9
  17. data/sessions/14b48eadf665/original.csv +0 -33
  18. data/sessions/14b48eadf665/original_upload.csv +0 -33
  19. data/sessions/14b48eadf665/results.json +0 -3035
  20. data/sessions/14b48eadf665/stdout.log +0 -0
  21. data/sessions/a402006b7ddd/cleaned.csv +0 -33
  22. data/sessions/a402006b7ddd/done.txt +0 -1
  23. data/sessions/a402006b7ddd/metadata.json +0 -9
  24. data/sessions/a402006b7ddd/original.csv +0 -33
  25. data/sessions/a402006b7ddd/original_upload.csv +0 -33
  26. data/sessions/a402006b7ddd/results.json +0 -0
  27. data/sessions/a402006b7ddd/stdout.log +0 -0
  28. data/sessions/c71065e6ffab/cleaned.csv +0 -0
  29. data/sessions/c71065e6ffab/done.txt +0 -1
  30. data/sessions/c71065e6ffab/metadata.json +0 -8
  31. data/sessions/c71065e6ffab/original.csv +0 -0
  32. data/sessions/c71065e6ffab/original_upload.csv +0 -0
  33. data/sessions/c71065e6ffab/results.json +0 -0
  34. data/sessions/c71065e6ffab/stdout.log +0 -0
  35. grip_force_vs_height.png +3 -0
  36. grip_force_vs_weight.png +3 -0
  37. height_vs_weight.png +3 -0
  38. main.py +251 -8
  39. requirements.txt +2 -2
  40. ui/copilot.py +11 -6
  41. ui/export.py +77 -9
  42. web/app.js +559 -124
  43. web/index.html +156 -85
  44. web/style.css +286 -25
  45. workflows/pipeline.py +77 -49
.gitignore CHANGED
@@ -1,59 +1,77 @@
1
- # Python
2
  __pycache__/
3
- *.pyc
4
- *.pyo
5
- *.pyd
 
 
 
 
6
  .Python
7
- env/
8
- venv/
9
- .venv/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  pip-log.txt
11
  pip-delete-this-directory.txt
12
 
13
- # Environment Variables
 
 
 
 
 
 
 
 
14
  .env
15
  .env.local
16
-
17
- # Streamlit
18
- .streamlit/secrets.toml
19
-
20
- # Project Outputs
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 specific
32
  .DS_Store
33
  Thumbs.db
 
 
34
 
35
- # Replit
36
- .replit
37
- replit.md
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
- *.db
 
 
 
 
 
 
 
 
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 Agent Data Analysis with Crew AI" width="100%" />
20
  </p>
21
 
22
  <p align="center">
23
- <img src="assets/stars.svg" alt="5-star" height="28" />
24
- &nbsp;&nbsp;
25
- <img src="assets/badge_crewai.svg" alt="crewai" height="28" />
26
- <img src="assets/badge_pandas.svg" alt="pandas" height="28" />
27
- <img src="assets/badge_matplotlib.svg" alt="matplotlib" height="28" />
28
- <img src="assets/badge_seaborn.svg" alt="seaborn" height="28" />
29
- <img src="assets/badge_ollama.svg" alt="ollama" height="28" />
30
  </p>
31
 
32
- > **Data Analysis as a Service** | A premium, modular data-analyst pipeline powered by LLM-driven agents. Feed it a CSV and it will perform professional data quality assurance, cleaning, relationship mapping, visualization code generation, and business intelligence synthesis.
33
-
34
- ## Quick Links
35
 
36
- - **Web App**: `streamlit run app.py`
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
- Run the interactive web interface:
47
- ```bash
48
- streamlit run app.py
49
- ```
50
 
51
- ### Option 2: Command Line Interface
52
 
53
- Run the pipeline directly from the terminal:
54
- ```bash
55
- python crew.py
56
- ```
57
 
58
- ## Deploying to Hugging Face Spaces
59
-
60
- **[View Live Demo](https://huggingface.co/spaces/sowmiyan-s/Multi-Agent-Data-Analysis-with-CrewAI)**
61
-
62
- 1. **Create a New Space**:
63
- * Go to [huggingface.co/spaces](https://huggingface.co/spaces).
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
- 2. Configure your LLM provider in `.env` (Groq, OpenAI, Ollama, Hugging Face, Mistral, etc.).
102
-
103
- 3. Run the application.
104
-
105
- 4. Upload your CSV file (Web App) or enter the path (CLI).
106
 
107
- ## What you'll get
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
- - **Premium Web Interface**: A sleek, single-page application with a modern "Obsidian & Electric Violet" theme.
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/ # AI agent factory functions
124
- │ ├── cleaner.py # Data Cleaner agent
125
- │ ├── relation.py # Relationship Analyst agent
126
- │ ├── insights.py # Business Intelligence Analyst agent
127
- │ └── visualizer.py # Data Visualizer agent
128
- ├── config/ # Configuration
129
- │ ├── llm_config.py # LLM provider config & param builder
130
  │ └── __init__.py
131
- ├── data/ # Input data
132
- │ └── sessions/ # Per-session working copies (auto-created)
 
 
 
 
 
 
 
 
 
 
133
  │ └── <session_id>/
134
- │ ├── original.csv # Pre-cleaning backup
135
- ── cleaned.csv # Cleaned dataset (modified in-place)
136
- ── outputs/ # Generated visualizations
137
- │ └── <session_id>/ # Per-session PNG plots (auto-created)
138
- ── tools/ # CrewAI tool definitions
139
- │ └── dataset_tools.py # read_head, get_info, clean_python, exec_viz
140
- ├── ui/ # Streamlit UI helpers
141
- ├── styles.py # CSS injection (glassmorphism theme)
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 usage guide
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
- - 📖 [Usage Guide](USAGE.md)
183
- - 📋 [Changelog](CHANGELOG.md)
184
 
185
  ---
186
 
187
- *Multi Agent Data Analysis with Crew AI*
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
+ &nbsp;&nbsp;
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

  • SHA256: 86288f3ed10bb35a0f56a5ca2f12419a857ff2f3b19b86fb0c944e481bd790ed
  • Pointer size: 131 Bytes
  • Size of remote file: 124 kB
broad_jump_vs_situps.png ADDED

Git LFS Details

  • SHA256: caf78de154e7f5fc1bef658b4167ea40c286e5a39a9a4985f1f444678e5554b5
  • Pointer size: 131 Bytes
  • Size of remote file: 125 kB
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

  • SHA256: e19f36b7111baf73e30d58734cbef3b11d8d08d4f067d7df2fbca56a5a8e53b7
  • Pointer size: 131 Bytes
  • Size of remote file: 122 kB
grip_force_vs_weight.png ADDED

Git LFS Details

  • SHA256: a5241852f3e750ee7df2ee8af3a760448a831537c88fe6ea2e4cc4479178c6ed
  • Pointer size: 131 Bytes
  • Size of remote file: 119 kB
height_vs_weight.png ADDED

Git LFS Details

  • SHA256: 456445266e055dc084a971393aaf8f4eb2bf34e28df4b5cbe59c93bae87610f1
  • Pointer size: 131 Bytes
  • Size of remote file: 102 kB
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([f.name for f in output_dir.glob("*.png")])
 
 
 
 
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
- save_project_metadata(session_id, meta)
293
- except Exception:
294
- pass
 
 
 
 
 
 
 
 
 
 
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
- raise HTTPException(status_code=404, detail="Results not ready or not found.")
572
 
573
  with open(results_path, "r", encoding="utf-8") as f:
574
- return json.load(f)
 
 
 
 
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 2023.12.25 has pre-built Windows wheels (no Rust required)
29
- regex==2023.12.25
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 above (exact spelling, case-sensitive).
127
- 3. Perform any required analysis, aggregation, or computation.
128
- 4. Print a clear, formatted answer to stdout.
129
- 5. If the query asks for a chart/plot/graph:
 
 
 
 
130
  - Call `import matplotlib; matplotlib.use('Agg')` BEFORE importing pyplot.
131
- - Generate a professional chart and save it to: '{plot_path.as_posix()}'
 
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
- report_filename = filename or "Data Analysis Report"
253
- timestamp = datetime.now().strftime("%B %d, %Y at %I:%M %p")
 
254
 
255
  cover_content = [
256
- Paragraph("<b>EXECUTIVE DATA ANALYSIS REPORT</b>", title_style),
257
  Paragraph("Autonomous Business Intelligence &amp; Strategy Recommendations", subtitle_style),
258
- Paragraph(f"Dataset: <b>{report_filename}</b>", meta_style),
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"✔ &nbsp; {_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
- insights_text = result.get("insights", "").strip()
335
- if insights_text:
336
  story.append(Paragraph("💡 Strategic Business Insights", h1_style))
337
- insight_items = re.split(r"\d+\.\s+", insights_text)
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 &amp; 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"✔ &nbsp; {_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 &amp; 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 &amp; 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 &amp; 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 || '/default_preview.png'}" class="db-project-preview-img" alt="Project Preview" />
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
- showScreen('landing');
 
 
 
 
 
 
 
 
 
 
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
- <circle cx="80" cy="50" r="30" class="pov-chord-line" stroke-dasharray="2,2"></circle>
1315
- <circle cx="80" cy="50" r="35" class="pov-chord-line active"></circle>
1316
-
1317
- <circle cx="50" cy="30" r="4" class="pov-node"></circle>
1318
- <circle cx="110" cy="30" r="4" class="pov-node"></circle>
1319
- <circle cx="50" cy="70" r="4" class="pov-node"></circle>
1320
- <circle cx="110" cy="70" r="4" class="pov-node"></circle>
1321
- <circle cx="80" cy="15" r="4" class="pov-node"></circle>
1322
- <circle cx="80" cy="85" r="4" class="pov-node"></circle>
1323
-
1324
- <path d="M50 30 L110 70" class="pov-chord-line active" stroke-dasharray="none"></path>
1325
- <path d="M110 30 L50 70" class="pov-chord-line" stroke-dasharray="4,4"></path>
1326
- <path d="M80 15 L80 85" class="pov-chord-line active" stroke-dasharray="none"></path>
 
 
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
- activateTab('preview');
 
 
 
 
 
 
 
 
 
 
1530
  }
1531
 
1532
  // ── Stats row ────────────────────────────────────────────────────────────────
@@ -1566,20 +1732,22 @@ function renderPreview(rows) {
1566
  }
1567
 
1568
  // Toggle preview minimize
1569
- els.togglePreviewBtn.addEventListener('click', () => {
1570
- state.previewMinimized = !state.previewMinimized;
1571
- if (state.previewMinimized) {
1572
- els.previewTableWrap.innerHTML = `
1573
- <div class="preview-minimized">
1574
- Preview minimized — ${state.results?.rows_count?.toLocaleString() ?? '?'} rows × ${state.columns.length} columns.
1575
- </div>`;
1576
- els.togglePreviewBtn.textContent = '🔼 Expand Preview';
1577
- } else {
1578
- els.previewTableWrap.innerHTML = '<div id="previewTable" class="data-table-container"></div>';
1579
- renderPreview(state.results?.preview || []);
1580
- els.togglePreviewBtn.textContent = '🔽 Minimize';
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
- if (!lines.length || (lines.length === 1 && lines[0].toLowerCase().includes('skipped'))) {
1603
- els.relationsContent.innerHTML = '<p style="color:var(--text-muted)">No relationships found.</p>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1604
  return;
1605
  }
1606
 
1607
- // Extract correlation strength value if any mention like r=0.87 or (0.87)
1608
- function extractStrength(line) {
1609
- const m = line.match(/r[=:]\s*([0-9.+-]+)|\(([0-9.+-]+)\)/);
1610
- if (m) { const v = parseFloat(m[1] || m[2]); return isNaN(v) ? null : v; }
1611
- return null;
1612
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1613
 
1614
- // Color the strength bar: negative=rose, low=amber, high=emerald
1615
- function strengthColor(r) {
1616
- if (r === null) return 'var(--violet-light)';
1617
- const abs = Math.abs(r);
1618
- if (abs >= 0.7) return r > 0 ? 'var(--emerald)' : 'var(--rose)';
1619
- if (abs >= 0.4) return 'var(--amber)';
1620
- return 'var(--cyan)';
 
 
 
 
 
 
 
 
 
 
 
 
 
1621
  }
1622
 
1623
- // Chart type icons
1624
- const chartTypeIcon = { scatter: '⬥', bar: '▬', line: '∿', box: '☐', histogram: '▤', hist: '▤', heatmap: '▦' };
1625
 
1626
- els.relationsContent.innerHTML = lines.map(line => {
1627
- const xMatch = line.match(/X:\s*([^|]+)/i);
1628
- const yMatch = line.match(/Y:\s*([^|]+)/i);
1629
- const typeMatch = line.match(/Type:\s*([^|]+)/i);
1630
- const rVal = extractStrength(line);
 
 
1631
 
1632
- if (xMatch && yMatch) {
1633
- const xCol = xMatch[1].trim();
1634
- const yCol = yMatch[1].trim();
1635
- const typ = typeMatch ? typeMatch[1].trim() : 'relation';
1636
- const typLower = typ.toLowerCase();
1637
- const icon = chartTypeIcon[typLower] || '';
1638
- const color = strengthColor(rVal);
1639
- const barPct = rVal !== null ? Math.round(Math.abs(rVal) * 100) : 60;
1640
 
1641
- return `
1642
- <div class="relation-card">
1643
- <div class="relation-card-top">
1644
- <div class="relation-cols">
1645
- <span class="relation-col x-col">${escHtml(xCol)}</span>
1646
- <span class="relation-arrow" style="color:${color}">${icon}</span>
1647
- <span class="relation-col y-col">${escHtml(yCol)}</span>
1648
- </div>
1649
- <span class="relation-type">${escHtml(typ)}</span>
1650
- </div>
1651
- <div class="relation-strength-bar-wrap">
1652
- <div class="relation-strength-bar" style="width:${barPct}%;background:${color};"></div>
1653
- </div>
1654
- ${rVal !== null ? `<div class="relation-rval" style="color:${color}">r = ${rVal.toFixed(3)}</div>` : ''}
1655
- </div>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1656
  }
1657
- return `<div class="relation-card"><div class="cleaning-text">${escHtml(line.replace(/^[-*•]\s*/, ''))}</div></div>`;
1658
- }).join('');
 
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
- // Robust parsing: handle "1. ", "**1.** ", bullet "• ", or double-newline separation
1693
- let items = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1694
 
1695
- // Try numbered list first (e.g. "1. " or "**1." or "1)")
1696
- const numberedSplit = text.split(/\n(?=\s*(?:\*{0,2}\d+[.):]\*{0,2}|#{1,3}\s))/);
1697
- if (numberedSplit.length > 1) {
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
- if (!items.length) {
1705
- els.insightsContent.innerHTML = `<div class="insight-card"><div class="insight-section-text">${formatInlineMarkdown(text.trim())}</div></div>`;
1706
- return;
 
 
 
 
 
 
 
1707
  }
1708
 
1709
- const LABEL_CFG = [
1710
- { re: /^(?:observation|finding|pattern)[s]?[:]/i, cls: 'obs', icon: '🔍', label: 'Observation' },
1711
- { re: /^(?:business\s+)?implication[s]?[:]/i, cls: 'impl', icon: '💼', label: 'Implication' },
1712
- { re: /^(?:actionable\s+)?strateg(?:y|ies)[:]/i, cls: 'strat', icon: '🚀', label: 'Strategy' },
1713
- { re: /^(?:recommendation|action|next\s+step)[s]?[:]/i, cls: 'strat', icon: '✅', label: 'Recommendation' },
1714
- { re: /^(?:risk|concern|warning)[s]?[:]/i, cls: 'impl', icon: '⚠️', label: 'Risk' },
1715
- { re: /^(?:kpi|metric|measure)[s]?[:]/i, cls: 'obs', icon: '📊', label: 'Metric' },
1716
- ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1717
 
1718
- els.insightsContent.innerHTML = items.map((item, i) => {
1719
- const parts = item.split('\n').map(p => p.trim()).filter(Boolean);
1720
-
1721
- const sectionHtml = parts.map(p => {
1722
- for (const cfg of LABEL_CFG) {
1723
- if (cfg.re.test(p)) {
1724
- const body = p.replace(cfg.re, '').trim();
1725
- return `<div class="insight-section">
1726
- <div class="insight-section-label ${cfg.cls}">${cfg.icon} ${cfg.label}</div>
1727
- <div class="insight-section-text">${formatInlineMarkdown(body || p)}</div>
1728
- </div>`;
 
 
 
 
 
 
 
 
 
1729
  }
1730
- }
1731
- // Bullet sub-item
1732
- if (/^[-*•]/.test(p)) {
1733
- return `<div class="insight-bullet">${formatInlineMarkdown(p)}</div>`;
1734
- }
1735
- // Heading within insight
1736
- if (/^#{1,3}\s/.test(p)) {
1737
- return `<div class="insight-sub-heading">${escHtml(p.replace(/^#+\s*/, ''))}</div>`;
1738
- }
1739
- return `<div class="insight-section-text">${formatInlineMarkdown(p)}</div>`;
 
 
 
 
 
1740
  }).join('');
 
1741
 
1742
- return `<div class="insight-card">
1743
- <div class="insight-num-pill">${i + 1}</div>
1744
- <div class="insight-body">${sectionHtml}</div>
1745
- </div>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 &amp; 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 &amp; 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 for your data analysis project to get started.</p>
99
- <div class="field-group mt-16" style="width: 100%; max-width: 400px; margin: 16px auto 0;">
100
- <input type="text" id="wizardProjectName" class="field-input text-center" placeholder="e.g. Sales Analysis Q3" style="text-align: center; font-size: 1rem; padding: 10px 14px;" />
 
 
 
 
 
 
 
 
 
 
 
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
- <!-- Tab panels -->
295
- <div class="tab-panels">
 
 
 
296
 
297
- <!-- DATA PREVIEW -->
298
- <div class="tab-panel active" id="panel-preview">
299
- <div class="panel-toolbar">
300
- <h3 class="panel-title">📊 Dataset Explorer</h3>
301
- <button id="togglePreviewBtn" class="btn-secondary btn-sm">🔽 Minimize</button>
 
 
 
 
 
 
 
 
 
 
 
302
  </div>
303
- <div id="previewTableWrap" class="table-wrap">
304
- <div id="previewTable" class="data-table-container"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- <!-- RELATIONS -->
315
- <div class="tab-panel" id="panel-relations">
316
- <h3 class="panel-title">🔗 Column Relationships</h3>
317
- <div id="relationsContent" class="relations-grid"></div>
 
 
 
 
 
 
318
  </div>
319
 
320
- <!-- INSIGHTS -->
321
- <div class="tab-panel" id="panel-insights">
322
- <h3 class="panel-title">💡 Strategic Business Insights</h3>
323
- <div id="insightsContent" class="insights-list"></div>
 
 
324
  </div>
325
 
326
- <!-- CHARTS (Plotly + PNG + Viz Code) -->
327
- <div class="tab-panel" id="panel-charts">
328
- <h3 class="panel-title">📈 Visual Intelligence</h3>
329
-
330
- <div id="plotlyChartsWrap" class="charts-grid"></div>
 
 
331
 
332
- <div id="pngChartsWrap" class="png-charts-wrap hidden">
333
- <h4 class="sub-title">🖼 Agent-Generated Charts</h4>
334
- <div id="pngCharts" class="png-grid"></div>
 
335
  </div>
336
 
337
- <!-- Viz code collapsible -->
338
- <details class="viz-code-details hidden" id="vizCodeDetails">
339
- <summary>⚙️ Visualization Architecture Code</summary>
340
- <pre id="vizCodeBlock" class="code-block"></pre>
341
- </details>
342
- </div>
343
 
344
- <!-- AI COPILOT CHAT -->
345
- <div class="tab-panel" id="panel-chat">
346
- <div class="chat-layout">
347
- <div class="chat-main">
348
- <div class="chat-header">
349
- <h3 class="panel-title">💬 AI Data Copilot</h3>
350
- <button id="clearChatBtn" class="btn-secondary btn-sm">Clear Chat</button>
351
- </div>
352
- <div id="chatMessages" class="chat-messages"></div>
353
- <div class="chat-input-area">
354
- <!-- Column / command picker -->
355
- <div id="colPickerDropdown" class="col-picker-dropdown hidden"></div>
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
- </div><!-- /tab-panels -->
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 &amp; 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 &amp; 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 &amp; 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: var(--bg-card);
580
- border: 1px solid var(--border);
581
- border-radius: var(--r-lg);
582
- padding: 16px 24px;
 
 
583
  overflow-x: auto;
 
584
  }
585
  .stage-item {
586
  display: flex;
587
  flex-direction: column;
588
  align-items: center;
589
- gap: 6px;
590
  flex: 1;
591
  position: relative;
592
  }
593
  .stage-item:not(:last-child)::after {
594
  content: '';
595
  position: absolute;
596
- top: 8px;
597
- left: calc(50% + 14px);
598
- right: calc(-50% + 14px);
599
- height: 2px;
600
- background: var(--border-mid);
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: 18px; height: 18px;
 
607
  border-radius: 50%;
608
- background: var(--bg-elevated);
609
- border: 2px solid var(--border-mid);
610
  z-index: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  transition: all 0.3s;
612
  }
613
- .stage-item.done .stage-dot { background: var(--emerald); border-color: var(--emerald); }
614
- .stage-item.active .stage-dot { background: var(--violet-light); border-color: var(--violet-light); animation: pulse 1.2s infinite; box-shadow: 0 0 10px var(--violet-glow); }
615
- .stage-label { font-size: 0.7rem; color: var(--text-secondary); font-weight: 500; }
616
- .stage-item.done .stage-label { color: var(--emerald); }
617
- .stage-item.active .stage-label { color: var(--violet-light); }
 
 
 
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: 48px 32px;
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: 200px;
1822
- width: 100%;
1823
- max-width: 520px;
1824
- margin: 0 auto;
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(session_id: str, profile: str = "", selected_tasks: Optional[list[str]] = None, deep_analysis: bool = False) -> tuple[list, list]:
 
 
 
 
 
 
 
 
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
- "Using the dataset profile and identified relationships provided below, "
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
  )