dataprocessing-ai 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dataprocessing_ai-0.1.1/LICENSE +21 -0
- dataprocessing_ai-0.1.1/PKG-INFO +212 -0
- dataprocessing_ai-0.1.1/README.md +175 -0
- dataprocessing_ai-0.1.1/dataprocessing/__init__.py +0 -0
- dataprocessing_ai-0.1.1/dataprocessing/analyse/__init__.py +56 -0
- dataprocessing_ai-0.1.1/dataprocessing/api.py +162 -0
- dataprocessing_ai-0.1.1/dataprocessing/clean/__init__.py +60 -0
- dataprocessing_ai-0.1.1/dataprocessing/ingest/__init__.py +102 -0
- dataprocessing_ai-0.1.1/dataprocessing/mcp_server.py +164 -0
- dataprocessing_ai-0.1.1/dataprocessing/transform/__init__.py +51 -0
- dataprocessing_ai-0.1.1/dataprocessing/visualise/__init__.py +98 -0
- dataprocessing_ai-0.1.1/dataprocessing_ai.egg-info/PKG-INFO +212 -0
- dataprocessing_ai-0.1.1/dataprocessing_ai.egg-info/SOURCES.txt +23 -0
- dataprocessing_ai-0.1.1/dataprocessing_ai.egg-info/dependency_links.txt +1 -0
- dataprocessing_ai-0.1.1/dataprocessing_ai.egg-info/entry_points.txt +2 -0
- dataprocessing_ai-0.1.1/dataprocessing_ai.egg-info/requires.txt +24 -0
- dataprocessing_ai-0.1.1/dataprocessing_ai.egg-info/top_level.txt +1 -0
- dataprocessing_ai-0.1.1/pyproject.toml +65 -0
- dataprocessing_ai-0.1.1/setup.cfg +4 -0
- dataprocessing_ai-0.1.1/tests/test_analyse.py +92 -0
- dataprocessing_ai-0.1.1/tests/test_api.py +145 -0
- dataprocessing_ai-0.1.1/tests/test_clean.py +83 -0
- dataprocessing_ai-0.1.1/tests/test_ingest.py +67 -0
- dataprocessing_ai-0.1.1/tests/test_transform.py +96 -0
- dataprocessing_ai-0.1.1/tests/test_visualise.py +105 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gbemi Lesi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dataprocessing-ai
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: AI-native data processing library — REST API, MCP server, and Python package
|
|
5
|
+
Author: oluwagbemilesi
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Techlon/dataprocessing-ai
|
|
8
|
+
Project-URL: Repository, https://github.com/Techlon/dataprocessing-ai
|
|
9
|
+
Project-URL: Issues, https://github.com/Techlon/dataprocessing-ai/issues
|
|
10
|
+
Keywords: data,processing,AI,REST,MCP,pandas
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: pandas>=2.2.0
|
|
18
|
+
Requires-Dist: numpy>=1.26.0
|
|
19
|
+
Provides-Extra: api
|
|
20
|
+
Requires-Dist: fastapi>=0.110.0; extra == "api"
|
|
21
|
+
Requires-Dist: uvicorn>=0.27.0; extra == "api"
|
|
22
|
+
Requires-Dist: pydantic>=2.6.0; extra == "api"
|
|
23
|
+
Requires-Dist: python-multipart>=0.0.9; extra == "api"
|
|
24
|
+
Provides-Extra: mcp
|
|
25
|
+
Requires-Dist: mcp>=0.9.0; extra == "mcp"
|
|
26
|
+
Provides-Extra: formats
|
|
27
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "formats"
|
|
28
|
+
Requires-Dist: pyarrow>=15.0.0; extra == "formats"
|
|
29
|
+
Provides-Extra: all
|
|
30
|
+
Requires-Dist: dataprocessing-ai[api,formats,mcp]; extra == "all"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: dataprocessing-ai[all]; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# DataProcessing AI
|
|
39
|
+
|
|
40
|
+
An AI-native Python library for data processing and transformation.
|
|
41
|
+
Built to be called by any AI agent via REST API, MCP server, or direct Python import.
|
|
42
|
+
|
|
43
|
+
## What it does
|
|
44
|
+
|
|
45
|
+
- **Ingest** - read CSV, JSON, Excel, Parquet files into a standard format
|
|
46
|
+
- **Clean** - remove nulls, duplicates, outliers, standardise column names
|
|
47
|
+
- **Transform** - filter, sort, group, pivot, merge, reshape datasets
|
|
48
|
+
- **Analyse** - generate statistics, correlations, distributions, outlier reports
|
|
49
|
+
- **Visualise** - produce Vega-Lite chart specs (histogram, bar, scatter, line, correlation heatmap)
|
|
50
|
+
|
|
51
|
+
## Three ways to use it
|
|
52
|
+
|
|
53
|
+
### 1. REST API (any AI, any language)
|
|
54
|
+
|
|
55
|
+
Install with the API extra, then start the server:
|
|
56
|
+
```bash
|
|
57
|
+
pip install "dataprocessing-ai[api]"
|
|
58
|
+
uvicorn dataprocessing.api:app --host 0.0.0.0 --port 8000
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Call it:
|
|
62
|
+
```bash
|
|
63
|
+
curl -X POST http://localhost:8000/ingest -F "file=@data.csv"
|
|
64
|
+
curl -X POST http://localhost:8000/clean -H "Content-Type: application/json" -d '{"data": [...]}'
|
|
65
|
+
curl -X POST http://localhost:8000/transform -H "Content-Type: application/json" -d '{"data": [...], "operation": "filter_rows", "params": {"column": "age", "operator": "gt", "value": 25}}'
|
|
66
|
+
curl -X POST http://localhost:8000/analyse -H "Content-Type: application/json" -d '{"data": [...]}'
|
|
67
|
+
curl -X POST http://localhost:8000/visualise -H "Content-Type: application/json" -d '{"data": [...], "chart": "histogram", "params": {"column": "age", "bins": 10}}'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2. MCP Server (Claude native tools)
|
|
71
|
+
|
|
72
|
+
Install with the MCP extra:
|
|
73
|
+
```bash
|
|
74
|
+
pip install "dataprocessing-ai[mcp]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then add to your `claude_desktop_config.json`:
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcpServers": {
|
|
81
|
+
"dataprocessing": {
|
|
82
|
+
"command": "dataprocessing-mcp"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
No paths, no `PYTHONPATH` — the installed package provides the
|
|
89
|
+
`dataprocessing-mcp` command directly.
|
|
90
|
+
|
|
91
|
+
### 3. Python package (direct import)
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from dataprocessing.ingest import read_file
|
|
95
|
+
from dataprocessing.clean import clean_all
|
|
96
|
+
from dataprocessing.transform import filter_rows
|
|
97
|
+
from dataprocessing.analyse import full_report
|
|
98
|
+
from dataprocessing.visualise import histogram, correlation_heatmap
|
|
99
|
+
|
|
100
|
+
df = read_file("data.csv")
|
|
101
|
+
df = clean_all(df)
|
|
102
|
+
report = full_report(df)
|
|
103
|
+
|
|
104
|
+
# Chart functions return Vega-Lite spec dicts (JSON-serialisable)
|
|
105
|
+
hist_spec = histogram(df, column="age", bins=10)
|
|
106
|
+
heatmap_spec = correlation_heatmap(df)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Installation
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install dataprocessing-ai
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
That gives you the core library (`ingest`, `clean`, `transform`, `analyse`,
|
|
116
|
+
`visualise`) with a minimal dependency footprint.
|
|
117
|
+
|
|
118
|
+
Optional extras add the interfaces and file formats you need:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pip install "dataprocessing-ai[mcp]" # MCP server (for Claude and other agents)
|
|
122
|
+
pip install "dataprocessing-ai[api]" # REST API server
|
|
123
|
+
pip install "dataprocessing-ai[formats]" # Excel (.xlsx) and Parquet support
|
|
124
|
+
pip install "dataprocessing-ai[all]" # everything
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Installing from source
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git clone https://github.com/Techlon/dataprocessing-ai.git
|
|
131
|
+
cd dataprocessing-ai
|
|
132
|
+
python3 -m venv .venv
|
|
133
|
+
source .venv/bin/activate
|
|
134
|
+
pip install -e ".[dev]"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## API Endpoints
|
|
138
|
+
|
|
139
|
+
| Method | Endpoint | Description |
|
|
140
|
+
|--------|----------|-------------|
|
|
141
|
+
| GET | /health | API status |
|
|
142
|
+
| POST | /ingest | Upload and read a data file |
|
|
143
|
+
| POST | /clean | Clean a dataset |
|
|
144
|
+
| POST | /transform | Transform a dataset |
|
|
145
|
+
| POST | /analyse | Analyse a dataset |
|
|
146
|
+
| POST | /visualise | Produce a Vega-Lite chart spec from a dataset |
|
|
147
|
+
|
|
148
|
+
## Charts
|
|
149
|
+
|
|
150
|
+
The `/visualise` endpoint (and the `visualise_data` MCP tool) accept a `chart`
|
|
151
|
+
name and a `params` object. Each returns a [Vega-Lite](https://vega.github.io/vega-lite/)
|
|
152
|
+
spec dict that any Vega-Lite renderer can display.
|
|
153
|
+
|
|
154
|
+
| Chart | Params | Notes |
|
|
155
|
+
|-------|--------|-------|
|
|
156
|
+
| `histogram` | `column` (numeric), `bins` (int, default 10) | Distribution of a numeric column |
|
|
157
|
+
| `bar_chart` | `column` (any) | Counts per category |
|
|
158
|
+
| `scatter` | `x` (numeric), `y` (numeric) | Relationship between two numeric columns |
|
|
159
|
+
| `line_chart` | `x` (any), `y` (numeric) | `x` may be a date or category; `y` must be numeric |
|
|
160
|
+
| `correlation_heatmap` | `columns` (list, optional) | Correlations across numeric columns; defaults to all numeric columns |
|
|
161
|
+
|
|
162
|
+
A bad chart name, a missing column, or a non-numeric column where a numeric one
|
|
163
|
+
is required returns HTTP 400 (or raises `ValueError` when called directly).
|
|
164
|
+
|
|
165
|
+
### Example response
|
|
166
|
+
|
|
167
|
+
A call with `{"chart": "histogram", "params": {"column": "age", "bins": 3}}`
|
|
168
|
+
returns a Vega-Lite spec like:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
|
173
|
+
"description": "Histogram of age",
|
|
174
|
+
"data": {
|
|
175
|
+
"values": [
|
|
176
|
+
{"bin_start": 23.0, "bin_end": 29.33, "count": 4},
|
|
177
|
+
{"bin_start": 29.33, "bin_end": 35.67, "count": 3},
|
|
178
|
+
{"bin_start": 35.67, "bin_end": 42.0, "count": 1}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
"mark": "bar",
|
|
182
|
+
"encoding": {
|
|
183
|
+
"x": {"field": "bin_start", "type": "quantitative"},
|
|
184
|
+
"x2": {"field": "bin_end", "type": "quantitative"},
|
|
185
|
+
"y": {"field": "count", "type": "quantitative"}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Paste the returned spec into the [Vega-Lite editor](https://vega.github.io/editor/)
|
|
191
|
+
or render it with any Vega-Lite-compatible frontend.
|
|
192
|
+
|
|
193
|
+
## MCP Tools
|
|
194
|
+
|
|
195
|
+
The MCP server exposes each capability as a Claude-native tool:
|
|
196
|
+
|
|
197
|
+
| Tool | Arguments | Description |
|
|
198
|
+
|------|-----------|-------------|
|
|
199
|
+
| `ingest_file` | `file_path` | Read a data file (CSV, JSON, Excel, Parquet) from disk |
|
|
200
|
+
| `clean_data` | `data`, `drop_null_threshold`, `remove_dupes`, `standardise_cols` | Remove nulls, duplicates, and standardise column names |
|
|
201
|
+
| `transform_data` | `data`, `operation`, `params` | Apply a named transform (filter, select, rename, sort, group, add column) |
|
|
202
|
+
| `analyse_data` | `data` | Full statistical report (summary, correlations, missing, outliers) |
|
|
203
|
+
| `visualise_data` | `data`, `chart`, `params` | Produce a Vega-Lite chart spec (see [Charts](#charts) for names and params) |
|
|
204
|
+
|
|
205
|
+
The `visualise_data` tool uses the same chart names and params as the `/visualise`
|
|
206
|
+
endpoint and returns a Vega-Lite spec dict.
|
|
207
|
+
|
|
208
|
+
## Supported formats
|
|
209
|
+
CSV, JSON, Excel (.xlsx), Parquet
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
MIT
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# DataProcessing AI
|
|
2
|
+
|
|
3
|
+
An AI-native Python library for data processing and transformation.
|
|
4
|
+
Built to be called by any AI agent via REST API, MCP server, or direct Python import.
|
|
5
|
+
|
|
6
|
+
## What it does
|
|
7
|
+
|
|
8
|
+
- **Ingest** - read CSV, JSON, Excel, Parquet files into a standard format
|
|
9
|
+
- **Clean** - remove nulls, duplicates, outliers, standardise column names
|
|
10
|
+
- **Transform** - filter, sort, group, pivot, merge, reshape datasets
|
|
11
|
+
- **Analyse** - generate statistics, correlations, distributions, outlier reports
|
|
12
|
+
- **Visualise** - produce Vega-Lite chart specs (histogram, bar, scatter, line, correlation heatmap)
|
|
13
|
+
|
|
14
|
+
## Three ways to use it
|
|
15
|
+
|
|
16
|
+
### 1. REST API (any AI, any language)
|
|
17
|
+
|
|
18
|
+
Install with the API extra, then start the server:
|
|
19
|
+
```bash
|
|
20
|
+
pip install "dataprocessing-ai[api]"
|
|
21
|
+
uvicorn dataprocessing.api:app --host 0.0.0.0 --port 8000
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Call it:
|
|
25
|
+
```bash
|
|
26
|
+
curl -X POST http://localhost:8000/ingest -F "file=@data.csv"
|
|
27
|
+
curl -X POST http://localhost:8000/clean -H "Content-Type: application/json" -d '{"data": [...]}'
|
|
28
|
+
curl -X POST http://localhost:8000/transform -H "Content-Type: application/json" -d '{"data": [...], "operation": "filter_rows", "params": {"column": "age", "operator": "gt", "value": 25}}'
|
|
29
|
+
curl -X POST http://localhost:8000/analyse -H "Content-Type: application/json" -d '{"data": [...]}'
|
|
30
|
+
curl -X POST http://localhost:8000/visualise -H "Content-Type: application/json" -d '{"data": [...], "chart": "histogram", "params": {"column": "age", "bins": 10}}'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. MCP Server (Claude native tools)
|
|
34
|
+
|
|
35
|
+
Install with the MCP extra:
|
|
36
|
+
```bash
|
|
37
|
+
pip install "dataprocessing-ai[mcp]"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then add to your `claude_desktop_config.json`:
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"dataprocessing": {
|
|
45
|
+
"command": "dataprocessing-mcp"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
No paths, no `PYTHONPATH` — the installed package provides the
|
|
52
|
+
`dataprocessing-mcp` command directly.
|
|
53
|
+
|
|
54
|
+
### 3. Python package (direct import)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from dataprocessing.ingest import read_file
|
|
58
|
+
from dataprocessing.clean import clean_all
|
|
59
|
+
from dataprocessing.transform import filter_rows
|
|
60
|
+
from dataprocessing.analyse import full_report
|
|
61
|
+
from dataprocessing.visualise import histogram, correlation_heatmap
|
|
62
|
+
|
|
63
|
+
df = read_file("data.csv")
|
|
64
|
+
df = clean_all(df)
|
|
65
|
+
report = full_report(df)
|
|
66
|
+
|
|
67
|
+
# Chart functions return Vega-Lite spec dicts (JSON-serialisable)
|
|
68
|
+
hist_spec = histogram(df, column="age", bins=10)
|
|
69
|
+
heatmap_spec = correlation_heatmap(df)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install dataprocessing-ai
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
That gives you the core library (`ingest`, `clean`, `transform`, `analyse`,
|
|
79
|
+
`visualise`) with a minimal dependency footprint.
|
|
80
|
+
|
|
81
|
+
Optional extras add the interfaces and file formats you need:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install "dataprocessing-ai[mcp]" # MCP server (for Claude and other agents)
|
|
85
|
+
pip install "dataprocessing-ai[api]" # REST API server
|
|
86
|
+
pip install "dataprocessing-ai[formats]" # Excel (.xlsx) and Parquet support
|
|
87
|
+
pip install "dataprocessing-ai[all]" # everything
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Installing from source
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/Techlon/dataprocessing-ai.git
|
|
94
|
+
cd dataprocessing-ai
|
|
95
|
+
python3 -m venv .venv
|
|
96
|
+
source .venv/bin/activate
|
|
97
|
+
pip install -e ".[dev]"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API Endpoints
|
|
101
|
+
|
|
102
|
+
| Method | Endpoint | Description |
|
|
103
|
+
|--------|----------|-------------|
|
|
104
|
+
| GET | /health | API status |
|
|
105
|
+
| POST | /ingest | Upload and read a data file |
|
|
106
|
+
| POST | /clean | Clean a dataset |
|
|
107
|
+
| POST | /transform | Transform a dataset |
|
|
108
|
+
| POST | /analyse | Analyse a dataset |
|
|
109
|
+
| POST | /visualise | Produce a Vega-Lite chart spec from a dataset |
|
|
110
|
+
|
|
111
|
+
## Charts
|
|
112
|
+
|
|
113
|
+
The `/visualise` endpoint (and the `visualise_data` MCP tool) accept a `chart`
|
|
114
|
+
name and a `params` object. Each returns a [Vega-Lite](https://vega.github.io/vega-lite/)
|
|
115
|
+
spec dict that any Vega-Lite renderer can display.
|
|
116
|
+
|
|
117
|
+
| Chart | Params | Notes |
|
|
118
|
+
|-------|--------|-------|
|
|
119
|
+
| `histogram` | `column` (numeric), `bins` (int, default 10) | Distribution of a numeric column |
|
|
120
|
+
| `bar_chart` | `column` (any) | Counts per category |
|
|
121
|
+
| `scatter` | `x` (numeric), `y` (numeric) | Relationship between two numeric columns |
|
|
122
|
+
| `line_chart` | `x` (any), `y` (numeric) | `x` may be a date or category; `y` must be numeric |
|
|
123
|
+
| `correlation_heatmap` | `columns` (list, optional) | Correlations across numeric columns; defaults to all numeric columns |
|
|
124
|
+
|
|
125
|
+
A bad chart name, a missing column, or a non-numeric column where a numeric one
|
|
126
|
+
is required returns HTTP 400 (or raises `ValueError` when called directly).
|
|
127
|
+
|
|
128
|
+
### Example response
|
|
129
|
+
|
|
130
|
+
A call with `{"chart": "histogram", "params": {"column": "age", "bins": 3}}`
|
|
131
|
+
returns a Vega-Lite spec like:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
|
136
|
+
"description": "Histogram of age",
|
|
137
|
+
"data": {
|
|
138
|
+
"values": [
|
|
139
|
+
{"bin_start": 23.0, "bin_end": 29.33, "count": 4},
|
|
140
|
+
{"bin_start": 29.33, "bin_end": 35.67, "count": 3},
|
|
141
|
+
{"bin_start": 35.67, "bin_end": 42.0, "count": 1}
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
"mark": "bar",
|
|
145
|
+
"encoding": {
|
|
146
|
+
"x": {"field": "bin_start", "type": "quantitative"},
|
|
147
|
+
"x2": {"field": "bin_end", "type": "quantitative"},
|
|
148
|
+
"y": {"field": "count", "type": "quantitative"}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Paste the returned spec into the [Vega-Lite editor](https://vega.github.io/editor/)
|
|
154
|
+
or render it with any Vega-Lite-compatible frontend.
|
|
155
|
+
|
|
156
|
+
## MCP Tools
|
|
157
|
+
|
|
158
|
+
The MCP server exposes each capability as a Claude-native tool:
|
|
159
|
+
|
|
160
|
+
| Tool | Arguments | Description |
|
|
161
|
+
|------|-----------|-------------|
|
|
162
|
+
| `ingest_file` | `file_path` | Read a data file (CSV, JSON, Excel, Parquet) from disk |
|
|
163
|
+
| `clean_data` | `data`, `drop_null_threshold`, `remove_dupes`, `standardise_cols` | Remove nulls, duplicates, and standardise column names |
|
|
164
|
+
| `transform_data` | `data`, `operation`, `params` | Apply a named transform (filter, select, rename, sort, group, add column) |
|
|
165
|
+
| `analyse_data` | `data` | Full statistical report (summary, correlations, missing, outliers) |
|
|
166
|
+
| `visualise_data` | `data`, `chart`, `params` | Produce a Vega-Lite chart spec (see [Charts](#charts) for names and params) |
|
|
167
|
+
|
|
168
|
+
The `visualise_data` tool uses the same chart names and params as the `/visualise`
|
|
169
|
+
endpoint and returns a Vega-Lite spec dict.
|
|
170
|
+
|
|
171
|
+
## Supported formats
|
|
172
|
+
CSV, JSON, Excel (.xlsx), Parquet
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
MIT
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
|
|
5
|
+
def summary_stats(df, columns=None):
|
|
6
|
+
if columns is None:
|
|
7
|
+
columns = df.select_dtypes(include=[np.number]).columns
|
|
8
|
+
stats = {}
|
|
9
|
+
for column in columns:
|
|
10
|
+
col_data = df[column]
|
|
11
|
+
stats[column] = {
|
|
12
|
+
'count': len(col_data), 'mean': col_data.mean(), 'median': col_data.median(),
|
|
13
|
+
'std': col_data.std(), 'min': col_data.min(), 'max': col_data.max(),
|
|
14
|
+
'quartiles': {'25%': np.percentile(col_data, 25), '75%': np.percentile(col_data, 75)}
|
|
15
|
+
}
|
|
16
|
+
return stats
|
|
17
|
+
|
|
18
|
+
def correlation_matrix(df, columns=None):
|
|
19
|
+
if columns is None:
|
|
20
|
+
columns = df.select_dtypes(include=[np.number]).columns
|
|
21
|
+
return df[columns].corr().to_dict()
|
|
22
|
+
|
|
23
|
+
def value_counts(df, column):
|
|
24
|
+
if not pd.api.types.is_numeric_dtype(df[column]):
|
|
25
|
+
raise ValueError("Column must be numeric")
|
|
26
|
+
return df[column].value_counts().to_dict()
|
|
27
|
+
|
|
28
|
+
def missing_report(df):
|
|
29
|
+
total = len(df)
|
|
30
|
+
report = {}
|
|
31
|
+
for column in df.columns:
|
|
32
|
+
null_count = df[column].isnull().sum()
|
|
33
|
+
percentage = (null_count / total) * 100 if total > 0 else 0
|
|
34
|
+
report[column] = {'missing_count': int(null_count), 'percentage': float(percentage)}
|
|
35
|
+
return report
|
|
36
|
+
|
|
37
|
+
def distribution(df, column, bins=10):
|
|
38
|
+
if not pd.api.types.is_numeric_dtype(df[column]):
|
|
39
|
+
raise ValueError("Column must be numeric")
|
|
40
|
+
hist, bin_edges = np.histogram(df[column], bins=bins)
|
|
41
|
+
return {'bin_edges': list(bin_edges), 'counts': list(hist)}
|
|
42
|
+
|
|
43
|
+
def detect_outliers(df, columns=None):
|
|
44
|
+
if columns is None:
|
|
45
|
+
columns = df.select_dtypes(include=[np.number]).columns
|
|
46
|
+
outliers = {}
|
|
47
|
+
for column in columns:
|
|
48
|
+
q1 = np.percentile(df[column], 25); q3 = np.percentile(df[column], 75)
|
|
49
|
+
iqr = q3 - q1
|
|
50
|
+
lo = q1 - 1.5 * iqr; hi = q3 + 1.5 * iqr
|
|
51
|
+
outliers[column] = list(df[(df[column] < lo) | (df[column] > hi)].index)
|
|
52
|
+
return outliers
|
|
53
|
+
|
|
54
|
+
def full_report(df):
|
|
55
|
+
return {'summary_stats': summary_stats(df), 'correlation_matrix': correlation_matrix(df),
|
|
56
|
+
'missing_values': missing_report(df), 'outliers': detect_outliers(df)}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DataProcessing REST API
|
|
3
|
+
Exposes ingest, clean, transform and analyse modules over HTTP.
|
|
4
|
+
Any AI agent can call these endpoints.
|
|
5
|
+
"""
|
|
6
|
+
from fastapi import FastAPI, UploadFile, File, HTTPException
|
|
7
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from typing import Optional, List, Dict, Any
|
|
10
|
+
import pandas as pd
|
|
11
|
+
import io
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from dataprocessing.clean import drop_nulls, remove_duplicates, standardise_columns
|
|
15
|
+
from dataprocessing.transform import (
|
|
16
|
+
filter_rows, select_columns, rename_columns,
|
|
17
|
+
sort_rows, group_and_aggregate, pivot, add_column
|
|
18
|
+
)
|
|
19
|
+
from dataprocessing.analyse import full_report
|
|
20
|
+
|
|
21
|
+
from dataprocessing.visualise import (
|
|
22
|
+
histogram, bar_chart, scatter, line_chart, correlation_heatmap
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
app = FastAPI(title="DataProcessing API", version="0.1.0")
|
|
26
|
+
|
|
27
|
+
app.add_middleware(
|
|
28
|
+
CORSMiddleware,
|
|
29
|
+
allow_origins=["*"],
|
|
30
|
+
allow_methods=["*"],
|
|
31
|
+
allow_headers=["*"],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def df_to_json(df):
|
|
35
|
+
return json.loads(df.where(df.notna(), other=None).to_json(orient="records"))
|
|
36
|
+
|
|
37
|
+
def to_native(obj):
|
|
38
|
+
"""Recursively convert numpy scalars/arrays to JSON-serialisable Python types.
|
|
39
|
+
|
|
40
|
+
full_report() returns dicts containing numpy int64/float64 values and numpy
|
|
41
|
+
arrays, which FastAPI's encoder cannot serialise. This normalises them.
|
|
42
|
+
"""
|
|
43
|
+
import numpy as np
|
|
44
|
+
if isinstance(obj, dict):
|
|
45
|
+
return {to_native(k): to_native(v) for k, v in obj.items()}
|
|
46
|
+
if isinstance(obj, (list, tuple)):
|
|
47
|
+
return [to_native(v) for v in obj]
|
|
48
|
+
if isinstance(obj, np.generic):
|
|
49
|
+
return obj.item()
|
|
50
|
+
if isinstance(obj, np.ndarray):
|
|
51
|
+
return [to_native(v) for v in obj.tolist()]
|
|
52
|
+
if isinstance(obj, float) and (obj != obj): # NaN -> null
|
|
53
|
+
return None
|
|
54
|
+
return obj
|
|
55
|
+
|
|
56
|
+
class CleanRequest(BaseModel):
|
|
57
|
+
data: List[Dict[str, Any]]
|
|
58
|
+
drop_null_threshold: float = 0.5
|
|
59
|
+
remove_outliers: bool = False
|
|
60
|
+
|
|
61
|
+
class TransformRequest(BaseModel):
|
|
62
|
+
data: List[Dict[str, Any]]
|
|
63
|
+
operation: str
|
|
64
|
+
params: Dict[str, Any] = {}
|
|
65
|
+
|
|
66
|
+
class AnalyseRequest(BaseModel):
|
|
67
|
+
data: List[Dict[str, Any]]
|
|
68
|
+
|
|
69
|
+
@app.get("/health")
|
|
70
|
+
def health():
|
|
71
|
+
return {"status": "ok", "version": "0.1.0"}
|
|
72
|
+
|
|
73
|
+
@app.post("/ingest")
|
|
74
|
+
async def ingest(file: UploadFile = File(...)):
|
|
75
|
+
try:
|
|
76
|
+
contents = await file.read()
|
|
77
|
+
ext = file.filename.rsplit(".", 1)[-1].lower()
|
|
78
|
+
if ext == "csv":
|
|
79
|
+
df = pd.read_csv(io.BytesIO(contents))
|
|
80
|
+
elif ext == "json":
|
|
81
|
+
df = pd.read_json(io.BytesIO(contents))
|
|
82
|
+
elif ext == "xlsx":
|
|
83
|
+
df = pd.read_excel(io.BytesIO(contents))
|
|
84
|
+
elif ext == "parquet":
|
|
85
|
+
df = pd.read_parquet(io.BytesIO(contents))
|
|
86
|
+
else:
|
|
87
|
+
raise HTTPException(status_code=400, detail=f"Unsupported file type: {ext}")
|
|
88
|
+
return {"data": df_to_json(df), "rows": len(df), "columns": list(df.columns)}
|
|
89
|
+
except HTTPException:
|
|
90
|
+
raise
|
|
91
|
+
except Exception as e:
|
|
92
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
93
|
+
|
|
94
|
+
@app.post("/clean")
|
|
95
|
+
def clean(req: CleanRequest):
|
|
96
|
+
try:
|
|
97
|
+
df = pd.DataFrame(req.data)
|
|
98
|
+
df = drop_nulls(df, threshold=req.drop_null_threshold)
|
|
99
|
+
df = remove_duplicates(df)
|
|
100
|
+
df = standardise_columns(df)
|
|
101
|
+
return {"data": df_to_json(df), "rows": len(df), "columns": list(df.columns)}
|
|
102
|
+
except Exception as e:
|
|
103
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
104
|
+
|
|
105
|
+
@app.post("/transform")
|
|
106
|
+
def transform(req: TransformRequest):
|
|
107
|
+
try:
|
|
108
|
+
df = pd.DataFrame(req.data)
|
|
109
|
+
ops = {
|
|
110
|
+
"filter_rows": filter_rows,
|
|
111
|
+
"select_columns": select_columns,
|
|
112
|
+
"rename_columns": rename_columns,
|
|
113
|
+
"sort_rows": sort_rows,
|
|
114
|
+
"group_and_aggregate": group_and_aggregate,
|
|
115
|
+
"pivot": pivot,
|
|
116
|
+
"add_column": add_column,
|
|
117
|
+
}
|
|
118
|
+
if req.operation not in ops:
|
|
119
|
+
raise HTTPException(status_code=400, detail=f"Unknown operation: {req.operation}. Valid: {list(ops.keys())}")
|
|
120
|
+
result = ops[req.operation](df, **req.params)
|
|
121
|
+
return {"data": df_to_json(result), "rows": len(result), "columns": list(result.columns)}
|
|
122
|
+
except HTTPException:
|
|
123
|
+
raise
|
|
124
|
+
except Exception as e:
|
|
125
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
126
|
+
|
|
127
|
+
@app.post("/analyse")
|
|
128
|
+
def analyse(req: AnalyseRequest):
|
|
129
|
+
try:
|
|
130
|
+
df = pd.DataFrame(req.data)
|
|
131
|
+
return to_native(full_report(df))
|
|
132
|
+
except Exception as e:
|
|
133
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
134
|
+
|
|
135
|
+
class VisualiseRequest(BaseModel):
|
|
136
|
+
data: List[Dict[str, Any]]
|
|
137
|
+
chart: str
|
|
138
|
+
params: Dict[str, Any] = {}
|
|
139
|
+
|
|
140
|
+
@app.post("/visualise")
|
|
141
|
+
def visualise(req: VisualiseRequest):
|
|
142
|
+
try:
|
|
143
|
+
df = pd.DataFrame(req.data)
|
|
144
|
+
charts = {
|
|
145
|
+
"histogram": histogram,
|
|
146
|
+
"bar_chart": bar_chart,
|
|
147
|
+
"scatter": scatter,
|
|
148
|
+
"line_chart": line_chart,
|
|
149
|
+
"correlation_heatmap": correlation_heatmap,
|
|
150
|
+
}
|
|
151
|
+
if req.chart not in charts:
|
|
152
|
+
raise HTTPException(
|
|
153
|
+
status_code=400,
|
|
154
|
+
detail=f"Unknown chart: {req.chart}. Valid: {list(charts.keys())}",
|
|
155
|
+
)
|
|
156
|
+
result = charts[req.chart](df, **req.params)
|
|
157
|
+
return to_native(result)
|
|
158
|
+
except HTTPException:
|
|
159
|
+
raise
|
|
160
|
+
except Exception as e:
|
|
161
|
+
# bad/missing/non-numeric column raises ValueError -> client error
|
|
162
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
def drop_nulls(df, threshold=0.5):
|
|
6
|
+
cols_to_drop = df.columns[df.isnull().mean() > threshold]
|
|
7
|
+
df.drop(columns=cols_to_drop, inplace=True)
|
|
8
|
+
df.dropna(inplace=True)
|
|
9
|
+
return df
|
|
10
|
+
|
|
11
|
+
def fix_types(df):
|
|
12
|
+
for col in df.columns:
|
|
13
|
+
# Check bool BEFORE numeric: pandas treats bool as a numeric dtype, so
|
|
14
|
+
# the numeric branch would otherwise swallow boolean columns.
|
|
15
|
+
if pd.api.types.is_bool_dtype(df[col]):
|
|
16
|
+
df[col] = df[col].astype(bool)
|
|
17
|
+
elif pd.api.types.is_numeric_dtype(df[col]):
|
|
18
|
+
try:
|
|
19
|
+
df[col] = pd.to_numeric(df[col], errors='coerce')
|
|
20
|
+
except Exception as e:
|
|
21
|
+
print(f"Error converting column {col} to numeric: {e}")
|
|
22
|
+
elif pd.api.types.is_datetime64_any_dtype(df[col]):
|
|
23
|
+
continue
|
|
24
|
+
else:
|
|
25
|
+
try:
|
|
26
|
+
df[col] = pd.to_datetime(df[col], errors='coerce')
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print(f"Error converting column {col} to datetime: {e}")
|
|
29
|
+
return df
|
|
30
|
+
|
|
31
|
+
def remove_duplicates(df, subset=None):
|
|
32
|
+
if subset is not None:
|
|
33
|
+
df.drop_duplicates(subset=subset, inplace=True)
|
|
34
|
+
else:
|
|
35
|
+
df.drop_duplicates(inplace=True)
|
|
36
|
+
return df
|
|
37
|
+
|
|
38
|
+
def remove_outliers(df, columns=None, method='iqr'):
|
|
39
|
+
if method == 'iqr':
|
|
40
|
+
if columns is None:
|
|
41
|
+
numeric_df = df.select_dtypes(include=[np.number])
|
|
42
|
+
else:
|
|
43
|
+
numeric_df = df[columns]
|
|
44
|
+
Q1 = numeric_df.quantile(0.25); Q3 = numeric_df.quantile(0.75)
|
|
45
|
+
IQR = Q3 - Q1
|
|
46
|
+
lower = Q1 - 1.5 * IQR; upper = Q3 + 1.5 * IQR
|
|
47
|
+
df = df[~((numeric_df < lower) | (numeric_df > upper)).any(axis=1)]
|
|
48
|
+
return df
|
|
49
|
+
|
|
50
|
+
def standardise_columns(df):
|
|
51
|
+
df.columns = [col.lower().replace(" ", "_") for col in df.columns]
|
|
52
|
+
return df
|
|
53
|
+
|
|
54
|
+
def clean_all(df):
|
|
55
|
+
df = drop_nulls(df)
|
|
56
|
+
df = fix_types(df)
|
|
57
|
+
df = remove_duplicates(df)
|
|
58
|
+
df = remove_outliers(df)
|
|
59
|
+
df = standardise_columns(df)
|
|
60
|
+
return df
|