Metadata-Version: 2.5
Name: site-calc-investment
Version: 1.5.4
Summary: Python client for Site-Calc investment planning (capacity sizing, ROI analysis)
Project-URL: Homepage, https://github.com/stranma/site-calc-investment
Project-URL: Documentation, https://github.com/stranma/site-calc-investment#readme
Project-URL: Repository, https://github.com/stranma/site-calc-investment
Project-URL: Issues, https://github.com/stranma/site-calc-investment/issues
Author-email: Site-Calc Team <info@site-calc.example.com>
License: MIT
License-File: LICENSE
Keywords: capacity-planning,energy,investment,npv,roi
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: httpx>=0.24
Requires-Dist: numpy>=1.24
Requires-Dist: pydantic>=2.0
Requires-Dist: python-dateutil>=2.8
Requires-Dist: tzdata>=2024.1
Provides-Extra: dev
Requires-Dist: fastmcp>=2.0; extra == 'dev'
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pandas>=2.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest-timeout>=2.2; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: fastmcp>=2.0; extra == 'mcp'
Description-Content-Type: text/markdown

# Site-Calc Investment Client

Python client for Site-Calc investment planning API - long-term capacity planning and ROI analysis.

## Installation

```bash
pip install site-calc-investment
```

## Service compatibility

Use **site-calc-investment 1.5.4** with the investment service release deployed on
**2026-09-11**. That service reports **API 1.5** and **server 1.5.2** at `/health`.
Earlier server 1.5.2 builds predate the strategy controls, so the version number
alone does not establish support; confirm the deployed release with your service
operator when using another installation.

| Client | Compatibility with the 2026-09-11 investment service |
| --- | --- |
| **1.5.4** | Full Python/MCP strategy and SOC-policy controls, absolute/relative tolerances, and result certificates. |
| **1.5.3** | Existing planning, polling, results, and cancellation APIs remain available; upgrade for the new controls and typed certificate fields. |

Jobs remain asynchronous. Unspecified options follow the service's defaults,
including its monthly-fixed SOC policy; use the explicit controls in 1.5.4 when
you need a particular policy. New response fields are optional when reading
results from older services, but older services may not implement new request
options. Match client and service API MAJOR.MINOR versions and confirm feature
support before requesting decomposition on an older deployment.

```bash
pip install "site-calc-investment==1.5.4"
```

## Quick Start

```python
from datetime import datetime
from zoneinfo import ZoneInfo
from site_calc_investment import InvestmentClient
from site_calc_investment.models import (
    Resolution,
    Site,
    Battery,
    ElectricityImport,
    ElectricityExport,
    InvestmentPlanningRequest,
    InvestmentParameters,
    OptimizationConfig,
)
from site_calc_investment.models.requests import TimeSpanInvestment

# Initialize client
client = InvestmentClient(base_url="https://api.site-calc.example.com", api_key="inv_your_api_key_here")

# Create 1-week planning horizon (1-hour resolution)
timespan = TimeSpanInvestment(
    start=datetime(2025, 1, 1, tzinfo=ZoneInfo("Europe/Prague")),
    intervals=168,  # 1 week = 7 days × 24 hours
    resolution=Resolution.HOUR_1,
)

# Generate hourly prices (example: day/night pattern)
prices = [30.0 if h % 24 < 6 else 80.0 if 8 <= h % 24 < 20 else 50.0 for h in range(168)]

# Define devices (NO ancillary_services field)
battery = Battery(
    name="Battery1",
    properties={"capacity": 10.0, "max_power": 5.0, "efficiency": 0.90, "initial_soc": 0.5},
    investment={"capital_cost": 500000, "annual_opex": 5000},  # For client-side NPV
)

grid_import = ElectricityImport(name="GridImport", properties={"price": prices, "max_import": 10.0})

grid_export = ElectricityExport(name="GridExport", properties={"price": prices, "max_export": 10.0})

site = Site(site_id="investment_site", devices=[battery, grid_import, grid_export])

# Global investment parameters (per-device costs live on the devices)
inv_params = InvestmentParameters(
    discount_rate=0.05,
    project_lifetime_years=10,  # Required field
)

# Create and submit optimization request
request = InvestmentPlanningRequest(
    sites=[site],
    timespan=timespan,
    investment_parameters=inv_params,
    optimization_config=OptimizationConfig(
        objective="maximize_profit",  # Options: maximize_profit, minimize_cost, maximize_self_consumption
        time_limit_seconds=300,  # Max 3600 seconds (60 min)
    ),
)

job = client.create_planning_job(request)
result = client.wait_for_completion(job.job_id, poll_interval=5, timeout=600)

print(f"Status: {result.status}")
print(f"Solver: {result.summary.solver_status}")  # "Optimal", or "Feasible" when the time limit cut the solve short
print(f"Profit: €{result.summary.expected_profit:,.2f}")
if result.summary.is_optimal is False:  # "is False", not "not ...": None means an older service that does not report it
    # Best plan found so far; optimality_gap says how far it may be from the true optimum
    print(f"Stopped early ({result.summary.termination_reason}), relative gap {result.summary.optimality_gap}")
```

Optional `OptimizationConfig` fields select `strategy`, absolute or relative
tolerance (`abs_gap` / `mip_gap`), and `soc_boundary_policy`. Unspecified values
use service defaults; pass `mip_gap=0.01` explicitly to keep the previous 1%
relative request. The `monthly_fixed` policy fixes month-end and terminal SOC
to half the installed energy capacity, including on monolithic fallback.
See [strategy, bounds, and model-policy behavior](docs/INVESTMENT_CLIENT_SPEC.md#1111-strategy-tolerances-and-soc-boundary-policy)
before choosing a policy or interpreting nullable result bounds.

## Features

- ✅ Long-term capacity planning (1-10 years)
- ✅ Investment ROI analysis (NPV, IRR, payback)
- ✅ Per-device investment costs (capital_cost / annual_opex) for client-side NPV
- ✅ Capacity reservations: per-period capacity limits and charges with automatic cheapest-tariff assignment
- ✅ BESS investment sizing: optimizer-sized battery power (MW) and energy capacity (MWh)
- ✅ Czech distribution-tariff import device (monthly T1/T2 capacity tariff, 2027 structure)
- ✅ Scenario comparison utilities
- ✅ Financial analysis helpers
- ✅ 1-hour resolution optimization
- ✅ Multi-site optimization
- ✅ Type-safe Pydantic models
- ✅ Automatic retry and error handling
- ✅ Job management (cancel single or all jobs)

## Capabilities

| Feature | Value |
|---------|-------|
| Max Horizon | 100,000 intervals (~11 years at 1-hour) |
| Resolution | 1-hour only |
| ANS Support | No |
| Binary Variables | Relaxed to continuous |
| Timeout | 3600 seconds (60 minutes) max |

## Supported Devices

- Battery (NO ANS; optional `power_sizing` / `capacity_sizing` reservations let the optimizer size installed MW / MWh)
- CHP - Combined Heat and Power (continuous operation)
- Heat Accumulator
- Photovoltaic: `photovoltaic_nonsteerable` (exact profile) / `photovoltaic_steerable` (curtailable up to a max-power profile)
- Fixed Production / Fixed Consumption (follow a power profile exactly)
- Max-Power Production / Consumption (steerable in [0, max] at a linear EUR/MWh cost or value)
- Heat Demand
- Electricity Demand
- Electricity Import/Export (market interface; optional `capacity_reservation` for per-period grid capacity tariffs)
- Czech Distribution Import (`cz_distribution_import`: electricity import billed under the Czech monthly T1/T2 capacity tariff)
- Import with Overflow (`electricity_import_with_overflow`: net-metered grid connection, consumption billed at the import price and the surplus paid at the overflow price; either importing or exporting in any hour, never both)
- Gas Import (market interface)
- Heat Export (market interface)

Every device also accepts an optional `investment` block (`{"capital_cost": EUR, "annual_opex": EUR/year}`) used only for client-side NPV/IRR analysis -- it is stripped from the API payload.

## Job Management

The client provides methods for managing optimization jobs:

```python
# Create a job
job = client.create_planning_job(request)
print(f"Job ID: {job.job_id}")

# Check job status
status = client.get_job_status(job.job_id)
print(f"Status: {status.status}, Progress: {status.progress}%")

# Wait for completion
result = client.wait_for_completion(job.job_id, poll_interval=30, timeout=7200)

# Cancel a single job
cancelled = client.cancel_job(job.job_id)

# Cancel all pending/running jobs (bulk cancel)
result = client.cancel_all_jobs()
print(f"Cancelled {result['cancelled_count']} jobs")
```

## Financial Analysis

```python
from site_calc_investment.analysis import (
    calculate_investment_metrics,
    calculate_npv,
    calculate_irr,
    calculate_payback_period,
    compare_scenarios,
)

# All-in-one: NPV/IRR/payback from annual aggregates + device investment blocks
metrics = calculate_investment_metrics(
    annual_revenues=result.investment_metrics.annual_revenue_by_year,
    annual_costs=result.investment_metrics.annual_costs_by_year,  # already includes capacity-reservation charges
    discount_rate=0.05,
    devices=site.devices,
)
print(metrics["npv"], metrics["irr"], metrics["payback_period_years"])

# NPV calculation
npv = calculate_npv(cash_flows=annual_revenues, discount_rate=0.05, initial_investment=-1500000)

# IRR calculation
irr = calculate_irr([-1500000] + annual_revenues)

# Scenario comparison
comparison = compare_scenarios([result_5mw, result_10mw, result_15mw], names=["5 MW", "10 MW", "15 MW"])
print(comparison)  # DataFrame with NPV, IRR, costs, revenues
```

## MCP Server (LLM Integration)

The package includes an MCP server for use with Claude Desktop, ChatGPT, and other MCP-compatible LLM tools.

### Installation

```bash
pip install site-calc-investment[mcp]
```

### Claude Desktop Configuration

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "site-calc-investment": {
      "command": "uvx",
      "args": ["--from", "site-calc-investment[mcp]", "site-calc-investment-mcp"],
      "env": {
        "INVESTMENT_API_URL": "http://your-api-url",
        "INVESTMENT_API_KEY": "inv_your_key_here",
        "INVESTMENT_DATA_DIR": "/path/to/data/directory"
      }
    }
  }
}
```

For local development against a source checkout, use `uv run --directory` instead:

```json
{
  "mcpServers": {
    "site-calc-investment": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/client-investment", "site-calc-investment-mcp"],
      "env": {
        "INVESTMENT_API_URL": "http://your-api-url",
        "INVESTMENT_API_KEY": "inv_your_key_here",
        "INVESTMENT_DATA_DIR": "/path/to/data/directory"
      }
    }
  }
}
```

### ChatGPT Configuration

ChatGPT supports MCP via streaming HTTP or SSE (stdio is not supported). This requires:

- An eligible ChatGPT plan with Developer Mode access (**Pro, Plus, Business, Enterprise, or Education**). Business plan restricts Developer Mode to admins/owners; other plans may have similar admin gating.
- **Developer Mode** enabled (navigate to **Settings** -- the exact path may vary by account type)

**Step 1 -- Start the MCP server in HTTP mode:**

```bash
cd /path/to/client-investment
INVESTMENT_API_URL="http://your-api-url" \
INVESTMENT_API_KEY="inv_your_key_here" \
INVESTMENT_DATA_DIR="/path/to/data" \
uv run fastmcp run site_calc_investment.mcp.server:mcp --transport http --port 8000
```

**Step 2 -- Expose to the internet (for local development):**

```bash
ngrok http 8000
```

For production, deploy the server to a publicly accessible host instead.

**Step 3 -- Add to ChatGPT:**

1. Open ChatGPT **Settings** > **Apps & Connectors** (or **Connectors**) > **Advanced settings** > Enable **Developer Mode**
2. Click **Create app**
3. Enter the ngrok HTTPS URL (e.g. `https://abc123.ngrok.io/mcp`) as the server endpoint
4. Name: "Site-Calc Investment", Description: "Investment planning optimization tools"
5. Click **Refresh** to load the tool list

> **Note:** ChatGPT shows tool call details and may require manual confirmation for actions.
> The `save_data_file` tool requires the server to have local filesystem access.

### Tools (17)

| Tool | Description |
|------|-------------|
| `get_version` | Get server and package version info |
| `create_scenario` | Create a new draft scenario |
| `add_device` | Add a device (battery, CHP, PV, etc.), optionally with an `investment` cost block |
| `set_timespan` | Set optimization time horizon |
| `set_investment_params` | Set global financial parameters (discount rate, project lifetime) |
| `review_scenario` | Review scenario before submission |
| `remove_device` | Remove a device |
| `delete_scenario` | Delete a scenario |
| `list_scenarios` | List all draft scenarios |
| `submit_scenario` | Submit for optimization |
| `get_job_status` | Check job progress |
| `get_job_result` | Get optimization results (incl. capacity reservation summaries) |
| `cancel_job` | Cancel a job |
| `list_jobs` | List all jobs |
| `get_device_schema` | Get device property schema |
| `save_data_file` | Save generated data as CSV |
| `fetch_url` | Download a file (e.g. CSV price data) from a URL |

`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.

See [docs/MCP_SERVER_SPEC.md](docs/MCP_SERVER_SPEC.md) for full specification.

## Documentation

Full documentation available at: https://github.com/stranma/site-calc-investment#readme

## Examples

See `examples/` directory for complete examples:
- `01_basic_capacity_planning.py` - one-week battery capacity planning workflow
- `02_scenario_comparison.py` - compare device configurations
- `03_financial_analysis.py` - investment ROI calculation
- `04_import_with_overflow.py` - net-metered import with overflow: the device, a hand-built pairing, and why it matters

## Requirements

- Python ≥ 3.10
- API key with `inv_` prefix (investment client)

## Key Differences from Operational Client

- ❌ No `/optimal-bidding` endpoint
- ❌ No `ancillary_services` on devices
- ❌ No 15-minute resolution (1-hour only)
- ✅ Up to 100,000 intervals (vs. 296)
- ✅ Investment metrics (NPV, IRR, payback)
- ✅ Financial analysis helpers

## Development

```bash
# Install with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Format code
ruff format .

# Type check
mypy site_calc_investment
```

## License

MIT License

## Support

- Issues: https://github.com/stranma/site-calc-investment/issues
- Documentation: https://github.com/stranma/site-calc-investment#readme

---

Part of the [Site-Calc](https://github.com/stranma/site-calc) platform.
