site-calc-investment 1.2.2__tar.gz → 1.2.3__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.
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/CHANGELOG.md +19 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/PKG-INFO +55 -1
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/README.md +54 -0
- site_calc_investment-1.2.3/docs/MCP_SERVER_SPEC.md +413 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/pyproject.toml +1 -1
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/__init__.py +1 -1
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/mcp/config.py +6 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/mcp/data_loaders.py +70 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/mcp/server.py +62 -8
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_mcp/test_data_loaders.py +107 -1
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_mcp/test_integration.py +23 -1
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_mcp/test_tools.py +49 -1
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/uv.lock +193 -194
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/.github/workflows/ci.yml +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/.github/workflows/publish.yml +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/.gitignore +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/CONTRIBUTING.md +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/LICENSE +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/MIGRATION_GUIDE.md +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/QUICK_START.md +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/READY_TO_PUBLISH.md +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/docs/INVESTMENT_CLIENT_SPEC.md +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/examples/01_basic_capacity_planning.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/examples/02_scenario_comparison.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/examples/03_financial_analysis.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/publish_to_github.bat +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/publish_to_github.sh +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/analysis/__init__.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/analysis/comparison.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/analysis/financial.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/api/__init__.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/api/client.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/exceptions.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/mcp/__init__.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/mcp/scenario.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/models/__init__.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/models/common.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/models/devices.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/models/requests.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/models/responses.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/conftest.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_api_client.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_common_models.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_device_models.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_financial_analysis.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_mcp/__init__.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_mcp/conftest.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_mcp/test_mcp_production.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_mcp/test_scenario.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_production.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_request_models.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/tests/test_scenario_comparison.py +0 -0
- {site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/verify_ready.py +0 -0
|
@@ -5,6 +5,25 @@ All notable changes to the Site-Calc Investment Client will be documented in thi
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.3] - 2026-02-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`save_data_file` MCP tool**: New tool (#15) that writes generated data (price arrays, demand
|
|
12
|
+
profiles) to CSV files on the local filesystem. Solves the problem where the LLM cannot write
|
|
13
|
+
files directly but the MCP server can.
|
|
14
|
+
- Supports named columns with automatic `.csv` extension
|
|
15
|
+
- Relative paths resolve against `INVESTMENT_DATA_DIR` environment variable
|
|
16
|
+
- Returned file path can be used directly in `add_device` via `{"file": "...", "column": "..."}`
|
|
17
|
+
- **`INVESTMENT_DATA_DIR` environment variable**: Optional config for `save_data_file` base directory
|
|
18
|
+
- **MCP Server specification**: Full docs at `docs/MCP_SERVER_SPEC.md`
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- MCP server instructions updated to inform the LLM about `save_data_file` capability
|
|
22
|
+
- MCP tool count: 14 -> 15
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
|
|
8
27
|
## [1.2.2] - 2026-02-04
|
|
9
28
|
|
|
10
29
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: site-calc-investment
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: Python client for Site-Calc investment planning (capacity sizing, ROI analysis)
|
|
5
5
|
Project-URL: Homepage, https://github.com/stranma/site-calc-investment
|
|
6
6
|
Project-URL: Documentation, https://github.com/stranma/site-calc-investment#readme
|
|
@@ -212,6 +212,60 @@ comparison = compare_scenarios(
|
|
|
212
212
|
print(comparison) # DataFrame with NPV, IRR, costs, revenues
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
+
## MCP Server (Claude Desktop Integration)
|
|
216
|
+
|
|
217
|
+
The package includes an MCP server for use with Claude Desktop and other LLM tools.
|
|
218
|
+
|
|
219
|
+
### Installation
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
pip install site-calc-investment[mcp]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Claude Desktop Configuration
|
|
226
|
+
|
|
227
|
+
Add to `claude_desktop_config.json`:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"mcpServers": {
|
|
232
|
+
"site-calc-investment": {
|
|
233
|
+
"command": "uv",
|
|
234
|
+
"args": ["run", "--directory", "/path/to/client-investment", "site-calc-investment-mcp"],
|
|
235
|
+
"env": {
|
|
236
|
+
"INVESTMENT_API_URL": "http://your-api-url",
|
|
237
|
+
"INVESTMENT_API_KEY": "inv_your_key_here",
|
|
238
|
+
"INVESTMENT_DATA_DIR": "/path/to/data/directory"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Tools (15)
|
|
246
|
+
|
|
247
|
+
| Tool | Description |
|
|
248
|
+
|------|-------------|
|
|
249
|
+
| `create_scenario` | Create a new draft scenario |
|
|
250
|
+
| `add_device` | Add a device (battery, CHP, PV, etc.) |
|
|
251
|
+
| `set_timespan` | Set optimization time horizon |
|
|
252
|
+
| `set_investment_params` | Set financial parameters (NPV, IRR) |
|
|
253
|
+
| `review_scenario` | Review scenario before submission |
|
|
254
|
+
| `remove_device` | Remove a device |
|
|
255
|
+
| `delete_scenario` | Delete a scenario |
|
|
256
|
+
| `list_scenarios` | List all draft scenarios |
|
|
257
|
+
| `submit_scenario` | Submit for optimization |
|
|
258
|
+
| `get_job_status` | Check job progress |
|
|
259
|
+
| `get_job_result` | Get optimization results |
|
|
260
|
+
| `cancel_job` | Cancel a job |
|
|
261
|
+
| `list_jobs` | List all jobs |
|
|
262
|
+
| `get_device_schema` | Get device property schema |
|
|
263
|
+
| `save_data_file` | Save generated data as CSV |
|
|
264
|
+
|
|
265
|
+
`save_data_file` lets the LLM write generated data (price arrays, demand profiles) to local CSV files, which can then be referenced in `add_device` properties.
|
|
266
|
+
|
|
267
|
+
See [docs/MCP_SERVER_SPEC.md](docs/MCP_SERVER_SPEC.md) for full specification.
|
|
268
|
+
|
|
215
269
|
## Documentation
|
|
216
270
|
|
|
217
271
|
Full documentation available at: https://github.com/stranma/site-calc-investment#readme
|
|
@@ -172,6 +172,60 @@ comparison = compare_scenarios(
|
|
|
172
172
|
print(comparison) # DataFrame with NPV, IRR, costs, revenues
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
+
## MCP Server (Claude Desktop Integration)
|
|
176
|
+
|
|
177
|
+
The package includes an MCP server for use with Claude Desktop and other LLM tools.
|
|
178
|
+
|
|
179
|
+
### Installation
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
pip install site-calc-investment[mcp]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Claude Desktop Configuration
|
|
186
|
+
|
|
187
|
+
Add to `claude_desktop_config.json`:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"mcpServers": {
|
|
192
|
+
"site-calc-investment": {
|
|
193
|
+
"command": "uv",
|
|
194
|
+
"args": ["run", "--directory", "/path/to/client-investment", "site-calc-investment-mcp"],
|
|
195
|
+
"env": {
|
|
196
|
+
"INVESTMENT_API_URL": "http://your-api-url",
|
|
197
|
+
"INVESTMENT_API_KEY": "inv_your_key_here",
|
|
198
|
+
"INVESTMENT_DATA_DIR": "/path/to/data/directory"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Tools (15)
|
|
206
|
+
|
|
207
|
+
| Tool | Description |
|
|
208
|
+
|------|-------------|
|
|
209
|
+
| `create_scenario` | Create a new draft scenario |
|
|
210
|
+
| `add_device` | Add a device (battery, CHP, PV, etc.) |
|
|
211
|
+
| `set_timespan` | Set optimization time horizon |
|
|
212
|
+
| `set_investment_params` | Set financial parameters (NPV, IRR) |
|
|
213
|
+
| `review_scenario` | Review scenario before submission |
|
|
214
|
+
| `remove_device` | Remove a device |
|
|
215
|
+
| `delete_scenario` | Delete a scenario |
|
|
216
|
+
| `list_scenarios` | List all draft scenarios |
|
|
217
|
+
| `submit_scenario` | Submit for optimization |
|
|
218
|
+
| `get_job_status` | Check job progress |
|
|
219
|
+
| `get_job_result` | Get optimization results |
|
|
220
|
+
| `cancel_job` | Cancel a job |
|
|
221
|
+
| `list_jobs` | List all jobs |
|
|
222
|
+
| `get_device_schema` | Get device property schema |
|
|
223
|
+
| `save_data_file` | Save generated data as CSV |
|
|
224
|
+
|
|
225
|
+
`save_data_file` lets the LLM write generated data (price arrays, demand profiles) to local CSV files, which can then be referenced in `add_device` properties.
|
|
226
|
+
|
|
227
|
+
See [docs/MCP_SERVER_SPEC.md](docs/MCP_SERVER_SPEC.md) for full specification.
|
|
228
|
+
|
|
175
229
|
## Documentation
|
|
176
230
|
|
|
177
231
|
Full documentation available at: https://github.com/stranma/site-calc-investment#readme
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# MCP Server Specification
|
|
2
|
+
|
|
3
|
+
**Package:** `site-calc-investment[mcp]`
|
|
4
|
+
**Server name:** `site-calc-investment`
|
|
5
|
+
**Protocol:** MCP (Model Context Protocol) via FastMCP
|
|
6
|
+
**Tools:** 15
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Overview
|
|
11
|
+
|
|
12
|
+
The MCP server exposes the Site-Calc investment planning API as tools that LLMs (e.g., Claude Desktop) can call interactively. Users describe what they want to optimize in natural language, and the LLM assembles scenarios, submits jobs, and retrieves results through these tools.
|
|
13
|
+
|
|
14
|
+
### 1.1 Architecture
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Claude Desktop (LLM)
|
|
18
|
+
|
|
|
19
|
+
| MCP protocol (stdio)
|
|
20
|
+
v
|
|
21
|
+
site-calc-investment-mcp (local process)
|
|
22
|
+
|
|
|
23
|
+
| HTTPS / REST
|
|
24
|
+
v
|
|
25
|
+
Site-Calc API (remote server)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The MCP server runs **locally** on the user's machine. It has full filesystem access (for CSV data files) and network access (for the optimization API).
|
|
29
|
+
|
|
30
|
+
### 1.2 Key Capabilities
|
|
31
|
+
|
|
32
|
+
| Feature | Value |
|
|
33
|
+
|---------|-------|
|
|
34
|
+
| Tools | 15 |
|
|
35
|
+
| Device types | 10 |
|
|
36
|
+
| Max horizon | 100,000 intervals (~11 years) |
|
|
37
|
+
| Resolution | 1-hour |
|
|
38
|
+
| Local filesystem | Read + Write (CSV data files) |
|
|
39
|
+
| API connection | HTTPS to Site-Calc server |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. Configuration
|
|
44
|
+
|
|
45
|
+
### 2.1 Environment Variables
|
|
46
|
+
|
|
47
|
+
| Variable | Required | Description |
|
|
48
|
+
|----------|----------|-------------|
|
|
49
|
+
| `INVESTMENT_API_URL` | Yes | Site-Calc API base URL |
|
|
50
|
+
| `INVESTMENT_API_KEY` | Yes | API key (starts with `inv_`) |
|
|
51
|
+
| `INVESTMENT_DATA_DIR` | No | Default directory for `save_data_file` relative paths |
|
|
52
|
+
|
|
53
|
+
`INVESTMENT_API_URL` and `INVESTMENT_API_KEY` are required for job submission tools (`submit_scenario`, `get_job_status`, `get_job_result`, `cancel_job`). Scenario assembly tools work without them.
|
|
54
|
+
|
|
55
|
+
`INVESTMENT_DATA_DIR` sets the base directory for resolving relative paths in `save_data_file`. If not set, relative paths resolve against the current working directory.
|
|
56
|
+
|
|
57
|
+
### 2.2 Claude Desktop Configuration
|
|
58
|
+
|
|
59
|
+
Add to `claude_desktop_config.json`:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"site-calc-investment": {
|
|
65
|
+
"command": "C:\\Users\\Admin\\.local\\bin\\uv.exe",
|
|
66
|
+
"args": [
|
|
67
|
+
"run",
|
|
68
|
+
"--directory", "C:\\my_source\\site-calc\\client-investment",
|
|
69
|
+
"site-calc-investment-mcp"
|
|
70
|
+
],
|
|
71
|
+
"env": {
|
|
72
|
+
"INVESTMENT_API_URL": "https://api.site-calc.example.com",
|
|
73
|
+
"INVESTMENT_API_KEY": "inv_your_api_key_here",
|
|
74
|
+
"INVESTMENT_DATA_DIR": "C:\\my_source\\BESS_Optimization_Tool"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 2.3 Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install site-calc-investment[mcp]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Or with uv (recommended for development):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
cd client-investment
|
|
91
|
+
uv sync --group dev
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 3. Tool Reference
|
|
97
|
+
|
|
98
|
+
### 3.1 Scenario Assembly Tools
|
|
99
|
+
|
|
100
|
+
#### `create_scenario`
|
|
101
|
+
|
|
102
|
+
Create a new draft optimization scenario.
|
|
103
|
+
|
|
104
|
+
| Parameter | Type | Required | Description |
|
|
105
|
+
|-----------|------|----------|-------------|
|
|
106
|
+
| `name` | string | Yes | Human-readable name |
|
|
107
|
+
| `description` | string | No | Longer description |
|
|
108
|
+
|
|
109
|
+
**Returns:** `{"scenario_id": "sc_...", "name": "..."}`
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
#### `add_device`
|
|
114
|
+
|
|
115
|
+
Add a device to a draft scenario.
|
|
116
|
+
|
|
117
|
+
| Parameter | Type | Required | Description |
|
|
118
|
+
|-----------|------|----------|-------------|
|
|
119
|
+
| `scenario_id` | string | Yes | Target scenario |
|
|
120
|
+
| `device_type` | string | Yes | One of 10 device types (see Section 4) |
|
|
121
|
+
| `name` | string | Yes | Unique device name within scenario |
|
|
122
|
+
| `properties` | object | Yes | Device-specific properties |
|
|
123
|
+
| `schedule` | object | No | Runtime constraints |
|
|
124
|
+
|
|
125
|
+
Properties support data shorthand:
|
|
126
|
+
- A number (e.g., `50.0`) -- expanded to constant array matching the timespan
|
|
127
|
+
- A list (e.g., `[30, 40, 80, 50]`) -- used directly
|
|
128
|
+
- A file reference (e.g., `{"file": "prices.csv", "column": "price_eur"}`) -- loaded from local CSV
|
|
129
|
+
|
|
130
|
+
**Returns:** Summary string.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
#### `set_timespan`
|
|
135
|
+
|
|
136
|
+
Set the optimization time horizon.
|
|
137
|
+
|
|
138
|
+
| Parameter | Type | Required | Default | Description |
|
|
139
|
+
|-----------|------|----------|---------|-------------|
|
|
140
|
+
| `scenario_id` | string | Yes | | Target scenario |
|
|
141
|
+
| `start_year` | int | Yes | | Start year (e.g., 2025) |
|
|
142
|
+
| `years` | int | No | 1 | Number of years |
|
|
143
|
+
|
|
144
|
+
One year = 8,760 intervals. Maximum ~11 years (100,000 intervals).
|
|
145
|
+
|
|
146
|
+
**Returns:** Confirmation string with interval count.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
#### `set_investment_params`
|
|
151
|
+
|
|
152
|
+
Set financial parameters for ROI calculation (NPV, IRR, payback).
|
|
153
|
+
|
|
154
|
+
| Parameter | Type | Required | Default | Description |
|
|
155
|
+
|-----------|------|----------|---------|-------------|
|
|
156
|
+
| `scenario_id` | string | Yes | | Target scenario |
|
|
157
|
+
| `discount_rate` | float | No | 0.05 | Annual discount rate (0-0.5) |
|
|
158
|
+
| `project_lifetime_years` | int | No | timespan years | Project lifetime |
|
|
159
|
+
| `device_capital_costs` | object | No | | CAPEX by device name (EUR) |
|
|
160
|
+
| `device_annual_opex` | object | No | | Annual O&M by device name (EUR) |
|
|
161
|
+
|
|
162
|
+
**Returns:** Confirmation string.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
#### `review_scenario`
|
|
167
|
+
|
|
168
|
+
Show a summary of the draft scenario before submitting.
|
|
169
|
+
|
|
170
|
+
| Parameter | Type | Required | Description |
|
|
171
|
+
|-----------|------|----------|-------------|
|
|
172
|
+
| `scenario_id` | string | Yes | Scenario to review |
|
|
173
|
+
|
|
174
|
+
**Returns:** Dict with `name`, `devices`, `timespan`, `investment_params`, `validation`.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
#### `remove_device`
|
|
179
|
+
|
|
180
|
+
Remove a device from a draft scenario.
|
|
181
|
+
|
|
182
|
+
| Parameter | Type | Required | Description |
|
|
183
|
+
|-----------|------|----------|-------------|
|
|
184
|
+
| `scenario_id` | string | Yes | Target scenario |
|
|
185
|
+
| `device_name` | string | Yes | Device to remove |
|
|
186
|
+
|
|
187
|
+
**Returns:** Confirmation string.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
#### `delete_scenario`
|
|
192
|
+
|
|
193
|
+
Delete a draft scenario entirely.
|
|
194
|
+
|
|
195
|
+
| Parameter | Type | Required | Description |
|
|
196
|
+
|-----------|------|----------|-------------|
|
|
197
|
+
| `scenario_id` | string | Yes | Scenario to delete |
|
|
198
|
+
|
|
199
|
+
**Returns:** Confirmation string.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
#### `list_scenarios`
|
|
204
|
+
|
|
205
|
+
List all active draft scenarios.
|
|
206
|
+
|
|
207
|
+
**Returns:** List of `{"id", "name", "device_count", "has_timespan", "job_count"}`.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### 3.2 Job Submission and Management Tools
|
|
212
|
+
|
|
213
|
+
#### `submit_scenario`
|
|
214
|
+
|
|
215
|
+
Submit a draft scenario for server-side optimization.
|
|
216
|
+
|
|
217
|
+
| Parameter | Type | Required | Default | Description |
|
|
218
|
+
|-----------|------|----------|---------|-------------|
|
|
219
|
+
| `scenario_id` | string | Yes | | Scenario to submit |
|
|
220
|
+
| `objective` | string | No | `maximize_profit` | Optimization objective |
|
|
221
|
+
| `solver_timeout` | int | No | 300 | Time limit in seconds (max 900) |
|
|
222
|
+
|
|
223
|
+
Objectives: `maximize_profit`, `minimize_cost`, `maximize_self_consumption`.
|
|
224
|
+
|
|
225
|
+
**Returns:** `{"job_id": "...", "status": "pending"}`
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
#### `get_job_status`
|
|
230
|
+
|
|
231
|
+
Check job status and progress.
|
|
232
|
+
|
|
233
|
+
| Parameter | Type | Required | Description |
|
|
234
|
+
|-----------|------|----------|-------------|
|
|
235
|
+
| `job_id` | string | Yes | Job identifier |
|
|
236
|
+
|
|
237
|
+
**Returns:** Dict with `job_id`, `status`, and optionally `progress`, `message`, `estimated_completion_seconds`, `solver_time_seconds`, `error`.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
#### `get_job_result`
|
|
242
|
+
|
|
243
|
+
Retrieve completed optimization results.
|
|
244
|
+
|
|
245
|
+
| Parameter | Type | Required | Default | Description |
|
|
246
|
+
|-----------|------|----------|---------|-------------|
|
|
247
|
+
| `job_id` | string | Yes | | Job identifier |
|
|
248
|
+
| `detail_level` | string | No | `summary` | `summary`, `monthly`, or `full` |
|
|
249
|
+
|
|
250
|
+
Detail levels:
|
|
251
|
+
- **summary** -- Aggregated totals (profit, cost, solve time, investment metrics). Compact.
|
|
252
|
+
- **monthly** -- Summary + per-device monthly breakdowns.
|
|
253
|
+
- **full** -- All data including hourly schedules. Can be very large.
|
|
254
|
+
|
|
255
|
+
**Returns:** Result dict at requested detail level.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
#### `cancel_job`
|
|
260
|
+
|
|
261
|
+
Cancel a pending or running job.
|
|
262
|
+
|
|
263
|
+
| Parameter | Type | Required | Description |
|
|
264
|
+
|-----------|------|----------|-------------|
|
|
265
|
+
| `job_id` | string | Yes | Job to cancel |
|
|
266
|
+
|
|
267
|
+
**Returns:** `{"job_id": "...", "status": "cancelled"}`
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
#### `list_jobs`
|
|
272
|
+
|
|
273
|
+
List all scenarios and their associated jobs.
|
|
274
|
+
|
|
275
|
+
**Returns:** List of `{"scenario_id", "scenario_name", "job_ids", "job_count"}`.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### 3.3 Data File Tools
|
|
280
|
+
|
|
281
|
+
#### `save_data_file`
|
|
282
|
+
|
|
283
|
+
Save generated data to a CSV file on the local filesystem.
|
|
284
|
+
|
|
285
|
+
This tool exists because the LLM cannot write files directly, but this MCP server runs locally and can. Use it to persist generated data arrays (prices, demand profiles, etc.) so they can be referenced in `add_device` via `{"file": "<path>", "column": "<name>"}`.
|
|
286
|
+
|
|
287
|
+
| Parameter | Type | Required | Default | Description |
|
|
288
|
+
|-----------|------|----------|---------|-------------|
|
|
289
|
+
| `file_path` | string | Yes | | Filename or path (e.g., `"prices_2025.csv"`) |
|
|
290
|
+
| `columns` | object | Yes | | Named columns: `{"col_name": [float, ...]}` |
|
|
291
|
+
| `overwrite` | bool | No | false | Allow overwriting existing files |
|
|
292
|
+
|
|
293
|
+
Path resolution:
|
|
294
|
+
- Absolute paths are used as-is
|
|
295
|
+
- Relative paths resolve against `INVESTMENT_DATA_DIR` (or cwd if not set)
|
|
296
|
+
- `.csv` extension is appended if missing
|
|
297
|
+
- Non-`.csv` extensions are rejected
|
|
298
|
+
|
|
299
|
+
**Returns:**
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"file_path": "C:\\Users\\Admin\\data\\prices.csv",
|
|
303
|
+
"columns": ["hour", "price_eur_mwh"],
|
|
304
|
+
"rows": 8760,
|
|
305
|
+
"message": "Saved 8760 rows to C:\\Users\\Admin\\data\\prices.csv"
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Typical workflow:**
|
|
310
|
+
```
|
|
311
|
+
1. LLM generates price array (8760 values)
|
|
312
|
+
2. LLM calls save_data_file(file_path="prices_2025.csv", columns={"price_eur": [...]})
|
|
313
|
+
3. LLM calls add_device(properties={"price": {"file": "C:/.../prices_2025.csv", "column": "price_eur"}, ...})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
### 3.4 Helper Tools
|
|
319
|
+
|
|
320
|
+
#### `get_device_schema`
|
|
321
|
+
|
|
322
|
+
Get the properties schema for a device type.
|
|
323
|
+
|
|
324
|
+
| Parameter | Type | Required | Description |
|
|
325
|
+
|-----------|------|----------|-------------|
|
|
326
|
+
| `device_type` | string | Yes | Device type name |
|
|
327
|
+
|
|
328
|
+
**Returns:** Schema dict with `properties`, `supports_schedule`, `example`.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## 4. Supported Device Types
|
|
333
|
+
|
|
334
|
+
| Device Type | Description | Key Properties |
|
|
335
|
+
|-------------|-------------|----------------|
|
|
336
|
+
| `battery` | Battery energy storage | capacity, max_power, efficiency |
|
|
337
|
+
| `chp` | Combined heat and power | gas_input, el_output, heat_output |
|
|
338
|
+
| `heat_accumulator` | Thermal storage | capacity, max_power, efficiency |
|
|
339
|
+
| `photovoltaic` | Solar PV | peak_power_mw, location, tilt, azimuth |
|
|
340
|
+
| `electricity_import` | Buy from grid | price, max_import |
|
|
341
|
+
| `electricity_export` | Sell to grid | price, max_export |
|
|
342
|
+
| `gas_import` | Gas supply | price, max_import |
|
|
343
|
+
| `heat_export` | Sell heat | price, max_export |
|
|
344
|
+
| `electricity_demand` | Electricity load | max_demand_profile |
|
|
345
|
+
| `heat_demand` | Heat load | max_demand_profile |
|
|
346
|
+
|
|
347
|
+
Use `get_device_schema(device_type)` for full property documentation.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 5. End-to-End Example
|
|
352
|
+
|
|
353
|
+
A typical session for battery arbitrage analysis:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
User: "Evaluate a 10 MWh battery with 2025 German electricity prices"
|
|
357
|
+
|
|
358
|
+
LLM actions:
|
|
359
|
+
1. Generate 8760 hourly prices for 2025
|
|
360
|
+
2. save_data_file(file_path="de_prices_2025.csv",
|
|
361
|
+
columns={"hour": [0..8759], "price_eur_mwh": [32.1, 28.5, ...]})
|
|
362
|
+
3. create_scenario(name="10 MWh Battery - DE 2025")
|
|
363
|
+
4. set_timespan(scenario_id=sid, start_year=2025)
|
|
364
|
+
5. add_device(scenario_id=sid, device_type="battery", name="BESS",
|
|
365
|
+
properties={"capacity": 10.0, "max_power": 5.0, "efficiency": 0.90})
|
|
366
|
+
6. add_device(scenario_id=sid, device_type="electricity_import", name="GridBuy",
|
|
367
|
+
properties={"price": {"file": ".../de_prices_2025.csv", "column": "price_eur_mwh"},
|
|
368
|
+
"max_import": 5.0})
|
|
369
|
+
7. add_device(scenario_id=sid, device_type="electricity_export", name="GridSell",
|
|
370
|
+
properties={"price": {"file": ".../de_prices_2025.csv", "column": "price_eur_mwh"},
|
|
371
|
+
"max_export": 5.0})
|
|
372
|
+
8. set_investment_params(scenario_id=sid, discount_rate=0.05,
|
|
373
|
+
device_capital_costs={"BESS": 500000})
|
|
374
|
+
9. review_scenario(scenario_id=sid)
|
|
375
|
+
10. submit_scenario(scenario_id=sid)
|
|
376
|
+
11. get_job_status(job_id=jid) -- poll until complete
|
|
377
|
+
12. get_job_result(job_id=jid, detail_level="summary")
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
LLM then presents the results: profit, NPV, IRR, payback period.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## 6. Error Handling
|
|
385
|
+
|
|
386
|
+
Tools raise standard Python exceptions that FastMCP translates to MCP error responses:
|
|
387
|
+
|
|
388
|
+
| Exception | Cause |
|
|
389
|
+
|-----------|-------|
|
|
390
|
+
| `ValueError` | Invalid parameters (wrong device type, missing properties, bad column data) |
|
|
391
|
+
| `FileNotFoundError` | Referenced CSV/JSON file does not exist |
|
|
392
|
+
| `FileExistsError` | `save_data_file` with `overwrite=False` on existing file |
|
|
393
|
+
| `KeyError` | Nonexistent scenario_id or device_name |
|
|
394
|
+
|
|
395
|
+
The LLM receives error messages and can retry with corrected parameters.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 7. Testing
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
# Run MCP server tests
|
|
403
|
+
cd client-investment && uv run pytest tests/test_mcp/ -v
|
|
404
|
+
|
|
405
|
+
# Run full test suite
|
|
406
|
+
cd client-investment && uv run pytest tests/ -v
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Test coverage includes:
|
|
410
|
+
- 11 tests for `save_csv` data layer
|
|
411
|
+
- 2 tests for `save_data_file` tool integration
|
|
412
|
+
- 7 MCP protocol integration tests (via FastMCP Client)
|
|
413
|
+
- 77+ tests for scenario assembly and job management tools
|
{site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/mcp/config.py
RENAMED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_data_dir() -> Optional[str]:
|
|
9
|
+
"""Get the configured data directory from INVESTMENT_DATA_DIR, or None."""
|
|
10
|
+
return os.environ.get("INVESTMENT_DATA_DIR") or None
|
|
5
11
|
|
|
6
12
|
|
|
7
13
|
@dataclass(frozen=True)
|
{site_calc_investment-1.2.2 → site_calc_investment-1.2.3}/site_calc_investment/mcp/data_loaders.py
RENAMED
|
@@ -169,3 +169,73 @@ def _find_first_numeric_column(headers: list[str], file_path: str) -> int:
|
|
|
169
169
|
if hint in h_lower:
|
|
170
170
|
return i
|
|
171
171
|
return 0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _resolve_save_path(file_path: str, data_dir: Optional[str] = None) -> str:
|
|
175
|
+
"""Resolve a file path for saving, applying data_dir for relative paths.
|
|
176
|
+
|
|
177
|
+
:param file_path: Filename or path (relative or absolute).
|
|
178
|
+
:param data_dir: Base directory for relative paths (or None for cwd).
|
|
179
|
+
:returns: Absolute path string.
|
|
180
|
+
:raises ValueError: If the extension is present but not '.csv'.
|
|
181
|
+
"""
|
|
182
|
+
_, ext = os.path.splitext(file_path)
|
|
183
|
+
if ext and ext.lower() != ".csv":
|
|
184
|
+
raise ValueError(f"Only .csv files are supported, got '{ext}'. Use a .csv extension or omit the extension.")
|
|
185
|
+
if not ext:
|
|
186
|
+
file_path = file_path + ".csv"
|
|
187
|
+
|
|
188
|
+
if os.path.isabs(file_path):
|
|
189
|
+
return file_path
|
|
190
|
+
|
|
191
|
+
base = data_dir if data_dir else os.getcwd()
|
|
192
|
+
return os.path.abspath(os.path.join(base, file_path))
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def save_csv(
|
|
196
|
+
file_path: str,
|
|
197
|
+
columns: dict[str, list[float]],
|
|
198
|
+
data_dir: Optional[str] = None,
|
|
199
|
+
overwrite: bool = False,
|
|
200
|
+
) -> str:
|
|
201
|
+
"""Save column data as a CSV file.
|
|
202
|
+
|
|
203
|
+
:param file_path: Filename or path. Relative paths resolve against data_dir (or cwd).
|
|
204
|
+
Extension '.csv' is appended if missing.
|
|
205
|
+
:param columns: Named columns of numeric data. All must have the same length.
|
|
206
|
+
:param data_dir: Base directory for relative paths.
|
|
207
|
+
:param overwrite: Allow overwriting an existing file (default: False).
|
|
208
|
+
:returns: Absolute path to the saved file.
|
|
209
|
+
:raises ValueError: If columns are empty, have no rows, or have mismatched lengths.
|
|
210
|
+
:raises FileExistsError: If file exists and overwrite is False.
|
|
211
|
+
"""
|
|
212
|
+
if not columns:
|
|
213
|
+
raise ValueError("columns must not be empty -- provide at least one named column.")
|
|
214
|
+
|
|
215
|
+
lengths = {name: len(vals) for name, vals in columns.items()}
|
|
216
|
+
unique_lengths = set(lengths.values())
|
|
217
|
+
|
|
218
|
+
if unique_lengths == {0}:
|
|
219
|
+
raise ValueError("All columns have 0 rows -- provide at least one row of data.")
|
|
220
|
+
if len(unique_lengths) > 1:
|
|
221
|
+
raise ValueError(f"All columns must have the same length, got: {lengths}")
|
|
222
|
+
|
|
223
|
+
resolved = _resolve_save_path(file_path, data_dir)
|
|
224
|
+
|
|
225
|
+
if not overwrite and os.path.exists(resolved):
|
|
226
|
+
raise FileExistsError(f"File already exists: {resolved}. Set overwrite=True to replace it.")
|
|
227
|
+
|
|
228
|
+
parent = os.path.dirname(resolved)
|
|
229
|
+
if parent:
|
|
230
|
+
os.makedirs(parent, exist_ok=True)
|
|
231
|
+
|
|
232
|
+
col_names = list(columns.keys())
|
|
233
|
+
row_count = len(next(iter(columns.values())))
|
|
234
|
+
|
|
235
|
+
with open(resolved, "w", encoding="utf-8", newline="") as f:
|
|
236
|
+
writer = csv.writer(f)
|
|
237
|
+
writer.writerow(col_names)
|
|
238
|
+
for i in range(row_count):
|
|
239
|
+
writer.writerow([columns[name][i] for name in col_names])
|
|
240
|
+
|
|
241
|
+
return resolved
|