@mseep/csv-editor 1.0.0
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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
- package/.github/workflows/deploy-docs.yml +62 -0
- package/.github/workflows/publish-github.yml +52 -0
- package/.github/workflows/publish.yml +44 -0
- package/.github/workflows/test.yml +32 -0
- package/.pre-commit-config.yaml +157 -0
- package/ALTERNATIVE_PUBLISHING.md +175 -0
- package/ARCHITECTURE.md +1011 -0
- package/CHANGELOG.md +99 -0
- package/CODE_OF_CONDUCT.md +41 -0
- package/CONTRIBUTING.md +427 -0
- package/Dockerfile +22 -0
- package/LICENSE +21 -0
- package/MCP_CONFIG.md +505 -0
- package/PUBLISHING.md +210 -0
- package/README.md +400 -0
- package/SECURITY.md +61 -0
- package/docs/README.md +41 -0
- package/docs/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs/blog/2021-08-26-welcome/index.md +29 -0
- package/docs/blog/authors.yml +25 -0
- package/docs/blog/tags.yml +19 -0
- package/docs/docs/api/overview.md +183 -0
- package/docs/docs/installation.md +252 -0
- package/docs/docs/intro.md +87 -0
- package/docs/docs/tutorial-basics/_category_.json +8 -0
- package/docs/docs/tutorial-basics/congratulations.md +23 -0
- package/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
- package/docs/docs/tutorial-basics/create-a-document.md +57 -0
- package/docs/docs/tutorial-basics/create-a-page.md +43 -0
- package/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
- package/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
- package/docs/docs/tutorial-extras/_category_.json +7 -0
- package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
- package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
- package/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
- package/docs/docs/tutorial-extras/translate-your-site.md +88 -0
- package/docs/docs/tutorials/quickstart.md +365 -0
- package/docs/docusaurus.config.ts +163 -0
- package/docs/package-lock.json +17493 -0
- package/docs/package.json +48 -0
- package/docs/sidebars.ts +33 -0
- package/docs/src/components/HomepageFeatures/index.tsx +71 -0
- package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- package/docs/src/css/custom.css +30 -0
- package/docs/src/pages/index.module.css +23 -0
- package/docs/src/pages/index.tsx +44 -0
- package/docs/src/pages/markdown-page.md +7 -0
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/docusaurus-social-card.jpg +0 -0
- package/docs/static/img/docusaurus.png +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo.svg +1 -0
- package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docs/tsconfig.json +8 -0
- package/examples/README.md +48 -0
- package/examples/auto_save_demo.py +206 -0
- package/examples/auto_save_overwrite.py +201 -0
- package/examples/basic_usage.py +135 -0
- package/examples/demo.py +139 -0
- package/examples/history_demo.py +317 -0
- package/examples/test_default_autosave.py +124 -0
- package/examples/update_consignee_example.py +179 -0
- package/package.json +51 -0
- package/plans/2026-04-19-fastmcp3-migration-plan.md +1045 -0
- package/pyproject.toml +331 -0
- package/requirements-dev.txt +30 -0
- package/requirements.txt +22 -0
- package/scripts/publish.py +67 -0
- package/smithery.yaml +15 -0
- package/specs/2026-04-19-fastmcp3-migration-design.md +243 -0
- package/src/csv_editor/__init__.py +8 -0
- package/src/csv_editor/models/__init__.py +39 -0
- package/src/csv_editor/models/auto_save.py +246 -0
- package/src/csv_editor/models/csv_session.py +468 -0
- package/src/csv_editor/models/data_models.py +244 -0
- package/src/csv_editor/models/history_manager.py +456 -0
- package/src/csv_editor/prompts/__init__.py +0 -0
- package/src/csv_editor/prompts/data_prompts.py +13 -0
- package/src/csv_editor/resources/__init__.py +0 -0
- package/src/csv_editor/resources/csv_resources.py +22 -0
- package/src/csv_editor/server.py +640 -0
- package/src/csv_editor/tools/__init__.py +5 -0
- package/src/csv_editor/tools/analytics.py +700 -0
- package/src/csv_editor/tools/auto_save_operations.py +235 -0
- package/src/csv_editor/tools/data_operations.py +3 -0
- package/src/csv_editor/tools/history_operations.py +315 -0
- package/src/csv_editor/tools/io_operations.py +431 -0
- package/src/csv_editor/tools/transformations.py +663 -0
- package/src/csv_editor/tools/validation.py +822 -0
- package/src/csv_editor/utils/__init__.py +0 -0
- package/src/csv_editor/utils/validators.py +205 -0
- package/tests/README.md +65 -0
- package/tests/__init__.py +7 -0
- package/tests/conftest.py +50 -0
- package/tests/test_auto_save.py +378 -0
- package/tests/test_basic.py +103 -0
- package/tests/test_integration.py +356 -0
- package/tests/test_server_boot.py +50 -0
- package/tests/test_settings.py +184 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Example showing auto-save that overwrites the original file."""
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import tempfile
|
|
6
|
+
import os
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# Setup path for imports
|
|
11
|
+
import sys
|
|
12
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
13
|
+
|
|
14
|
+
from src.csv_editor.models.csv_session import CSVSession
|
|
15
|
+
from src.csv_editor.models.auto_save import AutoSaveConfig, AutoSaveMode, AutoSaveStrategy
|
|
16
|
+
from src.csv_editor.models.data_models import OperationType
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def overwrite_same_file_example():
|
|
20
|
+
"""Demonstrate auto-save that updates the same file."""
|
|
21
|
+
|
|
22
|
+
print("=" * 60)
|
|
23
|
+
print("Auto-Save: Overwrite Same File Example")
|
|
24
|
+
print("=" * 60)
|
|
25
|
+
|
|
26
|
+
# Create a temporary CSV file
|
|
27
|
+
temp_dir = tempfile.mkdtemp()
|
|
28
|
+
original_file = os.path.join(temp_dir, "data.csv")
|
|
29
|
+
|
|
30
|
+
# Create initial data
|
|
31
|
+
initial_data = pd.DataFrame({
|
|
32
|
+
'product': ['Laptop', 'Mouse', 'Keyboard'],
|
|
33
|
+
'price': [999.99, 29.99, 79.99],
|
|
34
|
+
'stock': [50, 200, 150]
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
# Save initial data to file
|
|
38
|
+
initial_data.to_csv(original_file, index=False)
|
|
39
|
+
print(f"\n✓ Created file: {original_file}")
|
|
40
|
+
print(f"Initial content:\n{initial_data}")
|
|
41
|
+
|
|
42
|
+
# Configure auto-save to OVERWRITE the same file
|
|
43
|
+
config = AutoSaveConfig(
|
|
44
|
+
enabled=True,
|
|
45
|
+
mode=AutoSaveMode.AFTER_OPERATION, # Save after each operation
|
|
46
|
+
strategy=AutoSaveStrategy.OVERWRITE, # Overwrite the original file
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Create session with auto-save config
|
|
50
|
+
session = CSVSession(auto_save_config=config)
|
|
51
|
+
|
|
52
|
+
# Load the CSV file
|
|
53
|
+
df = pd.read_csv(original_file)
|
|
54
|
+
session.load_data(df, file_path=original_file) # Pass the original file path
|
|
55
|
+
|
|
56
|
+
print(f"\n✓ Loaded data into session from: {original_file}")
|
|
57
|
+
|
|
58
|
+
# Modify the data
|
|
59
|
+
print("\n--- Making changes to the data ---")
|
|
60
|
+
|
|
61
|
+
# Change 1: Increase all prices by 10%
|
|
62
|
+
session.df['price'] = session.df['price'] * 1.1
|
|
63
|
+
session.record_operation(OperationType.TRANSFORM, {"operation": "price_increase_10%"})
|
|
64
|
+
await session.trigger_auto_save_if_needed()
|
|
65
|
+
print("✓ Increased prices by 10% and auto-saved to same file")
|
|
66
|
+
|
|
67
|
+
# Verify the file was updated
|
|
68
|
+
df_check1 = pd.read_csv(original_file)
|
|
69
|
+
print(f"File content after price increase:\n{df_check1}")
|
|
70
|
+
|
|
71
|
+
# Change 2: Add a new product
|
|
72
|
+
new_row = pd.DataFrame({
|
|
73
|
+
'product': ['Monitor'],
|
|
74
|
+
'price': [329.989], # Will be rounded when saved
|
|
75
|
+
'stock': [75]
|
|
76
|
+
})
|
|
77
|
+
session.df = pd.concat([session.df, new_row], ignore_index=True)
|
|
78
|
+
session.record_operation(OperationType.ADD_COLUMN, {"operation": "add_monitor"})
|
|
79
|
+
await session.trigger_auto_save_if_needed()
|
|
80
|
+
print("\n✓ Added new product and auto-saved to same file")
|
|
81
|
+
|
|
82
|
+
# Verify the file was updated again
|
|
83
|
+
df_check2 = pd.read_csv(original_file)
|
|
84
|
+
print(f"File content after adding product:\n{df_check2}")
|
|
85
|
+
|
|
86
|
+
# Change 3: Filter out low stock items (this removes data)
|
|
87
|
+
session.df = session.df[session.df['stock'] >= 100]
|
|
88
|
+
session.record_operation(OperationType.FILTER, {"condition": "stock >= 100"})
|
|
89
|
+
await session.trigger_auto_save_if_needed()
|
|
90
|
+
print("\n✓ Filtered low stock items and auto-saved to same file")
|
|
91
|
+
|
|
92
|
+
# Final verification
|
|
93
|
+
df_final = pd.read_csv(original_file)
|
|
94
|
+
print(f"Final file content:\n{df_final}")
|
|
95
|
+
|
|
96
|
+
print("\n" + "=" * 60)
|
|
97
|
+
print("Summary:")
|
|
98
|
+
print("=" * 60)
|
|
99
|
+
print(f"✓ Original file: {original_file}")
|
|
100
|
+
print("✓ Same file was overwritten after each operation")
|
|
101
|
+
print("✓ No backup copies were created")
|
|
102
|
+
print(f"✓ Final file has {len(df_final)} rows (started with {len(initial_data)})")
|
|
103
|
+
|
|
104
|
+
# Show that only one file exists (no backups)
|
|
105
|
+
files_in_dir = list(Path(temp_dir).glob("*.csv"))
|
|
106
|
+
print(f"\nFiles in directory: {[f.name for f in files_in_dir]}")
|
|
107
|
+
print(f"Total CSV files: {len(files_in_dir)} (only the original, no backups)")
|
|
108
|
+
|
|
109
|
+
return original_file
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async def hybrid_example_with_overwrite():
|
|
113
|
+
"""Example showing overwrite with periodic saves."""
|
|
114
|
+
|
|
115
|
+
print("\n" + "=" * 60)
|
|
116
|
+
print("Hybrid Mode with Overwrite Example")
|
|
117
|
+
print("=" * 60)
|
|
118
|
+
|
|
119
|
+
# Create a temporary CSV file
|
|
120
|
+
temp_dir = tempfile.mkdtemp()
|
|
121
|
+
original_file = os.path.join(temp_dir, "periodic_data.csv")
|
|
122
|
+
|
|
123
|
+
# Create initial data
|
|
124
|
+
data = pd.DataFrame({
|
|
125
|
+
'id': [1, 2, 3],
|
|
126
|
+
'value': [100, 200, 300]
|
|
127
|
+
})
|
|
128
|
+
data.to_csv(original_file, index=False)
|
|
129
|
+
print(f"\n✓ Created file: {original_file}")
|
|
130
|
+
|
|
131
|
+
# Configure hybrid mode with overwrite
|
|
132
|
+
config = AutoSaveConfig(
|
|
133
|
+
enabled=True,
|
|
134
|
+
mode=AutoSaveMode.HYBRID, # Both periodic and after-operation
|
|
135
|
+
strategy=AutoSaveStrategy.OVERWRITE,
|
|
136
|
+
interval_seconds=2 # Save every 2 seconds
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
session = CSVSession(auto_save_config=config)
|
|
140
|
+
df = pd.read_csv(original_file)
|
|
141
|
+
session.load_data(df, file_path=original_file)
|
|
142
|
+
|
|
143
|
+
# Start periodic saves
|
|
144
|
+
await session.auto_save_manager.start_periodic_save(session._save_callback)
|
|
145
|
+
print("✓ Started periodic auto-save (every 2 seconds)")
|
|
146
|
+
|
|
147
|
+
# Make some changes
|
|
148
|
+
print("\nMaking gradual changes...")
|
|
149
|
+
|
|
150
|
+
# Change 1
|
|
151
|
+
session.df['value'] = session.df['value'] + 10
|
|
152
|
+
print(" Added 10 to all values")
|
|
153
|
+
|
|
154
|
+
# Wait for periodic save
|
|
155
|
+
await asyncio.sleep(2.5)
|
|
156
|
+
|
|
157
|
+
# Change 2
|
|
158
|
+
session.df['value'] = session.df['value'] * 2
|
|
159
|
+
session.record_operation(OperationType.TRANSFORM, {"operation": "double_values"})
|
|
160
|
+
await session.trigger_auto_save_if_needed()
|
|
161
|
+
print(" Doubled all values (triggered immediate save)")
|
|
162
|
+
|
|
163
|
+
# Wait for another periodic save
|
|
164
|
+
await asyncio.sleep(2.5)
|
|
165
|
+
|
|
166
|
+
# Stop periodic saves
|
|
167
|
+
await session.auto_save_manager.stop_periodic_save()
|
|
168
|
+
print("\n✓ Stopped periodic saves")
|
|
169
|
+
|
|
170
|
+
# Check final file
|
|
171
|
+
df_final = pd.read_csv(original_file)
|
|
172
|
+
print(f"\nFinal file content:\n{df_final}")
|
|
173
|
+
|
|
174
|
+
return original_file
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def main():
|
|
178
|
+
"""Run all examples."""
|
|
179
|
+
|
|
180
|
+
# Example 1: Simple overwrite
|
|
181
|
+
file1 = await overwrite_same_file_example()
|
|
182
|
+
|
|
183
|
+
# Example 2: Hybrid mode with overwrite
|
|
184
|
+
file2 = await hybrid_example_with_overwrite()
|
|
185
|
+
|
|
186
|
+
print("\n" + "=" * 60)
|
|
187
|
+
print("✅ All examples completed!")
|
|
188
|
+
print("=" * 60)
|
|
189
|
+
print("\nKey Takeaways:")
|
|
190
|
+
print("• Use strategy='overwrite' to update the same file")
|
|
191
|
+
print("• Original file path must be provided when loading data")
|
|
192
|
+
print("• No backup copies are created with overwrite strategy")
|
|
193
|
+
print("• Works with all modes: after_operation, periodic, hybrid")
|
|
194
|
+
print("\nFor production use, consider:")
|
|
195
|
+
print("• Backup strategy for safety (creates copies)")
|
|
196
|
+
print("• Overwrite strategy for direct updates")
|
|
197
|
+
print("• Hybrid mode for maximum protection")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
if __name__ == "__main__":
|
|
201
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Basic usage example for CSV MCP Server
|
|
4
|
+
|
|
5
|
+
This script demonstrates basic CSV operations using the server's tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# Add parent directory to path
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
14
|
+
|
|
15
|
+
from src.csv_editor.tools.io_operations import (
|
|
16
|
+
load_csv_from_content,
|
|
17
|
+
export_csv,
|
|
18
|
+
get_session_info
|
|
19
|
+
)
|
|
20
|
+
from src.csv_editor.tools.transformations import (
|
|
21
|
+
filter_rows,
|
|
22
|
+
sort_data,
|
|
23
|
+
fill_missing_values
|
|
24
|
+
)
|
|
25
|
+
from src.csv_editor.tools.analytics import (
|
|
26
|
+
get_statistics,
|
|
27
|
+
get_column_statistics
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Sample CSV data
|
|
31
|
+
SAMPLE_DATA = """product,category,price,quantity,date
|
|
32
|
+
Laptop,Electronics,999.99,10,2024-01-15
|
|
33
|
+
Mouse,Electronics,29.99,50,2024-01-16
|
|
34
|
+
Desk,Furniture,299.99,5,2024-01-17
|
|
35
|
+
Chair,Furniture,149.99,15,2024-01-18
|
|
36
|
+
Keyboard,Electronics,79.99,25,2024-01-19
|
|
37
|
+
Monitor,Electronics,399.99,8,2024-01-20
|
|
38
|
+
Bookshelf,Furniture,199.99,7,2024-01-21
|
|
39
|
+
Headphones,Electronics,89.99,20,2024-01-22
|
|
40
|
+
Table,Furniture,499.99,3,2024-01-23
|
|
41
|
+
Webcam,Electronics,59.99,30,2024-01-24
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
async def main():
|
|
45
|
+
print("CSV MCP Server - Basic Usage Example")
|
|
46
|
+
print("=" * 40)
|
|
47
|
+
|
|
48
|
+
# 1. Load CSV data
|
|
49
|
+
print("\n1. Loading CSV data...")
|
|
50
|
+
result = await load_csv_from_content(
|
|
51
|
+
content=SAMPLE_DATA,
|
|
52
|
+
delimiter=","
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if not result["success"]:
|
|
56
|
+
print(f"Failed to load data: {result.get('error', 'Unknown error')}")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
session_id = result["session_id"]
|
|
60
|
+
print(f"✓ Loaded {result['rows_affected']} rows")
|
|
61
|
+
print(f" Session ID: {session_id}")
|
|
62
|
+
print(f" Columns: {', '.join(result['columns_affected'])}")
|
|
63
|
+
|
|
64
|
+
# 2. Get statistics
|
|
65
|
+
print("\n2. Calculating statistics...")
|
|
66
|
+
stats = await get_statistics(
|
|
67
|
+
session_id=session_id,
|
|
68
|
+
columns=["price", "quantity"]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if stats["success"]:
|
|
72
|
+
print("✓ Statistics calculated:")
|
|
73
|
+
for col, col_stats in stats["statistics"].items():
|
|
74
|
+
if isinstance(col_stats, dict):
|
|
75
|
+
print(f"\n {col}:")
|
|
76
|
+
print(f" Mean: ${col_stats.get('mean', 0):.2f}" if col == "price" else f" Mean: {col_stats.get('mean', 0):.1f}")
|
|
77
|
+
print(f" Min: ${col_stats.get('min', 0):.2f}" if col == "price" else f" Min: {col_stats.get('min', 0):.0f}")
|
|
78
|
+
print(f" Max: ${col_stats.get('max', 0):.2f}" if col == "price" else f" Max: {col_stats.get('max', 0):.0f}")
|
|
79
|
+
|
|
80
|
+
# 3. Filter data
|
|
81
|
+
print("\n3. Filtering electronics over $50...")
|
|
82
|
+
filtered = await filter_rows(
|
|
83
|
+
session_id=session_id,
|
|
84
|
+
conditions=[
|
|
85
|
+
{"column": "category", "operator": "==", "value": "Electronics"},
|
|
86
|
+
{"column": "price", "operator": ">", "value": 50}
|
|
87
|
+
],
|
|
88
|
+
mode="and"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if filtered["success"]:
|
|
92
|
+
print(f"✓ Filtered: {filtered['rows_before']} → {filtered['rows_after']} rows")
|
|
93
|
+
|
|
94
|
+
# 4. Sort data
|
|
95
|
+
print("\n4. Sorting by price (descending)...")
|
|
96
|
+
sorted_result = await sort_data(
|
|
97
|
+
session_id=session_id,
|
|
98
|
+
columns=[{"column": "price", "ascending": False}]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if sorted_result["success"]:
|
|
102
|
+
print("✓ Data sorted by price")
|
|
103
|
+
|
|
104
|
+
# 5. Export results
|
|
105
|
+
print("\n5. Exporting results...")
|
|
106
|
+
output_dir = Path("output")
|
|
107
|
+
output_dir.mkdir(exist_ok=True)
|
|
108
|
+
|
|
109
|
+
# Export as JSON
|
|
110
|
+
json_file = output_dir / "filtered_products.json"
|
|
111
|
+
export_result = await export_csv(
|
|
112
|
+
session_id=session_id,
|
|
113
|
+
file_path=str(json_file),
|
|
114
|
+
format="json"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if export_result["success"]:
|
|
118
|
+
print(f"✓ Exported to: {json_file}")
|
|
119
|
+
|
|
120
|
+
# Export as CSV
|
|
121
|
+
csv_file = output_dir / "filtered_products.csv"
|
|
122
|
+
export_result = await export_csv(
|
|
123
|
+
session_id=session_id,
|
|
124
|
+
file_path=str(csv_file),
|
|
125
|
+
format="csv"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if export_result["success"]:
|
|
129
|
+
print(f"✓ Exported to: {csv_file}")
|
|
130
|
+
|
|
131
|
+
print("\n" + "=" * 40)
|
|
132
|
+
print("Example completed successfully!")
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__":
|
|
135
|
+
asyncio.run(main())
|
package/examples/demo.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
CSV MCP Server Demo
|
|
4
|
+
|
|
5
|
+
Demonstrates the full capabilities of the CSV MCP Server.
|
|
6
|
+
Run this to see all features in action.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
14
|
+
|
|
15
|
+
from src.csv_editor.tools.io_operations import load_csv_from_content
|
|
16
|
+
from src.csv_editor.tools.transformations import filter_rows, sort_data
|
|
17
|
+
from src.csv_editor.tools.analytics import get_statistics, profile_data
|
|
18
|
+
from src.csv_editor.tools.validation import check_data_quality
|
|
19
|
+
|
|
20
|
+
# Demo data
|
|
21
|
+
DEMO_CSV = """employee_id,name,department,salary,years_experience,performance_rating
|
|
22
|
+
1001,Alice Johnson,Engineering,95000,5,4.5
|
|
23
|
+
1002,Bob Smith,Marketing,65000,3,3.8
|
|
24
|
+
1003,Charlie Davis,Engineering,105000,7,4.7
|
|
25
|
+
1004,Diana Wilson,Sales,72000,4,4.2
|
|
26
|
+
1005,Eve Martinez,Engineering,88000,4,4.1
|
|
27
|
+
1006,Frank Brown,Marketing,58000,2,3.5
|
|
28
|
+
1007,Grace Lee,Sales,78000,5,4.4
|
|
29
|
+
1008,Henry Taylor,Engineering,115000,10,4.9
|
|
30
|
+
1009,Iris Chen,Marketing,70000,4,4.0
|
|
31
|
+
1010,Jack White,Sales,68000,3,3.9
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
class Demo:
|
|
35
|
+
@staticmethod
|
|
36
|
+
def header(title: str):
|
|
37
|
+
print(f"\n{'='*50}")
|
|
38
|
+
print(f" {title}")
|
|
39
|
+
print(f"{'='*50}")
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def success(msg: str):
|
|
43
|
+
print(f"✅ {msg}")
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def info(msg: str):
|
|
47
|
+
print(f"ℹ️ {msg}")
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def result(label: str, value: any):
|
|
51
|
+
print(f" {label}: {value}")
|
|
52
|
+
|
|
53
|
+
async def run_demo():
|
|
54
|
+
Demo.header("CSV MCP Server - Feature Demo")
|
|
55
|
+
|
|
56
|
+
# Load data
|
|
57
|
+
Demo.info("Loading employee data...")
|
|
58
|
+
result = await load_csv_from_content(content=DEMO_CSV)
|
|
59
|
+
|
|
60
|
+
if not result["success"]:
|
|
61
|
+
print(f"Error loading data: {result.get('error')}")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
session_id = result["session_id"]
|
|
65
|
+
Demo.success(f"Loaded {result['rows_affected']} employees")
|
|
66
|
+
|
|
67
|
+
# Data Quality Check
|
|
68
|
+
Demo.header("Data Quality Assessment")
|
|
69
|
+
quality = await check_data_quality(session_id=session_id)
|
|
70
|
+
|
|
71
|
+
if quality["success"]:
|
|
72
|
+
metrics = quality["quality_results"]["metrics"]
|
|
73
|
+
Demo.result("Overall Score", f"{quality['quality_results']['overall_score']:.1f}%")
|
|
74
|
+
Demo.result("Completeness", f"{metrics['completeness']:.1f}%")
|
|
75
|
+
Demo.result("Uniqueness", f"{metrics['uniqueness']:.1f}%")
|
|
76
|
+
Demo.result("Consistency", f"{metrics['consistency']:.1f}%")
|
|
77
|
+
|
|
78
|
+
# Statistics
|
|
79
|
+
Demo.header("Salary Statistics by Department")
|
|
80
|
+
stats = await get_statistics(
|
|
81
|
+
session_id=session_id,
|
|
82
|
+
columns=["salary", "years_experience", "performance_rating"]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if stats["success"]:
|
|
86
|
+
salary_stats = stats["statistics"].get("salary", {})
|
|
87
|
+
Demo.result("Average Salary", f"${salary_stats.get('mean', 0):,.2f}")
|
|
88
|
+
Demo.result("Salary Range", f"${salary_stats.get('min', 0):,.0f} - ${salary_stats.get('max', 0):,.0f}")
|
|
89
|
+
Demo.result("Median Salary", f"${salary_stats.get('50%', 0):,.2f}")
|
|
90
|
+
|
|
91
|
+
# Filter high performers
|
|
92
|
+
Demo.header("High Performers Analysis")
|
|
93
|
+
filtered = await filter_rows(
|
|
94
|
+
session_id=session_id,
|
|
95
|
+
conditions=[
|
|
96
|
+
{"column": "performance_rating", "operator": ">=", "value": 4.0},
|
|
97
|
+
{"column": "salary", "operator": ">", "value": 70000}
|
|
98
|
+
],
|
|
99
|
+
mode="and"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if filtered["success"]:
|
|
103
|
+
Demo.success(f"Found {filtered['rows_after']} high performers")
|
|
104
|
+
Demo.info(f"({filtered['rows_after']}/{filtered['rows_before']} = {filtered['rows_after']/filtered['rows_before']*100:.1f}% of employees)")
|
|
105
|
+
|
|
106
|
+
# Profile the data
|
|
107
|
+
Demo.header("Data Profile Summary")
|
|
108
|
+
profile = await profile_data(
|
|
109
|
+
session_id=session_id,
|
|
110
|
+
include_correlations=True,
|
|
111
|
+
include_outliers=False
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if profile["success"]:
|
|
115
|
+
summary = profile["profile"]["summary"]
|
|
116
|
+
Demo.result("Total Rows", summary["total_rows"])
|
|
117
|
+
Demo.result("Total Columns", summary["total_columns"])
|
|
118
|
+
Demo.result("Numeric Columns", summary["numeric_columns"])
|
|
119
|
+
Demo.result("Text Columns", summary["text_columns"])
|
|
120
|
+
|
|
121
|
+
# Show correlations if any
|
|
122
|
+
if "correlations" in profile["profile"]:
|
|
123
|
+
correlations = profile["profile"]["correlations"]
|
|
124
|
+
if "significant_correlations" in correlations:
|
|
125
|
+
Demo.info("\nSignificant Correlations:")
|
|
126
|
+
for corr in correlations["significant_correlations"][:3]:
|
|
127
|
+
Demo.result(
|
|
128
|
+
f"{corr['column1']} ↔ {corr['column2']}",
|
|
129
|
+
f"{corr['correlation']:.3f}"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
Demo.header("Demo Complete!")
|
|
133
|
+
Demo.success("All features demonstrated successfully")
|
|
134
|
+
Demo.info(f"Session ID: {session_id}")
|
|
135
|
+
print()
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
print("\n🚀 Starting CSV MCP Server Demo...\n")
|
|
139
|
+
asyncio.run(run_demo())
|